¿El resultado esperado de un juego aleatorio de ajedrez?

Imagine un juego de ajedrez donde ambos jugadores generan una lista de movimientos legales y eligen uno uniformemente al azar.

P : ¿Cuál es el resultado esperado para las blancas?

  • 1 punto por las negras en jaque mate, 0,5 por tablas, 0 por las blancas en jaque mate. Entonces el resultado esperado está dado por
    PAG r [ negro en jaque mate ] + 0.5   PAG r [ dibujar ] .
  • No se da de baja ningún jugador, ni hay ofertas de empate ni reclamaciones.

Como jugador de ajedrez, tengo curiosidad por saber si las blancas (que juegan primero) tienen alguna ventaja aquí.


No espero que sea posible una respuesta exacta. Los resultados parciales (por ejemplo, que la expectativa es >0,5) y los resultados experimentales son bienvenidos. (La expectativa no es 0 o 1, ya que hay juegos posibles donde las blancas no ganan y donde las negras no ganan).

Supongo que esto se ha analizado antes, así que decidí preguntar primero (en lugar de implementar un motor de ajedrez que haga movimientos aleatorios y espero encontrar algo más que "dibujar, dibujar, dibujar, dibujar,..."). La búsqueda de "juego aleatorio de ajedrez" enumera Chess960 y otras variantes aleatorias, que no es lo que quiero.


tecnicismos:

  • Captura al paso, enroque, coronación de peón, etc., todo se aplica como de costumbre.

  • Las Leyes del Ajedrez de la FIDE se actualizarán el 1 de julio de 2014 con lo siguiente:

    9.6 Si ocurre uno o ambos de los siguientes, entonces el juego es empatado:

    • a. ha aparecido la misma posición, como en 9.2b, durante al menos cinco movimientos alternativos consecutivos de cada jugador.

    • b. cada jugador ha completado cualquier serie consecutiva de 75 movimientos sin el movimiento de ningún peón y sin ninguna captura. Si el último movimiento resultó en jaque mate, eso tendrá prioridad.

    Esto significa que los juegos de ajedrez deben ser finitos y, por lo tanto, hay un número finito de juegos de ajedrez posibles.

Solo estoy adivinando aquí, pero esperaría que el juego promedio sea muy largo, porque es difícil hacer jaque mate con movimientos aleatorios. Por la misma razón, sospecho que la mayoría de estos juegos terminarán en empate. Y además, debido a que los juegos se vuelven tan largos, simular suficientes para obtener datos confiables llevará mucho tiempo. Para fines de simulación, podría ser más fácil adoptar reglas de ajedrez de alta velocidad, en las que no es ilegal mover el rey a una casilla amenazada (o dejarlo allí), y el juego se gana capturando al rey.
No conozco ningún resultado de este tipo, ni ninguna forma fácil de calcularlo. Las dos partidas más cortas posibles y, por lo tanto, intuitivamente las rutas de muestra modales (más comunes), serían el mate del tonto (1.f4 e5 2.g4 Dh4# y 1.f4 e6 2.g4 Dh4#, con posibilidades de transposición, es decir, 1. g4 antes de 2.f4), donde las negras ganan en ambos casos. Pero la probabilidad de cualquiera de estos caminos sigue siendo bastante pequeña, alrededor de ( 1 20 ) 4 , asumiendo que cada lado tiene aproximadamente 20 movimientos legales durante las etapas iniciales. Las negras tendrían unas 20 veces menos probabilidades de sufrir un destino similar tan pronto.
@Bhoot. Hay más variantes para obtener mate en 2. Tanto Dh4 como g4 son movimientos necesarios para las negras y las blancas, respectivamente. Pero las negras pueden jugar e6 o e5 en su primer movimiento y las blancas pueden jugar f3 o f4 en el movimiento 1 o 2 (siempre que el otro movimiento sea g4). Sus ejemplos tomaron en cuenta la transposición, pero no f3. En total, obtendrías 8 órdenes de movimiento diferentes para llegar a mate en 2.
@Rebecca ¿Juegas en ICC por casualidad?
Ver wismuth.com/chess/random-games.html Más de 29,28 mil millones de juegos de ajedrez aleatorios Las blancas tienen una probabilidad levemente mayor de dar jaque mate que las negras (7,7340 % frente a 7,7293 %).

Respuestas (5)

Encontré un error en el código dado en la respuesta de Hooked (lo que significa que mi nuevo análisis original también fue defectuoso): también hay que verificar si hay material insuficiente al evaluar un sorteo, es decir

int(board.is_stalemate())

debe ser reemplazado con

int(board.is_insufficient_material() or board.is_stalemate())

Esto cambia bastante las cosas. La probabilidad de empate aumenta bastante. hasta ahora con norte = 5 10 5 muestras que encuentro

mi [ Negro ] 0.5002
mi [ Blanco ] 0.4998
PAG [ Dibujar ] 84.4 %

Una simple prueba de hipótesis muestra que con PAG ( blanco ) = PAG ( negro ) = 0.078 ,   PAG ( dibujar ) = 0.844 y norte = 5 10 5 muestra la probabilidad de obtener | mi [ Negro ] 0.5 | > 0.002 es 25 % por lo que nuestros resultados son perfectamente consistentes con mi [ Blanco ] = mi [ Negro ] = 0.5 . La "joroba" permanece, pero ahora se explica fácilmente: se debe a que ganan las negras o las blancas. O ganan temprano o el juego acaba en empate.

ingrese la descripción de la imagen aquí
(fuente: folk.uio.no )

Aquí está uno de los juegos más cortos que encontré, el negro estúpido queda enmarañado en cuatro movimientos:

ingrese la descripción de la imagen aquí
(fuente: folk.uio.no )

¡Buena captura, atribuya esto al poder de la revisión por pares! Para ser honesto, esta biblioteca fue simplemente la más fácil de instalar y poner en marcha, me imagino que una biblioteca C puede ser mucho más rápida para ejecutar simulaciones largas. Además, el mate es posible con dos movimientos .
Sin embargo, dos sugerencias, los recuentos reales utilizados por las simulaciones y un valor p de dos caras. Como estás mucho, mucho más cerca de la mitad, sería interesante ver qué dicen las estadísticas. ¡"perfectamente consistente" no es lo suficientemente preciso!
No entiendo por qué el cambio que hiciste alteró los resultados. Después de todo, en cualquier caso donde no haya material suficiente para dar mate, el resultado debe ser tablas por repetición, ahogamiento o la regla de los 50 (o 75) movimientos. ¿Cómo aumentó la probabilidad de empate al agregar esa condición explícita?
@mjqxxxx, creo que es porque la biblioteca de ajedrez considera que el juego ha terminado cuando ninguno de los jugadores tiene suficiente material para dar mate, y así rompe el bucle, pero no lo considera un punto muerto, por lo que no se contó correctamente. .
Jeje, se suponía que debía hacer una revisión por pares para un documento real... pero hice esto en su lugar. Utilicé la biblioteca yo mismo (y también su código) como una caja negra al principio, solo por accidente traté de generar el código FEN para ver las posiciones en las que lo noté. Sí, c ++ debería ser mucho más rápido, Python es lento :) Estoy de acuerdo, intentaré hacer esto más preciso cuando tenga algo de tiempo.
@mjqxxxx La razón por la que hizo un cambio es que en la salida de cada juego, el código podría devolver sorteo = 0 (Falso) cuando en realidad había un sorteo. Al analizar los datos posteriormente, el último jugador en hacer un movimiento obtuvo la victoria en estos casos como: total-draw = sum(column 1), blackwins = sum(column 2) - total-draw, ...
Está bien, ya veo. Entonces, los estados finales del juego eran todos correctos, pero simplemente no se analizaban correctamente, porque su estado (ganador vs empate) no se estaba escribiendo en el archivo correctamente.

Actualización : el siguiente código tiene un pequeño pero significativo descuido. No sabía que un empate no se contaría de la misma manera que un tablero con piezas insuficientes para jugar y esto cambia la respuesta. @Winther solucionó el error y volvió a ejecutar las simulaciones . Dicho esto, todavía hay valor en el código que se publica, así que lo dejaré para que cualquier otra persona repita los experimentos (¡y encuentre más errores!).


Reformulando ligeramente tu pregunta,

¿Es el resultado esperado para EX[blanco] = 1/2 en un juego aleatorio?

Para probar esto, simulé 10^5 juegos usando la biblioteca python-chess. El código se publica a continuación para aquellos que deseen repetir el experimento numérico (esto lleva unas 4 horas en una máquina de 8 núcleos). En los 100000 juegos, 46123 fueron victorias para las blancas y 6867 juegos fueron empates. Esto pone el valor esperado del juego en

EX [ w h i t mi ] = 0.495565

Usando la aproximación normal de 2 caras a la prueba binomial de un juego limpio, obtenemos un valor p de 0.00511. Por lo tanto, podemos rechazar la hipótesis nula de que el juego es justo. Esto fue sorprendente para mí.

En otras palabras, EX [ w h i t mi ] < 1 / 2 parece ser estadísticamente significativo , sin embargo, la ventaja para el negro es muy pequeña.

Personalmente, la pregunta más interesante es la distribución de la duración del juego, por lo que se incluye un gráfico a continuación.

import chess, random, itertools, multiprocessing
simulations = 10**5

def random_move(board):
    return random.choice(list(board.legal_moves))

def play(game_n):
    board = chess.Bitboard()
    ply = 0
    while not board.is_game_over():
        board.push( random_move(board) )
        ply += 1

    # board.turn == 0 -> White, == 1 -> Black
    return game_n, int(board.is_stalemate()), board.turn, ply

P = multiprocessing.Pool()
results = P.imap(play,xrange(simulations))

with open("results.txt",'w') as FOUT:
    for game in results:
        s = "{} {} {} {}\n".format(*game)
        FOUT.write(s)

ingrese la descripción de la imagen aquí

Hay mucho que extraer de este conjunto de datos, pero no soy un aficionado al ajedrez. No estoy seguro de por qué la distribución contiene dos "jorobas", los comentarios son bienvenidos.

Muy bueno, ++i. También sería interesante ver de dónde provienen las ganancias adicionales de las negras, es decir, cuál es la distribución de la duración del juego para las ganancias de las blancas en comparación con las ganancias de las negras.
Sería bueno ver una actualización basada en la respuesta de @Winther: realmente no tiene ningún sentido que el blanco tenga una desventaja, por pequeña que sea.
@nbubis Voy a dejar que Winther se lleve el crédito por encontrar la falla en el código anterior. Yo lo suficientemente feliz, mi respuesta inicial, por defectuosa que fuera, fue suficiente para estimular una mejor investigación. Actualizaré mi respuesta y señalaré la suya, en mi humilde opinión, responde la pregunta correctamente y debería obtener el cheque.

código completo y resultados

from queue import Queue
import chess, random, _thread
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats

def outcome(board):
    if board.is_checkmate():
        if board.turn:
            return "Black"
        else:
            return "White"
    else:
        return "Draw"

#calc total moves
def moves(board):
    if board.turn:
        return board.fullmove_number * 2 - 1
    else:
        return board.fullmove_number * 2 - 2

def play(i):
    board = chess.Board()
    while not board.is_game_over():
        board.push(random.choice(list(board.legal_moves)))
    return i, outcome(board), moves(board)

def thread_wrapper(i, func, stat, q):
    def run():
        q.put(func)
        stat[i] = True
    return run

workers = 500000
status = [False for i in range(workers)]
q = Queue()
for i in range(workers):
    _thread.start_new_thread(thread_wrapper(i, play(i), status, q), tuple())

while not all(status):
    pass

results = []
while not q.empty():
    results.append(q.get())
results_df = pd.DataFrame(results, columns=['game_n', 'outcome', 'moves'])
#TODO process the results

results_df.to_csv('my_file.csv')

black = results_df.loc[results_df['outcome'] == 'Black']
white = results_df.loc[results_df['outcome'] == 'White']
draw = results_df.loc[results_df['outcome'] == 'Draw']
win = results_df.loc[results_df['outcome'] != 'Draw']

Total = len(results_df.index)
Wins = len(win.index)

PercentBlack = "Black Wins ≈ %s" % ('{0:.2%}'.format(len(black.index)/Total))
PercentWhite = "White Wins ≈ %s" % ('{0:.2%}'.format(len(white.index)/Total))
PercentDraw = "Draw ≈ %s" % ('{0:.2%}'.format(len(draw.index)/Total))
AllTitle = 'Distribution of Moves by All Outcomes (nSample = %s)' % workers

a = draw.moves
b = black.moves
c = white.moves

kdea = scipy.stats.gaussian_kde(a)
kdeb = scipy.stats.gaussian_kde(b)
kdec = scipy.stats.gaussian_kde(c)

grid = np.arange(700)

#weighted kde curves
wa = kdea(grid)*(len(a)/float(len(a)+len(b)+len(c)))
wb = kdeb(grid)*(len(b)/float(len(a)+len(b)+len(c)))
wc = kdec(grid)*(len(c)/float(len(a)+len(b)+len(c)))

total = wa+wb+wc
wtotal = wb+wc

plt.figure(figsize=(10,5))
plt.plot(grid, total, lw=2, label="Total")
plt.plot(grid, wa, lw=1, label=PercentDraw)
plt.plot(grid, wb, lw=1, label=PercentBlack)
plt.plot(grid, wc, lw=1, label=PercentWhite)
plt.title(AllTitle)
plt.ylabel('Density')
plt.xlabel('Number of Moves')
plt.legend()
plt.show()

ExpectedBlack = "EV Black Wins ≈ %s" % ('{0:.2%}'.format(len(black.index)/Wins))
ExpectedWhite = "EV White Wins ≈ %s" % ('{0:.2%}'.format(len(white.index)/Wins))
WinTitle = 'Distribution of Moves by Wins (nWins = %s)' % Wins

plt.figure(figsize=(10,5))
plt.plot(grid, wtotal, lw=2, label="Wins")
plt.plot(grid, wb, lw=1, label=ExpectedBlack)
plt.plot(grid, wc, lw=1, label=ExpectedWhite)
plt.title(WinTitle)
plt.ylabel('Density')
plt.xlabel('Number of Moves')
plt.legend()
plt.show()

print("Most frequent moves of All:", grid[total.argmax()], round(max(total), 4), "for", Total, "games")
print("Most frequent moves of Draws:", grid[wa.argmax()], round(max(wa), 4), "for", len(draw.index), "games")
print("Most frequent moves of Wins:", grid[wtotal.argmax()], round(max(wtotal), 4), "for", Wins, "games")
print("Most frequent moves of Black wins:", grid[wb.argmax()], round(max(wb), 4), "for", len(black.index), "games")
print("Most frequent moves of White wins:", grid[wc.argmax()], round(max(wc), 4), "for", len(white.index), "games")

Todos los resultados:

Todos los resultados

Resultados sin empate:

Resultados de victorias

Movimientos más frecuentes de todos: 368 0.0036 para 500000 juegos

Movimientos más frecuentes de Draws: 370 0.0035 para 422856 juegos

Movimientos más frecuentes de Wins: 135 0.0008 para 77144 juegos

Movimientos más frecuentes de victorias negras: 133 0.0004 para 38546 juegos

Movimientos más frecuentes de victorias blancas: 137 0.0004 para 38598 juegos

Su respuesta no parece dar nada más que las ya existentes.
@ArnaudD. Discrepar. Verifica las respuestas existentes, reduciendo la posibilidad de que haya más errores. Sería más útil si fuera código en un nuevo lenguaje (no python) y/o especificando las reglas desde cero, pero sigue siendo útil.
@ArnaudD. No estoy de acuerdo. Incluye algunas estadísticas de longitud media que no contiene ninguna otra respuesta.

@Winther, @Hooked, @yaoster No tengo suficiente reputación para comentar, noté que su código asumía board.turn==1que significaba el turno de Black , pero corríjame si me equivoco, parece que es todo lo contrario, ya que verifiqué python-chessla documentación ( es decir, board.turn==1significa el turno de las blancas ).

Si esto es cierto, su análisis sugeriría que las blancas tienen una ligera ventaja, ¿qué es lo que la mayoría de la gente esperaría?

De python-chessla documentación:

chess.WHITE = True

http://python-chess.readthedocs.io/en/latest/core.html?highlight=turn#chess.WHITE

No estoy seguro de por qué los votos negativos, ¡parece un comentario útil si es cierto!

Sé que 350 juegos no son muestras suficientes, pero incluiré esto en caso de que alguien tenga curiosidad sobre la distribución de tipo sorteo:

ingrese la descripción de la imagen aquí

Nota: en el caso de varios tipos (ejemplo: Jaque mate + Regla de los cincuenta movimientos) solo aumentó el primero en orden de prioridad: Jaque mate, Punto muerto, Rep. triple, Material insuficiente, Regla de los 50 movimientos.