main

  1import pygame, sys, os, button
  2from tower import Tower
  3from enemy import Enemy
  4from waves import Wave
  5
  6FPS = 60
  7fpsClock = pygame.time.Clock()
  8window_width = 800
  9window_height = 600
 10
 11pygame.display.set_caption('Tower Defense Game')
 12pygame.init()
 13window = pygame.display.set_mode((window_width, window_height))
 14
 15
 16class StartScreen:
 17    """
 18    A class to represent the start screen of the Tower Defense Game.
 19    Attributes:
 20    
 21    Attributes:
 22    window: The window surface where the start screen will be rendered.
 23    background: The background image of the start screen.
 24    font: The font used for rendering text on the start screen.
 25    title_text: The rendered text surface for the game title.
 26    start_text: The rendered text surface for the start button.
 27    start_button_rect: The rectangle area of the start button.
 28   
 29    Methods:
 30    render(): Renders the start screen with the background, title, and start button.
 31    check_for_click(): Checks for mouse click events and returns True if the start button is clicked.
 32    """
 33    def __init__(self, window):
 34        """
 35        Initializes the main game window and loads the start screen assets.
 36        Args:
 37            window: The window surface where the start screen will be rendered.
 38        """
 39        self.window = window
 40        self.background = pygame.image.load(os.path.join('game_assests', 'start_screen_background.jpg'))
 41        self.background = pygame.transform.scale(self.background, (window_width, window_height))
 42        self.font = pygame.font.SysFont(None, 55)
 43        self.title_text = self.font.render('Tower Defense Game', True, (255, 255, 255))
 44        self.start_text = self.font.render('Click to Start', True, (255, 255, 255))
 45
 46    def render(self):
 47        """
 48        Renders the start screen with the background, title, and start button.
 49        """
 50        self.window.blit(self.background, (0, 0))
 51        self.window.blit(self.title_text, (window_width // 2 - self.title_text.get_width() // 2, 100))
 52        start_text_rect = self.start_text.get_rect(center=(window_width // 2, 400))
 53        rect_x = start_text_rect.x - 10
 54        rect_y = start_text_rect.y - 10
 55        rect_width = start_text_rect.width + 20
 56        rect_height = start_text_rect.height + 20
 57
 58        pygame.draw.rect(self.window, (39, 145, 39), (rect_x, rect_y, rect_width, rect_height))
 59        self.window.blit(self.start_text, start_text_rect.topleft)
 60        pygame.display.update()
 61        self.start_button_rect = pygame.Rect(rect_x, rect_y, rect_width, rect_height)
 62
 63    def check_for_click(self):
 64        """
 65        Checks for mouse click events and returns True if the start button is clicked.
 66
 67        Returns:
 68            bool: True if the start button is clicked, False otherwise.
 69        """
 70        for event in pygame.event.get():
 71            if event.type == pygame.QUIT:
 72                pygame.quit()
 73                sys.exit()
 74            if event.type == pygame.MOUSEBUTTONDOWN:
 75                mouse_pos = pygame.mouse.get_pos()
 76                if self.start_button_rect.collidepoint(mouse_pos):
 77                    return True
 78        return False
 79
 80
 81class MainGameScreen:
 82    """The game screen which shows the map and handles the generation of enemies, towers, and player stats."""
 83    def __init__(self, window):
 84        self.window = window
 85        """The window for the game."""
 86        self.background = pygame.image.load(os.path.join('game_assests', 'map_one.png'))
 87        """Loads the image."""
 88        self.background = pygame.transform.scale(self.background, (window_width - 100, window_height - 100))
 89        """Scales the image in self.background appropriately and then saves it back to self.background."""
 90        self.font = pygame.font.SysFont(None, 22)
 91        """Sets the font style to be used."""
 92        self.health = 0
 93        """Holds the amount of health the player has remaining."""
 94        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
 95        """Renders the health text so the player knows how much health they have remaining."""
 96        self.money = 0
 97        """Holds the amount of money the player has"""
 98        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
 99        """Renders the money text so the player knows how much money they have remaining."""
100
101        # Pause button
102        pause_img = pygame.image.load(os.path.join('game_assests', 'Play-Pause.png')).convert_alpha()
103        """The image of the pause button."""
104        self.pause_button = button.Button(710, 510, pause_img, 0.15)
105        """Makes the pause button a button to be clicked, using the image stored in pause_img."""
106        self.pause = True
107        """Sets pause to True when initially ran."""
108
109        # Map Variables
110        self.map_path = ((0, 274), (116, 274), (116, 124), (258, 124), (258, 322), (444, 322),
111                         (444, 226), (700, 226), (800, 226))
112        """Sets the path for enemies to traverse."""
113        self.collision_rects = [
114            pygame.Rect(min(0, 140), min(248, 300), max(0, 140) - min(0, 140), max(248, 300) - min(248, 300)),
115            pygame.Rect(min(140, 96), min(300, 100), max(140, 96) - min(140, 96), max(300, 100) - min(300, 100)),
116            pygame.Rect(min(96, 278), min(100, 146), max(96, 278) - min(96, 278), max(146, 100) - min(100, 146)),
117            pygame.Rect(min(278, 230), min(146, 348), max(278, 230) - min(278, 230), max(348, 146) - min(146, 348)),
118            pygame.Rect(min(230, 470), min(348, 299), max(470, 230) - min(230, 470), max(348, 299) - min(299, 348)),
119            pygame.Rect(min(470, 418), min(299, 200), max(470, 418) - min(470, 418), max(299, 200) - min(200, 299)),
120            pygame.Rect(min(418, 700), min(200, 250), max(700, 418) - min(418, 700), max(250, 200) - min(200, 250)),
121        ]
122        """Sets up some collision spots, where the towers cannot be placed. (ie. the enemy path)"""
123
124        # Tower Variables
125        self.grid_active = False
126        """Used to show the grid"""
127        self.grid_size = 14  # Will remove later, some things still depend on this.
128        """Determines the grid size."""
129        self.tower_size = 3
130        """Determines the tower size"""
131        self.selected_tower = None
132        """Determines which tower the player has selected."""
133        self.placed_towers = []
134        """A list to hold all the towers that have been placed."""
135
136        # Wave and Enemy
137        self.wave = 1
138        """Stores which wave number is currently displayed to the player"""
139        self._waves = []  # list of waves
140        self._time_since_previous_spawn = 0
141        self._enemy_list = []
142        self._current_wave = 0
143        self.wave_text = self.font.render(f'Wave: {self.wave}', True, (255, 255, 255))
144        """Renders the current wave number."""
145
146        for wave_number in range(1, 60):
147            """Loop for generating waves, changes are planned to further improve."""
148            enemy_count = 3 * wave_number
149            """Determines amount of enemies to be spawned, dependent on wave number."""
150            enemy_hp = 10 + (5 * wave_number)
151            """Increases health of the enemies, dependent on wave number."""
152            wave_data = [
153                Enemy('circle', enemy_hp, 1, 1, self.map_path) for i in range(enemy_count)
154            ]
155            """Generate wave data, to be added to self._waves"""
156            self._waves.append(Wave(wave_data, 60))
157
158            # Debugging Variables
159        self.debug = False
160        """Used to help debug."""
161        self.cursor_text = self.font.render('', True, (255, 255, 255))
162        """Displays the text for the cursor."""
163
164    def render(self):
165        """Used to render the game and enemies."""
166        self.window.blit(self.background, (0, 0))
167        """Sets window"""
168
169        class Rectangle():
170            """Creates rectangles for the UI."""
171            def __init__(self, x, y, width, height, color):
172                self.x = x
173                """The x position of the rectangle."""
174                self.y = y
175                """The y position of the rectangle"""
176                self.width = width
177                """The width of the rectangle."""
178                self.height = height
179                """The height of the rectangle."""
180                self.color = color
181                """The color of the rectangle."""
182
183            def draw(self):
184                """Draws the rectangle."""
185                pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height))
186
187        # create menus, tower slots, and tower image
188        bottom_bar = Rectangle(0, (window_height - 100), window_width, 100, (150, 150, 150))
189        """Menu on the bottom, currently hold nothing."""
190        side_bar = Rectangle((window_width - 100), 0, 100, window_height, (150, 150, 150))
191        """Menu on the right side, shows player stats and towers that can be used."""
192        tower_boxes = [
193            Rectangle(705, 100, 90, 90, (100, 100, 100)),
194            Rectangle(705, 200, 90, 90, (100, 100, 100)),
195            Rectangle(705, 300, 90, 90, (100, 100, 100)),
196            Rectangle(705, 400, 90, 90, (100, 100, 100))
197        ]
198        """Makes the rectangles for the tower boxes."""
199        tower_image = pygame.image.load(os.path.join("game_assests", "tower.png"))
200        """Loads tower image."""
201        tower_image = pygame.transform.scale(tower_image, (90, 90))
202        """Scales tower image."""
203
204        if self.grid_active:
205            """Checks if grid is currently active, then renders a preview of the tower selected."""
206            self.render_tower_preview()
207
208        if self.selected_tower:
209            """Renders the attack radius of the selected tower."""
210            self.draw_radius(self.selected_tower._position, self.selected_tower.get_range(), (128, 128, 128, 100))
211            # Logic for upgrades and tower selection info should go here
212
213        self.tower1_price = self.font.render(f'$200', True, (255, 255, 255))
214        """Renders the price of tower1."""
215
216        for tower in self.placed_towers:
217            """Renders each placed tower."""
218            tower.render(self.window)
219        
220        for enemy in self._enemy_list:
221            enemy.render(self.window)
222
223        if self.debug:
224            """When debug is true, updates the game and renders various aspects."""
225            self.update_cursor_position()
226            self.draw_enemy_path()
227            self.render_tower_preview()
228            self.render_collision_rects()
229            '''
230            for tower in self.placed_towers:
231                print(tower._position) 
232            '''
233        if not self.pause:
234            
235            self.update_waves()
236            for enemy in self._enemy_list:
237                """For each active enemy, the enemy will move along the set path towards the player base."""
238                if enemy.is_alive():
239                    enemy._move()
240                    if enemy._path_index >= len(enemy._path) - 1:
241                        enemy.damage_base(self)
242                        self.remove_health(enemy._strength)
243                        self.remove_money(enemy._resource_worth)
244            self.update_attacks()
245
246        # Display menu and UI
247        bottom_bar.draw()
248        side_bar.draw()
249        for box in tower_boxes:
250            """Draws each box in the tower_boxes list."""
251            box.draw()
252        self.window.blit(tower_image, (705, 95))
253        self.window.blit(self.health_text, (705, 10))
254        self.window.blit(self.money_text, (705, 40))
255        self.window.blit(self.wave_text, (705, 70))
256        self.window.blit(self.tower1_price, (731, 167))
257        self.pause_button.draw(window)
258        pygame.display.update()
259
260    def check_for_click(self):
261        for event in pygame.event.get():
262            if event.type == pygame.QUIT:
263                pygame.quit()
264                sys.exit()
265            elif event.type == pygame.MOUSEBUTTONDOWN:
266                mouse_pos = pygame.mouse.get_pos()
267                tower_boxes = [
268                    pygame.Rect(705, 100, 90, 90),
269                    pygame.Rect(705, 200, 90, 90),
270                    pygame.Rect(705, 300, 90, 90),
271                    pygame.Rect(705, 400, 90, 90)
272                ]
273                for box in tower_boxes:
274                    if box.collidepoint(mouse_pos):
275                        self.grid_active = not self.grid_active
276                        return
277                if self.grid_active:
278                    self.place_tower(mouse_pos)
279                    return
280
281                # selecting towers.
282                tower_clicked = False
283                for tower in self.placed_towers:
284                    tower_size = int(self.grid_size * self.tower_size * 0.8)
285                    tower_rect = pygame.rect.Rect(
286                        tower._position[0] - tower_size // 2,
287                        tower._position[1] - tower_size // 2,
288                        tower_size, tower_size
289                    )
290                    if tower_rect.collidepoint(mouse_pos):
291                        self.selected_tower = tower
292                        tower_clicked = True
293                        break
294                if not tower_clicked:
295                    self.selected_tower = None
296
297                # Pause button functionality
298                pause_button = pygame.Rect(710, 510, 75, 75)
299                if pause_button.collidepoint(mouse_pos):
300                    if self.pause == True:
301                        self.pause = False
302                    elif self.pause == False:
303                        self.pause = True
304
305            # Debug toggle button.
306            elif event.type == pygame.KEYDOWN:
307                if event.key == pygame.K_p:
308                    if self.debug == True:
309                        self.debug = False
310                    else:
311                        self.debug = True
312                    return
313
314    def draw_radius(self, center, radius, color):
315        range_surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
316        range_surface.fill((0, 0, 0, 0))
317        pygame.draw.circle(
318            range_surface, color, (radius, radius), radius
319        )
320        top_left = (center[0] - radius, center[1] - radius)
321        self.window.blit(range_surface, top_left)
322
323    def render_tower_preview(self):
324        mouse_x, mouse_y = pygame.mouse.get_pos()
325        temp_tower = Tower("Archer Tower", 50, 3, 100, 80, 1)
326        if self.check_collision(mouse_x, mouse_y):
327            color = (255, 0, 0, 100)
328        else:
329            color = (128, 128, 128, 100)
330        self.draw_radius((mouse_x, mouse_y), temp_tower.get_range(), color)
331        tower_surface = pygame.transform.scale(
332            temp_tower._image, (temp_tower.size * 3, temp_tower.size * 3)
333        )
334        self.window.blit(
335            tower_surface,
336            (mouse_x - tower_surface.get_width() // 2, mouse_y - tower_surface.get_height() // 2)
337        )
338
339    def place_tower(self, mouse_pos):
340        if not self.check_collision(mouse_pos[0], mouse_pos[1]):
341            if self.money < 200:
342                return
343            new_tower = Tower("Archer Tower", 20, 2, 1, 80, 1)
344            # need to expand on this to allow for different towers
345            new_tower.place(mouse_pos)
346            self.placed_towers.append(new_tower)
347            self.grid_active = False
348            self.selected_tower = False
349            self.remove_money(200)
350        else:
351            print("Cannot place the tower here. Collision detected.")
352
353    def check_collision(self, x, y):
354        preview_size = self.grid_size * self.tower_size
355
356        preview_rect = pygame.Rect(
357            x - preview_size // 2, y - preview_size // 2, preview_size, preview_size
358        )
359
360        for tower in self.placed_towers:  # checking to see if colliding with any placed towers.
361            tower_size = int(self.grid_size * self.tower_size * 0.8)
362            tower_rect = pygame.Rect(
363                tower._position[0] - tower_size // 2,
364                tower._position[1] - tower_size // 2,
365                tower_size, tower_size
366            )
367            if preview_rect.colliderect(tower_rect):
368                return True
369
370        for rect in self.collision_rects:  # checking to see if collide with map path.
371            if preview_rect.colliderect(rect):
372                return True
373        bottom_bar = pygame.Rect(0, window_height - 100, window_width, 100)
374        side_bar = pygame.Rect(window_width - 100, 0, 100, window_height)
375
376        if preview_rect.colliderect(bottom_bar) or preview_rect.colliderect(side_bar):
377            return True
378        return False
379
380    def update_attacks(self):
381        for tower in self.placed_towers:
382            tower.attack(self._enemy_list)
383        for enemy in self._enemy_list:
384            if not enemy.is_alive():
385                self.add_money(enemy._resource_worth)
386                self._enemy_list.remove(enemy)
387
388    def update_waves(self):
389        """Checks if the current wave is complete, moves on to the next wave prepared once it is."""
390        if self._current_wave < len(self._waves):
391            current_wave = self._waves[self._current_wave]
392            if current_wave._is_wave_complete():
393                self._current_wave += 1
394                if self._current_wave < len(self._waves):
395                    self.wave += 1
396                    self.wave_text = self.font.render(f"Wave: {self.wave}", True, (255, 255, 255))
397                return
398
399            self._time_since_previous_spawn += 1
400            if self._time_since_previous_spawn >= current_wave._spawn_timer:
401                if current_wave.spawn_enemy():
402                    new_enemy = current_wave._enemy_list[-1]
403                    """Records the next enemy to be spawned."""
404                    self._enemy_list.append(new_enemy)
405                self._time_since_previous_spawn = 0
406
407    def remove_health(self, health):
408        self.health -= health
409        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
410        if self.health <= 0:
411            self.game_over()
412
413    def add_money(self, money):
414        self.money += money
415        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
416
417    def remove_money(self, money):
418        self.money -= money
419        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
420
421    def set_health(self, health):
422        self.health = health
423        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
424
425    def set_money(self, money):
426        self.money = money
427        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
428
429    # Debugging Functions
430    def update_cursor_position(self):
431        """Update and render the cursor position."""
432        mouse_pos = pygame.mouse.get_pos()
433        self.cursor_text = self.font.render(f'Cursor: {mouse_pos}', True, (255, 255, 255))
434        self.window.blit(self.cursor_text, (10, 10))
435
436    def draw_enemy_path(self):
437        path_color = (255, 0, 0)
438        path_width = 3
439
440        for i in range(len(self.map_path) - 1):
441            start_pos = self.map_path[i]
442            end_pos = self.map_path[i + 1]
443            pygame.draw.line(self.window, path_color, start_pos, end_pos, path_width)
444
445    def render_collision_rects(self):
446        """Render the collision rectangles for debugging."""
447        for rect in self.collision_rects:
448            pygame.draw.rect(self.window, (255, 0, 0), rect, 2)
449
450
451def main():
452    start_screen = StartScreen(window)
453    main_game_screen = MainGameScreen(window)
454    game_state = 'start_screen'
455    main_game_screen.set_health(100)
456    main_game_screen.set_money(500)
457    while True:
458        if game_state == 'start_screen':
459            start_screen.render()
460            if start_screen.check_for_click():
461                game_state = 'main_game'
462
463        elif game_state == 'main_game':
464            main_game_screen.render()
465            main_game_screen.check_for_click()
466            for event in pygame.event.get():
467                if event.type == pygame.QUIT:
468                    pygame.quit()
469                    sys.exit()
470
471        fpsClock.tick(FPS)
472
473
474if __name__ == '__main__':
475    main()
FPS = 60
fpsClock = <Clock(fps=0.00)>
window_width = 800
window_height = 600
window = <Surface(800x600x32 SW)>
class StartScreen:
17class StartScreen:
18    """
19    A class to represent the start screen of the Tower Defense Game.
20    Attributes:
21    
22    Attributes:
23    window: The window surface where the start screen will be rendered.
24    background: The background image of the start screen.
25    font: The font used for rendering text on the start screen.
26    title_text: The rendered text surface for the game title.
27    start_text: The rendered text surface for the start button.
28    start_button_rect: The rectangle area of the start button.
29   
30    Methods:
31    render(): Renders the start screen with the background, title, and start button.
32    check_for_click(): Checks for mouse click events and returns True if the start button is clicked.
33    """
34    def __init__(self, window):
35        """
36        Initializes the main game window and loads the start screen assets.
37        Args:
38            window: The window surface where the start screen will be rendered.
39        """
40        self.window = window
41        self.background = pygame.image.load(os.path.join('game_assests', 'start_screen_background.jpg'))
42        self.background = pygame.transform.scale(self.background, (window_width, window_height))
43        self.font = pygame.font.SysFont(None, 55)
44        self.title_text = self.font.render('Tower Defense Game', True, (255, 255, 255))
45        self.start_text = self.font.render('Click to Start', True, (255, 255, 255))
46
47    def render(self):
48        """
49        Renders the start screen with the background, title, and start button.
50        """
51        self.window.blit(self.background, (0, 0))
52        self.window.blit(self.title_text, (window_width // 2 - self.title_text.get_width() // 2, 100))
53        start_text_rect = self.start_text.get_rect(center=(window_width // 2, 400))
54        rect_x = start_text_rect.x - 10
55        rect_y = start_text_rect.y - 10
56        rect_width = start_text_rect.width + 20
57        rect_height = start_text_rect.height + 20
58
59        pygame.draw.rect(self.window, (39, 145, 39), (rect_x, rect_y, rect_width, rect_height))
60        self.window.blit(self.start_text, start_text_rect.topleft)
61        pygame.display.update()
62        self.start_button_rect = pygame.Rect(rect_x, rect_y, rect_width, rect_height)
63
64    def check_for_click(self):
65        """
66        Checks for mouse click events and returns True if the start button is clicked.
67
68        Returns:
69            bool: True if the start button is clicked, False otherwise.
70        """
71        for event in pygame.event.get():
72            if event.type == pygame.QUIT:
73                pygame.quit()
74                sys.exit()
75            if event.type == pygame.MOUSEBUTTONDOWN:
76                mouse_pos = pygame.mouse.get_pos()
77                if self.start_button_rect.collidepoint(mouse_pos):
78                    return True
79        return False

A class to represent the start screen of the Tower Defense Game. Attributes:

Attributes: window: The window surface where the start screen will be rendered. background: The background image of the start screen. font: The font used for rendering text on the start screen. title_text: The rendered text surface for the game title. start_text: The rendered text surface for the start button. start_button_rect: The rectangle area of the start button.

Methods: render(): Renders the start screen with the background, title, and start button. check_for_click(): Checks for mouse click events and returns True if the start button is clicked.

StartScreen(window)
34    def __init__(self, window):
35        """
36        Initializes the main game window and loads the start screen assets.
37        Args:
38            window: The window surface where the start screen will be rendered.
39        """
40        self.window = window
41        self.background = pygame.image.load(os.path.join('game_assests', 'start_screen_background.jpg'))
42        self.background = pygame.transform.scale(self.background, (window_width, window_height))
43        self.font = pygame.font.SysFont(None, 55)
44        self.title_text = self.font.render('Tower Defense Game', True, (255, 255, 255))
45        self.start_text = self.font.render('Click to Start', True, (255, 255, 255))

Initializes the main game window and loads the start screen assets. Args: window: The window surface where the start screen will be rendered.

window
background
font
title_text
start_text
def render(self):
47    def render(self):
48        """
49        Renders the start screen with the background, title, and start button.
50        """
51        self.window.blit(self.background, (0, 0))
52        self.window.blit(self.title_text, (window_width // 2 - self.title_text.get_width() // 2, 100))
53        start_text_rect = self.start_text.get_rect(center=(window_width // 2, 400))
54        rect_x = start_text_rect.x - 10
55        rect_y = start_text_rect.y - 10
56        rect_width = start_text_rect.width + 20
57        rect_height = start_text_rect.height + 20
58
59        pygame.draw.rect(self.window, (39, 145, 39), (rect_x, rect_y, rect_width, rect_height))
60        self.window.blit(self.start_text, start_text_rect.topleft)
61        pygame.display.update()
62        self.start_button_rect = pygame.Rect(rect_x, rect_y, rect_width, rect_height)

Renders the start screen with the background, title, and start button.

def check_for_click(self):
64    def check_for_click(self):
65        """
66        Checks for mouse click events and returns True if the start button is clicked.
67
68        Returns:
69            bool: True if the start button is clicked, False otherwise.
70        """
71        for event in pygame.event.get():
72            if event.type == pygame.QUIT:
73                pygame.quit()
74                sys.exit()
75            if event.type == pygame.MOUSEBUTTONDOWN:
76                mouse_pos = pygame.mouse.get_pos()
77                if self.start_button_rect.collidepoint(mouse_pos):
78                    return True
79        return False

Checks for mouse click events and returns True if the start button is clicked.

Returns: bool: True if the start button is clicked, False otherwise.

class MainGameScreen:
 82class MainGameScreen:
 83    """The game screen which shows the map and handles the generation of enemies, towers, and player stats."""
 84    def __init__(self, window):
 85        self.window = window
 86        """The window for the game."""
 87        self.background = pygame.image.load(os.path.join('game_assests', 'map_one.png'))
 88        """Loads the image."""
 89        self.background = pygame.transform.scale(self.background, (window_width - 100, window_height - 100))
 90        """Scales the image in self.background appropriately and then saves it back to self.background."""
 91        self.font = pygame.font.SysFont(None, 22)
 92        """Sets the font style to be used."""
 93        self.health = 0
 94        """Holds the amount of health the player has remaining."""
 95        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
 96        """Renders the health text so the player knows how much health they have remaining."""
 97        self.money = 0
 98        """Holds the amount of money the player has"""
 99        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
100        """Renders the money text so the player knows how much money they have remaining."""
101
102        # Pause button
103        pause_img = pygame.image.load(os.path.join('game_assests', 'Play-Pause.png')).convert_alpha()
104        """The image of the pause button."""
105        self.pause_button = button.Button(710, 510, pause_img, 0.15)
106        """Makes the pause button a button to be clicked, using the image stored in pause_img."""
107        self.pause = True
108        """Sets pause to True when initially ran."""
109
110        # Map Variables
111        self.map_path = ((0, 274), (116, 274), (116, 124), (258, 124), (258, 322), (444, 322),
112                         (444, 226), (700, 226), (800, 226))
113        """Sets the path for enemies to traverse."""
114        self.collision_rects = [
115            pygame.Rect(min(0, 140), min(248, 300), max(0, 140) - min(0, 140), max(248, 300) - min(248, 300)),
116            pygame.Rect(min(140, 96), min(300, 100), max(140, 96) - min(140, 96), max(300, 100) - min(300, 100)),
117            pygame.Rect(min(96, 278), min(100, 146), max(96, 278) - min(96, 278), max(146, 100) - min(100, 146)),
118            pygame.Rect(min(278, 230), min(146, 348), max(278, 230) - min(278, 230), max(348, 146) - min(146, 348)),
119            pygame.Rect(min(230, 470), min(348, 299), max(470, 230) - min(230, 470), max(348, 299) - min(299, 348)),
120            pygame.Rect(min(470, 418), min(299, 200), max(470, 418) - min(470, 418), max(299, 200) - min(200, 299)),
121            pygame.Rect(min(418, 700), min(200, 250), max(700, 418) - min(418, 700), max(250, 200) - min(200, 250)),
122        ]
123        """Sets up some collision spots, where the towers cannot be placed. (ie. the enemy path)"""
124
125        # Tower Variables
126        self.grid_active = False
127        """Used to show the grid"""
128        self.grid_size = 14  # Will remove later, some things still depend on this.
129        """Determines the grid size."""
130        self.tower_size = 3
131        """Determines the tower size"""
132        self.selected_tower = None
133        """Determines which tower the player has selected."""
134        self.placed_towers = []
135        """A list to hold all the towers that have been placed."""
136
137        # Wave and Enemy
138        self.wave = 1
139        """Stores which wave number is currently displayed to the player"""
140        self._waves = []  # list of waves
141        self._time_since_previous_spawn = 0
142        self._enemy_list = []
143        self._current_wave = 0
144        self.wave_text = self.font.render(f'Wave: {self.wave}', True, (255, 255, 255))
145        """Renders the current wave number."""
146
147        for wave_number in range(1, 60):
148            """Loop for generating waves, changes are planned to further improve."""
149            enemy_count = 3 * wave_number
150            """Determines amount of enemies to be spawned, dependent on wave number."""
151            enemy_hp = 10 + (5 * wave_number)
152            """Increases health of the enemies, dependent on wave number."""
153            wave_data = [
154                Enemy('circle', enemy_hp, 1, 1, self.map_path) for i in range(enemy_count)
155            ]
156            """Generate wave data, to be added to self._waves"""
157            self._waves.append(Wave(wave_data, 60))
158
159            # Debugging Variables
160        self.debug = False
161        """Used to help debug."""
162        self.cursor_text = self.font.render('', True, (255, 255, 255))
163        """Displays the text for the cursor."""
164
165    def render(self):
166        """Used to render the game and enemies."""
167        self.window.blit(self.background, (0, 0))
168        """Sets window"""
169
170        class Rectangle():
171            """Creates rectangles for the UI."""
172            def __init__(self, x, y, width, height, color):
173                self.x = x
174                """The x position of the rectangle."""
175                self.y = y
176                """The y position of the rectangle"""
177                self.width = width
178                """The width of the rectangle."""
179                self.height = height
180                """The height of the rectangle."""
181                self.color = color
182                """The color of the rectangle."""
183
184            def draw(self):
185                """Draws the rectangle."""
186                pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height))
187
188        # create menus, tower slots, and tower image
189        bottom_bar = Rectangle(0, (window_height - 100), window_width, 100, (150, 150, 150))
190        """Menu on the bottom, currently hold nothing."""
191        side_bar = Rectangle((window_width - 100), 0, 100, window_height, (150, 150, 150))
192        """Menu on the right side, shows player stats and towers that can be used."""
193        tower_boxes = [
194            Rectangle(705, 100, 90, 90, (100, 100, 100)),
195            Rectangle(705, 200, 90, 90, (100, 100, 100)),
196            Rectangle(705, 300, 90, 90, (100, 100, 100)),
197            Rectangle(705, 400, 90, 90, (100, 100, 100))
198        ]
199        """Makes the rectangles for the tower boxes."""
200        tower_image = pygame.image.load(os.path.join("game_assests", "tower.png"))
201        """Loads tower image."""
202        tower_image = pygame.transform.scale(tower_image, (90, 90))
203        """Scales tower image."""
204
205        if self.grid_active:
206            """Checks if grid is currently active, then renders a preview of the tower selected."""
207            self.render_tower_preview()
208
209        if self.selected_tower:
210            """Renders the attack radius of the selected tower."""
211            self.draw_radius(self.selected_tower._position, self.selected_tower.get_range(), (128, 128, 128, 100))
212            # Logic for upgrades and tower selection info should go here
213
214        self.tower1_price = self.font.render(f'$200', True, (255, 255, 255))
215        """Renders the price of tower1."""
216
217        for tower in self.placed_towers:
218            """Renders each placed tower."""
219            tower.render(self.window)
220        
221        for enemy in self._enemy_list:
222            enemy.render(self.window)
223
224        if self.debug:
225            """When debug is true, updates the game and renders various aspects."""
226            self.update_cursor_position()
227            self.draw_enemy_path()
228            self.render_tower_preview()
229            self.render_collision_rects()
230            '''
231            for tower in self.placed_towers:
232                print(tower._position) 
233            '''
234        if not self.pause:
235            
236            self.update_waves()
237            for enemy in self._enemy_list:
238                """For each active enemy, the enemy will move along the set path towards the player base."""
239                if enemy.is_alive():
240                    enemy._move()
241                    if enemy._path_index >= len(enemy._path) - 1:
242                        enemy.damage_base(self)
243                        self.remove_health(enemy._strength)
244                        self.remove_money(enemy._resource_worth)
245            self.update_attacks()
246
247        # Display menu and UI
248        bottom_bar.draw()
249        side_bar.draw()
250        for box in tower_boxes:
251            """Draws each box in the tower_boxes list."""
252            box.draw()
253        self.window.blit(tower_image, (705, 95))
254        self.window.blit(self.health_text, (705, 10))
255        self.window.blit(self.money_text, (705, 40))
256        self.window.blit(self.wave_text, (705, 70))
257        self.window.blit(self.tower1_price, (731, 167))
258        self.pause_button.draw(window)
259        pygame.display.update()
260
261    def check_for_click(self):
262        for event in pygame.event.get():
263            if event.type == pygame.QUIT:
264                pygame.quit()
265                sys.exit()
266            elif event.type == pygame.MOUSEBUTTONDOWN:
267                mouse_pos = pygame.mouse.get_pos()
268                tower_boxes = [
269                    pygame.Rect(705, 100, 90, 90),
270                    pygame.Rect(705, 200, 90, 90),
271                    pygame.Rect(705, 300, 90, 90),
272                    pygame.Rect(705, 400, 90, 90)
273                ]
274                for box in tower_boxes:
275                    if box.collidepoint(mouse_pos):
276                        self.grid_active = not self.grid_active
277                        return
278                if self.grid_active:
279                    self.place_tower(mouse_pos)
280                    return
281
282                # selecting towers.
283                tower_clicked = False
284                for tower in self.placed_towers:
285                    tower_size = int(self.grid_size * self.tower_size * 0.8)
286                    tower_rect = pygame.rect.Rect(
287                        tower._position[0] - tower_size // 2,
288                        tower._position[1] - tower_size // 2,
289                        tower_size, tower_size
290                    )
291                    if tower_rect.collidepoint(mouse_pos):
292                        self.selected_tower = tower
293                        tower_clicked = True
294                        break
295                if not tower_clicked:
296                    self.selected_tower = None
297
298                # Pause button functionality
299                pause_button = pygame.Rect(710, 510, 75, 75)
300                if pause_button.collidepoint(mouse_pos):
301                    if self.pause == True:
302                        self.pause = False
303                    elif self.pause == False:
304                        self.pause = True
305
306            # Debug toggle button.
307            elif event.type == pygame.KEYDOWN:
308                if event.key == pygame.K_p:
309                    if self.debug == True:
310                        self.debug = False
311                    else:
312                        self.debug = True
313                    return
314
315    def draw_radius(self, center, radius, color):
316        range_surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
317        range_surface.fill((0, 0, 0, 0))
318        pygame.draw.circle(
319            range_surface, color, (radius, radius), radius
320        )
321        top_left = (center[0] - radius, center[1] - radius)
322        self.window.blit(range_surface, top_left)
323
324    def render_tower_preview(self):
325        mouse_x, mouse_y = pygame.mouse.get_pos()
326        temp_tower = Tower("Archer Tower", 50, 3, 100, 80, 1)
327        if self.check_collision(mouse_x, mouse_y):
328            color = (255, 0, 0, 100)
329        else:
330            color = (128, 128, 128, 100)
331        self.draw_radius((mouse_x, mouse_y), temp_tower.get_range(), color)
332        tower_surface = pygame.transform.scale(
333            temp_tower._image, (temp_tower.size * 3, temp_tower.size * 3)
334        )
335        self.window.blit(
336            tower_surface,
337            (mouse_x - tower_surface.get_width() // 2, mouse_y - tower_surface.get_height() // 2)
338        )
339
340    def place_tower(self, mouse_pos):
341        if not self.check_collision(mouse_pos[0], mouse_pos[1]):
342            if self.money < 200:
343                return
344            new_tower = Tower("Archer Tower", 20, 2, 1, 80, 1)
345            # need to expand on this to allow for different towers
346            new_tower.place(mouse_pos)
347            self.placed_towers.append(new_tower)
348            self.grid_active = False
349            self.selected_tower = False
350            self.remove_money(200)
351        else:
352            print("Cannot place the tower here. Collision detected.")
353
354    def check_collision(self, x, y):
355        preview_size = self.grid_size * self.tower_size
356
357        preview_rect = pygame.Rect(
358            x - preview_size // 2, y - preview_size // 2, preview_size, preview_size
359        )
360
361        for tower in self.placed_towers:  # checking to see if colliding with any placed towers.
362            tower_size = int(self.grid_size * self.tower_size * 0.8)
363            tower_rect = pygame.Rect(
364                tower._position[0] - tower_size // 2,
365                tower._position[1] - tower_size // 2,
366                tower_size, tower_size
367            )
368            if preview_rect.colliderect(tower_rect):
369                return True
370
371        for rect in self.collision_rects:  # checking to see if collide with map path.
372            if preview_rect.colliderect(rect):
373                return True
374        bottom_bar = pygame.Rect(0, window_height - 100, window_width, 100)
375        side_bar = pygame.Rect(window_width - 100, 0, 100, window_height)
376
377        if preview_rect.colliderect(bottom_bar) or preview_rect.colliderect(side_bar):
378            return True
379        return False
380
381    def update_attacks(self):
382        for tower in self.placed_towers:
383            tower.attack(self._enemy_list)
384        for enemy in self._enemy_list:
385            if not enemy.is_alive():
386                self.add_money(enemy._resource_worth)
387                self._enemy_list.remove(enemy)
388
389    def update_waves(self):
390        """Checks if the current wave is complete, moves on to the next wave prepared once it is."""
391        if self._current_wave < len(self._waves):
392            current_wave = self._waves[self._current_wave]
393            if current_wave._is_wave_complete():
394                self._current_wave += 1
395                if self._current_wave < len(self._waves):
396                    self.wave += 1
397                    self.wave_text = self.font.render(f"Wave: {self.wave}", True, (255, 255, 255))
398                return
399
400            self._time_since_previous_spawn += 1
401            if self._time_since_previous_spawn >= current_wave._spawn_timer:
402                if current_wave.spawn_enemy():
403                    new_enemy = current_wave._enemy_list[-1]
404                    """Records the next enemy to be spawned."""
405                    self._enemy_list.append(new_enemy)
406                self._time_since_previous_spawn = 0
407
408    def remove_health(self, health):
409        self.health -= health
410        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
411        if self.health <= 0:
412            self.game_over()
413
414    def add_money(self, money):
415        self.money += money
416        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
417
418    def remove_money(self, money):
419        self.money -= money
420        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
421
422    def set_health(self, health):
423        self.health = health
424        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
425
426    def set_money(self, money):
427        self.money = money
428        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
429
430    # Debugging Functions
431    def update_cursor_position(self):
432        """Update and render the cursor position."""
433        mouse_pos = pygame.mouse.get_pos()
434        self.cursor_text = self.font.render(f'Cursor: {mouse_pos}', True, (255, 255, 255))
435        self.window.blit(self.cursor_text, (10, 10))
436
437    def draw_enemy_path(self):
438        path_color = (255, 0, 0)
439        path_width = 3
440
441        for i in range(len(self.map_path) - 1):
442            start_pos = self.map_path[i]
443            end_pos = self.map_path[i + 1]
444            pygame.draw.line(self.window, path_color, start_pos, end_pos, path_width)
445
446    def render_collision_rects(self):
447        """Render the collision rectangles for debugging."""
448        for rect in self.collision_rects:
449            pygame.draw.rect(self.window, (255, 0, 0), rect, 2)

The game screen which shows the map and handles the generation of enemies, towers, and player stats.

MainGameScreen(window)
 84    def __init__(self, window):
 85        self.window = window
 86        """The window for the game."""
 87        self.background = pygame.image.load(os.path.join('game_assests', 'map_one.png'))
 88        """Loads the image."""
 89        self.background = pygame.transform.scale(self.background, (window_width - 100, window_height - 100))
 90        """Scales the image in self.background appropriately and then saves it back to self.background."""
 91        self.font = pygame.font.SysFont(None, 22)
 92        """Sets the font style to be used."""
 93        self.health = 0
 94        """Holds the amount of health the player has remaining."""
 95        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
 96        """Renders the health text so the player knows how much health they have remaining."""
 97        self.money = 0
 98        """Holds the amount of money the player has"""
 99        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
100        """Renders the money text so the player knows how much money they have remaining."""
101
102        # Pause button
103        pause_img = pygame.image.load(os.path.join('game_assests', 'Play-Pause.png')).convert_alpha()
104        """The image of the pause button."""
105        self.pause_button = button.Button(710, 510, pause_img, 0.15)
106        """Makes the pause button a button to be clicked, using the image stored in pause_img."""
107        self.pause = True
108        """Sets pause to True when initially ran."""
109
110        # Map Variables
111        self.map_path = ((0, 274), (116, 274), (116, 124), (258, 124), (258, 322), (444, 322),
112                         (444, 226), (700, 226), (800, 226))
113        """Sets the path for enemies to traverse."""
114        self.collision_rects = [
115            pygame.Rect(min(0, 140), min(248, 300), max(0, 140) - min(0, 140), max(248, 300) - min(248, 300)),
116            pygame.Rect(min(140, 96), min(300, 100), max(140, 96) - min(140, 96), max(300, 100) - min(300, 100)),
117            pygame.Rect(min(96, 278), min(100, 146), max(96, 278) - min(96, 278), max(146, 100) - min(100, 146)),
118            pygame.Rect(min(278, 230), min(146, 348), max(278, 230) - min(278, 230), max(348, 146) - min(146, 348)),
119            pygame.Rect(min(230, 470), min(348, 299), max(470, 230) - min(230, 470), max(348, 299) - min(299, 348)),
120            pygame.Rect(min(470, 418), min(299, 200), max(470, 418) - min(470, 418), max(299, 200) - min(200, 299)),
121            pygame.Rect(min(418, 700), min(200, 250), max(700, 418) - min(418, 700), max(250, 200) - min(200, 250)),
122        ]
123        """Sets up some collision spots, where the towers cannot be placed. (ie. the enemy path)"""
124
125        # Tower Variables
126        self.grid_active = False
127        """Used to show the grid"""
128        self.grid_size = 14  # Will remove later, some things still depend on this.
129        """Determines the grid size."""
130        self.tower_size = 3
131        """Determines the tower size"""
132        self.selected_tower = None
133        """Determines which tower the player has selected."""
134        self.placed_towers = []
135        """A list to hold all the towers that have been placed."""
136
137        # Wave and Enemy
138        self.wave = 1
139        """Stores which wave number is currently displayed to the player"""
140        self._waves = []  # list of waves
141        self._time_since_previous_spawn = 0
142        self._enemy_list = []
143        self._current_wave = 0
144        self.wave_text = self.font.render(f'Wave: {self.wave}', True, (255, 255, 255))
145        """Renders the current wave number."""
146
147        for wave_number in range(1, 60):
148            """Loop for generating waves, changes are planned to further improve."""
149            enemy_count = 3 * wave_number
150            """Determines amount of enemies to be spawned, dependent on wave number."""
151            enemy_hp = 10 + (5 * wave_number)
152            """Increases health of the enemies, dependent on wave number."""
153            wave_data = [
154                Enemy('circle', enemy_hp, 1, 1, self.map_path) for i in range(enemy_count)
155            ]
156            """Generate wave data, to be added to self._waves"""
157            self._waves.append(Wave(wave_data, 60))
158
159            # Debugging Variables
160        self.debug = False
161        """Used to help debug."""
162        self.cursor_text = self.font.render('', True, (255, 255, 255))
163        """Displays the text for the cursor."""
window

The window for the game.

background

Scales the image in self.background appropriately and then saves it back to self.background.

font

Sets the font style to be used.

health

Holds the amount of health the player has remaining.

health_text

Renders the health text so the player knows how much health they have remaining.

money

Holds the amount of money the player has

money_text

Renders the money text so the player knows how much money they have remaining.

pause_button

Makes the pause button a button to be clicked, using the image stored in pause_img.

pause

Sets pause to True when initially ran.

map_path

Sets the path for enemies to traverse.

collision_rects

Sets up some collision spots, where the towers cannot be placed. (ie. the enemy path)

grid_active

Used to show the grid

grid_size

Determines the grid size.

tower_size

Determines the tower size

selected_tower

Determines which tower the player has selected.

placed_towers

A list to hold all the towers that have been placed.

wave

Stores which wave number is currently displayed to the player

wave_text

Renders the current wave number.

debug

Used to help debug.

cursor_text

Displays the text for the cursor.

def render(self):
165    def render(self):
166        """Used to render the game and enemies."""
167        self.window.blit(self.background, (0, 0))
168        """Sets window"""
169
170        class Rectangle():
171            """Creates rectangles for the UI."""
172            def __init__(self, x, y, width, height, color):
173                self.x = x
174                """The x position of the rectangle."""
175                self.y = y
176                """The y position of the rectangle"""
177                self.width = width
178                """The width of the rectangle."""
179                self.height = height
180                """The height of the rectangle."""
181                self.color = color
182                """The color of the rectangle."""
183
184            def draw(self):
185                """Draws the rectangle."""
186                pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height))
187
188        # create menus, tower slots, and tower image
189        bottom_bar = Rectangle(0, (window_height - 100), window_width, 100, (150, 150, 150))
190        """Menu on the bottom, currently hold nothing."""
191        side_bar = Rectangle((window_width - 100), 0, 100, window_height, (150, 150, 150))
192        """Menu on the right side, shows player stats and towers that can be used."""
193        tower_boxes = [
194            Rectangle(705, 100, 90, 90, (100, 100, 100)),
195            Rectangle(705, 200, 90, 90, (100, 100, 100)),
196            Rectangle(705, 300, 90, 90, (100, 100, 100)),
197            Rectangle(705, 400, 90, 90, (100, 100, 100))
198        ]
199        """Makes the rectangles for the tower boxes."""
200        tower_image = pygame.image.load(os.path.join("game_assests", "tower.png"))
201        """Loads tower image."""
202        tower_image = pygame.transform.scale(tower_image, (90, 90))
203        """Scales tower image."""
204
205        if self.grid_active:
206            """Checks if grid is currently active, then renders a preview of the tower selected."""
207            self.render_tower_preview()
208
209        if self.selected_tower:
210            """Renders the attack radius of the selected tower."""
211            self.draw_radius(self.selected_tower._position, self.selected_tower.get_range(), (128, 128, 128, 100))
212            # Logic for upgrades and tower selection info should go here
213
214        self.tower1_price = self.font.render(f'$200', True, (255, 255, 255))
215        """Renders the price of tower1."""
216
217        for tower in self.placed_towers:
218            """Renders each placed tower."""
219            tower.render(self.window)
220        
221        for enemy in self._enemy_list:
222            enemy.render(self.window)
223
224        if self.debug:
225            """When debug is true, updates the game and renders various aspects."""
226            self.update_cursor_position()
227            self.draw_enemy_path()
228            self.render_tower_preview()
229            self.render_collision_rects()
230            '''
231            for tower in self.placed_towers:
232                print(tower._position) 
233            '''
234        if not self.pause:
235            
236            self.update_waves()
237            for enemy in self._enemy_list:
238                """For each active enemy, the enemy will move along the set path towards the player base."""
239                if enemy.is_alive():
240                    enemy._move()
241                    if enemy._path_index >= len(enemy._path) - 1:
242                        enemy.damage_base(self)
243                        self.remove_health(enemy._strength)
244                        self.remove_money(enemy._resource_worth)
245            self.update_attacks()
246
247        # Display menu and UI
248        bottom_bar.draw()
249        side_bar.draw()
250        for box in tower_boxes:
251            """Draws each box in the tower_boxes list."""
252            box.draw()
253        self.window.blit(tower_image, (705, 95))
254        self.window.blit(self.health_text, (705, 10))
255        self.window.blit(self.money_text, (705, 40))
256        self.window.blit(self.wave_text, (705, 70))
257        self.window.blit(self.tower1_price, (731, 167))
258        self.pause_button.draw(window)
259        pygame.display.update()

Used to render the game and enemies.

def check_for_click(self):
261    def check_for_click(self):
262        for event in pygame.event.get():
263            if event.type == pygame.QUIT:
264                pygame.quit()
265                sys.exit()
266            elif event.type == pygame.MOUSEBUTTONDOWN:
267                mouse_pos = pygame.mouse.get_pos()
268                tower_boxes = [
269                    pygame.Rect(705, 100, 90, 90),
270                    pygame.Rect(705, 200, 90, 90),
271                    pygame.Rect(705, 300, 90, 90),
272                    pygame.Rect(705, 400, 90, 90)
273                ]
274                for box in tower_boxes:
275                    if box.collidepoint(mouse_pos):
276                        self.grid_active = not self.grid_active
277                        return
278                if self.grid_active:
279                    self.place_tower(mouse_pos)
280                    return
281
282                # selecting towers.
283                tower_clicked = False
284                for tower in self.placed_towers:
285                    tower_size = int(self.grid_size * self.tower_size * 0.8)
286                    tower_rect = pygame.rect.Rect(
287                        tower._position[0] - tower_size // 2,
288                        tower._position[1] - tower_size // 2,
289                        tower_size, tower_size
290                    )
291                    if tower_rect.collidepoint(mouse_pos):
292                        self.selected_tower = tower
293                        tower_clicked = True
294                        break
295                if not tower_clicked:
296                    self.selected_tower = None
297
298                # Pause button functionality
299                pause_button = pygame.Rect(710, 510, 75, 75)
300                if pause_button.collidepoint(mouse_pos):
301                    if self.pause == True:
302                        self.pause = False
303                    elif self.pause == False:
304                        self.pause = True
305
306            # Debug toggle button.
307            elif event.type == pygame.KEYDOWN:
308                if event.key == pygame.K_p:
309                    if self.debug == True:
310                        self.debug = False
311                    else:
312                        self.debug = True
313                    return
def draw_radius(self, center, radius, color):
315    def draw_radius(self, center, radius, color):
316        range_surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
317        range_surface.fill((0, 0, 0, 0))
318        pygame.draw.circle(
319            range_surface, color, (radius, radius), radius
320        )
321        top_left = (center[0] - radius, center[1] - radius)
322        self.window.blit(range_surface, top_left)
def render_tower_preview(self):
324    def render_tower_preview(self):
325        mouse_x, mouse_y = pygame.mouse.get_pos()
326        temp_tower = Tower("Archer Tower", 50, 3, 100, 80, 1)
327        if self.check_collision(mouse_x, mouse_y):
328            color = (255, 0, 0, 100)
329        else:
330            color = (128, 128, 128, 100)
331        self.draw_radius((mouse_x, mouse_y), temp_tower.get_range(), color)
332        tower_surface = pygame.transform.scale(
333            temp_tower._image, (temp_tower.size * 3, temp_tower.size * 3)
334        )
335        self.window.blit(
336            tower_surface,
337            (mouse_x - tower_surface.get_width() // 2, mouse_y - tower_surface.get_height() // 2)
338        )
def place_tower(self, mouse_pos):
340    def place_tower(self, mouse_pos):
341        if not self.check_collision(mouse_pos[0], mouse_pos[1]):
342            if self.money < 200:
343                return
344            new_tower = Tower("Archer Tower", 20, 2, 1, 80, 1)
345            # need to expand on this to allow for different towers
346            new_tower.place(mouse_pos)
347            self.placed_towers.append(new_tower)
348            self.grid_active = False
349            self.selected_tower = False
350            self.remove_money(200)
351        else:
352            print("Cannot place the tower here. Collision detected.")
def check_collision(self, x, y):
354    def check_collision(self, x, y):
355        preview_size = self.grid_size * self.tower_size
356
357        preview_rect = pygame.Rect(
358            x - preview_size // 2, y - preview_size // 2, preview_size, preview_size
359        )
360
361        for tower in self.placed_towers:  # checking to see if colliding with any placed towers.
362            tower_size = int(self.grid_size * self.tower_size * 0.8)
363            tower_rect = pygame.Rect(
364                tower._position[0] - tower_size // 2,
365                tower._position[1] - tower_size // 2,
366                tower_size, tower_size
367            )
368            if preview_rect.colliderect(tower_rect):
369                return True
370
371        for rect in self.collision_rects:  # checking to see if collide with map path.
372            if preview_rect.colliderect(rect):
373                return True
374        bottom_bar = pygame.Rect(0, window_height - 100, window_width, 100)
375        side_bar = pygame.Rect(window_width - 100, 0, 100, window_height)
376
377        if preview_rect.colliderect(bottom_bar) or preview_rect.colliderect(side_bar):
378            return True
379        return False
def update_attacks(self):
381    def update_attacks(self):
382        for tower in self.placed_towers:
383            tower.attack(self._enemy_list)
384        for enemy in self._enemy_list:
385            if not enemy.is_alive():
386                self.add_money(enemy._resource_worth)
387                self._enemy_list.remove(enemy)
def update_waves(self):
389    def update_waves(self):
390        """Checks if the current wave is complete, moves on to the next wave prepared once it is."""
391        if self._current_wave < len(self._waves):
392            current_wave = self._waves[self._current_wave]
393            if current_wave._is_wave_complete():
394                self._current_wave += 1
395                if self._current_wave < len(self._waves):
396                    self.wave += 1
397                    self.wave_text = self.font.render(f"Wave: {self.wave}", True, (255, 255, 255))
398                return
399
400            self._time_since_previous_spawn += 1
401            if self._time_since_previous_spawn >= current_wave._spawn_timer:
402                if current_wave.spawn_enemy():
403                    new_enemy = current_wave._enemy_list[-1]
404                    """Records the next enemy to be spawned."""
405                    self._enemy_list.append(new_enemy)
406                self._time_since_previous_spawn = 0

Checks if the current wave is complete, moves on to the next wave prepared once it is.

def remove_health(self, health):
408    def remove_health(self, health):
409        self.health -= health
410        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
411        if self.health <= 0:
412            self.game_over()
def add_money(self, money):
414    def add_money(self, money):
415        self.money += money
416        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
def remove_money(self, money):
418    def remove_money(self, money):
419        self.money -= money
420        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
def set_health(self, health):
422    def set_health(self, health):
423        self.health = health
424        self.health_text = self.font.render(f'Health: {self.health}', True, (255, 255, 255))
def set_money(self, money):
426    def set_money(self, money):
427        self.money = money
428        self.money_text = self.font.render(f'Money: {self.money}', True, (255, 255, 255))
def update_cursor_position(self):
431    def update_cursor_position(self):
432        """Update and render the cursor position."""
433        mouse_pos = pygame.mouse.get_pos()
434        self.cursor_text = self.font.render(f'Cursor: {mouse_pos}', True, (255, 255, 255))
435        self.window.blit(self.cursor_text, (10, 10))

Update and render the cursor position.

def draw_enemy_path(self):
437    def draw_enemy_path(self):
438        path_color = (255, 0, 0)
439        path_width = 3
440
441        for i in range(len(self.map_path) - 1):
442            start_pos = self.map_path[i]
443            end_pos = self.map_path[i + 1]
444            pygame.draw.line(self.window, path_color, start_pos, end_pos, path_width)
def render_collision_rects(self):
446    def render_collision_rects(self):
447        """Render the collision rectangles for debugging."""
448        for rect in self.collision_rects:
449            pygame.draw.rect(self.window, (255, 0, 0), rect, 2)

Render the collision rectangles for debugging.

def main():
452def main():
453    start_screen = StartScreen(window)
454    main_game_screen = MainGameScreen(window)
455    game_state = 'start_screen'
456    main_game_screen.set_health(100)
457    main_game_screen.set_money(500)
458    while True:
459        if game_state == 'start_screen':
460            start_screen.render()
461            if start_screen.check_for_click():
462                game_state = 'main_game'
463
464        elif game_state == 'main_game':
465            main_game_screen.render()
466            main_game_screen.check_for_click()
467            for event in pygame.event.get():
468                if event.type == pygame.QUIT:
469                    pygame.quit()
470                    sys.exit()
471
472        fpsClock.tick(FPS)