¿Qué algoritmo para un juego de tres en raya puedo usar para determinar el "mejor movimiento" para la IA?

votos
57

En una implementación de tres en raya creo que la parte más difícil es determinar el mejor movimiento para que la máquina juegue.

¿Cuáles son los algoritmos que se pueden perseguir? Estoy buscando implementaciones desde simples a complejas. ¿Cómo abordaría esta parte del problema?

Publicado el 24/09/2008 a las 04:26
fuente por usuario
En otros idiomas...                            


10 respuestas

votos
0

Clasifica cada uno de los cuadrados con puntajes numéricos. Si se toma un cuadrado, avance a la siguiente opción (ordenada en orden descendente por rango). Vas a tener que elegir una estrategia (hay dos principales para ir primero y tres (creo) para el segundo). Técnicamente, podría simplemente programar todas las estrategias y luego elegir una al azar. Eso haría un oponente menos predecible.

Respondida el 24/09/2008 a las 04:28
fuente por usuario

votos
14

El método de fuerza bruta de generar cada tablero posible y calificarlo basado en las tablas que luego produce más abajo no requiere mucha memoria, especialmente una vez que reconoces que las rotaciones de 90 grados son redundantes, como lo son las vueltas verticales, eje horizontal y diagonal.

Una vez que llegas a ese punto, hay algo así como menos de 1k de datos en un gráfico de árbol para describir el resultado y, por lo tanto, el mejor movimiento para la computadora.

-Adán

Respondida el 24/09/2008 a las 04:31
fuente por usuario

votos
6

Ya que solo está tratando con una matriz de 3x3 de posibles ubicaciones, sería muy fácil simplemente escribir una búsqueda a través de todas las posibilidades sin gravar su poder de cómputo. Para cada espacio abierto, calcule a través de todos los resultados posibles después de marcar ese espacio (recursivamente, diría), luego use el movimiento con la mayor cantidad de posibilidades de ganar.

Optimizar esto sería una pérdida de esfuerzo, de verdad. Aunque algunos más fáciles podrían ser:

  • Verifique primero las posibles victorias para el otro equipo, bloquee la primera que encuentre (si hay dos juegos de todos modos).
  • Siempre toma el centro si está abierto (y la regla anterior no tiene candidatos).
  • Tome las esquinas por delante de los lados (de nuevo, si las reglas anteriores están vacías)
Respondida el 24/09/2008 a las 04:33
fuente por usuario

votos
3

Puede hacer que la IA se reproduzca en algunos juegos de muestra para aprender. Use un algoritmo de aprendizaje supervisado para ayudarlo.

Respondida el 24/09/2008 a las 04:37
fuente por usuario

votos
53

La estrategia de Wikipedia para jugar un juego perfecto (ganar o empatar todas las veces) parece ser un pseudocódigo directo:

Cita de Wikipedia (Estrategia Tic Tac Toe #)

Un jugador puede jugar un juego perfecto de Tic-tac-toe (para ganar o, al menos, dibujar) si elige el primer movimiento disponible de la siguiente lista, cada turno, como se usa en Newell y Simon en 1972 tic-tac-toe programa. [6]

  1. Ganar: si tienes dos en una fila, juega el tercero para obtener tres en una fila.

  2. Bloque: si el oponente tiene dos seguidos, juega el tercero para bloquearlos.

  3. Tenedor: crea una oportunidad donde puedas ganar de dos maneras.

  4. Bloque del tenedor del oponente:

    Opción 1: crea dos en una fila para forzar al oponente a defenderse, siempre y cuando no resulte en que creen un tenedor o ganen. Por ejemplo, si "X" tiene una esquina, "O" tiene el centro, y "X" también tiene la esquina opuesta, "O" no debe jugar en una esquina para ganar. (Jugar en una esquina en este escenario crea una bifurcación para que "X" gane).

    Opción 2: si hay una configuración donde el oponente puede bifurcar, bloquea esa bifurcación.

  5. Centro: Juega el centro.

  6. Esquina opuesta: si el oponente está en la esquina, juega en la esquina opuesta.

  7. Esquina vacía: juegue una esquina vacía.

  8. Lado vacío: juega un lado vacío.

Reconocer cómo se ve una situación de "tenedor" podría hacerse de una manera bruta como se sugiere.

Nota: Un oponente "perfecto" es un buen ejercicio pero no vale la pena jugar contra él. Sin embargo, podría alterar las prioridades anteriores para dar debilidades características a las personalidades adversarias.

Respondida el 24/09/2008 a las 04:43
fuente por usuario

votos
35

Lo que necesitas (para tic-tac-toe o un juego mucho más difícil como el ajedrez) es el algoritmo minimax , o su variante ligeramente más complicada, la poda alfa-beta . Sin embargo, el minimax ingenuo ordinario funcionará bien para un juego con un espacio de búsqueda tan pequeño como el tic-tac-toe.

En pocas palabras, lo que quiere hacer no es buscar el movimiento que tenga el mejor resultado posible para usted, sino más bien el movimiento donde el peor resultado posible sea lo mejor posible. Si asumes que tu oponente está jugando de manera óptima, debes asumir que tomarán el movimiento que sea peor para ti y, por lo tanto, debes realizar el movimiento que minimice su ganancia máxima.

Respondida el 24/09/2008 a las 06:40
fuente por usuario

votos
3

Un intento sin necesidad de utilizar un campo de juego.

  1. para ganar (el doble)
  2. si no, no perder (rival doble)
  3. si no, es lo que ya tienen un tenedor (tiene un doble doble)
  4. en caso contrario, si el oponente tiene un tenedor
    1. Búsqueda en el bloqueo de puntos para su posible (victoria definitiva) doble y tenedor
    2. si no es buscar horquillas en puntos de bloqueo (que da el oponente las posibilidades más perdedoras)
    3. si no sólo los puntos de bloqueo (no perder)
  5. Si no busca doble y tenedor (victoria final)
  6. Si no busca solamente para tenedores que da oponente las posibilidades más perdedoras
  7. Si no busca solamente para un doble
  8. si no es callejón sin salida, lazo, al azar.
  9. Si no es así (esto significa que su primer movimiento)
    1. si es la primera jugada del juego;
      1. dar al oponente la posibilidad más perdedor (los resultados del algoritmo en sólo esquinas que da posibilidad punto 7 perdida para oponente)
      2. o para romper el aburrimiento simplemente al azar.
    2. si es segundo movimiento del juego;
      1. encontrar sólo los puntos no perder (da un poco más opciones)
      2. o encontrar los puntos de esta lista que tiene la mejor oportunidad de ganar (que puede ser aburrido, porque solamente resulta en todos los rincones o esquinas adyacentes o centro)

Nota: Cuando se tiene doble y horquillas, comprobar si su doble le da al oponente una double.if da, compruebe si que su nuevo punto obligatorio está incluido en la lista de tenedor.

Respondida el 19/06/2012 a las 13:41
fuente por usuario

votos
7

Un algo típico de tic-tac-dedo del pie debe tener este aspecto:

Junta: Un vector de nueve elementos en representación de la junta. Almacenamos 2 (indicando espacio en blanco), 3 (indicando X), o 5 (indicando O). A su vez: Un entero que indica que se mueven del juego a punto de ser jugado. Primera medida será indicada por 1, pasado por 9.

el Algoritmo

El algoritmo utiliza tres funciones principales.

Make2: rendimientos 5 si la plaza del centro de la junta es decir, en blanco si la junta [5] = 2. De lo contrario, esta función devuelve cualquier cuadrado no esquina (2,4,6 o 8).

Posswin (p): devuelve 0 si el jugador p no puede ganar en su siguiente movimiento; de lo contrario, devuelve el número de cuadrados que constituye una jugada ganadora. Esta función permitirá a los programas tanto para ganar y para bloquear oponentes ganan. Esta función se activa mediante la comprobación de cada una de las filas, columnas y diagonales. Al multiplicar los valores de su plaza juntos por toda una fila (o columna o diagonal), la posible situación de victoria se puede comprobar. Si ser el producto 18 (3 x 3 x 2), entonces X puede ganar. Si el producto es 50 (5 x 5 x 2), entonces O puede ganar. Si se encuentra una fila ganadora (columna o diagonal), el cuadrado blanco en ella se puede disuadir extraído y el número de ese cuadrado es devuelto por esta función.

Ir (n): hace un movimiento en la plaza n. esta placa de conjuntos de procedimiento [n] a 3 si la curva es impar, o 5 si la curva es par. También incrementa a su vez por uno.

El algoritmo tiene una estrategia integrada para cada movimiento. Se hace el movimiento impar si juega X, el movimiento de número par si juega O.

Turn =1 Go(1)   (upper left corner).
Turn =2 If Board[5] is blank, Go(5), else Go(1).
Turn =3 If Board[9] is blank, Go(9), else Go(3).
Turn =4 If Posswin(X) is not 0, then Go(Posswin(X)) i.e. [ block opponent’s win], else Go(Make2).
Turn =5 if Posswin(X) is not 0 then Go(Posswin(X)) [i.e. win], else if Posswin(O) is not 0, then Go(Posswin(O)) [i.e. block win], else if Board[7] is blank, then Go(7), else Go(3). [to explore other possibility if there be any ].
Turn =6 If Posswin(O) is not 0 then Go(Posswin(O)), else if Posswin(X) is not 0, then Go(Posswin(X)), else Go(Make2).
Turn =7 If Posswin(X) is not 0 then Go(Posswin(X)), else if Posswin(X) is not 0, then Go(Posswin(O)) else go anywhere that is blank.
Turn =8 if Posswin(O) is not 0 then Go(Posswin(O)), else if Posswin(X) is not 0, then Go(Posswin(X)), else go anywhere that is blank.
Turn =9 Same as Turn=7.

Lo he usado. Que me haga saber cómo se sienten ustedes.

Respondida el 13/07/2012 a las 17:16
fuente por usuario

votos
0

Esta respuesta supone que entendemos implementar el algoritmo perfecto para P1 y discute cómo lograr una victoria en condiciones contra jugadores humanos comunes, que van a hacer algunos errores con más frecuencia que otros.

El juego, por supuesto, debe terminar en un empate si ambos jugadores juegan de manera óptima. A nivel humano, P1 jugando en una esquina produce gana mucho más a menudo. Por alguna razón psicológica, P2 se ceba en el pensamiento de que jugar en el centro no es tan importante, que es desafortunado para ellos, ya que es la única respuesta que no crea un juego de ganar para P1.

Si P2 no bloquear correctamente en el centro, P1 debe desempeñar la esquina opuesta, porque una vez más, por la razón que sea psicológica, P2 preferirá la simetría de jugar una esquina, que a su vez produce un tablero perdedora para ellos.

Para cualquier movimiento P1 puede hacer para iniciar el movimiento, hay una P2 movimiento puede hacer que creará una victoria para P1 si ambos jugadores juegan de manera óptima a partir de entonces. En ese sentido P1 puede jugar donde sea. Los bordes movimientos son más débiles en el sentido de que la mayor fracción de las posibles respuestas a este movimiento produjo un empate, pero todavía hay respuestas que crearán una victoria para P1.

Empíricamente (más precisamente, anecdóticamente) los mejores movimientos de partida P1 parecen ser primera curva, segundo centro, y el último flanco.

El siguiente reto puede añadir, en persona o por medio de una interfaz gráfica de usuario, no es para mostrar el tablero. Sin duda, un humano puede recordar todo el estado, pero el reto añadido conduce a una preferencia para los tableros simétricos, que tienen un menor esfuerzo de recordar, lo que lleva a la equivocación he descrito en la primera rama.

Soy un montón de diversión en las fiestas, lo sé.

Respondida el 12/04/2016 a las 13:20
fuente por usuario

votos
0

Aquí es una solución que tenga en cuenta todos los movimientos posibles, y las consecuencias de cada movimiento para determinar el mejor movimiento posible.

vamos a necesitar una stracture de datos que representa la junta. Vamos a representar el tablero con una matriz de dos dimensiones. La matriz exterior representa toda la junta, y una matriz interior representa una fila. Este es el estado de un tablero vacío.

_gameBoard: [
    [“”, “”, “”],
    [“”, “”, “”],
    [“”, “”, “”]
]

Vamos a instalar en la placa con caracteres 'x' y 'o'.

A continuación vamos a necesitar una función que se puede comprobar el para el resultado. La función comprobará si hay una sucesión de caracteres. Cualquiera que sea el estado de la junta es, el resultado es una de las 4 opciones: o incompleto, el jugador X won, jugador ganó O o un empate. La función debe devolver el cual es el estado de la junta.

const SYMBOLS = {
  x:'X',
  o:'O'
}
const RESULT = {
  incomplete: 0,
  playerXWon: SYMBOLS.x,
  playerOWon: SYMBOLS.o,
  tie: 3
}
  function getResult(board){
      // returns an object with the result

      let result = RESULT.incomplete
      if (moveCount(board)<5){
        {result}
      }

      function succession (line){
        return (line === symbol.repeat(3))
      }

      let line

      //first we check row, then column, then diagonal
      for (var i = 0 ; i<3 ; i++){
        line = board[i].join('')
        if(succession(line)){
          result = symbol;
          return {result};
        }
      }

      for (var j=0 ; j<3; j++){
        let column = [board[0][j],board[1][j],board[2][j]]
        line = column.join('')
        if(succession(line)){
          result = symbol
          return {result};
        }
      }

      let diag1 = [board[0][0],board[1][1],board[2][2]]
      line = diag1.join('')
      if(succession(line)){
        result = symbol
        return {result};
      }

      let diag2 = [board[0][2],board[1][1],board[2][0]]
      line = diag2.join('')
      if(succession(line)){
        result = symbol
        return {result};
      }

      //Check for tie
      if (moveCount(board)==9){
        result=RESULT.tie
        return {result}
      }

      return {result}
    }

Ahora podemos añadir la función getBestMove, proporcionamos cualquier tablero dado, y el siguiente símbolo, la función se ejecutará comprobar todos los movimientos posibles con la función getResult. Si se trata de una victoria que le dan una puntuación de 1. Si se trata de una floja obtendrá una puntuación de -1, un empate obtendrá una puntuación de 0. Si es indeterminada vamos a la función recursiva getBestMove de averiguar la la puntuación de la siguiente movimiento. Dado que el próximo movimiento del oponente, su victoria es la pérdida de jugador actual, y será negada la partitura. Al final posible traspaso recibe una puntuación de 1,0, ya sea o -1, podemos clasificar los movimientos, y devolver el movimiento con la puntuación más alta.

  function getBestMove (board, symbol){

    function copyBoard(board) {
      let copy = []
       for (let row = 0 ; row<3 ; row++){
        copy.push([])
        for (let column = 0 ; column<3 ; column++){
          copy[row][column] = board[row][column]
        }
      }
      return copy
    }

    function getAvailableMoves (board) {
      let availableMoves = []
      for (let row = 0 ; row<3 ; row++){
        for (let column = 0 ; column<3 ; column++){
          if (board[row][column]===""){
            availableMoves.push({row, column})
          }
        }
      }
      return availableMoves
    }

    let availableMoves = getAvailableMoves(board)

    let availableMovesAndScores = []

    for (var i=0 ; i<availableMoves.length ; i++){
      let move = availableMoves[i]
      let newBoard = copyBoard(board)
      newBoard = applyMove(newBoard,move, symbol)
      result = getResult(newBoard,symbol).result
      let score
      if (result == RESULT.tie) {score = 0}
      else if (result == symbol) {
        score = 1
      }
      else {
        let otherSymbol = (symbol==SYMBOLS.x)? SYMBOLS.o : SYMBOLS.x
        nextMove = getBestMove(newBoard, otherSymbol)
        score = - (nextMove.score)
      }
      if(score === 1)
        return {move, score}
      availableMovesAndScores.push({move, score})
    }

    availableMovesAndScores.sort((moveA, moveB )=>{
        return moveB.score - moveA.score
      })
    return availableMovesAndScores[0]
  }

Algoritmo en acción , Github , explicando el proceso en más detalles

Respondida el 06/01/2018 a las 09:15
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more