web-dev-qa-db-fra.com

Puzzle de programmeur: encodage d'un état d'échiquier tout au long d'une partie

Pas strictement une question, plus un puzzle ...

Au fil des ans, j'ai participé à quelques entretiens techniques de nouveaux employés. En plus de poser les questions standard "connaissez-vous la technologie X", j'ai également essayé de comprendre comment ils abordent les problèmes. En règle générale, je leur envoyais la question par e-mail la veille de l'entretien et je m'attendais à ce qu'ils trouvent une solution le lendemain.

Souvent, les résultats étaient assez intéressants - faux, mais intéressants - et la personne recevrait toujours ma recommandation si elle pouvait expliquer pourquoi elle a adopté une approche particulière.

J'ai donc pensé lancer une de mes questions au public de Stack Overflow.

Question: Quelle est la manière la plus économe en espace à laquelle vous pouvez penser pour encoder l'état d'une partie d'échecs (ou un sous-ensemble de celui-ci)? et tous les mouvements légaux ultérieurs effectués par les joueurs dans le jeu.

Aucun code requis pour la réponse, juste une description de l'algorithme que vous utiliseriez.

EDIT: Comme l'a souligné l'une des affiches, je n'ai pas considéré l'intervalle de temps entre les mouvements. N'hésitez pas à en tenir compte également en option :)

EDIT2: Juste pour des éclaircissements supplémentaires ... N'oubliez pas, l'encodeur/décodeur est sensible aux règles. Les seules choses qui doivent vraiment être stockées sont les choix du joueur - tout le reste peut être supposé être connu par l'encodeur/décodeur.

EDIT3: Il va être difficile de choisir un gagnant ici :) Beaucoup de bonnes réponses!

91
Andrew Rollings

Mise à jour: J'ai tellement aimé ce sujet que j'ai écrit Programmation des puzzles, des positions d'échecs et du codage Huffman . Si vous lisez ceci, j'ai déterminé que la manière seulement de stocker un état de jeu complet consiste à stocker une liste complète de mouvements. Lisez la suite pour savoir pourquoi. J'utilise donc une version légèrement simplifiée du problème pour la disposition des pièces.

Le problème

Cette image illustre la position de départ des échecs. Les échecs se déroulent sur un plateau 8x8, chaque joueur commençant par un ensemble identique de 16 pièces composé de 8 pions, 2 tours, 2 chevaliers, 2 évêques, 1 reine et 1 roi comme illustré ici:

starting chess position

Les positions sont généralement enregistrées sous la forme d'une lettre pour la colonne suivie du numéro de la ligne de sorte que la reine de White est à d1. Les mouvements sont le plus souvent stockés dans notation algébrique , ce qui est sans ambiguïté et ne spécifie généralement que les informations minimales nécessaires. Considérez cette ouverture:

  1. e4 e5
  2. Nf3 Nc6

ce qui se traduit par:

  1. Blanc déplace le pion du roi de e2 à e4 (c'est la seule pièce qui peut arriver à e4 d'où "e4");
  2. Les noirs déplacent le pion du roi de e7 à e5;
  3. Blanc déplace le chevalier (N) sur f3;
  4. Noir déplace le chevalier en c6.

Le tableau ressemble à ceci:

early opening

Une capacité importante pour tout programmeur est de pouvoir spécifier correctement et sans ambiguïté le problème.

Alors, qu'est-ce qui manque ou est ambigu? Beaucoup, en fait.

État du plateau vs État du jeu

La première chose que vous devez déterminer est de savoir si vous stockez l'état d'un jeu ou la position des pièces sur le plateau. Encoder simplement les positions des pièces est une chose mais le problème dit "tous les mouvements légaux ultérieurs". Le problème ne dit rien non plus sur la connaissance des mouvements jusqu'à ce point. C'est en fait un problème comme je vais l'expliquer.

Roque

Le jeu s'est déroulé comme suit:

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

Le tableau se présente comme suit:

later opening

Le blanc a l'option roque . Une partie des exigences pour cela est que le roi et la tour concernée ne peuvent jamais avoir bougé, donc si le roi ou l'une des tours de chaque côté doit se déplacer devra être stocké. Évidemment, s'ils ne sont pas sur leur position de départ, ils ont bougé sinon il faut le préciser.

Il existe plusieurs stratégies qui peuvent être utilisées pour résoudre ce problème.

Premièrement, nous pourrions stocker 6 bits d'informations supplémentaires (1 pour chaque tour et roi) pour indiquer si cette pièce avait bougé. Nous pourrions rationaliser cela en ne stockant un peu pour l'un de ces six carrés que si la bonne pièce s'y trouve. Alternativement, nous pourrions traiter chaque pièce immobile comme un autre type de pièce.Au lieu de 6 types de pièces de chaque côté (pion, tour, chevalier, évêque, reine et roi), il y en a 8 (en ajoutant la tour immobile et le roi immobile).

En passant

Une autre règle particulière et souvent négligée dans les échecs est En Passant .

en passant

Le jeu a progressé.

  1. e4 e5
  2. Nf3 Nc6
  3. Bb5 a6
  4. Ba4 Bc5
  5. O-O b5
  6. Bb3 b4
  7. c4

Le pion noir sur b4 a maintenant la possibilité de déplacer son pion sur b4 vers c3 en prenant le pion blanc sur c4. Cela ne se produit que lors de la première opportunité, ce qui signifie que si Black passe l'option maintenant, il ne peut pas la prendre au prochain coup. Nous devons donc stocker cela.

Si nous connaissons le mouvement précédent, nous pouvons certainement répondre si En Passant est possible. Alternativement, nous pouvons stocker si chaque pion de son 4e rang vient de s'y déplacer avec un double mouvement vers l'avant. Ou nous pouvons regarder chaque position En Passant possible sur le tableau et avoir un drapeau pour indiquer si c'est possible ou non.

Promotion

pawn promotion

C’est le mouvement des Blancs. Si Blanc déplace son pion de h7 à h8, il peut être promu dans n'importe quelle autre pièce (mais pas au roi). 99% du temps, elle est promue reine, mais parfois ce n'est pas le cas, généralement parce que cela peut entraîner une impasse dans le cas contraire. Ceci est écrit comme:

  1. h8 = Q

Ceci est important dans notre problème car cela signifie que nous ne pouvons pas compter sur un nombre fixe de pièces de chaque côté. Il est tout à fait possible (mais incroyablement peu probable) qu'un côté se retrouve avec 9 reines, 10 tours, 10 évêques ou 10 chevaliers si les 8 pions sont promus.

Impasse

Lorsque vous êtes dans une position où vous ne pouvez pas gagner, votre meilleure tactique consiste à essayer une impasse . La variante la plus probable est celle où vous ne pouvez pas faire un mouvement légal (généralement parce que tout mouvement est mis en échec par votre roi). Dans ce cas, vous pouvez demander un match nul. Celui-ci est facile à entretenir.

La deuxième variante est par triple répétition . Si la même position de plateau se produit trois fois dans une partie (ou se produira une troisième fois au coup suivant), un match nul peut être réclamé. Les positions n'ont pas besoin de se produire dans un ordre particulier (ce qui signifie qu'il n'a pas à la même séquence de mouvements répétés trois fois). Celui-ci complique grandement le problème car vous devez vous souvenir de chaque position précédente du conseil d'administration. Si c'est une exigence du problème, la seule solution possible au problème est de stocker chaque mouvement précédent.

Enfin, il y a la règle des cinquante coups . Un joueur peut réclamer un nul si aucun pion n'a bougé et qu'aucune pièce n'a été prise au cours des cinquante coups consécutifs précédents. 6 bits (0-63).

A qui le tour?

Bien sûr, nous devons également savoir de qui il s'agit et il s'agit d'un seul élément d'information.

Deux problèmes

En raison de l'impasse, la seule façon possible ou raisonnable de stocker l'état du jeu est de stocker tous les mouvements qui ont conduit à cette position. Je vais aborder ce seul problème. Le problème de l'état de la carte sera simplifié comme suit: stocke la position actuelle de toutes les pièces sur la carte en ignorant les conditions de roque, en passant, en impasse et à qui revient le tour.

La disposition des pièces peut être largement gérée de deux manières: en stockant le contenu de chaque carré ou en stockant la position de chaque pièce.

Contenu simple

Il existe six types de pièces (pion, tour, chevalier, évêque, reine et roi). Chaque pièce peut être blanche ou noire donc un carré peut contenir l'une des 12 pièces possibles ou il peut être vide donc il y a 13 possibilités. 13 peut être stocké sur 4 bits (0-15) La solution la plus simple est donc de stocker 4 bits pour chaque carré de 64 carrés ou 256 bits d'informations.

L'avantage de cette méthode est que la manipulation est incroyablement facile et rapide. Cela pourrait même être étendu en ajoutant 3 possibilités supplémentaires sans augmenter les besoins de stockage: un pion qui a bougé de 2 cases au dernier tour, un roi qui n'a pas bougé et une tour qui n'a pas bougé, ce qui conviendra beaucoup des problèmes mentionnés précédemment.

Mais nous pouvons faire mieux.

Encodage en base 13

Il est souvent utile de considérer la position du conseil d'administration comme un très grand nombre. Cela se fait souvent en informatique. Par exemple, le problème d'arrêt traite un programme informatique (à juste titre) comme un grand nombre.

La première solution traite la position comme un nombre de base à 16 chiffres, mais comme démontré, il y a redondance dans ces informations (étant les 3 possibilités inutilisées par "chiffre") afin que nous puissions réduire l'espace numérique à 64 chiffres de base 13. Bien sûr, cela ne peut pas être fait aussi efficacement que la base 16, mais cela économisera sur les besoins de stockage (et minimiser l'espace de stockage est notre objectif).

Dans la base 10, le nombre 234 équivaut à 2 x 102 + 3 x 101 + 4 x 10.

En base 16, le nombre 0xA50 équivaut à 10 x 162 + 5 x 161 + 0 x 16 = 2640 (décimal).

Nous pouvons donc coder notre position comme p x 1363 + p1 x 1362 + ... + p63 x 13 où pje représente le contenu du carré i.

2256 est égal à environ 1,16e77. 1364 équivaut à environ 1,96e71, ce qui nécessite 237 bits d'espace de stockage. Cette économie de seulement 7,5% a un coût de significativement augmentation des coûts de manipulation.

Encodage à base variable

Dans les tableaux juridiques, certaines pièces ne peuvent pas apparaître dans certains carrés. Par exemple, les pions ne peuvent pas apparaître au premier ou au huitième rang, ce qui réduit les possibilités pour ces carrés à 11. Cela réduit les planches possibles à 1116 x 1348 = 1,35e70 (environ), nécessitant 233 bits d'espace de stockage.

En fait, l'encodage et le décodage de telles valeurs vers et à partir de décimales (ou binaires) est un peu plus compliqué, mais cela peut être fait de manière fiable et est laissé comme exercice au lecteur.

Alphabets à largeur variable

Les deux méthodes précédentes peuvent être décrites comme codage alphabétique à largeur fixe. Chacun des 11, 13 ou 16 membres de l'alphabet est substitué à une autre valeur. Chaque "caractère" a la même largeur mais l'efficacité peut être améliorée si l'on considère que chaque caractère n'est pas également probable.

morse code

Considérez code Morse (illustré ci-dessus). Les caractères d'un message sont codés comme une séquence de tirets et de points. Ces tirets et points sont transférés par radio (généralement) avec une pause entre eux pour les délimiter.

Remarquez comment la lettre E ( la lettre la plus courante en anglais ) est un seul point, la séquence la plus courte possible, tandis que Z (la moins fréquente) est deux tirets et deux bips.

Un tel schéma peut réduire considérablement la taille d'un message attendu mais a pour conséquence d'augmenter la taille d'une séquence de caractères aléatoires.

Il convient de noter que le code Morse a une autre fonctionnalité intégrée: les tirets sont aussi longs que trois points, de sorte que le code ci-dessus est créé en gardant cela à l'esprit afin de minimiser l'utilisation des tirets. Étant donné que les 1 et les 0 (nos blocs de construction) n'ont pas ce problème, ce n'est pas une fonctionnalité que nous devons répliquer.

Enfin, il existe deux types de silences dans le code Morse. Un court repos (la longueur d'un point) est utilisé pour faire la distinction entre les points et les tirets. Un espace plus long (la longueur d'un tiret) est utilisé pour délimiter les caractères.

Alors, comment cela s'applique-t-il à notre problème?

Codage Huffman

Il existe un algorithme pour traiter les codes de longueur variable appelé codage Huffman . Le codage Huffman crée une substitution de code de longueur variable, utilise généralement la fréquence attendue des symboles pour attribuer des valeurs plus courtes aux symboles les plus courants.

Huffman code tree

Dans l'arborescence ci-dessus, la lettre E est codée en 000 (ou gauche-gauche-gauche) et S est 1011. Il doit être clair que ce schéma de codage est sans ambiguïté.

Il s'agit d'une distinction importante par rapport au code Morse. Le code Morse a le séparateur de caractères afin qu'il puisse faire une substitution autrement ambiguë (par exemple, 4 points peuvent être H ou 2 Is) mais nous n'avons que 1 et 0, nous choisissons donc une substitution non ambiguë.

Voici une implémentation simple:

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; }
}

avec des données statiques:

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);
}

et:

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);
    }
  }
}

Une sortie possible est:

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

Pour une position de départ, cela équivaut à 32 x 1 + 16 x 3 + 12 x 5 + 4 x 6 = 164 bits.

Différence d'état

Une autre approche possible consiste à combiner la toute première approche avec le codage Huffman. Ceci est basé sur l'hypothèse que la plupart des échiquiers attendus (plutôt que ceux générés de manière aléatoire) sont plus susceptibles qu'improbables, au moins en partie, de ressembler à une position de départ.

Donc, ce que vous faites est XOR la position actuelle de la carte 256 bits avec une position de départ 256 bits, puis encodez cela (en utilisant le codage Huffman ou, disons, une méthode de codage de la longueur d'exécution ) Evidemment, cela sera très efficace au départ (64 0 correspondant probablement à 64 bits) mais une augmentation du stockage nécessaire au fur et à mesure de la progression du jeu.

Position de la pièce

Comme mentionné, une autre façon d'attaquer ce problème est de stocker à la place la position de chaque pièce d'un joueur. Cela fonctionne particulièrement bien avec les positions de fin de partie où la plupart des carrés seront vides (mais dans l'approche de codage Huffman, les carrés vides n'utilisent que 1 bit de toute façon).

Chaque côté aura un roi et 0-15 autres pièces. En raison de la promotion, la composition exacte de ces pièces peut varier suffisamment pour que vous ne puissiez pas supposer que les nombres basés sur les positions de départ sont des maxima.

La façon logique de diviser cela est de stocker une position composée de deux côtés (blanc et noir). Chaque côté a:

  • Un roi: 6 bits pour l'emplacement;
  • A des pions: 1 (oui), 0 (non);
  • Si oui, nombre de pions: 3 bits (0-7 + 1 = 1-8);
  • Si oui, l'emplacement de chaque pion est codé: 45 bits (voir ci-dessous);
  • Nombre de non-pions: 4 bits (0-15);
  • Pour chaque pièce: type (2 bits pour reine, tour, chevalier, évêque) et emplacement (6 bits)

Quant à l'emplacement du pion, les pions ne peuvent être que sur 48 cases possibles (pas 64 comme les autres). En tant que tel, il est préférable de ne pas gaspiller les 16 valeurs supplémentaires que l'utilisation de 6 bits par pion utiliserait. Donc, si vous avez 8 pions, il y en a 488 possibilités, soit 28.179.280.429.056. Vous avez besoin de 45 bits pour encoder autant de valeurs.

C'est 105 bits par côté ou 210 bits au total. Cependant, la position de départ est le pire des cas pour cette méthode et elle s'améliorera considérablement lorsque vous retirerez des pièces.

Il convient de souligner qu’il existe moins de 488 possibilités parce que les pions ne peuvent pas tous être dans le même carré Le premier a 48 possibilités, le second 47 et ainsi de suite. 48 x 47 x… x 41 = 1,52e13 = stockage 44 bits.

Vous pouvez encore améliorer cela en éliminant les carrés occupés par d'autres pièces (y compris l'autre côté) afin de pouvoir d'abord placer les non-pions blancs puis les non-pions noirs, puis les pions blancs et enfin les pions noirs. Sur une position de départ, cela réduit les exigences de stockage à 44 bits pour le blanc et 42 bits pour le noir.

Approches combinées

Une autre optimisation possible est que chacune de ces approches a ses forces et ses faiblesses. Vous pouvez, par exemple, choisir le meilleur 4, puis coder un sélecteur de schéma dans les deux premiers bits, puis le stockage spécifique au schéma après cela.

Avec des frais généraux aussi petits, ce sera de loin la meilleure approche.

État du jeu

Je reviens au problème de stocker un jeu plutôt qu'un position. En raison de la triple répétition, nous devons stocker la liste des mouvements qui se sont produits jusqu'à ce point.

Annotations

Une chose que vous devez déterminer est de simplement stocker une liste de mouvements ou d'annoter le jeu? Les parties d'échecs sont souvent annotées, par exemple:

  1. Bb5 !! Nc4?

Le mouvement des Blancs est marqué par deux points d'exclamation comme brillant tandis que les Noirs sont considérés comme une erreur. Voir ponctuation d'échecs .

De plus, vous pouvez également avoir besoin de stocker du texte libre pendant la description des déplacements.

Je suppose que les mouvements sont suffisants donc il n'y aura pas d'annotations.

Notation algébrique

Nous pourrions simplement stocker le texte du déplacement ici ("e4", "Bxb5", etc.). En incluant un octet de fin, vous regardez environ 6 octets (48 bits) par mouvement (pire cas). Ce n'est pas particulièrement efficace.

La deuxième chose à essayer est de stocker l'emplacement de départ (6 bits) et l'emplacement de fin (6 bits), donc 12 bits par mouvement. C'est bien mieux.

Alternativement, nous pouvons déterminer tous les mouvements légaux à partir de la position actuelle de manière prévisible et déterministe et indiquer ce que nous avons choisi. Cela revient ensuite au codage de base variable mentionné ci-dessus. Blanc et Noir ont 20 coups possibles chacun lors de leur premier coup, plus sur le second et ainsi de suite.

Conclusion

Il n'y a pas de réponse absolument juste à cette question. Il existe de nombreuses approches possibles, dont les précédentes ne sont que quelques-unes.

Ce que j'aime à propos de cela et des problèmes similaires, c'est qu'il exige des capacités importantes pour tout programmeur, comme la prise en compte du modèle d'utilisation, la détermination précise des exigences et la réflexion sur les cas d'angle.

Positions d'échecs prises comme captures d'écran de Chess Position Trainer .

131
cletus

Il est préférable de stocker les parties d'échecs dans un format standard lisible par l'homme.

Le Portable Game Notation prend une position de départ standard (bien qu'il ne doit pas ) et énumère simplement les mouvements, tour par tour. Un format standard compact, lisible par l'homme.

Par exemple.

[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

Si vous voulez le réduire, alors il suffit de le compresser . Travail accompli!

48
Robert Grant

Grand puzzle!

Je vois que la plupart des gens stockent la position de chaque pièce. Que diriez-vous d'adopter une approche plus simple et de stocker le contenu de chaque carré ? Cela prend en charge la promotion et les pièces capturées automatiquement.

Et il permet l'encodage Huffman . En fait, la fréquence initiale des pièces sur le plateau est presque parfaite pour cela: la moitié des carrés sont vides, la moitié des carrés restants sont des pions, etc.

Compte tenu de la fréquence de chaque pièce, j'ai construit un arbre Huffman sur papier, que je ne répéterai pas ici. Le résultat, où c représente la couleur (blanc = 0, noir = 1):

  • 0 pour les cases vides
  • 1c0 pour pion
  • 1c100 pour tour
  • 1c101 pour chevalier
  • 1c110 pour l'évêque
  • 1c1110 pour reine
  • 1c1111 pour le roi

Pour l'ensemble du conseil dans sa situation initiale, nous avons

  • carrés vides: 32 * 1 bit = 32 bits
  • pions: 16 * 3 bits = 48 bits
  • tour/chevaliers/évêques: 12 * 5 bits = 60 bits
  • reines/rois: 4 * 6 bits = 24 bits

Total: 164 bits pour l'état de la carte initial. Beaucoup moins que les 235 bits de la réponse actuellement la plus élevée. Et cela ne fera que se réduire à mesure que le jeu progresse (sauf après une promotion).

Je n'ai regardé que la position des pièces sur la planche; un état supplémentaire (dont le tour, qui a lancé, en passant, les mouvements répétés, etc.) devra être encodé séparément. Peut-être encore 16 bits au maximum, donc 180 bits pour tout l'état du jeu. Optimisations possibles:

  • En laissant de côté les pièces les moins fréquentes et en conservant leur position séparément. Mais cela n'aidera pas ... remplacer le roi et la reine par un carré vide permet d'économiser 5 bits, qui sont exactement les 5 bits dont vous avez besoin pour coder leur position d'une autre manière.
  • "Pas de pions sur la rangée arrière" pourrait facilement être encodé en utilisant une table Huffman différente pour les rangées arrière, mais je doute que cela aide beaucoup. Vous vous retrouveriez probablement toujours avec le même arbre Huffman.
  • "Un évêque blanc, un noir" peut être encodé en introduisant des symboles supplémentaires qui n'ont pas le bit c, qui peut ensuite être déduit du carré sur lequel l'évêque se trouve. (Les pions promus évêques perturbent ce schéma ...)
  • Les répétitions de carrés vides pourraient être codées sur toute la longueur en introduisant des symboles supplémentaires pour, disons, "2 carrés vides dans une rangée" et "4 carrés vides dans une rangée". Mais il n'est pas si facile d'estimer la fréquence de ceux-ci, et si vous vous trompez, cela va faire mal plutôt que d'aider.
14
Thomas

L'approche de la table de recherche vraiment grande

Position - 18 octets
Le nombre estimé de postes juridiques est 1043
Il suffit de les énumérer tous et la position peut être stockée en seulement 143 bits. 1 bit supplémentaire est nécessaire pour indiquer quel côté doit jouer ensuite

L'énumération n'est pas pratique bien sûr, mais cela montre qu'au moins 144 bits sont nécessaires.

Déplace - 1 octet
Il y a généralement environ 30 à 40 mouvements légaux pour chaque position, mais le nombre peut atteindre 218 Permet d'énumérer tous les mouvements légaux pour chaque position. Désormais, chaque mouvement peut être codé en un octet.

Nous avons encore beaucoup de place pour des mouvements spéciaux tels que 0xFF pour représenter la démission.

9
John La Rooy

Attaquer un sous-problème d'encodage des étapes après avoir encodé une position initiale. L'approche consiste à créer une "liste chaînée" d'étapes.

Chaque étape du jeu est codée comme la paire "ancienne position -> nouvelle position". Vous connaissez la position initiale au début de la partie d'échecs; en parcourant la liste des étapes liées, vous pouvez accéder à l'état après le déplacement de X.

Pour encoder chaque étape, vous avez besoin de 64 valeurs pour encoder la position de départ (6 bits pour 64 carrés sur la carte - 8x8 carrés) et 6 bits pour la position finale. 16 bits pour 1 mouvement de chaque côté.

La quantité d'espace qu'encoderait un jeu donné serait alors proportionnelle au nombre de coups:

10 x (nombre de mouvements blancs + nombre de mouvements noirs) bits.

MISE À JOUR: complication potentielle avec des pions promus. Besoin de pouvoir indiquer à quoi le pion est promu - peut nécessiter des bits spéciaux (utiliser du code gris pour cela pour économiser de l'espace, car la promotion du pion est extrêmement rare).

MISE À JOUR 2: Vous n'avez pas à encoder les coordonnées complètes de la position finale. Dans la plupart des cas, la pièce déplacée ne peut pas se déplacer vers plus de X emplacements. Par exemple, un pion peut avoir un maximum de 3 options de déplacement à un moment donné. En réalisant ce nombre maximum de coups pour chaque type de pièce, nous pouvons économiser des bits sur l'encodage de la "destination".

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

Ainsi, la complexité spatiale par mouvement de noir ou blanc devient

6 bits pour la position initiale + (nombre variable de bits en fonction du type de la chose déplacée).

4
Alex Weinstein

À chaque position, obtenez le nombre de tous les mouvements possibles.

le prochain mouvement est généré comme

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

la meilleure efficacité de l'espace pour le stockage de jeu généré aléatoirement et nécessite environ 5 bits/mouvement en moyenne, car vous avez 30 à 40 mouvements possibles. L'assemblage du stockage génère simplement n dans l'ordre inverse.

La position de stockage est plus difficile à craquer, en raison d'une grande redondance. (Il peut y avoir jusqu'à 9 reines à bord pour un même site, mais dans ce cas, il n'y a pas de pions et les évêques si le conseil est sur des carrés de couleur opposée), mais c'est généralement comme stocker une combinaison des mêmes pièces sur les carrés restants.)

MODIFIER:

Le point de sauvegarde des mouvements est de ne stocker que l'indice de déplacement. Au lieu de stocker Kc1-c2 et d'essayer de réduire ces informations, nous devons ajouter uniquement l'index de mouvement généré à partir du générateur de mouvements déterministe (position)

A chaque mouvement nous ajoutons des informations de taille

num_of_moves = get_number_of_possible_moves(postion) ;

dans la piscine et ce nombre ne peut pas être réduit

la génération du pool d'informations est

n=n*num_of_moves+ index_current_move

extra

S'il n'y a qu'un seul coup disponible en position finale, enregistrez-le comme nombre de coups forcés précédemment effectués. Exemple: si la position de départ a 1 coups forcés pour chaque côté (2 coups) et que nous voulons sauvegarder cela comme un jeu à un coup, stocker 1 dans le pool n.

exemple de stockage dans le pool d'informations

Supposons que nous connaissions les positions de départ et que nous effectuions 3 mouvements.

Au premier coup il y a 5 coups disponibles, et nous prenons l'index de coup 4. Au deuxième coup il y a 6 coups disponibles et nous prenons l'index de position 3 et au 3ème coup il y a 7 coups disponibles pour ce côté et il a choisi de choisir l'indice de coup 2.

Forme vectorielle; index = [4,3,2] n_moves = [5,6,7]

Nous encodons ces informations à l'envers, donc n = 4 + 5 * (3 + 6 * (2)) = 79 (pas de multiplication par 7 nécessaire)

Comment débloquer cela? Nous avons d'abord une position et nous découvrons qu'il y a 5 mouvements disponibles. Alors

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

Nous prenons l'indice de mouvement 4 et examinons à nouveau la position et à partir de ce point, nous découvrons qu'il y a 6 mouvements possibles.

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

Et nous prenons l'indice de mouvement 3 qui nous amène à une position avec 7 mouvements possibles.

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

Nous faisons le dernier mouvement index 2 et nous atteignons la position finale.

Comme vous pouvez le voir, la complexité temporelle est O(n) ansd la complexité de l’espace est O (n). Edit: la complexité temporelle est en fait O (n ^ 2) car le nombre que vous multipliez par augmente, mais il ne devrait pas y avoir de problème pour stocker jusqu'à 10 000 coups.


position de sauvegarde

Peut être fait près de l'optimum.

Lorsque nous découvrons des informations et stockons des informations, laissez-moi en parler davantage. L'idée générale est de diminuer la redondance (j'en parlerai plus tard). Supposons qu'il n'y ait eu aucune promotion et aucune prise, il y a donc 8 pions, 2 tours, 2 chevaliers, 2 évêques 1 roi et 1 reine par côté.

Que devons-nous épargner: 1. position de chaque paix 2. possibilités de roque 3. possibilités d'en-passant 4. côté qui se déplace disponible

Supposons que chaque pièce puisse se tenir n'importe où mais pas 2 pièces au même endroit. Le nombre de façons dont 8 pions de même couleur peuvent être disposés à bord est C(64/8) (binomial) qui est de 32 bits, puis 2 tours 2R-> C (56/2), 2B -> C (54/2), 2N-> C (52/2), 1Q-> C (50/1), 1K -> C(49/1) et les mêmes pour un autre site mais commençant par 8P -> C(48/8) et ainsi de suite.

En multipliant cela ensemble pour les deux sites, nous obtenons le numéro 4634726695587809641192045982323285670400000 qui est d'environ 142 bits, nous devons ajouter 8 pour un en-passant possible (le pion en-passant peut être dans l'un des 8 endroits), 16 (4 bits) pour les limitations de roque et un bit pour le site qui a bougé. On se retrouve avec 142 + 3 + 4 + 1 = 150bits

Mais maintenant, allons à la recherche de redondance sur la carte avec 32 pièces et aucune prise.

  1. les pions noirs et blancs sont sur la même colonne et se font face. Chaque pion fait face à un autre pion, ce qui signifie que le pion blanc peut être au plus au 6e rang. Cela nous apporte 8 * C (6/2) au lieu de C (64/8) * C (48/8) qui diminuent les informations de 56 bits.

  2. possibilité de roque est également redondante. Si les tours ne sont pas au point de départ, il n'y a aucune possibilité de roquer avec cette tour. Nous pouvons donc imaginaly ajouter 4 carrés à bord pour obtenir les informations supplémentaires si le roque avec cette tour est possible et supprimer 4 bits de roque. Donc, au lieu de C (56/2) * C (40/2) * 16, nous avons C (58/2) * C (42/2) et nous avons perdu 3,76 bits (presque tous les 4 bits)

  3. en-passant: Lorsque nous stockons l'une des 8 possibilités en passant, nous connaissons la position du pion noir et réduisons la redondance informationnelle (s'il s'agit d'un mouvement blanc et a un 3ème pion en passant, cela signifie que le pion noir est sur c5 et que le pion blanc est soit c2, c3 ou c4) donc instancé de C(6/2) nous avons 3 et nous avons perdu 2,3 ​​bits. Nous diminuons la redondance si nous stockons également le nombre en-passant de côté duquel peut être fait (3 possibilités-> gauche, droite, les deux) et nous connaissons la possibilité de pion qui peut prendre en passant. (par exemple de l'exemple précédent en passant avec du noir sur c5 ce qui peut être à gauche, à droite ou les deux. est sur un site, nous avons 2 * 3 (3 pour stocker les psissibilites et 2 mouvements possibles pour le pion noir au 7e ou 6e rang) insted C(6/2) et nous réduisons de 1,3 bits et si des deux côtés, nous réduisons de 4,2 bits. De cette façon, nous pouvons réduire de 2,3 + 1,3 = 3,6 bits par passant.

  4. évêques: les bisops ne peuvent être que sur des carrés d'opostites, ce qui réduit la redondance de 1 bit pour chaque site.

Si nous résumons, nous avons besoin de 150-56-4-3.6-2 = 85 bits pour stocker la position d'échecs s'il n'y avait pas de gains

Et probablement pas beaucoup plus s'il y a des recettes et des promotions prises en compte (mais j'écrirai à ce sujet plus tard si quelqu'un trouvera ce long post utile)

4
Luka Rahne

Cela ajouterait de l'intérêt à optimiser la taille du cas moyen pour les jeux typiques joués par les humains, au lieu du pire des cas. (L'énoncé du problème ne dit pas lequel; la plupart des réponses supposent le pire des cas.)

Pour la séquence de mouvements, un bon moteur d'échecs génère des mouvements à partir de chaque position; il produira une liste de k coups possibles, classés par ordre de classement de leur qualité. Les gens choisissent généralement les bons coups plus souvent que les coups aléatoires, nous devons donc apprendre un mappage de chaque position de la liste à la probabilité que les gens choisissent un coup qui est "bon". En utilisant ces probabilités (basées sur un corpus de jeux provenant d'une base de données d'échecs Internet), encodez les coups avec codage arithmétique . (Le décodeur doit utiliser le même moteur d'échecs et la même cartographie.)

Pour la position de départ, l'approche de ralu fonctionnerait. Nous pourrions également l'affiner avec un codage arithmétique, si nous avions un moyen de pondérer les choix par probabilité - par exemple les pièces apparaissent souvent dans des configurations se défendant, pas au hasard. Il est plus difficile de trouver un moyen facile d'intégrer ces connaissances. Une idée: se replier sur l'encodage de déplacement ci-dessus à la place, à partir de la position d'ouverture standard et trouver une séquence qui se termine dans le tableau souhaité. (Vous pouvez essayer une recherche A * avec une distance heuristique égale à la somme des distances des pièces par rapport à leur position finale, ou quelque chose du genre.) connaissance. (Vous pouvez récupérer une partie de l'inefficacité en éliminant les choix de mouvement qui conduiraient à une position précédemment explorée dans la recherche A *: ceux-ci peuvent obtenir le poids 0 dans le code arithmétique.)

Il est également assez difficile d'estimer les économies que cela vous permettrait de gagner en complexité moyenne, sans collecter des statistiques à partir d'un corpus réel. Mais le point de départ avec tous les mouvements également probables, je pense, battrait déjà la plupart des propositions ici: le codage arithmétique n'a pas besoin d'un nombre entier de bits par mouvement.

4
Darius Bacon

J'ai vu cette question hier soir et cela m'a intrigué alors je me suis assis au lit à réfléchir à des solutions. Ma réponse finale est assez similaire aux int3 en fait.

Solution basique

En supposant une partie d'échecs standard et que vous n'encodiez pas les règles (comme les blancs sont toujours les premiers), vous pouvez économiser beaucoup en encodant uniquement les mouvements de chaque pièce.

Il y a 32 pièces au total, mais à chaque mouvement, vous savez de quelle couleur se déplace, il n'y a donc que 16 carrés à vous soucier, ce qui est 4 bits pour quelle pièce se déplace ce tour-ci.

Chaque pièce n'a qu'un moveet limité, que vous pourriez énumérer d'une manière ou d'une autre.

  • Pion: 4 options, 2 bits (1 pas en avant, 2 pas en avant, 1 en diagonale)
  • Tour: 14 options, 4 bits (max 7 dans chaque direction)
  • Bishop: 13 options, 4 bits (si vous en avez 7 dans une diagonale, vous n'en avez que 6 dans l'autre)
  • Chevalier: 8 options, bits
  • Reine: 27 options, 5 bits (tour + évêque)
  • Roi: 9 options, 4 bits (8 mouvements en une étape, plus l'option de roque)

Pour la promotion, il y a 4 pièces au choix (tour, évêque, chevalier, reine), alors ce mouvement, nous ajouterions 2 bits pour le spécifier. Je pense que toutes les autres règles sont couvertes automatiquement (par exemple en passant).

Optimisations supplémentaires

Tout d'abord, après avoir capturé 8 morceaux d'une seule couleur, vous pouvez réduire l'encodage des morceaux à 3 bits, puis 2 bits pour 4 morceaux, etc.

Cependant, l'optimisation principale consiste à énumérer uniquement les mouvements possibles à chaque point du jeu. Supposons que nous stockons les mouvements d'un pion sous la forme {00, 01, 10, 11} pour 1 pas en avant, 2 pas en avant, diagonale gauche et diagonale droite respectivement. Si certains mouvements ne sont pas possibles, nous pouvons les supprimer de l'encodage pour ce tour.

Nous connaissons l'état du jeu à chaque étape (en suivant tous les mouvements), donc après avoir lu quelle pièce va se déplacer, nous pouvons toujours déterminer combien de bits nous devons lire. Si nous réalisons que les seuls mouvements d'un pion à ce stade sont capturés en diagonale à droite ou en avant, nous savons que lire 1 bit seulement.

En bref, le stockage de bits répertorié ci-dessus pour chaque pièce est un maximum uniquement. Presque chaque mouvement aura moins d'options et souvent moins de bits.

4
DisgruntledGoat

La position sur une carte peut être définie en 7 bits (0-63, et 1 valeur spécifiant qu'elle n'est plus sur la carte). Donc, pour chaque pièce du plateau, spécifiez où elle se trouve.

32 pièces * 7 bits = 224 bits

EDIT: comme Cadrian l'a souligné ... nous avons également le cas du pion promu à la reine. Je suggère que nous ajoutions des bits supplémentaires à la fin pour indiquer quel pion a été promu.

Donc pour chaque pion qui a été promu, nous suivons les 224 bits avec 5 bits qui indiquent l'index du pion qui a été promu, et 11111 s'il s'agit de la fin de la liste.

Donc, le cas minimal (sans promotion) est de 224 bits + 5 (sans promotion). Pour chaque pion promu, ajoutez 5 bits.

EDIT: Comme le souligne la grenouille hirsute, nous avons besoin d'un autre bit à la fin pour indiquer de qui il s'agit; ^)

3
Toad

La plupart des gens ont encodé l'état de la carte, mais en ce qui concerne les mouvements eux-mêmes .. Voici une description de l'encodage binaire.

Bits par pièce:

  • ID pièce: Max 4 bits pour identifier les 16 pièces par côté. Le blanc/noir peut être déduit. Faites définir une commande sur les pièces. Comme le nombre de pièces tombe en dessous des puissances respectives de deux, utilisez moins de bits pour décrire les pièces restantes.
  • Pion: 3 possibilités au premier coup, donc +2 bits (en avant d'un ou deux carrés, en passant). Les coups suivants ne permettent pas d'avancer de deux, donc +1 bit est suffisant. La promotion peut être déduite dans le processus de décodage en notant quand le pion a atteint le dernier rang. Si le pion est connu pour être promu, le décodeur attendra 2 autres bits indiquant à laquelle des 4 pièces principales il a été promu.
  • Évêque: +1 bit pour la diagonale utilisée, jusqu'à +4 bits pour la distance le long de la diagonale (16 possibilités). Le décodeur peut déduire la distance maximale possible que la pièce peut parcourir le long de cette diagonale, donc s'il s'agit d'une diagonale plus courte, utilisez moins de bits.
  • Chevalier: 8 coups possibles, +3 bits
  • Tour: +1 bit pour horizontal/vertical, +4 bits pour la distance le long de la ligne.
  • Roi: 8 coups possibles, +3 bits. Indiquez le roque avec un mouvement `` impossible '' - puisque le roque n'est possible que lorsque le roi est au premier rang, codez ce mouvement avec une instruction de déplacer le roi `` en arrière '' - c'est-à-dire hors du plateau.
  • Reine: 8 directions possibles, + 3bits. Jusqu'à +4 bits supplémentaires pour la distance le long de la ligne/diagonale (moins si la diagonale est plus courte, comme dans le cas de l'évêque)

En supposant que toutes les pièces sont sur le plateau, ce sont les bits par coup: Pion - 6 bits au premier coup, 5 par la suite. 7 si promu. Évêque: 9 bits (max), Chevalier: 7, Tour: 9, Roi: 7, Reine: 11 (max).

3
int3

Le problème est-il de donner un encodage le plus efficace pour les parties d'échecs typiques, ou celui qui a l'encodage le plus court dans le pire des cas?

Pour ces derniers, le moyen le plus efficace est aussi le plus opaque: créer une énumération de toutes les paires possibles (planche initiale, séquence légale de coups), qui, avec la position draw-on-trois fois répétée et pas plus que -fifty-moves depuis les dernières règles de déplacement-capture-pion, est récursif. L'index d'une position dans cette séquence finie donne alors le codage dans le pire des cas le plus court, mais aussi et aussi long dans les cas typiques, et est, je suppose, très coûteux à calculer. Le jeu d'échecs le plus long possible est censé être supérieur à 5000 coups, avec généralement 20 à 30 coups disponibles dans chaque position pour chaque joueur (bien que moins quand il reste peu de pièces) - cela donne quelque chose comme 40000 bits nécessaires pour cet encodage.

L'idée d'énumération peut être appliquée pour donner une solution plus souple, comme décrit dans la suggestion de Henk Holterman pour encoder les mouvements ci-dessus. Ma suggestion: pas minime, mais plus courte que les exemples ci-dessus que j'ai examinés et raisonnablement maniable:

  1. 64 bits pour représenter les carrés occupés (matrice d'occupation), ainsi que la liste des pièces dans chaque carré occupé (peut avoir 3 bits pour les pions et 4 bits pour les autres pièces): cela donne 190 bits pour la position de départ. Puisqu'il ne peut y avoir plus de 32 pièces à bord, le codage de la matrice d'occupation est redondant et donc quelque chose comme des positions de carte communes peut être codé, disons comme 33 bits de réglage plus l'index de carte de la liste des cartes communes.

  2. 1 bit pour dire qui fait le premier pas

  3. Mouvements de code selon la suggestion de Henk: généralement 10 bits par paire de mouvements blancs/noirs, bien que certains mouvements prennent 0 bits, lorsqu'un joueur n'a pas de mouvements alternatifs.

Cela suggère 490 bits pour coder un jeu typique à 30 coups, et serait une représentation raisonnablement efficace pour les jeux typiques.

Abouth codant la position de tirage trois fois répété et pas plus de cinquante coups depuis les dernières règles de déplacement ou de capture du pion: si vous codez le dernier mouvement revient au dernier mouvement ou capture du pion, alors vous avoir suffisamment d'informations pour décider si ces règles s'appliquent: pas besoin de l'historique complet du jeu.

3
Charles Stewart

Si le temps de calcul n'est pas un problème, vous pouvez utiliser un générateur de position déterministe possible pour attribuer des identifiants uniques à une position donnée.

À partir d'une position donnée, générez d'abord un nombre de positions possibles dans un manoir déterministe, par ex. commençant en bas à gauche et se déplaçant en haut à droite. Cela détermine le nombre de bits dont vous aurez besoin pour le prochain mouvement, dans certaines situations, cela pourrait être aussi petit qu'un. Ensuite, lorsque le déplacement est effectué, stockez uniquement l'ID unique de ce déplacement.

La promotion et les autres règles comptent simplement comme des mouvements valides tant qu'elles sont traitées de manière déterministe, par ex. à la reine, à la tour, à l'évêque, chacun compte comme un mouvement distinct.

La position initiale est la plus difficile et pourrait générer environ 250 millions de positions possibles (je pense) qui nécessiteraient environ 28 bits plus un bit supplémentaire pour déterminer de quel mouvement il s'agit.

En supposant que nous savons qui est son tour (chaque tour passe du blanc au noir), le générateur déterministe ressemblerait à quelque chose comme:

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

'obtenir la liste des mouvements possibles' ferait quelque chose comme:

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.

Si le roi est en échec, alors chaque "liste des mouvements xxx possibles" ne renvoie que des mouvements valides qui changent la situation de contrôle.

2
snowdude

Tout comme ils encodent des jeux sur des livres et des papiers: chaque pièce a un symbole; comme c'est un jeu "légal", les blancs se déplacent en premier - pas besoin d'encoder le blanc ou le noir séparément, il suffit de compter le nombre de coups pour déterminer qui a bougé. De plus, chaque mouvement est codé comme (pièce, position de fin) où la 'position de fin' est réduite au moindre nombre de symboles qui permet de discerner les ambiguïtés (peut être zéro). La durée du jeu détermine le nombre de coups. On peut également encoder le temps en minutes (depuis le dernier coup) à chaque pas.

L'encodage de la pièce peut être effectué soit en attribuant un symbole à chacun (32 au total), soit en attribuant un symbole à la classe, et en utilisant la position de fin pour comprendre laquelle de la pièce a été déplacée. Par exemple, un pion a 6 positions de fin possibles; mais en moyenne, seul un couple est disponible à chaque tour. Donc, statistiquement, le codage par position de fin peut être le meilleur pour ce scénario.

Des codages similaires sont utilisés pour les trains de pointes en neuroscience computationnelle (AER).

Inconvénients: vous devez rejouer tout le jeu pour obtenir l'état actuel et générer un sous-ensemble, un peu comme parcourir une liste chaînée.

2
lorenzog

J'essaierais d'utiliser encodage Huffman . La théorie derrière cela est la suivante: dans chaque partie d'échecs, il y aura des pièces qui bougeront beaucoup et d'autres qui ne bougeront pas beaucoup ou qui seront éliminées tôt. Si la position de départ a déjà des morceaux supprimés - tant mieux. Il en va de même pour les carrés - certains carrés voient toutes les actions, tandis que d'autres ne sont pas beaucoup touchés.

J'aurais donc deux tables Huffman - une pour les pièces, l'autre pour les carrés. Ils seront générés en regardant le jeu réel. Je pourrais avoir une grande table pour chaque paire pièce-carré, mais je pense que ce serait assez inefficace car il n'y a pas beaucoup d'exemples de la même pièce se déplaçant à nouveau sur le même carré.

Chaque pièce aurait un ID attribué. Puisqu'il y a 32 pièces différentes, je n'aurais besoin que de 5 bits pour l'ID de pièce. Les identifiants des pièces ne changent pas d'un jeu à l'autre. Il en va de même pour les identifiants carrés, pour lesquels j'aurais besoin de 6 bits.

Les arbres de Huffman seraient encodés en écrivant chaque nœud pendant qu'ils sont parcourus dans l'ordre (c'est-à-dire, d'abord le nœud est sorti, puis ses enfants de gauche à droite). Pour chaque nœud, il y aura un bit spécifiant s'il s'agit d'un nœud feuille ou d'un nœud branche. S'il s'agit d'un nœud feuille, il sera suivi des bits donnant l'ID.

La position de départ sera simplement donnée par une série de paires emplacement-pièce. Après cela, il y aura une paire de pièces pour chaque mouvement. Vous pouvez trouver la fin du descripteur de position de départ (et le début du descripteur de mouvements) simplement en trouvant la première pièce mentionnée deux fois. Dans le cas où un pion est promu, il y aura 2 bits supplémentaires spécifiant ce qu'il devient, mais l'ID de la pièce ne changera pas.

Pour tenir compte de la possibilité qu'un pion soit promu au début du jeu, il y aura également une "table de promotion" entre les arbres de huffman et les données. Au début, il y aura 4 bits spécifiant le nombre de pions mis à niveau. Ensuite, pour chaque pion, il y aura son ID codé par Huffman et 2 bits spécifiant ce qu'il est devenu.

Les arbres de huffman seront générés en tenant compte de toutes les données (à la fois la position de départ et les mouvements) et la table de promotion. Bien que normalement la table de promotion soit vide ou contienne seulement quelques entrées.

Pour résumer graphiquement:

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

Ajouté: Cela pourrait encore être optimisé. Chaque pièce n'a que quelques mouvements légaux. Au lieu de simplement encoder le carré cible, on pourrait donner des ID basés sur 0 pour les mouvements possibles de chaque pièce. Les mêmes identifiants seraient réutilisés pour chaque pièce, donc au total il n'y aurait pas plus de 21 identifiants différents (la reine peut avoir au maximum 21 options de déplacement possibles). Mettez cela dans une table de Huffman au lieu des champs.

Cela présenterait cependant une difficulté à représenter l'état d'origine. On pourrait générer une série de mouvements pour mettre chaque pièce à sa place. Dans ce cas, il faudrait en quelque sorte marquer la fin de l'état initial et le début des mouvements.

Ils peuvent également être placés à l'aide d'ID carrés non compressés de 6 bits.

Si cela présenterait une diminution globale de la taille - je ne sais pas. Probablement, mais devrait expérimenter un peu.

Ajouté 2: Encore un cas spécial. Si l'état du jeu n'a AUCUN coup, il devient important de distinguer qui bouge ensuite. Ajoutez un peu plus à la fin pour cela. :)

2
Vilx-

Il y a 64 positions de carte possibles, vous avez donc besoin de 6 bits par position. Il y a 32 pièces initiales, donc nous avons jusqu'à présent 192 bits au total, où tous les 6 bits indiquent la position de la pièce donnée. Nous pouvons prédéterminer l'ordre dans lequel les pièces apparaissent, nous n'avons donc pas à dire lequel est lequel.

Et si un morceau est hors du plateau? Eh bien, nous pouvons placer un morceau au même endroit qu'un autre morceau pour indiquer qu'il est hors du plateau, car ce serait illégal sinon. Mais nous ne savons pas non plus si la première pièce sera sur le plateau ou non. Nous ajoutons donc 5 bits indiquant quelle pièce est la première (32 possibilités = 5 bits pour représenter la première pièce). Ensuite, nous pouvons utiliser cet endroit pour les pièces suivantes qui sont hors du plateau. Cela nous amène à 197 bits au total. Il doit y avoir au moins une pièce sur le tableau, donc cela fonctionnera.

Ensuite, nous avons besoin d'un bit pour le tour duquel il est - nous amène à 198 bits.

Et la promotion des pions? Nous pouvons le faire mal en ajoutant 3 bits par pion, en ajoutant 42 bits. Mais alors nous pouvons remarquer que la plupart du temps, les pions ne sont pas promus.

Donc, pour chaque pion qui est sur le plateau, le bit '0' indique qu'il n'est pas promu. Si un pion n'est pas sur le plateau, nous n'en avons pas besoin du tout. Ensuite, nous pouvons utiliser des chaînes de bits de longueur variable pour quelle promotion il a. Le plus souvent, ce sera une reine, donc "10" peut signifier QUEEN. "110" signifie tour, "1110" signifie évêque et "1111" signifie chevalier.

L'état initial prendra 198 + 16 = 214 bits, puisque les 16 pions sont sur la carte et non promus. Une fin de partie avec deux reines pion promues pourrait prendre quelque chose comme 198 + 4 + 4, ce qui signifie 4 pions vivants et non promus et 2 pions reine, pour 206 bits total. Semble assez robuste!

===

L'encodage Huffman, comme d'autres l'ont souligné, serait la prochaine étape. Si vous observez quelques millions de jeux, vous remarquerez que chaque pièce est beaucoup plus susceptible d'être sur certains carrés. Par exemple, la plupart du temps, les pions restent en ligne droite, ou un à gauche/un à droite. Le roi reste généralement autour de la base d'attache.

Par conséquent, concevez un schéma de codage Huffman pour chaque position distincte. Les pions ne prendront probablement qu'en moyenne 3-4 bits au lieu de 6. Le roi devrait également prendre quelques bits.

Toujours dans ce schéma, incluez "pris" comme position possible. Cela peut également gérer le roque de manière très robuste - chaque tour et chaque roi auront un état "position d'origine, déplacé" supplémentaire. Vous pouvez également encoder en passant dans les pions de cette façon - "position d'origine, pouvez en passant".

Avec suffisamment de données, cette approche devrait donner de très bons résultats.

2
Claudiu

La plupart des réponses ont négligé la répétition 3 fois. malheureusement pour la répétition 3 fois vous devez mémoriser toutes les positions jouées jusqu'ici ...

La question nous obligeait à stocker de manière économe en espace, donc nous n'avons vraiment pas besoin de stocker la position tant que nous pouvons la construire à partir de la liste des mouvements (à condition que nous ayons une position de départ standard). Nous pouvons optimiser PGN et c'est tout. Ci-dessous est un schéma simple.

Il y a 64 cases sur le plateau, 64 = 2 ^ 6. Si nous stockons uniquement les cases initiale et finale de chaque mouvement, cela prendrait 12 bits (la promotion sera abordée plus tard). Notez que ce schéma couvre déjà le joueur à déplacer, l'emphassant, la pièce capturée, le roque, etc. à partir de ceux-ci peuvent être construits à partir de la simple lecture de la liste des coups.

pour la promotion, nous pouvons garder un tableau séparé de vecteurs qui diraient "au mouvement N promouvoir en Piece XYZ". on peut garder un vecteur de (int, octet).

Il est également tentant d'optimiser le vecteur (To, From), car beaucoup de ces vecteurs (To, From) ne sont pas possibles aux échecs. par exemple. il n'y aura pas de passage de e1 à d8, etc. Mais je n'ai pas pu trouver de plan. Toute autre idée est la bienvenue.

2
Umair Ahmed

[édité après avoir lu la question correctement] Si vous supposez que chaque position légale peut être atteinte à partir de la position initiale (qui est une définition possible de "légal"), alors toute position peut être exprimée comme la séquence de mouvements depuis le début. Un extrait de jeu à partir d'une position non standard peut être exprimé comme la séquence de mouvements nécessaires pour atteindre le départ, un interrupteur pour allumer la caméra, suivi de mouvements ultérieurs.

Appelons donc l'état initial de la carte le bit unique "0".

Les mouvements de n'importe quelle position peuvent être énumérés en numérotant les carrés et en ordonnant les mouvements par (début, fin), avec le saut conventionnel de 2 carrés indiquant le roque. Il n'est pas nécessaire d'encoder les mouvements illégaux, car la position du plateau et les règles sont toujours déjà connues. Le drapeau pour allumer la caméra peut être exprimé comme un mouvement spécial dans la bande, ou plus judicieusement comme un numéro de mouvement hors bande.

Il y a 24 mouvements d'ouverture pour chaque côté, qui peuvent tenir dans 5 bits chacun. Les mouvements ultérieurs peuvent nécessiter plus ou moins de bits, mais les mouvements légaux sont toujours énumérables, de sorte que la largeur de chaque mouvement peut heureusement augmenter ou s'étendre. Je n'ai pas calculé, mais j'imagine que les positions 7 bits seraient rares.

En utilisant ce système, un jeu de 100 demi-coups pourrait être encodé en environ 500 bits. Cependant, il pourrait être judicieux d'utiliser un livre d'ouverture. Supposons qu'il contienne un million de séquences. Supposons ensuite qu'un 0 initial indique un début à partir de la carte standard et un 1 suivi d'un nombre de 20 bits indique un début à partir de cette séquence d'ouverture. Les jeux avec des ouvertures quelque peu conventionnelles pourraient être raccourcis, disons de 20 demi-coups, ou 100 bits.

Ce n'est pas la plus grande compression possible, mais (sans le livre d'ouverture), il serait très facile à mettre en œuvre si vous avez déjà un modèle d'échecs, ce que la question suppose.

Pour compresser davantage, vous voudriez ordonner les mouvements selon la probabilité plutôt que dans un ordre arbitraire, et encoder les séquences probables en moins de bits (en utilisant par exemple des jetons Huffman comme les gens l'ont mentionné).

2
Douglas Bagnall

J'utiliserais un encodage de longueur de course. Certaines pièces sont uniques (ou n'existent que deux fois), je peux donc omettre la longueur après. Comme cletus, j'ai besoin de 13 états uniques, donc je peux utiliser un quartet (4 bits) pour encoder la pièce. Le tableau initial ressemblerait alors à ceci:

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

ce qui me laisse avec 8 + 2 + 4 + 2 + 8 quartets = 24 quartets = 96 bits. Je ne peux pas coder 16 avec un quartet mais comme "Empty, 0" n'a pas de sens, je peux traiter "0" comme "16".

Si le plateau est vide mais pour un seul pion dans le coin supérieur gauche, j'obtiens "Pion, 1, Vide, 16, Vide, 16, Vide 16, Vide, 15" = 10 grignotages = 40 bits.

Le pire des cas, c'est quand j'ai un carré vide entre chaque pièce. Mais pour l'encodage de la pièce, j'ai juste besoin de 13 valeurs sur 16, donc je peux peut-être en utiliser une autre pour dire "Empty1". Ensuite, j'ai besoin de 64 nibbles == 128bits.

Pour les mouvements, j'ai besoin de 3 bits pour la pièce (la couleur est donnée par le fait que le blanc bouge toujours en premier) plus 5 bits (0..63) pour la nouvelle position = un octet par mouvement. La plupart du temps, je n'ai pas besoin de l'ancienne position car une seule pièce sera à portée. Pour les cas étranges, je dois utiliser le code unique non utilisé (j'ai juste besoin de 7 codes pour coder la pièce), puis 5 bits pour l'ancien et 5 bits pour la nouvelle position.

Cela me permet d'encoder le roque en 13 morsures (je peux déplacer le Roi vers la Tour ce qui suffit pour dire ce que j'ai l'intention).

[EDIT] Si vous autorisez un encodeur intelligent, j'ai besoin de 0 bits pour la configuration initiale (car il n'a pas besoin d'être encodé de quelque façon que ce soit: il est statique) plus un octet par mouvement.

[EDIT2] Ce qui laisse la transformation du pion. Si un pion atteint la dernière ligne, je peux le déplacer pour dire "transforme" puis ajouter les 3 bits pour la pièce avec laquelle il est remplacé (vous n'avez pas besoin d'utiliser une reine; vous pouvez remplacer le pion avec n'importe quoi mais le roi).

2
Aaron Digulla

J'y ai pensé depuis longtemps (+ - 2 heures). Et il n'y a pas de réponses évidentes.

En supposant:

  1. Ignorer l'état temporel (Un joueur qui n'avait pas l'habitude d'avoir une limite de temps pouvait donc forcer un match nul en ne jouant pas)
  2. Quand le jeu a-t-il été joué?!? C'est important parce que les règles ont changé au fil du temps (donc supposera un jeu moderne au point suivant un jeu moderne ...) Veuillez vous référer à la règle du pion mort par exemple (wikipedia a un problème très célèbre le montrant), et si vous voulez pour remonter le temps, bonne chance, l'évêque ne se déplaçait que lentement et les dés étaient utilisés. lol.

... il est donc à jour des règles modernes. Tout d'abord indépendamment de la répétition et déplacer la limite de répétition.

-C 25 octets arrondis (64b + 32 * 4b + 5b = 325b)

= 64 bits (quelque chose/rien) + 32 * 4 bits [1bit = couleur {noir/blanc} + 3bit = type de pièce {King, Queen, Bishop, kNight, Rook, Pawn, MovedPawn} NB: Moved pawn ... Par exemple, si c'était le dernier pion déplacé au tour précédent, indiquant qu'un "en passant" est faisable. ] + 5bit pour l'état actuel (qui est au tour, en passant, possibilité de rooker ou non de chaque côté)

Jusqu'ici tout va bien. Peut probablement être amélioré, mais il y aurait alors une durée et une promotion variables à prendre en considération!?

Maintenant, les règles suivantes ne sont applicables que lorsqu'un joueur postule pour un match nul, IT IS NON automatique! Donc, considérez que ces 90 mouvements sans capture ou qu'un mouvement de pion est possible si aucun joueur n'appelle pour un match nul) ! Cela signifie que tous les mouvements doivent être enregistrés ... et disponibles.

-D répétition de la position ... par exemple état du plateau tel que mentionné ci-dessus (voir C) ou non ... (voir ci-dessous concernant les règles de la FIDE) -E Cela laisse le problème complexe de l'allocation de 50 mouvements sans capture ni déplacement de pion là un compteur est nécessaire ... Cependant.

Alors, comment gérez-vous cela? ... Eh bien, vraiment, il n'y a aucun moyen. Parce qu'aucun des joueurs ne veut dessiner ou se rendre compte que cela s'est produit. Maintenant, au cas où E un compteur pourrait suffire ... mais voici l'astuce et même la lecture des règles FIDE (http://www.fide.com/component/handbook/?id=124&view=article) Je ne trouve pas de réponse ... qu'en est-il de la perte de capacité de tour. Est-ce une répétition? Je pense que non mais alors c'est un sujet flou non abordé, pas clarifié.

Voici donc deux règles qui sont deux complexes ou indéfinies même pour tenter de coder ... A bientôt.

Donc, la seule façon de vraiment encoder un jeu est de tout enregistrer depuis le début ... ce qui entre alors en conflit (ou pas?) Avec la question de "l'état du plateau".

J'espère que cette aide ... pas trop de mathématiques :-) Juste pour montrer que certaines questions ne sont pas aussi faciles, trop ouvertes pour que l'interprétation ou la pré-connaissance soit correcte et efficace. Pas un que je considérerais pour un entretien car il ouvre trop de boîte de ver.

2
sylvain.bouche

Amélioration possible de la position de départ dans la solution de Yacoby

Aucune position légale n'a plus de 16 pièces de chaque couleur. Le nombre de façons de placer jusqu'à 16 pièces noires et 16 pièces blanches sur 64 carrés est d'environ 3,63e27. Log2 (3,63e27) = 91,55. Cela signifie que vous pouvez encoder la position et la couleur de toutes les pièces en 92 bits. C'est moins que les 64 bits pour la position + jusqu'à 32 bits pour la couleur que la solution de Yacoby requiert. Vous pouvez enregistrer 4 bits dans le pire des cas au détriment d'une complexité considérable dans le codage.

D'un autre côté, cela augmente la taille des positions avec 5 pièces ou plus manquantes. Ces positions ne représentent que <4% de toutes les positions, mais il s'agit probablement de la majorité des cas où vous souhaitez enregistrer une position de départ différente de la position initiale.

Cela conduit à la solution complète

  1. Encodez la position et la couleur des pièces selon la méthode ci-dessus. 92 bits.
  2. Pour spécifier le type de chaque pièce, utilisez un code Huffman: pion: '0', tour: '100', chevalier: '101', évêque: '110', reine: '1110', roi: '1111'. Cela nécessite (16 * 1 + 12 * 3 + 4 * 4) = 68 bits pour un ensemble complet de pièces. La position de la carte complète peut être encodée en 92 + 68 = 160 bits maximum.
  3. Un état de jeu supplémentaire devrait être ajouté: tour: 1 bit, quel roque est possible: 4 bits, "en passant" possible: jusqu'à 4 bits (1 bit indique que c'est le cas et 3 bits indiquent lequel). La position de départ est codée en = 160 + 9 = 169 bits
  4. Pour la liste des coups, énumérez tous les coups possibles pour une position donnée et stockez la position du coup dans la liste. La liste des mouvements comprend tous les cas spéciaux (roque, en passant et démissionnaire). Utilisez uniquement autant de bits que nécessaire pour stocker la position la plus élevée. En moyenne, il ne doit pas dépasser 7 bits par coup (16 pièces possibles et 8 coups légaux par pièce en moyenne). Dans certains cas, lorsqu'un déplacement est forcé, il ne nécessite qu'un bit (déplacement ou démission).
2
Florian F

Il y a 32 pièces sur le plateau. Chaque pièce a une position (une sur 64 cases). Il vous suffit donc de 32 entiers positifs.

Je sais que 64 positions tiennent en 6 bits mais je ne ferais pas ça. Je garderais les derniers bits pour quelques drapeaux (pièce lâchée, pion reine)

1
cadrian

L'algorithme doit énumérer de manière déterministe toutes les destinations possibles à chaque déplacement. Nombre de destinations:

  • 2 évêques, 13 destinations chacun = 26
  • 2 tours, 14 destinations chacune = 28
  • 2 chevaliers, 8 destinations chacun = 16
  • reine, 27 destinations
  • roi, 8 destinations

8 pattes pourraient toutes devenir des reines dans le pire des cas (en termes d'énumération), faisant ainsi le plus grand nombre de destinations possibles 9 * 27 + 26 + 28 + 16 + 8 = 321. Ainsi, toutes les destinations pour n'importe quel déplacement peuvent être énumérées par un nombre de 9 bits.

Le nombre maximum de coups des deux parties est de 100 (si je ne me trompe pas, pas un joueur d'échecs). Ainsi, n'importe quel jeu pourrait être enregistré en 900 bits. De plus, la position initiale de chaque pièce peut être enregistrée en utilisant des nombres de 6 bits, ce qui donne 32 * 6 = 192 bits. Plus un bit pour le record "qui bouge le premier". Ainsi, tout jeu peut être enregistré en utilisant 900 + 192 + 1 = 1093 bits.

1
zufar

cletus 'la réponse est bonne, mais il a aussi oublié de coder à qui c'est le tour. Il fait partie de l'état actuel et est nécessaire si vous utilisez cet état pour piloter un algorithme de recherche (comme un dérivé alpha-bêta).

Je ne suis pas un joueur d'échecs, mais je pense qu'il y a aussi un autre cas de coin: combien de coups ont été répétés. Une fois que chaque joueur fait le même coup trois fois, le jeu est nul, non? Si c'est le cas, vous devez enregistrer ces informations dans l'état car après la troisième répétition, l'état est maintenant terminal.

1
Shaggy Frog

Comme plusieurs autres l'ont mentionné, vous pouvez pour chacune des 32 pièces vous pouvez stocker sur quelle case elles se trouvent et si elles sont sur la carte ou non, cela donne 32 * (log2 (64) + 1) = 224 bits.

Cependant, les évêques ne peuvent occuper que les carrés noirs ou blancs, donc pour ceux-ci, vous n'avez besoin que de log2 (32) bits pour la position, ce qui donne 28 * 7 + 4 * 6 = 220 bits.

Et comme les pions ne démarrent pas à l'arrière et ne peuvent avancer que sur 56, il devrait être possible d'utiliser cette limitation pour réduire le nombre de bits nécessaires pour les pions.

1
Andreas Brinck

Enregistrement de l'état de la carte

La façon la plus simple à laquelle j'ai pensé est trop d'abord d'avoir un tableau de 8 * 8 bits représentant l'emplacement de chaque pièce (donc 1 s'il y a une pièce d'échecs et 0 s'il n'y en a pas). Comme il s'agit d'une longueur fixe, nous n'avons pas besoin de terminateur.

Représentez ensuite chaque pièce d'échecs dans l'ordre de son emplacement. En utilisant 4 bits par pièce, cela prend 32 * 4 bits (128 au total). Ce qui est vraiment vraiment un gaspillage.

En utilisant un arbre binaire, nous pouvons représenter un pion dans un octet, un chevalier et une tour et un évêque dans 3 et un roi et une reine dans 4. Comme nous devons également stocker la couleur de la pièce, ce qui prend un octet supplémentaire, il finit par comme (pardonnez-moi si c'est faux, je n'ai jamais regardé codage Huffman en détail auparavant):

  • Pion: 2
  • Tour: 4
  • Chevalier: 4
  • Évêque: 4
  • Roi: 5
  • Reine: 5

Compte tenu des totaux:

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

Qui bat en utilisant un ensemble de bits de taille fixe par 28 bits.

Donc, la meilleure méthode que j'ai trouvée est de le stocker dans un 82 + Tableau 100 bits

8*8 + 100 = 164



Stockage des mouvements
La première chose que nous devons savoir, c'est quelle pièce se déplace vers où. Étant donné qu'il y a au maximum 32 pièces sur la carte et que nous savons ce que chaque pièce est, plutôt qu'un entier représentant le carré, nous pouvons avoir un entier représentant le décalage de la pièce, ce qui signifie que nous n'avons qu'à ajuster 32 valeurs possibles pour représenter un pièce.

Malheureusement, il existe diverses règles spéciales, comme le roque ou le renversement du roi et la formation d'une république (référence Terry Pratchett), donc avant de stocker la pièce à déplacer, nous avons besoin d'un seul bit indiquant s'il s'agit d'un mouvement spécial ou non.

Donc, pour chaque mouvement normal, nous avons un 1 + 5 = 6 morceaux. (1 type de bit, 5 bits pour la pièce)

Une fois le numéro de pièce décodé, nous connaissons le type de pièce et chaque pièce doit représenter son mouvement de la manière la plus efficace. Par exemple (si mes règles d'échecs sont à la hauteur), un pion a un total de 4 coups possibles (prendre à gauche, prendre à droite, avancer d'un, avancer de deux).
Donc, pour représenter un mouvement de pion, nous avons besoin de bits "6 + 2 = 8". (6 bits pour l'en-tête de déplacement initial, 2 bits pour quel déplacement)

Se déplacer pour la reine serait plus complexe, dans la mesure où il serait préférable d'avoir une direction (8 directions possibles, donc 3 bits) et un total de 8 carrés possibles vers lesquels se déplacer pour chaque direction (donc 3 autres bits). Donc, pour représenter le déplacement d'une reine, il faudrait 6 + 3 + 3 = 12 morceaux.

La dernière chose qui m'arrive, c'est que nous devons stocker les joueurs à qui il appartient. Cela devrait être un seul bit (blanc ou noir pour passer ensuite)



Format résultant
Donc le format de fichier ressemblerait à quelque chose comme ça

[64 bits] Emplacements initiaux des pièces
[100 bits max] Pièces initiales [1 bit] Tour du joueur
[n bits] Déplace

Où est un mouvement
[1 bit] Type de déplacement (spécial ou normal)
[n bits] Déplacer les détails

Si le mouvement est un mouvement normal, le détail du mouvement ressemble à quelque chose
[5 bits] pièce
[n bits] déplacement de pièce spécifique (généralement dans la plage de 2 à 6 bits]

Si c'est un coup spécial
Il doit avoir un type entier et ensuite toute information supplémentaire (comme s'il s'agit d'un roque). Je ne me souviens pas du nombre de coups spéciaux, il peut donc être OK juste pour indiquer qu'il s'agit d'un coup spécial (s'il n'y en a qu'un)

1
Yacoby

C'est ainsi que je coderais les étapes du jeu. Pour un jeu de 40 étapes, cela prendra environ 180 bits.

Tout d'abord, créez une liste de tous les choix à l'aide d'un moteur qui connaît toutes les règles d'échecs. À chaque étape, procédez comme suit:

  1. Énumérer toutes les pièces pouvant être déplacées (au début, les blancs peuvent déplacer 8 pions et 2 chevaliers, 10 au total).
  2. Stockez à la fois le nombre de choix possibles et le choix lui-même.
  3. Énumérer toutes les positions de mouvement possibles. (lorsque le pion a été choisi au début, vous pouvez déplacer 1 ou 2 champs vers l'avant, vous avez donc 2 choix possibles.
  4. Encore une fois, stockez le nombre de choix possibles et le choix lui-même.

Cela vous donnera une liste comme celle-ci:

[[10, 3], # choose white pawn at index #3
 [2, 0],  # move it one step forward
 [10, 2], # choose black pawn #2 
 [2, 1],  # move it two steps forward
 ...
]

Etc. Pour l'encoder, il vous suffit de stocker le choix, pas le nombre de coups possibles. Une façon de le stocker est de savoir combien de bits sont nécessaires pour chaque choix:

[[10, 3], # 10 choices => 4 bits
 [2, 0],  # 2 choices => 1 bit
 [10, 2], # 10 choices => 4 bits
 [2, 1],  # 2 choices => 1 bit
 ...
]

Totaux 4+1+4+1=10 bits pour les deux premiers mouvements. Mais quelques bits sont gaspillés, utiliser 4 bits pour 10 choix gaspille 6 choix possibles.

Il est possible de faire mieux: inverser la liste, et calculer un nombre en fonction des choix possibles et du choix fait:

n = 0         # last position
n = n*2 + 1   # from [2, 1]   n=1
n = n*10 + 2  # from [10, 2]  n=12
n = n*2 + 0   # from [2, 0]   n=24
n = n*10 + 3  # from [10, 3]  n=243

Maintenant, nous avons le nombre 243, binaire 11110011, qui code toutes les étapes ci-dessus en seulement 8 bits.

Pour décoder, on sait que la position d'ouverture initiale a 10 choix possibles. Calculer

n = 243
choice = n % 10  # we know there are 10 moveable pieces. => choice=3
n /= 10          # n=24
choice = n % 2   # we know 2 possible moves for selected pawn => choice=0
n /= 2           # n=12
choice = n % 10  # 10 moveable pieces for black player. => choice=2
n /= 10          # n=1
choice = n % 2   # 2 possible moves for pawn => choice=1
n /= 2           # n=0, finished decoding

L'encodage est extrêmement efficace, en particulier la phase finale car il n'y a pas beaucoup de choix possibles. De plus, quand il ne vous reste qu'un seul coup possible, vous n'avez pas besoin de stockage pour ce mouvement.

1
martinus

Chaque pièce peut être représentée par 4 bits (pion à roi, 6 types), noir/blanc = 12 valeurs

Chaque carré de la carte peut être représenté par 6 bits (x coord, y coord).

Les positions initiales nécessitent un maximum de 320 bits (32 pièces, 4 + 6 bits)

Chaque mouvement suivant peut être représenté par 16 bits (de position, à position, pièce).

Le roque nécessiterait 16 bits supplémentaires, car il s'agit d'un mouvement double.

Les pions queen peuvent être représentés par l'une des 4 valeurs de réserve sur 4 bits.

Sans faire le calcul en détail, cela commence à économiser de l'espace après le premier mouvement par rapport au stockage de 32 * 7 bits (tableau prédéfini de pièces) ou 64 * 4 bits (affectation prédéfinie de carrés)

Après 10 mouvements des deux côtés, l'espace maximum requis est de 640 bits

... mais là encore, si nous identifions chaque pièce de manière unique (5 bits) et ajoutons un sixième bit pour signaler les pions queened, alors nous n'avons besoin que de piece-id + to-position pour chaque mouvement. Cela change le calcul en ...

Positions initiales = max 384 bits (32 pièces, 6 + 6 bits) Chaque mouvement = 12 bits (position, id pièce)

Ensuite, après 10 mouvements de chaque côté, l'espace maximum requis est de 624 bits

1
Steve De Caux

Thomas a la bonne approche pour encoder la carte. Cependant, cela devrait être combiné avec l'approche de ralu pour stocker les mouvements. Faites une liste de tous les mouvements possibles, écrivez le nombre de bits nécessaires pour exprimer ce nombre. Puisque le décodeur effectue le même calcul, il sait combien sont possibles et peut savoir combien de bits à lire, aucun code de longueur n'est nécessaire.

Ainsi, nous obtenons 164 bits pour les pièces, 4 bits pour les informations de roque (en supposant que nous stockons un fragment d'un jeu, sinon il peut être reconstruit), 3 bits pour les informations d'admissibilité en passant - stockez simplement la colonne où le mouvement s'est produit ( Si en passant n'est pas possible, stockez une colonne là où ce n'est pas possible - de telles colonnes doivent exister) et 1 pour qui doit se déplacer.

Les déplacements prennent généralement 5 ou 6 bits mais peuvent varier de 1 à 8.

Un raccourci supplémentaire - si l'encodage commence par 12 1 bits (une situation invalide - même un fragment n'aura pas deux rois d'un côté), vous abandonnez le décodage, essuyez le tableau et configurez un nouveau jeu. Le bit suivant sera un bit de déplacement.

1
Loren Pechtel

Une carte a 64 carrés et peut être représentée par 64 bits indiquant si un carré est vide ou non. Nous n'avons besoin d'informations sur les pièces que si un carré a une pièce. Puisque le player + piece prend 4 bits (comme indiqué plus haut), nous pouvons obtenir l'état actuel en 64 + 4 * 32 = 192 bits. Ajoutez au tour en cours et vous avez 193 bits.

Cependant, nous devons également coder les mouvements légaux pour chaque pièce. Tout d'abord, nous calculons le nombre de mouvements légaux pour chaque pièce, et ajoutons autant de bits après l'identificateur de pièce d'un carré complet. J'ai calculé comme suit:

Pion: En avant, tournez d'abord deux en avant, en passant * 2, promotion = 7 bits. Vous pouvez combiner le premier tour en avant et la promotion en un seul bit car ils ne peuvent pas se produire à partir de la même position, vous avez donc 6. Tour: 7 carrés verticaux, 7 carrés horizontaux = 14 bits Chevalier: 8 carrés = 8 bits Évêque: 2 diagonales * 7 = 14 bits Queen: 7 verticaux, 7 horizontaux, 7 diagonaux, 7 diagonaux = 28 bits King: 8 carrés environnants

Cela signifie toujours que vous auriez besoin de mapper les carrés ciblés en fonction de la position actuelle, mais cela (devrait être) un calcul simple.

Étant donné que nous avons 16 pions, 4 tours/chevaliers/évêques et 2 reines/rois, cela fait 16 * 6 + 4 * 14 + 4 * 8 + 4 * 14 + 2 * 28 + 2 * 8 = 312 bits supplémentaires, apportant le total à 505 bits dans l'ensemble.

En ce qui concerne le nombre de bits requis par pièce pour d'éventuels déplacements, certaines optimisations pourraient y être ajoutées et le nombre de bits probablement réduit, j'ai simplement utilisé des nombres faciles à travailler. Par exemple, pour les pièces coulissantes, vous pouvez stocker la distance à laquelle elles pourraient se déplacer, mais cela nécessiterait des calculs supplémentaires.

Pour faire court: stockez uniquement des données supplémentaires (pièce, etc.) lorsqu'un carré est occupé, et stockez uniquement le nombre minimum de bits pour chaque pièce pour représenter ses mouvements légaux.

EDIT1: J'ai oublié le roque et la promotion du pion sur n'importe quelle pièce. Cela pourrait porter le total avec des positions explicites à 557 coups (3 bits de plus pour les pions, 2 pour les rois)

1
Cullen Walsh

Comme Robert G, j'aurais tendance à utiliser PGN car il est standard et peut être utilisé par une large gamme d'outils.

Si, cependant, je joue une IA d'échecs qui est sur une sonde spatiale lointaine, et donc chaque bit est précieux, c'est ce que je ferais pour les mouvements. Je reviendrai plus tard sur l'encodage de l'état initial.

Les mouvements n'ont pas besoin d'enregistrer l'état; le décodeur peut garder une trace de l'état ainsi que des mouvements légaux à un moment donné. Tout ce que les mouvements doivent enregistrer, c'est laquelle des différentes alternatives légales est choisie. Comme les joueurs alternent, un coup n'a pas besoin d'enregistrer la couleur du joueur. Puisqu'un joueur ne peut déplacer que ses propres pièces de couleur, le premier choix est la pièce qu'il déplace (je reviendrai sur une alternative qui utilise un autre choix plus tard). Avec au plus 16 pièces, cela nécessite au plus 4 bits. Lorsqu'un joueur perd des pièces, le nombre de choix diminue. De plus, un état de jeu particulier peut limiter le choix des pièces. Si un roi ne peut pas bouger sans se mettre en échec, le nombre de choix est réduit d'un. Si un roi est en échec, toute pièce qui ne peut pas mettre le roi hors de contrôle n'est pas un choix viable. Numérotez les pièces dans l'ordre principal commençant par a1 (h1 précède a2).

Une fois la pièce spécifiée, elle n'aura qu'un certain nombre de destinations légales. Le nombre exact dépend fortement de la disposition du plateau et de l'historique du jeu, mais nous pouvons déterminer certains maximums et valeurs attendues. Pour tous sauf le chevalier et pendant le roque, une pièce ne peut pas passer par une autre pièce. Ce sera une grande source de limites de mouvement, mais il est difficile à quantifier. Une pièce ne peut pas sortir du plateau, ce qui limitera également largement le nombre de destinations.

Nous codons la destination de la plupart des pièces en numérotant les carrés le long des lignes dans l'ordre suivant: W, NW, N, NE (le côté noir est N). Une ligne commence dans le carré le plus éloigné dans la direction donnée à laquelle il est légal de se déplacer et continue vers le. Pour un roi libre, la liste des mouvements est W, E, NW, SE, N, S, NE, SW. Pour le chevalier, nous commençons par 2W1N et continuons dans le sens des aiguilles d'une montre; la destination 0 est la première destination valide dans cet ordre.

  • Pions: Un pion immobile a 2 choix de destinations, nécessitant ainsi 1 bit. Si un pion peut en capturer un autre, normalement ou en passant (ce que le décodeur peut déterminer, car il garde la trace de son état), il a également 2 ou 3 choix de mouvements. En dehors de cela, un pion ne peut avoir qu'un seul choix, ne nécessitant aucun bit. Quand un pion est dans son 7e rang, nous plaçons également sur le choix de la promotion. Comme les pions sont généralement promus en reines, suivis des chevaliers, nous codons les choix comme suit:
    • reine: 0
    • chevalier: 10
    • évêque: 110
    • tour: 111
  • Bishop: au plus 13 destinations avec {d, e} {4,5} pour 4 bits.
  • Tour: au plus 14 destinations, 4 bits.
  • Chevaliers: au plus 8 destinations, 3 bits.
  • Rois: lorsque le roque est une option, le roi est de retour à S et ne peut pas se déplacer vers le bas; cela donne un total de 7 destinations. Le reste du temps, un roi a au maximum 8 coups, ce qui donne un maximum de 3 bits.
  • Reine: Identique aux choix pour l'évêque ou la tour, pour un total de 27 choix, soit 5 bits

Étant donné que le nombre de choix n'est pas toujours une puissance de deux, ce qui précède gaspille toujours des bits. Supposons que le nombre de choix soit C et le choix particulier est numéroté c, et n = ceil (lg ( C )) (le nombre de bits requis pour coder le choix). Nous utilisons ces valeurs autrement perdues en examinant le premier bit du choix suivant. Si c'est 0, ne faites rien. Si c'est 1 et c + C <2n, puis ajoutez C à c. Le décodage d'un nombre inverse ceci: si le reçu c> = C , soustrait C et définissez le premier bit du nombre suivant sur 1. Si c <2n- C , puis définissez le premier bit du nombre suivant sur 0. Si 2n- C <= c < C , puis ne fais rien. Appelez ce schéma "saturation".

Un autre type de choix potentiel qui pourrait raccourcir l'encodage est de choisir une pièce adverse à capturer. Cela augmente le nombre de choix pour la première partie d'un coup, choisir une pièce, pour au plus un bit supplémentaire (le nombre exact dépend du nombre de pièces que le joueur actuel peut déplacer). Ce choix est suivi d'un choix de pièce de capture, qui est probablement beaucoup plus petit que le nombre de coups pour l'une des pièces des joueurs donnés. Une pièce ne peut être attaquée que par une seule pièce de n'importe quelle direction cardinale plus les chevaliers pour un total d'au plus 10 pièces d'attaque; cela donne un total total de 9 bits pour un mouvement de capture, bien que je m'attende à 7 bits en moyenne. Cela serait particulièrement avantageux pour les captures par la reine, car il aura souvent plusieurs destinations légales.

Avec la saturation, le codage de capture n'offre probablement aucun avantage. Nous pourrions autoriser les deux options, en spécifiant dans l'état initial celles qui sont utilisées. Si la saturation n'est pas utilisée, l'encodage du jeu peut également utiliser des numéros de choix inutilisés ( C <= c <2n) pour modifier les options en cours de partie. À tout moment C est une puissance de deux, nous ne pouvions pas changer les options.

1
outis

Dans le cas de base du plateau initial plus les mouvements suivants, considérez ce qui suit.

Utilisez un programme d'échecs pour attribuer des probabilités à tous les coups possibles. Par exemple, 40% pour e2-e4 20% pour d2-d4, etc. Si certains mouvements sont légaux mais non pris en compte par ce programme, donnez-leur une faible probabilité. Utilisez le codage arithmétique pour enregistrer le choix effectué, qui sera un nombre compris entre 0 et 0,4 pour le premier mouvement, 0,4 et 0,6 pour le second, etc.

Faites de même pour l'autre côté. Par exemple, s'il y a 50% de chances que e7-e5 soit la réponse à e2-e4, alors le nombre codé sera compris entre 0 et 0,2. Répétez jusqu'à ce que le jeu soit terminé. Le résultat est une plage potentiellement très petite. Trouvez la fraction binaire avec la plus petite base qui correspond à cette plage. C'est du codage arithmétique.

C'est mieux que Huffman car il peut être considéré comme un codage de bits fractionné (plus certains à la fin du jeu pour arrondir à un bit entier).

Le résultat devrait être plus compact que Huffman, et il n'y a pas de cas particulier pour la promotion, en passant, le mouvement de la règle 50 et d'autres détails car ils sont gérés par le programme d'évaluation des échecs.

Pour rejouer, utilisez à nouveau le programme d'échecs pour évaluer le plateau et attribuer toutes les probabilités à chaque coup. Utilisez la valeur codée arithmétique pour déterminer quel mouvement a été réellement joué. Répétez jusqu'à ce que vous ayez terminé.

Si votre programme d'échecs est assez bon, vous pouvez obtenir une meilleure compression avec un encodeur à deux états, où les probabilités sont définies en fonction des mouvements pour le noir et le blanc. Dans le cas le plus extrême de quelque 200+ états, cela code l'ensemble complet de tous les jeux d'échecs possibles, et n'est donc pas réalisable.

C'est à peu près une façon différente de dire ce que Darius a déjà écrit, seulement avec un petit exemple de la façon dont le codage arithmétique pourrait fonctionner, et un exemple réel d'utilisation d'un programme d'échecs existant pour aider à évaluer la probabilité des prochains coups.

1
Andrew Dalke