33from dataclasses import dataclass
44from lib .terminal_graphics import Terminal , ScreenData , Coords
55from lib .sprites import Sprites
6+ from lib .parameters import MagicNumbers
67import keyboard
78
8- GROUND_WIDTH = 80
9-
109class Ground ():
1110 def __init__ (self , screen : ScreenData , width : int , pos : Coords ):
12- super ().__init__ ()
11+ super ().__init__ () # I don't know why this is here, but it's behavior looks good to me
1312 self .width = width
14- self .sprite = Sprites .GROUND_SPRITE * self .width
1513 self .pos = pos
16- self .xpos = pos .xpos
17- self .ypos = pos .ypos
14+ self .xpos = pos .xpos # xpos short for X position
15+ self .ypos = pos .ypos # ypos short for Y position
1816 self .screen = screen
17+
18+ def place_ground (self , xpos : int , ypos : int ):
19+ """Place a ground block in each coordinate.
20+
21+ Args:
22+ xpos (int): The x position of the ground block.
23+ ypos (int): The y position of the ground block.
24+
25+ Returns:
26+ None"""
27+ # Place a ground block in each coordinate, reads as the following:
28+ # For each number in the range of the width: <--+
29+ # Place a ground block in coordnates: | number
30+ # X = Ground's root x position + the number from the for loop ---+
31+ for number in range (self .width ):
32+ Terminal .place_sprite (
33+ self .screen ,
34+ Sprites .GROUND_SPRITE ,
35+ Coords (xpos + number , ypos )
36+ )
37+
38+ def get_collision_coords (self ):
39+ """
40+ Get the coordinates for collision detection with the ground.
41+
42+ Returns:
43+ List[Coords]: A list of coordinates representing the ground.
44+ """
45+ # Initialize an empty list to hold the collision coordinates
46+ collision_coords = []
47+
48+ # Iterate over the range of the width
49+ for number in range (self .width ):
50+ # Calculate the x coordinate for each number and add it to the list
51+ # The x coordinate is the ground's root x position plus the number
52+ collision_coords .append (Coords (self .xpos + number , self .ypos ))
53+
54+ # Return the list of collision coordinates
55+ return collision_coords
1956
2057 def render (self ):
21- for i in range (self .width ):
22- self .screen [Coords (self .xpos + i , self .ypos )] = Sprites .GROUND_SPRITE
23- collision_coords = [Coords (self .xpos + i , self .ypos ) for i in range (self .width )]
58+ # Place ground tiles on the screen
59+ self .place_ground (self .xpos , self .ypos )
60+ collision_coords = self .get_collision_coords ()
61+
2462 return self .screen , collision_coords
2563
26- class Coin ():
64+ # Please note that refactoring the other classes to inherit from another thing may be
65+ # bad for the program length, despite doing the exact same thing.
66+ class Coin (): # Also known as the Mother of All Objects
2767 def __init__ (self , screen : ScreenData , pos : Coords ):
68+ # Initialize the position of the coin
2869 self .pos = pos
70+ # Initialize the position of the coin one row above the current position
2971 self .secondary_pos = Coords (pos .xpos , pos .ypos - 1 )
72+ # Initialize the screen to render the coin on
3073 self .screen = screen
74+ # Initialize a flag to indicate if the coin is hidden
3175 self .hide = False
76+ # Initialize a flag to indicate if the coin has been broken
3277 self .broken = False
78+ # Initialize a flag to indicate if the coin has been updated
3379 self .updated = False
80+ # Initialize a flag to indicate if the coin has been killed
3481 self .killed = False
3582
36- def hide_coin (self ):
37- self .hide = True
38-
39- def __del__ (self ):
40- if not self .updated :
41- self .screen [self .pos ] = ' ' # Clear the coin's position from the screen
42- self .updated = True
43- self .broken , self .killed = True , True
44- else : pass
83+ def hide_coin (self ): self .hide = True
84+ # R.I.P __del__() dunder method, didn't even get used in the 1st place
4585
4686 def render (self , previous_pos : Coords = None ):
47- new_screen : ScreenData = {
48- self .pos : Sprites .COIN_SPRITE if not self .hide else ' ' # 'C' represents the coin
49- }
50- self .screen .update (new_screen )
87+ # Determine the sprite to render based on the hide flag
88+ sprite_to_render = Sprites .COIN_SPRITE if not self .hide else ' '
89+ # Place the sprite on the screen at the current position
90+ Terminal .place_sprite (self .screen , sprite_to_render , self .pos )
91+ # Return the updated screen data
5192 return self .screen
5293
5394class Powerup (Coin ):
5495 def render (self , previous_pos : Coords = None ):
55- new_screen : ScreenData = {
56- self .pos : Sprites .POWERUP_BLOCK_SPRITE if not self .hide else Sprites .HIT_POWERUP_BLOCK_SPRITE # '?' represents the powerup block
57- }
58- self .screen .update (new_screen )
96+ # Determine the sprite to render based on the hide flag
97+ if self .hide : sprite_to_render = Sprites .HIT_POWERUP_BLOCK_SPRITE # Hide block has been hit
98+ else : sprite_to_render = Sprites .POWERUP_BLOCK_SPRITE # Hide block has not been hit
99+ # If hide flag is in an unknown state
100+ if sprite_to_render is None : sprite_to_render = Sprites .UNKNOWN_BLOCK_SPRITE # This should never happen but just in case
101+
102+ # Render the sprite on the screen at the current position
103+ Terminal .place_sprite (self .screen , sprite_to_render , self .pos )
104+ # Return the updated screen data
59105 return self .screen
60106
61107class StompableEnemy (Coin ):
62108 def __init__ (self , screen : ScreenData , pos : Coords ):
109+ # Call the __init__ method of the parent class (Coin)
110+ # Pass in the screen and position arguments
63111 super ().__init__ (screen , pos )
112+ # Set the killed flag to False
64113 self .killed = False
65- self .sidepos = (
66- Coords (pos .xpos - 1 , pos .ypos ),
67- Coords (pos .xpos + 1 , pos .ypos ),
68- )
114+
115+ # Create a tuple of Coords objects representing the enemy's sides
116+ # These Coords objects are one space to the left and right of the enemy's current position
117+
118+ # Create a Coords object representing the enemy's secondary position
119+ # This is the enemy's position one row above its current position
69120 self .secondary_pos = Coords (pos .xpos , pos .ypos - 1 )
121+
122+ # Store the previous position of the enemy
70123 self .previous_pos = pos
71124
72125 def move_towards_player (self , player_pos : Coords ):
73- if self .killed :
74- return # Do not move if the enemy is killed
126+ if self .killed : return # Do not move if the enemy is killed
75127
76- new_xpos = self .pos .xpos
77- if self .pos .xpos < player_pos .xpos :
78- new_xpos += 1
79- elif self .pos .xpos > player_pos .xpos :
80- new_xpos -= 1
81-
128+ new_xpos = self .calculate_new_xpos (player_pos ) # Calculate the new position of the enemy
82129 new_ypos = self .pos .ypos
130+
83131 self .previous_pos = self .pos
84132 self .pos = Coords (new_xpos , new_ypos )
85- self .sidepos = (
86- Coords (new_xpos - 1 , new_ypos ),
87- Coords (new_xpos + 1 , new_ypos ),
88- )
133+ self .sidepos = self .get_side_positions (self .pos )
89134 self .secondary_pos = Coords (new_xpos , new_ypos - 1 )
90135
91- def render (self , previous_pos : Coords = None ):
92- new_screen : ScreenData = {}
136+ def render (self ):
137+ # Determine the sprite to render based on the hide/kill flags
138+ sprite_to_use = Sprites .ENEMY1_SPRITE if (not self .hide ) or (not self .killed ) else ' '
93139
94- if not self .hide :
95- new_screen [self .pos ] = Sprites .ENEMY1_SPRITE
96- if self .previous_pos != self .pos :
97- new_screen [self .previous_pos ] = ' ' # Clear the previous position
98- elif self .hide or self .killed :
99- if not self .updated :
100- new_screen [self .pos ] = ' '
101- self .updated = True
102- self .screen .update (new_screen )
140+ # Render the enemy on the screen at its current position
141+ Terminal .place_sprite (self .screen , sprite_to_use , self .pos )
142+
143+ # If the enemy has moved to a new position, clear its previous position
144+ if self .previous_pos != self .pos :
145+ Terminal .place_sprite (self .screen , ' ' , self .previous_pos )
146+ # If the enemy is hidden or killed, clear its current position
147+ if self .hide or self .killed : Terminal .place_sprite (self .screen , ' ' , self .pos )
148+
149+ # Return the updated screen data
103150 return self .screen
104151
152+ def get_side_positions (self , pos : Coords ):
153+ sidepos = (
154+ Coords (pos .xpos - 1 , pos .ypos ), # Left side
155+ Coords (pos .xpos + 1 , pos .ypos ), # Right side
156+ )
157+ return sidepos
158+
159+ def calculate_new_xpos (self , player_pos : Coords ):
160+ # Calculate the new position of the enemy
161+ # If the enemy is on the left side of the player, move it to the right
162+ # If the enemy is on the right side of the player, move it to the left
163+ # If the enemy is already at the player's position, keep its position
164+ new_xpos = self .pos .xpos
165+ if self .pos .xpos < player_pos .xpos :
166+ new_xpos += 1
167+ elif self .pos .xpos > player_pos .xpos :
168+ new_xpos -= 1
169+ return new_xpos
170+
105171class Fireball (Coin ):
106172 def __init__ (self , screen : ScreenData , pos : Coords , direction : int , enemies : tuple [StompableEnemy ]):
173+ # Initialize the Fireball object with the necessary parameters
107174 super ().__init__ (screen , pos )
108- self .direction = direction # Store the direction of the fireball
109- self .creation_time = time .time () # Record the creation time of the fireball
175+ # Store the direction of the fireball
176+ self .direction = direction
177+ # Set the hit flag to False
110178 self .hit = False
111- self .old_pos = None # Initialize old position as None
179+ # Initialize old position as None
180+ self .old_pos = None
181+ # Store the enemies that the fireball can collide with
112182 self .enemies = enemies
113183
114184 def render (self , previous_pos : Coords = None ):
115- new_screen : ScreenData = {}
116- if self .hit :
117- new_screen [self .pos ] = ' '
118- elif not self .old_pos :
119- new_screen [self .pos ] = Sprites .FIREBALL_RIGHT_SPRITE if self .direction == 1 else Sprites .FIREBALL_LEFT_SPRITE # Render the fireball at its current position
120- elif self .pos != self .old_pos :
121- new_screen [self .pos ] = Sprites .FIREBALL_RIGHT_SPRITE if self .direction == 1 else Sprites .FIREBALL_LEFT_SPRITE # Render the fireball at its new position
122- new_screen [self .old_pos ] = ' ' # Clear the fireball's previous position
123- if self .pos == self .old_pos : new_screen [self .pos ] = ' ' # Clear the fireball's current position
124- if self .pos .xpos == 0 : new_screen [self .pos ] = ' '
125- if self .pos .xpos == (GROUND_WIDTH - 1 ): new_screen [self .pos ] = ' '
185+ # Render the fireball on the screen at its current position
186+ Terminal .place_sprite (self .screen , Sprites .FIREBALL_RIGHT_SPRITE if self .direction == 1 else Sprites .FIREBALL_LEFT_SPRITE , self .pos )
187+
188+ # If the fireball has hit something, clear its position
189+ if self .hit : Terminal .place_sprite (self .screen , ' ' , self .pos )
190+
191+ # If the fireball has moved to a new position, clear its previous position
192+ elif self .old_pos and self .pos != self .old_pos :
193+ Terminal .place_sprite (self .screen , ' ' , self .old_pos )
194+ Terminal .place_sprite (self .screen , Sprites .FIREBALL_RIGHT_SPRITE if self .direction == 1 else Sprites .FIREBALL_LEFT_SPRITE , self .pos )
195+
196+ # If the fireball is at its old position, clear its position
197+ elif self .pos == self .old_pos :
198+ Terminal .place_sprite (self .screen , ' ' , self .pos )
199+
200+ # If the fireball is at the edge of the screen, clear its position
201+ if self .pos .xpos == 0 :
202+ Terminal .place_sprite (self .screen , ' ' , self .pos )
203+ if self .pos .xpos == (MagicNumbers .GROUND_WIDTH - 1 ):
204+ Terminal .place_sprite (self .screen , ' ' , self .pos )
205+
206+ # Check for collisions with enemies
126207 for enemy in self .enemies :
127208 for pos in enemy .sidepos :
128209 if (pos == self .pos ) and (not enemy .killed ):
129210 self .hit = True
130- new_screen [pos ] = ' '
211+ enemy .killed = True
212+ Terminal .place_sprite (self .screen , ' ' , enemy .pos )
213+ Terminal .place_sprite (self .screen , ' ' , pos )
131214
132- new_screen [ Coords (5 , 1 )] = f'Last fireball position: { self .pos .xpos } , { self .pos .ypos } '
215+ new_screen = { Coords (5 , 1 ): f'Last fireball position: { self .pos .xpos } , { self .pos .ypos } ' }
133216 self .screen .update (new_screen )
134217 return self .screen
135218
@@ -143,30 +226,29 @@ def next_pos(self):
143226 self .pos = Coords (new_xpos , new_ypos ) # Create a new Coords instance with the updated position
144227 self .secondary_pos = Coords (new_xpos , new_ypos - 1 ) # Update secondary position accordingly
145228
146- # Check collision with ground border
147- if self .pos .xpos >= (GROUND_WIDTH ) or self .pos .xpos < 0 :
229+ return self .ground_border_collision_check ()
230+
231+ def ground_border_collision_check (self ):
232+ if self .pos .xpos >= (MagicNumbers .GROUND_WIDTH ) or self .pos .xpos < 0 :
148233 self .screen [self .old_pos ] = ' ' # Clear the character on the old position
149234 return True # Indicate collision with ground border
150- return False # Indicate no collision
151-
152- def check_lifetime (self ):
153- # Check if the fireball has existed for more than 3 seconds
154- return time .time () - self .creation_time > 3
235+ return False
155236
156237class Brick (Coin ):
157238 def render (self , previous_pos : Coords = None ):
158- new_screen : ScreenData = {}
239+ """
240+ Render the brick on the screen at its current position.
241+ """
242+ # If the brick is not hidden, render it
159243 if not self .hide :
160- new_screen : ScreenData = {
161- self .pos : Sprites .BRICK_SPRITE
162- }
244+ Terminal .place_sprite (self .screen , Sprites .BRICK_SPRITE , self .pos )
245+ # If the brick is hidden or broken, clear its position
163246 elif self .hide or self .broken :
247+ # If the brick has not been updated yet, clear its position
164248 if not self .updated :
165- new_screen : ScreenData = {
166- self .pos : ' '
167- }
249+ Terminal .place_sprite (self .screen , ' ' , self .pos )
168250 self .updated = True
169- self . screen . update ( new_screen )
251+ # Return the updated screen data
170252 return self .screen
171253
172254class Player ():
@@ -198,7 +280,7 @@ def __init__(
198280 for enemy in self .enemies : self .enemy_coords .append (enemy .pos )
199281
200282 self .coins_collected : int = 0
201- self .powerstate = 0
283+ self .powerstate = MagicNumbers . STARTING_POWERSTATE
202284 self .fire_cooldown = 0
203285
204286 class Left : pass
@@ -265,7 +347,8 @@ def update_position(self):
265347 if powcoord == Coords (self .pos .xpos , new_ypos ):
266348 new_ypos = self .pos .ypos + 1
267349 self .velocity_y = 0
268- self .powerstate += 1 if not self .powerups [id ].hide else 0
350+ if (not self .powerups [id ].hide ) and (self .powerstate < 2 ):
351+ self .powerstate += 1
269352 self .powerups [id ].hide_coin ()
270353 elif brick_collision :
271354 for id , brkcoord in enumerate (self .brick_coords ):
@@ -334,7 +417,7 @@ def enemy_check(self, enemies: tuple[StompableEnemy]):
334417def main ():
335418 screen : ScreenData = {}
336419
337- level_ground = Ground (screen , GROUND_WIDTH , Coords (0 , 10 ))
420+ level_ground = Ground (screen , MagicNumbers . GROUND_WIDTH , Coords (0 , 10 ))
338421 screen .update (level_ground .render ()[0 ])
339422
340423 coins : tuple = (
0 commit comments