KOTH: Todos aman las fichas

24

En este juego, dos jugadores compiten para comer la mayor cantidad de puntos en fichas, ¡pero hay un giro! ¡Comer varias fichas en una fila del mismo color ofrece una bonificación cada vez mayor, pero ten cuidado, o tu oponente frustrará tus planes al comer las fichas que quieras antes de que puedas!

Reglas:

  • 1 contra 1
  • n por n tablero (tamaño aleatorio entre 5x5 y 15x15)
  • Tú y tu oponente aparecerán en la misma celda aleatoria
  • En todo el tablero se generarán números aleatoriamente en algunas celdas con un valor de 1-3
  • Se generarán 2 * tokens (el ancho del tablero), pero puede haber anulaciones, por lo que puede haber menos por casualidad.
  • Cada número será uno de los 3 colores: rojo, verde o azul, en formato RGB hexadecimal
  • En cada ronda, el jugador 1 se mueve y el tablero se actualiza, luego el jugador 2 se mueve y el tablero se actualiza. Por lo tanto, cada jugador puede decir con eficacia qué movimiento realizó el jugador anterior en función del cambio en el estado del tablero. Esto continúa hasta que el juego termina, como se describe más adelante.
  • Tienes 6 acciones posibles para un turno: ARRIBA, DERECHA, ABAJO, IZQUIERDA, COMER y PASAR
  • Los 4 comandos de movimiento se explican por sí mismos y PUEDES pasar tu turno. Si devuelve un movimiento sin sentido, asumiremos que se refería al pase. Si intentas moverte fuera del borde del tablero, no te moverás. Los bordes no se envuelven.
  • EAT consume el número que estás actualmente en el mismo espacio que
  • Ganas tantos puntos como el número que consumes
  • Si comes 2 números seguidos del mismo color, obtienes +1
  • Si comes 3 números seguidos del mismo color, obtienes +2
  • Si comes m números en una fila del mismo color, obtienes + (m-1)
  • Estas bonificaciones se agregan acumulativamente, por lo que obtener m números seguidos lleva a m * (m-1) / 2 en bonificación total para el momento en que coma un color diferente.
  • Condiciones de finalización del juego:
    • Todos los números son consumidos
    • Los turnos de 4 * (el ancho del tablero) han transcurrido sin una alimentación efectiva (solo diciendo "COMER" sin ninguna ficha donde no se cuenta) por cualquiera de los jugadores (cualquier ficha se puede alcanzar en 2 * (el ancho) movimientos, por lo que este límite solo se superará si ambos jugadores no tienen en mente una ficha de objetivo único)
  • Tu IA debería tomar menos de un segundo para hacer un movimiento, de lo contrario se asumirá PASS como tu elección.

El torneo será round robin con una gran cantidad de rondas, digamos 100 o 1000. Se genera un tablero aleatorio, y cada par ordenado de jugadores diferentes se ejecuta en ese tablero. Una vez que se complete el torneo, clasificaremos a las personas según su puntaje total. Entonces, incluso si eres el jugador 2 para un juego, tu objetivo es obtener la mayor cantidad de puntos posible.

Envío de AI: el idioma que admite mi controlador es Javascript. Se permiten múltiples envíos. Todos envían un constructor para un objeto como este:

function (player1) {
    this.yourMove = function (b) {
        return "MOVE";
    }
}

La entrada player1es un booleano que dice si eres el jugador 1 o no. Su constructor debe tener la yourMovefunción, pero también puede tener cualquier número de funciones o valores adicionales. No defina ninguna variable global, simplemente colóquelas como variables en su objeto. Se creará una nueva versión de su objeto al comienzo de cada partida, y yourMovese le llamará, con el tablero actual como entrada, en cada uno de sus turnos, y debería devolver un movimiento válido.

b, la entrada a yourMove, es una copia de la placa actual, aquí están los constructores, con ejemplos de entrada, aunque no puede llamarlos usted mismo:

function token(color, points) {
    this.color = color; //"#FF0000"
    this.points = points; //5
}

function player(pos, score, colorBonus, lastColor) {
    this.pos = pos; //[5, 5]
    this.score = score; //9
    this.colorBonus = colorBonus; //i.e. 2 if you just ate 3 blue tokens in a row
                                  //0 if you just ate two different colors.
    this.lastColor = lastColor; //"#00FF00", is "#000000" at start
}

function board(player1, player2, tokens) {
    this.player1 = player1; //new player([5, 5], 9, 2, "#00FF00")
    this.player2 = player2; //new player([5, 5], 9, 2, "#00FF00")
    this.tokens = tokens; //[[new token("#0000FF", 5), false],
                      // [new token("#0000FF", 5), false]]
}

La matriz de tokens tiene "falso" para cualquier cuadrado vacío, y los tokens [a] [b] son ​​los tokens en x = a, y = b, numerados a partir de la esquina superior izquierda.

Controlador: aquí hay un enlace al controlador en GitHub. Es un archivo html que puede ejecutar para ver cómo funciona el juego y el round-robin, y viene con dos IA, una aleatoria que se mueve en una dirección aleatoria cada turno pero come fichas en su posición y un algoritmo ingenuo que va por la ficha más cercana que da más puntos. Agregaré cada IA ​​a medida que se envíe.

A continuación se muestra un fragmento que le permite ejecutar el controlador en la IA predeterminada. IA actuales:

  • KindaRandomAI
  • NaiveAI
  • MirrorBot
  • HungryBot

Melón Fricativo
fuente
12
Yay, un KOTH! Ha pasado una eternidad desde la última.
TheNumberOne
2
De acuerdo, me encanta un buen KOTH y esto parece ser una gran premisa. Soy un poco verde para js, ¿cómo persiste el estado del juego entre movimientos si no podemos guardar los resultados dentro del objeto del jugador?
DoctorHeckle
¿Se pasa el ancho de la placa a la función?
TheNumberOne
@BentNeeHumor Sí, la función que toma el player1booleano es el constructor de su IA, que tendrá una yourMovefunción que toma la placa actual como entrada, como b.
Melón fricativo
1
@DylanSp A veces no están permitidos debido a las posibilidades de colusión, pero en este caso, la colusión tendría beneficios mínimos, por lo que permitiré múltiples envíos.
Melón fricativo

Respuestas:

4

HungryBot

Utiliza un sistema de puntos para agregar peso al valor de buscar cada ficha. Utiliza una variedad de factores diferentes en su consideración y los reevalúa en cada turno para garantizar que siga la mejor estrategia.

function hungryBot(first) {
  // Set up "self"
  var self = this;

  // Determine player order
  this.player = -(first - 2);
  this.enemy = first + 1;

  // Action associative array
  this.actions = ['EAT', 'LEFT', 'RIGHT', 'UP', 'DOWN'];

  //Logic handler
  this.yourMove = function(board) {
    // Determine player object
    var player = board['player' + self.player];
    var enemy = board['player' + self.enemy];

    // Point value action grid
    var actions = [0, 0, 0, 0, 0]; // Associative with "this.actions"

    // Board dimensions
    var size = board.tokens.length;
    var maxDist = size * 2;

    // Colors remaining
    var colors = {
      '#FF0000': 0,
      '#00FF00': 0,
      '#0000FF': 0
    };

    // Averaged value weight
    var average = [0, 0];

    // Total points
    var points = 0;

    // Token holder
    var tokens = [];

    // Token parser
    for (var i = 0, x = 0, y = 0; i < size * size; i += 1, x = i % size, y = i / size | 0) {
      if (!board.tokens[x][y]) {
        continue;
      } else {
        var token = {};
        token.points = board.tokens[x][y].points;
        token.color = board.tokens[x][y].color;
        token.x = x - player.pos[0];
        token.y = y - player.pos[1];
        token.distX = Math.abs(token.x);
        token.distY = Math.abs(token.y);
        token.dist = token.distX + token.distY;
        token.distE = Math.abs(x - enemy.pos[0]) + Math.abs(y - enemy.pos[1]);
        token.value = -token.points - (player.colorBonus + 1) * (token.color == player.lastColor) * ((token.dist == 0) + 1) * 1.618 - (enemy.colorBonus + 1) * (token.color == enemy.lastColor);
        tokens.push(token);
        colors[token.color] += 1;
        points += token.points;
        average[0] += x * token.points;
        average[1] += y * token.points;
      }
    }

    // Determine actual average
    average[0] = average[0] / points | 0;
    average[1] = average[1] / points | 0;

    // Pick best token
    var best = 0;

    // Calculate point values of tokens
    for (i = 0; i < tokens.length; i++) {
      var token = tokens[i];
      // Add remaining numbers of tokens of color as factor
      token.value -= (colors[token.color] / tokens.length) * 1.618;
      // Subtract distance as a factor
      token.value += token.dist;
      // Add distance to average to value
      token.value += (Math.abs(average[0] - (token.x + player.pos[0])) + Math.abs(average[1] - (token.y + player.pos[1]))) / Math.sqrt(2);
      // Consider them higher value if we are closer, and lower if they are
      token.value += ((token.dist - token.distE) / (token.dist + token.distE + 0.001)) * token.dist;
      // Don't go for it if enemy is already there
      token.value += (token.distE == 0 && token.dist > 0) * 100;

      if (tokens[best].value > tokens[i].value || (tokens[best].value === tokens[i].value && Math.round(Math.random()))) {
        best = i;
      }
    }

    // Set token to best token
    var token = tokens[best];

    // What to respond with
    var response = 'PASS';

    // Find best action to get token
    if (token.dist == 0) {
      response = 'EAT'; // We're on the token
    } else if (token.distX >= token.distY) { // Token is more horizontal
      if (token.x < 0) { // Token is left
        response = 'LEFT';
      } else if (token.x > 0) { // Token is right
        response = 'RIGHT';
      }
    } else if (token.distX < token.distY) { // Token is more vertical
      if (token.y < 0) { // Token is above
        response = 'UP';
      } else if (token.y > 0) { // Token is below
        response = 'DOWN';
      }
    }

    // Return response
    return response;
  }
};
Mwr247
fuente
¿Eres un programador de Python?
CalculatorFeline
@CatsAreFluffy ¿Realmente no ...?
Mwr247
Solo pensé que eras porque self:)
CalculatorFeline
¿Por qué usar self? No es thissuficiente?
Conor O'Brien
2

RUTA bot

Acrónimo significa Pathfinding And Tree Heuristics Bot

EDITAR: a partir de ahora, aquí están las clasificaciones de las IA, con los puntos

  1. HungryBot (6422)
  2. RUTA bot (4591)
  3. NaiveAI (3811)
  4. KandRandomAI (618)
  5. MirrorBot (193)
  6. LazyBot (25)

Enlace para completar el controlador en github

Descripción: Al igual que NaiveAI, este bot encuentra la ficha más cercana que le otorgará la mayor cantidad de puntos. Sin embargo, también simula los resultados de cada uno de sus movimientos, hasta 6 veces.

Justificación: Debido a que NaiveAI ya es bastante bueno, pensé que lo mejoraría. Sin mirar primero el código (gran error).

Ritmos: todos excepto HungryBot
Pierde a: Ninguno excepto HungryBot

Problemas:

  • No se puede simular una racha aumentada
  • Se cuelga mientras se calcula el mejor token
  • Puede teletransportarse

Todavía no sé por qué se estaba teletransportando, pero lo arreglé. Viejo video aquí: https://youtu.be/BIhSKycF9iA

Código completo:

pathBot = function (player1)
{
    this.pathNode = function(pos,ppt,parents,par)
    {
        this.pos = pos;this.ppt = ppt;this.parents = parents;this.par=par;
        this.childs=[];
    }
    this.addChildren = function (pn,children)
    {
        pn.childs=[];
        for(var i=0; i<children.length; i=i+1)
        {
            if(pn.parents.indexOf(children[i].pos)==-1&&pn.pos!=children[i].pos)
                pn.childs.push(
                    new this.pathNode(
                        children[i].pos,
                        children[i].ppt*pn.ppt,
                        pn.parents.concat([pn.pos]),
                        pn
                    )
                );
        }
    }
    this.orderTokensByPPT = function(b,pos){
        var tokens = [];
        for(var y=0; y<b.tokens.length; y=y+1)
        {
            for(var x=0; x<b.tokens[y].length; x=x+1)
            {
                var tok = b.tokens[y][x];
                if(tok)
                {
                    tokens.push(
                        new this.pathNode(
                            [y,x],
                            (tok.points+(tok.color==this.color ? this.streak : 0)) / this.lenOfMovesTo(pos,[y,x]),
                            [],
                            undefined
                        )
                    );
                }
            }
        }
        tokens.sort(function(a,b){
            return b.ppt - a.ppt;
        });
        return tokens;
    }
    this.lenOfMovesTo = function(cur,pos)
    {
        return Math.abs(cur[0]-pos[0])+Math.abs(cur[1]-pos[1])+1;
    }
    this.startAndGoalToCommand = function (start, goal) {
        var diff = [goal[0] - start[0], goal[1] - start[1]];
        if (diff[0] > 0) { return "RIGHT"; }
        else if (diff[1] > 0) { return "DOWN"; }
        else if (diff[1] < 0) { return "UP"; }
        else if (diff[0] < 0) { return "LEFT"; }
        else { return "EAT"; }
    }
    this.color = 0;
    this.streak = 0;
    this.eatTok = function(b)
    {
        if(b.tokens[this.me.pos[0]][this.me.pos[1]].color==this.color)
        {
            this.streak++;
        }
        else{
            this.streak = 0;
            this.color = b.tokens[this.me.pos[0]][this.me.pos[1]].color;
        }
        this.bestToken = false;
        return "EAT";
    }

    this.recurLen = 6;
    this.include = 4;
    this.recurDown = function(b,pn,level)
    {
        if(level==0) return pn;
        this.addChildren(pn,this.orderTokensByPPT(b,pn.pos));
        var newChilds = [];
        for(var i=0; i<pn.childs.length&&i<this.include; i=i+1)
        {
            newChilds.push(this.recurDown(b,pn.childs[i],level-1));
        }
        pn.childs = newChilds;
        return pn;
    }
    this.findMax = function(pn)
    {
        if(pn.childs)
        {
            var maxList = [];
            for(var i=0; i<pn.childs.length; i=i+1)
                maxList.push(this.findMax(pn.childs[i]));
            maxList.sort(
                function(a,b)
                {
                    return b.ppt-a.ppt;
                }
            );
            return maxList[0];
        }
        return pn;
    }
    this.findMaxList = function(pnList)
    {
        for(var i=0; i<pnList.lenght; i=i+1)
        {
            pnList[i] = this.findMax(pnList[i]);
        }
        pnList.sort(function(a,b){return b.ppt-a.ppt;});
        return pnList[0];
    }
    this.bestToken=false;
    this.yourMove = function(b){
        this.op = player1 ? b.player2 : b.player1;
        this.me = player1 ? b.player1 : b.player2;
        if(this.bestToken)
        {
            if(b.tokens[this.bestToken.pos[0]][this.bestToken.pos[1]]==undefined)
                this.bestToken = false;
        }
        if(!this.bestToken)
        {
            var paths = this.orderTokensByPPT(b,this.me.pos);
            for(var i=0; i<paths.length; i++)
            {
                paths[i] = this.recurDown(b,paths[i],this.recurLen);
            }
            var max = this.findMaxList(paths);
            while(max.par)
            {
                max = max.par;
            }
            this.bestToken = max;
        }
        var move = this.startAndGoalToCommand(this.me.pos,this.bestToken.pos);
        if(move=="EAT") return this.eatTok(b);
        else return move;
    }
}
Azul
fuente
SLaNTbot está disminuyendo la velocidad de giro y consumiendo el 15% de mi CPU ... D: EDITAR: ¿Y también no comer nada?
Mwr247
@ Mwr247 la velocidad, sí, modela ~ 2500 posibilidades en cada tic. Pero para comer, no sé exactamente por qué. Como dije en la pregunta, solo se teletransporta (también se mueve múltiples espacios en 1 turno), y se sienta allí sin hacer nada. Puse una alerta justo antes del regreso y parece estar dando las instrucciones correctas cada vez.
Azul
Tal vez esto: "Tu IA debería tomar menos de un segundo para hacer un movimiento, de lo contrario se asumirá PASS como tu elección". No he leído el controlador, pero si está tomando más de un segundo, ¿está asumiendo PASS?
Mwr247
@ Mwr247 Investigaré eso, pero parece poco probable dado que estaba tardando <1 segundo (o eso pensé) en mi máquina. Aún así, nunca está de más mirar. ¡Gracias!
Azul
@ Mwr247 Después de algunas pruebas más, eso no es todo. Está tomando decisiones casi tan rápido (al menos para mí) como NaiveAi. Además, es más probable que experimentes teletransportación en mapas grandes
Azul
1

NaiveAI

Comenzando con r=0, mira todas las fichas con una distancia de taxi rlejos de tu posición. Si hay alguno, elija uno que le otorgaría el puntaje más alto si lo obtiene en este momento. De lo contrario, aumente ren 1 e intente nuevamente.

naiveAI = function(player1) {
  this.player1 = player1;
  this.yourMove = function(b) {
    var me;
    if (this.player1) {
      me = b.player1;
    } else {
      me = b.player2;
    }
    var d = 0;
    var tokenP;
    while (tokenP == undefined) {
      var arr = this.findTokensAtDistance(me.pos, d)
      tokenP = this.findBestToken(arr, b.tokens, me);
      d += 1;
    }
    return this.startAndGoalToCommand(me.pos, tokenP);
  }
  this.findTokensAtDistance = function(p, d) {
    if (d == 0) {
      return [
        [p[0], p[1]]
      ];
    }
    var myArr = [];
    for (i = 0; i <= d; i++) {
      myArr[i] = [i, d - i];
    }
    var mySecArr = [];
    for (i = 0; i <= d; i++) {
      mySecArr[i] = [myArr[i][0] + p[0], myArr[i][1] + p[1]];
    }
    mySecArr[mySecArr.length] = [myArr[0][0] + p[0], -myArr[0][1] + p[1]];
    for (i = 1; i < myArr.length - 1; i++) {
      mySecArr[mySecArr.length] = [-myArr[i][0] + p[0], myArr[i][1] + p[1]]
      mySecArr[mySecArr.length] = [myArr[i][0] + p[0], -myArr[i][1] + p[1]]
      mySecArr[mySecArr.length] = [-myArr[i][0] + p[0], -myArr[i][1] + p[1]]
    }
    mySecArr[mySecArr.length] = [-myArr[myArr.length - 1][0] + p[0], myArr[myArr.length - 1][1] + p[1]];
    return mySecArr;
  }
  this.findBestToken = function(arr, t, player) {
    var tokenPos;
    for (i = 0; i < arr.length; i++) {
      if (arr[i][0] >= 0 && arr[i][0] < t.length && arr[i][1] >= 0 && arr[i][1] < t.length) {
        if (t[arr[i][0]][arr[i][1]] != false && ((tokenPos == undefined) || (this.tokenScore(player, t[arr[i][0]][arr[i][1]]) > this.tokenScore(player, t[tokenPos[0]][tokenPos[1]])))) {
          tokenPos = [arr[i][0],
            [arr[i][1]]
          ];
        }
      }
    }
    return tokenPos;
  }
  this.tokenScore = function(player, token) {
    if (player.lastColor == token.color) {
      return player.colorBonus + 1 + token.points;
    } else {
      return token.points;
    }
  }
  this.startAndGoalToCommand = function(start, goal) {
    var diff = [goal[0] - start[0], goal[1] - start[1]];
    if (diff[0] > 0) {
      return "RIGHT";
    } else if (diff[1] > 0) {
      return "DOWN";
    } else if (diff[1] < 0) {
      return "UP";
    } else if (diff[0] < 0) {
      return "LEFT";
    } else {
      return "EAT";
    }
  }
}
Melón Fricativo
fuente
1

KindaRandomAI

Cada turno, haga lo siguiente: Si hay una ficha en su posición, "COMER". De lo contrario, muévase en una dirección viable aleatoria, es decir, si está en el borde izquierdo, no diga "IZQUIERDA".

kindaRandomAI = function(player1) {
    this.player1 = player1;
    this.yourMove = function(b) {
        var me;
        if (this.player1) {
            me = b.player1;
        } else {
            me = b.player2;
        }
        if (b.tokens[me.pos[0]][me.pos[1]] != false) {
            return "EAT";
        } else {
            var dirs = this.getViableDirections(b, me.pos);
            var rand = Math.floor(Math.random() * dirs.length);
            return dirs[rand];
        }
    }
    this.getViableDirections = function(b, p) {
        var dirs = [];
        if (p[0] > 0) {
            dirs.push("LEFT");
        }
        if (p[1] > 0) {
            dirs.push("UP");
        }
        if (p[1] < b.tokens.length - 1) {
            dirs.push("DOWN");
        }
        if (p[0] < b.tokens.length - 1) {
            dirs.push("RIGHT");
        }
        return dirs;
    }
}
Melón Fricativo
fuente
-1 no completamente al azar
CalculatorFeline
¡Eso es mejor!.
CalculatorFeline
1

LazyBot

Solo come algo si se genera. Esto no tiene posibilidad de ganar, pero el desafío no tenía uno de estos, entonces, ¿por qué no?

lazyBot = function (player1) {
    this.yourMove = function(b) {
        return "EAT";
    }
}
Bálint
fuente
1
Cada koth tiene un EmoWolf ...
Azul
3
@Blue No es 100% emo, trata de comer.
Bálint
1

MirrorBot

Debería llamarse "carne de cañón"

Descripción: Mueve exactamente lo contrario de lo que hizo el otro jugador

Justificación: quería volver a programar cómodamente en JS. Esto no debería ganar

Golpeará: nadie

Perderá con: Todos

function mirror(player1) {
    this.hasStarted=false;
    this.player1 = player1;
    this.opl=[0,0];
    this.yourMove = function(b){
        this.op = this.player1 ? b.player2.pos : b.player1.pos;
        out = "EAT";
        console.log(this.op);
        console.log(this.opl);
        if(this.hasStarted){
            if(this.opl[0] < this.op[0]) out = "RIGHT";
            if(this.opl[0] > this.op[0]) out = "LEFT";
            if(this.opl[1] < this.op[1]) out = "UP";
            if(this.opl[1] > this.op[1]) out = "DOWN";
        }
        this.opl = [this.op[0],this.op[1]];
        this.hasStarted = true;
        return out;
    }
}
Azul
fuente
Hay algunos problemas con su código. Derecha e izquierda no son opuestas, y la definición de función para yourMove no es una sintaxis válida. Mi código también estaba roto antes, así que en el proceso de encontrar y solucionar el problema en mi código, también arreglé su código. Puedes mirar el código fijo en mi script.
Melón fricativo
@FricativeMelon Arregle la definición de función rota. Tengo que contrarrestar la afirmación de que los derechos no son opuestos.
Azul
0,0 es la esquina superior izquierda, por lo que x positivo es correcto y x negativo es izquierdo. Si la nueva posición x tiene un valor mayor que la posición x anterior, el otro jugador se movió hacia la derecha, por lo que debe moverse hacia la izquierda y viceversa. Además, debe usar en var out = "EAT";lugar de out = "EAT";, ya que el último define una variable global. Nitpicking un poco, las líneas tercera y cuarta no hacen nada y también se pueden eliminar, y oppueden ser una variable local como en outlugar de una propiedad.
Melón fricativo
@FricativeMelon ah, entiendo lo que estás diciendo. He actualizado el código. ¡Gracias!
Azul
Puse su nuevo código en el script, y está funcionando ahora. Sin embargo, no supera a RandomAI :(
Fricative Melon
0

OneTarget

Encuentra el token que le dará más puntos en el menor tiempo e irá por ese. Clasifica las fichas del mismo color un poco más alto debido al efecto acumulativo.

function (player1) {
    this.yourMove = function (b) {
        var me = player1? b.player1: b.player2;
        var him= player1? b.player2: b.player1;
        var x = me.pos[0];
        var y = me.pos[1];
        var maxVal = -1;
        var maxX = 0;
        var maxY = 0;
        for(var i = 0;i < b.tokens.length;i++){
            for(var j = 0;j < b.tokens.length;j++){
                if(b.tokens[i][j]){
                    var dist = Math.abs(x-i) + Math.abs(y-j);
                    var val = this.valueOf(b.tokens[i][j]);
                    val /= (dist + 1);
                    if(val > maxVal){
                        maxVal = val;
                        maxX = i;
                        maxY = j;
                    }
                }
            }
        }
        if(maxY < y)
            return "UP";
        if(maxX < x)
            return "LEFT";
        if(maxY > y)
            return "DOWN";
        if(maxX > x)
            return "RIGHT";
        return "EAT";
    }
    this.valueOf = function(t){
        //how many points would it give you?
        return t.points + (this.lastColor == t.color? 2 * this.colorBonus + 1 : 0);
    }
}
MegaTom
fuente
0

Cantidad Jugador

Lo único que le importa a QuantityPlayer es la cantidad de puntos que come, no el valor o el color de los puntos. Él sabe que aunque todos los puntos son diferentes, deben ser tratados de la misma manera.

QuantityBot = function(playernum) {

this.dist = function(token) {
    return (Math.abs(token[0])+Math.abs(token[1]))
}

this.yourMove = function(game_board) {

    board_size = game_board.tokens.length
    board_area = board_size * board_size
    fete = board_size = size * 2

    token_list = []
    count = curr_x = curr_y = 0
    while(count < board_area) {
        if(game_board.tokens[x][y]) {
        token_list.push([x-player.pos[0],y-player.pos[1]])
        }
        count++; x = count % board_size; y = Math.floor(count / size)
    }

    closest_token = token_list[0]
    count = 1
    while(count < token_list.length) {
        curr_token = token_list[count]
        if(dist(curr_token) < dist(closest_token)){closest_token = curr_token}

        count++
    }

    if(dist(closest_token)==0){return 'EAT'}
    else{
    if(closest_token[0] >= closest_token[1]) {if(closest_token[0]<0) {return 'LEFT'} {return 'RIGHT'}}
    else{if(closest_token[1]<0) {return 'UP'} {return 'DOWN'}}
    }

}

}
Benjamin Philippe
fuente