Coverage for zombie_nomnom/engine/models.py: 100%
64 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-05 01:14 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-05 01:14 +0000
1import uuid
3from pydantic import BaseModel, Field
5from zombie_nomnom.models.bag import DieBag
6from zombie_nomnom.models.dice import Die, Face, DieFace
9def uuid_str() -> str:
10 """Creates a stringified uuid that we can use.
12 **Returns**
13 - `str`: stringified uuid
14 """
15 return str(uuid.uuid4())
18def is_scoring_face(face: Face | DieFace) -> bool:
19 """Checks if a face is scoring.
21 A scoring face is a face that will score points for the player.
22 This is useful for determining if a player should continue rolling or not.
24 **Arguments**
25 - `face: Face | DieFace`: The face to check.
27 **Returns**
28 - `bool`: If the face is a scoring face.
29 """
30 return (
31 isinstance(face, Face)
32 and face == Face.BRAIN
33 or isinstance(face, DieFace)
34 and face.score
35 )
38def is_damaging_face(face: Face | DieFace) -> bool:
39 """Checks if a face is damaging.
41 A damaging face is a face that will limit the amount of dice the player can roll.
42 This is useful for determining if a player should stop rolling or not.
44 **Arguments**
45 - `face: Face | DieFace`: The face to check.
47 **Returns**
48 - `bool`: If the face is a damaging face.
49 """
50 return (
51 isinstance(face, Face)
52 and face == Face.SHOTGUN
53 or isinstance(face, DieFace)
54 and face.damage
55 )
58def is_blank_face(face: Face | DieFace) -> bool:
59 """Checks if a face is blank.
61 A blank face is a face that doesn't score any points nor does it damage the player.
62 This is useful for determining if a die should be rolled again.
64 **Arguments**
65 - `face: Face | DieFace`: The face to check.
67 **Returns**
68 - `bool`: True if the face is blank.
69 """
70 return (
71 isinstance(face, Face)
72 and face == Face.FOOT
73 or isinstance(face, DieFace)
74 and not face.score
75 and not face.damage
76 )
79class Player(BaseModel):
80 """Player in the game. This manages the total points
81 they have and the dice they pulled from the bag. Has
82 methods to manage the hand and then calculate points.
83 Also has ways to be able to tell if players turn should finish.
84 """
86 id: str = Field(default_factory=uuid_str)
87 """id field used to identify the player more unqiuely"""
88 name: str
89 """name of the player that we display on the screen"""
90 total_brains: int = 0
91 """total points they currently have in the game"""
92 hand: list[Die] = []
93 """the dice the player is currently holding"""
95 @property
96 def rerolls(self) -> list[Die]:
97 """A view of the hand that shows all the dice that are re-rollable.
99 **Returns**:
100 - `list[zombie_nomnom.Die]`: re-rollable dice
101 """
102 return [die for die in self.hand if is_blank_face(die.current_face)]
104 @property
105 def brains(self) -> list[Die]:
106 """A view of the hand that shows all the dice that scores points.
108 **Returns**
109 - `list[zombie_nomnom.Die]`: scoreable dice
110 """
111 return [die for die in self.hand if is_scoring_face(die.current_face)]
113 @property
114 def shots(self) -> list[Die]:
115 """A view of the hand that shows the shots you have taken.
117 **Returns**
118 - `list[zombie_nomnom.Die]`: damaging dice
119 """
120 return [die for die in self.hand if is_damaging_face(die.current_face)]
122 def is_player_dead(self) -> bool:
123 """Calculates whether or not the player should be dead based on the shots in their hand.
125 **Returns**
126 - bool: True when player is considered dead.
127 """
128 total = 0
129 for shot in self.shots:
130 face = shot.current_face
131 if isinstance(face, DieFace):
132 total += face.damage
133 else:
134 total += 1
135 # if you have 3 shots you are dead XP
136 return total >= 3
138 def add_dice(self, *dice: Die) -> "Player":
139 """Creates a new player adding the dice the caller gives plus any dice in their hand currently.
141 **Returns**
142 - `Player`: New player instance with updated hand.
143 """
144 return Player(
145 id=self.id,
146 name=self.name,
147 hand=[*self.hand, *dice],
148 total_brains=self.total_brains,
149 )
151 def clear_hand(self) -> "Player":
152 """Creates a new player with no dice in their hand.
154 **Returns**
155 - `Player`: New player instance with empty hand.
156 """
157 return Player(
158 id=self.id,
159 name=self.name,
160 total_brains=self.total_brains,
161 )
163 def reset(self) -> "Player":
164 """Creates a new player instance with all the game related fields set back to their default values.
166 **Returns**
167 - `Player`: New player instance with all values reset.
168 """
169 return Player(
170 id=self.id,
171 name=self.name,
172 total_brains=0,
173 hand=[],
174 )
176 def calculate_score(self) -> "Player":
177 """Creates a new player that has added the score of the current hand and leaves an empty hand.
179 **Returns**
180 - `Player`: New player instance with score adjusted for scoring dice in hand and hand reset back to empty list.
181 """
182 additional_score = 0
183 for brain_xo in self.brains:
184 face = brain_xo.current_face
185 if isinstance(face, DieFace):
186 additional_score += face.score
187 else:
188 additional_score += 1
189 return Player(
190 id=self.id,
191 name=self.name,
192 total_brains=additional_score + self.total_brains,
193 )
196class RoundState(BaseModel):
197 """
198 Object representing the state of a round in the game. Keeps track of the bag, player,
199 and whether or not the round has ended.
200 """
202 bag: DieBag
203 """Bag that is currently being played in the round"""
204 player: Player
205 """Player that is currently playing"""
206 ended: bool = False
207 """Records whether or not the current round is over"""
210class DieRecipe(BaseModel):
211 """The recipe for a dice to be used in the bag. Will have the array of faces
212 that we will use for our die and how many instances to add to our bag.
213 """
215 faces: list[Face]
216 """The faces that will make up the dice."""
217 amount: int
218 """The amount of dice that will be added to the bag."""