'use strict'; // ══════════════════════════════════════════════ // NN ARCHITECTURE — change these to reconfigure // ══════════════════════════════════════════════ const INPUT_SIZE = 10; // inputs const HIDDEN_LAYER_1_SIZE = 64; const HIDDEN_LAYER_2_SIZE = 16; const OUTPUT_SIZE = 7; const LEARNING_RATE = 0.012; const TRAINING_STEPS_PER_FRAME = 1; const OUTPUT_LABELS = ['ROT←','ROT→','FWD','BACK','STR←','STR→','FIRE']; const INPUT_LABELS = ['dx','dy','dist','sinA','cosA','LOS','rx','ry','rd','ehp']; // ══════════════════════════════════════════════ // NEURAL NETWORK — 3-layer (INPUT → HIDDEN1 → HIDDEN2 → OUTPUT) // ══════════════════════════════════════════════ const createRandomFloat32Array = (length, scale) => { const buffer = new Float32Array(length); for (let i = 0; i < length; i++) buffer[i] = (Math.random() * 2 - 1) * scale; return buffer; }; const sig = x => 1/(1+Math.exp(-Math.max(-15,Math.min(15,x)))); const nn = { weightsInputToHidden1:createRandomFloat32Array(HIDDEN_LAYER_1_SIZE*INPUT_SIZE,0.3), biasesHidden1:new Float32Array(HIDDEN_LAYER_1_SIZE), weightsHidden1ToHidden2:createRandomFloat32Array(HIDDEN_LAYER_2_SIZE*HIDDEN_LAYER_1_SIZE,0.3), biasesHidden2:new Float32Array(HIDDEN_LAYER_2_SIZE), weightsHidden2ToOutput:createRandomFloat32Array(OUTPUT_SIZE*HIDDEN_LAYER_2_SIZE,0.25), biasesOutput:new Float32Array(OUTPUT_SIZE), momentW1:new Float32Array(HIDDEN_LAYER_1_SIZE*INPUT_SIZE), momentVW1:new Float32Array(HIDDEN_LAYER_1_SIZE*INPUT_SIZE), momentB1:new Float32Array(HIDDEN_LAYER_1_SIZE), momentVB1:new Float32Array(HIDDEN_LAYER_1_SIZE), momentW2:new Float32Array(HIDDEN_LAYER_2_SIZE*HIDDEN_LAYER_1_SIZE), momentVW2:new Float32Array(HIDDEN_LAYER_2_SIZE*HIDDEN_LAYER_1_SIZE), momentB2:new Float32Array(HIDDEN_LAYER_2_SIZE), momentVB2:new Float32Array(HIDDEN_LAYER_2_SIZE), momentW3:new Float32Array(OUTPUT_SIZE*HIDDEN_LAYER_2_SIZE), momentVW3:new Float32Array(OUTPUT_SIZE*HIDDEN_LAYER_2_SIZE), momentB3:new Float32Array(OUTPUT_SIZE), momentVB3:new Float32Array(OUTPUT_SIZE), linearHidden1:new Float32Array(HIDDEN_LAYER_1_SIZE), activationHidden1:new Float32Array(HIDDEN_LAYER_1_SIZE), linearHidden2:new Float32Array(HIDDEN_LAYER_2_SIZE), activationHidden2:new Float32Array(HIDDEN_LAYER_2_SIZE), outputActivations:new Float32Array(OUTPUT_SIZE), step:1, }; function nnForward(inp) { for (let h=0; h 0 ? sum : 0; } for (let h=0; h 0 ? sum : 0; } for (let o=0; o 0 ? deltaHidden2[h] : 0; gradB2[h] += dz; for (let i=0; i 0 ? deltaHidden1[h] : 0; gradB1[h] += dz; for (let i=0; i 0; } function dodgeDir(p) { return Math.cos(p.a)*(en.y-p.y) - Math.sin(p.a)*(en.x-p.x) > 0 ? 1 : -1; } function getInput() { const dx=(pl.x-en.x)/MAX_D, dy=(pl.y-en.y)/MAX_D; const dist=Math.hypot(dx,dy); const los=hasLOS(en.x,en.y,pl.x,pl.y)?1:0; const nr=nearestRocket(); const rx=nr?(nr.x-en.x)/MAX_D:0; const ry=nr?(nr.y-en.y)/MAX_D:0; const rd=nr?Math.min(1,nr.dist/MAX_D):1; return [dx,dy,dist,Math.sin(en.a),Math.cos(en.a),los,rx,ry,rd,en.hp/en.maxHp]; } function getIdeal() { const dx=pl.x-en.x, dy=pl.y-en.y; const dist=Math.hypot(dx,dy); const los=hasLOS(en.x,en.y,pl.x,pl.y); const targetOutputs=new Float32Array(OUTPUT_SIZE); const nr=nearestRocket(); // Use pathfinding when no LOS to navigate around walls let navDx = dx, navDy = dy; if (!los) { const nav = pathDir(en.x, en.y, pl.x, pl.y); navDx = nav.dx; navDy = nav.dy; } let da=Math.atan2(navDy,navDx)-en.a; while(da> Math.PI) da-=2*Math.PI; while(da<-Math.PI) da+=2*Math.PI; if (nr && nr.dist<10 && isApproaching(nr)) { // Dodge incoming rocket const dd=dodgeDir(nr); targetOutputs[dd>0?5:4]=1.0; // strafe right or left targetOutputs[3]=0.6; // back away if (da> 0.15) targetOutputs[1]=0.45; else if (da<-0.15) targetOutputs[0]=0.45; } else { // Hunt player if (da> 0.1) targetOutputs[1]=1; else if (da<-0.1) targetOutputs[0]=1; if (dist>2.5) targetOutputs[2]=1; else if (dist<1.2) targetOutputs[3]=1; // Strafe while approaching for harder-to-hit movement if (dist>2.5 && dist<8) { // Alternate strafe direction based on steps targetOutputs[(steps>>5)&1 ? 4 : 5] = 0.5; } // Fire on LOS only if (los && dist<11 && Math.abs(da)<0.5) targetOutputs[6]=1; } return targetOutputs; }