Escribí un juego de tic-tac-toe en Java, y mi método actual para determinar el final del juego tiene en cuenta los siguientes escenarios posibles para el final del juego:
- El tablero está lleno y aún no se ha declarado ningún ganador: el juego es un empate.
- Cross ha ganado.
- Circle ha ganado.
Desafortunadamente, para hacerlo, lee un conjunto predefinido de estos escenarios de una tabla. Esto no es necesariamente malo considerando que solo hay 9 espacios en un tablero y, por lo tanto, la mesa es algo pequeña, pero ¿hay una mejor forma algorítmica de determinar si el juego ha terminado? La determinación de si alguien ha ganado o no es el meollo del problema, ya que comprobar si 9 espacios están llenos es trivial.
El método de la tabla podría ser la solución, pero si no, ¿cuál es? Además, ¿y si el tablero no fuera de tamaño n=9
? ¿Y si fuera un tablero mucho más grande, por ejemplo n=16
, n=25
y así sucesivamente, haciendo que el número de artículos colocados consecutivamente a ganar para estar x=4
, x=5
etc? ¿Un algoritmo general para usar con todos n = { 9, 16, 25, 36 ... }
?
fuente
3
). Por lo tanto, puede realizar un seguimiento de los conteos de cada uno y solo comenzar a verificar las ganancias si son más altas.Respuestas:
Usted sabe que un movimiento ganador solo puede ocurrir después de que X u O hayan hecho su movimiento más reciente, por lo que solo puede buscar filas / columnas con diag opcionales que están contenidas en ese movimiento para limitar su espacio de búsqueda al intentar determinar una mesa ganadora. Además, dado que hay un número fijo de movimientos en un juego de tres en raya una vez que se realiza el último movimiento, si no fue un movimiento ganador, por defecto es un juego de empate.
editar: este código es para un tablero de n por n con n en una fila para ganar (el tablero 3x3 requiere 3 en una fila, etc.)
editar: código agregado para verificar el anti diag, no pude encontrar una forma sin bucle para determinar si el punto estaba en el anti diag, por eso falta ese paso
fuente
puedes usar un cuadrado mágico http://mathworld.wolfram.com/MagicSquare.html si alguna fila, columna o diag suma 15, entonces un jugador ha ganado.
fuente
¿Qué tal este pseudocódigo:
Después de que un jugador coloca una pieza en la posición (x, y):
Usaría una matriz de char [n, n], con O, X y espacio para vacío.
fuente
i==1
yn==3
, serdiag
debe marcar en(1, 3)
y(1, 3-1+1)
es igual a las coordenadas correctas, pero(1, 3-(1+1))
no.Esto es similar a la respuesta de Osama ALASSIRY , pero intercambia el espacio constante y el tiempo lineal por el espacio lineal y el tiempo constante. Es decir, no hay bucle después de la inicialización.
Inicialice un par
(0,0)
para cada fila, cada columna y las dos diagonales (diagonal y anti-diagonal). Estos pares representan el acumulado(sum,sum)
de las piezas en la fila, columna o diagonal correspondiente, dondeCuando un jugador coloca una pieza, actualiza el par de filas, el par de columnas y los pares diagonales correspondientes (si están en las diagonales). Si alguna fila, columna o par diagonal recién actualizado es igual a
(n,0)
o,(0,n)
entonces A o B ganaron, respectivamente.Análisis asintótico:
Para el uso de la memoria, usa
4*(n+1)
enteros.Ejercicio: ¿Puedes ver cómo probar un empate en O (1) tiempo por movimiento? Si es así, puede terminar el juego temprano en un empate.
fuente
O(sqrt(n))
tiempo, pero debe hacerse después de cada movimiento, donde n es el tamaño del tablero. Entonces terminas conO(n^1.5)
. Para esta solución, obtienesO(n)
tiempo en general.Aquí está mi solución que escribí para un proyecto en el que estoy trabajando en javascript. Si no le importa el costo de memoria de algunas matrices, probablemente sea la solución más rápida y sencilla que encontrará. Supone que conoces la posición del último movimiento.
fuente
Acabo de escribir esto para mi clase de programación C.
Lo estoy publicando porque ninguno de los otros ejemplos aquí funcionará con cualquier tamaño de cuadrícula rectangular y cualquier número de marcas consecutivas de N -en-fila para ganar.
Encontrarás mi algoritmo, tal como está, en la
checkWinner()
función. No usa números mágicos ni nada elegante para buscar un ganador, simplemente usa cuatro bucles for - El código está bien comentado, así que lo dejaré hablar por sí mismo, supongo.fuente
Si el tablero es n × n, entonces hay n filas, n columnas y 2 diagonales. Marque cada uno de ellos en busca de todas las X o todas las O para encontrar un ganador.
Si solo se necesitan x < n cuadrados consecutivos para ganar, entonces es un poco más complicado. La solución más obvia es verificar cada cuadrado de x × x en busca de un ganador. Aquí hay un código que lo demuestra.
(En realidad, no probé este * tos *, pero se compiló en el primer intento, ¡sí!)
fuente
No conozco muy bien Java, pero sí sé C, así que probé la idea del cuadrado mágico de adk (junto con la restricción de búsqueda de Hardwareguy ).
Compila y prueba bien.
¡Eso fue divertido, gracias!
En realidad, pensándolo bien, no necesitas un cuadrado mágico, solo un recuento para cada fila / columna / diagonal. Esto es un poco más fácil que generalizar un cuadrado mágico a matrices
n
×n
, ya que solo necesita contarn
.fuente
Me hicieron la misma pregunta en una de mis entrevistas. Mis pensamientos: Inicialice la matriz con 0. Mantenga 3 matrices 1) sum_row (tamaño n) 2) sum_column (tamaño n) 3) diagonal (tamaño 2)
Para cada movimiento de (X) disminuya el valor de la casilla en 1 y para cada movimiento de (0) increméntelo en 1. En cualquier momento si la fila / columna / diagonal que se ha modificado en el movimiento actual suma -3 o + 3 significa que alguien ha ganado el juego. Para un sorteo, podemos usar el enfoque anterior para mantener la variable moveCount.
¿Crees que me estoy perdiendo algo?
Editar: Lo mismo se puede usar para la matriz nxn. La suma debe ser incluso +3 o -3.
fuente
una forma sin bucle para determinar si el punto estaba en el anti diag:
fuente
Llegué tarde a la fiesta, pero quería señalar un beneficio que encontré al usar un cuadrado mágico , es decir, que se puede usar para obtener una referencia al cuadrado que causaría la victoria o la derrota en el siguiente turno, en lugar de solo se usa para calcular cuándo termina un juego.
Toma este cuadrado mágico:
Primero, configure una
scores
matriz que se incrementa cada vez que se realiza un movimiento. Consulte esta respuesta para obtener más detalles. Ahora, si jugamos ilegalmente X dos veces seguidas en [0,0] y [0,1], entonces lascores
matriz se ve así:Y el tablero se ve así:
Entonces, todo lo que tenemos que hacer para obtener una referencia a qué casilla ganar / bloquear es:
En realidad, la implementación requiere algunos trucos adicionales, como manejar claves numeradas (en JavaScript), pero lo encontré bastante sencillo y disfruté de las matemáticas recreativas.
fuente
Hice algunas optimizaciones en las verificaciones de filas, columnas y diagonales. Se decide principalmente en el primer ciclo anidado si necesitamos verificar una columna o diagonal en particular. Por lo tanto, evitamos revisar columnas o diagonales ahorrando tiempo. Esto tiene un gran impacto cuando el tamaño de la placa es mayor y un número significativo de celdas no está lleno.
Aquí está el código java para eso.
fuente
Me gusta este algoritmo porque utiliza una representación de la placa de 1x9 vs 3x3.
fuente
Otra opción: genera tu tabla con código. Hasta la simetría, solo hay tres formas de ganar: fila de borde, fila del medio o diagonal. Tome esos tres y gírelos de todas las formas posibles:
Estas simetrías pueden tener más usos en su código de juego: si llega a un tablero del que ya ha visto una versión rotada, puede simplemente tomar el valor almacenado en caché o el mejor movimiento almacenado en caché de ese (y anular la rotación). Esto suele ser mucho más rápido que evaluar el subárbol del juego.
(Girar hacia la izquierda y hacia la derecha puede ayudar de la misma manera; no fue necesario aquí porque el conjunto de rotaciones de los patrones ganadores es simétrico en espejo).
fuente
Aquí hay una solución que se me ocurrió, esto almacena los símbolos como caracteres y usa el valor int del char para averiguar si X u O han ganado (mire el código del árbitro)
También aquí están mis pruebas unitarias para validar que realmente funciona
Solución completa: https://github.com/nashjain/tictactoe/tree/master/java
fuente
¿Qué tal un siguiente enfoque para 9 ranuras? Declare 9 variables enteras para una matriz de 3x3 (a1, a2 .... a9) donde a1, a2, a3 representan fila-1 y a1, a4, a7 formarían la columna-1 (entiendes la idea). Utilice '1' para indicar Jugador-1 y '2' para indicar Jugador-2.
Hay 8 posibles combinaciones ganadoras: Win-1: a1 + a2 + a3 (la respuesta podría ser 3 o 6 según el jugador que ganó) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7
Ahora sabemos que si el jugador uno cruza a1, entonces necesitamos reevaluar la suma de 3 variables: Win-1, Win-4 y Win-7. Cualquiera que sea 'Win-?' las variables llega a 3 o 6 primero gana el juego. Si la variable Win-1 llega a 6 primero, el Jugador-2 gana.
Entiendo que esta solución no es escalable fácilmente.
fuente
Esta es una forma realmente sencilla de comprobarlo.
}
fuente
Si tiene un campo de frontera 5 * 5 por ejemplo, utilicé el siguiente método de verificación:
Creo que está más claro, pero probablemente no sea la forma más óptima.
fuente
Aquí está mi solución usando una matriz bidimensional:
fuente
Solución de tiempo constante, se ejecuta en O (8).
Almacene el estado de la placa como un número binario. El bit más pequeño (2 ^ 0) es la fila superior izquierda del tablero. Luego va hacia la derecha, luego hacia abajo.
ES DECIR
Cada jugador tiene su propio número binario para representar el estado (porque tic-tac-toe) tiene 3 estados (X, O y espacio en blanco) por lo que un solo número binario no funcionará para representar el estado del tablero para varios jugadores.
Por ejemplo, una tabla como:
Observe que los bits del jugador X están separados de los bits del jugador O, esto es obvio porque X no puede poner una pieza donde O tiene una pieza y viceversa.
Para comprobar si un jugador ha ganado, debemos comparar todas las posiciones cubiertas por ese jugador con una posición que sabemos que es una posición ganadora. En este caso, la forma más fácil de hacerlo sería mediante la opción AND de la posición del jugador y la posición ganadora y ver si las dos son iguales.
p.ej.
Nota:,
X & W = W
entonces X está en un estado de victoria.Esta es una solución de tiempo constante, depende solo del número de posiciones ganadoras, porque la aplicación de la puerta AND es una operación de tiempo constante y el número de posiciones ganadoras es finito.
También simplifica la tarea de enumerar todos los estados válidos de la placa, solo todos los números representables por 9 bits. Pero, por supuesto, necesita una condición adicional para garantizar que un número es un estado de placa válido (por ejemplo,
0b111111111
es un número válido de 9 bits, pero no es un estado de placa válido porque X acaba de realizar todos los turnos).El número de posibles posiciones ganadoras se puede generar sobre la marcha, pero aquí están de todos modos.
Para enumerar todas las posiciones de la placa, puede ejecutar el siguiente ciclo. Aunque dejaré a otra persona para determinar si un número es un estado válido de la junta.
NOTA: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)
fuente
No estoy seguro de si este enfoque está publicado todavía. Esto debería funcionar para cualquier tablero m * n y se supone que un jugador debe ocupar la posición consecutiva de " pospuestos ". La idea se basa en ejecutar la ventana.
fuente
Una vez desarrollé un algoritmo para esto como parte de un proyecto científico.
Básicamente, divide el tablero de forma recursiva en un montón de rectos 2x2 superpuestos, probando las diferentes combinaciones posibles para ganar en un cuadrado 2x2.
Es lento, pero tiene la ventaja de funcionar en placas de cualquier tamaño, con requisitos de memoria bastante lineales.
Ojalá pudiera encontrar mi implementación
fuente