From e769f240770b7c7c27ad28fd7270a006bd4f4f76 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:55:51 +0200 Subject: [PATCH 1/8] Some optimitzations --- engine/build_tools.py | 24 +++------ engine/core/__init__.py | 28 +++++------ engine/core/image.py | 37 +++++--------- engine/saveload.py | 107 +++++++++++++++------------------------- 4 files changed, 70 insertions(+), 126 deletions(-) diff --git a/engine/build_tools.py b/engine/build_tools.py index 7571554..9d065c4 100644 --- a/engine/build_tools.py +++ b/engine/build_tools.py @@ -19,17 +19,14 @@ def build(directory: Path, ENGINE_DATA_PATH: str) -> None: - """ - Build the game + """Build the game. Args: directory (Path): Path to build the game to ENGINE_DATA_PATH (str): Path of the data directory """ - launch_game_script = None - - if not os.path.exists(directory): + if not directory.exists(): logger( f'Build directory "{str(directory.resolve())}" does not exist.', status=Status.WARNING ) @@ -39,19 +36,10 @@ def build(directory: Path, ENGINE_DATA_PATH: str) -> None: ) return - with open(resource_path("data/scripts/launch_game.py")) as f: - launch_game_script = f.read() - f.close() - - with open(os.path.join(directory, "run.py"), "w") as f: - f.write(launch_game_script) - f.close() + launch_game_script = Path(resource_path("data/scripts/launch_game.py")).read_text(encoding="utf-8") + (directory / "run.py").write_text(launch_game_script, encoding="utf-8") ignore = shutil.ignore_patterns("*.pyc", "__pycache__") - shutil.copytree( - engine_path, os.path.join(directory, "engine"), dirs_exist_ok=True, ignore=ignore - ) - shutil.copytree( - ENGINE_DATA_PATH, os.path.join(directory, "data"), dirs_exist_ok=True, ignore=ignore - ) + shutil.copytree(engine_path, directory / "engine", dirs_exist_ok=True, ignore=ignore) + shutil.copytree(Path(ENGINE_DATA_PATH), directory / "data", dirs_exist_ok=True, ignore=ignore) diff --git a/engine/core/__init__.py b/engine/core/__init__.py index 7e971e9..4f62b0a 100644 --- a/engine/core/__init__.py +++ b/engine/core/__init__.py @@ -259,7 +259,6 @@ def __init__(self, *, parent: "Game") -> None: self.game: "Game" = parent self.objects: list[Entity] = [] - self.no_entities: bool = True logger("Initialized scene") @@ -307,9 +306,6 @@ def add(self, obj: Entity) -> None: if not obj.did_init: obj.init() - if self.no_entities: - self.no_entities = False - def update(self, dt: float) -> None: """ Update all entities in the scene. @@ -318,9 +314,8 @@ def update(self, dt: float) -> None: dt (float): Time elapsed since last update """ - if not self.no_entities: - for obj in self.objects: - obj.update(dt) + for obj in self.objects: + obj.update(dt) def event(self, event: pygame.event.Event) -> None: """ @@ -330,9 +325,8 @@ def event(self, event: pygame.event.Event) -> None: event (pygame.event.Event): Event passed to each entity. """ - if not self.no_entities: - for obj in self.objects: - obj.event(event) + for obj in self.objects: + obj.event(event) def draw(self, surface: pygame.Surface) -> None: """ @@ -342,9 +336,8 @@ def draw(self, surface: pygame.Surface) -> None: surface (pygame.Surface): Surface to draw entities onto. """ - if not self.no_entities: - for obj in self.objects: - obj.draw(surface) + for obj in self.objects: + obj.draw(surface) def remove(self, obj: Entity) -> None: """ @@ -355,7 +348,6 @@ def remove(self, obj: Entity) -> None: """ self.objects.remove(obj) - self.no_entities = len(self.objects) == 0 class Game: @@ -514,15 +506,17 @@ def step(self, dt: float) -> None: dt (float): The time elapsed since the last step. """ + active_scene = self.scenes[self.current_scene] + for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False else: - self.scenes[self.current_scene].event(event) + active_scene.event(event) - self.scenes[self.current_scene].update(dt) + active_scene.update(dt) self.screen.fill(self._bg_color) - self.scenes[self.current_scene].draw(self.screen) + active_scene.draw(self.screen) pygame.display.flip() def run(self, fps: int = 60) -> None: diff --git a/engine/core/image.py b/engine/core/image.py index 05f86f4..a7e33eb 100644 --- a/engine/core/image.py +++ b/engine/core/image.py @@ -16,43 +16,30 @@ class EntityImage: """ surface: Optional[pygame.Surface] + _scaled_surface: Optional[pygame.Surface] + _scaled_size: tuple[int, int] | None def __init__(self, image_path: str) -> None: - """ - Initialize the EntityImage by loading the image at ``image_path``. - - Args: - image_path (str): The path to the image file. - """ - self.surface = None - + self._scaled_surface = None + self._scaled_size = None self.set_image(image_path) def set_image(self, image_path: str) -> None: - """ - Load ``image_path`` and store it as an alpha-enabled pygame surface. - - Args: - image_path (str): The path to the image file. - """ - assert pygame.get_init(), ( # nosec B101 "EntityImage: pygame must be initialized before loading images" ) self.surface = pygame.image.load(image_path).convert_alpha() + self._scaled_surface = None + self._scaled_size = None def draw(self, surface: pygame.Surface, rect: pygame.Rect) -> None: - """ - Draw the image scaled to ``rect`` onto ``surface``. - - Args: - surface (pygame.Surface): surface to draw onto - rect (pygame.Rect): rect to scale image to - """ - assert self.surface is not None, "EntityImage.surface was not initialized" # nosec B101 - scaled_image = pygame.transform.scale(self.surface, (rect.width, rect.height)) - surface.blit(scaled_image, (rect.x, rect.y)) + size = (rect.width, rect.height) + if self._scaled_surface is None or self._scaled_size != size: + self._scaled_surface = pygame.transform.scale(self.surface, size) + self._scaled_size = size + + surface.blit(self._scaled_surface, (rect.x, rect.y)) diff --git a/engine/saveload.py b/engine/saveload.py index d218d82..9a5ede2 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -18,91 +18,66 @@ def resource_path(relative: str) -> str: - """ - Convert a relative resource path into an absolute path. - - Args: - relative (str): Relative path to a resource. - - Returns: - str: Absolute path to the resource. - """ + """Convert a relative resource path into an absolute path.""" if hasattr(sys, "_MEIPASS"): return os.path.join(sys._MEIPASS, relative) # pyright: ignore[reportAttributeAccessIssue] - return os.path.join(os.path.abspath("."), relative) + return str(Path.cwd() / relative) def save_project(engine: Any) -> Optional[str]: - """ - Save the project as an absp file - - Args: - engine (Any): engine instance to extract the project information from + """Save the engine project to a .absp file.""" - Returns: - Optional[Any]: IO object of the file or None - """ + directory = filedialog.askdirectory() - dir = filedialog.askdirectory() + if directory is None: + return None - if dir and os.path.exists(dir): - gamefile = str( - Path(dir) / "game.absp", - ) + project_path = Path(directory) / "game.absp" + project_path.parent.mkdir(parents=True, exist_ok=True) - with open(gamefile, "w", encoding="utf-8") as f: - dump( - { - "name": engine.project_name, - "game": { - "dimensions": engine.game_dimensions, - "cursor_visible": engine.cursor_visible, - "fullscreen": engine.fullscreen, - }, - "entities": engine.entities, + with project_path.open("w", encoding="utf-8") as f: + dump( + { + "name": engine.project_name, + "game": { + "dimensions": engine.game_dimensions, + "cursor_visible": engine.cursor_visible, + "fullscreen": engine.fullscreen, }, - f, - ) - - messagebox.showinfo("Success", "Project saved successfully.") - - return gamefile + "entities": engine.entities, + }, + f, + indent=2, + ) - return None + messagebox.showinfo("Success", "Project saved successfully.") + return str(project_path) def load_project() -> Optional[list]: - """ - Ask the user to open an absp file, then return the contents - - Returns: - Optional[list]: file content - """ - - dir = filedialog.askdirectory() + """Ask the user for a project directory and return the loaded game data.""" - if dir and os.path.exists(dir): - gamefile = str(Path(dir) / "game.absp") + directory = filedialog.askdirectory() - if not os.path.exists(gamefile): - logger( - "game.absp file not found in selected directory. Creating.", - status=LoggerStatus.WARNING, - ) + if directory is None: + return None - with open(gamefile, "w", encoding="utf-8") as f: - f.write("{}") + project_path = Path(directory) / "game.absp" - return [{}, gamefile] - - if os.path.isdir(gamefile): - messagebox.showerror("Error", "game.absp project file is a directory.") - return None + if not project_path.exists(): + logger( + "game.absp file not found in selected directory. Creating.", + status=LoggerStatus.WARNING, + ) + project_path.write_text("{}", encoding="utf-8") + return [{}, str(project_path)] - with open(gamefile, "r") as f: - data: dict = load(f) + if project_path.is_dir(): + messagebox.showerror("Error", "game.absp project file is a directory.") + return None - return [data, gamefile] + with project_path.open("r", encoding="utf-8") as f: + data: dict = load(f) - return None + return [data, str(project_path)] From 79777d44f7cc5408aedf02cf3f2781a711f3fa89 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:06:42 +0200 Subject: [PATCH 2/8] fix almost all linter errors --- engine/build_tools.py | 2 -- engine/saveload.py | 58 +++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/engine/build_tools.py b/engine/build_tools.py index 9d065c4..ae0036f 100644 --- a/engine/build_tools.py +++ b/engine/build_tools.py @@ -5,8 +5,6 @@ Build and development utilities for the engine. """ -import os - import shutil from tkinter import messagebox diff --git a/engine/saveload.py b/engine/saveload.py index 9a5ede2..4d254d5 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -1,36 +1,49 @@ # Copyright (C) Natuworkguy # See the LICENSE file for GPLv3 -""" -Handles saving and loading of engine projects and data. -""" +"""Handles saving and loading of engine projects and data.""" -from tkinter import messagebox as messagebox -from tkinter import filedialog as filedialog from json import dump, load -from typing import Optional, Any +import os from pathlib import Path +import sys +from tkinter import filedialog, messagebox +from typing import Any, Optional, Tuple, Dict from .logger import logger, Status as LoggerStatus -import sys -import os - def resource_path(relative: str) -> str: - """Convert a relative resource path into an absolute path.""" + """Convert a relative resource path into an absolute path. + + Args: + relative: The relative path to the resource file. + Returns: + The absolute file path as a string. + """ if hasattr(sys, "_MEIPASS"): - return os.path.join(sys._MEIPASS, relative) # pyright: ignore[reportAttributeAccessIssue] + # Use getattr to safely read PyInstaller runtime attributes without mypy errors + meipass_path: str = getattr(sys, "_MEIPASS") + return os.path.join(meipass_path, relative) return str(Path.cwd() / relative) def save_project(engine: Any) -> Optional[str]: - """Save the engine project to a .absp file.""" + """Save the engine project to a .absp file. + + Args: + engine: The engine instance containing project data and properties + (e.g., project_name, game_dimensions, cursor_visible, + fullscreen, and entities). - directory = filedialog.askdirectory() + Returns: + The path to the saved project file as a string if saved, or None + if the user cancelled the file dialog. + """ + directory: str = filedialog.askdirectory() - if directory is None: + if not directory: return None project_path = Path(directory) / "game.absp" @@ -55,12 +68,15 @@ def save_project(engine: Any) -> Optional[str]: return str(project_path) -def load_project() -> Optional[list]: - """Ask the user for a project directory and return the loaded game data.""" +def load_project() -> Optional[Tuple[Dict[str, Any], str]]: + """Ask the user for a project directory and return the loaded game data. - directory = filedialog.askdirectory() + Returns: + A tuple of `(data_dict, file_path_str)`, or None if cancelled/failed. + """ + directory: str = filedialog.askdirectory() - if directory is None: + if not directory: return None project_path = Path(directory) / "game.absp" @@ -71,13 +87,13 @@ def load_project() -> Optional[list]: status=LoggerStatus.WARNING, ) project_path.write_text("{}", encoding="utf-8") - return [{}, str(project_path)] + return {}, str(project_path) if project_path.is_dir(): messagebox.showerror("Error", "game.absp project file is a directory.") return None with project_path.open("r", encoding="utf-8") as f: - data: dict = load(f) + data: Dict[str, Any] = load(f) - return [data, str(project_path)] + return data, str(project_path) \ No newline at end of file From e2ffa28a5606f56400be9d8a1a40f46d24e0f6b8 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:13:54 +0200 Subject: [PATCH 3/8] Fix Flake linter errors --- engine/saveload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/saveload.py b/engine/saveload.py index b2ba96e..9a5a54f 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -94,4 +94,4 @@ def load_project() -> Optional[Tuple[Dict[str, Any], str]]: with project_path.open("r", encoding="utf-8") as f: data: Dict[str, Any] = load(f) - return data, str(project_path) \ No newline at end of file + return data, str(project_path) From c0c3623696bfbe7d3dda59419946ac9ef7a58d66 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:15:29 +0200 Subject: [PATCH 4/8] I think i fixed Ruff's linter things --- engine/saveload.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/engine/saveload.py b/engine/saveload.py index 9a5a54f..4afe77d 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -23,12 +23,11 @@ def resource_path(relative: str) -> str: The absolute file path as a string. """ if hasattr(sys, "_MEIPASS"): - # Use getattr to safely read PyInstaller runtime attributes without mypy errors - meipass_path: str = getattr(sys, "_MEIPASS") + # Accessing PyInstaller runtime attribute; type ignore prevents mypy error while satisfying ruff B009 + meipass_path: str = sys._MEIPASS # type: ignore[attr-defined] return os.path.join(meipass_path, relative) return str(Path.cwd() / relative) - def save_project(engine: Any, dir: Optional[str] = None) -> Optional[str]: """ Save the project as an absp file From f0ba4f606ba35a33e4a1e2be5c75c7707c46cda9 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:19:06 +0200 Subject: [PATCH 5/8] I think i fixed both now --- engine/saveload.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/engine/saveload.py b/engine/saveload.py index 4afe77d..3a8bf75 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -8,9 +8,9 @@ from pathlib import Path import sys from tkinter import filedialog, messagebox -from typing import Any, Optional, Tuple, Dict +from typing import Any, Dict, Optional, Tuple -from .logger import logger, Status as LoggerStatus +from .logger import Status as LoggerStatus, logger def resource_path(relative: str) -> str: @@ -23,27 +23,30 @@ def resource_path(relative: str) -> str: The absolute file path as a string. """ if hasattr(sys, "_MEIPASS"): - # Accessing PyInstaller runtime attribute; type ignore prevents mypy error while satisfying ruff B009 meipass_path: str = sys._MEIPASS # type: ignore[attr-defined] return os.path.join(meipass_path, relative) return str(Path.cwd() / relative) + def save_project(engine: Any, dir: Optional[str] = None) -> Optional[str]: - """ - Save the project as an absp file + """Save the engine project to a .absp file. Args: - engine (Any): engine instance to extract the project information from - dir (Optional[str]): Directory to save the project in + engine: The engine instance containing project data and properties + (e.g., project_name, game_dimensions, cursor_visible, + fullscreen, and entities). + dir: An optional explicit directory path to save into. Returns: - Optional[str]: Path to the saved file or None + The path to the saved project file as a string if saved, or None + if the user cancelled the file dialog. """ + directory: str = dir if dir else filedialog.askdirectory() - if dir is None: - dir = filedialog.askdirectory() + if not directory: + return None - project_path = Path(dir) / "game.absp" + project_path = Path(directory) / "game.absp" project_path.parent.mkdir(parents=True, exist_ok=True) with project_path.open("w", encoding="utf-8") as f: @@ -93,4 +96,4 @@ def load_project() -> Optional[Tuple[Dict[str, Any], str]]: with project_path.open("r", encoding="utf-8") as f: data: Dict[str, Any] = load(f) - return data, str(project_path) + return data, str(project_path) \ No newline at end of file From 3ce153b0343796b196bccea370b6b0bdcabf7905 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:20:56 +0200 Subject: [PATCH 6/8] Add a newline at saveload --- engine/saveload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/saveload.py b/engine/saveload.py index 3a8bf75..d1db735 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -96,4 +96,4 @@ def load_project() -> Optional[Tuple[Dict[str, Any], str]]: with project_path.open("r", encoding="utf-8") as f: data: Dict[str, Any] = load(f) - return data, str(project_path) \ No newline at end of file + return data, str(project_path) From 1bf82ad6a9cff944f4496979e11f29948558eab1 Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:30:12 +0200 Subject: [PATCH 7/8] Fix some linter errors --- engine/build_tools.py | 5 ++++- engine/core/__init__.py | 1 + engine/saveload.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/build_tools.py b/engine/build_tools.py index ae0036f..a86d521 100644 --- a/engine/build_tools.py +++ b/engine/build_tools.py @@ -5,6 +5,7 @@ Build and development utilities for the engine. """ + import shutil from tkinter import messagebox @@ -34,7 +35,9 @@ def build(directory: Path, ENGINE_DATA_PATH: str) -> None: ) return - launch_game_script = Path(resource_path("data/scripts/launch_game.py")).read_text(encoding="utf-8") + launch_game_script = Path(resource_path("data/scripts/launch_game.py")).read_text( + encoding="utf-8" + ) (directory / "run.py").write_text(launch_game_script, encoding="utf-8") ignore = shutil.ignore_patterns("*.pyc", "__pycache__") diff --git a/engine/core/__init__.py b/engine/core/__init__.py index e3d7779..4dc4970 100644 --- a/engine/core/__init__.py +++ b/engine/core/__init__.py @@ -265,6 +265,7 @@ def __init__(self, *, parent: "Game") -> None: self.game: "Game" = parent self.objects: list[Entity] = [] + self.no_entities: bool = True logger("Initialized scene") diff --git a/engine/saveload.py b/engine/saveload.py index d1db735..9902352 100644 --- a/engine/saveload.py +++ b/engine/saveload.py @@ -23,7 +23,7 @@ def resource_path(relative: str) -> str: The absolute file path as a string. """ if hasattr(sys, "_MEIPASS"): - meipass_path: str = sys._MEIPASS # type: ignore[attr-defined] + meipass_path: str = sys._MEIPASS # type: ignore[unused-ignore] return os.path.join(meipass_path, relative) return str(Path.cwd() / relative) From fffecc187ab405b8a082df2a01631b0e6f00cdea Mon Sep 17 00:00:00 2001 From: Pacsfury <280690649+Pacsfury@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:32:25 +0200 Subject: [PATCH 8/8] Finally no linter errors --- engine/build_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/build_tools.py b/engine/build_tools.py index a86d521..d9da939 100644 --- a/engine/build_tools.py +++ b/engine/build_tools.py @@ -5,7 +5,6 @@ Build and development utilities for the engine. """ - import shutil from tkinter import messagebox