gh-space-shooter 0.0.1__tar.gz → 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/.github/workflows/publish.yml +3 -3
- gh_space_shooter-0.0.3/.github/workflows/test.yml +40 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/.gitignore +3 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/PKG-INFO +45 -14
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/README.md +41 -12
- gh_space_shooter-0.0.3/action.yml +66 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/pyproject.toml +13 -4
- gh_space_shooter-0.0.3/src/gh_space_shooter/constants.py +19 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/bullet.py +3 -4
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/explosion.py +12 -8
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/github_client.py +0 -2
- gh_space_shooter-0.0.3/tests/conftest.py +27 -0
- gh_space_shooter-0.0.3/tests/test_bullet_collision.py +121 -0
- gh_space_shooter-0.0.3/tests/test_strategies.py +276 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/uv.lock +52 -2
- gh_space_shooter-0.0.1/czl9707_contributions.json +0 -2015
- gh_space_shooter-0.0.1/src/gh_space_shooter/constants.py +0 -11
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/.github/dependabot.yml +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/.python-version +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/LICENSE +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/example.gif +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/__init__.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/cli.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/console_printer.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/__init__.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/animator.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/__init__.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/drawable.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/enemy.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/ship.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/starfield.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/game_state.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/render_context.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/renderer.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/strategies/__init__.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/strategies/base_strategy.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/strategies/column_strategy.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/strategies/random_strategy.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/strategies/row_strategy.py +0 -0
- {gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/py.typed +0 -0
|
@@ -30,9 +30,6 @@ jobs:
|
|
|
30
30
|
- name: Install Python ${{ env.PYTHON_LATEST }}
|
|
31
31
|
run: uv python install ${{ env.PYTHON_LATEST }}
|
|
32
32
|
|
|
33
|
-
- name: Build
|
|
34
|
-
run: uv build
|
|
35
|
-
|
|
36
33
|
- name: Bump version
|
|
37
34
|
id: bump
|
|
38
35
|
uses: callowayproject/bump-my-version@master
|
|
@@ -41,6 +38,9 @@ jobs:
|
|
|
41
38
|
with:
|
|
42
39
|
args: ${{ inputs.release_type }}
|
|
43
40
|
github-token: ${{ secrets.GH_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Build
|
|
43
|
+
run: uv build
|
|
44
44
|
|
|
45
45
|
- name: Publish
|
|
46
46
|
run: uv publish --token ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Run Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
types: [opened, synchronize, reopened]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
PYTHON_LATEST: 3.13
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Test Python ${{ matrix.python-version }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.13"]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout code
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v7
|
|
26
|
+
|
|
27
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
28
|
+
run: uv python install ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync --extra dev
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: uv run pytest tests/ -v
|
|
35
|
+
|
|
36
|
+
- name: Test Summary
|
|
37
|
+
if: always()
|
|
38
|
+
run: |
|
|
39
|
+
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
|
40
|
+
echo "Tests completed for Python ${{ matrix.python-version }}" >> $GITHUB_STEP_SUMMARY
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gh-space-shooter
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: A CLI tool that visualizes GitHub contribution graphs as gamified GIFs
|
|
5
5
|
Author-email: zane <czl970721@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Requires-Dist: httpx>=0.27.0
|
|
9
|
-
Requires-Dist: pillow>=10.
|
|
9
|
+
Requires-Dist: pillow>=10.1.0
|
|
10
10
|
Requires-Dist: python-dotenv>=1.0.0
|
|
11
11
|
Requires-Dist: rich>=13.0.0
|
|
12
12
|
Requires-Dist: typer>=0.12.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
13
15
|
Description-Content-Type: text/markdown
|
|
14
16
|
|
|
15
17
|
# gh-space-shooter 🚀
|
|
@@ -18,19 +20,49 @@ Transform your GitHub contribution graph into an epic space shooter game!
|
|
|
18
20
|
|
|
19
21
|

|
|
20
22
|
|
|
21
|
-
##
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### GitHub Action
|
|
26
|
+
|
|
27
|
+
Automatically update your game GIF daily using GitHub Actions! Add this workflow to your repository at `.github/workflows/update-game.yml`:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
name: Update Space Shooter Game
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
schedule:
|
|
34
|
+
- cron: '0 0 * * *' # Daily at midnight UTC
|
|
35
|
+
workflow_dispatch: # Allow manual trigger
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
37
|
+
permissions:
|
|
38
|
+
contents: write
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
update-game:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- uses: czl9707/gh-space-shooter@v1
|
|
47
|
+
with:
|
|
48
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
49
|
+
output-path: 'game.gif'
|
|
50
|
+
strategy: 'random'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then display it in your README:
|
|
54
|
+
```markdown
|
|
55
|
+

|
|
56
|
+
```
|
|
30
57
|
|
|
31
|
-
|
|
58
|
+
**Action Inputs:**
|
|
59
|
+
- `github-token` (required): GitHub token for fetching contributions
|
|
60
|
+
- `username` (optional): Username to generate game for (defaults to repo owner)
|
|
61
|
+
- `output-path` (optional): Where to save the GIF (default: `gh-space-shooter.gif`)
|
|
62
|
+
- `strategy` (optional): Attack pattern - `column`, `row`, or `random` (default: `random`)
|
|
63
|
+
- `commit-message` (optional): Commit message for the update
|
|
32
64
|
|
|
33
|
-
### From PyPI
|
|
65
|
+
### From PyPI
|
|
34
66
|
|
|
35
67
|
```bash
|
|
36
68
|
pip install gh-space-shooter
|
|
@@ -131,8 +163,7 @@ When saved to JSON, the data includes:
|
|
|
131
163
|
}
|
|
132
164
|
]
|
|
133
165
|
}
|
|
134
|
-
]
|
|
135
|
-
"fetched_at": "2024-12-30T12:00:00"
|
|
166
|
+
]
|
|
136
167
|
}
|
|
137
168
|
```
|
|
138
169
|
|
|
@@ -4,19 +4,49 @@ Transform your GitHub contribution graph into an epic space shooter game!
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### GitHub Action
|
|
10
|
+
|
|
11
|
+
Automatically update your game GIF daily using GitHub Actions! Add this workflow to your repository at `.github/workflows/update-game.yml`:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
name: Update Space Shooter Game
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
schedule:
|
|
18
|
+
- cron: '0 0 * * *' # Daily at midnight UTC
|
|
19
|
+
workflow_dispatch: # Allow manual trigger
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
update-game:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- uses: czl9707/gh-space-shooter@v1
|
|
31
|
+
with:
|
|
32
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
output-path: 'game.gif'
|
|
34
|
+
strategy: 'random'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then display it in your README:
|
|
38
|
+
```markdown
|
|
39
|
+

|
|
40
|
+
```
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
**Action Inputs:**
|
|
43
|
+
- `github-token` (required): GitHub token for fetching contributions
|
|
44
|
+
- `username` (optional): Username to generate game for (defaults to repo owner)
|
|
45
|
+
- `output-path` (optional): Where to save the GIF (default: `gh-space-shooter.gif`)
|
|
46
|
+
- `strategy` (optional): Attack pattern - `column`, `row`, or `random` (default: `random`)
|
|
47
|
+
- `commit-message` (optional): Commit message for the update
|
|
18
48
|
|
|
19
|
-
### From PyPI
|
|
49
|
+
### From PyPI
|
|
20
50
|
|
|
21
51
|
```bash
|
|
22
52
|
pip install gh-space-shooter
|
|
@@ -117,8 +147,7 @@ When saved to JSON, the data includes:
|
|
|
117
147
|
}
|
|
118
148
|
]
|
|
119
149
|
}
|
|
120
|
-
]
|
|
121
|
-
"fetched_at": "2024-12-30T12:00:00"
|
|
150
|
+
]
|
|
122
151
|
}
|
|
123
152
|
```
|
|
124
153
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: 'GitHub Space Shooter'
|
|
2
|
+
description: 'Transform your GitHub contribution graph into an space shooter game GIF'
|
|
3
|
+
author: 'zane'
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: 'navigation-2'
|
|
7
|
+
color: 'black'
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
github-token:
|
|
11
|
+
description: 'GitHub token for fetching contribution data (usually secrets.GITHUB_TOKEN)'
|
|
12
|
+
required: true
|
|
13
|
+
username:
|
|
14
|
+
description: 'GitHub username to generate game for (defaults to repository owner)'
|
|
15
|
+
required: false
|
|
16
|
+
default: ${{ github.repository_owner }}
|
|
17
|
+
output-path:
|
|
18
|
+
description: 'Path where the GIF should be saved'
|
|
19
|
+
required: false
|
|
20
|
+
default: 'gh-space-shooter.gif'
|
|
21
|
+
strategy:
|
|
22
|
+
description: 'Enemy attack strategy (column, row, or random)'
|
|
23
|
+
required: false
|
|
24
|
+
default: 'random'
|
|
25
|
+
commit-message:
|
|
26
|
+
description: 'Commit message for the GIF update'
|
|
27
|
+
required: false
|
|
28
|
+
default: 'Update space shooter game GIF'
|
|
29
|
+
|
|
30
|
+
outputs:
|
|
31
|
+
gif-path:
|
|
32
|
+
description: 'Path to the generated GIF file'
|
|
33
|
+
value: ${{ steps.generate.outputs.gif-path }}
|
|
34
|
+
|
|
35
|
+
runs:
|
|
36
|
+
using: 'composite'
|
|
37
|
+
steps:
|
|
38
|
+
- name: Set up Python
|
|
39
|
+
uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: '3.13'
|
|
42
|
+
|
|
43
|
+
- name: Install gh-space-shooter
|
|
44
|
+
shell: bash
|
|
45
|
+
run: |
|
|
46
|
+
python -m pip install --upgrade pip
|
|
47
|
+
pip install gh-space-shooter
|
|
48
|
+
|
|
49
|
+
- name: Generate game GIF
|
|
50
|
+
id: generate
|
|
51
|
+
shell: bash
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ inputs.github-token }}
|
|
54
|
+
run: |
|
|
55
|
+
gh-space-shooter ${{ inputs.username }} \
|
|
56
|
+
--output ${{ inputs.output-path }} \
|
|
57
|
+
--strategy ${{ inputs.strategy }}
|
|
58
|
+
|
|
59
|
+
- name: Commit and push GIF
|
|
60
|
+
shell: bash
|
|
61
|
+
run: |
|
|
62
|
+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
63
|
+
git config --local user.name "github-actions[bot]"
|
|
64
|
+
git add ${{ inputs.output-path }}
|
|
65
|
+
git diff --staged --quiet || git commit -m "${{ inputs.commit-message }}"
|
|
66
|
+
git push
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gh-space-shooter"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
description = "A CLI tool that visualizes GitHub contribution graphs as gamified GIFs"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -12,7 +12,12 @@ dependencies = [
|
|
|
12
12
|
"httpx>=0.27.0",
|
|
13
13
|
"python-dotenv>=1.0.0",
|
|
14
14
|
"rich>=13.0.0",
|
|
15
|
-
"pillow>=10.
|
|
15
|
+
"pillow>=10.1.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=8.0.0",
|
|
16
21
|
]
|
|
17
22
|
|
|
18
23
|
[project.scripts]
|
|
@@ -24,7 +29,7 @@ build-backend = "hatchling.build"
|
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
[tool.bumpversion]
|
|
27
|
-
current_version = "0.0.
|
|
32
|
+
current_version = "0.0.3"
|
|
28
33
|
parse = """(?x)
|
|
29
34
|
(?P<major>0|[1-9]\\d*)\\.
|
|
30
35
|
(?P<minor>0|[1-9]\\d*)\\.
|
|
@@ -47,4 +52,8 @@ commit_args = ""
|
|
|
47
52
|
[[tool.bumpversion.files]]
|
|
48
53
|
filename = "pyproject.toml"
|
|
49
54
|
search = "version = \"{current_version}\""
|
|
50
|
-
replace = "version = \"{new_version}\""
|
|
55
|
+
replace = "version = \"{new_version}\""
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Global constants for the application."""
|
|
2
|
+
|
|
3
|
+
# GitHub contribution graph dimensions
|
|
4
|
+
NUM_WEEKS = 52 # Number of weeks in contribution graph
|
|
5
|
+
NUM_DAYS = 7 # Number of days in a week (Sun-Sat)
|
|
6
|
+
SHIP_POSITION_Y = NUM_DAYS + 3 # Ship is positioned just below the grid
|
|
7
|
+
|
|
8
|
+
SHIP_SPEED = 0.25 # Cells per frame the ship moves
|
|
9
|
+
BULLET_SPEED = 0.15 # Cells per frame the bullet moves
|
|
10
|
+
BULLET_TRAILING_LENGTH = 3 # Number of trailing segments for bullets
|
|
11
|
+
FRAME_DURATION_MS = 20 # Duration of each frame in milliseconds
|
|
12
|
+
SHIP_SHOOT_COOLDOWN_FRAMES = 10 # Frames between ship shots
|
|
13
|
+
|
|
14
|
+
EXPLOSION_PARTICLE_COUNT_LARGE = 8 # Number of particles in a large explosion
|
|
15
|
+
EXPLOSION_PARTICLE_COUNT_SMALL = 4 # Number of particles in a small explosion
|
|
16
|
+
EXPLOSION_MAX_RADIUS_LARGE = 20 # Max radius for large explosions
|
|
17
|
+
EXPLOSION_MAX_RADIUS_SMALL = 10 # Max radius for small explosions
|
|
18
|
+
EXPLOSION_MAX_FRAMES_LARGE = 20 # Frames for large explosion animation
|
|
19
|
+
EXPLOSION_MAX_FRAMES_SMALL = 6 # Frames for small explosion animation
|
{gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/bullet.py
RENAMED
|
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from PIL import ImageDraw
|
|
6
6
|
|
|
7
|
-
from ...constants import BULLET_SPEED, SHIP_POSITION_Y
|
|
7
|
+
from ...constants import BULLET_SPEED, BULLET_TRAILING_LENGTH, SHIP_POSITION_Y
|
|
8
8
|
from .drawable import Drawable
|
|
9
9
|
from .explosion import Explosion
|
|
10
10
|
|
|
@@ -52,10 +52,9 @@ class Bullet(Drawable):
|
|
|
52
52
|
def draw(self, draw: ImageDraw.ImageDraw, context: "RenderContext") -> None:
|
|
53
53
|
"""Draw the bullet with trailing tail effect."""
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
for i in range(trail_num):
|
|
55
|
+
for i in range(BULLET_TRAILING_LENGTH):
|
|
57
56
|
trail_y = self.y + (i + 1) * BULLET_SPEED
|
|
58
|
-
fade_factor = (i + 1) /
|
|
57
|
+
fade_factor = (i + 1) / BULLET_TRAILING_LENGTH / 2
|
|
59
58
|
self._draw_bullet(draw, context, (self.x, trail_y), fade_factor=fade_factor)
|
|
60
59
|
|
|
61
60
|
self._draw_bullet(draw, context, (self.x, self.y), fade_factor=0.3, offset=.6)
|
{gh_space_shooter-0.0.1 → gh_space_shooter-0.0.3}/src/gh_space_shooter/game/drawables/explosion.py
RENAMED
|
@@ -6,6 +6,14 @@ from typing import TYPE_CHECKING, Literal
|
|
|
6
6
|
|
|
7
7
|
from PIL import ImageDraw
|
|
8
8
|
|
|
9
|
+
from ...constants import (
|
|
10
|
+
EXPLOSION_MAX_FRAMES_LARGE,
|
|
11
|
+
EXPLOSION_MAX_FRAMES_SMALL,
|
|
12
|
+
EXPLOSION_MAX_RADIUS_LARGE,
|
|
13
|
+
EXPLOSION_MAX_RADIUS_SMALL,
|
|
14
|
+
EXPLOSION_PARTICLE_COUNT_LARGE,
|
|
15
|
+
EXPLOSION_PARTICLE_COUNT_SMALL,
|
|
16
|
+
)
|
|
9
17
|
from .drawable import Drawable
|
|
10
18
|
|
|
11
19
|
if TYPE_CHECKING:
|
|
@@ -30,10 +38,9 @@ class Explosion(Drawable):
|
|
|
30
38
|
self.y = y
|
|
31
39
|
self.game_state = game_state
|
|
32
40
|
self.frame = 0
|
|
33
|
-
self.max_frames =
|
|
34
|
-
self.max_radius =
|
|
35
|
-
self.particle_count =
|
|
36
|
-
# Generate random angles for each particle
|
|
41
|
+
self.max_frames = EXPLOSION_MAX_FRAMES_SMALL if size == "small" else EXPLOSION_MAX_FRAMES_LARGE
|
|
42
|
+
self.max_radius = EXPLOSION_MAX_RADIUS_SMALL if size == "small" else EXPLOSION_MAX_RADIUS_LARGE
|
|
43
|
+
self.particle_count = EXPLOSION_PARTICLE_COUNT_SMALL if size == "small" else EXPLOSION_PARTICLE_COUNT_LARGE
|
|
37
44
|
self.particle_angles = [random.uniform(0, 2 * math.pi) for _ in range(self.particle_count)]
|
|
38
45
|
|
|
39
46
|
def animate(self) -> None:
|
|
@@ -44,21 +51,18 @@ class Explosion(Drawable):
|
|
|
44
51
|
|
|
45
52
|
def draw(self, draw: ImageDraw.ImageDraw, context: "RenderContext") -> None:
|
|
46
53
|
"""Draw expanding particle explosion with fade effect."""
|
|
47
|
-
|
|
54
|
+
|
|
48
55
|
progress = self.frame / self.max_frames
|
|
49
56
|
fade = 1 - progress # Fade out as animation progresses
|
|
50
57
|
|
|
51
|
-
# Get center position
|
|
52
58
|
center_x, center_y = context.get_cell_position(self.x, self.y)
|
|
53
59
|
center_x += context.cell_size // 2
|
|
54
60
|
center_y += context.cell_size // 2
|
|
55
61
|
|
|
56
|
-
# Draw expanding particles in random directions
|
|
57
62
|
for i in range(self.particle_count):
|
|
58
63
|
distance = progress * self.max_radius
|
|
59
64
|
angle = self.particle_angles[i]
|
|
60
65
|
|
|
61
|
-
# Particle position using random angle
|
|
62
66
|
px = int(center_x + distance * math.cos(angle))
|
|
63
67
|
py = int(center_y + distance * math.sin(angle))
|
|
64
68
|
|
|
@@ -32,7 +32,6 @@ class ContributionData(TypedDict):
|
|
|
32
32
|
username: str
|
|
33
33
|
total_contributions: int
|
|
34
34
|
weeks: list[ContributionWeek]
|
|
35
|
-
fetched_at: str
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
class GitHubAPIError(Exception):
|
|
@@ -157,7 +156,6 @@ class GitHubClient:
|
|
|
157
156
|
"username": username,
|
|
158
157
|
"total_contributions": calendar["totalContributions"],
|
|
159
158
|
"weeks": weeks,
|
|
160
|
-
"fetched_at": datetime.now().isoformat(),
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
LEVEL_MAP = {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Shared test fixtures for gh-space-shooter tests."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from gh_space_shooter.game.game_state import GameState
|
|
5
|
+
from gh_space_shooter.github_client import ContributionData
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def empty_contribution_data() -> ContributionData:
|
|
10
|
+
"""Create contribution data with no contributions."""
|
|
11
|
+
return {
|
|
12
|
+
"weeks": [
|
|
13
|
+
{
|
|
14
|
+
"days": [
|
|
15
|
+
{"level": 0, "date": "", "count": 0} for _ in range(7)
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
for _ in range(52)
|
|
19
|
+
],
|
|
20
|
+
"total_contributions": 0,
|
|
21
|
+
"username": "test_user",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def default_game_state(empty_contribution_data):
|
|
26
|
+
"""Create a game state with no enemies."""
|
|
27
|
+
return GameState(empty_contribution_data)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Tests for bullet collision detection logic."""
|
|
2
|
+
|
|
3
|
+
from gh_space_shooter.game.game_state import GameState
|
|
4
|
+
from gh_space_shooter.game.drawables import Bullet, Enemy
|
|
5
|
+
from gh_space_shooter.constants import EXPLOSION_MAX_FRAMES_SMALL, EXPLOSION_MAX_FRAMES_LARGE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestBulletCollision:
|
|
9
|
+
"""Tests for bullet collision detection and behavior."""
|
|
10
|
+
|
|
11
|
+
def test_collision_detection_same_x_position(self, default_game_state: GameState) -> None:
|
|
12
|
+
"""Test that bullet detects collision when at same x position as enemy."""
|
|
13
|
+
|
|
14
|
+
enemy = Enemy(x=5, y=3, health=2, game_state=default_game_state)
|
|
15
|
+
default_game_state.enemies.append(enemy)
|
|
16
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
17
|
+
bullet.y = 2.0
|
|
18
|
+
default_game_state.bullets.append(bullet)
|
|
19
|
+
|
|
20
|
+
hit_enemy = bullet._check_collision()
|
|
21
|
+
assert hit_enemy is enemy
|
|
22
|
+
|
|
23
|
+
bullet.animate()
|
|
24
|
+
assert bullet not in default_game_state.bullets
|
|
25
|
+
|
|
26
|
+
def test_collision_detection_enemy_above_bullet(self, default_game_state: GameState) -> None:
|
|
27
|
+
"""Test that collision is detected when enemy.y >= bullet.y."""
|
|
28
|
+
|
|
29
|
+
enemy = Enemy(x=5, y=3, health=2, game_state=default_game_state)
|
|
30
|
+
default_game_state.enemies.append(enemy)
|
|
31
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
32
|
+
bullet.y = 2.5
|
|
33
|
+
default_game_state.bullets.append(bullet)
|
|
34
|
+
|
|
35
|
+
hit_enemy = bullet._check_collision()
|
|
36
|
+
assert hit_enemy is enemy
|
|
37
|
+
|
|
38
|
+
bullet.animate()
|
|
39
|
+
assert bullet not in default_game_state.bullets
|
|
40
|
+
|
|
41
|
+
def test_no_collision_different_x_position(self, default_game_state: GameState) -> None:
|
|
42
|
+
"""Test that bullet doesn't detect collision at different x positions."""
|
|
43
|
+
|
|
44
|
+
enemy = Enemy(x=5, y=3, health=2, game_state=default_game_state)
|
|
45
|
+
default_game_state.enemies.append(enemy)
|
|
46
|
+
|
|
47
|
+
bullet = Bullet(x=6, game_state=default_game_state)
|
|
48
|
+
bullet.y = 3.0
|
|
49
|
+
|
|
50
|
+
hit_enemy = bullet._check_collision()
|
|
51
|
+
assert hit_enemy is None
|
|
52
|
+
|
|
53
|
+
def test_bullet_damages_enemy_on_collision(self, default_game_state: GameState) -> None:
|
|
54
|
+
"""Test that bullet damages enemy on collision."""
|
|
55
|
+
|
|
56
|
+
enemy = Enemy(x=5, y=3, health=3, game_state=default_game_state)
|
|
57
|
+
default_game_state.enemies.append(enemy)
|
|
58
|
+
|
|
59
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
60
|
+
bullet.y = 2.0
|
|
61
|
+
default_game_state.bullets.append(bullet)
|
|
62
|
+
|
|
63
|
+
bullet.animate()
|
|
64
|
+
assert enemy.health == 2
|
|
65
|
+
|
|
66
|
+
def test_enemy_destroyed_when_health_zero(self, default_game_state: GameState) -> None:
|
|
67
|
+
"""Test that enemy is removed when health reaches zero."""
|
|
68
|
+
|
|
69
|
+
enemy = Enemy(x=5, y=3, health=1, game_state=default_game_state)
|
|
70
|
+
default_game_state.enemies.append(enemy)
|
|
71
|
+
|
|
72
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
73
|
+
bullet.y = 2.0
|
|
74
|
+
default_game_state.bullets.append(bullet)
|
|
75
|
+
|
|
76
|
+
bullet.animate()
|
|
77
|
+
assert enemy not in default_game_state.enemies
|
|
78
|
+
|
|
79
|
+
def test_explosion_created_on_collision(self, default_game_state: GameState) -> None:
|
|
80
|
+
"""Test that explosion is created when bullet hits enemy."""
|
|
81
|
+
|
|
82
|
+
enemy = Enemy(x=5, y=3, health=2, game_state=default_game_state)
|
|
83
|
+
default_game_state.enemies.append(enemy)
|
|
84
|
+
|
|
85
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
86
|
+
bullet.y = 2.0
|
|
87
|
+
default_game_state.bullets.append(bullet)
|
|
88
|
+
|
|
89
|
+
bullet.animate()
|
|
90
|
+
assert len(default_game_state.explosions) == 1
|
|
91
|
+
assert default_game_state.explosions[0].max_frames == EXPLOSION_MAX_FRAMES_SMALL
|
|
92
|
+
|
|
93
|
+
def test_large_explosion_on_enemy_destroyed(self, default_game_state: GameState) -> None:
|
|
94
|
+
"""Test that large explosion is created when enemy is destroyed."""
|
|
95
|
+
|
|
96
|
+
enemy = Enemy(x=5, y=3, health=1, game_state=default_game_state)
|
|
97
|
+
default_game_state.enemies.append(enemy)
|
|
98
|
+
|
|
99
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
100
|
+
bullet.y = 2.0
|
|
101
|
+
default_game_state.bullets.append(bullet)
|
|
102
|
+
|
|
103
|
+
bullet.animate()
|
|
104
|
+
# Should have 2 explosions: small from bullet hit, large from enemy destruction
|
|
105
|
+
assert len(default_game_state.explosions) == 2
|
|
106
|
+
explosion_max_frames = [exp.max_frames for exp in default_game_state.explosions]
|
|
107
|
+
assert EXPLOSION_MAX_FRAMES_SMALL in explosion_max_frames
|
|
108
|
+
assert EXPLOSION_MAX_FRAMES_LARGE in explosion_max_frames
|
|
109
|
+
|
|
110
|
+
def test_bullet_removed_when_off_screen(self, default_game_state: GameState) -> None:
|
|
111
|
+
"""Test that bullet is removed when it goes off screen (y < -10)."""
|
|
112
|
+
|
|
113
|
+
bullet = Bullet(x=5, game_state=default_game_state)
|
|
114
|
+
bullet.y = -5.0
|
|
115
|
+
default_game_state.bullets.append(bullet)
|
|
116
|
+
|
|
117
|
+
for _ in range(50):
|
|
118
|
+
if bullet in default_game_state.bullets:
|
|
119
|
+
bullet.animate()
|
|
120
|
+
|
|
121
|
+
assert bullet not in default_game_state.bullets
|