From 473f81c987b24bd63fdf04fcfcdfbcb94f955a8a Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Thu, 20 Feb 2025 13:38:50 +0100 Subject: [PATCH 01/20] revert old .github workflow this will revert the .github workflow that @PurpleProg helped with, only thing that is added now is to have it check on python files, so any markdown or yml configurations will not use this workflow anymore --- .github/workflows/lint_and_test.yml | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/lint_and_test.yml diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml new file mode 100644 index 0000000..ed5f6c4 --- /dev/null +++ b/.github/workflows/lint_and_test.yml @@ -0,0 +1,42 @@ +name: Test (lint and typehints) + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint_and_typehints: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check for Python files + id: check_files + run: | + git ls-files '*.py' > files.txt + if [ ! -s files.txt ]; then + echo "No Python files found. Skipping lint and type-check." + exit 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff mypy pygame-ce pytmx + + - name: Run ruff + run: | + ruff check $(git-ls '*.py') + + - name: Run mypy + run: | + mypy main.py \ No newline at end of file From 613f37cf10b0090732ff1b589d330f15d1cb2067 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Fri, 28 Feb 2025 00:20:13 +0100 Subject: [PATCH 02/20] Refactor: Use reusable workflow for linting and type hints from .github repository - Updated the workflow to use the reusable workflow `lint_and_test.yml` from the PyCeas/.github repository. - Simplified workflow maintenance by centralizing the logic in the `.github` repository. - Removed duplication of Python version management and dependency installation logic in individual workflows. - Added flexibility for future updates and enhancements to global workflows. --- .github/workflows/lint_and_test.yml | 42 ------------------------ .github/workflows/lint_and_typehints.yml | 16 +++++++++ 2 files changed, 16 insertions(+), 42 deletions(-) delete mode 100644 .github/workflows/lint_and_test.yml create mode 100644 .github/workflows/lint_and_typehints.yml diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml deleted file mode 100644 index ed5f6c4..0000000 --- a/.github/workflows/lint_and_test.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Test (lint and typehints) - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] - -jobs: - lint_and_typehints: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check for Python files - id: check_files - run: | - git ls-files '*.py' > files.txt - if [ ! -s files.txt ]; then - echo "No Python files found. Skipping lint and type-check." - exit 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install ruff mypy pygame-ce pytmx - - - name: Run ruff - run: | - ruff check $(git-ls '*.py') - - - name: Run mypy - run: | - mypy main.py \ No newline at end of file diff --git a/.github/workflows/lint_and_typehints.yml b/.github/workflows/lint_and_typehints.yml new file mode 100644 index 0000000..b31a1f6 --- /dev/null +++ b/.github/workflows/lint_and_typehints.yml @@ -0,0 +1,16 @@ +name: Lint and Typehints + +on: + push: + branches: + - main + pull_request: + types: [ opened, synchronize, reopened ] + +jobs: + lint_and_typehints: + uses: PyCeas/.github/.github/workflows/lint_and_test.yml@main + with: + requirements_path: 'requirements.txt' # Optional: Path to requirements file + secrets: + repo_token: ${{ secrets.GITHUB_TOKEN }} From da411e1d36ed4cfe290f342df67a8080db1beed3 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Fri, 28 Feb 2025 00:26:15 +0100 Subject: [PATCH 03/20] docs: restructure readme a bit and fix a license url This will fix the readme once again, just some things I forgot since my last PR, it will make the changes more clear and better to read. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0bbe077..993389a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Discord](https://discord.com/api/guilds/1272287320934056066/widget.png)](https://discord.gg/s2P9fZbeZs) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/) -[![License](https://img.shields.io/github/license/ultimateownsz/PyCeas)](https://github.com/PyCeas/Pyceas/blob/main/LICENSE) +[![License](https://img.shields.io/github/license/PyCeas/PyCeas)](https://github.com/PyCeas/Pyceas/blob/main/LICENSE) ## Table of Contents - [PyCeas - Open Source Pirate (Adventure) Board Game](#pyceas---open-source-pirate-adventure-board-game) @@ -37,11 +37,7 @@ Every game needs thorough documentation, and you can find our Game Design Docume - **Create something enjoyable**: Contributing to a project that results in a game you and others can enjoy. - **Join our community on [Discord](https://discord.gg/MZ5MHqDnGW)**: Whether you want to help with ideas, give feedback, or simply enjoy the game, everyone is welcome! -## Getting Started - -No need to worry if you are new to programming. This guide will walk you through the setup step by step. by the end, you'll have everything ready to run the PyCeas project. - -> [!IMPORTANT] +> [!NOTE] > > This project was previously known as **PySeas** and has been renamed to **PyCeas**. > If you have already cloned the repository under the old name, update your local repository’s remote URL: @@ -50,6 +46,10 @@ No need to worry if you are new to programming. This guide will walk you through > git remote set-url origin https://github.com/PyCeas/Pyceas.git > ``` +## Getting Started + +No need to worry if you are new to programming. This guide will walk you through the setup step by step. by the end, you'll have everything ready to run the PyCeas project. + 1. **Clone the Repository:** First you'll need to copy the PyCeas project to your computer through a process called "cloning". From 63876fe48c8f531a6865bc98e21330bdb37ca58f Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Fri, 28 Feb 2025 01:13:37 +0100 Subject: [PATCH 04/20] run ruff & mypy testing if the linter now properly works on .py files --- src/GUI/gameloop.py | 54 ++++++++++---- src/settings.py | 7 +- src/sprites.py | 42 ++++++++--- src/states/game_running.py | 52 +++++++++---- src/support.py | 145 +++++++++++++++++++++---------------- 5 files changed, 196 insertions(+), 104 deletions(-) diff --git a/src/GUI/gameloop.py b/src/GUI/gameloop.py index 5828ec4..8b6d493 100644 --- a/src/GUI/gameloop.py +++ b/src/GUI/gameloop.py @@ -53,7 +53,7 @@ def import_assets(self): self.world_frames = { "water": import_folder(".", "images", "tilesets", "water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships") + "ships": all_character_import(".", "images", "tilesets", "ships"), } # print(self.world_frames["ships"]) @@ -62,37 +62,62 @@ def setup(self, tmx_maps, player_start_pos): # Sea for x, y, surface in tmx_maps.get_layer_by_name("Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Water animated for obj in tmx_maps.get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) + AnimatedSprites( + (x, y), + self.world_frames["water"], + self.all_sprites, + WORLD_LAYERS["water"], + ) # Shallow water for x, y, surface in tmx_maps.get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Islands islands = tmx_maps.get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Enitites for obj in tmx_maps.get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos = (obj.x, obj.y), - frames = self.world_frames["ships"]["player_test_ship"], - groups = self.all_sprites) + pos=(obj.x, obj.y), + frames=self.world_frames["ships"]["player_test_ship"], + groups=self.all_sprites, + ) # Coast for obj in tmx_maps.get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) - + AnimatedSprites( + (obj.x, obj.y), + self.world_frames["coast"][terrain][side], + self.all_sprites, + WORLD_LAYERS["bg"], + ) def run(self) -> None: """main loop of the game""" @@ -114,12 +139,15 @@ def render(self) -> None: self.screen.fill("#000000") self.all_sprites.update(dt) - self.all_sprites.draw(self.player.rect.center, self.player.player_preview, self.player.player_preview_rect) + self.all_sprites.draw( + self.player.rect.center, + self.player.player_preview, + self.player.player_preview_rect, + ) - '''No need to loop through the players because it is now in the sprite group AllSprites''' + """No need to loop through the players because it is now in the sprite group AllSprites""" # draw players on top of the other sprites # for player in self.players: # player.render(surface=self.screen) pygame.display.update() - diff --git a/src/settings.py b/src/settings.py index f46d776..8eab317 100644 --- a/src/settings.py +++ b/src/settings.py @@ -7,12 +7,7 @@ TILE_SIZE = 16 ANIMATION_SPEED = 4 -WORLD_LAYERS = { - "water": 0, - "bg": 1, - "main": 2, - "top": 3 -} +WORLD_LAYERS = {"water": 0, "bg": 1, "main": 2, "top": 3} FPS = 60 diff --git a/src/sprites.py b/src/sprites.py index 6592fce..8bbcb41 100644 --- a/src/sprites.py +++ b/src/sprites.py @@ -2,7 +2,13 @@ import pygame from pygame import FRect -from src.settings import TILE_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH, ANIMATION_SPEED, WORLD_LAYERS +from src.settings import ( + TILE_SIZE, + SCREEN_HEIGHT, + SCREEN_WIDTH, + ANIMATION_SPEED, + WORLD_LAYERS, +) from src.inventory import Inventory @@ -23,7 +29,7 @@ def __init__(self, pos, frames, groups): self.image = pygame.Surface((TILE_SIZE, TILE_SIZE)) self.image.fill("red") # self.image = self.frames[self.get_state()][self.frame_index] - self.rect = self.image.get_frect(center = pos) + self.rect = self.image.get_frect(center=pos) def animate(self, dt): self.frame_index += ANIMATION_SPEED * dt @@ -48,7 +54,7 @@ def __init__(self): self.display_surface = pygame.display.get_surface() if not self.display_surface: raise ValueError("Display surface is not initialized") - + self.offset = pygame.math.Vector2() self.scale = 2.0 @@ -56,21 +62,35 @@ def draw(self, player_center): self.offset.x = -(player_center[0] * self.scale - SCREEN_WIDTH / 2) self.offset.y = -(player_center[1] * self.scale - SCREEN_HEIGHT / 2) - background_sprites = [sprite for sprite in self if sprite.z < WORLD_LAYERS["main"]] + background_sprites = [ + sprite for sprite in self if sprite.z < WORLD_LAYERS["main"] + ] main_sprites = [sprite for sprite in self if sprite.z == WORLD_LAYERS["main"]] - foreground_sprites = [sprite for sprite in self if sprite.z > WORLD_LAYERS["main"]] + foreground_sprites = [ + sprite for sprite in self if sprite.z > WORLD_LAYERS["main"] + ] for layer in (background_sprites, main_sprites, foreground_sprites): for sprite in layer: - scaled_image = pygame.transform.scale(sprite.image, - (int(sprite.rect.width * self.scale), int(sprite.rect.height * self.scale))) - scaled_rect = scaled_image.get_rect(center=(sprite.rect.center[0] * self.scale, sprite.rect.center[1] * self.scale)) + scaled_image = pygame.transform.scale( + sprite.image, + ( + int(sprite.rect.width * self.scale), + int(sprite.rect.height * self.scale), + ), + ) + scaled_rect = scaled_image.get_rect( + center=( + sprite.rect.center[0] * self.scale, + sprite.rect.center[1] * self.scale, + ) + ) scaled_rect.topleft += self.offset self.display_surface.blit(scaled_image, scaled_rect.topleft) # scaling of the ghost preview - # scaled_preview = pygame.transform.scale(player_preview, + # scaled_preview = pygame.transform.scale(player_preview, # (int(player_preview_rect.width * self.scale), int(player_preview_rect.height * self.scale))) # scaled_preview_rect = scaled_preview.get_rect(center=(player_preview_rect.center[0] * self.scale, player_preview_rect.center[1] * self.scale)) # scaled_preview_rect.topleft += self.offset @@ -217,7 +237,7 @@ def draw( class Sprite(pygame.sprite.Sprite): - def __init__(self, pos, surf, groups, z = WORLD_LAYERS["main"]): + def __init__(self, pos, surf, groups, z=WORLD_LAYERS["main"]): super().__init__(groups) self.image = surf self.rect = self.image.get_frect(topleft=pos) @@ -225,7 +245,7 @@ def __init__(self, pos, surf, groups, z = WORLD_LAYERS["main"]): class AnimatedSprites(Sprite): - def __init__(self, pos, frames, groups, z = WORLD_LAYERS["main"]): + def __init__(self, pos, frames, groups, z=WORLD_LAYERS["main"]): self.frame_index, self.frames = 0, frames super().__init__(pos, frames[self.frame_index], groups, z) diff --git a/src/states/game_running.py b/src/states/game_running.py index 3edaa62..ed05328 100644 --- a/src/states/game_running.py +++ b/src/states/game_running.py @@ -52,41 +52,69 @@ def setup(self, player_start_pos): self.world_frames = { "water": import_folder(".", "images", "tilesets", "temporary_water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships") + "ships": all_character_import(".", "images", "tilesets", "ships"), } # Sea for x, y, surface in self.tmx_map["map"].get_layer_by_name("Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Water animated for obj in self.tmx_map["map"].get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) + AnimatedSprites( + (x, y), + self.world_frames["water"], + self.all_sprites, + WORLD_LAYERS["water"], + ) # Shallow water - for x, y, surface in self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + for x, y, surface in ( + self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles() + ): + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Islands islands = self.tmx_map["map"].get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Enitites for obj in self.tmx_map["map"].get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos = (obj.x, obj.y), - frames = self.world_frames["ships"]["player_test_ship"], - groups = self.all_sprites) + pos=(obj.x, obj.y), + frames=self.world_frames["ships"]["player_test_ship"], + groups=self.all_sprites, + ) # Coast for obj in self.tmx_map["map"].get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) + AnimatedSprites( + (obj.x, obj.y), + self.world_frames["coast"][terrain][side], + self.all_sprites, + WORLD_LAYERS["bg"], + ) def load_inventory_from_json(self, file_path: str): """Load initial inventory items from JSON file.""" @@ -117,8 +145,6 @@ def update(self, events) -> None: def render(self, screen) -> None: """draw sprites to the canvas""" screen.fill("#000000") - self.all_sprites.draw( - self.player.rect.center - ) + self.all_sprites.draw(self.player.rect.center) pygame.display.update() diff --git a/src/support.py b/src/support.py index 3b56490..b4086e8 100644 --- a/src/support.py +++ b/src/support.py @@ -3,78 +3,101 @@ from os import walk # from pytmx.util_pygame import load_pygame -# imports -def import_image(*path, alpha = True, format = 'png'): - full_path = join(*path) + f'.{format}' - surf = pygame.image.load(full_path).convert_alpha() if alpha else pygame.image.load(full_path).convert() - return surf + +# imports +def import_image(*path, alpha=True, format="png"): + full_path = join(*path) + f".{format}" + surf = ( + pygame.image.load(full_path).convert_alpha() + if alpha + else pygame.image.load(full_path).convert() + ) + return surf + def import_folder(*path): - frames = [] - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in sorted(image_names, key = lambda name: int(name.split('.')[0])): - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames.append(surf) - return frames + frames = [] + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in sorted(image_names, key=lambda name: int(name.split(".")[0])): + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames.append(surf) + return frames + def import_folder_dict(*path): - frames = {} - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in image_names: - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames[image_name.split('.')[0]] = surf - return frames + frames = {} + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in image_names: + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames[image_name.split(".")[0]] = surf + return frames + def import_sub_folders(*path): - frames = {} - for _, sub_folders, __ in walk(join(*path)): - if sub_folders: - for sub_folder in sub_folders: - frames[sub_folder] = import_folder(*path, sub_folder) - return frames + frames = {} + for _, sub_folders, __ in walk(join(*path)): + if sub_folders: + for sub_folder in sub_folders: + frames[sub_folder] = import_folder(*path, sub_folder) + return frames + def import_tilemap(cols, rows, *path): - frames = {} - surf = import_image(*path) - cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows - for col in range(cols): - for row in range(rows): - cutout_rect = pygame.Rect(col * cell_width, row * cell_height,cell_width,cell_height) - cutout_surf = pygame.Surface((cell_width, cell_height)) - cutout_surf.fill('green') - cutout_surf.set_colorkey('green') - cutout_surf.blit(surf, (0,0), cutout_rect) - frames[(col, row)] = cutout_surf - return frames + frames = {} + surf = import_image(*path) + cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows + for col in range(cols): + for row in range(rows): + cutout_rect = pygame.Rect( + col * cell_width, row * cell_height, cell_width, cell_height + ) + cutout_surf = pygame.Surface((cell_width, cell_height)) + cutout_surf.fill("green") + cutout_surf.set_colorkey("green") + cutout_surf.blit(surf, (0, 0), cutout_rect) + frames[(col, row)] = cutout_surf + return frames + def coast_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - terrains = ["sand"] - sides = { - 'topleft': (0,0), 'top': (1,0), 'topright': (2,0), - 'left': (0,1), 'right': (2,1), 'bottomleft': (0,2), - 'bottom': (1,2), 'bottomright': (2,2)} - for index, terrain in enumerate(terrains): - new_dict[terrain] = {} - for key, pos in sides.items(): - new_dict[terrain][key] = [frame_dict[(pos[0] + index * 3, pos[1] + row)] for row in range(0, rows, 3)] - return new_dict + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + terrains = ["sand"] + sides = { + "topleft": (0, 0), + "top": (1, 0), + "topright": (2, 0), + "left": (0, 1), + "right": (2, 1), + "bottomleft": (0, 2), + "bottom": (1, 2), + "bottomright": (2, 2), + } + for index, terrain in enumerate(terrains): + new_dict[terrain] = {} + for key, pos in sides.items(): + new_dict[terrain][key] = [ + frame_dict[(pos[0] + index * 3, pos[1] + row)] + for row in range(0, rows, 3) + ] + return new_dict + def character_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - for row, direction in enumerate(("down", "left", "right", "up")): - new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] - new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] - return new_dict + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + for row, direction in enumerate(("down", "left", "right", "up")): + new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] + new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] + return new_dict + def all_character_import(*path): - new_dict = {} - for _, _, image_names in walk(join(*path)): - for image in image_names: - image_name = image.split(".")[0] - new_dict[image_name] = character_importer(7, 4, *path, image_name) - return new_dict \ No newline at end of file + new_dict = {} + for _, _, image_names in walk(join(*path)): + for image in image_names: + image_name = image.split(".")[0] + new_dict[image_name] = character_importer(7, 4, *path, image_name) + return new_dict From e560fc00b28eefcd1d428b50d8d9b3bcf8bf61ad Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Fri, 28 Feb 2025 01:17:22 +0100 Subject: [PATCH 05/20] Revert "run ruff & mypy" This reverts commit 63876fe48c8f531a6865bc98e21330bdb37ca58f. --- src/GUI/gameloop.py | 54 ++++---------- src/settings.py | 7 +- src/sprites.py | 42 +++-------- src/states/game_running.py | 52 ++++--------- src/support.py | 145 ++++++++++++++++--------------------- 5 files changed, 104 insertions(+), 196 deletions(-) diff --git a/src/GUI/gameloop.py b/src/GUI/gameloop.py index 8b6d493..5828ec4 100644 --- a/src/GUI/gameloop.py +++ b/src/GUI/gameloop.py @@ -53,7 +53,7 @@ def import_assets(self): self.world_frames = { "water": import_folder(".", "images", "tilesets", "water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships"), + "ships": all_character_import(".", "images", "tilesets", "ships") } # print(self.world_frames["ships"]) @@ -62,62 +62,37 @@ def setup(self, tmx_maps, player_start_pos): # Sea for x, y, surface in tmx_maps.get_layer_by_name("Sea").tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Water animated for obj in tmx_maps.get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites( - (x, y), - self.world_frames["water"], - self.all_sprites, - WORLD_LAYERS["water"], - ) + AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) # Shallow water for x, y, surface in tmx_maps.get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Islands islands = tmx_maps.get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Enitites for obj in tmx_maps.get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos=(obj.x, obj.y), - frames=self.world_frames["ships"]["player_test_ship"], - groups=self.all_sprites, - ) + pos = (obj.x, obj.y), + frames = self.world_frames["ships"]["player_test_ship"], + groups = self.all_sprites) # Coast for obj in tmx_maps.get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites( - (obj.x, obj.y), - self.world_frames["coast"][terrain][side], - self.all_sprites, - WORLD_LAYERS["bg"], - ) + AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) + def run(self) -> None: """main loop of the game""" @@ -139,15 +114,12 @@ def render(self) -> None: self.screen.fill("#000000") self.all_sprites.update(dt) - self.all_sprites.draw( - self.player.rect.center, - self.player.player_preview, - self.player.player_preview_rect, - ) + self.all_sprites.draw(self.player.rect.center, self.player.player_preview, self.player.player_preview_rect) - """No need to loop through the players because it is now in the sprite group AllSprites""" + '''No need to loop through the players because it is now in the sprite group AllSprites''' # draw players on top of the other sprites # for player in self.players: # player.render(surface=self.screen) pygame.display.update() + diff --git a/src/settings.py b/src/settings.py index 8eab317..f46d776 100644 --- a/src/settings.py +++ b/src/settings.py @@ -7,7 +7,12 @@ TILE_SIZE = 16 ANIMATION_SPEED = 4 -WORLD_LAYERS = {"water": 0, "bg": 1, "main": 2, "top": 3} +WORLD_LAYERS = { + "water": 0, + "bg": 1, + "main": 2, + "top": 3 +} FPS = 60 diff --git a/src/sprites.py b/src/sprites.py index 8bbcb41..6592fce 100644 --- a/src/sprites.py +++ b/src/sprites.py @@ -2,13 +2,7 @@ import pygame from pygame import FRect -from src.settings import ( - TILE_SIZE, - SCREEN_HEIGHT, - SCREEN_WIDTH, - ANIMATION_SPEED, - WORLD_LAYERS, -) +from src.settings import TILE_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH, ANIMATION_SPEED, WORLD_LAYERS from src.inventory import Inventory @@ -29,7 +23,7 @@ def __init__(self, pos, frames, groups): self.image = pygame.Surface((TILE_SIZE, TILE_SIZE)) self.image.fill("red") # self.image = self.frames[self.get_state()][self.frame_index] - self.rect = self.image.get_frect(center=pos) + self.rect = self.image.get_frect(center = pos) def animate(self, dt): self.frame_index += ANIMATION_SPEED * dt @@ -54,7 +48,7 @@ def __init__(self): self.display_surface = pygame.display.get_surface() if not self.display_surface: raise ValueError("Display surface is not initialized") - + self.offset = pygame.math.Vector2() self.scale = 2.0 @@ -62,35 +56,21 @@ def draw(self, player_center): self.offset.x = -(player_center[0] * self.scale - SCREEN_WIDTH / 2) self.offset.y = -(player_center[1] * self.scale - SCREEN_HEIGHT / 2) - background_sprites = [ - sprite for sprite in self if sprite.z < WORLD_LAYERS["main"] - ] + background_sprites = [sprite for sprite in self if sprite.z < WORLD_LAYERS["main"]] main_sprites = [sprite for sprite in self if sprite.z == WORLD_LAYERS["main"]] - foreground_sprites = [ - sprite for sprite in self if sprite.z > WORLD_LAYERS["main"] - ] + foreground_sprites = [sprite for sprite in self if sprite.z > WORLD_LAYERS["main"]] for layer in (background_sprites, main_sprites, foreground_sprites): for sprite in layer: - scaled_image = pygame.transform.scale( - sprite.image, - ( - int(sprite.rect.width * self.scale), - int(sprite.rect.height * self.scale), - ), - ) - scaled_rect = scaled_image.get_rect( - center=( - sprite.rect.center[0] * self.scale, - sprite.rect.center[1] * self.scale, - ) - ) + scaled_image = pygame.transform.scale(sprite.image, + (int(sprite.rect.width * self.scale), int(sprite.rect.height * self.scale))) + scaled_rect = scaled_image.get_rect(center=(sprite.rect.center[0] * self.scale, sprite.rect.center[1] * self.scale)) scaled_rect.topleft += self.offset self.display_surface.blit(scaled_image, scaled_rect.topleft) # scaling of the ghost preview - # scaled_preview = pygame.transform.scale(player_preview, + # scaled_preview = pygame.transform.scale(player_preview, # (int(player_preview_rect.width * self.scale), int(player_preview_rect.height * self.scale))) # scaled_preview_rect = scaled_preview.get_rect(center=(player_preview_rect.center[0] * self.scale, player_preview_rect.center[1] * self.scale)) # scaled_preview_rect.topleft += self.offset @@ -237,7 +217,7 @@ def draw( class Sprite(pygame.sprite.Sprite): - def __init__(self, pos, surf, groups, z=WORLD_LAYERS["main"]): + def __init__(self, pos, surf, groups, z = WORLD_LAYERS["main"]): super().__init__(groups) self.image = surf self.rect = self.image.get_frect(topleft=pos) @@ -245,7 +225,7 @@ def __init__(self, pos, surf, groups, z=WORLD_LAYERS["main"]): class AnimatedSprites(Sprite): - def __init__(self, pos, frames, groups, z=WORLD_LAYERS["main"]): + def __init__(self, pos, frames, groups, z = WORLD_LAYERS["main"]): self.frame_index, self.frames = 0, frames super().__init__(pos, frames[self.frame_index], groups, z) diff --git a/src/states/game_running.py b/src/states/game_running.py index ed05328..3edaa62 100644 --- a/src/states/game_running.py +++ b/src/states/game_running.py @@ -52,69 +52,41 @@ def setup(self, player_start_pos): self.world_frames = { "water": import_folder(".", "images", "tilesets", "temporary_water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships"), + "ships": all_character_import(".", "images", "tilesets", "ships") } # Sea for x, y, surface in self.tmx_map["map"].get_layer_by_name("Sea").tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Water animated for obj in self.tmx_map["map"].get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites( - (x, y), - self.world_frames["water"], - self.all_sprites, - WORLD_LAYERS["water"], - ) + AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) # Shallow water - for x, y, surface in ( - self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles() - ): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + for x, y, surface in self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles(): + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Islands islands = self.tmx_map["map"].get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) + src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) # Enitites for obj in self.tmx_map["map"].get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos=(obj.x, obj.y), - frames=self.world_frames["ships"]["player_test_ship"], - groups=self.all_sprites, - ) + pos = (obj.x, obj.y), + frames = self.world_frames["ships"]["player_test_ship"], + groups = self.all_sprites) # Coast for obj in self.tmx_map["map"].get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites( - (obj.x, obj.y), - self.world_frames["coast"][terrain][side], - self.all_sprites, - WORLD_LAYERS["bg"], - ) + AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) def load_inventory_from_json(self, file_path: str): """Load initial inventory items from JSON file.""" @@ -145,6 +117,8 @@ def update(self, events) -> None: def render(self, screen) -> None: """draw sprites to the canvas""" screen.fill("#000000") - self.all_sprites.draw(self.player.rect.center) + self.all_sprites.draw( + self.player.rect.center + ) pygame.display.update() diff --git a/src/support.py b/src/support.py index b4086e8..3b56490 100644 --- a/src/support.py +++ b/src/support.py @@ -3,101 +3,78 @@ from os import walk # from pytmx.util_pygame import load_pygame - -# imports -def import_image(*path, alpha=True, format="png"): - full_path = join(*path) + f".{format}" - surf = ( - pygame.image.load(full_path).convert_alpha() - if alpha - else pygame.image.load(full_path).convert() - ) - return surf - +# imports +def import_image(*path, alpha = True, format = 'png'): + full_path = join(*path) + f'.{format}' + surf = pygame.image.load(full_path).convert_alpha() if alpha else pygame.image.load(full_path).convert() + return surf def import_folder(*path): - frames = [] - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in sorted(image_names, key=lambda name: int(name.split(".")[0])): - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames.append(surf) - return frames - + frames = [] + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in sorted(image_names, key = lambda name: int(name.split('.')[0])): + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames.append(surf) + return frames def import_folder_dict(*path): - frames = {} - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in image_names: - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames[image_name.split(".")[0]] = surf - return frames - + frames = {} + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in image_names: + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames[image_name.split('.')[0]] = surf + return frames def import_sub_folders(*path): - frames = {} - for _, sub_folders, __ in walk(join(*path)): - if sub_folders: - for sub_folder in sub_folders: - frames[sub_folder] = import_folder(*path, sub_folder) - return frames - + frames = {} + for _, sub_folders, __ in walk(join(*path)): + if sub_folders: + for sub_folder in sub_folders: + frames[sub_folder] = import_folder(*path, sub_folder) + return frames def import_tilemap(cols, rows, *path): - frames = {} - surf = import_image(*path) - cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows - for col in range(cols): - for row in range(rows): - cutout_rect = pygame.Rect( - col * cell_width, row * cell_height, cell_width, cell_height - ) - cutout_surf = pygame.Surface((cell_width, cell_height)) - cutout_surf.fill("green") - cutout_surf.set_colorkey("green") - cutout_surf.blit(surf, (0, 0), cutout_rect) - frames[(col, row)] = cutout_surf - return frames - + frames = {} + surf = import_image(*path) + cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows + for col in range(cols): + for row in range(rows): + cutout_rect = pygame.Rect(col * cell_width, row * cell_height,cell_width,cell_height) + cutout_surf = pygame.Surface((cell_width, cell_height)) + cutout_surf.fill('green') + cutout_surf.set_colorkey('green') + cutout_surf.blit(surf, (0,0), cutout_rect) + frames[(col, row)] = cutout_surf + return frames def coast_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - terrains = ["sand"] - sides = { - "topleft": (0, 0), - "top": (1, 0), - "topright": (2, 0), - "left": (0, 1), - "right": (2, 1), - "bottomleft": (0, 2), - "bottom": (1, 2), - "bottomright": (2, 2), - } - for index, terrain in enumerate(terrains): - new_dict[terrain] = {} - for key, pos in sides.items(): - new_dict[terrain][key] = [ - frame_dict[(pos[0] + index * 3, pos[1] + row)] - for row in range(0, rows, 3) - ] - return new_dict - + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + terrains = ["sand"] + sides = { + 'topleft': (0,0), 'top': (1,0), 'topright': (2,0), + 'left': (0,1), 'right': (2,1), 'bottomleft': (0,2), + 'bottom': (1,2), 'bottomright': (2,2)} + for index, terrain in enumerate(terrains): + new_dict[terrain] = {} + for key, pos in sides.items(): + new_dict[terrain][key] = [frame_dict[(pos[0] + index * 3, pos[1] + row)] for row in range(0, rows, 3)] + return new_dict def character_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - for row, direction in enumerate(("down", "left", "right", "up")): - new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] - new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] - return new_dict - + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + for row, direction in enumerate(("down", "left", "right", "up")): + new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] + new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] + return new_dict def all_character_import(*path): - new_dict = {} - for _, _, image_names in walk(join(*path)): - for image in image_names: - image_name = image.split(".")[0] - new_dict[image_name] = character_importer(7, 4, *path, image_name) - return new_dict + new_dict = {} + for _, _, image_names in walk(join(*path)): + for image in image_names: + image_name = image.split(".")[0] + new_dict[image_name] = character_importer(7, 4, *path, image_name) + return new_dict \ No newline at end of file From 7870ac48d03e7c185cbafe746e0a4d1af0bf7b87 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Fri, 28 Feb 2025 21:28:11 +0100 Subject: [PATCH 06/20] update correct requirements_dev.txt file --- .github/workflows/lint_and_typehints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint_and_typehints.yml b/.github/workflows/lint_and_typehints.yml index b31a1f6..ce7bbc1 100644 --- a/.github/workflows/lint_and_typehints.yml +++ b/.github/workflows/lint_and_typehints.yml @@ -11,6 +11,6 @@ jobs: lint_and_typehints: uses: PyCeas/.github/.github/workflows/lint_and_test.yml@main with: - requirements_path: 'requirements.txt' # Optional: Path to requirements file + requirements_path: 'requirements_dev.txt' # Optional: Path to requirements file secrets: repo_token: ${{ secrets.GITHUB_TOKEN }} From 36f994a008ff6114ba68483609ba3ad16abf3662 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Tue, 11 Mar 2025 23:12:12 +0100 Subject: [PATCH 07/20] run ruff over the new changes testing out if the workflow works again properly --- src/GUI/gameloop.py | 54 ++++++++++---- src/settings.py | 7 +- src/sprites.py | 42 ++++++++--- src/states/game_running.py | 52 +++++++++---- src/support.py | 145 +++++++++++++++++++++---------------- 5 files changed, 196 insertions(+), 104 deletions(-) diff --git a/src/GUI/gameloop.py b/src/GUI/gameloop.py index 5828ec4..8b6d493 100644 --- a/src/GUI/gameloop.py +++ b/src/GUI/gameloop.py @@ -53,7 +53,7 @@ def import_assets(self): self.world_frames = { "water": import_folder(".", "images", "tilesets", "water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships") + "ships": all_character_import(".", "images", "tilesets", "ships"), } # print(self.world_frames["ships"]) @@ -62,37 +62,62 @@ def setup(self, tmx_maps, player_start_pos): # Sea for x, y, surface in tmx_maps.get_layer_by_name("Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Water animated for obj in tmx_maps.get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) + AnimatedSprites( + (x, y), + self.world_frames["water"], + self.all_sprites, + WORLD_LAYERS["water"], + ) # Shallow water for x, y, surface in tmx_maps.get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Islands islands = tmx_maps.get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Enitites for obj in tmx_maps.get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos = (obj.x, obj.y), - frames = self.world_frames["ships"]["player_test_ship"], - groups = self.all_sprites) + pos=(obj.x, obj.y), + frames=self.world_frames["ships"]["player_test_ship"], + groups=self.all_sprites, + ) # Coast for obj in tmx_maps.get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) - + AnimatedSprites( + (obj.x, obj.y), + self.world_frames["coast"][terrain][side], + self.all_sprites, + WORLD_LAYERS["bg"], + ) def run(self) -> None: """main loop of the game""" @@ -114,12 +139,15 @@ def render(self) -> None: self.screen.fill("#000000") self.all_sprites.update(dt) - self.all_sprites.draw(self.player.rect.center, self.player.player_preview, self.player.player_preview_rect) + self.all_sprites.draw( + self.player.rect.center, + self.player.player_preview, + self.player.player_preview_rect, + ) - '''No need to loop through the players because it is now in the sprite group AllSprites''' + """No need to loop through the players because it is now in the sprite group AllSprites""" # draw players on top of the other sprites # for player in self.players: # player.render(surface=self.screen) pygame.display.update() - diff --git a/src/settings.py b/src/settings.py index f46d776..8eab317 100644 --- a/src/settings.py +++ b/src/settings.py @@ -7,12 +7,7 @@ TILE_SIZE = 16 ANIMATION_SPEED = 4 -WORLD_LAYERS = { - "water": 0, - "bg": 1, - "main": 2, - "top": 3 -} +WORLD_LAYERS = {"water": 0, "bg": 1, "main": 2, "top": 3} FPS = 60 diff --git a/src/sprites.py b/src/sprites.py index 6592fce..8bbcb41 100644 --- a/src/sprites.py +++ b/src/sprites.py @@ -2,7 +2,13 @@ import pygame from pygame import FRect -from src.settings import TILE_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH, ANIMATION_SPEED, WORLD_LAYERS +from src.settings import ( + TILE_SIZE, + SCREEN_HEIGHT, + SCREEN_WIDTH, + ANIMATION_SPEED, + WORLD_LAYERS, +) from src.inventory import Inventory @@ -23,7 +29,7 @@ def __init__(self, pos, frames, groups): self.image = pygame.Surface((TILE_SIZE, TILE_SIZE)) self.image.fill("red") # self.image = self.frames[self.get_state()][self.frame_index] - self.rect = self.image.get_frect(center = pos) + self.rect = self.image.get_frect(center=pos) def animate(self, dt): self.frame_index += ANIMATION_SPEED * dt @@ -48,7 +54,7 @@ def __init__(self): self.display_surface = pygame.display.get_surface() if not self.display_surface: raise ValueError("Display surface is not initialized") - + self.offset = pygame.math.Vector2() self.scale = 2.0 @@ -56,21 +62,35 @@ def draw(self, player_center): self.offset.x = -(player_center[0] * self.scale - SCREEN_WIDTH / 2) self.offset.y = -(player_center[1] * self.scale - SCREEN_HEIGHT / 2) - background_sprites = [sprite for sprite in self if sprite.z < WORLD_LAYERS["main"]] + background_sprites = [ + sprite for sprite in self if sprite.z < WORLD_LAYERS["main"] + ] main_sprites = [sprite for sprite in self if sprite.z == WORLD_LAYERS["main"]] - foreground_sprites = [sprite for sprite in self if sprite.z > WORLD_LAYERS["main"]] + foreground_sprites = [ + sprite for sprite in self if sprite.z > WORLD_LAYERS["main"] + ] for layer in (background_sprites, main_sprites, foreground_sprites): for sprite in layer: - scaled_image = pygame.transform.scale(sprite.image, - (int(sprite.rect.width * self.scale), int(sprite.rect.height * self.scale))) - scaled_rect = scaled_image.get_rect(center=(sprite.rect.center[0] * self.scale, sprite.rect.center[1] * self.scale)) + scaled_image = pygame.transform.scale( + sprite.image, + ( + int(sprite.rect.width * self.scale), + int(sprite.rect.height * self.scale), + ), + ) + scaled_rect = scaled_image.get_rect( + center=( + sprite.rect.center[0] * self.scale, + sprite.rect.center[1] * self.scale, + ) + ) scaled_rect.topleft += self.offset self.display_surface.blit(scaled_image, scaled_rect.topleft) # scaling of the ghost preview - # scaled_preview = pygame.transform.scale(player_preview, + # scaled_preview = pygame.transform.scale(player_preview, # (int(player_preview_rect.width * self.scale), int(player_preview_rect.height * self.scale))) # scaled_preview_rect = scaled_preview.get_rect(center=(player_preview_rect.center[0] * self.scale, player_preview_rect.center[1] * self.scale)) # scaled_preview_rect.topleft += self.offset @@ -217,7 +237,7 @@ def draw( class Sprite(pygame.sprite.Sprite): - def __init__(self, pos, surf, groups, z = WORLD_LAYERS["main"]): + def __init__(self, pos, surf, groups, z=WORLD_LAYERS["main"]): super().__init__(groups) self.image = surf self.rect = self.image.get_frect(topleft=pos) @@ -225,7 +245,7 @@ def __init__(self, pos, surf, groups, z = WORLD_LAYERS["main"]): class AnimatedSprites(Sprite): - def __init__(self, pos, frames, groups, z = WORLD_LAYERS["main"]): + def __init__(self, pos, frames, groups, z=WORLD_LAYERS["main"]): self.frame_index, self.frames = 0, frames super().__init__(pos, frames[self.frame_index], groups, z) diff --git a/src/states/game_running.py b/src/states/game_running.py index 3edaa62..ed05328 100644 --- a/src/states/game_running.py +++ b/src/states/game_running.py @@ -52,41 +52,69 @@ def setup(self, player_start_pos): self.world_frames = { "water": import_folder(".", "images", "tilesets", "temporary_water"), "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships") + "ships": all_character_import(".", "images", "tilesets", "ships"), } # Sea for x, y, surface in self.tmx_map["map"].get_layer_by_name("Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Water animated for obj in self.tmx_map["map"].get_layer_by_name("Water"): for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites((x, y), self.world_frames["water"], self.all_sprites, WORLD_LAYERS["water"]) + AnimatedSprites( + (x, y), + self.world_frames["water"], + self.all_sprites, + WORLD_LAYERS["water"], + ) # Shallow water - for x, y, surface in self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + for x, y, surface in ( + self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles() + ): + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Islands islands = self.tmx_map["map"].get_layer_by_name("Islands") for x, y, surface in islands.tiles(): - src.sprites.Sprite((x * TILE_SIZE, y * TILE_SIZE), surface, self.all_sprites, WORLD_LAYERS["bg"]) + src.sprites.Sprite( + (x * TILE_SIZE, y * TILE_SIZE), + surface, + self.all_sprites, + WORLD_LAYERS["bg"], + ) # Enitites for obj in self.tmx_map["map"].get_layer_by_name("Ships"): if obj.name == "Player" and obj.properties["pos"] == player_start_pos: self.player = src.sprites.Player( - pos = (obj.x, obj.y), - frames = self.world_frames["ships"]["player_test_ship"], - groups = self.all_sprites) + pos=(obj.x, obj.y), + frames=self.world_frames["ships"]["player_test_ship"], + groups=self.all_sprites, + ) # Coast for obj in self.tmx_map["map"].get_layer_by_name("Coast"): terrain = obj.properties["terrain"] side = obj.properties["side"] - AnimatedSprites((obj.x, obj.y), self.world_frames["coast"][terrain][side], self.all_sprites, WORLD_LAYERS["bg"]) + AnimatedSprites( + (obj.x, obj.y), + self.world_frames["coast"][terrain][side], + self.all_sprites, + WORLD_LAYERS["bg"], + ) def load_inventory_from_json(self, file_path: str): """Load initial inventory items from JSON file.""" @@ -117,8 +145,6 @@ def update(self, events) -> None: def render(self, screen) -> None: """draw sprites to the canvas""" screen.fill("#000000") - self.all_sprites.draw( - self.player.rect.center - ) + self.all_sprites.draw(self.player.rect.center) pygame.display.update() diff --git a/src/support.py b/src/support.py index 3b56490..b4086e8 100644 --- a/src/support.py +++ b/src/support.py @@ -3,78 +3,101 @@ from os import walk # from pytmx.util_pygame import load_pygame -# imports -def import_image(*path, alpha = True, format = 'png'): - full_path = join(*path) + f'.{format}' - surf = pygame.image.load(full_path).convert_alpha() if alpha else pygame.image.load(full_path).convert() - return surf + +# imports +def import_image(*path, alpha=True, format="png"): + full_path = join(*path) + f".{format}" + surf = ( + pygame.image.load(full_path).convert_alpha() + if alpha + else pygame.image.load(full_path).convert() + ) + return surf + def import_folder(*path): - frames = [] - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in sorted(image_names, key = lambda name: int(name.split('.')[0])): - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames.append(surf) - return frames + frames = [] + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in sorted(image_names, key=lambda name: int(name.split(".")[0])): + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames.append(surf) + return frames + def import_folder_dict(*path): - frames = {} - for folder_path, sub_folders, image_names in walk(join(*path)): - for image_name in image_names: - full_path = join(folder_path, image_name) - surf = pygame.image.load(full_path).convert_alpha() - frames[image_name.split('.')[0]] = surf - return frames + frames = {} + for folder_path, sub_folders, image_names in walk(join(*path)): + for image_name in image_names: + full_path = join(folder_path, image_name) + surf = pygame.image.load(full_path).convert_alpha() + frames[image_name.split(".")[0]] = surf + return frames + def import_sub_folders(*path): - frames = {} - for _, sub_folders, __ in walk(join(*path)): - if sub_folders: - for sub_folder in sub_folders: - frames[sub_folder] = import_folder(*path, sub_folder) - return frames + frames = {} + for _, sub_folders, __ in walk(join(*path)): + if sub_folders: + for sub_folder in sub_folders: + frames[sub_folder] = import_folder(*path, sub_folder) + return frames + def import_tilemap(cols, rows, *path): - frames = {} - surf = import_image(*path) - cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows - for col in range(cols): - for row in range(rows): - cutout_rect = pygame.Rect(col * cell_width, row * cell_height,cell_width,cell_height) - cutout_surf = pygame.Surface((cell_width, cell_height)) - cutout_surf.fill('green') - cutout_surf.set_colorkey('green') - cutout_surf.blit(surf, (0,0), cutout_rect) - frames[(col, row)] = cutout_surf - return frames + frames = {} + surf = import_image(*path) + cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows + for col in range(cols): + for row in range(rows): + cutout_rect = pygame.Rect( + col * cell_width, row * cell_height, cell_width, cell_height + ) + cutout_surf = pygame.Surface((cell_width, cell_height)) + cutout_surf.fill("green") + cutout_surf.set_colorkey("green") + cutout_surf.blit(surf, (0, 0), cutout_rect) + frames[(col, row)] = cutout_surf + return frames + def coast_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - terrains = ["sand"] - sides = { - 'topleft': (0,0), 'top': (1,0), 'topright': (2,0), - 'left': (0,1), 'right': (2,1), 'bottomleft': (0,2), - 'bottom': (1,2), 'bottomright': (2,2)} - for index, terrain in enumerate(terrains): - new_dict[terrain] = {} - for key, pos in sides.items(): - new_dict[terrain][key] = [frame_dict[(pos[0] + index * 3, pos[1] + row)] for row in range(0, rows, 3)] - return new_dict + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + terrains = ["sand"] + sides = { + "topleft": (0, 0), + "top": (1, 0), + "topright": (2, 0), + "left": (0, 1), + "right": (2, 1), + "bottomleft": (0, 2), + "bottom": (1, 2), + "bottomright": (2, 2), + } + for index, terrain in enumerate(terrains): + new_dict[terrain] = {} + for key, pos in sides.items(): + new_dict[terrain][key] = [ + frame_dict[(pos[0] + index * 3, pos[1] + row)] + for row in range(0, rows, 3) + ] + return new_dict + def character_importer(cols, rows, *path): - frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} - for row, direction in enumerate(("down", "left", "right", "up")): - new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] - new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] - return new_dict + frame_dict = import_tilemap(cols, rows, *path) + new_dict = {} + for row, direction in enumerate(("down", "left", "right", "up")): + new_dict[direction] = [frame_dict[(col, row)] for col in range(cols)] + new_dict[f"{direction}_idle"] = [frame_dict[(0, row)]] + return new_dict + def all_character_import(*path): - new_dict = {} - for _, _, image_names in walk(join(*path)): - for image in image_names: - image_name = image.split(".")[0] - new_dict[image_name] = character_importer(7, 4, *path, image_name) - return new_dict \ No newline at end of file + new_dict = {} + for _, _, image_names in walk(join(*path)): + for image in image_names: + image_name = image.split(".")[0] + new_dict[image_name] = character_importer(7, 4, *path, image_name) + return new_dict From 84b09d6091f780126d66a503b355b228d3c44df7 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Tue, 11 Mar 2025 23:42:17 +0100 Subject: [PATCH 08/20] add a globally check for imports and other important configs this will help by reducing to put in some things like ignore imports for mypy. import pygame is a good example for this, since the pip package is called pygame-ce it is still referred as pygame and mypy brakes on this for some reason. --- src/pyproject.toml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/pyproject.toml diff --git a/src/pyproject.toml b/src/pyproject.toml new file mode 100644 index 0000000..d392cef --- /dev/null +++ b/src/pyproject.toml @@ -0,0 +1,34 @@ +# Ruff Configuration: This section configures the Ruff linter +[tool.ruff] +line-length = 88 # Sets the maximum allowed line length (e.g., for PEP 8 compliance). +select = ["E", "F", "I"] # Specifies the linting rules to enable: + # - "E": pycodestyle errors (e.g., whitespace issues). + # - "F": pyflakes errors (e.g., undefined variables). + # - "I": import-related rules (e.g., sorting, unused imports). + +ignore = ["W503"] # Disables specific linting rules: + # - "W503": Ignore line break before a binary operator (PEP 8 allows both styles). + +fixable = ["F", "I"] # Specifies which issues Ruff can automatically fix: + # - "F": pyflakes fixes (e.g., unused variables). + # - "I": import fixes (e.g., sorting, removing unused imports). + +exclude = ["build/", # Directories to exclude from linting: + "dist/", # - "build/": Temporary build files. + ".venv/"] # - ".venv/": Virtual environment directory. + +# Mypy Configuration: This section configures the Mypy type checker +[tool.mypy] +python_version = "3.12" # Specifies the Python version used in the project for type checking. +strict = true # Enables "strict mode," which applies several stricter type-checking rules: + # - Disallows untyped function definitions and calls. + # - Enforces type annotations more rigorously. + +ignore_missing_imports = true # Suppresses errors for missing type stubs in third-party libraries. + # (e.g., when libraries like `pygame` lack official type annotations). + +disallow_untyped_calls = true # Prohibits calls to functions without type annotations. + +disallow_untyped_defs = true # Prohibits defining functions without type annotations. + +check_untyped_defs = true # Checks the bodies of functions that lack type annotations for type errors. From 01fbd58c8bd2231180234018eeeb3c44568e731f Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Tue, 11 Mar 2025 23:43:59 +0100 Subject: [PATCH 09/20] adds a message about ignoring some imports this adds the message in settings.py why some imports are not type checked because of, see previous commit. --- src/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings.py b/src/settings.py index 8eab317..d9fa6a6 100644 --- a/src/settings.py +++ b/src/settings.py @@ -11,6 +11,7 @@ FPS = 60 +# For some imports like pygame.freetype, Mypy can't infer the type of this attribute, so we suppress the error. if not getattr(pygame, "IS_CE", False): raise ImportError( "The game requires Pygame CE to function. " From 1bd2bf7e7b832d92ee564e4557936973ba006fec Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Tue, 11 Mar 2025 23:55:05 +0100 Subject: [PATCH 10/20] fixing ruff configs --- src/pyproject.toml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/pyproject.toml b/src/pyproject.toml index d392cef..04c7682 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -6,9 +6,6 @@ select = ["E", "F", "I"] # Specifies the linting rules to enable: # - "F": pyflakes errors (e.g., undefined variables). # - "I": import-related rules (e.g., sorting, unused imports). -ignore = ["W503"] # Disables specific linting rules: - # - "W503": Ignore line break before a binary operator (PEP 8 allows both styles). - fixable = ["F", "I"] # Specifies which issues Ruff can automatically fix: # - "F": pyflakes fixes (e.g., unused variables). # - "I": import fixes (e.g., sorting, removing unused imports). @@ -18,17 +15,14 @@ exclude = ["build/", # Directories to exclude from linting: ".venv/"] # - ".venv/": Virtual environment directory. # Mypy Configuration: This section configures the Mypy type checker -[tool.mypy] -python_version = "3.12" # Specifies the Python version used in the project for type checking. -strict = true # Enables "strict mode," which applies several stricter type-checking rules: - # - Disallows untyped function definitions and calls. - # - Enforces type annotations more rigorously. -ignore_missing_imports = true # Suppresses errors for missing type stubs in third-party libraries. - # (e.g., when libraries like `pygame` lack official type annotations). - -disallow_untyped_calls = true # Prohibits calls to functions without type annotations. +[tool.mypy] +python_version = "3.12" # Use Python 3.12 for type checking +strict = true # Enable strict type checking +ignore_missing_imports = true # Ignore missing stubs (e.g., for pygame) +disallow_untyped_calls = true # Disallow calling untyped functions +disallow_untyped_defs = true # Disallow defining functions without type annotations +check_untyped_defs = true # Check the bodies of untyped functions -disallow_untyped_defs = true # Prohibits defining functions without type annotations. +[mypy-pygame.*] # Suppresses errors for missing type stubs in the pygame package. -check_untyped_defs = true # Checks the bodies of functions that lack type annotations for type errors. From 9a5f0dfb949aae88ab8f892cbb7be050b6addb63 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Tue, 11 Mar 2025 23:57:37 +0100 Subject: [PATCH 11/20] forgot missing imports line --- src/pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyproject.toml b/src/pyproject.toml index 04c7682..c6fede1 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -24,5 +24,6 @@ disallow_untyped_calls = true # Disallow calling untyped functions disallow_untyped_defs = true # Disallow defining functions without type annotations check_untyped_defs = true # Check the bodies of untyped functions -[mypy-pygame.*] # Suppresses errors for missing type stubs in the pygame package. - +# Suppresses errors for missing type stubs in the pygame package. +[mypy-pygame.*] +ignore_missing_imports = true From c5b91ed287d36efd9718c53669aa2558fc902f05 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 00:00:12 +0100 Subject: [PATCH 12/20] adding double "" --- src/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyproject.toml b/src/pyproject.toml index c6fede1..b1bcb22 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -25,5 +25,5 @@ disallow_untyped_defs = true # Disallow defining functions without type annotat check_untyped_defs = true # Check the bodies of untyped functions # Suppresses errors for missing type stubs in the pygame package. -[mypy-pygame.*] +["mypy-pygame.*"] ignore_missing_imports = true From ae5343aff1e507f69a1a0cb4394f4c1437d4ae9b Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 00:01:41 +0100 Subject: [PATCH 13/20] move toml file to root directory --- src/pyproject.toml => pyproject.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pyproject.toml => pyproject.toml (100%) diff --git a/src/pyproject.toml b/pyproject.toml similarity index 100% rename from src/pyproject.toml rename to pyproject.toml From ec42a8472bd743a221f78751b02139330fbc7453 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 01:03:39 +0100 Subject: [PATCH 14/20] fix correct toml config line length is 120 correct ruff commands added removed mypy commands no longer needed --- pyproject.toml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b1bcb22..30fe0af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ # Ruff Configuration: This section configures the Ruff linter [tool.ruff] -line-length = 88 # Sets the maximum allowed line length (e.g., for PEP 8 compliance). -select = ["E", "F", "I"] # Specifies the linting rules to enable: +line-length = 120 +lint.select = ["E", "F", "I"] # Specifies the linting rules to enable: # - "E": pycodestyle errors (e.g., whitespace issues). # - "F": pyflakes errors (e.g., undefined variables). # - "I": import-related rules (e.g., sorting, unused imports). -fixable = ["F", "I"] # Specifies which issues Ruff can automatically fix: +lint.fixable = ["F", "I"] # Specifies which issues Ruff can automatically fix: # - "F": pyflakes fixes (e.g., unused variables). # - "I": import fixes (e.g., sorting, removing unused imports). @@ -14,16 +14,10 @@ exclude = ["build/", # Directories to exclude from linting: "dist/", # - "build/": Temporary build files. ".venv/"] # - ".venv/": Virtual environment directory. -# Mypy Configuration: This section configures the Mypy type checker - +# Suppresses errors for missing type stubs in the pygame package. [tool.mypy] python_version = "3.12" # Use Python 3.12 for type checking -strict = true # Enable strict type checking -ignore_missing_imports = true # Ignore missing stubs (e.g., for pygame) -disallow_untyped_calls = true # Disallow calling untyped functions -disallow_untyped_defs = true # Disallow defining functions without type annotations -check_untyped_defs = true # Check the bodies of untyped functions +check_untyped_defs = true -# Suppresses errors for missing type stubs in the pygame package. ["mypy-pygame.*"] ignore_missing_imports = true From 59ba4fa8d67f0cabdd36f8cb3479424ab2696548 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 01:04:33 +0100 Subject: [PATCH 15/20] fix ruff errors when running the commands from the readme it will make it more clear what needs to be changed and where exactly --- main.py | 1 - src/GUI/gameloop.py | 308 +++++++++++++++++++------------------ src/game_manager.py | 3 +- src/inventory.py | 20 +-- src/inventory_gui.py | 30 ++-- src/settings.py | 4 +- src/sprites.py | 53 ++++--- src/states/base_state.py | 1 + src/states/game_running.py | 26 ++-- src/states/paused.py | 34 ++-- src/support.py | 23 +-- tests/test_inventory.py | 9 +- 12 files changed, 239 insertions(+), 273 deletions(-) diff --git a/main.py b/main.py index e781854..2f48b5d 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,6 @@ # import Pygame specific objects, functions and functionality from src.game_manager import GameStateManager - if __name__ == "__main__": game = GameStateManager() game.run() diff --git a/src/GUI/gameloop.py b/src/GUI/gameloop.py index 8b6d493..d30f12f 100644 --- a/src/GUI/gameloop.py +++ b/src/GUI/gameloop.py @@ -1,153 +1,155 @@ -from os.path import join -import sys - -# import dataclasses and typchecking -from dataclasses import dataclass, field - -# import pygame related -import pygame -from pytmx.util_pygame import load_pygame # type: ignore - -# import Pygame specific objects, functions and functionality -from src.settings import SCREEN_WIDTH, SCREEN_HEIGHT, TILE_SIZE, WORLD_LAYERS -from src.support import import_folder, coast_importer, all_character_import -import src.sprites -from src.sprites import AnimatedSprites - - -@dataclass -class GUI: - """Graphial User Interface vertion of the game, using pygame-ce""" - - screen_size: tuple[int, int] = (SCREEN_WIDTH, SCREEN_HEIGHT) - screen: pygame.Surface = field(init=False) - - # groups - # all_sprites: pygame.sprite.Group = field( - # init=False, default_factory=pygame.sprite.Group - # ) - - def __post_init__(self): - pygame.init() - self.screen = pygame.display.set_mode(self.screen_size) - pygame.display.set_caption("PySeas") - self.clock = pygame.Clock() - - # self.players: list[src.sprites.Player] = [src.sprites.Player()] - - self.all_sprites = src.sprites.AllSprites() - self.running = True - self.import_assets() - self.setup( - tmx_maps=self.tmx_map["map"], player_start_pos="top_left_island" - ) # The start positions will be one of the 4 islands in the corners of the board - self.camera_mode = "drag" - - def import_assets(self): - """load the map""" - # The map was made as a basic start for the game, it can be changes or altered if it is better for the overall flow of the game - self.tmx_map = { - "map": load_pygame(join(".", "data", "new_maps", "100x100_map.tmx")) - } - - self.world_frames = { - "water": import_folder(".", "images", "tilesets", "water"), - "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), - "ships": all_character_import(".", "images", "tilesets", "ships"), - } - # print(self.world_frames["ships"]) - - def setup(self, tmx_maps, player_start_pos): - """create tiles""" - - # Sea - for x, y, surface in tmx_maps.get_layer_by_name("Sea").tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) - - # Water animated - for obj in tmx_maps.get_layer_by_name("Water"): - for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): - for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): - AnimatedSprites( - (x, y), - self.world_frames["water"], - self.all_sprites, - WORLD_LAYERS["water"], - ) - - # Shallow water - for x, y, surface in tmx_maps.get_layer_by_name("Shallow Sea").tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) - - # Islands - islands = tmx_maps.get_layer_by_name("Islands") - for x, y, surface in islands.tiles(): - src.sprites.Sprite( - (x * TILE_SIZE, y * TILE_SIZE), - surface, - self.all_sprites, - WORLD_LAYERS["bg"], - ) - - # Enitites - for obj in tmx_maps.get_layer_by_name("Ships"): - if obj.name == "Player" and obj.properties["pos"] == player_start_pos: - self.player = src.sprites.Player( - pos=(obj.x, obj.y), - frames=self.world_frames["ships"]["player_test_ship"], - groups=self.all_sprites, - ) - - # Coast - for obj in tmx_maps.get_layer_by_name("Coast"): - terrain = obj.properties["terrain"] - side = obj.properties["side"] - AnimatedSprites( - (obj.x, obj.y), - self.world_frames["coast"][terrain][side], - self.all_sprites, - WORLD_LAYERS["bg"], - ) - - def run(self) -> None: - """main loop of the game""" - while self.running: - self.handle_events() - self.render() - - def handle_events(self) -> None: - """get events like keypress or mouse clicks""" - for event in pygame.event.get(): - match event.type: - case pygame.QUIT: - pygame.quit() - sys.exit() - - def render(self) -> None: - """draw sprites to the canvas""" - dt = self.clock.tick() / 1000 - self.screen.fill("#000000") - - self.all_sprites.update(dt) - self.all_sprites.draw( - self.player.rect.center, - self.player.player_preview, - self.player.player_preview_rect, - ) - - """No need to loop through the players because it is now in the sprite group AllSprites""" - # draw players on top of the other sprites - # for player in self.players: - # player.render(surface=self.screen) - - pygame.display.update() +# import sys + +# # import dataclasses and typchecking +# from dataclasses import dataclass, field +# from os.path import join + +# # import pygame related +# import pygame +# from pytmx.util_pygame import load_pygame # type: ignore + +# import src.sprites + +# # import Pygame specific objects, functions and functionality +# from src.settings import SCREEN_HEIGHT, SCREEN_WIDTH, TILE_SIZE, WORLD_LAYERS +# from src.sprites import AnimatedSprites +# from src.support import all_character_import, coast_importer, import_folder + + +# @dataclass +# class GUI: +# """Graphial User Interface vertion of the game, using pygame-ce""" + +# screen_size: tuple[int, int] = (SCREEN_WIDTH, SCREEN_HEIGHT) +# screen: pygame.Surface = field(init=False) + +# # groups +# # all_sprites: pygame.sprite.Group = field( +# # init=False, default_factory=pygame.sprite.Group +# # ) + +# def __post_init__(self): +# pygame.init() +# self.screen = pygame.display.set_mode(self.screen_size) +# pygame.display.set_caption("PySeas") +# self.clock = pygame.Clock() + +# # self.players: list[src.sprites.Player] = [src.sprites.Player()] + +# self.all_sprites = src.sprites.AllSprites() +# self.running = True +# self.import_assets() +# self.setup( +# tmx_maps=self.tmx_map["map"], player_start_pos="top_left_island" +# ) # The start positions will be one of the 4 islands in the corners of the board +# self.camera_mode = "drag" + +# def import_assets(self): +# """load the map""" +# # The map was made as a basic start for the game, it can be changes or altered if it is better for the overall +# flow of the game +# self.tmx_map = { +# "map": load_pygame(join(".", "data", "new_maps", "100x100_map.tmx")) +# } + +# self.world_frames = { +# "water": import_folder(".", "images", "tilesets", "water"), +# "coast": coast_importer(6, 6, ".", "images", "tilesets", "coast"), +# "ships": all_character_import(".", "images", "tilesets", "ships"), +# } +# # print(self.world_frames["ships"]) + +# def setup(self, tmx_maps, player_start_pos): +# """create tiles""" + +# # Sea +# for x, y, surface in tmx_maps.get_layer_by_name("Sea").tiles(): +# src.sprites.Sprite( +# (x * TILE_SIZE, y * TILE_SIZE), +# surface, +# self.all_sprites, +# WORLD_LAYERS["bg"], +# ) + +# # Water animated +# for obj in tmx_maps.get_layer_by_name("Water"): +# for x in range(int(obj.x), int(obj.x + obj.width), TILE_SIZE): +# for y in range(int(obj.y), int(obj.y + obj.height), TILE_SIZE): +# AnimatedSprites( +# (x, y), +# self.world_frames["water"], +# self.all_sprites, +# WORLD_LAYERS["water"], +# ) + +# # Shallow water +# for x, y, surface in tmx_maps.get_layer_by_name("Shallow Sea").tiles(): +# src.sprites.Sprite( +# (x * TILE_SIZE, y * TILE_SIZE), +# surface, +# self.all_sprites, +# WORLD_LAYERS["bg"], +# ) + +# # Islands +# islands = tmx_maps.get_layer_by_name("Islands") +# for x, y, surface in islands.tiles(): +# src.sprites.Sprite( +# (x * TILE_SIZE, y * TILE_SIZE), +# surface, +# self.all_sprites, +# WORLD_LAYERS["bg"], +# ) + +# # Enitites +# for obj in tmx_maps.get_layer_by_name("Ships"): +# if obj.name == "Player" and obj.properties["pos"] == player_start_pos: +# self.player = src.sprites.Player( +# pos=(obj.x, obj.y), +# frames=self.world_frames["ships"]["player_test_ship"], +# groups=self.all_sprites, +# ) + +# # Coast +# for obj in tmx_maps.get_layer_by_name("Coast"): +# terrain = obj.properties["terrain"] +# side = obj.properties["side"] +# AnimatedSprites( +# (obj.x, obj.y), +# self.world_frames["coast"][terrain][side], +# self.all_sprites, +# WORLD_LAYERS["bg"], +# ) + +# def run(self) -> None: +# """main loop of the game""" +# while self.running: +# self.handle_events() +# self.render() + +# def handle_events(self) -> None: +# """get events like keypress or mouse clicks""" +# for event in pygame.event.get(): +# match event.type: +# case pygame.QUIT: +# pygame.quit() +# sys.exit() + +# def render(self) -> None: +# """draw sprites to the canvas""" +# dt = self.clock.tick() / 1000 +# self.screen.fill("#000000") + +# self.all_sprites.update(dt) +# self.all_sprites.draw( +# self.player.rect.center, +# self.player.player_preview, +# self.player.player_preview_rect, +# ) + +# """No need to loop through the players because it is now in the sprite group AllSprites""" +# # draw players on top of the other sprites +# # for player in self.players: +# # player.render(surface=self.screen) + +# pygame.display.update() diff --git a/src/game_manager.py b/src/game_manager.py index a02072f..fc8984d 100644 --- a/src/game_manager.py +++ b/src/game_manager.py @@ -4,9 +4,10 @@ """ import sys + import pygame -from src.settings import SCREEN_WIDTH, SCREEN_HEIGHT, FPS +from src.settings import FPS, SCREEN_HEIGHT, SCREEN_WIDTH # import basestate for typehint from src.states.base_state import BaseState diff --git a/src/inventory.py b/src/inventory.py index e1591c1..5cd5d31 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -39,14 +39,10 @@ def add_item(self, item_name: str, quantity: int) -> str: """Add an item to the inventory""" if item_name in self.items: self.items[item_name] += quantity - return get_message( - "inventory", "add_success", item=item_name, quantity=quantity - ) + return get_message("inventory", "add_success", item=item_name, quantity=quantity) else: self.items[item_name] = quantity - return get_message( - "inventory", "add_success", item=item_name, quantity=quantity - ) + return get_message("inventory", "add_success", item=item_name, quantity=quantity) def remove_item(self, item_name: str, quantity: int) -> str: """Remove an item from the inventory. Return True if successful.""" @@ -54,18 +50,12 @@ def remove_item(self, item_name: str, quantity: int) -> str: self.items[item_name] -= quantity if self.items[item_name] == 0: del self.items[item_name] - return get_message( - "inventory", "remove_success", item=item_name, quantity=quantity - ) - return get_message( - "inventory", "remove_fail", item=item_name, quantity=quantity - ) + return get_message("inventory", "remove_success", item=item_name, quantity=quantity) + return get_message("inventory", "remove_fail", item=item_name, quantity=quantity) def use_item(self, item_name: str) -> str: """Use an item, applying its effect. Return a message.""" - if self.remove_item(item_name, 1) == get_message( - "inventory", "remove_success", item=item_name, quantity=1 - ): + if self.remove_item(item_name, 1) == get_message("inventory", "remove_success", item=item_name, quantity=1): return get_message("inventory", "use_success", item=item_name) return get_message("inventory", "use_fail", item=item_name) diff --git a/src/inventory_gui.py b/src/inventory_gui.py index abb1776..72ff5d8 100644 --- a/src/inventory_gui.py +++ b/src/inventory_gui.py @@ -1,5 +1,7 @@ from typing import Dict, Tuple + import pygame + from src.inventory import Inventory @@ -20,9 +22,7 @@ def __init__(self, screen: pygame.Surface, inventory: Inventory) -> None: # Load sprite sheet and extract the icons (Testing purposes) # To be replaced when: # 1) Spritesheet has been decide. 2) A 'Buy', 'Found' or 'Add' in-game feature has been implemented - self.sprite_sheet = pygame.image.load( - "images/tilesets/Treasure+.png" - ).convert_alpha() + self.sprite_sheet = pygame.image.load("images/tilesets/Treasure+.png").convert_alpha() self.icons = { "Gold Coin": self.extract_icon(0, 0), "Silver Coin": self.extract_icon(16, 0), @@ -83,23 +83,17 @@ def handle_events(self, event): if event.type == pygame.MOUSEWHEEL: # Adjust scroll offset self.scroll_offset = max(0, self.scroll_offset - event.y) - max_offset = max( - 0, len(self.inventory.get_items()) - self.max_visible_items - ) + max_offset = max(0, len(self.inventory.get_items()) - self.max_visible_items) self.scroll_offset = min(self.scroll_offset, max_offset) def extract_icon(self, x, y, size=16): """Extract a single icon from the sprite sheet.""" return self.sprite_sheet.subsurface((x, y, size, size)) - def draw_buttons( - self, x: int, y: int, item: str - ) -> Tuple[pygame.Rect, pygame.Rect]: + def draw_buttons(self, x: int, y: int, item: str) -> Tuple[pygame.Rect, pygame.Rect]: """Draw Use and Discard buttons for a specific item.""" use_button = pygame.Rect(x, y, self.button_width, self.button_height) - discard_button = pygame.Rect( - x + self.button_width + 10, y, self.button_width, self.button_height - ) + discard_button = pygame.Rect(x + self.button_width + 10, y, self.button_width, self.button_height) pygame.draw.rect(self.screen, (0, 255, 0), use_button) # Green pygame.draw.rect(self.screen, (150, 75, 0), discard_button) # Brown @@ -121,9 +115,7 @@ def draw(self): # Draw the inventory items items = list(self.inventory.get_items().items()) - visible_items = items[ - self.scroll_offset : self.scroll_offset + self.max_visible_items - ] + visible_items = items[self.scroll_offset : self.scroll_offset + self.max_visible_items] y_offset = 50 # Start below the title for item, quantity in visible_items: @@ -147,9 +139,7 @@ def draw(self): y_offset += 60 # Move down for the next item # Draw hint - hint_text = self.font.render( - "Press 'I' to close inventory", True, (200, 200, 200) - ) # Light gray text + hint_text = self.font.render("Press 'I' to close inventory", True, (200, 200, 200)) # Light gray text self.screen.blit(hint_text, (50, self.screen.get_height() - 60)) # Display action message above the hint @@ -183,9 +173,7 @@ def handle_mouse_click(self, mouse_pos) -> None: """Handle mouse clicks on buttons.""" for item, (use_button, discard_button) in self.button_actions.items(): if use_button.collidepoint(mouse_pos): - self.message = self.inventory.use_item( - item - ) # `self.message` stores strings + self.message = self.inventory.use_item(item) # `self.message` stores strings self.message_end_time = pygame.time.get_ticks() + 3000 # 3 seconds elif discard_button.collidepoint(mouse_pos): self.message = self.inventory.remove_item(item, 1) diff --git a/src/settings.py b/src/settings.py index d9fa6a6..97b2a72 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,5 +1,6 @@ import sys import warnings + import pygame import pygame.freetype @@ -14,8 +15,7 @@ # For some imports like pygame.freetype, Mypy can't infer the type of this attribute, so we suppress the error. if not getattr(pygame, "IS_CE", False): raise ImportError( - "The game requires Pygame CE to function. " - "(hint: type pip uninstall pygame and then pip install pygame-ce)" + "The game requires Pygame CE to function. (hint: type pip uninstall pygame and then pip install pygame-ce)" ) if sys.version_info < (3, 12): diff --git a/src/sprites.py b/src/sprites.py index 8bbcb41..d3aa27b 100644 --- a/src/sprites.py +++ b/src/sprites.py @@ -2,14 +2,15 @@ import pygame from pygame import FRect + +from src.inventory import Inventory from src.settings import ( - TILE_SIZE, + ANIMATION_SPEED, SCREEN_HEIGHT, SCREEN_WIDTH, - ANIMATION_SPEED, + TILE_SIZE, WORLD_LAYERS, ) -from src.inventory import Inventory class Entity(pygame.sprite.Sprite): @@ -42,7 +43,7 @@ def get_state(self): self.facing_direction = "right" if self.direction.x > 0 else "left" if self.direction.y != 0: self.facing_direction = "down" if self.direction.x > 0 else "up" - return f"{self.facing_direction}{"" if moving else "_idle"}" + return f"{self.facing_direction}{'' if moving else '_idle'}" class AllSprites(pygame.sprite.Group): @@ -59,19 +60,19 @@ def __init__(self): self.scale = 2.0 def draw(self, player_center): + # Calculate offsets self.offset.x = -(player_center[0] * self.scale - SCREEN_WIDTH / 2) self.offset.y = -(player_center[1] * self.scale - SCREEN_HEIGHT / 2) - background_sprites = [ - sprite for sprite in self if sprite.z < WORLD_LAYERS["main"] - ] + # Separate sprites into layers + background_sprites = [sprite for sprite in self if sprite.z < WORLD_LAYERS["main"]] main_sprites = [sprite for sprite in self if sprite.z == WORLD_LAYERS["main"]] - foreground_sprites = [ - sprite for sprite in self if sprite.z > WORLD_LAYERS["main"] - ] + foreground_sprites = [sprite for sprite in self if sprite.z > WORLD_LAYERS["main"]] + # Render each layer for layer in (background_sprites, main_sprites, foreground_sprites): for sprite in layer: + # Scale the image scaled_image = pygame.transform.scale( sprite.image, ( @@ -79,20 +80,31 @@ def draw(self, player_center): int(sprite.rect.height * self.scale), ), ) + + # Adjust the rect to the new scale scaled_rect = scaled_image.get_rect( center=( - sprite.rect.center[0] * self.scale, - sprite.rect.center[1] * self.scale, + int(sprite.rect.center[0] * self.scale), + int(sprite.rect.center[1] * self.scale), ) ) - scaled_rect.topleft += self.offset + # Add offset to the rect position + scaled_rect.topleft = ( + scaled_rect.topleft[0] + int(self.offset.x), + scaled_rect.topleft[1] + int(self.offset.y), + ) + + # Ensure display_surface is valid before blitting + if self.display_surface is None: + raise ValueError("self.display_surface cannot be None") self.display_surface.blit(scaled_image, scaled_rect.topleft) # scaling of the ghost preview # scaled_preview = pygame.transform.scale(player_preview, - # (int(player_preview_rect.width * self.scale), int(player_preview_rect.height * self.scale))) - # scaled_preview_rect = scaled_preview.get_rect(center=(player_preview_rect.center[0] * self.scale, player_preview_rect.center[1] * self.scale)) + # (int(player_preview_rect.width * self.scale), int(player_preview_rect.height * self.scale))) + # scaled_preview_rect = scaled_preview.get_rect(center=( + # player_preview_rect.center[0] * self.scale, player_preview_rect.center[1] * self.scale)) # scaled_preview_rect.topleft += self.offset # self.display_surface.blit(scaled_preview, scaled_preview_rect.topleft) @@ -110,13 +122,16 @@ class Player(Entity): def __init__(self, pos, frames, groups): super().__init__(pos, frames, groups) + # Ensure self.image is not None + if self.image is None: + raise ValueError("self.image cannot be None") + # ghost preview self.player_preview = self.image.copy() self.player_preview.set_alpha(128) self.inventory = Inventory() self.mouse_have_been_pressed: bool = False - self.draggin = False self.offset_x = 0 self.offset_y = 0 @@ -228,16 +243,14 @@ def __init__( self.image: pygame.Surface = self.surf self.rect: pygame.FRect = self.image.get_frect(topleft=self.pos) - def draw( - self, display_surface: pygame.Surface, offset: tuple[float, float] = (0, 0) - ) -> None: + def draw(self, display_surface: pygame.Surface, offset: tuple[float, float] = (0, 0)) -> None: """Could be useful for a camera?""" offset_rect = self.rect.move(offset) display_surface.blit(self.image, offset_rect) class Sprite(pygame.sprite.Sprite): - def __init__(self, pos, surf, groups, z=WORLD_LAYERS["main"]): + def __init__(self, pos: tuple[int, int], surf: pygame.Surface, groups, z=WORLD_LAYERS["main"]): super().__init__(groups) self.image = surf self.rect = self.image.get_frect(topleft=pos) diff --git a/src/states/base_state.py b/src/states/base_state.py index 94c892e..14a32ec 100644 --- a/src/states/base_state.py +++ b/src/states/base_state.py @@ -6,6 +6,7 @@ """ from abc import ABC, abstractmethod + import pygame diff --git a/src/states/game_running.py b/src/states/game_running.py index ed05328..9db1919 100644 --- a/src/states/game_running.py +++ b/src/states/game_running.py @@ -2,19 +2,19 @@ Represents the GameRunning state, where the player controls a ship and interacts with the game world. """ -import os import json +import os + import pygame from pytmx.util_pygame import load_pygame # type: ignore -from src.states.base_state import BaseState -from src.states.paused import Paused +import src.sprites from src.inventory import Inventory -from src.support import import_folder, coast_importer, all_character_import -from src.sprites import AnimatedSprites - from src.settings import TILE_SIZE, WORLD_LAYERS -import src.sprites +from src.sprites import AnimatedSprites +from src.states.base_state import BaseState +from src.states.paused import Paused +from src.support import all_character_import, coast_importer, import_folder class GameRunning(BaseState): @@ -45,9 +45,7 @@ def setup(self, player_start_pos): """ setup the map and player from the tiled file """ - self.tmx_map = { - "map": load_pygame(os.path.join(".", "data", "new_maps", "100x100_map.tmx")) - } + self.tmx_map = {"map": load_pygame(os.path.join(".", "data", "new_maps", "100x100_map.tmx"))} self.world_frames = { "water": import_folder(".", "images", "tilesets", "temporary_water"), @@ -76,9 +74,7 @@ def setup(self, player_start_pos): ) # Shallow water - for x, y, surface in ( - self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles() - ): + for x, y, surface in self.tmx_map["map"].get_layer_by_name("Shallow Sea").tiles(): src.sprites.Sprite( (x * TILE_SIZE, y * TILE_SIZE), surface, @@ -138,9 +134,7 @@ def update(self, events) -> None: for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_i: # Toggle inventory with "I" key - self.game_state_manager.enter_state( - Paused(self.game_state_manager, self.player_inventory) - ) + self.game_state_manager.enter_state(Paused(self.game_state_manager, self.player_inventory)) def render(self, screen) -> None: """draw sprites to the canvas""" diff --git a/src/states/paused.py b/src/states/paused.py index 5711b96..ab77c05 100644 --- a/src/states/paused.py +++ b/src/states/paused.py @@ -6,10 +6,10 @@ from typing import Dict, Tuple import pygame -from src.states.base_state import BaseState -from src.inventory import Inventory # for typehints -from src.settings import SCREEN_WIDTH, SCREEN_HEIGHT +from src.inventory import Inventory # for typehints +from src.settings import SCREEN_HEIGHT, SCREEN_WIDTH +from src.states.base_state import BaseState class Paused(BaseState): @@ -37,9 +37,7 @@ def __init__(self, game_state_manager, inventory: Inventory) -> None: # To be replaced when: # 1) Spritesheet has been decide. # 2) A 'Buy', 'Found' or 'Add' in-game feature has been implemented - self.sprite_sheet = pygame.image.load( - "images/tilesets/Treasure+.png" - ).convert_alpha() + self.sprite_sheet = pygame.image.load("images/tilesets/Treasure+.png").convert_alpha() self.icons = { "Gold Coin": self.extract_icon(0, 0), "Silver Coin": self.extract_icon(16, 0), @@ -99,9 +97,7 @@ def handle_mouse_click(self, mouse_pos) -> None: """Handle mouse clicks on buttons.""" for item, (use_button, discard_button) in self.button_actions.items(): if use_button.collidepoint(mouse_pos): - self.message = self.inventory.use_item( - item - ) # `self.message` stores strings + self.message = self.inventory.use_item(item) # `self.message` stores strings self.message_end_time = pygame.time.get_ticks() + 3000 # 3 seconds elif discard_button.collidepoint(mouse_pos): self.message = self.inventory.remove_item(item, 1) @@ -111,14 +107,10 @@ def extract_icon(self, x, y, size=16): """Extract a single icon from the sprite sheet.""" return self.sprite_sheet.subsurface((x, y, size, size)) - def draw_buttons( - self, x: int, y: int, item: str - ) -> Tuple[pygame.Rect, pygame.Rect]: + def draw_buttons(self, x: int, y: int, item: str) -> Tuple[pygame.Rect, pygame.Rect]: """Draw Use and Discard buttons for a specific item.""" use_button = pygame.Rect(x, y, self.button_width, self.button_height) - discard_button = pygame.Rect( - x + self.button_width + 10, y, self.button_width, self.button_height - ) + discard_button = pygame.Rect(x + self.button_width + 10, y, self.button_width, self.button_height) pygame.draw.rect(self.screen, (0, 255, 0), use_button) # Green pygame.draw.rect(self.screen, (150, 75, 0), discard_button) # Brown @@ -145,9 +137,7 @@ def update(self, events): self.handle_mouse_click(event.pos) case pygame.MOUSEWHEEL: self.scroll_offset = max(0, self.scroll_offset - event.y) - max_offset = max( - 0, len(self.inventory.get_items()) - self.max_visible_items - ) + max_offset = max(0, len(self.inventory.get_items()) - self.max_visible_items) self.scroll_offset = min(self.scroll_offset, max_offset) def render(self, screen: pygame.Surface) -> None: @@ -160,9 +150,7 @@ def render(self, screen: pygame.Surface) -> None: # Draw the inventory items items = list(self.inventory.get_items().items()) - visible_items = items[ - self.scroll_offset : self.scroll_offset + self.max_visible_items - ] + visible_items = items[self.scroll_offset : self.scroll_offset + self.max_visible_items] y_offset = 50 # Start below the title for item, quantity in visible_items: @@ -186,9 +174,7 @@ def render(self, screen: pygame.Surface) -> None: y_offset += 60 # Move down for the next item # Draw hint - hint_text = self.font.render( - "Press 'I' to close inventory", True, (200, 200, 200) - ) # Light gray text + hint_text = self.font.render("Press 'I' to close inventory", True, (200, 200, 200)) # Light gray text self.screen.blit(hint_text, (50, self.screen.get_height() - 60)) # Display action message above the hint diff --git a/src/support.py b/src/support.py index b4086e8..43881bf 100644 --- a/src/support.py +++ b/src/support.py @@ -1,17 +1,15 @@ -import pygame -from os.path import join from os import walk +from os.path import join + +import pygame + # from pytmx.util_pygame import load_pygame # imports def import_image(*path, alpha=True, format="png"): full_path = join(*path) + f".{format}" - surf = ( - pygame.image.load(full_path).convert_alpha() - if alpha - else pygame.image.load(full_path).convert() - ) + surf = pygame.image.load(full_path).convert_alpha() if alpha else pygame.image.load(full_path).convert() return surf @@ -50,9 +48,7 @@ def import_tilemap(cols, rows, *path): cell_width, cell_height = surf.get_width() / cols, surf.get_height() / rows for col in range(cols): for row in range(rows): - cutout_rect = pygame.Rect( - col * cell_width, row * cell_height, cell_width, cell_height - ) + cutout_rect = pygame.Rect(col * cell_width, row * cell_height, cell_width, cell_height) cutout_surf = pygame.Surface((cell_width, cell_height)) cutout_surf.fill("green") cutout_surf.set_colorkey("green") @@ -63,7 +59,7 @@ def import_tilemap(cols, rows, *path): def coast_importer(cols, rows, *path): frame_dict = import_tilemap(cols, rows, *path) - new_dict = {} + new_dict: dict[str, dict] = {} terrains = ["sand"] sides = { "topleft": (0, 0), @@ -78,10 +74,7 @@ def coast_importer(cols, rows, *path): for index, terrain in enumerate(terrains): new_dict[terrain] = {} for key, pos in sides.items(): - new_dict[terrain][key] = [ - frame_dict[(pos[0] + index * 3, pos[1] + row)] - for row in range(0, rows, 3) - ] + new_dict[terrain][key] = [frame_dict[(pos[0] + index * 3, pos[1] + row)] for row in range(0, rows, 3)] return new_dict diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 01649f7..7fc7afb 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -1,11 +1,12 @@ -import sys import os +import sys # Add the project root to sys.path to allow imports to work when running tests directly with `python`. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import unittest -from src.GUI.inventory import Inventory, Chest, Quest + +from src.inventory import Chest, Inventory, Quest class TestInventory(unittest.TestCase): @@ -33,9 +34,7 @@ def test_remove_item_success(self): self.inventory.add_item("Potion", 3) result = self.inventory.remove_item("Potion", 2) self.assertEqual(self.inventory.items, {"Potion": 1}) - self.assertEqual( - result, "Successfully removed 2 Potion(s) from your inventory." - ) + self.assertEqual(result, "Successfully removed 2 Potion(s) from your inventory.") def test_remove_item_fail(self): """Test failing to remove an item not in inventory or insufficient quantity.""" From ced4706ba6af98c9311f3f89a547b1cb9845dcdf Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 01:09:26 +0100 Subject: [PATCH 16/20] Update pyproject.toml --- pyproject.toml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 30fe0af..45c1f7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,13 @@ -# Ruff Configuration: This section configures the Ruff linter [tool.ruff] line-length = 120 -lint.select = ["E", "F", "I"] # Specifies the linting rules to enable: - # - "E": pycodestyle errors (e.g., whitespace issues). - # - "F": pyflakes errors (e.g., undefined variables). - # - "I": import-related rules (e.g., sorting, unused imports). +lint.select = ["E", "F", "I"] +lint.fixable = ["F", "I"] +exclude = ["build/", "dist/", ".venv/"] -lint.fixable = ["F", "I"] # Specifies which issues Ruff can automatically fix: - # - "F": pyflakes fixes (e.g., unused variables). - # - "I": import fixes (e.g., sorting, removing unused imports). - -exclude = ["build/", # Directories to exclude from linting: - "dist/", # - "build/": Temporary build files. - ".venv/"] # - ".venv/": Virtual environment directory. - -# Suppresses errors for missing type stubs in the pygame package. [tool.mypy] -python_version = "3.12" # Use Python 3.12 for type checking +python_version = "3.12" check_untyped_defs = true +ignore_missing_imports = true ["mypy-pygame.*"] ignore_missing_imports = true From 24d0d8743ecc79f5a45654a9625579cacbf7cd3a Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 01:20:03 +0100 Subject: [PATCH 17/20] fix error in base state when importing states from different files, mypy didn't like it so no it should be ignored by the toml config --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 45c1f7a..063326e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ exclude = ["build/", "dist/", ".venv/"] python_version = "3.12" check_untyped_defs = true ignore_missing_imports = true +explicit_package_bases = true ["mypy-pygame.*"] ignore_missing_imports = true From 41b6552dc8ee2191d34406f48689b010b404ab32 Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 01:20:45 +0100 Subject: [PATCH 18/20] add type ignore for safety reasons i added # type: ignore to not let mypy typecheck pygame imports --- src/game_manager.py | 2 +- src/inventory_gui.py | 2 +- src/settings.py | 4 ++-- src/sprites.py | 2 +- src/states/base_state.py | 2 +- src/states/game_running.py | 2 +- src/states/paused.py | 2 +- src/support.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/game_manager.py b/src/game_manager.py index fc8984d..a3fc152 100644 --- a/src/game_manager.py +++ b/src/game_manager.py @@ -5,7 +5,7 @@ import sys -import pygame +import pygame # type: ignore from src.settings import FPS, SCREEN_HEIGHT, SCREEN_WIDTH diff --git a/src/inventory_gui.py b/src/inventory_gui.py index 72ff5d8..4231309 100644 --- a/src/inventory_gui.py +++ b/src/inventory_gui.py @@ -1,6 +1,6 @@ from typing import Dict, Tuple -import pygame +import pygame # type: ignore from src.inventory import Inventory diff --git a/src/settings.py b/src/settings.py index 97b2a72..34f9b4f 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,8 +1,8 @@ import sys import warnings -import pygame -import pygame.freetype +import pygame # type: ignore +import pygame.freetype # type: ignore SCREEN_WIDTH, SCREEN_HEIGHT = 1280, 720 TILE_SIZE = 16 diff --git a/src/sprites.py b/src/sprites.py index d3aa27b..24ba2fd 100644 --- a/src/sprites.py +++ b/src/sprites.py @@ -1,6 +1,6 @@ """custom sprites classes""" -import pygame +import pygame # type: ignore from pygame import FRect from src.inventory import Inventory diff --git a/src/states/base_state.py b/src/states/base_state.py index 14a32ec..6217f6a 100644 --- a/src/states/base_state.py +++ b/src/states/base_state.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod -import pygame +import pygame # type: ignore class BaseState(ABC): diff --git a/src/states/game_running.py b/src/states/game_running.py index 9db1919..2faeff2 100644 --- a/src/states/game_running.py +++ b/src/states/game_running.py @@ -5,7 +5,7 @@ import json import os -import pygame +import pygame # type: ignore from pytmx.util_pygame import load_pygame # type: ignore import src.sprites diff --git a/src/states/paused.py b/src/states/paused.py index ab77c05..f95283d 100644 --- a/src/states/paused.py +++ b/src/states/paused.py @@ -5,7 +5,7 @@ from typing import Dict, Tuple -import pygame +import pygame # type: ignore from src.inventory import Inventory # for typehints from src.settings import SCREEN_HEIGHT, SCREEN_WIDTH diff --git a/src/support.py b/src/support.py index 43881bf..548302b 100644 --- a/src/support.py +++ b/src/support.py @@ -1,7 +1,7 @@ from os import walk from os.path import join -import pygame +import pygame # type: ignore # from pytmx.util_pygame import load_pygame From 01701e7468e1e88dd28459180da647f9f08f192a Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 11:52:56 +0100 Subject: [PATCH 19/20] Making virtual env instructions more clear This will help make the instructions for setting up pyceas via a virtual environment for multiple os's --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 993389a..6cad685 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ - [Game Design Document](#game-design-document) - [Why Join PyCeas?](#why-join-pyceas) - [Getting Started](#getting-started) + - [Clone the Repository](#clone-the-repository) + - [Install Python](#install-python) + - [Set up a virtual environment](#set-up-a-virtual-environment) + - [Install Required Software](#install-required-software) + - [Run the project](#run-the-project) + - [Deactivate the Virtual Environment:](#deactivate-the-virtual-environment) - [Reporting bugs \& requesting features](#reporting-bugs--requesting-features) - [Local Development](#local-development) - [Linting and Formatting for developers](#linting-and-formatting-for-developers) @@ -51,16 +57,54 @@ Every game needs thorough documentation, and you can find our Game Design Docume No need to worry if you are new to programming. This guide will walk you through the setup step by step. by the end, you'll have everything ready to run the PyCeas project. -1. **Clone the Repository:** +### Clone the Repository First you'll need to copy the PyCeas project to your computer through a process called "cloning". ``` git clone https://github.com/PyCeas/Pyceas.git ``` -2. **Set up a virtual environment:** +### Install Python +Check if Python is installed on your system. You can check by running the following command in your terminal prompt: + +- ##### For Mac Users: + 1. Open the terminal and install Python using `Homebrew`. If Homebrew is not installed, [follow these instructions](https://brew.sh/): + ```bash + brew install python3 + ``` + 1. After installation, verify it by running: + ```bash + python3 --version + ``` + +- ##### For Linux Users + 1. Open the terminal and update your package list: + ```bash + sudo apt update + ``` + 2. Install Python + ```bash + sudo apt install python3 + ``` + 3. After installation, verify it by running: + ```bash + python3 --version + ``` + +- ##### For Windows Users + 1. Download the Python installer from the [official Python website](https://www.python.org/downloads/). + 2. Run the installer and follow the on-screen instructions. + 3. Make sure to check the box "Add Python to PATH" before clicking "Install Now". + 4. After installation, verify by running: + ```bash + python --version + ``` + + +### Set up a virtual environment A virtual environment is like a seperate space on your computer where you can install the software needed for this project without affecting other programs. -- **For Mac or Linux Users:** + +- ##### For Mac or Linux Users: - In your terminal, navigate to the folder where you downloaded the project (usually the 'PyCeas' folder) using the 'cd' command: ```bash cd PyCeas @@ -74,43 +118,43 @@ A virtual environment is like a seperate space on your computer where you can in source venv/bin/activate ``` -- **For Windows Users**: +- ##### For Windows Users: - Open Command Prompt and navigate to the 'PyCeas' folder (where you downloaded the project) using the 'cd' command: ```bash cd PyCeas ``` - Set up the virtual environment by typing: ```bash - python3 -m venv venv + python -m venv venv ``` - Activate the virtual environment: ```bash venv\Scripts\activate ``` -3. **Install Required Software**: +### Install Required Software Now, you'll need to install the necessary software that the project depends on. - Make sure you're still in the 'PyCeas' directory/folder and that the virtual environment is active. - Install the software by typing the following command: - ``` + ```bash pip install -r requirements.txt # For running the game (runtime dependencies) ``` This installs everything you need to run the project. - If you plan to do any local development or modifications, also run: - ``` + ```bash pip install -r requirements_dev.txt # For local development ``` This step is optional and only needed if you want to make changes to the project. -4. **Run the project** +### Run the project Now you are ready to start the project! - Simply type: - ``` + ```bash python main.py ``` - The project should start running, and you'll see it in action! -**Deactivate the Virtual Environment**: +### Deactivate the Virtual Environment: When you’re done working, you can deactivate the virtual environment using: ```bash deactivate From 79d81c3595d91618a39e6977239b41602102ae8b Mon Sep 17 00:00:00 2001 From: ultimateownsz Date: Wed, 12 Mar 2025 22:53:47 +0100 Subject: [PATCH 20/20] Ignore all PyCharm project files by enabling .idea/ in .gitignore - Enabled .idea/ in the .gitignore to ensure that all PyCharm-specific project files are ignored. - This prevents unnecessary files such as workspace.xml, caches, and other IDE-specific settings from being committed to version control. - Helps maintain a cleaner repository and avoids conflicts with collaborators using different environments or IDEs. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 82f9275..7b6caf3 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/