Browse Source

Refactor code structure for improved readability and maintainability

master
Matteo Benedetto 1 week ago
parent
commit
b60ffd87aa
  1. 107
      .github/instructions/pixel-art-sprite-workflow.instructions.md
  2. BIN
      assets/Rat/BMP_1_CAVE_DOWN.png
  3. BIN
      assets/Rat/BMP_1_CAVE_LEFT.png
  4. BIN
      assets/Rat/BMP_1_CAVE_RIGHT.png
  5. BIN
      assets/Rat/BMP_1_CAVE_UP.png
  6. BIN
      assets/Rat/BMP_1_E.png
  7. BIN
      assets/Rat/BMP_1_EN.png
  8. BIN
      assets/Rat/BMP_1_ES.png
  9. BIN
      assets/Rat/BMP_1_FLOWER_1.png
  10. BIN
      assets/Rat/BMP_1_FLOWER_2.png
  11. BIN
      assets/Rat/BMP_1_FLOWER_3.png
  12. BIN
      assets/Rat/BMP_1_FLOWER_4.png
  13. BIN
      assets/Rat/BMP_1_GRASS_1.png
  14. BIN
      assets/Rat/BMP_1_GRASS_2.png
  15. BIN
      assets/Rat/BMP_1_GRASS_3.png
  16. BIN
      assets/Rat/BMP_1_GRASS_4.png
  17. BIN
      assets/Rat/BMP_1_N.png
  18. BIN
      assets/Rat/BMP_1_NE.png
  19. BIN
      assets/Rat/BMP_1_NW.png
  20. BIN
      assets/Rat/BMP_1_S.png
  21. BIN
      assets/Rat/BMP_1_SE.png
  22. BIN
      assets/Rat/BMP_1_SW.png
  23. BIN
      assets/Rat/BMP_1_W.png
  24. BIN
      assets/Rat/BMP_1_WN.png
  25. BIN
      assets/Rat/BMP_1_WS.png
  26. BIN
      assets/Rat/BMP_BABY_DOWN.png
  27. BIN
      assets/Rat/BMP_BABY_LEFT.png
  28. BIN
      assets/Rat/BMP_BABY_RIGHT.png
  29. BIN
      assets/Rat/BMP_BABY_UP.png
  30. BIN
      assets/Rat/BMP_BOMB0.png
  31. BIN
      assets/Rat/BMP_BOMB1.png
  32. BIN
      assets/Rat/BMP_BOMB2.png
  33. BIN
      assets/Rat/BMP_BOMB3.png
  34. BIN
      assets/Rat/BMP_BOMB4.png
  35. BIN
      assets/Rat/BMP_FEMALE_DOWN.png
  36. BIN
      assets/Rat/BMP_FEMALE_LEFT.png
  37. BIN
      assets/Rat/BMP_FEMALE_RIGHT.png
  38. BIN
      assets/Rat/BMP_FEMALE_UP.png
  39. BIN
      assets/Rat/BMP_GAS.png
  40. BIN
      assets/Rat/BMP_GAS_DOWN.png
  41. BIN
      assets/Rat/BMP_GAS_LEFT.png
  42. BIN
      assets/Rat/BMP_GAS_RIGHT.png
  43. BIN
      assets/Rat/BMP_GAS_UP.png
  44. BIN
      assets/Rat/BMP_MALE_DOWN.png
  45. BIN
      assets/Rat/BMP_MALE_LEFT.png
  46. BIN
      assets/Rat/BMP_MALE_RIGHT.png
  47. BIN
      assets/Rat/BMP_MALE_UP.png
  48. BIN
      assets/Rat/backup/BMP_1_CAVE_DOWN_original.png
  49. BIN
      assets/Rat/backup/BMP_1_CAVE_LEFT_original.png
  50. BIN
      assets/Rat/backup/BMP_1_CAVE_RIGHT_original.png
  51. BIN
      assets/Rat/backup/BMP_1_CAVE_UP_original.png
  52. BIN
      assets/Rat/backup/BMP_1_EN_original.png
  53. BIN
      assets/Rat/backup/BMP_1_ES_original.png
  54. BIN
      assets/Rat/backup/BMP_1_E_original.png
  55. BIN
      assets/Rat/backup/BMP_1_FLOWER_1_original.png
  56. BIN
      assets/Rat/backup/BMP_1_FLOWER_2_original.png
  57. BIN
      assets/Rat/backup/BMP_1_FLOWER_3_original.png
  58. BIN
      assets/Rat/backup/BMP_1_FLOWER_4_original.png
  59. BIN
      assets/Rat/backup/BMP_1_GRASS_1_original.png
  60. BIN
      assets/Rat/backup/BMP_1_GRASS_1_v2.png
  61. BIN
      assets/Rat/backup/BMP_1_GRASS_2_original.png
  62. BIN
      assets/Rat/backup/BMP_1_GRASS_3_original.png
  63. BIN
      assets/Rat/backup/BMP_1_GRASS_4_original.png
  64. BIN
      assets/Rat/backup/BMP_1_NE_original.png
  65. BIN
      assets/Rat/backup/BMP_1_NW_original.png
  66. BIN
      assets/Rat/backup/BMP_1_N_original.png
  67. BIN
      assets/Rat/backup/BMP_1_SE_original.png
  68. BIN
      assets/Rat/backup/BMP_1_SW_original.png
  69. BIN
      assets/Rat/backup/BMP_1_S_original.png
  70. BIN
      assets/Rat/backup/BMP_1_WN_original.png
  71. BIN
      assets/Rat/backup/BMP_1_WS_original.png
  72. BIN
      assets/Rat/backup/BMP_1_W_original.png
  73. BIN
      assets/Rat/backup/BMP_2_GRASS_1_original.png
  74. BIN
      assets/Rat/backup/BMP_2_GRASS_2_original.png
  75. BIN
      assets/Rat/backup/BMP_2_GRASS_3_original.png
  76. BIN
      assets/Rat/backup/BMP_2_GRASS_4_original.png
  77. BIN
      assets/Rat/backup/BMP_3_GRASS_1_original.png
  78. BIN
      assets/Rat/backup/BMP_3_GRASS_2_original.png
  79. BIN
      assets/Rat/backup/BMP_3_GRASS_3_original.png
  80. BIN
      assets/Rat/backup/BMP_3_GRASS_4_original.png
  81. BIN
      assets/Rat/backup/BMP_4_GRASS_1_original.png
  82. BIN
      assets/Rat/backup/BMP_4_GRASS_2_original.png
  83. BIN
      assets/Rat/backup/BMP_4_GRASS_3_original.png
  84. BIN
      assets/Rat/backup/BMP_4_GRASS_4_original.png
  85. BIN
      assets/Rat/backup/BMP_GAS_DOWN_original.png
  86. BIN
      assets/Rat/backup/BMP_GAS_LEFT_original.png
  87. BIN
      assets/Rat/backup/BMP_GAS_RIGHT_original.png
  88. BIN
      assets/Rat/backup/BMP_GAS_UP_original.png
  89. BIN
      assets/Rat/backup/BMP_GAS_original.png
  90. 37
      engine/graphics.py
  91. 1
      rats.py
  92. 3
      requirements.txt
  93. 62
      tools/generate_rat_sprites.py
  94. 362
      tools/generate_weapon_sprites.py
  95. 24712
      tools/vernon/output/BMP_1_GRASS_1.json
  96. 1
      tools/vernon/output/BMP_1_GRASS_1_v2.json
  97. BIN
      tools/vernon/output/BMP_1_GRASS_1_v2.png
  98. 1
      tools/vernon/output/BMP_BOMB0.json
  99. 24712
      tools/vernon/output/BMP_BOMB0_v2.json
  100. BIN
      tools/vernon/output/BMP_BOMB0_v2.png
  101. Some files were not shown because too many files have changed in this diff Show More

107
.github/instructions/pixel-art-sprite-workflow.instructions.md

@ -0,0 +1,107 @@
---
applyTo: "tools/vernon/**,assets/Rat/**"
---
# Pixel Art Sprite Workflow — mice project
## Strumenti disponibili
| Script | Uso |
|--------|-----|
| `tools/vernon/image_to_json.py <INPUT.png> <OUTPUT.json>` | Converte PNG → matrice JSON RGBA 64×64 |
| `tools/vernon/json_to_png.py <INPUT.json> <OUTPUT.png>` | Converte matrice JSON RGBA → PNG |
Entrambi usano Pillow e richiedono il `venv` attivo:
```bash
source .venv/bin/activate
```
## Formato JSON
```json
{
"source": "BMP_BOMB0.png",
"width": 64,
"height": 64,
"mode": "RGBA",
"pixels": [
[ [R, G, B, A], ... ], // riga 0, 64 pixel
... // 64 righe totali
]
}
```
Ogni pixel è `[R, G, B, A]` con valori 0–255.
## Convenzioni cromatiche del gioco
- **Colore trasparente (chromakey):** `[128, 128, 128, 192]` — usato come sfondo, il motore lo rende hidden
- **Alpha standard:** `192` per tutti i pixel visibili (coerente con gli asset originali)
## Workflow iterativo di redesign (passi 0–4)
```
0. BACKUP → prima di sovrascrivere, copia l'originale:
cp assets/Rat/<NAME>.png assets/Rat/backup/<NAME>_original.png
1. image_to_json.py → esamina JSON e PNG originale
2. capire struttura: sfondo, palette, forma principale
3. modificare JSON (o generarlo via script Python) con:
- più livelli di shading (8+ valori invece di 3)
- dettagli geometrici aggiuntivi (texture, bordi, ombre interne)
- palette più ricca mantenendo stile pixel art (bordi netti, no anti-alias)
4. json_to_png.py → valuta risultato visivo; se non soddisfacente, torna a 3
```
## Pattern Python per generare JSON programmaticamente
```python
import json, math
from pathlib import Path
W, H = 64, 64
A = 192 # alpha standard
def px(r, g, b): return [r, g, b, A]
TRANSPARENT = px(128, 128, 128)
grid = [[TRANSPARENT[:] for _ in range(W)] for _ in range(H)]
def put(x, y, col):
if 0 <= x < W and 0 <= y < H:
grid[y][x] = col[:]
# ... disegna su grid ...
data = {"source": "BMP_X.png", "width": W, "height": H, "mode": "RGBA", "pixels": grid}
Path("tools/vernon/output/BMP_X_v2.json").write_text(json.dumps(data, indent=2))
```
## Tecniche pixel art a 64×64
- **Shading sferico:** calcola normale + dot product con luce per N livelli di grigio discreti
- **Rope/miccia:** traccia bezier quadratica, alterna 2–3 toni in sequenza (effetto intrecciato)
- **Scintilla:** pixel centrali chiari (bianco/giallo), bordi che degradano in arancio → rosso
- **Outline:** bordo di 1px nero (`[0,0,0,192]`) attorno a tutte le forme principali
- **Nessun anti-aliasing:** ogni pixel è un colore solido discreto della palette scelta
## Asset da redesignare (tutti 64×64)
| File | Gruppo |
|------|--------|
| `BMP_BOMB0.png``BMP_BOMB4.png` | Animazione bomba (0=quieta, 4=accesa) |
| `BMP_1_GRASS_1.png``BMP_1_GRASS_4.png` | Tile erba tema 1 (verde) — **redesignate con FBM 7-toni** |
| `BMP_2_GRASS_1.png``BMP_2_GRASS_4.png` | Tile erba tema 2 (secca/autunnale) |
| `BMP_3_GRASS_1.png``BMP_3_GRASS_4.png` | Tile erba tema 3 (dungeon/pietra) |
| `BMP_4_GRASS_1.png``BMP_4_GRASS_4.png` | Tile erba tema 4 (fuoco/lava) |
| `BMP_GAS.png`, `BMP_GAS_{DIR}.png` | Gas generico + 4 direzioni |
| `BMP_EXPLOSION.png`, `BMP_EXPLOSION_{DIR}.png` | Esplosione generica + 4 direzioni |
| `BMP_NUCLEAR.png` | Fungo nucleare |
| `BMP_POISON.png` | Veleno |
## Note sull'animazione BOMB (frame 0–4)
- `BOMB0`: bomba ferma, scintilla piccola a riposo
- `BOMB1`–`BOMB3`: miccia che brucia (la scintilla avanza verso il corpo, la corda si accorcia)
- `BOMB4`: quasi esplode (glow rosso/arancio sul corpo, scintilla grande)
Per i frame animati: mantieni identici corpo + miccia, varia solo posizione/dimensione scintilla e eventuale glow progressivo.

BIN
assets/Rat/BMP_1_CAVE_DOWN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/Rat/BMP_1_CAVE_LEFT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/Rat/BMP_1_CAVE_RIGHT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/Rat/BMP_1_CAVE_UP.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/Rat/BMP_1_E.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 683 B

BIN
assets/Rat/BMP_1_EN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 665 B

BIN
assets/Rat/BMP_1_ES.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 685 B

BIN
assets/Rat/BMP_1_FLOWER_1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
assets/Rat/BMP_1_FLOWER_2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/Rat/BMP_1_FLOWER_3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/Rat/BMP_1_FLOWER_4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/Rat/BMP_1_GRASS_1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/Rat/BMP_1_GRASS_2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/Rat/BMP_1_GRASS_3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/Rat/BMP_1_GRASS_4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/Rat/BMP_1_N.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 711 B

BIN
assets/Rat/BMP_1_NE.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 713 B

BIN
assets/Rat/BMP_1_NW.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

After

Width:  |  Height:  |  Size: 618 B

BIN
assets/Rat/BMP_1_S.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 695 B

BIN
assets/Rat/BMP_1_SE.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 696 B

BIN
assets/Rat/BMP_1_SW.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 661 B

BIN
assets/Rat/BMP_1_W.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 728 B

BIN
assets/Rat/BMP_1_WN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 717 B

BIN
assets/Rat/BMP_1_WS.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 722 B

BIN
assets/Rat/BMP_BABY_DOWN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 390 B

BIN
assets/Rat/BMP_BABY_LEFT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 405 B

BIN
assets/Rat/BMP_BABY_RIGHT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 403 B

BIN
assets/Rat/BMP_BABY_UP.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 396 B

BIN
assets/Rat/BMP_BOMB0.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/Rat/BMP_BOMB1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/Rat/BMP_BOMB2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/Rat/BMP_BOMB3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/Rat/BMP_BOMB4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/Rat/BMP_FEMALE_DOWN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 772 B

BIN
assets/Rat/BMP_FEMALE_LEFT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 788 B

BIN
assets/Rat/BMP_FEMALE_RIGHT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 B

After

Width:  |  Height:  |  Size: 771 B

BIN
assets/Rat/BMP_FEMALE_UP.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 775 B

BIN
assets/Rat/BMP_GAS.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/Rat/BMP_GAS_DOWN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
assets/Rat/BMP_GAS_LEFT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
assets/Rat/BMP_GAS_RIGHT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
assets/Rat/BMP_GAS_UP.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
assets/Rat/BMP_MALE_DOWN.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 780 B

BIN
assets/Rat/BMP_MALE_LEFT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 800 B

BIN
assets/Rat/BMP_MALE_RIGHT.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 841 B

After

Width:  |  Height:  |  Size: 780 B

BIN
assets/Rat/BMP_MALE_UP.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 773 B

BIN
assets/Rat/backup/BMP_1_CAVE_DOWN_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

BIN
assets/Rat/backup/BMP_1_CAVE_LEFT_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

BIN
assets/Rat/backup/BMP_1_CAVE_RIGHT_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

BIN
assets/Rat/backup/BMP_1_CAVE_UP_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

BIN
assets/Rat/backup/BMP_1_EN_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

BIN
assets/Rat/backup/BMP_1_ES_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

BIN
assets/Rat/backup/BMP_1_E_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

BIN
assets/Rat/backup/BMP_1_FLOWER_1_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

BIN
assets/Rat/backup/BMP_1_FLOWER_2_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

BIN
assets/Rat/backup/BMP_1_FLOWER_3_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

BIN
assets/Rat/backup/BMP_1_FLOWER_4_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
assets/Rat/backup/BMP_1_GRASS_1_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

BIN
assets/Rat/backup/BMP_1_GRASS_1_v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/Rat/backup/BMP_1_GRASS_2_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

BIN
assets/Rat/backup/BMP_1_GRASS_3_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

BIN
assets/Rat/backup/BMP_1_GRASS_4_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

BIN
assets/Rat/backup/BMP_1_NE_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

BIN
assets/Rat/backup/BMP_1_NW_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
assets/Rat/backup/BMP_1_N_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

BIN
assets/Rat/backup/BMP_1_SE_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

BIN
assets/Rat/backup/BMP_1_SW_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

BIN
assets/Rat/backup/BMP_1_S_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

BIN
assets/Rat/backup/BMP_1_WN_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
assets/Rat/backup/BMP_1_WS_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
assets/Rat/backup/BMP_1_W_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
assets/Rat/backup/BMP_2_GRASS_1_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

BIN
assets/Rat/backup/BMP_2_GRASS_2_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
assets/Rat/backup/BMP_2_GRASS_3_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

BIN
assets/Rat/backup/BMP_2_GRASS_4_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

BIN
assets/Rat/backup/BMP_3_GRASS_1_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

BIN
assets/Rat/backup/BMP_3_GRASS_2_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

BIN
assets/Rat/backup/BMP_3_GRASS_3_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
assets/Rat/backup/BMP_3_GRASS_4_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

BIN
assets/Rat/backup/BMP_4_GRASS_1_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

BIN
assets/Rat/backup/BMP_4_GRASS_2_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

BIN
assets/Rat/backup/BMP_4_GRASS_3_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

BIN
assets/Rat/backup/BMP_4_GRASS_4_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

BIN
assets/Rat/backup/BMP_GAS_DOWN_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

BIN
assets/Rat/backup/BMP_GAS_LEFT_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

BIN
assets/Rat/backup/BMP_GAS_RIGHT_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

BIN
assets/Rat/backup/BMP_GAS_UP_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

BIN
assets/Rat/backup/BMP_GAS_original.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

37
engine/graphics.py

@ -2,6 +2,7 @@ import os
import random
from engine import maze
from engine.collision_system import CollisionLayer
from runtime_paths import bundle_path
class Graphics():
@ -88,6 +89,14 @@ class Graphics():
)
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]
},
"explosions": {
direction: self.render_engine.load_image(
f"Rat/BMP_{theme_index}_EXPLOSION_{direction}.png",
transparent_color=((125, 125, 125), (128, 128, 128)),
surface=False,
)
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]
},
"edges": {
direction: self.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
for direction in ["N", "S", "E", "W"]
@ -112,6 +121,7 @@ class Graphics():
self.grasses = theme_assets["grasses"]
self.flowers = theme_assets["flowers"]
self.caves = theme_assets["caves"]
self.explosions = theme_assets["explosions"]
self.edges = theme_assets["edges"]
self.corners = theme_assets["corners"]
self.inner_corners = theme_assets["inner_corners"]
@ -133,7 +143,18 @@ class Graphics():
self.draw_blood_layer()
def draw_cave_foreground(self):
for surface, x, y in self.cave_foreground_tiles:
active_cave_explosions = {}
for unit in self.units.values():
if unit.collision_layer != CollisionLayer.EXPLOSION:
continue
if not self.map.is_tunnel(*unit.position):
continue
active_cave_explosions[unit.position] = getattr(unit, "cave_direction", None)
for cell_x, cell_y, direction, surface, x, y in self.cave_foreground_tiles:
if (cell_x, cell_y) in active_cave_explosions:
explosion_direction = active_cave_explosions[(cell_x, cell_y)] or direction
surface = self.explosions.get(explosion_direction, surface)
self.render_engine.draw_image(x, y, surface, anchor="nw", tag="cave")
def draw_blood_layer(self):
@ -150,8 +171,8 @@ class Graphics():
def draw(surface, x, y):
texture_tiles.append((surface, x, y))
def draw_cave(surface, x, y):
self.cave_foreground_tiles.append((surface, x, y))
def draw_cave(surface, x, y, direction):
self.cave_foreground_tiles.append((x // self.cell_size, y // self.cell_size, direction, surface, x, y))
def occupied(x, y):
return self.map.in_bounds(x, y) and self.map.get_cell(x, y) != maze.MAP_EMPTY
@ -259,21 +280,21 @@ class Graphics():
else:
draw(random_flower(), px + half_cell, py + half_cell)
else:
draw_cave(self.caves["RIGHT"], px, py)
draw_cave(self.caves["RIGHT"], px, py, "RIGHT")
else:
draw(self.grasses[0], px + half_cell, py + half_cell)
draw_cave(self.caves["LEFT"], px, py)
draw_cave(self.caves["LEFT"], px, py, "LEFT")
else:
draw_cave(self.caves["DOWN"], px, py)
draw_cave(self.caves["DOWN"], px, py, "DOWN")
else:
draw(self.grasses[0], px + half_cell, py + half_cell)
draw_cave(self.caves["UP"], px, py)
draw_cave(self.caves["UP"], px, py, "UP")
# Blood stains now handled separately as overlay layer
self.background_texture = self.render_engine.create_texture(texture_tiles, fill_color=(128, 128, 128))
def add_blood_stain(self, position):
"""Add a blood stain as sprite overlay (optimized - no background regeneration)"""
"""Add a blood stain as sprite overlay (opti mized - no background regeneration)"""
# Pick random blood texture from pre-generated pool
if not self.blood_stain_textures:
return

1
rats.py

@ -333,6 +333,7 @@ class MiceMaze(
self.unit_positions.setdefault(unit.position, []).append(unit)
self.unit_positions_before.setdefault(unit.position_before, []).append(unit)
# Fourth pass: check collisions and draw
for unit in self.units.copy().values():
unit.collisions()

3
requirements.txt

@ -1,4 +1,5 @@
pysdl2
Pillow
pyaml
numpy
numpy
requests

62
tools/generate_rat_sprites.py

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Generate directional rat sprites (LEFT, RIGHT, UP, DOWN) from source LEFT PNGs.
Source files (tools/vernon/output/):
BMP_MALE_LEFT.png
BMP_FEMALE_LEFT.png
BMP_BABY_LEFT.png
Output goes to assets/Rat/ as:
BMP_<SEX>_LEFT.png copy of source
BMP_<SEX>_RIGHT.png horizontal flip
BMP_<SEX>_UP.png rotate 270° (nose up)
BMP_<SEX>_DOWN.png rotate 90° (nose down)
Usage (from project root):
python tools/generate_rat_sprites.py
"""
import sys
import os
from pathlib import Path
from PIL import Image
REPO_ROOT = Path(__file__).resolve().parent.parent
SOURCE_DIR = REPO_ROOT / "tools" / "vernon" / "output"
OUTPUT_DIR = REPO_ROOT / "assets" / "Rat"
SEXES = ["MALE", "FEMALE", "BABY"]
def generate(sex: str) -> None:
src_path = SOURCE_DIR / f"BMP_{sex}_LEFT.png"
if not src_path.exists():
print(f" SKIP {sex}: source not found at {src_path}", file=sys.stderr)
return
src = Image.open(src_path).convert("RGBA")
variants = {
"LEFT": src,
"RIGHT": src.transpose(Image.FLIP_LEFT_RIGHT),
"UP": src.rotate(270, expand=True),
"DOWN": src.rotate(90, expand=True),
}
for direction, img in variants.items():
out_path = OUTPUT_DIR / f"BMP_{sex}_{direction}.png"
img.save(out_path)
print(f" wrote {out_path.relative_to(REPO_ROOT)} {img.size}")
def main() -> None:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
for sex in SEXES:
print(f"[{sex}]")
generate(sex)
print("Done.")
if __name__ == "__main__":
main()

362
tools/generate_weapon_sprites.py

@ -0,0 +1,362 @@
#!/usr/bin/env python3
"""Generate detailed 64×64 pixel art weapon sprites for mice game.
Logical grid: 16×16, each logical pixel = 4×4 real pixels 64×64 output.
Sprites generated:
BMP_BOMB0 BMP_BOMB4 bomb fuse animation (0=long fuse, 4=spark)
BMP_GAS toxic gas cloud (symmetric)
BMP_GAS_LEFT/RIGHT/UP/DOWN gas half-sprites (directional clip)
BMP_EXPLOSION central starburst
BMP_EXPLOSION_LEFT/RIGHT/UP/DOWN
BMP_NUCLEAR mushroom cloud
BMP_POISON poison vial with skull
Usage (from project root):
python tools/generate_weapon_sprites.py
"""
from pathlib import Path
from PIL import Image, ImageDraw
import math
REPO = Path(__file__).resolve().parent.parent
OUT = REPO / "assets" / "Rat"
SZ = 64 # canvas size (real pixels)
LP = 4 # logical pixel size in real pixels
LG = SZ // LP # 16 — logical grid dimension
# ─── Palette ───────────────────────────────────────────────────────────────────
T = (0, 0, 0, 0)
# Bomb body
BK = (10, 10, 20, 255) # outline
BD = (35, 35, 60, 255) # dark body
BM = (58, 58, 90, 255) # mid body
BH = (88, 92, 130, 255) # highlight
BG = (150, 155, 205, 255) # gloss spot
# Fuse rope
FD = (70, 40, 10, 255) # dark strand
FL = (120, 80, 28, 255) # light strand
# Sparks
SK = (255, 225, 30, 255) # yellow
SO = (255, 130, 0, 255) # orange
EW = (255, 255, 210, 255) # spark white core
# Gas / toxic cloud
GK = (15, 80, 8, 255) # dark-green outline
GD = (30, 140, 18, 255) # dark green body
GM = (55, 195, 40, 255) # mid green
GL = (120, 235, 75, 255) # light green
GH = (205, 255, 155, 255) # gloss highlight
# Explosion
EK = (140, 10, 0, 255) # dark red core
EM = (230, 70, 0, 255) # orange rays
EL = (255, 200, 0, 255) # yellow outer
# Nuclear cloud
NC = (120, 120, 120, 255) # cloud dark grey
NL = (185, 185, 185, 255) # cloud mid grey
NH = (245, 245, 245, 255) # cloud light / highlight
NK = (155, 30, 0, 255) # stem dark red
NM = (235, 110, 15, 255) # stem orange
NY = (255, 215, 25, 255) # inner glow yellow
# Poison vial
PD = (70, 0, 115, 255) # dark purple stopper
PM = (125, 20, 170, 255) # mid purple cork
PW = (235, 235, 235, 255) # white glass / label
PBK = (5, 5, 10, 255) # skull black
PG = (25, 165, 20, 255) # green liquid
PGH = (85, 230, 55, 255) # green highlight
# ─── Drawing helpers ────────────────────────────────────────────────────────────
def _img():
return Image.new("RGBA", (SZ, SZ), (0, 0, 0, 0))
def _put(img, lx, ly, color):
"""Paint one logical pixel (LP×LP block)."""
if 0 <= lx < LG and 0 <= ly < LG:
drw = ImageDraw.Draw(img)
x0, y0 = lx * LP, ly * LP
drw.rectangle([x0, y0, x0 + LP - 1, y0 + LP - 1], fill=color)
def _circle(img, cx, cy, layers):
"""
Paint concentric circles. cx/cy in logical float coords.
layers = [(outer_radius, color), ...] tested in order; first hit wins.
"""
drw = ImageDraw.Draw(img)
for ly in range(LG):
for lx in range(LG):
d = math.sqrt((lx - cx) ** 2 + (ly - cy) ** 2)
for r, color in layers:
if d <= r:
x0, y0 = lx * LP, ly * LP
drw.rectangle([x0, y0, x0 + LP - 1, y0 + LP - 1], fill=color)
break
def _grid(img, rows, palette):
"""
Paint from a character-grid string list.
rows: list of 16 strings, each with 16 non-space chars.
'.' = skip (transparent). palette maps char RGBA.
"""
for row_idx, row_str in enumerate(rows):
chars = [c for c in row_str if c != ' ']
for col_idx, ch in enumerate(chars):
if ch == '.' or col_idx >= LG or row_idx >= LG:
continue
color = palette.get(ch)
if color:
_put(img, col_idx, row_idx, color)
# ─── BOMB ──────────────────────────────────────────────────────────────────────
# Fuse rope path (logical coords), body-attachment → spark tip
_FUSE = [(9, 6), (9, 5), (10, 4), (11, 3), (11, 2), (12, 1)]
def make_bomb(frame: int) -> Image.Image:
"""frame 0 = long fuse / tiny spark; frame 4 = no fuse / huge spark."""
img = _img()
# Body: nested circles centered at logical (7.5, 10.0)
cx, cy = 7.5, 10.0
_circle(img, cx, cy, [
(5.4, BK), # outline ring
(4.9, BD), # dark body edge
(4.0, BM), # mid body fill
])
# Highlight blob (upper-left of body)
_circle(img, 5.5, 7.8, [(2.3, BH), (1.2, BG)])
# Fuse rope — show only the remaining segments
segs = max(1, len(_FUSE) - frame)
for i in range(segs):
fx, fy = _FUSE[i]
_put(img, fx, fy, FL if i % 2 == 0 else FD)
# Spark at the fuse tip — grows with frame
ti = min(segs - 1, len(_FUSE) - 1)
tx, ty = _FUSE[ti]
if frame == 0:
_put(img, tx, ty, SK)
elif frame == 1:
_put(img, tx, ty, EW)
_put(img, tx, ty - 1, SK)
elif frame == 2:
_put(img, tx, ty, EW)
_put(img, tx + 1, ty, SK)
_put(img, tx, ty - 1, SK)
elif frame == 3:
for dx, dy, c in [(-1, 0, SK), (1, 0, SK), (0, -1, SK), (0, 1, SO), (0, 0, EW)]:
_put(img, tx + dx, ty + dy, c)
else: # frame 4 — about to detonate
for dx, dy, c in [
(-2, 0, SO), (-1, 0, SK), (-1, -1, SK), (-1, 1, SO),
(0, -2, SK), (0, -1, EW), (0, 0, EW), (0, 1, SK),
(1, 0, SK), (1, -1, SK), (2, 0, SO), (0, -3, SO),
]:
_put(img, tx + dx, ty + dy, c)
return img
# ─── GAS ───────────────────────────────────────────────────────────────────────
_GAS_ROWS = [
'. . . . . . . . . . . . . . . .',
'. . . . . K K K K . . . . . . .',
'. . . K K D D D D K K . . . . .',
'. . K D D M M M M D D K . . . .',
'. K D M M M L L M M D D K . . .',
'. K D M L G L L G L M D K . . .',
'K D M M L L L L L L M D D K . .',
'K D M L L L L L L L L M D K . .',
'K D M M L L L L L L M M D K . .',
'K D D M M L L L L M M D D K . .',
'. K D D M M M M M M D D K . . .',
'. . K D D D M M D D D K . . . .',
'. . . K K D D D D K K . . . . .',
'. . . . . K K K K . . . . . . .',
'. . . . . . . . . . . . . . . .',
'. . . . . . . . . . . . . . . .',
]
_GAS_PAL = {'K': GK, 'D': GD, 'M': GM, 'L': GL, 'G': GH}
def make_gas(direction=None) -> Image.Image:
img = _img()
_grid(img, _GAS_ROWS, _GAS_PAL)
# Directional clip: erase the half the gas does NOT flow toward
drw = ImageDraw.Draw(img)
clips = {
'LEFT': (32, 0, 63, 63),
'RIGHT': (0, 0, 31, 63),
'UP': (0, 32, 63, 63),
'DOWN': (0, 0, 63, 31),
}
if direction in clips:
drw.rectangle(list(clips[direction]), fill=(0, 0, 0, 0))
return img
# ─── EXPLOSION ─────────────────────────────────────────────────────────────────
def make_explosion(direction=None) -> Image.Image:
img = _img()
cx, cy = 7.5, 7.5
# 8-way diagonal rays (thin)
for deg in range(0, 360, 45):
rad = math.radians(deg)
for step in range(1, 110):
d = step * 0.07
if d > 7.8:
break
lx = round(cx + d * math.cos(rad))
ly = round(cy + d * math.sin(rad))
if 0 <= lx < LG and 0 <= ly < LG:
c = EM if d > 6 else (EL if d > 3.5 else EW)
_put(img, lx, ly, c)
# 4-way cardinal rays (thicker — 3 pixels wide)
for deg in [0, 90, 180, 270]:
rad = math.radians(deg)
perp = math.radians(deg + 90)
for step in range(1, 120):
d = step * 0.07
if d > 7.8:
break
for spread in (-0.35, 0.0, 0.35):
lx = round(cx + d * math.cos(rad) + spread * math.cos(perp))
ly = round(cy + d * math.sin(rad) + spread * math.sin(perp))
if 0 <= lx < LG and 0 <= ly < LG:
c = EM if d > 5.5 else (EL if d > 3.0 else EW)
_put(img, lx, ly, c)
# Hot core
_circle(img, cx, cy, [(2.5, EK), (1.8, EM), (1.0, EL), (0.5, EW)])
# Directional clip
drw = ImageDraw.Draw(img)
clips = {
'LEFT': (32, 0, 63, 63),
'RIGHT': (0, 0, 31, 63),
'UP': (0, 32, 63, 63),
'DOWN': (0, 0, 63, 31),
}
if direction in clips:
drw.rectangle(list(clips[direction]), fill=(0, 0, 0, 0))
return img
# ─── NUCLEAR (mushroom cloud) ───────────────────────────────────────────────────
_NUCLEAR_ROWS = [
'. . . . . . . . . . . . . . . .',
'. . . . N L L L L L L N . . . .',
'. . . N L H H H H H H L N . . .',
'. . N L H H N N H H N H L N . .',
'. . N L H N N N H N N H L N . .',
'. . N L N N N H H N N N L N . .',
'. . N L N N N H H N N N L N . .',
'. . . N L H H H H H H L N . . .',
'. . . . N L L L L L L N . . . .',
'. . . . . . M Y Y M . . . . . .',
'. . . . . . M Y Y M . . . . . .',
'. . . . . Z M Y Y M Z . . . . .',
'. . . . Z Z M Y Y M Z Z . . . .',
'. . . Z Z Z M Y Y M Z Z Z . . .',
'. . . Z Z Z Z Z Z Z Z Z Z . . .',
'. . . . . . . . . . . . . . . .',
]
_NUCLEAR_PAL = {'N': NC, 'L': NL, 'H': NH, 'M': NM, 'Y': NY, 'Z': NK}
def make_nuclear() -> Image.Image:
img = _img()
_grid(img, _NUCLEAR_ROWS, _NUCLEAR_PAL)
return img
# ─── POISON VIAL ───────────────────────────────────────────────────────────────
_POISON_ROWS = [
'. . . . . . . . . . . . . . . .',
'. . . . . . . D D D . . . . . .',
'. . . . . . D M M M D . . . . .',
'. . . . . . W W W W W W . . . .',
'. . . . . W B B B B B B W . . .',
'. . . . . W B W . . B B W . . .',
'. . . . . W B . W W . B W . . .',
'. . . . . W B G G G B B W . . .',
'. . . . . W B G H G B B W . . .',
'. . . . . W B G G G B B W . . .',
'. . . . . W B B B B B B W . . .',
'. . . . . W B B B B B B W . . .',
'. . . . . . W W W W W W . . . .',
'. . . . . . . . . . . . . . . .',
'. . . . . . . . . . . . . . . .',
'. . . . . . . . . . . . . . . .',
]
_POISON_PAL = {
'D': PD, # dark purple stopper
'M': PM, # mid purple cork
'W': PW, # white glass
'B': PBK, # skull / label black
'G': PG, # green liquid
'H': PGH, # green highlight
}
def make_poison() -> Image.Image:
img = _img()
_grid(img, _POISON_ROWS, _POISON_PAL)
return img
# ─── Main ───────────────────────────────────────────────────────────────────────
def main():
OUT.mkdir(parents=True, exist_ok=True)
# Bombs (5 animation frames)
for frame in range(5):
p = OUT / f"BMP_BOMB{frame}.png"
make_bomb(frame).save(p)
print(f" wrote {p.name}")
# Gas
for direction in [None, 'LEFT', 'RIGHT', 'UP', 'DOWN']:
suffix = f"_{direction}" if direction else ""
p = OUT / f"BMP_GAS{suffix}.png"
make_gas(direction).save(p)
print(f" wrote {p.name}")
# Explosion
for direction in [None, 'LEFT', 'RIGHT', 'UP', 'DOWN']:
suffix = f"_{direction}" if direction else ""
p = OUT / f"BMP_EXPLOSION{suffix}.png"
make_explosion(direction).save(p)
print(f" wrote {p.name}")
# Nuclear & Poison
make_nuclear().save(OUT / "BMP_NUCLEAR.png")
print(" wrote BMP_NUCLEAR.png")
make_poison().save(OUT / "BMP_POISON.png")
print(" wrote BMP_POISON.png")
total = 5 + 5 + 5 + 2
print(f"\nDone — {total} sprites saved to {OUT}")
if __name__ == "__main__":
main()

24712
tools/vernon/output/BMP_1_GRASS_1.json

File diff suppressed because it is too large Load Diff

1
tools/vernon/output/BMP_1_GRASS_1_v2.json

File diff suppressed because one or more lines are too long

BIN
tools/vernon/output/BMP_1_GRASS_1_v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

1
tools/vernon/output/BMP_BOMB0.json

File diff suppressed because one or more lines are too long

24712
tools/vernon/output/BMP_BOMB0_v2.json

File diff suppressed because it is too large Load Diff

BIN
tools/vernon/output/BMP_BOMB0_v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save