Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f1d1402
Move the inventory_gui.py file to the new src/sprites/gui directory.
ultimateownsz Mar 30, 2025
bd1a64a
Add typehints to inventory_gui.py
ultimateownsz Mar 30, 2025
5b8be30
Merge branch 'refs/heads/main' into refactor(sprites.py-file)
ultimateownsz Mar 30, 2025
e8ee9eb
Merge branch 'refs/heads/main' into Grid-overlay-(103)
ultimateownsz Mar 31, 2025
cfb9206
Add grid manager to the game_running.py state
ultimateownsz Mar 31, 2025
06f3f01
create grid manager class that handles the grid overlay over the game
ultimateownsz Mar 31, 2025
a26adf7
Edit the player class to handle a simple implementation of the grid
ultimateownsz Mar 31, 2025
99fabbe
Edit the PlayerCamera class to make the grid workable in the camera p…
ultimateownsz Mar 31, 2025
31008cd
testgrid functionality for movement
ultimateownsz Mar 31, 2025
0696853
Revert "Edit the PlayerCamera class to make the grid workable in the …
ultimateownsz Mar 31, 2025
3fc7749
Edit the PlayerCamera to handle the grid showing when moving over the…
ultimateownsz Mar 31, 2025
1fdabe3
Revert "Edit the PlayerCamera to handle the grid showing when moving …
ultimateownsz Mar 31, 2025
c0315fb
Add logic from the grid to the player camera that if the player moves…
ultimateownsz Mar 31, 2025
426032a
Revert "Add logic from the grid to the player camera that if the play…
ultimateownsz Mar 31, 2025
747fa01
Add logic from the grid to the player camera that if the player moves…
ultimateownsz Mar 31, 2025
c431746
Uncomment old movement system for now.
ultimateownsz Mar 31, 2025
0fe8751
Fixed a bug that showed a copy of the player in the starting position…
ultimateownsz May 17, 2025
634c8bf
- Add pathfinding to requirements.txt
ultimateownsz May 17, 2025
9f798b3
- Implement pathfinding to GridManager class
ultimateownsz May 17, 2025
137f6a8
- Properly transform the mouse position from screen coordinates to wo…
ultimateownsz May 18, 2025
a5c4f88
- Pass Player Position and Mouse Position: The draw method now passes…
ultimateownsz May 18, 2025
e057823
- Fixed player position start on integer position instead of a floati…
ultimateownsz May 19, 2025
88fbc5f
Optimize grid rendering and add FPS display.
ultimateownsz May 19, 2025
4d5bd66
Clamp mouse position and pathfinding coordinates to grid boundaries
ultimateownsz May 19, 2025
332f014
Implement pathfinding caching and update player movement logic
ultimateownsz May 19, 2025
c0b12b9
Refactor player movement and grid rendering: use numpy for grid matri…
ultimateownsz May 21, 2025
b064fdb
Fix player input and grid coordinate calculations: incorporate camera…
ultimateownsz May 21, 2025
559f182
Refactor player and grid methods: comment out unused code and update …
ultimateownsz May 21, 2025
fc31894
Minor refactor player and grid methods: comment out unused camera par…
ultimateownsz May 21, 2025
49a441a
Refactor grid rendering logic into smaller private methods.
ultimateownsz May 24, 2025
25e5c1a
Refactor PlayerCamera import and remove obsolete code.
ultimateownsz May 24, 2025
397c64c
Add PathFinder class and refactor pathfinding logic
ultimateownsz May 24, 2025
b06a989
Refactor pathfinding logic: improved pathfinding for the use case to …
ultimateownsz May 24, 2025
4b9cd80
Refactor imports and improve type hinting across multiple files
ultimateownsz May 26, 2025
28d794a
Refactor indentation and replace assertions with exceptions.
ultimateownsz May 26, 2025
a3795f5
chore(unit tests): Add unit test and refactor GridManager
ultimateownsz May 28, 2025
7e5d1b5
Merge branch 'main' into Grid-overlay-(103)-numpy
ultimateownsz May 28, 2025
c12df4b
Squashed commit of the following:
ultimateownsz May 28, 2025
fdc47b7
Merge remote-tracking branch 'origin/Grid-overlay-(103)-numpy' into G…
ultimateownsz May 28, 2025
21fbd29
docs(add guides): Add documentation movement rework
ultimateownsz May 28, 2025
ad01b75
chore(ruff/mypy): Fix ruff and mypy issues
ultimateownsz May 28, 2025
89d57d2
chore(add hypothesis dep): Add property based testing
ultimateownsz May 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/Guides/CameraGuide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Camera Guide

The word `world` refers in this context to the `pytmx` map, which is the game world where the player interacts with
tiles.

## Overview

This document explains how to handle camera offset and scale in a game development context. Understanding these concepts
is crucial for accurate rendering and interaction with the game world.

## Camera Offset

- **Purpose**: The camera offset shifts the visible area of the game world to center the player or a specific point of
interest on the screen.
- **Effect**: The player stays centered or the view follows the player.
- **Conversion**: When converting screen (mouse) coordinates to world coordinates, the camera offset must be subtracted
from the mouse position, it's necessary to adjust for the camera offset to get the correct position in the game world.

## Camera Scale (Zoom)

- **Purpose**:The camera scale changes the size of the tiles on the screen.
- **Effect**: This affects how the game world is displayed and how the player interacts with it.
- **Conversion**: The scale factor is used to convert between pixel coordinates and grid coordinates.
- To convert between screen and world coordinates, you must divide by the camera scale.

## Importance

Properly accounting for camera offset and scale is essential for:

- Ensuring that the game world is rendered correctly on the screen.
- Allowing accurate interaction with the game world, such as clicking on tiles or objects.
- Maintaining consistency between the visual representation and the underlying game logic.
- Making sure that mouse clicks correspond to the correct positions in the game world.

## In Code

```python
# Constants
TILE_SIZE = 16 # Define the size of each tile in pixels
camera_offset_x, camera_offset_y = 100, 50 # Example camera offset values
camera_scale = 1.5 # Example camera scale value

# Mouse position
mouse_x, mouse_y = 320, 240 # Example mouse coordinates

# Convert mouse position to world coordinates
world_x = (mouse_x - camera_offset_x) / camera_scale
world_y = (mouse_y - camera_offset_y) / camera_scale

# Convert world position to grid (tile) coordinates
grid_x = int(world_x // TILE_SIZE)
grid_y = int(world_y // TILE_SIZE)
```
80 changes: 80 additions & 0 deletions docs/Guides/CoordinateConversionsGuide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Coordinate Conversions Guide

## Overview

This document explains how to convert between grid (tile) coordinates and pixel coordinates in the context of game
development. These conversions are essential for translating game logic positions to screen positions and vice versa.

### Description of the Grid coordinates

The grid coordinates are a system used to represent the position of tiles on the game board. Each tile is represented by
a pair of coordinates, which can be converted to pixel coordinates for rendering on the screen.

## Coordinate System

### Grid (Tile) Coordinates

- **Description**: Grid coordinates represent the position of a tile on the game board
- **Notation**: The x and y-axis as `(tile_x, tile_y)`, where `tile_x` is the column and `tile_y` is the row.
- **Example**: `(16, 10)` refers to the tile at column 16 and row 10.

### Pixel Coordinates

- **Description**: Pixel coordinates represent the exact position on the screen, measured in pixels,
- **Notation**: Denoted as `(pixel_x, pixel_y)`.
- **Example**: `(256, 160)` refers to the pixel at x=256 and y=160.

## Conversion Between Coordinate System

```py

# Constants
TILE_SIZE: int = 16

# From grid to pixel conversion
tile_x, tile_y: int = 16, 10
pixel_x: int = tile_x * TILE_SIZE # 256
pixel_y: int = tile_y * TILE_SIZE # 160

# From pixel to grid conversion
grid_x: int = pixel_x // TILE_SIZE # 16
grid_y: int = pixel_y // TILE_SIZE # 10
```

## Conversion in mathematical notation

For grid (position) to pixel conversion:

$$
\begin{align*}
\text{pixel}_x &= \text{tile}_x \times \text{TILE_SIZE} \\
\text{pixel}_y &= \text{tile}_y \times \text{TILE_SIZE}
\end{align*}
$$

Given $\text{TILE_SIZE} = 16$, $\text{tile_x} = 16$, and $\text{tile_y} = 10$

$$
\begin{align*}
\text{256}_x &= \text{16}_x \times \text{16} \\
\text{160}_y &= \text{10}_y \times \text{16}
\end{align*}
$$

For pixel (position) to grid conversion:

$$
\begin{align*}
\text{grid}_x &= \left\lfloor \frac{\text{pixel}_x}{\text{TILE_SIZE}} \right\rfloor \\
\text{grid}_y &= \left\lfloor \frac{\text{pixel}_y}{\text{TILE_SIZE}} \right\rfloor
\end{align*}
$$

Given $\text{TILE_SIZE} = 16$, $\text{tile_x} = 256$, and $\text{tile_y} = 160$

$$
\begin{align*}
\text{16}_x &= \left\lfloor \frac{\text{256}_x}{\text{16}} \right\rfloor \\
\text{10}_y &= \left\lfloor \frac{\text{160}_y}{\text{16}} \right\rfloor
\end{align*}
$$
85 changes: 85 additions & 0 deletions docs/Guides/GridManagerGuide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Grid Manager Guide

The word `world` refers in this context to the `pytmx` map, which is the game world where the player interacts with
tiles.

### Key Features

- **Grid Drawing**: The grid is drawn on the screen, allowing players to see the layout of the game world.
- **Pathfinding Visualization**: The system can visualize the pathfinding process, showing the route taken by the AI or
player.
- **Edge Case Handling**: The system handles edge cases, such as clicks outside the `pytmx` remain withing the bounds of
the `pytmx map`.
- **Mouse Indicator**: A mouse indicator is rendered to show the current tile under the mouse cursor, helping players
understand where they are clicking.
- **Coordinate Conversion**: The system handles the conversion between grid coordinates and pixel coordinates, ensuring
accurate rendering and interaction with the game world.
- **Customizable Grid Size**: The grid size can be adjusted to fit different game worlds, allowing for flexibility in
design.

## Controls Documentation

### Controls Summary

- **Keyboard**: Press `G` to toggle the grid on and off.
- **Mouse**: When the grid is enabled, clicking on a tile will show its pixel coordinates.
Without it, you can still click on the tile, but it will not show the pixel coordinates and the grid will not be
drawn.

## Coordinate Systems

### Mouse Coordinates Summary

- **Description**: Represents the position of the mouse cursor on the screen.
- **Notation**: Denoted as `(mouse_x, mouse_y)`.

### World Coordinates Summary

- **Description**: Represents the position in the game world, accounting for camera offset and scale.
- **Notation**: Denoted as `(world_x, world_y)`.

### Grid Coordinates Summary

- **Description**: Represents the position of a tile in the grid, based on the world coordinates.
- **Notation**: Denoted as `(grid_x, grid_y)`.

### Screen (Pixel) Coordinates Summary

- **Description**: Represents the position on the screen in pixels.
- **Notation**: Denoted as `(screen_x, screen_y)`.

## Conversion Processes

### Mouse to World Coordinates

- **Description**: Converts mouse coordinates to world coordinates by adjusting for camera offset and scale.

**Mathematical Notation**:

$$
\begin{align*}
\text{world\_x} &= \frac{\text{mouse\_x} - \text{camera\_offset.x}}{\text{camera\_scale}} \\
\text{world\_y} &= \frac{\text{mouse\_y} - \text{camera\_offset.y}}{\text{camera\_scale}}
\end{align*}
$$

**Code Example**:

```python
def _convert_mouse_to_world(mouse_pos, camera_offset, camera_scale):
world_x = (mouse_pos[0] - camera_offset.x) / camera_scale
world_y = (mouse_pos[1] - camera_offset.y) / camera_scale
return world_x, world_y
```

## Running Tests

Tests in the project use `pytest`. To run the tests:
`pytest tests/test_grid_manager.py`

## No Collisions Implemented Yet

- With the rework, you can still move over objects, like 'Islands,' this is intentional and needs to be changed later on
with enhancements.
- The ` 0` is the tile that is walkable, and the `1` is the tile that is not walkable. This is a placeholder for future
collision detection implementation.
71 changes: 71 additions & 0 deletions docs/Guides/PathfindingAStar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Pathfinding and Movement System

## Overview

This document describes the pathfinding and movement system used in the game. The system utilizes the A* algorithm to
find optimal paths on a grid, allowing characters or objects to navigate from a starting point to an endpoint while
avoiding obstacles.

## Components

### Coordinate Dataclass

- **Purpose**: Represents a coordinate in the grid.
- **Attributes**:
- `x`: The x-coordinate on the grid.
- `y`: The y-coordinate on the grid.

### PathCache Class

- **Purpose**: Caches paths to avoid recalculating them, improving performance.
- **Methods**:
- `get_cached_path(start, end)`: Retrieves a cached path if the start and end coordinates match.
- `update_cache(start, end, path)`: Updates the cache with a new path.

### PathFinder Class

- **Purpose**: Finds paths on a grid using the A* algorithm.
- **Attributes**:
- `grid`: A grid representing the game world, where `0` is walkable and `1` is blocked.
- `_cache`: An instance of `PathCache` to store and retrieve paths.
- **Methods**:
- `find_path(start, end)`: Finds a path from the start to the end coordinates.
- `_calculate_path(start, end)`: Uses the A* algorithm to calculate the path.

## Pathfinding Algorithm

### A* Algorithm

- **Description**: A* is a popular pathfinding algorithm that efficiently finds the shortest path between two points on
a grid. It uses a heuristic to estimate the cost of reaching the goal from each node, which helps in prioritizing the
exploration of promising paths.
- **Heuristic**: The algorithm uses the Euclidean distance or Manhattan distance as a heuristic to estimate the cost.
- **Diagonal Movement**: The algorithm supports diagonal movement, which can be enabled or disabled based on the game's
requirements.

### Movement Types

- **DiagonalMovement.always**: Allows movement in all eight possible directions (horizontal, vertical, and diagonal).
- **DiagonalMovement.never**: Restricts movement to four directions (horizontal and vertical only).
- **DiagonalMovement.only_when_no_obstacle**: Allows diagonal movement only when there are no obstacles.
- **DiagonalMovement.if_at_most_one_obstacle**: Allows diagonal movement if there is at most one obstacle.

## Usage

### Finding a Path

To find a path from a starting point to an endpoint, use the `find_path` method of the `PathFinder` class:

```python
grid_matrix = [
[0, 0, 0, 0],
[1, 1, 0, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
]

path_finder = PathFinder(grid_matrix)
start = (0, 0)
end = (3, 3)
path = path_finder.find_path(start, end)
```
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pygame-ce>=2.5.0
pytmx>=3.32
pytmx>=3.32
pathfinding~=1.0.17
numpy~=2.2.6
3 changes: 2 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ruff>=0.5.3
mypy>=1.11.1
pytest>=7.4.4
pytest>=7.4.4
hypothesis>=6.80.0
7 changes: 4 additions & 3 deletions src/game_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pygame # type: ignore

from src.settings import FPS, SCREEN_HEIGHT, SCREEN_WIDTH
from src.settings import SCREEN_HEIGHT, SCREEN_WIDTH

# import base state for typehint
from src.states.base_state import BaseState
Expand All @@ -29,7 +29,7 @@ def __init__(self) -> None:
# init pygame
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("PyCeas")
# pygame.display.set_caption("PyCeas")

self.clock = pygame.Clock()
self.running = True
Expand Down Expand Up @@ -83,4 +83,5 @@ def run(self) -> None:
self.states_stack[-1].render(self.screen)

# magic value, use 'a' FPS const in settings or delta time
self.clock.tick(FPS)
self.clock.tick()
pygame.display.set_caption(f"{self.clock.get_fps():.2f} FPS")
2 changes: 1 addition & 1 deletion src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
ANIMATION_SPEED = 4

WORLD_LAYERS = {"water": 0, "bg": 1, "main": 2, "top": 3}
FPS = 60
FPS = 30


# For some imports like pygame.freetype, Mypy can't infer the type of this attribute, so we suppress the error.
Expand Down
4 changes: 2 additions & 2 deletions src/sprites/animations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pygame
from pygame.sprite import Group
import pygame # type: ignore
from pygame.sprite import Group # type: ignore

from src.settings import ANIMATION_SPEED, WORLD_LAYERS
from src.sprites.base import BaseSprite
Expand Down
6 changes: 3 additions & 3 deletions src/sprites/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from abc import ABC

from pygame import FRect, Surface, Vector2
from pygame.sprite import Group, Sprite
from pygame import Surface, Vector2 # type: ignore
from pygame.sprite import Group, Sprite # type: ignore

from src.settings import ANIMATION_SPEED, WORLD_LAYERS

Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(
raise ValueError("The `surf` parameter must be a valid pygame.Surface.")

self.image = surf
self.rect: FRect = self.image.get_frect(topleft=pos)
self.rect = self.image.get_rect(topleft=pos)
self.z = z

self.frames = frames or [surf]
Expand Down
4 changes: 2 additions & 2 deletions src/sprites/camera/group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod

import pygame
from pygame.sprite import Group
import pygame # type: ignore
from pygame.sprite import Group # type: ignore


# class CameraGroup(pygame.sprite.Group, ABC):
Expand Down
Loading