Construye una IA AI determinista

11

Aquí hay un problema interesante que pensé el otro día, que involucra fragmentos de código que compiten contra otros bits de código no solo en una propiedad que tiene el código, sino al jugar un juego contra esos otros bits de código.

Su tarea es construir un programa que tome el estado actual de un tablero de Go y determine qué movimiento hacer o aprobar.

Su programa aceptará lo siguiente como entrada:

  • 19 líneas, cada una con 19 caracteres, que representan las piezas actualmente en el tablero Go. Un carácter de 0representa un cuadrado vacío, 1es negro y 2es blanco.

  • Dos números que representan el número de piezas de prisioneros que tiene cada jugador (negro, luego blanco).

  • Un número que representa a quién le toca moverse (blanco o negro). Como arriba, 1es negro y 2es blanco.

y generar uno de los siguientes:

  • Un par de coordenadas que a brepresentan las coordenadas a las cuales moverse. 1 1es el cuadrado superior izquierdo, y los números primero y segundo representan moverse hacia abajo y hacia la derecha respectivamente.

  • La cadena pass, que representa un movimiento para pasar.

Por ejemplo, el programa puede recibir la siguiente entrada:

0000000000000000000
0000000000000000000
0000000000000000000
0001000000000002000
0000000000000000000
0000000000000000000
0001210000000000000
0000100000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0002000000000001000
0000000000000000000
0000000000000000000
0000000000000000000
0 0 1

que representa un juego donde solo se han jugado unos pocos movimientos.

Entonces el programa podría salir 6 5, lo que significa "poner una piedra negra en el sexto punto desde arriba y el quinto desde la izquierda". Esto capturaría la piedra blanca en 7 5. El estado de la junta luego cambiaría a:

0000000000000000000
0000000000000000000
0000000000000000000
0001000000000002000
0000000000000000000
0000100000000000000
0001010000000000000
0000100000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0000000000000000000
0002000000000001000
0000000000000000000
0000000000000000000
0000000000000000000
1 0 2

(Tenga en cuenta que aunque se capturó una piedra blanca, cuenta como prisionero para negro).

Su código debe cumplir adicionalmente las siguientes propiedades:

  • Si su programa tiene el mismo estado de entrada, siempre debe producir la misma salida. Este es el determinismo de Go AI. No debe tener un componente aleatorio.

  • Su programa no debe tomar más de aproximadamente 60 segundos para determinar qué movimiento hacer. Esta regla no se aplicará estrictamente debido a las variaciones en el poder de cómputo, pero debe hacer un movimiento en un período de tiempo razonable.

  • El código fuente de su programa no debe exceder un total de 1 megabyte (1,048,576 bytes).

  • Su programa siempre debe hacer movimientos legales. Su programa no puede hacer un movimiento donde ya existe una piedra, y no puede colocar una pieza que resultaría en la captura de un grupo de sus propias piedras. (Una excepción a las reglas para los propósitos de este desafío es que un programa puede crear una posición que originalmente estaba allí, ya que solo se le da la posición actual de un tablero, no se puede esperar que almacene los movimientos que se han realizado antes de.)

Su envío se jugará en un torneo de todo contra todos los demás envíos, en un juego de Go donde el estado del tablero comienza como vacío, y cada programa se turna para alimentar la posición del tablero y hacer un movimiento. .

Cada par de presentaciones jugará dos rondas, una ronda con cada jugador negro. Debido a que las IA en este problema son completamente deterministas, dos de las mismas IA que juegan juntas siempre darán como resultado exactamente el mismo juego.

Las condiciones para ganar son las siguientes:

  • Si su programa juega hasta el final del juego, las reglas de puntuación de Go de China se utilizarán para determinar el ganador. No se aplicará komi.

  • Si su programa juega hasta el punto en que se alcanza un estado anterior, causando así un bucle infinito, los dos programas se declararán vinculados.

Su envío se puntuará según la cantidad de puntos que obtenga frente a otros envíos. Una victoria vale 1 punto, y un empate vale medio punto. La presentación con más puntos es el ganador general.


Este es un desafío del rey de la colina, en el que cualquiera puede publicar una nueva entrada en cualquier momento, y la clasificación se volverá a evaluar periódicamente cuando esto suceda.

Joe Z.
fuente
77
Ok, esperar todas las otras presentaciones y luego escribir las mías para vencerlas, debería ser posible ya que las soluciones son deterministas.
Howard
1
Parece que jugar en ko tal que la posición anterior se repite está permitida pero conduce a un sorteo inmediato (ya que causa un bucle). Interesante ...
FireFly
2
Parece que su problema es demasiado difícil y nadie trabajaría lo suficiente como para producir una respuesta que valga la pena (realmente es mucho trabajo). Es un buen problema, pero ir es demasiado difícil de trabajar.
Victor Stafusa
1
¿Por qué no usar una tabla más pequeña? 9x9 es lo suficientemente común para jugadores principiantes. Reduce drásticamente el espacio de búsqueda, pero no es tan pequeño que haya sido "superado" aún por análisis (creo que el más grande que se ha resuelto completamente es 5x6).
Geobits
1
¿Cómo funciona la entrada? stdin o argumentos de línea de comando?
Ypnypn

Respuestas:

7

Aquí está mi entrada para despegar este desafío. Código de Python:

print "pass"

Según sus reglas, siempre jugar "pasar" es una estrategia válida (aunque mala).

Björn Lindqvist
fuente
Su código siempre perderá contra cualquiera que juegue contra él. Aún así, buena respuesta de caso base.
Joe Z.
1
@JoeZ. Y por lo que parece, ganó con él: P
David Mulder
4

Java: elige un lugar, cualquier lugar

Simplemente elige puntos en el tablero para probar su validez. Utiliza el PRNG, pero con una semilla establecida, así que eso es determinista. Utiliza diferentes fragmentos del ciclo PRNG dependiendo de cuántos turnos hayan pasado.

Para cada posición de candidato, verifica que sea un movimiento válido (pero no que sea un movimiento inteligente ). Si no lo es, pasa al siguiente candidato. Si no puede encontrar un movimiento válido después de 1000 intentos, pasa.

import java.util.Random;
import java.util.Scanner;

public class GoNaive {

    int[][] board;
    boolean[] checked;
    int me;

    public static void main(String[] args) {
        new GoNaive().run();
    }

    void run(){
        int turns = init();
        Random rand = new Random(seed);

        for(int i=0;i<turns*tries;i++)
            rand.nextInt(size*size);

        for(int i=0;i<tries;i++){
            int pos = rand.nextInt(size*size);
            for(int c=0;c<size*size;c++)
                checked[c]=false;
            if(board[pos%size][pos/size] == 0)
                if(hasLiberties(pos, me)){
                    System.out.print((pos%size+1) + " " + (pos/size+1));
                    System.exit(0);
                }
        }
        System.out.print("pass");
    }

    boolean hasLiberties(int pos, int color){
        if(checked[pos])
            return false;
        checked[pos] = true;

        int x = pos%size, y=pos/size, n;

        if(x>0){
            n = board[x-1][y];
            if(n==0 || (n==me && hasLiberties(y*size+x-1, color)))
                return true;
        }
        if(size-x>1){
            n = board[x+1][y];
            if(n==0 || (n==me && hasLiberties(y*size+x+1, color)))
                return true;
        }
        if(y>0){
            n = board[x][y-1];
            if(n==0 || (n==me && hasLiberties((y-1)*size+x, color)))
                return true;
        }
        if(size-y>1){
            n = board[x][y+1];
            if(n==0 || (n==me && hasLiberties((y+1)*size+x, color)))
                return true;
        }
        return false;
    }

    int init(){
        int turns = 0;
        board = new int[size][size];
        checked = new boolean[size*size];
        turns = 0;
        Scanner s = new Scanner(System.in);
        String line;
        for(int i=0;i<size;i++){
            line = s.nextLine();
            for(int j=0;j<size;j++){
                board[j][i] = line.charAt(j)-48;
                if(board[j][i] > 0)
                    turns++;
            }
        }
        String[] tokens = s.nextLine().split(" ");
        turns += Integer.valueOf(tokens[0]);
        turns += Integer.valueOf(tokens[1]);
        me = Integer.valueOf(tokens[2]);
        s.close();
        return turns;
    }

    final static int size = 19;
    final static int seed = 0xdeadface;
    final static int tries = 1000;
}
Geobits
fuente
2

Algunos Scala:

package go;

class Go {
  def main(args : Array[String]) {
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    System.out.printLn("1 1")
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    readLine()
    System.out.printLn("pass")
  }
}

Al leer Wikipedia, creo que esto superará la solución actual.

yayestechlab
fuente
De hecho, ganará por 361 puntos en ambos casos.
Joe Z.
En realidad, tendré que retirar eso, no sigue las especificaciones. Se supone que la IA no tiene estado. En realidad, solo se supone que imprime una cosa dado el estado del tablero, y lo has hecho imprimir dos ( 1 1y pass).
Joe Z.
@JoeZ. Arreglado. No habría compilado de todos modos.
yayestechlab
Eso siempre se imprimirá 1 1, en realidad, ya que el programa siempre se ejecuta de nuevo cada vez que el tablero cambia.
Joe Z.
1

Java

public class Go {
  public static void main(String[] args) {
    Scanner s = new Scanner(System.in);
    for (int i = 0; i < 361;) {
      char c = s.nextChar();
      if (c != '\n') {
        if (c == '0') {
          System.out.println((i % 19 + 1) + " " + (i / 19 + 1));
          System.exit(0);
        }
        i++;
      }
    }
  }
}

Elige el primer espacio vacío. Gana contra cualquiera de las IA al momento de la publicación.

Ypnypn
fuente
2
Esto no garantiza un movimiento legal. Si el primer espacio disponible no tiene libertades, no se puede jugar. Por ejemplo, si esta IA se jugó sola: después de la primera fila de piezas alternas, 1 1sería capturada por el blanco (ahora vacío) y no podría ser jugada por el negro en el siguiente turno.
Geobits