El problema del Tres en raya

Aliaga Cornejo María Eugenia xmary17@gmail.com Pilares León Charming Catherine charm.cathpl@gmail.com

Universidad Nacional de San Agustin, Arequipa Escuela Profesional de Ingeniería de Sistemas

Resumen Los orígenes del Tres en Raya se remontan a hace mucho tiempo en un país del lejano Oriente. Muchos autores piensan que el Tres en Raya (el llamado Tic Tac Toe en inglés) se originó en China, como muchos otros juegos de mesa. El tres en raya es un «juego de lápiz y papel» entre dos jugadores: O y X, que marcan los espacios de un tablero de 3×3 alternadamente. Un jugador gana si consigue tener una línea de tres de sus símbolos: la línea puede ser horizontal, vertical o diagonal.

1.

INTRODUCCIÓN El juego Tres en raya ha sido propuesto desde sus inicios como un juego entre adversarios (dos jugadores) y con manejo de fichas o simplemente a lápiz y papel. Con el tiempo esta idea ha ido evolucionando y ha tenido un gran impacto con la computación, debido a que se trata de un problema de razonamiento y estrategia, y que puede ser utilizado como base introductoria a lo que es Inteligencia Artificial. El proceso de razonamiento va enfocado principalmente a que el jugador escoja la mejor jugada, debido a que el objeto de búsqueda es encontrar el camino desde un estado de punto de partida a un estado objetivo, lo que lo hace un problema algo complicado. Ante este problema es que se han planteado diferentes algoritmos para su resolución, siendo la mejor opción la utilización del algoritmo Minimax, el que será detallado y desarrollado en el transcurso de este documento.

2.

DEFINICIÓN DEL PROBLEMA El objetivo del tres en raya, es que dado dos jugadores: O y X, marquen los espacios de un tablero de 3×3 alternadamente hasta conseguir que un ganador gane, esto sucede cuando se consigue tener una línea de tres símbolos (el símbolo varía dependiendo del jugador) que puede ser horizontal, vertical o diagonal. Este problema tiene diferentes propuestas de solución, las que detallaremos a continuación:

Solución #1 Una primera solución directa a este juego podría ser la de almacenar en un vector las 19.693 (39) posibilidades de un tablero de 3 x 3 con tres valores posibles en cada casilla (vacío-X-O), así como las correspondientes jugadas sucesoras. Para realizar una jugada, bastaría con acceder a la posición del tablero actual y la jugada sucesora correspondiente.

1

necesita gran cantidad de memoria . y una estimación de la probabilidad de que esa jugada lleve a la victoria.alguien debe realizar el pesado trabajo de introducir todas las jugadas y sus sucesoras . Para decidir la siguiente jugada se tienen en cuenta las posiciones de tablero que resultan de cada movimiento posible. para así calcular si la partida ha finalizado o está aún en proceso. es superior a las demás soluciones pues podría ser ampliado para manipular juegos más complicados.Las desventajas de este eficiente programa son bastante obvias: . o no lo está. y un 2 . estado: GANADOR_JUEGO_O. el cual inspecciona secuencias de movimientos intentando maximizar la probabilidad de victoria. El tablero es representado como una lista de posiciones del tablero. donde las componentes del vector se corresponden con las posiciones del tablero de la siguiente forma: Figura 1: Representación del tablero También consideramos una variable que determine la posición dentro del vector para calcular si ese campo está vacío. 3. donde se puede relacionar con el siguiente movimiento. GANADOR_JUEGO_X. Solución #2 Considerando una estrategia para cada turno de jugador. PARAR. se analiza el posible triunfo a partir de un estado del tablero dado. Sin embargo. así como una lista de posiciones del tablero que podrían ser el próximo movimiento. tiene la ventaja que es más eficiente en términos de espacio. y en caso contrario considerando todos los movimientos que el oponente puede realizar asumiendo que éste elegirá el peor para nosotros. además también se han considerado otros estados propios del desarrollo del juego propiamente dicho: COMENZAR. en este caso fue construida una estructura vector. EMPATE. Su estrategia es más fácil de comprender y realizar cambios. MODELADO DEL PROBLEMA El problema del Tres en raya se enuncia inicialmente así: Empieza inicialmente con un tablero de 3x3 que es representado por un vector de nueve componentes. Solución #3 Implementar una estructura que contenga el tablero actual. Necesita mucho más tiempo que los demás. Esta última solución cuenta con un algoritmo. Se decide la posición que corresponde a la mejor jugada. JUGANDO. es decir. Para ello se define variables constantes que determinen el estado del juego. considerando si la jugada produce la victoria. aunque el programador debe comprender la totalidad de la estrategia de antemano. Aunque es menos eficiente que la solución anterior en términos de tiempo.el juego no se puede ampliar. por ejemplo a tres dimensiones. ya que debe realizar una búsqueda en una estructura de posibilidades antes de realizar cada movimiento.

número que representa una estimación de la probabilidad de que la jugada lleve a la victoria al jugador que mueve. y asignar la clasificación de mejor movimiento a la posición actual. Asumir que el oponente realizará este movimiento. se debe tener en cuenta las posiciones del tablero que resultarán de cada posible movimiento. Cualquier puesto que tenga la jugada. Si ocurre catalogarla como la mejor dándole el mejor puesto en la clasificación. se realiza para cada una de ellas la siguiente: Ver si se produce la victoria. realizar la jugada que corresponda a esa posición. El mejor nodo es el que resulte con un puesto más alto. de la siguiente manera: Figura 2: Evaluación jugada Y así determinamos que posibilidades existe para realizar la siguiente jugada. se realiza una evaluación por cada jugada. dependiendo de la posición de esta jugada inicial. asignarla al nodo que se está considerando. - Para determinar cómo son dadas las jugadas simulamos el compartimiento de cada una: Figura 3: Distribución de las jugadas 3 . considerar todos los posibles movimientos que el oponente puede realizar en la siguiente jugada. Para determinar cómo es evaluado cada nodo o estado de la jugada. Aplicación del Algoritmo Para poder decidir la siguiente jugada. En caso contrario. Para decidir cuál de todas las posibles posiciones es mejor. Decidir qué posición es la mejor.

Simbolo_actual = _jugador. char mejores_movimientos[9] = {0}. int indice = 0. int val = movimiento_Min(_tablero. Intenta maximizar la probabilidad de victoria. } _tablero[lista_movimientos. se ha determinado considerar las variables: +ORDENADOR y –ORDENADOR. lista_movimientos). como ya se había mencionado previamente es Minimax. lista_movimientos. if(val > mejor_valor) { mejor_valor = val. Heurística Para este problema. Una vez implementado el algoritmo queda de la siguiente manera: int MiniMax(char _tablero[9]. la estructura principal usada es un array de listas.El algoritmo utilizado. jugador _jugador) { int mejor_valor = -ORDENADOR. este algoritmo inspecciona varias secuencias de movimientos para encontrar aquella que lleva a la victoria.front()+1. } return mejores_movimientos[indice]. Como el juego es del tipo «Humano vs computadora». donde se almacenan las movidas realizadas por el jugador. 4 . generar_movimientos(_tablero. } En esta implementación del algoritmo Minimax. Con el fin de aplicar este algoritmo es necesario que se genere una lista de movimientos. indice = 0. que calculara si el computador está ganando o está perdiendo. } else if(val == mejor_valor) { mejores_movimientos[++indice]=lista_movimientos.empty()) { _tablero[lista_movimientos.front()]= _jugador. para luego determinar los resultados en base a las posiciones del tablero ocupadas.simbolo.front()]= 0. _jugador). se ha implementado en un lenguaje determinado para que pueda ser resuelto. debido a su facilidad en manipulación de objetos y estructuras. 4. RESOLUCIÓN En base al modelo presentado. El lenguaje de programación utilizado es C++ bajo Visual Studio. este recibe como parámetros un tablero definido con las 9 posiciones de posible juego descritas anteriormente y un jugador que realizara el movimiento respectivo.front()+ 1. o simplemente el valor 0 si se trata de un empate La variación entre el optimista. Se parte de la idea inicial de tener el tablero con sus respectivas posiciones. mediante la suposición de que el oponente intentará minimizar dicha probabilidad.simbolo.pop_front(). char mejores_movimientos[9] = {0}. /* Utilización de la estructura */ std::list<int> lista_movimientos. mejores_movimientos[indice]=lista_movimientos. las heurísticas utilizadas en este estudio fueron diseñados para ser simples. std::list<int> lista_movimientos. pesimista y heurística Indeciso desarrollado a partir del juego de tener que resolver el valor de una junta sin terminar / indecidible que resulta de un control de mayor profundidad para reducir el tiempo de búsqueda. se ha utilizado dos estructuras de datos para la representación del árbol formado por este algoritmo. Para la resolución de este problema se ha aplicado el algoritmo Minimax. while(!lista_movimientos.

jugador _jugador) { int estado_tablero = evaluar_estado_tablero(_tablero. _jugador). } } } Donde se muestra claramente que cada movimiento realizado es almacenado en la lista creada. std::list<int> lista_movimientos. Dicha función esta implementada como: void generar_movimientos(char _tablero[9].front()] = 0. if(val < mejor_valor) { mejor_valor = val. jugador _jugador) { int estado_tablero = evaluar_estado_tablero(_tablero. int val = movimiento_Max(_tablero. std::list<int> &lista_movimientos) { for(int i = 0. lista_movimientos. _jugador).empty()) { if(_jugador. lista_movimientos). _jugador).front()] = Simbolo_actual. if(estado_tablero != -1) { return estado_tablero. mediante la función propia del tipo lista: lista_movimientos. generar_movimientos(_tablero. if(estado_tablero != -1) { return estado_tablero. std::list<int> lista_movimientos. lista_movimientos. Por ello que se han implementado como otras funciones: // Encontrar el mejor movimiento para minimizar int movimiento_Min(char _tablero[9].En esta función se llama a la función: generar_movimientos(_tablero. } _tablero[lista_movimientos. i < 9.pop_front().pop_front().simbolo == 'X') Simbolo_actual = 'O' else Simbolo_actual = 'X'. if(val > mejor_valor) { mejor_valor = val.front()] = Simbolo_actual. int val = movimiento_Min(_tablero. lista_movimientos). lo que corresponde a calcular el mínimo y máximo valor. } 5 . } return mejor_valor. Esta función que representa el comportamiento del algoritmo Minimax.push_back(i).push_back(i).front()] = 0. } // Encontrar el mejor movimiento para maximizar int movimiento_Max(char _tablero[9]. } _tablero[lista_movimientos. while(!lista_movimientos. _tablero[lista_movimientos. lista_movimientos). esta implementada basándose en dos conceptos de este algoritmo. _tablero[lista_movimientos.simbolo == 'X') Simbolo_actual = 'X' else Simbolo_actual = 'O'. generar_movimientos(_tablero. ++i) { if(_tablero[i] == 0) { lista_movimientos. } return mejor_valor. } int mejor_valor = -ORDENADOR. } int mejor_valor = +ORDENADOR.empty()) { if (_jugador. while(!lista_movimientos. _jugador).

Martin Caplan. Minimax Search in Computers and Games. Hauk. MTU Department of Computer Science. es que se ha definido la función: int obtener_nivel_juego() Esta función determina el nivel de dificultad del juego. Schaeffer. 2006 [4] 6 . Using Heuristics to Evaluate the Playability of Games. 2004 Desconocido. Chapter 4 Heather Desurvire. REFERENCIAS [1] [2] [3] Martha Mercaldi.Para el cálculo de la profundidad del árbol generado por el algoritmo. Heuristic Search. Jozsef Toth. Katarzyna Wilamowska General Heuristic for Tic-Tac-Toe. este término especifica que tanto de entrenamiento se le ha asignado al computador para tal juego. Buro. Este código nos ejecutará el resultado para el juego de modo consola: Figura 4: Resultado de ejecución 5.

Sign up to vote on this title
UsefulNot useful