gh-space-shooter 0.0.2__tar.gz → 0.0.4__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.2 → gh_space_shooter-0.0.4}/.github/workflows/publish.yml +1 -1
- gh_space_shooter-0.0.4/.github/workflows/test.yml +40 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/.gitignore +3 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/PKG-INFO +51 -14
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/README.md +47 -12
- gh_space_shooter-0.0.4/action.yml +71 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/pyproject.toml +13 -4
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/cli.py +16 -5
- gh_space_shooter-0.0.4/src/gh_space_shooter/constants.py +30 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/animator.py +10 -7
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/bullet.py +11 -8
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/drawable.py +6 -2
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/enemy.py +1 -1
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/explosion.py +22 -14
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/ship.py +13 -8
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/starfield.py +31 -15
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/game_state.py +13 -9
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/github_client.py +0 -2
- gh_space_shooter-0.0.4/tests/conftest.py +27 -0
- gh_space_shooter-0.0.4/tests/test_bullet_collision.py +124 -0
- gh_space_shooter-0.0.4/tests/test_strategies.py +276 -0
- gh_space_shooter-0.0.4/uv.lock +305 -0
- gh_space_shooter-0.0.2/czl9707_contributions.json +0 -2015
- gh_space_shooter-0.0.2/src/gh_space_shooter/constants.py +0 -11
- gh_space_shooter-0.0.2/uv.lock +0 -255
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/.github/dependabot.yml +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/.python-version +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/LICENSE +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/example.gif +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/__init__.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/console_printer.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/__init__.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/__init__.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/render_context.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/renderer.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/strategies/__init__.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/strategies/base_strategy.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/strategies/column_strategy.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/strategies/random_strategy.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/strategies/row_strategy.py +0 -0
- {gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/py.typed +0 -0
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
- name: GitHub Release v${{ steps.bump.outputs.current-version }}
|
|
49
49
|
uses: ncipollo/release-action@v1
|
|
50
50
|
with:
|
|
51
|
-
name:
|
|
51
|
+
name: gh-space-shooter v${{ steps.bump.outputs.current-version }}
|
|
52
52
|
tag: v${{ steps.bump.outputs.current-version }}
|
|
53
53
|
body: "Automated release of version v${{ steps.bump.outputs.current-version }}"
|
|
54
54
|
artifacts: dist/*
|
|
@@ -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.4
|
|
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,50 @@ 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`:
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- 🎮 **Smart enemy AI** - Multiple attack strategies (columns, rows, random patterns)
|
|
26
|
-
- 💥 **Particle effects** - Explosions with randomized particles and smooth animations
|
|
27
|
-
- 🎨 **Polished graphics** - Rounded enemies, smooth ship design, starfield background
|
|
28
|
-
- 📈 **Contribution stats** - View your coding activity statistics
|
|
29
|
-
- 💾 **Export options** - Save both the GIF and raw JSON data
|
|
29
|
+
```yaml
|
|
30
|
+
name: Update Space Shooter Game
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
on:
|
|
33
|
+
schedule:
|
|
34
|
+
- cron: '0 0 * * *' # Daily at midnight UTC
|
|
35
|
+
workflow_dispatch: # Allow manual trigger
|
|
32
36
|
|
|
33
|
-
|
|
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
|
+
```
|
|
57
|
+
|
|
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
|
+
- `fps` (optional): Frames per second for the animation (default: `40`)
|
|
64
|
+
- `commit-message` (optional): Commit message for the update
|
|
65
|
+
|
|
66
|
+
### From PyPI
|
|
34
67
|
|
|
35
68
|
```bash
|
|
36
69
|
pip install gh-space-shooter
|
|
@@ -92,6 +125,11 @@ gh-space-shooter torvalds -o my-game.gif
|
|
|
92
125
|
gh-space-shooter torvalds --strategy column # Enemies attack in columns
|
|
93
126
|
gh-space-shooter torvalds --strategy row # Enemies attack in rows
|
|
94
127
|
gh-space-shooter torvalds -s random # Random chaos (default)
|
|
128
|
+
|
|
129
|
+
# Adjust animation frame rate
|
|
130
|
+
gh-space-shooter torvalds --fps 25 # Slower, smaller file size
|
|
131
|
+
gh-space-shooter torvalds --fps 40 # Default frame rate
|
|
132
|
+
gh-space-shooter torvalds --fps 50 # Smoother animation
|
|
95
133
|
```
|
|
96
134
|
|
|
97
135
|
This creates an animated GIF showing:
|
|
@@ -131,8 +169,7 @@ When saved to JSON, the data includes:
|
|
|
131
169
|
}
|
|
132
170
|
]
|
|
133
171
|
}
|
|
134
|
-
]
|
|
135
|
-
"fetched_at": "2024-12-30T12:00:00"
|
|
172
|
+
]
|
|
136
173
|
}
|
|
137
174
|
```
|
|
138
175
|
|
|
@@ -4,19 +4,50 @@ 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`:
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- 🎮 **Smart enemy AI** - Multiple attack strategies (columns, rows, random patterns)
|
|
12
|
-
- 💥 **Particle effects** - Explosions with randomized particles and smooth animations
|
|
13
|
-
- 🎨 **Polished graphics** - Rounded enemies, smooth ship design, starfield background
|
|
14
|
-
- 📈 **Contribution stats** - View your coding activity statistics
|
|
15
|
-
- 💾 **Export options** - Save both the GIF and raw JSON data
|
|
13
|
+
```yaml
|
|
14
|
+
name: Update Space Shooter Game
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
on:
|
|
17
|
+
schedule:
|
|
18
|
+
- cron: '0 0 * * *' # Daily at midnight UTC
|
|
19
|
+
workflow_dispatch: # Allow manual trigger
|
|
18
20
|
|
|
19
|
-
|
|
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
|
+
```
|
|
41
|
+
|
|
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
|
+
- `fps` (optional): Frames per second for the animation (default: `40`)
|
|
48
|
+
- `commit-message` (optional): Commit message for the update
|
|
49
|
+
|
|
50
|
+
### From PyPI
|
|
20
51
|
|
|
21
52
|
```bash
|
|
22
53
|
pip install gh-space-shooter
|
|
@@ -78,6 +109,11 @@ gh-space-shooter torvalds -o my-game.gif
|
|
|
78
109
|
gh-space-shooter torvalds --strategy column # Enemies attack in columns
|
|
79
110
|
gh-space-shooter torvalds --strategy row # Enemies attack in rows
|
|
80
111
|
gh-space-shooter torvalds -s random # Random chaos (default)
|
|
112
|
+
|
|
113
|
+
# Adjust animation frame rate
|
|
114
|
+
gh-space-shooter torvalds --fps 25 # Slower, smaller file size
|
|
115
|
+
gh-space-shooter torvalds --fps 40 # Default frame rate
|
|
116
|
+
gh-space-shooter torvalds --fps 50 # Smoother animation
|
|
81
117
|
```
|
|
82
118
|
|
|
83
119
|
This creates an animated GIF showing:
|
|
@@ -117,8 +153,7 @@ When saved to JSON, the data includes:
|
|
|
117
153
|
}
|
|
118
154
|
]
|
|
119
155
|
}
|
|
120
|
-
]
|
|
121
|
-
"fetched_at": "2024-12-30T12:00:00"
|
|
156
|
+
]
|
|
122
157
|
}
|
|
123
158
|
```
|
|
124
159
|
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
fps:
|
|
26
|
+
description: 'Frames per second for the animation (default: 40)'
|
|
27
|
+
required: false
|
|
28
|
+
default: '40'
|
|
29
|
+
commit-message:
|
|
30
|
+
description: 'Commit message for the GIF update'
|
|
31
|
+
required: false
|
|
32
|
+
default: 'Update space shooter game GIF'
|
|
33
|
+
|
|
34
|
+
outputs:
|
|
35
|
+
gif-path:
|
|
36
|
+
description: 'Path to the generated GIF file'
|
|
37
|
+
value: ${{ steps.generate.outputs.gif-path }}
|
|
38
|
+
|
|
39
|
+
runs:
|
|
40
|
+
using: 'composite'
|
|
41
|
+
steps:
|
|
42
|
+
- name: Set up Python
|
|
43
|
+
uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: '3.13'
|
|
46
|
+
|
|
47
|
+
- name: Install gh-space-shooter
|
|
48
|
+
shell: bash
|
|
49
|
+
run: |
|
|
50
|
+
python -m pip install --upgrade pip
|
|
51
|
+
pip install gh-space-shooter
|
|
52
|
+
|
|
53
|
+
- name: Generate game GIF
|
|
54
|
+
id: generate
|
|
55
|
+
shell: bash
|
|
56
|
+
env:
|
|
57
|
+
GH_TOKEN: ${{ inputs.github-token }}
|
|
58
|
+
run: |
|
|
59
|
+
gh-space-shooter ${{ inputs.username }} \
|
|
60
|
+
--output ${{ inputs.output-path }} \
|
|
61
|
+
--strategy ${{ inputs.strategy }} \
|
|
62
|
+
--fps ${{ inputs.fps }}
|
|
63
|
+
|
|
64
|
+
- name: Commit and push GIF
|
|
65
|
+
shell: bash
|
|
66
|
+
run: |
|
|
67
|
+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
68
|
+
git config --local user.name "github-actions[bot]"
|
|
69
|
+
git add ${{ inputs.output-path }}
|
|
70
|
+
git diff --staged --quiet || git commit -m "${{ inputs.commit-message }}"
|
|
71
|
+
git push
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gh-space-shooter"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.4"
|
|
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.4"
|
|
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"]
|
|
@@ -8,8 +8,8 @@ import typer
|
|
|
8
8
|
from dotenv import load_dotenv
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
|
|
11
|
+
from .constants import DEFAULT_FPS
|
|
12
|
+
from .game.strategies.base_strategy import BaseStrategy
|
|
13
13
|
from .console_printer import ContributionConsolePrinter
|
|
14
14
|
from .game import Animator, ColumnStrategy, RandomStrategy, RowStrategy
|
|
15
15
|
from .github_client import ContributionData, GitHubAPIError, GitHubClient
|
|
@@ -55,6 +55,11 @@ def main(
|
|
|
55
55
|
"-s",
|
|
56
56
|
help="Strategy for clearing enemies (column, row, random)",
|
|
57
57
|
),
|
|
58
|
+
fps: int = typer.Option(
|
|
59
|
+
DEFAULT_FPS,
|
|
60
|
+
"--fps",
|
|
61
|
+
help="Frames per second for the animation",
|
|
62
|
+
),
|
|
58
63
|
) -> None:
|
|
59
64
|
"""
|
|
60
65
|
Fetch or load GitHub contribution graph data and display it.
|
|
@@ -90,7 +95,7 @@ def main(
|
|
|
90
95
|
_save_data_to_file(data, raw_output)
|
|
91
96
|
|
|
92
97
|
# Generate GIF if requested
|
|
93
|
-
_generate_gif(data, out, strategy)
|
|
98
|
+
_generate_gif(data, out, strategy, fps)
|
|
94
99
|
|
|
95
100
|
except CLIError as e:
|
|
96
101
|
err_console.print(f"[bold red]Error:[/bold red] {e}")
|
|
@@ -146,8 +151,14 @@ def _save_data_to_file(data: ContributionData, file_path: str) -> None:
|
|
|
146
151
|
raise CLIError(f"Failed to save file '{file_path}': {e}")
|
|
147
152
|
|
|
148
153
|
|
|
149
|
-
def _generate_gif(data: ContributionData, file_path: str, strategy_name: str) -> None:
|
|
154
|
+
def _generate_gif(data: ContributionData, file_path: str, strategy_name: str, fps: int) -> None:
|
|
150
155
|
"""Generate animated GIF visualization."""
|
|
156
|
+
# GIF format limitation: delays below 20ms (>50 FPS) are clamped by most browsers
|
|
157
|
+
if fps > 50:
|
|
158
|
+
console.print(
|
|
159
|
+
f"[yellow]Warning:[/yellow] FPS > 50 may not display correctly in browsers "
|
|
160
|
+
f"(GIF delay will be {1000 // fps}ms, but browsers clamp delays < 20ms to ~100ms)"
|
|
161
|
+
)
|
|
151
162
|
console.print("\n[bold blue]Generating GIF animation...[/bold blue]")
|
|
152
163
|
|
|
153
164
|
if strategy_name == "column":
|
|
@@ -163,7 +174,7 @@ def _generate_gif(data: ContributionData, file_path: str, strategy_name: str) ->
|
|
|
163
174
|
|
|
164
175
|
# Create animator and generate GIF
|
|
165
176
|
try:
|
|
166
|
-
animator = Animator(data, strategy)
|
|
177
|
+
animator = Animator(data, strategy, fps=fps)
|
|
167
178
|
animator.generate_gif(file_path)
|
|
168
179
|
console.print(f"[green]✓[/green] GIF saved to {file_path}")
|
|
169
180
|
except Exception as e:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Global constants for the application."""
|
|
2
|
+
|
|
3
|
+
# Animation settings
|
|
4
|
+
DEFAULT_FPS = 40 # Default frames per second for animation
|
|
5
|
+
|
|
6
|
+
# GitHub contribution graph dimensions
|
|
7
|
+
NUM_WEEKS = 52 # Number of weeks in contribution graph
|
|
8
|
+
NUM_DAYS = 7 # Number of days in a week (Sun-Sat)
|
|
9
|
+
SHIP_POSITION_Y = NUM_DAYS + 3 # Ship is positioned just below the grid
|
|
10
|
+
|
|
11
|
+
# Speeds in cells per second (frame-rate independent)
|
|
12
|
+
SHIP_SPEED = 12.5 # Cells per second the ship moves
|
|
13
|
+
BULLET_SPEED = 7.5 # Cells per second the bullet moves
|
|
14
|
+
BULLET_TRAILING_LENGTH = 3 # Number of trailing segments for bullets
|
|
15
|
+
BULLET_TRAIL_SPACING = 0.15 # Spacing between trail segments in cells
|
|
16
|
+
|
|
17
|
+
# Durations in seconds (frame-rate independent)
|
|
18
|
+
SHIP_SHOOT_COOLDOWN = 0.2 # Seconds between ship shots
|
|
19
|
+
|
|
20
|
+
# Explosion settings
|
|
21
|
+
EXPLOSION_PARTICLE_COUNT_LARGE = 8 # Number of particles in a large explosion
|
|
22
|
+
EXPLOSION_PARTICLE_COUNT_SMALL = 4 # Number of particles in a small explosion
|
|
23
|
+
EXPLOSION_MAX_RADIUS_LARGE = 20 # Max radius for large explosions
|
|
24
|
+
EXPLOSION_MAX_RADIUS_SMALL = 10 # Max radius for small explosions
|
|
25
|
+
EXPLOSION_DURATION_LARGE = 0.4 # Seconds for large explosion animation
|
|
26
|
+
EXPLOSION_DURATION_SMALL = 0.12 # Seconds for small explosion animation
|
|
27
|
+
|
|
28
|
+
# Starfield settings (speeds in cells per second)
|
|
29
|
+
STAR_SPEED_MIN = 1.0 # Minimum star speed (dimmer/farther stars)
|
|
30
|
+
STAR_SPEED_MAX = 2.5 # Maximum star speed (brighter/closer stars)
|
|
@@ -5,7 +5,6 @@ from typing import Iterator
|
|
|
5
5
|
from PIL import Image
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
from ..constants import FRAME_DURATION_MS
|
|
9
8
|
from ..github_client import ContributionData
|
|
10
9
|
from .game_state import GameState
|
|
11
10
|
from .renderer import Renderer
|
|
@@ -20,7 +19,7 @@ class Animator:
|
|
|
20
19
|
self,
|
|
21
20
|
contribution_data: ContributionData,
|
|
22
21
|
strategy: BaseStrategy,
|
|
23
|
-
|
|
22
|
+
fps: int,
|
|
24
23
|
):
|
|
25
24
|
"""
|
|
26
25
|
Initialize animator.
|
|
@@ -28,11 +27,15 @@ class Animator:
|
|
|
28
27
|
Args:
|
|
29
28
|
contribution_data: The GitHub contribution data
|
|
30
29
|
strategy: The strategy to use for clearing enemies
|
|
31
|
-
|
|
30
|
+
fps: Frames per second for the animation
|
|
32
31
|
"""
|
|
33
32
|
self.contribution_data = contribution_data
|
|
34
33
|
self.strategy = strategy
|
|
35
|
-
self.
|
|
34
|
+
self.fps = fps
|
|
35
|
+
self.frame_duration = 1000 // fps
|
|
36
|
+
# Delta time in seconds per frame
|
|
37
|
+
# Used to scale all speeds (cells/second) to per-frame movement
|
|
38
|
+
self.delta_time = 1.0 / fps
|
|
36
39
|
|
|
37
40
|
def generate_gif(self, output_path: str) -> None:
|
|
38
41
|
"""
|
|
@@ -79,18 +82,18 @@ class Animator:
|
|
|
79
82
|
for action in self.strategy.generate_actions(game_state):
|
|
80
83
|
game_state.ship.move_to(action.x)
|
|
81
84
|
while game_state.can_take_action() is False:
|
|
82
|
-
game_state.animate()
|
|
85
|
+
game_state.animate(self.delta_time)
|
|
83
86
|
yield renderer.render_frame()
|
|
84
87
|
|
|
85
88
|
if action.shoot:
|
|
86
89
|
game_state.shoot()
|
|
87
|
-
game_state.animate()
|
|
90
|
+
game_state.animate(self.delta_time)
|
|
88
91
|
yield renderer.render_frame()
|
|
89
92
|
|
|
90
93
|
force_kill_countdown = 100
|
|
91
94
|
# Add final frames showing completion
|
|
92
95
|
while not game_state.is_complete():
|
|
93
|
-
game_state.animate()
|
|
96
|
+
game_state.animate(self.delta_time)
|
|
94
97
|
yield renderer.render_frame()
|
|
95
98
|
|
|
96
99
|
force_kill_countdown -= 1
|
{gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/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, BULLET_TRAIL_SPACING, SHIP_POSITION_Y
|
|
8
8
|
from .drawable import Drawable
|
|
9
9
|
from .explosion import Explosion
|
|
10
10
|
|
|
@@ -37,9 +37,13 @@ class Bullet(Drawable):
|
|
|
37
37
|
return enemy
|
|
38
38
|
return None
|
|
39
39
|
|
|
40
|
-
def animate(self) -> None:
|
|
41
|
-
"""Update bullet position, check for collisions, and remove on hit.
|
|
42
|
-
|
|
40
|
+
def animate(self, delta_time: float) -> None:
|
|
41
|
+
"""Update bullet position, check for collisions, and remove on hit.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
delta_time: Time elapsed since last frame in seconds.
|
|
45
|
+
"""
|
|
46
|
+
self.y -= BULLET_SPEED * delta_time
|
|
43
47
|
hit_enemy = self._check_collision()
|
|
44
48
|
if hit_enemy:
|
|
45
49
|
explosion = Explosion(self.x, self.y, "small", self.game_state)
|
|
@@ -52,10 +56,9 @@ class Bullet(Drawable):
|
|
|
52
56
|
def draw(self, draw: ImageDraw.ImageDraw, context: "RenderContext") -> None:
|
|
53
57
|
"""Draw the bullet with trailing tail effect."""
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fade_factor = (i + 1) / trail_num / 2
|
|
59
|
+
for i in range(BULLET_TRAILING_LENGTH):
|
|
60
|
+
trail_y = self.y + (i + 1) * BULLET_TRAIL_SPACING
|
|
61
|
+
fade_factor = (i + 1) / BULLET_TRAILING_LENGTH / 2
|
|
59
62
|
self._draw_bullet(draw, context, (self.x, trail_y), fade_factor=fade_factor)
|
|
60
63
|
|
|
61
64
|
self._draw_bullet(draw, context, (self.x, self.y), fade_factor=0.3, offset=.6)
|
{gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/drawable.py
RENAMED
|
@@ -13,8 +13,12 @@ class Drawable(ABC):
|
|
|
13
13
|
"""Interface for objects that can be animated and drawn."""
|
|
14
14
|
|
|
15
15
|
@abstractmethod
|
|
16
|
-
def animate(self) -> None:
|
|
17
|
-
"""Update the object's state for the next animation frame.
|
|
16
|
+
def animate(self, delta_time: float) -> None:
|
|
17
|
+
"""Update the object's state for the next animation frame.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
delta_time: Time elapsed since last frame in seconds.
|
|
21
|
+
"""
|
|
18
22
|
pass
|
|
19
23
|
|
|
20
24
|
@abstractmethod
|
{gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/enemy.py
RENAMED
|
@@ -42,7 +42,7 @@ class Enemy(Drawable):
|
|
|
42
42
|
self.game_state.explosions.append(explosion)
|
|
43
43
|
self.game_state.enemies.remove(self)
|
|
44
44
|
|
|
45
|
-
def animate(self) -> None:
|
|
45
|
+
def animate(self, delta_time: float) -> None:
|
|
46
46
|
"""Update enemy state for next frame (enemies don't animate currently)."""
|
|
47
47
|
pass
|
|
48
48
|
|
{gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/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_DURATION_LARGE,
|
|
11
|
+
EXPLOSION_DURATION_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:
|
|
@@ -29,36 +37,36 @@ class Explosion(Drawable):
|
|
|
29
37
|
self.x = x
|
|
30
38
|
self.y = y
|
|
31
39
|
self.game_state = game_state
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
self.max_radius =
|
|
35
|
-
self.particle_count =
|
|
36
|
-
# Generate random angles for each particle
|
|
40
|
+
self.elapsed_time = 0.0 # Seconds elapsed since explosion started
|
|
41
|
+
self.duration = EXPLOSION_DURATION_SMALL if size == "small" else EXPLOSION_DURATION_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
|
-
def animate(self) -> None:
|
|
40
|
-
"""Progress the explosion animation and remove when complete.
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
def animate(self, delta_time: float) -> None:
|
|
47
|
+
"""Progress the explosion animation and remove when complete.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
delta_time: Time elapsed since last frame in seconds.
|
|
51
|
+
"""
|
|
52
|
+
self.elapsed_time += delta_time
|
|
53
|
+
if self.elapsed_time >= self.duration:
|
|
43
54
|
self.game_state.explosions.remove(self)
|
|
44
55
|
|
|
45
56
|
def draw(self, draw: ImageDraw.ImageDraw, context: "RenderContext") -> None:
|
|
46
57
|
"""Draw expanding particle explosion with fade effect."""
|
|
47
|
-
|
|
48
|
-
progress = self.
|
|
58
|
+
|
|
59
|
+
progress = self.elapsed_time / self.duration
|
|
49
60
|
fade = 1 - progress # Fade out as animation progresses
|
|
50
61
|
|
|
51
|
-
# Get center position
|
|
52
62
|
center_x, center_y = context.get_cell_position(self.x, self.y)
|
|
53
63
|
center_x += context.cell_size // 2
|
|
54
64
|
center_y += context.cell_size // 2
|
|
55
65
|
|
|
56
|
-
# Draw expanding particles in random directions
|
|
57
66
|
for i in range(self.particle_count):
|
|
58
67
|
distance = progress * self.max_radius
|
|
59
68
|
angle = self.particle_angles[i]
|
|
60
69
|
|
|
61
|
-
# Particle position using random angle
|
|
62
70
|
px = int(center_x + distance * math.cos(angle))
|
|
63
71
|
py = int(center_y + distance * math.sin(angle))
|
|
64
72
|
|
{gh_space_shooter-0.0.2 → gh_space_shooter-0.0.4}/src/gh_space_shooter/game/drawables/ship.py
RENAMED
|
@@ -19,7 +19,7 @@ class Ship(Drawable):
|
|
|
19
19
|
"""Initialize the ship at starting position."""
|
|
20
20
|
self.x: float = 25 # Start middle of screen
|
|
21
21
|
self.target_x = self.x
|
|
22
|
-
self.shoot_cooldown = 0 #
|
|
22
|
+
self.shoot_cooldown = 0.0 # Seconds until ship can shoot again
|
|
23
23
|
self.game_state = game_state
|
|
24
24
|
|
|
25
25
|
def move_to(self, x: int):
|
|
@@ -37,18 +37,23 @@ class Ship(Drawable):
|
|
|
37
37
|
|
|
38
38
|
def can_shoot(self) -> bool:
|
|
39
39
|
"""Check if ship can shoot (cooldown has finished)."""
|
|
40
|
-
return self.shoot_cooldown
|
|
40
|
+
return self.shoot_cooldown <= 0
|
|
41
41
|
|
|
42
|
-
def animate(self) -> None:
|
|
43
|
-
"""Update ship position, moving toward target at constant speed.
|
|
42
|
+
def animate(self, delta_time: float) -> None:
|
|
43
|
+
"""Update ship position, moving toward target at constant speed.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
delta_time: Time elapsed since last frame in seconds.
|
|
47
|
+
"""
|
|
48
|
+
delta_x = SHIP_SPEED * delta_time
|
|
44
49
|
if self.x < self.target_x:
|
|
45
|
-
self.x = min(self.x +
|
|
50
|
+
self.x = min(self.x + delta_x, self.target_x)
|
|
46
51
|
elif self.x > self.target_x:
|
|
47
|
-
self.x = max(self.x -
|
|
52
|
+
self.x = max(self.x - delta_x, self.target_x)
|
|
48
53
|
|
|
49
|
-
# Decrement shoot cooldown
|
|
54
|
+
# Decrement shoot cooldown (scaled by delta_time)
|
|
50
55
|
if self.shoot_cooldown > 0:
|
|
51
|
-
self.shoot_cooldown -=
|
|
56
|
+
self.shoot_cooldown -= delta_time
|
|
52
57
|
|
|
53
58
|
def draw(self, draw: ImageDraw.ImageDraw, context: "RenderContext") -> None:
|
|
54
59
|
"""Draw a simple Galaga-style ship."""
|