Nuove regole

This commit is contained in:
Fores 2026-02-19 15:58:52 +01:00
parent 1980e7996f
commit 44248db15e
5 changed files with 380 additions and 191 deletions

View file

@ -1,7 +1,8 @@
{ {
"grid_size": 100, "grid_size": 100,
"food": [61, 59], "food": [31, 32],
"ants": [[55,59,0.81],[33,64,0.81],[30,84,0.82],[21,90,0.89],[49,59,0.98],[37,59,0.85],[20,56,0.96],[54,57,0.81],[27,83,0.96],[33,24,0.83],[29,59,0.84],[22,59,0.94],[23,58,0.80],[26,90,0.81],[30,74,0.78],[33,60,0.89],[24,90,0.98],[52,59,0.90],[25,59,0.91],[26,73,0.83]], "predator": [45, 66],
"episode": 415780, "ants": [[45,80,1.00]],
"epsilon": 0.050 "episode": 1209,
"epsilon": 0.020
} }

View file

@ -53,104 +53,91 @@
</div> </div>
<script> <script>
const canvas = document.getElementById('gridCanvas'); // ... setup canvas e context esistente ...
const ctx = canvas.getContext('2d'); const canvas = document.getElementById('gridCanvas');
const ctx = canvas.getContext('2d');
const epEl = document.getElementById('episode');
const epsEl = document.getElementById('epsilon');
const foodEl = document.getElementById('food-coords');
const epEl = document.getElementById('episode'); function drawWorld(data) {
const epsEl = document.getElementById('epsilon'); // ... (setup grid size e clear rect come prima) ...
const foodEl = document.getElementById('food-coords'); gridSize = data.grid_size;
const margin = 40;
const availableW = document.getElementById('container').clientWidth - margin;
const availableH = document.getElementById('container').clientHeight - margin;
const canvasSize = Math.min(availableW, availableH);
canvas.width = canvasSize;
canvas.height = canvasSize;
const cellSize = canvasSize / gridSize;
let gridSize = 100; ctx.fillStyle = "#1a1a1a";
ctx.fillRect(0, 0, canvas.width, canvas.height);
function drawWorld(data) { // ... (disegno griglia opzionale come prima) ...
gridSize = data.grid_size;
const margin = 40; // Disegno CIBO
const availableW = document.getElementById('container').clientWidth - margin; if (data.food) {
const availableH = document.getElementById('container').clientHeight - margin; const fx = data.food[0] * cellSize;
const fy = data.food[1] * cellSize;
ctx.shadowBlur = 15;
ctx.shadowColor = "#0f0";
ctx.fillStyle = "#00ff00";
ctx.beginPath();
ctx.arc(fx + cellSize/2, fy + cellSize/2, cellSize/1.5, 0, Math.PI*2);
ctx.fill();
ctx.shadowBlur = 0;
}
const canvasSize = Math.min(availableW, availableH); // NUOVO: Disegno PREDATORE
if (data.predator) {
const px = data.predator[0] * cellSize;
const py = data.predator[1] * cellSize;
canvas.width = canvasSize; ctx.shadowBlur = 20;
canvas.height = canvasSize; ctx.shadowColor = "#f00";
ctx.fillStyle = "#ff0000";
const cellSize = canvasSize / gridSize; // Disegno come un quadrato "cattivo"
ctx.fillRect(px, py, cellSize, cellSize);
ctx.fillStyle = "#1a1a1a"; ctx.shadowBlur = 0;
ctx.fillRect(0, 0, canvas.width, canvas.height); }
if (cellSize > 4) { // Disegno FORMICHE
ctx.strokeStyle = "#2a2a2a"; if (data.ants) {
ctx.lineWidth = 1; data.ants.forEach(ant => {
const ax = ant[0] * cellSize;
const ay = ant[1] * cellSize;
const speed = ant[2];
let color = "orange";
if (speed > 1.3) color = `rgb(255, ${255 - (speed*80)}, 0)`; // Giallo/Rosso veloce
else if (speed < 0.9) color = `rgb(${speed*100}, ${speed*100}, 255)`; // Blu lento
ctx.fillStyle = color;
ctx.beginPath(); ctx.beginPath();
for(let i=0; i<=gridSize; i++) { ctx.arc(ax + cellSize/2, ay + cellSize/2, cellSize/2, 0, Math.PI*2);
const pos = i * cellSize;
ctx.moveTo(pos, 0); ctx.lineTo(pos, canvas.height);
ctx.moveTo(0, pos); ctx.lineTo(canvas.width, pos);
}
ctx.stroke();
}
if (data.food) {
const fx = data.food[0] * cellSize;
const fy = data.food[1] * cellSize;
ctx.shadowBlur = 15;
ctx.shadowColor = "#0f0";
ctx.fillStyle = "#00ff00";
ctx.beginPath();
ctx.arc(fx + cellSize/2, fy + cellSize/2, cellSize/1.5, 0, Math.PI*2);
ctx.fill(); ctx.fill();
});
ctx.shadowBlur = 0;
}
if (data.ants) {
data.ants.forEach(ant => {
const ax = ant[0] * cellSize;
const ay = ant[1] * cellSize;
const speed = ant[2];
let color = "orange";
if (speed > 1.3) color = `rgb(255, ${255 - (speed*80)}, 0)`;
else if (speed < 0.9) color = `rgb(${speed*100}, ${speed*100}, 255)`;
ctx.fillStyle = color;
if (cellSize < 3) {
ctx.fillRect(ax, ay, cellSize, cellSize);
} else {
ctx.beginPath();
ctx.arc(ax + cellSize/2, ay + cellSize/2, cellSize/2, 0, Math.PI*2);
ctx.fill();
}
});
}
} }
}
async function update() { async function update() {
try { try {
const response = await fetch('ant_state.json?t=' + Date.now()); const response = await fetch('ant_state.json?t=' + Date.now());
if (!response.ok) return; if (!response.ok) return;
const data = await response.json();
const data = await response.json(); epEl.innerText = data.episode;
epsEl.innerText = data.epsilon;
if(data.food) foodEl.innerText = `[${data.food[0]}, ${data.food[1]}]`;
epEl.innerText = data.episode; drawWorld(data);
epsEl.innerText = data.epsilon; } catch (e) { console.error(e); }
if(data.food) foodEl.innerText = `[${data.food[0]}, ${data.food[1]}]`; }
drawWorld(data); setInterval(update, 50);
} catch (e) { </script>
console.error(e);
}
}
setInterval(update, 50);
window.addEventListener('resize', () => {
});
</script>
</body> </body>
</html> </html>

BIN
best_brain.bin Normal file

Binary file not shown.

View file

@ -2,21 +2,20 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
pub const GRID_SIZE = 100; pub const GRID_SIZE = 100;
pub const NUM_ANTS = 20; pub const NUM_ANTS = 1;
pub const TILE_EMPTY = 0; pub const TILE_EMPTY = 0;
pub const TILE_WALL = 1; pub const TILE_WALL = 1;
pub const TILE_FOOD = 2; pub const TILE_FOOD = 2;
// Aggiungiamo il tipo visuale per il predatore (non usato nella grid array, ma per logica)
pub const TILE_PREDATOR = 3;
pub const Ant = struct { pub const Ant = struct {
x: usize, x: usize,
y: usize, y: usize,
alive: bool, alive: bool,
energy: f32, energy: f32,
gene_speed: f32, gene_speed: f32,
gene_metabolism: f32, gene_metabolism: f32,
generation: usize, generation: usize,
score: usize, score: usize,
}; };
@ -28,6 +27,10 @@ pub const World = struct {
food_x: usize, food_x: usize,
food_y: usize, food_y: usize,
// NUOVO: Coordinate del predatore
pred_x: usize,
pred_y: usize,
best_ant_idx: usize, best_ant_idx: usize,
prng: std.Random.DefaultPrng, prng: std.Random.DefaultPrng,
@ -38,6 +41,8 @@ pub const World = struct {
.ants = undefined, .ants = undefined,
.food_x = 0, .food_x = 0,
.food_y = 0, .food_y = 0,
.pred_x = 0, // Init
.pred_y = 0, // Init
.best_ant_idx = 0, .best_ant_idx = 0,
.prng = std.Random.DefaultPrng.init(seed), .prng = std.Random.DefaultPrng.init(seed),
}; };
@ -46,8 +51,8 @@ pub const World = struct {
} }
pub fn reset(self: *World) void { pub fn reset(self: *World) void {
const random = self.prng.random(); //const random = self.prng.random();
// Reset Grid (codice invariato...)
for (0..GRID_SIZE) |y| { for (0..GRID_SIZE) |y| {
for (0..GRID_SIZE) |x| { for (0..GRID_SIZE) |x| {
self.pheromones[y][x] = 0.0; self.pheromones[y][x] = 0.0;
@ -60,6 +65,10 @@ pub const World = struct {
} }
self.respawnFood(); self.respawnFood();
// NUOVO: Respawna il predatore in un angolo lontano
self.pred_x = GRID_SIZE - 5;
self.pred_y = GRID_SIZE - 5;
const center = GRID_SIZE / 2; const center = GRID_SIZE / 2;
for (0..NUM_ANTS) |i| { for (0..NUM_ANTS) |i| {
self.ants[i] = Ant{ self.ants[i] = Ant{
@ -67,14 +76,18 @@ pub const World = struct {
.y = center, .y = center,
.alive = true, .alive = true,
.energy = 100.0, .energy = 100.0,
.gene_speed = 0.8 + random.float(f32) * 0.4,
.gene_metabolism = 1.0, // PRIMA ERA: .gene_speed = 0.8 + random.float(f32) * 0.4,
.gene_speed = 1.0, // Velocità fissa e garantita
.gene_metabolism = 1.0, // Consumo fisso
.generation = 0, .generation = 0,
.score = 0, .score = 0,
}; };
} }
} }
// respawnFood e getScent invariati...
fn respawnFood(self: *World) void { fn respawnFood(self: *World) void {
const random = self.prng.random(); const random = self.prng.random();
while (true) { while (true) {
@ -102,41 +115,89 @@ pub const World = struct {
return false; return false;
} }
// spawnChild invariato...
pub fn spawnChild(self: *World, parent_idx: usize) Ant { pub fn spawnChild(self: *World, parent_idx: usize) Ant {
const parent = self.ants[parent_idx]; const parent = self.ants[parent_idx];
const random = self.prng.random();
var new_speed = parent.gene_speed + (random.float(f32) * 0.2 - 0.1); // Abbiamo rimosso tutta la logica del random e le mutazioni
if (new_speed < 0.5) new_speed = 0.5;
if (new_speed > 3.0) new_speed = 3.0;
const new_metabolism = new_speed * new_speed;
return Ant{ return Ant{
.x = GRID_SIZE / 2, .x = GRID_SIZE / 2,
.y = GRID_SIZE / 2, .y = GRID_SIZE / 2,
.alive = true, .alive = true,
.energy = 100.0, .energy = 100.0,
.gene_speed = new_speed, .gene_speed = 1.0, // Il figlio ha sempre velocità 1.0
.gene_metabolism = new_metabolism, .gene_metabolism = 1.0, // Il figlio ha sempre metabolismo 1.0
.generation = parent.generation + 1, .generation = parent.generation + 1,
.score = 0, .score = 0,
}; };
} }
// updateBestAnt invariato...
pub fn updateBestAnt(self: *World) void { pub fn updateBestAnt(self: *World) void {
var max_score: usize = 0; var max_score: usize = 0;
var best_idx: usize = 0; var best_idx: usize = 0;
for (self.ants, 0..) |ant, i| { for (self.ants, 0..) |ant, i| {
if (ant.alive and ant.score > max_score) { if (ant.alive and ant.score > max_score) {
max_score = ant.score; max_score = ant.score;
best_idx = i; best_idx = i;
} }
} }
if (max_score > 0) { if (max_score > 0) self.best_ant_idx = best_idx;
self.best_ant_idx = best_idx; }
// NUOVO: Logica movimento predatore
pub fn stepPredator(self: *World) void {
const random = self.prng.random();
// Il predatore si muove verso la formica più vicina con una certa probabilità,
// altrimenti si muove a caso (per non renderlo impossibile da battere).
var target_x = self.pred_x;
var target_y = self.pred_y;
// Trova formica più vicina
var min_dist: usize = 9999;
var has_target = false;
for (self.ants) |ant| {
if (!ant.alive) continue;
// Distanza Manhattan
const dx = if (ant.x > self.pred_x) ant.x - self.pred_x else self.pred_x - ant.x;
const dy = if (ant.y > self.pred_y) ant.y - self.pred_y else self.pred_y - ant.y;
const dist = dx + dy;
if (dist < min_dist and dist < 200) { // Insegue solo se entro 20 caselle
min_dist = dist;
target_x = ant.x;
target_y = ant.y;
has_target = true;
}
}
var move_x: isize = 0;
var move_y: isize = 0;
// 60% insegue, 40% random
if (has_target and random.float(f32) < 0.6) {
if (target_x > self.pred_x) move_x = 1 else if (target_x < self.pred_x) move_x = -1;
if (target_y > self.pred_y) move_y = 1 else if (target_y < self.pred_y) move_y = -1;
} else {
// Random walk
const r = random.intRangeAtMost(u8, 0, 3);
if (r == 0) move_y = -1;
if (r == 1) move_y = 1;
if (r == 2) move_x = -1;
if (r == 3) move_x = 1;
}
const new_px = @as(usize, @intCast(@as(isize, @intCast(self.pred_x)) + move_x));
const new_py = @as(usize, @intCast(@as(isize, @intCast(self.pred_y)) + move_y));
// Controllo muri
if (self.grid[new_py][new_px] != TILE_WALL) {
self.pred_x = new_px;
self.pred_y = new_py;
} }
} }
@ -144,11 +205,16 @@ pub const World = struct {
var ant = &self.ants[ant_idx]; var ant = &self.ants[ant_idx];
if (!ant.alive) return .{ 0.0, true }; if (!ant.alive) return .{ 0.0, true };
const random = self.prng.random(); // NUOVO: Controllo morte immediata da predatore (inizio turno)
if (ant.x == self.pred_x and ant.y == self.pred_y) {
self.ants[ant_idx] = self.spawnChild(self.best_ant_idx); // Respawn
return .{ -500.0, true }; // Morte dolorosa
}
const random = self.prng.random();
// ... (Logica energia/movimento invariata fino al loop) ...
var moves_to_make: usize = 0; var moves_to_make: usize = 0;
var chance = ant.gene_speed; var chance = ant.gene_speed;
while (chance > 0) { while (chance > 0) {
if (chance >= 1.0) { if (chance >= 1.0) {
moves_to_make += 1; moves_to_make += 1;
@ -158,7 +224,6 @@ pub const World = struct {
chance = 0; chance = 0;
} }
} }
if (moves_to_make == 0) { if (moves_to_make == 0) {
ant.energy -= 0.1; ant.energy -= 0.1;
if (ant.energy <= 0) return .{ -10.0, true }; if (ant.energy <= 0) return .{ -10.0, true };
@ -169,7 +234,6 @@ pub const World = struct {
for (0..moves_to_make) |_| { for (0..moves_to_make) |_| {
if (ant.energy <= 0) break; if (ant.energy <= 0) break;
ant.energy -= (0.2 * ant.gene_metabolism); ant.energy -= (0.2 * ant.gene_metabolism);
const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x; const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x;
@ -191,21 +255,37 @@ pub const World = struct {
ant.x = new_x; ant.x = new_x;
ant.y = new_y; ant.y = new_y;
// NUOVO: Controllo collisione predatore DOPO il movimento
if (ant.x == self.pred_x and ant.y == self.pred_y) {
self.ants[ant_idx] = self.spawnChild(self.best_ant_idx);
return .{ -500.0, true }; // Morte
}
const dist_pred_x = if (ant.x > self.pred_x) ant.x - self.pred_x else self.pred_x - ant.x;
const dist_pred_y = if (ant.y > self.pred_y) ant.y - self.pred_y else self.pred_y - ant.y;
const dist_pred = dist_pred_x + dist_pred_y;
if (dist_pred == 1) {
total_reward -= 5.0; // Troppo vicina! Rischio altissimo.
} else if (dist_pred == 2) {
total_reward -= 2.0; // Zona di allerta.
}
self.pheromones[new_y][new_x] = 1.0; self.pheromones[new_y][new_x] = 1.0;
const new_dist = if (new_x > self.food_x) new_x - self.food_x else self.food_x - new_x; 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 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); 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.5 + scent;
if (total_new_dist > old_dist) total_reward -= 1.0; if (total_new_dist > old_dist) total_reward -= 0.1;
if (new_x == self.food_x and new_y == self.food_y) { if (new_x == self.food_x and new_y == self.food_y) {
self.respawnFood(); self.respawnFood();
ant.energy = 100.0; ant.energy = 100.0;
ant.score += 1; ant.score += 1;
total_reward += 50.0; total_reward += 50.0;
self.updateBestAnt(); self.updateBestAnt();
} }
} }
@ -218,6 +298,7 @@ pub const World = struct {
return .{ total_reward, false }; return .{ total_reward, false };
} }
// evaporatePheromones invariato...
pub fn evaporatePheromones(self: *World) void { pub fn evaporatePheromones(self: *World) void {
for (0..GRID_SIZE) |y| { for (0..GRID_SIZE) |y| {
for (0..GRID_SIZE) |x| { for (0..GRID_SIZE) |x| {
@ -226,14 +307,17 @@ pub const World = struct {
} }
} }
// Modificato per includere il predatore
pub fn getAntObservation(self: *World, allocator: Allocator, ant_idx: usize) ![]f32 { pub fn getAntObservation(self: *World, allocator: Allocator, ant_idx: usize) ![]f32 {
var obs = try allocator.alloc(f32, 15); // Aumentiamo la dimensione a 20 floats
var obs = try allocator.alloc(f32, 20);
const ant = self.ants[ant_idx]; const ant = self.ants[ant_idx];
var idx: usize = 0; var idx: usize = 0;
const ax = @as(i32, @intCast(ant.x)); const ax = @as(i32, @intCast(ant.x));
const ay = @as(i32, @intCast(ant.y)); const ay = @as(i32, @intCast(ant.y));
// 3x3 Grid locale (9 inputs)
var dy: i32 = -1; var dy: i32 = -1;
while (dy <= 1) : (dy += 1) { while (dy <= 1) : (dy += 1) {
var dx: i32 = -1; var dx: i32 = -1;
@ -241,10 +325,13 @@ pub const World = struct {
const py = @as(usize, @intCast(ay + dy)); const py = @as(usize, @intCast(ay + dy));
const px = @as(usize, @intCast(ax + dx)); const px = @as(usize, @intCast(ax + dx));
var val: f32 = 0.0; var val: f32 = 0.0;
if (self.grid[py][px] == TILE_WALL) { if (self.grid[py][px] == TILE_WALL) {
val = -1.0; val = -1.0;
} else if (px == self.food_x and py == self.food_y) { } else if (px == self.food_x and py == self.food_y) {
val = 1.0; val = 1.0;
} else if (px == self.pred_x and py == self.pred_y) {
val = -5.0; // PREDATORE VICINO! PAURA!
} else if (self.isOccupied(px, py) and (dx != 0 or dy != 0)) { } else if (self.isOccupied(px, py) and (dx != 0 or dy != 0)) {
val = -0.5; val = -0.5;
} }
@ -252,12 +339,28 @@ pub const World = struct {
idx += 1; idx += 1;
} }
} }
// Input originali (9..14)
obs[9] = self.getScent(ant.x, ant.y); obs[9] = self.getScent(ant.x, ant.y);
obs[10] = self.pheromones[ant.y][ant.x]; obs[10] = self.pheromones[ant.y][ant.x];
obs[11] = if (self.food_y < ant.y) 1.0 else 0.0; obs[11] = if (self.food_y < ant.y) 1.0 else 0.0; // Cibo Su
obs[12] = if (self.food_y > ant.y) 1.0 else 0.0; obs[12] = if (self.food_y > ant.y) 1.0 else 0.0; // Cibo Giù
obs[13] = if (self.food_x < ant.x) 1.0 else 0.0; obs[13] = if (self.food_x < ant.x) 1.0 else 0.0; // Cibo SX
obs[14] = if (self.food_x > ant.x) 1.0 else 0.0; obs[14] = if (self.food_x > ant.x) 1.0 else 0.0; // Cibo DX
// NUOVI INPUT: Sensori Predatore (15..19)
// 15: Prossimità predatore (1.0 = vicinissimo, 0.0 = lontano)
const pdx = if (ant.x > self.pred_x) ant.x - self.pred_x else self.pred_x - ant.x;
const pdy = if (ant.y > self.pred_y) ant.y - self.pred_y else self.pred_y - ant.y;
const dist_pred = @as(f32, @floatFromInt(pdx + pdy));
obs[15] = if (dist_pred == 0) 1.0 else 1.0 / dist_pred;
// 16-19: Direzione Predatore
obs[16] = if (self.pred_y < ant.y) 1.0 else 0.0; // Predatore è SOPRA
obs[17] = if (self.pred_y > ant.y) 1.0 else 0.0; // Predatore è SOTTO
obs[18] = if (self.pred_x < ant.x) 1.0 else 0.0; // Predatore è SINISTRA
obs[19] = if (self.pred_x > ant.x) 1.0 else 0.0; // Predatore è DESTRA
return obs; return obs;
} }
}; };

View file

@ -3,12 +3,41 @@ const World = @import("env.zig").World;
const env = @import("env.zig"); const env = @import("env.zig");
const Network = @import("modular_network.zig").Network; const Network = @import("modular_network.zig").Network;
const GAMMA: f32 = 0.9; // --- PARAMETRI DI TRAINING ---
const LR: f32 = 0.005; const GAMMA: f32 = 0.98;
const LR: f32 = 0.001;
const EPSILON_START: f32 = 1.0; const EPSILON_START: f32 = 1.0;
const EPSILON_END: f32 = 0.05; const EPSILON_END: f32 = 0.05;
const DECAY_RATE: f32 = 0.0001; const DECAY_RATE: f32 = 0.0001;
const SAVE_INTERVAL: usize = 500;
// Export JSON
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();
var buffer: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
var ants_json = std.ArrayList(u8){};
defer ants_json.deinit(allocator);
try ants_json.appendSlice(allocator, "[");
var first = true;
for (world.ants) |ant| {
if (!ant.alive) continue; // Esporta solo le formiche vive
if (!first) try ants_json.appendSlice(allocator, ",");
try std.fmt.format(ants_json.writer(allocator), "[{d},{d},{d:.2}]", .{ ant.x, ant.y, ant.gene_speed });
first = false;
}
try ants_json.appendSlice(allocator, "]");
const json = try std.fmt.allocPrint(allocator, "{{\n \"grid_size\": {d},\n \"food\": [{d}, {d}],\n \"predator\": [{d}, {d}],\n \"ants\": {s},\n \"episode\": {d},\n \"epsilon\": {d:.3}\n}}", .{ env.GRID_SIZE, world.food_x, world.food_y, world.pred_x, world.pred_y, ants_json.items, episode, avg_epsilon });
try file.writeAll(json);
}
// Funzioni helper
fn maxVal(slice: []const f32) f32 { fn maxVal(slice: []const f32) f32 {
var m: f32 = -1.0e20; var m: f32 = -1.0e20;
for (slice) |v| if (v > m) { for (slice) |v| if (v > m) {
@ -29,97 +58,166 @@ fn argmax(slice: []const f32) usize {
return idx; return idx;
} }
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();
var buffer: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
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},{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, avg_epsilon });
try file.writeAll(json);
}
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
defer _ = gpa.deinit(); defer _ = gpa.deinit();
var world = World.init(12345); // ==========================================
var net = Network.init(allocator); // 🎛 PANNELLO DI CONTROLLO PRINCIPALE
defer net.deinit(); // ==========================================
const INFERENCE_MODE = true; // TRUE = Duello 1vs1. FALSE = Training massivo.
const LOAD_EXISTING = false; // (Valido solo se INFERENCE_MODE=false)
// ==========================================
try net.addLayer(15, 40, 111, true); var world = World.init(12345);
try net.addLayer(40, 4, 222, false); var net: Network = undefined;
if (INFERENCE_MODE) {
std.debug.print(">> CARICAMENTO CERVELLO PER ARENA 1vs1...\n", .{});
net = Network.load(allocator, "best_brain.bin") catch |err| {
std.debug.print("ERRORE: Non trovo 'best_brain.bin'. Fai prima il training!\nErrore: {}\n", .{err});
return;
};
} else if (LOAD_EXISTING) {
std.debug.print(">> RIPRESA TRAINING: Caricamento 'best_brain.bin'...\n", .{});
net = try Network.load(allocator, "best_brain.bin");
} else {
std.debug.print(">> NUOVO TRAINING: Creazione rete neurale...\n", .{});
net = Network.init(allocator);
try net.addLayer(20, 45, 111, true);
try net.addLayer(45, 4, 222, false);
}
defer net.deinit();
var prng = std.Random.DefaultPrng.init(999); var prng = std.Random.DefaultPrng.init(999);
const random = prng.random(); const random = prng.random();
std.debug.print("--- HIVE MIND TRAINING START ---\n", .{});
std.debug.print("Mappa: {d}x{d} | Formiche: {d}\n", .{ env.GRID_SIZE, env.GRID_SIZE, env.NUM_ANTS });
var global_step: usize = 0; var global_step: usize = 0;
var epsilon: f32 = EPSILON_START;
while (true) { // ==========================================
world.evaporatePheromones(); // MODALITÀ: INFERENZA (DUELLO 1 VS 1)
// ==========================================
if (INFERENCE_MODE) {
std.debug.print("--- ARENA 1 VS 1: INIZIO DUELLO ---\n", .{});
for (0..env.NUM_ANTS) |i| { const margin = 5;
const current_obs = try world.getAntObservation(allocator, i); world.ants[0].energy = 500.0;
world.ants[0].x = margin;
world.ants[0].y = margin;
world.pred_x = env.GRID_SIZE - margin;
world.pred_y = env.GRID_SIZE - margin;
var score: usize = 0;
while (world.ants[0].alive) {
// FIX 1: Stessa velocità del training!
if (global_step % 2 == 0) world.stepPredator();
const current_obs = try world.getAntObservation(allocator, 0);
defer allocator.free(current_obs); defer allocator.free(current_obs);
var action: usize = 0;
const q_values = net.forward(current_obs); const q_values = net.forward(current_obs);
var action: usize = 0;
if (random.float(f32) < epsilon) { // FIX 3: Lasciamo un 2% di "istinto" (epsilon) per non farla incantare sui muri
if (random.float(f32) < 0.02) {
action = random.intRangeAtMost(usize, 0, 3); action = random.intRangeAtMost(usize, 0, 3);
} else { } else {
action = argmax(q_values); action = argmax(q_values);
} }
const result = world.stepAnt(i, action); const result = world.stepAnt(0, action);
const reward = result[0]; const reward = result[0];
var target_val = reward; if (reward > 10.0) {
score += 1;
const next_obs = try world.getAntObservation(allocator, i); std.debug.print("SCORE: {d} | Cibo preso!\n", .{score});
defer allocator.free(next_obs); world.ants[0].energy = 500.0;
const next_q_values = net.forward(next_obs);
target_val += GAMMA * maxVal(next_q_values);
var target_vector = try allocator.alloc(f32, 4);
defer allocator.free(target_vector);
for (0..4) |j| target_vector[j] = q_values[j];
target_vector[action] = target_val;
_ = try net.train(current_obs, target_vector, LR);
}
global_step += 1;
if (epsilon > EPSILON_END) {
epsilon -= DECAY_RATE;
}
if (global_step % 10 == 0) {
try exportAntJSON(&world, "ant_state.json", global_step, epsilon);
if (global_step % 100 == 0) {
std.debug.print("Step: {d} | Epsilon: {d:.3} | Cibo: [{d},{d}]\r", .{ global_step, epsilon, world.food_x, world.food_y });
} }
std.Thread.sleep(100 * 1_000_000); try exportAntJSON(&world, "ant_state.json", global_step, 0.02);
global_step += 1;
std.Thread.sleep(50 * 1_000_000);
if (!world.ants[0].alive) {
std.debug.print("\n>>> GAME OVER! La formica e' durata {d} step e ha mangiato {d} cibi. <<<\n", .{ global_step, score });
break;
}
}
}
// ==========================================
// MODALITÀ: TRAINING (SCIAME)
// ==========================================
else {
std.debug.print("--- HIVE MIND: INIZIO TRAINING ---\n", .{});
var epsilon: f32 = EPSILON_START;
var record_score: usize = 0;
while (true) {
world.evaporatePheromones();
if (global_step % 2 == 0) world.stepPredator();
for (0..env.NUM_ANTS) |i| {
if (!world.ants[i].alive) continue;
const current_obs = try world.getAntObservation(allocator, i);
defer allocator.free(current_obs);
var action: usize = 0;
const q_values = net.forward(current_obs);
if (random.float(f32) < epsilon) {
action = random.intRangeAtMost(usize, 0, 3);
} else {
action = argmax(q_values);
}
const result = world.stepAnt(i, action);
const reward = result[0];
const died = result[1] and reward <= -50.0;
var target_val = reward;
if (!died) {
const next_obs = try world.getAntObservation(allocator, i);
defer allocator.free(next_obs);
const next_q_values = net.forward(next_obs);
target_val += GAMMA * maxVal(next_q_values);
}
var target_vector = try allocator.alloc(f32, 4);
defer allocator.free(target_vector);
for (0..4) |j| target_vector[j] = q_values[j];
target_vector[action] = target_val;
_ = try net.train(current_obs, target_vector, LR);
}
global_step += 1;
if (epsilon > EPSILON_END) epsilon -= DECAY_RATE;
if (global_step % SAVE_INTERVAL == 0) {
var current_total_score: usize = 0;
for (world.ants) |ant| {
if (ant.alive) {
current_total_score += ant.score;
}
}
if (current_total_score > record_score) {
record_score = current_total_score;
try net.save("best_brain.bin");
}
}
if (global_step % 10 == 0) {
try exportAntJSON(&world, "ant_state.json", global_step, epsilon);
if (global_step % 100 == 0) {
std.debug.print("Step: {d} | Eps: {d:.3} | Record: {d} | Pred: [{d},{d}]\r", .{ global_step, epsilon, record_score, world.pred_x, world.pred_y });
}
std.Thread.sleep(20 * 1_000_000); // Veloce per il training
}
} }
} }
} }