From bbe3c76a5de1f7d8922b55ad1c5a7b24047b0767 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 20:58:32 -0400 Subject: [PATCH 01/12] REFACTOR: removed no-member false positives --- planetoids/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/planetoids/main.py b/planetoids/main.py index 09671e5..f72bfe5 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -1,3 +1,5 @@ +# pylint: disable=no-member + import os import pygame From 36249fa29152f640abc299924decee83bb8e0eb1 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:15:39 -0400 Subject: [PATCH 02/12] REFACTOR: moved controls into separate function --- planetoids/main.py | 60 +++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index f72bfe5..8f3b3fc 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -1,4 +1,6 @@ -# pylint: disable=no-member +"""Main entry point for the game""" + +# pylint: disable=no-member,invalid-name import os @@ -16,19 +18,19 @@ dotenv.load_dotenv() DEBUG_MODE = os.getenv("DEBUG", "False").lower() in ("true", "1") -def main(): +def main() -> None: + """Main entry point for the game""" logger.info("Game start") pygame.init() settings = Settings() fullscreen = settings.get("fullscreen_enabled") - # fullscreen = False game_start = True while True: # Main game loop that allows restarting pygame.mouse.set_visible(False) - # ✅ Apply Fullscreen or Windowed Mode + # Apply Fullscreen or Windowed Mode screen_mode = pygame.FULLSCREEN if settings.get("fullscreen_enabled") else 0 if fullscreen: screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.FULLSCREEN) @@ -54,9 +56,8 @@ def main(): game_state = GameState(screen, settings, clock) game_state.spawn_asteroids(10) - # ✅ Display controls overlay for first few seconds + # Display controls overlay for first few seconds show_controls_timer = 5 # Show for 3 seconds - font = pygame.font.Font(get_font_path(), 36) running = True while running: @@ -77,17 +78,12 @@ def main(): # Draw everything game_state.draw_all(screen) - if show_controls_timer > 0: - font_size = {"minimum":36, "medium": 48, "maximum": 64}.get(settings.get("pixelation"), 36) - - controls_font = pygame.font.Font(get_font_path(), font_size) - - _draw_text(screen, "CONTROLS:", config.WIDTH // 2 - 80, config.HEIGHT // 3 + 180, config.YELLOW, controls_font) - _draw_text(screen, "Arrow keys - Movement", config.WIDTH // 2 - 50, config.HEIGHT // 3 + 220, config.GREEN, controls_font) - _draw_text(screen, "SPACE - Shoot", config.WIDTH // 2 - 50, config.HEIGHT // 3 + 260, config.GREEN, controls_font) - _draw_text(screen, "P - Pause", config.WIDTH // 2 - 50, config.HEIGHT // 3 + 300, config.GREEN, controls_font) - - show_controls_timer -= dt # Decrease timer + show_controls_timer, dt =_show_controls( + show_controls_timer, + settings, + screen, + dt + ) if settings.get("crt_enabled"): crt_effect.apply_crt_effect( @@ -106,6 +102,36 @@ def main(): if restart_game: running = False # Exit game loop, return to start menu +def _show_controls(show_controls_timer, settings, screen, dt): + if show_controls_timer > 0: + font_size = { + "minimum":36, + "medium": 48, + "maximum": 64 + }.get(settings.get("pixelation"), 36) + + controls_font = pygame.font.Font(get_font_path(), font_size) + + controls = ( + ("CONTROLS:", -80, 180, config.YELLOW), + ("Arrow keys - Movement", -50, 220, config.GREEN), + ("SPACE - Shoot", -50, 260, config.GREEN), + ("P - Pause", -50, 300, config.GREEN) + ) + half_width = config.WIDTH // 2 + third_height = config.HEIGHT // 3 + for control in controls: + _draw_text( + screen, + control[0], + half_width - control[1], + third_height + control[2], + control[3], + controls_font + ) + show_controls_timer -= dt # Decrease timer + return show_controls_timer, dt + def _event_handler(game_state): """Handle key input events""" for event in pygame.event.get(): From b592eddfbeb1cc2f62247b46d93728d195fb292c Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:18:24 -0400 Subject: [PATCH 03/12] REFACTOR: removed unnecessary arguments --- planetoids/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 8f3b3fc..da7949b 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -78,7 +78,7 @@ def main() -> None: # Draw everything game_state.draw_all(screen) - show_controls_timer, dt =_show_controls( + show_controls_timer =_show_controls( show_controls_timer, settings, screen, @@ -102,7 +102,8 @@ def main() -> None: if restart_game: running = False # Exit game loop, return to start menu -def _show_controls(show_controls_timer, settings, screen, dt): +def _show_controls(show_controls_timer, settings, screen, dt) -> float: + """Return show_controls_timer and draws controls to the screen""" if show_controls_timer > 0: font_size = { "minimum":36, @@ -130,7 +131,7 @@ def _show_controls(show_controls_timer, settings, screen, dt): controls_font ) show_controls_timer -= dt # Decrease timer - return show_controls_timer, dt + return show_controls_timer def _event_handler(game_state): """Handle key input events""" From c6bc993f2d9907d7b3c959edeed433486a31aff4 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:20:19 -0400 Subject: [PATCH 04/12] REFACTOR: moved CRT effects into priv func _draw_crt_effects --- planetoids/main.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index da7949b..55e87c0 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -85,12 +85,7 @@ def main() -> None: dt ) - if settings.get("crt_enabled"): - crt_effect.apply_crt_effect( - screen, - intensity=settings.get("glitch_intensity"), - pixelation=settings.get("pixelation") - ) + _draw_crt_effects(settings, screen) pygame.display.flip() # Check for Game Over condition @@ -102,6 +97,15 @@ def main() -> None: if restart_game: running = False # Exit game loop, return to start menu +def _draw_crt_effects(settings, screen) -> None: + """Draw CRT effects if enabled""" + if settings.get("crt_enabled"): + crt_effect.apply_crt_effect( + screen, + intensity=settings.get("glitch_intensity"), + pixelation=settings.get("pixelation") + ) + def _show_controls(show_controls_timer, settings, screen, dt) -> float: """Return show_controls_timer and draws controls to the screen""" if show_controls_timer > 0: From ad7a58dc3b8205ef28f601ec2b9f6d904e7ff10c Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:29:37 -0400 Subject: [PATCH 05/12] REFACTOR: moved coords into function --- planetoids/main.py | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 55e87c0..4ea639a 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -10,8 +10,7 @@ from planetoids.effects import crt_effect from planetoids.core.config import config from planetoids.core.game_state import GameState -from planetoids.core.settings import Settings -from planetoids.core.settings import get_font_path +from planetoids.core.settings import Settings, get_font_path from planetoids.core.logger import logger from planetoids.ui import IntroAnimation, GameOver, StartMenu @@ -24,20 +23,12 @@ def main() -> None: pygame.init() settings = Settings() - fullscreen = settings.get("fullscreen_enabled") game_start = True while True: # Main game loop that allows restarting pygame.mouse.set_visible(False) - # Apply Fullscreen or Windowed Mode - screen_mode = pygame.FULLSCREEN if settings.get("fullscreen_enabled") else 0 - if fullscreen: - screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.FULLSCREEN) - # screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.RESIZABLE) - else: - fixed_size = (960, 540) # Fixed window size - screen = pygame.display.set_mode(fixed_size, pygame.RESIZABLE) + screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.FULLSCREEN) pygame.display.set_caption("Planetoids") clock = pygame.time.Clock() @@ -109,14 +100,6 @@ def _draw_crt_effects(settings, screen) -> None: def _show_controls(show_controls_timer, settings, screen, dt) -> float: """Return show_controls_timer and draws controls to the screen""" if show_controls_timer > 0: - font_size = { - "minimum":36, - "medium": 48, - "maximum": 64 - }.get(settings.get("pixelation"), 36) - - controls_font = pygame.font.Font(get_font_path(), font_size) - controls = ( ("CONTROLS:", -80, 180, config.YELLOW), ("Arrow keys - Movement", -50, 220, config.GREEN), @@ -126,13 +109,13 @@ def _show_controls(show_controls_timer, settings, screen, dt) -> float: half_width = config.WIDTH // 2 third_height = config.HEIGHT // 3 for control in controls: + coords = (half_width - control[1], third_height + control[2]) _draw_text( screen, control[0], - half_width - control[1], - third_height + control[2], - control[3], - controls_font + coords, + settings, + control[3] ) show_controls_timer -= dt # Decrease timer return show_controls_timer @@ -151,12 +134,16 @@ def _event_handler(game_state): pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.RESIZABLE) game_state.handle_powerup_expiration(event) -def _draw_text(screen, text, x, y, color=config.WHITE, font=None): +def _draw_text(screen, text, coords, settings, color=config.WHITE): """Helper function to render sharp, readable text.""" - if font is None: - font = pygame.font.Font(None, 36) # Default font if none provided + font_size = { + "minimum":36, + "medium": 48, + "maximum": 64 + }.get(settings.get("pixelation"), 36) + font = pygame.font.Font(get_font_path(), font_size) rendered_text = font.render(text, True, color) - screen.blit(rendered_text, (x, y)) + screen.blit(rendered_text, coords) if __name__ == "__main__": main() From 6312ac9cb3c7fea92ccc2b0b824e50f71b50f94c Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:33:35 -0400 Subject: [PATCH 06/12] REFACTOR: mvoed game over code into separate function --- planetoids/main.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 4ea639a..774704d 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -79,14 +79,18 @@ def main() -> None: _draw_crt_effects(settings, screen) pygame.display.flip() - # Check for Game Over condition - if game_state.life.lives <= 0: - game_state.score.maybe_save_high_score() - game_over_screen = GameOver(game_state, settings) - restart_game = game_over_screen.game_over(screen, dt) - - if restart_game: - running = False # Exit game loop, return to start menu + running = _check_for_game_over(game_state, settings, screen, dt) + +def _check_for_game_over(game_state, settings, screen, dt) -> bool: + """Return Boolean check for game running""" + if game_state.life.lives <= 0: + game_state.score.maybe_save_high_score() + game_over_screen = GameOver(game_state, settings) + restart_game = game_over_screen.game_over(screen, dt) + + if restart_game: + running = False # Exit game loop, return to start menu + return running def _draw_crt_effects(settings, screen) -> None: """Draw CRT effects if enabled""" From 62224bfcadc859e6ee7a5b1111595c9401c06085 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:38:39 -0400 Subject: [PATCH 07/12] ADD: added type hints to funcs --- planetoids/main.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 774704d..0ae11d0 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -29,6 +29,7 @@ def main() -> None: pygame.mouse.set_visible(False) screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.FULLSCREEN) + print(type(screen)) pygame.display.set_caption("Planetoids") clock = pygame.time.Clock() @@ -81,7 +82,10 @@ def main() -> None: running = _check_for_game_over(game_state, settings, screen, dt) -def _check_for_game_over(game_state, settings, screen, dt) -> bool: +def _check_for_game_over( + game_state: GameState, settings: Settings, + screen: pygame.Surface, dt: float + ) -> bool: """Return Boolean check for game running""" if game_state.life.lives <= 0: game_state.score.maybe_save_high_score() @@ -92,7 +96,7 @@ def _check_for_game_over(game_state, settings, screen, dt) -> bool: running = False # Exit game loop, return to start menu return running -def _draw_crt_effects(settings, screen) -> None: +def _draw_crt_effects(settings: Settings, screen: pygame.Surface) -> None: """Draw CRT effects if enabled""" if settings.get("crt_enabled"): crt_effect.apply_crt_effect( @@ -101,7 +105,10 @@ def _draw_crt_effects(settings, screen) -> None: pixelation=settings.get("pixelation") ) -def _show_controls(show_controls_timer, settings, screen, dt) -> float: +def _show_controls( + show_controls_timer: float, settings: Settings, + screen: pygame.Surface, dt: float + ) -> float: """Return show_controls_timer and draws controls to the screen""" if show_controls_timer > 0: controls = ( From c18171b88c9a104cf3bdeee44ab5c7923d9ebf59 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:40:21 -0400 Subject: [PATCH 08/12] REFACTOR: add type hints for _draw_text --- planetoids/main.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 0ae11d0..418cb01 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -3,6 +3,7 @@ # pylint: disable=no-member,invalid-name import os +from typing import Tuple import pygame import dotenv @@ -83,7 +84,7 @@ def main() -> None: running = _check_for_game_over(game_state, settings, screen, dt) def _check_for_game_over( - game_state: GameState, settings: Settings, + game_state: GameState, settings: Settings, screen: pygame.Surface, dt: float ) -> bool: """Return Boolean check for game running""" @@ -106,7 +107,7 @@ def _draw_crt_effects(settings: Settings, screen: pygame.Surface) -> None: ) def _show_controls( - show_controls_timer: float, settings: Settings, + show_controls_timer: float, settings: Settings, screen: pygame.Surface, dt: float ) -> float: """Return show_controls_timer and draws controls to the screen""" @@ -131,7 +132,7 @@ def _show_controls( show_controls_timer -= dt # Decrease timer return show_controls_timer -def _event_handler(game_state): +def _event_handler(game_state: GameState): """Handle key input events""" for event in pygame.event.get(): if event.type == pygame.KEYDOWN: @@ -145,7 +146,10 @@ def _event_handler(game_state): pygame.display.set_mode((config.WIDTH, config.HEIGHT), pygame.RESIZABLE) game_state.handle_powerup_expiration(event) -def _draw_text(screen, text, coords, settings, color=config.WHITE): +def _draw_text( + screen: pygame.Surface, text: str, coords: Tuple[int, int], + settings: Settings, color: Tuple[int, int, int]=config.WHITE + ): """Helper function to render sharp, readable text.""" font_size = { "minimum":36, From 1222140e5a1629237086c66bb7757e067343f9d7 Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:41:05 -0400 Subject: [PATCH 09/12] REFACTOR: added return types --- planetoids/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planetoids/main.py b/planetoids/main.py index 418cb01..879b08b 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -132,7 +132,7 @@ def _show_controls( show_controls_timer -= dt # Decrease timer return show_controls_timer -def _event_handler(game_state: GameState): +def _event_handler(game_state: GameState) -> None: """Handle key input events""" for event in pygame.event.get(): if event.type == pygame.KEYDOWN: @@ -149,7 +149,7 @@ def _event_handler(game_state: GameState): def _draw_text( screen: pygame.Surface, text: str, coords: Tuple[int, int], settings: Settings, color: Tuple[int, int, int]=config.WHITE - ): + ) -> None: """Helper function to render sharp, readable text.""" font_size = { "minimum":36, From 8fe28b1cd3ccb4b0dcfbbe1f8868245b4f4f6c8f Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:42:44 -0400 Subject: [PATCH 10/12] REFACTOR: added type hints to methods --- planetoids/core/config.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/planetoids/core/config.py b/planetoids/core/config.py index 79cb291..eafac8a 100644 --- a/planetoids/core/config.py +++ b/planetoids/core/config.py @@ -2,7 +2,8 @@ import pygame import importlib.resources -def get_local_version(): +def get_local_version() -> str: + """Return version number from version.txt""" with importlib.resources.open_text("planetoids.core", "version.txt") as f: return f.read().strip() @@ -27,12 +28,12 @@ class Config: VERSION = get_local_version() - def __init__(self): + def __init__(self) -> None: """Initialize screen dimensions and scaled properties.""" self._initialize_screen_size() self._scale_properties() - def _initialize_screen_size(self): + def _initialize_screen_size(self) -> None: """Ensure Pygame is initialized before fetching display info.""" if not pygame.get_init(): pygame.init() @@ -45,15 +46,7 @@ def _initialize_screen_size(self): self.WIDTH = int(self.SCREEN_WIDTH * (self.BASE_WIDTH / self.SCREEN_WIDTH)) self.HEIGHT = int(self.SCREEN_HEIGHT * (self.BASE_HEIGHT / self.SCREEN_HEIGHT)) - # self.WIDTH, self.HEIGHT = 960, 540 - - def _scale_properties(self): - """Update any game elements that depend on screen size.""" - self.PLANET_X = random.randint(int(200 * (self.WIDTH / self.BASE_WIDTH)), int(self.WIDTH - 200 * (self.WIDTH / self.BASE_WIDTH))) - self.PLANET_Y = random.randint(int(200 * (self.HEIGHT / self.BASE_HEIGHT)), int(self.HEIGHT - 200 * (self.HEIGHT / self.BASE_HEIGHT))) - self.PLANET_RADIUS = random.randint(int(50 * (self.WIDTH / self.BASE_WIDTH)), int(150 * (self.WIDTH / self.BASE_WIDTH))) - - def update_screen_size(self): + def update_screen_size(self) -> None: """Allows dynamic screen resizing and updates dependent properties.""" self._initialize_screen_size() self._scale_properties() From 555ef1863aca28333851a6bf29f1af81b59407db Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 21:56:46 -0400 Subject: [PATCH 11/12] REFACTOR: refactored config.py to fit pylint --- .pylintrc | 633 ++++++++++++++++++++++++++++++++++++++ planetoids/core/config.py | 8 +- planetoids/main.py | 7 +- 3 files changed, 642 insertions(+), 6 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..379bf90 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,633 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + no-member, + invalid-name, + too-few-public-methods + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/planetoids/core/config.py b/planetoids/core/config.py index eafac8a..158614c 100644 --- a/planetoids/core/config.py +++ b/planetoids/core/config.py @@ -1,7 +1,9 @@ -import random -import pygame +"""Store central configurations""" + import importlib.resources +import pygame + def get_local_version() -> str: """Return version number from version.txt""" with importlib.resources.open_text("planetoids.core", "version.txt") as f: @@ -31,7 +33,6 @@ class Config: def __init__(self) -> None: """Initialize screen dimensions and scaled properties.""" self._initialize_screen_size() - self._scale_properties() def _initialize_screen_size(self) -> None: """Ensure Pygame is initialized before fetching display info.""" @@ -49,7 +50,6 @@ def _initialize_screen_size(self) -> None: def update_screen_size(self) -> None: """Allows dynamic screen resizing and updates dependent properties.""" self._initialize_screen_size() - self._scale_properties() # Global instance config = Config() diff --git a/planetoids/main.py b/planetoids/main.py index 879b08b..f10d2b0 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -81,11 +81,14 @@ def main() -> None: _draw_crt_effects(settings, screen) pygame.display.flip() - running = _check_for_game_over(game_state, settings, screen, dt) + running = _check_for_game_over( + game_state, settings, screen, dt, running + ) def _check_for_game_over( game_state: GameState, settings: Settings, - screen: pygame.Surface, dt: float + screen: pygame.Surface, dt: float, + running: bool ) -> bool: """Return Boolean check for game running""" if game_state.life.lives <= 0: From 1341288f19ed927217eb44ca1f0b85a4433944ee Mon Sep 17 00:00:00 2001 From: Christopher Greening Date: Tue, 25 Mar 2025 22:30:03 -0400 Subject: [PATCH 12/12] REFACTOR: refactored game state for pylint --- .pylintrc | 3 +- planetoids/core/game_state.py | 178 ++++++++++++++++++++-------------- planetoids/entities/player.py | 6 +- planetoids/main.py | 4 +- 4 files changed, 109 insertions(+), 82 deletions(-) diff --git a/.pylintrc b/.pylintrc index 379bf90..84a36d8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -430,7 +430,8 @@ disable=raw-checker-failed, use-symbolic-message-instead, no-member, invalid-name, - too-few-public-methods + too-few-public-methods, + too-many-instance-attributes # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/planetoids/core/game_state.py b/planetoids/core/game_state.py index c21dcbc..9672e37 100644 --- a/planetoids/core/game_state.py +++ b/planetoids/core/game_state.py @@ -1,9 +1,17 @@ +"""Contains the central game state manager""" + import random +from typing import List import pygame -from planetoids.entities.asteroid import Asteroid, ExplodingAsteroid, ShieldAsteroid -from planetoids.entities.powerups import PowerUp, TemporalSlowdownPowerUp, RicochetShotPowerUp, InvincibilityPowerUp, TrishotPowerUp, QuadShotPowerUp +from planetoids.entities.asteroid import ( + Asteroid, ExplodingAsteroid, ShieldAsteroid +) +from planetoids.entities.powerups import ( + PowerUp, TemporalSlowdownPowerUp, RicochetShotPowerUp, + InvincibilityPowerUp, TrishotPowerUp, QuadShotPowerUp +) from planetoids.entities.bullet import Bullet from planetoids.entities.player import Player from planetoids.ui.pause_menu import PauseMenu @@ -12,12 +20,15 @@ from planetoids.core.life import Life from planetoids.core.config import config from planetoids.core.logger import logger -from planetoids.core.settings import get_font_path +from planetoids.core.settings import Settings from planetoids.entities.score_popup import ScorePopup class GameState: - def __init__(self, screen, settings, clock): - """GameState manages all game objects, including the player and asteroids.""" + """GameState manages all game objects, including the player and asteroids.""" + def __init__( + self, screen: pygame.Surface, settings: Settings, + clock: pygame.time.Clock + ): self.screen = screen self.settings = settings self.clock = clock @@ -43,24 +54,18 @@ def __init__(self, screen, settings, clock): self.start_time = pygame.time.get_ticks() @property - def font(self): + def font(self) -> pygame.font.Font: + """Returns font adjusted due to pixelation intensity""" return pygame.font.Font( self.settings.FONT_PATH, {"minimum":36, "medium": 48, "maximum": 64}.get(self.settings.get("pixelation"), 36) ) - @property - def score_font(self): - return pygame.font.Font( - self.settings.FONT_PATH, - {"minimum":36, "medium": 48, "maximum": 60}.get(self.settings.get("pixelation"), 36) - ) - - def update_dt(self, dt): + def update_dt(self, dt: float) -> None: """Updates dt each frame to maintain FPS independence.""" self.dt = dt - def toggle_pause(self): + def toggle_pause(self) -> None: """Toggles pause and shows the pause screen.""" if not self.paused: self.paused = True @@ -68,20 +73,20 @@ def toggle_pause(self): self.paused = False self.dt = 0 - def spawn_powerup(self, x, y): + def spawn_powerup(self, x: int, y: int) -> None: """Spawns a power-up with a probability, allowing multiple to exist at once.""" if len(self.powerups) < 3 and random.random() < .1: powerup_class = PowerUp.get_powerup_type() self.powerups.append(powerup_class(self, x, y)) - def check_for_clear_map(self): + def check_for_clear_map(self) -> None: """Checks if all asteroids are destroyed and resets the map if so.""" if not self.asteroids: self.level.increment_level() self.spawn_asteroids(5 + self.level.get_level() * 2) self.player.set_invincibility() - def spawn_asteroids(self, count=5): + def spawn_asteroids(self, count: int=5) -> None: """Spawn initial asteroids using weighted selection from asteroid types.""" for _ in range(count): asteroid_type = Asteroid.get_asteroid_type() @@ -89,8 +94,9 @@ def spawn_asteroids(self, count=5): self.asteroids.append(asteroid_type(self)) logger.info("{count} asteroids spawned") - def update_all(self, keys, dt): - """Update all game objects, including power-ups, bullets, asteroids, and explosions, using delta time (dt).""" + def update_all(self, keys, dt: float) -> None: + """Update all game objects, including power-ups, bullets, asteroids, + and explosions, using delta time (dt).""" self.player.slowed_by_ice = False # Reset ice slowdown before checking @@ -101,7 +107,7 @@ def update_all(self, keys, dt): self.check_powerup_collisions() if self.player.explosion_timer > 0: - self.player._update_explosion() + self.player.update_explosion() for debris in self.debris: debris.update(dt) @@ -110,11 +116,7 @@ def update_all(self, keys, dt): self.debris = [d for d in self.debris if d.lifetime > 0] self.score.update_multiplier(dt) - # if not self.player.slowed_by_ice: - # self.player.velocity_x = max(self.player.velocity_x, self.player.base_velocity_x * dt * 60) - # self.player.velocity_y = max(self.player.velocity_y, self.player.base_velocity_y * dt * 60) - - def _update_respawn(self, keys): + def _update_respawn(self, keys) -> None: """Handles player respawn countdown and resets the player when ready, using delta time.""" if self.respawn_timer > 0: @@ -127,14 +129,15 @@ def _update_respawn(self, keys): else: self.player.update(keys) - def _update_bullets(self): + def _update_bullets(self) -> None: """Updates bullets and removes expired ones using delta time.""" for bullet in self.bullets: bullet.update() self.bullets = [b for b in self.bullets if b.lifetime > self.dt * 60] - def _update_asteroids(self): - """Updates asteroids, handles explosion animations, and removes destroyed asteroids using delta time.""" + def _update_asteroids(self) -> None: + """Updates asteroids, handles explosion animations, and removes + destroyed asteroids using delta time.""" asteroids_to_remove = [] for asteroid in self.asteroids: @@ -148,38 +151,38 @@ def _update_asteroids(self): # Remove exploding asteroids after animation finishes self.asteroids = [a for a in self.asteroids if a not in asteroids_to_remove] - def _update_powerups(self): + def _update_powerups(self) -> None: """Updates power-ups and removes expired ones using delta time.""" for powerup in self.powerups: powerup.update() self.powerups = [p for p in self.powerups if not p.is_expired()] - def handle_powerup_expiration(self, event): + def handle_powerup_expiration(self, event: pygame.event.Event) -> None: """Handles expiration events for power-ups.""" if event.type == pygame.USEREVENT + 5: self.asteroid_slowdown_active = False - def _draw_bullets(self, screen): + def _draw_bullets(self, screen: pygame.Surface) -> None: for bullet in self.bullets: bullet.draw(screen) - def _draw_asteroids(self, screen): + def _draw_asteroids(self, screen: pygame.Surface) -> None: for asteroid in self.asteroids: asteroid.draw(screen) if isinstance(asteroid, ExplodingAsteroid) and asteroid.exploding: asteroid.draw_explosion(screen) - def _draw_powerups(self, screen): + def _draw_powerups(self, screen: pygame.Surface) -> None: for powerup in self.powerups: powerup.draw(screen) - def _draw_player(self, screen): + def _draw_player(self, screen: pygame.Surface) -> None: if self.player.explosion_timer > 0: - self.player._draw_explosion(screen) + self.player.draw_explosion(screen) else: self.player.draw(screen) - def draw_all(self, screen): + def draw_all(self, screen: pygame.Surface) -> None: """Draw all game objects, including power-ups.""" self._draw_player(screen) self._draw_asteroids(screen) @@ -194,15 +197,15 @@ def draw_all(self, screen): self._asteroid_slowdown_active(screen) - def _draw_debris(self, screen): + def _draw_debris(self, screen: pygame.Surface) -> None: for debris in self.debris: debris.draw(screen) - def _draw_score_popups(self, screen): + def _draw_score_popups(self, screen: pygame.Surface) -> None: for popup in self.score_popups: - popup.draw(screen, self.score_font) + popup.draw(screen, self.font) - def _asteroid_slowdown_active(self, screen): + def _asteroid_slowdown_active(self, screen: pygame.Surface) -> None: # Draw slowdown visual effect if self.asteroid_slowdown_active: # Calculate elapsed time since slowdown started @@ -217,15 +220,16 @@ def _asteroid_slowdown_active(self, screen): overlay.fill((0, 150, 255, fade_intensity)) # Softer cyan overlay screen.blit(overlay, (0, 0)) - def check_powerup_collisions(self): + def check_powerup_collisions(self) -> None: """Checks if the player collects a power-up.""" for powerup in self.powerups[:]: - if self.calculate_collision_distance(self.player, powerup) < powerup.radius + self.player.size: + combined_size = powerup.radius + self.player.size + if self.calculate_collision_distance(self.player, powerup) < combined_size: print(f"Player collected {powerup.__class__.__name__}!") # Debug self.apply_powerup(powerup) # Pass powerup instance self.powerups.remove(powerup) # Remove after collection - def apply_powerup(self, powerup): + def apply_powerup(self, powerup: PowerUp) -> None: """Applies the collected power-up effect.""" if isinstance(powerup, TemporalSlowdownPowerUp): self.slowdown_timer = pygame.time.get_ticks() + 5000 # 5 seconds @@ -234,7 +238,7 @@ def apply_powerup(self, powerup): else: powerup.apply(self.player) # Call the power-up's apply() method - def _handle_bullet_asteroid_collision(self): + def _handle_bullet_asteroid_collision(self) -> None: """Handles collisions between bullets and asteroids.""" bullets_to_remove = [] @@ -244,17 +248,27 @@ def _handle_bullet_asteroid_collision(self): for bullet in self.bullets[:]: # Iterate over a copy for asteroid in self.asteroids[:]: # Iterate over a copy if self._is_bullet_asteroid_collision(bullet, asteroid): - self._process_bullet_hit(bullet, asteroid, bullets_to_remove, asteroids_to_remove, new_asteroids) + self._process_bullet_hit( + bullet, asteroid, bullets_to_remove, + asteroids_to_remove, new_asteroids + ) self._remove_destroyed_asteroids(asteroids_to_remove) self.asteroids.extend(new_asteroids) # Add newly split asteroids - self.bullets = [b for b in self.bullets if b not in bullets_to_remove] # Remove used bullets + self.bullets = [b for b in self.bullets if b not in bullets_to_remove] - def _is_bullet_asteroid_collision(self, bullet, asteroid): + def _is_bullet_asteroid_collision( + self, bullet: Bullet, asteroid: Asteroid + ) -> bool: """Returns True if a bullet collides with an asteroid.""" return self.calculate_collision_distance(bullet, asteroid) < asteroid.size - def _process_bullet_hit(self, bullet, asteroid, bullets_to_remove, asteroids_to_remove, new_asteroids): + #pylint: disable=too-many-arguments + def _process_bullet_hit( + self, bullet: Bullet, asteroid: Asteroid, + bullets_to_remove: List[Bullet], + asteroids_to_remove: List[Asteroid], new_asteroids: List[Asteroid] + ) -> None: """Handles the effects of a bullet hitting an asteroid.""" self._apply_bullet_effects(bullet, asteroid) @@ -270,7 +284,7 @@ def _process_bullet_hit(self, bullet, asteroid, bullets_to_remove, asteroids_to_ self._handle_powerup_spawn(asteroid) self._handle_ricochet_bullet(bullet, asteroid) - def _apply_bullet_effects(self, bullet, asteroid): + def _apply_bullet_effects(self, bullet: Bullet, asteroid: Asteroid): """Applies effects when a bullet hits an asteroid.""" bullet.on_hit_asteroid(asteroid) self.score.update_score(asteroid) @@ -282,7 +296,10 @@ def _apply_bullet_effects(self, bullet, asteroid): score_value = 300 * self.score.multiplier self.score_popups.append(ScorePopup(asteroid.x, asteroid.y, score_value)) # Example score - def _handle_asteroid_destruction(self, asteroid, asteroids_to_remove, new_asteroids): + def _handle_asteroid_destruction( + self, asteroid: Asteroid, + asteroids_to_remove: List[Asteroid], new_asteroids: List[Asteroid] + ): """Determines how an asteroid is destroyed or split.""" if isinstance(asteroid, ExplodingAsteroid): self._handle_exploding_asteroid(asteroid, asteroids_to_remove, new_asteroids) @@ -290,16 +307,21 @@ def _handle_asteroid_destruction(self, asteroid, asteroids_to_remove, new_astero asteroids_to_remove.append(asteroid) # Remove normal asteroids new_asteroids.extend(asteroid.split()) # Add split asteroids - def _handle_powerup_spawn(self, asteroid): + def _handle_powerup_spawn(self, asteroid: Asteroid) -> None: """Spawns a power-up at the asteroid’s location if conditions are met.""" self.spawn_powerup(asteroid.x, asteroid.y) - def _handle_ricochet_bullet(self, bullet, asteroid): + def _handle_ricochet_bullet( + self, bullet: Bullet, asteroid: Asteroid + ) -> None: """Creates a ricochet bullet if the player has ricochet active.""" if self.player.ricochet_active and not bullet.ricochet: self._spawn_ricochet_bullet(asteroid.x, asteroid.y) - def _handle_exploding_asteroid(self, asteroid, asteroids_to_remove, new_asteroids): + def _handle_exploding_asteroid( + self, asteroid: Asteroid, + asteroids_to_remove: List[Asteroid], new_asteroids: List[Asteroid] + ) -> None: """Triggers an asteroid explosion and manages affected asteroids.""" if not asteroid.exploding: # Start explosion if not already started @@ -311,75 +333,80 @@ def _handle_exploding_asteroid(self, asteroid, asteroids_to_remove, new_asteroid asteroids_to_remove.append(exploded_asteroid) new_asteroids.extend(exploded_asteroid.split()) - def _spawn_ricochet_bullet(self, x, y): + def _spawn_ricochet_bullet(self, x: int, y: int) -> None: """Creates and adds a ricochet bullet.""" new_angle = random.randint(0, 360) # Random ricochet angle - ricochet_bullet = Bullet(self, x, y, new_angle, ricochet=True, color=RicochetShotPowerUp.color, radius=14) + ricochet_bullet = Bullet( + self, x, y, new_angle, ricochet=True, + color=RicochetShotPowerUp.color, radius=14 + ) self.bullets.append(ricochet_bullet) - def _remove_destroyed_asteroids(self, asteroids_to_remove): + def _remove_destroyed_asteroids(self, asteroids_to_remove: List[Asteroid]) -> None: """Removes non-exploding asteroids that were destroyed.""" self.asteroids = [ a for a in self.asteroids if a not in asteroids_to_remove or (isinstance(a, ExplodingAsteroid) and a.exploding) ] - def _handle_player_asteroid_collision(self, screen): - """Handles collisions between the player and asteroids, triggering the explosion before respawn.""" + def _handle_player_asteroid_collision(self) -> None: + """Handles collisions between the player and asteroids, triggering the + explosion before respawn.""" if self.respawn_timer > 0: return # Player is currently respawning, ignore collisions for asteroid in self.asteroids: if self._is_collision(self.player, asteroid): - self._trigger_player_explosion(screen) + self._trigger_player_explosion() break # Stop checking after first collision def _is_collision(self, entity1, entity2): """Returns True if two entities are colliding based on their distance.""" return self.calculate_collision_distance(entity1, entity2) < entity2.size - def _trigger_player_explosion(self, screen): + def _trigger_player_explosion(self) -> None: """Handles the player explosion animation and sets up respawn or game over.""" if self.player.invincible: return # Skip if player is currently invincible if self.player.shield_active: self.player.take_damage() return # Shield absorbs the hit - self.player._generate_explosion() # Trigger explosion animation + self.player.generate_explosion() # Trigger explosion animation self.respawn_timer = 30 # Delay respawn for explosion duration self.life.decrement() - def check_for_collisions(self, screen): + def check_for_collisions(self) -> None: """Check for bullet-asteroid and player-asteroid collisions.""" self._handle_bullet_asteroid_collision() - self._handle_player_asteroid_collision(screen) + self._handle_player_asteroid_collision() - def handle_player_collision(self, screen): - """Handles player collision logic, including shield effects, death animation, and respawn/game over.""" + def handle_player_collision(self, screen: pygame.Surface) -> None: + """Handles player collision logic, including shield effects, + death animation, and respawn/game over.""" if self._player_is_invincible(): return if self._player_has_shield(): return self._process_player_death(screen) - def _player_is_invincible(self): + def _player_is_invincible(self) -> bool: """Checks if the player is invincible after respawn.""" return self.player.invincible - def _player_has_shield(self): + def _player_has_shield(self) -> bool: """Checks if the player has an active shield and applies damage if so.""" if self.player.shield_active: self.player.take_damage() return True return False - def _process_player_death(self, screen): + def _process_player_death(self, screen: pygame.Surface) -> None: """Handles player death animation, life count, and respawn or game over.""" self.player.death_animation(screen) # Play death effect self.life.decrement() if self.life.get_lives() > 0: self.respawn_player() - def respawn_player(self): + def respawn_player(self) -> None: """Respawns the player at the center after the timer expires.""" if self.respawn_timer > 0: return @@ -388,8 +415,9 @@ def respawn_player(self): self.player.invincible = True pygame.time.set_timer(pygame.USEREVENT + 2, 2000) # 2 sec invincibility - def _draw_powerup_timer(self, screen): - """Draws a shrinking timer bar for active powerups with their corresponding colors and labels.""" + def _draw_powerup_timer(self, screen: pygame.Surface) -> None: + """Draws a shrinking timer bar for active powerups with their + corresponding colors and labels.""" y_offset = {"minimum": 75, "medium": 85, "maximum": 100}.get( self.settings.get("pixelation"), 85 ) @@ -424,8 +452,8 @@ def _draw_powerup_timer(self, screen): break - def calculate_collision_distance(self, obj1, obj2): - """Calculates distance between two game objects.""" + def calculate_collision_distance(self, obj1, obj2) -> None: + """Calculates Euclidean distance between two game objects.""" dx = obj1.x - obj2.x dy = obj1.y - obj2.y - return (dx ** 2 + dy ** 2) ** 0.5 # Euclidean distance formula + return (dx ** 2 + dy ** 2) ** 0.5 diff --git a/planetoids/entities/player.py b/planetoids/entities/player.py index 3fc6c65..8376167 100644 --- a/planetoids/entities/player.py +++ b/planetoids/entities/player.py @@ -305,7 +305,7 @@ def _draw_thruster(self, screen, angle_rad, left, right): ) pygame.draw.polygon(screen, config.ORANGE, [thruster_tip, left, right]) - def _generate_explosion(self): + def generate_explosion(self): """Initializes the explosion effect when the player dies.""" self.explosion_particles = [] # Temporary explosion effect self.fragments = [] # Pieces of the ship @@ -337,7 +337,7 @@ def _generate_explosion(self): logger.info("Player explosion generated") - def _update_explosion(self): + def update_explosion(self): """Updates explosion animation frame by frame using delta time.""" if self.explosion_timer > 0: self.explosion_timer -= self.game_state.dt * 60 @@ -370,7 +370,7 @@ def _update_fragments(self, fragments): fragment["pos"][1] + fragment["vel"][1] * self.game_state.dt * 60 ) - def _draw_explosion(self, screen): + def draw_explosion(self, screen): """Draws the explosion effect and ship fragments.""" if self.explosion_timer > 0: for fragment in self.fragments: diff --git a/planetoids/main.py b/planetoids/main.py index f10d2b0..308e889 100644 --- a/planetoids/main.py +++ b/planetoids/main.py @@ -1,7 +1,5 @@ """Main entry point for the game""" -# pylint: disable=no-member,invalid-name - import os from typing import Tuple @@ -66,7 +64,7 @@ def main() -> None: keys = pygame.key.get_pressed() game_state.update_all(keys, dt) game_state.check_for_clear_map() - game_state.check_for_collisions(screen) + game_state.check_for_collisions() # Draw everything game_state.draw_all(screen)