Todos conocemos el ajedrez, aunque no todos sepamos jugar o conozcamos todas sus reglas. Partiendo de que conocemos el juego podemos decir que el ajedrez es un juego de información completa ya que antes de empezar a jugar ya conocemos reglas y disposición del escenario de juego, el tablero de 8×8, conocemos todo lo que se puede hacer!!!

Pero el objetivo de este articulo va más allá de conocer el ajedrez, si no en entender como una máquina puede aprender a jugar, y no solo a jugar, si no que lo haga bien y con cierta estrategia. Para esto debemos saber que el juego de ajedrez se inicia con las blancas donde eligen uno entre los 20 movimientos posibles (legales). Siguiendo este movimiento de las blancas, las negras tienen 20 opciones de respuesta a cualquiera que haya sido la elección de las blancas.

De acuerdo a esto, 400 son las posiciones distintas que pueden surgir sólo de la primera jugada de una partida de ajedrez. Después de 2 jugadas el número de posiciones posibles crece sobre las 20.000, llegando a una cifra astronómica después de varias jugadas más. El «árbol de ajedrez» posee más posiciones que la cantidad de átomos presentes en la via láctea.

De acuerdo al reglamento del juego una partida es tablas (empate) si transcurren 50 jugadas sin que se haya realizado algún movimiento de peón y sin que se haya producido algún cambio de pieza, por lo cual según algunos cálculos una partida de ajedrez puede durar como máximo 3150 jugadas, por lo que el árbol de variantes puede tener una cantidad de posiciones limitada a esta cantidad.

Si fuese posible examinar el árbol por completo, buscando todas las líneas de juego y sus conclusiones sería posible determinar cuál es el mejor movimiento inicial. Sin embargo, en la práctica el árbol es demasiado extenso para considerar este mecanismo. Incluso el determinar el mejor movimiento buscando en el árbol de variantes a partir de una avanzada posición de medio juego resulta imposible.

En los inicios de 1970 la búsqueda en árboles de variantes alcanzaba la cantidad aproximada de 200 posiciones por segundo. En el año 2003, DEEP BLUE buscaba en 2.000.000 de posiciones por segundo.

IBM

Una vez explicadas las bases de como se puede desarrollar el algoritmo de este juego, podemos mostrar el objetivo que se se trabajo en esta actividad.

CHESSAI

El objetivo es crear un programa en Python donde una máquina jugará contra si misma. Se basará en mejorar la aleatoriedad de la cual se parte con la lista de movimientos permitidos en el escenario inicial.

Con esta actividad se intenta acercar al alumno a ese concepto llamado «Aprendizaje por Refuerzo», es decir entrenar a un «agente» (nuestras piezas blancas) a que sea capaz de interactuar de forma inteligente sobre un entorno (nuestro tablero de ajedrez). Esta rama de la Inteligencia Artificial es compleja y avanzada para tratarlas en edades escolares, por lo tanto una de mis misiones fue descender su nivel de complejidad en varias partes.

Entendemos que aprender a partir de la interacción de «prueba y error» es una idea fundamental y común a todas la teorías del aprendizaje y la inteligencia.

Por lo tanto lo que trabajermos con esta actividad será la computación simbólica, interactuar con un escenario de pruebas (demo) y ver hasta donde podemos llegar en ese arbol de decisión que hemos visto al inicio de este articulo.

En el caso que abordaremos aquí, crearemos un jugador de ajedrez que mejore la aleatoriedad de movimientos legales mirando en 1 o dos niveles más bajo del árbol, de esta manera será recompensado bajo unos criterios de decisión y tendrá la capacidad de elegir el mejor movimiento evitando la aleatoriedad de su contrincante (piezas negras), el cual seguirá jugando toda la partida con movimientos aleatorios.

Descendemos la complejidad del aprendizaje por refuerzo a la decisión basada en recompensa

Empezamos

Para esta actividad usaremos Python 3 en un Google Colab o directamente puede programarse sobre un Jupyter en local.

Necesitamos instalar la libreria python-chess ya que Google Colab no la tiene en su Core

!pip install chess
import chess

Usaremos la biblioteca de ajedrez de la siguiente manera:

  1. Cree una instancia de chess.Board
  2. La instancia de chess.Board genera automáticamente todos los movimientos posibles para el jugador actual
  3. El jugador actual elige un movimiento
  4. Vaya al paso 2 y repita hasta ganar, perder o empatar.

Hemos reducido el hecho de jugar una partida de ajedrez válida a simplemente seleccionar un movimiento en cada turno. Para jugar una buena partida de ajedrez, querrás elegir «la mejor jugada» en cada turno.

Un jugador será una función que toma una instancia del tablero como argumento y devuelve un movimiento codificado como una cadena en el formato de la Interfaz Universal de Ajedrez (uci)


def player(board):
    ### ¿Que puede ocurrir aqui dentro?
    return move_code

El tablero

La clase Board realiza un seguimiento de quién es el turno, posibles movimientos y un historial de todos los movimientos pasados. Esta clase puede deshacer y rehacer movimientos y realiza un seguimiento de los estados repetidos.

Primero, creamos una instancia de placa:

board = chess.Board()

El valor de board.turn es un booleano que indica de quién es el turno. Los valores son Verdadero para el blanco o Falso para el negro.

El juego siempre comienza con el turno de las blancas. Si olvidas cuál es Verdadero, puedes preguntar al módulo de ajedrez:

board.turn == chess.WHITE

La clase chess.Board se encarga de generar todos los movimientos posibles, de quién es el turno, realizar un seguimiento de la colocación de todas las piezas y realizar cada movimiento. El tablero de ajedrez representa una matriz bidimensional de 8 x 8.

Aquí hay una representación visual de un tablero de ajedrez:

board

El tablero bidimensional está dispuesto de modo que cada posición se indique mediante una letra de columna y un número de fila. Sin embargo, la representación interna es secuencial. Digamos que queríamos ver qué había en la ubicación ‘c1’ que podíamos usar:

chess.G1

Para obtener la ubicación interna de la columna / fila

board.piece_at(chess.D1)

En forma de caracter

str(board.piece_at(chess.A8))

Hacer movimientos

Con este comando se pueden generar todos los movimientos legales posibles. Esto se guadará en una lista con todos ellos.

list(board.legal_moves)

Por ejemplo podemos obtener el primer movimiento, igual que cuando recorremos una lista o un array

move = list(board.legal_moves)[0]
Universal Chess Interface

Universal Chess Interface (uci) es la representación de un movimeinto de una casilla a otra.

move.uci()

Generar movimientos aleatorios

Importamos la libreria ramdom y creamos una función donde le pasamos el tablero board

import random

def random_player(board):
    move = random.choice(list(board.legal_moves))
    return move.uci()

#Sacar una jugada posible al azar
random_player(board)

Para realizar un movimiento usamos la función board.push_uciy le pasamos como parámetro el movimiento a realizar. En este caso le pasamos nuestra función que genera movimientos aleatorios random_player()

#Realizar el movimiento
board.push_uci(random_player(board))

#Mostrar el tablero con un mensaje 
print("El Próximo movimiento podría ser: " + random_player(board))
board
Comandos del Juego
  • board.move_stack Devuelve una lista de los movimientos realziados en la partida
  • board.peek()Devuelve el ultimo movimiento realizado
  • board.pop()Restaura en el tablero el ultimo movimiento en move_statck
  • board.push()Coloca una figura en cualquier posición (no atiende a reglas)
  • board.reset_board() Resetea el tablero
  • board.copy()Hacer una copia del tablero en otra variable
Propiedades Movimientos
  • Devuelve un boolean si esa puede ser atacada (parametros: color y posición)
    board.is_attacked_by(chess.BLACK,chess.A4)
  • Devuelve tablero ASCII de la posición del atacante (parametros: color y posición)
    board.attackers(chess.BLACK,chess.F4)
  • Devuelve la posición del Rey (parametro: color)
    board.king(chess.WHITE)
  • Devuelve color del turno
    print(who(board.turn))
  • Devuelve el valor ASCII (numérico) de la figura. Ejemplo peón
    chess.PAWN
  • Devuelve una lista de donde están situadas las piezas. Parametros: Figura y color
    list(board.pieces(chess.PAWN, chess.BLACK))
  • Distancia en pasos de una casilla a otra (Devuelve un int).
    chess.square_distance(chess.A1, chess.F1)
  • Copiar el tablero y las jugadas
    newboard=board.copy()
Verificar
  • Verifica si la posición actual es Mate (devuelve Boolean)
    board.is_checkmate()
  • Verifica si la posición actual es situación de ahogamiento (devuelve Boolean)
    board.is_stalemate()
  • Desde el 1 de julio de 2014, un juego se empata automáticamente (sin un reclamo por parte de uno de los jugadores) si se produce una posición por quinta vez.(devuelve Boolean)
    board.is_fivefold_repetition()
  • Comprueba si el color no tiene suficientes figuras para ganar. Se garantiza que se devolverá False si el color aún puede ganar el juego.
    board.is_insufficient_material()
  • Comprueba si el jugador que se mueve puede reclamar un empate por la regla de los cincuenta movimientos o por repetición triple. Tenga en cuenta que comprobar este último puede ser lento.
    board.can_claim_draw()
  • Comprueba si la posición actual se ha repetido 3 (o un número determinado de) veces.
    board.is_repetition (count: int = 3)
  • Muestra el resultado una vez que el juego ha terminado
    board.result()
  • Comprueba si el juego ha terminado debido a jaque mate, estancamiento, material insuficiente, la regla de los setenta y cinco movimientos, repetición de cinco veces
    board.is_game_over()

Para que os resulte más cómodo aquí tenéis el notebook completo con toda la introducción al simulador de ajedrez de Python

Si habéis llegado hasta aquí es que estaréis preparados para afrontar la actividad de hacer a nuestras «piezas blancas» un poco más inteligentes y comprobar de forma estadística que el jugador blanco (inteligente) es mejor que el jugador negro (aleatorio)

Lo que haremos en el siguiente Notebook es crear un algoritmo que puntúe los movimientos de las piezas según el tipo, es decir que no es lo mismo comer/capturar con un peón que con una reina. Después usaremos las funciones de verificación para comprobar que ocurre en el siguiente nivel del arbol con cada uno de los movimientos legales y de esta forma sacar la puntuación de todos los movimientos posibles y finalmente elegir el mejor de ellos.

Un video donde se muestra jugando la máquina sobre si misma un total de 100 partidas y donde el jugador de piezas blancas tiene un cierto criterio de elección en los movimientos legales gracias a pequeñas recompensas.

Aquí os dejo el notebook con sus comentarios.