texastoast 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.
- texastoast-0.1.0/.gitignore +13 -0
- texastoast-0.1.0/LICENSE +17 -0
- texastoast-0.1.0/PKG-INFO +235 -0
- texastoast-0.1.0/README.md +210 -0
- texastoast-0.1.0/examples/game_template.py +181 -0
- texastoast-0.1.0/examples/hello_world.py +51 -0
- texastoast-0.1.0/examples/magma_hub_demo.py +98 -0
- texastoast-0.1.0/examples/rpg_demo.py +180 -0
- texastoast-0.1.0/examples/sprite_demo.py +159 -0
- texastoast-0.1.0/examples/tilemap_demo.py +52 -0
- texastoast-0.1.0/pyproject.toml +37 -0
- texastoast-0.1.0/tests/test_camera.py +56 -0
- texastoast-0.1.0/tests/test_collision.py +55 -0
- texastoast-0.1.0/tests/test_entity.py +45 -0
- texastoast-0.1.0/tests/test_i2c.py +80 -0
- texastoast-0.1.0/tests/test_input.py +38 -0
- texastoast-0.1.0/tests/test_tilemap.py +68 -0
- texastoast-0.1.0/texastoast/__init__.py +55 -0
- texastoast-0.1.0/texastoast/core/__init__.py +13 -0
- texastoast-0.1.0/texastoast/core/config.py +18 -0
- texastoast-0.1.0/texastoast/core/game.py +70 -0
- texastoast-0.1.0/texastoast/core/loop.py +67 -0
- texastoast-0.1.0/texastoast/i2c/__init__.py +5 -0
- texastoast-0.1.0/texastoast/i2c/bus.py +116 -0
- texastoast-0.1.0/texastoast/i2c/hub.py +128 -0
- texastoast-0.1.0/texastoast/i2c/protocol.py +78 -0
- texastoast-0.1.0/texastoast/input/__init__.py +16 -0
- texastoast-0.1.0/texastoast/input/abstract.py +44 -0
- texastoast-0.1.0/texastoast/input/keyboard.py +47 -0
- texastoast-0.1.0/texastoast/input/magma_hub.py +76 -0
- texastoast-0.1.0/texastoast/render/__init__.py +16 -0
- texastoast-0.1.0/texastoast/render/camera.py +39 -0
- texastoast-0.1.0/texastoast/render/canvas.py +76 -0
- texastoast-0.1.0/texastoast/render/sprite.py +108 -0
- texastoast-0.1.0/texastoast/ui/__init__.py +5 -0
- texastoast-0.1.0/texastoast/ui/dialogue.py +133 -0
- texastoast-0.1.0/texastoast/ui/hud.py +111 -0
- texastoast-0.1.0/texastoast/ui/menu.py +153 -0
- texastoast-0.1.0/texastoast/world/__init__.py +5 -0
- texastoast-0.1.0/texastoast/world/collision.py +81 -0
- texastoast-0.1.0/texastoast/world/entity.py +56 -0
- texastoast-0.1.0/texastoast/world/tilemap.py +77 -0
- texastoast-0.1.0/tools/tile_editor.py +407 -0
texastoast-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 magmacrunch media
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: texastoast
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python RPG engine and Magma Hub I2C layer for magmacrunch game systems
|
|
5
|
+
Author: magmacrunch media
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: game,i2c,raspberry-pi,rpg,tkinter
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Games/Entertainment
|
|
18
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Provides-Extra: hardware
|
|
23
|
+
Requires-Dist: smbus2>=0.6; extra == 'hardware'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# texastoast
|
|
27
|
+
|
|
28
|
+
Python RPG engine and Magma Hub I2C layer for magmacrunch game systems.
|
|
29
|
+
|
|
30
|
+
A tkinter-based 2D game engine inspired by [adenosine](https://github.com/magmacrunchmedia/adenosine), with I2C hardware abstraction for Raspberry Pi + Pico "Magma Hub" controllers.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Basic install (keyboard-only, no hardware)
|
|
36
|
+
pip install -e .
|
|
37
|
+
|
|
38
|
+
# With I2C hardware support (Raspberry Pi)
|
|
39
|
+
pip install -e ".[hardware]"
|
|
40
|
+
|
|
41
|
+
# Development (includes pytest)
|
|
42
|
+
pip install -e ".[dev]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from texastoast import Game, CanvasRenderer, TileMap, Entity, KeyboardInput
|
|
49
|
+
|
|
50
|
+
game = Game(title="My Game", width=400, height=300, fps=30)
|
|
51
|
+
renderer = CanvasRenderer(game.canvas, 400, 300)
|
|
52
|
+
keyboard = KeyboardInput(game.root)
|
|
53
|
+
|
|
54
|
+
tilemap = TileMap([
|
|
55
|
+
[1, 1, 1, 1, 1],
|
|
56
|
+
[1, 0, 0, 0, 1],
|
|
57
|
+
[1, 1, 1, 1, 1],
|
|
58
|
+
], tile_size=20, solid_tiles={1})
|
|
59
|
+
|
|
60
|
+
player = Entity(x=40, y=40, width=14, height=14, speed=100)
|
|
61
|
+
|
|
62
|
+
def update(dt):
|
|
63
|
+
state = keyboard.poll()
|
|
64
|
+
player.move(state.dx, state.dy, tilemap)
|
|
65
|
+
renderer.camera.follow(player.center_x, player.center_y,
|
|
66
|
+
map_width=tilemap.width, map_height=tilemap.height)
|
|
67
|
+
|
|
68
|
+
def render():
|
|
69
|
+
renderer.clear()
|
|
70
|
+
renderer.draw_tilemap(tilemap, {0: "#7cb342", 1: "#5d4037"})
|
|
71
|
+
renderer.draw_rect(player.x, player.y, player.width, player.height, "#e94560")
|
|
72
|
+
|
|
73
|
+
game.set_update(update)
|
|
74
|
+
game.set_render(render)
|
|
75
|
+
game.start()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
| Example | Description |
|
|
81
|
+
|---------|-------------|
|
|
82
|
+
| `examples/hello_world.py` | Minimal movement demo |
|
|
83
|
+
| `examples/tilemap_demo.py` | Walk around a larger map |
|
|
84
|
+
| `examples/sprite_demo.py` | Animated character sprites |
|
|
85
|
+
| `examples/rpg_demo.py` | NPCs, dialogue, menus, HUD |
|
|
86
|
+
| `examples/game_template.py` | Full game starting point |
|
|
87
|
+
| `examples/magma_hub_demo.py` | I2C controller input |
|
|
88
|
+
| `tools/tile_editor.py` | Tile map editor GUI |
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Core
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from texastoast import Game, Config, GameLoop
|
|
96
|
+
|
|
97
|
+
game = Game(title="My Game", width=640, height=480, fps=30)
|
|
98
|
+
game.set_update(update_fn) # def update(dt: float): ...
|
|
99
|
+
game.set_render(render_fn) # def render(): ...
|
|
100
|
+
game.start()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Rendering
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from texastoast import CanvasRenderer, Camera
|
|
107
|
+
|
|
108
|
+
renderer = CanvasRenderer(game.canvas, 640, 480)
|
|
109
|
+
renderer.draw_tilemap(tilemap, {0: "#7cb342", 1: "#5d4037"})
|
|
110
|
+
renderer.draw_rect(x, y, w, h, color)
|
|
111
|
+
renderer.draw_image(x, y, photo_image)
|
|
112
|
+
renderer.draw_hud_text(x, y, text, fill="#fff", font=("Courier", 10))
|
|
113
|
+
|
|
114
|
+
# Camera
|
|
115
|
+
renderer.camera.follow(target_x, target_y, map_width=800, map_height=600)
|
|
116
|
+
renderer.camera.set_position(x, y)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### World
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from texastoast import TileMap, Entity, AABB
|
|
123
|
+
|
|
124
|
+
# Tile map
|
|
125
|
+
tilemap = TileMap(grid_data, tile_size=16, solid_tiles={1, 2})
|
|
126
|
+
tilemap = TileMap.from_file("map.json", tile_size=16)
|
|
127
|
+
tilemap.save("map.json")
|
|
128
|
+
tilemap.get(col, row) # -> tile_id
|
|
129
|
+
tilemap.is_solid(col, row) # -> bool
|
|
130
|
+
tilemap.is_solid_at(world_x, world_y) # -> bool
|
|
131
|
+
|
|
132
|
+
# Entity
|
|
133
|
+
player = Entity(x=0, y=0, width=16, height=16, speed=100)
|
|
134
|
+
player.move(dx, dy, tilemap) # with collision
|
|
135
|
+
player.aabb # -> AABB for overlap checks
|
|
136
|
+
player.collides_with(other_entity) # -> bool
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Input
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from texastoast import KeyboardInput, InputState
|
|
143
|
+
|
|
144
|
+
keyboard = KeyboardInput(game.root)
|
|
145
|
+
state = keyboard.poll()
|
|
146
|
+
|
|
147
|
+
state.up, state.down, state.left, state.right # bool
|
|
148
|
+
state.a, state.b, state.start, state.select # bool
|
|
149
|
+
state.dx, state.dy # float (-1, 0, 1)
|
|
150
|
+
state.is_any_direction() # bool
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### I2C (Magma Hub)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from texastoast import I2CBus, MagmaHub, MagmaHubInput, CompositeInput
|
|
157
|
+
|
|
158
|
+
# Direct I2C
|
|
159
|
+
bus = I2CBus(1) # I2C bus number
|
|
160
|
+
hubs = MagmaHub.scan_buses(bus_numbers=[1])
|
|
161
|
+
hub = hubs[0]
|
|
162
|
+
hub.poll() # -> [ControllerState, ...]
|
|
163
|
+
|
|
164
|
+
# Input adapter (same interface as KeyboardInput)
|
|
165
|
+
hub_input = MagmaHubInput(hub, controller_index=0)
|
|
166
|
+
state = hub_input.poll()
|
|
167
|
+
|
|
168
|
+
# Auto-fallback composite
|
|
169
|
+
controls = CompositeInput(keyboard, hub_input)
|
|
170
|
+
state = controls.poll() # uses hub if connected, else keyboard
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### UI
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from texastoast.ui import DialogueBox, Menu, HUD
|
|
177
|
+
|
|
178
|
+
# Dialogue
|
|
179
|
+
dialogue = DialogueBox(game.canvas, 640, 480)
|
|
180
|
+
dialogue.show("Hello, world!", speaker="NPC", on_complete=callback)
|
|
181
|
+
dialogue.dismiss()
|
|
182
|
+
|
|
183
|
+
# Menu
|
|
184
|
+
menu = Menu(game.canvas, 640, 480)
|
|
185
|
+
menu.show(["Play", "Settings", "Quit"],
|
|
186
|
+
on_select=lambda i, label: print(label),
|
|
187
|
+
on_cancel=lambda: menu.hide())
|
|
188
|
+
menu.move_up()
|
|
189
|
+
menu.move_down()
|
|
190
|
+
menu.confirm()
|
|
191
|
+
|
|
192
|
+
# HUD
|
|
193
|
+
hud = HUD(game.canvas, 640, 480)
|
|
194
|
+
hud.add_stat("hp", "HP", value=100, max_value=100, color="#e94560")
|
|
195
|
+
hud.set_stat("hp", 75)
|
|
196
|
+
hud.add_text("score", "Score: 0", 10, 10, fill="#fdd835")
|
|
197
|
+
hud.set_text("score", "Score: 100")
|
|
198
|
+
hud.render()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Magma Hub Hardware
|
|
202
|
+
|
|
203
|
+
The Magma Hub is a Raspberry Pico acting as an I2C slave, providing controller input (buttons + joystick) to the Pi master. texastoast abstracts this hardware so games don't need to know about I2C details.
|
|
204
|
+
|
|
205
|
+
### Protocol
|
|
206
|
+
|
|
207
|
+
| Address | Data | Description |
|
|
208
|
+
|---------|------|-------------|
|
|
209
|
+
| 0x00 | 8-bit | Button status (bitmask) |
|
|
210
|
+
| 0x01 | 8-bit | Joystick status |
|
|
211
|
+
|
|
212
|
+
### Button Bitmask
|
|
213
|
+
|
|
214
|
+
| Bit | Button |
|
|
215
|
+
|-----|--------|
|
|
216
|
+
| 0 | Up |
|
|
217
|
+
| 1 | Down |
|
|
218
|
+
| 2 | Left |
|
|
219
|
+
| 3 | Right |
|
|
220
|
+
| 4 | A |
|
|
221
|
+
| 5 | B |
|
|
222
|
+
| 6 | Start |
|
|
223
|
+
| 7 | Select |
|
|
224
|
+
|
|
225
|
+
## Design Philosophy
|
|
226
|
+
|
|
227
|
+
- **No opinions** — engines provide systems, you wire them together
|
|
228
|
+
- **Configurable** — pass callbacks and data, don't inherit from base classes
|
|
229
|
+
- **Tiny** — small, focused modules with minimal dependencies
|
|
230
|
+
- **Graceful fallback** — I2C hardware is optional, keyboard always works
|
|
231
|
+
- **Testable** — game logic doesn't depend on tkinter
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
Apache-2.0. Copyright 2026 magmacrunch media.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# texastoast
|
|
2
|
+
|
|
3
|
+
Python RPG engine and Magma Hub I2C layer for magmacrunch game systems.
|
|
4
|
+
|
|
5
|
+
A tkinter-based 2D game engine inspired by [adenosine](https://github.com/magmacrunchmedia/adenosine), with I2C hardware abstraction for Raspberry Pi + Pico "Magma Hub" controllers.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Basic install (keyboard-only, no hardware)
|
|
11
|
+
pip install -e .
|
|
12
|
+
|
|
13
|
+
# With I2C hardware support (Raspberry Pi)
|
|
14
|
+
pip install -e ".[hardware]"
|
|
15
|
+
|
|
16
|
+
# Development (includes pytest)
|
|
17
|
+
pip install -e ".[dev]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from texastoast import Game, CanvasRenderer, TileMap, Entity, KeyboardInput
|
|
24
|
+
|
|
25
|
+
game = Game(title="My Game", width=400, height=300, fps=30)
|
|
26
|
+
renderer = CanvasRenderer(game.canvas, 400, 300)
|
|
27
|
+
keyboard = KeyboardInput(game.root)
|
|
28
|
+
|
|
29
|
+
tilemap = TileMap([
|
|
30
|
+
[1, 1, 1, 1, 1],
|
|
31
|
+
[1, 0, 0, 0, 1],
|
|
32
|
+
[1, 1, 1, 1, 1],
|
|
33
|
+
], tile_size=20, solid_tiles={1})
|
|
34
|
+
|
|
35
|
+
player = Entity(x=40, y=40, width=14, height=14, speed=100)
|
|
36
|
+
|
|
37
|
+
def update(dt):
|
|
38
|
+
state = keyboard.poll()
|
|
39
|
+
player.move(state.dx, state.dy, tilemap)
|
|
40
|
+
renderer.camera.follow(player.center_x, player.center_y,
|
|
41
|
+
map_width=tilemap.width, map_height=tilemap.height)
|
|
42
|
+
|
|
43
|
+
def render():
|
|
44
|
+
renderer.clear()
|
|
45
|
+
renderer.draw_tilemap(tilemap, {0: "#7cb342", 1: "#5d4037"})
|
|
46
|
+
renderer.draw_rect(player.x, player.y, player.width, player.height, "#e94560")
|
|
47
|
+
|
|
48
|
+
game.set_update(update)
|
|
49
|
+
game.set_render(render)
|
|
50
|
+
game.start()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
| Example | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `examples/hello_world.py` | Minimal movement demo |
|
|
58
|
+
| `examples/tilemap_demo.py` | Walk around a larger map |
|
|
59
|
+
| `examples/sprite_demo.py` | Animated character sprites |
|
|
60
|
+
| `examples/rpg_demo.py` | NPCs, dialogue, menus, HUD |
|
|
61
|
+
| `examples/game_template.py` | Full game starting point |
|
|
62
|
+
| `examples/magma_hub_demo.py` | I2C controller input |
|
|
63
|
+
| `tools/tile_editor.py` | Tile map editor GUI |
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
### Core
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from texastoast import Game, Config, GameLoop
|
|
71
|
+
|
|
72
|
+
game = Game(title="My Game", width=640, height=480, fps=30)
|
|
73
|
+
game.set_update(update_fn) # def update(dt: float): ...
|
|
74
|
+
game.set_render(render_fn) # def render(): ...
|
|
75
|
+
game.start()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Rendering
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from texastoast import CanvasRenderer, Camera
|
|
82
|
+
|
|
83
|
+
renderer = CanvasRenderer(game.canvas, 640, 480)
|
|
84
|
+
renderer.draw_tilemap(tilemap, {0: "#7cb342", 1: "#5d4037"})
|
|
85
|
+
renderer.draw_rect(x, y, w, h, color)
|
|
86
|
+
renderer.draw_image(x, y, photo_image)
|
|
87
|
+
renderer.draw_hud_text(x, y, text, fill="#fff", font=("Courier", 10))
|
|
88
|
+
|
|
89
|
+
# Camera
|
|
90
|
+
renderer.camera.follow(target_x, target_y, map_width=800, map_height=600)
|
|
91
|
+
renderer.camera.set_position(x, y)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### World
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from texastoast import TileMap, Entity, AABB
|
|
98
|
+
|
|
99
|
+
# Tile map
|
|
100
|
+
tilemap = TileMap(grid_data, tile_size=16, solid_tiles={1, 2})
|
|
101
|
+
tilemap = TileMap.from_file("map.json", tile_size=16)
|
|
102
|
+
tilemap.save("map.json")
|
|
103
|
+
tilemap.get(col, row) # -> tile_id
|
|
104
|
+
tilemap.is_solid(col, row) # -> bool
|
|
105
|
+
tilemap.is_solid_at(world_x, world_y) # -> bool
|
|
106
|
+
|
|
107
|
+
# Entity
|
|
108
|
+
player = Entity(x=0, y=0, width=16, height=16, speed=100)
|
|
109
|
+
player.move(dx, dy, tilemap) # with collision
|
|
110
|
+
player.aabb # -> AABB for overlap checks
|
|
111
|
+
player.collides_with(other_entity) # -> bool
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Input
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from texastoast import KeyboardInput, InputState
|
|
118
|
+
|
|
119
|
+
keyboard = KeyboardInput(game.root)
|
|
120
|
+
state = keyboard.poll()
|
|
121
|
+
|
|
122
|
+
state.up, state.down, state.left, state.right # bool
|
|
123
|
+
state.a, state.b, state.start, state.select # bool
|
|
124
|
+
state.dx, state.dy # float (-1, 0, 1)
|
|
125
|
+
state.is_any_direction() # bool
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### I2C (Magma Hub)
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from texastoast import I2CBus, MagmaHub, MagmaHubInput, CompositeInput
|
|
132
|
+
|
|
133
|
+
# Direct I2C
|
|
134
|
+
bus = I2CBus(1) # I2C bus number
|
|
135
|
+
hubs = MagmaHub.scan_buses(bus_numbers=[1])
|
|
136
|
+
hub = hubs[0]
|
|
137
|
+
hub.poll() # -> [ControllerState, ...]
|
|
138
|
+
|
|
139
|
+
# Input adapter (same interface as KeyboardInput)
|
|
140
|
+
hub_input = MagmaHubInput(hub, controller_index=0)
|
|
141
|
+
state = hub_input.poll()
|
|
142
|
+
|
|
143
|
+
# Auto-fallback composite
|
|
144
|
+
controls = CompositeInput(keyboard, hub_input)
|
|
145
|
+
state = controls.poll() # uses hub if connected, else keyboard
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### UI
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from texastoast.ui import DialogueBox, Menu, HUD
|
|
152
|
+
|
|
153
|
+
# Dialogue
|
|
154
|
+
dialogue = DialogueBox(game.canvas, 640, 480)
|
|
155
|
+
dialogue.show("Hello, world!", speaker="NPC", on_complete=callback)
|
|
156
|
+
dialogue.dismiss()
|
|
157
|
+
|
|
158
|
+
# Menu
|
|
159
|
+
menu = Menu(game.canvas, 640, 480)
|
|
160
|
+
menu.show(["Play", "Settings", "Quit"],
|
|
161
|
+
on_select=lambda i, label: print(label),
|
|
162
|
+
on_cancel=lambda: menu.hide())
|
|
163
|
+
menu.move_up()
|
|
164
|
+
menu.move_down()
|
|
165
|
+
menu.confirm()
|
|
166
|
+
|
|
167
|
+
# HUD
|
|
168
|
+
hud = HUD(game.canvas, 640, 480)
|
|
169
|
+
hud.add_stat("hp", "HP", value=100, max_value=100, color="#e94560")
|
|
170
|
+
hud.set_stat("hp", 75)
|
|
171
|
+
hud.add_text("score", "Score: 0", 10, 10, fill="#fdd835")
|
|
172
|
+
hud.set_text("score", "Score: 100")
|
|
173
|
+
hud.render()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Magma Hub Hardware
|
|
177
|
+
|
|
178
|
+
The Magma Hub is a Raspberry Pico acting as an I2C slave, providing controller input (buttons + joystick) to the Pi master. texastoast abstracts this hardware so games don't need to know about I2C details.
|
|
179
|
+
|
|
180
|
+
### Protocol
|
|
181
|
+
|
|
182
|
+
| Address | Data | Description |
|
|
183
|
+
|---------|------|-------------|
|
|
184
|
+
| 0x00 | 8-bit | Button status (bitmask) |
|
|
185
|
+
| 0x01 | 8-bit | Joystick status |
|
|
186
|
+
|
|
187
|
+
### Button Bitmask
|
|
188
|
+
|
|
189
|
+
| Bit | Button |
|
|
190
|
+
|-----|--------|
|
|
191
|
+
| 0 | Up |
|
|
192
|
+
| 1 | Down |
|
|
193
|
+
| 2 | Left |
|
|
194
|
+
| 3 | Right |
|
|
195
|
+
| 4 | A |
|
|
196
|
+
| 5 | B |
|
|
197
|
+
| 6 | Start |
|
|
198
|
+
| 7 | Select |
|
|
199
|
+
|
|
200
|
+
## Design Philosophy
|
|
201
|
+
|
|
202
|
+
- **No opinions** — engines provide systems, you wire them together
|
|
203
|
+
- **Configurable** — pass callbacks and data, don't inherit from base classes
|
|
204
|
+
- **Tiny** — small, focused modules with minimal dependencies
|
|
205
|
+
- **Graceful fallback** — I2C hardware is optional, keyboard always works
|
|
206
|
+
- **Testable** — game logic doesn't depend on tkinter
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
Apache-2.0. Copyright 2026 magmacrunch media.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Game template — a starting point for new texastoast games.
|
|
3
|
+
|
|
4
|
+
This demonstrates the full feature set:
|
|
5
|
+
- Game loop with fixed timestep
|
|
6
|
+
- Tile map with collision
|
|
7
|
+
- Camera following player
|
|
8
|
+
- Keyboard input
|
|
9
|
+
- HUD with stats
|
|
10
|
+
- Dialogue system
|
|
11
|
+
- Pause menu
|
|
12
|
+
|
|
13
|
+
Copy this file and modify it to build your own game.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from texastoast import Game, CanvasRenderer, TileMap, Entity, KeyboardInput
|
|
17
|
+
from texastoast.ui import DialogueBox, Menu, HUD
|
|
18
|
+
|
|
19
|
+
# ── Config ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
SCREEN_W = 400
|
|
22
|
+
SCREEN_H = 300
|
|
23
|
+
TILE_SIZE = 20
|
|
24
|
+
FPS = 30
|
|
25
|
+
PLAYER_SPEED = 100
|
|
26
|
+
|
|
27
|
+
# ── Colors ──────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
TILE_COLORS = {
|
|
30
|
+
0: "#7cb342", # grass
|
|
31
|
+
1: "#5d4037", # wall
|
|
32
|
+
2: "#1e88e5", # water
|
|
33
|
+
3: "#fdd835", # path
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ── Map ─────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
LEVEL_1 = [
|
|
39
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
40
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
41
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
42
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
43
|
+
[1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1],
|
|
44
|
+
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
45
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
46
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
47
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
48
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
49
|
+
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
50
|
+
[1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1],
|
|
51
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
52
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
53
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# ── Init ────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
game = Game(title="texastoast game template", width=SCREEN_W, height=SCREEN_H, fps=FPS)
|
|
59
|
+
renderer = CanvasRenderer(game.canvas, SCREEN_W, SCREEN_H)
|
|
60
|
+
keyboard = KeyboardInput(game.root)
|
|
61
|
+
dialogue = DialogueBox(game.canvas, SCREEN_W, SCREEN_H)
|
|
62
|
+
menu = Menu(game.canvas, SCREEN_W, SCREEN_H)
|
|
63
|
+
hud = HUD(game.canvas, SCREEN_W, SCREEN_H)
|
|
64
|
+
|
|
65
|
+
tilemap = TileMap(LEVEL_1, tile_size=TILE_SIZE, solid_tiles={1, 2})
|
|
66
|
+
player = Entity(x=60, y=60, width=14, height=14, speed=PLAYER_SPEED)
|
|
67
|
+
|
|
68
|
+
# ── Game state ──────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
paused = False
|
|
71
|
+
showing_dialogue = False
|
|
72
|
+
hp = 100
|
|
73
|
+
xp = 0
|
|
74
|
+
score = 0
|
|
75
|
+
|
|
76
|
+
hud.add_stat("hp", "HP", value=hp, max_value=100, color="#e94560")
|
|
77
|
+
hud.add_stat("xp", "XP", value=xp, max_value=100, color="#4fc3f7")
|
|
78
|
+
hud.add_text("score", "Score: 0", SCREEN_W - 120, 4, fill="#fdd835")
|
|
79
|
+
hud.add_text("pos", "", 4, SCREEN_H - 16, fill="#666666", font=("Courier", 8))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── Input handling ──────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
def handle_keypress(event):
|
|
85
|
+
global paused, showing_dialogue, hp, xp, score
|
|
86
|
+
|
|
87
|
+
key = event.keysym
|
|
88
|
+
|
|
89
|
+
if dialogue.active:
|
|
90
|
+
if key in ("z", "Z", "Return", "space"):
|
|
91
|
+
dialogue.dismiss()
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if menu.active:
|
|
95
|
+
if key in ("Up", "w", "W"):
|
|
96
|
+
menu.move_up()
|
|
97
|
+
elif key in ("Down", "s", "S"):
|
|
98
|
+
menu.move_down()
|
|
99
|
+
elif key in ("z", "Z", "Return"):
|
|
100
|
+
menu.confirm()
|
|
101
|
+
elif key in ("x", "X", "Escape"):
|
|
102
|
+
menu.cancel()
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if key in ("Escape", "p", "P"):
|
|
106
|
+
toggle_pause()
|
|
107
|
+
elif key in ("z", "Z", "Return"):
|
|
108
|
+
# Example: press Z near water to "fish"
|
|
109
|
+
col, row = tilemap.to_grid_coords(player.center_x, player.center_y)
|
|
110
|
+
for dr in range(-1, 2):
|
|
111
|
+
for dc in range(-1, 2):
|
|
112
|
+
if tilemap.get(col + dc, row + dr) == 2:
|
|
113
|
+
showing_dialogue = True
|
|
114
|
+
dialogue.show("You caught a fish! +10 XP",
|
|
115
|
+
on_complete=_on_dialogue_done)
|
|
116
|
+
xp = min(100, xp + 10)
|
|
117
|
+
hud.set_stat("xp", xp)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _on_dialogue_done():
|
|
122
|
+
global showing_dialogue
|
|
123
|
+
showing_dialogue = False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def toggle_pause():
|
|
127
|
+
global paused
|
|
128
|
+
paused = not paused
|
|
129
|
+
if paused:
|
|
130
|
+
menu.show(["Resume", "Restart", "Quit"],
|
|
131
|
+
on_select=_on_menu_select,
|
|
132
|
+
on_cancel=lambda: toggle_pause(),
|
|
133
|
+
title="PAUSED")
|
|
134
|
+
else:
|
|
135
|
+
menu.hide()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _on_menu_select(index, label):
|
|
139
|
+
global paused, hp, xp, score
|
|
140
|
+
if label == "Resume":
|
|
141
|
+
paused = False
|
|
142
|
+
elif label == "Restart":
|
|
143
|
+
hp, xp, score = 100, 0, 0
|
|
144
|
+
player.x, player.y = 60, 60
|
|
145
|
+
hud.set_stat("hp", hp)
|
|
146
|
+
hud.set_stat("xp", xp)
|
|
147
|
+
paused = False
|
|
148
|
+
elif label == "Quit":
|
|
149
|
+
game.quit()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
game.bind_key("<Key>", handle_keypress)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ── Update ──────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def update(dt):
|
|
158
|
+
if dialogue.active or menu.active or paused:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
state = keyboard.poll()
|
|
162
|
+
player.move(state.dx, state.dy, tilemap)
|
|
163
|
+
renderer.camera.follow(
|
|
164
|
+
player.center_x, player.center_y,
|
|
165
|
+
map_width=tilemap.width, map_height=tilemap.height,
|
|
166
|
+
)
|
|
167
|
+
hud.set_text("pos", f"({int(player.x)}, {int(player.y)})")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ── Render ──────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
def render():
|
|
173
|
+
renderer.clear()
|
|
174
|
+
renderer.draw_tilemap(tilemap, TILE_COLORS)
|
|
175
|
+
renderer.draw_rect(player.x, player.y, player.width, player.height, "#e94560")
|
|
176
|
+
hud.render()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
game.set_update(update)
|
|
180
|
+
game.set_render(render)
|
|
181
|
+
game.start()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Minimal texastoast example — a colored square you can move with arrow keys."""
|
|
2
|
+
|
|
3
|
+
from texastoast import Game, CanvasRenderer, TileMap, Entity, KeyboardInput
|
|
4
|
+
|
|
5
|
+
# --- Setup ---
|
|
6
|
+
game = Game(title="texastoast hello", width=320, height=240, fps=30)
|
|
7
|
+
renderer = CanvasRenderer(game.canvas, 320, 240)
|
|
8
|
+
keyboard = KeyboardInput(game.root)
|
|
9
|
+
|
|
10
|
+
# A tiny map: 0 = grass, 1 = wall
|
|
11
|
+
tilemap = TileMap(
|
|
12
|
+
[
|
|
13
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
14
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
15
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
16
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
17
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
18
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
19
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
20
|
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
|
21
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
22
|
+
],
|
|
23
|
+
tile_size=16,
|
|
24
|
+
solid_tiles={1},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
TILE_COLORS = {0: "#7cb342", 1: "#5d4037"}
|
|
28
|
+
|
|
29
|
+
player = Entity(x=48, y=48, width=12, height=12, speed=100)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# --- Update ---
|
|
33
|
+
def update(dt: float):
|
|
34
|
+
state = keyboard.poll()
|
|
35
|
+
player.move(state.dx, state.dy, tilemap)
|
|
36
|
+
renderer.camera.follow(
|
|
37
|
+
player.center_x, player.center_y,
|
|
38
|
+
map_width=tilemap.width, map_height=tilemap.height,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- Render ---
|
|
43
|
+
def render():
|
|
44
|
+
renderer.clear()
|
|
45
|
+
renderer.draw_tilemap(tilemap, TILE_COLORS)
|
|
46
|
+
renderer.draw_rect(player.x, player.y, player.width, player.height, "#e94560")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
game.set_update(update)
|
|
50
|
+
game.set_render(render)
|
|
51
|
+
game.start()
|