¿Qué algoritmo es mejor para una variante 4 * 4 * 4 * 4 del último dedo del pie tic-tac considerando un límite de tiempo de 15 segundos?

El espacio de estado del último tic-tac-toe es mucho más grande que el simple 3 × 3 tic-tac-toe con el que todos estamos familiarizados.

Si bien el árbol Minimax de 3 × 3 tic-tac-toe se puede construir fácilmente debido a su factor de ramificación pequeño y constantemente degradante, no se puede decir lo mismo de t3 ultimate (tic-tac-toe). Con un factor de ramificación inicial de 81 y degradando constantemente en 1 después de cada turno. ¡Esto significa que el nodo hoja tendrá 81! posibles estados Ni siquiera podemos almacenar este valor correctamente en lenguaje C.

Esto nos lleva a la conclusión de que no puedo usar Minimax o sus variantes para este problema, al menos no de manera eficiente.

Ahora, antes de pasar a métodos más complejos, siempre es bueno analizar los recursos disponibles. Así que mencionaste en tu pregunta que el límite de tiempo es de 15 segundos. Esta es una gran cantidad de tiempo para elegir en un movimiento. Una implementación eficiente de NegaMax podría alcanzar una profundidad decente con esa limitación de tiempo. Pero lo podemos hacer mejor.

Soluciones posibles:

  1. Búsqueda de árbol de Monte Carlo (MCTS) : en lugar de buscar todos los movimientos posibles desde el estado actual, intente realizar jugadas aleatorias desde el estado actual hasta que finalice el juego. Haga esto suficientes veces y tendrá una estimación decente de qué movimientos tienen la mejor posibilidad de ganar. MCTS es mejor que MiniMax, ya que no necesita ninguna función de costo compleja y precisa y, por lo tanto, ahorra muchos cálculos. Existen muchas implementaciones eficientes de MCTS que funcionan mejor que MiniMax en la mayoría de los casos.
  2. Aprendizaje de refuerzo (RL) : deje que su bot aprenda de sus errores y aprenda automáticamente el mejor movimiento para tomar desde cada posición del tablero. RL es muy parecido a Minimax, excepto que una vez entrenado solo tiene que mirar un paso adelante para decidir el próximo movimiento.
  3. Por qué no ambos : el uso de poderosas heurísticas no es infrecuente en MCTS para aumentar sus estadísticas de búsqueda. La heurística entrenada con MCTS evita que la búsqueda se desvíe y pierda tiempo en caminos subóptimos. Incluso con el uso de la heurística, MTCS es mucho más rápido y preciso que Minimax, ya que no tiene que buscar en todas las partes del árbol.

La belleza de estos 2 métodos es que pueden aumentar enormemente usando programación paralela. La ejecución de muchas instancias del agente RL le permitirá conocer la política óptima mucho más rápido. Y ejecutar muchas instancias paralelas de MCTS dará como resultado un aumento masivo en su confianza en la estadística de movimiento.

Sobre esto, intente implementar la lógica de la placa utilizando tablas de bits . No solo le ahorrarán mucha memoria, sino que también le permitirán acelerar el cálculo necesario de varias actualizaciones de la placa con el uso de operaciones morfológicas a nivel de bits (dilatación, erosión, etc.)

Usando estos métodos, estoy seguro de que puedes hacer un bot muy poderoso para el juego

Aquí está mi INTENTO en C.

#include
#include
#include
#include

#define ROWS 9
#define COLS 9

typedef char Board [FILAS] [COLS];
typedef char MetaBoard [FILAS / 3] [COLS / 3];
typedef enum {VALID, NOT_A_DIGIT, NOT_IN_BOARD, SPACE_OCCUPIED, OUT_OF_BOUNDS} MoveStatus;
marcas de const estáticas [3] = {‘O’, ‘X’, ‘#’};

void fillSubBoard (placa del tablero, int x, int y, char c);
int getBound (int in);
anular printBoard (tablero);
static int checkMeta (MetaBoard meta);
static int checkBoard (tablero Board, MetaBoard meta, int player, int row, int column);
MoveStatus validCoords (placa del tablero, int fila, int columna, int rowBound, int columnBound);

void fillSubBoard (Tablero, int x, int y, char c)
{
x – = (x% 3); // establece rápidamente x en el límite izquierdo del sub-tablero
y – = (y% 3); // establece rápidamente y en el límite superior de la placa secundaria
para (int rowMax = x + 2, row = x; row <= rowMax; ++ row)
{
para (int columnMax = y + 2, column = y; column <= columnMax; ++ column)
{
tablero [fila] [columna] = c;
}
}
}

int getBound (int in)
{
return ((en <9)? in / 3: -1);
}

vacío printBoard (tablero)
{
printf (“\ n ============= || =========== || ============= \ n”) ;

para (int fila = 0; fila {
printf (“||”);
for (int column = 0; column {
if (‘-‘ == tablero [fila] [columna]) printf (“% d,% d |”, fila, columna);
else printf (“% c |”, tablero [fila] [columna]);
if (0 == (columna + 1)% 3) printf (“|”);
}
if (0 == (fila + 1)% 3) printf (“\ n ============= || =========== || ==== ========= \ n “);
else printf (“\ n—– | – | – || – | – | – || – | – | —– \ n”);
}
}

static int checkMeta (MetaBoard meta)
{
const int xStart [ROWS – 1] = {0, 0, 0, 0, 1, 2, 0, 0};
const int yStart [COLS – 1] = {0, 1, 2, 0, 0, 0, 0, 2};
const int xDelta [FILAS – 1] = {1, 1, 1, 0, 0, 0, 1, 1};
const int yDelta [COLS – 1] = {0, 0, 0, 1, 1, 1, 1, 1};
int startx = 0;
int starty = 0;
int deltax = 0;
int deltay = 0;

for (int trip = 0; trip {
startx = xStart [viaje];
starty = yStart [viaje];
deltax = xDelta [viaje];
deltay = yDelta [viaje];
// lógica principal para verificar si una placa base tiene un ganador
if (‘-‘! = meta [startx] [starty] &&
meta [startx] [starty] == meta [startx + deltax] [starty + deltay] &&
meta [startx] [starty] == meta [startx + deltax + deltax] [starty + deltay + deltay]) return 1;
}
devuelve 0;
}

checkBoard estático int (Tablero de tablero, MetaBoard meta, int jugador, int fila, int columna)
{
const int xStart [ROWS – 1] = {0, 0, 0, 0, 1, 2, 0, 0};
const int yStart [COLS – 1] = {0, 1, 2, 0, 0, 0, 0, 2};
const int xDelta [FILAS – 1] = {1, 1, 1, 0, 0, 0, 1, 1};
const int yDelta [COLS – 1] = {0, 0, 0, 1, 1, 1, 1, 1};
int startx = 0;
int starty = 0;
int deltax = 0;
int deltay = 0;
int oCounter = 0;
int xCounter = 0;
int estado = 0;

fila – = (fila% 3); // establece rápidamente x en el límite izquierdo del tablero secundario
columna – = (columna% 3); // establece rápidamente y en el límite superior de la placa secundaria

// verifica si el tablero ya se ganó o si hay empate
for (int rowMax = row + 2, startx = row; startx <= rowMax; ++ startx) // usa el valor startx para reducir el número de variables
{
for (int columnMax = column + 2, starty = column; starty <= columnMax; ++ starty) // usa starty para reducir el número de variables
{
if (tablero [iniciox] [inicio] == marcas [0]) ++ oCounter;
if (tablero [iniciox] [inicio] == marcas [1]) ++ xCounter;
if (9 == oCounter || 9 == xCounter) estado = 1; // haz que el jugador pueda moverse a cualquier parte
if (9 == oCounter + xCounter)
{
fillSubBoard (tablero, fila, columna, marcas [2]); // establece el tablero en un personaje neutral
meta [getBound (fila)] [getBound (columna)] = marcas [2]; // establece las coordenadas del metaboard en un estado vinculado
return checkMeta (meta); // no necesitamos verificar si el tablero se ganó, solo descubrimos que era un empate
}
}
}

// verifica si el tablero se ganó esta ronda
for (int trip = 0; trip {
startx = row + xStart [viaje];
starty = column + yStart [trip];
deltax = xDelta [viaje];
deltay = yDelta [viaje];
if (‘-‘! = tablero [iniciox] [inicio] &&
tablero [iniciox] [inicio] == tablero [iniciox + deltax] [inicio + deltay] &&
tablero [iniciox] [inicio] == tablero [iniciox + deltax + deltax] [inicio + deltay + deltay])
{
fillSubBoard (tablero, fila, columna, marcas [jugador]);
meta [getBound (fila)] [getBound (columna)] = marcas [jugador];
}
}
return (estado + checkMeta (meta)); // siempre verifica si el juego tiene un ganador
}

MoveStatus validCoords (placa del tablero, int fila, int columna, int rowBound, int columnBound)
{
if (! isdigit ((unsigned char) (((int) ‘0’) + row)) &&! isdigit ((unsigned char) (((int) ‘0’) + column))) return NOT_A_DIGIT; // los cables suministrados no son dígitos 1-9
de lo contrario, si (fila> FILAS – 1 || columna> COLS – 1) devuelve NOT_IN_BOARD; // los cables suministrados no están dentro de los límites del tablero
sino if (‘-‘! = tablero [fila] [columna]) return SPACE_OCCUPIED; // los cables suministrados están ocupados por otro personaje
de lo contrario si (-1 == rowBound && -1 == columnBound) devuelve VALID; // los cables suministrados pueden moverse a cualquier parte
de lo contrario if (((row> rowBound * 3 + 2 || column> columnBound * 3 + 2) ||
(fila (rowBound> = 0 && columnBound> = 0)) return OUT_OF_BOUNDS; // las coordenadas no están dentro del sub-tablero especificado por el movimiento anterior
de lo contrario devuelve VÁLIDO; // no falló en ningún otro lugar, por lo que los códigos son válidos
}

int main (nulo)
{
int ganador = 0;
int fila = 0;
columna int = 0;
int rowBound = -1; // establecido inicialmente para que el jugador 1 pueda moverse a cualquier parte
int columnBound = -1; // establecido inicialmente para que el jugador 1 pueda moverse a cualquier parte
int error = 0;
char tempRow = ‘\ 0’;
char tempColumn = ‘\ 0’;
Tablero junta;
MetaBoard meta;
static char const * errores [] =
{
0,
“Entrada inválida.”,
“Fuera de los límites del tablero”,
“Ese espacio ya está en uso”,
“Tu movimiento fue en el tablero secundario equivocado”.
};

// inicializa tableros y rellena con ‘-‘
memset (placa, ‘-‘, FILAS * COLS);
memset (meta, ‘-‘, (ROWS / 3) * (COLS / 3));

// bucle de juego
for (int turn = 0; turn {
int jugador = turno% 2;
printBoard (tablero);
printf (“Jugador% d, ingrese las coordenadas (x, y) para colocar% c:”, jugador + 1, (1 == jugador)? ‘X’: ‘O’);
hacer
{
scanf (“% c,% c”, & tempRow, & tempColumn);
while (getchar ()! = ‘\ n’); // recoge datos superfluos para que no tengamos problemas cuando escaneamos los datos nuevamente
fila = abs ((int) tempRow – ‘0’);
column = abs ((int) tempColumn – ‘0’);
if (0! = (error = validCoords (tablero, fila, columna, rowBound, columnBound))) printf (“% s Re-enter:”, errores [error]);
} while (error);

tablero [fila] [columna] = marcas [jugador];
switch (checkBoard (tablero, meta, jugador, fila, columna))
{
caso 1:
// el próximo movimiento puede ser en cualquier lugar
rowBound = -1;
columnBound = -1;
rotura;
caso 2:
ganador = jugador + 1;
rotura;
defecto:
rowBound = fila% 3;
columnBound = columna% 3;
rotura;
}
}
printBoard (tablero);

if (! ganador) printf (“El juego es un empate \ n”);
else printf (“El jugador% d ha ganado \ n”, ganador);
}

More Interesting

¿Cuáles son las aplicaciones prácticas / de la vida real / industriales de Dijkstra, Kruskal y Algortithm de Prim?

¿Debo postularme a trabajos de desarrollo web si puedo construir aplicaciones CRUD pero no asimilo la notación Big O y nunca he trabajado en un proyecto grupal?

¿Cuáles son los algoritmos que se pueden usar en R para la predicción de datos categóricos?

¿Cuál es una manera sencilla de encontrar big-O, big-Theta y big-Omega para una función determinada?

¿Cuál es una buena estructura de datos para un editor de texto como Word / Google Docs?

¿Qué proyectos de aprendizaje automático se ven bien en un currículum?

¿Cuánto tiempo / horas debo pasar todos los días para ser un buen programador de Java para poder resolver estructuras de datos y algoritmos con ese lenguaje en el futuro?

Si una computadora toma el control total del control del tráfico aéreo, ¿cómo será el algoritmo? ¿Cómo manejará los aterrizajes de emergencia y cómo manejará una pista paralela?

¿Por qué el problema de detención se considera no solucionable mientras manipulamos / negamos la respuesta nosotros mismos con la máquina N al final de la máquina X?

Dado que solo quedan 2 meses para las regiones regionales de ACM ICPC, ¿cuántos problemas podría resolver allí si comenzara a practicar ahora, teniendo solo la idea más básica sobre algoritmos?

¿Qué significa <K extiende comparables > en Java en el contexto de hacer árboles de búsqueda binarios?

¿Cómo puedo calcular de manera eficiente el número de intercambios requeridos por los métodos de ordenación lenta como la ordenación por inserción y la ordenación por burbujas para ordenar una matriz determinada?

¿Qué enunciado describe mejor por qué la notación Big-O es una forma muy útil de analizar la complejidad del algoritmo?

¿Podría haber un límite superior en la 'inteligencia' de una IA?

Encontré un problema algorítmico y no sé cómo resolverlo. ¿Alguien me puede ayudar?