diff --git a/ant_viewer.html b/ant_viewer.html
index a70dea3..cbfdc21 100644
--- a/ant_viewer.html
+++ b/ant_viewer.html
@@ -106,11 +106,24 @@
ctx.shadowBlur = 0;
}
+ // 5. FORMICHE (Colorate in base alla velocità)
if (data.ants) {
- ctx.fillStyle = "#ff8800";
data.ants.forEach(ant => {
+ // ant[0] = x, ant[1] = y, ant[2] = speed
const ax = ant[0] * cellSize;
const ay = ant[1] * cellSize;
+ const speed = ant[2];
+
+ // Interpolazione Colore
+ // Speed 0.5 (Lenta) -> Blu (0, 0, 255)
+ // Speed 1.0 (Media) -> Arancio (255, 165, 0)
+ // Speed 2.5 (Veloce) -> Rosso Fuoco (255, 0, 0)
+
+ let color = "orange";
+ if (speed > 1.3) color = `rgb(255, ${255 - (speed*80)}, 0)`; // Diventa rossa
+ else if (speed < 0.9) color = `rgb(${speed*100}, ${speed*100}, 255)`; // Diventa blu
+
+ ctx.fillStyle = color;
if (cellSize < 3) {
ctx.fillRect(ax, ay, cellSize, cellSize);
diff --git a/src/env.zig b/src/env.zig
index e426851..add1a29 100644
--- a/src/env.zig
+++ b/src/env.zig
@@ -12,7 +12,14 @@ pub const Ant = struct {
x: usize,
y: usize,
alive: bool,
- steps: usize,
+ energy: f32, // Sostituisce 'steps'. Parte da 100, scende a 0.
+
+ // GENI EVOLUTIVI
+ gene_speed: f32, // 0.8 (Lenta) -> 2.5 (Schizzata)
+ gene_metabolism: f32, // 0.5 (Efficiente) -> 2.0 (Sprecona)
+
+ generation: usize, // Per vedere l'evoluzione
+ score: usize, // Quanti cibi ha mangiato in vita
};
pub const World = struct {
@@ -21,7 +28,8 @@ pub const World = struct {
ants: [NUM_ANTS]Ant,
food_x: usize,
food_y: usize,
- max_steps: usize,
+
+ best_ant_idx: usize, // L'indice della formica "Regina" (miglior genitore)
prng: std.Random.DefaultPrng,
pub fn init(seed: u64) World {
@@ -31,7 +39,7 @@ pub const World = struct {
.ants = undefined,
.food_x = 0,
.food_y = 0,
- .max_steps = 1000,
+ .best_ant_idx = 0,
.prng = std.Random.DefaultPrng.init(seed),
};
w.reset();
@@ -39,6 +47,9 @@ pub const World = struct {
}
pub fn reset(self: *World) void {
+ const random = self.prng.random();
+
+ // 1. Reset Mappa
for (0..GRID_SIZE) |y| {
for (0..GRID_SIZE) |x| {
self.pheromones[y][x] = 0.0;
@@ -49,16 +60,21 @@ pub const World = struct {
}
}
}
-
self.respawnFood();
+ // 2. Spawn Formiche (Adamo ed Eva: Geni casuali standard)
const center = GRID_SIZE / 2;
for (0..NUM_ANTS) |i| {
self.ants[i] = Ant{
.x = center,
.y = center,
.alive = true,
- .steps = 0,
+ .energy = 100.0,
+ // Geni iniziali random ma bilanciati
+ .gene_speed = 0.8 + random.float(f32) * 0.4, // 0.8 - 1.2
+ .gene_metabolism = 1.0,
+ .generation = 0,
+ .score = 0,
};
}
}
@@ -90,88 +106,163 @@ pub const World = struct {
return false;
}
+ // --- LOGICA EVOLUTIVA ---
+ // Crea una nuova formica clonando i geni del genitore + mutazione
+ pub fn spawnChild(self: *World, parent_idx: usize) Ant {
+ const parent = self.ants[parent_idx];
+ const random = self.prng.random();
+
+ // Mutazione: +/- 10%
+ var new_speed = parent.gene_speed + (random.float(f32) * 0.2 - 0.1);
+
+ // Limiti biologici (nessuna formica supersonica o immobile)
+ if (new_speed < 0.5) new_speed = 0.5;
+ if (new_speed > 3.0) new_speed = 3.0;
+
+ // Metabolismo: Chi è veloce brucia esponenzialmente di più
+ // Formula: Costo = Speed^2
+ const new_metabolism = new_speed * new_speed;
+
+ return Ant{
+ .x = GRID_SIZE / 2,
+ .y = GRID_SIZE / 2,
+ .alive = true,
+ .energy = 100.0, // Energia piena alla nascita
+ .gene_speed = new_speed,
+ .gene_metabolism = new_metabolism,
+ .generation = parent.generation + 1,
+ .score = 0,
+ };
+ }
+
+ // Aggiorniamo chi è il genitore migliore
+ pub fn updateBestAnt(self: *World) void {
+ var max_score: usize = 0;
+ var best_idx: usize = 0;
+
+ for (self.ants, 0..) |ant, i| {
+ if (ant.alive and ant.score > max_score) {
+ max_score = ant.score;
+ best_idx = i;
+ }
+ }
+ // Se qualcuno ha fatto punti, diventa il nuovo standard
+ if (max_score > 0) {
+ self.best_ant_idx = best_idx;
+ }
+ }
+
pub fn stepAnt(self: *World, ant_idx: usize, action: usize) struct { f32, bool } {
var ant = &self.ants[ant_idx];
if (!ant.alive) return .{ 0.0, true };
- ant.steps += 1;
+ const random = self.prng.random();
- const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x;
- const old_dist_y = if (ant.y > self.food_y) ant.y - self.food_y else self.food_y - ant.y;
- const old_dist = old_dist_x + old_dist_y;
+ // 1. GESTIONE VELOCITÀ (Azione Probabilistica)
+ // Se speed < 1.0, a volte salta il turno.
+ // Se speed > 1.0, a volte fa un doppio turno (ma qui semplifichiamo: fa sempre 1 turno ma costa diverso)
+ // Facciamo così: Speed determina QUANTI passi fa in un loop interno.
- var new_x = ant.x;
- var new_y = ant.y;
+ var moves_to_make: usize = 0;
+ var chance = ant.gene_speed;
- if (action == 0) new_y -= 1;
- if (action == 1) new_y += 1;
- if (action == 2) new_x -= 1;
- if (action == 3) new_x += 1;
-
- if (self.grid[new_y][new_x] == TILE_WALL) return .{ -5.0, false };
-
- if (self.isOccupied(new_x, new_y)) {
- if (old_dist > 5) return .{ -0.5, false };
- }
-
- const dist_x = if (new_x > self.food_x) new_x - self.food_x else self.food_x - new_x;
- const dist_y = if (new_y > self.food_y) new_y - self.food_y else self.food_y - new_y;
- const new_dist = dist_x + dist_y;
-
- var reward: f32 = -0.1;
-
- const scent_intensity = self.getScent(new_x, new_y);
-
- const pheromone_level = self.pheromones[new_y][new_x];
-
- if (scent_intensity > 0.15) {
- reward += 0.1;
- } else {
- if (pheromone_level > 0.1) {
- reward -= 0.5 * pheromone_level;
+ // Esempio: Speed 2.3 -> Fa sicuri 2 passi, e 30% probabilità del 3°
+ while (chance > 0) {
+ if (chance >= 1.0) {
+ moves_to_make += 1;
+ chance -= 1.0;
} else {
- reward += 0.2;
+ if (random.float(f32) < chance) moves_to_make += 1;
+ chance = 0;
}
}
- if (new_dist < old_dist) {
- reward += 1.5 + (scent_intensity * 2.0);
- } else if (new_dist > old_dist) {
- reward -= 1.0;
+ // Se è troppo lenta e salta il turno, perde comunque un po' di energia vitale
+ if (moves_to_make == 0) {
+ ant.energy -= 0.1;
+ if (ant.energy <= 0) return .{ -10.0, true };
+ return .{ 0.0, false };
}
- ant.x = new_x;
- ant.y = new_y;
+ var total_reward: f32 = 0.0;
- self.pheromones[new_y][new_x] = 1.0;
+ // Loop di movimento (Speed)
+ for (0..moves_to_make) |_| {
+ // Se muore durante i passi multipli, stop
+ if (ant.energy <= 0) break;
- if (new_x == self.food_x and new_y == self.food_y) {
- self.respawnFood();
- ant.steps = 0;
- return .{ 100.0, false };
+ // CONSUMO ENERGIA BASATO SUL METABOLISMO
+ ant.energy -= (0.2 * ant.gene_metabolism);
+
+ // Logica movimento classica (semplificata per brevità dello snippet)
+ const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x;
+ const old_dist_y = if (ant.y > self.food_y) ant.y - self.food_y else self.food_y - ant.y;
+ const old_dist = old_dist_x + old_dist_y;
+
+ var new_x = ant.x;
+ var new_y = ant.y;
+ if (action == 0) new_y -= 1;
+ if (action == 1) new_y += 1;
+ if (action == 2) new_x -= 1;
+ if (action == 3) new_x += 1;
+
+ // Collisioni
+ if (self.grid[new_y][new_x] == TILE_WALL or self.isOccupied(new_x, new_y)) {
+ // Sbatte: perde energia e ferma il movimento multiplo
+ ant.energy -= 1.0;
+ total_reward -= 0.5;
+ break;
+ }
+
+ // Movimento valido
+ ant.x = new_x;
+ ant.y = new_y;
+ self.pheromones[new_y][new_x] = 1.0;
+
+ // Olfatto Reward
+ const new_dist = if (new_x > self.food_x) new_x - self.food_x else self.food_x - new_x;
+ const total_new_dist = new_dist + (if (new_y > self.food_y) new_y - self.food_y else self.food_y - new_y);
+
+ const scent = self.getScent(new_x, new_y);
+ if (total_new_dist < old_dist) total_reward += 1.5 + scent;
+ if (total_new_dist > old_dist) total_reward -= 1.0;
+
+ // Mangia?
+ if (new_x == self.food_x and new_y == self.food_y) {
+ self.respawnFood();
+ // RICARICA ENERGIA!
+ ant.energy = 100.0; // Pancia piena
+ ant.score += 1;
+ total_reward += 50.0;
+
+ // Aggiorna chi è il migliore
+ self.updateBestAnt();
+ }
}
- if (ant.steps >= self.max_steps) {
- ant.x = GRID_SIZE / 2;
- ant.y = GRID_SIZE / 2;
- ant.steps = 0;
- return .{ -10.0, false };
+ // Morte
+ if (ant.energy <= 0) {
+ // RESPAWN EVOLUTIVO
+ // Rinasce come figlia della migliore formica attuale
+ self.ants[ant_idx] = self.spawnChild(self.best_ant_idx);
+ return .{ -10.0, true };
}
- return .{ reward, false };
+ return .{ total_reward, false };
}
pub fn evaporatePheromones(self: *World) void {
for (0..GRID_SIZE) |y| {
for (0..GRID_SIZE) |x| {
- if (self.pheromones[y][x] > 0) {
- self.pheromones[y][x] *= 0.995;
- }
+ if (self.pheromones[y][x] > 0) self.pheromones[y][x] *= 0.995;
}
}
}
pub fn getAntObservation(self: *World, allocator: Allocator, ant_idx: usize) ![]f32 {
+ // Stessa logica di prima (15 input), copiato dal messaggio precedente
+ // Per brevità non lo riscrivo tutto, usa quello con i sensori binari
+ // IMPORTANTE: Assicurati di avere la versione con 15 input qui!
var obs = try allocator.alloc(f32, 15);
const ant = self.ants[ant_idx];
var idx: usize = 0;
@@ -185,7 +276,6 @@ pub const World = struct {
while (dx <= 1) : (dx += 1) {
const py = @as(usize, @intCast(ay + dy));
const px = @as(usize, @intCast(ax + dx));
-
var val: f32 = 0.0;
if (self.grid[py][px] == TILE_WALL) {
val = -1.0;
@@ -198,15 +288,12 @@ pub const World = struct {
idx += 1;
}
}
-
obs[9] = self.getScent(ant.x, ant.y);
obs[10] = self.pheromones[ant.y][ant.x];
-
obs[11] = if (self.food_y < ant.y) 1.0 else 0.0;
obs[12] = if (self.food_y > ant.y) 1.0 else 0.0;
obs[13] = if (self.food_x < ant.x) 1.0 else 0.0;
obs[14] = if (self.food_x > ant.x) 1.0 else 0.0;
-
return obs;
}
};
diff --git a/src/main.zig b/src/main.zig
index 36a7f42..cfbbead 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -29,7 +29,7 @@ fn argmax(slice: []const f32) usize {
return idx;
}
-fn exportAntJSON(world: *World, file_path: []const u8, episode: usize, epsilon: f32) !void {
+fn exportAntJSON(world: *World, file_path: []const u8, episode: usize, avg_epsilon: f32) !void {
const file = try std.fs.cwd().createFile(file_path, .{});
defer file.close();
@@ -37,18 +37,19 @@ fn exportAntJSON(world: *World, file_path: []const u8, episode: usize, epsilon:
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
+ // JSON Formiche COMPLESSO: [[x, y, speed_gene], ...]
var ants_json = std.ArrayList(u8){};
defer ants_json.deinit(allocator);
try ants_json.appendSlice(allocator, "[");
for (world.ants, 0..) |ant, i| {
if (i > 0) try ants_json.appendSlice(allocator, ",");
- try std.fmt.format(ants_json.writer(allocator), "[{d},{d}]", .{ ant.x, ant.y });
+ // Passiamo anche la GENETICA (ant.gene_speed)
+ try std.fmt.format(ants_json.writer(allocator), "[{d},{d},{d:.2}]", .{ ant.x, ant.y, ant.gene_speed });
}
try ants_json.appendSlice(allocator, "]");
- const json = try std.fmt.allocPrint(allocator, "{{\n \"grid_size\": {d},\n \"food\": [{d}, {d}],\n \"ants\": {s},\n \"episode\": {d},\n \"epsilon\": {d:.3}\n}}", .{ env.GRID_SIZE, world.food_x, world.food_y, ants_json.items, episode, epsilon });
-
+ const json = try std.fmt.allocPrint(allocator, "{{\n \"grid_size\": {d},\n \"food\": [{d}, {d}],\n \"ants\": {s},\n \"episode\": {d},\n \"epsilon\": {d:.3}\n}}", .{ env.GRID_SIZE, world.food_x, world.food_y, ants_json.items, episode, avg_epsilon });
try file.writeAll(json);
}