Nim sum (
Estados perdedores:
Estados ganadores:
Nim sum:
10: 1010
12: 1100
5: 0101
----------
3: 0011
Movimiento óptimo:
9: 1001
12: 1100
5: 0101
----------
0: 0000
Estado | Nim Sum ( |
Resultado | Comentario |
---|---|---|---|
Ganador | Hay un movimiento que lleva al estado perdedor: cambiar |
||
Perdedor | Ningún movimiento puede evitar que el oponente gane si juega óptimamente. | ||
Perdedor | Estado final del juego, no hay movimientos posibles. |
El teorema de Sprague-Grundy generaliza la estrategia utilizada en el nim a todos los juegos que cumplan los siguientes requisitos:
Los jugadores tienen información completa sobre los estados y los movimientos permitidos y no hay aleatoriedad en el juego.
La idea es calcular para cada estado del juego un número de Grundy que corresponda al número de palitos en un montón de nim. Cuando conocemos los números de Grundy de todos los estados, podemos jugar el juego como el juego del nim.
El número de Grundy para un estado de juego se define como:
Grundy(state) = mex({g1, g2, ..., gn})
Donde:
mex
(Minimum EXcluded) da el menor número no negativo que no está en el conjunto.mex({0, 1, 3}) = 2
, ya que 2 es el menor número no negativo que no pertenece al conjunto, Si no hay movimientos posibles desde un estado (es un estado perdedor), su número de Grundy es 0 mex(Ø) = 0
Los números de Grundy son los siguientes:
Estado perdedor:
Estado ganador:
considere un juego en el que los jugadores mueven una figura en un laberinto.
La siguiente imagen muestra un posible estado inicial del juego, donde @
denota la figura y #
denota un cuadrado donde se puede mover.
Los estados del juego son todos los cuadrados del suelo del laberinto. En el laberinto anterior, los números de Grundy son los siguientes:
En el juego del laberinto, cada estado equivale a un montón (heap) en el juego Nim, y su número de Grundy determina si es un estado ganador o perdedor. Por ejemplo, el cuadrado inferior derecho, con un número de Grundy de 2, es un estado ganador desde el cual es posible llegar a un estado perdedor y asegurar la victoria. A diferencia de Nim, se puede mover a estados con un número de Grundy mayor, pero el oponente siempre podrá contrarrestar esos movimientos, asegurando que desde un estado perdedor no es posible escapar.
El algoritmo Minimax es una técnica de retroceso usada en la teoría de juegos y en la toma de decisiones para encontrar el movimiento óptimo en un juego de dos jugadores. Es ampliamente utilizado en juegos como Tic-Tac-Toe, Backgammon, Mancala, Chess, entre otros.
Los caminos para alcanzar estos estados parten de la raíz hacia las 4 hojas de un árbol binario perfecto. Supongamos que eres el jugador maximizador y tienes el primer turno (es decir, estás en la raíz del árbol). Tu oponente, el minimizador, juega en el siguiente nivel. ¿Qué movimiento harías como jugador maximizador, considerando que tu oponente también juega de forma óptima?
Dado que este es un algoritmo basado en retroceso (backtracking), evalúa todos los movimientos posibles, retrocede y luego toma una decisión.
Siendo el maximizador, elegirías el valor más alto, que es 3. Por lo tanto, el movimiento óptimo para el maximizador es ir a la IZQUIERDA, y el valor óptimo es 3.
Ahora, el árbol de juego se ve como se muestra a continuación:
Observaciones:
- Aunque en el subárbol derecho hay un valor más alto (9), el minimizador nunca lo seleccionará, ya que juega de forma óptima y elegirá el menor valor (2).
- El maximizador siempre debe considerar que su oponente jugará de manera ideal.
Devuelve el valor óptimo que el maximizador puede obtener. depth
es la profundidad actual en el árbol de juego. nodeIndex
es el índice del nodo actual en scores[]
. isMax
es verdadero si el movimiento actual es del maximizador, de lo contrario, es falso. scores[]
almacena las hojas del árbol de juego. h
es la altura máxima.
int minimax(int depth, int nodeIndex, bool isMax, vector<int> scores, int h) {
// Condición de terminación: se alcanza un nodo hoja
if (depth == h) return scores[nodeIndex];
// Si el movimiento actual es del maximizador, encuentra el valor máximo alcanzable
if (isMax)
return max(minimax(depth + 1, nodeIndex * 2, false, scores, h),
minimax(depth + 1, nodeIndex * 2 + 1, false, scores, h));
// Si el movimiento actual es del minimizador, encuentra el valor mínimo alcanzable
else
return min(minimax(depth + 1, nodeIndex * 2, true, scores, h),
minimax(depth + 1, nodeIndex * 2 + 1, true, scores, h));
}
int main() {
// la cantidad de elementos debe ser par
vector<int> scores = {3, 5, 2, 9, 12, 5, 23, 23};
int n = scores.size();
int h = log2(n);
int res = minimax(0, 0, true, scores, h);
cout << "El valor optimo es: " << res << endl;
return 0;
}
Explica o que expliquen los estados de: - Tic Tac Toe (GATO): los jugadores se turnan para colocar X u O en una cuadrícula de 3x3 hasta que un jugador consiga tres en fila horizontal, vertical o diagonal, o hasta que se llenen los espacios. - Piedra-Papel-Tijera: cada jugador elige simultáneamente una de tres opciones (piedra, papel, tijera). El ganador se determina mediante un conjunto de reglas: piedra vence a tijera, tijera vence a papel y papel vence a piedra.