Ajedrez (paso 9): creando un contrincante “inteligente” con una función de evaluación de estática

Cómo hacer que la máquina “piense” una jugada

Para que el ordenador pueda jugar al ajedrez razonablemente bien, debe empezar por ser capaz de distinguir las situaciones más beneficiosas (para él) de las que no lo son tanto. Por ejemplo, debe saber que tener un peón en el centro del tablero es mucho mejor que tenerlo en un lateral, o que la reina es mucho más valiosa que un caballo.

Para lograrlo existen varios métodos, pero el más simple es la función de evaluación estática. Se trata de una función matemática que, partiendo de las piezas que hay en el tablero y su ubicación, devuelve un valor numérico que determina la calidad de la situación para un jugador dado.

Supongamos que el ordenador juega con las negras. Si, cuando le toca su turno, la función devuelve, por ejemplo, el número 525, quiere decir que la situación actual del tablero es muy beneficiosa para las negras. Si la función devuelve, en cambio, un número más pequeño (próximo a cero), la situación es más o menos igual de buena para las negras y para las blancas. Si devuelve un número muy negativo (por ejemplo, -650), quiere decir que la situación es muy negativa para las negras, es decir, muy positiva para las blancas.

Obviamente, la función de evaluación debe funcionar exactamente al revés para el jugador blanco.

Gracias a la función de evaluación estática, el ordenador puede saber qué movimiento le resultará más beneficioso y, de esta manera, decidirse por un movimiento concreto de entre los muchos posibles.

Una función de evaluación sencilla

A continuación se muestra una función de evaluación sencilla y fácil de implementar. Con una función así, ningún programa de ajedrez se proclamará campeón del mundo, desde luego, pero sí que conseguiremos resultados bastante aceptables.

  • PEÓN
    • Por cada peón propio, sumar 100 puntos.
    • Si el peón está en el centro del tablero, añadir 12 puntos más
    • Añadir 2 puntos por cada casilla que haya avanzado el peón desde su punto de partida
  • CABALLO
    • Cada caballo propio suma 315 puntos
    • Añadir entre 0 y 15 puntos si está cerca del centro del tablero (más cuanto más cerca del centro)
    • Quitar entre 0 y 15 puntos si está lejos del centro (quitar más cuanto más lejos)
  • ALFIL
    • Cada alfil suma 330 puntos
    • Añadir un punto más por cada casilla a la que pueda moverse libremente (es decir, sin que se lo impida otra pieza que esté en medio)
  • TORRE
    • Cada torre suma 500 puntos
    • Añadir un punto más por cada casilla a la que pueda moverse libremente (es decir, sin que se lo impida otra pieza que esté en medio)
  • DAMA
    • La dama representa 940 puntos
    • Como en el caballo, añadir o quitar puntos (de 0 a 10) dependiendo de lo cerca o lejos que esté del centro del tablero

Todos estos puntos se calculan según las piezas del jugador al que le toca mover. Depués, se calculan del mismo modo para las piezas del jugador contrario, y ambas cantidades se restan. El resultado es lo que debe devolver la función de evaluación.

Un posible algoritmo para implementar esta función sería algo así:

total = 0;
para x desde 1 hasta 8
inicio
  para y desde 1 hasta 8
  inicio
    puntos_pieza = 0;
    según (tablero[x][y].pieza)
    inicio
       caso PEON: puntos_pieza = <<calcular puntos de este peón>>
       caso TORRE: puntos_pieza = <<calcular puntos de esta torre>>
       <<añadir el resto de piezas>>
    fin

    si (tablero[x][y].color_pieza == turno) // La pieza es nuestra
      total = total + puntos_pieza; // Sumamos los puntos al total
    si_no // La pieza NO es nuestra
      total = total - puntos_pieza; // Restamos los puntos al total
    fin (para)
  fin (para)
devolver(total);

La función se puede complicar tanto como queramos. En Internet y en muchos libros especializados sobre ajedrez puedes encontrar otras funciones de evaluación que pueden llegar a ser realmente complicadas. Sin embargo, hay que encontrar un compromiso entre exactitud y complejidad, porque si la función es excesivamente compleja tardará mucho tiempo en calcularse, y, por tanto, el programa tardará demasiado tiempo en “pensar” los movimientos (sobre todo cuando, en la fase 10, añadamos búsqueda recursiva a las rutinas de inteligencia)

Aplicación de la función al programa del ajedrez

Veamos ahora cómo puede el ordenador decidir su próximo movimiento.

Dada una posición cualquiera del tablero, el ordenador puede efectuar muchos movimientos diferentes. Se trata de que busque todos los movimientos que puede hacer, y evalúe, para cada uno de ellos, en qué situación le deja. Para la evaluación usará la función de evaluación estática que acabamos de ver, u otra parecida. Al final, escogerá el movimiento que le conduce a una situación en la que el valor devuelto por la función de evaluación es máximo.

Para buscar todos los movimientos, la máquina debe recorrer todas las casillas del tablero buscando piezas de su color. Para cada pieza que encuentre, volverá a recorrer el tablero, y, para cada casilla, probará a mover la pieza a dicha casilla. Usará las funciones de comprobación de movimientos (las hicimos en la fase 4) para comprobar si el movimiento es posible. Si el movimiento es posible, lo evaluará con la función de evaluación, quedándose con el máximo

Expresado algorítmicamente:

maximo = -infinito;
para ox desde 1 hasta 8
  inicio
  para oy desde 1 hasta 8
  inicio
    si (tablero[ox][oy].color_pieza == turno) // Hemos encontrado una pieza nuestra
    inicio
    para dx desde 1 hasta 8
    inicio
      para dy desde 1 hasta 8
      inicio
        si (es posible el movimiento de (ox,oy) a (dx,dy))
        inicio // Hemos encontrado un destino válido
          tablero2 = copiar_tablero(tablero); // Hacemos el movimiento
          realizar_movimiento (tablero2, ox, oy, dx, dy);
          puntos = evaluar_posicion (tablero2); // Evaluamos el resultado
          si (puntos > maximo)  // Es el mejor encontrado hasta ahora
          inicio
             maximo = puntos; // Guardamos el mejor movimiento
             mejor_ox = ox; mejor_oy = oy;
             mejor_dx = dx; mejor_dy = dy;
          fin
         fin (si)
       fin (para dy)
     fin (para dx)
   fin (si)
 fin (para oy)
fin (para ox)

Observe que el algoritmo realiza los movimientos sobre una copia del tablero (llamada aquí “tablero2″). Esto se hace para no modificar el tablero real, ya que estos movimientos no se están haciendo realmente, sino que sólo se están probando para ver cual es el mejor de todos ellos.

Al final del proceso, en el par (mejor_ox, mejor_oy) tendremos el origen del mejor movimiento posible, y en (mejor_dx, mejor_dy), el destino. El ordenador deberá realizar ese movimiento para llevar al tablero a la situación más conveniente para sus intereses.

Pero recuerde que esto no es suficiente para lograr un programa que juegue razonablemente bien al ajedrez. Procediendo de este modo, el ordenador sólo está mirando un movimiento más allá del estado actual del trablero y se comportará exactamente como lo que es: como un jugador con una lamentable cortedad de miras. En la siguiente (y última) fase de desarrollo daremos el salto definitivo para hacer de nuestro programa un jugador de ajedrez competente usando una técnica clásica de Inteligencia Artificia: Minimax.

Categorías

Licencia

ClustrMaps