Created
June 3, 2026 08:25
-
-
Save tylearymf/8c112d1de01c5750f6c086c5ff679e59 to your computer and use it in GitHub Desktop.
agentank-squid-tank
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var F_MEM = typeof F_MEM === "undefined" ? { self: [], enemy: [], bullets: [] } : F_MEM; | |
| var F_DIRS = ["up", "right", "down", "left"]; | |
| var F_VEC = { up: [0, -1], right: [1, 0], down: [0, 1], left: [-1, 0] }; | |
| function onIdle(me, enemy, game) { | |
| var w = F_world(me, enemy, game); | |
| F_remember(w); | |
| var lineEscape = F_lineShotEscapeAction(w); | |
| if (lineEscape) { | |
| F_act(me, lineEscape, w); | |
| return; | |
| } | |
| var starTrap = F_starLineTrapAction(w); | |
| if (starTrap) { | |
| F_act(me, starTrap, w); | |
| return; | |
| } | |
| var pocketBreak = F_leftPocketBreakAction(w); | |
| if (pocketBreak) { | |
| F_act(me, pocketBreak, w); | |
| return; | |
| } | |
| var map15Return = F_map15ReturnAction(w); | |
| if (map15Return) { | |
| F_act(me, map15Return, w); | |
| return; | |
| } | |
| var stuckEscape = F_stuckFaceEscapeAction(w); | |
| if (stuckEscape) { | |
| F_act(me, stuckEscape, w); | |
| return; | |
| } | |
| var tailEscape = F_tailEscapeAction(w); | |
| if (tailEscape) { | |
| F_act(me, tailEscape, w); | |
| return; | |
| } | |
| var cornerKill = F_cornerKillAction(w); | |
| if (cornerKill) { | |
| F_act(me, cornerKill, w); | |
| return; | |
| } | |
| var escape = F_threeStarEscape(w); | |
| if (escape) { | |
| F_act(me, escape, w); | |
| return; | |
| } | |
| var forecast = F_forecast(w, 7); | |
| var plans = F_plans(w, forecast); | |
| var best = F_pick(w, forecast, plans); | |
| F_setTailEscape(w, best.actions[0]); | |
| F_act(me, best.actions[0], w); | |
| } | |
| function F_world(me, enemy, game) { | |
| return { | |
| map: game.map, | |
| w: game.map ? game.map.length : 0, | |
| h: game.map && game.map[0] ? game.map[0].length : 0, | |
| frame: game.frames || 0, | |
| star: game.star ? F_cp(game.star) : null, | |
| me: { | |
| p: F_cp(me.tank.position), | |
| d: me.tank.direction, | |
| stars: me.stars || 0, | |
| bullet: F_bullet(me.bullet), | |
| skill: me.skill || {}, | |
| status: me.status || {}, | |
| }, | |
| enemy: { | |
| tank: enemy && enemy.tank ? { p: F_cp(enemy.tank.position), d: enemy.tank.direction } : null, | |
| bullet: enemy ? F_bullet(enemy.bullet) : null, | |
| skill: enemy && enemy.skill ? enemy.skill : {}, | |
| status: enemy && enemy.status ? enemy.status : {}, | |
| }, | |
| cache: {}, | |
| }; | |
| } | |
| function F_remember(w) { | |
| F_push(F_MEM.self, { f: w.frame, p: F_cp(w.me.p), d: w.me.d }, 10); | |
| if (w.enemy.tank) F_push(F_MEM.enemy, { f: w.frame, p: F_cp(w.enemy.tank.p), d: w.enemy.tank.d }, 10); | |
| F_trackBullets(w); | |
| } | |
| function F_trackBullets(w) { | |
| if (F_MEM.bulletFrame != null && w.frame < F_MEM.bulletFrame) F_MEM.bullets = []; | |
| if (w.enemy.bullet && w.enemy.bullet.d && F_VEC[w.enemy.bullet.d]) { | |
| var b = { p: F_cp(w.enemy.bullet.p), d: w.enemy.bullet.d, f: w.frame }; | |
| F_MEM.bullets = [b]; | |
| var sk = w.enemy.skill || {}, cd = Number(sk.cooldownFrames) || 0, rem = Number(sk.remainingCooldownFrames) || 0; | |
| var inferOver = sk.type === "overload" && cd > 0 && cd - rem <= 4; | |
| if (!inferOver && !w.star && w.enemy.tank) inferOver = (b.d === "up" || b.d === "down") ? Math.abs(w.enemy.tank.p[0] - b.p[0]) === 1 : Math.abs(w.enemy.tank.p[1] - b.p[1]) === 1; | |
| if (inferOver) { | |
| var p = F_cp(b.p); | |
| if (b.d === "up" || b.d === "down") p[0] += w.enemy.tank && w.enemy.tank.p[0] !== p[0] ? -1 : 1; | |
| else p[1] += w.enemy.tank && w.enemy.tank.p[1] !== p[1] ? -1 : 1; | |
| if (F_inside(w, p)) F_MEM.bullets.push({ p: p, d: b.d, f: w.frame }); | |
| } | |
| F_MEM.bulletFrame = w.frame; | |
| return; | |
| } | |
| var next = []; | |
| for (var i = 0; i < (F_MEM.bullets || []).length; i++) { | |
| var b = F_advanceBullet(w, F_MEM.bullets[i], w.frame); | |
| if (b) next.push(b); | |
| } | |
| F_MEM.bullets = next; | |
| F_MEM.bulletFrame = w.frame; | |
| } | |
| function F_advanceBullet(w, b, frame) { | |
| if (!b || !b.p || !F_VEC[b.d]) return null; | |
| var p = F_cp(b.p), v = F_VEC[b.d], frames = Math.max(0, frame - (b.f || frame)); | |
| for (var t = 0; t < frames; t++) { | |
| for (var s = 0; s < 2; s++) { | |
| p = F_add(p, v); | |
| if (!F_inside(w, p) || F_blocksBullet(w, p)) return null; | |
| } | |
| } | |
| return { p: p, d: b.d, f: frame }; | |
| } | |
| function F_push(a, v, n) { | |
| a.push(v); | |
| while (a.length > n) a.shift(); | |
| } | |
| function F_bullet(b) { | |
| return b ? { p: F_cp(b.position), d: b.direction } : null; | |
| } | |
| function F_plans(w, forecast) { | |
| var atoms = F_atoms(w, forecast); | |
| var depth = 2; | |
| var out = [{ name: "wait", actions: [] }]; | |
| F_extend(out, [], atoms, depth); | |
| F_addRoutePlans(w, out); | |
| return out; | |
| } | |
| function F_atoms(w, forecast) { | |
| var a = [{ type: "go" }, { type: "turn", side: "left" }, { type: "turn", side: "right" }, { type: "wait" }]; | |
| if (!w.me.bullet && !w.me.status.fireLocked) a.push({ type: "fire" }); | |
| var sk = w.me.skill || {}; | |
| if (sk.type && sk.type !== "none" && sk.remainingCooldownFrames === 0) { | |
| if (sk.type === "shield") a.push({ type: "skill", skill: "shield" }); | |
| else if (sk.type === "boost") a.push({ type: "skill", skill: "boost" }); | |
| else if (sk.type === "overload") a.push({ type: "skill", skill: "overload" }); | |
| else if (w.enemy.tank) a.push({ type: "skill", skill: sk.type }); | |
| } | |
| if (sk.type === "teleport" && sk.remainingCooldownFrames === 0) { | |
| var ts = F_teleports(w); | |
| for (var i = 0; i < ts.length; i++) a.push({ type: "teleport", x: ts[i][0], y: ts[i][1] }); | |
| } | |
| return a; | |
| } | |
| function F_extend(out, prefix, atoms, depth) { | |
| if (prefix.length >= depth) return; | |
| for (var i = 0; i < atoms.length; i++) { | |
| var next = prefix.concat([atoms[i]]); | |
| out.push({ name: "", actions: next }); | |
| if (atoms[i].type !== "fire" && atoms[i].type !== "teleport") F_extend(out, next, atoms, depth); | |
| } | |
| } | |
| function F_addRoutePlans(w, out) { | |
| if (!w.star) return; | |
| var route = F_routeStep(w, w.me.p, w.star); | |
| if (!route) return; | |
| if (route.type === "go") out.push({ name: "route_go_go", actions: [{ type: "go" }, { type: "go" }] }); | |
| else out.push({ name: "route_turn_go", actions: [route, { type: "go" }] }); | |
| } | |
| function F_pick(w, forecast, plans) { | |
| var best = plans[0], bestScore = -1e15; | |
| for (var i = 0; i < plans.length; i++) { | |
| var score = F_score(w, forecast, plans[i]); | |
| if (score > bestScore) { | |
| best = plans[i]; | |
| bestScore = score; | |
| } | |
| } | |
| return best; | |
| } | |
| function F_score(w, forecast, plan) { | |
| var sim = F_sim(w, plan, forecast.h); | |
| if (!sim.ok) return -1e9; | |
| var first = plan.actions[0] || { type: "wait" }; | |
| var firstSkill = first.type === "skill" ? first.skill : null; | |
| var killAt = F_killFrame(w, plan, sim, firstSkill); | |
| var score = 0; | |
| for (var t = 0; t < forecast.h; t++) { | |
| var th = F_danger(forecast, t, sim.pos[t]); | |
| var v = th.v; | |
| if (th.k === "ghost" && w.star && F_same(sim.pos[t], w.star)) v = 0; | |
| if (killAt != null && th.k === "fire" && t > killAt) v = 0; | |
| if (firstSkill === "shield" && t <= 1) v = Math.max(0, v - 1000); | |
| if (v >= 900) score -= 250000 - t * 30000; | |
| else if (v >= 700) score -= 90000 - t * 12000; | |
| else score -= v; | |
| } | |
| score += F_starScore(w, forecast, plan, sim, firstSkill); | |
| score += F_attackScore(w, plan, sim, firstSkill); | |
| score += F_skillScore(w, forecast, plan, sim, firstSkill); | |
| score += F_loopScore(w, sim, first); | |
| score += F_contactScore(w, plan, sim); | |
| if (first.type === "wait") score -= 80; | |
| return score; | |
| } | |
| function F_starScore(w, forecast, plan, sim, firstSkill) { | |
| if (!w.star) return 0; | |
| var stuck = F_stuck(w); | |
| var final = sim.pos[sim.pos.length - 1]; | |
| var base = F_dist(w, w.me.p, w.star); | |
| var fd = F_dist(w, final, w.star); | |
| var score = 0; | |
| if (F_same(final, w.star)) score += 130000; | |
| if (base < 99 && fd < 99) score += (base - fd) * 9000 + Math.max(0, 20 - fd) * 700; | |
| if (final[0] === w.star[0] || final[1] === w.star[1]) { | |
| if (!F_lineBlock(w, final, w.star)) score += 2500; | |
| } | |
| if (plan.actions[0] && plan.actions[0].type === "fire" && F_fireBreaksStarMound(w)) score += 12000; | |
| if (F_enemyMayWinStar(w) && !F_same(final, w.star)) score -= 2500; | |
| if (stuck) score -= 18000; | |
| return score; | |
| } | |
| function F_attackScore(w, plan, sim, firstSkill) { | |
| if (!w.enemy.tank) return 0; | |
| var score = 0; | |
| var fireAt = F_fireAt(plan); | |
| var over = firstSkill === "overload" || (w.me.status && w.me.status.overloaded); | |
| if (fireAt != null) { | |
| var pos = sim.pos[Math.min(fireAt, sim.pos.length - 1)]; | |
| var dir = sim.dir[Math.min(fireAt, sim.dir.length - 1)]; | |
| if (F_shotHits(w, pos, dir, w.enemy.tank.p, over)) { | |
| score += 110000 - fireAt * 12000; | |
| if (fireAt === 0 && F_man(pos, w.enemy.tank.p) <= 2) score += 230000; | |
| } | |
| else if (F_fireHitsMound(w, pos, dir) || (over && F_fireHitsMound(w, dir === "up" || dir === "down" ? [pos[0] + 1, pos[1]] : [pos[0], pos[1] + 1], dir))) { | |
| if (w.star && !F_map15Shape(w) && F_dist(w, w.me.p, w.star) < 99 && !F_fireHitsStarMound(w, pos, dir, over)) score -= 2200; | |
| else score += over ? 22000 : (w.star ? 3500 : 7000); | |
| } | |
| else score -= 1800; | |
| } else { | |
| var final = sim.pos[sim.pos.length - 1]; | |
| score += (F_man(w.me.p, w.enemy.tank.p) - F_man(final, w.enemy.tank.p)) * (F_stuck(w) ? 1800 : 220); | |
| if (F_shotHits(w, final, sim.dir[sim.dir.length - 1], w.enemy.tank.p, false)) score += 2200; | |
| } | |
| if (F_stuck(w) && plan.actions[0] && plan.actions[0].type === "fire" && F_fireHitsMound(w, w.me.p, w.me.d)) score += 15000; | |
| return score; | |
| } | |
| function F_skillScore(w, forecast, plan, sim, sk) { | |
| if (!sk) return 0; | |
| var now = F_danger(forecast, 0, w.me.p).v; | |
| if (sk === "shield" && now >= 900) return 140000; | |
| if (sk === "boost" && w.star) return 4000; | |
| if (sk === "overload" && F_fireAt(plan) != null) return 6000; | |
| return w.enemy.tank ? 2500 : -200; | |
| } | |
| function F_loopScore(w, sim, first) { | |
| var score = 0; | |
| var p = sim.pos[sim.pos.length - 1]; | |
| for (var i = 0; i < F_MEM.self.length; i++) { | |
| if (F_same(p, F_MEM.self[i].p)) score -= 450 * (i + 1); | |
| } | |
| if (first.type === "turn" && F_MEM.self.length >= 2) { | |
| var a = F_MEM.self[F_MEM.self.length - 1], b = F_MEM.self[F_MEM.self.length - 2]; | |
| if (F_same(a.p, b.p) && F_same(a.p, w.me.p) && a.d !== b.d) score -= 3000; | |
| } | |
| return score; | |
| } | |
| function F_contactScore(w, plan, sim) { | |
| if (!w.enemy.tank || F_fireAt(plan) != null) return 0; | |
| var score = 0, starMode = !!w.star; | |
| for (var t = 0; t < sim.pos.length; t++) { | |
| var p = sim.pos[t], m = F_man(p, w.enemy.tank.p); | |
| var line = (p[0] === w.enemy.tank.p[0] || p[1] === w.enemy.tank.p[1]) && !F_lineBlock(w, p, w.enemy.tank.p); | |
| if (starMode) { | |
| if (m <= 1) score -= 65000 - t * 7000; | |
| continue; | |
| } | |
| if (m <= 1) score -= 65000 - t * 7000; | |
| else if (m === 2) score -= 18000 - t * 2500; | |
| if (line && m <= 4) score -= 12000 - t * 1500; | |
| } | |
| return score; | |
| } | |
| function F_stuck(w) { | |
| if (!F_MEM.self || F_MEM.self.length < 8) return false; | |
| var seen = {}; | |
| for (var i = Math.max(0, F_MEM.self.length - 8); i < F_MEM.self.length; i++) { | |
| seen[F_MEM.self[i].p[0] + "," + F_MEM.self[i].p[1]] = true; | |
| } | |
| var n = 0; | |
| for (var k in seen) if (Object.prototype.hasOwnProperty.call(seen, k)) n++; | |
| return n <= 4; | |
| } | |
| function F_lineShotEscapeAction(w) { | |
| if (!w.enemy.tank) return null; | |
| if (F_map15Shape(w) && F_same(w.star, [12, 1]) && F_same(w.me.p, [12, 2])) return w.me.d === "up" ? { type: "go" } : { type: "turn", side: F_sideTo(w.me.d, "up") }; | |
| if (F_map15Shape(w) && w.star && w.star[1] >= 6 && F_same(w.me.p, [12, 2])) { | |
| if (w.enemy.tank.p[0] === 12 && F_tile(w, [12, 7]) !== "m") return (w.me.bullet || (w.me.status && w.me.status.fireLocked)) ? { type: "wait" } : (w.me.d === "down" ? { type: "fire" } : { type: "turn", side: F_sideTo(w.me.d, "down") }); | |
| return w.me.d === "down" ? { type: "go" } : { type: "turn", side: F_sideTo(w.me.d, "down") }; | |
| } | |
| if (F_map15Shape(w) && w.star && w.star[1] >= 6 && F_recentStill(w, 5) && F_same(w.me.p, [13, 2]) && w.me.d === "left") return { type: "go" }; | |
| var hitPos = w.me.p, future = false, s = w.enemy.skill || {}, cd = Number(s.cooldownFrames) || 0, rem = Number(s.remainingCooldownFrames) || 0, eo = !F_map15Shape(w) && s.type === "overload" && cd > 0 && cd - rem > 1 && cd - rem <= 4; | |
| if (!F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, hitPos, eo)) { | |
| hitPos = F_add(w.me.p, F_VEC[w.me.d]); | |
| future = true; | |
| if (!F_stand(w, hitPos) || F_enemyHasBullet(w) || !F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, hitPos, eo)) return null; | |
| } | |
| if (F_man(hitPos, w.enemy.tank.p) > (eo ? 8 : 5)) return null; | |
| if (w.enemy.status && w.enemy.status.fireLocked && !F_enemyHasBullet(w)) return null; | |
| if (!future && !w.me.bullet && !(w.me.status && w.me.status.fireLocked) && F_man(w.me.p, w.enemy.tank.p) <= 2 && F_shotHits(w, w.me.p, w.me.d, w.enemy.tank.p, false)) return null; | |
| var fwd = F_add(w.me.p, F_VEC[w.me.d]); if (!future && F_stand(w, fwd) && !F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, fwd, eo)) return { type: "go" }; | |
| var best = null, bestBad = 9; | |
| for (var i = 0; i < 2; i++) { | |
| var side = i ? "right" : "left"; | |
| var d = F_turn(w.me.d, side), n = F_add(w.me.p, F_VEC[d]); | |
| var bad = F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, n, eo) ? 3 : 0; | |
| if (!F_stand(w, n)) bad += 1; | |
| if (bad < bestBad) { | |
| bestBad = bad; | |
| best = side; | |
| } | |
| } | |
| return best ? { type: "turn", side: best } : null; | |
| } | |
| function F_starLineTrapAction(w) { | |
| if (!w.star || !w.enemy.tank || F_enemyHasBullet(w) || (w.enemy.status && w.enemy.status.fireLocked)) return null; | |
| var n = F_add(w.me.p, F_VEC[w.me.d]); | |
| if (!F_same(n, w.star) || F_man(w.enemy.tank.p, w.star) > 5 || F_lineBlock(w, w.enemy.tank.p, w.star)) return null; | |
| for (var i = 0; i < 2; i++) { | |
| var side = i ? "right" : "left", p = F_add(w.me.p, F_VEC[F_turn(w.me.d, side)]); | |
| if (F_stand(w, p) && !F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, p, false)) return { type: "turn", side: side }; | |
| } | |
| return { type: "wait" }; | |
| } | |
| function F_leftPocketBreakAction(w) { | |
| if (!w.star || w.star[1] < 6 || w.me.bullet || (w.me.status && w.me.status.fireLocked)) return null; | |
| if (F_map15Shape(w) && w.star[0] === 4 && w.star[1] >= 8 && F_same(w.me.p, [2, 6]) && (!w.enemy.tank || F_man(w.me.p, w.enemy.tank.p) > 6) && F_tile(w, [4, 6]) === "m") return w.me.d === "right" ? { type: "fire" } : { type: "turn", side: F_sideTo(w.me.d, "right") }; | |
| if (F_map15Shape(w) && w.star[0] <= 5 && w.star[1] >= 8 && F_same(w.me.p, [3, 6]) && F_tile(w, [4, 6]) === "m") return w.me.d === "right" ? { type: "fire" } : { type: "turn", side: F_sideTo(w.me.d, "right") }; | |
| if (w.star[0] > 4) return null; | |
| if (w.star[0] > 3) return null; | |
| if (!F_same(w.me.p, [4, 3]) || F_tile(w, [3, 3]) !== "m") return null; | |
| if (w.me.d === "left") return { type: "fire" }; | |
| return { type: "turn", side: F_turn(w.me.d, "left") === "left" ? "left" : "right" }; | |
| } | |
| function F_map15ReturnAction(w) { | |
| if (!F_map15Shape(w) || !w.star || !F_same(w.star, [3, 4]) || F_tile(w, [3, 3]) !== "m") { | |
| F_MEM.map15ReturnUntil = null; | |
| return null; | |
| } | |
| if (w.enemy.tank && F_shotHits(w, w.enemy.tank.p, w.enemy.tank.d, w.me.p, false)) return null; | |
| if (F_same(w.me.p, [12, 2]) && F_recentStill(w, 3)) F_MEM.map15ReturnUntil = w.frame + 24; | |
| if (F_MEM.map15ReturnUntil == null || w.frame > F_MEM.map15ReturnUntil) return null; | |
| if (w.me.p[1] === 2 && w.me.p[0] > 3) { | |
| if (w.me.d === "left") return { type: "go" }; | |
| return { type: "turn", side: F_sideTo(w.me.d, "left") }; | |
| } | |
| if (F_same(w.me.p, [3, 2])) { | |
| if (w.me.d === "down" && !w.me.bullet && !(w.me.status && w.me.status.fireLocked)) return { type: "fire" }; | |
| return { type: "turn", side: F_sideTo(w.me.d, "down") }; | |
| } | |
| var step = F_routeStep(w, w.me.p, [3, 2]); | |
| return step; | |
| } | |
| function F_map15Shape(w) { | |
| return w.w === 15 && w.h === 11 && F_tile(w, [1, 3]) === "m" && F_tile(w, [3, 6]) === "o" && F_tile(w, [4, 4]) === "x" && F_tile(w, [5, 5]) === "x"; | |
| } | |
| function F_stuckFaceEscapeAction(w) { | |
| if (!w.star || !F_recentStill(w, 3)) return null; | |
| if (!w.enemy.tank || F_man(w.me.p, w.enemy.tank.p) > 2) return null; | |
| var best = null, bestV = 1e9; | |
| for (var i = 0; i < 2; i++) { | |
| var side = i ? "right" : "left"; | |
| var d = F_turn(w.me.d, side), n = F_add(w.me.p, F_VEC[d]); | |
| if (!F_stand(w, n)) continue; | |
| if (w.enemy.tank && F_same(w.enemy.tank.p, n)) continue; | |
| var v = F_dist(w, n, w.star) * 10 + F_man(n, w.star); | |
| if (v < bestV) { | |
| bestV = v; | |
| best = side; | |
| } | |
| } | |
| return best ? { type: "turn", side: best } : null; | |
| } | |
| function F_cornerKillAction(w) { | |
| if (w.star || !w.enemy.tank || F_enemyHasBullet(w) || w.me.bullet || (w.me.status && w.me.status.fireLocked)) return null; | |
| if (w.w !== 19 || w.h !== 15 || w.me.p[1] !== 1 || w.me.p[0] < 12 || F_tile(w, [w.me.p[0], 0]) !== "x") return null; | |
| var ep = w.enemy.tank.p; | |
| if (ep[0] !== w.me.p[0] || ep[1] <= w.me.p[1] || ep[1] - w.me.p[1] > 3 || F_lineBlock(w, w.me.p, ep)) return null; | |
| if (F_shotHits(w, w.me.p, w.me.d, ep, false)) return { type: "fire" }; | |
| return { type: "turn", side: F_sideTo(w.me.d, "down") }; | |
| } | |
| function F_recentStill(w, n) { | |
| if (!F_MEM.self || F_MEM.self.length < n) return false; | |
| for (var i = F_MEM.self.length - n; i < F_MEM.self.length; i++) { | |
| if (!F_same(F_MEM.self[i].p, w.me.p) || F_MEM.self[i].d !== w.me.d) return false; | |
| } | |
| return true; | |
| } | |
| function F_threeStarEscape(w) { | |
| if (w.star || !w.enemy.tank || w.me.stars < 3 || F_enemyHasBullet(w)) return null; | |
| if (F_man(w.me.p, w.enemy.tank.p) !== 1) return null; | |
| if (w.me.p[0] !== w.enemy.tank.p[0] && w.me.p[1] !== w.enemy.tank.p[1]) return null; | |
| var n = F_add(w.me.p, F_VEC[w.me.d]); | |
| if (F_stand(w, n) && F_man(n, w.enemy.tank.p) > 1) return { type: "go" }; | |
| return null; | |
| } | |
| function F_tailEscapeAction(w) { | |
| var c = F_MEM.tailTurn; | |
| if (!c || c.f !== w.frame) return null; | |
| F_MEM.tailTurn = null; | |
| var d = F_turn(w.me.d, c.side); | |
| if (F_stand(w, F_add(w.me.p, F_VEC[d]))) return { type: "turn", side: c.side }; | |
| return null; | |
| } | |
| function F_setTailEscape(w, a) { | |
| if (!a || a.type !== "go") return; | |
| var src = F_chaseSource(w, w.me.p, w.me.d); | |
| if (!src) return; | |
| var n = F_add(w.me.p, F_VEC[w.me.d]); | |
| if (!F_stand(w, n)) return; | |
| var right = F_turn(w.me.d, "right"), left = F_turn(w.me.d, "left"); | |
| if (F_stand(w, F_add(n, F_VEC[right]))) F_MEM.tailTurn = { f: w.frame + 1, side: "right" }; | |
| else if (F_stand(w, F_add(n, F_VEC[left]))) F_MEM.tailTurn = { f: w.frame + 1, side: "left" }; | |
| } | |
| function F_chaseSource(w, p, d) { | |
| var bs = []; | |
| if (w.enemy.bullet) bs.push(w.enemy.bullet); | |
| for (var i = 0; i < (F_MEM.bullets || []).length; i++) bs.push(F_MEM.bullets[i]); | |
| for (var j = 0; j < bs.length; j++) { | |
| var b = bs[j]; | |
| if (b && b.d === d && F_chaseLine(w, b.p, p, d)) return b; | |
| } | |
| return null; | |
| } | |
| function F_chaseLine(w, b, p, d) { | |
| if (!b || (d === "left" || d === "right" ? b[1] !== p[1] : b[0] !== p[0])) return false; | |
| if (F_lineBlock(w, b, p)) return false; | |
| var v = F_VEC[d], dist = (p[0] - b[0]) * v[0] + (p[1] - b[1]) * v[1]; | |
| return dist > 0 && dist <= 10; | |
| } | |
| function F_sim(w, plan, horizon) { | |
| var p = F_cp(w.me.p), d = w.me.d, pos = [], dirs = [], ok = true; | |
| for (var t = 0; t < horizon; t++) { | |
| var a = plan.actions[t] || { type: "wait" }; | |
| if (a.type === "go") { | |
| var n = F_add(p, F_VEC[d]); | |
| if (!F_stand(w, n)) ok = false; | |
| else p = n; | |
| } else if (a.type === "turn") d = F_turn(d, a.side); | |
| else if (a.type === "teleport") { | |
| var tp = [a.x, a.y]; | |
| if (!F_stand(w, tp)) ok = false; | |
| else p = tp; | |
| } | |
| pos.push(F_cp(p)); | |
| dirs.push(d); | |
| } | |
| return { ok: ok, pos: pos, dir: dirs }; | |
| } | |
| function F_forecast(w, horizon) { | |
| var f = { h: horizon, c: {} }; | |
| for (var bi = 0; bi < (F_MEM.bullets || []).length; bi++) F_bulletPath(f, w, 0, F_MEM.bullets[bi].p, F_MEM.bullets[bi].d, 1000, "bullet"); | |
| var sources = F_enemySources(w); | |
| for (var si = 0; si < sources.length; si++) { | |
| var src = sources[si]; | |
| var od = !src.ghost && F_enemyOverload(w); | |
| var fk = src.ghost ? "ghost" : "fire"; | |
| var hasBullet = F_enemyHasBullet(w); | |
| var soonGone = !w.enemy.bullet && F_enemyMemoryBulletSoonGone(w); | |
| if ((!hasBullet || soonGone) && (src.ghost || F_enemyCanFire(w) || soonGone)) { | |
| var start = hasBullet ? 1 : 0; | |
| for (var t = start; t < horizon; t++) F_shotField(f, w, t, src.p, src.d, od, t ? 900 : 1000, fk); | |
| for (var t1 = Math.max(1, start); t1 < horizon; t1++) { | |
| F_shotField(f, w, t1, src.p, F_turn(src.d, "left"), od, 900, fk); | |
| F_shotField(f, w, t1, src.p, F_turn(src.d, "right"), od, 900, fk); | |
| } | |
| var back = F_turn(F_turn(src.d, "left"), "left"); | |
| if (!F_facingBlock(w, src.p, src.d)) { | |
| for (var t2 = Math.max(2, start + 1); t2 < horizon; t2++) F_shotField(f, w, t2, src.p, back, od, 850, fk); | |
| } | |
| } else if (!src.ghost && F_enemyHasBullet(w)) { | |
| for (var t3 = 2; t3 < horizon; t3++) F_shotField(f, w, t3, src.p, src.d, od, 750, fk); | |
| } | |
| if (!src.ghost && !od && w.enemy.skill && w.enemy.skill.type === "overload" && w.enemy.skill.remainingCooldownFrames === 0) { | |
| for (var t4 = 2; t4 < horizon; t4++) F_shotField(f, w, t4, src.p, src.d, true, 900, "fire"); | |
| } | |
| } | |
| return f; | |
| } | |
| function F_enemySources(w) { | |
| if (w.enemy.tank) return [{ p: w.enemy.tank.p, d: w.enemy.tank.d, ghost: false }]; | |
| var last = F_MEM.enemy.length ? F_MEM.enemy[F_MEM.enemy.length - 1] : null; | |
| if (!last || w.frame - last.f > 10) return []; | |
| var out = [], seen = {}; | |
| function add(p, d) { | |
| var k = p[0] + "," + p[1]; | |
| if (!seen[k] && F_stand(w, p)) { | |
| seen[k] = true; | |
| out.push({ p: F_cp(p), d: d, ghost: true }); | |
| } | |
| } | |
| add(last.p, last.d); | |
| for (var i = 0; i < 4; i++) { | |
| var p = F_add(last.p, F_VEC[F_DIRS[i]]); | |
| if (F_tile(w, p) === "o") add(p, F_DIRS[i]); | |
| } | |
| return out; | |
| } | |
| function F_enemyOverload(w) { | |
| if (w.enemy.status && w.enemy.status.overloaded) return true; | |
| var s = w.enemy.skill || {}; | |
| var cd = Number(s.cooldownFrames) || 0, rem = Number(s.remainingCooldownFrames) || 0; | |
| return s.type === "overload" && cd > 0 && cd - rem <= 1; | |
| } | |
| function F_enemyCanFire(w) { | |
| if (w.enemy.status && w.enemy.status.fireLocked) return false; | |
| return !F_enemyHasBullet(w); | |
| } | |
| function F_enemyHasBullet(w) { | |
| return !!(w.enemy.bullet || (F_MEM.bullets && F_MEM.bullets.length)); | |
| } | |
| function F_facingBlock(w, p, d) { | |
| return F_blocksBullet(w, F_add(p, F_VEC[d])); | |
| } | |
| function F_enemyMemoryBulletSoonGone(w) { | |
| var bs = F_MEM.bullets || []; | |
| if (!bs.length) return false; | |
| for (var i = 0; i < bs.length; i++) { | |
| if (!F_advanceBullet(w, bs[i], w.frame + 1)) return true; | |
| } | |
| return false; | |
| } | |
| function F_shotField(f, w, t, p, d, overload, v, kind) { | |
| F_bulletPath(f, w, t, p, d, v, kind); | |
| F_moundFollow(f, w, t, p, d, v, kind); | |
| if (overload) { | |
| var q = d === "up" || d === "down" ? [p[0] + 1, p[1]] : [p[0], p[1] + 1]; | |
| F_bulletPath(f, w, t, q, d, v, kind); | |
| F_moundFollow(f, w, t, q, d, v, kind); | |
| } | |
| } | |
| function F_moundFollow(f, w, t, p0, d, v, kind) { | |
| if (t + 1 >= f.h || !p0 || !F_VEC[d]) return; | |
| var p = F_cp(p0), step = F_VEC[d], opened = false; | |
| while (true) { | |
| p = F_add(p, step); | |
| if (!F_inside(w, p) || F_tile(w, p) === "x") return; | |
| if (!opened) { | |
| if (F_tile(w, p) === "m") { | |
| opened = true; | |
| continue; | |
| } | |
| continue; | |
| } | |
| if (F_blocksBullet(w, p)) return; | |
| for (var tt = t + 1; tt < f.h; tt++) F_mark(f, tt, p, Math.max(700, v - 100), kind); | |
| } | |
| } | |
| function F_bulletPath(f, w, start, p0, d, v, kind) { | |
| if (!p0 || !F_VEC[d]) return; | |
| var p = F_cp(p0), step = F_VEC[d]; | |
| for (var t = start; t < f.h; t++) { | |
| if (F_inside(w, p)) F_mark(f, t, p, v, kind); | |
| for (var i = 0; i < 2; i++) { | |
| p = F_add(p, step); | |
| if (!F_inside(w, p)) return; | |
| F_mark(f, t, p, v, kind); | |
| if (F_blocksBullet(w, p)) return; | |
| } | |
| } | |
| } | |
| function F_mark(f, t, p, v, kind) { | |
| var k = t + ":" + p[0] + "," + p[1]; | |
| if (!f.c[k] || f.c[k].v < v) f.c[k] = { v: v, k: kind }; | |
| } | |
| function F_danger(f, t, p) { | |
| return f.c[t + ":" + p[0] + "," + p[1]] || { v: 0, k: "" }; | |
| } | |
| function F_fireAt(plan) { | |
| for (var i = 0; i < plan.actions.length; i++) if (plan.actions[i].type === "fire") return i; | |
| return null; | |
| } | |
| function F_killFrame(w, plan, sim, firstSkill) { | |
| if (!w.enemy.tank) return null; | |
| var fireAt = F_fireAt(plan); | |
| if (fireAt == null) return null; | |
| var pos = sim.pos[Math.min(fireAt, sim.pos.length - 1)]; | |
| var dir = sim.dir[Math.min(fireAt, sim.dir.length - 1)]; | |
| if (!F_shotHits(w, pos, dir, w.enemy.tank.p, firstSkill === "overload" || (w.me.status && w.me.status.overloaded))) return null; | |
| return fireAt + Math.floor((F_man(pos, w.enemy.tank.p) - 1) / 2); | |
| } | |
| function F_shotHits(w, p, d, target, overload) { | |
| if (!target || !F_VEC[d]) return false; | |
| if (overload) { | |
| var q = d === "up" || d === "down" ? [p[0] + 1, p[1]] : [p[0], p[1] + 1]; | |
| if (F_shotHits(w, q, d, target, false)) return true; | |
| } | |
| var v = F_VEC[d], x = F_cp(p); | |
| while (true) { | |
| x = F_add(x, v); | |
| if (!F_inside(w, x) || F_blocksBullet(w, x)) return false; | |
| if (F_same(x, target)) return true; | |
| } | |
| } | |
| function F_fireHitsMound(w, p, d) { | |
| return !!F_firstMound(w, p, d); | |
| } | |
| function F_fireHitsStarMound(w, p, d, over) { | |
| var m = F_firstMound(w, p, d); | |
| if (m && F_starMoundUseful(w, m)) return true; | |
| if (!over) return false; | |
| var q = d === "up" || d === "down" ? [p[0] + 1, p[1]] : [p[0], p[1] + 1]; | |
| m = F_firstMound(w, q, d); | |
| return !!(m && F_starMoundUseful(w, m)); | |
| } | |
| function F_firstMound(w, p, d) { | |
| var v = F_VEC[d], x = F_cp(p); | |
| while (true) { | |
| x = F_add(x, v); | |
| if (!F_inside(w, x) || F_tile(w, x) === "x") return null; | |
| if (F_tile(w, x) === "m") return x; | |
| } | |
| } | |
| function F_starMoundUseful(w, m) { | |
| return w.star && (m[0] === w.star[0] || m[1] === w.star[1] || F_man(m, w.star) <= 3); | |
| } | |
| function F_fireBreaksStarMound(w) { | |
| if (!w.star) return false; | |
| return F_fireHitsMound(w, w.me.p, w.me.d) && (w.me.p[0] === w.star[0] || w.me.p[1] === w.star[1]); | |
| } | |
| function F_lineBlock(w, a, b) { | |
| if (!a || !b) return true; | |
| if (a[0] !== b[0] && a[1] !== b[1]) return true; | |
| var ck = "L" + a[0] + "," + a[1] + ">" + b[0] + "," + b[1]; | |
| if (w.cache && w.cache[ck] != null) return w.cache[ck]; | |
| var d = a[0] === b[0] ? (a[1] < b[1] ? "down" : "up") : (a[0] < b[0] ? "right" : "left"); | |
| var p = F_cp(a), v = F_VEC[d]; | |
| while (true) { | |
| p = F_add(p, v); | |
| if (F_same(p, b)) return w.cache ? (w.cache[ck] = false) : false; | |
| if (!F_inside(w, p) || F_blocksBullet(w, p)) return w.cache ? (w.cache[ck] = true) : true; | |
| } | |
| } | |
| function F_enemyMayWinStar(w) { | |
| if (!w.star || !w.enemy.tank) return false; | |
| return F_dist(w, w.enemy.tank.p, w.star) <= F_dist(w, w.me.p, w.star) + 1; | |
| } | |
| function F_nearEnemyStar(w, sim) { | |
| if (!w.star || !w.enemy.tank) return false; | |
| var p = sim.pos[sim.pos.length - 1]; | |
| return F_man(w.enemy.tank.p, w.star) <= 7 || F_man(w.enemy.tank.p, p) <= 6; | |
| } | |
| function F_dist(w, start, goal) { | |
| if (!start || !goal) return 99; | |
| if (F_same(start, goal)) return 0; | |
| var ck = start[0] + "," + start[1] + ">" + goal[0] + "," + goal[1]; | |
| if (w.cache && w.cache[ck] != null) return w.cache[ck]; | |
| var q = [start], seen = {}, head = 0; | |
| seen[start[0] + "," + start[1]] = 0; | |
| while (head < q.length && head < 220) { | |
| var p = q[head++], d = seen[p[0] + "," + p[1]]; | |
| for (var i = 0; i < 4; i++) { | |
| var n = F_add(p, F_VEC[F_DIRS[i]]), k = n[0] + "," + n[1]; | |
| if (seen[k] != null || !F_stand(w, n)) continue; | |
| if (F_same(n, goal)) return w.cache ? (w.cache[ck] = d + 1) : d + 1; | |
| seen[k] = d + 1; | |
| q.push(n); | |
| } | |
| } | |
| return w.cache ? (w.cache[ck] = 99) : 99; | |
| } | |
| function F_routeStep(w, start, goal) { | |
| var best = null, bestD = 99; | |
| for (var i = 0; i < 4; i++) { | |
| var d = F_DIRS[i], n = F_add(start, F_VEC[d]); | |
| if (!F_stand(w, n)) continue; | |
| var dist = F_dist(w, n, goal); | |
| if (dist < bestD) { | |
| bestD = dist; | |
| best = d; | |
| } | |
| } | |
| if (!best) return null; | |
| if (best === w.me.d) return { type: "go" }; | |
| return { type: "turn", side: F_turn(w.me.d, "left") === best ? "left" : "right" }; | |
| } | |
| function F_teleports(w) { | |
| var out = [], seen = {}; | |
| function add(p) { | |
| var k = p[0] + "," + p[1]; | |
| if (F_stand(w, p) && !seen[k]) { | |
| seen[k] = true; | |
| out.push(p); | |
| } | |
| } | |
| if (w.star) { | |
| add(w.star); | |
| for (var i = 0; i < 4; i++) add(F_add(w.star, F_VEC[F_DIRS[i]])); | |
| } | |
| if (w.enemy.tank) add(w.enemy.tank.p); | |
| return out.slice(0, w.star ? 2 : 1); | |
| } | |
| function F_guardGo(w, a) { var n = F_add(w.me.p, F_VEC[w.me.d]); if (!F_liveBulletDanger(w, n)) return a; for (var i = 0; i < 2; i++) { var side = i ? "right" : "left"; if (!F_liveBulletDanger(w, w.me.p)) return { type: "turn", side: side }; } return { type: "wait" }; } | |
| function F_liveBulletDanger(w, p) { var bs = w.enemy.bullet ? [w.enemy.bullet] : []; for (var i = 0; i < (F_MEM.bullets || []).length; i++) bs.push(F_MEM.bullets[i]); for (var j = 0; j < bs.length; j++) { var b = bs[j], q = F_cp(b.p); for (var s = 0; s < 3; s++) { if (F_same(q, p)) return true; if (s < 2) { q = F_add(q, F_VEC[b.d]); if (!F_inside(w, q) || F_blocksBullet(w, q)) break; } } } return false; } | |
| function F_act(me, a, w) { | |
| if (w && a && a.type === "go") a = F_guardGo(w, a); | |
| if (!a || a.type === "wait") return; | |
| if (a.type === "go") me.go(); | |
| else if (a.type === "turn") me.turn(a.side); | |
| else if (a.type === "fire") me.fire(); | |
| else if (a.type === "teleport") me.teleport(a.x, a.y); | |
| else if (a.type === "skill" && a.skill && typeof me[a.skill] === "function") me[a.skill](); | |
| } | |
| function F_turn(d, side) { | |
| var i = F_DIRS.indexOf(d); | |
| return F_DIRS[(i + (side === "left" ? 3 : 1)) % 4]; | |
| } | |
| function F_sideTo(d, target) { return F_turn(d, "left") === target ? "left" : "right"; } | |
| function F_stand(w, p) { | |
| var t = F_tile(w, p); | |
| return t !== "x" && t !== "m"; | |
| } | |
| function F_blocksBullet(w, p) { | |
| var t = F_tile(w, p); | |
| return t === "x" || t === "m"; | |
| } | |
| function F_tile(w, p) { | |
| if (!F_inside(w, p)) return "x"; | |
| return w.map[p[0]][p[1]]; | |
| } | |
| function F_inside(w, p) { | |
| return p && p[0] >= 0 && p[1] >= 0 && p[0] < w.w && p[1] < w.h; | |
| } | |
| function F_add(a, b) { return [a[0] + b[0], a[1] + b[1]]; } | |
| function F_cp(p) { return p ? [p[0], p[1]] : null; } | |
| function F_same(a, b) { | |
| return !!(a && b && a[0] === b[0] && a[1] === b[1]); | |
| } | |
| function F_man(a, b) { return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]); } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://agentank.ai/share/tanks/tnk_26WNWFj1bytBhxWvr V2版本代码