|
|
|
@ -0,0 +1,773 @@ |
|
|
|
|
|
|
|
# NumPy Tutorial: Dal Tuo Sistema di Collisioni al Codice Ottimizzato |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Questo documento spiega NumPy usando come esempio reale il sistema di collisioni di Mice!, confrontando il tuo approccio originale con la versione ottimizzata. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Indice |
|
|
|
|
|
|
|
1. [Introduzione: Il Problema delle Performance](#1-introduzione-il-problema-delle-performance) |
|
|
|
|
|
|
|
2. [Cos'è NumPy e Perché Serve](#2-cosè-numpy-e-perché-serve) |
|
|
|
|
|
|
|
3. [Concetti Base di NumPy](#3-concetti-base-di-numpy) |
|
|
|
|
|
|
|
4. [Dal Tuo Codice a NumPy: Caso Pratico](#4-dal-tuo-codice-a-numpy-caso-pratico) |
|
|
|
|
|
|
|
5. [Spatial Hashing: L'Algoritmo Intelligente](#5-spatial-hashing-lalgoritmo-intelligente) |
|
|
|
|
|
|
|
6. [Operazioni Vettoriali in NumPy](#6-operazioni-vettoriali-in-numpy) |
|
|
|
|
|
|
|
7. [Best Practices e Pitfalls](#7-best-practices-e-pitfalls) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Introduzione: Il Problema delle Performance |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Il Tuo Sistema Originale (Funzionava Bene!) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# rats.py - Il tuo approccio originale |
|
|
|
|
|
|
|
def update_maze(self): |
|
|
|
|
|
|
|
# Popolava dizionari con le posizioni delle unità |
|
|
|
|
|
|
|
self.unit_positions = {} |
|
|
|
|
|
|
|
self.unit_positions_before = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.move() |
|
|
|
|
|
|
|
# Raggruppa unità per posizione |
|
|
|
|
|
|
|
self.unit_positions.setdefault(unit.position, []).append(unit) |
|
|
|
|
|
|
|
self.unit_positions_before.setdefault(unit.position_before, []).append(unit) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.collisions() # Ogni unità controlla le proprie collisioni |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Il Problema con 200+ Unità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Con 5-10 topi: **funziona perfetto** ✅ |
|
|
|
|
|
|
|
Con 200+ topi: **FPS crollano** ❌ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Perché?** |
|
|
|
|
|
|
|
- Ogni topo controlla collisioni con TUTTI gli altri topi |
|
|
|
|
|
|
|
- 200 topi = 200 × 200 = **40,000 controlli per frame!** |
|
|
|
|
|
|
|
- Complessità: **O(n²)** - cresce in modo quadratico |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Cos'è NumPy e Perché Serve |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### NumPy in 3 Parole |
|
|
|
|
|
|
|
**Array multidimensionali ottimizzati** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Perché è Veloce? |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Python puro (lento ❌) |
|
|
|
|
|
|
|
distances = [] |
|
|
|
|
|
|
|
for i in range(1000): |
|
|
|
|
|
|
|
for j in range(1000): |
|
|
|
|
|
|
|
dx = x[i] - y[j] |
|
|
|
|
|
|
|
dy = x[i] - y[j] |
|
|
|
|
|
|
|
distances.append((dx**2 + dy**2)**0.5) |
|
|
|
|
|
|
|
# Tempo: ~500ms con 1 milione di operazioni |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# NumPy (veloce ✅) |
|
|
|
|
|
|
|
import numpy as np |
|
|
|
|
|
|
|
distances = np.sqrt((x[:, None] - y[None, :])**2 + (x[:, None] - y[None, :])**2) |
|
|
|
|
|
|
|
# Tempo: ~5ms - 100 volte più veloce! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Perché la Differenza? |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1. **Codice C Compilato**: NumPy è scritto in C/C++, non Python interpretato |
|
|
|
|
|
|
|
2. **Operazioni Vettoriali**: Calcola migliaia di valori in parallelo |
|
|
|
|
|
|
|
3. **Memoria Contigua**: Dati organizzati efficientemente in RAM |
|
|
|
|
|
|
|
4. **CPU SIMD**: Usa istruzioni speciali della CPU per parallelismo hardware |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Concetti Base di NumPy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Array vs Liste Python |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Lista Python (flessibile ma lenta) |
|
|
|
|
|
|
|
lista = [1, 2, 3, 4, 5] |
|
|
|
|
|
|
|
lista.append("sei") # OK - tipi misti |
|
|
|
|
|
|
|
lista[0] = "uno" # OK - cambio tipo |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Array NumPy (veloce ma rigido) |
|
|
|
|
|
|
|
import numpy as np |
|
|
|
|
|
|
|
array = np.array([1, 2, 3, 4, 5]) |
|
|
|
|
|
|
|
# array[0] = "uno" # ERRORE! Tipo fisso: int64 |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Regola**: NumPy sacrifica flessibilità per velocità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Operazioni Elemento per Elemento |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Python puro |
|
|
|
|
|
|
|
lista_a = [1, 2, 3] |
|
|
|
|
|
|
|
lista_b = [4, 5, 6] |
|
|
|
|
|
|
|
risultato = [] |
|
|
|
|
|
|
|
for a, b in zip(lista_a, lista_b): |
|
|
|
|
|
|
|
risultato.append(a + b) |
|
|
|
|
|
|
|
# risultato = [5, 7, 9] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# NumPy (broadcasting) |
|
|
|
|
|
|
|
array_a = np.array([1, 2, 3]) |
|
|
|
|
|
|
|
array_b = np.array([4, 5, 6]) |
|
|
|
|
|
|
|
risultato = array_a + array_b # [5, 7, 9] - automatico! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Broadcasting: Operazioni su Array di Dimensioni Diverse |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Esempio reale dal tuo gioco: calcolare distanze |
|
|
|
|
|
|
|
unit_positions = np.array([[10, 20], [30, 40], [50, 60]]) # 3 unità |
|
|
|
|
|
|
|
target = np.array([25, 35]) # 1 bersaglio |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Vogliamo: distanza di ogni unità dal bersaglio |
|
|
|
|
|
|
|
# Senza broadcasting (noioso): |
|
|
|
|
|
|
|
distances = [] |
|
|
|
|
|
|
|
for pos in unit_positions: |
|
|
|
|
|
|
|
dx = pos[0] - target[0] |
|
|
|
|
|
|
|
dy = pos[1] - target[1] |
|
|
|
|
|
|
|
distances.append(np.sqrt(dx**2 + dy**2)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Con broadcasting (elegante): |
|
|
|
|
|
|
|
diff = unit_positions - target # NumPy espande target automaticamente |
|
|
|
|
|
|
|
distances = np.sqrt((diff**2).sum(axis=1)) |
|
|
|
|
|
|
|
# Output: [18.03, 7.07, 28.28] |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Come funziona?** |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
unit_positions: [[10, 20], target: [25, 35] |
|
|
|
|
|
|
|
[30, 40], |
|
|
|
|
|
|
|
[50, 60]] Broadcasting lo espande a: |
|
|
|
|
|
|
|
[[25, 35], |
|
|
|
|
|
|
|
[25, 35], |
|
|
|
|
|
|
|
[25, 35]] |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Dal Tuo Codice a NumPy: Caso Pratico |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 1: Il Tuo Approccio con i Dizionari |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# units/rat.py - Il tuo codice originale |
|
|
|
|
|
|
|
def collisions(self): |
|
|
|
|
|
|
|
# Prende unità nella stessa cella |
|
|
|
|
|
|
|
units_here = self.game.unit_positions.get(self.position, []) |
|
|
|
|
|
|
|
units_before = self.game.unit_positions_before.get(self.position_before, []) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Controlla ogni unità |
|
|
|
|
|
|
|
for other_unit in units_here + units_before: |
|
|
|
|
|
|
|
if other_unit.id == self.id: |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Logica di collisione... |
|
|
|
|
|
|
|
if self.sex == other_unit.sex and self.fight: |
|
|
|
|
|
|
|
self.die(other_unit) |
|
|
|
|
|
|
|
elif self.sex != other_unit.sex: |
|
|
|
|
|
|
|
self.fuck(other_unit) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Pro del Tuo Approccio:** |
|
|
|
|
|
|
|
- ✅ Semplice e leggibile |
|
|
|
|
|
|
|
- ✅ Usa dizionari Python nativi |
|
|
|
|
|
|
|
- ✅ Funziona perfettamente con poche unità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Problema con 200+ Unità:** |
|
|
|
|
|
|
|
- ❌ Ogni topo itera su liste di Python |
|
|
|
|
|
|
|
- ❌ Controlli ripetuti (topo A controlla B, poi B controlla A) |
|
|
|
|
|
|
|
- ❌ Nessuna ottimizzazione per distanze |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 2: Spatial Hashing (L'Idea Geniale) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Prima di NumPy, serve un algoritmo migliore: **Spatial Hashing** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Concetto: dividi il mondo in "celle" (griglia) |
|
|
|
|
|
|
|
# Ogni cella contiene solo le unità al suo interno |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Mondo di gioco: |
|
|
|
|
|
|
|
# 0 1 2 3 |
|
|
|
|
|
|
|
# 0 [ ] [ ] [ ] [ ] |
|
|
|
|
|
|
|
# 1 [ ] [A] [B] [ ] |
|
|
|
|
|
|
|
# 2 [ ] [ ] [C] [ ] |
|
|
|
|
|
|
|
# 3 [ ] [ ] [ ] [ ] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Dizionario spatial hash: |
|
|
|
|
|
|
|
spatial_grid = { |
|
|
|
|
|
|
|
(1, 1): [unit_A], |
|
|
|
|
|
|
|
(2, 1): [unit_B], |
|
|
|
|
|
|
|
(2, 2): [unit_C] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Quando unit_A cerca collisioni: |
|
|
|
|
|
|
|
# Controlla SOLO celle (1,1) e adiacenti (0,0), (0,1), (0,2), (1,0), (1,2), (2,0), (2,1), (2,2) |
|
|
|
|
|
|
|
# Non controlla unit_C a (2,2) - troppo lontano! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Vantaggio**: Da O(n²) a O(n)! |
|
|
|
|
|
|
|
- 200 unità: da 40,000 controlli a ~1,800 controlli (celle adiacenti) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 3: NumPy per Calcoli Massivi |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# engine/collision_system.py - Il nuovo approccio |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CollisionSystem: |
|
|
|
|
|
|
|
def __init__(self, cell_size=32): |
|
|
|
|
|
|
|
# Pre-allocazione: prepara spazio per array NumPy |
|
|
|
|
|
|
|
self.unit_ids = np.zeros(100, dtype=np.int64) # Array di ID |
|
|
|
|
|
|
|
self.bboxes = np.zeros((100, 4), dtype=np.float32) # Array di bounding box |
|
|
|
|
|
|
|
self.positions = np.zeros((100, 2), dtype=np.int32) # Array di posizioni |
|
|
|
|
|
|
|
self.current_size = 0 # Quante unità registrate |
|
|
|
|
|
|
|
self.capacity = 100 # Capacità massima prima di resize |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Perché Pre-allocazione?** |
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Cattivo: crescita lenta ❌ |
|
|
|
|
|
|
|
array = np.array([]) |
|
|
|
|
|
|
|
for i in range(1000): |
|
|
|
|
|
|
|
array = np.append(array, i) # Crea NUOVO array ogni volta! |
|
|
|
|
|
|
|
# Tempo: ~200ms |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Buono: pre-allocazione ✅ |
|
|
|
|
|
|
|
array = np.zeros(1000) |
|
|
|
|
|
|
|
for i in range(1000): |
|
|
|
|
|
|
|
array[i] = i # Modifica array esistente |
|
|
|
|
|
|
|
# Tempo: ~2ms |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 4: Registrazione Unità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
def register_unit(self, unit_id, bbox, position, position_before, collision_layer): |
|
|
|
|
|
|
|
"""Registra un'unità nel sistema di collisione""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Se array pieno, raddoppia capacità |
|
|
|
|
|
|
|
if self.current_size >= self.capacity: |
|
|
|
|
|
|
|
self._resize_arrays(self.capacity * 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
idx = self.current_size |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Inserisci dati negli array NumPy |
|
|
|
|
|
|
|
self.unit_ids[idx] = unit_id |
|
|
|
|
|
|
|
self.bboxes[idx] = bbox # [x1, y1, x2, y2] |
|
|
|
|
|
|
|
self.positions[idx] = position |
|
|
|
|
|
|
|
self.position_before[idx] = position_before |
|
|
|
|
|
|
|
self.layers[idx] = collision_layer.value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Spatial hashing: aggiungi a griglia |
|
|
|
|
|
|
|
cell = (position[0], position[1]) |
|
|
|
|
|
|
|
self.spatial_grid[cell].append(idx) # Salva INDICE, non unità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.current_size += 1 |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Nota Importante**: Salviamo **indici** negli array, non oggetti Python! |
|
|
|
|
|
|
|
- `spatial_grid[(5, 10)] = [0, 3, 7]` → Unità agli indici 0, 3, 7 degli array NumPy |
|
|
|
|
|
|
|
- Accesso veloce: `self.bboxes[0]`, `self.bboxes[3]`, `self.bboxes[7]` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 5: Collisioni Vettoriali con NumPy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
def get_collisions_for_unit(self, unit_id, bbox, collision_layer): |
|
|
|
|
|
|
|
"""Trova tutte le collisioni per un'unità""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 1. Trova celle da controllare (spatial hashing) |
|
|
|
|
|
|
|
x, y = bbox[0] // self.cell_size, bbox[1] // self.cell_size |
|
|
|
|
|
|
|
cells_to_check = [ |
|
|
|
|
|
|
|
(x-1, y-1), (x, y-1), (x+1, y-1), |
|
|
|
|
|
|
|
(x-1, y), (x, y), (x+1, y), |
|
|
|
|
|
|
|
(x-1, y+1), (x, y+1), (x+1, y+1) |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Raccogli candidati da celle adiacenti |
|
|
|
|
|
|
|
candidates = [] |
|
|
|
|
|
|
|
for cell in cells_to_check: |
|
|
|
|
|
|
|
candidates.extend(self.spatial_grid.get(cell, [])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(candidates) < 10: |
|
|
|
|
|
|
|
# POCHI candidati: usa Python normale |
|
|
|
|
|
|
|
collisions = [] |
|
|
|
|
|
|
|
for idx in candidates: |
|
|
|
|
|
|
|
if self.unit_ids[idx] == unit_id: |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
if self._check_bbox_collision(bbox, self.bboxes[idx]): |
|
|
|
|
|
|
|
collisions.append((self.layers[idx], self.unit_ids[idx])) |
|
|
|
|
|
|
|
return collisions |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
# MOLTI candidati: USA NUMPY! ✨ |
|
|
|
|
|
|
|
return self._vectorized_collision_check(unit_id, bbox, candidates) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Fase 6: La Magia di NumPy - Vectorized Collision Check |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
def _vectorized_collision_check(self, unit_id, bbox, candidate_indices): |
|
|
|
|
|
|
|
"""Controlla collisioni usando NumPy per massima velocità""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Converti candidati in array NumPy |
|
|
|
|
|
|
|
candidate_indices = np.array(candidate_indices, dtype=np.int32) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Filtra l'unità stessa (non collidere con se stessi) |
|
|
|
|
|
|
|
mask = self.unit_ids[candidate_indices] != unit_id |
|
|
|
|
|
|
|
candidate_indices = candidate_indices[mask] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(candidate_indices) == 0: |
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Estrai bounding box di TUTTI i candidati in un colpo solo |
|
|
|
|
|
|
|
candidate_bboxes = self.bboxes[candidate_indices] # Shape: (N, 4) |
|
|
|
|
|
|
|
# candidate_bboxes = [[x1, y1, x2, y2], # candidato 0 |
|
|
|
|
|
|
|
# [x1, y1, x2, y2], # candidato 1 |
|
|
|
|
|
|
|
# ...] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Controllo collisione AABB (Axis-Aligned Bounding Box) |
|
|
|
|
|
|
|
# Due rettangoli collidono se: |
|
|
|
|
|
|
|
# - bbox.x1 < other.x2 AND |
|
|
|
|
|
|
|
# - bbox.x2 > other.x1 AND |
|
|
|
|
|
|
|
# - bbox.y1 < other.y2 AND |
|
|
|
|
|
|
|
# - bbox.y2 > other.y1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# NumPy calcola TUTTE le collisioni contemporaneamente! 🚀 |
|
|
|
|
|
|
|
colliding_mask = ( |
|
|
|
|
|
|
|
(bbox[0] < candidate_bboxes[:, 2]) & # bbox.x1 < others.x2 |
|
|
|
|
|
|
|
(bbox[2] > candidate_bboxes[:, 0]) & # bbox.x2 > others.x1 |
|
|
|
|
|
|
|
(bbox[1] < candidate_bboxes[:, 3]) & # bbox.y1 < others.y2 |
|
|
|
|
|
|
|
(bbox[3] > candidate_bboxes[:, 1]) # bbox.y2 > others.y1 |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
# colliding_mask = [True, False, True, False, True, ...] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Filtra solo unità che collidono |
|
|
|
|
|
|
|
colliding_indices = candidate_indices[colliding_mask] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Restituisci coppie (layer, unit_id) |
|
|
|
|
|
|
|
return list(zip( |
|
|
|
|
|
|
|
self.layers[colliding_indices], |
|
|
|
|
|
|
|
self.unit_ids[colliding_indices] |
|
|
|
|
|
|
|
)) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Spiegazione Dettagliata del Codice NumPy:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Esempio concreto con 3 candidati |
|
|
|
|
|
|
|
bbox = [10, 20, 30, 40] # Nostro topo: x1=10, y1=20, x2=30, y2=40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
candidate_bboxes = np.array([ |
|
|
|
|
|
|
|
[5, 15, 25, 35], # Candidato 0 |
|
|
|
|
|
|
|
[50, 60, 70, 80], # Candidato 1 (lontano) |
|
|
|
|
|
|
|
[15, 25, 35, 45] # Candidato 2 |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Controllo bbox[0] < candidate_bboxes[:, 2] |
|
|
|
|
|
|
|
# bbox[0] = 10 |
|
|
|
|
|
|
|
# candidate_bboxes[:, 2] = [25, 70, 35] # Colonna x2 di tutti i candidati |
|
|
|
|
|
|
|
# 10 < [25, 70, 35] = [True, True, True] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Controllo bbox[2] > candidate_bboxes[:, 0] |
|
|
|
|
|
|
|
# bbox[2] = 30 |
|
|
|
|
|
|
|
# candidate_bboxes[:, 0] = [5, 50, 15] # Colonna x1 |
|
|
|
|
|
|
|
# 30 > [5, 50, 15] = [True, False, True] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ... altri controlli ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Combinazione finale (AND logico): |
|
|
|
|
|
|
|
colliding_mask = [True, False, True] # Solo 0 e 2 collidono! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 5. Spatial Hashing: L'Algoritmo Intelligente |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Visualizzazione Pratica |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
Mondo di gioco 640x480, cell_size=32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Griglia spaziale: |
|
|
|
|
|
|
|
0 1 2 3 4 5 ... 19 |
|
|
|
|
|
|
|
┌────┬────┬────┬────┬────┬────┬────┬────┐ |
|
|
|
|
|
|
|
0 │ │ │ │ │ │ │ │ │ |
|
|
|
|
|
|
|
├────┼────┼────┼────┼────┼────┼────┼────┤ |
|
|
|
|
|
|
|
1 │ │ R1 │ R2 │ │ │ │ │ │ R = Rat |
|
|
|
|
|
|
|
├────┼────┼────┼────┼────┼────┼────┼────┤ B = Bomb |
|
|
|
|
|
|
|
2 │ │ R3 │ B1 │ R4 │ │ │ │ │ M = Mine |
|
|
|
|
|
|
|
├────┼────┼────┼────┼────┼────┼────┼────┤ |
|
|
|
|
|
|
|
3 │ │ │ M1 │ │ │ │ │ │ |
|
|
|
|
|
|
|
└────┴────┴────┴────┴────┴────┴────┴────┘ |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Come Funziona il Lookup |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# R1 cerca collisioni da cella (1, 1) |
|
|
|
|
|
|
|
def get_collisions_for_unit(self, unit_id, bbox): |
|
|
|
|
|
|
|
x, y = 1, 1 # Posizione R1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Controlla 9 celle (3x3 centrato su R1): |
|
|
|
|
|
|
|
cells = [ |
|
|
|
|
|
|
|
(0,0), (1,0), (2,0), # Riga sopra |
|
|
|
|
|
|
|
(0,1), (1,1), (2,1), # Riga centrale (include R1) |
|
|
|
|
|
|
|
(0,2), (1,2), (2,2) # Riga sotto |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# spatial_grid è un dizionario: |
|
|
|
|
|
|
|
# { |
|
|
|
|
|
|
|
# (1, 1): [idx_R1], |
|
|
|
|
|
|
|
# (2, 1): [idx_R2], |
|
|
|
|
|
|
|
# (1, 2): [idx_R3], |
|
|
|
|
|
|
|
# (2, 2): [idx_B1, idx_R4], |
|
|
|
|
|
|
|
# (2, 3): [idx_M1] |
|
|
|
|
|
|
|
# } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
candidates = [] |
|
|
|
|
|
|
|
for cell in cells: |
|
|
|
|
|
|
|
candidates.extend(self.spatial_grid.get(cell, [])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# candidates = [idx_R1, idx_R2, idx_R3, idx_B1, idx_R4] |
|
|
|
|
|
|
|
# NON include idx_M1 perché (2,3) è fuori dal range 3x3! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Benefici Misurabili |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# SENZA spatial hashing (O(n²)): |
|
|
|
|
|
|
|
# 200 unità → 200 × 200 = 40,000 controlli |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# CON spatial hashing (O(n)): |
|
|
|
|
|
|
|
# 200 unità, distribuite su 20×15=300 celle |
|
|
|
|
|
|
|
# Media 0.67 unità per cella |
|
|
|
|
|
|
|
# Ogni unità controlla 9 celle × 0.67 = ~6 candidati |
|
|
|
|
|
|
|
# 200 unità × 6 candidati = 1,200 controlli |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
# Miglioramento: 40,000 → 1,200 = 33x più veloce! 🚀 |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Operazioni Vettoriali in NumPy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Broadcasting Avanzato: Esplosioni |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
def get_units_in_area(self, positions, layer_filter=None): |
|
|
|
|
|
|
|
"""Trova unità in un'area (es. esplosione bomba)""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# positions: lista di posizioni esplose |
|
|
|
|
|
|
|
# es. [(10, 10), (10, 11), (11, 10), (11, 11)] # Esplosione 2x2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.current_size == 0: |
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Converti in array NumPy |
|
|
|
|
|
|
|
area_positions = np.array(positions, dtype=np.int32) # Shape: (4, 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Prendi posizioni di TUTTE le unità |
|
|
|
|
|
|
|
all_positions = self.positions[:self.current_size] # Shape: (N, 2) |
|
|
|
|
|
|
|
# es. all_positions = [[5, 5], [10, 10], [15, 15], [10, 11], ...] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Broadcasting trick per confrontare OGNI posizione esplosione con OGNI unità |
|
|
|
|
|
|
|
# area_positions[:, None, :] → Shape: (4, 1, 2) |
|
|
|
|
|
|
|
# all_positions[None, :, :] → Shape: (1, N, 2) |
|
|
|
|
|
|
|
# Risultato → Shape: (4, N, 2) - tutte le combinazioni! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
matches = (area_positions[:, None, :] == all_positions[None, :, :]).all(axis=2) |
|
|
|
|
|
|
|
# matches[i, j] = True se esplosione i colpisce unità j |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# any(axis=0): almeno una posizione esplosione colpisce quella unità? |
|
|
|
|
|
|
|
unit_hit_mask = matches.any(axis=0) # Shape: (N,) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Filtra per layer se richiesto |
|
|
|
|
|
|
|
if layer_filter: |
|
|
|
|
|
|
|
valid_layers = self.layers[:self.current_size] == layer_filter.value |
|
|
|
|
|
|
|
unit_hit_mask = unit_hit_mask & valid_layers |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Restituisci ID delle unità colpite |
|
|
|
|
|
|
|
hit_indices = np.where(unit_hit_mask)[0] |
|
|
|
|
|
|
|
return self.unit_ids[hit_indices].tolist() |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Spiegazione con Esempio Concreto:** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Bomba esplode creando 4 celle di fuoco |
|
|
|
|
|
|
|
area_positions = np.array([[10, 10], [10, 11], [11, 10], [11, 11]]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Ci sono 3 topi nel gioco |
|
|
|
|
|
|
|
all_positions = np.array([[5, 5], [10, 10], [10, 11]]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Broadcasting: |
|
|
|
|
|
|
|
area_positions[:, None, :].shape # (4, 1, 2) |
|
|
|
|
|
|
|
all_positions[None, :, :].shape # (1, 3, 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Confronto elemento per elemento: |
|
|
|
|
|
|
|
matches = (area_positions[:, None, :] == all_positions[None, :, :]).all(axis=2) |
|
|
|
|
|
|
|
# matches = [ |
|
|
|
|
|
|
|
# [False, False, False], # Esplosione (10,10) vs [(5,5), (10,10), (10,11)] |
|
|
|
|
|
|
|
# [False, True, False], # Esplosione (10,11) vs ... |
|
|
|
|
|
|
|
# [False, False, True ], # Esplosione (11,10) vs ... |
|
|
|
|
|
|
|
# [False, False, False] # Esplosione (11,11) vs ... |
|
|
|
|
|
|
|
# ] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Collassa su asse 0 (almeno UNA esplosione colpisce?) |
|
|
|
|
|
|
|
unit_hit_mask = matches.any(axis=0) # [False, True, True] |
|
|
|
|
|
|
|
# Topo 0 (5,5): NON colpito |
|
|
|
|
|
|
|
# Topo 1 (10,10): COLPITO (da esplosione 0) |
|
|
|
|
|
|
|
# Topo 2 (10,11): COLPITO (da esplosione 1) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Calcolo Distanze Vettoriale |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Esempio: trovare tutti i topi entro raggio 50 pixel da una bomba |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bomb_position = np.array([100, 100]) # Posizione bomba |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Posizioni di tutti i topi (array NumPy) |
|
|
|
|
|
|
|
rat_positions = self.positions[:self.current_size] # Shape: (N, 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Calcolo distanze usando broadcasting |
|
|
|
|
|
|
|
diff = rat_positions - bomb_position # Shape: (N, 2) |
|
|
|
|
|
|
|
# diff[i] = [rat_x - bomb_x, rat_y - bomb_y] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
distances = np.sqrt((diff ** 2).sum(axis=1)) # Shape: (N,) |
|
|
|
|
|
|
|
# distances[i] = sqrt((dx)^2 + (dy)^2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Trova topi entro raggio |
|
|
|
|
|
|
|
within_radius = distances < 50 # Boolean mask |
|
|
|
|
|
|
|
hit_rat_indices = np.where(within_radius)[0] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Esempio output: |
|
|
|
|
|
|
|
# rat_positions = [[90, 90], [110, 110], [200, 200]] |
|
|
|
|
|
|
|
# diff = [[-10, -10], [10, 10], [100, 100]] |
|
|
|
|
|
|
|
# distances = [14.14, 14.14, 141.42] |
|
|
|
|
|
|
|
# within_radius = [True, True, False] |
|
|
|
|
|
|
|
# hit_rat_indices = [0, 1] # Primi due topi colpiti! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Best Practices e Pitfalls |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ Quando Usare NumPy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# BUONO: Operazioni su molti dati |
|
|
|
|
|
|
|
positions = np.array([[...] for _ in range(1000)]) |
|
|
|
|
|
|
|
distances = np.sqrt(((positions - target)**2).sum(axis=1)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# CATTIVO: Operazioni su pochi dati (overhead NumPy!) |
|
|
|
|
|
|
|
positions = np.array([[10, 20], [30, 40]]) # Solo 2 elementi |
|
|
|
|
|
|
|
distances = np.sqrt(((positions - target)**2).sum(axis=1)) |
|
|
|
|
|
|
|
# Più lento di un semplice loop Python! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Regola nel tuo codice:** |
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
if len(candidates) < 10: |
|
|
|
|
|
|
|
# Usa Python normale |
|
|
|
|
|
|
|
for idx in candidates: |
|
|
|
|
|
|
|
... |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
# Usa NumPy |
|
|
|
|
|
|
|
self._vectorized_collision_check(...) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ Pre-allocazione vs Append |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# CATTIVO ❌ (lento con array grandi) |
|
|
|
|
|
|
|
array = np.array([]) |
|
|
|
|
|
|
|
for i in range(10000): |
|
|
|
|
|
|
|
array = np.append(array, i) # O(n) ad ogni append! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# BUONO ✅ (veloce) |
|
|
|
|
|
|
|
array = np.zeros(10000) |
|
|
|
|
|
|
|
for i in range(10000): |
|
|
|
|
|
|
|
array[i] = i # O(1) ad ogni assegnazione |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# MIGLIORE ✅ (senza loop!) |
|
|
|
|
|
|
|
array = np.arange(10000) # Operazione vettoriale nativa |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ Memory Layout e Performance |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# Row-major (C order) - default NumPy |
|
|
|
|
|
|
|
array = np.zeros((1000, 3), order='C') |
|
|
|
|
|
|
|
# Memoria: [x0, y0, z0, x1, y1, z1, ...] |
|
|
|
|
|
|
|
# Veloce per accesso righe: array[i, :] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Column-major (Fortran order) |
|
|
|
|
|
|
|
array = np.zeros((1000, 3), order='F') |
|
|
|
|
|
|
|
# Memoria: [x0, x1, ..., y0, y1, ..., z0, z1, ...] |
|
|
|
|
|
|
|
# Veloce per accesso colonne: array[:, j] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Nel tuo caso (posizioni): |
|
|
|
|
|
|
|
self.positions = np.zeros((100, 2)) # Row-major è perfetto |
|
|
|
|
|
|
|
# Accesso frequente: self.positions[idx] → [x, y] di un'unità |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ⚠️ Pitfall Comuni |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. Copy vs View |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# View (condivide memoria) |
|
|
|
|
|
|
|
a = np.array([1, 2, 3]) |
|
|
|
|
|
|
|
b = a[:] # b è una VIEW di a |
|
|
|
|
|
|
|
b[0] = 999 |
|
|
|
|
|
|
|
print(a) # [999, 2, 3] - modificato anche a! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Copy (memoria separata) |
|
|
|
|
|
|
|
a = np.array([1, 2, 3]) |
|
|
|
|
|
|
|
b = a.copy() |
|
|
|
|
|
|
|
b[0] = 999 |
|
|
|
|
|
|
|
print(a) # [1, 2, 3] - a è immutato |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Nel tuo codice:** |
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
def get_collisions_for_unit(self, ...): |
|
|
|
|
|
|
|
# Filtriamo candidati |
|
|
|
|
|
|
|
candidate_indices = candidate_indices[mask] # Crea VIEW |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Se modifichi candidate_indices dopo, potresti modificare l'originale! |
|
|
|
|
|
|
|
# Soluzione: .copy() se necessario |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. Broadcasting Inatteso |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
a = np.array([1, 2, 3]) |
|
|
|
|
|
|
|
b = np.array([[1], [2], [3]]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Cosa succede? |
|
|
|
|
|
|
|
result = a + b |
|
|
|
|
|
|
|
# Broadcasting espande a: |
|
|
|
|
|
|
|
# [[1, 2, 3], [[1], [1], [1]] [[2, 3, 4], |
|
|
|
|
|
|
|
# [1, 2, 3], + [2], [2], [2]] = [3, 4, 5], |
|
|
|
|
|
|
|
# [1, 2, 3]] [3], [3], [3]] [4, 5, 6]] |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Verifica sempre le shape:** |
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
print(f"Shape: {array.shape}") # Sempre prima di operazioni complesse! |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. Integer Overflow |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# ATTENZIONE con dtype piccoli! |
|
|
|
|
|
|
|
a = np.array([250], dtype=np.uint8) # Max 255 |
|
|
|
|
|
|
|
b = a + 10 # 260, ma uint8 wrap: diventa 4! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Soluzione: usa dtype appropriati |
|
|
|
|
|
|
|
a = np.array([250], dtype=np.int32) # Max ~2 miliardi |
|
|
|
|
|
|
|
b = a + 10 # 260 ✅ |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Confronto Finale: Prima vs Dopo |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Codice Originale (Il Tuo) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# rats.py |
|
|
|
|
|
|
|
def update_maze(self): |
|
|
|
|
|
|
|
self.unit_positions = {} |
|
|
|
|
|
|
|
self.unit_positions_before = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.move() |
|
|
|
|
|
|
|
self.unit_positions.setdefault(unit.position, []).append(unit) |
|
|
|
|
|
|
|
self.unit_positions_before.setdefault(unit.position_before, []).append(unit) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.collisions() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# units/rat.py |
|
|
|
|
|
|
|
def collisions(self): |
|
|
|
|
|
|
|
for other_unit in self.game.unit_positions.get(self.position, []): |
|
|
|
|
|
|
|
if other_unit.id == self.id: |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
# Controlla collisione... |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Complessità**: O(n²) nel caso peggiore |
|
|
|
|
|
|
|
**Performance**: ~50ms con 200 unità |
|
|
|
|
|
|
|
**Memoria**: Dizionari Python + liste di oggetti |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Codice Ottimizzato (NumPy) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python |
|
|
|
|
|
|
|
# rats.py - 4-pass loop |
|
|
|
|
|
|
|
def update_maze(self): |
|
|
|
|
|
|
|
self.collision_system.clear() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pass 1: Pre-registra posizioni |
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
self.collision_system.register_unit(unit.id, unit.bbox, ...) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pass 2: Movimento |
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.move() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pass 3: Ri-registra dopo movimento |
|
|
|
|
|
|
|
self.collision_system.clear() |
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
self.collision_system.register_unit(unit.id, unit.bbox, ...) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pass 4: Collisioni |
|
|
|
|
|
|
|
for unit in self.units.values(): |
|
|
|
|
|
|
|
unit.collisions() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# units/rat.py |
|
|
|
|
|
|
|
def collisions(self): |
|
|
|
|
|
|
|
collisions = self.game.collision_system.get_collisions_for_unit( |
|
|
|
|
|
|
|
self.id, self.bbox, self.collision_layer |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
for _, other_id in collisions: |
|
|
|
|
|
|
|
other_unit = self.game.get_unit_by_id(other_id) |
|
|
|
|
|
|
|
if isinstance(other_unit, Rat): |
|
|
|
|
|
|
|
# Controlla collisione... |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Complessità**: O(n) con spatial hashing + NumPy |
|
|
|
|
|
|
|
**Performance**: ~3ms con 250 unità (16x più veloce!) |
|
|
|
|
|
|
|
**Memoria**: Array NumPy pre-allocati (più efficiente) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Conclusione |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Cosa Hai Imparato |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1. **NumPy**: Array veloci per operazioni matematiche massive |
|
|
|
|
|
|
|
2. **Broadcasting**: Operazioni automatiche su array di dimensioni diverse |
|
|
|
|
|
|
|
3. **Spatial Hashing**: Ridurre O(n²) a O(n) con partizionamento spaziale |
|
|
|
|
|
|
|
4. **Vectorization**: Sostituire loop Python con operazioni NumPy parallele |
|
|
|
|
|
|
|
5. **Pre-allocazione**: Evitare allocazioni ripetute per velocità |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Quando Applicare Queste Tecniche |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ **Usa NumPy quando**: Hai 50+ elementi da processare con operazioni matematiche |
|
|
|
|
|
|
|
- ✅ **Usa Spatial Hashing quando**: Controlli collisioni/prossimità in spazio 2D/3D |
|
|
|
|
|
|
|
- ✅ **Usa Vectorization quando**: Stesso calcolo ripetuto su molti dati |
|
|
|
|
|
|
|
- ❌ **Non usare quando**: Pochi elementi (< 10) o logica complessa non matematica |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Risorse per Approfondire |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- **NumPy Documentation**: https://numpy.org/doc/stable/ |
|
|
|
|
|
|
|
- **NumPy Quickstart**: https://numpy.org/doc/stable/user/quickstart.html |
|
|
|
|
|
|
|
- **Broadcasting**: https://numpy.org/doc/stable/user/basics.broadcasting.html |
|
|
|
|
|
|
|
- **Performance Tips**: https://numpy.org/doc/stable/user/c-info.performance.html |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Il tuo codice originale era ottimo per il caso d'uso iniziale.** L'ottimizzazione con NumPy è stata necessaria solo quando hai scalato a 200+ unità. Questo è un esempio perfetto di "ottimizza quando serve", non prematuramente! 🎯 |