아트 어택 KoTH


61

출품작이 마감되었습니다. 새로운 항목이나 편집 내용은 최종 실행에서 계산되지 않습니다.

채팅에 참여하십시오!

도전

캔버스를 가능한 많은 페인트로 채 웁니다. 당신의 노력에 페인트 칠 수도있는 다른 봇에주의하십시오!

참고 : 이 챌린지 설명에서 페인트 는 그리드의 정사각형 색상을 변경하는 것을 의미하고 페인트 되지 않은 것은 그리드의 정사각형의 색상이 0이며 봇 중 하나에 기인하지 않음을 의미합니다.

입력

귀하의 기능에는 네 가지 인수, 즉 자신, 그리드, 그리드상의 모든 봇의 위치 및 게임 정보가 제공됩니다.

자기

격자의 색상과 위치를 나타내는 1D 배열입니다 [id, xpos, ypos].

그리드의 왼쪽 상단이 위치 (0, 0)입니다. 위치 (1,0)는 오른쪽이고 위치 (0,1)는 아래입니다

귀하의 ID는 귀하의 색상과 동의어입니다 (ID가 그리드 페인트 방법에 어떤 영향을 미치는지 알아 보려면 아래 참조). 귀하의 ID는 봇마다 고유합니다.

그리드

이것은 각 셀의 색을 알려주는 정수를 포함하는 2D 배열입니다. 그리드 셀의 수가 인 경우 0셀이 페인트되지 않음을 의미합니다. 그리드 셀의 수가 정수 x이면 셀이 ID로 봇에 의해 페인트되었음을 ​​나타 x냅니다.

position의 그리드 색상을 얻으려면 다음 (x, y)과 같이 배열을 사용하십시오 grid[x][y].

이것은 봇의 위치에 대한 정보를 포함하는 배열입니다. bots 배열의 각 요소는 각 봇을 설명하는 배열이며 다음과 같습니다 [id, xpos, ypos]. 여기서 id봇의 ID는 봇 xpos의 x 위치이고 봇 ypos의 y 위치입니다.

이 배열에는 자신의 봇 위치와 ID가 포함됩니다. 제거 된 봇은이 어레이에 포함되지 않습니다.

게임 정보

이것은 현재 게임에 대한 정보를 포함하는 배열이며 다음과 같습니다 : [roundNum, maxRounds]where roundNum현재 라운드의 수 (1 인덱스)이고 maxRounds현재 게임의 라운드 수입니다.

산출

출력은 함수가 반환 한 문자열이어야합니다. 이것이 이동 명령입니다.

이동 명령에 따라 다음 이동이 결정됩니다. 사용 가능한 명령은 다음과 같습니다.

up
down
left
right
wait

움직일 때마다 이동하는 사각형을 페인트합니다. (자세한 내용은 아래 참조)

어디에서 wait움직이지 않는다는 의미입니다. (하지만 당신은 당신이 머물 사각형을 페인트)

그리드 외부로 이동하려고하면 명령이 무시되고 동일한 위치에 유지됩니다.

그리드 페인팅

정사각형으로 이동할 때마다 페인트를 칠하지만 해당 정사각형의 색상을 결정하는 규칙이 있습니다.

사각형이 페인트되지 않은 경우 (0), 단순히 자신의 ID와 동일한 색상으로 페인트합니다. 그러나 정사각형이 이전에 (0이 아닌) 페인트 된 경우 정사각형의 결과 색상은 다음 JavaScript 코드에 따라 나타납니다.

[botColour, 0, floorColour][Math.abs(botColour - floorColour)%3]

이 공식은 봇이 페인트를 다시 칠하지 않고 자체 색상 위로 이동할 수 있도록 만들어졌습니다.

제거

5 라운드 후에 하나 이상의 정사각형이 그려진 경우 (그리드에서 동일한 색상의 정사각형 수) 제거됩니다. 이것은 더 이상 게임에 참여하지 않고 자동으로 패배한다는 것을 의미합니다.

규칙

  • 코드는 유형의 함수 여야합니다
function(myself, grid, bots, gameInfo) {
    // Code here
    return move;
}
  • 그리드는 측면 길이의 정사각형입니다Number of competing bots×3
  • 특정 봇이 타겟팅하는 것을 방지하기 위해 봇의 ID는 무작위로 지정됩니다.
  • 두 봇이 같은 공간을 차지하면 해당 공간의 색상이 페인트되지 않습니다.
  • 운동 차례 기반 즉 라운드 동안은 모든 봇은 동일한 함께 제공됩니다 grid, botsgameInfo인수


  • 최대 3 개의 봇을 만들 수 있습니다
  • 봇은 함께 작동 할 수 있지만 서로 통신하지 않아야하며 서로의 ID를 알 수 없습니다. 승리는 팀이 아닌 개별적으로 수여됩니다.
  • 미리 선택된 단일 봇을 의도적으로 대상으로하는 봇을 생성해서는 안됩니다. 그러나 일반 봇 클래스의 전술을 목표로 삼을 수 있습니다.
  • 봇은에 데이터를 저장할 수 있습니다 window.localStorage. 각 봇은 자체 데이터 객체를 사용해야합니다. 봇이 다른 봇 데이터를 실수로 또는 의도적으로 읽는 것으로 판명되면 문제가 해결 될 때까지 실격 처리됩니다.
  • 봇이 난수를 사용하는 경우 Math.random()

제어 장치

컨트롤러는 여기에서 찾을 수 있습니다 :

https://gist.github.com/beta-decay/10f026b15c3babd63c004db1f937eb14

또는 여기에서 실행할 수 있습니다 : https://beta-decay.github.io/art_attack

참고 : 웹 페이지는 언제든지 변경 될 수 있으므로 오프라인 테스트 (gist에서 컨트롤러 다운로드)를 수행하는 것이 좋습니다.

모든 봇이 추가되면 그래픽 인터페이스없이 제거 된 컨트롤러로 10,000 개의 게임을 실행합니다. https://beta-decay.github.io/art_attack/fast에서 실행할 수 있습니다.

승리

캔버스를 최대한 채운 플레이어가 게임에서 이깁니다 (게임은 2000 라운드입니다). 추첨의 경우 모든 추첨 플레이어가 승리합니다.

10,000 번의 게임 중에서 가장 많은 승리를 거둔 플레이어가 도전에 승리합니다.

10,000 개의 게임은 다음주 월요일 (2018-08-27 23:00 UTC + 1)에있을 것으로 예상됩니다.


7
[botColour, 0, floorColour][Math.abs(botColour - floorColour)%3]영어로 번역 해 주시겠습니까?
Nic Hartley

8
@NicHartley 3의 배수 인 ID를 가진 봇은 서로의 페인트를 직접 덮을 수 있습니다. 3의 배수보다 1 이상인 봇은 서로의 페인트를 지울 수 있지만 다시 칠하려면 다른 라운드가 필요합니다. 3의 배수보다 2 이상인 봇은 서로의 페인트에 전혀 영향을 미치지 않습니다.
니모닉

4
@RushabhMehta 나는 [botColour, 0, floorColour][Math.abs(botColour - floorColour)%3]봇이 운이 좋 거나 큰 경쟁자를 칠하거나 페인트 칠을 할 것인지 여부와 상관없이 공식 과 관련이 있다고 생각합니다 . 또한 자신이 선택한 봇을 한 손으로 파괴 할 수있는 트롤 / 헌터도 고려해야합니다. 어느 쪽이든, 그것은 10000 게임 평균입니다.
dzaima

9
토너먼트 진행 상황에 대한 뉴스가 있습니까?
Hein Wessels

3
누군가 시도하고 싶다면 지금 호스팅하고 있습니다. cypressf.com/art-attack github.com/cypressf/art-attack
Cypress Frankenfeld

답변:


13

function([mc, mx, my], grid, bots, [round, maxRound]) {const ID = 2;
  var S = this;
  const botAm = 3;
  function log(...args) {
    //if (round > 1) console.log(ID+" "+args[0], ...args.slice(1));
    return true;
  }
  if (round == 1) {
    var all = new Array(bots.length).fill().map((_,i)=>i+1);
    S.fs = new Array(botAm).fill().map(c =>
      [all.slice(), all.slice(), all.slice(), all.slice()]
    );
    S.doneSetup = false;
    var center = grid.length/2;
    // UL=0; DL=1; DR=2; UR=3
    S.dir = mx<center? (my<center? 0 : 1) : (my<center? 3 : 2);
    S.job = 0;
    S.setupFail = S.finished = false;
    S.tbotc = undefined;
    S.botAm = bots.length;
    S.botEvilness = new Array(bots.length+1).fill(0);
    S.keys = [[1,1,0,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,0,0],
              [0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,0,0,1,1],
              [1,0,0,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0,1,1,1,0]];
    /*if (ID == 2) */{
      S.chased = 0;
      S.ignore = [];
      S.badMoves = 0;
      S.pastMoves = new Array(100).fill("-1;0");
      S.timer = 0;
      S.jimFn = function([mc, mx, my], grid, bots, [round, maxRound]) { // ---------- BEGIN JIM ---------- \\
        var output;
        var allowRetracing = false;

        var checkSize = 3;
        var eatSize = 5;
        var myScore;
        var scoreboard;



        if (grid[mx][my] == 0 && !bots.some(([col, bx, by])=> col != mc && bx==mx && by==my)) return "wait"; // collect those sweet points

        // rescore every now and then
        if (S.timer > 200) rescore();

        S.pastMoves.push(mx+";"+my);
        S.pastMoves.shift();


        var orth = [[-1,0],[0,-1],[1,0],[0,1]];
        if (S.atTarget
        || S.targetX === undefined || S.targetY === undefined
        || S.targetX === mx && S.targetY === my
        || orth.map(([x,y])=>[mx+x,my+y]).filter(c=>get(c)==0 && inbounds(c)).length > 2) {

          S.atTarget = true;
          var neighbors = orth
            .map(([x,y]) => [x+mx, y+my])
            .filter(inbounds)
            .filter(([x,y]) => !bots.some(([bid, bx, by]) => bx==x && by==y))
            .map(c=>[c,get(c)]);

          let test = (neighbors, f, msg) => {
            return bestOf(neighbors.filter(f).map(c=>c[0])) && log(msg);
          }

          if (test(neighbors, ([,c]) => c===0, "good")) return output;
          if (test(neighbors, ([,c]) => overMap(c, 1) && S.BCs,  "sad")) return output;

          S.atTarget = false;
          S.targetX = S.targetY = undefined;
          let bestScore = 7;
          let bfscore = 0;

          for (let dist = 4; dist < 8; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds([cx, cy]) && grid[cx][cy] === 0  ) {
                  let score = scoreOf(cx, cy, 1, false);
                  if(score>bfscore)bfscore=score;
                  if (score > bestScore) {
                    bestScore = score;
                    S.targetX = cx;
                    S.targetY = cy;
                  }
                }
              }
            }
          }
          if (S.targetX) {
            log("short goto", S.targetX, S.targetY,"(rel",S.targetX-mx, S.targetY-my,") score", bestScore);
            return to([S.targetX, S.targetY]);
          } else log("long goto",bfscore);


          rescore();
          return to([S.targetX, S.targetY]);
        } else log("going to target", S.targetX, S.targetY);

        return to([S.targetX, S.targetY]);

        function myScore() {
          if (!myScore) calculateScoreboard();
          return myScore;
        }
        function calculateScoreboard() {
          scoreboard = grid.map(column=> {
            var arr = new Int16Array(grid.length);
            column.forEach((c, x) => (
              myScore+= c==mc,
              arr[x] = overMap(c, 1, 0, 0, 0, 5)
            ));
            return arr;
          });
          for (let [bc, bx, by] of bots) if (bc != mc) {
            scoreboard[bx][by] = -100;
            if (inbounds([bx-2, by])) scoreboard[bx-2][by] = -50;
            if (inbounds([bx+2, by])) scoreboard[bx+2][by] = -50;
            if (inbounds([bx, by-2])) scoreboard[bx][by-2] = -50;
            if (inbounds([bx, by+2])) scoreboard[bx][by+2] = -50;
          }
        }
        function scoreOf (x, y, size, includeEnemies) {
          if (!scoreboard) calculateScoreboard();
          let score = 0;
          for (let dx = -size; dx <= size; dx++) {
            let cx = dx + x;
            if (cx < 1 || cx >= grid.length-1) continue;
            for (let dy = -size; dy <= size; dy++) {
              let cy = dy + y;
              if (cy < 1 || cy >= grid.length-1) continue;
              let cs = scoreboard[cx][cy];
              if (cs > 0 || includeEnemies) score+= cs;
            }
          }
          return score;
        }
        function rescore() { // heatmap of best scoring places
          //log(JSON.stringify(scoreboard));
          S.bestScore = -Infinity;
          var blur = grid.map((column, x)=>column.map((c, y) => {
            let score = scoreOf(x, y, checkSize, true);
            if (score > S.bestScore) {
              S.bestScore = score;
              S.targetX = x;
              S.targetY = y;
            }
            return score;
          }));
          S.atTarget = false;
          S.timer = 0;
          S.bestScore = scoreOf(S.targetX, S.targetY, eatSize);
          S.badMoves = 0;
          // log("scored to", S.targetX, S.targetY, S.bestScore);
        }
        function over(col) { // 1 if overrides happen, -1 if overrides don't happen, 0 if override = 0
          let res = Math.abs(mc-col) % 3;
          return res==1? 0 : res==0? 1 : -1;
        }
        function overMap(col, best = 0, good = 0, bad = 0, mine = 0, zero = 0) { // best if overrides happen, bad if overrides don't happen, good if override = 0
          let res = Math.abs(mc-col) % 3;
          return col == 0? zero : col == mc? mine : res==1? good : res==0? best : bad;
        }
        function iwin   (col) { return over(col) == 1; }
        function zeroes (col) { return over(col) == 0; }
        function to([x, y]) {
          //debugger
          var LR = x > mx? [mx+1, my] : x < mx? [mx-1, my] : null;
          var UD = y > my? [mx, my+1] : y < my? [mx, my-1] : null;
          if (LR && UD) {
            var LRScore = overMap(LR, 1, 0, 0, 0, 3);
            var UDScore = overMap(UD, 1, 0, 0, 0, 3);
            if (LRScore == UDScore) return toPos([LR, UD][Math.random()>.5? 1 : 0])
            else if (LRScore > UDScore) return toPos(LR);
            else return toPos(UD);
          } else return toPos(LR || UD || [x, y]);
        }
        function toPos([x,y]) {
            if (x > mx) return "right";
            if (x < mx) return "left";
            if (y < my) return "up";
            if (y > my) return "down";
            return 'wait';
        }
        function inbounds([x, y]) {
          // if (x<grid.length && y<grid.length && x>=0 && y>=0) return true;
          if (x<grid.length-1 && y<grid.length-1 && x>=1 && y>=1) return true;
          return false;
        }
        function get([x,y]) {
          if (inbounds([x, y])) return grid[x][y];
          return 0;
        }
        function bestOf (arr) {
          if (arr.length == 0) return false;
          var bestScore = -Infinity;
          var bestPos;
          for (var [x, y] of arr) {
            let score = 0;
            for (var [bcol, bx, by] of bots) {
              let dist = Math.sqrt((x-bx)**2 + (y-by)**2);
              let res = over(bcol);
              let power = res==0? 1 : res==1? 0.4 : 1.4;
              score+= power * dist;
            }
            score-= Math.sqrt((x-S.targetX)**2 + (y-S.targetY)**2);
            if (S.pastMoves.includes(x+";"+y)) score-= 1000000;

            if (score > bestScore) {
              bestScore = score;
              bestPos = [x,y];
            }
          }
          if (bestScore < -500000) {
            if (allowRetracing) log("RETRACING");
            else return false;
          }
          output = to(bestPos);
          return true;
        }
      } // ---------- END JIM ---------- \\
    }
  }
  const dirs = ['up','left','down','right'];

  if (!S.doneSetup && round < 37) { // ---------- HANDSHAKE ---------- \\
    let finished = 0;
    if (round != 1) {
      for (let id = 0; id < botAm; id++) {
        let f = S.fs[id];
        let remaining = f.map(c=>c.length).reduce((a,b)=>a+b);
        if (remaining == 1) {
          finished++;
          continue;
        }
        if (remaining == 0) {
          // mourn the loss of a good friend
          finished++;
          continue;
        }
        for (let dir = 0; dir < 4; dir++) {
          let possible = f[dir];

          for (let i = possible.length-1; i >= 0; i--) {
            let bc = possible[i];
            let curr =       bots.find(c=>c[0]==bc);
            let prev = S.pastBots.find(c=>c[0]==bc);
            if (!curr || !prev) {
              possible.splice(i,1);
              continue;
            }
            let dx = curr[1]-prev[1];
            let dy = curr[2]-prev[2];
            let move;
            if (dy == 0) {
              if (dx == 1) move = 'right';
              else         move =  'left';
            } else {
              if (dy == 1) move =  'down';
              else         move =    'up';
            }
            let omove = rotate(move, dir);
            let expected = ['down','right'][S.keys[id][round-1]];
            // if (id == 0 && dir == S.dir) log();
            if (omove != expected) possible.splice(i,1);
          }
        }
      }
    }
    S.pastBots = bots;
    if (finished == botAm) {
      S.doneSetup = true;
      S.pastBots = undefined;
      S.BCs = new Array(botAm).fill().map((_,i) => (S.fs[i].find(c=>c.length > 0) || [-1])[0]); // AKA idtoc
      S.fighters = S.BCs.slice(0,2);
      S.ctoid = {[S.BCs[0]]:0, [S.BCs[1]]:1, [S.BCs[2]]:2};
      log("identified", S.BCs);
      if (ID == 2) {
        log("can beat", bots.filter(c=>S.fighters.filter(b=>Math.abs(b-c[0])%3 != 2).length > 0).map(c=>c[3]));
      }
    } else {
      // log(ID,S.fs);
      return rotate(['down','right'][S.keys[ID][round]], S.dir);
    }
  }
  if (!S.doneSetup) { // HANDSHAKE FAILED
    S.setupFail = true;
    S.BCs=[];
    S.fighters = [];
    S.ctoid = {};
  }


  if (S.pastGrid) for (let [bc, bx, by] of bots) { // calculate bot evilness
    let prev = S.pastGrid[bx][by];
    let fID = S.BCs.indexOf(prev);
    if (fID === 2) S.botEvilness[bc]+= 10;
    else if (fID !== -1) S.botEvilness[bc]+= 5;
    else {
      let over = Math.abs(bc - prev) % 3;
      if (over === 0) S.botEvilness[bc]+= 1;
      else if (over === 1) S.botEvilness[bc]+= 2;
    }

  }


  S.pastGrid = grid;

  if (ID == 2) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]);





  if (S.setupFail || !bots.find(c=>c[0]==S.fighters[1-ID])) return 'wait'; // for my demise
  // TODO yeah no


  if (round < 50 || !bots.find(c=>c[0]==S.BCs[2])) return S.jimFn([mc, mx, my], grid, bots, [round, maxRound]); // if Jim's dead or if it's early game, be Jim so others don't win needlessly/scoreboard becomes more clear


  let tbot = bots.find(c=>c[0] == S.tbotc);


  // ---------- NEW TARGET ---------- \\
  let tried;


  // {
  //   let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
  //   for (let column of grid) for (let item of column) scores[item]++;
  //   log("scores", scores.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  //   log("evilness", S.botEvilness.map((score, id) => [botName(id), score]).sort((a,b)=>b[1]-a[1]));
  // }

  let makeSureImNotStupidAgain = 0;
  while ((!S.tbotc || !tbot) && !S.finished) {
    makeSureImNotStupidAgain++;
    if (makeSureImNotStupidAgain > 100) {
      console.log("dzaima is stupid");
      S.finished = true;
      break;
    }
    if (!tried) tried = S.BCs.slice();
    S.gotoX = S.gotoY = undefined;
    let scores = S.botEvilness.slice(); // new Array(S.botAm+1).fill(0);
    for (let column of grid) for (let item of column) scores[item]++;
    var bbc, bbs=-Infinity;
    for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && !tried.includes(i)) {
      bbs = scores[i];
      bbc = i;
    }
    S.tbotc = bbc;
    tbot = bots.find(c=>c[0] == bbc);
    if (!tbot) {
      tried.push(bbc);
    } else {
      S.jobs = [0,0];
      let executers = S.fighters.filter(c=>Math.abs(c-bbc)%3 == 1).concat(S.fighters.filter(c=>Math.abs(c-bbc)%3 == 0));
      if (executers.length > 1) {
        S.jobs[S.ctoid[executers.pop()]] = 1;
        S.jobs[S.ctoid[executers.pop()]] = 2;
        //S.jobs.forEach((c,id) => c==0? S.jobs[id]=2 : 0);
        log("targetting", botName(bbc),"jobs",S.jobs);
      } else {
        // cry
        tried.push(bbc);
        S.tbotc = tbot = undefined;
      }
      S.job = S.jobs[ID];
    }
    if (tried.length >= bots.length) {
      // everyone is dead
      S.job = 0;
      S.jobs = new Array(2).fill(0);
      S.finished = true;
      break;
    }
  }

  if (tbot && !S.finished) {
    let [_, tx, ty] = tbot;

    switch (S.job) {
      case 1: // follow
        return to(tx, ty, S.tbotc);
      break;
      case 2: // erase
        let endingClearing = false;
        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          S.gotoX = undefined;
          var ending = [S.tbotc, ...S.fighters.filter(c=>c != mc)].map(c => bots.find(b=>b[0]==c)).filter(I=>I);
          search: for (let dist = 1; dist < grid.length*2+2; dist++) {
            for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
              for (let i = 0; i < dist; i++) {
                let cx = dx*i + dsx*dist + mx;
                let cy = dy*i + dsy*dist + my;
                if (inbounds(cx, cy)) {
                  if (grid[cx][cy] == S.tbotc && ending.every(([_,bx,by]) => (bx-cx)**2 + (by-cy)**2 > Math.random()*10)) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
          if (S.gotoX === undefined) {
            let available = [];
            grid.forEach((column, x) => column.forEach((c, y) => c==S.tbotc? available.push([x,y]) : 0));
            [S.gotoX, S.gotoY] = available[Math.floor(Math.random()*available.length)];
            endingClearing = true;
          }
        }
        return to(S.gotoX, S.gotoY, endingClearing? undefined : S.tbotc);
      break;
      case 0: // exercise

        if (S.gotoX === undefined  ||  S.gotoX==mx && S.gotoY==my  ||  grid[S.gotoX][S.gotoY] != S.tbotc) {
          let scores = new Uint32Array(S.botAm+1);
          for (let column of grid) for (let item of column) scores[item]++;
          var bbc, bbs=-Infinity;
          for (let i = 1; i < S.botAm+1; i++) if (scores[i] > bbs && Math.abs(mc-i)%3 == 0 && !S.BCs.includes(i)) {
            bbs = scores[i];
            bbc = i;
          }
          if (bbc) {
            S.gotoX = undefined;
            search: for (let dist = 1; dist < grid.length*2+2; dist++) {
              for (let [dsx, dsy, dx, dy] of [[0,-1,1,1], [1,0,-1,1], [0,1,-1,-1], [-1,0,1,-1]]) {
                for (let i = 0; i < dist; i++) {
                  let cx = dx*i + dsx*dist + mx;
                  let cy = dy*i + dsy*dist + my;
                  if (inbounds(cx, cy) && grid[cx][cy] == bbc) {
                    S.gotoX = cx;
                    S.gotoY = cy;
                    break search;
                  }
                }
              }
            }
          }
        }
        if (S.gotoX !== undefined) return to(S.gotoX, S.gotoY);
        return dirs[Math.floor(Math.random()*4)];
      break;
    }
  }


  function to (x, y, col) {
    if  (x == mx&&y== my) return 'wait';
    let dx =   x    - mx ;
    let dy =      y - my ;
    let ax = Math.abs(dx);
    let ay = Math.abs(dy);
    var          diag;
    if   (     ax==ay   ) {
      if (col&&ax+ ay==2) {
        let i=[[x, my], [mx, y]].findIndex(c=>grid[c[0]][c[1]]==col);
        if (i<0) diag = Math.random()>=.5;
        else     diag =           i  == 0;
      } else     diag = Math.random()>=.5;
    }
    if (ax==ay?  diag :  ax>ay) {
      if (dx>0) return 'right';
      else      return  'left';
    } else {
      if (dy>0) return  'down';
      else      return    'up';
    }
  }

  function rotate (move, dir) {
    if ((move == 'up' || move == 'down') && (dir && dir<3)) {
      if (move == 'up') return 'down';
      else return 'up';
    }
    if ((move == 'left' || move == 'right') && dir>1) {
      if (move == 'left') return 'right';
      else return 'left';
    }
    return move;
  }
  function botName(id) {
    let bot = bots.find(c=>c[0]==id);
    if (!bot) return id.toString();
    return bot[3] + "/" + id;
  }
  function inbounds(x, y) { return x<grid.length && y<grid.length && x>=0 && y>=0 }
}

이 봇의 전략에 대한 간단한 설명은 다음과 같습니다.

  • 승리

2
짐이 칠 거야!
Shaggy

12

트롤 봇

칠할 수있는 가장 가까운 봇을 선택하고 팔로우합니다. 유효한 봇을 찾을 수 없으면 가장 가까운 빈 공간으로 이동하십시오. 빈 공간을 찾지 못하면 무작위로 이동하십시오.

참고 : Simon의 많은 기여

function(myself, grid, bots, gameInfo) {
    var c = myself[0];
    var x = myself[1];
    var y = myself[2];

    var cd = -1;
    var cx = -1;
    var cy = -1;
    var i;
    for(i = 0; i < bots.length; i++){
        var bc = bots[i][0];
        var bx = bots[i][1];
        var by = bots[i][2];

        if (c != bc && Math.abs(c-bc)%3 == 0) {
            var d = Math.abs(x-bx)+Math.abs(y-by);

            if (d > 0 && (cd == -1 || d<cd)) {
                cd = d;
                cx = bx;
                cy = by;
            }
        }
    }

    if (cd == -1) {
        var j;
        for(i=0; i<grid.length; i++) {
            for(j=0; j<grid.length; j++) {
                if (grid[i][j] == 0) {
                    var d = Math.abs(x-i)+Math.abs(y-j);
                    var sharingWithBot = (i == x && j == y && bots.filter((item) => item[1] == i && item[2] == j).length > 1);
                    if (!sharingWithBot && (cd == -1 || d<cd)) {
                        cd = d;
                        cx = i;
                        cy = j;
                    }
                }
            }
        }
    }


    var move;
    var dx = cx-x;
    var dy = cy-y;
    if (cd == -1) {
        move = ["up","down","left","right"][Math.random() *4 |0];
    } else if (dx == 0 && dy == 0) {
        move = "wait";
    } else if (Math.abs(dx) > Math.abs(dy) || (Math.abs(dx) == Math.abs(dy) && Math.random() * 2 < 1)) {
        if (dx > 0) {
            move = "right";
        } else {
            move = "left";
        }
    } else {
        if (dy > 0) {
            move = "down";
        } else {
            move = "up";
        }
    }
    return move;
}

3
이 아이디어가 마음에 들어서 사용해 보았습니다. 불행히도, 그것은 그대로 트롤을 시도합니다 (즉, 항상 따라야합니다). 변경 if (Math.abs(c-bc)%3 == 0) {if (c != bc && Math.abs(c-bc)%3 == 0) {} else if (dx>dy) {} else if (Math.abs(dx) > Math.abs(dy)) {의도 한대로 작동하는 것 같다.
Simon

아. 넵. 약간의 문제입니다. 지적 해 주셔서 감사합니다!
Lispy Louie

움직이지 않는 봇에 걸리지 않도록 다른 향상된 기능이 있습니다. 편집할까요?
Simon

계속하십시오 (이 코멘트를 추가 할 수 있도록 더 많은 문자)
Lispy Louie

10

¯ \ _ (ツ) _ / ¯ (임의의 움직임)

function(myself, grid, bots, gameInfo) {
    return ["up","down","left","right"][Math.random() *4 |0]
}

1
정보 : 이것은 임의의 필러보다 약 2 배 더 나쁩니다. 주로 보통 같은 위치에 있기 때문입니다.
user202729 2016 년

10

끊임없이 보드를 페인트하지만 화가는 아닌 봇

function (me, board, painters, info) {
    let id = me[0], meX = me[1], meY = me[2], s = board.length, ss = Math.ceil(s / 3), pl = painters.length, r = info[0], storage, sk = 'jijdfoadofsdfasz', s1, s2, scores = [], i, j;

    let bos = [
        [0, 0, ss - 1, ss - 1], [ss, 0, (ss * 2) - 1, ss - 1], [ss * 2, 0, s - 1, ss - 1], [ss * 2, ss, s - 1, (ss * 2) - 1],
        [ss * 2, ss * 2, s - 1, s - 1], [ss, ss * 2, (ss * 2) - 1, s - 1], [0, ss * 2, ss - 1, s - 1], [0, ss, ss - 1, (ss * 2) - 1],
    ];

    if (r === 1 || typeof this[sk] === 'undefined') {
        let n = ss + painters[0][1];
        s1 = bos[n % 8];
        s2 = bos[(n + 1) % 8];
        storage = this[sk] = {s1: s1, s2: s2, bs: null, c: 0};
    } else {
        storage = this[sk];
        s1 = storage.s1;
        s2 = storage.s2;
    }

    let getDistance = function (x1, y1, x2, y2) {
        return (Math.abs(x1 - x2) + Math.abs(y1 - y2)) + 1;
    };

    let getColorValue = function (c) {
        if (c === 0) return 2;
        if (c === id) return -1;
        let value = 2 - (Math.abs(id - c) % 3);
        if (value === 1) return 0.1;
        return value;
    };

    let getEnemyValue = function (eId) {
        if (eId === id) return 0;
        let value = 2 - (Math.abs(id - eId) % 3);
        return (value === 1 ? 1.75 : value);
    };

    let isInSection = function (x, y, s) {
        return (x >= s[0] && y >= s[1] && x <= s[2] && y <= s[3]);
    };

    let bs = null;
    if (storage.bs === null || storage.c <= 0) {
        let mysi = null;
        for (i = 0; i < bos.length; i++) {
            if (isInSection(meX, meY, bos[i])) mysi = i;
            if ((bos[i][0] === s1[0] && bos[i][1] === s1[1] && bos[i][2] === s1[2] && bos[i][3] === s1[3]) || (r < 5e2 && bos[i][0] === s2[0] && bos[i][1] === s2[1] && bos[i][2] === s2[2] && bos[i][3] === s2[3])) {
                scores[i] = -100000;
            } else {
                scores[i] = 0;
                for (let bX = Math.max(bos[i][0], 1); bX < Math.min(bos[i][2], s - 1); bX++) for (let bY = Math.max(bos[i][1], 1); bY < Math.min(bos[i][3], s - 1); bY++) scores[i] += getColorValue(board[bX][bY]);
                for (j = 0; j < pl; j++) {
                    let pId = painters[j][0], pX = painters[j][1], pY = painters[j][2];
                    if (pId === id || pX === 0 || pX === s - 1 || pY === 0 || pY === s - 1 || !isInSection(pX, pY, bos[i])) continue;
                    scores[i] -= (getEnemyValue(pId) * ss) * 4;
                }
            }
        }
        let bss = null;
        for (i = 0; i < scores.length; i++) {
            if (bss === null || bss < scores[i]) {
                bss = scores[i];
                bs = bos[i];
            }
        }
        if (mysi !== null && scores[mysi] * 1.1 > bss) bs = bos[mysi];
        storage.bs = bs;
        storage.c = 250;
    } else {
        bs = storage.bs;
        storage.c--;
    }

    let getScore = function (x, y) {
        let score = 0;
        if (!isInSection(x, y, bs)) score -= s * 10;
        for (let bX = bs[0]; bX <= bs[2]; bX++) {
            for (let bY = bs[1]; bY <= bs[3]; bY++) {
                let distance = getDistance(x, y, bX, bY);
                let colorValue = getColorValue(board[bX][bY]);
                let factor = 1;
                if (distance === 1) factor = 3;
                else if (distance === 2) factor = 2;
                score += (colorValue / (distance / 4)) * factor;
                if (x === meX && y === meY && x === bX && y === bY && colorValue < 2) score -= 1000;
            }
        }
        for (let i = 0; i < pl; i++) {
            let pId = painters[i][0], pX = painters[i][1], pY = painters[i][2];
            if (pId === id || pX === 0 || pX === s - 1 || pY === 0 || pY === s - 1) continue;
            let pDistance = getDistance(x, y, pX, pY);
            if (pDistance > 5) continue;
            let pIdValue = getEnemyValue(pId);
            let factor = 4;
            if (pDistance === 1) factor = 8;
            else if (pDistance === 2) factor = 6;
            else score -= (pIdValue / pDistance) * factor;
        }
        return score + (Math.random() * 10);
    };

    if (isInSection(meX, meY, bs)) {
        let possibleMoves = [{x: 0, y: 0, c: 'wait'}];
        if (meX > 1) possibleMoves.push({x: -1, y: 0, c: 'left'});
        if (meY > 1) possibleMoves.push({x: -0, y: -1, c: 'up'});
        if (meX < s - 2) possibleMoves.push({x: 1, y: 0, c: 'right'});
        if (meY < s - 2) possibleMoves.push({x: 0, y: 1, c: 'down'});
        let topCommand, topScore = null;
        for (i = 0; i < possibleMoves.length; i++) {
            let score = getScore(meX + possibleMoves[i].x, meY + possibleMoves[i].y);
            if (topScore === null || score > topScore) {
                topScore = score;
                topCommand = possibleMoves[i].c;
            }
        }
        return topCommand;
    } else {
        let dX = ((bs[0] + bs[2]) / 2) - meX, dY = ((bs[1] + bs[3]) / 2) - meY;
        if (Math.abs(dX) > Math.abs(dY)) return (dX < 0 ? 'left' : 'right');
        else return (dY < 0 ? 'up' : 'down');
    }
}

이 봇은 보드의 가장 좋은 영역을 찾으려고 노력합니다. 그런 다음 해당 영역에서 가능한 각 이동에 대한 점수를 생성하여 최상의 결정을 내립니다. 가장 좋은 영역은 일정 간격으로 계속 다시 선택되며 봇은 필요한 경우 새로운 더 나은 영역으로 이동합니다. 이 봇에는 나중에 설명 할 몇 가지 다른 세부 정보가 있습니다.


10

MC

function (myself, grid, bots, gameInfo) {

"use strict";

if (this.O == null) this.O = {};
const O = this.O;

// console.log(this);

const MAXBOTS = 60;
const MAXSZ = 3 * MAXBOTS;
const MAXID = MAXBOTS + 1;

if (gameInfo[0] == 1) {
    if (bots.length > MAXBOTS) {
        alert("ASSERTION FAILED: MAXBOTS EXCEEDED (contact @tomsmeding)");
        return 0;
    }

    for (const b of bots) {
        if (b[0] < 0 || b[0] > MAXID) {
            alert("ASSERTION FAILED: MAXID EXCEEDED (contact @tomsmeding)");
            return 0;
        }
    }
}

function from_base64(bs) {
    if (bs.length % 4 != 0) throw new Error("Invalid Base64 string");

    const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    const beta = new Array(256).fill(-1);

    for (let i = 0; i < alpha.length; i++) beta[alpha.charCodeAt(i)] = i;

    const arrbuf = new ArrayBuffer(bs.length / 4 * 3 | 0);
    const buf = new Uint8Array(arrbuf);

    let j = 0;
    for (let i = 0; i < bs.length; i += 4) {
        buf[j++] = (beta[bs.charCodeAt(i+0)] << 2) | (beta[bs.charCodeAt(i+1)] >> 4);
        if (bs[i+2] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+1)] << 4) | (beta[bs.charCodeAt(i+2)] >> 2);
        if (bs[i+3] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+2)] << 6) | (beta[bs.charCodeAt(i+3)] >> 0);
    }

    return new Uint8Array(arrbuf, 0, j);
}

function repeat(str, times) {
    return new Array(times + 1).join(str);
}

function println_func(ptr) {
    let s = "";
    for (; ptr < O.wa_membuf.length; ptr++) {
        if (O.wa_membuf[ptr] == 0) break;
        s += String.fromCharCode(O.wa_membuf[ptr]);
    }
    console.log(s);
}

function print_int_func(value) {
    console.log(value);
}

function seed_random() {
    for (let i = 0; i < O.wa_rand_state.length; i++) {
        O.wa_rand_state[i] = (Math.random() * 256) & 0xff;
    }
}

function transfer_myself(myself) {
    O.wa_my_id[0] = myself[0];
}

function transfer_grid(grid) {
    const W = grid.length, H = grid[0].length;
    O.wa_width[0] = W;
    O.wa_height[0] = H;
    for (let x = 0; x < W; x++) {
        for (let y = 0; y < H; y++) {
            O.wa_grid[W * y + x] = grid[x][y];
        }
    }
}

function transfer_bots(bots) {
    O.wa_nbots[0] = bots.length;
    for (let i = 0; i < bots.length; i++) {
        O.wa_bots[3 * i + 0] = bots[i][0];
        O.wa_bots[3 * i + 1] = bots[i][1];
        O.wa_bots[3 * i + 2] = bots[i][2];
    }
}

function transfer_gameInfo(gameInfo) {
    O.wa_round_idx[0] = gameInfo[0];
    O.wa_total_rounds[0] = gameInfo[1];
}

function stringify(thing) {
    if (Array.isArray(thing)) {
        return "[" + thing.map(stringify).join(",") + "]";
    } else if (thing instanceof Int8Array) {
        return "[" + thing.toString() + "]";
    } else {
        return thing.toString();
    }
}

function mc_calcmove() {
    // console.log("mc_calcmove(" + stringify(myself) + "," + stringify(grid) + "," + stringify(bots) + "," + stringify(gameInfo) + ")");
    transfer_myself(myself);
    transfer_grid(grid);
    transfer_bots(bots);
    transfer_gameInfo(gameInfo);
    return ["right", "down", "left", "up", "wait"][O.wa_mc_calcmove()];
    // return O.wa_mc_calcmove();
}

if (O.wasm_bytes == null) {
    O.wasm_bytes = from_base64(
// INSERT-WASM-HERE
"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AAC0BgAAvQYAAL8GAADIBgAAygYAACIHAAAkBwAALwcAADEHAAA6BwAAPAcAAEcHAABJBwAAUgcAAFQHAABkBwAAZgcAAG8HAABxBwAAegcAAHwHAACFBwAAhwcAAJAHAACSBwAArwcAALEHAAC6BwAAvAcAAMUHAADHBwAA0AcAANIHAADbBwAA3QcAAPUHAAD3BwAAAAgAAAIIAAALCAAADQgAABYIAAAYCAAAIQgAACMIAAAAAAAAAAAAAIcDAACLAwAAogMAANIDAAAAAAAAAAAAAD4FAACcBgAAngYAAKcGAACpBgAAsgYAALQGAAC9BgAAvwYAAMgGAADKBgAAIgcAACQHAAAvBwAAMQcAADoHAAA8BwAARwcAAEkHAABSBwAAVAcAAGMHAABmBwAAbwcAAHEHAAB6BwAAfAcAAIUHAACHBwAAkAcAAJIHAACnBwAAsQcAALoHAAC8BwAAxQcAAMcHAADQBwAA0gcAANsHAADdBwAA9AcAAPcHAAAACAAAAggAAAsIAAANCAAAFggAABgIAAAhCAAAIwgAAEUIAABWCAAA8QgAAAAJAAAHCQAAAAAAAAAAAABfBQAAmgUAAB8GAAA0BgAAngYAAKcGAACpBgAAsgYAALQGAAC9BgAAvwYAAMgGAADKBgAA2AYAACYHAAAvBwAAMQcAADoHAAA+BwAARwcAAEkHAABSBwAAVAcAAGMHAABmBwAAbwcAAHEHAAB6BwAAfAcAAIUHAACHBwAAkAcAAJIHAACgBwAAsQcAALoHAAC8BwAAxQcAAMcHAADQBwAA0gcAANsHAADdBwAA6wcAAPcHAAAACAAAAggAAAsIAAANCAAAFggAABgIAAAhCAAAIwgAADEIAAAAAAAAAAAAAF8FAACaBQAAHwYAADQGAACeBgAApwYAAKkGAACyBgAAtAYAAL0GAAC/BgAAyAYAAMoGAADYBgAAJgcAAC8HAAAxBwAAOgcAAD4HAABHBwAASQcAAFIHAABUBwAAYwcAAGYHAABvBwAAcQcAAHoHAAB8BwAAhQcAAIcHAACQBwAAkgcAAKAHAACxBwAAugcAALwHAADFBwAAxwcAANAHAADSBwAA2wcAAN0HAADrBwAA9wcAAAAIAAACCAAACwgAAA0IAAAWCAAAGAgAACEIAAAjCAAAMQgAAAAAAAAAAAAAXwUAAJoFAAAVBgAAnAYAAJ4GAACnBgAAqQYAALIGAAC0BgAAvQYAAL8GAADIBgAAygYAAOEGAAAmBwAALwcAADEHAAA6BwAAPgcAAEcHAABJBwAAUgcAAFQHAABjBwAAZgcAAG8HAABxBwAAegcAAHwHAACFBwAAhwcAAJAHAACSBwAApwcAALEHAAC6BwAAvAcAAMUHAADHBwAA0AcAANIHAADbBwAA3QcAAPQHAAD3BwAAAAgAAAIIAAALCAAADQgAABYIAAAYCAAAIQgAACMIAABFCAAAAAAAAAAAAAC2BQAA7QUAAOYGAAABBwAAHgcAACIHAAAkBwAAJgcAADwHAAA+BwAAAAAAAAAAAAC2BQAA7QUAAOYGAAABBwAAHgcAACIHAAAkBwAAJgcAADwHAAA+BwAAAAAAAAAAAABfBQAAnAYAAJ4GAACnBgAAqQYAALIGAAC0BgAAvQYAAL8GAADIBgAAygYAACIHAAAkBwAALwcAADEHAAA6BwAAPAcAAEcHAABJBwAAUgcAAFQHAABjBwAAZgcAAG8HAABxBwAAegcAAHwHAACFBwAAhwcAAJAHAACSBwAApwcAALEHAAC6BwAAvAcAAMUHAADHBwAA0AcAANIHAADbBwAA3QcAAPQHAAD3BwAAAAgAAAIIAAALCAAADQgAABYIAAAYCAAAIQgAACMIAABFCAAAVggAAOQIAAAAAAAAAAAAAF8FAACcBgAAngYAAKcGAACpBgAAsgYAALQGAAC9BgAAvwYAAMgGAADKBgAAIgcAACQHAAAvBwAAMQcAADoHAAA8BwAARwcAAEkHAABSBwAAVAcAAGMHAABmBwAAbwcAAHEHAAB6BwAAfAcAAIUHAACHBwAAkAcAAJIHAACnBwAAsQcAALoHAAC8BwAAxQcAAMcHAADQBwAA0gcAANsHAADdBwAA9AcAAPcHAAAACAAAAggAAAsIAAANCAAAFggAABgIAAAhCAAAIwgAAEUIAABWCAAA8QgAAAAAAAAAAAAAPgUAAJwGAACeBgAApwYAAKkGAACyBgAAtAYAAL0GAAC/BgAAyAYAAMoGAAAiBwAAJAcAAC8HAAAxBwAAOgcAADwHAABHBwAASQcAAFIHAABUBwAAYwcAAGYHAABvBwAAcQcAAHoHAAB8BwAAhQcAAIcHAACQBwAAkgcAAKcHAACxBwAAugcAALwHAADFBwAAxwcAANAHAADSBwAA2wcAAN0HAAD0BwAA9wcAAAAIAAACCAAACwgAAA0IAAAWCAAAGAgAACEIAAAjCAAARQgAAFYIAAAVCQAAAAAAAAAAAACfBAAAnAYAAJ4GAACnBgAAqQYAALIGAAC0BgAAvQYAAL8GAADIBgAAygYAACIHAAAkBwAALwcAADEHAAA6BwAAPAcAAEcHAABJBwAAUgcAAFQHAABjBwAAZgcAAG8HAABxBwAAegcAAHwHAACFBwAAhwcAAJAHAACSBwAApwcAALEHAAC6BwAAvAcAAMUHAADHBwAA0AcAANIHAADbBwAA3QcAAPQHAAD3BwAAAAgAAAIIAAALCAAADQgAABYIAAAYCAAAIQgAACMIAABFCAAAVggAAFQJAAAAAAAAAAAAAJ8EAACcBgAAngYAAKcGAACpBgAAsgYAALQGAAC9BgAAvwYAAMgGAADKBgAAIgcAACQHAAAvBwAAMQcAADoHAAA8BwAARwcAAEkHAABSBwAAVAcAAGMHAABmBwAAbwcAAHEHAAB6BwAAfAcAAIUHAACHBwAAkAcAAJIHAACnBwAAsQcAALoHAAC8BwAAxQcAAMcHAADQBwAA0gcAANsHAADdBwAA9AcAAPcHAAAACAAAAggAAAsIAAANCAAAFggAABgIAAAhCAAAIwgAAEUIAABWCAAAYQkAAAAAAAAAAAAARgIAAF4CAABiAgAAhAIAAIYCAACcBgAAngYAAKcGAACpBgAAsgYAALQGAAC9BgAAvwYAAMgGAADKBgAAIgcAACQHAAAvBwAAMQcAADoHAAA8BwAARwcAAEkHAABSBwAAVAcAAGMHAABmBwAAbwcAAHEHAAB6BwAAfAcAAIUHAACHBwAAkAcAAJIHAACnBwAAsQcAALoHAAC8BwAAxQcAAMcHAADQBwAA0gcAANsHAADdBwAA9AcAAPcHAAAACAAAAggAAAsIAAANCAAAFggAABgIAAAhCAAAIwgAAEUIAABWCAAAeQkAAAAAAAAAAAAAgQEAAIYBAACpAQAAXgIAAGICAACEAgAAhgIAAJwGAACeBgAApwYAAKkGAACyBgAAtAYAAL0GAAC/BgAAyAYAAMoGAAAiBwAAJAcAAC8HAAAxBwAAOgcAADwHAABHBwAASQcAAFIHAABUBwAAYwcAAGYHAABvBwAAcQcAAHoH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"
    );

    // require("fs").writeFileSync("reverse-base64-output.txt", Buffer.from(O.wasm_bytes));

    O.memory = new WebAssembly.Memory({initial: 15});
    // O.importObject = {js: {mem: O.memory}, env: {println: println_func}};
    O.importObject = {
        env: {
            println: println_func,
            print_int: print_int_func,
            __linear_memory: O.memory,
            __indirect_function_table: new WebAssembly.Table({initial: 0, element: "anyfunc"}),
        },
    };

    // let wa_membuf, wa_width, wa_height, wa_nbots, wa_round_idx, wa_total_rounds, wa_my_id, wa_grid, wa_bots, wa_rand_state;

    /*const promise = fetch('../out/main.wasm').then(response =>
        response.arrayBuffer()
    ).then(bytes =>
        WebAssembly.instantiate(bytes, O.importObject)
    );*/
    // const promise = WebAssembly.instantiate(fs.readFileSync("hotpatcher/out.wasm"), O.importObject);
    const promise = WebAssembly.instantiate(O.wasm_bytes, O.importObject);

    promise.then(results => {
        const instance = results.instance;

        // console.log(instance.exports);

        // First set some pointers
        instance.exports.entry_fn(0);

        O.wa_membuf = new Uint8Array(O.memory.buffer);
        const ptrs = new Uint32Array(O.memory.buffer, 0, 9 * 4);

        O.wa_width = new Int32Array(O.memory.buffer, ptrs[0], 1);
        O.wa_height = new Int32Array(O.memory.buffer, ptrs[1], 1);
        O.wa_nbots = new Int32Array(O.memory.buffer, ptrs[2], 1);
        O.wa_round_idx = new Int32Array(O.memory.buffer, ptrs[3], 1);
        O.wa_total_rounds = new Int32Array(O.memory.buffer, ptrs[4], 1);
        O.wa_my_id = new Int32Array(O.memory.buffer, ptrs[5], 1);
        O.wa_grid = new Uint8Array(O.memory.buffer, ptrs[6], MAXSZ * MAXSZ);
        O.wa_bots = new Uint8Array(O.memory.buffer, ptrs[7], MAXBOTS * 3);
        O.wa_rand_state = new Uint8Array(O.memory.buffer, ptrs[8], 5 * 4);

        O.wa_mc_calcmove = function() { return instance.exports.entry_fn(2); }

        seed_random();

        // Signal that we're done setting up, and the wasm code can set itself up
        instance.exports.entry_fn(1);

        O.instantiated = true;
        console.log("MC: Instantiated!");
    }).catch(console.error);
}

if (gameInfo[0] > 5) {
    if (O.instantiated) {
        // const start = new Date();
        const output = mc_calcmove();
        // const end = new Date();


        // if (O.time_sum == null) O.time_sum = 0;
        // O.time_sum += end - start;

        // if (gameInfo[0] % 50 == 0) {
        //     console.log("Average time taken: " + O.time_sum / (gameInfo[0] - 1));
        // }

        return output;
    } else {
        throw new Error("SCREAM FIRE wasm instantiation");
    }
} else {
    console.log("MC: RANDOM MOVE BEFORE INSTANTIATE");
    return ["right", "down", "left", "up"][Math.random() * 4 | 0];
}

}

몬테카를로 봇. 5 개의 가능한 움직임마다 100 개의 랜덤 플레이 아웃을합니다. 여기서 "플레이 아웃"은 모든 사람으로부터 5 개의 랜덤으로 움직입니다. 어쨌든 물건은 예측할 수 없기 때문에 단지 5입니다. 각 플레이 아웃이 끝나면 봇의 점수가 계산됩니다. 최상의 플레이 아웃 결과로 이동합니다.

이 봇은 WebAssembly로 코딩되었습니다. 설명은 내 P 봇 을 참조하십시오 .


9

function (myself, grid, bots, gameInfo) {

"use strict";

if (this.O == null) this.O = {};
const O = this.O;

// console.log(this);

const MAXBOTS = 60;
const MAXSZ = 3 * MAXBOTS;
const MAXID = MAXBOTS + 1;

if (gameInfo[0] == 1) {
    if (bots.length > MAXBOTS) {
        alert("ASSERTION FAILED: MAXBOTS EXCEEDED (contact @tomsmeding)");
        return 0;
    }

    for (const b of bots) {
        if (b[0] < 0 || b[0] > MAXID) {
            alert("ASSERTION FAILED: MAXID EXCEEDED (contact @tomsmeding)");
            return 0;
        }
    }
}

function from_base64(bs) {
    if (bs.length % 4 != 0) throw new Error("Invalid Base64 string");

    const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    const beta = new Array(256).fill(-1);

    for (let i = 0; i < alpha.length; i++) beta[alpha.charCodeAt(i)] = i;

    const arrbuf = new ArrayBuffer(bs.length / 4 * 3 | 0);
    const buf = new Uint8Array(arrbuf);

    let j = 0;
    for (let i = 0; i < bs.length; i += 4) {
        buf[j++] = (beta[bs.charCodeAt(i+0)] << 2) | (beta[bs.charCodeAt(i+1)] >> 4);
        if (bs[i+2] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+1)] << 4) | (beta[bs.charCodeAt(i+2)] >> 2);
        if (bs[i+3] == "=") break;
        buf[j++] = (beta[bs.charCodeAt(i+2)] << 6) | (beta[bs.charCodeAt(i+3)] >> 0);
    }

    return new Uint8Array(arrbuf, 0, j);
}

function repeat(str, times) {
    return new Array(times + 1).join(str);
}

function println_func(ptr) {
    let s = "";
    for (; ptr < O.wa_membuf.length; ptr++) {
        if (O.wa_membuf[ptr] == 0) break;
        s += String.fromCharCode(O.wa_membuf[ptr]);
    }
    console.log(s);
}

function print_int_func(value) {
    console.log(value);
}

function seed_random() {
    for (let i = 0; i < O.wa_rand_state.length; i++) {
        O.wa_rand_state[i] = (Math.random() * 256) & 0xff;
    }
}

function transfer_myself(myself) {
    O.wa_my_id[0] = myself[0];
}

function transfer_grid(grid) {
    const W = grid.length, H = grid[0].length;
    O.wa_width[0] = W;
    O.wa_height[0] = H;
    for (let x = 0; x < W; x++) {
        for (let y = 0; y < H; y++) {
            O.wa_grid[W * y + x] = grid[x][y];
        }
    }
}

function transfer_bots(bots) {
    O.wa_nbots[0] = bots.length;
    for (let i = 0; i < bots.length; i++) {
        O.wa_bots[3 * i + 0] = bots[i][0];
        O.wa_bots[3 * i + 1] = bots[i][1];
        O.wa_bots[3 * i + 2] = bots[i][2];
    }
}

function transfer_gameInfo(gameInfo) {
    O.wa_round_idx[0] = gameInfo[0];
    O.wa_total_rounds[0] = gameInfo[1];
}

function stringify(thing) {
    if (Array.isArray(thing)) {
        return "[" + thing.map(stringify).join(",") + "]";
    } else if (thing instanceof Int8Array) {
        return "[" + thing.toString() + "]";
    } else {
        return thing.toString();
    }
}

function mc_calcmove() {
    // console.log("mc_calcmove(" + stringify(myself) + "," + stringify(grid) + "," + stringify(bots) + "," + stringify(gameInfo) + ")");
    transfer_myself(myself);
    transfer_grid(grid);
    transfer_bots(bots);
    transfer_gameInfo(gameInfo);
    return ["right", "down", "left", "up", "wait"][O.wa_mc_calcmove()];
    // return O.wa_mc_calcmove();
}

if (O.wasm_bytes == null) {
    O.wasm_bytes = from_base64(
// INSERT-WASM-HERE
"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LmdpdCAwZTAxMjk4NGIwOTkwMzJkZDhhNjUxNTFhNzg4MmMzMDVlMWYzN2I0KSAoaHR0cDovL2xsdm0ub3JnL2dpdC9sbHZtLmdpdCAzZDc2NWNlNGI3ZjJmZDI1YmRiYzBlZmMyNmFmZGY0MmU4NGZlY2IyKQBtYWluLmMAL2hvbWUvdG9tL3BwY2ctMTcwOTA4L3dhc20AcHRycwBfX0FSUkFZX1NJWkVfVFlQRV9fAHdpZHRoAGludABoZWlnaHQAbmJvdHMAcm91bmRfaWR4AHRvdGFsX3JvdW5kcwBteV9pZABncmlkAHVuc2lnbmVkIGNoYXIAdWludDhfdABib3RzAGlkAHgAeQBib3QAcmFuZF9zdGF0ZQB1bnNpZ25lZCBpbnQAdWludDMyX3QAaGF2ZV9iZWVuAF9Cb29sAHByZXZpb3VzX2lzX21lAGJvdF9ldmlsX3Njb3JlAGJvdF9keABib3RfZHkAZGlyX2R4AGRpcl9keQBwbGFjZV9oYXNfYm90AGJvdF9wcmV2cG9zAGhlYXRfbWFwAGZsb2F0AGV2aWxfZmFjdG9yX2NhY2hlAHByZXZfcm91bmRfaWR4AHN0YXRlAGxvbmcgbG9uZyB1bnNpZ25lZCBpbnQAdWludDY0X3QAcF9zZXR1cF9kYXRhAG1lbXNldF94AGRzdF8AdmFsdWUAbgBkc3Q2NABkc3Q4AGkAcG9wdWxhdGVfcHRycwBwX2NhbGNtb3ZlAG1lSWR4AG1heG51bQBtYXhhdABwb3MAbl9fAHBhaW50X3ZhbHVlAGZsb29yAHBfcG9wdWxhdGVfaGVhdG1hcABwX2ZpbGxfZXZpbF9mYWN0b3JfY2FjaGUAY3gAY3kAZHkAZHgAcF9ldmlsX2ZhY3RvcgBzYwBieQBieABqAGQAZGV0ZXJtaW5pc3RpY19yYW5kAHhvcndvdwB0AHMAcF9wYWludFNjb3JlAGlkeABlbnRyeV9mbgBwX2ZpbmRwYXRoAG1vZGUAZGVwdGgAb3JpZ19oYXZlX2JlZW4AAP4OCi5kZWJ1Z19sb2MrAAAAXAAAAAMAEQCfAAAAAAAAAACnAAAABxQAAAMAEACfAAAAAAAAAACnAAAAugAAAAMAEQCfAAAAAAAAAAD3AAAA9wAAAAMAEQCf9wAAAA0BAAADABEBnw0BAAAYAQAAAwARAp8YAQAAIwEAAAMAEQOfIwEAAC4BAAADABEEny4BAAA5AQAAAwARBZ85AQAARAEAAAMAEQafRAEAAE8BAAADABEHn08BAABaAQAAAwARCJ9aAQAAZQEAAAMAEQmfZQEAAHABAAADABEKn3ABAAB7AQAAAwARC597AQAAhgEAAAMAEQyfhgEAAJEBAAADABENn5EBAACcAQAAAwARDp+cAQAApwEAAAMAEQ+fpwEAALIBAAADABEQn7IBAAC9AQAAAwAREZ+9AQAAyAEAAAMAERKfyAEAANMBAAADABEUn9MBAADeAQAAAwARE5/eAQAA6QEAAAMAERWf6QEAAPQBAAADABEWn/QBAAD/AQAAAwARF5//AQAACgIAAAMAERifCgIAABUCAAADABEZnxUCAAAgAgAAAwARGp8gAgAAKwIAAAMAERufKwIAADYCAAADABEcnzYCAABBAgAAAwARHZ9BAgAATAIAAAMAER6fTAIAAAcUAAADABEfnwAAAAAAAAAATAIAAFcCAAADABEAn1cCAABiAgAAAwARAZ9iAgAAbQIAAAMAEQKfbQIAAHgCAAADABEDn3gCAACDAgAAAwARBJ+DAgAAjgIAAAMAEQafjgIAAJkCAAADABEFn5kCAACkAgAAAwARB5+kAgAArwIAAAMAEQifrwIAALoCAAADABEJn7oCAADFAgAAAwARCp/FAgAA0AIAAAMAEQuf0AIAANsCAAADABEMn9sCAADmAgAAAwARDZ/mAgAA8QIAAAMAEQ6f8QIAAPwCAAADABEPn/wCAAAHAwAAAwAREJ8HAwAAEgMAAAMAERGfEgMAAB0DAAADABESnx0DAAAoAwAAAwARE58oAwAAMwMAAAMAERSfMwMAAD4DAAADABEVnz4DAABJAwAAAwARFp9JAwAAVAMAAAMAERefVAMAAF8DAAADABEYn18DAABqAwAAAwARGZ9qAwAAdQMAAAMAERqfdQMAAIADAAADABEbn4ADAACLAwAAAwARHJ+LAwAAlgMAAAMAER2flgMAAKEDAAADABEen6EDAAAHFAAAAwARH58AAAAAAAAAAKEDAACsAwAAAwARAJ+sAwAAtwMAAAMAEQGftwMAAMIDAAADABECn8IDAADNAwAAAwARA5/NAwAA2AMAAAMAEQSf2AMAAOMDAAADABEFn+MDAADuAwAAAwARBp/uAwAA+QMAAAMAEQef+QMAAAQEAAADABEInwQEAAAPBAAAAwARCZ8PBAAAGgQAAAMAEQqfGgQAACUEAAADABELnyUEAAAwBAAAAwARDJ8wBAAAOwQAAAMAEQ2fOwQAAEYEAAADABEOn0YEAABRBAAAAwARD59RBAAAXAQAAAMAERCfXAQAAGcEAAADABERn2cEAAByBAAAAwAREp9yBAAAfQQAAAMAEROffQQAAIgEAAADABEUn4gEAACTBAAAAwARFZ+TBAAAngQAAAMAERafngQAAKkEAAADABEXn6kEAAC0BAAAAwARGJ+0BAAAvwQAAAMAERmfvwQAAMoEAAADABEan8oEAADVBAAAAwARG5/VBAAA4AQAAAMAERyf4AQAAOsEAAADABEdn+sEAADrBAAAAwARHp/rBAAABxQAAAMAER+fAAAAAAAAAACyBQAA5wUAAAMAEQCfAAAAAAAAAAAsBgAAhAYAAAMAEQCfAAAAAAAAAABPCgAAhAoAAAMAEQCfAAAAAAAAAADBCwAAzAsAAAMAEXifAAAAAAAAAADjCwAACAwAAAMAEXifcg0AAJsNAAADABF4n5sNAADODQAAAwAReZ/ODQAAAQ4AAAMAEXqfAQ4AADQOAAADABF7nzQOAABnDgAAAwARfJ9nDgAAmg4AAAMAEX2fmg4AAM0OAAADABF+n80OAADdDgAAAwARf5/dDgAAIw8AAAMAEQCfIw8AAEMPAAADABEBn0MPAABoDwAAAwARAp9oDwAAjQ8AAAMAEQOfjQ8AALIPAAADABEEn7IPAADXDwAAAwARBZ/XDwAA/A8AAAMAEQaf/A8AACEQAAADABEHnyEQAAAHFAAAAwARCJ8AAAAAAAAAAIYNAAAHFAAAAwAQAJ8AAAAAAAAAAIYNAAAHFAAAAwARAJ8AAAAAAAAAAGQMAABkDAAAAwARAJ9kDAAAZAwAAAMAEQKfZAwAADkNAAADABEBnzkNAAAHFAAAAwARA58AAAAAAAAAAGEQAACAEAAABwAQgICA/AufAAAAAAAAAABhEAAAgBAAAAMAEX+fjRAAANgQAAADABEAn+oQAAAxEQAAAwARAZ8+EQAAgREAAAMAEQKfihEAAIATAAADABEDnwAAAAAAAAAAgRMAAKcTAAADABEAnwAAAAAAAAAAxhQAANkUAAAHABCAgID8C58AAAAAAAAAAACQBA0uZGVidWdfYWJicmV2AREBJQ4TBQMOEBcbDhEBVRcAAAI0AAMOSRM/GToLOwsCGAAAAwEBSRMAAAQhAEkTNwsAAAUPAAAABiQAAw4LCz4LAAAHJAADDj4LCwsAAAghAEkTNwUAAAkWAEkTAw46CzsLAAAKEwEDDgsLOgs7CwAACw0AAw5JEzoLOws4CwAADDQAAw5JEzoLOwsCGAAADS4BAAAONAADDkkTOgs7BQIYAAAPJgBJEwAAEA8ASRMAABEuAAMOOgs7CyALAAASLgEDDjoLOwsnGSALAAATBQADDjoLOwtJEwAAFDQAAw46CzsLSRMAABULAQAAFi4AAw46CzsFIAsAABcuAQMOOgs7BUkTIAsAABg0AAMOOgs7BUkTAAAZLgEDDjoLOwsnGUkTIAsAABouAQMOOgs7CyALAAAbLgERARIGAw46CzsFJxlJEz8ZAAAcBQADDjoLOwVJEwAAHR0BMRMRARIGWAtZBQAAHh0BMRMRARIGWAtZCwAAHwUAHA8xEwAAIAUAMRMAACELAVUXAAAiNAACFzETAAAjCwERARIGAAAkNAAxEwAAJQUAAhcxEwAAJh0BMRNVF1gLWQsAACcFABwNMRMAACgdATETVRdYC1kFAAApHQAxExEBEgZYC1kFAAAqLgERARIGAw46CzsLJxlJEwAAKzQAAhcDDjoLOwtJEwAAAADoEwsuZGVidWdfaW5mb9gJAAAEAAAAAAAEAQAAAAAMAKUAAAAAAAAArAAAAAAAAAAACwAAAscAAAA3AAAAARcFAwAAAAADQwAAAAREAAAACQAFBswAAAAIBwLgAAAAXAAAAAEZBQMoAAAAB+YAAAAFBALqAAAAXAAAAAEaBQMsAAAAAvEAAABcAAAAARsFAzAAAAAC9wAAAFwAAAABHAUDNAAAAAIBAQAAXAAAAAEdBQM4AAAAAg4BAABcAAAAAR4FAzwAAAACFAEAAMkAAAABIAUDQAAAAAPWAAAACEQAAACQfgAJ4QAAACcBAAABBwcZAQAACAECLwEAAPkAAAABIgUD0H4AAAMFAQAABEQAAAA8AAo7AQAAAwETCzQBAADWAAAAARQACzcBAADWAAAAARQBCzkBAADWAAAAARQCAAI/AQAAQwEAAAEpBQOQfwAAA08BAAAERAAAAAUACVoBAABXAQAAAQgHSgEAAAcEDGABAAByAQAAAZMFA7B/AAADfwEAAAhEAAAAkH4AB2oBAAACAQxwAQAAcgEAAAGUBQNA/gAADH8BAACoAQAAAZUFA9B8AQADXAAAAAREAAAAPgAMjgEAAKgBAAABlwUD0H0BAAyVAQAAqAEAAAGYBQPQfgEADQ6cAQAA/AEAAAFZAQUDoPkDAA6jAQAA/AEAAAFaAQUDsPkDAAADCAIAAAREAAAABAAPXAAAAAyqAQAAcgEAAAGbBQPQfwEADLgBAACoAQAAAZYFA2D+AQAMxAEAAEACAAABnAUDwPkDAANNAgAACEQAAACQfgAHzQEAAAQEDNMBAABAAgAAAZ0FA2D/AQANDOUBAABcAAAAAT0FAwD0BQAM9AEAAEMBAAABPiMDEPQFAJMEAyD0BQCTBAMo9AUAkwQDMPQFAJMEA0D0BQCTBAAQrAIAAAm3AgAAEQIAAAEJB/oBAAAHCBDWAAAAERoCAAAB/AESJwIAAAFtARMwAgAAAW1DAAAAEzUCAAABbdYAAAATOwIAAAFtXAAAABQ9AgAAAW6nAgAAFEMCAAABc74CAAAVFEgCAAABb1wAAAAAFRRIAgAAAXRcAAAAAAAWSgIAAAH7AQEXWAIAAAEFAVwAAAABGGMCAAABJwFcAAAAGDcBAAABMQFcAAAAGDkBAAABMQFcAAAAGGkCAAABOAFNAgAAGHACAAABOQFcAAAAFRhIAgAAAQoBXAAAABUYdgIAAAELAVwAAAAAABUYSAIAAAEoAVwAAAAAFRh6AgAAAUoBTQIAAAAVGHoCAAABSwFNAgAAABUYegIAAAFMAU0CAAAAFRh6AgAAAU0BTQIAAAAVGEgCAAABVQFcAAAAAAAZfgIAAAF8XAAAAAETigIAAAF81gAAABM0AQAAAXzWAAAAABqQAgAAAewBFRRIAgAAAe1cAAAAAAASowIAAAG7ARO8AgAAAbtcAAAAE78CAAABu1wAAAAVFMICAAABvFwAAAAVFDkBAAABvVwAAAAVFMUCAAABwVwAAAAVFDcBAAABw1wAAAAAAAAAABnIAgAAAadNAgAAARM3AQAAAadcAAAAEzkBAAABp1wAAAAU1gIAAAGoTQIAABUUSAIAAAGqXAAAABUU2QIAAAGsXAAAABTcAgAAAaxcAAAAFRTfAgAAAa5cAAAAFRThAgAAAa9NAgAAAAAAAAAZ4wIAAAE8TwEAAAETYwIAAAE8XAAAAAAZ9gIAAAEtTwEAAAET9AEAAAEtJwUAABT9AgAAAS5PAQAAFP8CAAABLk8BAAAAEE8BAAAbAwAAAAQUAAASAwAAAQ4CXAAAABwmAwAAAQ4CXAAAAB3DAgAAKwAAAM0EAAABFwIeywIAAEIAAABuAAAAAf0fAN4CAAAg6QIAACEAAAAAIgAAAAALAwAAACN1AAAAMAAAACQYAwAAAAAeywIAALgAAAA9AAAAAf4lFQAAAN4CAAAg6QIAACO4AAAAGQAAACIqAAAACwMAAAAj2QAAABwAAAAkGAMAAAAAJssCAAAYAAAAAf8fAN4CAAAn+AHpAgAAIRgCAAAiPwAAAAsDAAAAACjLAgAAGAQAAAEAASEYBQAAIucBAAALAwAAAAAoywIAABgGAAABAQEhGAcAACKPAwAACwMAAAAAACklAwAAIwUAAI0AAAABEAIoLgMAABgIAAABHAIkOwMAACRHAwAAJFMDAAAi4wYAAF8DAAAi/AYAAGsDAAAdywIAAM0FAABjAAAAAQkBHwDeAgAAIOkCAAAjzQUAADMAAAAiNwUAAAsDAAAAIwAGAAAwAAAAJBgDAAAAACGoCAAAIkwFAAB4AwAAIWAIAAAkhQMAACjoAwAASAgAAAEOASD0AwAAAAAAIy4KAAAhAAAAJJQDAAAAKAsEAADgCAAAAS8BIfgIAAAiYQUAABQEAAAe6AMAAKsKAAApAAAAAfUg/wMAAAAAACghBAAAEAkAAAE2ASApBAAAIDQEAAAhmAkAACJ2BQAAQAQAACGACQAAJEwEAAAjAAwAAEAEAAAiiwUAAFgEAAAhaAkAACRkBAAAHnQEAABHDAAABAEAAAHHIIAEAAAgiwQAACJ9BgAAlgQAACNHDAAABAEAACKSBgAAogQAACNkDAAA1QAAACSuBAAAJLkEAAAhSAkAACKnBgAAxQQAACEoCQAAJNEEAAAAAAAAAAAAAAAAI40QAABNAAAAJKIDAAAAI+oQAABHAAAAJLADAAAAIz4RAABCAAAAJL4DAAAAIbAJAAAkzAMAAAAd4QQAANQRAACkAQAAAVEBIO0EAAAm+QQAAMgJAAABQSQQBQAAJBsFAAAAJvkEAAAACgAAAU8kEAUAACQbBQAAACb5BAAAMAoAAAFMJBsFAAAkEAUAAAAm+QQAAEgKAAABSyQQBQAAJBsFAAAAHvkEAAAHEwAAFQAAAAFNJBsFAAAkEAUAAAAe+QQAACcTAAAZAAAAAU4kEAUAACQbBQAAAAAjgRMAAE8AAAAiRQcAANoDAAAAAAAZAQMAAAGfTQIAAAETDgMAAAGfXAAAABSKAgAAAaDiCAAAAA/WAAAAKgkUAADpAgAAGwMAAAHOTQIAABM3AQAAAc5cAAAAEzkBAAABzlwAAAATKwMAAAHOXAAAABQOAwAAAc8IAgAAK1oHAABpAgAAAddNAgAAFDEDAAAB1H8BAAAmvwgAAGAKAAAB0SDLCAAAJNYIAAAm6AMAAIAKAAABoiD0AwAAAAAj4BQAADwAAAAUegIAAAHgTQIAAAAhmAoAABR6AgAAAeFNAgAAACN+FQAANgAAABR6AgAAAeJNAgAAACG4CgAAFHoCAAAB400CAAAAJr8IAADQCgAAAekgywgAACTWCAAAJugDAADoCgAAAaIg9AMAAAAAAAAAphYNLmRlYnVnX3Jhbmdlc0IAAAB1AAAApwAAALAAAAAAAAAAAAAAAPcAAABQAgAAVwIAAFsCAABiAgAAZgIAAG0CAABxAgAAeAIAAHwCAACDAgAAhwIAAI4CAACSAgAAmQIAAJ0CAACkAgAAqAIAAK8CAACzAgAAugIAAL4CAADFAgAAyQIAANACAADUAgAA2wIAAN8CAADmAgAA6gIAAPECAAD1AgAA/AIAAAADAAAHAwAACwMAABIDAAAWAwAAHQMAACEDAAAoAwAALAMAADMDAAA3AwAAPgMAAEIDAABJAwAATQMAAFQDAABYAwAAXwMAAGMDAABqAwAAbgMAAHUDAAB5AwAAgAMAAIQDAACLAwAAjwMAAJYDAACaAwAAoQMAAKUDAACsAwAAsAMAALcDAAC7AwAAwgMAAMYDAADNAwAA0QMAANgDAADcAwAA4wMAAOcDAADuAwAA8gMAAPkDAAD9AwAABAQAAAgEAAAPBAAAEwQAABoEAAAeBAAAJQQAACkEAAAwBAAANAQAADsEAAA/BAAARgQAAEoEAABRBAAAVQQAAFwEAABgBAAAZwQAAGsEAAByBAAAdgQAAH0EAACBBAAAiAQAAIwEAACTBAAAlwQAAJ4EAACiBAAAqQQAAK0EAAC0BAAAuAQAAL8EAADDBAAAygQAAM4EAADVBAAA2QQAAOAEAADkBAAA6wQAAO8EAAD2BAAA+AQAAAAAAAAAAAAA9wAAAFACAABXAgAAWwIAAGICAABmAgAAbQIAAHECAAB4AgAAfAIAAIMCAACHAgAAjgIAAJICAACZAgAAnQIAAKQCAACoAgAArwIAALMCAAC6AgAAvgIAAMUCAADJAgAA0AIAANQCAADbAgAA3wIAAOYCAADqAgAA8QIAAPUCAAD8AgAAAAMAAAcDAAALAwAAEgMAABYDAAAdAwAAIQMAACgDAAAsAwAAMwMAADcDAAA+AwAAQgMAAEkDAABNAwAAVAMAAFgDAABfAwAAYwMAAGoDAABuAwAAdQMAAHkDAACAAwAAhAMAAIsDAACPAwAAlgMAAJoDAAChAwAApQMAAKwDAACwAwAAtwMAALsDAADCAwAAxgMAAM0DAADRAwAA2AMAANwDAADjAwAA5wMAAO4DAADyAwAA+QMAAP0DAAAEBAAACAQAAA8EAAATBAAAGgQAAB4EAAAlBAAAKQQAADAEAAA0BAAAOwQAAD8EAABGBAAASgQAAFEEAABVBAAAXAQAAGAEAABnBAAAawQAAHIEAAB2BAAAfQQAAIEEAACIBAAAjAQAAJMEAACXBAAAngQAAKIEAACpBAAArQQAALQEAAC4BAAAvwQAAMMEAADKBAAAzgQAANUEAADZBAAA4AQAAOQEAADrBAAA7wQAAPYEAAD4BAAAAAAAAAAAAABQAgAAVwIAAFsCAABiAgAAZgIAAG0CAABxAgAAeAIAAHwCAACDAgAAhwIAAI4CAACSAgAAmQIAAJ0CAACkAgAAqAIAAK8CAACzAgAAugIAAL4CAADFAgAAyQIAANACAADUAgAA2wIAAN8CAADmAgAA6gIAAPECAAD1AgAA/AIAAAADAAAHAwAACwMAABIDAAAWAwAAHQMAACEDAAAoAwAALAMAADMDAAA3AwAAPgMAAEIDAABJAwAATQMAAFQDAABYAwAAXwMAAGMDAABqAwAAbgMAAHUDAAB5AwAAgAMAAIQDAACLAwAAjwMAAJYDAACaAwAAoQMAAAAAAAAAAAAAUAIAAFcCAABbAgAAYgIAAGYCAABtAgAAcQIAAHgCAAB8AgAAgwIAAIcCAACOAgAAkgIAAJkCAACdAgAApAIAAKgCAACvAgAAswIAALoCAAC+AgAAxQIAAMkCAADQAgAA1AIAANsCAADfAgAA5gIAAOoCAADxAgAA9QIAAPwCAAAAAwAABwMAAAsDAAASAwAAFgMAAB0DAAAhAwAAKAMAACwDAAAzAwAANwMAAD4DAABCAwAASQMAAE0DAABUAwAAWAMAAF8DAABjAwAAagMAAG4DAAB1AwAAeQMAAIADAACEAwAAiwMAAI8DAACWAwAAmgMAAKEDAAAAAAAAAAAAAKUDAACsAwAAsAMAALcDAAC7AwAAwgMAAMYDAADNAwAA0QMAANgDAADcAwAA4wMAAOcDAADuAwAA8gMAAPkDAAD9AwAABAQAAAgEAAAPBAAAEwQAABoEAAAeBAAAJQQAACkEAAAwBAAANAQAADsEAAA/BAAARgQAAEoEAABRBAAAVQQAAFwEAABgBAAAZwQAAGsEAAByBAAAdgQAAH0EAACBBAAAiAQAAIwEAACTBAAAlwQAAJ4EAACiBAAAqQQAAK0EAAC0BAAAuAQAAL8EAADDBAAAygQAAM4EAADVBAAA2QQAAOAEAADkBAAA6wQAAO8EAAD2BAAAAAAAAAAAAAClAwAArAMAALADAAC3AwAAuwMAAMIDAADGAwAAzQMAANEDAADYAwAA3AMAAOMDAADnAwAA7gMAAPIDAAD5AwAA/QMAAAQEAAAIBAAADwQAABMEAAAaBAAAHgQAACUEAAApBAAAMAQAADQEAAA7BAAAPwQAAEYEAABKBAAAUQQAAFUEAABcBAAAYAQAAGcEAABrBAAAcgQAAHYEAAB9BAAAgQQAAIgEAACMBAAAkwQAAJcEAACeBAAAogQAAKkEAACtBAAAtAQAALgEAAC/BAAAwwQAAMoEAADOBAAA1QQAANkEAADgBAAA5AQAAOsEAADvBAAA9gQAAAAAAAAAAAAAsgUAADcHAABHBwAAJwgAADcIAABCCQAAUgkAAPEJAAABCgAAAxQAAAAAAAAAAAAAuAYAAOkGAADOCAAA+wgAAAAAAAAAAAAAdAYAADcHAABHBwAAiwcAAKgHAAAnCAAANwgAAHsIAACOCAAAQgkAAFIJAABgCQAAdgkAAPEJAAABCgAADwoAAAAAAAAAAAAAMAYAADcHAABHBwAAJwgAADcIAABCCQAAUgkAAPEJAAABCgAALgoAAPkLAAAADAAAAAAAAAAAAABPCgAAWAoAAF0KAAAfCwAAAAAAAAAAAABPCgAAWAoAAF0KAAAfCwAAAAAAAAAAAADQCwAA4wsAAAAMAABoEAAAAAAAAAAAAABkDAAAawwAAHMMAACFDAAAjQwAADkNAAAAAAAAAAAAAGQMAABrDAAAcwwAAIUMAACNDAAAOQ0AAAAAAAAAAAAADgwAAF8NAAB5DQAAQBAAAAAAAAAAAAAA0AsAAOMLAAAADAAAQBAAAAAAAAAAAAAA0AsAAOMLAAAADAAAaBAAAAAAAAAAAAAAjhEAAMMRAADNEQAA0xEAAAAAAAAAAAAA8REAAPoRAAD+EQAADBIAABASAAAeEgAAIhIAADISAAA2EgAASxIAAE0SAAB4EgAAAAAAAAAAAACMEgAAkxIAAO8SAAD4EgAAHBMAACUTAABAEwAASRMAAEsTAAB4EwAAAAAAAAAAAAClEgAAsRIAAOYSAADvEgAAAAAAAAAAAADFEgAAyRIAANkSAADmEgAAAAAAAAAAAABUFAAArhQAAH8WAACIFgAAxRYAAOsWAAAAAAAAAAAAAG4UAACgFAAAfxYAAIEWAAAAAAAAAAAAADAVAAAyFQAAORUAAEQVAABGFQAAbRUAAAAAAAAAAAAAvhUAAMAVAADHFQAA+xUAAAAAAAAAAAAAIhYAAH4WAACJFgAArRYAAAAAAAAAAAAAPhYAAHAWAACJFgAAixYAAAAAAAAAAAAAAwAAAAcUAAAJFAAA8hYAAAAAAAAAAAAAABAOLmRlYnVnX21hY2luZm8AAIEYCy5kZWJ1Z19saW5l8QsAAAQAHgAAAAEBAfsODQABAQEBAAAAAQAAAQBtYWluLmMAAAAAAAAFAgMAAAADjQQBBQYKyQUZA+59CJ4FIQYuBRl0BR+QBRgGA/J+WAUUBjwDkX+QBQwGA/AAugUUjwUCBroFFQZrBRwGdAOMf5AFCwYD9QBKBRzxBSIGLgUcWAOMf1gFFAYD7wAuBgORf5AFDAYD8ACCBRSPBgORf/IFCwYD9QCCBRzxBSIGLgUcWAOMf1gFDAYD8AAuBgLZAhJ0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnRKdEp0SnQFAQYDuQMuBgPXeyAFBgYDjwQgBQMDFQgS1wUBhgYD13sgBQoGA/wDIC/HCBTGMcUyxDPDNAN6yDUDecg2A3jIBQEDLWYGA9d7IAUdBgOJAiAFJQYuBR10BSOsBRgGA+Z+dAUUBjwFAnQDkX8uBQwGA/AAugUUjwUCBroFFQZrBRwGdAUCWAULBi8FHPEFIgYuBRxYBQI8A4x/SgUWBgOKAkoFFAYISgUCIAUWLgUCAiISA/Z9WAUdBgOLAlgGA/V9LgUTA4sCugUdSgUTggUpPAUfggUDBq0FFgY8BRIGPgUPBkoFElgFGzwFEQYD8H4uBRoGCHQFAlgFAAOCfzwFAgP+AEoFAAOCf5AFPQYDjgJYBQcGWAPyfUoFPQOOAjwFBzwD8n0uBgOQAghYBgggBR4GPQYD732QBSkGA5ICIAUOBlgD7n0uBSUGA5cCCIIFG3gFBHEFIwaQBT0uBSNYBRc8BQQGOwUjBpAFF6wD6X08BRQGA4oCIAUeBnQFFFgFAlgFHQatBgP1fS4FEwOLAroFHUoFE4IFKTwFH4IFAwatBRYGPAUSBj4FDwZKBRJYBRs8BQAD8n0uBQcGA5ACCEoGCCAFHgY9BgPvfZAFKQYDkgIgBQ4GWAPufS4FJQYDlwIIggUbeAUEcQUjBpAFPS4FI1gFFzwFBAY7BSMGkAUXrAPpfTwFFAYDigIgBQIGugUdBmcFEwbWBR1KBROCBSkgBR+CBQMGkQUWBjwFEgY+BQ8GSgUSWAUAA/J9WAURBgP+AEoFGgYIWAUCWAUAA4J/PAUCA/4ASgOCf3QFPQYDjgJYBQAGA/J9dAUHBgOQAghKBR4ISwYD732QBSkGA5ICIAUOBlgD7n0uBRsGA5sCCIIGA+V9WAUUBgOKAiAFHgZ0BRRYBQJYBR0GSwUTBtYFHUoFE4IFKSAFH4IFAwaRBRYGPAUSBj4FDwZKBRJYBQAD8n1YBQcGA5ACCEoFHghLBgPvfZAFKQYDkgIgBQ4GWAPufS4FGwYDmwIIggYD5X1YBRQGA4oCIAUCBroFEgYDHwggBQ8GSgUSWAUHIAUUBi0FHgZ0BRRYBQJYBRQGA0VKBQIGWAOTfkoFFAPtAVgFAlgDk34uBQQGA/MBCMgGA41+CGYFDAYD9AGsBQQGWAURBgOKfy4FGgYIWAUCWAUAA4J/PAUCA/4ASgOCf3QFIAYD9QEIZgUEBlgDi35YBQ8GA+4ByAURBi4FDzwFFAY7BScGdAUUWAUCWAUSBgPGAEoFFiwFKQbIBRIGaAUWOtgFAgY8BRuQA819ngUOBgO9AQJ1AQUJkQUHBiAFCQYvBQcGWAPBfi4FFAYDigIIWAUDA7d/dAYDv34uBQgGA8IBugUQBqwFGlgFDwY9BQqRPQYDu350BR4GA6sBCC4FBwasBSLkBQgGQQUlcAUWhQUVCB0FJIUFGwYIIAUPIAUTBmcFDgYgBQggBggjBRZUBQgIywUkVQUbBgggBQ8gBRMGZwUOBiAFCCAGaQUWVAUI9QUkVQUbBvIFDyAFEwZnBQ4GIAUIIAPQfjwFFAYDqgEgBQIGugUgBgMdWAUEBnQFJYIDuX5YBTwGA8EBIAUmBnQFA1gDv35mBQgGA8IBdAYDvn5mBgPFAVgFIDAFBAZ0BSWeA7l+PAUQBgPCASAFCAbWA75+kAYDxQFYBSAwBQQGdAUlngO5fjwFEAYDwgEgBQgG1gO+fpAGA8UBWAUgMAUEBnQFJZ4DuX48BRAGA8IBIAUIBtYDvn6QBgPFAVgFIDAFBAZ0BSWeA7l+PAUQBgPCASAFCAbWA75+kAYDxQFYBSAwBQQGdAUlngO5fjwFEAYDwgEgBQgG1gO+fpAGA8UBWAUgMAUEBnQFJZ4DuX48BRAGA8IBIAUIBtYDvn6QBgPFAVgFIDAFBAZ0BSWeA7l+PAUaBgPCASAFCAasBRpYBQhYA75+SgYDxAEgBgO8flgGA8UBWAUgMAUEBnQFJZ4DuX48BQgGA8UBZgUgMAUEBnQFJZ4FCAY3BgO+fkoGA8UBWAUgMAUEBnQFJZ4DuX48BRoGA8IBIAUIBnQDvn4uBgPFAVgFIDAFBAZ0BSWeA7l+PAUaBgPCASAFCAZ0A75+LgYDxQFYBSAwBQQGdAUlngO5fjwFGgYDwgEgBQgGdAO+fi4GA8UBWAUgMAUEBnQFJZ4DuX48BRoGA8IBIAUIBnQDvn4uBgPFAVgFIDAFBAZ0BSWeA7l+PAUaBgPCASAFCAZ0A75+LgYDxQFYBSAwBQQGdAUlngO5fjwFGgYDwgEgBQgGdAO+fi4GA8UBWAUgMAUEBnQFJZ4DuX48BQgGA8IBZgUgawUEBnQFJZ4FOwYDdTwFJQZ0BQJYA8R+SgU7A7wBIAUldAUCWAPEfkoGA8oCrAYIIAYvBoIDtX1KBgPKAiAGAjISggYILwbIA7V9LgPLAiACMBIuLlgG1wYDtH1mA8wCIAO0fVgDzAIgAisSLi5YA7R91gYDzQIgBgOzfZADzQJKAjASA7N9WAUMBgPRAiAFBgZ0BQADr30uBQYGA8AAdAUTBggSBRB0BQYgLgUSBgNuLgUGAxKQBQ0DcUoFCwZ0BQYGAw90BSIDcUoFIAZ0BQYGAw90BTsDcUoFNQZ0BQYGAw+QBRcDdUoFBgMLCEoFBANyLisFCQYuBQRYBQkGWQUEBiAGPgUJIQUEBlgFCwYhkQYDS5AFBgYDwAAgBREyBQaMBRcDdYIFBgMLdAUZNQUbBqwFCQYDaFgFBAYgBQkGdQUEBiAFBgYDEDwFDTMFEwbIBQkGA25YBQQdBRkDGDwFGwasBQkGA2dYBQQGIAY/BQk6BQRbHwUJPQUEBlgFCwYeBQYDD5AFFzQFCQNpyAUEBiAFCQZ1BQQGIAY+BQk9BQQGWAUgBh4FBgMPkAUJA28uBQQGWAUJBnUFBAYgBj4FCT0FBAZYBTUGHgUGAw+QBQkDby4FBAZYBQkGdQUEBiAGPgUJPQUEBlgFCwYhkQYDS6wFNQYD0QIgBgOvfXQFHgYD1QIgBRwG5AUUdAUCIAUeLgUVBq0FHwasBRcuBR+6BRUgBRQGOwUnBi4FFFgFAjwFHgZRBTMGWAUtugUePAUcugUXPAUxPAUCIAVCkAOkfTwFAQYDqQQgAgMAAQEABQIJFAAAA80BAQUSCpEFGAaQBRxYBQwGWQUGBgguBQoGLwUSCHIFDwNSLgUYcwUM1wUGBjwFAAPffi4FBgYD/QCCBRF1BRoGCDwFAlgDgn+eBSAGA6IBWAUGBlgD3n5KBRgGA9QBIAUR8wYDq350BQIGA+ABdAYILgIxEoIDoH48BgPhASAGCC4udKwuCJ4uLlgDn348BgPiASAG8gIqEi4uWAOefjwGA+MBIAaQLnQCKBIuLlgDnX48BREGA+cBIAUJ2AUPA7h/CHQFGI8FDNcFBgY8BQAD334uBQYGA/0AggURdQUaBgg8BQJYA4J/ngUgBgOiAVgFBgZYA95+SgUgA6IBPAUGPAPefkoFIAOiATwFBjwD3n4uBQ8GA6MBIAUMBtYFJCAFIlgFCVgD3X48BTQGA+kBIAUyBroFFyAFCVgFKyAFCSAFAQYhBgOWfiAFJQYDowEgBSQG1gUPIAUM1gUiIAUJWAPdfjwFGAYD0QEgBQEDGVgCAQABAQDDCQdsaW5raW5nAQjWhICAACUAAAIIZW50cnlfZm4BAAZoZWlnaHQDAAQBAAV3aWR0aAIABAECCWhhdmVfYmVlbgsAkP0BAQIOcHJldmlvdXNfaXNfbWUMAJD9AQECDmJvdF9ldmlsX3Njb3JlDQD4AQECBmJvdF9keA4A+AEBAgZib3RfZHkPAPgBAQIGLkwuc3RyAQABABAAABABAQAEcHRycwAAJAEABW5ib3RzBAAEAQAJcm91bmRfaWR4BQAEAQAMdG90YWxfcm91bmRzBgAEAQAFbXlfaWQHAAQBAARncmlkCACQ/QEBAARib3RzCQC0AQEACnJhbmRfc3RhdGUKABQBAg1wbGFjZV9oYXNfYm90EACQ/QEBAgtib3RfcHJldnBvcxEA+AEBAghoZWF0X21hcBUAwPQHAQIRZXZpbF9mYWN0b3JfY2FjaGUSAMD0BwACAwpwX2ZpbmRwYXRoAQIhZGV0ZXJtaW5pc3RpY19yYW5kLnByZXZfcm91bmRfaWR4FgAEAQIaZGV0ZXJtaW5pc3RpY19yYW5kLnN0YXRlLjMaAAQBAhpkZXRlcm1pbmlzdGljX3JhbmQuc3RhdGUuMhkABAECGmRldGVybWluaXN0aWNfcmFuZC5zdGF0ZS4xGAAEAQIaZGV0ZXJtaW5pc3RpY19yYW5kLnN0YXRlLjAXAAQBAhpkZXRlcm1pbmlzdGljX3JhbmQuc3RhdGUuNBsABAECEXBfY2FsY21vdmUuZGlyX2R4EwAQAQIRcF9jYWxjbW92ZS5kaXJfZHkUABADAgUDAgYDAgcDAgkDAgsF2ISAgAAcCS5ic3MucHRycxAADi5yb2RhdGEuLkwuc3RyAQAKLmJzcy53aWR0aAQACy5ic3MuaGVpZ2h0BAAKLmJzcy5uYm90cwQADi5ic3Mucm91bmRfaWR4BAARLmJzcy50b3RhbF9yb3VuZHMEAAouYnNzLm15X2lkBAAJLmJzcy5ncmlkEAAJLmJzcy5ib3RzEAAPLmJzcy5yYW5kX3N0YXRlEAAOLmJzcy5oYXZlX2JlZW4QABMuYnNzLnByZXZpb3VzX2lzX21lEAATLmJzcy5ib3RfZXZpbF9zY29yZRAACy5ic3MuYm90X2R4EAALLmJzcy5ib3RfZHkQ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"
    );

    // require("fs").writeFileSync("reverse-base64-output.txt", Buffer.from(O.wasm_bytes));

    O.memory = new WebAssembly.Memory({initial: 15});
    // O.importObject = {js: {mem: O.memory}, env: {println: println_func}};
    O.importObject = {
        env: {
            println: println_func,
            print_int: print_int_func,
            __linear_memory: O.memory,
            __indirect_function_table: new WebAssembly.Table({initial: 0, element: "anyfunc"}),
        },
    };

    // let wa_membuf, wa_width, wa_height, wa_nbots, wa_round_idx, wa_total_rounds, wa_my_id, wa_grid, wa_bots, wa_rand_state;

    /*const promise = fetch('../out/main.wasm').then(response =>
        response.arrayBuffer()
    ).then(bytes =>
        WebAssembly.instantiate(bytes, O.importObject)
    );*/
    // const promise = WebAssembly.instantiate(fs.readFileSync("hotpatcher/out.wasm"), O.importObject);
    const promise = WebAssembly.instantiate(O.wasm_bytes, O.importObject);

    promise.then(results => {
        const instance = results.instance;

        // console.log(instance.exports);

        // First set some pointers
        instance.exports.entry_fn(0);

        O.wa_membuf = new Uint8Array(O.memory.buffer);
        const ptrs = new Uint32Array(O.memory.buffer, 0, 9 * 4);

        O.wa_width = new Int32Array(O.memory.buffer, ptrs[0], 1);
        O.wa_height = new Int32Array(O.memory.buffer, ptrs[1], 1);
        O.wa_nbots = new Int32Array(O.memory.buffer, ptrs[2], 1);
        O.wa_round_idx = new Int32Array(O.memory.buffer, ptrs[3], 1);
        O.wa_total_rounds = new Int32Array(O.memory.buffer, ptrs[4], 1);
        O.wa_my_id = new Int32Array(O.memory.buffer, ptrs[5], 1);
        O.wa_grid = new Uint8Array(O.memory.buffer, ptrs[6], MAXSZ * MAXSZ);
        O.wa_bots = new Uint8Array(O.memory.buffer, ptrs[7], MAXBOTS * 3);
        O.wa_rand_state = new Uint8Array(O.memory.buffer, ptrs[8], 5 * 4);

        O.wa_mc_calcmove = function() { return instance.exports.entry_fn(2); }

        seed_random();

        // Signal that we're done setting up, and the wasm code can set itself up
        instance.exports.entry_fn(1);

        O.instantiated = true;
        console.log("MC: Instantiated!");
    }).catch(console.error);
}

if (gameInfo[0] > 5) {
    if (O.instantiated) {
        // const start = new Date();
        const output = mc_calcmove();
        // const end = new Date();


        // if (O.time_sum == null) O.time_sum = 0;
        // O.time_sum += end - start;

        // if (gameInfo[0] % 50 == 0) {
        //     console.log("Average time taken: " + O.time_sum / (gameInfo[0] - 1));
        // }

        return output;
    } else {
        throw new Error("SCREAM FIRE wasm instantiation");
    }
} else {
    console.log("MC: RANDOM MOVE BEFORE INSTANTIATE");
    return ["right", "down", "left", "up"][Math.random() * 4 | 0];
}

}

P를 만나십시오 P는 실제로 좋은 봇이 되려고하지 않거나 게임에서이기려고하는 것이 아니라 사각형을 그리는 것입니다. 그 일을하는 내용입니다. 봇이 꼬리에있을 때 P는 긴장하지만 페인트 칠할 수있는 사각형이있는 곳으로 가고 있습니다.

설명

내 봇 P와 MC는 기본적으로 동일한 코드로 컴파일됩니다. 모든 작동 방식에 대한 설명은 아래에서 확인할 수 있습니다.

봇 위에 표시된 포장지 자바 코드 및 C 코드로 구성되어 도시 아래에 링크. (코드가 좋은 포스트 길이의 SE의 아이디어를 너무 오래 게시물을 만들어 포함.) 제출 (또는 테스트), C 코드가 정교한 과정을 통해 컴파일을위한 로봇을 준비 할 때,에 후술 wasm파일, 이는 인코딩입니다 base64에서 Javascript 파일에 삽입됩니다. Javascript 파일은 WebAssembly.Instance사용할 수 있도록 wasm 코드를 인스턴스화해야 하지만 API는 비동기식입니다. 따라서 게임에서 봇이 처음 호출 될 때 인스턴스화를 시작하고 비동기 인스턴스화가 반환되고 wasm 코드를 사용할 준비가 될 때까지 임의 이동을 수행합니다. 이 인스턴스화는 일반적으로 약 2 턴이 걸리며, 이는 중요하지 않습니다.

봇이 인스턴스화되면 wasm 코드의 내 보낸 함수 하나 entry_fn가 모드 인수 0과 함께 ( )로 호출 되며 ptrs, 그리드 데이터, 봇 배열과 같은 주변 정보를 포함하는 다양한 전역 변수에 대한 포인터로 테이블 을 채우도록 지시합니다. , 봇 수 등.이 테이블에는 내부 PRNG에 사용되는 상태 배열에 대한 포인터도 포함되어 있습니다. WebAssembly는 기본적으로 결정적인 작업 만 수행 할 수 있으며 내장 임의 소스가 없습니다. 따라서 ptrs배열이 채워진 후 Javascript 코드는 일부 난수를 생성 Math.random()하고이 rand_state배열을 임의의 데이터로 채 웁니다 . 완료되면entry_fn 모드 인수 1과 함께 호출되어 JS 코드 설정이 완료되었으며 wasm 코드가 데이터 초기화를 시작할 수 있음을 나타냅니다.

wasm 코드 설정이 완료되고 이동을 위해 봇이 다시 entry_fn호출되면 마지막 모드 인수 인 2가 호출되어 이동을 계산합니다. 이를 수행하기 전에 JS 코드는 먼저 필요한 모든 회전 정보 (그리드, 봇, 나 자신 등)를 포인터가 ptrs배열에 배치 된 전역 변수에 복사합니다 . entry_fn(2)그런 다음 선택한 이동을 배열의 인덱스로 반환합니다 ["right", "down", "left", "up", "wait"].

C 코드는 이 요지 에서 찾을 수 있으며 , 이에 대한 설명은 www.

C 코드

하나의 내 보낸 함수 만 있고 이름과 전역 변수를 사용하여 전역 변수를 내 보내지 않는 이유는이 쓰레기를 만드는 것이 어렵고 도구가 함께 작동하지 않고 다른 도구 및 브라우저와 호환되지 않기 때문입니다. 내 컴파일 파이프 라인은 나중에 설명합니다. wasm ABI는 32 비트이므로 포인터를로 반환하는 int것이 실제로 안전하고 sizeof(void*) == 32좋습니다 ( ).

나는 약간의 매크로 매직을 사용하여 실제로 컴파일 할 봇을 선택합니다. MC와 P의 소스는 모두 C 소스에 있으며 사용할 소스는 USED_CALCMOVE_PREFIXdefine에 의해 결정됩니다 . 이면 p_P의 소스가 사용되고 mc_이면 MC의 소스가 사용됩니다. 어느 시점에서, 나는 서로의 논리를 실행할 수 있었지만 함께 작동하도록 계획했지만

해당 정의 값에 따라 이동을 계산할 때 제어가 mc_calcmove()또는 로 전송됩니다 p_calcmove().

MC

MC는 몬테 카를로 (Monte Carlo) 플레이어로 , 현재 위치에서 가능한 모든 움직임을 시도합니다 (이 게임에서는 5 이하). 결과 보드 위치에서 수많은 무작위 게임을 시뮬레이션합니다. 랜덤 플레이 아웃 후 최고의 평균 결과로 이동합니다. 이 경우 100 번의 랜덤 플레이 아웃을합니다. 보통 이런 종류의 게임 AI에서는 게임이 각 플레이 아웃이 끝날 때까지 실행되지만이 게임은 때때로 완전히 예측할 수 없으므로 유용하지 않은 것 같습니다. 어쨌든 너무 느 렸을 것입니다.

최적화 목적으로, 각 보드 후 전체 보드가 재설정되지 않고 실제로 보드에서 변경된 셀만 재설정됩니다. 이것은 modified배열 로 수행됩니다 . 또한 점수 델타는에 유지 mc_random_step되므로 각 플레이 아웃 후 전체 보드의 점수를 계산할 필요가 없습니다. 둘 다이 알고리즘을 실행하는 데 필요한 시간을 줄이고 어떤 식 으로든 결정 또는 계산 된 점수에 영향을 미치지 않습니다.

대부분의 경우 C 코드는 여기에있는 이전 MC 제출 버전의 JS 코드와 완전히 평행하므로 원할 경우 찾아 볼 수 있습니다. 유일한 변경 사항은 last_dir무작위 재생 중에 뒤로 걷는 것을 방지하기 위해 어레이를 사용하는 것입니다 .

P는 MC보다 복잡한 봇입니다. 모든 특정 작업에 대해 설명하지는 않습니다. 이제 소스를 가지고 있지만 그 작업에 대한 일반적인 개요를 알려 드리겠습니다.

주요 기능은 DFSbot과 멀지 않습니다. 향후 8 (= P_WALK_DISTANCE) 이동에 사용할 수있는 가능한 경로를 깊이 우선 검색 하여 경로에 사용 된 각 셀에 주어진 점수의 합계로 각 경로의 점수를 매 깁니다. 셀별 점수 외에, 봇이 꼬리에있는 다른 봇을 피하는 데 도움이되는 "이블 팩터"를 사용하여 각 경로 시작 이동 (즉, 지금 당장 이동)을 추가로 점수를 매 깁니다. (간단히 : 봇 적어도 20 턴, 로봇의 위치가 P 반발 이동있어 방향으로 앞의 두 개에 대한 P의 바닥에 방해되는 경우, 참조 p_evil_factor(). bot_evil_score, 침입 으면 0으로 다시 신속하게 하강 이동의 수가 기록 봇이 더 이상 침입하지 않습니다.)

기본 셀 점수는에 의해 주어집니다. p_paintScore()자체 위로 달리는 것을 좋아하지 않으며 직접 페인트 할 수없는 물건을 페인트하고 다른 봇으로 직접 뛰는 경우 퍼포먼스를 위해!), 또는 이미 게임에 있었던 곳을 걷는 것. 해당 점수에는에 의해 주어진 점수가 추가되며이 점수는에 의해 heat_map계산됩니다 p_populate_heatmap(). 흰색 사각형은 양호하고 P의 자체 사각형은 나쁘고 P가 자신의 색으로 페인트 할 수있는 다른 사각형은 적당히 좋으며 그렇지 않으면 적당히 나쁩니다.

각 지점에서 경로의 꼬리 끝과 곱해지는 0.98의 요소가 있으므로 가능한 경로의 시작 부분이 꼬리 끝보다 중요합니다. 이 계수는 의도적으로 1에 매우 근접하여 선택되므로 전체 영향은 크지 않지만 실제로 타이 브레이커 기능을 제공 할만큼 충분히 커집니다. (동일하지만 두 끝에 흰색 사각형이 있고 다른 하나에 흰색 사각형이 있다는 차이점 만있는 경우 두 번째 경로가 분명히 더 좋으며이 가중치는이를 보장합니다.)

많은 배열이 있으며, 그중 일부는 값을 계속 넘습니다. 이 데이터를 관리하려면 코드가 약간 필요합니다 p_calcmove(). 코드는 매우 아름답지는 않지만 작동하는 것 같습니다. ¯ \ _ (ツ) _ / ¯

컴파일 과정

작업 디렉토리에 compiler다음을 포함 하는 폴더 가 있습니다.

  • llvm컴파일 된 LLVM 빌드 -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly. 버전은 복제 시점의 마스터 팁이며 commit 3d765ce4b7f2fd25bdbc0efc26afdf42e84fecb2입니다. 3 개의 스레드로 이것을 컴파일하는 데 약 1 시간이 걸렸습니다. 다시 시작 build폴더는 약 29GB이므로 디스크 공간이 부족한 경우이 작업을 직접 수행해야합니다. ._.
  • 의 빌드 binaryen 에서 binaryen커밋에서 57328f8e1e4db509b9956b53dd5300fc49e424eb.
  • 의 빌드 WABT은wabt,에 커밋 71647d4f0e86a4c738f742f69f65b2cc41d4c004.

JS 랩퍼 소스가 main.js있고 C 소스가에 main.c있습니다. 내가 작성한 것을 테스트하고 싶을 때 파일 (대부분 main.c)을 저장하고 ./CFIT.sh실행하고 pbcopy <main.js( main.js클립 보드에 복사) 실행 한 다음 @dzaima의 컨트롤러 상자에 붙여 넣습니다. 실제로이 기능을 쉽게 수행 할 수 있도록 모든 기능을 갖춘 컨트롤러를 제공 한 @dzaima에게 감사합니다.

이 스크립트 CFIT.sh는 컴파일 프로세스를 주도하며 다음과 같이 보입니다.

#!/usr/bin/env bash
# Compile, Fix, Insert, Test

set -e

./compile.sh
hotpatcher/hotpatcher main.wasm out.wasm
compiler/binaryen/build/bin/wasm-opt -O4 out.wasm -o out.wasm
./insert_wasm.sh out.wasm
./run_test.js

핫 패처는 완전히 다른 이야기이며 나중에 언급합니다. 스크립트 compile.sh는 다음과 같습니다.

#!/usr/bin/env bash
compiler/llvm/build/bin/clang -Wall -Wextra main.c -c --target=wasm32-unknown-unknown -O3 -fno-builtin-memcpy -fno-builtin-memset -g -o main.wasm
compiler/wabt/build/wasm2wat main.wasm -o main.wat

플래그는 -fno-builtin-{memset,memcpy}달리 LLVM은 충실하게 내의 몸 인식하기 때문에 필요 memset_xmemcpy_x내장 등의 기능을 memset하고 memcpy그 다음이 함수에 정의되지 않은 참조 나를 떠나와 코드를 대체하는 기능 에도 불구하고 대체를 제공합니다.

insert_wasm.sh다음과 같이 스크립트 같습니다

#!/usr/bin/env bash
set -e
set -o pipefail

if [[ -n "$1" ]]; then wasmfile="$1"; else wasmfile="main.wasm"; fi
subject="main.js"
tmpfile=".insert_wasm_tmpfile.txt"

[[ -f "$subject" ]] || exit 1
[[ -f "$wasmfile" ]] || exit 1

echo "Inserting into $subject from wasm file $wasmfile"

./tostring.sh "$wasmfile" >"$tmpfile"

result="$( \
    cat "$subject" \
    | sed -n ':b; s/INSERT-WASM-HERE/INSERT-WASM-HERE/; p; t i; n; b b; :i; r '"$tmpfile"$'\n''; n' \
)"

cat >"$subject" <<<"$result"

rm "$tmpfile"

이것은 main.js의 거대한 base64 문자열을 컴파일 된 wasm 코드로 직접 대체합니다. tostring.sh다시 다음과 같이 스크립트 같습니다

#!/usr/bin/env bash
if [[ $# -ge 1 ]]; then
    exec <"$1"
fi

# $HOME/code/bin2c/bin2c | sed 's/^"//; s/"$//; s/\\a/\\7/g' | tr -d '\n'
base64 | tr -d '\n' | python3 -c 'import sys, re; text = sys.stdin.read(); print("\"" + re.sub(r"(.)\1{29,}", lambda m: "\"+repeat(\"" + m.group(0)[0] + "\"," + str(len(m.group(0))) + ")+\"", text, flags=re.I) + "\"")'

run_test.js스크립트에는 main.js(새로 삽입 된 wasm 코드 포함) 만 포함하고 eval문제가 발생한 게임에서 가져온 일부 매개 변수로 두 번 호출하여 wasm 코드가 인스턴스화되도록 두 호출 사이에 시간을 기다려야합니다. 이것은 가장 기본적인 통합 테스트를 제공합니다. 이 방법으로 문제가 해결되지 않으면 문제가 발생하는 경우가 많으며 대개 실제로 문제가있는 경우이 테스트가 실패하고 충돌하는 경우가 있습니다.

핫 패처

핫패 처가 남습니다. 이 모든 것이 정직하게 진행된 곳입니다. clang은 훌륭한 wasm 코드를 생성 하지만 일반 명령을 생성 하는 이외의 작업을 수행하면 잘못 실패합니다 . 내 보낸 함수 나 내 보낸 변수에 대한 내보내기를 내 보내지 않으며 어떤 이유로 스택 포인터의 저장소로 사용 된 내 보낸 변경 가능한 전역 을 내 보냅니다 . 더 나쁜 것은 실제로는 전 세계를 사용한다는 것입니다. (다행히도 실제로 사용 하지는 않습니다 : 함수 입력시에만 읽고 함수 종료를 복원합니다. 이는 읽기를 상수로 바꾸고 쓰기를 drop.) 이제 변경 가능한 전역은 내가 본 wasm 구현에서 지원되지 않으므로 clang이 이것을하는 것이 좋은 생각이라고 생각하는 이유는 저를 벗어났습니다. 나는 clang 이이 다른 작업을 수행하는 데 성공하지 못했으며 게으른 프로그래머이기 때문에이 모든 수정 프로그램을 손으로 직접 만드는 것을 좋아하지 않아서 나를 위해 프로그램을 작성했습니다.

이 프로그램은 wasm 파일을 구문 분석, 수정 및 다시 직렬화하는 1385 행 C ++ 프로그램이되었습니다. 해당 프로그램의 소스는 이 요지 에서 찾을 수 있습니다 .

나는 그것이 무엇을하는지도 설명하지 않을 것입니다. 실제 피 묻은 것만 패치 는 않습니다에 hotpatcher.cpp; 나머지는 그 기능을 가능하게하는 것입니다.

결론

이 쓰레기 전체에 결론이 필요합니까? ¯ \ _ (ツ) _ / ¯ 그러나 나는 이것을 emscripten을 사용하여 더 쉽게 할 수 있다고 말해야 할 것 같습니다. 내가 그것을 사용하지 않은 이유는 emcc를 사용하여 C 파일을 컴파일 할 때 기본적으로 JS 파일과 HTML 파일의 완전한 괴물을 생성하고 그 물건이 너무 미쳤 기 때문에 emcc를 좋아했기 때문입니다. 모든 일을 스스로하는 것. 나중에 나는 emscripten이 "공유 라이브러리"모드 (공유 라이브러리는 하위 레벨이며 JS는 당신이 아닌 이름을 훔치지 않습니다)를 가지고 있다고 어딘가에 읽었습니다. 나는 이미 그것을 시도하기 위해 내 hotpather (도박꾼의 타락한 사람?)를 부 풀리는 모든 재미있는 것들에 너무 멀었습니다.

나는 이것이 실제로 누군가에게 유용하고 이것을 작성하는 데 SE의 포스트 크기 제한을 넘지 않기를 바랍니다.


2
관련 기술에 주로 관심이있는 모든 내용을 읽었습니다. 내가 물어 봐야 - "무슨 정확히 중지하고, 자신에게 물어 않았다 과정에서 어떤 시점에 있어요 ? 어쨌든 여기서 뭐"
Caleb

왜 더 유용한 것을하지 않습니까? 아마도 핫 패처를 쓰는 동안 언젠가. 몇 가지 명령을 패치하는 것이 명확 해지면 각 개별 명령을 완전히 파싱하고 이해할 수 있어야합니다 (wasm에는 가변 폭 instrs가 있으므로), 포기하고 다른 일을 생각했습니다. 그러나 지금은 며칠 동안 휴가를 보내고 있기 때문에, 침몰 한 비용 오류가 발생했으며 어떤 식 으로든 wasm이 어떻게 작동했는지 배우고 싶었 습니다. (instr 파서는 그렇게 어렵지 않았다.) 그리고 나는 그 결과를 자랑스럽게 생각한다.
tomsmeding

1
코드에 쏟아지는 작업량, 모든 부품이 어떻게 작동하는지에 대한 설명은 3 개의 엄청나게 공감하는 것보다 훨씬 더 가치있는 설명을하지 마십시오. 나를 위해 빼앗아가는 가치가 "멀리 도망 갔다"고해도 그것은 교훈적이었습니다. ;-)
Caleb

9

레오나르도 다 픽 슬리

function(myself, grid, bots, gameInfo) {
    var ME = this;
    var w='up',a='left',s='down',d='right';
    var ps = [
        [
            // Castlevania Simon Belmont
            16,30,0,11,
            [s,s,d,s,d,d,w,w,d,d,s,a,s,d,d,s,d,s,s,a,s,a,a,s,a,s,s,a,s,s,s,s,s,s,s,a,a,s,s,d,w,d,s,d,d,d,w,w,a,s,a,w,w,d,w,d,w,a,a,s,w,w,w,d,s,d,w,w,a,d,d,w,d,w,d,s,d,s,s,d,s,d,s,d,s,s,s,s,a,s,d,d,d,d,w,a,a,w,d,d,w,a,a,w,d,d,w,a,a,w,d,a,w,a,s,a,w,w,a,d,d,w,w,w,w,a,a,a,a,s,a,w,w,d,d,d,d,d,d,s,w,w,a,a,a,a,a,w,d,d,d,d,w,a,a,a,a,a,a,w,d,d,d,d,d,d,d,w,a,a,a,a,a,a,w,d,d,d,d,d,d,d,d,w,w,s,s,a,a,w,a,a,a,a,a,a,w,w,a,s,a,a,s,a,a,a,d,d,s,d,w,w,d,d,d,d,d,d,d,d,w,d,d,a,w,w,a,s,a,s,a,a,a,w,a,w,d,d,s,d,w,d,w,d,a,a,a,a,a,s,a,a,s,a,a,w,w,w,d,a,w,w,d,w,d,s,d,s,s,d,d,d,w,a,a,w,d,a,w,a]
        ],
        [
            // Final Fantasy White Mage
            17,25,2,10,
            [a,w,a,w,w,w,d,w,d,w,d,w,d,d,w,d,d,w,d,d,d,w,d,d,d,d,d,s,d,s,s,s,s,s,a,a,w,a,w,s,d,s,d,s,s,s,s,a,s,s,a,d,s,d,s,d,s,s,s,s,s,s,s,s,s,a,s,a,a,w,w,s,a,s,a,a,w,w,w,s,a,s,a,w,s,s,a,a,w,s,a,a,w,a,s,w,a,w,w,d,a,a,w,w,w,d,s,w,d,d,d,s,d,s,d,s,w,w,d,a,a,a,w,w,d,w,d,a,a,w,w,d,d,d,w,d,s,d,d,d,s,d,s,s,w,w,a,w,a,a,a,a,a,a,a,s,a,a,a,s,a,s,w,w,w,w,d,w,d,d,w,w,w,d,d,s,s,w,w,d,d,d,d,s,s,s,w,w,d,w,a,w,a,w,a,w,a,s,s,d,a,a,w,w,a,s,a,w,a,s,a,s,d,d,s,a,d,s,s,a]
        ],
        [
            // Megaman
            21,24,11,0,
            [d,s,d,s,s,d,d,s,d,s,s,s,s,w,w,w,w,a,a,s,s,s,w,w,a,a,s,s,a,w,d,w,w,a,w,w,a,w,a,a,s,a,s,d,d,w,s,d,s,a,a,a,a,s,d,d,a,s,a,d,s,s,s,d,a,a,a,s,a,a,s,s,a,s,s,s,d,d,w,a,w,w,d,w,d,s,w,d,w,d,s,d,d,d,d,d,d,d,w,w,a,a,a,a,d,d,d,d,s,d,d,d,s,s,d,s,s,s,a,a,w,d,w,w,a,w,a,s,w,a,a,s,a,a,a,a,a,a,s,d,d,d,d,d,d,s,a,a,a,a,a,a,s,d,d,d,d,d,d,s,a,a,a,a,a,a,s,a,s,a,s,d,s,a,a,a,s,a,a,d,d,d,d,d,d,w,w,w,d,s,w,w,d,s,w,d,d,s,d,s,d,s,s,d,d,d,d,d,d,a,a,w,a,a,a,w,d,w,a,w,a,s,a,w]
        ],
        [
            // Mario mushroom
            16,16,7,0,
            [a,s,a,a,s,a,s,a,s,s,a,s,s,s,s,s,s,d,s,d,s,s,d,s,d,d,d,d,d,d,d,d,d,w,d,w,w,d,w,d,w,w,w,w,w,w,a,w,w,a,w,a,w,a,a,w,a,a,a,s,d,s,a,s,d,d,s,a,a,a,w,s,a,a,a,w,s,d,s,d,a,s,s,s,a,s,a,a,s,d,s,d,w,d,w,d,s,d,s,s,w,w,d,d,d,s,s,w,w,d,w,d,s,d,s,d,w,d,w,a,a,w,a,w,w,w,a,w,d,d,w]
        ],
        [
            // Mario Bullet Bill
            16,14,15,0,
            [a,a,s,a,s,s,d,d,w,d,s,s,s,s,s,s,s,s,s,s,s,a,w,w,w,w,w,w,w,w,w,a,s,s,s,s,s,s,s,s,a,w,w,w,w,w,w,a,w,d,w,a,w,w,a,s,s,s,a,w,w,w,a,s,s,s,s,s,w,a,w,w,w,w,s,a,a,s,d,s,s,a,w,s,s,a,w,s,s,s,s,d,s,d,d,d,d,w,d,d,w,s,s,s,s,a,w,w,a,s,s,a,w,a,s,a,w,a,a,w,a,w,w,w,w,a,s,s,s,s,w,a,w,w,w,a,s,w,w,w,w,d,d,s,w,w,a,d,d,w,a,d,d,w,d,s,w,d,w,d,d,d,d,d]
        ],
        [
            // Pac-Man Ghost
            14,14,2,6,
            [w,a,s,a,s,d,s,a,s,d,s,a,s,d,s,a,s,w,w,d,d,w,w,w,d,s,s,s,s,d,s,d,w,w,a,w,d,w,a,w,d,w,a,d,w,w,w,w,a,w,a,a,a,s,w,d,d,w,d,d,s,w,w,d,s,s,s,s,s,s,s,s,s,s,s,d,w,w,w,w,w,w,d,s,a,s,s,d,s,s,s,s,s,d,w,w,w,w,w,d,s,s,s,s,w,d,d,s,d,s,w,w,w,a,a,w,d,d,w,a,a,a,w,d,d,d,w,a,a,w,d,w,a,w,d,a,w,a,s,w,w,a,s,a,w,w,a,s,s,s,w]
        ],
        [
            // Mario Goomba's shoe
            16,27,2,11,
            [a,s,s,s,s,d,a,a,s,s,s,s,s,s,s,s,d,d,d,w,w,w,a,d,d,w,s,a,s,s,s,a,a,a,s,d,s,d,s,d,d,d,d,d,d,d,d,d,d,d,d,w,d,w,w,w,w,w,a,w,a,a,a,a,a,s,w,d,d,d,w,w,w,a,a,a,a,a,a,a,a,a,s,s,a,d,w,w,d,d,d,d,d,d,d,d,d,d,w,w,w,w,a,w,d,w,d,w,w,w,a,a,a,w,w,d,d,w,d,a,w,a,s,a,w,a,s,a,s,s,s,w,w,w,a,a,a,s,a,d,w,d,d,w,d,d,w,w,w,a,a,a,a,s,a,a,s,a,s,a,s,s,s,d,d,d,s,s,d,a,s,w,a,w,w,a,a,a,s,s,s,s,d,s,d,d,d,d,d,w,d,w,s,s,d,w,s,d,d]
        ],
        [
            // Zelda Triforce
            20,20,5,10,
            [a,s,d,s,d,s,a,a,w,a,s,s,a,s,d,d,w,d,s,d,w,d,s,s,d,s,a,a,w,a,s,a,w,a,s,a,w,a,s,s,a,s,d,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,a,w,d,w,d,w,a,w,w,a,s,a,w,a,s,a,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,w,d,s,d,w,d,s,d,d,w,a,w,w,a,s,a,w,a,s,a,a,w,d,w,w,d,s,d,d,w,a,w,w,a,s]
        ],
        [
            // Final Fantasy Black Mage
            18,26,4,8,
            [a,a,a,w,a,w,w,d,d,d,d,d,d,w,d,d,w,d,d,w,d,w,d,d,w,d,d,d,s,s,s,a,s,s,a,s,s,s,a,d,s,d,s,d,s,d,s,s,a,a,s,s,a,d,s,s,s,s,s,a,s,s,d,s,d,s,s,a,a,a,a,w,w,d,a,s,a,w,a,a,w,w,w,a,a,a,d,d,w,w,w,a,a,d,w,d,d,w,d,d,a,a,s,a,s,s,s,s,d,s,s,s,d,d,s,s,a,a,a,a,a,a,a,a,a,a,a,w,w,d,w,w,w,w,w,d,d,s,s,d,s,d,s,s,d,d,a,a,w,w,a,w,a,w,w,w,w,s,s,a,a,a,w,w,w,d,d,a,w,w,w,d,w,d,d,w,w,d,d,d,s,a,s,s,a,a,s,a,d,d,s,d,w,d,s,w,d,w,w,w,d,s,s,s,d,w,w,w,s,d,s,s,d,w,w,s,s,d,w,d]
        ],
        [
            // Final Fantasy Fighter
            18,26,4,7,
            [a,w,s,a,s,w,a,w,w,d,w,w,a,w,d,w,d,s,w,w,s,d,d,w,s,d,d,w,d,d,d,d,d,d,d,s,d,s,a,a,d,d,d,s,d,a,s,a,d,s,s,d,a,s,a,d,s,s,a,s,a,a,w,w,w,a,a,a,s,s,w,a,w,w,a,a,s,a,a,d,s,s,w,w,a,a,s,s,s,a,d,s,s,d,s,d,d,d,d,w,d,s,w,w,d,w,d,d,d,d,s,d,s,d,s,a,s,a,w,a,a,a,a,s,s,d,d,s,d,s,d,d,w,w,s,s,a,s,a,a,a,w,s,a,a,a,a,w,w,w,d,w,d,w,a,a,a,s,a,w,a,w,a,a,a,w,s,a,d,s,d,s,d,s,w,a,w,a,a,s,s,s,d,d,a,a,a,s,s,s,d,d,d,d,w,w,d,s,w,w,d,d,a,a,s,s,d,s,d,d,d,d,d,s,d,s,s,s,s,a,s,a,a,a,a,a,w,a,d,w,w,w,w,s,d,d,d,d,d]
        ],
        [
            // Final Fantasy Chocobo
            12,16,11,0,
            [a,a,a,a,a,a,a,a,s,s,s,d,s,a,w,w,w,a,a,s,a,s,s,s,s,s,d,d,w,d,d,d,d,a,s,a,s,a,s,a,s,d,s,d,s,d,s,d,s,s,a,d,d,w,d,s,d,d,d,a,w,a,a,w,d,w,d,d,w,d,w,d,w,d,w,d,a,w,w,d,a,w,s,a,s,a,d,s,a,a,a,s,a,a,s,w,w,w,w,w,d,w,w,a,d,w,d,s,w,d,w,d]
        ],
        [
            // Pac-Man Ghost (scared)
            14,14,0,6,
            [s,s,d,s,a,s,s,s,s,w,d,w,d,w,d,s,s,d,s,d,w,w,a,d,d,w,d,s,d,s,s,d,w,w,d,s,w,w,d,s,d,s,d,s,w,w,w,w,w,w,w,a,s,s,s,w,a,a,a,s,a,w,a,a,a,s,a,w,a,a,w,a,w,w,w,w,d,w,d,w,d,d,w,d,s,s,a,a,s,d,s,a,a,w,s,a,s,s,d,w,s,s,d,d,d,d,d,d,d,d,w,a,w,d,d,w,w,a,s,a,w,w,d,a,w,a,a,w,a,s,s,d,d,s,s,a,w,a,a,s,d,s,a,s,d,s]
        ],
        [
            // Pokemon Pokeball
            14,14,5,0,
            [d,d,d,s,d,d,s,d,s,d,s,s,d,s,s,a,w,a,s,a,a,d,w,w,a,d,d,w,a,w,a,s,a,w,w,d,a,a,s,a,w,w,d,a,a,a,a,s,a,s,a,d,d,d,w,d,s,s,a,a,a,a,s,a,s,d,d,w,d,d,a,s,s,d,d,s,s,d,d,d,w,w,w,a,a,a,s,a,a,a,a,a,s,s,d,s,s,d,s,d,d,s,d,d,d,d,d,w,d,d,w,d,w,w,d,w]
        ]
    ];
    if(ME.c === undefined){
        ME.c = 9999;
        ME.t = [];
        ME.n = Math.floor(Math.random()*Math.floor(ps.length));
    }
    if(gameInfo[0] == 1 && myself[1] < grid.length-ps[ME.n][0]+ps[ME.n][2] && myself[1] > ps[ME.n][2] && myself[2] < grid.length-ps[ME.n][1]+ps[ME.n][3] && myself[2] > ps[ME.n][3]){
        ME.c = 0;
    }
    if(ps[ME.n][4][ME.c] !== undefined){
        return ps[ME.n][4][ME.c++];
    }
    else if(ME.c < 9999){
        ME.c = 9999;
        ME.n = Math.floor(Math.random()*Math.floor(ps.length));
    }
    if(ME.t.length == 0){
        var rand = [
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]],
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]],
                [parseInt(Math.random()*(grid.length-ps[ME.n][0]))+ps[ME.n][2],parseInt(Math.random()*(grid.length-ps[ME.n][1]))+ps[ME.n][3]]
            ],
            colorable = [0,0,0],
            i, j, k;
        for(i=0;i<rand.length;i++){
            for(j=rand[i][0]-ps[ME.n][2];j<rand[i][0]-ps[ME.n][2]+ps[ME.n][0];j++){
                for(k=rand[i][1]-ps[ME.n][3];k<rand[i][1]-ps[ME.n][2]+ps[ME.n][1];k++){
                    if(grid[j][k] == 0 || (grid[j][k] != myself[0] && grid[j][k]%3 == 0)){
                        colorable[i]++;
                    }
                }
            }
        }
        if(colorable[0] >= colorable[1] && colorable[0] >= colorable[2]){
            ME.t = [rand[0][0],rand[0][1]];
        }
        else if(colorable[1] >= colorable[2]){
            ME.t = [rand[1][0],rand[1][1]];
        }
        else{
            ME.t = [rand[2][0],rand[2][1]];
        }
    }
    if(ME.t[0] > myself[1]){
        return 'right';
    }
    else if(ME.t[0] < myself[1]){
        return 'left';
    }
    else if(ME.t[1] > myself[2]){
        return 'down';
    }
    else if(ME.t[1] < myself[2]){
        return 'up';
    }
    else{
        ME.t = [];
        ME.c = 0;
    }
}

그림 그리기를 좋아하는 (픽셀 화 된) 그림 목록이 있습니다. 임의의 캔버스 / 배치 및 페인트와 함께 무작위로 하나를 선택합니다. 그러나 페인트가 캔버스를 청소하고 다른 경우에는 캔버스에 전혀 달라 붙지 않는 것처럼 페인트에 문제가있는 것 같습니다. 그러나 페인터 봇은 이것에 의해 답답하지 않습니다.

2018 년 8 월 25 일 업데이트 :

  • 새로운 사진
  • 그의 사진을보고 싶기 때문에 더 나은 배치를 선택하려고합니다.

2018 년 8 월 29 일 업데이트 :

  • 새로운 사진 + 기존 사진으로 업데이트

이것은 아름답다 : D
Beta Decay

8

랜덤 필러

function([id, x, y], grid, bots, gameInfo) {
    let painted = {
        false: {
            un: [],
            other: [],
            me: [],
        },
        true: {
            un: [],
            other: [],
            me: [],
        },
    };
    let moves = {
        left: {x: x - 1, y},
        up: {x, y: y - 1},
        right: {x: x + 1, y},
        down: {x, y: y + 1},
        wait: {x, y},
    };
    let isbot = m => bots.some(([, x, y]) => m.x == x && m.y == y);
    let whose = n => n ? n == id || Math.abs(id - n) % 3 > 1 ? "me" : "other" : "un";
    for (let dir in moves) {
        let move = moves[dir];
        if (move.x >= 0 && move.x < grid.length && move.y >= 0 && move.y < grid.length)
            painted[isbot(move)][whose(grid[move.x][move.y])].push(dir);
    }
    choices = [painted.false.un, painted.false.other, painted.true.un, painted.true.other, painted.false.me, painted.true.me].find(choices => choices.length);
    let move = choices[Math.random() * choices.length | 0];
    return move;
}

도색되지 않은 사각형으로 이동하는 것을 선호하여 무작위로 걸은 다음 다시 칠할 수있는 사각형 (필요한 경우 두 번), 그런 다음 사각형으로 이동합니다. 편집 : 봇을 포함하지 않는 사각형을 선호하도록 업데이트되었습니다. 단, 봇이있는 사각형은 페인트 할 수없는 사각형보다 선호됩니다.


그래서 당신의 봇은 몇 초 전에 게시 한 것과 똑같은 업그레이드입니까?
Adyrem

5
@Adyrem 위대한 생각은 비슷하게 생각합니까?
Neil

6

경계선

function(myself, grid, bots, gameInfo) {
    // Check if already on border
    if (myself[1] == 0 || myself[1] == grid.length-1 || myself[2] == 0 || myself[2] == grid.length-1) {
        // Move anticlockwise around the border
        if (myself[1] == 0 && myself[2] != 0 && myself[2] != grid.length-1) {
            return "down";
        }
        if (myself[1] == 0 && myself[2] == 0) {
            return "down";
        }

        if (myself[2] == grid.length-1 && myself[1] != 0 && myself[1] != grid.length-1) {
            return "right";
        }
        if (myself[1] == 0 && myself[2] == grid.length-1) {
            return "right";
        }

        if (myself[1] == grid.length-1 && myself[2] != 0 && myself[2] != grid.length-1) {
            return "up";
        }
        if (myself[1] == grid.length-1 && myself[2] == grid.length-1) {
            return "up";
        }

        if (myself[2] == 0 && myself[1] != 0 && myself[1] != grid.length-1) {
            return "left";
        }
        if (myself[1] == grid.length-1 && myself[2] == 0) {
            return "left";
        }
    } else {
        // Find the nearest border and move to it
        if (myself[1] <= grid.length-1 - myself[1]) {
            // Move to left border
            return "left";
        } else {
            // Move to right border
            return "right";
        }
    }
}

너무 흥미롭지 않고 그리드의 가장자리를 움직입니다.


이것은 높은 FPS에서 달릴 때 궤도 위성처럼 보입니다!
Night2

1
이 노래 는 내 봇을 테스트하는 동안 내 머리에 붙어 있습니다.
조.

이 컨테스트는 실제로 종료되었으며 결과가 전혀 없습니까?
NoOorZ24

6

MADS

function(myself, grid, bots, gameInfo)
{
    const w = 6, h = 6;
    let my_c = myself[0], my_x = myself[1], my_y = myself[2], size = grid.length, roundnum = gameInfo[0];

    if (!localStorage.steelyeyedmissileman) {
        var offset_x = Math.random() *(size-w-1) |0;
        var offset_y = Math.random() *(size-h-1) |0;
        localStorage.steelyeyedmissileman = JSON.stringify([offset_x, offset_y]);
    }
    offsets = JSON.parse(localStorage.steelyeyedmissileman);
    offset_x = offsets[0];
    offset_y = offsets[1];

    let targets = [];
    for(let grid_x = offset_x; grid_x < offset_x+6; grid_x++)
    {
        for(let grid_y = offset_y; grid_y < offset_y+6; grid_y++)
        {
            if(grid[grid_x][grid_y] != my_c)
            {
                targets.push([grid_x, grid_y]);
            }
        }
    }
    let target = targets.pop();
    if(target == undefined) return 'wait';
    if(target[0] > my_x) return 'right';
    if(target[0] < my_x) return 'left';
    if(target[1] > my_y) return 'down';
    if(target[1] < my_y) return 'up';
    return "left";
}

보드에서 6x6 지점을 골라 방어합니다.


6

유클리드

function euclidFn(myself, grid, bots, gameInfo) {
    const W = grid.length, H = grid[0].length;
    const meIdx = bots.findIndex(b => b[0] == myself[0]);
    const meClr = bots[meIdx][0];

    const botIdToIndex = {};
    for (let i = 0; i < bots.length; i++) {
        botIdToIndex[bots[i][0]] = i;
    }

    function paintValue(floor, clr) {
        if (floor == 0) return clr;
        else return [clr, 0, floor][Math.abs(clr - floor) % 3];
    }

    function paint(gr, x, y, clr) {
        gr[x][y] = paintValue(gr[x][y], clr);
    }

    function distance(x1, y1, x2, y2) {
        return Math.abs(y2 - y1) + Math.abs(x2 - x1);
    }

    function calcHeatmap() {
        const heat = new Array(W).fill(0).map(() => new Array(H).fill(0));

        function weight(dx, dy) {
            const d = dx + dy;
            return d < 3 ? 1 / (1 + d) : 0;
        }

        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                let s=0;
                for (let x2 = Math.max(x-3, 0); x2 <= Math.min(W-1, x+3); x2++) {
                    for (let y2 = Math.max(y-3, 0); y2 <= Math.min(H-1, y+3); y2++) {
                        if (grid[x2][y2] == meClr) {
                            s += weight(Math.abs(x2 - x), Math.abs(y2 - y));
                        }
                    }
                }
                heat[x][y] = s;
            }
        }

        return heat;
    }

    const heatmap = calcHeatmap();

    function scorePos(px, py) {
        let sc = 0;
        if (grid[px][py] != meClr && paintValue(grid[px][py], meClr) == meClr) {
            sc += 100;
        }

        let mindist = W + H + 1;
        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] != meClr && paintValue(grid[x][y], meClr) == meClr) {
                    let d = distance(px, py, x, y);
                    if (d < mindist) mindist = d;
                }
            }
        }
        sc -= 3 * mindist;

        mindist = W + H + 1;
        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] == largestBotId) {
                    let d = distance(px, py, x, y);
                    if (d < mindist) mindist = d;
                }
            }
        }
        sc -= 6 * mindist;

        sc -= 3 * heatmap[px][py];

        sc += Math.random();
        return sc;
    }

    function calcBotScores() {
        const res = new Array(bots.length).fill(0).map((_,i) => [bots[i][0], 0]);

        for (let x = 0; x < W; x++) {
            for (let y = 0; y < H; y++) {
                if (grid[x][y] > 0) {
                    let i = botIdToIndex[grid[x][y]];
                    if (i != undefined) res[i][1]++;
                }
            }
        }

        return res;
    }

    const botScores = calcBotScores();  // [id, size]
    const largestBotId = botScores
        .filter(p => p[0] != meClr && paintValue(p[0], meClr) == meClr)
        .sort((a,b) => b[1] - a[1])
        [0][0];

    const dxes = [1, 0, -1, 0, 0], dyes = [0, 1, 0, -1, 0];
    const outputs = ["right", "down", "left", "up", "wait"];

    let allscores = [];
    let maxscore = -Infinity, maxat = -1;

    let allowWait = grid[bots[meIdx][1]][bots[meIdx][2]] == 0;

    for (let i = 0; i < 4 + allowWait; i++) {
        const nx = bots[meIdx][1] + dxes[i];
        const ny = bots[meIdx][2] + dyes[i];
        if (nx < 0 || nx >= W || ny < 0 || ny >= H) {
            allscores.push(null);
            continue;
        }

        let score = scorePos(nx, ny);
        if (i == 4) score -= 20;
        if (euclidFn.lastMove != undefined && i != euclidFn.lastMove) score -= 3;

        allscores.push(~~(score * 1000) / 1000);

        if (score > maxscore) {
            maxscore = score;
            maxat = i;
        }
    }

    // console.log([maxscore, maxat], allscores);

    let move = maxscore == -1 ? Math.random() * 5 | 0 : maxat;
    euclidFn.lastMove = move;

    return outputs[move];
}

이것은 모든 종류의 임의의 일을 수행합니다. 이름이 잘못 선택되었지만 거리가 많은 여러 가지 작업을 수행하므로 어딘가에 의미가 있다고 생각합니다.

나는 최대화 scorePos하지 않은 움직임을 선택합니다 . 이것은 정사각형을 이전 색이 아닌 색으로 칠하는 것을 좋아하고 그렇지 않으면 채색 가능한 사각형 또는 가장 큰 채색 가능한 봇과는 거리가 먼 것을 좋아하지 않습니다 (실제로 잘 작동하는지 확실하지 않음) . 또한 근처에있는 것을 좋아하지 않습니다. 그렇지 않으면 어떤 이유로 든 자주 오버 페인팅되기 때문입니다.

편집 전에 오류가 발생했습니다. 지금 올바른지 확인하십시오 ...


나는 한 단지 높은 점수 잠재력있는 지역의 주위에 유지하려고 짐 v3의 테스트를 완료하고이 팝업 : P는
dzaima

5

헌터 킬러

function(myself, grid, bots, gameInfo) {
    targetColour = myself[0] % 3;
    // If I can paint someone else's space to my colour, do so.
    var options = [];
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] % 3 === targetColour && grid[myself[1] - 1][myself[2]] !== myself[0] && grid[myself[1] - 1][myself[2]] !== 0)
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] % 3 === targetColour && grid[myself[1] + 1][myself[2]] !== myself[0] && grid[myself[1] + 1][myself[2]] !== 0)
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] % 3 === targetColour && grid[myself[1]][myself[2] - 1] !== myself[0] && grid[myself[1]][myself[2] - 1] !== 0)
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] % 3 === targetColour && grid[myself[1]][myself[2] + 1] !== myself[0] && grid[myself[1]][myself[2] + 1] !== 0)
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    // Otherwise, move to the closest bot I can paint over.
    var targetBots = bots.filter(bot => {
        if (bot[0] === myself[0] || bot[0] % 3 !== targetColour) return false;
        return true;
    });
    if (targetBots.length > 0) {
        targetBots.sort((a, b) => (Math.abs(a[1] - myself[1]) + Math.abs(a[2] - myself[2])) < (Math.abs(a[1] - myself[1]) + Math.abs(a[2] - myself[2])));
        if (Math.abs(targetBots[0][1] - myself[1]) > Math.abs(targetBots[0][2] - myself[2])){
            return targetBots[0][1] - myself[1] > 0 ? "right" : "left";
        }
        return targetBots[0][2] - myself[2] > 0 ? "down" : "up";
    }

    options = [];
    // If we've killed them all, try to move to a blank space.
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] === 0 && grid[myself[1] - 1][myself[2]] !== myself[0])
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] === 0 && grid[myself[1] + 1][myself[2]] !== myself[0])
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] === 0 && grid[myself[1]][myself[2] - 1] !== myself[0])
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] === 0 && grid[myself[1]][myself[2] + 1] !== myself[0])
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    options = [];
    // If there aren't any, try to move to a space I can paint white.
    targetColour = (targetColour + 2) % 3
    if (myself[1] !== 0 && grid[myself[1] - 1][myself[2]] % 3 === 0 && grid[myself[1] - 1][myself[2]] !== myself[0])
        options.push("left");
    if (myself[1] !== grid.length - 1 && grid[myself[1] + 1][myself[2]] % 3 === 0 && grid[myself[1] + 1][myself[2]] !== myself[0])
        options.push("right");
    if (myself[2] !== 0 && grid[myself[1]][myself[2] - 1] % 3 === 0 && grid[myself[1]][myself[2] - 1] !== myself[0])
        options.push("up");
    if (myself[2] !== grid.length - 1 && grid[myself[1]][myself[2] + 1] % 3 === 0 && grid[myself[1]][myself[2] + 1] !== myself[0])
        options.push("down");
    if (options.length > 0) return options[Math.random() * options.length | 0];

    // Otherwise, pick one at random.
    return ["up","down","left","right"][Math.random() * 4 | 0];
}

헌터 킬러는 가장 가까운 봇을 대상으로 페인트 칠을 할 수 있으며 모든 공간에 페인트 칠을 시도하여 제거합니다. 그것들을 모두 얻으면 무작위 페인팅 알고리즘으로 돌아가서 첫 번째 공백으로 이동하고 두 번째 공백으로 이동하여 흰색으로 만들 수 있습니다.

좋은 전략을 가진 봇에 걸고 잠시 동안 따라갈 수 있다면 잘하는 것처럼 보이지만 모든 대상을 빠르게 죽일 경우 (또는 대상이 약한 봇) 중간 정도입니다.

봇이 제거 될 때 제거되지 않으므로 현재 버전의 컨트롤러에서는 제대로 작동하지 않습니다. 변경되지 않은 경우 몇 차례 이동하지 않은 봇을 무시하도록 다시 작성합니다 (잠재적으로는 아니지만 봇이 생존 할 수있는 거북이 전략을 잠재적으로 허용합니다).

이 문제를 해결하기 위해 필요한 것은 runBots의 첫 번째 루프를

for (var j = 0; j < botData.length; j++) {
    if (!botData[j].eliminated)
        bots_array.push([botData[j].uid, botData[j].x, botData[j].y]);
}

컨트롤러가 변경되지 않으면 (잠재적으로 매우 게으른 제안) 있습니다. 현재 라운드를 확인하고 보드에있는 각 색상 수를 계산하여 어떤 봇이 제거되었는지 알 수 있습니다.
Zacharý

변경되었지만 감사합니다 :)
Spitemaster

5

GiveMeSpace

function(myself, grid, bots, gameInfo){
    if(!localStorage.givemespace){
        localStorage.givemespace = JSON.stringify({
            recent:[],
            timeout:-1,
            corner:[9999,-1,-1],
            following:[]
        });
    }
    var firstchoice = {up:-1,down:-1,left:-1,right:-1}, nearestblank = [9999,-1,-1], store = JSON.parse(localStorage.givemespace), unique = [], numunique = 0,
        currdist, i, j;
    if(store.timeout >= 0 && store.corner[1] >= 0 && store.corner[2] >= 0){
        store.timeout--;
        persiststorage(store);
        if(store.corner[2] < myself[2]){
            return 'up';
        }
        if(store.corner[2] > myself[2]){
            return 'down';
        }
        if(store.corner[1] < myself[1]){
            return 'left';
        }
        if(store.corner[1] > myself[1]){
            return 'right';
        }
    }
    if(store.recent.length == 20){
        for(i=0;i<store.recent.length;i++){
            if(unique.indexOf(store.recent[i][1]+"_"+store.recent[i][2]) == -1){
                unique.push(store.recent[i][1]+"_"+store.recent[i][2]);
                numunique++;
            }
        }
        if(numunique <= 6){
            store.recent = [];
            store.timeout = 10+numunique;
            store.corner = [[-1,0,0],[-1,0,grid.length-1],[-1,grid.length-1,0],[-1,grid.length-1,grid.length-1]][Math.random()*4|0];
            persiststorage(store);
        }
    }
    function dist(a,b){
        return Math.abs(a[1]-b[1])+Math.abs(a[2]-b[2]);
    }
    function finalcolor(a,b){
        return Math.abs(a-b)%3;
    }
    function persiststorage(store){
        if(store.recent.length > 20) store.recent = store.recent.slice(1);
        localStorage.givemespace = JSON.stringify(store);
    }
    store.recent.push(myself);
    persiststorage(store);
    if(myself[2] > 0 && myself[0] != grid[myself[1]][myself[2]-1] && (grid[myself[1]][myself[2]-1] == 0 || finalcolor(myself[0],grid[myself[1]][myself[2]-1]) == 0)){
        firstchoice.up = 9999;
    }
    if(myself[2] < grid.length-1 && myself[0] != grid[myself[1]][myself[2]+1] && (grid[myself[1]][myself[2]+1] == 0 || finalcolor(myself[0],grid[myself[1]][myself[2]+1]) == 0)){
        firstchoice.down = 9999;
    }
    if(myself[1] > 0 && myself[0] != grid[myself[1]-1][myself[2]] && (grid[myself[1]-1][myself[2]] == 0 || finalcolor(myself[0],grid[myself[1]-1][myself[2]]) == 0)){
        firstchoice.left = 9999;
    }
    if(myself[1] < grid.length-1 && myself[0] != grid[myself[1]+1][myself[2]] && (grid[myself[1]+1][myself[2]] == 0 || finalcolor(myself[0],grid[myself[1]+1][myself[2]]) == 0)){
        firstchoice.right = 9999;
    }
    if(firstchoice.up > 0 || firstchoice.down > 0 || firstchoice.left > 0 || firstchoice.right > 0){
        for(i=0;i<bots.length;i++){
            if(bots[i][0] != myself[0]){
                if(firstchoice.up > 0){
                    currdist = dist(bots[i],[0,myself[1],myself[2]-1]);
                    if(currdist < firstchoice.up){
                        firstchoice.up = currdist;
                    }
                }
                if(firstchoice.down > 0){
                    currdist = dist(bots[i],[0,myself[1],myself[2]+1]);
                    if(currdist < firstchoice.down){
                        firstchoice.down = currdist;
                    }
                }
                if(firstchoice.left > 0){
                    currdist = dist(bots[i],[0,myself[1]-1,myself[2]]);
                    if(currdist < firstchoice.left){
                        firstchoice.left = currdist;
                    }
                }
                if(firstchoice.right > 0){
                    currdist = dist(bots[i],[0,myself[1]+1,myself[2]]);
                    if(currdist < firstchoice.right){
                        firstchoice.right = currdist;
                    }
                }
            }
        }
        if(firstchoice.up >= firstchoice.down && firstchoice.up >= firstchoice.left && firstchoice.up >= firstchoice.right){
            return 'up';
        }
        else if(firstchoice.down >= firstchoice.left && firstchoice.down >= firstchoice.right){
            return 'down';
        }
        else if(firstchoice.left >= firstchoice.right){
            return 'left';
        }
        else{
            return 'right';
        }
    }
    for(i=0;i<grid.length;i++){
        for(j=0;j<grid.length;j++){
            if((i != myself[1] || j != myself[2]) && grid[i][j] != myself[0] && (grid[i][j] == 0 || finalcolor(myself[0],grid[i][j]) == 0)){
                currdist = dist(myself,[0,i,j]);
                if(currdist < nearestblank[0]){
                    nearestblank[0] = currdist;
                    nearestblank[1] = i;
                    nearestblank[2] = j;
                }
            }
        }
    }
    if(nearestblank[0] < 9999){
        if(nearestblank[2] < myself[2]){
            return 'up';
        }
        if(nearestblank[2] > myself[2]){
            return 'down';
        }
        if(nearestblank[1] < myself[1]){
            return 'left';
        }
        if(nearestblank[1] > myself[1]){
            return 'right';
        }
    }
    return ['up','down','left','right'][Math.random()*4|0];
}

옆에 칠할 수있는 공간 (자체 색으로 칠할 수있는) 공간을 확인하고 다른 봇에서 가장 먼 공간을 선택합니다. 봇에 인접한 페인트 가능한 공간이 없으면 가장 가까운 페인트 가능한 공간을 찾아 향합니다.

현재 무한 루프를 피합니다.

해야 할 일 : 사냥꾼을 피하십시오.


5

여행자

function TRAVELER([myColor, myX, myY], grid, bots, [frame, maxFrames]) {
    class BinaryHeapStrategy {
        constructor(options) {
            this.comparator = options.comparator;
            this.data = [];
            this.heapify();
        }
        heapify() {
            if (this.data.length > 0) {
                for (let i = 0; i < this.data.length; i++) {
                    this.bubbleUp(i);
                }
            }
        }
        queue(value) {
            this.data.push(value);
            this.bubbleUp(this.data.length - 1);
        }
        dequeue() {
            const ret = this.data[0];
            const last = this.data.pop();
            if (this.data.length > 0 && last !== undefined) {
                this.data[0] = last;
                this.bubbleDown(0);
            }
            return ret;
        }
        peek() {
            return this.data[0];
        }
        clear() {
            this.data.length = 0;
        }
        bubbleUp(pos) {
            while (pos > 0) {
                const parent = (pos - 1) >>> 1;
                if (this.comparator(this.data[pos], this.data[parent]) < 0) {
                    const x = this.data[parent];
                    this.data[parent] = this.data[pos];
                    this.data[pos] = x;
                    pos = parent;
                }
                else {
                    break;
                }
            }
        }
        bubbleDown(pos) {
            let last = this.data.length - 1;
            while (true) {
                const left = (pos << 1) + 1;
                const right = left + 1;
                let minIndex = pos;
                if (left <= last && this.comparator(this.data[left], this.data[minIndex]) < 0) {
                    minIndex = left;
                }
                if (right <= last && this.comparator(this.data[right], this.data[minIndex]) < 0) {
                    minIndex = right;
                }
                if (minIndex !== pos) {
                    const x = this.data[minIndex];
                    this.data[minIndex] = this.data[pos];
                    this.data[pos] = x;
                    pos = minIndex;
                }
                else {
                    break;
                }
            }
            return void 0;
        }
    }
    class PriorityQueue {
        constructor(options) {
            this.length = 0;
            this.length = 0;
            this.strategy = new BinaryHeapStrategy(options);
        }
        queue(value) {
            this.length++;
            this.strategy.queue(value);
        }
        dequeue() {
            if (!this.length)
                return;
            this.length--;
            return this.strategy.dequeue();
        }
        peek() {
            if (!this.length)
                return;
            return this.strategy.peek();
        }
        clear() {
            this.length = 0;
            this.strategy.clear();
        }
    }
    const mapSize = {
        width: grid[0].length,
        height: grid.length
    };
    const mapArea = mapSize.width * mapSize.height;
    const maxOpenNodes = 300;
    const centerNode = Node(myX, myY);
    const colorStats = new Array(bots.length + 1).fill(0);
    const nearestBotAtNode = new Array(mapArea);
    for (let x = 0; x < mapSize.width; ++x) {
        let row = grid[x];
        for (let y = 0; y < mapSize.height; ++y) {
            let color = row[y];
            ++colorStats[color];
            let id = nodeId(Node(x, y));
            let closestBots = null;
            for (let [botColor, botX, botY] of bots) {
                let distance = Math.max(1, manhattanDistance(x, y, botX, botY));
                if (closestBots === null || distance < closestBots.distance) {
                    closestBots = { distance, colors: [botColor] };
                }
                else if (distance == closestBots.distance) {
                    closestBots = { distance, colors: [...closestBots.colors, botColor] };
                }
            }
            nearestBotAtNode[id] = closestBots;
        }
    }
    const bestSpace = { node: null, space: 0 };
    const primaryEnemy = winningColor({ includeMyself: false, includeWhite: false, canErase: true });
    const isBehindWinner = winningColor({ includeMyself: true, includeWhite: false, canErase: false, ignoreColor: primaryEnemy }) == myColor;
    var step = Math.round(Math.max(1, mapSize.width / 30));
    for (let x = step; x < mapSize.width - step; x += step) {
        for (let y = step; y < mapSize.height - step; y += step) {
            let space = countSpace(x, y);
            if (bestSpace.node == null || space > bestSpace.space) {
                bestSpace.node = Node(x, y);
                bestSpace.space = space;
            }
        }
    }
    const goalNode = bestSpace.node;
    const isRetreat = nearestBotAtNode[nodeId(centerNode)].colors.length > 1;
    function Node(x, y) {
        return { x, y };
    }
    function AStarNode(node) {
        return Object.assign({}, node);
    }
    function nodeId(node) {
        return node.y * mapSize.height + node.x;
    }
    function defaultComparator(a, b) {
        return (a.cost + a.goal) - (b.cost + b.goal);
    }
    function nonDiagonalNodes(node) {
        return [
            node.x + 1 < mapSize.width ? Node(node.x + 1, node.y) : null,
            node.y + 1 < mapSize.height ? Node(node.x, node.y + 1) : null,
            node.x > 0 ? Node(node.x - 1, node.y) : null,
            node.y > 0 ? Node(node.x, node.y - 1) : null
        ].filter(x => x);
    }
    function mixColor(floorColor, botColor) {
        return [botColor, 0, floorColor][Math.abs(botColor - floorColor) % 3];
    }
    function countSpace(x, y, area = -1) {
        if (x < 0 || y < 0 || x >= mapSize.width || y >= mapSize.height) {
            return 0;
        }
        let color = grid[x][y];
        if (area == -1) {
            area = 0;
            while (countSpace(x, y, area)) {
                ++area;
            }
            return area * 2 * 4;
        }
        else if (area == 0) {
            if (color == myColor) {
                return 0;
            }
            if (color == 0 || mixColor(color, myColor) == myColor) {
                return 1;
            }
            return 0;
        }
        else {
            for (let dx = -area; dx <= area; ++dx) {
                if (!countSpace(x + dx, y - area, 0))
                    return 0;
                if (!countSpace(x + dx, y + area, 0))
                    return 0;
            }
            for (let dy = -area + 1; dy <= area - 1; ++dy) {
                if (!countSpace(x - area, y + dy, 0))
                    return 0;
                if (!countSpace(x + area, y + dy, 0))
                    return 0;
            }
            return area * 2 * 4;
        }
    }
    function manhattanDistance(x1, y1, x2, y2) {
        return Math.abs(x2 - x1) + Math.abs(y2 - y1);
    }
    function moveFromNodes(node1, node2) {
        if (node2.x < node1.x)
            return 'left';
        if (node2.x > node1.x)
            return 'right';
        if (node2.y < node1.y)
            return 'up';
        if (node2.y > node1.y)
            return 'down';
        return 'wait';
    }
    function winningColor(options) {
        let winningColor = {
            color: 0,
            count: 0
        };
        for (let color = 0; color < colorStats.length; ++color) {
            if (color === 0) {
                if (!options.includeWhite) {
                    continue;
                }
            }
            else if (color === myColor) {
                if (!options.includeMyself) {
                    continue;
                }
            }
            else if (color === options.ignoreColor) {
                continue;
            }
            else if (options.canErase && mixColor(color, myColor) === color) {
                continue;
            }
            if (colorStats[color] > winningColor.count) {
                winningColor.color = color;
                winningColor.count = colorStats[color];
            }
        }
        if (winningColor.count === 0) {
            return null;
        }
        return winningColor.color;
    }
    function goal(node) {
        let goal = manhattanDistance(node.x, node.y, goalNode.x, goalNode.y);
        return goal;
    }
    function cost(node, changes, depth) {
        let cost = 0;
        let id = nodeId(node);
        let color;
        if (changes[id] !== undefined) {
            color = changes[id];
        }
        else {
            color = grid[node.x][node.y];
        }
        if (color !== 0 && color !== myColor) {
            let mixedColor = changes[id] = mixColor(color, myColor);
            if (mixedColor === myColor) {
                if (nearestBotAtNode[nodeId(centerNode)].colors.includes(color) || nearestBotAtNode[id].colors.includes(color)) {
                    cost += 5000;
                }
                if (color === primaryEnemy) {
                    if (isBehindWinner) {
                        cost -= 80;
                    }
                    else {
                        cost -= 60;
                    }
                }
                else {
                    cost -= 30;
                }
            }
            else if (mixedColor === color) {
                cost += 0;
            }
            else if (mixedColor === 0) {
                if (color === primaryEnemy) {
                    if (isBehindWinner) {
                        cost -= 15;
                    }
                    else {
                        cost -= 10;
                    }
                }
                else {
                    cost -= 5;
                }
            }
        }
        if (color === 0) {
            changes[id] = myColor;
            let nearestBot = nearestBotAtNode[id];
            if (nearestBot.colors.includes(myColor)) {
                if (depth == 1 && nearestBot.colors.length > 1) {
                    cost += 20;
                }
                else {
                    cost -= 60;
                }
            }
            else {
                if (depth == 1 && nearestBot.distance == 0) {
                    cost += 30;
                }
                else {
                    let distanceDelta = depth - nearestBot.distance;
                    if (distanceDelta >= 0) {
                        cost += -50 + distanceDelta * 20;
                    }
                    else {
                        cost += -60;
                    }
                }
            }
        }
        return cost;
    }
    function bestMove(options) {
        let walkCost = 25;
        let goalImportance = 3;
        let goalTarget = false;
        if (options.strategy === 'long-sighted') {
            walkCost = 0;
            goalImportance = 25;
            goalTarget = true;
        }
        options.maxOpenNodes = Math.min(options.maxOpenNodes, mapArea);
        const openNodes = new PriorityQueue({
            comparator: defaultComparator
        });
        const bestNodes = new PriorityQueue({
            comparator: defaultComparator
        });
        const closedNodes = {};
        const closeNode = (node) => closedNodes[nodeId(node)] = node;
        const getClosedNode = (node) => closedNodes[nodeId(node)];
        if (!isRetreat) {
            const waitNode = AStarNode(options.fromNode);
            waitNode.depth = 0;
            waitNode.changes = {};
            waitNode.goal = goal(waitNode);
            waitNode.cost = cost(waitNode, waitNode.changes, waitNode.depth);
            bestNodes.queue(waitNode);
            closeNode(waitNode);
        }
        const startNode = AStarNode(options.fromNode);
        startNode.depth = 0;
        startNode.changes = {};
        startNode.goal = goal(startNode);
        startNode.cost = cost(startNode, startNode.changes, startNode.depth);
        openNodes.queue(startNode);
        while (openNodes.length && openNodes.length < maxOpenNodes) {
            let bestNode = openNodes.dequeue();
            nonDiagonalNodes(bestNode).forEach(node => {
                let nextNode = AStarNode(node);
                nextNode.depth = bestNode.depth + 1;
                nextNode.changes = Object.assign({}, bestNode.changes);
                nextNode.parent = bestNode;
                nextNode.goal = goal(node) * goalImportance;
                nextNode.cost = bestNode.cost + walkCost + cost(nextNode, nextNode.changes, nextNode.depth);
                if (goalTarget && nextNode.x == goalNode.x && nextNode.y == goalNode.y) {
                    bestNodes.queue(nextNode);
                    return;
                }
                let closedNode = getClosedNode(node);
                if (!closedNode || defaultComparator(nextNode, closedNode) < 0) {
                    openNodes.queue(nextNode);
                }
            });
            closeNode(bestNode);
            if (bestNode != startNode) {
                bestNodes.queue(bestNode);
            }
        }
        let directions = [];
        let bestNode = bestNodes.peek();
        if (options.strategy === 'short-sighted' && bestNode.depth < 6 && !isRetreat) {
            return bestMove(Object.assign({}, options, { strategy: 'long-sighted' }));
        }
        else {
            let nextMoveNode = bestNode;
            while (nextMoveNode.parent) {
                directions.unshift(moveFromNodes(nextMoveNode.parent, nextMoveNode));
                if (nextMoveNode.parent === startNode) {
                    break;
                }
                nextMoveNode = nextMoveNode.parent;
            }
            return moveFromNodes(startNode, nextMoveNode);
        }
    }
    return bestMove({
        strategy: 'short-sighted',
        fromNode: centerNode,
        maxOpenNodes,
    });
}

Stateless 봇은 A-star 경로를 사용하여 가장 최적의 경로를 찾습니다 (최저 비용 경로가 최적). 일반적으로 평균 150 개의 움직임이 예상되지만, 가장 좋은 경로의 움직임이 6보다 작 으면 봇은 가장 많은 색상을 얻을 수있는 클러스터로 이동해야합니다.

승리하는 색상을 결정하면 일부 비용이 발생하며 일부 비용은 주변을 돌고 트롤을 잃지 않도록 설정됩니다.

비용 값을 편집하거나 비용 기능을 다시 작성하여 코드를 개선하려는 사람은 누구나 환영합니다.

모두 재미있게 보내십시오.

편집 : 채팅에 회신 할 수 없지만 (새 계정으로는 할 수 없음) 의견을 보았습니다. 400ms에서 50ms 미만으로 최적화했습니다. 시간이 지남에 따라 더 빨라질 것입니다. 필요한 경우 더 많이 최적화하고 채팅이나 댓글에 메시지를 남겨주세요.

편집 2 : 여기에 A-Star는 두 점 사이의 경로를 찾는 데만 사용할 수 있으며 일반적으로 경로 알고리즘에서 G (목표 함수)를 조정하여 0을 반환하여 경로를 찾게합니다. 목표가 아니라 모든 방향에서 가장 효율적인 경로를 찾는 것입니다. 각 경로 비용은 각 단계의 누적 비용입니다. 적의 정사각형 색상을 우리와 함께 다시 칠하는 등 좋은 단계 또는 나쁜 단계 인 경우 단계 비용을 정의 할 수 있으며, 다시 만들 수없는 정사각형 위를 걷는 것은 나쁜 단계입니다. 그러나 goal()함수가 0을 반환하지 않는 대신 페인트 가능한 정사각형이 가장 많은 클러스터쪽으로 약간 걷는 데 약간 기여합니다. tldr; 경로 끝이 가장 좋은 클러스터이고 장애물은 색상이 혼합되는 방식입니다.


2
PPCG에 오신 것을 환영합니다! 이것은 좋은 설명이며 좋은 대답입니다. 커뮤니티가 친절하고 도움이된다는 것을 알게되어 기쁩니다. 앞으로도 좋은 경험을 계속 하시길 바랍니다.
Mego

4

원거리

function([id, x, y], grid, bots, gameInfo) {
    let value = n => n ? n == id ? 0 : 2 - Math.abs(id - n) % 3 : 2;
    let directions = [
        {name: "wait", x: 0, y: 0},
        {name: "left", x: -1, y: 0},
        {name: "up", x: 0, y: -1},
        {name: "right", x: 1, y: 0},
        {name: "down", x: 0, y: 1},
    ];
    for (let d of directions) {
        d.score = 0;
        for (let i = 1; ; i++) {
            let px = x + i * d.x;
            let py = y + i * d.y;
            if (px < 0 || py < 0 || px == grid.length || py == grid.length) break;
            if (bots.some(([, x, y]) => x == px && y == py)) break;
            d.score += value(grid[px][py]) / i;
        }
    }
    let best = Math.max(...directions.map(({score}) => score));
    return directions.find(({score}) => score == best).name;
}

NearSightedGreed에서 뻔뻔스럽게 이름이 gir 다. 거리와 색상에 따라 모든 기본 방향에서 보이는 모든 사각형의 점수를 매기고 합계가 가장 높은 방향을 선택합니다.


4

DFS 봇

function(myself, grid, bots, gameinfo) {
    let max_scores = Array(12);
    max_scores[11] = 0;
    max_scores[10] = 0.05 * (1 + 1e-6 + 16.1 * 1e-10);
    for (let i = 9; i >= 0; i--) {
        max_scores[i] = max_scores[10] + 0.95 * max_scores[i + 1];
    }
    let my_id = myself[0];
    let my_x = myself[1];
    let my_y = myself[2];
    let delta = [[0, -1], [0, 1], [-1, 0], [1, 0], [0, 0]];
    let seen = Array(grid.length).fill().map(() => Array(grid.length).fill(false)); 
    let scores = Array(bots.length + 2).fill(0);
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid.length; j++) {
            scores[grid[i][j]]++;
        }
    }
    function search(x, y, depth) {
        if (depth == 11) {
            return [4, 0];
        }
        let max_score = 0;
        let best_move = 0;
        for (let i = 0; i < 4; i++) {
            let x1 = x + delta[i][0];
            let y1 = y + delta[i][1];
            if ((x1 < 0) || (x1 >= grid.length)) {
                continue;
            }
            if ((y1 < 0) || (y1 >= grid.length)) {
                continue;
            }
            if (seen[x1][y1]) {
                continue;
            }
            let n = 0;
            for (let dx = -1; dx <= 1; dx++) {
                let x2 = x1 + dx;
                if ((x2 < 0) || (x2 >= grid.length)) {
                    continue;
                }
                for (let dy = -1; dy <= 1; dy++) {
                    if ((dx == 0) && (dy == 0)) {
                        continue;
                    }
                    let y2 = y1 + dy;
                    if ((y2 < 0) || (y2 >= grid.length)) {
                        continue;
                    }
                    n++;
                    if (grid[x2][y2] == my_id) {
                        n++;
                    }
                }
            }
            let prev = grid[x1][y1];
            if (prev == 0) {
                next = my_id;
            } else {
                next = [my_id, 0, prev][Math.abs(my_id - prev) % 3];
            }
            let score = 0;
            score += 1e-10 * (n + 0.1 * Math.random());
            if (next != prev) {
                if (next == my_id) {
                    score += 1;
                }
                if (prev == 0 || scores[prev] > scores[my_id]) {
                    score += 1e-6;    
                } 
            }
            score *= 0.05;
            if (score + 0.95 * max_scores[depth + 1] <= max_score) {
                continue;
            }
            grid[x1][y1] = next;
            seen[x1][y1] = true;
            let final_score = score + 0.95 * search(x1, y1, depth + 1)[1];
            seen[x1][y1] = false;
            grid[x1][y1] = prev;
            if (final_score > max_score) {
                max_score = final_score;
                best_move = i;
            }
        }
        return [best_move, max_score];
    }
    let best_move = search(my_x, my_y, 0)[0];
    return ["up", "down", "left", "right", "wait"][best_move];
}

2
각 레벨에서 가능한 최대 점수 1 + 1e-6 + 8.1 * 1e-10이기 때문에 잘라내
기를

@ Night2 OK, 완료.
user1502040

@dzaima 자, 이제 훨씬 빨라질 것입니다.
user1502040

3

겸손한 페인트 세일즈맨

// Humble Paint Salesman
function(myself, grid, bots, gameInfo) {
    let [id, x, y] = myself;
    // if first move
    if(gameInfo[0] == 1) {
        this.size = grid.length;
        this.mid = this.size / 2;
        this.dx = x < this.mid ? "right" : "left";
        this.dy = y < this.mid ? "down" : "up";
        this.flip = function(v) {
            this.dict = this.dict || {
                right: "left",
                left: "right",
                down: "up",
                up: "down"
            };
            return this.dict[v];
        }
        this.queue = [];
    }
    if(grid[x][y] == 0) {
        return "wait";
    }
    else if(this.queue.length) {
        return this.queue.shift();
    }
    else if(x == 0 || x + 1 == this.size) {
        this.dx = this.flip(this.dx);
        if(y == 0 || y + 1 == this.size) {
            this.dy = this.flip(this.dy);
        }
        this.queue.push(this.dx);
        return this.dy;
    }
    return this.dx;
}

보드를 간단히 덮고 보드를 위아래로 반복합니다. 그 아래의 셀이 비어 있으면 대기합니다 (판매원이 자신의 상품을 검색해야합니다!)


1
이봐, 지금 gameInfo[0] == 2바꿀 수있어 gameInfo[0] == 1오류가 수정되었습니다
Beta Decay

@BetaDecay 감사합니다! 언급 한 것을 잊어 버렸습니다. 끝까지 수정되었습니다.”
Conor O'Brien

3

드래곤 봇

function dragonCurve(myself, grid, bots, gameInfo){
  dCurve=n=>{
    if(n==0){return "1 "}
    return dCurve(n-1).replace(/(.)(.)/g,"1$10$2")
  }
  [id,x,y]=myself;
  dir=0;
  if(gameInfo[0]==1){
    dragon=dCurve(12);
    if(x<3*bots.length-x){
      if(y<x){dir=0;}
      else if(3*bots.length-y<x){dir=2;}
      else{dir=3;}
    }
    else{
      if(y<3*bots.length-x){dir=0;}
      else if(y>x){dir=2;}
      else{dir=1;}
    }
    window.localStorage.setItem("dragon",dragon);
    window.localStorage.setItem("dragonDir",dir);
    window.localStorage.setItem("dragonStep",0);
    return ["up","right","down","left"][dir];
  }
  dragon=window.localStorage.getItem("dragon")
  dir=window.localStorage.getItem("dragonDir")-0;
  step=window.localStorage.getItem("dragonStep")-0;
  if(gameInfo[0]%2==0){
    return ["up","right","down","left"][dir];
  }
  validStep=false;
  while(!validStep){
    if(-dragon[step]){dir=(dir+1)%4;}
    else{dir=(dir+3)%4;}
    step+=1;
    validStep=((dir==0&&y!=0)||(dir==3&&x!=0)||(dir==1&&x!=3*bots.length-1)||(dir==2&&y!=3*bots.length-1));
  }
  window.localStorage.setItem("dragon",dragon);
  window.localStorage.setItem("dragonDir",dir);
  window.localStorage.setItem("dragonStep",step);
  return ["up","right","down","left"][dir];
}

DragonBot은 단순히 측면 길이가 2 인 용 곡선을 그리려고합니다 (공백을 남깁니다).


사람들이 그림을 그리는 데 너무 오래 걸린다는 것에 놀랐습니다!
Beta Decay

드래곤 커브 기능을 알아내는 데 시간이 걸렸습니다. 벽에 부딪 치면 작동하는 명령을 찾을 때까지 오른쪽과 왼쪽 명령을 계속 진행합니다. 드래곤 커브의 길이를 8k 요소로 확장 하여이 프로세스가 소진되지 않도록하십시오.
fəˈnɛtɪk

DragonBot 오류 :window.localStorage.getItem is not a function or its return value is not iterable (line 23 column 41)
kenorb

window.localStorage사용 방법 은 내 봇 "TheFollower"를 참조하십시오 . window.localStorage.NAME = VALUE작동해야합니다.
Zacharý

@ Zacharý 그것은 이미 키 값 쌍을 사용하여 작동
fəˈnɛtɪk

3

드론 -BEA7

function(myData, gridData, botData, gameInfoData) {
  function customSetup(fThis) {
    fThis.botUID = 0;
    fThis.swarm = new Array(3);
    fThis.matchedSize = 0;
    bots.forEach(b => { b.failedSignal = 0; b.trespass = 0; b.desecrate = 0; });
    delete fThis.connected;
    delete fThis.target;
    delete fThis.chaser;
    delete fThis.cleaners;
    delete fThis.roamers;
  }

  let XY = this.xyClass;
  let Bot = this.botClass;
  let Cell = this.cellClass;

  function at(pos, usedGrid = grid) { // NEVER EVER THINK ABOUT PUTTING THIS ON THE GRID ITSELF
    return pos.withinBounds() ? usedGrid[pos.toIndex()] : new Cell(null);
  }

  if (gameInfoData[0] === 1) {
    XY = this.xyClass = (class XY {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }

      static fromIndex(index) {
        return new XY(Math.floor(index / gridSize), index % gridSize);
      }
      toIndex() {
        return this.x * gridSize + this.y;
      }

      add(other) {
        return new XY(this.x + other.x, this.y + other.y);
      }
      sub(other) {
        return new XY(this.x - other.x, this.y - other.y);
      }
      div(value) {
        return new XY(Math.round(this.x / v), Math.round(this.y / v));
      }
      mul(value) {
        return new XY(Math.round(this.x * m), Math.round(this.y * m));
      }
      equals(other) {
        return this.x === other.x && this.y === other.y;
      }

      distance(other) {
        return Math.abs(other.x - this.x) + Math.abs(other.y - this.y);
      }
      chebyshevDistance(other) {
        return Math.max(Math.abs(other.x - this.x), Math.abs(other.y - this.y));
      }

      withinBounds() {
        return this.x >= 0 && this.x < gridSize && this.y >= 0 && this.y < gridSize;
      }

      getNeighbors() {
        return neighbors.map(p => this.add(p));
      }
      getRealNeighbors() {
        return this.getNeighbors().filter(p => p.withinBounds());
      }
    });
    Bot = this.botClass = (class Bot extends XY {
      constructor(botData) {
        super(botData[1], botData[2]);
        this.id = botData[0];
        this.score = 0;
        this.dead = true;
      }
    });
    Cell = this.cellClass = (class Cell {
      constructor(id, xy) {
        this.id = id;
        this.pos = xy;
      }
    });

    this.botMap = [];
    this.botIDs = [];
    botData.forEach(d => { this.botMap[d[0]] = new Bot(d); this.botIDs.push(d[0]); });
    this.currentRound = 0;

    delete this.prevGrid;
  }

  const gridSize = gridData.length;
  const gridSizeSqr = gridSize * gridSize;
  const grid = new Array(gridSize * gridSize);
  for (var x = 0; x < gridSize; x++) {
    for (var y = 0; y < gridSize; y++) {
      grid[x * gridSize + y] = new Cell(gridData[x][y], new XY(x, y));
    }
  }
  const prevGrid = this.prevGrid;
  this.prevGrid = grid;

  const bots = [];
  const botMap = this.botMap;
  this.botIDs.forEach(id => botMap[id].dead = true);
  botData.forEach(d => {
    const r = botMap[d[0]];
    r.dead = false;
    r.lastPosition = new XY(r.x, r.y);
    r.x = d[1];
    r.y = d[2];
    r.score = grid.reduce((sum, cell) => sum + (cell.id === r.id), 0);
    bots.push(r);
    at(r).bot = r;
  });
  const me = botMap[myData[0]];

  const currentRound = this.currentRound++;
  const maxRound = gameInfoData[1] - 1;

  const zero = new XY(0, 0);
  const neighbors = [new XY(1, 0), new XY(0, 1), new XY(-1, 0), new XY(0, -1)];
  const moves = ["right", "down", "left", "up", "wait"];

  if (gameInfoData[0] === 1) {
    customSetup(this);
  }

  function rand(max = 1, min = 0) {
    return min + Math.random() * (max - min);
  }
  function randInt(max, min = 0) {
    return Math.floor(rand(max, min));
  }
  function roll(chance = 0.5) {
    return Math.random() < chance;
  }

  function separation(id1, id2) {
    return Math.abs(id1 - id2) % 3;
  }

  function value(id, bot = me) {
    return id === bot.id ? 1 : id === 0 ? 4 : id === null ? 0 : [5, 3, 2][separation(bot.id, id)];
  }

  function travelTo(goal, start = me) {
    const relative = goal.sub(start);
    return Math.abs(relative.x) > Math.abs(relative.y) ? (
      relative.x > 0 ? 0 : 2
    ) : (
      relative.y > 0 ? 1 : relative.y < 0 ? 3 : 4
    );
  }
  function travelToList(goal, start = me) {
    const relative = goal.sub(start);
    return [...start.getRealNeighbors(), start].sort((a, b) => (a.chebyshevDistance(goal) - b.chebyshevDistance(goal)) * gridSizeSqr + (a.distance(goal) - b.distance(goal)));
  }

  const swarm = this.swarm;
  const swarmSize = swarm.length;
  const botUID = this.botUID;

  const signalPatterns = [[3, 0, 1, 1, 0], [0, 1, 2, 2, 2, 3, 3, 2, 2, 1], [2, 3, 2, 3, 0, 0, 1, 0, 3, 3]];
  function patternMove(pos, round, ...pattern) {
    const e = pattern[round % pattern.length];
    const f = (e + 2) % 4;
    function calcPos(d) { return pos.add(neighbors[d]); }
    if (calcPos(e).withinBounds()) {
      return e;
    } else {
      return f;
    }
  }
  function signal(uid = botUID, pos = me, round = currentRound) {
    return patternMove(pos, round, ...signalPatterns[uid]);
  }

  if (currentRound) {
    for (var i = 0; i < swarmSize; i++) {
      if (!swarm[i]) {
        const consideredBots = bots.filter(b => !(b.failedSignal & (1 << i)));
        const matchedBots = consideredBots.filter(b => {
          const prevPos = b.lastPosition;
          const expected = neighbors[signal(i, prevPos, currentRound - 1)];
          const performed = b.sub(prevPos);
          const matched = performed.equals(expected);
          if (!matched) {
            b.failedSignal |= (1 << i);
          }
          return matched;
        });
        if (matchedBots.length === 1) {
          swarm[i] = matchedBots[0];
          swarm[i].member = true;
          this.matchedSize++;
          console.log("Swarm member", i, "found!");
        }
      }
    }
  }

  function findTarget() {
    const lists = [];
    lists.unshift(bots.filter(b => b.removal.candidate));
    lists.unshift(lists[0].filter(b => b.removal.separations[0] === 0));
    lists.unshift(lists[0].filter(b => b.removal.speed === 3));
    lists.unshift(lists[2].filter(b => b.removal.separations[0] === 2));
    const bestList = lists.find(l => l.length);
    if (!bestList) {
      console.log("No more targets!");
      return undefined;
    }
    const bestTarget = bestList.sort((a, b) => b.trespass - a.trespass)[0]; // TODO: Remove sort. TODO: Improve.
    console.log("Best target:", bestTarget);
    return bestTarget;
  }

  if (this.matchedSize === swarmSize) {
    if (!this.connected) {
      bots.forEach(b => {
        const separations = swarm.map(m => separation(b.id, m.id));
        const speed = Math.floor(separations.reduce((sum, val) => sum + (val < 2 ? 1 : 0.5), 0));
        b.removal = {separations: separations, speed: speed, candidate: speed > 1 && !b.member};
      });
      console.log("All connections established.");
      this.connected = true;
    }

    bots.forEach(b => {
      if (b.removal.separations[0] !== 2 && at(b, prevGrid).id === swarm[0].id) {
        b.desecrate++;
      }
      swarm.forEach((m, i) => {
        if (b.removal.separations[i] !== 2 && at(b, prevGrid).id === m.id) {
          b.trespass++;
        }
      });
    });

    if (!this.target || this.target.dead) {
      this.target = findTarget();

      swarm.forEach(b => {
        delete b.partner;
      });

      const sep = this.target.removal.separations;
      const overwriters = [];
      const eraser = [];
      const helpers = []; 
      for (var i = 0; i < swarmSize; i++) {
        if (swarm[i].partner) {
          continue;
        }
        if (sep[i] === 0) {
          overwriters.push(swarm[i]);
        } else if (sep[i] === 1) {
          eraser.push(swarm[i]);
        } else if (sep[i] === 2) {
          for (var j = i + 1; j < swarmSize; j++) {
            if (sep[j] === 2) {
              swarm[j].partner = swarm[i];
              swarm[i].partner = swarm[j];
              eraser.push(swarm[i]);
              break;
            }
          }
          if (!swarm[i].partner) {
            helpers.push(swarm[i]);
          }
        }
      }

      this.chaser = eraser.pop() || overwriters.pop();
      this.cleaners = [...overwriters, ...eraser];
      this.roamers = helpers; // TODO: Make helpers more useful by making them simply target the next guy?
    }

    function findImmediate(target, bot = me) {
      const list = travelToList(target, bot);
      return list.find(p => !at(p).reserved) || list[0];
    }

    grid.forEach(c => c.reserved = 0);
    function reserve(bot, target) {
      if (!bot.target) {
        bot.immediateTarget = findImmediate(target, bot);
        bot.target = target;
        at(bot.immediateTarget).reserved++;
        at(target).reserved++;
      }
    }
    function unreserve(bot) {
      if (bot.target) {
        at(bot.immediateTarget).reserved--;
        at(bot.target).reserved--;
        delete bot.immediateTarget;
        delete bot.target;
      }
    }

    reserve(this.chaser, chase(this.target));

    for (var i = 0; i < swarmSize; i++) {
      const emergency = preserveLife(swarm[i]);
      if (emergency) {
        unreserve(swarm[i]);
        reserve(swarm[i], emergency);
      }
    }

    this.cleaners.forEach(b => reserve(b, clean(b, this.target, this.cleaners)));
    this.roamers.forEach(b => reserve(b, roam(b)));

    const immediateTarget = me.immediateTarget || findImmediate(me.partner.target);
    swarm.forEach(b => unreserve(b));

    return moves[travelTo(immediateTarget)];
  } else {
    return moves[signal()];
  }

  function chase(target) {
    return target;
  }
  function clean(bot, target, cleaners) {
    return grid.filter(c => {
      return c.id === target.id && !c.reserved;
    }).reduce((best, c) => {
      const closest = Math.min(...cleaners.map(b => b.distance(c.pos)));
      const distance = bot.distance(c.pos);
      const wrongness = distance - closest;
      const distanceFromTarget = target.distance(c.pos);
      if (wrongness < best.wrongness || (wrongness === best.wrongness && (distance < best.distance || (distance === best.distance && distanceFromTarget > best.distanceFromTarget)))) {
        return {wrongness: wrongness, distance: distance, distanceFromTarget: distanceFromTarget, pos: c.pos};
      } else {
        return best;
      }
    }, {wrongness: Infinity, distance: Infinity, distanceFromTarget: -Infinity, pos: bot}).pos;
  }
  function roam(bot) {
    const dangerousBots = bots.filter(b => !b.member && separation(b.id, bot.id) !== 2);
    return grid.filter(c => {
      return value(c.id, bot) >= 4 && !c.bot && !c.reserved && !swarm.find(m => m.id === c.id);
    }).reduce((best, c) => {
      const val = value(c.id, bot);
      const distance = bot.distance(c.pos);
      const comfyness = c.pos.getNeighbors().reduce((sum, next) => sum + (value(at(next).id, bot) <= 2), 0);
      const closestBotDist = Math.min(...dangerousBots.map(b => b.distance(c.pos)));
      if (distance < best.distance || (distance === best.distance && (val > best.val || (val === best.val && (comfyness > best.comfyness || (comfyness === best.comfyness && closestBotDist > best.closestBotDist)))))) {
        return {distance: distance, val: val, comfyness: comfyness, closestBotDist: closestBotDist, pos: c.pos};
      } else {
        return best;
      }
    }, {distance: Infinity, val: -Infinity, comfyness: -Infinity, closestBotDist: -Infinity, pos: bot}).pos;
  }
  function preserveLife(bot) {
    if (bot.score < 20) {
      return roam(bot);
    }
  }
}

이것은 드론 트리오의 리더입니다. 그들의 임무는 간단합니다 : 코딩과 알고리즘의 힘으로 적의 화가를 파괴하십시오! 보다 필요한 성능 최적화와 함께보다 자세한 설명이 곧 제공 될 예정입니다.

변경 로그

1.1

  • 공식 컨트롤러에서 수정되었습니다.
  • ~ 25 % 성능 향상

1.0

  • 초판

3

성가신

function(myself, grid, bots, gameInfo) {   

    // Some paramters      
    var brother_loop_count = 0;
    var brother_score = -1;          
    var brother_id = 0;        
    var number_of_brothers_followed = 0;
    var num_of_bots = -1;

    var saw_all_brothers_moves = 0;
    var moves_write = 0;  
    let moves_to_follow = 30;      // How much moves will we follow? 
    let moves_to_use = 5; // Only follow the last 5 elements of this array
    var moves_saw = makeArray(moves_to_follow, 2, 0);  

    var my_id = myself[0];
    var my_x = myself[1];
    var my_y = myself[2];
    var round = gameInfo[0];
    var end_round = gameInfo[1];
    var last_num_of_bots = 0;  

    // Handle Storage 
    if(!localStorage.LB_nfirst){ // First round (Dont rely on round number)
      localStorage.LB_nfirst = true;

      brother_loop_count = 0;// lock on to anyone
      moves_write = 0;
      moves_saw = makeArray(moves_to_follow, 2, 0);
      let num_of_bots = bots.length;

      localStorage.LB_moves_saw = encode_moves(moves_saw);
      localStorage.LB_moves_write = moves_write;// Save it
      localStorage.LB_brother_id = brother_id;// Save it            
      localStorage.LB_brother_loop_count = brother_loop_count; // Save it     
      localStorage.LB_saw_all_brothers_moves = saw_all_brothers_moves;
      localStorage.LB_number_of_brothers_followed = number_of_brothers_followed;
      localStorage.LB_num_of_bots = num_of_bots;
    }
    else{
      moves_saw = decode_moves(localStorage.LB_moves_saw);
      moves_write = parseInt(localStorage.LB_moves_write);
      brother_id = parseInt(localStorage.LB_brother_id); 
      brother_loop_count = parseInt(localStorage.LB_brother_loop_count);
      saw_all_brothers_moves = parseInt(localStorage.LB_saw_all_brothers_moves);
      last_num_of_bots = parseInt(localStorage.LB_last_num_of_bots);
      number_of_brothers_followed = parseInt(localStorage.LB_number_of_brothers_followed);
      num_of_bots = parseInt(localStorage.LB_num_of_bots);
    }

    // Check if our big brother was eliminated
    if(last_num_of_bots !== bots.length){
      // A bot was elimitated. Just tell LittleBrother to search for a new brother
      var found = false;
      for(var i = 0; i<bots.length; i++){
          if (bots[i][0]==brother_id){
              found = true;
              break;
          }
      }
      if(!found){
          brother_loop_count = 0;
          brother_id = 0;
      }
      last_num_of_bots = bots.length;       
    }
    // Check if we are in a infinite loop with big brother
    function equals(a, b) {
        return a[0]===b[0] && a[1]===b[1];
    }    
    if (brother_id !== 0 && (saw_all_brothers_moves===1)){   
        var found_curr_step = new Uint32Array(moves_to_use);
        var left = (moves_write+1)%moves_to_follow;
        var right = (moves_write+1+moves_to_use)%moves_to_follow;
        if (right > left){var comp = moves_saw.slice(left,right);}
        else{var comp = moves_saw.slice(left);comp.push(...moves_saw.slice(0,right));}        
        for (var i = 0; i < moves_to_follow-moves_to_use; i++){
            for (var j = 0; j < moves_to_use; j++){if(equals(comp[j], moves_saw[(i+right)%moves_to_follow])){found_curr_step[j]=true;}}
        }
        var should_clear = true;
        for(var j = 0; j < moves_to_use; j++){if(!found_curr_step[j]){should_clear = false;break;}}
        if (should_clear){
            brother_loop_count = 0;
            brother_id = 0;
        }

    }

    // Are we tired of this brother yet?
    if (brother_loop_count === 0){
      // Determine each bot's score
      var bot_scores = new Uint32Array(num_of_bots+1);
      for (var x = 0; x < grid.length; x++) {
        for (var y = 0; y < grid.length; y++) {
          bot_scores[grid[x][y]] += 1;    // Increase the score of the bot's who color this is
          // The eliminated bots' scores will just stay zero
        }
      }

      // Find a bot to follow
      brother_id = 0;
      if (Math.random() > 0.6){
        var backup_bro = 0;
        var tolerance = 0;
        var chance = Math.random();
        if (chance > 2){tolerance = 1;} // Never
        if (chance > 2){tolerance = 2;} // Never
        for (var uid = 1; uid < bot_scores.length; uid++){
          if (bot_scores[uid]>brother_score && my_id!==uid){
            if (Math.abs(my_id - uid)%3<=tolerance){// Will it be annoying to the brother?  
              brother_score = bot_scores[uid];
              brother_id = uid;
            }
            else{
              if(Math.abs(my_id - uid)%3<2){
                backup_id = uid; // In case we didn't find what we wanted.
              }
            }
          }
        }
      }
      // If we don't have a brother yet, find a random one
      if (brother_id === 0){
        var tries = 0;
        do{
          var ridx = Math.round(Math.random()*(bots.length-1));
          if(bots[ridx][0]!==my_id && Math.abs(my_id - bots[ridx][0])%3===0){
            brother_id = bots[ridx][0];
          }
        }while(brother_id === 0 && tries++<=20);
      }
      if (brother_id===0){brother_id = (my_id===1)?2:1;}

      // Start the brother follow counter
      moves_write = 0;
      saw_all_brothers_moves = 0;
      brother_loop_count = 200 + 300*number_of_brothers_followed;
      number_of_brothers_followed ++;
    }

    // Decrease the loop count variable to make sure we don't stagnate
    brother_loop_count -= 1; // But only for so long

    // Now do the actual following
    var aim_x = -1;
    var aim_y = -1;
    var bro_x = -1;
    var bro_y = -1;
    if (brother_id > 0){

    // Find where brother currently is
    for (var i = 0; i < bots.length; i++){
      if (bots[i][0] === brother_id){
        brother_idx = i;
        break;
      }
    }

    // Which point are we aiming for?
    if(saw_all_brothers_moves === 1 || moves_write > moves_to_use){ // Did I see how my brother moves?

      // Calculate the slice of steps we are going to use
      var left = ((saw_all_brothers_moves===1) ? moves_write+1 : 0)%moves_to_follow;
      var right = ((saw_all_brothers_moves===1) ? moves_write+moves_to_use+1 : moves_to_use)%moves_to_follow;
      if (right > left){// want to read left --> right in moves_saw
         var steps_to_use = moves_saw.slice(left,right);
      }
      else{
        var steps_to_use = moves_saw.slice(0,right)
        steps_to_use.push(...moves_saw.slice(left));
      }

      // Check if we are in his footsteps?
      var in_brothers_footsteps = false;
      for (var step = 0; step<steps_to_use.length; step++){
        if ((steps_to_use[step][0] === my_x) && ((steps_to_use[step][1] === my_y))){
          in_brothers_footsteps = true;
          break;
        }
      }

      if(in_brothers_footsteps === true){
        // We are in his footsteps. Go to the next one!;
        step++; if (step >= steps_to_use.length){step=0;}
        aim_x = steps_to_use[step][0];aim_y = steps_to_use[step][1];
      }
      else{
        // We are not in his footsteps, aim for the footsteps
        aim_x = 0; aim_y = 0;
        for (var step = 0; step<steps_to_use.length; step++){// Calculate step's center of mass
           aim_x += steps_to_use[step][0];aim_y += steps_to_use[step][1];
        }
        aim_x /= moves_to_use; aim_y /= moves_to_use;
      }
    }
    else{
      // No, not yet. Just run towards him
      aim_x = bots[brother_idx][1];
      aim_y = bots[brother_idx][2];
    }  

    // Check if we might touch big brother
    let [dx, dy] = PosAt(toPos([aim_x, aim_y]));       
    if (my_x+dx===bots[brother_idx][1] && my_y+dy===bots[brother_idx][2]){
      // EEEUUW. Flinch away, because it's weird.
      aim_x = my_x; aim_y = my_y; 
    }
    }

    // Watch big brother's moves
    if(brother_id > 0){
      moves_saw[moves_write][0] = bots[brother_idx][1];
      moves_saw[moves_write][1] = bots[brother_idx][2];           
      moves_write ++;
      if (moves_write===moves_to_follow){
        moves_write = 0; // Wrap counter for circular buffer

        // Have I seen enough of them?
        if(saw_all_brothers_moves === 0){
          saw_all_brothers_moves = 1;          
        }
      }            
    }

    // Save updated variables
    localStorage.LB_moves_saw = encode_moves(moves_saw); 
    localStorage.LB_moves_write = moves_write;// Save it
    localStorage.LB_brother_id = brother_id;// Save it            
    localStorage.LB_brother_loop_count = brother_loop_count; // Save it       l     
    localStorage.LB_saw_all_brothers_moves = saw_all_brothers_moves;
    localStorage.LB_last_num_of_bots = last_num_of_bots;
    localStorage.LB_number_of_brothers_followed = number_of_brothers_followed;      

    // Finish function          
    if (brother_id <= 0){ // If not following anybody, move randomly
      return ["up","down","left","right"][Math.random()*4|0];
    }
    else{
      // Following a big brother!
      return toPos([aim_x, aim_y]);
    }

    // Some functions to ease the load
    function toPos([x,y]) {
      var dx = x - my_x;
      var dy = y - my_y;
      if(Math.abs(dx)>Math.abs(dy)){
        if (x > my_x) return "right";
        if (x < my_x) return "left";
        if (y < my_y) return "up";
        if (y > my_y) return "down";
      }
      else{              
        if (y < my_y) return "up";
        if (y > my_y) return "down";
        if (x > my_x) return "right";
        if (x < my_x) return "left";
      }
      return 'wait';
    }
    function PosAt(dir){
      if (dir === 'left') return [-1,0];
      if (dir === 'right') return [1, 0];
      if (dir === 'up') return [0, -1];
      if (dir === 'down') return [0, 1];
      return [0,0];    
      }
      function decode_moves(moves_str){            
      var moves_array = [];
      var moves_strs = moves_str.split(';');
      for (var i = 0; i<moves_to_follow; i++){         
        var splot = moves_strs[i].split(',');              
        moves_array[i] = [];
        moves_array[i][0] = parseInt(splot[0]);
        moves_array[i][1] = parseInt(splot[1]);
      }
    return moves_array;
    }
      function encode_moves(moves_array){
      var moves_str = "";
      for (var i = 0; i < moves_array.length; i++){              
        moves_str += moves_array[i][0] + ',' + moves_array[i][1];
        if (i < moves_array.length - 1){moves_str += ';';}              
      }
      return moves_str;
    }
    function makeArray(w, h, val) {
      var arr = [];
      for(i = 0; i < w; i++) {
        arr[i] = [];
        for(j = 0; j < h; j++) {
          arr[i][j] = 0;
        }
      }
      return arr;
    }
}

이 작은 봇은 남동생과 같습니다. 그것은 것입니다 당신에 래치 하고 거울 모든 단계를 . 당신의 남동생이 그의 큰 부츠로 당신의 발자국으로 뛰어들 것입니다. 그러나 그는 당신을 귀찮게 할 수있는 경우에만 당신을 따를 것입니다. 그는 결국 당신의 남동생입니다.

본질적으로 그것은 큰 형으로서 영향을 줄 수있는 최고 순위 봇을 선택하고 끊임없이 따라갑니다. 처음에는 그대로 직진하지만 큰 형의 움직임을 기억하기 시작하고 단계별로 따라갑니다 (원형 버퍼 마법 사용).

이것은 이번 SE에서 처음으로 제출 한 것이며 Javascript에서 처음으로 프로그래밍 한 것입니다. 따라서 모든 조언 / 피드백은 대단히 감사하겠습니다!

나는 LittleBrother너희들을 너무 귀찮게하지 않기를 바란다 .)

참고 : 함수 자체는 상당히 크지 만 매우 빠릅니다. 시간이 많이 걸리지 않습니다.

2018 년 8 월 22 일 업데이트 20:42 :

  • 빅 브라더 선택은 실제로 작동하도록 개선되었습니다. 이제는 색이 다른 사람을 쫓을 확률이 30 %에 불과합니다. 나머지 시간은 색상을 덮어 쓰려고 시도합니다. 그것은 아무것도 할 수없는 사람을 따르지 않을 것입니다.
  • 게임 시간이 지남에 따라 더 오랫동안 연결됩니다.
  • 변수 초기화를 위해 라운드 번호에 의존하지 마십시오.

2018 년 8 월 23 일 업데이트 11:19 :

  • 동생이 큰 동생을 찾으려고 노력해도 더 이상 많은 것을 얻지 못합니다.
  • 그는 요한을 무서워하기 때문에 이제는 더 가벼워졌습니다. 그래서 그는 절대로 짐이나 다른 동생을 밟지 않을 것입니다.

2018 년 8 월 25 일 업데이트 19:26 ( Niceness Update) :

  • Lil'Bro는 더 이상 지도자를 사로 잡지 않습니다. 그것은 따라야 할 임의의 오빠 만 찾습니다. 그러나 우리 모두가 그러 하듯이 때때로 그는 질투합니다. 그래서 그는 40 %의 확률로 최고의 형제를 따르기로했습니다.
  • 그는 또한 좋은 사람들이 밟히는 것을 알고 있습니다. 그래서 그는 이제 멀리서 봇을 따라갑니다. 일반적으로 약 25 개의 셀이 뒤에 있지만 여전히 모든 단계를 따릅니다 . 이것이 요한을 평화롭게 해 줄 것입니다.
  • 이 업데이트의 큰 목적은 무한 루프를 제한하는 것입니다. 탑 봇은 일반적으로 가장 가까운 사각형을 사용하는데, 이는 보통 Lil'Bro가 방금 가져온 것입니다. 따라서 Lil'Bro는 무작위 봇을 따라 가기 때문에 쉽게 알아 차리지 않기를 바랍니다. 또한, 지금까지 뒤를 쫓아서 Lil'Bro가 방금 덮어 쓴 것보다 일반적으로 대상에 대한 더 좋은 미끼가 있습니다.

업데이트 2018 년 8 월 27 일 21:32 (일부 새로운 스마트) :

  • 다른 플레이어가 죽으면 서 똑똑해졌습니다. 더 이상 재설정하지 않고 LittleBrother가 이제 실제로 관련이 있는지 확인합니다.
  • LittleBrother는 동생과의 싸움 (무한 루프)에 들어가면 그의 점수가 심각하게 억제된다는 것을 깨달았습니다. 그는 화가 나고, 이런 일이 발생하면 다른 사람을 찾습니다.

이 게임에서 오류가 발생했습니다 : moves_str is undefined (line 187 column 13). 그 줄 번호가 정확한지 확실하지는 않지만 아마도 알아낼 수 있습니다 : p
tomsmeding

@dzaima에게 감사합니다, 나는 오래된 컨트롤러로 놀고 있었고 업데이트하는 것을 잊었습니다. 지금 업데이트되었습니다.
Hein Wessels

1
@ Night2 버기 남동생 죄송합니다. 나는 몇 시간 안에 그를 고칠 것이다. 그것은 내가 실행 한 테스트에서 작동하지만 오랫동안 실행하지는 않습니다. 알려 줘서 고마워! :)
Hein Wessels

1
@HeinWessels 실수로 유감스럽게 생각합니다. 컨트롤러가 버그가 있고 1 대신 2 번부터 게임을 시작한 것 같습니다.
Night2

2

가까운 광경

function(myself, grid, bots, gameInfo) {
    let ret = [];
    let col = myself[0];
    let myX = myself[1];
    let myY = myself[2];

    if(grid[myX][myY] != col){
        return "wait";
    }
    if(myX != 0 && grid[myX-1][myY] != col){
        ret.push("up")
    }
    if(myX != grid.length-1 && grid[myX+1][myY] != col){
        ret.push("down")
    }
    if(myY != 0 && grid[myX][myY-1] != col){
        ret.push("left")
    }
    if(myY != grid[0].length && grid[myX][myY+1] != col){
        ret.push("right")
    }
    return ret[Math.random() * ret.length|0]
}

적의 색상으로 인접한 필드로 이동하려고 시도하고, 그렇지 않으면 무작위로 이동합니다.
항상 올바른 색상이 될 때까지 현재 필드를 페인팅하는 것을 선호합니다.


2

복서

function(myself, grid, bots, gameInfo) {
    let val = gameInfo[0] % 16;
    if(val < 3){
        return "right";
    }else if(val < 6){
        return "up";
    }else if(val < 9){
        return "left";
    }else if(val < 12){
        return "down";
    }else if(val < 14){
        return ["up","down","left","right"][Math.random() *4 |0];
    }else{
        let xdist = myself[1];
        let ydist = myself[2];
        let xfardist = grid.length - 1 - myself[1];
        let yfardist = grid.length - 1 - myself[2];
        if(gameInfo[0] % 400 < 200){
            if (xdist < ydist && xdist < xfardist && xdist < yfardist){
                return "right";
            }else if (ydist < xfardist && ydist < yfardist){
                return "down";
            }else if (xfardist < yfardist){
                return "left";
            }else{
                return "up";
            }
        }else{
            if (xdist > ydist && xdist > xfardist && xdist > yfardist){
                return "right";
            }else if (ydist > xfardist && ydist > yfardist){
                return "up";
            }else if (xfardist > yfardist){
                return "left";
            }else{
                return "down";
            }
        }
    }
}

꽤 직설적 인; 작은 4x4 상자에서 반복적으로 움직이며, 매 루프마다 임의의 단계를 거친 다음 몇 단계를 중앙에서 더 가깝거나 더 이동시킵니다. 사냥꾼은 작은 구역에서 움직이므로 꽤 취약합니다. 주요 목표는 한 영역을 제어하는 ​​것입니다.


2

NearSightedGrid의 논리 (그러한 봇을 만들고 싶었습니다)로 시작하여 다음 전략을 수립했습니다.

  • 오른쪽 하단에서 왼쪽 상단으로 이동합니다 (즉, 먼저 왼쪽으로 이동하여 맨 아래 행, 오른쪽으로 이동하여 맨 아래 행 등). 따라서 오른쪽 하단에서 시작하면 이점이 있습니다.
  • 그러나 마지막 이동의 반대 이동은 절대하지 않습니다 (이전 버전에서는 멈출 수 없음).
  • 또한 해당 위치의 현재 색상에 따라 다른 라운드를 유지해야하는 위치로 이동하는 것을 선호하지 않습니다. 가능하면 그러한 이동은 실행되지 않습니다. 그러나 대안이 무작위로 이동하면 수행됩니다.
  • 위에서 움직임을 찾을 수 없으면 무작위로 움직입니다. 일단 무작위로 움직여서 다시 무작위로 움직이려고하면 같은 방향으로 움직여 패배 할 수없는 큰 색상의 필드에 걸리지 않습니다. 경계를 벗어나면 다시 무작위로 이동합니다.

참고 : 개발자가 아니므로 코드가 끔찍합니다. 그러나 전략적으로 그렇게 나쁘지는 않습니다.

function (myself, grid, bots, gameInfo) {
    var col = myself[0];
    var myX = myself[1];
    var myY = myself[2];

    var notPreferred = [];

    var move = "wait";
    if(grid[myX][myY] != col){
        var go = checkMove(move, grid[myX][myY]);
        if(go) {
            if(go == "notPreferred") {
                //notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "left";
    if(myX > 0 && grid[myX-1][myY] != col){
        var go = checkMove(move, grid[myX-1][myY]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "right";
    if(myX < grid.length-1 && grid[myX+1][myY] != col){
        var go = checkMove(move, grid[myX+1][myY]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "up";
    if(myY > 0 && grid[myX][myY-1] != col){
        var go = checkMove(move, grid[myX][myY-1]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    move = "down";
    if(myY < grid[0].length && grid[myX][myY+1] != col){
        var go = checkMove(move, grid[myX][myY+1]);
        if(go) {
            if(go == "notPreferred") {
                notPreferred.push(move);
            } else {
                nextMove(move, "standard");
                return move;
            }
        }
    }

    if(notPreferred[0]) {
        nextMove(notPreferred[0], "notPreferred");
        return notPreferred[0];
    }

    var random = randomMove();
    nextMove(random, "random");
    return random;

    function checkMove(move, currentColor) {
        var go = false;
        if(currentColor === 0) {
            go = true;
        } else {
            var z = [col, 0, currentColor][Math.abs(col - currentColor)%3]
            go = z == 0 ? "notPreferred" : z != currentColor;
        }

        if(go) {
            if(localStorage.jacksNextMoveShouldNotBe && localStorage.jacksNextMoveShouldNotBe == move) {
                return false;
            }
        }
        return go;
    }

    function randomMove() {
        if(localStorage.jacksPreviousMoveWasRandom) {
            var repeatMove = localStorage.jacksPreviousMoveWasRandom;
            if(repeatMove == "left" && myX > 0 || repeatMove == "right" && myX < grid.length-1 || repeatMove == "up" && myY > 0 || repeatMove == "down" && myY < grid.length-1){
                return repeatMove;
            }
        }

        var random = ["up","down","left","right"][Math.random() *4|0];
        localStorage.jacksPreviousMoveWasRandom = random;
        return random;
    }

    function nextMove(move, message) {
        var oppositeMove = "wait";
        if(move == "left") {
            oppositeMove = "right";
        } else if(move == "right") {
            oppositeMove = "left";
        } else if(move == "up") {
            oppositeMove = "down";
        } else if(move == "down") {
            oppositeMove = "up";
        }
        localStorage.jacksNextMoveShouldNotBe = oppositeMove;
        if(message != "random") {
            localStorage.jacksPreviousMoveWasRandom = "";
        }
    }
}

2

영리한 이름

function(myself, grid, bots, gameInfo) {
    // Do a quick dance for identification.
    let round = gameInfo[0];
    if (round < 5) {
        return ["down", "right", "up", "left"][round % 4];
    }

    // Parse the arguments.
    let [myId, myX, myY] = myself;

    // Check each square to see if it's a good target.
    let targetX, targetY, targetDist = Infinity;
    let numAtDist;
    for (let x = 0; x < grid.length; x++) {
        for (let y = 0; y < grid.length; y++) {
            // Whoever's fighting for this square can have it.
            if (x === myX && y === myY) { continue; }
            // We don't care about our own squares.
            if (grid[x][y] === myId) { continue; }

            // Only squares that we can recolor are useful.
            if (grid[x][y] === 0 || Math.abs(grid[x][y] - myId) % 3 !== 2) {
                // Avoid squares that take effort.
                if (Math.abs(grid[x][y] - myId) % 3 === 1 && Math.random() < 0.5) { continue; }

                // If this is the closest we've seen, target it.
                let dist = Math.abs(myX - x) + Math.abs(myY - y);
                if (dist < targetDist) {
                    targetX = x;
                    targetY = y;
                    targetDist = dist;
                    numAtDist = 1;
                // If it's tied for the closest, sometimes target it.
                } else if (dist === targetDist) {
                    numAtDist++;
                    if (Math.floor(numAtDist * Math.random()) === 0) {
                        targetX = x;
                        targetY = y;
                    }
                }
            }
        }
    }

    // Move toward the target.
    if (targetX < myX) { return "left"; }
    if (targetX > myX) { return "right"; }
    if (targetY < myY) { return "up"; }
    if (targetY > myY) { return "down"; }
    return "wait";
}

최선을 다하는 두 가지 방법이 있습니다. 이것은 이전의 접근법을 취합니다. 색칠 할 수있는 가장 가까운 사각형 중 하나를 향해 탐욕스럽게 움직입니다.


2

니 캡퍼

function(myself, grid, bots, gameInfo) {
    let [myId, myX, myY] = myself;
    let round = gameInfo[0];

    // Find our friend.
    if (round === 1) {
        localStorage.kneecapper_possibleAllies = JSON.stringify(bots.map(bot => bot[0]));
    }
    let possibleAllies = JSON.parse(localStorage.kneecapper_possibleAllies);

    // Players who don't do the identifying dance aren't allies.
    if (1 < round && round <= 5) {
        let previousPositions = JSON.parse(localStorage.kneecapper_previousPositions);
        let expectedDx = [-1, 0, 1, 0];
        let expectedDy = [0, 1, 0, -1];
        let notAllies = [];
        for (let i = 0; i < possibleAllies.length; i++) {
            let j = possibleAllies[i] - 1;
            let dx = bots[j][1] - previousPositions[j][1];
            let dy = bots[j][2] - previousPositions[j][2];
            if (dx === 0 && dy === 0) {
                if (expectedDx === -1 && bots[j][1] !== 0) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDx === 1 && bots[j][1] !== grid.length - 1) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDy === -1 && bots[j][2] !== 0) {
                    notAllies.push(possibleAllies[i]);
                } else if (expectedDy === 1 && bots[j][2] !== grid.length - 1) {
                    notAllies.push(possibleAllies[i]);
                }
            }
            if (dx !== expectedDx[round % 4] || dy !== expectedDy[round % 4]) {
                notAllies.push(possibleAllies[i]);
            }
        }
        possibleAllies = possibleAllies.filter(id => notAllies.indexOf(id) < 0);
        localStorage.kneecapper_possibleAllies = JSON.stringify(possibleAllies);
    }
    localStorage.kneecapper_previousPositions = JSON.stringify(bots);

    let partner = possibleAllies[0];

    // Figure out who's doing well.
    let targets = bots.map(bot => bot[0]).filter(id => (id !== myId) && (id !== partner) && (Math.abs(id - myId) % 3 !== 2));
    let flatGrid = [].concat.apply([], grid);
    targets = targets.sort((a, b) => flatGrid.reduce((n, val) => n + (val === a) - (val === b), 0));

    let targetX, targetY;
    let targetScore = 0;
    for (let x = 0; x < grid.length; x++) {
        for (let y = 0; y < grid.length; y++) {
            let dist = Math.abs(x - myX) + Math.abs(y - myY);
            let scariness = targets.indexOf(grid[x][y]) + 1;
            if (scariness === 0) { continue; }

            // Find a successful opponent who's not too far away.
            let score = scariness ** 1.5 / (dist + 1);
            if (score > targetScore) {
                targetX = x;
                targetY = y;
                targetScore = score;
            }
        }
    }

    // Move toward the target.
    if (targetX < myX) { return "left"; }
    if (targetX > myX) { return "right"; }
    if (targetY < myY) { return "up"; }
    if (targetY > myY) { return "down"; }
    return "wait";
}

최선을 다하는 두 가지 방법이 있습니다. 이것은 후자의 접근법을 취합니다. 점수가 높은 플레이어가 소유 한 근처 광장을 찾아 제거합니다.


2

퍼지 가이

function(myself, grid, bots, gameInfo) {
    var i,j,x,y = 0;
    this.answerToLifeTheUniverseAndEverything = 42;
    this.round = gameInfo[0];
    this.coloringStruggle = [];
    this.myColor = myself[0];
    this.botCount = bots.length;
    this.sizeOfGrid = grid.length;
    this.storageName = 'm53kp1of6igcnpsq';
    this.storageName2 = 'ji38df8djsdf8zf0a';
    this.distances = {up: 0, right: 0, down: 0, left: 0};
    this.foodSmell = {up: 0, right: 0, down: 0, left: 0};
    this.botSmell = {up: 0, right: 0, down: 0, left: 0};
    this.botPredictedSmell = {up: 0, right: 0, down: 0, left: 0};
    this.directionPoints = {up: 0, right: 0, down: 0, left: 0};

    this.blockedMoves = function() {
        var backwards = 'wait', prevDirection, blocked = [];
        if(myself[1] == 0) {
            blocked.push('left');
        }
        if(myself[2] == 0) {
            blocked.push('up');
        }
        if(myself[1] == this.sizeOfGrid - 1) {
            blocked.push('right');
        }
        if(myself[2] == this.sizeOfGrid - 1) {
            blocked.push('down');
        }

        if (this.round > 1) {
            prevDirection = JSON.parse(localStorage.getItem(this.storageName2));
            backwards = (prevDirection == 'up' ? 'down' : backwards);
            backwards = (prevDirection == 'down' ? 'up' : backwards);
            backwards = (prevDirection == 'left' ? 'right' : backwards);
            backwards = (prevDirection == 'right' ? 'left' : backwards);
            blocked.push(backwards);
        }

        return blocked;
    }

    this.getDistance = function(x1,y1) {
        return [Math.abs(myself[1]-x1), Math.abs(myself[2]-y1)];
    }

    this.finddeliciousDirection = function() {
        for (x = 0; x < this.sizeOfGrid; x++) {
            for (y = 0; y < this.sizeOfGrid; y++) {
                if (y < myself[2]) {
                    this.foodSmell.up+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (y > myself[2]) {
                    this.foodSmell.down+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (x < myself[1]) {
                    this.foodSmell.left+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
                if (x > myself[1]) {
                    this.foodSmell.right+= ((1.9 - this.coloringStruggle[x][y]) / this.getDistance(x, y).reduce((a, b) => a + b, 0)) / 4;
                }
            }
        }
    }

    this.predictFuture = function(x0,y0,x1,y1) {
        var xMovement = x1-x0;
        var yMovement = y1-y0;
        var xAfter2Turns = x1 + xMovement * 2;
        var yAfter2Turns = y1 + yMovement * 2;
        var hitsWall = [1, 1];

        if (xMovement == 1) {
            hitsWall = [2, 1]
        } else if (xMovement == -1) {
            hitsWall = [0, 1]
        } else if (yMovement == 1) {
            hitsWall = [1, 2]
        } else if (yMovement == -1) {
            hitsWall = [1, 0]
        } else {
            hitsWall = [1, 1]
        }

        if (xAfter2Turns < 0) {
            xAfter2Turns = 0;
        } else if (xAfter2Turns >= this.sizeOfGrid) {
            xAfter2Turns = this.sizeOfGrid -1;
        }

        if (yAfter2Turns < 0) {
            yAfter2Turns = 0;
        } else if (yAfter2Turns >= this.sizeOfGrid) {
            yAfter2Turns = this.sizeOfGrid -1;
        }

        return [xAfter2Turns, yAfter2Turns, hitsWall];
    }

    this.findCloseBots = function() {
        var prevPositions;
        var currentBot;
        var future;
        if (this.round > 1) {
            prevPositions = JSON.parse(localStorage.getItem(this.storageName));
        }

        for (i = 0; i < bots.length; i++) {
            if (bots[i][2] < myself[2]) {
                this.botSmell.up+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][2] > myself[2]) {
                this.botSmell.down+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][1] < myself[1]) {
                this.botSmell.left+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }
            if (bots[i][1] > myself[1]) {
                this.botSmell.right+= 3 / (this.getDistance(bots[i][1], bots[i][2]).reduce((a, b) => a + b, 0));
            }

            if (this.round > 1) {
                currentBot = prevPositions.find(function(element) {
                    return element[0] == bots[i][0];
                });

                if (currentBot[0] != this.myColor) {
                    future = this.predictFuture(currentBot[1], currentBot[2], bots[i][1], bots[i][2]);
                    if (future[1] < myself[2]) {
                        this.botPredictedSmell.up+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[1] > myself[2]) {
                        this.botPredictedSmell.down+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[0] < myself[1]) {
                        this.botPredictedSmell.left+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }
                    if (future[0] > myself[1]) {
                        this.botPredictedSmell.right+= (3.14159 / 3 * ([Math.abs(this.myColor - bots[i][0])%3] + 1)) / (this.getDistance(future[0], future[1]).reduce((a, b) => a + b, 0));
                    }

                    if (future[2][0] == 0) {
                        this.botPredictedSmell.left+=0.314159;
                    }
                    if (future[2][0] == 2) {
                        this.botPredictedSmell.right+=0.314159;
                    }
                    if (future[2][1] == 0) {
                        this.botPredictedSmell.up+=0.314159;
                    }
                    if (future[2][1] == 2) {
                        this.botPredictedSmell.down+=0.314159;
                    }
                }
            }
        }

        localStorage.setItem(this.storageName, JSON.stringify(bots));
    }


    this.calculateColoringStruggle = function() {
        for (x = 0; x < this.sizeOfGrid; x++) {
            var yAxis = [];
            for (y = 0; y < this.sizeOfGrid; y++) {
                if (this.myColor == grid[x][y]) {
                    yAxis[y] = 2;
                } else if (grid[x][y] == 0) {
                    yAxis[y] = 0;
                }
                else {
                    yAxis[y] = [0, 1, 2][Math.abs(this.myColor - grid[x][y])%3];
                }
            }
            this.coloringStruggle.push(yAxis);
        }
    }

    this.getEmptySlotsInDirection = function() {

        for (x = (myself[1] + 1); x < this.sizeOfGrid; x++) {
            if (grid[x][myself[2]] == 0) {
                this.distances.right = (x-myself[1]) * 1.23456789;
            } else {
                if (x-myself[1]-1 == 0) {
                    this.distances.right = 0;
                }
                break;
            }
        }
        for (y = (myself[2] + 1); y < this.sizeOfGrid; y++) {
            if (grid[myself[1]][y] == 0) {
                this.distances.down = (y-myself[2]) * 1.23456789;
            } else {
                if (y-myself[2]-1 == 0) {
                    this.distances.down = 0;
                }
                break;
            }
        }
        for (x = (myself[1] - 1); x > -1; x--) {
            if (grid[x][myself[2]] == 0) {
                this.distances.left = (myself[1]-x) * 1.23456789;
            } else {
                if (myself[1]-x-1 == 0) {
                    this.distances.left = 0;
                }
                break;
            }
        }
        for (y = (myself[2] - 1); y > -1; y--) {
            if (grid[myself[1]][y] == 0) {
                this.distances.up = (myself[2]-y) * 1.23456789;
            } else {
                if (myself[2]-y-1 == 0) {
                    this.distances.up = 0;
                }
                break;
            }
        }
    }
    this.getBestDistance = function() {
        var max = -999, maxDir = 'up';
        for (var property in this.distances) {
            if (this.distances.hasOwnProperty(property)) {
                this.directionPoints[property] = (this.distances[property] + this.foodSmell[property] - this.botSmell[property] - this.botPredictedSmell[property]);
                if (this.directionPoints[property] > max && this.blockedMoves().indexOf(property) == -1) {
                    max = this.directionPoints[property];
                    maxDir = property;
                }
            }
        }

        return maxDir;
    };

    this.findCloseBots();
    this.calculateColoringStruggle();
    this.getEmptySlotsInDirection();
    this.finddeliciousDirection();

    var answer = this.getBestDistance();
    localStorage.setItem(this.storageName2, JSON.stringify(answer));

    return(answer);
}

이것은 내가 처음 여기에 참여한 것이지만, 나는이 KoTH 아이디어가 정말로 마음에 들기 때문에 마지막이 아니라고 생각합니다.

  1. 각 방향으로 얼마나 많은 음식과 거리가 있는지 계산
  2. 봇이 각 방향에 얼마나 많은지 얼마나 가까운지를 계산합니다
  3. 좀 더 "매우 유용한"데이터를 계산합니다
  4. 업데이트 -다음 차례에 이전 셀로 이동하지 않습니다

결국 퍼지 로직을 사용하여 각 방향에 가중치를 부여하고 최고의 가치를 가진 방향을 선택합니다

나중에 봇을 만들려고했기 때문에 나중에 처음부터 새로운 봇을 만들 것이라고 생각합니다.


2

클레임

        function (myself, grid, bots, gameInfo) {
            let my_c = myself[0], my_x = myself[1], my_y = myself[2], size = grid.length, roundnum = gameInfo[0];

            let getDistance = function (x1, y1, x2, y2) {
                return (Math.abs(x1 - x2) + Math.abs(y1 - y2));
            };

            let getColorValue = function (color) {
                if (color === 0) {
                    return my_c;
                }
                return [my_c, 0, color][Math.abs(my_c - color) % 3];
            };

            if (!localStorage.claim) {
                let lastMove = "";
                localStorage.claim = JSON.stringify([lastMove]);
            }
            offsets = JSON.parse(localStorage.claim);
            lastMove = offsets[0];

            let targets = [];
            let distance = 999999;
            let lowestDistance = 999999;
            for (let grid_x = 0; grid_x < size; grid_x++)
            {
                for (let grid_y = 0; grid_y < size; grid_y++)
                {
                    if (grid[grid_x][grid_y] !== my_c && getColorValue(grid[grid_x][grid_y]) === my_c)
                    {
                        distance = getDistance(my_x, my_y, grid_x, grid_y);
                        targets[distance] = [grid_x, grid_y];

                        if (distance < lowestDistance) {
                            lowestDistance = distance;
                        }
                    }
                }
            }
            let target = targets[lowestDistance];

            //Nothing directly paintable available, search for erasable
            if (target === undefined)
            {
                targets = [];
                distance = 999999;
                lowestDistance = 999999;
                for (let grid_x = 0; grid_x < size; grid_x++)
                {
                    for (let grid_y = 0; grid_y < size; grid_y++)
                    {
                        if (grid[grid_x][grid_y] !== my_c && getColorValue(grid[grid_x][grid_y]) !== grid[grid_x][grid_y])
                        {
                            distance = getDistance(my_x, my_y, grid_x, grid_y);
                            targets[distance] = [grid_x, grid_y];

                            if (distance < lowestDistance) {
                                lowestDistance = distance;
                            }
                        }
                    }
                }
            }
            target = targets[lowestDistance];

            let move = "";
            if (target === undefined) {
                move = 'wait';
            } else if (target[0] > my_x) {
                move = 'right';
            } else if (target[0] < my_x) {
                move = 'left';
            } else if (target[1] > my_y) {
                move = 'down';
            } else if (target[1] < my_y) {
                move = 'up';
            } else {
                move = "wait";
            }

            if (move === "wait" && lastMove === "wait") {
                move = "left";
            }

            localStorage.claim = JSON.stringify([move]);

            return move;
        }

2

지우개

이 봇은 가능한 많은 보드를 지우려고합니다. 무한 루프에 빠지지 않도록 셀을 만든 봇과 매우 가까운 셀은 무시합니다.

우선 순위 :

  1. 현재 셀을 지울 수 있으면 기다립니다
  2. 지울 수있는 영역의 가장자리를 시계 반대 방향으로 움직 입니다 (지운 경계선이 지워지지 않도록 변경됨)
  3. 가장 가까운 지울 수있는 셀로 이동
  4. 왼쪽으로 이동
function([id,x,y],grid,bots){
    function manhattan_search(x,y,board_size,callback){
        var dest_x,dest_y;
        try{
            for(var dist=1;dist<grid.length*2;dist++){
                check(0, dist); //x+
                check(0,-dist); //x-
                check( dist,0); //y+
                check(-dist,0); //y-
                for(var i=1;i<dist;i++){
                    check( i,  dist-i ); //++
                    check(-i,  dist-i ); //-+
                    check( i,-(dist-i)); //+-
                    check(-i,-(dist-i)); //--
                }
            }
            return undefined;
        }catch(e){
            //console.log(e);
            return [dest_x,dest_y];
        }
        function check(vx,vy){
            dest_x=x+vx;
            dest_y=y+vy;
            if(callback(dest_x,dest_y))
                throw undefined;
        }
    }
    function can_erase(x,y){
        if(grid[x]!==undefined && grid[x][y]!==undefined && grid[x][y]!==0 && Math.abs(id-grid[x][y])%3===1){
            for(var i=0;i<bots.length;i++)
                if(bots[i][0]===grid[x][y])
                    break;
            if(bots[i])
                return Math.abs(x-bots[i][1])+Math.abs(y-bots[i][2])>3;
        }
    }

    if(can_erase(x,y))
        return "wait";
    var name=["up","right","down","left"];
    var dx=[0,1,0,-1],dy=[-1,0,1,0];
    dir=this.last_dir-1&3;
    for(var i=1;i<=4;i++){
        if(can_erase(x+dx[dir],y+dy[dir]))
            return name[this.last_dir=dir];
        dir=dir+1&3;
    }
    var dest=manhattan_search(x,y,grid.length,can_erase);
    if(dest){
        return name[this.last_dir=[
            [0,0,1],
            [3,3,1],
            [3,2,2]
        ][Math.sign(dest[1]-y)+1][Math.sign(dest[0]-x)+1]];
    }
    return "left";
}

코드 블록 앞에 빈 주석을 넣을 수 있습니다. 더 나은 방법은 언어 사양 설명을 코드 블록 앞에 두십시오.
Neil

봇이 제거 될 때 마지막 셀이 제거되었다고 생각했습니다. 지금 작동합니다.
12Me21

2

먼처

function(myself, grid, bots, gameInfo) {
    const W = grid.length, H = grid[0].length;
    const rounds_left = gameInfo[1] - gameInfo[0];
    const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]];

    function rank_square([x, y]) {
        if (grid[x][y] == myself[0]) return 3;
        if (grid[x][y] == 0) return 1;
        var value = Math.abs(grid[x][y] - myself[0]) % 3;
        if (value) value += 1;
        return value;
    }


    function select_long_paths() {
        const ranked = directions.map(to_coords).filter(legal).map((coords)=>{
            return calculate_min_score(4, [coords]);
        });
        const min = Math.min(...ranked);
        const result = directions.filter((dir, index)=>{return ranked[index] == min;});
        return result;
    }

    function new_coords([x, y], path) {
        const last_coords = path[path.length - 1];
        return [x + last_coords[0], y + last_coords[1]]; 
    }

    function calculate_min_score(num_steps, path_so_far) {
        if (!num_steps) return 0;
            var scores = directions.map((dir)=>{
            return new_coords(dir, path_so_far);
        }).filter(legal).filter((coords)=>{
            var i;
            for (i = 0; i < path_so_far.length; i++) {
                if (path_so_far[i] == coords) return false;
            }
            return true;
        }).map((coords)=>{
            var new_path = path_so_far.slice();
            new_path.push(coords);
            return rank_square(coords) + calculate_min_score(num_steps - 1, new_path);
        });
        return Math.min(...scores);
    }

    function to_coords([x, y]) {
        return [x + myself[1], y + myself[2]];
    }

    function legal([x, y]) {
        return 0 <= x && x < W && 0 <= y && y < H;
    }

    function filter_by_strength(dirs) {
        const ranked = dirs.map(to_coords).filter(legal).map(rank_square);
        const min = Math.min(...ranked);
        const result = dirs.filter((dir, index)=>{return ranked[index] == min;});
        return result;
    }

    function convert([x, y]) {
        x += myself[1];
        y += myself[2];

        if (x > myself[1]) return "right";
        if (x < myself[1]) return "left";
        if (y < myself[2]) return "up";
        return "down";
    }

    const options = select_long_paths();
    const choices = filter_by_strength(options);

    return convert(choices[Math.random() * choices.length |0]);
}

Muncher는 내가 실행 한 최근 게임에서 오류를 던졌습니다. 그리드를 추적하는 데 도움이된다면 그리드의 가장자리 근처에서 발생하는 것처럼 보였습니다.
조.

1
나는군요 TypeError: (destructured parameter) is undefinedconvert(아마 다른 모서리도 함께 발생) Muncher를 바닥 가장자리에있을 때. 선택 목록이 비어 있다고 생각합니다.
12Me21

1
을 받고 TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined있습니다.
kenorb

if(choices.length)앞에 추가하여 문제를 해결할 수 있어야 합니다 return.
12Me21

1
이 봇이 23:00 UTC + 1에 의해 수정되지 않으면, 경쟁에서 경쟁 할 수 없습니다
Beta Decay

2

오버 오버 금지

function(myself, grid, bots, gameInfo) {
    this.setupDone = false; if(this.setupDone == false) {
    var c = myself[0];
    var x = myself[1];
    var y = myself[2];
    var n = grid.length;

    var dirs = ["left", "up", "down", "right"]
    for(var _ = 0; _ < 4; _++) {
     var dir = dirs.splice(Math.random() * dirs.length | 0, 1);
     if(dir == "left" && x != 0 && grid[x-1][y] == 0) {
      return "left";
     }
     if(dir == "right" && x != n - 1&& grid[x+1][y] == 0) {
      return "right";
     }
     if(dir == "up" && y != 0 && grid[x][y-1] == 0) {
      return "up";
     }
     if(dir == "down" && y != n - 1 && grid[x][y+1] == 0) {
      return "down";
     }
     if(dir == "left" && x != 0 && grid[x-1][y] != c) {
      return "left";
     }
     if(dir == "right" && x != n - 1 && grid[x+1][y] != c) {
      return "right";
     }
     if(dir == "up" && y != 0 && grid[x][y-1] != c) {
      return "up";
     }
     if(dir == "down" && y != n - 1 && grid[x][y+1] != c) {
      return "down";
     }
    }
    dirs = [];
    if(x != 0) dirs[dirs.length] = "left";
    if(x != n - 1) dirs[dirs.length] = "right";
    if(y != 0) dirs[dirs.length] = "up";
    if(y != n - 1) dirs[dirs.length] = "down";
    return dirs[Math.random() * dirs.length | 0];
} }

다른 옵션이 "대기"가 아닌 한 자체 색상으로 페인트하지 않으므로 "No Do Overs"라는 이름이 지정됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.