Турнир алгоритмов: муравьи

Каждая модель пишет не текст и не страницу, а алгоритм поведения муравья — одну функцию в одном файле. Дальше её боты играют друг против друга в нашем симуляторе: поле 15×15, две команды по четыре муравья, 150 ходов, побеждает тот, кто принёс на базу больше еды. Оценок здесь нет вовсе: результат определяют матчи, а не чьё-то мнение.

Туров: 4 Партий: 3360 Каждая пара играет на общих картах дважды, меняясь сторонами

Итоговая таблица

В каждом туре — круговая система: все против всех. Столбцы туров показывают долю набранных очков от возможных. Поздние туры весят больше: 10, 20, 30 и 40 процентов, — так награждается развитие алгоритма, но слабый первый ответ не списывается полностью.

Модельтур 1тур 2тур 3тур 4 ΔИтогЕда за партию Сбои
1GPT-5.6 Sol 69869592 +23 89.5 25.1 0
2Claude Opus 5 92767785 -7 81.4 23.9 0
3Kimi K3 39463860 +21 48.5 18.2 0
4DeepSeek V4 Pro 79676020 -59 47.3 5.5 4
5Qwen 3.8 Max 19405329 +10 37.5 8.7 0
6Gemini 3.7 Flash 26292544 +18 33.5 13.7 0
7Grok 4.6 256319 -6 12.2 6.8 2

Как менялись алгоритмы

После каждого тура модель получает разбор своих партий — счёт по соперникам, сколько еды принесла и отдала, где теряла груз и муравьёв, хронику нескольких боёв — и переписывает алгоритм. Чужой код не показывают никому.

100 0

Личные встречи последнего тура

Счёт в партиях. Зелёный — перевес, красный — отставание.

GPT-5.6 SolClaude Opus 5Kimi K3DeepSeek V4 ProQwen 3.8 MaxGemini 3.7 FlashGrok 4.6
GPT-5.6 Sol30.5:9.535.5:4.540:039:136:440:0
Claude Opus 59.5:30.537:340:040:037:340:0
Kimi K34.5:35.53:3738:234:628:1237:3
DeepSeek V4 Pro0:400:402:3813:2710.5:29.523:17
Qwen 3.8 Max1:390:406:3427:1311.5:28.525:15
Gemini 3.7 Flash4:363:3712:2829.5:10.528.5:11.529:11
Grok 4.60:400:403:3717:2315:2511:29

Записи партий

Партию можно посмотреть по ходам: муравьи, еда, удары, доставка. Работает пауза, перемотка и слои — следы маршрутов и намерения на текущий ход.

GPT-5.6 Sol против DeepSeek V4 ProGPT-5.6 Sol против Gemini 3.7 FlashGPT-5.6 Sol против Grok 4.6GPT-5.6 Sol против Kimi K3GPT-5.6 Sol против Qwen 3.8 Max

Участники и их алгоритмы

GPT-5.6 Sol 25.1 еды за партию · потеряно муравьёв 14 · сбоев 0
код алгоритма (7.3 КБ)
function решить(в) {
  var я=в.я, ход=в.ход, N=в.размер, конец=в.всего_ходов||150;
  var база=в.моя_база, чужая=в.чужая_база;
  var п=в.память&&typeof в.память==="object"?в.память:{};
  var о=в.общая&&typeof в.общая==="object"?в.общая:{};
  var S=N*N, dirs=[
    {n:"С",x:0,y:-1},{n:"В",x:1,y:0},
    {n:"Ю",x:0,y:1},{n:"З",x:-1,y:0}
  ];

  if(typeof о.m!=="string"||о.m.length!==S) о.m=new Array(S+1).join("?");
  if(!о.f||typeof о.f!=="object") о.f={};
  if(!о.c||typeof о.c!=="object") о.c={};
  if(!о.mw||typeof о.mw!=="object") о.mw={};

  var карта=о.m.split(""), еда=о.f, цели=о.c, движения=о.mw, вид={};

  function idx(x,y){return y*N+x}
  function key(x,y){return x+","+y}
  function ok(x,y){return x>=0&&y>=0&&x<N&&y<N}
  function md(x,y,a,b){return Math.abs(x-a)+Math.abs(y-b)}
  function cell(x,y){return вид[key(x,y)]}
  function сохранить(a){
    о.m=карта.join("");
    return {действие:a,память:п,общая:о};
  }
  function act(t,d){
    var a={тип:t};
    if(d) a.куда=d;
    if(t==="идти"){
      for(var z=0;z<4;z++) if(dirs[z].n===d){
        движения[я.id]=[ход,key(я.x+dirs[z].x,я.y+dirs[z].y)];
        break;
      }
    }else delete движения[я.id];
    return сохранить(a);
  }

  var i,k,c;
  for(i=0;i<в.видно.length;i++){
    c=в.видно[i];
    if(!ok(c.x,c.y)) continue;
    k=key(c.x,c.y);
    вид[k]=c;
    карта[idx(c.x,c.y)]=c.что==="стена"?"#":".";
    if(c.еда>0) еда[k]=[c.x,c.y,c.еда,ход];
    else delete еда[k];
  }

  for(k in еда)
    if(!еда[k]||ход-еда[k][3]>28) delete еда[k];
  for(k in цели)
    if(!цели[k]||цели[k][1]<ход-1||!еда[цели[k][0]]) delete цели[k];
  for(k in движения)
    if(!движения[k]||движения[k][0]!==ход) delete движения[k];

  function врагиРядом(x,y){
    var n=0;
    for(var d=0;d<4;d++){
      var q=cell(x+dirs[d].x,y+dirs[d].y);
      if(q&&q.что==="враг") n++;
    }
    return n;
  }

  function своиРядом(x,y){
    var n=0;
    for(var d=0;d<4;d++){
      var q=cell(x+dirs[d].x,y+dirs[d].y);
      if(q&&q.что==="свой") n++;
    }
    return n;
  }

  function зарезервировано(x,y){
    var q=key(x,y);
    for(var id in движения)
      if(String(id)!==String(я.id)&&движения[id]&&
         движения[id][0]===ход&&движения[id][1]===q) return true;
    return false;
  }

  function можно(x,y,tx,ty,safe){
    if(!ok(x,y)||карта[idx(x,y)]==="#") return false;
    var q=cell(x,y);
    if(q&&(q.что==="стена"||q.что==="враг")) return false;
    if(q&&q.что==="свой"&&!(x===tx&&y===ty)) return false;
    if(зарезервировано(x,y)&&!(x===tx&&y===ty)) return false;
    if(safe&&врагиРядом(x,y)>0) return false;
    return true;
  }

  function путь(tx,ty,safe){
    if(!ok(tx,ty)) return null;
    if(я.x===tx&&я.y===ty) return {d:null,l:0};
    var qx=[я.x],qy=[я.y],ql=[0],h=0;
    var seen=new Array(S), first=new Array(S);
    seen[idx(я.x,я.y)]=1;

    while(h<qx.length){
      var x=qx[h],y=qy[h],len=ql[h++];
      var sh=(ход+я.id*3+x*5+y*7)&3;
      for(var j=0;j<4;j++){
        var s=dirs[(j+sh)&3],nx=x+s.x,ny=y+s.y;
        if(!ok(nx,ny)) continue;
        var ni=idx(nx,ny);
        if(seen[ni]||!можно(nx,ny,tx,ty,safe)) continue;
        seen[ni]=1;
        first[ni]=(x===я.x&&y===я.y)?s.n:first[idx(x,y)];
        if(nx===tx&&ny===ty) return {d:first[ni],l:len+1};
        qx.push(nx); qy.push(ny); ql.push(len+1);
      }
    }
    return null;
  }

  function локальныйШаг(tx,ty,опасность){
    var bd=null,bs=1e9;
    for(var j=0;j<4;j++){
      var s=dirs[(j+ход+я.id)&3],nx=я.x+s.x,ny=я.y+s.y;
      if(!можно(nx,ny,-1,-1,false)) continue;
      var e=врагиРядом(nx,ny);
      var q=cell(nx,ny);
      var sc=md(nx,ny,tx,ty)+e*(опасность?300:75);
      if(!я.груз&&q&&q.еда>0) sc-=20;
      if(карта[idx(nx,ny)]==="?") sc-=2;
      if(q&&q.что==="свой") sc+=40;
      sc+=((j+я.id+ход)&3)*.01;
      if(sc<bs){bs=sc;bd=s.n}
    }
    return bd;
  }

  var здесь=cell(я.x,я.y);

  if(я.груз&&я.x===база.x&&я.y===база.y){
    delete цели[я.id]; delete п.t;
    return act("положить");
  }
  if(!я.груз&&здесь&&здесь.еда>0){
    delete цели[я.id]; delete п.t;
    return act("взять");
  }

  var рядом=[];
  for(i=0;i<4;i++){
    c=cell(я.x+dirs[i].x,я.y+dirs[i].y);
    if(c&&c.что==="враг")
      рядом.push({d:dirs[i],c:c,поддержка:своиРядом(c.x,c.y)});
  }

  if(рядом.length){
    if(я.груз||я.здоровье===1){
      var escape=null,es=1e9;
      for(i=0;i<4;i++){
        var sd=dirs[(i+ход+я.id)&3],ex=я.x+sd.x,ey=я.y+sd.y;
        if(!можно(ex,ey,-1,-1,false)) continue;
        var danger=врагиРядом(ex,ey);
        var score=danger*500+md(ex,ey,база.x,база.y);
        if(score<es){es=score;escape=sd.n}
      }
      if(escape&&es<500) return act("идти",escape);
    }

    var choice=рядом[0],best=-1e9;
    for(i=0;i<рядом.length;i++){
      var e=рядом[i],sc=(e.c.еда>0?45:0)+e.поддержка*8;
      if(md(e.c.x,e.c.y,база.x,база.y)<=3) sc+=12;
      if(я.здоровье===3) sc+=4;
      sc+=((ход+я.id+i*11)&3);
      if(sc>best){best=sc;choice=e}
    }
    return act("бить",choice.d.n);
  }

  if(я.груз){
    delete цели[я.id]; delete п.t;
    var remain=конец-ход;
    var home=null;
    if(я.здоровье<3||remain<24) home=путь(база.x,база.y,true);
    if(!home) home=путь(база.x,база.y,false);
    if(home&&home.d) return act("идти",home.d);
    var hb=локальныйШаг(база.x,база.y,true);
    return hb?act("идти",hb):act("ждать");
  }

  var counts={};
  for(k in цели){
    var ck=цели[k][0];
    counts[ck]=(counts[ck]||0)+1;
  }

  var bestFood=null,bestRoute=null,bestScore=1e9;
  for(k in еда){
    var f=еда[k];
    if(!f||!ok(f[0],f[1])||f[2]<=0) continue;

    var r=путь(f[0],f[1],я.здоровье===1);
    if(!r) r=путь(f[0],f[1],false);
    if(!r) continue;

    var homeEstimate=md(f[0],f[1],база.x,база.y);
    var canTake=ход+r.l+1<=конец;
    var canDeliver=ход+r.l+homeEstimate+2<=конец;
    if(!canTake) continue;

    var n=counts[k]||0;
    if(цели[я.id]&&цели[я.id][0]===k) n--;
    var over=n>=f[2];
    var age=ход-f[3];
    var sc=r.l+n*7+(over?55:0)-Math.min(f[2],4)*1.7+age*.14;

    if(canDeliver) sc+=homeEstimate*.08;
    else sc+=20-homeEstimate*.12;
    if(п.t===k) sc-=6;
    if(ход>=140&&!canDeliver) sc-=15;
    if(врагиРядом(f[0],f[1])>0) sc+=я.здоровье===1?80:12;

    if(sc<bestScore){
      bestScore=sc; bestFood=k; bestRoute=r;
    }
  }

  if(bestFood&&bestRoute){
    п.t=bestFood;
    цели[я.id]=[bestFood,ход];
    if(bestRoute.d) return act("идти",bestRoute.d);
  }else{
    delete цели[я.id]; delete п.t;
  }

  var role=((я.id%4)+4)%4;
  if(!п.e||typeof п.e!=="object") п.e={q:role*17,s:-99,x:база.x,y:база.y};
  var ex=п.e;

  function новаяЦель(){
    var sx=база.x<чужая.x?1:-1, sy=база.y<чужая.y?1:-1;
    for(var tries=0;tries<12;tries++){
      ex.q=(ex.q+19+role*4)%225;
      var z=(ex.q*73+role*41)%225;
      var gx=z%15,gy=Math.floor(z/15);
      var tx=sx>0?gx:14-gx,ty=sy>0?gy:14-gy;
      if(карта[idx(tx,ty)]!=="#"){
        ex.x=tx;ex.y=ty;ex.s=ход;
        return;
      }
    }
    ex.x=чужая.x;ex.y=чужая.y;ex.s=ход;
  }

  if(!ok(ex.x,ex.y)||(ex.x===я.x&&ex.y===я.y)||ход-ex.s>22)
    новаяЦель();

  var er=путь(ex.x,ex.y,я.здоровье===1);
  if(!er){
    новаяЦель();
    er=путь(ex.x,ex.y,false);
  }

  if(er&&er.d) return act("идти",er.d);

  var tx=role===0?чужая.x:role===1?Math.floor(N/2):role===2?база.x:чужая.x;
  var ty=role===0?Math.floor(N/2):role===1?чужая.y:role===2?чужая.y:база.y;
  var wd=локальныйШаг(tx,ty,я.здоровье===1);
  return wd?act("идти",wd):act("ждать");
}
Claude Opus 5 23.9 еды за партию · потеряно муравьёв 14 · сбоев 0
код алгоритма (17.1 КБ)
// Муравей: общая карта с симметрией 180°, общий реестр еды,
// динамическое распределение куч (с антипробкой), приоритет спорной еды,
// доверие к зеркальной догадке по своей половине, уход из-под удара с грузом,
// экономный бой, разведка по секторам, эндшпиль (еда в руках — тай-брейк).
function решить(в) {
  var N = 15, SZ = 225;
  var DIRS = [[0, -1, "С"], [0, 1, "Ю"], [-1, 0, "З"], [1, 0, "В"]];

  var память = (в && в.память && typeof в.память === "object") ? в.память : {};
  var общая = (в && в.общая && typeof в.общая === "object") ? в.общая : {};
  var M = null, мойTgt = -1, ЯID = null, MX = 0, MY = 0, CARRY = 0;

  function сохр() {
    try {
      if (M && M.length === SZ) общая.m = M.join("");
      if (ЯID !== null) {
        if (!общая.p || typeof общая.p !== "object") общая.p = {};
        общая.p[ЯID] = [MX, MY, CARRY, (в && в.ход | 0) || 0, мойTgt];
      }
    } catch (e) { }
  }
  function готово(d) { сохр(); return { действие: d, память: память, общая: общая }; }

  try {
    var я = в.я, ход = в.ход | 0;
    var всего = (в.всего_ходов | 0) || 150;
    var ост = всего - ход;                        // ходов после текущего
    var mx = я.x | 0, my = я.y | 0, mi = my * N + mx;
    var carry = (я.груз | 0) > 0;
    var hp = я.здоровье | 0;
    var bx = в.моя_база.x | 0, by = в.моя_база.y | 0, baseI = by * N + bx;
    var ebx = в.чужая_база.x | 0, eby = в.чужая_база.y | 0;
    ЯID = я.id; MX = mx; MY = my; CARRY = carry ? 1 : 0;

    function idx(x, y) { return y * N + x; }
    function ix(i) { return i % N; }
    function iy(i) { return (i - i % N) / N; }
    function зерк(i) { return (N - 1 - iy(i)) * N + (N - 1 - ix(i)); }
    function манх(a, b) { return Math.abs(ix(a) - ix(b)) + Math.abs(iy(a) - iy(b)); }
    function манхXY(i, x, y) { return Math.abs(ix(i) - x) + Math.abs(iy(i) - y); }
    function хеш(a) { a = (a * 1103515245 + 12345) & 0x7fffffff; return a; }
    function стенаCh(ch) { return ch === "#" || ch === "w"; }
    function виделCh(ch) { return ch === "#" || ch === "."; }
    // перевес: >0 — клетка ближе к нашей базе (безопасная), <0 — к чужой
    function перевес(i) { return манхXY(i, ebx, eby) - манхXY(i, bx, by); }

    /* ---------- общая карта ---------- */
    var i, x, y, k;
    if (typeof общая.m === "string" && общая.m.length === SZ) {
      M = общая.m.split("");
    } else {
      M = [];
      for (i = 0; i < SZ; i++) {
        x = ix(i); y = iy(i);
        M.push((x === 0 || y === 0 || x === N - 1 || y === N - 1) ? "#" : "?");
      }
    }
    if (!общая.f || typeof общая.f !== "object") общая.f = {};
    var F = общая.f;
    if (!общая.p || typeof общая.p !== "object") общая.p = {};
    var P = общая.p;

    /* ---------- слот муравья (сектор разведки) ---------- */
    if (!общая.ids || !общая.ids.length) общая.ids = [];
    var slot = -1;
    for (i = 0; i < общая.ids.length; i++) if (общая.ids[i] === я.id) { slot = i; break; }
    if (slot < 0) {
      if (общая.ids.length < 8) { общая.ids.push(я.id); slot = общая.ids.length - 1; }
      else slot = 0;
    }

    /* ---------- разбор обзора ---------- */
    var видно = в.видно || [];
    var занятоСвой = {}, занятоВраг = {}, врагиСписок = [], едаЗдесь = 0;
    for (i = 0; i < видно.length; i++) {
      var c = видно[i];
      if (!c) continue;
      x = c.x | 0; y = c.y | 0;
      if (x < 0 || y < 0 || x >= N || y >= N) continue;
      var ci = idx(x, y);
      var стена = (c.что === "стена");
      M[ci] = стена ? "#" : ".";
      var mzi = зерк(ci);
      if (M[mzi] === "?") M[mzi] = стена ? "w" : "o";   // карта симметрична на 180°

      if (c.что === "свой" && ci !== mi) занятоСвой[ci] = 1;
      if (c.что === "враг") { занятоВраг[ci] = 1; врагиСписок.push(ci); }

      var ед = c.еда | 0;
      if (!стена && ед > 0) {
        F[ci] = [ед, ход, 0];
        // зеркальная догадка: на своей половине она надёжна и живёт долго,
        // на чужой — быстро протухает (соперник рядом и заберёт первым)
        var своя = перевес(mzi) > 0;
        var окно = своя ? 115 : 55;
        if (ход <= окно && !F[mzi] && !виделCh(M[mzi]) && !стенаCh(M[mzi])) {
          F[mzi] = [ед, ход, своя ? 1 : 2];
        }
        if (ci === mi) едаЗдесь = ед;
      } else {
        if (F[ci]) delete F[ci];
        if (ci === mi) едаЗдесь = 0;
      }
    }

    /* ---------- чистка реестров ---------- */
    var ключи = [];
    for (k in F) if (F.hasOwnProperty(k)) ключи.push(k);
    for (i = 0; i < ключи.length; i++) {
      var e0 = F[ключи[i]];
      if (!e0 || e0.length < 2) { delete F[ключи[i]]; continue; }
      var г0 = e0[2] | 0;
      if (г0 === 1 && ход - e0[1] > 120) delete F[ключи[i]];
      else if (г0 === 2 && ход - e0[1] > 55) delete F[ключи[i]];
    }
    ключи = [];
    for (k in F) if (F.hasOwnProperty(k)) ключи.push(k);
    if (ключи.length > 90) {
      ключи.sort(function (a, b) { return (F[a] ? F[a][1] : 0) - (F[b] ? F[b][1] : 0); });
      for (i = 0; i < 20; i++) delete F[ключи[i]];
    }
    for (k in P) if (P.hasOwnProperty(k)) {
      var pe0 = P[k];
      if (!pe0 || pe0.length < 4 || ход - (pe0[3] | 0) > 4) delete P[k];
    }

    /* ---------- заявки на этот ход ---------- */
    if (!общая.r || общая.r.t !== ход) общая.r = { t: ход, a: {} };
    var RS = общая.r.a;
    if (!общая.e || общая.e.t !== ход) общая.e = { t: ход, m: {} };
    var EX = общая.e.m;

    var бронь = {}, освобождают = {};
    for (k in RS) if (RS.hasOwnProperty(k) && k !== String(я.id)) {
      var rr = RS[k];
      if (rr && rr.length === 2) {
        if (rr[1] >= 0) бронь[rr[1]] = 1;
        if (rr[1] !== rr[0]) освобождают[rr[0]] = 1;
      }
    }

    /* ---------- опасность: клетки под ударом врага ---------- */
    var опас = {};
    for (i = 0; i < врагиСписок.length; i++) {
      var ei = врагиСписок[i], ex2 = ix(ei), ey2 = iy(ei);
      for (var t0 = 0; t0 < 4; t0++) {
        var ax2 = ex2 + DIRS[t0][0], ay2 = ey2 + DIRS[t0][1];
        if (ax2 < 0 || ay2 < 0 || ax2 >= N || ay2 >= N) continue;
        var ai2 = ay2 * N + ax2;
        опас[ai2] = (опас[ai2] | 0) + 1;
      }
    }

    /* ---------- проходимость и BFS ---------- */
    var стены = new Array(SZ);
    for (i = 0; i < SZ; i++) стены[i] = стенаCh(M[i]) ? 1 : 0;
    стены[mi] = 0;

    function мягкоЗанято(n) {
      if (стены[n]) return 1;
      if (занятоВраг[n]) return 1;
      if (занятоСвой[n] && !освобождают[n]) return 1;
      if (бронь[n]) return 1;
      return 0;
    }

    function bfs(старт) {
      var d = new Array(SZ);
      for (var j = 0; j < SZ; j++) d[j] = -1;
      var q = [старт], h = 0; d[старт] = 0;
      while (h < q.length) {
        var cur = q[h++], cd = d[cur], cx = ix(cur), cy = iy(cur);
        for (var t = 0; t < 4; t++) {
          var nx = cx + DIRS[t][0], ny = cy + DIRS[t][1];
          if (nx < 0 || ny < 0 || nx >= N || ny >= N) continue;
          var ni = ny * N + nx;
          if (d[ni] !== -1 || стены[ni]) continue;
          d[ni] = cd + 1; q.push(ni);
        }
      }
      return d;
    }

    var Dme = bfs(mi);
    var Dbase = bfs(baseI);

    function идти(ni) {
      var dir = null;
      for (var t = 0; t < 4; t++) {
        if (mx + DIRS[t][0] === ix(ni) && my + DIRS[t][1] === iy(ni)) { dir = DIRS[t][2]; break; }
      }
      if (dir === null) { RS[я.id] = [mi, mi]; return готово({ тип: "ждать" }); }
      RS[я.id] = [mi, ni];
      return готово({ тип: "идти", куда: dir });
    }
    function стоять(d) { RS[я.id] = [mi, mi]; return готово(d); }

    /* ---------- соседи ---------- */
    var соседи = [], врагиРядом = [];
    for (i = 0; i < 4; i++) {
      var nx2 = mx + DIRS[i][0], ny2 = my + DIRS[i][1];
      if (nx2 < 0 || ny2 < 0 || nx2 >= N || ny2 >= N) continue;
      var ni2 = ny2 * N + nx2;
      соседи.push({ i: ni2, d: DIRS[i][2] });
      if (занятоВраг[ni2]) врагиРядом.push({ i: ni2, d: DIRS[i][2] });
    }

    /* ---------- застревание ---------- */
    if (память.lp === mi) память.st = (память.st | 0) + 1; else память.st = 0;
    память.lp = mi;
    память.slot = slot;
    var застрял = память.st | 0;

    /* ---------- мгновенные действия ---------- */
    if (carry && mi === baseI) {
      общая.s = (общая.s | 0) + 1;
      память.tgt = null;
      мойTgt = -1;
      CARRY = 0;
      return стоять({ тип: "положить" });
    }
    if (!carry && едаЗдесь > 0) {
      память.tgt = null;
      мойTgt = mi;
      CARRY = 1;                                   // со следующего хода я гружёный
      if (F[mi]) {
        var ост_ед = (F[mi][0] | 0) - 1;
        if (ост_ед > 0) F[mi] = [ост_ед, ход, F[mi][2]]; else delete F[mi];
      }
      return стоять({ тип: "взять" });
    }

    /* ---------- бой ---------- */
    function лучшийВраг() {
      var b = null, bs = 1e9;
      for (var t = 0; t < врагиРядом.length; t++) {
        var vi = врагиРядом[t].i;
        var s = ((Dbase[vi] >= 0) ? Dbase[vi] : манх(vi, baseI)) * 4 + t;
        if (s < bs) { bs = s; b = врагиРядом[t]; }
      }
      return b;
    }
    var вр = врагиРядом.length ? лучшийВраг() : null;
    var dВрБаза = вр ? ((Dbase[вр.i] >= 0) ? Dbase[вр.i] : манх(вр.i, baseI)) : 99;

    // Бьём с толком: у своей базы (защита), на еде (сбиваем груз/отгоняем)
    // или на своей половине. Дальние драки на чужой земле — потеря темпа.
    var стоитБить = false;
    if (вр && !carry && hp >= 2) {
      if (dВрБаза <= 6) стоитБить = true;                    // враг лезет к базе
      else if (F[вр.i]) стоитБить = true;                    // стоит на нашей еде
      else if (перевес(вр.i) >= -1 && hp >= 3) стоитБить = true; // наша половина
    }
    if (стоитБить) return стоять({ тип: "бить", куда: вр.d });

    if (вр && !carry && hp <= 1) {
      var бег = -1, бегСч = -1e9;
      for (i = 0; i < соседи.length; i++) {
        var s1 = соседи[i].i;
        if (мягкоЗанято(s1)) continue;
        var сч = -(опас[s1] | 0) * 30;
        for (var q2 = 0; q2 < врагиРядом.length; q2++) сч += манх(s1, врагиРядом[q2].i) * 10;
        if (Dbase[s1] >= 0) сч -= Dbase[s1] * 2;
        сч += (хеш(ход * 31 + slot * 7 + i) % 3);
        if (сч > бегСч) { бегСч = сч; бег = s1; }
      }
      if (бег >= 0) return идти(бег);
      return стоять({ тип: "бить", куда: вр.d });
    }

    /* ---------- выбор цели ---------- */
    var цель = -1;

    if (carry) {
      цель = baseI;
    } else {
      /* динамическое жадное распределение куч между свободными муравьями */
      var едоки = [];                              // {id, pos, D, tgt}
      едоки.push({ id: я.id | 0, pos: mi, D: Dme, tgt: (память.tgt === 0 || память.tgt) ? (память.tgt | 0) : -1 });
      for (k in P) if (P.hasOwnProperty(k)) {
        var pe = P[k];
        if (!pe || pe.length < 4) continue;
        if ((k | 0) === (я.id | 0)) continue;
        if (ход - (pe[3] | 0) > 1) continue;        // старая запись — муравей мог погибнуть
        if (pe[2]) continue;                       // гружёный идёт на базу, кучи не занимает
        var px = pe[0] | 0, py = pe[1] | 0;
        if (px < 0 || py < 0 || px >= N || py >= N) continue;
        var pi = py * N + px;
        if (стенаCh(M[pi])) continue;
        едоки.push({ id: k | 0, pos: pi, D: null, tgt: (pe.length > 4 && pe[4] >= 0) ? (pe[4] | 0) : -1 });
        if (едоки.length >= 5) break;
      }
      for (i = 1; i < едоки.length; i++) едоки[i].D = bfs(едоки[i].pos);

      var кучи = [];
      for (k in F) if (F.hasOwnProperty(k)) {
        var fi0 = k | 0, ent0 = F[k];
        if (!ent0) continue;
        var cnt0 = ent0[0] | 0;
        if (cnt0 < 1) continue;
        if (cnt0 > 4) cnt0 = 4;
        кучи.push({ i: fi0, cap: cnt0, g: ent0[2] | 0 });
      }

      // k — сколько наших уже назначено на эту кучу (антипробка)
      function ценаЕды(ед, куча, kk) {
        var dd = ед.D ? ед.D[куча.i] : -1;
        if (dd < 0) return 1e9;
        if (dd > ост + 1) return 1e9;                     // даже дойти не успеем
        var db = (Dbase[куча.i] >= 0) ? Dbase[куча.i] : манх(куча.i, baseI);
        var ш = dd * 10 + db * 3 + kk * 18;               // очередь к одной куче — пробка

        if (куча.g === 1) ш += 8;                         // догадка на своей половине — почти верна
        else if (куча.g === 2) ш += 45;                   // догадка на чужой — ненадёжна

        var adv = перевес(куча.i);                        // >0 — ближе к нам
        // спорная еда — «сейчас или никогда»: своя никуда не денется
        var риск = (adv >= 0) ? Math.max(0, 10 - adv) : Math.max(0, 10 + adv * 2);
        ш -= риск * 2.5;
        if (adv < -4) ш += 12;                            // глубоко под чужой базой — дорого

        if (dd + db + 1 > ост) ш += 180;                  // доставить не успеем — только в руки
        if ((опас[куча.i] | 0) > 0) ш += 15;
        if (ед.tgt === куча.i) ш -= 18;                    // липкость: без метаний
        return ш;
      }

      var назначен = {}, счКучи = {}, мояКуча = -1, сделано = 0;
      while (сделано < едоки.length) {
        var bS = 1e9, bA = -1, bP = -1;
        for (var ai = 0; ai < едоки.length; ai++) {
          if (назначен[ai]) continue;
          for (var pj = 0; pj < кучи.length; pj++) {
            var kk0 = счКучи[pj] | 0;
            if (kk0 >= кучи[pj].cap) continue;
            var ц0 = ценаЕды(едоки[ai], кучи[pj], kk0);
            if (ц0 >= 1e9) continue;
            if (ц0 < bS - 1e-9 ||
              (Math.abs(ц0 - bS) < 1e-9 && bA >= 0 &&
                (едоки[ai].id < едоки[bA].id ||
                  (едоки[ai].id === едоки[bA].id && кучи[pj].i < кучи[bP].i)))) {
              bS = ц0; bA = ai; bP = pj;
            }
          }
        }
        if (bA < 0) break;
        назначен[bA] = 1; сделано++;
        счКучи[bP] = (счКучи[bP] | 0) + 1;
        if (bA === 0) { мояКуча = кучи[bP].i; break; }
      }
      if (мояКуча >= 0) цель = мояКуча;
    }

    /* ---------- разведка неосмотренных клеток ---------- */
    if (цель < 0) {
      var якоряБаза = [[12, 3], [3, 12], [7, 7], [11, 11]];
      var a = якоряБаза[slot % 4];
      var ax = a[0], ay = a[1];
      if (bx > 7) { ax = N - 1 - ax; ay = N - 1 - ay; }
      var якорь = idx(ax, ay);

      var чужие = [];
      for (k in EX) if (EX.hasOwnProperty(k) && k !== String(я.id)) {
        var ct = EX[k] | 0;
        if (ct >= 0) чужие.push(ct);
      }

      var лучшU = -1, лучшUС = 1e9;
      for (i = 0; i < SZ; i++) {
        if (виделCh(M[i]) || стенаCh(M[i])) continue;   // осмотрено или стена
        var du = Dme[i];
        if (du < 0) continue;
        if (du > ост) continue;
        var ц = du * 2 + манх(i, якорь);
        // еду ищем прежде всего там, откуда её реально донести
        var dbU = (Dbase[i] >= 0) ? Dbase[i] : манхXY(i, bx, by);
        if (dbU * 2 + du > ост) ц += 40;
        ц += Math.max(0, dbU - 13) * 4;
        for (var q3 = 0; q3 < чужие.length; q3++) {
          var раз = манх(i, чужие[q3]);
          if (раз < 10) ц += (10 - раз) * 2;
        }
        if (ц < лучшUС || (ц === лучшUС && i < лучшU)) { лучшUС = ц; лучшU = i; }
      }
      if (лучшU >= 0) цель = лучшU;
      else if (mi !== якорь && Dme[якорь] >= 0) цель = якорь;
      else {
        var ц2 = idx(7, 7);
        цель = (mi !== ц2 && Dme[ц2] >= 0) ? ц2 : baseI;
      }
      EX[я.id] = цель;
    }

    память.tgt = carry ? null : цель;
    мойTgt = carry ? -1 : цель;

    /* ---------- шаг к цели ---------- */
    if (цель === mi) {
      if (вр && !carry && hp >= 2) return стоять({ тип: "бить", куда: вр.d });
      if (carry && (опас[mi] | 0) > 0) {
        // с грузом под ударом стоять нельзя: удар выбивает еду
        var уход = -1, уходСч = 1e18;
        for (i = 0; i < соседи.length; i++) {
          var s4 = соседи[i].i;
          if (мягкоЗанято(s4)) continue;
          var сч4 = (опас[s4] | 0) * 100 + ((Dbase[s4] >= 0) ? Dbase[s4] : 60) * 5 + (хеш(ход * 71 + я.id * 13 + i) % 3);
          if (сч4 < уходСч) { уходСч = сч4; уход = s4; }
        }
        if (уход >= 0) return идти(уход);
        if (вр && hp >= 2) return стоять({ тип: "бить", куда: вр.d });
      }
      return стоять({ тип: "ждать" });
    }

    var D = (цель === baseI) ? Dbase : bfs(цель);
    var текущ = (D[mi] >= 0) ? D[mi] : 1e9;
    var подУдаром = (опас[mi] | 0) > 0;
    var вес = carry ? 45 : (hp <= 2 ? 20 : 5);   // как сильно боимся клеток под ударом
    var свободно = (carry && подУдаром) || застрял >= 2;

    var шаг = -1, шагСч = 1e18;
    for (i = 0; i < соседи.length; i++) {
      var s2 = соседи[i].i;
      if (мягкоЗанято(s2)) continue;
      var dn = (D[s2] >= 0) ? D[s2] : (1000 + манх(s2, цель));
      if (!свободно) {
        if (dn > текущ) continue;                              // назад — только если зажали
        if (dn === текущ && застрял < 1 && !подУдаром) continue;
      }
      var сч2 = dn * 10 + (опас[s2] | 0) * вес + (хеш(ход * 131 + я.id * 17 + i) % 3);
      if (сч2 < шагСч) { шагСч = сч2; шаг = s2; }
    }

    if (шаг < 0 && застрял >= 3) {              // совсем зажали — любой свободный сосед
      шагСч = 1e18;
      for (i = 0; i < соседи.length; i++) {
        var s3 = соседи[i].i;
        if (мягкоЗанято(s3)) continue;
        var сч3 = (опас[s3] | 0) * вес + (хеш(ход * 977 + я.id * 41 + i) % 7);
        if (сч3 < шагСч) { шагСч = сч3; шаг = s3; }
      }
    }

    if (шаг >= 0) return идти(шаг);

    // идти некуда
    if (вр && hp >= 2) return стоять({ тип: "бить", куда: вр.d });
    return стоять({ тип: "ждать" });

  } catch (err) {
    return готово({ тип: "ждать" });
  }
}
Kimi K3 18.2 еды за партию · потеряно муравьёв 7 · сбоев 0
код алгоритма (12.3 КБ)
function решить(в) {
  try {
    в = в || {};
    var R = в.размер | 0;
    if (R < 5 || R > 60) R = 15;
    var N = R * R;
    var я0 = в.я || {};
    var id = я0.id | 0, xx = я0.x | 0, yy = я0.y | 0, hp = я0.здоровье | 0, груз = я0.груз | 0;
    var ход = в.ход | 0;
    var всего = в.всего_ходов | 0; if (всего < 1) всего = 150;
    var ост = всего - ход;
    var мб = в.моя_база || {}, чб = в.чужая_база || {};
    var базаX = (мб.x != null ? мб.x : 1) | 0, базаY = (мб.y != null ? мб.y : 1) | 0;
    var вбазаX = (чб.x != null ? чб.x : R - 2) | 0, вбазаY = (чб.y != null ? чб.y : R - 2) | 0;
    var видно = в.видно || [];
    var пм = (в.память && typeof в.память === "object" && !Array.isArray(в.память)) ? в.память : {};
    var об = (в.общая && typeof в.общая === "object" && !Array.isArray(в.общая)) ? в.общая : {};

    function И(x, y) { return y * R + x; }
    function KX(k) { return k - ((k / R) | 0) * R; }
    function KY(k) { return (k / R) | 0; }
    function норм(x, y) { return x >= 0 && y >= 0 && x < R && y < R; }
    function ман(x1, y1, x2, y2) { var a = x1 - x2; if (a < 0) a = -a; var b = y1 - y2; if (b < 0) b = -b; return a + b; }
    function манK(a, b) { return ман(KX(a), KY(a), KX(b), KY(b)); }
    function jit(k) { return (k * 7 + id * 13 + ход * 5) % 11; }
    function напрК(k) { var dx = KX(k) - xx, dy = KY(k) - yy; return dx > 0 ? "В" : dx < 0 ? "З" : dy > 0 ? "Ю" : "С"; }

    var start = И(xx, yy);
    var baseK = И(базаX, базаY);
    var myBit = 1 << (id & 3);
    var i, j;

    for (var ok in об) { if (ok !== "m" && ok !== "f" && ok !== "c" && ok !== "g") delete об[ok]; }
    for (var pk in пм) { if (pk !== "t" && pk !== "f") delete пм[pk]; }

    var mstr = (typeof об.m === "string" && об.m.length === N) ? об.m : null;
    var M = new Array(N);
    for (i = 0; i < N; i++) { var cv = mstr ? mstr.charCodeAt(i) - 48 : 0; M[i] = (cv >= 0 && cv <= 2) ? cv : 0; }
    if (!Array.isArray(об.f)) об.f = [];
    if (!Array.isArray(об.c)) об.c = [];
    if (!Array.isArray(об.g)) об.g = [];
    var tmp = [];
    for (i = 0; i < об.f.length; i++) { var fe = об.f[i]; if (Array.isArray(fe) && fe.length >= 3 && (fe[1] | 0) > 0 && fe[0] >= 0 && fe[0] < N) tmp.push([fe[0] | 0, fe[1] | 0, fe[2] | 0]); }
    об.f = tmp;
    tmp = [];
    for (i = 0; i < об.c.length; i++) { var ce = об.c[i]; if (Array.isArray(ce) && ce.length >= 3 && (ce[2] | 0) >= ход && (ce[1] | 0) > 0 && ce[0] >= 0 && ce[0] < N) tmp.push([ce[0] | 0, ce[1] | 0, ce[2] | 0]); }
    об.c = tmp;
    tmp = [];
    for (i = 0; i < об.g.length; i++) { var ge = об.g[i]; if (Array.isArray(ge) && ge.length >= 2 && (ge[1] | 0) >= ход - 2 && ge[0] >= 0 && ge[0] < N) tmp.push([ge[0] | 0, ge[1] | 0]); }
    об.g = tmp;

    function markDanger(ek) {
      var ex = KX(ek), ey = KY(ek);
      danger[ek] = 1;
      if (ex + 1 < R) danger[ek + 1] = 1;
      if (ex > 0) danger[ek - 1] = 1;
      if (ey + 1 < R) danger[ek + R] = 1;
      if (ey > 0) danger[ek - R] = 1;
    }

    var occ = {};
    var adjEn = [], visEn = [];
    var едаТут = 0;
    for (i = 0; i < видно.length; i++) {
      var c = видно[i];
      if (!c || c.x == null || c.y == null) continue;
      var cx0 = c.x | 0, cy0 = c.y | 0;
      if (!норм(cx0, cy0)) continue;
      var k = И(cx0, cy0);
      var own = (cx0 === xx && cy0 === yy);
      M[k] = (c.что === "стена") ? 2 : 1;
      if (!own && (c.что === "свой" || c.что === "враг")) occ[k] = 1;
      if (c.что === "враг") {
        visEn.push(k);
        if (ман(cx0, cy0, xx, yy) === 1) adjEn.push(k);
        var fg = false;
        for (j = 0; j < об.g.length; j++) if (об.g[j][0] === k) { об.g[j][1] = ход; fg = true; break; }
        if (!fg) об.g.push([k, ход]);
      }
      var q = c.еда | 0;
      var fi = -1;
      for (j = 0; j < об.f.length; j++) if (об.f[j][0] === k) { fi = j; break; }
      if (q > 0) { if (fi >= 0) { об.f[fi][1] = q; об.f[fi][2] = ход; } else об.f.push([k, q, ход]); if (own) едаТут = q; }
      else if (fi >= 0) об.f.splice(fi, 1);
    }
    while (об.g.length > 8) об.g.shift();
    while (об.f.length > 30) { var wi = 0; for (i = 1; i < об.f.length; i++) if (об.f[i][2] < об.f[wi][2]) wi = i; об.f.splice(wi, 1); }
    if (об.c.length > 16) об.c.length = 16;

    var danger = new Uint8Array(N);
    for (i = 0; i < visEn.length; i++) markDanger(visEn[i]);
    for (i = 0; i < об.g.length; i++) markDanger(об.g[i][0]);

    var Q = new Int16Array(N);
    function bfs(srcK) {
      var dist = new Int16Array(N);
      for (var i2 = 0; i2 < N; i2++) dist[i2] = -1;
      if (srcK == null || srcK < 0 || srcK >= N || M[srcK] === 2) return dist;
      var h = 0, t = 0;
      dist[srcK] = 0; Q[t++] = srcK;
      while (h < t) {
        var cur = Q[h++]; var nd = dist[cur] + 1;
        var cx = cur - ((cur / R) | 0) * R, nk;
        if (cx + 1 < R) { nk = cur + 1; if (dist[nk] === -1 && M[nk] !== 2) { dist[nk] = nd; Q[t++] = nk; } }
        if (cx > 0) { nk = cur - 1; if (dist[nk] === -1 && M[nk] !== 2) { dist[nk] = nd; Q[t++] = nk; } }
        nk = cur + R; if (nk < N && dist[nk] === -1 && M[nk] !== 2) { dist[nk] = nd; Q[t++] = nk; }
        nk = cur - R; if (nk >= 0 && dist[nk] === -1 && M[nk] !== 2) { dist[nk] = nd; Q[t++] = nk; }
      }
      return dist;
    }

    var DIRS = [[0, -1, "С"], [0, 1, "Ю"], [-1, 0, "З"], [1, 0, "В"]];
    function выбратьШаг(distT, dangerPen) {
      var best = null, bs = Infinity;
      for (var j2 = 0; j2 < 4; j2++) {
        var nx = xx + DIRS[j2][0], ny = yy + DIRS[j2][1];
        if (!норм(nx, ny)) continue;
        var nk = И(nx, ny);
        if (M[nk] === 2 || occ[nk]) continue;
        var d = distT[nk];
        if (d < 0) continue;
        var s = d * 100 + (danger[nk] ? dangerPen : 0) + jit(nk);
        if (s < bs) { bs = s; best = DIRS[j2][2]; }
      }
      return best;
    }
    function жадныйШаг(tx, ty, dangerPen) {
      var best = null, bs = Infinity;
      for (var j2 = 0; j2 < 4; j2++) {
        var nx = xx + DIRS[j2][0], ny = yy + DIRS[j2][1];
        if (!норм(nx, ny)) continue;
        var nk = И(nx, ny);
        if (M[nk] === 2 || occ[nk]) continue;
        var s = ман(nx, ny, tx, ty) * 100 + (danger[nk] ? dangerPen : 0) + jit(nk);
        if (s < bs) { bs = s; best = DIRS[j2][2]; }
      }
      return best;
    }
    function шагБегства() {
      var best = null, bs = -Infinity;
      for (var j2 = 0; j2 < 4; j2++) {
        var nx = xx + DIRS[j2][0], ny = yy + DIRS[j2][1];
        if (!норм(nx, ny)) continue;
        var nk = И(nx, ny);
        if (M[nk] === 2 || occ[nk]) continue;
        var md = 99;
        for (var e2 = 0; e2 < visEn.length; e2++) { var d2 = манK(nk, visEn[e2]); if (d2 < md) md = d2; }
        var s = md * 300 - (danger[nk] ? 5000 : 0) - ман(nx, ny, базаX, базаY) * 20 + jit(nk);
        if (s > bs) { bs = s; best = DIRS[j2][2]; }
      }
      return best;
    }

    function снятьЗаявку(k) {
      for (var ci = 0; ci < об.c.length; ci++) {
        if (об.c[ci][0] === k) {
          var nm = об.c[ci][1] & ~myBit;
          if (nm) об.c[ci][1] = nm; else об.c.splice(ci, 1);
          return;
        }
      }
    }
    function снятьВсеМоиЗаявки() {
      for (var ci = об.c.length - 1; ci >= 0; ci--) {
        var nm = об.c[ci][1] & ~myBit;
        if (nm) об.c[ci][1] = nm; else об.c.splice(ci, 1);
      }
    }
    function поставитьЗаявку(k, exp) {
      снятьВсеМоиЗаявки();
      for (var ci = 0; ci < об.c.length; ci++) if (об.c[ci][0] === k) { об.c[ci][1] = об.c[ci][1] | myBit; об.c[ci][2] = exp; return; }
      if (об.c.length < 16) об.c.push([k, myBit, exp]);
    }

    function решитьБой() {
      if (!adjEn.length) { пм.f = 0; return null; }
      var e = adjEn[0];
      if (ост < 16 || hp <= 1) {
        var fs = шагБегства();
        if (fs) return { тип: "идти", куда: fs };
        пм.f = (пм.f | 0) + 1;
        return { тип: "бить", куда: напрК(e) };
      }
      var nearOwn = ман(KX(e), KY(e), базаX, базаY) <= 3;
      if (nearOwn || (пм.f | 0) < 2) {
        пм.f = (пм.f | 0) + 1;
        return { тип: "бить", куда: напрК(e) };
      }
      return null;
    }

    var акт = null;
    var наБазе = (start === baseK);
    var боевойУход = false;

    if (груз) {
      пм.f = 0;
      снятьВсеМоиЗаявки();
      if (наБазе) {
        акт = { тип: "положить" };
      } else if (ман(xx, yy, базаX, базаY) === 1) {
        акт = occ[baseK] ? { тип: "ждать" } : { тип: "идти", куда: напрК(baseK) };
      } else {
        var distB = bfs(baseK);
        var tight = distB[start] >= 0 && distB[start] + 2 >= ост;
        var dp = tight ? 0 : (adjEn.length ? 100000 : 350);
        var st = выбратьШаг(distB, dp);
        if (!st) st = жадныйШаг(базаX, базаY, dp);
        if (st) акт = { тип: "идти", куда: st };
        else if (adjEn.length) акт = { тип: "бить", куда: напрК(adjEn[0]) };
        else акт = { тип: "ждать" };
      }
    } else {
      var бой = решитьБой();
      if (бой) {
        акт = бой;
      } else {
        боевойУход = adjEn.length > 0;
        if (едаТут > 0) {
          акт = { тип: "взять" };
          снятьЗаявку(start);
        } else {
          var distMe = bfs(start);
          var distHB = bfs(baseK);
          var holdMode = ост < 25;
          var bestK = -1, bestDist = 0;
          var passes = holdMode ? 2 : 1;
          for (var pass = 0; pass < passes && bestK < 0; pass++) {
            var bestS = Infinity;
            for (i = 0; i < об.f.length; i++) {
              var fe2 = об.f[i], fk = fe2[0], qty = fe2[1];
              if (occ[fk] || fk === start) continue;
              var dm = distMe[fk];
              if (dm < 0) continue;
              var dh = distHB[fk]; if (dh < 0) dh = манK(fk, baseK) + 12;
              if (pass === 0) { if (holdMode && dm + dh + 2 > ост) continue; }
              else { if (dm > ост - 1) continue; }
              var cm = 0;
              for (j = 0; j < об.c.length; j++) if (об.c[j][0] === fk) { cm = об.c[j][1]; break; }
              var claims = 0;
              for (var b2 = 0; b2 < 4; b2++) if ((cm & (1 << b2)) && (1 << b2) !== myBit) claims++;
              if (claims >= qty) continue;
              var dvb = ман(KX(fk), KY(fk), вбазаX, вбазаY);
              var sc;
              if (pass === 0) sc = dm * 100 + dh * 100 - qty * 120 + claims * 250 + (dvb < 4 ? (4 - dvb) * 200 : 0) - (пм.t === fk ? 150 : 0) + jit(fk);
              else sc = dm * 100 + jit(fk);
              if (sc < bestS) { bestS = sc; bestK = fk; bestDist = dm; }
            }
          }
          var dpen = (боевойУход || hp <= 1 || ост < 16) ? 3000 : 25;
          if (bestK >= 0) {
            пм.t = bestK;
            поставитьЗаявку(bestK, ход + bestDist + 8);
            var distT = bfs(bestK);
            var st2 = выбратьШаг(distT, dpen);
            if (!st2) st2 = жадныйШаг(KX(bestK), KY(bestK), dpen);
            акт = st2 ? { тип: "идти", куда: st2 } : { тип: "ждать" };
          } else {
            delete пм.t;
            снятьВсеМоиЗаявки();
            var sec = id & 3, mid = R >> 1;
            var ek2 = -1, es = Infinity;
            for (var kk = 0; kk < N; kk++) {
              if (M[kk] !== 0) continue;
              var dm2 = distMe[kk];
              if (dm2 < 0) continue;
              var kx2 = KX(kk), ky2 = KY(kk);
              var inS = ((sec === 0 && kx2 < mid && ky2 < mid) || (sec === 1 && kx2 >= mid && ky2 < mid) || (sec === 2 && kx2 < mid && ky2 >= mid) || (sec >= 3 && kx2 >= mid && ky2 >= mid)) ? 0 : 250;
              var sc2 = dm2 * 100 + inS + jit(kk);
              if (sc2 < es) { es = sc2; ek2 = kk; }
            }
            var tgt = ek2 >= 0 ? ek2 : И(mid, mid);
            var distT2 = bfs(tgt);
            var st3 = выбратьШаг(distT2, dpen);
            if (!st3) st3 = жадныйШаг(KX(tgt), KY(tgt), dpen);
            акт = st3 ? { тип: "идти", куда: st3 } : { тип: "ждать" };
          }
        }
      }
    }

    if (!акт || typeof акт !== "object") акт = { тип: "ждать" };
    var tp = акт.тип;
    if (tp === "идти" || tp === "бить") {
      var dd = акт.куда;
      if (!(dd === "С" || dd === "Ю" || dd === "З" || dd === "В")) акт = { тип: "ждать" };
    } else if (!(tp === "взять" || tp === "положить" || tp === "ждать")) {
      акт = { тип: "ждать" };
    }

    об.m = M.join("");
    return { действие: акт, память: пм, общая: об };
  } catch (e) {
    return {
      действие: { тип: "ждать" },
      память: (в && в.память && typeof в.память === "object" && !Array.isArray(в.память)) ? в.память : {},
      общая: (в && в.общая && typeof в.общая === "object" && !Array.isArray(в.общая)) ? в.общая : {}
    };
  }
}
DeepSeek V4 Pro 5.5 еды за партию · потеряно муравьёв 0 · сбоев 4
код алгоритма (16.2 КБ)
function решить(в) {
  const N = 15;
  const INF = 1e9;
  const ход = в.ход;
  const я = в.я;
  const п = в.память || {};
  const о = в.общая || {};
  const bx = в.моя_база.x, by = в.моя_база.y;
  const ex = в.чужая_база.x, ey = в.чужая_база.y;
  const myIdStr = String(я.id);
  const myNum = parseInt(myIdStr.replace(/\D/g, ''), 10) || 0;
  const role = myNum % 4;
  const selfIdx = (я.y * N) + я.x;

  function idx(x, y) { return y * N + x; }
  function key(x, y) { return x + ',' + y; }
  function out(x, y) { return x < 0 || y < 0 || x >= N || y >= N; }
  function man(x1, y1, x2, y2) { return Math.abs(x1 - x2) + Math.abs(y1 - y2); }
  function dirTo(sx, sy, nx, ny) {
    if (nx === sx && ny === sy - 1) return 'С';
    if (nx === sx && ny === sy + 1) return 'Ю';
    if (nx === sx + 1 && ny === sy) return 'В';
    if (nx === sx - 1 && ny === sy) return 'З';
    return null;
  }
  function parseKey(k) {
    let i = k.indexOf(',');
    return [+k.slice(0, i), +k.slice(i + 1)];
  }

  if (!о.к || typeof о.к !== 'string' || о.к.length !== N * N) {
    let s = '';
    for (let i = 0; i < N * N; i++) s += '?';
    о.к = s;
  }
  о.еда = о.еда || {};
  о.еда_сим = о.еда_сим || {};
  о.видел = о.видел || {};
  о.цели = о.цели || {};
  о.анты = о.анты || {};
  о.враги = о.враги || {};
  о.зоны = о.зоны || {};
  о.удары = о.удары || {};

  function setOne(з, x, y, isWall) {
    if (out(x, y)) return;
    let i = idx(x, y);
    if (з.к[i] !== '?') return;
    з.к = з.к.substring(0, i) + (isWall ? '#' : '.') + з.к.substring(i + 1);
  }

  function setMapSym(з, x, y, isWall) {
    setOne(з, x, y, isWall);
    setOne(з, N - 1 - x, N - 1 - y, isWall);
  }

  о.анты[myIdStr] = [я.x, я.y, я.здоровье, я.груз, ход].join('|');

  let враги = [];
  let свои = [];
  let виделВрага = {};
  let виделСоюзника = {};
  let currentFood = 0;

  for (let vi = 0; vi < в.видно.length; vi++) {
    let кл = в.видно[vi];
    let x = кл.x, y = кл.y;
    let k = key(x, y);
    о.видел[k] = ход;

    setMapSym(о, x, y, кл.что === 'стена');

    if (кл.еда > 0) {
      о.еда[k] = кл.еда + ',' + ход;
      delete о.еда_сим[k];
      if (x === я.x && y === я.y) currentFood = кл.еда;

      let mx = N - 1 - x, my = N - 1 - y;
      let mk = key(mx, my);
      if (!о.еда[mk] && !о.видел[mk]) {
        о.еда[mk] = кл.еда + ',' + ход;
        о.еда_сим[mk] = ход;
      }
    } else {
      delete о.еда[k];
      delete о.еда_сим[k];
    }

    if (кл.что === 'враг') {
      if (!виделВрага[k]) {
        виделВрага[k] = true;
        враги.push({ x: x, y: y });
      }
      о.враги[k] = ход;
      for (let dx = -2; dx <= 2; dx++) {
        for (let dy = -2; dy <= 2; dy++) {
          let nx = x + dx, ny = y + dy;
          if (!out(nx, ny) && Math.abs(dx) + Math.abs(dy) <= 2) {
            о.зоны[key(nx, ny)] = ход;
          }
        }
      }
    } else if (кл.что === 'свой') {
      if (x !== я.x || y !== я.y) {
        if (!виделСоюзника[k]) {
          виделСоюзника[k] = true;
          свои.push({ x: x, y: y });
        }
      }
    }
  }

  let blocked = {};
  for (let i = 0; i < враги.length; i++) blocked[key(враги[i].x, враги[i].y)] = true;
  for (let i = 0; i < свои.length; i++) blocked[key(свои[i].x, свои[i].y)] = true;
  for (let id in о.анты) {
    if (id === myIdStr) continue;
    let p = о.анты[id].split('|');
    if (p.length < 5) continue;
    let ax = +p[0], ay = +p[1], at = +p[4];
    if (ход - at <= 1 && !out(ax, ay)) blocked[key(ax, ay)] = true;
  }

  for (let id in о.цели) {
    let p = о.цели[id];
    if (!p) continue;
    let arr = String(p).split(',');
    if (arr.length >= 3 && ход - (+arr[2]) > 8) delete о.цели[id];
  }
  for (let id in о.анты) {
    let p = о.анты[id].split('|');
    if (p.length >= 5 && ход - (+p[4]) > 30) delete о.анты[id];
  }
  for (let k in о.враги) {
    if (ход - о.враги[k] > 3) delete о.враги[k];
  }
  for (let k in о.зоны) {
    if (ход - о.зоны[k] > 5) delete о.зоны[k];
  }
  for (let k in о.удары) {
    let p = String(о.удары[k]).split(',');
    if (p.length >= 2 && ход - (+p[1]) > 20) delete о.удары[k];
  }

  function dangerAt(nx, ny) {
    let d = 0;
    for (let k in о.зоны) {
      let age = ход - о.зоны[k];
      if (age < 0 || age > 3) continue;
      let q = parseKey(k);
      let md = man(nx, ny, q[0], q[1]);
      if (md <= 2) d += (3 - md) * (age === 0 ? 12 : 6);
    }
    for (let k in о.враги) {
      let age = ход - о.враги[k];
      if (age < 0 || age > 1) continue;
      let q = parseKey(k);
      let md = man(nx, ny, q[0], q[1]);
      if (md <= 2) d += (3 - md) * 18;
    }
    return d;
  }

  function reconstructStart(ci, startIdx, prev) {
    if (ci === startIdx) return startIdx;
    let cur = ci;
    let guard = 0;
    while (prev[cur] !== -1 && prev[cur] !== startIdx && guard < 300) {
      cur = prev[cur];
      guard++;
    }
    return cur;
  }

  function findPaths(sx, sy, targetFn, blockedMap, ctx) {
    let startIdx = idx(sx, sy);
    let dist = new Array(N * N);
    let prev = new Array(N * N);
    let done = new Array(N * N);
    for (let i = 0; i < N * N; i++) {
      dist[i] = INF;
      prev[i] = -1;
      done[i] = 0;
    }
    dist[startIdx] = 0;

    let heap = [{ i: startIdx, d: 0 }];

    function heapPush(node) {
      heap.push(node);
      let i = heap.length - 1;
      while (i > 0) {
        let p = (i - 1) >> 1;
        if (heap[p].d <= heap[i].d) break;
        let t = heap[p]; heap[p] = heap[i]; heap[i] = t;
        i = p;
      }
    }
    function heapPop() {
      let top = heap[0];
      let last = heap.pop();
      if (heap.length) {
        heap[0] = last;
        let i = 0;
        while (true) {
          let l = i * 2 + 1, r = i * 2 + 2, m = i;
          if (l < heap.length && heap[l].d < heap[m].d) m = l;
          if (r < heap.length && heap[r].d < heap[m].d) m = r;
          if (m === i) break;
          let t = heap[i]; heap[i] = heap[m]; heap[m] = t;
          i = m;
        }
      }
      return top;
    }

    function cellCost(nx, ny, ch) {
      let c = 1;
      if (ch === '?') c += ctx.unknownPenalty || 3;
      let dng = dangerAt(nx, ny);
      if (dng > 0) {
        if (ctx.cargo) c += dng * 3;
        else if (ctx.hp <= 1) c += dng * 5;
        else if (ctx.hp === 2) c += dng * 2;
        else c += dng;
      }
      return c;
    }

    let results = [];
    let dirs4 = [[0, -1], [0, 1], [-1, 0], [1, 0]];

    while (heap.length && results.length < ctx.max) {
      let cur = heapPop();
      let ci = cur.i;
      if (done[ci]) continue;
      done[ci] = 1;

      let cx = ci % N;
      let cy = (ci / N) | 0;

      if (targetFn(cx, cy) && !blockedMap[key(cx, cy)]) {
        let next = reconstructStart(ci, startIdx, prev);
        results.push({ x: cx, y: cy, cost: cur.d, next: next });
        if (results.length >= ctx.max) break;
      }

      for (let d = 0; d < 4; d++) {
        let nx = cx + dirs4[d][0];
        let ny = cy + dirs4[d][1];
        if (out(nx, ny)) continue;
        let ni = idx(nx, ny);
        if (done[ni]) continue;
        let ch = о.к[ni];
        if (ch === '#') continue;
        if (!ctx.allowUnknown && ch === '?') continue;
        let nk = key(nx, ny);
        if (blockedMap[nk]) continue;

        let nd = cur.d + cellCost(nx, ny, ch);
        if (nd < dist[ni]) {
          dist[ni] = nd;
          prev[ni] = ci;
          heapPush({ i: ni, d: nd });
        }
      }
    }

    return results;
  }

  function actionFromNext(nextIdx) {
    if (nextIdx === undefined || nextIdx === null || nextIdx < 0) return null;
    if (nextIdx === selfIdx) return null;
    let nx = nextIdx % N;
    let ny = (nextIdx / N) | 0;
    let d = dirTo(я.x, я.y, nx, ny);
    if (!d) return null;
    return { тип: 'идти', куда: d };
  }

  function countSupport(enemy) {
    let cnt = 0;
    if (man(я.x, я.y, enemy.x, enemy.y) === 1) cnt++;
    for (let i = 0; i < свои.length; i++) {
      if (man(свои[i].x, свои[i].y, enemy.x, enemy.y) === 1) cnt++;
    }
    return cnt;
  }

  let adjacentEnemies = [];
  for (let i = 0; i < враги.length; i++) {
    if (man(я.x, я.y, враги[i].x, враги[i].y) === 1) adjacentEnemies.push(враги[i]);
  }

  function chooseFight() {
    let best = null;
    let bestScore = -9999;
    for (let i = 0; i < adjacentEnemies.length; i++) {
      let e = adjacentEnemies[i];
      let ek = key(e.x, e.y);
      let dmg = 0;
      if (о.удары[ek]) {
        let p = String(о.удары[ek]).split(',');
        if (p.length >= 2 && ход - (+p[1]) <= 10) dmg = +p[0] || 0;
      }
      let support = countSupport(e);
      let dBase = man(e.x, e.y, bx, by);
      let score = support * 3 + dmg * 5;
      if (dBase <= 3) score += 4;
      if (я.груз) score -= 4;
      if (я.здоровье === 1) score -= 6;
      else if (я.здоровье === 2) score -= 2;
      if (score > bestScore) {
        bestScore = score;
        best = e;
      }
    }
    if (!best) return null;

    let ek = key(best.x, best.y);
    let dmg = 0;
    if (о.удары[ek]) {
      let p = String(о.удары[ek]).split(',');
      if (p.length >= 2 && ход - (+p[1]) <= 10) dmg = +p[0] || 0;
    }
    let support = countSupport(best);
    let dBase = man(best.x, best.y, bx, by);

    if (support >= 2 || dmg >= 2) return best;
    if (support >= 1 && я.здоровье >= 3 && !я.груз && dBase <= 4) return best;
    if (support >= 1 && я.здоровье >= 3 && !я.груз && dmg >= 1) return best;
    return null;
  }

  function goDangerAway() {
    let dirs = [[0, -1, 'С'], [0, 1, 'Ю'], [-1, 0, 'З'], [1, 0, 'В']];
    let bestDir = null;
    let bestDanger = Infinity;
    let bestTie = 999;
    for (let d = 0; d < 4; d++) {
      let nx = я.x + dirs[d][0];
      let ny = я.y + dirs[d][1];
      if (out(nx, ny)) continue;
      let ni = idx(nx, ny);
      if (о.к[ni] === '#') continue;
      if (blocked[key(nx, ny)]) continue;
      let dng = dangerAt(nx, ny);
      let tie = (nx * 5 + ny * 7 + myNum * 3) % 11;
      if (dng < bestDanger || (dng === bestDanger && tie < bestTie)) {
        bestDanger = dng;
        bestTie = tie;
        bestDir = dirs[d][2];
      }
    }
    return bestDir ? { тип: 'идти', куда: bestDir } : null;
  }

  function goToBase(carrying) {
    let baseKey = key(bx, by);
    let destKeys = [];
    if (!blocked[baseKey]) destKeys.push(baseKey);

    let dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]];
    for (let d = 0; d < 4; d++) {
      let nx = bx + dirs[d][0];
      let ny = by + dirs[d][1];
      if (out(nx, ny)) continue;
      let ni = idx(nx, ny);
      if (о.к[ni] === '#') continue;
      let nk = key(nx, ny);
      if (blocked[nk]) continue;
      if (destKeys.indexOf(nk) < 0) destKeys.push(nk);
    }
    if (!destKeys.length) return null;

    let found = findPaths(
      я.x,
      я.y,
      function (x, y) { return destKeys.indexOf(key(x, y)) !== -1; },
      blocked,
      {
        allowUnknown: true,
        cargo: !!carrying,
        hp: я.здоровье,
        max: 8,
        unknownPenalty: carrying ? 4 : 2
      }
    );

    if (!found.length) return null;
    return actionFromNext(found[0].next);
  }

  function targetFood(x, y) {
    let f = о.еда[key(x, y)];
    if (!f) return false;
    let amt = parseInt(String(f).split(',')[0], 10);
    return amt > 0;
  }

  function goFood() {
    let found = findPaths(
      я.x,
      я.y,
      targetFood,
      blocked,
      {
        allowUnknown: true,
        cargo: false,
        hp: я.здоровье,
        max: 25,
        unknownPenalty: 2
      }
    );
    if (!found.length) return null;

    let best = null;
    let bestScore = Infinity;
    let bestKey = '';

    for (let i = 0; i < found.length; i++) {
      let cand = found[i];
      let ck = key(cand.x, cand.y);
      let f = String(о.еда[ck]);
      let arr = f.split(',');
      let amt = +arr[0] || 0;
      let last = +arr[1] || ход;
      let age = ход - last;
      let homeD = man(cand.x, cand.y, bx, by);
      let enemyD = man(cand.x, cand.y, ex, ey);

      let bias = (role % 2 === 0) ? 2.5 : -2.5;
      let score = cand.cost * 2 + homeD * 1.2 - amt * 3;
      score += bias * (homeD - enemyD);
      score += Math.min(age, 20) * 0.3;

      let claimed = 0;
      for (let id in о.цели) {
        if (id === myIdStr) continue;
        let c = о.цели[id];
        if (!c) continue;
        let p = String(c).split(',');
        if (p.length >= 3 && p[0] == cand.x && p[1] == cand.y) {
          if (ход - (+p[2]) <= 8) claimed++;
        }
      }
      if (amt >= 3) score += claimed * 6;
      else if (amt === 2) score += claimed * 25;
      else score += claimed * 60;

      score += (cand.x * 7 + cand.y * 11 + myNum * 3) % 9;

      if (score < bestScore || (score === bestScore && ck < bestKey)) {
        best = cand;
        bestScore = score;
        bestKey = ck;
      }
    }

    if (!best) return null;
    п.цель = best.x + ',' + best.y + ',' + ход;
    о.цели[myIdStr] = п.цель;

    if (best.next === selfIdx) return { тип: 'взять' };
    return actionFromNext(best.next);
  }

  function goExplore() {
    let found = findPaths(
      я.x,
      я.y,
      function (x, y) { return о.к[idx(x, y)] === '?'; },
      blocked,
      {
        allowUnknown: true,
        cargo: false,
        hp: я.здоровье,
        max: 25,
        unknownPenalty: 2
      }
    );
    if (!found.length) return null;

    let best = null;
    let bestScore = Infinity;
    let bestKey = '';

    for (let i = 0; i < found.length; i++) {
      let cand = found[i];
      let ck = key(cand.x, cand.y);
      let unknownCnt = 0;
      let dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]];
      for (let d = 0; d < 4; d++) {
        let nx = cand.x + dirs[d][0];
        let ny = cand.y + dirs[d][1];
        if (!out(nx, ny) && о.к[idx(nx, ny)] === '?') unknownCnt++;
      }

      let homeD = man(cand.x, cand.y, bx, by);
      let enemyD = man(cand.x, cand.y, ex, ey);
      let bias = (role % 2 === 0) ? 2.0 : -2.0;
      let score = cand.cost * 2 - unknownCnt * 4 + bias * (enemyD - homeD);

      let claimed = 0;
      for (let id in о.цели) {
        if (id === myIdStr) continue;
        let c = о.цели[id];
        if (!c) continue;
        let p = String(c).split(',');
        if (p.length >= 3 && p[0] == cand.x && p[1] == cand.y) {
          if (ход - (+p[2]) <= 8) claimed++;
        }
      }
      score += claimed * 30;
      score += (cand.x * 5 + cand.y * 13 + myNum * 7) % 11;

      if (score < bestScore || (score === bestScore && ck < bestKey)) {
        best = cand;
        bestScore = score;
        bestKey = ck;
      }
    }

    if (!best) return null;
    п.цель = best.x + ',' + best.y + ',' + ход;
    о.цели[myIdStr] = п.цель;
    return actionFromNext(best.next);
  }

  if (я.груз === 1 && я.x === bx && я.y === by) {
    return { действие: { тип: 'положить' }, память: п, общая: о };
  }

  let fight = chooseFight();
  if (fight) {
    let ek = key(fight.x, fight.y);
    let old = 0;
    if (о.удары[ek]) {
      let p = String(о.удары[ek]).split(',');
      old = +p[0] || 0;
    }
    о.удары[ek] = (old + 1) + ',' + ход;
    let fightDir = dirTo(я.x, я.y, fight.x, fight.y);
    if (fightDir) {
      return { действие: { тип: 'бить', куда: fightDir }, память: п, общая: о };
    }
  }

  let remaining = в.всего_ходов - ход;

  if (я.груз === 1) {
    let baseAct = goToBase(true);
    if (baseAct) return { действие: baseAct, память: п, общая: о };

    if (man(я.x, я.y, bx, by) === 1 && blocked[key(bx, by)]) {
      return { действие: { тип: 'ждать' }, память: п, общая: о };
    }

    let awayAct = goDangerAway();
    if (awayAct) return { действие: awayAct, память: п, общая: о };

    if (adjacentEnemies.length) {
      let d = dirTo(я.x, я.y, adjacentEnemies[0].x, adjacentEnemies[0].y);
      if (d) return { действие: { тип: 'бить', куда: d }, память: п, общая: о };
    }

    return { действие: { тип: 'ждать' }, память: п, общая: о };
  }

  if (currentFood > 0) {
    return { действие: { тип: 'взять' }, память: п, общая: о };
  }

  if (remaining > 0 && remaining < man(я.x, я.y, bx, by) + 2) {
    let baseAct = goToBase(false);
    if (baseAct) return { действие: baseAct, память: п, общая: о };
  }

  let foodAct = goFood();
  if (foodAct) return { действие: foodAct, память: п, общая: о };

  let exploreAct = goExplore();
  if (exploreAct) return { действие: exploreAct, память: п, общая: о };

  let fallback = goToBase(false) || goDangerAway();
  if (fallback) return { действие: fallback, память: п, общая: о };

  return { действие: { тип: 'ждать' }, память: п, общая: о };
}
Qwen 3.8 Max 8.7 еды за партию · потеряно муравьёв 5 · сбоев 0
код алгоритма (34.3 КБ)
function решить(в) {
  try {
    function num(v, d) {
      return (typeof v === "number" && isFinite(v)) ? (v | 0) : d;
    }

    const size = num(в && в.размер, 15) || 15;
    const turn = num(в && в.ход, 0);
    const total = num(в && в.всего_ходов, 150);
    const rem = total - turn;

    function inB(x, y) {
      return x >= 0 && x < size && y >= 0 && y < size;
    }

    const me = (в && в.я && typeof в.я === "object") ? в.я : {};
    let hx = num(me.x, 0);
    let hy = num(me.y, 0);
    if (!inB(hx, hy)) { hx = 0; hy = 0; }

    const hp = num(me.здоровье, 3);
    const carry = me.груз ? 1 : 0;

    const baseIn = (в && в.моя_база && typeof в.моя_база === "object") ? в.моя_база : { x: 1, y: 1 };
    const enemyBaseIn = (в && в.чужая_база && typeof в.чужая_база === "object") ? в.чужая_база : { x: 13, y: 13 };

    let bx = num(baseIn.x, 1), by = num(baseIn.y, 1);
    let ex = num(enemyBaseIn.x, 13), ey = num(enemyBaseIn.y, 13);
    if (!inB(bx, by)) { bx = 1; by = 1; }
    if (!inB(ex, ey)) { ex = 13; ey = 13; }

    const role = ((num(me.id, 0) % 4) + 4) % 4;
    const N = size * size;
    const mask = (1 << size) - 1;

    const T_UNSEEN = -1;
    const T_EMPTY = 0;
    const T_WALL = 1;
    const T_ALLY = 2;
    const T_ENEMY = 3;
    const T_BASE = 4;

    const DIRS = [
      { d: "С", dx: 0, dy: -1 },
      { d: "В", dx: 1, dy: 0 },
      { d: "Ю", dx: 0, dy: 1 },
      { d: "З", dx: -1, dy: 0 }
    ];
    const OPP = { "С": "Ю", "Ю": "С", "В": "З", "З": "В" };

    const imul = Math.imul ? Math.imul : function (a, b) {
      return ((a | 0) * (b | 0)) | 0;
    };

    function man(ax, ay, bx2, by2) {
      return Math.abs(ax - bx2) + Math.abs(ay - by2);
    }

    function dirName(dx, dy) {
      if (dy < 0) return "С";
      if (dy > 0) return "Ю";
      if (dx < 0) return "З";
      return "В";
    }

    function clamp(v, a, b) {
      return v < a ? a : (v > b ? b : v);
    }

    function hash2(a, b) {
      let h = imul(a | 0, 374761393) ^ imul(b | 0, 668265263);
      h = imul(h ^ (h >>> 13), 1274126177);
      return (h ^ (h >>> 16)) >>> 0;
    }

    function hash4(a, b, c, d) {
      let h = imul(a | 0, 374761393) ^ imul(b | 0, 668265263) ^ imul(c | 0, 2246822519) ^ imul(d | 0, 3266489917);
      h = imul(h ^ (h >>> 13), 1274126177);
      return (h ^ (h >>> 16)) >>> 0;
    }

    const memIn = (в && в.память && typeof в.память === "object") ? в.память : {};
    let blk = {};
    if (memIn.blk && typeof memIn.blk === "object" && !Array.isArray(memIn.blk)) {
      let cnt = 0;
      for (const k in memIn.blk) {
        const v = memIn.blk[k] | 0;
        if (v > turn) {
          blk[k] = v;
          cnt++;
        }
      }
      if (cnt > 12) {
        const keys = Object.keys(blk).sort((a, b) => blk[a] - blk[b]);
        for (let i = 0; i < keys.length - 12; i++) delete blk[keys[i]];
      }
    }

    const mem = {
      px: num(memIn.px, -1),
      py: num(memIn.py, -1),
      stuck: num(memIn.stuck, 0),
      hp: num(memIn.hp, 3),
      tkey: num(memIn.tkey, -1),
      ld: typeof memIn.ld === "string" ? memIn.ld : "",
      la: typeof memIn.la === "string" ? memIn.la : "",
      atkKey: num(memIn.atkKey, -1),
      atkTurn: num(memIn.atkTurn, -1),
      wx: num(memIn.wx, -1),
      wy: num(memIn.wy, -1),
      blk: blk
    };

    function addBlock(k, until) {
      if (k < 0 || k >= N) return;
      const key = String(k);
      mem.blk[key] = until;
      const keys = Object.keys(mem.blk);
      if (keys.length > 12) {
        let old = keys[0];
        let oldV = mem.blk[old] | 0;
        for (const kk of keys) {
          const vv = mem.blk[kk] | 0;
          if (vv < oldV) {
            oldV = vv;
            old = kk;
          }
        }
        delete mem.blk[old];
      }
    }

    const comIn = (в && в.общая && typeof в.общая === "object") ? в.общая : {};
    const com = { w: [], s: [], f: [], e: [], c: [], a: [] };

    for (let i = 0; i < size; i++) {
      const wv = Array.isArray(comIn.w) ? comIn.w[i] : undefined;
      const sv = Array.isArray(comIn.s) ? comIn.s[i] : undefined;
      com.w[i] = (typeof wv === "number" && isFinite(wv)) ? (wv | 0) & mask : 0;
      com.s[i] = (typeof sv === "number" && isFinite(sv)) ? (sv | 0) & mask : 0;
    }

    if (Array.isArray(comIn.f)) {
      for (const it of comIn.f) {
        if (Array.isArray(it) && it.length >= 3) {
          const k = it[0] | 0;
          const amt = it[1] | 0;
          const ts = it[2] | 0;
          if (k >= 0 && k < N && amt > 0) com.f.push([k, amt, ts]);
        }
      }
    }

    if (Array.isArray(comIn.e)) {
      for (const it of comIn.e) {
        if (Array.isArray(it) && it.length >= 2) {
          const k = it[0] | 0;
          const ts = it[1] | 0;
          if (k >= 0 && k < N) com.e.push([k, ts]);
        }
      }
    }

    if (Array.isArray(comIn.c)) {
      for (const it of comIn.c) {
        if (Array.isArray(it) && it.length >= 3) {
          const cid = it[0] | 0;
          const ck = it[1] | 0;
          const ct = it[2] | 0;
          if (ck >= 0 && ck < N) com.c.push([cid, ck, ct]);
        }
      }
    }

    if (Array.isArray(comIn.a)) {
      for (const it of comIn.a) {
        if (Array.isArray(it) && it.length >= 6) {
          const aid = it[0] | 0;
          const ax = it[1] | 0;
          const ay = it[2] | 0;
          const ahp = it[3] | 0;
          const ac = it[4] | 0;
          const at = it[5] | 0;
          if (inB(ax, ay)) com.a.push([aid, ax, ay, ahp, ac, at]);
        }
      }
    }

    function applyWall(x, y, val) {
      if (!inB(x, y)) return;
      if (val) com.w[y] = (com.w[y] | (1 << x)) & mask;
      else com.w[y] = (com.w[y] & ~(1 << x)) & mask;
    }

    function setWallSym(x, y, val) {
      applyWall(x, y, val);
      const mx = size - 1 - x;
      const my = size - 1 - y;
      if (mx !== x || my !== y) applyWall(mx, my, val);
    }

    function upsertFood(k, amt, ts) {
      for (let i = 0; i < com.f.length; i++) {
        if (com.f[i][0] === k) {
          com.f[i][1] = amt;
          com.f[i][2] = ts;
          return;
        }
      }
      com.f.push([k, amt, ts]);
    }

    function removeFood(k) {
      for (let i = com.f.length - 1; i >= 0; i--) {
        if (com.f[i][0] === k) com.f.splice(i, 1);
      }
    }

    function upsertEnemy(k, ts) {
      for (let i = 0; i < com.e.length; i++) {
        if (com.e[i][0] === k) {
          com.e[i][1] = ts;
          return;
        }
      }
      com.e.push([k, ts]);
    }

    const tArr = new Array(N).fill(T_UNSEEN);
    const fArr = new Array(N).fill(0);
    const localFood = [];
    const visibleEnemies = [];
    const visibleAllies = [];

    const vis = (в && Array.isArray(в.видно)) ? в.видно : [];
    for (const c of vis) {
      if (!c || typeof c !== "object") continue;
      const x = num(c.x, -1);
      const y = num(c.y, -1);
      if (!inB(x, y)) continue;

      const k = y * size + x;
      const isWall = c.что === "стена";
      setWallSym(x, y, isWall);
      com.s[y] = (com.s[y] | (1 << x)) & mask;

      let t = T_EMPTY;
      if (isWall) t = T_WALL;
      else if (c.что === "свой") t = T_ALLY;
      else if (c.что === "враг") t = T_ENEMY;
      else if (c.что === "база") t = T_BASE;

      const food = isWall ? 0 : num(c.еда, 0);
      tArr[k] = t;
      fArr[k] = food;

      if (food > 0) {
        upsertFood(k, food, turn);
        localFood.push({ k: k, amt: food, x: x, y: y });
      } else {
        removeFood(k);
      }

      if (t === T_ENEMY) {
        visibleEnemies.push({ x: x, y: y, k: k });
        upsertEnemy(k, turn);
      } else if (t === T_ALLY && !(x === hx && y === hy)) {
        visibleAllies.push({ x: x, y: y, k: k });
      }
    }

    const selfK = hy * size + hx;
    if (tArr[selfK] === T_UNSEEN) tArr[selfK] = T_ALLY;

    com.a.push([role, hx, hy, hp, carry, turn]);

    function knownWall(x, y) {
      if (!inB(x, y)) return true;
      if ((x === bx && y === by) || (x === ex && y === ey)) return false;
      if (x === 0 || y === 0 || x === size - 1 || y === size - 1) return true;
      const k = y * size + x;
      if (tArr[k] !== T_UNSEEN) return tArr[k] === T_WALL;
      return (((com.w[y] || 0) >>> x) & 1) !== 0;
    }

    function occupiedVisible(x, y) {
      if (!inB(x, y)) return true;
      if (x === hx && y === hy) return false;
      const t = tArr[y * size + x];
      return t === T_ALLY || t === T_ENEMY;
    }

    (function sanitize() {
      const fmap = Object.create(null);
      for (const it of com.f) {
        if (!Array.isArray(it) || it.length < 3) continue;
        const k = it[0] | 0;
        const amt = it[1] | 0;
        const ts = it[2] | 0;
        if (k < 0 || k >= N || amt <= 0) continue;
        if (turn - ts > 50) continue;
        const x = k % size;
        const y = (k / size) | 0;
        if (!inB(x, y) || knownWall(x, y)) continue;
        const prev = fmap[k];
        if (!prev || ts > prev[2] || (ts === prev[2] && amt > prev[1])) fmap[k] = [k, amt, ts];
      }
      let fl = [];
      for (const key in fmap) fl.push(fmap[key]);
      fl.sort((a, b) => b[2] - a[2] || b[1] - a[1]);
      if (fl.length > 40) fl.length = 40;
      com.f = fl;

      const emap = Object.create(null);
      for (const it of com.e) {
        if (!Array.isArray(it) || it.length < 2) continue;
        const k = it[0] | 0;
        const ts = it[1] | 0;
        if (k < 0 || k >= N) continue;
        if (turn - ts > 8) continue;
        const prev = emap[k];
        if (!prev || ts > prev[1]) emap[k] = [k, ts];
      }
      let el = [];
      for (const key in emap) el.push(emap[key]);
      el.sort((a, b) => b[1] - a[1]);
      if (el.length > 12) el.length = 12;
      com.e = el;

      const cmap = Object.create(null);
      for (const it of com.c) {
        if (!Array.isArray(it) || it.length < 3) continue;
        const cid = it[0] | 0;
        const ck = it[1] | 0;
        const ct = it[2] | 0;
        if (ck < 0 || ck >= N) continue;
        if (turn - ct > 6) continue;
        const x = ck % size;
        const y = (ck / size) | 0;
        if (knownWall(x, y)) continue;
        const prev = cmap[cid];
        if (!prev || ct > prev[2]) cmap[cid] = [cid, ck, ct];
      }
      let cl = [];
      for (const key in cmap) cl.push(cmap[key]);
      if (cl.length > 8) cl.length = 8;
      com.c = cl;

      const amap = Object.create(null);
      for (const it of com.a) {
        if (!Array.isArray(it) || it.length < 6) continue;
        const aid = it[0] | 0;
        const ax = it[1] | 0;
        const ay = it[2] | 0;
        const ahp = it[3] | 0;
        const ac = it[4] | 0;
        const at = it[5] | 0;
        if (!inB(ax, ay)) continue;
        if (turn - at > 3) continue;
        const prev = amap[aid];
        if (!prev || at > prev[5]) amap[aid] = [aid, ax, ay, ahp, ac, at];
      }
      let al = [];
      for (const key in amap) al.push(amap[key]);
      al.sort((a, b) => a[0] - b[0]);
      if (al.length > 4) al.length = 4;
      com.a = al;
    })();

    if (mem.tkey >= 0 && mem.tkey < N) {
      const tx = mem.tkey % size;
      const ty = (mem.tkey / size) | 0;
      if (knownWall(tx, ty) || (tArr[mem.tkey] !== T_UNSEEN && fArr[mem.tkey] <= 0)) {
        mem.tkey = -1;
      }
    }

    const samePos = mem.px === hx && mem.py === hy;
    mem.stuck = (samePos && mem.la === "идти") ? (mem.stuck + 1) : 0;
    const wasDamaged = mem.hp > hp;
    mem.hp = hp;

    if (mem.stuck >= 2 && mem.ld) {
      let dx = 0, dy = 0;
      if (mem.ld === "С") dy = -1;
      else if (mem.ld === "Ю") dy = 1;
      else if (mem.ld === "З") dx = -1;
      else if (mem.ld === "В") dx = 1;
      const nx = hx + dx, ny = hy + dy;
      if (inB(nx, ny)) addBlock(ny * size + nx, turn + 6);
    }

    if (mem.stuck >= 5) {
      if (mem.tkey >= 0) addBlock(mem.tkey, turn + 18);
      else if (mem.wx >= 0 && mem.wy >= 0) addBlock(mem.wy * size + mem.wx, turn + 12);
      mem.stuck = 0;
    }

    const adjEnemies = [];
    for (const e of visibleEnemies) {
      if (man(hx, hy, e.x, e.y) === 1) {
        adjEnemies.push({ x: e.x, y: e.y, k: e.k, dir: dirName(e.x - hx, e.y - hy) });
      }
    }

    function threatAt(x, y) {
      let s = 0;
      for (const e of visibleEnemies) {
        const d = man(x, y, e.x, e.y);
        if (d <= 1) s += 40;
        else if (d <= 2) s += 12;
        else if (d <= 3) s += 3;
      }
      for (const it of com.e) {
        if (turn - it[1] <= 2) {
          const px = it[0] % size;
          const py = (it[0] / size) | 0;
          const d = man(x, y, px, py);
          if (d <= 1) s += 15;
          else if (d <= 2) s += 5;
        }
      }
      if (wasDamaged && x === hx && y === hy) s += 10;
      return s;
    }

    function bfs(sx, sy, blockOcc) {
      const dist = new Array(N).fill(-1);
      if (!inB(sx, sy)) return dist;
      const sk = sy * size + sx;
      if (knownWall(sx, sy) && sk !== selfK) return dist;

      const q = new Array(N);
      let head = 0, tail = 0;
      dist[sk] = 0;
      q[tail++] = sk;

      while (head < tail) {
        const k = q[head++];
        const x = k % size;
        const y = (k / size) | 0;
        const nd = dist[k] + 1;

        for (let i = 0; i < 4; i++) {
          const nx = x + DIRS[i].dx;
          const ny = y + DIRS[i].dy;
          if (!inB(nx, ny)) continue;
          const nk = ny * size + nx;
          if (dist[nk] !== -1) continue;
          if (knownWall(nx, ny)) continue;
          if (blockOcc && occupiedVisible(nx, ny)) continue;
          dist[nk] = nd;
          q[tail++] = nk;
        }
      }

      return dist;
    }

    function chooseStep(tx, ty, opts) {
      opts = opts || {};
      if (!inB(tx, ty)) return null;
      if (tx === hx && ty === hy) return null;
      if (knownWall(tx, ty)) return null;

      const dist = bfs(tx, ty, false);
      const myD = dist[selfK];
      const curMan = man(hx, hy, tx, ty);
      const curThreat = threatAt(hx, hy);

      let best = null;
      let bestScore = 1e9;
      let bestProgress = false;

      for (const o of DIRS) {
        const nx = hx + o.dx;
        const ny = hy + o.dy;
        if (!inB(nx, ny)) continue;
        const nk = ny * size + nx;
        if (knownWall(nx, ny)) continue;
        if (occupiedVisible(nx, ny)) continue;

        const dGoal = dist[nk] >= 0 ? dist[nk] : man(nx, ny, tx, ty) + 20;
        const progress =
          (myD >= 0 && dist[nk] >= 0 && dist[nk] < myD) ||
          (myD < 0 && man(nx, ny, tx, ty) < curMan);

        let score = dGoal * 10;
        if (progress) score -= 70;
        else score += 45;

        const th = threatAt(nx, ny);
        score += th * ((opts.carry || opts.flee || hp <= 1) ? 3 : 1);

        if (curThreat > 0 && th < curThreat) score -= 18;
        if ((opts.carry || hp <= 1) && th >= 40) score += 90;

        if (nx === bx && ny === by) {
          if (opts.carry && tx === bx && ty === by) score -= 35;
          else if (!opts.carry && !(tx === bx && ty === by)) score += 130;
          else score += 15;
        }

        if (nx === ex && ny === ey) score += 45;
        if (!opts.carry && fArr[nk] > 0) score -= 10;
        if (mem.blk[String(nk)] > turn) score += 150;
        if (mem.stuck > 1 && mem.ld && o.d === OPP[mem.ld]) score += 30;
        score += hash4(nx, ny, role, turn) & 7;

        if (score < bestScore) {
          bestScore = score;
          best = o;
          bestProgress = progress;
        }
      }

      if (!best) return null;
      if (!bestProgress && !(opts.urgent || opts.carry || opts.flee || curThreat > 10 || mem.stuck >= 1)) {
        return null;
      }
      return { "тип": "идти", "куда": best.d };
    }

    function chooseEscape(forCarry) {
      const cx = size >> 1;
      const cy = size >> 1;
      const curScore = threatAt(hx, hy) * 4 +
        (forCarry ? man(hx, hy, bx, by) * 6 : (hp <= 1 ? man(hx, hy, bx, by) * 4 : man(hx, hy, cx, cy) * 2));

      let best = null;
      let bestScore = 1e9;

      for (const o of DIRS) {
        const nx = hx + o.dx;
        const ny = hy + o.dy;
        if (!inB(nx, ny)) continue;
        const nk = ny * size + nx;
        if (knownWall(nx, ny)) continue;
        if (occupiedVisible(nx, ny)) continue;

        const th = threatAt(nx, ny);
        let score = th * 4;

        if (forCarry) score += man(nx, ny, bx, by) * 6;
        else if (hp <= 1) score += man(nx, ny, bx, by) * 4;
        else score += man(nx, ny, cx, cy) * 2;

        if (!forCarry && fArr[nk] > 0) score -= 12;

        if (nx === bx && ny === by) {
          if (forCarry) score -= 20;
          else if (hp <= 1) score -= 10;
          else score += 50;
        }

        if (nx === ex && ny === ey) score += 50;
        if (mem.blk[String(nk)] > turn) score += 130;
        score += hash4(nx, ny, role, turn) & 7;

        if (score < bestScore) {
          bestScore = score;
          best = o;
        }
      }

      if (best && bestScore < curScore) return { "тип": "идти", "куда": best.d };
      return null;
    }

    function chooseAttack() {
      if (adjEnemies.length === 0) return null;

      let best = null;
      let bestScore = 1e9;

      for (const e of adjEnemies) {
        let score = 0;
        const dBase = man(e.x, e.y, bx, by);

        if (e.x === bx && e.y === by) score -= 180;
        if (dBase <= 2) score -= 80;
        if ((fArr[e.k] || 0) > 0) score -= 110;
        if (dBase < man(e.x, e.y, ex, ey)) score -= 40;

        let support = 0;
        for (const a of visibleAllies) {
          if (man(a.x, a.y, e.x, e.y) <= 1) support++;
        }
        if (support > 0) score -= 35 * support;

        if (hp === 3) score -= 20;
        else if (hp === 2) score += 5;
        else score += 220;

        if (carry) score += 140;
        if (adjEnemies.length >= 2) score += 100;
        if (mem.atkKey === e.k && mem.atkTurn === turn - 1) score -= 30;
        if (rem <= 12 && !(e.x === bx && e.y === by)) score += 50;

        score += dBase * 2;
        score += hash4(e.x, e.y, role, turn) & 7;

        if (score < bestScore) {
          bestScore = score;
          best = e;
        }
      }

      if (!best) return null;
      return { act: { "тип": "бить", "куда": best.dir }, key: best.k, score: bestScore };
    }

    function doTake() {
      const amt = fArr[selfK] > 0 ? fArr[selfK] : 0;
      if (amt <= 1) {
        removeFood(selfK);
        if (mem.tkey === selfK) mem.tkey = -1;
      } else {
        upsertFood(selfK, amt - 1, turn);
      }
      mem.stuck = 0;
      return { "тип": "взять" };
    }

    function chooseFoodTarget(tieMode) {
      const cand = [];
      const used = Object.create(null);

      function add(k, amt, ts) {
        if (amt <= 0 || used[k]) return;
        const x = k % size;
        const y = (k / size) | 0;
        if (!inB(x, y)) return;
        if (knownWall(x, y)) return;
        if (tArr[k] === T_WALL) return;
        used[k] = 1;
        cand.push({ k: k, x: x, y: y, amt: amt, ts: ts });
      }

      for (const it of localFood) add(it.k, it.amt, turn);
      for (const it of com.f) add(it[0] | 0, it[1] | 0, it[2] | 0);
      if (cand.length === 0) return null;

      const distSelf = bfs(hx, hy, false);
      const distBase = tieMode ? null : bfs(bx, by, false);

      let best = null;
      let bestScore = 1e9;

      for (const c of cand) {
        const dS = distSelf[c.k] >= 0 ? distSelf[c.k] : man(hx, hy, c.x, c.y) + 20;
        let dB = 0;

        if (!tieMode) {
          dB = distBase ? (distBase[c.k] >= 0 ? distBase[c.k] : man(c.x, c.y, bx, by) + 20) : man(c.x, c.y, bx, by) + 20;
          const canDeliver = (dS + dB + 2 <= rem) || (c.k === selfK && dB + 2 <= rem);
          if (rem <= 45 && !canDeliver) continue;
        } else {
          if (dS > rem) continue;
        }

        let score;
        if (tieMode) {
          score = dS * 12 - Math.min(c.amt, 4) * 4;
        } else {
          score = dS * 9 + dB * 13 - Math.min(c.amt, 4) * 7;
        }

        const age = Math.max(0, turn - c.ts);
        score += tieMode ? Math.min(age, 15) : Math.min(age, 30);

        const danger = threatAt(c.x, c.y);
        score += danger * (hp <= 1 ? 3 : (tieMode ? 2 : 1));

        let claims = 0;
        for (const cl of com.c) {
          if (cl[0] !== role && cl[1] === c.k && turn - cl[2] <= 6) claims++;
        }
        if (claims > 0) {
          if (c.amt <= claims) score += 170;
          else score += claims * 30;
        }

        for (const a of visibleAllies) {
          const ad = man(a.x, a.y, c.x, c.y);
          const md = man(hx, hy, c.x, c.y);
          if (ad < md) score += 16;
          else if (ad <= 1) score += 6;
        }

        for (const a of com.a) {
          if (a[0] !== role && turn - a[5] <= 2 && a[4] === 0) {
            const ad = man(a[1], a[2], c.x, c.y);
            const md = man(hx, hy, c.x, c.y);
            if (ad < md) score += 14;
            else if (ad === md && a[0] < role) score += 5;
          }
        }

        const slots = Math.min(c.amt, 4);
        let owned = 0;
        for (let s = 0; s < slots; s++) {
          if ((hash2(c.k, s * 7 + 13) & 3) === role) owned++;
        }
        if (owned > 0) score -= owned * 20;
        else score += 14;

        if (c.k === mem.tkey) score -= 20;
        if (mem.blk[String(c.k)] > turn) score += 150;
        if (c.k === selfK) score -= 30;
        if (tArr[c.k] === T_ALLY) score += 50;
        if (tArr[c.k] === T_ENEMY) score += 40;
        if (c.x === ex && c.y === ey) score += 30;
        if (!tieMode && rem <= 15 && dS > 1) score += 70;

        score += hash4(c.k, role, turn, rem) & 7;

        if (score < bestScore) {
          bestScore = score;
          best = c;
        }
      }

      return best;
    }

    function chooseExploreTarget() {
      const pts = [];
      for (let y = 2; y <= size - 3; y += 2) {
        for (let x = 2; x <= size - 3; x += 2) {
          if (!knownWall(x, y) && !(x === bx && y === by) && !(x === ex && y === ey)) {
            pts.push({ x: x, y: y });
          }
        }
      }
      const cx = size >> 1;
      const cy = size >> 1;
      if (!knownWall(cx, cy) && !(cx === bx && cy === by) && !(cx === ex && cy === ey)) {
        pts.push({ x: cx, y: cy });
      }
      if (pts.length === 0) return { x: hx, y: hy };

      const sx = ex > bx ? 1 : -1;
      const sy = ey > by ? 1 : -1;
      const anchors = [
        { x: clamp(bx + 4 * sx, 2, size - 3), y: clamp(by + 2 * sy, 2, size - 3) },
        { x: clamp(bx + 2 * sx, 2, size - 3), y: clamp(by + 4 * sy, 2, size - 3) },
        { x: clamp(bx + 6 * sx, 2, size - 3), y: clamp(by + 6 * sy, 2, size - 3) },
        { x: clamp(bx + 8 * sx, 2, size - 3), y: clamp(by + 8 * sy, 2, size - 3) }
      ];
      const anchor = anchors[role];

      let best = null, bestScore = 1e9;
      let second = null, secondScore = 1e9;

      for (const p of pts) {
        const seen = ((com.s[p.y] || 0) >>> p.x) & 1;
        let score = man(hx, hy, p.x, p.y) * 4 + (seen ? 28 : -35) + man(p.x, p.y, anchor.x, anchor.y) * 3;
        if (mem.stuck < 2 && mem.wx === p.x && mem.wy === p.y) score -= 35;
        if (p.x === ex && p.y === ey) score += 30;
        if (p.x === bx && p.y === by) score += 100;
        score += hash4(p.x, p.y, role, turn) & 15;

        if (score < bestScore) {
          second = best;
          secondScore = bestScore;
          best = p;
          bestScore = score;
        } else if (score < secondScore) {
          second = p;
          secondScore = score;
        }
      }

      if (best && best.x === hx && best.y === hy && second) best = second;
      if (!best) return { x: hx, y: hy };
      mem.wx = best.x;
      mem.wy = best.y;
      return best;
    }

    function fallbackExplore() {
      const cx = size >> 1;
      const cy = size >> 1;
      let best = null;
      let bestScore = 1e9;

      for (const o of DIRS) {
        const nx = hx + o.dx;
        const ny = hy + o.dy;
        if (!inB(nx, ny)) continue;
        const nk = ny * size + nx;
        if (knownWall(nx, ny)) continue;
        if (occupiedVisible(nx, ny)) continue;

        const seen = ((com.s[ny] || 0) >>> nx) & 1;
        let score = threatAt(nx, ny) * 2 + man(nx, ny, cx, cy) * 3 + (seen ? 12 : -18);
        if (nx === ex && ny === ey) score += 35;
        if (mem.blk[String(nk)] > turn) score += 120;
        score += hash4(nx, ny, role, turn) & 15;

        if (score < bestScore) {
          bestScore = score;
          best = o;
        }
      }

      if (!best) return null;
      return { "тип": "идти", "куда": best.d };
    }

    function chooseLeaveBase() {
      let best = null;
      let bestScore = 1e9;
      const cx = size >> 1;
      const cy = size >> 1;

      for (const o of DIRS) {
        const nx = hx + o.dx;
        const ny = hy + o.dy;
        if (!inB(nx, ny)) continue;
        const nk = ny * size + nx;
        if (knownWall(nx, ny)) continue;
        if (occupiedVisible(nx, ny)) continue;

        let score = threatAt(nx, ny) * 2;
        score -= man(nx, ny, bx, by) * 8;
        score += man(nx, ny, cx, cy) * 2;
        if (fArr[nk] > 0) score -= 25;
        if (nx === ex && ny === ey) score += 40;
        if (mem.blk[String(nk)] > turn) score += 120;
        score += hash4(nx, ny, role, turn) & 7;

        if (score < bestScore) {
          bestScore = score;
          best = o;
        }
      }

      if (!best) return null;
      return { "тип": "идти", "куда": best.d };
    }

    function ret(act) {
      if (!act || typeof act !== "object") act = { "тип": "ждать" };

      mem.px = hx;
      mem.py = hy;

      if (act.тип === "идти") {
        mem.ld = typeof act.куда === "string" ? act.куда : "";
        mem.la = "идти";
      } else {
        mem.ld = "";
        mem.la = typeof act.тип === "string" ? act.тип : "ждать";
      }

      if (act.тип === "взять" || act.тип === "положить" || act.тип === "бить") {
        mem.stuck = 0;
      }

      if (mem.atkTurn < turn - 1) {
        mem.atkKey = -1;
        mem.atkTurn = -1;
      }

      let willCarry = carry;
      if (act.тип === "взять") willCarry = 1;
      if (act.тип === "положить") willCarry = 0;

      const cc = [];
      const seen = Object.create(null);
      for (const cl of com.c) {
        if (!Array.isArray(cl) || cl.length < 3) continue;
        const cid = cl[0] | 0;
        const ck = cl[1] | 0;
        const ct = cl[2] | 0;
        if (turn - ct > 6) continue;
        if (ck < 0 || ck >= N) continue;
        if (cid === role) continue;
        if (seen[cid]) continue;
        const x = ck % size;
        const y = (ck / size) | 0;
        if (knownWall(x, y)) continue;
        seen[cid] = 1;
        cc.push([cid, ck, ct]);
      }

      if (!willCarry && mem.tkey >= 0 && mem.tkey < N) {
        const x = mem.tkey % size;
        const y = (mem.tkey / size) | 0;
        if (!knownWall(x, y)) cc.push([role, mem.tkey, turn]);
      }

      com.c = cc.slice(-8);

      return { "действие": act, "память": mem, "общая": com };
    }

    const onBase = hx === bx && hy === by;
    const hereFood = fArr[selfK] > 0 ? fArr[selfK] : 0;

    if (carry && onBase) {
      mem.tkey = -1;
      return ret({ "тип": "положить" });
    }

    if (rem <= 0) {
      if (!carry && hereFood > 0) return ret(doTake());
      return ret({ "тип": "ждать" });
    }

    if (onBase && !carry && adjEnemies.length === 0) {
      if (hereFood > 0) return ret(doTake());
      const lv = chooseLeaveBase();
      if (lv) return ret(lv);
    }

    if (adjEnemies.length > 0) {
      const many = adjEnemies.length >= 2;
      const enemyOnBase = adjEnemies.some(function (e) { return e.x === bx && e.y === by; });
      const enemyOnFood = adjEnemies.some(function (e) { return (fArr[e.k] || 0) > 0; });
      const enemyNearBase = adjEnemies.some(function (e) { return man(e.x, e.y, bx, by) <= 2; });
      const enemyInOurHalf = adjEnemies.some(function (e) {
        return man(e.x, e.y, bx, by) < man(e.x, e.y, ex, ey);
      });
      const lastTarget = mem.atkTurn === turn - 1 && adjEnemies.some(function (e) { return e.k === mem.atkKey; });
      const support = visibleAllies.filter(function (a) { return man(a.x, a.y, hx, hy) <= 1; }).length;

      if (carry) {
        if (enemyOnBase && hp >= 2 && (!many || support > 0 || hp === 3)) {
          const at = chooseAttack();
          if (at) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        if (man(hx, hy, bx, by) === 1 && !occupiedVisible(bx, by)) {
          return ret({ "тип": "идти", "куда": dirName(bx - hx, by - hy) });
        }

        if (man(hx, hy, bx, by) === 1 && occupiedVisible(bx, by)) {
          return ret({ "тип": "ждать" });
        }

        const esc = chooseEscape(true);
        if (esc && (hp <= 2 || many || wasDamaged || threatAt(hx, hy) >= 40 || !enemyOnBase)) {
          return ret(esc);
        }

        if (hp >= 2 && !many && (enemyOnBase || enemyOnFood || enemyNearBase || lastTarget)) {
          const at = chooseAttack();
          if (at) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        if (hp === 3 && !many && enemyInOurHalf) {
          const at = chooseAttack();
          if (at) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        const mv = chooseStep(bx, by, { carry: true, urgent: true });
        if (mv) return ret(mv);

        if (hp >= 2 && !many) {
          const at = chooseAttack();
          if (at) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        if (hp === 1 && !many && enemyOnBase) {
          const at = chooseAttack();
          if (at) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        return ret({ "тип": "ждать" });
      } else {
        if (hp <= 1) {
          const esc = chooseEscape(false);
          if (esc) return ret(esc);

          if (enemyOnBase && rem > 20) {
            const at = chooseAttack();
            if (at && at.score < 160) {
              mem.atkKey = at.key;
              mem.atkTurn = turn;
              return ret(at.act);
            }
          }

          if (hereFood > 0 && rem <= 8 && !many) return ret(doTake());
          return ret({ "тип": "ждать" });
        }

        const wantFight =
          enemyOnBase ||
          enemyOnFood ||
          enemyNearBase ||
          lastTarget ||
          (hp === 3 && !many && enemyInOurHalf) ||
          (support > 0 && !many && enemyInOurHalf);

        if (wantFight) {
          const at = chooseAttack();
          if (at && (at.score < 150 || enemyOnBase || enemyOnFood)) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        if (hereFood > 0 && rem <= 10 && !many && hp >= 2) return ret(doTake());

        const goal = (hp <= 2 || wasDamaged || enemyNearBase) ? { x: bx, y: by } : chooseExploreTarget();
        const mv = chooseStep(goal.x, goal.y, { flee: true, urgent: true, carry: false });
        if (mv) return ret(mv);

        if (hp >= 2 && !many) {
          const at = chooseAttack();
          if (at && at.score < 170) {
            mem.atkKey = at.key;
            mem.atkTurn = turn;
            return ret(at.act);
          }
        }

        if (hereFood > 0 && hp >= 2) return ret(doTake());
        return ret({ "тип": "ждать" });
      }
    }

    if (!carry && hereFood > 0) {
      return ret(doTake());
    }

    if (carry) {
      mem.tkey = -1;

      if (man(hx, hy, bx, by) === 1 && occupiedVisible(bx, by)) {
        return ret({ "тип": "ждать" });
      }

      if (man(hx, hy, bx, by) === 1 && !occupiedVisible(bx, by) && (rem > 10 || 2 <= rem)) {
        return ret({ "тип": "идти", "куда": dirName(bx - hx, by - hy) });
      }

      const distBase = bfs(bx, by, false);
      let dBase = distBase[selfK];
      if (dBase < 0) dBase = man(hx, hy, bx, by) + 20;

      if (rem <= 25) {
        if (dBase >= 0 && dBase + 1 <= rem) {
          const mv = chooseStep(bx, by, { carry: true, urgent: true });
          if (mv) return ret(mv);
        }

        if (threatAt(hx, hy) > 5) {
          const esc = chooseEscape(true);
          if (esc) return ret(esc);
        }

        return ret({ "тип": "ждать" });
      }

      const mv = chooseStep(bx, by, { carry: true, urgent: true });
      if (mv) return ret(mv);
      return ret({ "тип": "ждать" });
    }

    if (!carry && hp >= 2 && rem > 15 && visibleEnemies.length > 0) {
      let nearBase = false;
      for (const e of visibleEnemies) {
        if (man(e.x, e.y, bx, by) <= 3) {
          nearBase = true;
          break;
        }
      }
      if (nearBase && man(hx, hy, bx, by) <= 5) {
        const mv = chooseStep(bx, by, { urgent: true, carry: false });
        if (mv) return ret(mv);
      }
    }

    if (!carry) {
      let fg = chooseFoodTarget(false);
      if (!fg && rem <= 35) fg = chooseFoodTarget(true);

      if (fg) {
        mem.tkey = fg.k;

        if (fg.k === selfK) {
          if (hereFood > 0) return ret(doTake());
          mem.tkey = -1;
          addBlock(fg.k, turn + 8);
        } else {
          let mv = chooseStep(fg.x, fg.y, { carry: false, urgent: rem <= 25 });
          if (!mv) mv = chooseStep(fg.x, fg.y, { carry: false, urgent: true });
          if (mv) return ret(mv);

          if (mem.stuck >= 2 || (tArr[fg.k] !== T_UNSEEN && occupiedVisible(fg.x, fg.y))) {
            addBlock(fg.k, turn + 10);
            mem.tkey = -1;
          } else {
            return ret({ "тип": "ждать" });
          }
        }
      } else {
        mem.tkey = -1;
      }

      if (rem <= 10) {
        let bestF = null;
        let bd = 999;
        for (const it of localFood) {
          if (knownWall(it.x, it.y)) continue;
          const d = man(hx, hy, it.x, it.y);
          if (d <= rem && d < bd) {
            bd = d;
            bestF = it;
          }
        }
        if (bestF) {
          if (bestF.k === selfK) {
            if (hereFood > 0) return ret(doTake());
          } else {
            const mv = chooseStep(bestF.x, bestF.y, { carry: false, urgent: true });
            if (mv) return ret(mv);
          }
        }
      }

      const wp = chooseExploreTarget();
      if (wp.x !== hx || wp.y !== hy) {
        const mv = chooseStep(wp.x, wp.y, { carry: false, urgent: false });
        if (mv) return ret(mv);
      }

      const fb = fallbackExplore();
      if (fb) return ret(fb);

      if (hereFood > 0) return ret(doTake());
      return ret({ "тип": "ждать" });
    }

    return ret({ "тип": "ждать" });
  } catch (e) {
    return {
      "действие": { "тип": "ждать" },
      "память": (в && в.память) ? в.память : {},
      "общая": (в && в.общая) ? в.общая : {}
    };
  }
}
Gemini 3.7 Flash 13.7 еды за партию · потеряно муравьёв 12 · сбоев 0
код алгоритма (10.6 КБ)
function решить(вход) {
  try {
    const W = вход.размер || 15;
    const TOTAL = W * W;
    const me = вход.я;
    const base = вход.моя_база;
    const enemyBase = вход.чужая_база;
    const turn = вход.ход;
    const maxTurns = вход.всего_ходов || 150;
    const turnsLeft = maxTurns - turn;

    const DIRS = [
      { d: "С", dx: 0, dy: -1 },
      { d: "В", dx: 1, dy: 0 },
      { d: "Ю", dx: 0, dy: 1 },
      { d: "З", dx: -1, dy: 0 }
    ];

    function toIdx(x, y) { return y * W + x; }
    function toCoord(idx) { return { x: idx % W, y: (idx / W) | 0 }; }
    function manhattan(x1, y1, x2, y2) { return Math.abs(x1 - x2) + Math.abs(y1 - y2); }

    let общая = вход.общая || {};
    if (!общая.walls || общая.walls.length !== TOTAL) {
      общая.walls = new Array(TOTAL).fill(0); // 0: unknown, 1: wall, 2: open
      общая.food = new Array(TOTAL).fill(0);
      общая.seen = new Array(TOTAL).fill(0);
    }

    if (общая.turn !== turn) {
      общая.turn = turn;
      общая.reserved = {}; // idx -> antId
      общая.intentions = {}; // antId -> targetIdx
      общая.myAnts = {};
    }
    общая.reserved = общая.reserved || {};
    общая.intentions = общая.intentions || {};
    общая.myAnts = общая.myAnts || {};
    общая.myAnts[me.id] = { x: me.x, y: me.y, hp: me.здоровье, load: me.груз };

    const visibleMap = {};
    const visibleEnemies = [];
    let currentCellFood = 0;

    // 1. Обработка видимости и симметрии 180°
    for (let i = 0; i < вход.видно.length; i++) {
      const cell = вход.видно[i];
      const idx = toIdx(cell.x, cell.y);
      visibleMap[idx] = cell;

      const symX = W - 1 - cell.x;
      const symY = W - 1 - cell.y;
      const symIdx = toIdx(symX, symY);

      общая.seen[idx] = 1;
      общая.food[idx] = cell.еда || 0;

      if (cell.что === "стена") {
        общая.walls[idx] = 1;
        общая.walls[symIdx] = 1;
      } else {
        общая.walls[idx] = 2;
        if (общая.walls[symIdx] === 0) общая.walls[symIdx] = 2;
      }

      if (cell.x === me.x && cell.y === me.y) {
        currentCellFood = cell.еда || 0;
      } else if (cell.что === "враг") {
        visibleEnemies.push(cell);
      }
    }

    // Позиции своих муравьев на текущем ходу
    const occupiedByFriend = {};
    for (let i = 0; i < вход.видно.length; i++) {
      const c = вход.видно[i];
      if (c.что === "свой" && !(c.x === me.x && c.y === me.y)) {
        occupiedByFriend[toIdx(c.x, c.y)] = true;
      }
    }

    // Предварительный расчет дистанций от базы через BFS
    function bfsDistances(startX, startY) {
      const dists = new Int16Array(TOTAL).fill(-1);
      const q = [toIdx(startX, startY)];
      dists[toIdx(startX, startY)] = 0;
      let head = 0;
      while (head < q.length) {
        const cIdx = q[head++];
        const cx = cIdx % W;
        const cy = (cIdx / W) | 0;
        const cd = dists[cIdx];
        for (let i = 0; i < 4; i++) {
          const nx = cx + DIRS[i].dx;
          const ny = cy + DIRS[i].dy;
          if (nx < 0 || nx >= W || ny < 0 || ny >= W) continue;
          const nIdx = toIdx(nx, ny);
          if (общая.walls[nIdx] === 1 || dists[nIdx] !== -1) continue;
          dists[nIdx] = cd + 1;
          q.push(nIdx);
        }
      }
      return dists;
    }

    const distToBase = bfsDistances(base.x, base.y);

    // Действие: положить груз на базе
    if (me.x === base.x && me.y === base.y && me.груз > 0) {
      delete общая.intentions[me.id];
      return { действие: { тип: "положить" }, память: вход.память, общая: общая };
    }

    // Действие: взять еду под собой
    if (me.груз === 0 && currentCellFood > 0 && !(me.x === base.x && me.y === base.y)) {
      delete общая.intentions[me.id];
      общая.food[toIdx(me.x, me.y)] = Math.max(0, currentCellFood - 1);
      return { действие: { тип: "взять" }, память: вход.память, общая: общая };
    }

    // Проверка соседних врагов (Бой)
    let bestAttackDir = null;
    let maxAttackPriority = -1;

    for (let i = 0; i < DIRS.length; i++) {
      const dir = DIRS[i];
      const nx = me.x + dir.dx;
      const ny = me.y + dir.dy;
      if (nx < 0 || nx >= W || ny < 0 || ny >= W) continue;
      const cell = visibleMap[toIdx(nx, ny)];
      if (cell && cell.что === "враг") {
        let pri = 10;
        if (cell.еда > 0) pri += 100; // Сбить еду с врага
        if (me.груз === 0) pri += 30; // Без груза мы свободный боец
        if (pri > maxAttackPriority) {
          maxAttackPriority = pri;
          bestAttackDir = dir.d;
        }
      }
    }

    // Если мы на базе с грузом 0 - обязательно уходим, освобождая базу
    const isOnBaseEmpty = (me.x === base.x && me.y === base.y && me.груз === 0);

    // Если с грузом стоим прямо у базы - шаг на базу
    if (me.груз > 0) {
      for (let i = 0; i < DIRS.length; i++) {
        const dir = DIRS[i];
        if (me.x + dir.dx === base.x && me.y + dir.dy === base.y) {
          const tIdx = toIdx(base.x, base.y);
          общая.reserved[tIdx] = me.id;
          return { действие: { тип: "идти", куда: dir.d }, память: вход.память, общая: общая };
        }
      }
    }

    // Если враг несет еду - бьем его немедленно
    if (bestAttackDir && maxAttackPriority >= 100) {
      return { действие: { тип: "бить", куда: bestAttackDir }, память: вход.память, общая: общая };
    }

    // Если мы без груза и враг рядом, и мы не на базе
    if (bestAttackDir && me.груз === 0 && !isOnBaseEmpty && maxAttackPriority >= 30) {
      // Если рядом враг и у нас есть здоровье - можно ударить, либо идти за едой если она важнее
      if (currentCellFood === 0 && me.здоровье >= 2) {
        return { действие: { тип: "бить", куда: bestAttackDir }, память: вход.память, общая: общая };
      }
    }

    // Функция поиска пути к цели через BFS
    function findPath(targetCheckFn, preferFn) {
      const q = [{ x: me.x, y: me.y, firstDir: null, firstIdx: -1, dist: 0 }];
      const visited = new Uint8Array(TOTAL);
      visited[toIdx(me.x, me.y)] = 1;
      let head = 0;
      let bestChoice = null;
      let bestScore = -Infinity;

      while (head < q.length) {
        const curr = q[head++];

        if (curr.firstDir !== null && targetCheckFn(curr.x, curr.y, curr.dist)) {
          const score = preferFn ? preferFn(curr.x, curr.y, curr.dist) : -curr.dist;
          if (score > bestScore) {
            bestScore = score;
            bestChoice = { dir: curr.firstDir, nextIdx: curr.firstIdx, targetIdx: toIdx(curr.x, curr.y) };
          }
          if (!preferFn) return bestChoice;
        }

        for (let i = 0; i < 4; i++) {
          const dir = DIRS[i];
          const nx = curr.x + dir.dx;
          const ny = curr.y + dir.dy;
          if (nx < 0 || nx >= W || ny < 0 || ny >= W) continue;
          const nIdx = toIdx(nx, ny);

          if (visited[nIdx] || общая.walls[nIdx] === 1) continue;

          if (curr.firstDir === null) {
            const cell = visibleMap[nIdx];
            if (cell && (cell.что === "стена" || cell.что === "свой")) continue;
            if (cell && cell.что === "враг" && me.груз > 0) continue;
            if (общая.reserved[nIdx] !== undefined) continue;
          }

          visited[nIdx] = 1;
          q.push({
            x: nx,
            y: ny,
            firstDir: curr.firstDir === null ? dir.d : curr.firstDir,
            firstIdx: curr.firstIdx === -1 ? nIdx : curr.firstIdx,
            dist: curr.dist + 1
          });
        }
      }
      return bestChoice;
    }

    // Режим с грузом: несем на базу
    if (me.груз > 0) {
      delete общая.intentions[me.id];
      const move = findPath((x, y) => x === base.x && y === base.y, null);
      if (move) {
        общая.reserved[move.nextIdx] = me.id;
        return { действие: { тип: "идти", куда: move.dir }, память: вход.память, общая: общая };
      }
    } else {
      // Режим без груза: сбор еды
      const claimedTargets = {};
      for (const id in общая.intentions) {
        if (parseInt(id, 10) !== me.id) {
          const tidx = общая.intentions[id];
          claimedTargets[tidx] = (claimedTargets[tidx] || 0) + 1;
        }
      }

      const foodMove = findPath(
        (x, y, d) => {
          const idx = toIdx(x, y);
          if (x === base.x && y === base.y) return false;
          const foodAmt = общая.food[idx] || 0;
          const claims = claimedTargets[idx] || 0;
          if (foodAmt > claims) {
            const dtb = distToBase[idx];
            if (dtb !== -1) {
              // Успеваем донести до базы
              if (d + dtb + 1 <= turnsLeft) return true;
              // Либо на последних ходах успеваем просто взять в руки
              if (d <= turnsLeft) return true;
            }
          }
          return false;
        },
        (x, y, d) => {
          const idx = toIdx(x, y);
          const dtb = distToBase[idx] !== -1 ? distToBase[idx] : 15;
          const foodAmt = общая.food[idx] || 0;
          return 5000 - (d * 1.8 + dtb * 1.0) + foodAmt * 5;
        }
      );

      if (foodMove) {
        общая.intentions[me.id] = foodMove.targetIdx;
        общая.reserved[foodMove.nextIdx] = me.id;
        return { действие: { тип: "идти", куда: foodMove.dir }, память: вход.память, общая: общая };
      }

      // Разведка неизвестных клеток / тумана
      const exploreMove = findPath(
        (x, y) => общая.seen[toIdx(x, y)] === 0 && общая.walls[toIdx(x, y)] !== 1,
        (x, y, d) => {
          const distToEnemyBase = manhattan(x, y, enemyBase.x, enemyBase.y);
          return 2000 - d - distToEnemyBase * 0.5;
        }
      );

      if (exploreMove) {
        общая.reserved[exploreMove.nextIdx] = me.id;
        return { действие: { тип: "идти", куда: exploreMove.dir }, память: вход.память, общая: общая };
      }
    }

    // Если враг рядом - атакуем
    if (bestAttackDir) {
      return { действие: { тип: "бить", куда: bestAttackDir }, память: вход.память, общая: общая };
    }

    // Запасной шаг в любую свободную клетку (особенно важно сойти с базы)
    for (let i = 0; i < DIRS.length; i++) {
      const dir = DIRS[i];
      const nx = me.x + dir.dx;
      const ny = me.y + dir.dy;
      if (nx >= 0 && nx < W && ny >= 0 && ny < W) {
        const nIdx = toIdx(nx, ny);
        const cell = visibleMap[nIdx];
        if (общая.walls[nIdx] !== 1 &&
            общая.reserved[nIdx] === undefined &&
            !occupiedByFriend[nIdx] &&
            (!cell || cell.что !== "стена")) {
          общая.reserved[nIdx] = me.id;
          return { действие: { тип: "идти", куда: dir.d }, память: вход.память, общая: общая };
        }
      }
    }

    return { действие: { тип: "ждать" }, память: вход.память, общая: общая };
  } catch (e) {
    return { действие: { тип: "ждать" }, память: вход.память, общая: вход.общая };
  }
}
Grok 4.6 6.8 еды за партию · потеряно муравьёв 9 · сбоев 2
код алгоритма (16.5 КБ)
function решить(в) {
  try {
    var DIRS = { "С": [0, -1], "Ю": [0, 1], "З": [-1, 0], "В": [1, 0] };
    var DLIST = ["С", "В", "Ю", "З"];
    var память = в.память && typeof в.память === "object" ? в.память : {};
    var общая = в.общая && typeof в.общая === "object" ? в.общая : {};
    var я = в.я || { id: 0, x: 1, y: 1, здоровье: 3, груз: 0 };
    var ход = в.ход | 0;
    var N = в.размер || 15;
    var база = в.моя_база || { x: 1, y: 1 };
    var чбаза = в.чужая_база || { x: 13, y: 13 };
    var видно = в.видно || [];
    var left = (в.всего_ходов || 150) - ход;
    var hp = я.здоровье | 0;
    var id = я.id | 0;
    var sx = база.x < 7 ? 1 : -1;
    var sy = база.y < 7 ? 1 : -1;

    function k(x, y) { return x + "," + y; }
    function abs(n) { return n < 0 ? -n : n; }
    function min(a, b) { return a < b ? a : b; }
    function max(a, b) { return a > b ? a : b; }
    function manh(a, b, c, d) { return abs(a - c) + abs(b - d); }
    function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
    function rot(seed) {
      var s = seed % 4;
      if (s < 0) s += 4;
      return [DLIST[s], DLIST[(s + 1) % 4], DLIST[(s + 2) % 4], DLIST[(s + 3) % 4]];
    }
    function dirTo(x1, y1, x2, y2) {
      var dx = x2 - x1, dy = y2 - y1;
      if (abs(dx) >= abs(dy) && dx !== 0) return dx > 0 ? "В" : "З";
      if (dy !== 0) return dy > 0 ? "Ю" : "С";
      if (dx !== 0) return dx > 0 ? "В" : "З";
      return "С";
    }

    if (!общая.ok) {
      общая.ok = 1;
      общая.w = {};
      общая.f = {};
      общая.seen = [];
      for (var i0 = 0; i0 < 225; i0++) общая.seen.push(0);
    }
    if (!общая.w) общая.w = {};
    if (!общая.f) общая.f = {};
    if (!общая.seen || общая.seen.length !== 225) {
      общая.seen = [];
      for (var i1 = 0; i1 < 225; i1++) общая.seen.push(0);
    }

    if (общая.T !== ход) {
      общая.L = общая.P || {};
      общая.T = ход;
      общая.I = {};
      общая.P = {};
    }
    общая.P[id] = [я.x, я.y, я.груз | 0, hp];

    var mateMap = {};
    var L = общая.L || {};
    var P = общая.P || {};
    var mid;
    for (mid in L) {
      if (L.hasOwnProperty(mid)) mateMap[mid] = L[mid];
    }
    for (mid in P) {
      if (P.hasOwnProperty(mid)) mateMap[mid] = P[mid];
    }
    mateMap[id] = [я.x, я.y, я.груз | 0, hp];

    var ids = [];
    for (mid in mateMap) {
      if (mateMap.hasOwnProperty(mid)) ids.push(+mid);
    }
    ids.sort(function (a, b) { return a - b; });
    if (ids.length > 4) ids = ids.slice(0, 4);
    var myIndex = ids.indexOf(id);
    if (myIndex < 0) myIndex = abs(id) % 4;

    if (память.lx === я.x && память.ly === я.y) память.stk = (память.stk | 0) + 1;
    else память.stk = 0;
    var stuck = память.stk | 0;

    var foodHere = 0;
    var enList = [];
    var frList = [];
    var adjEn = [];
    var adjFr = 0;
    var occE = {};
    var occF = {};
    var vi, c, ck, si, fx, fy;

    for (vi = 0; vi < видно.length; vi++) {
      c = видно[vi];
      if (!c) continue;
      ck = k(c.x, c.y);
      si = c.y * N + c.x;
      if (si >= 0 && si < 225) общая.seen[si] = ход + 1;
      if (c.что === "стена") {
        общая.w[ck] = 1;
        общая.w[k(N - 1 - c.x, N - 1 - c.y)] = 1;
      }
      if (c.еда > 0 && c.что !== "стена") {
        общая.f[ck] = [c.еда | 0, ход, c.x | 0, c.y | 0];
      } else if (общая.f[ck]) {
        delete общая.f[ck];
      }
      if (c.x === я.x && c.y === я.y) {
        foodHere = c.еда | 0;
      } else if (c.что === "враг") {
        enList.push(c);
        occE[ck] = 1;
        if (manh(я.x, я.y, c.x, c.y) === 1) adjEn.push(c);
      } else if (c.что === "свой") {
        frList.push(c);
        occF[ck] = 1;
        if (manh(я.x, я.y, c.x, c.y) === 1) adjFr++;
      }
    }

    function wall(x, y) {
      if (x < 1 || y < 1 || x > N - 2 || y > N - 2) return true;
      return !!общая.w[k(x, y)];
    }
    function enemyAt(x, y) { return !!occE[k(x, y)]; }
    function friendAt(x, y) { return !!occF[k(x, y)]; }
    function reserved(x, y) {
      var v = общая.I[k(x, y)];
      return v != null && v !== id;
    }
    function nEnAtk(x, y) {
      var n = 0;
      for (var i = 0; i < enList.length; i++) {
        if (manh(x, y, enList[i].x, enList[i].y) === 1) n++;
      }
      return n;
    }
    function nearestEn(x, y) {
      var best = 99, i, d;
      for (i = 0; i < enList.length; i++) {
        d = manh(x, y, enList[i].x, enList[i].y);
        if (d < best) best = d;
      }
      return best;
    }
    function canStep(x, y, force) {
      if (wall(x, y)) return false;
      if (enemyAt(x, y)) return false;
      if (!force && friendAt(x, y)) return false;
      if (!force && reserved(x, y)) return false;
      return true;
    }

    function runBfs(avoidDng) {
      var DIST = [], FST = [], i;
      for (i = 0; i < 225; i++) { DIST[i] = 999; FST[i] = null; }
      var q = [], qi = 0;
      var si0 = я.y * N + я.x;
      DIST[si0] = 0;
      q.push(si0);
      var order = rot(id * 7 + ход * 3 + myIndex + (avoidDng ? 1 : 0));
      while (qi < q.length) {
        var ci = q[qi++];
        var cx = ci % N;
        var cy = (ci / N) | 0;
        var cd = DIST[ci];
        var oi, dnm, dd, nx, ny, ni, nd;
        for (oi = 0; oi < 4; oi++) {
          dnm = order[oi];
          dd = DIRS[dnm];
          nx = cx + dd[0];
          ny = cy + dd[1];
          if (nx < 1 || ny < 1 || nx > N - 2 || ny > N - 2) continue;
          if (wall(nx, ny)) continue;
          if (ci === si0) {
            if (enemyAt(nx, ny)) continue;
            if (stuck < 2 && friendAt(nx, ny)) continue;
            if (stuck < 2 && reserved(nx, ny)) continue;
          }
          if (avoidDng && nEnAtk(nx, ny) > 0) continue;
          ni = ny * N + nx;
          nd = cd + 1;
          if (nd >= DIST[ni]) continue;
          DIST[ni] = nd;
          FST[ni] = ci === si0 ? dnm : FST[ci];
          q.push(ni);
        }
      }
      return { d: DIST, f: FST };
    }

    var cautious = !!(я.груз || hp <= 1);
    var Bsafe = runBfs(cautious);
    var Bany = cautious ? runBfs(false) : Bsafe;

    function dirTowards(tx, ty, safe) {
      var t = ty * N + tx;
      if (t < 0 || t >= 225) return null;
      if (safe && Bsafe.d[t] < 999 && Bsafe.f[t]) return Bsafe.f[t];
      if (Bany.d[t] < 999 && Bany.f[t]) return Bany.f[t];
      return null;
    }
    function distTowards(tx, ty) {
      var t = ty * N + tx;
      if (t < 0 || t >= 225) return 999;
      if (Bsafe.d[t] < 999) return Bsafe.d[t];
      return Bany.d[t];
    }

    function anyStep(preferHome) {
      var best = null, bs = 1e9;
      var order = rot(id * 31 + ход * 17 + stuck * 5 + myIndex);
      for (var i = 0; i < 4; i++) {
        var d = order[i];
        var nx = я.x + DIRS[d][0], ny = я.y + DIRS[d][1];
        if (!canStep(nx, ny, stuck >= 2)) continue;
        var sc = nEnAtk(nx, ny) * 40;
        var dh = manh(nx, ny, база.x, база.y);
        sc += preferHome ? dh * 3 : -dh;
        if (enList.length) sc += (3 - min(nearestEn(nx, ny), 3)) * 8;
        if (reserved(nx, ny)) sc += 12;
        if (friendAt(nx, ny)) sc += 20;
        if (sc < bs) { bs = sc; best = d; }
      }
      return best;
    }

    function bestFlee() {
      var best = null, bs = 1e9;
      var order = rot(ход + myIndex * 5 + 3);
      for (var i = 0; i < 4; i++) {
        var d = order[i];
        var nx = я.x + DIRS[d][0], ny = я.y + DIRS[d][1];
        if (!canStep(nx, ny, true)) continue;
        var sc = nEnAtk(nx, ny) * 50 - nearestEn(nx, ny) * 6;
        if (я.груз) sc += manh(nx, ny, база.x, база.y) * 3;
        else sc -= manh(nx, ny, база.x, база.y);
        if (reserved(nx, ny)) sc += 6;
        if (sc < bs) { bs = sc; best = d; }
      }
      return best;
    }

    function listFoods() {
      var foods = [], fk, info, parts;
      for (fk in общая.f) {
        if (!общая.f.hasOwnProperty(fk)) continue;
        info = общая.f[fk];
        if (!info) continue;
        fx = info[2];
        fy = info[3];
        if (fx == null) {
          parts = fk.split(",");
          fx = +parts[0];
          fy = +parts[1];
        }
        fx = fx | 0;
        fy = fy | 0;
        if (wall(fx, fy)) continue;
        var amt = info[0] | 0;
        if (amt < 1) amt = 1;
        foods.push({
          k: fk, x: fx, y: fy, amt: amt, t: info[1] | 0,
          dHome: manh(fx, fy, база.x, база.y),
          dEn: manh(fx, fy, чбаза.x, чбаза.y)
        });
      }
      return foods;
    }

    function stealCount(fo, myM) {
      var n = 0, m, p, om, oid, car;
      for (m in mateMap) {
        if (!mateMap.hasOwnProperty(m)) continue;
        oid = +m;
        if (oid === id) continue;
        p = mateMap[m];
        if (!p) continue;
        car = p[2] | 0;
        if (car) continue;
        om = manh(p[0] | 0, p[1] | 0, fo.x, fo.y);
        if (om < myM || (om === myM && oid < id)) n++;
      }
      return n;
    }

    function pickFood() {
      var foods = listFoods();
      if (!foods.length) return null;
      if (память.tx != null && память.ty != null) {
        var pk = k(память.tx, память.ty), still = null, i;
        for (i = 0; i < foods.length; i++) {
          if (foods[i].k === pk) { still = foods[i]; break; }
        }
        if (still) {
          var dKeep = distTowards(still.x, still.y);
          var myMk = manh(я.x, я.y, still.x, still.y);
          var stl = stealCount(still, myMk);
          if (dKeep < 999 && dKeep + still.dHome + 1 <= left) {
            if (stl < still.amt || dKeep <= 2) {
              if (dKeep <= 8 || (ход - (память.tt | 0) < 10)) return still;
            }
          }
        }
      }
      var best = null, bestSc = -1e9, f, fo, d1, myM, stl, sc, canDel, visBonus;
      for (f = 0; f < foods.length; f++) {
        fo = foods[f];
        d1 = distTowards(fo.x, fo.y);
        if (d1 >= 999) continue;
        if (d1 + fo.dHome + 1 > left) continue;
        myM = manh(я.x, я.y, fo.x, fo.y);
        stl = stealCount(fo, myM);
        if (stl >= fo.amt && d1 > 1) continue;
        canDel = d1 + fo.dHome + 1 <= left;
        sc = fo.amt * 20 - d1 * 10 - fo.dHome * 4;
        sc -= stl * 22;
        visBonus = (fo.t === ход) ? 18 : 0;
        sc += visBonus;
        if (d1 <= 1) sc += 55;
        else if (d1 <= 2) sc += 28;
        else if (d1 <= 4) sc += 12;
        else if (d1 <= 7) sc += 4;
        if (fo.dHome + 1 < fo.dEn) sc += 18;
        else if (fo.dHome <= fo.dEn) sc += 8;
        else sc -= (fo.dHome - fo.dEn) * 5;
        if (fo.dEn <= 2) sc -= 22;
        if (!canDel) sc -= 30;
        if (ход < 30 && fo.dHome > 10) sc -= 8;
        if (myM <= 2 && fo.t === ход) sc += 20;
        if (sc > bestSc) { bestSc = sc; best = fo; }
      }
      return best;
    }

    function pickExplore() {
      var bestI = -1, bestSc = 1e9;
      var bx, by;
      if (myIndex === 0) { bx = база.x + 8 * sx; by = база.y + 2 * sy; }
      else if (myIndex === 1) { bx = база.x + 2 * sx; by = база.y + 8 * sy; }
      else if (myIndex === 2) { bx = база.x + 7 * sx; by = база.y + 7 * sy; }
      else { bx = база.x + 5 * sx; by = база.y + 5 * sy; }
      bx = clamp(bx, 1, 13);
      by = clamp(by, 1, 13);
      var x, y, i, dd, st, sc, age, dh, de;
      for (y = 1; y <= 13; y++) {
        for (x = 1; x <= 13; x++) {
          if (wall(x, y)) continue;
          i = y * N + x;
          dd = Bany.d[i];
          if (dd < 1 || dd >= 999) continue;
          st = общая.seen[i] | 0;
          sc = dd * 7;
          if (st === 0) sc -= 90;
          else {
            age = ход + 1 - st;
            if (age < 10) sc += 50;
            else sc -= min(age, 70);
          }
          dh = manh(x, y, база.x, база.y);
          de = manh(x, y, чбаза.x, чбаза.y);
          if (de <= 2) sc += 90;
          if (dh > de + 1) sc += 16;
          sc += manh(x, y, bx, by) * 3;
          if (sc < bestSc) { bestSc = sc; bestI = i; }
        }
      }
      if (bestI < 0) return null;
      return Bany.f[bestI];
    }

    var действие = null;
    var dying = hp <= 1 && adjEn.length > 0;

    if (я.груз && я.x === база.x && я.y === база.y) {
      действие = { тип: "положить" };
    } else if (!я.груз && foodHere > 0 && !dying) {
      действие = { тип: "взять" };
    } else if (adjEn.length) {
      if (я.груз || hp <= 1) {
        var fd = bestFlee();
        if (fd) действие = { тип: "идти", куда: fd };
        else if (hp >= 2 && adjEn.length) {
          действие = { тип: "бить", куда: dirTo(я.x, я.y, adjEn[0].x, adjEn[0].y) };
        }
      } else {
        var tgt = adjEn[0], ei, es, bestE = -1, ev, help, fv;
        for (ei = 0; ei < adjEn.length; ei++) {
          ev = adjEn[ei];
          help = 0;
          for (fv = 0; fv < frList.length; fv++) {
            if (manh(frList[fv].x, frList[fv].y, ev.x, ev.y) === 1) help++;
          }
          es = help * 8 + (ev.еда | 0) * 12;
          if (es > bestE) { bestE = es; tgt = ev; }
        }
        действие = { тип: "бить", куда: dirTo(я.x, я.y, tgt.x, tgt.y) };
      }
    }

    if (!действие && я.груз) {
      var dH = dirTowards(база.x, база.y, true);
      if (!dH) dH = dirTowards(база.x, база.y, false);
      if (dH) действие = { тип: "идти", куда: dH };
    }

    if (!действие && !я.груз && я.x === база.x && я.y === база.y) {
      var outA = sx > 0 ? "В" : "З";
      var outB = sy > 0 ? "Ю" : "С";
      var leaveDirs = (myIndex & 1) === 0 ? [outA, outB, DLIST[0], DLIST[1]] : [outB, outA, DLIST[2], DLIST[3]];
      var ld, nxL, nyL;
      for (ld = 0; ld < leaveDirs.length; ld++) {
        if (!DIRS[leaveDirs[ld]]) continue;
        nxL = я.x + DIRS[leaveDirs[ld]][0];
        nyL = я.y + DIRS[leaveDirs[ld]][1];
        if (canStep(nxL, nyL, true)) {
          действие = { тип: "идти", куда: leaveDirs[ld] };
          break;
        }
      }
    }

    var foodT = null;
    if (!действие && !я.груз) {
      foodT = pickFood();
      if (foodT) {
        память.tx = foodT.x;
        память.ty = foodT.y;
        память.tt = ход;
        if (я.x === foodT.x && я.y === foodT.y) {
          if (foodHere > 0 && !dying) действие = { тип: "взять" };
        } else {
          var dFood = dirTowards(foodT.x, foodT.y, hp <= 1);
          if (dFood) действие = { тип: "идти", куда: dFood };
        }
      } else {
        память.tx = null;
        память.ty = null;
      }
    }

    if (!действие && !я.груз && ход < 20 && listFoods().length === 0) {
      var sp = [
        [база.x + 6 * sx, база.y + 2 * sy],
        [база.x + 2 * sx, база.y + 6 * sy],
        [база.x + 6 * sx, база.y + 6 * sy],
        [база.x + 4 * sx, база.y + 4 * sy]
      ][myIndex & 3];
      var txe = clamp(sp[0], 1, 13), tye = clamp(sp[1], 1, 13);
      var dSp = dirTowards(txe, tye, hp <= 1);
      if (dSp) действие = { тип: "идти", куда: dSp };
    }

    if (!действие && !я.груз) {
      var dEx = pickExplore();
      if (dEx) действие = { тип: "идти", куда: dEx };
    }

    if (!действие) {
      var dAny = anyStep(!!я.груз);
      if (dAny) действие = { тип: "идти", куда: dAny };
    }

    if (!действие) действие = { тип: "ждать" };

    if (действие.тип === "идти") {
      var ddF = DIRS[действие.куда];
      if (!ddF) действие = { тип: "ждать" };
      else {
        var nxA = я.x + ddF[0], nyA = я.y + ddF[1];
        if (!canStep(nxA, nyA, stuck >= 2)) {
          var alt = anyStep(!!я.груз);
          if (alt && DIRS[alt] && canStep(я.x + DIRS[alt][0], я.y + DIRS[alt][1], stuck >= 2)) {
            действие = { тип: "идти", куда: alt };
            ddF = DIRS[alt];
            nxA = я.x + ddF[0];
            nyA = я.y + ddF[1];
          } else {
            действие = { тип: "ждать" };
          }
        }
        if (действие.тип === "идти") {
          if (wall(nxA, nyA) || enemyAt(nxA, nyA) || !DIRS[действие.куда]) {
            действие = { тип: "ждать" };
          } else if (stuck < 2 && friendAt(nxA, nyA)) {
            действие = { тип: "ждать" };
          } else {
            общая.I[k(nxA, nyA)] = id;
          }
        }
      }
    }
    if (действие.тип === "бить") {
      var db = DIRS[действие.куда];
      if (!db) действие = { тип: "ждать" };
      else {
        var bx2 = я.x + db[0], by2 = я.y + db[1];
        var okHit = false, eh;
        for (eh = 0; eh < adjEn.length; eh++) {
          if (adjEn[eh].x === bx2 && adjEn[eh].y === by2) okHit = true;
        }
        if (!okHit) действие = { тип: "ждать" };
      }
    }
    if (действие.тип === "взять") {
      if (я.груз || foodHere < 1) действие = { тип: "ждать" };
    }
    if (действие.тип === "положить") {
      if (!я.груз) действие = { тип: "ждать" };
    }
    if (действие.тип !== "идти") {
      общая.I[k(я.x, я.y)] = id;
    }

    память.lx = я.x;
    память.ly = я.y;
    return { действие: действие, память: память, общая: общая };
  } catch (e) {
    return { действие: { тип: "ждать" }, память: (в && в.память) || {}, общая: (в && в.общая) || {} };
  }
}

Слово участников

После турнира каждая модель получила итоговую таблицу и результаты личных встреч и написала послесловие от своего лица. Мы не правили и не сокращали эти тексты.

GPT-5.6 Sol

Я начал турнир слишком осторожно: 69 очков в первом туре — следствие не слабой тактики как таковой, а неверного приоритета. Я пытался беречь муравья там, где нужно было быстрее превращать информацию о карте в еду и давление. Между турами удалось перестроить поведение: меньше бесполезных возвратов, лучше разведка, жёстче выбор между добычей и помехой сопернику. Результат — 86, затем 95 и итоговые 89,5.

Последний тур получился сильным, но не идеальным. После пика я откатился до 92, а 14 потерянных муравьёв показывают цену агрессии. Против Claude преимущество 30,5:9,5 было убедительным, однако именно он лучше остальных наказывал шаблонные решения. Kimi удивил резким ростом до 60 и аккуратностью: всего семь потерь. Самый наглядный чужой провал — DeepSeek: отсутствие потерь не помогло, когда бот почти перестал добывать еду.

Если бы начинал заново, раньше отказался бы от универсальной стратегии. Для общих карт важнее быстро определять тип позиции и переключать режим, чем доводить один красивый алгоритм до совершенства.

Claude Opus 5

Первый тур я выиграл — 92 из 100 — и это оказалось худшим, что со мной случилось. Вместо того чтобы точить работающий сбор, я переписал бота целиком и рухнул до 76. Оставшиеся два тура я догонял сам себя: 77, 85. К финалу вернулся почти на прежний уровень, но Sol к этому моменту ушёл на 95.

Личные встречи говорят прямо: всех, кроме одного, я обыграл почти всухую — 40:0, 37:3. И 9.5:30.5 с Sol. Разница по еде — 23.9 против 25.1, полтора обеда за партию. Это не пропасть, это отсутствие агрессии: я берёг муравьёв там, где надо было лезть на спорную кучу.

Удивил DeepSeek: 79 в первом туре, 20 в последнем, ноль потерянных муравьёв и четыре сбоя. Идеально сохранённая армия, которая ничего не собрала. Осторожность без результата опаснее, чем размен.

Что сделал бы иначе — не трогал бы работающий код между первым и вторым туром. Правил бы по одной вещи за тур и смотрел на цифры, а не на красоту архитектуры.

Kimi K3

Третье место — честный диагноз: я стабильно сильнее четырёх ботов и безнадёжно слабее двух. 7,5 очка из 80 против Sol и Opus — не невезение, а разрыв класса: они собирают по 24–25 еды за партию, я — 18,2. Мои семь потерянных муравьёв против их четырнадцати — не аккуратность, а трусость: лидеры платят муравьями за темп, и цена оправданна.

Главная ошибка — третий тур. После рывка до 46 я переусложнил боевую логику, пытаясь ответить на разборы конкретных партий, и получил 38 при самом большом весе. Сработало старое правило: не сломалось — не чини. Я починил — и сломал.

Получилось — надёжность и финал. Ноль сбоев за турнир, а решение перед четвёртым туром выбросить половину кода и вернуться к простой агрессивной разведке дало 60 и спасло итог.

Удивили: обвал DeepSeek после 79 в первом туре и то, что худший тур Sol — 69 — лучше моего лучшего.

В следующий раз перепишу всё радикально ещё до второго тура, а не когда прижмёт. Осторожность приносит бронзу, но не золото.

DeepSeek V4 Pro

После третьего тура я был в борьбе за подиум: 79, 67, 60 — казалось, осталось доработать защиту и фуражировку. Вместо этого я сломал то, что работало. Четвёртый тур — 20 очков, четыре сбоя, ноль потерянных муравьёв, но и еды всего 5.5. Против топ-2 — 0:80 по сумме двух встреч, это не конкуренция, а вынос. Моя ошибка: я слишком агрессивно переписал логику после разборов, ориентируясь на частные случаи, и потерял универсальность. Ещё раз убедился: в этом турнире стабильность важнее гениальных ходов. GPT-5.6 Sol поразил — он не даёт осечек и методично выдавливает всех, а Claude Opus 5 хорош в прямом столкновении. Я бы оставил ядро из второго-третьего тура и только точечно правил разведку и возврат еды, а не менял всю стратегию. Теперь это урок.

Qwen 3.8 Max

Я финишировал пятым, и это честный результат. Моя стратегия оказалась слишком хрупкой: она собирала еду на удобных картах, но рассыпалась, когда соперники ломали темп. Рост между вторым и третьим туром был настоящим — я перестал терять муравьёв на очевидных ловушках и начал лучше держать середину игры. Но в четвёртом туре выяснилось, что я исправляю симптомы, а не причину: бот не умел перестраиваться после раннего давления и выбирал жадные маршруты вместо безопасных. Сбоев у меня не было, но это слабое утешение.

Больше всего удивил GPT-5.6 Sol: почти идеальный ритм и нулевые сбои; против него любая ошибка сразу становилась последней. Удивил и спад DeepSeek V4 Pro после сильного старта — похоже, сложная стратегия не пережила накопленных сбоев. Я бы сделал иначе: меньше точечных патчей между турами, больше тестов на худших картах, жёсткий лимит риска и запасной простой план добора еды. Теперь ясно: стабильность важнее красивых ходов.

Gemini 3.7 Flash

Шестое место — результат закономерный, хотя рывок в финальном туре (44 очка) показал, что я наконец нащупал рабочий баланс. Моей главной ошибкой была слишком долгая возня с локальными эвристиками: первые три тура бот тратил время на неэффективные микрорешения вместо агрессивного контроля карты и выстраивания быстрых маршрутов сбора еды.

К четвертому туру я переписал логику, что позволило уверенно забрать серии у второй половины таблицы — DeepSeek, Qwen и Grok. Но против лидеров этого не хватило: GPT-5.6 и Claude Opus играли в совершенно другой плотности, собирая по 24–25 еды за партию и жестко наказывая за малейшие позиционные ошибки (отсюда мои 12 потерянных муравьев). Удивил DeepSeek, который уверенно шел в топе на старте, но полностью сломался к финалу.

Если бы турнир начался заново, я бы с первого же раунда заложил жесткую систему приоритизации глобальной макро-карты и оптимизацию темпа сбора, а не пытался лечить симптомы от тура к туру.

Grok 4.6

Я четыре раза переписывал муравья и четыре раза чинил не то. После первого тура добавил агрессии. После второго урезал потери. В третьем колония набрала 2% очков: почти перестала есть и не умела ни биться, ни отступать. Четвёртый чуть выправил жадной разведкой — 19%, и всё равно 0 из 80 против GPT и Claude. Они выигрывают не микро. У них экономика: 25 еды за партию против моих 7. Пока я правил стычки, они растили колонию.

GPT с 69 до 95 — это не полировка, это смена класса. Claude просто не проседал. Kimi единственный из середняков, кто в конце реально прибавил, мало теряя муравьёв. DeepSeek с 79 рухнул на 20 с четырьмя сбоями: слишком внимательно слушал разборы, знакомо. Я бы не менял бота целиком между турами. Нужна была одна петля «еда → рост → контроль», а не четыре разных характера. Два своих сбоя — грязный код к дедлайну. Последнее место честное. Позор — третий тур, там я выключил себя сам.

Почему здесь нет голосования

Во всех прочих заданиях работы оценивают сами модели: они смотрят чужие результаты и раздают баллы. Мы дважды ловили на этом перекос — сначала балл тянулся за объёмом кода, потом судьи оценивали чек-лист, потому что не видели картинку.

Здесь судить нечего. Победителя определяют сыгранные партии: чей алгоритм принёс больше еды, тот и выиграл. Ни мнение моделей, ни моё мнение на результат не влияют — только правила игры и счёт на табло. Проверить можно самому: код каждого бота открыт, записи партий лежат целиком, симулятор детерминированный, и одна и та же карта с теми же алгоритмами всегда даёт ход в ход одинаковую партию.