La compresión más pequeña del juego de ajedrez

8

Inspiración:

Muy inspirado por la compresión de tablero de ajedrez más pequeña , decidí hacer una competencia similar pero claramente diferente.

tl; dr

Tome el archivo Chess_Games.txt y comprímalo tanto como pueda para que pueda expandirlo al archivo original.

Objetivo:

Escriba un algoritmo para codificar y decodificar una base de datos de ajedrez completa desde la posición inicial hasta la final

La codificación debe poder determinar para todos los juegos todas las posiciones:

  • La ubicación de todas las piezas.
  • De quién es el turno
  • Si el jugador puede atacar en cada lado.
  • Si el jugador puede realizar un pase, y si es así, ¿cuál de sus peones?
  • El puesto anterior

Adicionalmente:

  • Cada juego también debe incluir quién ganó y cómo terminó (pérdida, empate, jaque mate, estancamiento, etc.)

De entrada y salida:

Debe haber 2 algoritmos de compresión / expansión que satisfagan las siguientes propiedades:

  • Comprimir toma un archivo de juegos mediante una secuencia de movimientos mediante notación de ajedrez y genera un archivo comprimido

  • Expandir hace lo contrario, toma un archivo comprimido y genera el archivo original con todos los juegos en el mismo orden

  • Corrección: Expandir (Comprimir (archivo)) = archivo para todos los archivos formados correctamente

Cualquier juego que no esté bien formado o que viole las reglas del ajedrez se considera malo. Todos los juegos malos pueden saltarse.

Debe poder analizar la notación sen. Mire chessgames.com y https://database.lichess.org/ para ver algunos ejemplos.

He compilado un archivo de los primeros 10000 juegos de "mayo de 2017" en Chess_Games.txt

El archivo debería tener el siguiente aspecto:

e4 d5 exd5 Nf6 d3 Qxd5 Nc3 Qf5 Be2 Bd7 g4 Qe6 g5 Nd5 Ne4 Bc6 Bg4 Qe5 f4 Qd4 Nf3 Qb6 Qe2 e6 Be3 Qa6 O-O Nd7 c4 Ne7 f5 Bxe4 dxe4 exf5 exf5 O-O-O f6 gxf6 gxf6 Nc6 Nd4 Nxd4 Bxd4 Bc5 Bxc5 Rhg8 Be7 Rde8 b4 Qxf6 Bxf6 Rxe2 h3 h5 Kh1 hxg4 Rg1 Nxf6 Rae1 Rxe1 Rxe1 gxh3 Kh2 Ng4+ Kxh3 Nf2+ Kh2 Ng4+ Kg3 f5 Kf4 Nh6 Re7 Rg4+ Ke5 Kd8 Kf6 Ng8+ Kf7 Nxe7 Kf8 f4 c5 f3 c6 f2 cxb7 Nc6 b8=Q+ Nxb8 Kf7 Kd7 0-1
d3 e6 e3 d5 Nf3 Nf6 Be2 Be7 O-O O-O Nc3 Nbd7 Ne1 c5 f3 e5 e4 d4 Nb1 b6 f4 exf4 Rxf4 Qc7 Rf3 Bd6 g3 Ng4 Rf1 Ndf6 Bxg4 Bxg4 Qd2 Rae8 Qf2 Re5 Bf4 Rh5 Bxd6 Qxd6 Nf3 c4 e5 Rxe5 Nxe5 Qxe5 Nd2 cxd3 cxd3 Be2 Rfe1 Qe3 Qxe3 dxe3 Ne4 Bxd3 Nxf6+ gxf6 Rxe3 Bg6 Rae1 Kg7 h4 h5 R1e2 Bf5 Rg2 Bg4 Re4 Kg6 Rge2 f5 Re5 f6 Re6 Rg8 Re7 Rg7 Re8 1-0
d4 d5 e4 dxe4 c4 Nf6 Qc2 Bf5 Nc3 Qxd4 Be3 Qe5 Nge2 e6 Ng3 Bb4 a3 Bxc3+ bxc3 Nc6 Be2 Na5 O-O Nxc4 Bd4 Qb5 Nxf5 exf5 Bxf6 gxf6 Rab1 Nxa3 Qa2 Qxb1 Rxb1 Nxb1 Qxb1 O-O-O Qb3 b6 Ba6+ Kb8 Qb5 Rhe8 Qc6 1-0
e3 c5 d3 d5 Nf3 Nc6 Be2 Nf6 O-O g6 h3 Bg7 Re1 O-O Nbd2 Re8 Nf1 e5 c3 b6 N3h2 Bb7 Qc2 Qc7 b3 Rac8 Bb2 a5 Rac1 a4 Qb1 axb3 axb3 Ba6 d4 Bxe2 Rxe2 exd4 cxd4 Qb8 dxc5 d4 Ree1 dxe3 Rxe3 Nd5 Rxe8+ Rxe8 Bxg7 Kxg7 Re1 Rxe1 Qxe1 bxc5 Qd1 Nd4 Ne3 Nf4 Neg4 Qxb3 Qe1 Qd5 Ne3 Qe6 Qf1 c4 Nhg4 Nde2+ Kh2 c3 g3 c2 gxf4 c1=Q Qxc1 Nxc1 Ng2 Qe4 Kg3 Nd3 f3 Qe2 Nh4 h5 Nh2 Qe1+ Kg2 Nf2 Nf5+ gxf5 Kg3 Qg1+ Kh4 Nxh3 Kxh5 1-0
e4 d5 exd5 Qxd5 Nc3 Qd8 d4 Bf5 Nf3 e6 Nh4 Bg6 Nxg6 hxg6 Be3 Bd6 Bc4 a6 Qf3 c6 O-O-O Nd7 Ne4 Qc7 Nxd6+ Qxd6 Bf4 Qb4 Bb3 Ngf6 Rhe1 O-O-O Bg3 a5 Qf4 Qb6 Re3 Rh5 Bc4 Rf5 Qd6 Ne8 Qa3 Qa7 Red3 b5 Bxb5 cxb5 Rc3+ Kb7 Qe7 b4 Rc5 Rxc5 dxc5 Qxc5 Qxd8 Ndf6 Qb8+ Ka6 Rd8 Qa7 Rxe8 Nxe8 Qxe8 Qc5 Qa8+ Kb5 Qb8+ Ka4 b3+ Ka3 Qf4 Qc3 Qe5 1-0
e4 d5 exd5 Qxd5 Nf3 Qd8 Nc3 e6 Bc4 Nf6 Bb3 Be7 a3 a6 O-O O-O h3 b6 d3 Bb7 Bg5 Nbd7 Ne4 h6 Bh4 Nxe4 Bxe7 Qxe7 dxe4 Bxe4 Re1 Bb7 Ba2 Nf6 b4 Rfd8 Qc1 Qd6 c4 Qc6 Qc2 Rd7 Rac1 Rad8 c5 bxc5 Qxc5 Qb5 Qxb5 axb5 Ne5 Rd2 Bb3 Rb2 Bd1 Rdd2 Re2 Rxe2 Bxe2 Rxe2 Nf3 Ra2 Rxc7 Bd5 Rc8+ Kh7 Ne5 Rxa3 Rf8 Ra1+ Kh2 h5 Rxf7 Kh6 Rf8 Kg5 g3 Kf5 Nd7 Ra2 Nxf6 gxf6 Rg8 Rxf2+ Kg1 Rg2+ Kf1 Rh2 g4+ hxg4 hxg4+ Ke5 Re8 Rh1+ Kf2 Rh2+ Kg3 Rg2+ Kh4 Rf2 Kg3 Rf4 g5 Re4 gxf6 Kxf6 Rf8+ Ke5 Rh8 Re3+ Kf2 Re4 Rh5+ Kd4 Rh6 Rf4+ Kg3 Re4 Rh8 Re3+ 0-1
...

Puntuación:

Para que las cosas sean objetivas, el ganador es el algoritmo que puede comprimir los archivos en https://database.lichess.org/ lo más pequeño posible. La base de datos de destino es "mayo de 2017" . El ganador es quien tiene el archivo más pequeño que se expande correctamente.

El archivo a utilizar es Chess_Games.txt, que son los primeros 10000 juegos de la base de datos de "mayo de 2017" en https://database.lichess.org/ con toda la información del encabezado eliminada. Este archivo será el punto de referencia para usar. Debe tener 2.789.897 bytes con un hash SHA-256 de 56b6d2fffc7644bcd126becd03d859a3cf6880fa01a47fa08d4d6a823a4866bc(Pastebin podría eliminar la última línea nueva después del juego 10000)

Solución ingenua:

Usando algoritmos de compresión genéricos:

  • zip: 647,772 bytes (76.841%)
  • 7z: 652,813 bytes (76.661%)
  • bz2: 722,158 bytes (74.182%)
  • xz: 784,980 bytes (71.936%)
  • rar: 853,482 bytes (69.487%)
  • gz: 923,474 bytes (66.985%)
nervioso
fuente
No debería haber un espacio despuéshttp://
JungHwan Min
Gracias por arreglarlo. El sitio no me permitió publicar una pregunta con más de 1 enlace, ya que no tengo la reputación adecuada. Así que agregué un espacio y esperé que alguien más lo arreglara o podría arreglarlo más tarde.
nervioso
2
¡Buena pregunta! ¿Podría elaborar más sobre cómo se calcula la puntuación? Además, ¿qué variante de la notación de ajedrez estándar deberíamos esperar y generar? En el futuro, puede encontrar útil el Sandbox :)
musicman523
1
¿Podríamos obtener el juego de ajedrez / notación que usará para calificar las respuestas?
wrymug
1
Eliminé las anotaciones del archivo de referencia y eliminé las referencias confusas de las reglas de dibujo para aclararlo todo. Cambié la declaración sobre el análisis para usar esos sitios como referencia.
nervioso

Respuestas:

3

Python, puntuación = 426508 bytes

Función de compresión: (m + 1) ⋅ log 2 (b + 1)

m es el número de movimientos y b es el factor de ramificación

Intento 1:

Respondí la pregunta en Compresión de tablero de ajedrez más pequeña, así que estoy tratando de arreglarlo para publicar aquí.

Ingenuamente pensé que puedo codificar cada movimiento en 12 bits, 4 tripletas de la forma (inicio x, inicio y, final x, final y) donde cada uno es de 3 bits.

Asumiríamos la posición inicial y moveríamos las piezas desde allí con las blancas primero. El tablero está dispuesto de tal manera que (0, 0) es la esquina inferior izquierda del blanco.

Por ejemplo el juego:

  e4    e5
 Nf3    f6
Nxe5  fxe5
...    ...

Sería codificado como:

100001 100010 100110 100100
110000 101010 101110 101101
101010 100100 101101 100100
...

Esto conduce a una codificación de 12 m bits, donde m es el número de movimientos realizados.

Intento 2:

Me di cuenta de que cada movimiento en el intento anterior codifica muchos movimientos ilegales. Así que decidí codificar solo movimientos legales. Enumeramos los posibles movimientos de la siguiente manera, numera cada cuadrado de manera que (0, 0) → 0, (1, 0) → 1, (x, y) → x + 8 y. Itere a través de las fichas y compruebe si hay una pieza y si puede moverse. Si es así, agregue las posiciones a las que puede ir a una lista. Elija el índice de la lista que es el movimiento que desea hacer. Agregue ese número al total acumulado de movimientos ponderados por 1 más el número de movimientos posibles.

Ejemplo como el anterior: desde la posición inicial, la primera pieza que puede moverse es el caballero en el cuadrado 1, puede moverse al cuadrado 16 o 18, así que agréguelos a la lista [(1,16),(1,18)]. El siguiente es el caballero en el cuadrado 6, agregue sus movimientos. En general obtenemos:

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Como queremos el movimiento (12, 28), codificamos esto como 13 en la base 20, ya que hay 20 movimientos posibles.

Entonces ahora tenemos el número de juego g 0 = 13

A continuación, hacemos lo mismo para el negro, excepto que numeramos las fichas en reversa (para que sea más fácil, no obligatorio) obtener la lista de movimientos:

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Como queremos el movimiento (11, 27), codificamos esto como 11 en la base 20 ya que hay 20 movimientos posibles.

Entonces ahora tenemos el número de juego g 1 = (11 ⋅ 20) + 13 = 233

A continuación obtenemos la siguiente lista de movimientos para las blancas:

[(1,16),(1,18),(3,12),(3,21),(3,30),(3,39),(4,12),(5,12),(5,19),(5,26),(5,33),(5,40),(6,12),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27)(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Como queremos el movimiento (6, 21), codificamos esto como 13 en la base 29 ya que hay 29 movimientos posibles.

Entonces ahora obtenemos el número de juego g 2 = ((13 ⋅ 20) + 11) 20 + 13 = 5433

A continuación obtenemos la siguiente lista de movimientos para las negras: [(1,11),(1,16),(1,18),(2,11),(2,20),(2,29),(2,38),(2,47),(3,11),(4,11),(4,18),(4,25),(4,32),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Como queremos el movimiento (10, 18), codificamos esto como 19 en la base 29 ya que hay 29 movimientos posibles.

Entonces ahora obtenemos el número de juego g 3 = (((19 ⋅ 29 + 13) 20) + 11) 20 + 13 = 225833

Y continúe este proceso para todos los movimientos restantes. Puede pensar en g como la función g (x, y, z) = x y + z. Así g 0 = g (1, 1, 13), g 1 = g (g (1, 1, 11), 20, 13), g 2 = g (g (g (1, 1, 13), 20, 11), 20, 13), g 3 = g (g (g (g (1, 1, 19), 29, 13), 20, 11), 20, 13)

Para decodificar un número de juego g 0 , comenzamos en la posición inicial y enumeramos todos los movimientos posibles. Luego calculamos g 1 = g 0 // l , m 0 = g 0 % l , donde l es el número de movimientos posibles, '//' es el operador de división entera y '%' es el operador de módulo. Debe sostener que g 0 = g 1 + m 0 . Luego hacemos el movimiento m 0 y repetimos.

Del ejemplo anterior si g 0 = 225833, entonces g 1 = 225833 // 20 = 11291 ym 0 = 225833% 20 = 13. A continuación g 2 = 11291 // 20 = 564 ym 1 = 11291% 20 = 11. Luego g 3 = 11291 // 20 = 564 y m 2 = 11291% 20 = 11. Por lo tanto, g 4 = 564 // 29 = 19 y_m_ 3 = 564% 29 = 13. Finalmente g 5 = 19 // 29 = 0 ym 4 = 19% 29 = 19.

Entonces, ¿cuántos bits se usan para codificar un juego de esta manera?

Para simplificar, digamos que siempre hay 35 movimientos por turno ( https://en.wikipedia.org/wiki/Branching_factor ) y para el peor de los casos siempre elegimos el más grande, 34. El número que obtendremos es 34 ⋅ 35 m + 34 ⋅ 35 m-1 + 34 ⋅ 35 m-2 + ⋯ + 34 ⋅ 35 + 34 = 35 m + 1 - 1 donde _m es el número de movimientos. Para codificar 35 m + 1 - 1 necesitamos aproximadamente log 2 (35 m + 1 ) bits, que es aproximadamente (m + 1) ⋅ log 2 (35) = 5.129 ⋅ (m + 1)

En promedio m = 80 (40 movimientos por jugador), por lo que se necesitarían 416 bits para codificar. Si estuviéramos grabando muchos juegos necesitaríamos una codificación universal ya que no sabemos cuántos bits necesitará cada número

El peor de los casos cuando m = 11741, por lo que se necesitarían 60229 bits para codificar.

Notas adicionales:

Tenga en cuenta que g = 0 denota un juego válido, aquel en el que la pieza en el cuadrado más bajo se mueve al cuadrado más bajo que puede.

Si desea referirse a una posición específica en el juego, es posible que deba codificar el índice. Esto se puede agregar manualmente, por ejemplo, concatenar el índice al juego, o agregar un movimiento "final" adicional como el último movimiento posible en cada turno. Esto ahora puede dar cuenta de los jugadores que conceden, o 2 seguidos para denotar que los jugadores aceptaron un empate. Esto solo es necesario si el juego no terminó en jaque mate o estancado según la posición, en este caso está implícito. En este caso, lleva el número de bits necesarios en promedio a 419 y, en el peor de los casos, 60706.

Una forma de manejar las bifurcaciones en un escenario hipotético es codificar el juego hasta la bifurcación y luego codificar cada rama comenzando en la posición bifurcada en lugar de la posición inicial.

Implementación:

Eche un vistazo a mi repositorio de github para el código: https://github.com/edggy/ChessCompress

nervioso
fuente
3

Python, puntuación = 418581 bytes

Utiliza una variante biyectiva de sistemas de numeración asimétrica . Debido a que es biyectivo, no solo puede comprimir cualquier lista válida de juegos de ajedrez en un archivo y expandirla de nuevo a la misma lista, ¡también puede expandir cualquier archivo a una lista válida de juegos de ajedrez y comprimirlo nuevamente en el mismo archivo! Perfecto para esconder tu colección porno.

Requiere python-ajedrez . Ejecute con python script.py compresso python script.py expand, los cuales leerán de la entrada estándar y escribirán en la salida estándar.

import chess
import sys

RESULTS = ['1-0\n', '1/2-1/2\n', '0-1\n', '*\n']
BITS = 24

def get_moves(board):
    if board.is_insufficient_material() or not board.legal_moves:
        return [board.result() + '\n']
    else:
        return RESULTS + sorted(
            board.legal_moves,
            key=lambda move: (move.from_square, move.to_square, move.promotion))

def read_bijective():
    buf = bytearray(getattr(sys.stdin, 'buffer', sys.stdin).read())
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] + 1
        buf[i] = carry & 0xff
        carry >>= 8
    if carry:
        buf.append(carry)
    return buf

def write_bijective(buf):
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] - 1
        buf[i] = carry & 0xff
        carry >>= 8
    while carry:
        carry = (carry << 8) + buf.pop() + 1
    getattr(sys.stdout, 'buffer', sys.stdout).write(buf)

def add_carry(buf, carry):
    for i in range(len(buf)):
        if carry == 0:
            break
        carry += buf[i]
        buf[i] = carry & 0xff
        carry >>= 8
    return carry

def do_compress():
    board = chess.Board()
    state = 0
    buf = bytearray()

    games = []
    for sans in sys.stdin:
        game = []
        for san in sans.split(' '):
            move = san if san in RESULTS else board.parse_san(san)
            moves = get_moves(board)
            game.append((len(moves), moves.index(move)))
            if move in RESULTS:
                board.reset()
            else:
                board.push(move)
        games.append(game)

    for game in reversed(games):
        for (n, i) in reversed(game):
            q = ((1 << BITS) - 1 - i) // n + 1
            while state >= q << 8:
                buf.append(state & 0xff)
                state >>= 8
            hi, j = divmod(state, q)
            lo = n * j + i
            state = hi << BITS | lo
        state += add_carry(buf, 1)

    while state:
        buf.append(state & 0xff)
        state >>= 8
    write_bijective(buf)

def do_expand():
    board = chess.Board()
    state = 0
    buf = read_bijective()

    while True:
        while buf and state < 1 << BITS:
            state = state << 8 | buf.pop()
        if state == 0:
            break
        state += add_carry(buf, -1)

        while True:
            moves = get_moves(board)
            while buf and state < 1 << BITS:
                state = state << 8 | buf.pop()
            n = len(moves)
            hi, lo = divmod(state, 1 << BITS)
            j, i = divmod(lo, n)
            q = ((1 << BITS) - 1 - i) // n + 1
            state = j + q * hi
            move = moves[i]
            if move in RESULTS:
                sys.stdout.write(move)
                board.reset()
                break
            else:
                sys.stdout.write(board.san(move).rstrip('+#') + ' ')
                board.push(move)

if __name__ == '__main__':
    {'compress': do_compress, 'expand': do_expand}[sys.argv[1]]()
Anders Kaseorg
fuente
1
Verificado en Ubuntu 17.04 usando Python 2.7.13 y ejecutó cada comando por separado.
nervioso
¡Esto es realmente genial! También me intriga cómo serán los juegos de ajedrez pornográficos en la práctica.
Anush