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?
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.
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 muestras que encuentro
Una simple prueba de hipótesis muestra que con y muestra la probabilidad de obtener es por lo que nuestros resultados son perfectamente consistentes con . 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.
(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:
(fuente: folk.uio.no )
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
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, 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)
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.
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:
Resultados sin empate:
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
@Winther, @Hooked, @yaoster No tengo suficiente reputación para comentar, noté que su código asumía board.turn==1
que significaba el turno de Black , pero corríjame si me equivoco, parece que es todo lo contrario, ya que verifiqué python-chess
la documentación ( es decir, board.turn==1
significa 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-chess
la documentación:
chess.WHITE = True
http://python-chess.readthedocs.io/en/latest/core.html?highlight=turn#chess.WHITE
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:
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.
Harald Hanche-Olsen
usuario105475
Vicente
Vicente
W máx.