Menor compressão de jogo de xadrez

8

Inspiração:

Fortemente inspirado pela menor compressão do tabuleiro de xadrez, decidi fazer uma competição semelhante, mas nitidamente diferente.

tl; dr

Pegue o arquivo Chess_Games.txt e compacte-o o máximo que puder para expandi-lo para o arquivo original.

Objetivo:

Escreva um algoritmo para codificar e decodificar um banco de dados de xadrez inteiro da posição inicial ao fim

A codificação deve ser capaz de determinar para todos os jogos todas as posições:

  • A localização de todas as peças
  • De quem é a vez
  • Se o jogador pode dominar de cada lado.
  • Se o jogador pode executar um passante e, em caso afirmativo, qual de seus peões?
  • A posição anterior

Além disso:

  • Cada jogo também deve incluir quem ganhou e como terminou (desistência, empate, xeque-mate, impasse, etc ...)

Entrada / Saída:

Deve haver 2 algoritmos Compactar / Expandir que atendam às seguintes propriedades:

  • O Compress recebe um arquivo de jogos através de uma sequência de movimentos através da notação de xadrez e gera um arquivo compactado

  • Expand faz o oposto, obtendo um arquivo compactado e produzindo o arquivo original com todos os jogos na mesma ordem

  • Correção: Expandir (Compactar (arquivo)) = arquivo para todos os arquivos formados corretamente

Qualquer jogo que não seja bem formado ou viole as regras do xadrez é considerado ruim. Todos os jogos ruins podem ser ignorados.

Ele deve ser capaz de analisar a notação. Veja chessgames.com e https://database.lichess.org/ para alguns exemplos.

Eu compilei um arquivo dos primeiros 10000 jogos de "maio de 2017" no Chess_Games.txt

O arquivo deve ter a seguinte aparência:

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
...

Pontuação:

Para tornar as coisas objetivas, o vencedor é o algoritmo que pode compactar os arquivos em https://database.lichess.org/ o menor possível. O banco de dados de destino é o "maio de 2017" . O vencedor é quem tiver o menor arquivo que se expande adequadamente.

O arquivo a ser usado é Chess_Games.txt, que é o primeiro 10000 jogos do banco de dados "Maio de 2017" em https://database.lichess.org/ com todas as informações do cabeçalho removidas. Este arquivo será a referência de uso. Deve ter 2.789.897 bytes com um hash SHA-256 56b6d2fffc7644bcd126becd03d859a3cf6880fa01a47fa08d4d6a823a4866bc(o Pastebin pode remover a última nova linha após o jogo 10000)

Solução ingênua:

Usando algoritmos de compactação 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%)
nervoso
fonte
Não deveria haver um espaço depoishttp://
JungHwan Min 16/07/19
Obrigado por corrigi-lo. O site não me deixou publicar uma pergunta com mais de um link, pois não tenho a reputação adequada. Então eu adicionei um espaço e esperava que alguém o corrigisse ou que eu pudesse corrigi-lo mais tarde.
Edggy
2
Boa pergunta! Você poderia elaborar mais sobre como a pontuação é calculada? Além disso, qual variante da notação de xadrez padrão devemos esperar e produzir? No futuro, você pode encontrar o Sandbox :) útil
musicman523
1
Poderíamos obter o jogo / notação de xadrez que você usará para marcar as respostas?
wrymug
1
Retirei as anotações do arquivo de referência e removi as referências confusas das regras do sorteio para tornar tudo mais claro. Alterei a declaração sobre a análise para usar esses sites como referência.
Edggy

Respostas:

3

Python, score = 426508 bytes

Função de compressão: (m + 1) ⋅ log 2 (b + 1)

m é o número de movimentos eb é fator de ramificação

Tentativa 1:

Eu respondi a pergunta na menor compressão do tabuleiro de xadrez, então estou tentando corrigi-la para postar aqui.

Ingenuamente, pensei que poderia codificar cada movimento em 12 bits, 4 trigêmeos da forma (início x, início y, final x, final y), onde cada um possui 3 bits.

Assumiríamos a posição inicial e moveríamos as peças a partir daí, com o branco indo primeiro. O quadro é organizado de forma que (0, 0) seja o canto inferior esquerdo do branco.

Por exemplo, o jogo:

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

Seria codificado como:

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

Isso leva a uma codificação de 12 m bits, em que m é o número de movimentos realizados.

Tentativa 2:

Percebi que cada movimento na tentativa anterior codifica muitos movimentos ilegais. Então, decidi codificar apenas movimentos legais. Enumeramos os movimentos possíveis da seguinte forma, numere cada quadrado de modo que (0, 0) → 0, (1, 0) → 1, (x, y) → x + 8 y. Itere através dos ladrilhos e verifique se existe uma peça e se ela pode se mover. Nesse caso, adicione as posições que podem ser encontradas em uma lista. Escolha o índice da lista que é a jogada que você deseja fazer. Adicione esse número ao total de jogadas em execução ponderado por 1 mais o número de jogadas possíveis.

Exemplo como acima: A partir da posição inicial, a primeira peça que pode ser movida é o cavaleiro no quadrado 1, pode ser movida para o quadrado 16 ou 18, portanto, adicione-as à lista [(1,16),(1,18)]. Em seguida é o cavaleiro no quadrado 6, adicione seus movimentos. No geral, temos:

[(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 a jogada (12, 28), a codificamos como 13 na base 20, pois há 20 jogadas possíveis.

Então agora temos o número do jogo g 0 = 13

Em seguida, fazemos o mesmo para o preto, exceto que numeramos os blocos ao contrário (para facilitar, não é necessário) para obter a lista de movimentos:

[(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 a jogada (11, 27), a codificamos como 11 na base 20, pois há 20 jogadas possíveis.

Então agora temos o número do jogo g 1 = (11 ⋅ 20) + 13 = 233

A seguir, temos a seguinte lista de movimentos para o branco:

[(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 a jogada (6, 21), nós a codificamos como 13 na base 29, pois existem 29 possíveis.

Então agora obtemos o número do jogo g 2 = ((13 ⋅ 20) + 11) 20 + 13 = 5433

A seguir, temos a seguinte lista de movimentos para preto: [(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 a movimentação (10, 18), a codificamos como 19 na base 29, pois existem 29 possíveis.

Então agora temos o número do jogo g 3 = (((19 ⋅ 29 + 13) 20) + 11) 20 + 13 = 225833

E continue esse processo para todos os movimentos restantes. Você pode pensar em g como a função g (x, y, z) = x y + z. Assim, 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 um número de jogo g 0 , começamos na posição inicial e enumeramos todos os movimentos possíveis. Então calculamos g 1 = g 0 // l , m 0 = g 0 % l , onde l é o número de movimentos possíveis, '//' é o operador de divisão inteira e '%' é o operador de módulo. Deveria sustentar que g 0 = g 1 + m 0 . Em seguida, fazemos o movimento m 0 e repetimos.

A partir do exemplo acima, se g 0 = 225833, em seguida, g 1 = 225833 = 20 // 11291 e m 0 = 225833% 20 = 13. Próximo g 2 = 11291 // 20 = 564 e m 1 = 11291% 20 = 11. Então g 3 = 11291 // 20 = 564 e m 2 = 11291% 20 = 11. Portanto, g 4 = 564 // 29 = 19 e_m_ 3 = 564% 29 = 13. Finalmente g 5 = 19 // 29 = 0 e m 4 = 19% 29 = 19.

Então, quantos bits são usados ​​para codificar um jogo dessa maneira?

Para simplificar, digamos que sempre haja 35 jogadas a cada turno ( https://en.wikipedia.org/wiki/Branching_factor ) e, no pior dos casos, sempre escolhemos a maior, 34. O número que obteremos é 34 is 35 m + 34 ⋅ 35 m-1 + 34 ⋅ 35 m-2 + ⋯ + 34 ⋅ 35 + 34 = 35 m + 1-1 onde _m é o número de movimentos. Para codificar 35 m + 1 - 1, precisamos do log 2 (35 m + 1 ) bits que é de cerca de (m + 1) ⋅ log 2 (35) = 5,129 ⋅ (m + 1)

Em média, m = 80 (40 movimentos por jogador), o que levaria 416 bits para codificar. Se estivéssemos gravando muitos jogos, precisaríamos de uma codificação universal, pois não sabemos quantos bits cada número precisará

No pior caso, quando m = 11741, isso levaria 60229 bits para codificar.

Notas Adicionais:

Observe que g = 0 indica um jogo válido, aquele em que a peça no quadrado mais baixo se move para o quadrado mais baixo possível.

Se você quiser se referir a uma posição específica no jogo, pode ser necessário codificar o índice. Isso pode ser adicionado manualmente, por exemplo, concatenar o índice para o jogo ou adicionar um movimento "final" adicional, como o último movimento possível a cada turno. Agora isso pode levar em conta os jogadores que concedem ou 2 em sequência para indicar que os jogadores concordaram com um empate. Isso só é necessário se o jogo não terminar em xeque-mate ou impasse com base na posição, neste caso, está implícito. Nesse caso, ele eleva o número de bits necessários, em média, para 419 e, no pior caso, 60706.

Uma maneira de lidar com garfos em um cenário hipotético é codificar o jogo até o garfo e, em seguida, codificar cada ramo começando na posição bifurcada, em vez da posição inicial.

Implementação:

Dê uma olhada no meu repositório no github para obter o código: https://github.com/edggy/ChessCompress

nervoso
fonte
3

Python, score = 418581 bytes

Isso usa uma variante bijetiva de sistemas numéricos assimétricos . Por ser bijetivo, você não pode apenas compactar qualquer lista válida de jogos de xadrez em um arquivo e expandi-lo de volta para a mesma lista - você também pode expandir qualquer arquivo em uma lista válida de jogos de xadrez e compactá-lo de volta para o mesmo arquivo! Perfeito para esconder sua coleção de pornografia.

Requer python-xadrez . Execute com python script.py compressou python script.py expand, ambos lerão da entrada padrão e gravarão na saída padrão.

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
fonte
1
Verificado no Ubuntu 17.04 usando o Python 2.7.13 e executando cada comando separadamente.
Edggy
Isso é realmente ótimo! Também estou intrigado como serão os jogos pornográficos de xadrez na prática.
Anush