tqrex 0.1.0__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.
- tqrex-0.1.0/.coverage +0 -0
- tqrex-0.1.0/.github/workflows/ci.yml +49 -0
- tqrex-0.1.0/.gitignore +10 -0
- tqrex-0.1.0/LICENSE +21 -0
- tqrex-0.1.0/PKG-INFO +226 -0
- tqrex-0.1.0/README.md +173 -0
- tqrex-0.1.0/demo.gif +0 -0
- tqrex-0.1.0/examples/basic_usage.py +58 -0
- tqrex-0.1.0/prototype.py +496 -0
- tqrex-0.1.0/pyproject.toml +90 -0
- tqrex-0.1.0/src/playgress/__init__.py +4 -0
- tqrex-0.1.0/src/playgress/api.py +148 -0
- tqrex-0.1.0/src/playgress/config.py +222 -0
- tqrex-0.1.0/src/playgress/engine/__init__.py +0 -0
- tqrex-0.1.0/src/playgress/engine/ai.py +49 -0
- tqrex-0.1.0/src/playgress/engine/game.py +159 -0
- tqrex-0.1.0/src/playgress/engine/obstacles.py +71 -0
- tqrex-0.1.0/src/playgress/engine/physics.py +103 -0
- tqrex-0.1.0/src/playgress/models.py +127 -0
- tqrex-0.1.0/src/playgress/terminal/__init__.py +0 -0
- tqrex-0.1.0/src/playgress/terminal/input.py +95 -0
- tqrex-0.1.0/src/playgress/terminal/render.py +180 -0
- tqrex-0.1.0/tests/__init__.py +0 -0
- tqrex-0.1.0/tests/test_api.py +52 -0
- tqrex-0.1.0/tests/test_models.py +46 -0
- tqrex-0.1.0/tests/test_physics.py +70 -0
tqrex-0.1.0/.coverage
ADDED
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint & type-check
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.11"
|
|
18
|
+
- name: Install dev deps
|
|
19
|
+
run: pip install -e ".[dev]"
|
|
20
|
+
- name: Ruff check
|
|
21
|
+
run: ruff check src/ tests/
|
|
22
|
+
- name: Ruff format
|
|
23
|
+
run: ruff format --check src/ tests/
|
|
24
|
+
- name: Mypy
|
|
25
|
+
run: mypy src/playgress
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
name: Test — Python ${{ matrix.python-version }} / ${{ matrix.os }}
|
|
29
|
+
runs-on: ${{ matrix.os }}
|
|
30
|
+
strategy:
|
|
31
|
+
fail-fast: false
|
|
32
|
+
matrix:
|
|
33
|
+
os: [ubuntu-latest, macos-latest]
|
|
34
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: ${{ matrix.python-version }}
|
|
40
|
+
- name: Install dev deps
|
|
41
|
+
run: pip install -e ".[dev]"
|
|
42
|
+
- name: Run tests (skip terminal-requiring tests in CI)
|
|
43
|
+
run: pytest -m "not terminal" --cov=playgress --cov-report=xml
|
|
44
|
+
- name: Upload coverage
|
|
45
|
+
uses: codecov/codecov-action@v4
|
|
46
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
|
|
47
|
+
with:
|
|
48
|
+
files: coverage.xml
|
|
49
|
+
fail_ci_if_error: false
|
tqrex-0.1.0/.gitignore
ADDED
tqrex-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikhail Samoilov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tqrex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tqrex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A progress bar that lets you play the Chrome T-Rex game in your terminal
|
|
5
|
+
Project-URL: Homepage, https://github.com/Samoilov2004/tqrex
|
|
6
|
+
Project-URL: Repository, https://github.com/Samoilov2004/tqrex
|
|
7
|
+
Project-URL: Issues, https://github.com/Samoilov2004/tqrex/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Samoilov2004/tqrex/releases
|
|
9
|
+
Author: Mikhail Samoilov
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Mikhail Samoilov
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: cli,dinosaur,fun,game,progress,progress-bar,terminal,tqdm,trex
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Environment :: Console
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Games/Entertainment :: Arcade
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Classifier: Topic :: Terminals
|
|
45
|
+
Classifier: Typing :: Typed
|
|
46
|
+
Requires-Python: >=3.11
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# playgress
|
|
55
|
+
|
|
56
|
+
> A progress bar that lets you play the Chrome T-Rex game in your terminal.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
Your long-running script doesn't have to be boring. **playgress** wraps any
|
|
61
|
+
iterable in a full T-Rex side-scroller — jump cacti, dodge birds, rack up a
|
|
62
|
+
high score — while your real work happens in the background. When the task
|
|
63
|
+
finishes the game keeps running until you decide to quit.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- **Zero runtime dependencies** — pure Python standard library
|
|
70
|
+
- **Drop-in `tqdm` replacement** — `track()` has the same call signature
|
|
71
|
+
- **`Progress` context manager** — for manual update control
|
|
72
|
+
- **60 FPS physics** — faithful gravity, jump arc, and speed curve from the
|
|
73
|
+
original Chrome dino
|
|
74
|
+
- **Autoplay AI** — built-in geometric look-ahead controller (`autoplay=True`)
|
|
75
|
+
- **Double-buffered renderer** — flicker-free diff-based redraw, single write
|
|
76
|
+
per frame
|
|
77
|
+
- **Cross-platform** — macOS, Linux, Windows (ANSI terminal required)
|
|
78
|
+
- **Python 3.11+**
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install playgress
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or for local development:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/mikhail-samoilov/playgress
|
|
92
|
+
cd playgress
|
|
93
|
+
pip install -e ".[dev]"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
### Drop-in replacement for `tqdm`
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import time
|
|
104
|
+
from playgress import track
|
|
105
|
+
|
|
106
|
+
for item in track(range(100), description="Processing"):
|
|
107
|
+
time.sleep(0.05)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Any iterable works
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
files = ["data_001.csv", "data_002.csv", ...]
|
|
114
|
+
|
|
115
|
+
for f in track(files, description="Crunching files"):
|
|
116
|
+
crunch(f)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Manual control with the `Progress` context manager
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from playgress import Progress
|
|
123
|
+
|
|
124
|
+
steps = ["Download", "Extract", "Build", "Install"]
|
|
125
|
+
with Progress(total=len(steps), description="Installing") as p:
|
|
126
|
+
for step in steps:
|
|
127
|
+
p.set_description(f"Step: {step}")
|
|
128
|
+
run(step)
|
|
129
|
+
p.update(1)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Let the AI play for you
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
for epoch in track(range(200), description="Training", autoplay=True):
|
|
136
|
+
train_one_epoch(epoch)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Keyboard controls
|
|
142
|
+
|
|
143
|
+
| Key | Action |
|
|
144
|
+
|-----|--------|
|
|
145
|
+
| `Space` / `↑` | Jump |
|
|
146
|
+
| `↓` | Duck on ground / fast-drop mid-air |
|
|
147
|
+
| `R` | Restart after game-over; replay after task completes |
|
|
148
|
+
| `Q` / `Ctrl-C` | Quit — unblocks the script |
|
|
149
|
+
|
|
150
|
+
> **Note:** The game keeps running after all items are processed so you can
|
|
151
|
+
> finish your current run. Press **Q** when you're done.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Themes
|
|
156
|
+
|
|
157
|
+
The default theme uses prototype-verified ASCII art that renders crisply in
|
|
158
|
+
every monospace font:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
++++ /|\/|\ <o=-
|
|
162
|
+
++ ++Q+++ | | |
|
|
163
|
+
+ +++++_ww
|
|
164
|
+
++++++
|
|
165
|
+
| |
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
An `EMOJI` theme (`theme=Theme.EMOJI`) for full Unicode width terminals is on
|
|
169
|
+
the roadmap.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## API reference
|
|
174
|
+
|
|
175
|
+
### `track(iterable, *, description, total, theme, autoplay, disable)`
|
|
176
|
+
|
|
177
|
+
| Parameter | Type | Default | Description |
|
|
178
|
+
|-----------|------|---------|-------------|
|
|
179
|
+
| `iterable` | `Iterable[T]` | — | Items to iterate over |
|
|
180
|
+
| `description` | `str` | `"Processing..."` | Status line label |
|
|
181
|
+
| `total` | `int \| None` | `None` | Item count; auto-inferred from `len()` |
|
|
182
|
+
| `theme` | `Theme` | `Theme.AUTO` | Sprite theme |
|
|
183
|
+
| `autoplay` | `bool` | `False` | Enable AI controller |
|
|
184
|
+
| `disable` | `bool` | `False` | Skip game; yield items silently |
|
|
185
|
+
|
|
186
|
+
Returns a generator that yields items from `iterable` unchanged.
|
|
187
|
+
|
|
188
|
+
### `Progress(total, description, theme, autoplay)`
|
|
189
|
+
|
|
190
|
+
| Method | Description |
|
|
191
|
+
|--------|-------------|
|
|
192
|
+
| `update(n=1)` | Advance by *n* steps (thread-safe) |
|
|
193
|
+
| `set_description(text)` | Update the status line label |
|
|
194
|
+
| `fraction` | Current progress as a float in `[0, 1]` |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## CI / non-interactive environments
|
|
199
|
+
|
|
200
|
+
When `stdout` is not a TTY (piped output, GitHub Actions, `pytest`) the game
|
|
201
|
+
is automatically bypassed and items are yielded with zero overhead. No
|
|
202
|
+
`disable=True` guard needed.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## How it works
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Main thread Game thread (60 FPS) Input thread
|
|
210
|
+
────────── ──────────────────── ────────────
|
|
211
|
+
for item in track():
|
|
212
|
+
yield item ←→ read ProgressState ←← push InputEvent
|
|
213
|
+
completed += 1 run physics (termios raw mode /
|
|
214
|
+
render diff frame msvcrt poll)
|
|
215
|
+
shutdown.wait()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Three threads, two shared objects: a `ProgressState` (protected by a
|
|
219
|
+
`threading.Lock`) and a `queue.Queue[InputEvent]` (lock-free). The game thread
|
|
220
|
+
owns all `GameState` mutations — no other thread touches it.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT — see [LICENSE](LICENSE).
|
tqrex-0.1.0/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# playgress
|
|
2
|
+
|
|
3
|
+
> A progress bar that lets you play the Chrome T-Rex game in your terminal.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Your long-running script doesn't have to be boring. **playgress** wraps any
|
|
8
|
+
iterable in a full T-Rex side-scroller — jump cacti, dodge birds, rack up a
|
|
9
|
+
high score — while your real work happens in the background. When the task
|
|
10
|
+
finishes the game keeps running until you decide to quit.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Zero runtime dependencies** — pure Python standard library
|
|
17
|
+
- **Drop-in `tqdm` replacement** — `track()` has the same call signature
|
|
18
|
+
- **`Progress` context manager** — for manual update control
|
|
19
|
+
- **60 FPS physics** — faithful gravity, jump arc, and speed curve from the
|
|
20
|
+
original Chrome dino
|
|
21
|
+
- **Autoplay AI** — built-in geometric look-ahead controller (`autoplay=True`)
|
|
22
|
+
- **Double-buffered renderer** — flicker-free diff-based redraw, single write
|
|
23
|
+
per frame
|
|
24
|
+
- **Cross-platform** — macOS, Linux, Windows (ANSI terminal required)
|
|
25
|
+
- **Python 3.11+**
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install playgress
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or for local development:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/mikhail-samoilov/playgress
|
|
39
|
+
cd playgress
|
|
40
|
+
pip install -e ".[dev]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
### Drop-in replacement for `tqdm`
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import time
|
|
51
|
+
from playgress import track
|
|
52
|
+
|
|
53
|
+
for item in track(range(100), description="Processing"):
|
|
54
|
+
time.sleep(0.05)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Any iterable works
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
files = ["data_001.csv", "data_002.csv", ...]
|
|
61
|
+
|
|
62
|
+
for f in track(files, description="Crunching files"):
|
|
63
|
+
crunch(f)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Manual control with the `Progress` context manager
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from playgress import Progress
|
|
70
|
+
|
|
71
|
+
steps = ["Download", "Extract", "Build", "Install"]
|
|
72
|
+
with Progress(total=len(steps), description="Installing") as p:
|
|
73
|
+
for step in steps:
|
|
74
|
+
p.set_description(f"Step: {step}")
|
|
75
|
+
run(step)
|
|
76
|
+
p.update(1)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Let the AI play for you
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
for epoch in track(range(200), description="Training", autoplay=True):
|
|
83
|
+
train_one_epoch(epoch)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Keyboard controls
|
|
89
|
+
|
|
90
|
+
| Key | Action |
|
|
91
|
+
|-----|--------|
|
|
92
|
+
| `Space` / `↑` | Jump |
|
|
93
|
+
| `↓` | Duck on ground / fast-drop mid-air |
|
|
94
|
+
| `R` | Restart after game-over; replay after task completes |
|
|
95
|
+
| `Q` / `Ctrl-C` | Quit — unblocks the script |
|
|
96
|
+
|
|
97
|
+
> **Note:** The game keeps running after all items are processed so you can
|
|
98
|
+
> finish your current run. Press **Q** when you're done.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Themes
|
|
103
|
+
|
|
104
|
+
The default theme uses prototype-verified ASCII art that renders crisply in
|
|
105
|
+
every monospace font:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
++++ /|\/|\ <o=-
|
|
109
|
+
++ ++Q+++ | | |
|
|
110
|
+
+ +++++_ww
|
|
111
|
+
++++++
|
|
112
|
+
| |
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
An `EMOJI` theme (`theme=Theme.EMOJI`) for full Unicode width terminals is on
|
|
116
|
+
the roadmap.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## API reference
|
|
121
|
+
|
|
122
|
+
### `track(iterable, *, description, total, theme, autoplay, disable)`
|
|
123
|
+
|
|
124
|
+
| Parameter | Type | Default | Description |
|
|
125
|
+
|-----------|------|---------|-------------|
|
|
126
|
+
| `iterable` | `Iterable[T]` | — | Items to iterate over |
|
|
127
|
+
| `description` | `str` | `"Processing..."` | Status line label |
|
|
128
|
+
| `total` | `int \| None` | `None` | Item count; auto-inferred from `len()` |
|
|
129
|
+
| `theme` | `Theme` | `Theme.AUTO` | Sprite theme |
|
|
130
|
+
| `autoplay` | `bool` | `False` | Enable AI controller |
|
|
131
|
+
| `disable` | `bool` | `False` | Skip game; yield items silently |
|
|
132
|
+
|
|
133
|
+
Returns a generator that yields items from `iterable` unchanged.
|
|
134
|
+
|
|
135
|
+
### `Progress(total, description, theme, autoplay)`
|
|
136
|
+
|
|
137
|
+
| Method | Description |
|
|
138
|
+
|--------|-------------|
|
|
139
|
+
| `update(n=1)` | Advance by *n* steps (thread-safe) |
|
|
140
|
+
| `set_description(text)` | Update the status line label |
|
|
141
|
+
| `fraction` | Current progress as a float in `[0, 1]` |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## CI / non-interactive environments
|
|
146
|
+
|
|
147
|
+
When `stdout` is not a TTY (piped output, GitHub Actions, `pytest`) the game
|
|
148
|
+
is automatically bypassed and items are yielded with zero overhead. No
|
|
149
|
+
`disable=True` guard needed.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## How it works
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Main thread Game thread (60 FPS) Input thread
|
|
157
|
+
────────── ──────────────────── ────────────
|
|
158
|
+
for item in track():
|
|
159
|
+
yield item ←→ read ProgressState ←← push InputEvent
|
|
160
|
+
completed += 1 run physics (termios raw mode /
|
|
161
|
+
render diff frame msvcrt poll)
|
|
162
|
+
shutdown.wait()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Three threads, two shared objects: a `ProgressState` (protected by a
|
|
166
|
+
`threading.Lock`) and a `queue.Queue[InputEvent]` (lock-free). The game thread
|
|
167
|
+
owns all `GameState` mutations — no other thread touches it.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT — see [LICENSE](LICENSE).
|
tqrex-0.1.0/demo.gif
ADDED
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from playgress import Progress, track
|
|
7
|
+
from playgress.config import Theme
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def example_track() -> None:
|
|
11
|
+
"""Process 60 items while playing the T-Rex game.
|
|
12
|
+
|
|
13
|
+
Keys during the game:
|
|
14
|
+
SPACE / ↑ jump
|
|
15
|
+
↓ duck / fast-drop mid-air
|
|
16
|
+
R restart after game-over or after task completes
|
|
17
|
+
Q quit (unblocks the script)
|
|
18
|
+
"""
|
|
19
|
+
files = [f"report_{i:04d}.csv" for i in range(60)]
|
|
20
|
+
for _f in track(files, description="Crunching reports"):
|
|
21
|
+
time.sleep(0.12) # simulate I/O work
|
|
22
|
+
|
|
23
|
+
def example_range() -> None:
|
|
24
|
+
"""Iterate over range(100) — total is inferred automatically."""
|
|
25
|
+
for _i in track(range(100), description="Training epochs"):
|
|
26
|
+
time.sleep(0.05)
|
|
27
|
+
|
|
28
|
+
def example_context() -> None:
|
|
29
|
+
"""Use Progress when you control the update timing explicitly."""
|
|
30
|
+
steps = ["Download", "Unpack", "Compile", "Link", "Install"]
|
|
31
|
+
with Progress(total=len(steps), description="Building package") as p:
|
|
32
|
+
for step in steps:
|
|
33
|
+
p.set_description(f"Step: {step}")
|
|
34
|
+
time.sleep(0.8)
|
|
35
|
+
p.update(1)
|
|
36
|
+
|
|
37
|
+
def example_autoplay() -> None:
|
|
38
|
+
"""Let the AI controller play while your batch job runs."""
|
|
39
|
+
dataset = list(range(80))
|
|
40
|
+
for _d in track(dataset, description="Embedding vectors", autoplay=True):
|
|
41
|
+
time.sleep(0.08)
|
|
42
|
+
|
|
43
|
+
_EXAMPLES: dict[str, tuple[str, object]] = {
|
|
44
|
+
"track": ("Process 60 files (default)", example_track),
|
|
45
|
+
"range": ("Iterate range(100)", example_range),
|
|
46
|
+
"context": ("Manual Progress manager", example_context),
|
|
47
|
+
"autoplay": ("Autoplay AI controller", example_autoplay),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
mode = sys.argv[1] if len(sys.argv) > 1 else "track"
|
|
52
|
+
if mode not in _EXAMPLES:
|
|
53
|
+
print(f"Unknown example '{mode}'. Choose from: {', '.join(_EXAMPLES)}")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
label, fn = _EXAMPLES[mode]
|
|
56
|
+
print(f"Running: {label}")
|
|
57
|
+
fn() # type: ignore[operator]
|
|
58
|
+
print("\nAll done. Goodbye!")
|