gh-space-shooter 0.0.4__tar.gz → 0.0.5__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.4 → gh_space_shooter-0.0.5}/.github/workflows/publish.yml +5 -4
- gh_space_shooter-0.0.5/CLAUDE.md +87 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/PKG-INFO +6 -5
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/README.md +5 -4
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/pyproject.toml +2 -2
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/cli.py +25 -4
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/animator.py +18 -8
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/renderer.py +31 -5
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/uv.lock +1 -1
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/.github/dependabot.yml +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/.github/workflows/test.yml +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/.gitignore +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/.python-version +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/LICENSE +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/action.yml +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/example.gif +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/__init__.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/console_printer.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/constants.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/__init__.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/__init__.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/bullet.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/drawable.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/enemy.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/explosion.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/ship.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/starfield.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/game_state.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/render_context.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/__init__.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/base_strategy.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/column_strategy.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/random_strategy.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/row_strategy.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/github_client.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/py.typed +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/tests/conftest.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/tests/test_bullet_collision.py +0 -0
- {gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/tests/test_strategies.py +0 -0
|
@@ -45,11 +45,12 @@ jobs:
|
|
|
45
45
|
- name: Publish
|
|
46
46
|
run: uv publish --token ${{ secrets.PYPI_API_TOKEN }}
|
|
47
47
|
|
|
48
|
-
- name: GitHub Release v${{ steps.bump.outputs.current-version }}
|
|
48
|
+
- name: GitHub Release PIP v${{ steps.bump.outputs.current-version }}
|
|
49
49
|
uses: ncipollo/release-action@v1
|
|
50
50
|
with:
|
|
51
|
-
name: gh-space-shooter v${{ steps.bump.outputs.current-version }}
|
|
51
|
+
name: gh-space-shooter pypi v${{ steps.bump.outputs.current-version }}
|
|
52
52
|
tag: v${{ steps.bump.outputs.current-version }}
|
|
53
|
-
body: "Automated release of version v${{ steps.bump.outputs.current-version }}"
|
|
53
|
+
body: "Automated release of pypi version v${{ steps.bump.outputs.current-version }}"
|
|
54
54
|
artifacts: dist/*
|
|
55
|
-
token: ${{ secrets.GH_TOKEN }}
|
|
55
|
+
token: ${{ secrets.GH_TOKEN }}
|
|
56
|
+
generateReleaseNotes: true
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build and Development Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies (uses uv package manager)
|
|
9
|
+
uv sync
|
|
10
|
+
|
|
11
|
+
# Install with dev dependencies
|
|
12
|
+
uv sync --extra dev
|
|
13
|
+
|
|
14
|
+
# Run the CLI
|
|
15
|
+
uv run gh-space-shooter <username>
|
|
16
|
+
|
|
17
|
+
# Run tests
|
|
18
|
+
uv run pytest tests/ -v
|
|
19
|
+
|
|
20
|
+
# Run a single test file
|
|
21
|
+
uv run pytest tests/test_strategies.py -v
|
|
22
|
+
|
|
23
|
+
# Run a specific test
|
|
24
|
+
uv run pytest tests/test_strategies.py::test_column_strategy -v
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Environment Setup
|
|
28
|
+
|
|
29
|
+
Requires a GitHub Personal Access Token with `read:user` scope:
|
|
30
|
+
```bash
|
|
31
|
+
export GH_TOKEN=your_token_here
|
|
32
|
+
# Or create a .env file with GH_TOKEN=your_token
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Project Context
|
|
36
|
+
|
|
37
|
+
**Current main usage**: GitHub Action that automatically updates a game GIF in user repositories daily (see `.github/workflows/` for the action definition).
|
|
38
|
+
|
|
39
|
+
**Planned**: Wrap this into a Python webapp for on-demand GIF generation.
|
|
40
|
+
|
|
41
|
+
## Architecture Overview
|
|
42
|
+
|
|
43
|
+
This is a CLI tool that transforms GitHub contribution graphs into animated space shooter GIFs using Pillow.
|
|
44
|
+
|
|
45
|
+
### Core Flow
|
|
46
|
+
|
|
47
|
+
1. **CLI (`cli.py`)** - Typer-based entry point that orchestrates the pipeline
|
|
48
|
+
2. **GitHubClient (`github_client.py`)** - Fetches contribution data via GitHub GraphQL API, returns typed `ContributionData` dict
|
|
49
|
+
3. **Animator (`game/animator.py`)** - Main game loop that coordinates strategy execution and frame generation
|
|
50
|
+
4. **GameState (`game/game_state.py`)** - Central state container holding ship, enemies, bullets, explosions
|
|
51
|
+
5. **Renderer (`game/renderer.py`)** - Converts GameState to PIL Images each frame
|
|
52
|
+
|
|
53
|
+
### Strategy Pattern
|
|
54
|
+
|
|
55
|
+
Strategies (`game/strategies/`) define how the ship clears enemies:
|
|
56
|
+
- `BaseStrategy` - Abstract base defining `generate_actions(game_state) -> Iterator[Action]`
|
|
57
|
+
- `ColumnStrategy` - Clears enemies column by column (left to right)
|
|
58
|
+
- `RowStrategy` - Clears enemies row by row (top to bottom)
|
|
59
|
+
- `RandomStrategy` - Targets enemies in random order
|
|
60
|
+
|
|
61
|
+
Strategies yield `Action(x, shoot)` objects. The Animator processes these: moving the ship to position `x`, waiting for movement/cooldown to complete, then shooting if `shoot=True`.
|
|
62
|
+
|
|
63
|
+
### Drawable System
|
|
64
|
+
|
|
65
|
+
All game objects inherit from `Drawable` (`game/drawables/drawable.py`):
|
|
66
|
+
- `animate(delta_time)` - Update state (position, cooldowns, particles)
|
|
67
|
+
- `draw(draw, context)` - Render to PIL ImageDraw
|
|
68
|
+
|
|
69
|
+
Drawables: `Ship`, `Enemy`, `Bullet`, `Explosion`, `Starfield`
|
|
70
|
+
|
|
71
|
+
The `RenderContext` (`game/render_context.py`) holds theming (colors, cell sizes, padding) and coordinate conversion helpers.
|
|
72
|
+
|
|
73
|
+
### Animation Loop
|
|
74
|
+
|
|
75
|
+
In `Animator._generate_frames()`:
|
|
76
|
+
1. Strategy yields next action
|
|
77
|
+
2. Ship moves to target x position (animate frames until arrived)
|
|
78
|
+
3. Ship shoots if action.shoot (animate frames for bullet travel + explosions)
|
|
79
|
+
4. Repeat until all enemies destroyed
|
|
80
|
+
|
|
81
|
+
Frame rate is configurable (default 40 FPS). All speeds use delta_time for frame-rate independence.
|
|
82
|
+
|
|
83
|
+
### Key Constants (`constants.py`)
|
|
84
|
+
|
|
85
|
+
- `NUM_WEEKS = 52` - Contribution graph width
|
|
86
|
+
- `NUM_DAYS = 7` - Contribution graph height
|
|
87
|
+
- Speeds are in cells/second, durations in seconds
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gh-space-shooter
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
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
|
|
@@ -122,14 +122,15 @@ gh-space-shooter torvalds --output my-epic-game.gif
|
|
|
122
122
|
gh-space-shooter torvalds -o my-game.gif
|
|
123
123
|
|
|
124
124
|
# Choose enemy attack strategy
|
|
125
|
-
gh-space-shooter torvalds --strategy column # Enemies attack in columns
|
|
126
125
|
gh-space-shooter torvalds --strategy row # Enemies attack in rows
|
|
127
126
|
gh-space-shooter torvalds -s random # Random chaos (default)
|
|
128
127
|
|
|
129
128
|
# Adjust animation frame rate
|
|
130
|
-
gh-space-shooter torvalds --fps 25 #
|
|
131
|
-
gh-space-shooter torvalds --fps 40 # Default
|
|
132
|
-
|
|
129
|
+
gh-space-shooter torvalds --fps 25 # Lower Frame rate, Smaller file size
|
|
130
|
+
gh-space-shooter torvalds --fps 40 # Default Frame rate, Larger file size
|
|
131
|
+
|
|
132
|
+
# Stop the animation earlier
|
|
133
|
+
gh-space-shooter torvalds --max-frame 200 # Stop after 200 frames
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
This creates an animated GIF showing:
|
|
@@ -106,14 +106,15 @@ gh-space-shooter torvalds --output my-epic-game.gif
|
|
|
106
106
|
gh-space-shooter torvalds -o my-game.gif
|
|
107
107
|
|
|
108
108
|
# Choose enemy attack strategy
|
|
109
|
-
gh-space-shooter torvalds --strategy column # Enemies attack in columns
|
|
110
109
|
gh-space-shooter torvalds --strategy row # Enemies attack in rows
|
|
111
110
|
gh-space-shooter torvalds -s random # Random chaos (default)
|
|
112
111
|
|
|
113
112
|
# Adjust animation frame rate
|
|
114
|
-
gh-space-shooter torvalds --fps 25 #
|
|
115
|
-
gh-space-shooter torvalds --fps 40 # Default
|
|
116
|
-
|
|
113
|
+
gh-space-shooter torvalds --fps 25 # Lower Frame rate, Smaller file size
|
|
114
|
+
gh-space-shooter torvalds --fps 40 # Default Frame rate, Larger file size
|
|
115
|
+
|
|
116
|
+
# Stop the animation earlier
|
|
117
|
+
gh-space-shooter torvalds --max-frame 200 # Stop after 200 frames
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
This creates an animated GIF showing:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gh-space-shooter"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.5"
|
|
4
4
|
description = "A CLI tool that visualizes GitHub contribution graphs as gamified GIFs"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -29,7 +29,7 @@ build-backend = "hatchling.build"
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
[tool.bumpversion]
|
|
32
|
-
current_version = "0.0.
|
|
32
|
+
current_version = "0.0.5"
|
|
33
33
|
parse = """(?x)
|
|
34
34
|
(?P<major>0|[1-9]\\d*)\\.
|
|
35
35
|
(?P<minor>0|[1-9]\\d*)\\.
|
|
@@ -60,6 +60,16 @@ def main(
|
|
|
60
60
|
"--fps",
|
|
61
61
|
help="Frames per second for the animation",
|
|
62
62
|
),
|
|
63
|
+
maxFrame: int | None = typer.Option(
|
|
64
|
+
None,
|
|
65
|
+
"--max-frame",
|
|
66
|
+
help="Maximum number of frames to generate",
|
|
67
|
+
),
|
|
68
|
+
watermark: bool = typer.Option(
|
|
69
|
+
False,
|
|
70
|
+
"--watermark",
|
|
71
|
+
help="Add watermark to the GIF",
|
|
72
|
+
),
|
|
63
73
|
) -> None:
|
|
64
74
|
"""
|
|
65
75
|
Fetch or load GitHub contribution graph data and display it.
|
|
@@ -95,7 +105,7 @@ def main(
|
|
|
95
105
|
_save_data_to_file(data, raw_output)
|
|
96
106
|
|
|
97
107
|
# Generate GIF if requested
|
|
98
|
-
_generate_gif(data, out, strategy, fps)
|
|
108
|
+
_generate_gif(data, out, strategy, fps, watermark, maxFrame)
|
|
99
109
|
|
|
100
110
|
except CLIError as e:
|
|
101
111
|
err_console.print(f"[bold red]Error:[/bold red] {e}")
|
|
@@ -151,7 +161,14 @@ def _save_data_to_file(data: ContributionData, file_path: str) -> None:
|
|
|
151
161
|
raise CLIError(f"Failed to save file '{file_path}': {e}")
|
|
152
162
|
|
|
153
163
|
|
|
154
|
-
def _generate_gif(
|
|
164
|
+
def _generate_gif(
|
|
165
|
+
data: ContributionData,
|
|
166
|
+
file_path: str,
|
|
167
|
+
strategy_name: str,
|
|
168
|
+
fps: int,
|
|
169
|
+
watermark: bool,
|
|
170
|
+
maxFrame: int | None
|
|
171
|
+
) -> None:
|
|
155
172
|
"""Generate animated GIF visualization."""
|
|
156
173
|
# GIF format limitation: delays below 20ms (>50 FPS) are clamped by most browsers
|
|
157
174
|
if fps > 50:
|
|
@@ -174,8 +191,12 @@ def _generate_gif(data: ContributionData, file_path: str, strategy_name: str, fp
|
|
|
174
191
|
|
|
175
192
|
# Create animator and generate GIF
|
|
176
193
|
try:
|
|
177
|
-
animator = Animator(data, strategy, fps=fps)
|
|
178
|
-
animator.generate_gif(
|
|
194
|
+
animator = Animator(data, strategy, fps=fps, watermark=watermark)
|
|
195
|
+
buffer = animator.generate_gif(maxFrame=maxFrame)
|
|
196
|
+
console.print("[bold blue]Saving GIF animation...[/bold blue]")
|
|
197
|
+
with open(file_path, "wb") as f:
|
|
198
|
+
f.write(buffer.getvalue())
|
|
199
|
+
|
|
179
200
|
console.print(f"[green]✓[/green] GIF saved to {file_path}")
|
|
180
201
|
except Exception as e:
|
|
181
202
|
raise CLIError(f"Failed to generate GIF: {e}")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Animator for generating GIF animations from game strategies."""
|
|
2
2
|
|
|
3
|
+
from io import BytesIO
|
|
3
4
|
from typing import Iterator
|
|
4
5
|
|
|
5
6
|
from PIL import Image
|
|
6
7
|
|
|
7
|
-
|
|
8
8
|
from ..github_client import ContributionData
|
|
9
9
|
from .game_state import GameState
|
|
10
10
|
from .renderer import Renderer
|
|
@@ -20,6 +20,7 @@ class Animator:
|
|
|
20
20
|
contribution_data: ContributionData,
|
|
21
21
|
strategy: BaseStrategy,
|
|
22
22
|
fps: int,
|
|
23
|
+
watermark: bool = False,
|
|
23
24
|
):
|
|
24
25
|
"""
|
|
25
26
|
Initialize animator.
|
|
@@ -28,16 +29,18 @@ class Animator:
|
|
|
28
29
|
contribution_data: The GitHub contribution data
|
|
29
30
|
strategy: The strategy to use for clearing enemies
|
|
30
31
|
fps: Frames per second for the animation
|
|
32
|
+
watermark: Whether to add watermark to the GIF
|
|
31
33
|
"""
|
|
32
34
|
self.contribution_data = contribution_data
|
|
33
35
|
self.strategy = strategy
|
|
34
36
|
self.fps = fps
|
|
37
|
+
self.watermark = watermark
|
|
35
38
|
self.frame_duration = 1000 // fps
|
|
36
39
|
# Delta time in seconds per frame
|
|
37
40
|
# Used to scale all speeds (cells/second) to per-frame movement
|
|
38
41
|
self.delta_time = 1.0 / fps
|
|
39
42
|
|
|
40
|
-
def generate_gif(self,
|
|
43
|
+
def generate_gif(self, maxFrame: int | None) -> BytesIO:
|
|
41
44
|
"""
|
|
42
45
|
Generate animated GIF and save to file.
|
|
43
46
|
|
|
@@ -46,20 +49,27 @@ class Animator:
|
|
|
46
49
|
"""
|
|
47
50
|
# Initialize game state
|
|
48
51
|
game_state = GameState(self.contribution_data)
|
|
49
|
-
renderer = Renderer(game_state, RenderContext.darkmode())
|
|
52
|
+
renderer = Renderer(game_state, RenderContext.darkmode(), watermark=self.watermark)
|
|
50
53
|
|
|
51
|
-
frames =
|
|
54
|
+
frames: list[Image.Image] = []
|
|
55
|
+
for frame in self._generate_frames(game_state, renderer):
|
|
56
|
+
frames.append(frame)
|
|
57
|
+
if maxFrame is not None and len(frames) >= maxFrame:
|
|
58
|
+
break
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
gif_buffer = BytesIO()
|
|
54
61
|
if frames:
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
frames[0].save(
|
|
63
|
+
gif_buffer,
|
|
64
|
+
format="gif",
|
|
57
65
|
save_all=True,
|
|
58
|
-
append_images=
|
|
66
|
+
append_images=frames[1:],
|
|
59
67
|
duration=self.frame_duration,
|
|
60
68
|
loop=0, # Loop forever
|
|
61
69
|
optimize=False,
|
|
62
70
|
)
|
|
71
|
+
|
|
72
|
+
return gif_buffer
|
|
63
73
|
|
|
64
74
|
def _generate_frames(
|
|
65
75
|
self, game_state: GameState, renderer: Renderer
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
"""Renderer for drawing game frames using Pillow."""
|
|
2
2
|
|
|
3
|
-
from PIL import Image, ImageDraw
|
|
3
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
4
4
|
|
|
5
5
|
from ..constants import NUM_WEEKS, SHIP_POSITION_Y
|
|
6
6
|
from .game_state import GameState
|
|
7
7
|
from .render_context import RenderContext
|
|
8
8
|
|
|
9
|
+
WATERMARK_TEXT = "by czl9707/gh-space-shooter"
|
|
10
|
+
|
|
11
|
+
|
|
9
12
|
class Renderer:
|
|
10
13
|
"""Renders game state as PIL Images."""
|
|
11
|
-
def __init__(self, game_state: GameState, render_context: RenderContext):
|
|
14
|
+
def __init__(self, game_state: GameState, render_context: RenderContext, watermark: bool = False):
|
|
12
15
|
"""
|
|
13
16
|
Initialize renderer.
|
|
14
17
|
|
|
15
18
|
Args:
|
|
16
19
|
game_state: The game state to render
|
|
17
20
|
render_context: Rendering configuration and theming
|
|
21
|
+
watermark: Whether to add watermark to frames
|
|
18
22
|
"""
|
|
19
23
|
self.game_state = game_state
|
|
20
|
-
|
|
21
24
|
self.context = render_context
|
|
25
|
+
self.watermark = watermark
|
|
22
26
|
|
|
23
27
|
self.grid_width = NUM_WEEKS * (self.context.cell_size + self.context.cell_spacing)
|
|
24
28
|
self.grid_height = SHIP_POSITION_Y * (self.context.cell_size + self.context.cell_spacing)
|
|
@@ -34,11 +38,33 @@ class Renderer:
|
|
|
34
38
|
"""
|
|
35
39
|
# Create image with background color
|
|
36
40
|
img = Image.new("RGB", (self.width, self.height), self.context.background_color)
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
# Draw game state
|
|
39
43
|
overlay = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
|
|
40
44
|
draw = ImageDraw.Draw(overlay, "RGBA")
|
|
41
45
|
self.game_state.draw(draw, self.context)
|
|
46
|
+
|
|
47
|
+
# Draw watermark if enabled
|
|
48
|
+
if self.watermark:
|
|
49
|
+
self._draw_watermark(draw)
|
|
50
|
+
|
|
42
51
|
combined = Image.alpha_composite(img.convert("RGBA"), overlay)
|
|
43
|
-
|
|
52
|
+
|
|
44
53
|
return combined.convert("RGB").convert("P", palette=Image.Palette.ADAPTIVE)
|
|
54
|
+
|
|
55
|
+
def _draw_watermark(self, draw: ImageDraw.ImageDraw) -> None:
|
|
56
|
+
"""Draw watermark text in the bottom-right corner."""
|
|
57
|
+
font = ImageFont.load_default()
|
|
58
|
+
color = (100, 100, 100, 128) # Semi-transparent gray
|
|
59
|
+
margin = 5
|
|
60
|
+
|
|
61
|
+
# Get text bounding box
|
|
62
|
+
bbox = draw.textbbox((0, 0), WATERMARK_TEXT, font=font)
|
|
63
|
+
text_width = bbox[2] - bbox[0]
|
|
64
|
+
text_height = bbox[3] - bbox[1]
|
|
65
|
+
|
|
66
|
+
# Position in bottom-right corner
|
|
67
|
+
x = self.width - text_width - margin
|
|
68
|
+
y = self.height - text_height - margin
|
|
69
|
+
|
|
70
|
+
draw.text((x, y), WATERMARK_TEXT, font=font, fill=color)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/__init__.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/bullet.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/drawable.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/enemy.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/explosion.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/ship.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/drawables/starfield.py
RENAMED
|
File without changes
|
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/render_context.py
RENAMED
|
File without changes
|
{gh_space_shooter-0.0.4 → gh_space_shooter-0.0.5}/src/gh_space_shooter/game/strategies/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|