Puzzle del programmatore: codifica uno stato della scacchiera durante una partita


95

Non strettamente una domanda, più di un puzzle ...

Negli anni sono stato coinvolto in alcune interviste tecniche a nuovi dipendenti. Oltre a porre le domande standard "conosci la tecnologia X", ho anche cercato di farmi un'idea di come affrontano i problemi. In genere, inviavo loro la domanda via e-mail il giorno prima dell'intervista e mi aspettavo che trovassero una soluzione entro il giorno successivo.

Spesso i risultati sarebbero piuttosto interessanti - sbagliati, ma interessanti - e la persona riceverebbe comunque la mia raccomandazione se potesse spiegare perché ha adottato un approccio particolare.

Quindi ho pensato di lanciare una delle mie domande al pubblico di Stack Overflow.

Domanda: Qual è il modo più efficiente in termini di spazio a cui puoi pensare per codificare lo stato di una partita di scacchi (o un suo sottoinsieme)? Cioè, data una scacchiera con i pezzi disposti legalmente, codifica sia questo stato iniziale che tutte le successive mosse legali prese dai giocatori nel gioco.

Nessun codice richiesto per la risposta, solo una descrizione dell'algoritmo che useresti.

EDIT: Come ha sottolineato uno dei poster, non ho considerato l'intervallo di tempo tra le mosse. Sentiti libero di tenerne conto anche come extra opzionale :)

EDIT2: Solo per ulteriori chiarimenti ... Ricorda, il codificatore / decodificatore è a conoscenza delle regole. Le uniche cose che hanno davvero bisogno di essere memorizzate sono le scelte del giocatore - qualsiasi altra cosa può essere considerata conosciuta dal codificatore / decodificatore.

EDIT3: Sarà difficile scegliere un vincitore qui :) Molte ottime risposte!


4
Lo stato iniziale di una partita a scacchi non è ben definito? Perché deve essere codificato? Penso che dovrebbe essere sufficiente codificare solo le differenze tra ogni turno (= mosse).
tanascius

1
Presume che il gioco possa iniziare con qualsiasi impostazione iniziale legale (proprio come nei puzzle di scacchi che puoi trovare sui giornali).
Aaron Digulla

6
per essere rigorosi, dovrai anche codificare tutte le posizioni passate, perché se la stessa posizione appare tre volte è un pareggio en.wikipedia.org/wiki/Threefold_repetition
flybywire

4
Suggerimento: rendi questo un vero concorso in cui le persone inviano le loro voci come programmi. Un programma prenderebbe una partita di scacchi come input (potresti definire un formato di base, leggibile dall'uomo e non ottimizzato per questo) e produrrebbe il gioco compresso. Quindi, con un parametro, prenderebbe il gioco compresso e rigenererebbe l'input originale che dovrebbe corrispondere.
Vilx-

2
Più precisamente, dimostrerebbe che non puoi seguire le istruzioni ... Anche il più ubercoder deve seguire le istruzioni ad un certo punto. Mi sono imbattuto in situazioni in cui mi è stato detto di implementare qualcosa in un certo modo, anche se ho pensato (e detto) che fosse un'implementazione stupida, solo per essere lasciato con l'uovo in faccia quando si è scoperto che c'era un'ottima ragione (che non conoscevo o non comprendevo) per implementarlo in quel modo.
Andrew Rollings

Risposte:


132

Aggiornamento: mi è piaciuto così tanto questo argomento che ho scritto Puzzle di programmazione, Posizioni degli scacchi e Codifica di Huffman . Se leggi questo articolo, ho determinato che l' unico modo per memorizzare uno stato di gioco completo è memorizzare un elenco completo di mosse. Continua a leggere per scoprire il motivo. Quindi uso una versione leggermente semplificata del problema per il layout dei pezzi.

Il problema

Questa immagine illustra la posizione iniziale degli scacchi. Gli scacchi si svolgono su una scacchiera 8x8 con ogni giocatore che inizia con un set identico di 16 pezzi composto da 8 pedoni, 2 torri, 2 cavalieri, 2 alfieri, 1 regina e 1 re come illustrato qui:

posizione di scacchi iniziale

Le posizioni sono generalmente registrate come una lettera per la colonna seguita dal numero per la riga, quindi la regina del bianco è in d1. Le mosse sono spesso memorizzate in notazione algebrica , che non è ambigua e generalmente specifica solo le informazioni minime necessarie. Considera questa apertura:

  1. e4 e5
  2. Nf3 Nc6
  3. ...

che si traduce in:

  1. Il bianco muove il pedone del re da e2 a e4 (è l'unico pezzo che può arrivare a e4 da qui "e4");
  2. Il nero muove il pedone del re da e7 a e5;
  3. Il bianco sposta il cavallo (N) in f3;
  4. Il nero sposta il cavaliere in c6.
  5. ...

La scheda ha questo aspetto:

apertura anticipata

Una capacità importante per qualsiasi programmatore è quella di essere in grado di specificare correttamente e in modo univoco il problema .

Quindi cosa manca o cosa è ambiguo? A quanto pare.

Board State vs Game State

La prima cosa che devi determinare è se stai memorizzando lo stato di una partita o la posizione dei pezzi sul tabellone. Codificare semplicemente le posizioni dei pezzi è una cosa, ma il problema dice "tutte le successive mosse legali". Il problema inoltre non dice nulla sulla conoscenza delle mosse fino a questo punto. Questo in realtà è un problema come spiegherò.

Arrocco

Il gioco è proceduto come segue:

  1. e4 e5
  2. Nf3 Nc6
  3. Si b5 a6
  4. Ba4 Bc5

Il tabellone appare come segue:

apertura successiva

Il bianco ha la possibilità di arroccare . Parte dei requisiti per questo è che il re e la torre in questione non possono mai essersi mossi, quindi se il re o una delle due torri di ciascuna parte ha mosso dovrà essere immagazzinato. Ovviamente se non sono nelle posizioni di partenza si sono spostati altrimenti è necessario specificarlo.

Esistono diverse strategie che possono essere utilizzate per affrontare questo problema.

In primo luogo, potremmo memorizzare 6 bit di informazioni extra (1 per ogni torre e re) per indicare se quel pezzo si è mosso. Potremmo semplificarlo memorizzando solo un po 'per uno di questi sei quadrati se il pezzo giusto si trova in esso. In alternativa potremmo trattare ogni pezzo non mosso come un altro tipo di pezzo, quindi invece di 6 tipi di pezzo su ciascun lato (pedone, torre, cavaliere, alfiere, regina e re) ce ne sono 8 (aggiungendo torre immobile e re immobile).

En Passant

Un'altra regola peculiare e spesso trascurata negli scacchi è En Passant .

en passant

Il gioco è andato avanti.

  1. e4 e5
  2. Nf3 Nc6
  3. Si b5 a6
  4. Ba4 Bc5
  5. OO b5
  6. Si b3 b4
  7. c4

Il pedone nero in b4 ora ha la possibilità di muovere il suo pedone in b4 in c3 prendendo il pedone bianco in c4. Questo accade solo alla prima opportunità, il che significa che se il Nero passa l'opzione ora non può eseguire la mossa successiva. Quindi dobbiamo archiviarlo.

Se conosciamo la mossa precedente possiamo sicuramente rispondere se En Passant è possibile. In alternativa possiamo memorizzare se ogni pedone sul suo 4 ° rango si è appena spostato lì con una doppia mossa in avanti. Oppure possiamo guardare ogni possibile posizione di En Passant sul tabellone e avere una bandiera per indicare se è possibile o meno.

Promozione

promozione del pedone

È la mossa di White. Se il Bianco muove il suo pedone da h7 a h8, può essere promosso a qualsiasi altro pezzo (ma non al re). Il 99% delle volte viene promosso a Regina ma a volte non lo è, in genere perché ciò potrebbe forzare uno stallo quando altrimenti vinceresti. Questo è scritto come:

  1. h8 = Q

Questo è importante nel nostro problema perché significa che non possiamo contare sul fatto che ci sia un numero fisso di pezzi su ciascun lato. È del tutto possibile (ma incredibilmente improbabile) che una parte finisca con 9 regine, 10 torri, 10 alfieri o 10 cavalieri se tutte le 8 pedine vengono promosse.

Stallo

Quando ti trovi in ​​una posizione dalla quale non puoi vincere, la tua migliore tattica è tentare lo stallo . La variante più probabile è quella in cui non puoi fare una mossa legale (di solito perché qualsiasi mossa quando metti il ​​tuo re sotto controllo). In questo caso puoi richiedere un pareggio. Questo è facile da soddisfare.

La seconda variante consiste nella triplice ripetizione . Se la stessa posizione sul tabellone si verifica tre volte in una partita (o si verificherà una terza volta nella mossa successiva), è possibile richiedere la patta. Le posizioni non devono necessariamente verificarsi in un ordine particolare (il che significa che non deve essere ripetuta tre volte la stessa sequenza di mosse). Questo complica enormemente il problema perché devi ricordare ogni precedente posizione in board. Se questo è un requisito del problema, l'unica possibile soluzione al problema è memorizzare ogni mossa precedente.

Infine, c'è la regola delle cinquanta mosse . Un giocatore può richiedere la patta se nessun pedone si è mosso e nessun pezzo è stato preso nelle cinquanta mosse consecutive precedenti, quindi dovremmo memorizzare quante mosse da quando un pedone è stato spostato o un pezzo preso (l'ultima delle due. Ciò richiede 6 bit (0-63).

Di chi è il turno?

Ovviamente dobbiamo anche sapere di chi è il turno e questa è una singola informazione.

Due problemi

A causa del caso di stallo, l'unico modo fattibile o sensato per memorizzare lo stato del gioco è memorizzare tutte le mosse che hanno portato a questa posizione. Affronterò quell'unico problema. Il problema dello stato della scacchiera sarà semplificato in questo modo: memorizzare la posizione attuale di tutti i pezzi sulla scacchiera ignorando le condizioni di arrocco, en passant, stallo e di chi è il turno .

La disposizione dei pezzi può essere gestita a grandi linee in due modi: memorizzando il contenuto di ogni quadrato o memorizzando la posizione di ogni pezzo.

Contenuti semplici

Ci sono sei tipi di pezzi (pedone, torre, cavaliere, alfiere, regina e re). Ogni pezzo può essere bianco o nero, quindi un quadrato può contenere uno dei 12 pezzi possibili o può essere vuoto quindi ci sono 13 possibilità. 13 può essere memorizzato in 4 bit (0-15) Quindi la soluzione più semplice è memorizzare 4 bit per ogni quadrato per 64 quadrati o 256 bit di informazione.

Il vantaggio di questo metodo è che la manipolazione è incredibilmente facile e veloce. Questo potrebbe anche essere esteso aggiungendo altre 3 possibilità senza aumentare i requisiti di stoccaggio: un pedone che si è mosso di 2 spazi nell'ultimo turno, un re che non si è mosso e una torre che non si è mossa, il che soddisferà molto dei problemi menzionati in precedenza.

Ma possiamo fare di meglio.

Codifica base 13

Spesso è utile pensare alla posizione nel consiglio di amministrazione come a un numero molto elevato. Questo è spesso fatto in informatica. Ad esempio, il problema dell'arresto tratta un programma per computer (giustamente) come un numero elevato.

La prima soluzione tratta la posizione come un numero a 64 cifre in base 16 ma, come dimostrato, c'è ridondanza in questa informazione (essendo le 3 possibilità inutilizzate per "cifra") quindi possiamo ridurre lo spazio numerico a 64 cifre in base 13. Ovviamente questo non può essere fatto nel modo più efficiente possibile con la base 16, ma risparmierà sui requisiti di archiviazione (e il nostro obiettivo è ridurre al minimo lo spazio di archiviazione).

In base 10 il numero 234 è equivalente a 2 x 10 2 + 3 x 10 1 + 4 x 10 0 .

In base 16 il numero 0xA50 è equivalente a 10 x 16 2 + 5 x 16 1 + 0 x 16 0 = 2640 (decimale).

Quindi possiamo codificare la nostra posizione come p 0 x 13 63 + p 1 x 13 62 + ... + p 63 x 13 0 dove p i rappresenta il contenuto del quadrato i .

2 256 è uguale a circa 1,16e77. 13 64 è uguale a circa 1,96e71, che richiede 237 bit di spazio di archiviazione. Quel risparmio di appena il 7,5% ha un costo di manipolazione significativamente maggiore.

Codifica a base variabile

Nei tabelloni legali alcuni pezzi non possono apparire in determinate caselle. Ad esempio, le pedine non possono comparire al primo o all'ottavo grado, riducendo le possibilità per quelle caselle a 11. Ciò riduce le possibili scacchiere a 11 16 x 13 48 = 1,35e70 (approssimativamente), richiedendo 233 bit di spazio di archiviazione.

In realtà la codifica e la decodifica di tali valori in e da decimale (o binario) è un po 'più complicata ma può essere eseguita in modo affidabile ed è lasciata come esercizio al lettore.

Alfabeti a larghezza variabile

I due metodi precedenti possono essere descritti entrambi come codifica alfabetica a larghezza fissa . Ciascuno degli 11, 13 o 16 membri dell'alfabeto viene sostituito con un altro valore. Ogni "carattere" ha la stessa larghezza ma l'efficienza può essere migliorata se si considera che ogni carattere non è ugualmente probabile.

codice Morse

Considera il codice Morse (nella foto sopra). I caratteri in un messaggio sono codificati come una sequenza di trattini e punti. Quei trattini e punti vengono trasferiti via radio (in genere) con una pausa tra di loro per delimitarli.

Notare come la lettera E ( la lettera più comune in inglese ) sia un singolo punto, la sequenza più breve possibile, mentre Z (la meno frequente) è composta da due trattini e due segnali acustici.

Un tale schema può ridurre significativamente la dimensione di un messaggio previsto , ma ha il costo di aumentare la dimensione di una sequenza di caratteri casuale.

Va notato che il codice Morse ha un'altra caratteristica incorporata: i trattini sono lunghi come tre punti, quindi il codice sopra è stato creato con questo in mente per ridurre al minimo l'uso dei trattini. Poiché 1 e 0 (i nostri elementi costitutivi) non hanno questo problema, non è una caratteristica che dobbiamo replicare.

Infine, ci sono due tipi di pause nel codice Morse. Una pausa breve (la lunghezza di un punto) viene utilizzata per distinguere tra punti e trattini. Uno spazio più lungo (la lunghezza di un trattino) viene utilizzato per delimitare i caratteri.

Quindi come si applica al nostro problema?

Codifica Huffman

Esiste un algoritmo per gestire i codici di lunghezza variabile chiamato codifica Huffman . La codifica di Huffman crea una sostituzione del codice di lunghezza variabile, in genere utilizza la frequenza prevista dei simboli per assegnare valori più brevi ai simboli più comuni.

Albero del codice di Huffman

Nell'albero sopra, la lettera E è codificata come 000 (o sinistra-sinistra-sinistra) e S è 1011. Dovrebbe essere chiaro che questo schema di codifica non è ambiguo .

Questa è una distinzione importante dal codice Morse. Il codice Morse ha il separatore di caratteri, quindi può eseguire sostituzioni ambigue (ad esempio, 4 punti possono essere H o 2 Is) ma abbiamo solo 1 e 0 quindi scegliamo invece una sostituzione non ambigua.

Di seguito è una semplice implementazione:

private static class Node {
  private final Node left;
  private final Node right;
  private final String label;
  private final int weight;

  private Node(String label, int weight) {
    this.left = null;
    this.right = null;
    this.label = label;
    this.weight = weight;
  }

  public Node(Node left, Node right) {
    this.left = left;
    this.right = right;
    label = "";
    weight = left.weight + right.weight;
  }

  public boolean isLeaf() { return left == null && right == null; }

  public Node getLeft() { return left; }

  public Node getRight() { return right; }

  public String getLabel() { return label; }

  public int getWeight() { return weight; }
}

con dati statici:

private final static List<string> COLOURS;
private final static Map<string, integer> WEIGHTS;

static {
  List<string> list = new ArrayList<string>();
  list.add("White");
  list.add("Black");
  COLOURS = Collections.unmodifiableList(list);
  Map<string, integer> map = new HashMap<string, integer>();
  for (String colour : COLOURS) {
    map.put(colour + " " + "King", 1);
    map.put(colour + " " + "Queen";, 1);
    map.put(colour + " " + "Rook", 2);
    map.put(colour + " " + "Knight", 2);
    map.put(colour + " " + "Bishop";, 2);
    map.put(colour + " " + "Pawn", 8);
  }
  map.put("Empty", 32);
  WEIGHTS = Collections.unmodifiableMap(map);
}

e:

private static class WeightComparator implements Comparator<node> {
  @Override
  public int compare(Node o1, Node o2) {
    if (o1.getWeight() == o2.getWeight()) {
      return 0;
    } else {
      return o1.getWeight() < o2.getWeight() ? -1 : 1;
    }
  }
}

private static class PathComparator implements Comparator<string> {
  @Override
  public int compare(String o1, String o2) {
    if (o1 == null) {
      return o2 == null ? 0 : -1;
    } else if (o2 == null) {
      return 1;
    } else {
      int length1 = o1.length();
      int length2 = o2.length();
      if (length1 == length2) {
        return o1.compareTo(o2);
      } else {
        return length1 < length2 ? -1 : 1;
      }
    }
  }
}

public static void main(String args[]) {
  PriorityQueue<node> queue = new PriorityQueue<node>(WEIGHTS.size(),
      new WeightComparator());
  for (Map.Entry<string, integer> entry : WEIGHTS.entrySet()) {
    queue.add(new Node(entry.getKey(), entry.getValue()));
  }
  while (queue.size() > 1) {
    Node first = queue.poll();
    Node second = queue.poll();
    queue.add(new Node(first, second));
  }
  Map<string, node> nodes = new TreeMap<string, node>(new PathComparator());
  addLeaves(nodes, queue.peek(), &quot;&quot;);
  for (Map.Entry<string, node> entry : nodes.entrySet()) {
    System.out.printf("%s %s%n", entry.getKey(), entry.getValue().getLabel());
  }
}

public static void addLeaves(Map<string, node> nodes, Node node, String prefix) {
  if (node != null) {
    addLeaves(nodes, node.getLeft(), prefix + "0");
    addLeaves(nodes, node.getRight(), prefix + "1");
    if (node.isLeaf()) {
      nodes.put(prefix, node);
    }
  }
}

Un possibile output è:

         White    Black
Empty          0 
Pawn       110      100
Rook     11111    11110
Knight   10110    10101
Bishop   10100    11100
Queen   111010   111011
King    101110   101111

Per una posizione iniziale, ciò equivale a 32 x 1 + 16 x 3 + 12 x 5 + 4 x 6 = 164 bit.

Differenza di stato

Un altro possibile approccio è combinare il primo approccio con la codifica di Huffman. Questo si basa sul presupposto che le scacchiere più attese (piuttosto che quelle generate casualmente) hanno maggiori probabilità che non assomiglino, almeno in parte, a una posizione di partenza.

Quindi quello che fai è XOR la ​​posizione corrente della scheda a 256 bit con una posizione iniziale di 256 bit e quindi codificarla (usando la codifica Huffman o, diciamo, qualche metodo di codifica della lunghezza di esecuzione ). Ovviamente questo sarà molto efficiente all'inizio (64 0 probabilmente corrispondono a 64 bit) ma aumenterà lo spazio di archiviazione richiesto man mano che il gioco procede.

Posizione del pezzo

Come accennato, un altro modo per affrontare questo problema è memorizzare invece la posizione di ogni pezzo che un giocatore ha. Questo funziona particolarmente bene con le posizioni di fine partita in cui la maggior parte dei quadrati sarà vuota (ma nell'approccio di codifica di Huffman, i quadrati vuoti usano comunque solo 1 bit).

Ogni lato avrà un re e 0-15 altri pezzi. A causa della promozione, la composizione esatta di quei pezzi può variare abbastanza da non poter presumere che i numeri basati sulle posizioni di partenza siano massimi.

Il modo logico per dividerlo è memorizzare una posizione composta da due lati (bianco e nero). Ogni lato ha:

  • Un re: 6 bit per la posizione;
  • Ha pedine: 1 (sì), 0 (no);
  • Se sì, numero di pedoni: 3 bit (0-7 + 1 = 1-8);
  • Se sì, la posizione di ogni pedone è codificata: 45 bit (vedi sotto);
  • Numero di non pedoni: 4 bit (0-15);
  • Per ogni pezzo: tipo (2 bit per regina, torre, cavaliere, alfiere) e posizione (6 bit)

Per quanto riguarda la posizione dei pedoni, i pedoni possono essere solo su 48 caselle possibili (non 64 come le altre). In quanto tale, è meglio non sprecare i 16 valori in più che utilizzare 6 bit per pedone. Quindi se hai 8 pedoni ci sono 48 8 possibilità, pari a 28.179.280.429.056. Hai bisogno di 45 bit per codificare tanti valori.

Sono 105 bit per lato o 210 bit in totale. Tuttavia, la posizione di partenza è il caso peggiore per questo metodo e migliorerà notevolmente man mano che rimuovi i pezzi.

Va sottolineato che ci sono meno di 48 8 possibilità perché i pedoni non possono essere tutti nella stessa casella. Il primo ha 48 possibilità, il secondo 47 e così via. 48 x 47 x… x 41 = 1.52e13 = 44 bit di archiviazione.

Puoi migliorare ulteriormente questo eliminando le caselle che sono occupate da altri pezzi (compreso l'altro lato) in modo da poter piazzare prima i pedoni bianchi poi i non pedoni neri, poi i pedoni bianchi e infine i pedoni neri. In una posizione iniziale, i requisiti di archiviazione vengono ridotti a 44 bit per il bianco e 42 bit per il nero.

Approcci combinati

Un'altra possibile ottimizzazione è che ciascuno di questi approcci ha i suoi punti di forza e di debolezza. Potresti, ad esempio, scegliere i migliori 4 e quindi codificare un selettore di schema nei primi due bit e quindi la memoria specifica dello schema dopo.

Con l'overhead così piccolo, questo sarà di gran lunga l'approccio migliore.

Stato del gioco

Torno al problema di memorizzare una partita piuttosto che una posizione . A causa della triplice ripetizione dobbiamo memorizzare l'elenco delle mosse che si sono verificate fino a questo punto.

Annotazioni

Una cosa che devi determinare è che stai semplicemente memorizzando un elenco di mosse o stai annotando il gioco? Le partite di scacchi sono spesso annotate, ad esempio:

  1. Bb5 !! Nc4?

La mossa del bianco è contrassegnata da due punti esclamativi come brillante mentre quella del nero è vista come un errore. Vedi punteggiatura degli scacchi .

Inoltre potresti anche dover memorizzare del testo libero man mano che vengono descritte le mosse.

Presumo che le mosse siano sufficienti quindi non ci saranno annotazioni.

Notazione algebrica

Potremmo semplicemente memorizzare il testo della mossa qui ("e4", "Bxb5", ecc.). Includendo un byte di terminazione che stai guardando a circa 6 byte (48 bit) per mossa (caso peggiore). Non è particolarmente efficiente.

La seconda cosa da provare è memorizzare la posizione iniziale (6 bit) e la posizione finale (6 bit) in modo da 12 bit per movimento. Questo è decisamente meglio.

In alternativa possiamo determinare tutte le mosse legali dalla posizione corrente in modo prevedibile e deterministico e in uno stato che abbiamo scelto. Questo poi risale alla codifica di base variabile menzionata sopra. Il Bianco e il Nero hanno 20 mosse possibili ciascuno per la prima mossa, di più nella seconda e così via.

Conclusione

Non esiste una risposta assolutamente giusta a questa domanda. Ci sono molti possibili approcci di cui sopra sono solo alcuni.

Quello che mi piace di questo e di problemi simili è che richiede abilità importanti per qualsiasi programmatore come considerare il modello di utilizzo, determinare accuratamente i requisiti e pensare ai casi d'angolo.

Posizioni di scacchi prese come screenshot da Chess Position Trainer .


3
e poi gzip il risultato (se le intestazioni non aumentano il risultato); ^)
Toad

Non avresti bisogno di raddoppiare lo spazio per indicare Black o White?
Daniel Elliott

5
Buon post. Piccola correzione: l'arrocco richiede 4 bit, uno per ogni modo di arrocco (bianco e nero, lato re e lato regina), perché le torri potrebbero essersi mosse e poi arretrate. Un po 'più importante: probabilmente dovresti includere di chi è la mossa. =)
A. Rex

9
Quanto alla promozione a cavaliere, l'ho fatto una volta. Situazione davvero selvaggia: era una mossa dall'accoppiamento con me, non potevo fermarlo. Aveva ignorato il mio pedone perché mentre lo promuoveva sarebbe stata una mossa in ritardo. Vorrei avere la mia macchina fotografica quando invece sono stato promosso a cavaliere e l'ho accoppiato!
Loren Pechtel

2
Sono sorpreso che il tuo articolo non abbia menzionato [FEN] [1], che gestisce l'arrocco, la disponibilità en passant, ecc. [1] en.wikipedia.org/wiki/FEN
Ross

48

È meglio archiviare le partite di scacchi in un formato standard leggibile dall'uomo.

La Portable Game Notation assume una posizione iniziale standard (anche se non è necessario ) e elenca solo le mosse, turno dopo turno. Un formato standard compatto, leggibile dall'uomo.

Per esempio

[Event "F/S Return Match"]
[Site "Belgrade, Serbia Yugoslavia|JUG"]
[Date "1992.11.04"]
[Round "29"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]

1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8  10. d4 Nbd7
11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5
Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6
23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5
hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5
35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6
Nf2 42. g4 Bd3 43. Re6 1/2-1/2

Se vuoi rimpicciolirlo, comprimilo . Lavoro fatto!


23
In mia difesa contro i 2 voti negativi ricevuti: 1) Fa quello che vuoi 2) Supera il test thedailywtf.com/articles/riddle-me-an-interview.aspx : "... alcune delle persone che possono risolvere questi indovinelli sono esattamente il tipo di persone che non vuoi come programmatori. Vorresti lavorare con il ragazzo che costruisce una bilancia / chiatta a spostamento d'acqua, fa un taxi con un 747 fino al molo e poi pesa il jumbo jet usando quello, invece di chiamare semplicemente Boeing in primo luogo? " Non assumi qualcuno che ti inventa una codifica casuale durante l'intervista, perché lo farà anche nel suo codice.
Rob Grant,

1
Bene, se sto specificatamente chiedendo loro di risolvere un problema per ottenere la loro tecnica di risoluzione dei problemi, allora puoi presumere che coprirò le altre cose con altre domande ...
Andrew Rollings

7
@reinier: Non sto dicendo di essere completamente all'oscuro dei problemi di densità delle informazioni (hai erroneamente diagnosticato la mia risposta come un'ammissione di incompetenza). Sicuramente vorrai assumere la persona che codifica in base a uno standard di archiviazione dati esistente e che riconosce che utilizzare strumenti esistenti appropriati piuttosto che utilizzare i propri può essere una buona idea: "Abbiamo inventato The Wheel 2.0! Ora è ancora più rotondo!" Sicuramente non vuoi assumere la persona che pensa - stranamente - che l'uso delle funzioni di libreria sia un segno di debolezza.
Rob Grant,

18
Questa sarebbe assolutamente la mia prima risposta a questa domanda in un'intervista. Vuoi dimostrare che il tuo primo istinto è cercare una soluzione pronta. Se l'intervistatore ti dice che vuole sentire cosa puoi inventare da solo, allora puoi andare in una soluzione di imballaggio.
Bill the Lizard

2
Sono con Robert su questo: la soluzione esistente è pratica, leggibile dall'uomo e abbastanza compatta. Sono tutte imprese PRINCIPALI rispetto alla soluzione personalizzata super imballata con algoritmi complicati per decodificarle. Se si tratta di colloquio considererei sicuramente anche l'aspetto pratico! Saresti stupito di quante volte persone davvero intelligenti escogitano soluzioni poco pratiche e complicate. Di solito è attribuito al fatto che possono gestire la complessità nella loro testa, ma poi - che dire del resto di noi ...
MaR

15

Grande puzzle!

Vedo che la maggior parte delle persone memorizza la posizione di ogni pezzo. Che ne dici di adottare un approccio più semplice e memorizzare il contenuto di ogni quadrato ? Questo si occupa della promozione e dei pezzi catturati automaticamente.

E consente la codifica di Huffman . In realtà, la frequenza iniziale dei pezzi sulla scacchiera è quasi perfetta per questo: metà delle caselle sono vuote, metà delle restanti caselle sono pedine, eccetera.

Considerando la frequenza di ogni pezzo, ho costruito un albero di Huffman su carta, che non ripeterò qui. Il risultato, dove csta per il colore (bianco = 0, nero = 1):

  • 0 per i quadrati vuoti
  • 1c0 per pedone
  • 1c100 per la torre
  • 1c101 per il cavaliere
  • 1c110 per vescovo
  • 1c1110 per la regina
  • 1c1111 per il re

Per l'intero consiglio nella sua situazione iniziale, abbiamo

  • quadrati vuoti: 32 * 1 bit = 32 bit
  • pedine: 16 * 3 bit = 48 bit
  • torri / cavalieri / alfieri: 12 * 5 bit = 60 bit
  • regine / re: 4 * 6 bit = 24 bit

Totale: 164 bit per lo stato iniziale della scheda. Significativamente inferiore ai 235 bit della risposta attualmente più votata. E diventerà sempre più piccolo con il progredire del gioco (tranne dopo una promozione).

Ho guardato solo la posizione dei pezzi sulla scacchiera; stato aggiuntivo (il cui turno, chi ha arroccato, en passant, ripetizione di mosse, ecc.) dovrà essere codificato separatamente. Forse altri 16 bit al massimo, quindi 180 bit per l'intero stato del gioco. Possibili ottimizzazioni:

  • Tralasciando i pezzi meno frequenti e conservando separatamente la loro posizione. Ma questo non aiuta ... sostituire re e regina con un quadrato vuoto fa risparmiare 5 bit, che sono esattamente i 5 bit necessari per codificare la loro posizione in un altro modo.
  • "Nessun pedone nell'ultima riga" potrebbe essere facilmente codificato utilizzando una tabella di Huffman diversa per le ultime righe, ma dubito che aiuti molto. Probabilmente ti ritroverai ancora con lo stesso albero di Huffman.
  • "Un alfiere bianco, uno nero" può essere codificato introducendo simboli extra che non hanno il cbit, che possono essere dedotti dal quadrato su cui si trova l'alfiere. (Le pedine promosse a vescovi interrompono questo schema ...)
  • Le ripetizioni di quadrati vuoti potrebbero essere codificate per la lunghezza di serie introducendo simboli aggiuntivi per, diciamo, "2 quadrati vuoti di fila" e "4 quadrati vuoti di fila". Ma non è così facile stimare la frequenza di questi, e se sbagli, farà male piuttosto che aiutare.

Nessuna pedina nei ranghi della banca fa risparmiare un po ': puoi tagliare il bit # 3 da tutti gli altri schemi. Quindi risparmierai un bit per pezzo in realtà su un banco.
Loren Pechtel

2
Puoi creare un albero di Huffman separato per ciascuno dei 64 quadrati, poiché alcuni probabilmente hanno alcuni pezzi più spesso di altri.
Claudiu

9

L'approccio della tabella di ricerca davvero grande

Posizione - 18 byte Il
numero stimato di posizioni legali è 10 43
È sufficiente enumerarle tutte e la posizione può essere memorizzata in soli 143 bit. È necessario 1 bit in più per indicare quale lato giocherà successivamente

L'enumerazione non è pratica ovviamente, ma questo mostra che sono richiesti almeno 144 bit.

Mosse - 1 byte
Di solito ci sono circa 30-40 mosse legali per ogni posizione, ma il numero può arrivare fino a 218 Consente di enumerare tutte le mosse legali per ogni posizione. Ora ogni mossa può essere codificata in un byte.

Abbiamo ancora molto spazio per mosse speciali come 0xFF per rappresentare le dimissioni.


3
Dritto al cuore del requisito "il modo più efficiente in termini di spazio a cui puoi pensare per codificare lo stato di una partita di scacchi" - Niente è meglio per schiacciare qualcosa di un dizionario, e questo include una mosca.
Andrew

1
Ho trovato un collegamento interessante su quanto tempo ci vorrebbe per generare un tale dizionario :) ioannis.virtualcomposer2000.com/math/EveryChess.html
Andrew Rollings

La stima di Shannons è un po 'datata :-) Non ha incluso né promozioni né acquisizioni, il che ha aumentato il numero di una discreta quantità. Victor Allis 1994 ha assegnato un limite superiore di 5x10 ^ 52
Gunther Piez

Sicuramente con una codifica a lunghezza variabile solo la media è almeno 10 ^ 43? Una codifica orientata verso più posizioni deve ridurlo, soprattutto perché molte delle posizioni sono impossibili.
Phil H

Il collegamento EveryChess è ora "in vendita", collegamento archive.org: web.archive.org/web/20120303094654/http://…
oPless

4

Aggiungerebbe interesse all'ottimizzazione per la dimensione media del case per i giochi tipici giocati da umani, invece del caso peggiore. (L'affermazione del problema non dice quale; la maggior parte delle risposte presume il caso peggiore.)

Per la sequenza delle mosse, fai in modo che un buon motore scacchistico generi mosse da ogni posizione; produrrà un elenco di k possibili mosse, ordinate in base alla loro classificazione in base alla loro qualità. Le persone generalmente scelgono le mosse buone più spesso delle mosse casuali, quindi dobbiamo imparare una mappatura da ogni posizione nell'elenco alla probabilità che le persone scelgano una mossa che "buona". Utilizzando queste probabilità (basate su un corpus di partite di alcuni database di scacchi in Internet), codifica le mosse con la codifica aritmetica . (Il decoder deve utilizzare lo stesso motore scacchistico e la stessa mappatura.)

Per la posizione di partenza, l'approccio di ralu funzionerebbe. Potremmo perfezionarlo con la codifica aritmetica anche lì, se avessimo un modo per pesare le scelte in base alla probabilità - ad esempio, i pezzi spesso appaiono in configurazioni che si difendono a vicenda, non a caso. È più difficile vedere un modo semplice per incorporare quella conoscenza. Un'idea: ripiegare invece sulla codifica della mossa sopra, partendo dalla posizione di apertura standard e trovando una sequenza che finisca nella scacchiera desiderata. (Potresti provare la ricerca A * con una distanza euristica pari alla somma delle distanze dei pezzi dalle loro posizioni finali, o qualcosa del genere). conoscenza.

È anche un po 'difficile stimare quanti risparmi questo ti comporterebbe nella complessità del caso medio, senza raccogliere alcune statistiche da un corpus reale. Ma il punto di partenza con tutte le mosse ugualmente probabili penso che già supererebbe la maggior parte delle proposte qui: la codifica aritmetica non necessita di un numero intero di bit per mossa.


La complessità per la memorizzazione di queste informazioni nel pool è O (n), controlla la mia risposta modificata.
Luka Rahne

ralu, non sono sicuro di quello che stai dicendo, ma se intendi che la tua rappresentazione di una sequenza di mosse usa lo spazio ottimale nel peggiore dei casi, allora non lo contraddico. L'idea qui è di sfruttare alcune mosse più probabili di altre.
Darius Bacon

Tutto ciò di cui hai bisogno per trovare posizioni più simili è usare un motore di scacchi deterministico (e forte) che ordina le mosse disponibili in una data posizione in modo deterministico.
Luka Rahne

4

Attaccare un sottoproblema della codifica dei passaggi dopo che una posizione iniziale è stata codificata. L'approccio consiste nel creare un "elenco collegato" di passaggi.

Ogni fase del gioco è codificata come la coppia "vecchia posizione-> nuova posizione". Conosci la posizione iniziale all'inizio della partita a scacchi; attraversando l'elenco collegato dei passaggi, puoi raggiungere lo stato dopo che X si muove.

Per codificare ogni passaggio, sono necessari 64 valori per codificare la posizione iniziale (6 bit per 64 quadrati sulla lavagna - 8x8 quadrati) e 6 bit per la posizione finale. 16 bit per 1 mossa di ogni lato.

La quantità di spazio che richiederebbe la codifica di un dato gioco è quindi proporzionale al numero di mosse:

10 x (numero di mosse bianche + numero di mosse nere) bit.

AGGIORNAMENTO: potenziale complicanza con pedoni promossi. È necessario essere in grado di indicare a cosa viene promosso il pedone - potrebbero essere necessari bit speciali (per risparmiare spazio, utilizzare un codice grigio, poiché la promozione del pedone è estremamente rara).

AGGIORNAMENTO 2: non è necessario codificare le coordinate complete della posizione finale. Nella maggior parte dei casi, il pezzo che viene spostato può spostarsi in non più di X posti. Ad esempio, un pedone può avere un massimo di 3 opzioni di movimento in un dato punto. Realizzando quel numero massimo di mosse per ogni tipo di pezzo, possiamo salvare bit sulla codifica della "destinazione".

Pawn: 
   - 2 options for movement (e2e3 or e2e4) + 2 options for taking = 4 options to encode
   - 12 options for promotions - 4 promotions (knight, biship, rook, queen) times 3 squares (because you can take a piece on the last row and promote the pawn at the same time)
   - Total of 16 options, 4 bits
Knight: 8 options, 3 bits
Bishop: 4 bits
Rook: 4 bits
King: 3 bits
Queen: 5 bits

Quindi la complessità spaziale per mossa di bianco o nero diventa

6 bit per la posizione iniziale + (numero variabile di bit in base al tipo di oggetto spostato).


Appena aggiornato, intendevo 128 combinazioni - chiaramente meno di 128 bit :) :)
Alex Weinstein

1
Uno stato di gioco non è la stessa cosa di una mossa. Ogni data posizione può essere pensata come un vertice o un nodo e una mossa legale può essere pensata come un bordo o una freccia diretti, che formano un grafo (diretto aciclico).
Shaggy Frog

Non sono sicuro del motivo per cui i voti negativi: mi piacerebbe sentire le opinioni delle persone sull'idea aggiornata.
Alex Weinstein

1
Questo non influisce sul tuo ragionamento, ma su una piccola correzione: un pedone può avere quattro mosse escluse le promozioni o 12 mosse comprese le promozioni. Pedone di esempio in e2: e3, e4, exd3, exf3. Pedone di esempio in e7: e8Q, e8N, e8R, e8B, exd8Q, exd8N, exd8R, exd8B, exf8Q, exf8N, exf8R, exf8B.
A. Rex

1
Un problema minore: 5 bit codificano solo 32 valori. Per specificare qualsiasi quadrato sulla lavagna sono necessari 6 bit.
Chris Dodd

4

Ho visto questa domanda la scorsa notte e mi ha incuriosito così mi sono seduto a letto a pensare a soluzioni. La mia risposta finale è abbastanza simile a quella di int3 in realtà.

Soluzione di base

Supponendo una partita di scacchi standard e che tu non codifichi le regole (come il Bianco va sempre per primo), allora puoi risparmiare molto codificando solo le mosse che ogni pezzo fa.

Ci sono 32 pezzi in totale, ma ad ogni mossa sai di che colore si sta muovendo, quindi ci sono solo 16 caselle di cui preoccuparti, ovvero 4 bit per quale pezzo si muove in questo turno.

Ogni pezzo ha solo un set di mosse limitato, che enumereresti in qualche modo.

  • Pedone: 4 opzioni, 2 bit (1 passo avanti, 2 passi avanti, 1 ogni diagonale)
  • Rook: 14 opzioni, 4 bit (massimo 7 in ciascuna direzione)
  • Alfiere: 13 opzioni, 4 bit (se hai 7 in una diagonale, ne hai solo 6 nell'altra)
  • Knight: 8 opzioni, 3 bit
  • Queen: 27 opzioni, 5 bit (Rook + Bishop)
  • Re: 9 opzioni, 4 bit (8 mosse di un passo, più l'opzione di arrocco)

Per la promozione, ci sono 4 pezzi tra cui scegliere (Torre, Alfiere, Cavaliere, Regina) quindi su quella mossa aggiungeremo 2 bit per specificarlo. Penso che tutte le altre regole siano coperte automaticamente (es. En passant).

Ulteriori ottimizzazioni

Per prima cosa, dopo che sono stati catturati 8 pezzi di un colore, potresti ridurre la codifica del pezzo a 3 bit, poi 2 bit per 4 pezzi e così via.

Tuttavia, l'ottimizzazione principale consiste nell'enumerare solo le possibili mosse in ogni punto del gioco. Supponiamo di memorizzare le mosse di un pedone come {00, 01, 10, 11}per 1 passo avanti, 2 passi avanti, diagonale sinistra e diagonale destra rispettivamente. Se alcune mosse non sono possibili, possiamo rimuoverle dalla codifica per questo turno.

Conosciamo lo stato del gioco in ogni fase (dal seguire tutte le mosse), quindi dopo aver letto quale pezzo si muoverà, possiamo sempre determinare quanti bit dobbiamo leggere. Se ci rendiamo conto che le uniche mosse di un pedone a questo punto sono catturare diagonalmente a destra o spostarci in avanti, sappiamo che leggiamo solo 1 bit.

In breve, l'archiviazione di bit sopra elencata per ogni pezzo è solo un massimo . Quasi ogni mossa avrà meno opzioni e spesso meno bit.


4

In ogni posizione ottieni il numero di tutte le mosse possibili.

la prossima mossa viene generata come

index_current_move =n % num_of_moves //this is best space efficiency
n=n/num_of_moves

la migliore efficienza dello spazio per la memorizzazione di giochi generati casualmente e richiede in media circa 5 bit / mossa poiché hai 30-40 mosse possibili. L'assemblaggio dello storage genera solo n in ordine inverso.

La memorizzazione della posizione è più difficile da decifrare, a causa della grande ridondanza. (Ci possono essere fino a 9 regine a bordo per un sito, ma in quel caso non ci sono pedine e gli alfieri se sul tabellone sono su caselle di colore opposto) ma generalmente è come conservare la combinazione degli stessi pezzi sulle caselle rimanenti.)

MODIFICARE:

Il punto nel salvare le mosse è memorizzare solo l'indice delle mosse. Invece di memorizzare Kc1-c2 e cercare di ridurre queste informazioni dovremmo aggiungere solo l'indice di movimento generato dal generatore di movimento deterministico (posizione)

Ad ogni mossa aggiungiamo informazioni sulla dimensione

num_of_moves = get_number_of_possible_moves(postion) ;

in piscina e questo numero non può essere ridotto

generazione di pool di informazioni è

n=n*num_of_moves+ index_current_move

extra

Se è disponibile una sola mossa nella posizione finale, salva come numero di mosse forzate eseguite in precedenza. Esempio: se la posizione di partenza ha 1 mossa forzata per ogni lato (2 mosse) e vogliamo salvarla come una partita di mosse, memorizzane 1 nel pool n.

esempio di memorizzazione nel pool di informazioni

Supponiamo di aver conosciuto le posizioni di partenza e di fare 3 mosse.

Nella prima mossa ci sono 5 mosse disponibili, e prendiamo la mossa indice 4. Nella seconda mossa ci sono 6 mosse disponibili e prendiamo la posizione indice 3 e nella 3a mossa ci sono 7 mosse disponibili per quel lato e ha scelto di scegliere l'indice di mossa 2.

Modulo vettoriale; indice = [4,3,2] n_moves = [5,6,7]

Stiamo codificando queste informazioni all'indietro, quindi n = 4 + 5 * (3 + 6 * (2)) = 79 (non è necessario moltiplicare per 7)

Come sbloccarlo? Per prima cosa abbiamo la posizione e scopriamo che ci sono 5 mosse disponibili. Così

index=79%5=4
n=79/5=15; //no remainder

Prendiamo la mossa indice 4 ed esaminiamo di nuovo la posizione e da questo punto scopriamo che ci sono 6 mosse possibili.

index=15%6=3
n=15/6=2

E prendiamo l'indice di mossa 3 che ci porta a una posizione con 7 mosse possibili.

index=2%7=2
n=2/7=0

Facciamo l'ultima mossa indice 2 e raggiungiamo la posizione finale.

Come puoi vedere la complessità temporale è O (n) e la complessità spaziale è O (n). Modifica: la complessità temporale è in realtà O (n ^ 2) perché il numero moltiplicato per aumenta, ma non dovrebbero esserci problemi a memorizzare le partite fino a 10.000 mosse.


posizione di salvataggio

Può essere fatto vicino all'ottimale.

Quando scopriamo informazioni e memorizziamo informazioni lasciatemi parlarne di più. L'idea generale è ridurre la ridondanza (ne parlerò più avanti). Supponiamo che non ci siano state promozioni e nessuna presa, quindi ci sono 8 pedoni, 2 torri, 2 cavalieri, 2 alfieri 1 re e 1 regina per lato.

Cosa dobbiamo salvare: 1. posizione di ogni pace 2. possibilità di arrocco 3. possibilità di en-passant 4. lato con movimento disponibile

Supponiamo che ogni pezzo possa stare ovunque ma non 2 pezzi nello stesso posto. Il numero di modi in cui possono essere disposti 8 pedoni dello stesso colore a bordo è C (64/8) (binomiale) che è di 32 bit, quindi 2 torri 2R-> C (56/2), 2B -> C (54/2) , 2N-> C (52/2), 1Q-> C (50/1), 1K -> C (49/1) e lo stesso per altri siti ma a partire da 8P -> C (48/8) e così via .

Moltiplicando questo insieme per entrambi i siti otteniamo il numero 4634726695587809641192045982323285670400000 che è di circa 142 bit, dobbiamo aggiungere 8 per un possibile en-passant (il pedone en-passant può essere in uno degli 8 posti), 16 (4 bit) per i limiti di arrocco e un po 'per il sito che si è spostato. Finiamo con 142 + 3 + 4 + 1 = 150 bit

Ma ora andiamo a caccia di ridondanza sulla scacchiera con 32 pezzi e niente prese.

  1. entrambi i pedoni bianchi e neri sono sulla stessa colonna e uno di fronte all'altro. Ogni pedone sta affrontando un altro pedone, il che significa che il pedone bianco può essere al massimo al 6 ° rango. Questo ci porta 8 * C (6/2) invece di C (64/8) * C (48/8) che riduce le informazioni di 56 bit.

  2. anche la possibilità di arrocco è ridondante. Se le torri non sono sulla posizione di partenza non c'è possibilità di arrocco con quella torre. Quindi possiamo immaginare aggiungere 4 caselle sulla tavola per ottenere le informazioni extra se è possibile l'arrocco con questa torre e rimuovere 4 bit di arrocco. Quindi invece di C (56/2) * C (40/2) * 16 abbiamo C (58/2) * C (42/2) e abbiamo perso 3,76 bit (quasi tutti 4 bit)

  3. en-passant: Quando memorizziamo una delle 8 possibilità en passant, conosciamo la posizione del pedone nero e riduciamo la redindancy informativa (se è mossa bianca e ha il 3 ° pedone en-passant significa che il pedone nero è in c5 e il pedone bianco è c2, c3 o c4) quindi invece di C (6/2) abbiamo 3 e abbiamo perso 2,3 bit. Diminuiamo un po 'di ridondanza se memorizziamo con numero en-passant anche il lato dal quale si può fare (3 possibilità-> sinistra, destra, entrambe) e conosciamo la possessione di pedone che può prendere en passant. (per esempio dall'esempio precedente en passant con il nero in c5 cosa può essere a sinistra, a destra o in entrambi. se è su un sito abbiamo 2 * 3 (3 per immagazzinare psissibilite e 2 possibili mosse per il pedone nero di 7 ° o 6 rango ) invece di C (6/2) e riduciamo di 1,3 bit e se su entrambi i lati riduciamo di 4,2 bit, in questo modo possiamo ridurre di 2,3 + 1,3 = 3.

  4. bishops: i bisops possono essere solo su quadrati opostiti, questo riduce la ridondanza di 1 bit per ogni sito.

Se sommiamo, abbiamo bisogno di 150-56-4-3.6-2 = 85 bit per memorizzare la posizione degli scacchi se non ci sono state prese

E probabilmente non molto di più se si tiene conto degli incassi e delle promozioni (ma ne parlerò più avanti se qualcuno troverà utile questo lungo post)


Approccio interessante. Aggiungi qualche dettaglio in più :)
Andrew Rollings

Ho aggiunto anche un approccio per il salvataggio della posizione. Sono sceso a 85 bit su posizioni senza presa ed è una buona illustrazione di quanto sia possibile arrivare. Penso che l'idea migliore sia salvare le possibilità di arrocco in cui quasi tutti i 4 bit sono ridondanti.
Luka Rahne

3

La maggior parte delle persone ha codificato lo stato della scheda, ma per quanto riguarda le mosse stesse .. Ecco una descrizione della codifica bit.

Bit per pezzo:

  • ID pezzo: Max 4 bit per identificare i 16 pezzi per lato. Si può dedurre bianco / nero. Avere un ordine definito sui pezzi. Poiché il numero di pezzi scende al di sotto delle rispettive potenze di due, usa meno bit per descrivere i pezzi rimanenti.
  • Pedone: 3 possibilità alla prima mossa, quindi +2 bit (avanti di una o due caselle, en passant). Le mosse successive non consentono di avanzare di due, quindi +1 bit è sufficiente. La promozione può essere dedotta nel processo di decodifica notando quando il pedone ha raggiunto l'ultimo rango. Se si sa che il pedone è stato promosso, il decodificatore si aspetterà altri 2 bit che indicano a quale dei 4 pezzi principali è stato promosso.
  • Alfiere: +1 bit per la diagonale utilizzata, Fino a +4 bit per la distanza lungo la diagonale (16 possibilità). Il decoder può dedurre la massima distanza possibile che il pezzo può percorrere lungo quella diagonale, quindi se è una diagonale più corta, usa meno bit.
  • Cavaliere: 8 mosse possibili, +3 bit
  • Torre: +1 bit per orizzontale / verticale, +4 bit per la distanza lungo la linea.
  • Re: 8 mosse possibili, +3 bit. Indica l'arrocco con una mossa "impossibile" - poiché l'arrocco è possibile solo mentre il re è in prima fila, codifica questa mossa con l'istruzione di muovere il re "all'indietro", cioè fuori dal tabellone.
  • Queen: 8 direzioni possibili, + 3 bit. Fino a +4 bit in più per la distanza lungo la linea / diagonale (meno se la diagonale è più corta, come nel caso dell'alfiere)

Supponendo che tutti i pezzi siano sulla scacchiera, questi sono i bit per mossa: Pedone - 6 bit alla prima mossa, 5 successivamente. 7 se promosso. Alfiere: 9 bit (max), Cavaliere: 7, Torre: 9, Re: 7, Regina: 11 (max).


32 bit per identificare il pezzo ??? Penso che intendessi 5 (32 pezzi). Oppure 6 se hai bisogno di codificare uno stato 'end',
Toad

Un pedone può anche essere promosso a torre, cavaliere o alfiere. Questo è comune per evitare lo stallo o evitare il confronto.
Kobi

Questo non influisce sul tuo ragionamento, ma su una piccola correzione: un pedone può avere quattro mosse escluse le promozioni o 12 mosse comprese le promozioni. Pedone di esempio in e2: e3, e4, exd3, exf3. Pedone di esempio in e7: e8Q, e8N, e8R, e8B, exd8Q, exd8N, exd8R, exd8B, exf8Q, exf8N, exf8R, exf8B.
A. Rex

Forse sto interpretando male, ma un pedone non può fare en passant alla prima mossa. In realtà non hai bisogno di una speciale notazione "en passant" poiché è nelle regole del gioco - sarà solo una mossa diagonale. La prima mossa è una delle 4 opzioni e le mosse successive sono fino a 3 opzioni.
DisgruntledGoat

3

Il problema è fornire una codifica più efficiente per le tipiche partite di scacchi o una che abbia la codifica più breve nel caso peggiore?

Per questi ultimi, il modo più efficiente è anche il più opaco: creare un'enumerazione di tutte le coppie possibili (tavola iniziale, sequenza legale di mosse), che, con la posizione draw-on-tre volte-ripetuta e no-more-than -fifty-mosse rispetto alle regole per la mossa o la cattura dell'ultimo pedone, è ricorsivo. Quindi l'indice di una posizione in questa sequenza finita fornisce la codifica più breve nel caso peggiore, ma anche una codifica altrettanto lunga per i casi tipici, ed è, immagino, molto costoso da calcolare. La partita di scacchi più lunga possibile dovrebbe superare le 5000 mosse, con in genere 20-30 mosse disponibili in ogni posizione per ogni giocatore (anche se meno quando ci sono pochi pezzi rimasti) - questo dà qualcosa come 40000 bit necessari per questa codifica.

L'idea di enumerazione può essere applicata per fornire una soluzione più trattabile, come descritto nel suggerimento di Henk Holterman per la codifica delle mosse sopra. Il mio suggerimento: non minimo, ma più breve degli esempi sopra che ho visto e ragionevolmente trattabile:

  1. 64 bit per rappresentare quali caselle sono occupate (matrice di occupazione), più l'elenco di quali pezzi sono in ogni casella occupata (può avere 3 bit per i pedoni e 4 bit per gli altri pezzi): questo dà 190 bit per la posizione iniziale. Poiché non possono esserci più di 32 pezzi a bordo, la codifica della matrice di occupazione è ridondante e quindi è possibile codificare qualcosa come le posizioni comuni della scheda, ad esempio 33 bit impostati più l'indice della scheda dall'elenco delle schede comuni.

  2. 1 bit per dire chi fa la prima mossa

  3. Mosse in codice secondo il suggerimento di Henk: in genere 10 bit per coppia di mosse bianche / nere, anche se alcune mosse richiedono 0 bit, quando un giocatore non ha mosse alternative.

Ciò suggerisce 490 bit per codificare un tipico gioco di 30 mosse e sarebbe una rappresentazione ragionevolmente efficiente per i giochi tipici.

Abouth codifica la posizione di estrazione tre volte ripetuta e non più di cinquanta mosse dall'ultima mossa o cattura del pedone: se codifichi le mosse precedenti all'ultima mossa o cattura del pedone, allora tu avere abbastanza informazioni per decidere se queste regole si applicano: non c'è bisogno dell'intera cronologia del gioco.


Supponiamo che prenderei una vasta selezione di giochi e prenda una media dei risultati.
Andrew Rollings

3

La posizione su una scheda può essere definita in 7 bit (0-63 e 1 valore specificando che non è più sulla scheda). Quindi per ogni pezzo sulla scacchiera specificare dove si trova.

32 pezzi * 7 bit = 224 bit

EDIT: come ha sottolineato Cadrian ... abbiamo anche il caso "pedone promosso a regina". Suggerisco di aggiungere bit extra alla fine per indicare quale pedone è stato promosso.

Quindi per ogni pedone promosso seguiamo i 224 bit con 5 bit che indicano l'indice del pedone che è stato promosso, e 11111 se è la fine della lista.

Quindi il caso minimo (nessuna promozione) è di 224 bit + 5 (nessuna promozione). Per ogni pedone promosso aggiungi 5 bit.

EDIT: Come sottolinea la rana irsuta, abbiamo bisogno di un altro pezzetto alla fine per indicare di chi è il turno; ^)


e poi gzip il risultato (se le intestazioni non aumentano il risultato); ^)
Toad

Puoi migliorarlo tenendo conto che alcuni pezzi non saranno mai trovati su certi colori quadrati?
Andrew Rollings

andrew: in realtà non posso. Ho dimenticato di prendere in considerazione una pedina promossa in una regina (come suggerisce la risposta di Adriano). Quindi sembra che avrò effettivamente bisogno di un altro pezzo in più
Toad

Posso vedere come i vescovi bianchi e neri possono essere definiti insieme. Però mi chiedo dei cavalieri ..
int3

1
Ti mancano promozioni non da regina.
Loren Pechtel

2

Userei una codifica della lunghezza di esecuzione. Alcuni pezzi sono unici (o esistono solo due volte), quindi posso omettere la lunghezza dopo di loro. Come cletus, ho bisogno di 13 stati unici, quindi posso usare un bocconcino (4 bit) per codificare il pezzo. La scheda iniziale sarebbe quindi simile a questa:

White Rook, W. Knight, W. Bishop, W. Queen, W. King, W. Bishop, W. Knight, W. Rook,
W. Pawn, 8,
Empty, 16, Empty, 16
B. Pawn, 8,
B. Rook, B. Knight, B. Bishop, B. Queen, B. King, B. Bishop, B. Knight, B. Rook

che mi lascia con 8 + 2 + 4 + 2 + 8 bocconcini = 24 bocconcini = 96 bit. Non posso codificare 16 con un bocconcino ma poiché "Vuoto, 0" non ha senso, posso trattare "0" come "16".

Se il tabellone è vuoto ma per una singola pedina nell'angolo in alto a sinistra, ottengo "Pedone, 1, Vuoto, 16, Vuoto, 16, Vuoto 16, Vuoto, 15" = 10 bocconcini = 40 bit.

Il caso peggiore è quando ho un quadrato vuoto tra ogni pezzo. Ma per la codifica del pezzo, ho solo bisogno di 13 valori su 16, quindi forse posso usarne un altro per dire "Vuoto1". Quindi, ho bisogno di 64 bocconcini == 128 bit.

Per i movimenti, ho bisogno di 3 bit per il pezzo (il colore è dato dal fatto che il bianco si muove sempre per primo) più 5 bit (0..63) per la nuova posizione = un byte per movimento. Il più delle volte, non ho bisogno della vecchia posizione poiché solo un singolo pezzo sarà nel raggio d'azione. Per il caso dispari, devo usare il singolo codice inutilizzato (mi servono solo 7 codici per codificare il pezzo) e poi 5 bit per il vecchio e 5 bit per la nuova posizione.

Questo mi permette di codificare l'arrocco in 13 morsi (posso muovere il Re verso la Torre, il che è sufficiente per dire quello che intendo).

[MODIFICA] Se consenti un codificatore intelligente, ho bisogno di 0 bit per la configurazione iniziale (perché non deve essere codificato in alcun modo: è statico) più un byte per mossa.

[EDIT2] Il che lascia la trasformazione del pedone. Se un pedone raggiunge l'ultima riga, posso spostarlo sul posto per dire "si trasforma" e poi aggiungere i 3 bit per il pezzo con cui è stato sostituito (non devi usare una regina; puoi sostituire il pedone con qualsiasi cosa ma il Re).


Il codificatore intelligente non può presumere che sia un intero gioco. Potrebbe essere un frammento di un gioco. Penso che avresti ancora bisogno di codificare le posizioni di partenza.
Andrew Rollings

Bene, nel peggiore dei casi, ho bisogno di 128 bit o, se il gioco è ancora in una fase iniziale, posso usare fino a 15 mosse per portarlo nella posizione iniziale = 120 bit.
Aaron Digulla

Poiché QUALSIASI stato deve essere codificato, e non solo lo stato iniziale della scheda, è necessario codificare anche i pezzi stessi. Quindi avrai bisogno di almeno 5 bit per pezzo. Quindi questo ti darà almeno 32 * 5 bit extra
Toad

@reiner: Ti sbagli. Ho solo bisogno di quattro bit per pezzo / quadrato vuoto. E l'ho già trattato nella prima parte della mia risposta, quindi niente "32 * 5 bit extra". Per lo stato iniziale, ho bisogno di 96 bit e per qualsiasi altro stato di avvio, ho bisogno di un massimo di 128 bit.
Aaron Digulla

Aaron: ancora, come dici tu, lo scenario peggiore è davvero il caso peggiore in questa codifica. Dopo 3 o 4 mosse da una startboard, la tua codifica richiederà molti più bit man mano che aggiungi sempre più vuoti
Toad

2

Proprio come codificano giochi su libri e giornali: ogni pezzo ha un simbolo; poiché è un gioco "legale", il bianco muove per primo - non c'è bisogno di codificare il bianco o il nero separatamente, conta semplicemente il numero di mosse per determinare chi ha mosso. Inoltre, ogni mossa è codificata come (pezzo, posizione finale) dove la 'posizione finale' è ridotta al minimo numero di simboli che consente di discernere le ambiguità (può essere zero). La durata del gioco determina il numero di mosse. Si può anche codificare il tempo in minuti (dall'ultima mossa) ad ogni passo.

La codifica del brano può essere eseguita assegnando un simbolo a ciascuno (32 in totale) o assegnando un simbolo alla classe, e utilizzare la posizione finale per capire quale pezzo è stato spostato. Ad esempio, un pedone ha 6 possibili posizioni finali; ma in media solo un paio sono disponibili per questo ad ogni turno. Quindi, statisticamente, la codifica per posizione finale potrebbe essere la migliore per questo scenario.

Codifiche simili vengono utilizzate per i treni di picco nelle neuroscienze computazionali (AER).

Svantaggi: è necessario riprodurre l'intero gioco per arrivare allo stato corrente e generare un sottoinsieme, proprio come quando si attraversa un elenco collegato.


Potrebbe essere solo un frammento di gioco. Non puoi presumere che il bianco si muova per primo.
Andrew Rollings

2

Ci sono 64 possibili posizioni sulla scheda, quindi sono necessari 6 bit per posizione. Ci sono 32 pezzi iniziali, quindi finora abbiamo 192 bit in totale, dove ogni 6 bit indica la posizione del pezzo dato. Possiamo predeterminare l'ordine in cui appaiono i pezzi, quindi non dobbiamo dire quale è quale.

E se un pezzo è fuori dal tabellone? Bene, possiamo posizionare un pezzo nello stesso punto di un altro pezzo per indicare che è fuori dal tabellone, poiché altrimenti sarebbe illegale. Ma non sappiamo nemmeno se il primo pezzo sarà sulla scacchiera o meno. Quindi aggiungiamo 5 bit che indicano quale pezzo è il primo (32 possibilità = 5 bit per rappresentare il primo pezzo). Quindi possiamo usare quel punto per i pezzi successivi che sono fuori dal tabellone. Questo ci porta a 197 bit in totale. Deve esserci almeno un pezzo sulla lavagna, in modo che funzioni.

Quindi abbiamo bisogno di un bit per il quale è il turno: ci porta a 198 bit .

E la promozione dei pedoni? Possiamo farlo in un modo sbagliato aggiungendo 3 bit per pedone, aggiungendo 42 bit. Ma poi possiamo notare che la maggior parte delle volte le pedine non vengono promosse.

Quindi, per ogni pedone che si trova sul tabellone, il bit "0" indica che non è promosso. Se un pedone non è sul tabellone, non ci serve affatto un po '. Quindi possiamo usare stringhe di bit di lunghezza variabile per la promozione che ha. Il più delle volte sarà una regina, quindi "10" può significare REGINA. Quindi "110" significa torre, "1110" significa alfiere e "1111" significa cavaliere.

Lo stato iniziale richiederà 198 + 16 = 214 bit , poiché tutte e 16 le pedine sono sul tabellone e non sono promosse. Una partita finale con due pedine regine promosse potrebbe richiedere qualcosa come 198 + 4 + 4, ovvero 4 pedoni vivi e non promossi e 2 pedoni regina, per 206 bit totali. Sembra piuttosto robusto!

===

La codifica di Huffman, come altri hanno sottolineato, sarebbe il passo successivo. Se osservi alcuni milioni di partite, noterai che è molto più probabile che ogni pezzo si trovi su determinati quadrati. Ad esempio, la maggior parte delle volte, i pedoni rimangono in linea retta, o uno a sinistra / uno a destra. Il re di solito resterà nella base.

Pertanto, ideare uno schema di codifica Huffman per ogni posizione separata. I pedoni probabilmente impiegheranno in media solo 3-4 bit invece di 6. Anche il re dovrebbe prendere pochi bit.

Anche in questo schema, includi "preso" come posizione possibile. Questo può anche gestire in modo molto robusto l'arrocco: ogni torre e re avrà uno stato extra di "posizione originale, mossa". Puoi anche codificare en passant nelle pedine in questo modo: "posizione originale, can en passant".

Con una quantità sufficiente di dati, questo approccio dovrebbe produrre risultati davvero buoni.


2
Assegna i pezzi rimossi alla stessa casella del re. Dal momento che il re non può mai essere rimosso, non sarebbe ambiguo
John La Rooy

Questo è un buon commento :) Anche gli aspetti piacevoli di questa soluzione. Non sapevo che sarebbe stato così difficile scegliere un vincitore.
Andrew Rollings,

2

Proverei a usare la codifica Huffman . La teoria alla base di questo è: in ogni partita di scacchi ci saranno alcuni pezzi che si muoveranno molto, e alcuni che non si muovono molto o vengono eliminati presto. Se la posizione di partenza ha alcuni pezzi già rimossi, tanto meglio. Lo stesso vale per i quadrati: alcuni possono vedere tutte le azioni, mentre altri non vengono toccati molto.

Quindi avrei due tavoli Huffman: uno per i pezzi, l'altro per i quadrati. Verranno generati guardando il gioco reale. Potrei avere un tavolo grande per ogni coppia di pezzi quadrati, ma penso che sarebbe piuttosto inefficiente perché non ci sono molti casi in cui lo stesso pezzo si muove di nuovo sulla stessa casella.

Ogni pezzo avrebbe un ID assegnato. Poiché ci sono 32 pezzi diversi, avrei bisogno di soli 5 bit per l'ID pezzo. Gli ID pezzo non cambiano da gioco a gioco. Lo stesso vale per gli ID quadrati, per i quali avrei bisogno di 6 bit.

Gli alberi di Huffman sarebbero codificati annotando ogni nodo mentre vengono attraversati in ordine (ovvero, prima viene emesso il nodo, quindi i suoi figli da sinistra a destra). Per ogni nodo ci sarà un bit che specifica se si tratta di un nodo foglia o di un nodo ramo. Se è un nodo foglia, sarà seguito dai bit che danno l'ID.

La posizione di partenza sarà semplicemente data da una serie di coppie pezzo-posizione. Dopodiché ci sarà una coppia di posizioni per ogni mossa. Puoi trovare la fine del descrittore della posizione iniziale (e l'inizio del descrittore delle mosse) semplicemente trovando il primo pezzo menzionato due volte. Nel caso in cui un pedone venga promosso ci saranno 2 bit extra che specificano cosa diventerà, ma l'ID pezzo non cambierà.

Per tenere conto della possibilità che un pedone venga promosso all'inizio del gioco ci sarà anche un "tavolo di promozione" tra gli alberi di huffman e i dati. All'inizio ci saranno 4 bit che specificano quante pedine vengono aggiornate. Quindi per ogni pedone ci sarà il suo ID codificato da huffman e 2 bit che specificano cosa è diventato.

Gli alberi di huffman verranno generati tenendo conto di tutti i dati (sia la posizione di partenza che le mosse) e la tabella di promozione. Anche se normalmente la tabella delle promozioni sarà vuota o avrà solo poche voci.

Per riassumere in termini grafici:

<Game> := <Pieces huffman tree> <squares huffman tree> <promotion table> <initial position> (<moves> | <1 bit for next move - see Added 2 below>)

<Pieces huffman tree> := <pieces entry 1> <pieces entry 2> ... <pieces entry N>
<pieces entry> := "0" | "1" <5 bits with piece ID>

<squares huffman tree> := <squares entry 1> <squares entry 2> ... <squares entry N>
<Squares entry> := "0" | "1" <6 bits with square ID>

<promotion table> := <4 bits with count of promotions> <promotion 1> <promotion 2> ... <promotion N>
<promotion> := <huffman-encoded piece ID> <2 bits with what it becomes>

<initial position> := <position entry 1> <position entry 2> ... <position entry N>
<moves> := <position entry 1> <position entry 2> ... <position entry N>
<position entry> := <huffman-encoded piece ID> <huffman-encoded squre ID> (<2 bits specifying the upgrade - optional>)

Aggiunto: questo potrebbe ancora essere ottimizzato. Ogni pezzo ha solo poche mosse legali. Invece di codificare semplicemente il quadrato di destinazione, è possibile fornire ID basati su 0 per le possibili mosse di ogni pezzo. Gli stessi ID verrebbero riutilizzati per ogni pezzo, quindi in totale non ci sarebbero più di 21 ID diversi (la regina può avere al massimo 21 diverse possibili opzioni di mossa). Mettilo in una tabella huffman invece che nei campi.

Ciò tuttavia presenterebbe una difficoltà nel rappresentare lo stato originale. Si potrebbe generare una serie di mosse per mettere ogni pezzo al suo posto. In questo caso sarebbe necessario segnare in qualche modo la fine dello stato iniziale e l'inizio delle mosse.

In alternativa possono essere posizionati utilizzando ID quadrati a 6 bit non compressi.

Se ciò presenterebbe una diminuzione generale delle dimensioni, non lo so. Probabilmente, ma dovrebbe sperimentare un po '.

Aggiunto 2: un altro caso speciale. Se lo stato del gioco NON ha mosse, diventa importante distinguere chi si muove dopo. Aggiungi un altro pezzetto alla fine per quello. :)


2

[modificato dopo aver letto correttamente la domanda] Se presumi che ogni posizione legale possa essere raggiunta dalla posizione iniziale (che è una possibile definizione di "legale"), allora ogni posizione può essere espressa come sequenza di mosse dall'inizio. Un frammento di gioco che inizia da una posizione non standard può essere espresso come la sequenza di mosse necessarie per raggiungere l'inizio, un interruttore per accendere la telecamera, seguito dalle mosse successive.

Quindi chiamiamo lo stato iniziale della scheda il singolo bit "0".

Le mosse da qualsiasi posizione possono essere enumerate numerando le caselle e ordinando le mosse per (inizio, fine), con il tradizionale salto di 2 caselle che indica l'arrocco. Non è necessario codificare mosse illegali, perché la posizione della scacchiera e le regole sono sempre già note. La bandiera per accendere la telecamera potrebbe essere espressa come una mossa speciale all'interno della banda, o più sensatamente come un numero di mossa fuori banda.

Ci sono 24 mosse di apertura per ogni lato, che possono contenere 5 bit ciascuna. Le mosse successive potrebbero richiedere più o meno bit, ma le mosse legali sono sempre enumerabili, quindi l'ampiezza di ogni mossa può felicemente crescere o espandersi. Non ho calcolato, ma immagino che le posizioni a 7 bit sarebbero rare.

Utilizzando questo sistema, un gioco di 100 mezze mosse potrebbe essere codificato in circa 500 bit. Tuttavia, potrebbe essere saggio usare un libro di apertura. Supponiamo che contenga un milione di sequenze. Lascia quindi che uno 0 iniziale indichi un inizio dalla scheda standard e un 1 seguito da un numero di 20 bit indichi un inizio da quella sequenza di apertura. I giochi con aperture un po 'convenzionali potrebbero essere accorciati diciamo di 20 mezze mosse o 100 bit.

Questa non è la massima compressione possibile, ma (senza il libro di apertura) sarebbe molto facile da implementare se hai già un modello di scacchi, cosa che la domanda presuppone.

Per comprimere ulteriormente, dovresti ordinare le mosse in base alla probabilità piuttosto che in un ordine arbitrario e codificare le sequenze probabili in meno bit (usando ad esempio i token di Huffman come le persone hanno menzionato).


La posizione iniziale non è necessariamente nota. Potrebbe essere un frammento di gioco.
Andrew Rollings

@ Andrew: sì. errore mio. Ho modificato per consentire frammenti di gioco.
Douglas Bagnall

2

Se il tempo di calcolo non è un problema, è possibile utilizzare un generatore di posizione possibile deterministico per assegnare ID univoci a una determinata posizione.

Da una data posizione generare prima il numero di possibili posizioni in un maniero deterministico, ad esempio partendo dal basso a sinistra per spostarsi in alto a destra. Questo determina quanti bit avrai bisogno per la prossima mossa, in alcune situazioni potrebbe essere uno solo. Quindi, quando viene eseguita la mossa, memorizzare solo l'ID univoco per quella mossa.

La promozione e le altre regole contano semplicemente come mosse valide fintanto che sono gestite in modo deterministico, ad esempio, la regina, la torre, l'alfiere, ciascuna conta come una mossa separata.

La posizione iniziale è la più difficile e potrebbe generare circa 250 milioni di possibili posizioni (credo) che richiederebbero circa 28 bit più un bit extra per determinare di chi si tratta.

Supponendo di sapere chi è il turno (ogni turno passa da bianco a nero) il generatore deterministico sarebbe simile a:

for each row
    for each column
        add to list ( get list of possible moves( current piece, players turn) )

'ottenere l'elenco delle possibili mosse' farebbe qualcosa di simile:

if current piece is not null 
    if current piece color is the same as the players turn
        switch( current piece type )
            king - return list of possible king moves( current piece )
            queen - return list of possible queen moves( current piece )
            rook - return list of possible rook moves( current piece )
            etc.

Se il re è in scacco, ogni "lista di possibili mosse xxx" restituisce solo mosse valide che cambiano la situazione di scacco.


È una soluzione subdola ... quindi ... in questo caso, descrivi il tuo algoritmo per generare il numero deterministico.
Andrew Rollings,

Ho trovato un collegamento interessante su quanto tempo ci vorrebbe per generare ogni posizione su una scacchiera :) ioannis.virtualcomposer2000.com/math/EveryChess.html
Andrew Rollings

2

La maggior parte delle risposte ha trascurato la ripetizione di 3 volte. sfortunatamente per la ripetizione di 3 volte devi memorizzare tutte le posizioni giocate finora ...

La domanda ci richiedeva di memorizzare in modo efficiente in termini di spazio, quindi non abbiamo davvero bisogno di memorizzare la posizione fintanto che possiamo costruirla dall'elenco delle mosse (a condizione che abbiamo una posizione di partenza standard). Possiamo ottimizzare PGN e questo è tutto. Di seguito è uno schema semplice.

Ci sono 64 caselle sul tabellone, 64 = 2 ^ 6. Se memorizziamo solo la casella iniziale e quella finale di ogni mossa, occorrerebbero 12 bit (la promozione verrà affrontata in seguito). Nota che questo schema copre già il giocatore da muovere, enfatizzare, pezzo catturato, arrocco, ecc; a partire da questi possono essere costruiti semplicemente riproducendo l'elenco delle mosse.

per la promozione possiamo mantenere un array separato di vettori che direbbero "alla mossa N promuovi al pezzo XYZ". possiamo mantenere un vettore di (int, byte).

Si è tentati di ottimizzare anche il vettore (A, Da), poiché molti di questi vettori (A, Da) non sono possibili negli scacchi. per esempio. non ci sarà un passaggio da e1 a d8 ecc. Ma non sono riuscito a trovare alcuno schema. Eventuali ulteriori idee sono i benvenuti.


2

Ci ho pensato a lungo (+ - 2 ore). E non ci sono risposte ovvie.

Supponendo:

  1. Ignorare lo stato del tempo (un giocatore non aveva un limite di tempo quindi potrebbe forzare un pareggio non giocando)
  2. Quando è stata giocata la partita?!? È importante perché le regole sono cambiate nel tempo (quindi assumerà un gioco moderno nel punto successivo un gioco moderno ...) Si prega di fare riferimento alla regola del pedone morto per esempio (wikipedia ha un problema molto famoso che lo mostra), e se vuoi per tornare indietro nel tempo, buona fortuna, l'alfiere si muoveva solo lentamente e si usavano i dadi. lol.

... le regole moderne così aggiornate lo sono. Primo indipendentemente dalla ripetizione e dal limite di ripetizione del movimento.

-C 25 byte arrotondati (64b + 32 * 4b + 5b = 325b)

= 64 bit (qualcosa / niente) + 32 * 4 bit [1 bit = colore {nero / bianco} + 3 bit = tipo di pezzo {Re, Regina, Alfiere, kNight, Rook, Pawn, MovedPawn} NB: Pedone spostato ... es. se è stato l'ultimo pedone mosso nel turno precedente, indicando che è fattibile un "en passant". ] + 5bit per lo stato attuale (chi è di turno, en passant, possibilità di rooking o meno da ogni lato)

Fin qui tutto bene. Probabilmente può essere migliorato ma poi ci sarebbe lunghezza variabile e promozione da tenere in considerazione !?

Ora, le seguenti regole sono applicabili solo QUANDO un giocatore chiede un pareggio, NON È automatico! Quindi considera che queste 90 mosse senza una cattura o una mossa di pedone sono fattibili se nessun giocatore chiede la patta! Significa che tutte le mosse devono essere registrate ... e disponibili.

-D ripetizione della posizione ... es. Stato di bordo come menzionato sopra (vedere C) o no ... (vedere di seguito per quanto riguarda le regole FIDE) -E Questo lascia il complesso problema di 50 mosse senza cattura o mossa di pedone lì a contatore è necessario ... Tuttavia.

Quindi come gestisci questo? ... Beh, davvero non c'è modo. Perché nessuno dei due giocatori potrebbe voler disegnare o rendersi conto che è successo. Ora nel caso in cui E potrebbe bastare un contatore ... ma ecco il trucco e anche leggendo le regole FIDE (http://www.fide.com/component/handbook/?id=124&view=article) non riesco a trovare un rispondi ... che dire della perdita di capacità di rooking. È una ripetizione? Penso di no ma poi questo è un soggetto sfocato non affrontato, non chiarito.

Quindi ecco due regole che sono due complesse o indefinite anche per provare a codificare ... Saluti.

Quindi l'unico modo per codificare veramente un gioco è registrare tutto dall'inizio ... il che poi è in conflitto (o no?) Con la domanda "board state".

Spero che questo aiuto ... non troppa matematica :-) Giusto per dimostrare che alcune domande non sono così facili, troppo aperte all'interpretazione o alla pre-conoscenza per essere corrette ed efficienti. Non uno che prenderei in considerazione per l'intervista in quanto apre troppo una lattina di worm.


2

Possibile miglioramento della posizione di partenza nella soluzione di Yacoby

Nessuna posizione legale ha più di 16 pezzi di ogni colore. Il numero di modi per posizionare fino a 16 pezzi neri e 16 bianchi su 64 quadrati è di circa 3.63e27. Log2 (3,63e27) = 91,55. Ciò significa che puoi codificare la posizione e il colore di tutti i pezzi in 92 bit. Questo è inferiore ai 64 bit per la posizione + fino a 32 bit per il colore richiesti dalla soluzione di Yacoby. È possibile salvare 4 bit nel caso peggiore a scapito di una notevole complessità nella codifica.

D'altra parte, aumenta la dimensione per le posizioni con 5 o più pezzi mancanti. Queste posizioni rappresentano solo <4% di tutte le posizioni, ma sono probabilmente la maggioranza dei casi in cui si desidera registrare una posizione iniziale diversa dalla posizione iniziale.

Questo porta alla soluzione completa

  1. Codifica la posizione e il colore dei pezzi secondo il metodo sopra. 92 bit .
  2. Per specificare il tipo di ogni pezzo, usa un codice Huffman: pedone: "0", torre: "100", cavaliere: "101", alfiere: "110", regina: "1110", re: "1111". Ciò richiede (16 * 1 + 12 * 3 + 4 * 4) = 68 bit per un set completo di pezzi. La posizione della scheda completa può essere codificata in 92 + 68 = 160 bit massimo .
  3. È necessario aggiungere uno stato di gioco aggiuntivo: turno: 1 bit, quale arrocco è possibile: 4 bit, "en passant" possibile: fino a 4 bit (1 bit indica il caso e 3 bit indicano quale). La posizione iniziale è codificata in = 160 + 9 = 169 bit
  4. Per l'elenco delle mosse, enumera tutte le mosse possibili per una data posizione e memorizza la posizione della mossa nell'elenco. L'elenco delle mosse include tutti i casi speciali (arrocco, en passant e dimissioni). Utilizzare solo il numero di bit necessario per memorizzare la posizione più alta. In media non dovrebbe superare i 7 bit per mossa (16 pezzi possibili e 8 mosse legali per pezzo in media). In alcuni casi, quando una mossa è forzata, richiede solo 1 bit (mossa o dimetti).

1

Ci sono 32 pezzi sul tabellone. Ogni pezzo ha una posizione (una su 64 quadrati). Quindi hai solo bisogno di 32 numeri interi positivi.

So che 64 posizioni sono tenute in 6 bit ma non lo farei. Conserverei gli ultimi bit per poche bandiere (pezzo caduto, pedina regina)


Non è necessario utilizzare flag per mantenere lo stato. Puoi presumere che il tuo codificatore sia abbastanza intelligente da "conoscere le regole". Quindi, se un pedone cambia improvvisamente in una regina, non dovrebbe necessariamente essere contrassegnato specificamente nella codifica (a meno che, suppongo, il giocatore non scelga di promuovere).
Andrew Rollings

sì dovrebbe, dal momento che non puoi dire dalla posizione iniziale di un pedone se il pedone è stato promosso o meno! Così da essere codificato nella configurazione iniziale
Toad

Ah, ma perché dovresti sapere se è già stato promosso? È solo un pezzo. Il suo stato passato sarebbe irrilevante in questo caso.
Andrew Rollings

Penso che se un pedone è ancora un pedone o è stato promosso a regina non è certo irrilevante per il resto del gioco. Se non la pensi così, mi piacerebbe fare una partita a scacchi con te; ^)
Toad

@reinier: Sta sostenendo è irrilevante se una corrente regina era in origine una regina o di origine un pedone.
A. Rex

1

La risposta di Cletus è buona, ma si è dimenticato di codificare anche a chi tocca. Fa parte dello stato corrente ed è necessario se utilizzi quello stato per guidare un algoritmo di ricerca (come un derivato alfa-beta).

Non sono un giocatore di scacchi, ma credo che ci sia anche un altro caso d'angolo: quante mosse sono state ripetute. Una volta che ogni giocatore fa la stessa mossa tre volte, il gioco è un pareggio, no? In tal caso, è necessario salvare tali informazioni nello stato perché dopo la terza ripetizione, lo stato è ora terminale.


seguendo questa strada, devi anche aggiungere il tempo di gioco per entrambi i giocatori poiché in una partita di scacchi reale entrambi i giocatori pensano solo a 1 o 2 ore in totale.
Toad

2
Non è necessario codificare le regole nei dati effettivi. Si può presumere che il codificatore stesso conosca le regole necessarie.
Andrew Rollings

Ah .. non ho considerato il tempo di giocare. Buona chiamata ... :)
Andrew Rollings

@Andrew Rollings: la regola è basata sullo stato, come in, si attiva solo quando viene soddisfatta una certa precondizione. Anche il monitoraggio di quello stato della precondizione fa parte di ... beh, stato. :)
Shaggy Frog

Irrilevante in questo caso. Se necessario, il decoder potrebbe esaminare lo stato per determinare il vincitore. Ricorda, il codificatore / decodificatore è a conoscenza delle regole. Le uniche cose che hanno davvero bisogno di essere codificate sono le scelte del giocatore - qualsiasi altra cosa può essere considerata conosciuta dal codificatore / decodificatore.
Andrew Rollings

1

Come molti altri hanno menzionato, potresti per ciascuno dei 32 pezzi puoi memorizzare in quale quadrato si trovano e se sono sul tabellone o meno, questo dà 32 * (log2 (64) + 1) = 224 bit.

Tuttavia i vescovi possono occupare solo i quadrati neri o bianchi, quindi per questi sono necessari solo log2 (32) bit per la posizione, che danno 28 * 7 + 4 * 6 = 220 bit.

E poiché i pedoni non iniziano dietro e possono solo avanzare, possono essere solo su 56, dovrebbe essere possibile utilizzare questa limitazione per ridurre il numero di bit necessari per i pedoni.


anche gli alfieri possono essere fuori dal tavolo, quindi hai bisogno di un po 'in più per quelli. Inoltre ti stai dimenticando delle pedine promosse e della persona che inizierà per prima. Tenendo conto di tutto ciò, fondamentalmente si finisce con la mia risposta; ^)
Toad

I 6 bit per i vescovi sono log2 (32) + 1 = 6, ma questa è sicuramente una domanda complicata se si considerano tutti i dettagli :)
Andreas Brinck

Stavo pensando in questa direzione, ma non aiuta. Guarda la risposta di Thomas e modifica la sua codifica huffman per rimuovere la nozione di spazi vuoti. Usi 64 bit per memorizzare la matrice di cui i quadrati sono occupati e rimuovi 1 bit da ciascuna codifica, recuperando così esattamente gli stessi 64 bit.
Loren Pechtel

1

Una scacchiera ha 64 caselle e può essere rappresentata da 64 bit che mostrano se una casella è vuota o meno. Abbiamo bisogno delle informazioni sul pezzo solo se un quadrato ha un pezzo. Poiché il giocatore + pezzo richiede 4 bit (come mostrato in precedenza) possiamo ottenere lo stato corrente in 64 + 4 * 32 = 192 bit. Lancia nel turno corrente e hai 193 bit.

Tuttavia, dobbiamo anche codificare le mosse legali per ogni pezzo. Innanzitutto, calcoliamo il numero di mosse legali per ogni pezzo e aggiungiamo altrettanti bit dopo l'identificatore del pezzo di un quadrato completo. Ho calcolato come segue:

Pedone: avanti, prima gira due avanti, en passant * 2, promozione = 7 bit. Puoi combinare il primo turno in avanti e la promozione in un unico bit poiché non possono avvenire dalla stessa posizione, quindi hai 6. Torre: 7 quadrati verticali, 7 quadrati orizzontali = 14 bit Cavaliere: 8 quadrati = 8 bit Alfiere: 2 diagonali * 7 = 14 bit Queen: 7 verticali, 7 orizzontali, 7 diagonali, 7 diagonali = 28 bit King: 8 quadrati circostanti

Ciò significa comunque che dovresti mappare i quadrati mirati in base alla posizione corrente, ma (dovrebbe essere) un semplice calcolo.

Dato che abbiamo 16 pedoni, 4 torri / cavalieri / vescovi e 2 regine / re, questo è 16 * 6 + 4 * 14 + 4 * 8 + 4 * 14 + 2 * 28 + 2 * 8 = 312 bit in più, portando il totale a 505 bit complessivi.

Per quanto riguarda il numero di bit richiesti per pezzo per le possibili mosse, a questo si potrebbero aggiungere alcune ottimizzazioni e il numero di bit probabilmente ridotto, ho usato solo numeri facili con cui lavorare. Ad esempio, per far scorrere i pezzi potresti memorizzare quanto lontano potrebbero spostarsi, ma ciò richiederebbe calcoli extra.

Per farla breve: memorizza i dati extra (pezzo, ecc.) Solo quando una casella è occupata e memorizza solo il numero minimo di bit per ogni pezzo per rappresentare le sue mosse legali.

EDIT1: Hai dimenticato l'arrocco e la promozione dei pedoni su qualsiasi pezzo. Questo potrebbe portare il totale con posizioni esplicite a 557 mosse (3 bit in più per i pedoni, 2 per i re)


1

Ogni pezzo può essere rappresentato da 4 bit (dal pedone al re, 6 tipi), nero / bianco = 12 valori

Ogni quadrato sulla scacchiera può essere rappresentato da 6 bit (x coord, y coord).

Le posizioni iniziali richiedono un massimo di 320 bit (32 pezzi, 4 + 6 bit)

Ogni mossa successiva può essere rappresentata da 16 bit (da posizione, a posizione, pezzo).

L'arrocco richiederebbe 16 bit extra, poiché è una doppia mossa.

Le pedine in coda potrebbero essere rappresentate da uno dei 4 valori di riserva su 4 bit.

Senza fare i calcoli in dettaglio, questo inizia a risparmiare spazio dopo la prima mossa rispetto alla memorizzazione di 32 * 7 bit (matrice predefinita di pezzi) o 64 * 4 bit (assegnazione predefinita di quadrati)

Dopo 10 mosse su entrambi i lati, lo spazio massimo richiesto è di 640 bit

... ma poi di nuovo, se identifichiamo ogni pezzo in modo univoco (5 bit) e aggiungiamo un sesto bit per contrassegnare i pedoni con la regina, allora abbiamo solo bisogno di piece-id + to-position per ogni mossa. Questo cambia il calcolo in ...

Posizioni iniziali = max 384 bit (32 pezzi, 6 + 6 bit) Ogni mossa = 12 bit (to-position, piece-id)

Quindi, dopo 10 mosse su ciascun lato, lo spazio massimo richiesto è di 624 bit


La seconda opzione ha il vantaggio aggiuntivo che la memorizzazione può essere letta come record a 12 bit, ogni record = posizione e pezzo. La prima mossa cabina è rilevabile dal fatto che il pezzo ha un'entrata precedente.
Steve De Caux

per il tempo tra le mosse, aggiungi x bit per il contatore a ciascun record. Per i record di installazione, questo sarà impostato su 0.
Steve De Caux

Questo è l'approccio che stavo per scrivere. Un'ottimizzazione è che per i giochi standard, non è necessario codificare affatto le posizioni iniziali - è sufficiente un singolo bit in testa che dice "questo è un gioco standard". Inoltre, l'arrocco non richiede una doppia mossa: poiché una mossa di arrocco è sempre ovvia, e c'è solo un modo valido per la Torre di muoversi quando si verifica una data metà di arrocco, sono informazioni ridondanti. Per la promozione, puoi semplicemente usare i 4 bit dopo che un pedone si è spostato sull'ultima riga per specificare il nuovo tipo di pezzo che diventa.
kyoryu

Quindi, per un gioco tipico, dopo 10 mosse saresti a 121 bit, supponendo che non ci siano promozioni. I giochi atipici richiederebbero 1 bit per bandiera, pezzi * 10 bit e un altro bit per indicare il primo giocatore. Inoltre, ogni mossa richiederebbe solo 12 bit, poiché il pezzo su una data casella è implicito dalle mosse precedenti nel gioco. Questo è probabilmente meno efficiente di alcuni dei metodi suggeriti, ma è abbastanza pulito e ragionevolmente efficiente per i giochi "standard".
kyoryu

@kyoro - Non sono convinto che 12 bit per mossa possano essere battuti - usando la tua idea di un valore nullo per l'impostazione standard (userei comunque 12 bit impostati su zero) - dopo 1000 mosse su ciascun lato questo è 24012 bit aka 3002 byte (arrotondati per eccesso) Anche usando una qualche forma di compressione devi imbrogliare per battere questo, dichiarando il tuo dizionario hard-coded (o logicamente derivabile, la stessa cosa)
Steve De Caux

1

Come Robert G, tenderei a usare PGN poiché è standard e può essere utilizzato da una vasta gamma di strumenti.

Se, tuttavia, sto giocando un'IA di scacchi su una sonda spaziale distante, e quindi ogni bit è prezioso, questo è quello che farei per le mosse. Verrò più tardi a codificare lo stato iniziale.

Le mosse non hanno bisogno di registrare lo stato; il decoder può tenere traccia dello stato e delle mosse legali in un dato momento. Tutto ciò che occorre registrare è quale delle varie alternative legali viene scelta. Poiché i giocatori si alternano, una mossa non ha bisogno di registrare il colore del giocatore. Poiché un giocatore può muovere solo i propri pezzi di colore, la prima scelta è quale pezzo muove (tornerò su un'alternativa che utilizza un'altra scelta in seguito). Con un massimo di 16 pezzi, questo richiede al massimo 4 bit. Man mano che un giocatore perde pezzi, il numero di scelte diminuisce. Inoltre, un particolare stato del gioco può limitare la scelta dei pezzi. Se un re non può muoversi senza mettersi sotto scacco, il numero di scelte è ridotto di uno. Se un re è sotto scacco, qualsiasi pezzo che non riesce a togliere l'assegno dal re non è una scelta praticabile.

Una volta specificato il pezzo, avrà solo un certo numero di destinazioni legali. Il numero esatto dipende in gran parte dal layout del tabellone e dalla cronologia del gioco, ma possiamo calcolare alcuni massimi e valori attesi. Per tutti tranne il cavaliere e durante l'arrocco, un pezzo non può muoversi attraverso un altro pezzo. Questa sarà una grande fonte di limiti di movimento, ma è difficile da quantificare. Un pezzo non può spostarsi fuori dal tabellone, il che limiterà anche in gran parte il numero di destinazioni.

Codifichiamo la destinazione della maggior parte dei pezzi numerando i quadrati lungo le linee nel seguente ordine: W, NW, N, NE (il lato nero è N). Una linea inizia nel quadrato più lontano nella direzione data in cui è consentito spostarsi e procede verso il. Per un re libero, l'elenco delle mosse è W, E, NW, SE, N, S, NE, SO. Per il cavaliere, iniziamo con 2W1N e procediamo in senso orario; destinazione 0 è la prima destinazione valida in questo ordine.

  • Pedine: una pedina immobile ha 2 scelte di destinazione, quindi richiede 1 bit. Se un pedone può catturarne un altro, normalmente o en passant (che il decoder può determinare, poiché tiene traccia dello stato), ha anche 2 o 3 scelte di mosse. Oltre a questo, un pedone può avere solo 1 scelta, senza richiedere bit. Quando un pedone è al 7 ° posto, viriamo anche sulla scelta della promozione. Poiché le pedine sono solitamente promosse a regine, seguite da cavalieri, codifichiamo le scelte come:
    • regina: 0
    • cavaliere: 10
    • vescovo: 110
    • torre: 111
  • Bishop: al massimo 13 destinazioni quando a {d, e} {4,5} per 4 bit.
  • Rook: al massimo 14 destinazioni, 4 bit.
  • Cavalieri: al massimo 8 destinazioni, 3 bit.
  • Kings: Quando l'arrocco è un'opzione, il re ha di nuovo la S e non può muovere verso il basso; questo dà un totale di 7 destinazioni. Il resto del tempo, un re ha al massimo 8 mosse, dando un massimo di 3 bit.
  • Regina: come le scelte per l'alfiere o la torre, per un totale di 27 scelte, ovvero 5 bit

Poiché il numero di scelte non è sempre un potere di due, quanto sopra spreca ancora bit. Supponiamo che il numero di scelte è C e la particolare scelta è numerata c , e n = ceil (lg ( C )) (il numero di bit necessari per codificare la scelta). Utilizziamo questi valori altrimenti sprecati esaminando il primo bit della scelta successiva. Se è 0, non fare nulla. Se è 1 e c + C <2 n , aggiungi C a c . La decodifica di un numero inverte questo: se il ricevuto c > = C , sottrarre C e impostare il primo bit del numero successivo a 1. Se c<2 n - C , quindi impostare il primo bit per il numero successivo a 0. Se 2 n - C <= c < C , non fare nulla. Chiama questo schema "saturazione".

Un altro potenziale tipo di scelta che potrebbe abbreviare la codifica è scegliere un pezzo dell'avversario da catturare. Ciò aumenta il numero di scelte per la prima parte di una mossa, selezionando un pezzo, per al massimo un bit aggiuntivo (il numero esatto dipende da quanti pezzi può muovere il giocatore corrente). Questa scelta è seguita da una scelta di catturare il pezzo, che è probabilmente molto più piccolo del numero di mosse per uno qualsiasi dei pezzi del giocatore dato. Un pezzo può essere attaccato solo da un pezzo da qualsiasi direzione cardinale più i cavalieri per un totale di massimo 10 pezzi attaccanti; questo dà un totale massimo di 9 bit per una mossa di cattura, anche se mi aspetto in media 7 bit. Ciò sarebbe particolarmente vantaggioso per le catture della regina, poiché spesso avrà alcune destinazioni legali.

Con la saturazione, la codifica dell'acquisizione probabilmente non offre alcun vantaggio. Potremmo consentire entrambe le opzioni, specificando nello stato iniziale quali vengono utilizzate. Se la saturazione non viene utilizzata, la codifica del gioco potrebbe anche utilizzare numeri di scelta non utilizzati ( C <= c <2 n ) per modificare le opzioni durante il gioco. Ogni volta che C è una potenza di due, non possiamo cambiare le opzioni.


1

Thomas ha l'approccio giusto per codificare la scheda. Tuttavia, questo dovrebbe essere combinato con l'approccio di ralu per la memorizzazione delle mosse. Fai un elenco di tutte le mosse possibili, scrivi il numero di bit necessari per esprimere questo numero. Poiché il decoder sta eseguendo lo stesso calcolo, sa quanti sono possibili e può sapere quanti bit leggere, non sono necessari codici di lunghezza.

Quindi otteniamo 164 bit per i pezzi, 4 bit per le informazioni sull'arrocco (supponendo di memorizzare un frammento di un gioco, altrimenti può essere ricostruito), 3 bit per le informazioni sull'idoneità en passant - memorizza semplicemente la colonna in cui è avvenuta la mossa ( Se en passant non è possibile memorizzare una colonna dove non è possibile - tali colonne devono esistere) e 1 per chi deve spostarsi.

Le mosse richiedono in genere 5 o 6 bit, ma possono variare da 1 a 8.

Una scorciatoia aggiuntiva: se la codifica inizia con 12 bit da 1 (una situazione non valida - nemmeno un frammento avrà due re su un lato) interrompi la decodifica, cancella il tabellone e prepara un nuovo gioco. Il prossimo bit sarà un po 'mossa.


1

L'algoritmo dovrebbe enumerare in modo deterministico tutte le possibili destinazioni ad ogni mossa. Numero di destinazioni:

  • 2 vescovi, 13 destinazioni ciascuno = 26
  • 2 torri, 14 destinazioni ciascuna = 28
  • 2 cavalieri, 8 destinazioni ciascuna = 16
  • regina, 27 destinazioni
  • re, 8 destinazioni

8 zampe potrebbero diventare tutte regine nel peggiore dei casi (in termini di enumerazione), rendendo così il maggior numero di possibili destinazioni 9 * 27 + 26 + 28 + 16 + 8 = 321. Pertanto, tutte le destinazioni per qualsiasi spostamento possono essere enumerate da un numero di 9 bit.

Il numero massimo di mosse di entrambe le parti è 100 (se non sbaglio, non un giocatore di scacchi). Quindi qualsiasi gioco potrebbe essere registrato in 900 bit. Oltre alla posizione iniziale, ogni brano può essere registrato utilizzando numeri a 6 bit, che ammontano a 32 * 6 = 192 bit. Più un bit per il record "chi muove per primo". Pertanto, qualsiasi gioco può essere registrato utilizzando 900 + 192 + 1 = 1093 bit.


1

Memorizzazione dello stato della scheda

Il modo più semplice a cui ho pensato è di avere un array di 8 * 8 bit che rappresentano la posizione di ogni pezzo (quindi 1 se c'è un pezzo degli scacchi lì e 0 se non c'è). Poiché si tratta di una lunghezza fissa, non è necessario alcun terminatore.

Quindi rappresenta ogni pezzo degli scacchi in ordine di posizione. Utilizzando 4 bit per pezzo, questo richiede 32 * 4 bit (128 in totale). Il che è davvero uno spreco.

Usando un albero binario, possiamo rappresentare un pedone in un byte, un cavaliere e una torre e un alfiere in 3 e un re e una regina in 4. Dato che dobbiamo anche memorizzare il colore del pezzo, che prende un byte in più finisce come (perdonami se questo è sbagliato, non ho mai guardato la codifica di Huffman in dettaglio prima):

  • Pedone: 2
  • Torre: 4
  • Cavaliere: 4
  • Vescovo: 4
  • Re: 5
  • Regina: 5

Dati i totali:

2*16 + 4*4 + 4*4 + 4*4 + 2*5 + 2*5 = 100

Che batte utilizzando un set di bit a dimensione fissa di 28 bit.

Quindi il metodo migliore che ho trovato è memorizzarlo in un array da 8 2 + 100 bit

8*8 + 100 = 164



Memorizzare le mosse
La prima cosa che dobbiamo sapere è quale pezzo si sta muovendo e dove. Dato che ci sono al massimo 32 pezzi sulla scacchiera e sappiamo qual è ogni pezzo, invece di un intero che rappresenta il quadrato, possiamo avere un numero intero che rappresenta l'offset del pezzo, il che significa che dobbiamo solo adattare 32 possibili valori per rappresentare un pezzo.

Sfortunatamente ci sono varie regole speciali, come l'arrocco o il rovesciamento del re e la formazione di una repubblica (riferimento a Terry Pratchett), quindi prima di memorizzare il pezzo da spostare abbiamo bisogno di un singolo bit che indichi se si tratta di una mossa speciale o meno.

Quindi per ogni mossa normale abbiamo un 1 + 5 = 6bit necessario . (Tipo 1 bit, 5 bit per il pezzo)

Dopo che il numero del pezzo è stato decodificato, conosciamo il tipo di pezzo e ogni pezzo dovrebbe rappresentare il suo movimento nel modo più efficiente. Ad esempio (se le mie regole degli scacchi sono all'altezza) un pedone ha un totale di 4 mosse possibili (prendi a sinistra, prendi a destra, muovi uno in avanti, muovi due in avanti).
Quindi per rappresentare una mossa di pedone abbiamo bisogno dei bit "6 + 2 = 8". (6 bit per l'intestazione della mossa iniziale, 2 bit per quale mossa)

Muoversi per la regina sarebbe più complesso, in quanto sarebbe meglio avere una direzione (8 possibili direzioni, quindi 3 bit) e un totale di 8 possibili quadrati in cui spostarsi per ogni direzione (quindi altri 3 bit). Quindi per rappresentare lo spostamento di una regina sarebbero necessari dei 6 + 3 + 3 = 12bit.

L'ultima cosa che mi capita è che dobbiamo memorizzare quali giocatori girano. Questo dovrebbe essere un singolo bit (bianco o nero per spostarsi dopo)



Formato risultante
Quindi il formato del file sarebbe simile a questo

[64 bit] Posizioni dei pezzi iniziali
[100 bit max] Pezzi iniziali [1 bit] Turno del giocatore
[n bit] Mosse

Dove uno spostamento è
[1 bit] Tipo di spostamento (speciale o normale)
[n bit] Dettaglio spostamento

Se il movimento è una mossa normale, il dettaglio del movimento assomiglia a
[5 bit] pezzo
[n bit] mossa specifica del pezzo (di solito nell'intervallo da 2 a 6 bit]

Se è una mossa speciale,
dovrebbe avere un tipo intero e quindi qualsiasi informazione aggiuntiva (come se fosse un arrocco). Non ricordo il numero di mosse speciali, quindi potrebbe essere corretto solo per indicare che si tratta di una mossa speciale (se ce n'è solo una)


1

Nel caso base della scacchiera iniziale più le mosse successive, considera quanto segue.

Usa un programma di scacchi per assegnare probabilità a tutte le mosse possibili. Ad esempio, 40% per e2-e4 20% per d2-d4 e così via. Se alcune mosse sono legali ma non considerate da quel programma, dai loro una bassa probabilità. Usa la codifica aritmetica per salvare la scelta che è stata presa, che sarà un numero compreso tra 0 e 0,4 per la prima mossa, 0,4 e 0,6 per la seconda e così via.

Fai lo stesso per l'altro lato. Ad esempio, se esiste una probabilità del 50% di e7-e5 come risposta a e2-e4, il numero codificato sarà compreso tra 0 e 0,2. Ripeti fino al termine del gioco. Il risultato è una gamma potenzialmente molto piccola. Trova la frazione binaria con la base più piccola che rientra in quell'intervallo. Questa è la codifica aritmetica.

Questo è meglio di Huffman perché può essere pensato come una codifica bit frazionaria (più alcuni alla fine del gioco per arrotondare a un bit intero).

Il risultato dovrebbe essere più compatto di Huffman e non ci sono casi speciali per la promozione, l'en passant, la mossa della regola del 50 e altri dettagli perché sono gestiti dal programma di valutazione degli scacchi.

Per rigiocare, usa di nuovo il programma di scacchi per valutare la scacchiera e assegnare tutte le probabilità a ciascuna mossa. Usa il valore codificato aritmetico per determinare quale mossa è stata effettivamente giocata. Ripeti fino al termine.

Se il tuo programma di scacchi è abbastanza buono, puoi ottenere una migliore compressione con un codificatore a due stati, in cui le probabilità sono definite in base alle mosse sia per il bianco che per il nero. Nel caso più estremo di circa 200+ stati, questo codifica l'intero set di tutte le possibili partite di scacchi e quindi non è fattibile.

Questo è praticamente un modo diverso di dire ciò che Darius ha già scritto, solo con un piccolo esempio di come potrebbe funzionare la codifica aritmetica, e un esempio reale di utilizzo di un programma di scacchi esistente per aiutare a valutare la probabilità delle mosse successive.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.