程序员难题:在整个游戏中对棋盘状态进行编码


95

严格来说不是一个问题,更像是一个难题...

多年来,我参与了一些新员工的技术面试。除了询问标准的“您是否知道X技术”问题之外,我还尝试了解它们如何解决问题。通常,我会在面试的前一天通过电子邮件将问题发送给他们,并希望他们在第二天提出解决方案。

通常,结果会很有趣-错误,但是很有趣-如果该人可以解释为什么采用特定方法,那么该人仍然会得到我的推荐。

所以我想我应该向Stack Overflow观众提出我的一个问题。

问题:您认为编码国际象棋游戏(或其子集)状态的最节省空间的方法是什么?也就是说,假设棋盘上的棋子是依法排列的,则对初始状态和游戏中所有后续棋步进行编码。

答案不需要任何代码,只需描述您要使用的算法即可。

编辑:正如其中一位海报指出的那样,我没有考虑过两次走步之间的时间间隔。也可以考虑将其作为可选的额外项目:)

EDIT2:只是为了进一步说明...请记住,编码器/解码器是规则感知的。真正需要存储的唯一内容是播放器的选择-可以假定编码器/解码器知道其他任何内容。

EDIT3:在这里挑选获奖者将很困难:)很多很棒的答案!


4
国际象棋游戏的初始状态不是很明确吗?为什么必须对其进行编码?我认为仅编码每个转弯之间的差异(=移动)就足够了。
tanascius

1
他认为游戏可以以任何合法的初始设置开始(就像在报纸上可以找到的象棋游戏难题一样)。
亚伦·迪古拉

6
要严格,你还必须编码所有过去的立场,因为如果在同一位置出现了三次这是一个平局en.wikipedia.org/wiki/Threefold_repetition
flybywire

4
建议:将这场竞赛变成一场真正的竞赛,人们将参赛作品作为程序提交。一个程序将把象棋游戏作为输入(您可以为此定义一些基本的,人类可读的,非优化的格式),然后输出压缩的游戏。然后,使用一个参数,它将使用压缩的游戏并重新生成必须匹配的原始输入。
Vilx-

2
更重要的是,这表明您无法遵循说明。即使是大多数ubercoder也需要在某些时候遵循说明。我遇到了被告知要以某种方式实现某些事情的情况,即使我曾经认为(并说过)这是一个愚蠢的实现,但事实证明,这样做只会让我不满有一个很好的理由(我不知道或理解)以这种方式实施它。
Andrew Rollings

Answers:


132

更新:我非常喜欢这个话题,我写了《编程难题》,《国际象棋位置》和《霍夫曼编码》。如果您通读此书,我确定存储完整游戏状态的唯一方法是存储完整的移动列表。继续阅读为什么。因此,我将问题的简化版本用于样板布局。

问题

此图显示了起始象棋位置。国际象棋发生在8x8的棋盘上,每位玩家从16组相同的棋子开始,包括8个棋子,2个新手,2个骑士,2个主教,1个皇后和1个国王,如下所示:

开始棋位置

位置通常记录为该列的字母,后跟该行的数字,因此怀特的女王/王后在d1。移动通常以代数表示法存储,该代数表示法是明确的,并且通常仅指定必要的最少信息。考虑一下这个开口:

  1. e4 e5
  2. Nf3 Nc6

转换为:

  1. White将国王的棋子从e2移到e4(这是可以到达e4的唯一片段,因此为“ e4”);
  2. 布莱克将国王的棋子从e7移到e5;
  3. 白方将骑士(N)移至f3;
  4. 布莱克将骑士移至c6。

董事会看起来像这样:

提前开放

对于任何程序员来说,一项重要功能就是能够正确,明确地指出问题

那么,什么缺失或模棱两可?事实证明。

董事会状态与游戏状态

您需要确定的第一件事是要存储游戏的状态还是棋盘上棋子的位置。仅对片段的位置进行编码是一回事,但问题是“随后的所有合法动作”。问题也无济于事。正如我将解释的,这实际上是一个问题。

卡斯特

游戏进行如下:

  1. e4 e5
  2. Nf3 Nc6
  3. BB5 A6
  4. Ba4 Bc5

该板看起来如下:

以后开

白方可以选择漂流。对此的部分要求是国王和相关车队永远都不能移动,因此需要存储国王或每边的车队是否已经移动。显然,如果他们不在开始位置,那么他们已经移动了,否则需要指定。

有几种策略可用于处理此问题。

首先,我们可以存储额外的6位信息(每个车和国王1位),以指示该信息是否已移动。如果恰好在其中,我们可以只为这六个正方形之一存储一点来简化此过程。或者,我们可以将每个未移动的棋子视为另一种棋子类型,因此在每一侧(典当,车队,骑士,主教,皇后和国王)的6个棋子类型,将有8个(添加未移动的车队和未移动的国王)。

En Passant

国际象棋中的另一个特殊且经常被忽略的规则是“ En Passant”

传人

游戏进行了。

  1. e4 e5
  2. Nf3 Nc6
  3. BB5 A6
  4. Ba4 Bc5
  5. OO b5
  6. bb3 b4
  7. c4

Black在b4上的棋子现在可以选择将b4上的棋子移动到c3,而在c4上使用白棋子。这仅在第一次机会时发生,这意味着如果布莱克现在通过了该期权,他将无法采取下一步行动。所以我们需要存储它。

如果我们知道前面的举动,我们肯定可以回答“ En Passant”是否可行。或者,我们可以存储第4位的每个棋子是否都向前移动了两次。或者,我们可以查看板上每个可能的“通过”位置并有一个标记,以指示是否可能。

晋升

典当促销

这是怀特的举动。如果怀特将他的棋子从h7移到h8,则可以将其提升为其他任何棋子(但不是国王)。99%的时间被晋升为女王,但有时却没有,通常是因为这样做可能会在您获胜时造成僵局。编写为:

  1. h8 = Q

这对我们的问题很重要,因为这意味着我们不能指望每边都有固定数量的零件。如果将全部8个棋子都提升,一侧完全有可能(但极不可能)有9个皇后,10个新人,10个主教或10个骑士。

僵持状态

当您无法赢得最佳策略时,就会陷入僵局。最可能的变体是您无法采取法律行动(通常是因为在检查国王时采取了任何行动)。在这种情况下,您可以要求平局。这很容易满足。

第二种是三重重复。如果同一局的位置在游戏中出现3次(或在下一个动作中第三次出现),则可以要求平局。位置不需要以任何特定顺序出现(这意味着它不必重复相同的移动顺序重复三次)。这个问题使问题变得更加复杂,因为您必须记住每个先前的电路板位置。如果这是问题的要求,则唯一可行的解​​决方案是存储每个先前的动作。

最后,有五十步法则。如果没有棋子移动并且在前五十次连续移动中没有抓取任何棋子,则玩家可以要求平局,因此我们需要存储自移动棋子或截取一块棋子以来的棋子(两次中的最新一个)。这需要6位(0-63)。

该轮到谁啦?

当然,我们还需要知道轮到谁了,这是一小部分信息。

两个问题

由于僵局,存储游戏状态的唯一可行或明智的方法是存储导致该位置的所有移动。我将解决这个问题。棋盘状态问题将简化为:将所有棋子的当前位置存储在棋盘上,而无须考虑连铸,传球,胶着状态以及轮到的情况

作品布局可以通过以下两种方式之一广泛处理:存储每个正方形的内容或存储每个作品的位置。

简单内容

有六种类型(棋子,车子,骑士,主教,皇后和国王)。每块可以是白色或黑色,因此一个正方形可能包含12种可能的块之一,也可能是空的,因此有13种可能性。13可以以4位(0-15)的形式存储,因此最简单的解决方案是每平方乘以4位或256位信息存储4位。

这种方法的优点是操作非常简单快捷。甚至可以在不增加存储需求的情况下增加3种可能性来扩展:在最后一圈移动了2个空格的棋子,没有移动的国王和没有移动的车子,这将满足很多需求前面提到的问题。

但是我们可以做得更好。

Base 13编码

将董事会职位视为一个很大的数字通常会很有帮助。这通常是在计算机科学中完成的。例如,暂停问题将计算机程序(正确地)视为大量程序。

第一种解决方案将位置视为64位基数的16位数字,但是如所示,此信息中存在冗余(每个“数字”有3个未使用的可能性),因此我们可以将数字空间减少到64位基数的13位数字。当然,这不能像base 16那样高效地完成,但是它将节省存储需求(并且最小化存储空间是我们的目标)。

在基数10中,数字234等效于2 x 10 2 + 3 x 10 1 + 4 x 10 0

在基数16中,数字0xA50等于10 x 16 2 + 5 x 16 1 + 0 x 16 0 = 2640(十进制)。

因此,我们可以将位置编码为p 0 x 13 63 + p 1 x 13 62 + ... + p 63 x 13 0,其中p i表示平方i的内容。

2 256约等于1.16e77。13 64大约等于1.96e71,这需要237位存储空间。仅仅节省7.5%的代价是大大增加了操作成本。

可变基码编码

在法律委员会中,某些作品不能出现在某些正方形中。例如,典当不能出现在第一或第八排,将那些正方形的可能性减小到11。这将可能的板减少到11 16 x 13 48 = 1.35e70(大约),需要233位存储空间。

实际上,将这些值与十进制(或二进制)之间进行编码和解码会有些复杂,但是可以可靠地完成,并留给读者练习。

可变宽度字母

前两种方法都可以描述为固定宽度字母编码。字母的11、13或16个成员中的每一个都替换为另一个值。每个“字符”具有相同的宽度,但是当您考虑每个字符的可能性不同时,可以提高效率。

莫尔斯电码

考虑莫尔斯电码(如上图所示)。消息中的字符被编码为破折号和点的序列。这些破折号和圆点通过无线电传送(通常),并在它们之间停顿以定界。

请注意,字母E(英语中最常见的字母)如何是单个点,可能的序列最短,而Z(最不频繁)是两个破折号和两次蜂鸣声。

这样的方案可以显着减小期望消息的大小,但是以增加随机字符序列的大小为代价。

应该注意的是,摩尔斯电码还有另一个内在的功能:破折号只要三个点,因此在创建上述代码时要尽量减少破折号的使用。由于1和0(我们的构建基块)不存在此问题,因此它不是我们需要复制的功能。

最后,莫尔斯电码中有两种类型的休止符。短暂休息(点的长度)用于区分点和破折号。较长的间隙(破折号的长度)用于分隔字符。

那么这如何适用于我们的问题呢?

霍夫曼编码

有一种用于处理可变长度代码的算法,称为霍夫曼编码。霍夫曼编码创建可变长度代码替换,通常使用符号的预期频率将较短的值分配给较常见的符号。

霍夫曼代码树

在上面的树中,字母E被编码为000(或左右-左-左),而S为1011。应该清楚的是,该编码方案是明确的

这是与摩尔斯电码的重要区别。摩尔斯电码具有字符分隔符,因此可以进行歧义替换(例如4个点可以为H或2 Is),但是我们只有1和0,因此我们选择了明确替换。

下面是一个简单的实现:

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

静态数据:

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

和:

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

一种可能的输出是:

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

对于起始位置,这等于32 x 1 + 16 x 3 + 12 x 5 + 4 x 6 = 164位。

状态差异

另一种可能的方法是将第一种方法与霍夫曼编码相结合。这是基于以下假设:大多数预期的国际象棋棋盘(而不是随机生成的棋盘)更有可能至少部分类似于起始位置。

因此,您要做的是将256位当前板位置与256位起始位置进行异或,然后对其进行编码(使用霍夫曼编码或游程长度编码的某种方法)。显然,从(64个0可能对应于64位)开始非常有效,但是随着游戏的进行,所需的存储空间也会增加。

件位置

如前所述,解决该问题的另一种方法是存储玩家拥有的每个棋子的位置。这对于大多数正方形将为空的残局位置特别有效(但在霍夫曼编码方法中,无论如何,空正方形仅使用1位)。

每边将有一个国王和0-15个其他棋子。由于晋升,这些作品的确切组成可能会变化很大,以至于您不能假设基于开始位置的数字是最大值。

逻辑上将其划分为一个位置,该位置包含两个面(白色和黑色)。每边都有:

  • 国王:位置为6位;
  • 有典当:1(是),0(否);
  • 如果是,则棋子数:3位(0-7 + 1 = 1-8);
  • 如果是,则对每个棋子的位置进行编码:45位(请参见下文);
  • 非典当数量:4位(0-15);
  • 每件作品:类型(女王,车王,骑士,主教2位)和位置(6位)

至于棋子的位置,棋子只能位于48个可能的正方形上(不能像其他正方形那样位于64个正方形上)。因此,最好不要浪费每个典当使用6位将使用的额外16个值。因此,如果您有8个典当,则有48种8种可能性,等于28,179,280,429,056。您需要45位才能对那么多值进行编码。

那是每边105位或总共210位。起始位置对于这种方法来说是最坏的情况,但是当您移开碎片时,起始位置会变得更好。

应当指出,少于88种可能性,因为典当不能全部都在同一个正方形中。第一种具有48种可能性,第二种具有47种,依此类推。48 x 47 x…x 41 = 1.52e13 = 44位存储空间。

您可以通过消除其他棋子(包括另一侧)所占据的正方形来进一步改善此效果,这样您可以首先放置白色非棋子,然后放置黑色非棋子,再放置白色棋子,最后放置黑色棋子。在开始位置,这将存储要求降低为白色的44位和黑色的42位。

组合方法

另一个可能的优化是,每种方法都有其优点和缺点。您可以说,选择最佳的4个,然后在前两位编码方案选择器,然后再编码方案特定的存储。

由于开销很小,这将是最好的方法。

游戏状态

我回到存储游戏而不是位置的问题。由于三重重复,我们必须存储到目前为止发生的移动的列表。

注解

您必须确定的一件事是只是存储一系列动作,还是要注释游戏?国际象棋游戏通常带有注释,例如:

  1. BB5!Nc4?

怀特的举动被两个感叹号标记为出色,而布莱克的举动被视为错误。参见国际象棋标点符号

另外,在描述动作时,您可能还需要存储自由文本。

我假设这些动作已经足够,所以将没有注释。

代数符号

我们可以简单地将移动文本存储在此处(“ e4”,“ Bxb5”等)。包括一个终止字节,您每次移动(最坏的情况)约为6个字节(48位)。这不是特别有效。

要尝试的第二件事是存储开始位置(6位)和结束位置(6位),因此每移动12位。那好多了。

或者,我们可以以可预测的确定性方式和状态来确定当前位置的所有合法举动。然后返回到上面提到的变量基础编码。白人和黑人在他们的第一个举动中有20个可能的举动,在第二个举动中还有更多,依此类推。

结论

这个问题没有绝对正确的答案。以上仅是几种可能的方法。

我对这个问题和类似问题的满意之处在于,它要求任何程序员都具备重要的能力,例如考虑使用模式,准确确定需求并考虑极端情况。

象棋位置作为象棋位置培训师的屏幕截图。


3
然后将结果gzip(如果标题不会增加结果); ^)
Toad

您是否不需要将空格加倍以表示黑色或白色?
丹尼尔·埃利奥特

5
好帖子。较小的更正:铸造需要4个位,每种铸造方式(白色和黑色,王室和皇后区)需要一个,因为新手可能先移动又移回。更重要的一点:您可能应该包括它的动作。=)
A. Rex

9
至于晋升为骑士,我已经做了一次。真是太荒唐了-他和我交配只是一步,我无法阻止。他没有理会我的棋子,因为它虽然可以推动它,但是却迟了一步。我希望当我升职为骑士并与他交配时才带上我的相机!
洛伦·佩希特尔

2
令您惊讶的是,您的文章没有提到[FEN] [1],该文件可处理Casting,无数可用信息等。[1] en.wikipedia.org/wiki/FEN
Ross 2010年

48

最好只是以人类可读的标准格式存储象棋游戏。

便携式游戏符号假定一个标准的起始位置(尽管它不具有),只是列出了动作,轮流转。一种紧凑的,人类可读的标准格式。

例如

[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

如果要缩小尺寸,只需将其压缩。任务完成!


23
为了抵御2次失败,我收到以下答辩:1)它可以满足您的要求2)通过thedailywtf.com/articles/riddle-me-an-interview.aspx测试:“ ...一些可以解决的人这些谜语恰好是您不希望成为程序员的那种人。您是否想与建造水位置换秤/驳船,滑行747到码头,然后使用它称重巨型喷气机的人一起工作,而不是首先打电话给波音?” 您不会雇用在面试中发明随机编码的人,因为他们也会在自己的代码中进行编码。
罗布·格兰特

1
好吧,如果我要他们专门解决问题以获得他们的问题解决技术,那么您可以假设我将用其他问题来介绍其他内容……
Andrew Rollings

7
@reinier:我并不是说我对信息密度问题一无所知(您将我的答复误认为是无能的承认)。当然,您想聘请按照现有数据存储标准编写代码的人,并且他认识到使用适当的现有工具而不是自己动手可以是一个好主意-“我们发明了Wheel 2.0!现在更圆滑了!” 您绝对不想雇用那些奇怪地认为使用库函数是软弱的迹象的人。
罗布·格兰特

18
这绝对是我在采访中对这个问题的第一个答案。您想表明自己的第一个本能是寻找现成的解决方案。如果面试官告诉您他们想听听自己可以提出的建议,那么您可以采用位打包解决方案。
比尔蜥蜴,

2
我与罗伯特(Robert)在一起–现有的解决方案是实用的,易于阅读且紧凑的。与定制的超级打包解决方案相比,所有这些都是壮举,具有复杂的算法来对其进行解码。如果是关于面试的话,我当然也会考虑实践方面!您会惊奇地发现,真正聪明的人有多少次提出了极为复杂的不切实际的解决方案。通常归因于这样一个事实,即他们可以应付复杂的事情,但是-我们其余的人呢...
2009年

15

大难题!

我看到大多数人都在存储每个作品的位置。如何采取更简单的方法,并存储每个正方形的内容?那会自动处理促销和捕获的作品。

它允许霍夫曼编码。实际上,棋盘上棋子的初始频率对此几乎是完美的:一半的正方形是空的,其余的一半是典当,等等。

考虑到每个片段的频率,我在纸上构造了一个霍夫曼树,在此不再赘述。结果,其中c代表颜色(白色= 0,黑色= 1):

  • 0代表空方块
  • 典当1c0
  • 车用1c100
  • 1c101骑士
  • 主教用1c110
  • 女王用1c1110
  • 国王用1c1111

对于处于初始状态的整个董事会,我们拥有

  • 空方格:32 * 1位= 32位
  • 棋子:16 * 3位= 48位
  • 车/骑士/主教:12 * 5位= 60位
  • 皇后/国王:4 * 6位= 24位

总计:初始板状态为164位。大大少于当前投票最高的答案的235位。随着游戏的进行,它只会变得越来越小(促销后除外)。

我只看了棋盘上棋子的位置;其他状态(轮到谁,有城堡的人,过客,重复的动作等)将必须分别编码。也许最多为16位,所以整个游戏状态为180位。可能的优化:

  • 忽略不频繁的片段,并分别存储其位置。但这无济于事...用一个空的正方形替换国王和王后会节省5位,这恰恰是您用另一种方式编码位置所需的5位。
  • 通过为后排使用不同的霍夫曼表,可以很容易地对“后排没有典当”进行编码,但是我怀疑这样做是否有帮助。您可能仍然会得到相同的霍夫曼树。
  • 可以通过引入没有该c位的额外符号来编码“一个白色,一个黑色主教” ,然后可以从主教所在的正方形中推断出这些符号。(被提升为主教的典当破坏了这一计划……)
  • 空方块的重复可以通过引入额外的符号来进行游程编码,例如“连续2个空方块”和“连续4个空方块”。但是,估计这些事件的发生频率并非易事,如果弄错了,那将是伤害而不是帮助。

银行排行榜上的任何棋子都不会节省一点-您可以从所有其他模式中砍掉第3位。因此,您实际上可以在银行级别上每位节省一位。
罗伦·佩希特尔

2
您可以为64个正方形中的每个正方形制作一棵单独的霍夫曼树,因为某些正方形可能比其他正方形具有更多的片断。
Claudiu)

9

真正的大查找表方法

头寸 -18个字节
合法头寸的估计数量为 10 43,
只需将它们全部枚举即可,头寸仅可以存储143位。还需要1位来指示接下来要播放哪一侧

枚举当然是不实际的,但这表明至少需要144位。

移动 - 1个字节
通常有30-40用于每个位置合法移动,但该数目可以是高达218允许枚举所有每个位置上的合法移动。现在,每一步都可以编码为一个字节。

对于特殊动作(例如0xFF),我们仍然有足够的空间表示辞职。


3
直截了当要求“可以想到的最节省空间的方式来编码国际象棋游戏的状态”的核心内容-压缩字典比折叠字典更好,而且其中包括苍蝇。
安德鲁2009年

1
我发现了一个有趣的链接,它涉及生成这样的词典需要多长时间:) ioannis.virtualcomposer2000.com/math/EveryChess.html
Andrew Rollings 2009年

Shannons的估计有点过时了:-)他没有包括晋升和捕获,这使这个数字相当可观。Victor Allis 1994
。– Gunther Piez,09年

当然使用可变长度编码时,平均值至少必须为10 ^ 43?偏向更多位置的编码必须减少这种情况,特别是因为许多位置是不可能的。
Phil H

EveryChess链接现在已“出售”,archive.org链接:web.archive.org/web/20120303094654/http
//…

4

它将有兴趣针对人类玩的典型游戏而不是最坏的情况来优化平均大小。(问题说明中并未说明是哪一项;大多数回答都假设是最坏的情况。)

对于移动顺序,要有一个好的象棋引擎从每个位置生成移动;它将产生k个可能动作的列表,并按其质量排名排序。人们通常会比随机动作更频繁地选择好动作,因此我们需要学习从列表中每个位置到人们选择“好”动作的概率的映射。使用这些概率(基于某些互联网象棋数据库中的一整套游戏),使用算术编码对移动进行编码。(解码器必须使用相同的象棋引擎和映射。)

对于开始的位置,ralu的方法会奏效。如果我们有某种方法可以按概率对选择进行加权,那么我们也可以使用算术编码对其进行优化,例如,碎片经常出现在相互防御的配置中,而不是随机的。很难找到一种简单的方法来吸收这些知识。一个想法:从标准的打开位置开始,找到以所需板块结尾的序列,而不是上面的移动编码。(您可以尝试进行A *搜索,试探距离等于棋子到其最终位置的距离之和,或者沿着这些直线的距离。)这会浪费一些效率,因为过度指定移动顺序会降低效率,而由于利用象棋游戏会降低效率知识。

在没有从实际语料库收集一些统计信息的情况下,很难以平均情况下的复杂性估算这将为您节省多少费用。但是所有动作的起点都可能相等,我认为这里已经超过了大多数建议:算术编码不需要每次动作都具有整数位数。


将此信息存储到池中的复杂度为O(n),请检查我编辑的答案。
卢卡·拉恩

拉露,我不确定您在说什么,但是如果您的意思是说一系列动作的表示在最坏的情况下使用了最佳空间,那么我并不矛盾。这里的想法是利用某些动作比其他动作更有可能的优势。
达里乌斯·培根

您所需要找到的更加利落的位置就是使用确定性(且强大)的国际象棋引擎,该引擎以给定位置确定性的方式对有效的移动进行排序。
卢卡·拉恩

4

在对初始位置进行编码之后,攻击对步骤进行编码的子问题。该方法是创建步骤的“链接列表”。

游戏中的每个步骤都被编码为“旧位置->新位置”对。您知道国际象棋游戏开始时的初始位置;通过遍历链接的步骤列表,您可以进入X移动后的状态。

为了对每个步骤进行编码,您需要64个值来编码起始位置(板上64个正方形为6位-8x8个正方形),结束位置为6位。每侧1个移动16位。

然后,对给定游戏进行编码所需的空间量与移动数量成正比:

10 x(白色移动次数+黑色移动次数)位。

更新:潜在的并发症与升级的典当。需要能够说明将典当升级到的内容-可能需要特殊的位(为此,将使用格雷码来节省空间,因为典当升级非常少见)。

更新2:您不必编码结束位置的完整坐标。在大多数情况下,要移动的作品最多只能移到X个位置。例如,一个棋子在任何给定点最多可以有3个移动选项。通过实现每种棋子的最大移动次数,我们可以在“目标”的编码上节省位。

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

因此,黑白移动的空间复杂度变为

初始位置为6位+(根据要移动的事物的类型而变化的位数)。


刚刚更新,我的意思是128个组合-明显少于128位:) :)
Alex Weinstein,

1
游戏状态与移动不同。任何给定位置都可以考虑一个顶点或节点,合法移动可以考虑一个有向边或箭头,从而形成一个(有向无环)图。
粗野的青蛙

我不确定为什么要投反对票-我很想听听人们对最新想法的看法。
亚历克斯·温斯坦

1
这不会影响您的推理,但会稍有改正:兵可以有四个动作(不包括促销),或十二个动作(包括促销)。e2处的典当示例:e3,e4,exd3,exf3。在e7处的示例典当:e8Q,e8N,e8R,e8B,exd8Q,exd8N,exd8R,exd8B,exf8Q,exf8N,exf8R,exf8B。
A. Rex

1
一个小问题-5位仅编码32个值。要在板上指定任何正方形,您需要6位。
克里斯·多德

4

昨晚我看到了这个问题,这让我很感兴趣,所以我坐在床上思考解决方案。我的最终答案与int3的实际上非常相似。

基本解决方案

假设是一个标准的国际象棋游戏,并且您不对规则进行编码(就像怀特总是先行),那么您只需对每个棋子的动作进行编码就可以节省很多。

总共有32个棋子,但是每一步您都知道移动的颜色,因此只需要担心16个正方形,这就是该棋子在此回合中移动的4位

每块都只有一个有限的移动集,您可以通过某种方式枚举。

  • 典当:4个选项,2位(向前1步,向前2步,每个对角线1个)
  • Rook:14个选项,4位(每个方向上最多7个)
  • 主教:13个选项,4位(如果一个对角线中有7个,而另一个对角线中只有6个)
  • 骑士:8个选项,3位
  • 女王:27个选项,5个位(Rook + Bishop)
  • 国王:9个选项,4个位(8个单步移动,加上连铸选项)

为了晋升,有4个乐段可供选择(Rook,Bishop,Knight,Queen),因此在这一步中,我们将添加2位来指定。我认为所有其他规则都是自动涵盖的(例如,pass pass)。

进一步优化

首先,在捕获了8种颜色后,可以将编码减少到3位,然后将2位减少到4种,依此类推。

尽管主要的优化是枚举游戏中每个点的可能动作。假设我们存储了一个Pawn的移动,分别{00, 01, 10, 11}向前1步,向前2步,向左斜和向右斜。如果无法进行某些移动,我们可以在本回合中将其从编码中删除。

我们知道每个阶段的游戏状态(通过跟随所有移动),因此在读取要移动的棋子之后,我们始终可以确定需要读取多少位。如果我们意识到典当机在此点的唯一动作是沿对角线向右捕获或向前移动,我们知道只能读取1位。

简而言之,上面列出的每件位存储仅是最大数量。几乎每一个动作都会有更少的选择,并且通常会有更少的位。


4

在每个位置获取所有可能的移动次数。

下一步动作生成为

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

可证明最佳的空间效率来存储随机生成的游戏,并且由于您有30-40次可能的移动,因此平均大约需要5位/移动。组装存储只是以相反的顺序生成n。

由于冗余度大,存储位置更难破解。(一个地点最多可容纳9个皇后,但在这种情况下,不会有典当,而如果主教在相反的彩色正方形上,则是主教),但通常就像在剩余的正方形上存储相同的组合。)

编辑:

保存移动的重点是仅存储移动索引。而不是存储Kc1-c2并尝试减少此信息,我们应该仅添加从确定性movegenerator(position)生成的移动索引

在每一步中,我们都会添加尺寸信息

num_of_moves = get_number_of_possible_moves(postion) ;

在游泳池里,这个数字不能减少

生成信息池是

n=n*num_of_moves+ index_current_move

额外

如果最终位置只有一个动作,则另存为以前执行的强制动作的数量。例如:如果起始位置的每一侧有1个强制移动(2个移动),而我们想将其保存为一个移动游戏,则将1存储在池n中。

存储到信息池中的示例

假设我们知道起始位置,并且进行了3次移动。

在第一个动作中有5个可用动作,我们取移动索引4。在第二个动作中有6个可用动作,我们取位置索引3,在第3个动作中,该侧可用7个动作,他选择了移动索引。 2。

向量形式 index = [4,3,2] n_moves = [5,6,7]

我们正在反向编码此信息,因此n = 4 + 5 *(3 + 6 *(2))= 79(无需乘以7)

如何解开这个?首先,我们有位置,我们发现有5个动作。所以

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

我们采用移动索引4并再次检查位置,从这一点上我们发现有6种可能的移动。

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

我们采用移动索引3,该索引将我们带到可能进行7次移动的位置。

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

我们最后移动索引2,然后到达最终位置。

如您所见,时间复杂度为O(n),空间复杂度为O(n)。编辑:时间复杂度实际上是O(n ^ 2),因为您乘以的次数增加了,但存储最多10,000步的游戏应该没有问题。


保存位置

可以做到接近最佳。

当我们找到有关信息和存储信息的信息时,让我进一步谈谈它。总体思路是减少冗余(我将在后面讨论)。假设没有晋升也没有收入,因此每边有8个棋子,2个新人,2个骑士,2个主教,1个国王和1个女王。

我们必须保存的是:1.每个和平的位置2. cast的可能性3.包容的可能性4.可以移动的一面

假设每块都可以站在任何地方,但不能在同一位置放置2个。可以在板上排列8个同色棋子的方式是C(64/8)(二项式),即32位,然后是2个菜鸟2R-> C(56/2),2B-> C(54/2) ,2N-> C(52/2),1Q-> C(50/1),1K-> C(49/1),其他站点相同,但以8P-> C(48/8)开头,依此类推。

将这两个站点的总和相乘得出编号4634726695587587809641192045982323285670400000,即约142位,我们必须为一个可能的passent(pass-pawn可以在8个位置之一)中添加8,为castling限制添加16(4位),一点点移动。我们最终得到142 + 3 + 4 + 1 = 150bits

但是,现在让我们以32个不带接缝的方式在板上寻找冗余。

  1. 黑白棋子都在同一列上并且彼此面对。每个棋子都面对另一个棋子,这意味着白色棋子最多只能位于6级。这给我们带来了8 * C(6/2)而不是C(64/8)* C(48/8),从而使信息减少了56位。

  2. 铸造的可能性也是多余的。如果新手不在起始位置,那该新手就不会有cast声。因此,我们可以想象地在船上添加4个正方形,以获取额外信息(如果有可能在该白嘴鸦中吐口水)并删除4个cast口位。因此,我们有了C(58/2)* C(42/2)而不是C(56/2)* C(40/2)* 16,并且丢失了3.76位(几乎所有4位)

  3. 传递:当我们存储8个传递可能性中的一个时,我们知道黑棋的位置并减少信息冗余(如果它是白色棋并具有第3个棋子传递,则意味着黑棋在c5上并且白棋是c2,c3或c4),因此受C(6/2)的影响,我们只有3位,并且丢失了2.3位。如果我们在另一边存储可以传递的通行数,则会减少一些冗余(3种可能性->左,右,两者),并且我们知道可以接受通行的典当的可能性。(例如,在c5上普遍存在的黑色示例中,可以在左侧,右侧或同时在左侧和右侧)。如果在一个站点上,我们有2 * 3(3个用于存储拟滑石,2个可能的移动用于第7或6级的黑兵) )插入C(6/2),我们减少了1.3位,如果在两边都减少了4.2位,那么我们可以减少2.3 + 1.3 = 3。

  4. 主教:bisops只能在opostite广场上,这样可以将每个站点的冗余减少1位。

如果总结一下,如果没有收入,我们需要150-56-4-3.6-2 = 85位来存储棋位置

如果考虑到收入和晋升的话,可能就不多了(但是如果有人觉得这篇长文章有用,我会在后面写)


有趣的方法。添加更多详细信息:)
Andrew Rollings

我还添加了保存位置的方法。我在没有采取任何措施的职位上跌到了85位,并且很好地说明了可以走多远。我认为最好的方法是保存几乎所有4位都是冗余的浇铸可能性。
卢卡·拉恩

3

大多数人已经在编码棋盘状态了,但是关于移动本身。.这是位编码的描述。

每片位数:

  • 件号:最多4位,用于识别每侧16件。可以推断出白色/黑色。在零件上定义顺序。当片段数下降到各自的2的幂以下时,请使用较少的位来描述其余片段。
  • 典当:第一次移动时有3种可能,因此+2位(向前传递一个或两个正方形,以此类推。)后续移动不允许向前移动2个,因此+1位就足够了。通过注意典当何时达到最后等级,可以在解码过程中推断提升。如果已知典当会被提升,则解码器将期待另外2位,指示已将其提升到4个主要片段中的哪一个。
  • Bishop: +1位用于对角线,最多+4位用于沿对角线的距离(16种可能性)。解码器可以推断出片段可以沿着对角线移动的最大可能距离,因此,如果对角线较短,则使用较少的位。
  • 骑士: 8种可能动作,+ 3位
  • 车钩: +1位表示水平/垂直,+ 4位表示沿线的距离。
  • 国王: 8个可能的动作,+ 3位。用“不可能”的举动来表示cast割-因为只有当国王处于第一等级时才有可能进行cast割,因此请用指示将国王“倒退”的指令(即,将其移出董事会)来编码此举动。
  • 女王: 8个可能的方向,+ 3位。沿线/对角线的距离最多增加+4位(如主教的情况,对角线更短则更少)

假设所有棋子都在棋盘上,则这些是每次移动的位:Pawn-第一次移动6位,随后移动5位。如果晋升为7。主教:9位(最大),骑士:7,车队:9,国王:7,女王:11(最大)。


32位识别一块???我想你的意思是5(32件)。或6,如果您需要编码“结束”状态,
Toad

典当也可以升级为菜鸟,骑士或主教。避免僵局或避免对抗是很常见的。
Kobi

这不会影响您的推理,但会稍有改正:兵可以有四个动作(不包括促销),或十二个动作(包括促销)。e2处的典当示例:e3,e4,exd3,exf3。在e7处的典当示例:e8Q,e8N,e8R,e8B,exd8Q,exd8N,exd8R,exd8B,exf8Q,exf8N,exf8R,exf8B。
A. Rex

也许我读错了,但典当不能一en而就。实际上,您不需要特殊的“过客”表示法,因为这在游戏规则中-只是对角线移动。第一步是4个选项之一,随后的步法最多是3个选项。
DisgruntledGoat

3

问题是给出一种对于典型的国际象棋游戏而言最有效的编码,还是给出最短的最坏情况编码的编码?

对于后者,最有效的方法也是最不透明的:创建所有可能的对的枚举(初始棋盘,合法的移动顺序),并以重复绘制三次的位置且不超过自上次典当移动或捕获规则起五十次移动是递归的。然后,在此有限序列中的位置索引会给出最短的最坏情况编码,但在典型情况下也会给出同样长的编码,我预计这将非常昂贵。最长的国际象棋游戏应该超过5000步,每个位置的每个玩家通常可以进行20到30步(但是当剩下的棋子少时则更少)-这给出了这种编码所需的40000位。

枚举的思想可用于给出更易于处理的解决方案,如Henk Holterman的上述编码移动建议中所述。我的建议:并非最小,但比我上面看过的示例短,并且合理易处理:

  1. 64位代表占用的正方形(占用矩阵),以及每个占用的正方形中的哪些块的列表(典当可以具有3位,其他块可以具有4位):这给开始位置提供190位。由于板上不能超过32件,因此占用矩阵的编码是多余的,因此可以对通用板位置进行编码,比如说33个设置位加上普通板列表中的板索引。

  2. 一言不发地说谁先走

  3. 根据Henk的建议进行代码移动:每对白色/黑色移动通常为10位,但是当玩家没有其他移动时,某些移动将占用0位。

这建议使用490位代码对典型的30步游戏进行编码,对于典型的游戏而言,这将是相当有效的表示方式。

自上次典当移动或捕获规则以来,大约对三次重复绘制位置进行了编码,并且移动不超过五十次:如果您将过去的移动编码回到最后的典当移动或捕获,那么您有足够的信息来决定这些规则是否适用:不需要整个游戏历史。


假设我会选择多种游戏并取平均结果。
Andrew Rollings

3

板上的位置可以用7位(0-63和1个值指定它不再在板上)定义。因此,对于板上的每一块,请指定其位置。

32个* 7位= 224位

编辑:正如Cadrian所指出的...我们也有“当兵升格为女王”案。我建议我们在末尾添加额外的位,以指示已升级的棋子。

因此,对于每个已提升的典当,我们在224位之后跟随5位,表示已提升的典当的索引;如果它是列表的末尾,则为11111。

因此,最小情况(无促销)为224位+ 5(无促销)。为每个提升的典当添加5位。

编辑:正如粗野的青蛙指出的那样,我们需要在末尾再说明一下该轮到谁了; ^)


,然后将结果gzip(如果标头不会增加结果); ^)
Toad

您是否可以考虑到某些正方形颜色永远找不到某些作品,从而改善这一点?
安德鲁·罗林斯

安德鲁:实际上我不能。我忘记考虑到升为女王的典当了(就像卡迪安的回答所暗示的那样)。因此,看来我实际上需要再多一点
-Toad

我可以看到如何将黑白主教定义在一起。我不知道骑士如何..
int3

1
您缺少非皇后促销活动。
洛伦·佩希特尔

2

我会使用游程编码。有些作品是独一无二的(或仅存在两次),因此我可以省略它们后面的长度。像cletus一样,我需要13个唯一状态,因此我可以使用半字节(4位)对片段进行编码。初始板将如下所示:

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

这给我留下了8 + 2 + 4 + 2 + 8个半字节= 24个半字节= 96位。我无法对16进行半字节编码,但是由于“ Empty,0”没有意义,因此我可以将“ 0”视为“ 16”。

如果面板是空的,但对于左上角的一个棋子,则得到“棋子,1,空,16,空,16,空16,空,15” = 10个半字节= 40位。

最糟糕的情况是我在每块之间都留有一个空的正方形。但是对于片段的编码,我只需要16个值中的13个,因此也许我可以使用另一个来表示“ Empty1”。然后,我需要64个半字节== 128bits。

对于机芯,我需要3个位(颜色由白色总是先移动这一事实所给定)加上新位置的5位(0..63)=每个机芯一个字节。大多数时候,我不需要旧的职位,因为只有一块在范围之内。对于奇怪的情况,我必须使用单个未使用的代码(我只需要7个代码来编码该片段),然后使用5位旧位和5位新位。

这使我可以将叮咬声编码为13比特(我可以将King移至Rook,这足以说明我的意图)。

[编辑]如果您允许使用智能编码器,则我需要0位用于初始设置(因为它不必以任何方式进行编码:它是静态的),而且每移动一个字节。

[EDIT2]保留了典当转换。如果典当到达最后一行,我可以将其移动到适当的位置以说出“ transforms”,然后为要替换的棋子添加3位(您不必使用皇后;您可以将典当替换为任何东西但国王)。


智能编码器不能假设它是一个完整的游戏。这可能只是游戏的一部分。我认为您仍然需要对开始位置进行编码。
Andrew Rollings

好吧,在最坏的情况下,我需要128位,或者,如果游戏仍处于早期阶段,我最多可以使用15步将其带入起始位置= 120位。
亚伦·迪古拉

由于必须对ANY状态进行编码,而不仅是对初始板状态进行编码,因此您还必须对片段本身进行编码。因此,您每块至少需要5位。因此,这至少会给您至少32 * 5位
Toad

@reiner:你错了。我只需要每块/空正方形四位。而且我已经在答案的第一部分中对此进行了介绍,因此没有“额外的32 * 5位”。对于初始状态,我需要96位,对于任何其他启动状态,我最多需要128位。
2009年

亚伦:就像您说的那样,最糟糕的情况实际上是这种编码中最糟糕的情况。从启动板移动3或4后,您的编码将需要更多的位,因为您要添加越来越多的空位
Toad 2009年

2

就像他们在书和纸上编码游戏一样:每件作品都有一个符号;由于这是一种“合法”游戏,因此白色先行-无需分别对白色或黑色进行编码,只需计算移动次数即可确定谁移动了。同样,每个移动都被编码为(片段,结束位置),其中“结束位置”减少到允许辨别歧义的符号数量最少(可以为零)。游戏时间决定了移动次数。您还可以在每一步中以分钟为单位(自上次移动以来)编码时间。

可以通过为每个片段分配一个符号(总共32个)或为该类分配一个符号来完成片段的编码,并使用结束位置来了解哪个片段被移动了。例如,一个棋子有6个可能的结束位置。但平均每轮只有一对夫妇可用。因此,从统计学上讲,对于这种情况,通过结束位置进行编码可能是最好的。

类似的编码用于计算神经科学(AER)中的峰值训练。

缺点:您需要重播整个游戏才能达到当前状态并生成子集,就像遍历链接列表一样。


它可能只是游戏片段。您不能假设白色动作会触发。
Andrew Rollings

2

板上有64个可能的位置,因此每个位置需要6位。初始有32个片段,因此到目前为止,我们总共有192位,其中每6位指示给定片段的位置。我们可以预先确定片段出现的顺序,因此我们不必说哪个是哪个。

如果一块不在板上怎么办?好吧,我们可以将一个棋子与另一个棋子放在同一位置以表明它不在棋盘上,因为否则这将是非法的。但是我们也不知道第一部分是否会出现在董事会上。因此,我们添加5位来指示哪一块是第一个(32个可能性= 5位代表第一块)。然后,我们可以将该点用于板外的后续部分。这使我们总共达到197位。板上至少必须有一块,这样才能起作用。

然后我们需要一个轮到谁-将我们带到198位

典当促销呢?我们可以通过将每个典当数增加3位,再加上42位来完成此操作。但是我们可以注意到,在大多数情况下,棋子没有得到提升。

因此,对于板上的每个棋子,位“ 0”表示未升级。如果棋子不在板上,那么我们根本就不需要。然后,我们可以使用他具有升迁功能的变长位串。最常见的是女王/王后,因此“ 10”表示QUEEN。然后,“ 110”表示菜鸟,“ 1110”表示主教,“ 1111”表示骑士。

初始状态将占用198 + 16 = 214位,因为所有16个棋子都在棋盘上且未被提升。带有两个提升的典当皇后的最终游戏可能需要198 + 4 + 4,这意味着4个活着但未被提升的典当和2个女王典当,总共206位。似乎很健壮!

===

正如其他人指出的那样,霍夫曼编码将是下一步。如果观察几百万场比赛,您会发现每一块都更有可能出现在特定的正方形上。例如,在大多数情况下,棋子保持在一条直线上,或保持在一条直线上(左向/一条在右侧)。国王通常会坚守本垒。

因此,为每个单独的位置设计霍夫曼编码方案。典当平均只可能需要3-4位,而不是6位。国王也应只使用几位。

同样在此方案中,包括“采取”作为可能的位置。这也可以非常稳健地处理滚动-每个白嘴鸦和国王都会有一个额外的“原始位置,已移动”状态。您也可以通过这种方式在典当中对“随行”进行编码-“原始位置,可以随行”。

有了足够的数据,这种方法应该会产生非常好的结果。


2
只需将删除的部分分配给与国王相同的正方形即可。由于国王永远都不会被罢免,所以它不会是模棱两可的
John La Rooy

这是一个很好的评论:)此解决方案也不错。我没有意识到要选择一个胜利者会如此困难。
Andrew Rollings

2

我会尝试使用霍夫曼编码。其背后的理论是-在每个国际象棋游戏中,都会有一些棋子会移动很多,而有些棋子不会移动太多或早被淘汰。如果起始位置已经去除了一些碎片,那就更好了。正方形也是如此-有些正方形可以看到所有动作,而有些则不会受到太多影响。

因此,我要有两个霍夫曼表-一个用于块,另一个用于正方形。它们将通过查看实际游戏生成。我可以为每个对角尺对使用一个大桌子,但是我认为这样效率很低,因为在同一正方形上不会再出现同一块的实例。

每件作品都有一个分配的ID。由于有32个不同的片段,因此片段ID仅需要5位。每个游戏的棋子ID都没有改变。正方形ID也是一样,我需要6位。

霍夫曼树将通过在顺序遍历每个节点时写下它们来进行编码(也就是说,首先输出该节点,然后从左到右输出其子节点)。对于每个节点,将有一个位指定是叶子节点还是分支节点。如果是叶节点,则其后是给出ID的位。

起始位置将简单地由一系列零件位置对给出。之后,每一步都会有一对位置对。您只需找到被提及两次的第一段,就可以找到起始位置描述符的结尾(以及移动描述符的起点)。如果典当被提升,将会有2个额外的位来指定它变成什么,但是棋子ID不会改变。

为了考虑在游戏开始时提升典当的可能性,霍夫曼树和数据之间还会有一个“促销表”。首先,将有4位指定要升级的棋子。然后,对于每个棋子,将有其霍夫曼编码的ID和2位指定其已变成的东西。

霍夫曼树将通过考虑所有数据(起始位置和移动)以及促销表来生成。尽管通常促销表将为空或只有几个条目。

总结一下:

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

补充:仍然可以优化。每件作品只有几处法律动作。与其简单地编码目标方块,不如为每个块的可能移动提供基于0的ID。相同的ID将被重复使用,因此总共不超过21个不同的ID(皇后最多可以有21种不同的可能移动选项)。将其放在霍夫曼表中而不是字段中。

然而,这将在表示原始状态方面带来困难。一个人可能会产生一系列的动作来将每一块放置在原处。在这种情况下,必须以某种方式标记初始状态的结束和移动的开始。

或者,可以使用未压缩的6位正方形ID放置它们。

这是否会整体上减小尺寸-我不知道。可能可以,但是应该尝试一下。

新增2:另外一种特殊情况。如果游戏状态为“否”,则区分下一个谁将变得很重要。最后再增加一点。:)


2

[在正确阅读问题后进行编辑]如果您假设可以从初始位置到达每个合法位置(这是“法律”的可能定义),那么任何位置都可以表示为从开始时的移动顺序。从非标准位置开始的一段游戏片段可以表示为到达起点所需的移动顺序,打开相机电源的开关以及随后的移动。

因此,我们将初始板状态称为一位“ 0”。

可以通过以下方式枚举从任何位置开始的移动:对正方形进行编号,并按(开始,结束)对移动进行排序,而传统的2跳表示掷骰。无需对非法举动进行编码,因为董事会位置和规则始终是已知的。打开相机的标志可以表示为特殊的带内移动,也可以更合理地表示为带外移动编号。

两侧各有24个打开动作,每个动作可容纳5个位。随后的移动可能需要更多或更少的位,但是合法移动总是可以枚举的,因此每个移动的宽度可以愉快地增长或扩展。我还没有计算,但我想7位位置将很少见。

使用该系统,可以用大约500位编码100个半步游戏。但是,使用入门书籍可能是明智的。假设它包含一百万个序列。然后,以0开头表示从标准板开始,以1开头的20位数字表示从该打开顺序开始。具有一些常规开口的游戏可能会缩短20个半步或100位。

这不是最大的压缩方式,但是(如果没有本书的话),如果您已经有一个国际象棋模型,就很容易实现。

为了进一步压缩,您希望根据似然性而不是任意顺序对移动进行排序,并以更少的比特编码可能的序列(如人们所提到的,使用霍夫曼令牌)。


初始位置不一定已知。这可能是游戏片段。
安德鲁·罗林斯2009年

@安德鲁:是的。我的错。我进行了编辑,以允许游戏片段。
Douglas Bagnall,

2

如果计算时间不是问题,则可以使用确定性位置生成器为给定位置分配唯一ID。

首先从给定位置生成确定性庄园中的多个可能位置,例如从左下开始移动到右上。这决定了下一步需要多少位,在某些情况下,可能少到一位。然后,在进行移动时,仅存储该移动的唯一ID。

促销和其他规则只要以确定性的方式进行处理即可简单地算作有效举动,例如以女王/王后,白手起家/毕晓普的方式将每个计数视为单独的举动。

初始位置最困难,可能会产生大约2.5亿个位置(我认为),这将需要大约28位加上一个额外的位来确定它的移动位置。

假设我们知道轮到谁了(每个轮次从白色翻转为黑色),确定性生成器将类似于:

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

“获取可能的动作列表”将执行以下操作:

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.

如果国王在检查中,则每个“可能的XXX动作列表”仅返回更改检查情况的有效动作。


这是一个偷偷摸摸的解决方案...所以...在这种情况下,请描述您用于生成确定性数字的算法。
Andrew Rollings

我确实找到了关于在棋盘上生成每个位置需要多长时间的有趣链接:) ioannis.virtualcomposer2000.com/math/EveryChess.html
Andrew Rollings 2009年

2

大多数答案都忽略了3次重复。不幸的是,对于3次重复,您必须存储到目前为止所打过的所有位置...

这个问题要求我们以节省空间的方式进行存储,因此只要可以从移动列表中构造位置(只要我们具有标准的起始位置),就不需要存储位置。我们可以优化PGN,仅此而已。波纹管是一个简单的方案。

板上有64个正方形,64 = 2 ^6。如果我们仅存储每次移动的初始和最终正方形,这将花费12位(促销将在以后处理)。请注意,该方案已经涵盖了玩家的移动,强调,抓拍,cast等动作;这些可以通过仅重播移动列表来构建。

为了进行促销,我们可以保留一个单独的向量数组,其中会说“在移动N时晋升为XYZ片”。我们可以保留(int,byte)的向量。

也很想优化(To,From)向量,因为许多(To,From)向量在国际象棋中都不可行。例如。从e1到d8不会有变化。但是我无法提出任何方案。任何进一步的想法都受到欢迎。


2

我已经考虑了很长时间(+-2小时)。而且没有明显的答案。

假设:

  1. 忽略时间状态(玩家过去没有时间限制,因此可能会因为不玩而强制平局)
  2. 游戏什么时候玩的?!?这一点很重要,因为规则会随着时间而改变(因此在以后的游戏中将假设是现代游戏……)请参考例如死典当规则(维基百科有一个非常著名的问题显示该规则),如果需要时光倒流,好运的主教过去只是缓慢移动,而以前使用骰子。大声笑。

...所以是最新的现代规则。首先,不考虑重复次数和移动排位限制。

-C 25字节舍入(64b + 32 * 4b + 5b = 325b)

= 64位(无(有/无))+ 32 * 4位[1bit =颜色{黑/有} + 3bit =棋子的类型{国王,皇后,主教,骑士,行,典当,MovedPawn}注意:Moving pawn ...例如,如果它是前一回合中最后移动的棋子,则表明“传人”是可行的。] + 5bit表示实际状态(轮到谁,通过,是否有可能发生漫游)

到目前为止,一切都很好。可能可以增强,但考虑到长度和提升的可变性,要考虑!!

现在,以下规则仅在玩家参加平局时才适用,这不是自动的!因此,如果没有玩家要求平局,那么考虑这90次移动而没有捕获或棋子移动是可行的!这意味着所有动作都需要记录...并且可用。

-D重复位置...例如上述板子状态(参见C)或不...(有关FIDE规则,请参见以下内容)-E留下了50个移动配额的复杂问题,而没有捕获或典当移动柜台是必要的...但是。

那么,您该如何处理呢?...真的没有办法。因为两个玩家都可能不想画画或意识到它已经发生。现在以防万一,计数器就足够了……但这是窍门,甚至阅读了FIDE规则(http://www.fide.com/component/handbook/?id=124&view=article)我找不到回答...丧失流氓能力的情况如何。那是重复吗?我认为不是,但是这是一个模糊的主题,没有解决,没有弄清楚。

因此,这里有两个规则,即使试图进行编码,它们也是两个复杂的或未定义的...干杯。

因此,真正对游戏进行编码的唯一方法是从头开始记录所有内容,然后与“棋盘状态”问题冲突(或不冲突)。

希望这个帮助...不要过多的数学:-)只是为了表明某些问题并不那么容易,对于解释或预先知识来说太开放了以至于是正确而有效的。我不会考虑面试,因为它打开了太多一罐蠕虫。


2

Yacoby解决方案中起始位置的可能改进

每个颜色的合法位置均不得超过16件。在64个正方形上最多放置16个黑色和16个白色块的方式大约为3.63e27。Log2(3.63e27)= 91.55。这意味着您可以将所有片段的位置和颜色编码为92位。这少于Yacoby解决方案所需的位置的64位+最多32位的颜色。在最坏的情况下,您可以节省4位,但会以相当大的编码复杂度为代价。

另一方面,它会增加缺少5个或更多零件的位置的尺寸。这些位置仅占所有位置的<4%,但是在大多数情况下,您想要记录与初始位置不同的开始位置。

这导致了完整的解决方案

  1. 根据上述方法对作品的位置和颜色进行编码。 92位
  2. 要指定每件作品的类型,请使用霍夫曼代码:典当:'0',车队:'100',骑士:'101',主教:'110',女王:'1110',国王:'1111'。对于完整的块,这需要(16 * 1 + 12 * 3 + 4 * 4)= 68位。整个板的位置可以编码为最大 92 + 68 = 160位
  3. 应当增加其他游戏状态:回合:1位,可能的掷骰:4位,“传人”的可能:最多4位(1位表明情况,3位表明哪一个)。起始位置编码为= 160 + 9 = 169位
  4. 对于移动列表,请列举给定位置的所有可能移动,并将移动位置存储在列表中。动作列表包括所有特殊情况(铸造,传人和辞职)。仅使用必要的位数来存储最高位置。平均而言,每次移动不应超过7位(平均每条可能移动16个片断和8个合法移动)。在某些情况下,当强制移动时,仅需要1位(移动或辞职)。

1

板上有32个。每一块都有一个位置(64个正方形中的一个)。因此,您只需要32个正整数。

我知道6位中有64个位置,但是我不会那样做。我会保留最后几个标志(掉下的棋子,女王典当)


您不需要使用标志来保持状态。您可以假设编码器足够聪明,可以“知道规则”。因此,如果典当突然变为皇后,则不必在编码中特别标记(除非,我想,玩家选择不升级)。
安德鲁·罗尔斯

是的,应该,因为您无法通过典当的初始位置来判断典当是否已升级!因此,它作为要在初始设置编码
蟾蜍

啊,但是为什么您需要知道它是否已经被推广?这只是一块。在这种情况下,过去的状态将是无关紧要的。
Andrew Rollings

我认为,如果典当仍然是典当或已晋升为女王,则与游戏的其余部分几乎无关。如果您不这样认为,我很乐意和您一起下棋; ^)
Toad

@reinier:他声称现任女王原先是女王还是典当都没有关系。
A. Rex

1

cletus的回答很好,但他忘了也编码轮到谁。它是当前状态的一部分,如果要使用该状态来驱动搜索算法(例如alpha-beta衍生物),则它是必需的。

我不是国际象棋棋手,但我相信还有一个极端的例子:重复了多少步。一旦每个玩家执行相同的动作三次,游戏就成为平局,不是吗?如果是这样,则您需要将该信息保存在状态中,因为在第三次重复之后,该状态现在处于终端状态。


沿着那条路线,您还需要增加两个玩家的游戏时间,因为在真正的国际象棋游戏中,两个玩家总共只需要花1或2个小时。
蟾蜍

2
您不必在实际数据中编码规则。您可以假设编码器本身知道任何必要的规则。
Andrew Rollings

啊..我没考虑上场时间。好电话... :)
安德鲁·罗林斯

@Andrew Rollings:规则是基于状态的,例如,它仅在满足某个先决条件时触发。跟踪前提条件的状态也是...状态的一部分。:)
Shaggy Frog

在这种情况下无关紧要。如有必要,解码器可以检查状态以确定获胜者。请记住,编码器/解码器是规则感知的。真正需要编码的唯一东西是播放器的选择-可以假定编码器/解码器知道其他任何东西。
Andrew Rollings

1

正如其他几个人提到的那样,您可以为32个块中的每个块存储它们所在的正方形,如果它们不在板上,则得到32 *(log2(64)+1)= 224位。

但是,主教只能占据黑色或白色正方形,因此对于这些位置,您只需要log2(32)位作为位置,即28 * 7 + 4 * 6 = 220位。

而且由于典当不是从后面开始而是只能向前移动,所以它们只能在56上,因此应该可以使用此限制来减少典当所需的位数。


主教也可以脱离董事会,因此您需要额外的钱。另外,您会忘记升职的棋子以及最先开始的人。考虑到所有这些,您基本上都会得到我的回答; ^)
Toad

主教的6位是log2(32)+ 1 = 6,但是当您考虑所有详细信息时,这肯定是一个复杂的问题:)
Andreas Brinck

我一直在沿着这些思路思考,但这无济于事。查看Thomas的答案,并修改他的霍夫曼编码以消除空白的概念。您使用64位来存储占用正方形的矩阵,然后从每个编码中删除1位,从而精确地恢复相同的64位。
洛伦·佩希特尔

1

一块木板有64个正方形,可以用64位表示,显示一个正方形是否为空。如果正方形有一块,我们只需要一块信息。由于播放器+片段需要4位(如前所示),所以我们可以得到64 + 4 * 32 = 192位的当前状态。在当前回合中,您有193位。

但是,我们还需要对每件作品的合法动作进行编码。首先,我们计算每块棋子的合法移动次数,并在完整正方形的棋子标识符后附加很多位。我计算如下:

典当:前进,先转两圈,向前* 2,提升= 7位。您可以将第一个转弯和提升组合为一个位,因为它们不可能从同一位置发生,所以您有6个。Rook:7个垂直正方形,7个水平正方形= 14位Knight:8个正方形= 8位Bishop:2个对角线* 7 = 14位女王/王后:垂直7位,水平7位,对角线7位,对角线7位= 28位国王:周围8个正方形

这仍然意味着您需要根据当前位置映射目标正方形,但这(应该是)很简单的计算。

由于我们有16个棋子,4个白嘴鸦/骑士/主教和2个皇后/国王,所以这又是16 * 6 + 4 * 14 + 4 * 8 + 4 * 14 + 2 * 28 + 2 * 8 = 312位,总共总共505位。

至于可能移动所需的每位位数,可以对其进行一些优化,并减少位数,我只是使用简单的数字来处理。例如,对于滑动件,您可以存储它们可以移动多远,但这将需要额外的计算。

长话短说:当一个正方形被占用时,仅存储额外的数据(片段等),并且仅存储每个片段的最小位数以表示其合法移动。

EDIT1:忘记了对任何物品进行隆重和典当促销。这可以使具有明确位置的总数增加到557次移动(典当再增加3位,国王增加2位)


1

每块可以用4位表示(典当到国王,6种类型),黑/白= 12个值

板上的每个方块可以用6位(x坐标,y坐标)表示。

初始位置最多需要320位(32个,4 + 6位)

随后的每个移动都可以用16位(从位置,到位置,片段)表示。

Castling需要额外的16位,因为这是双重动作。

可以用4位中的4个备用值之一来表示皇后卒。

无需详细进行数学运算,与存储32 * 7位(预定义的数组)或64 * 4位(预定义的正方形分配)相比,这在第一步移动之后开始节省空间。

两侧移动10次后,所需的最大空间为640位

...但是再说一次,如果我们唯一地标识每个棋子(5位)并添加第六位用于标记女王典当兵,那么我们每个动作只需要棋子ID +位置即可。这会将计算结果更改为...

初始位置=最多384位(32件,6 + 6位)每步= 12位(到位,件编号)

然后在每侧移动10次后,所需的最大空间为624位


第二种选择具有额外的优势,即存储可以读取为12位记录,每个记录=位置和片段。首先通过零件先进入的事实来检测驾驶室。
史蒂夫·德考克斯

对于两次移动之间的时间,将计数器的x位添加到每个记录。对于安装的记录,这将被设置为0
史蒂夫德考克斯

这就是我要写的方法。一种优化是,对于标准游戏,您根本不需要编码初始位置-只需一点点说“这是标准游戏”就足够了。此外,卡斯蒂尔并不会采取双重行动-因为卡斯蒂尔的举动总是显而易见的,而且当给定的卡斯蒂尔国王出现一半时,车队只有一种有效的移动方式,那就是多余的信息。为了提升,您可以在典当移至最后一行后使用4位来指定它变为新的片段类型。
kyoryu

因此,对于典型的游戏,假设没有晋升,则10步之后您将达到121位。非典型游戏需要1位用于标志,件数* 10位以及另一位来指示第一位玩家。同样,每个动作只需要12位,因为给定方块上的棋子与游戏中的先前动作是隐式的。这可能比某些建议的方法效率低,但相当干净,对于“标准”游戏也相当有效。
kyoryu

@kyoro-我不敢相信每招可以击败12位-使用您的null进行标准设置的想法(我仍将使用12位设置为bin 0)-每边经过1000次移动后,这就是24012位3002字节(四舍五入)即使使用某种形式的压缩,您也需要通过声明字典为硬编码(或逻辑上可衍生的相同内容)来欺骗击败它
Steve De Caux

1

像Robert G一样,我倾向于使用PGN,因为它是标准的,并且可以由多种工具使用。

但是,如果我在远处的太空探测器上玩国际象棋AI,因此每一点都是珍贵的,这就是我要做的动作。稍后我将对初始状态进行编码。

这些举动不需要记录状态。解码器可以跟踪状态以及在任何给定时间点的合法移动。需要记录的所有举动是选择了各种法律替代方法中的哪一种。由于玩家交替,因此移动无需记录玩家的颜色。由于玩家只能移动自己的颜色块,因此第一个选择是玩家移动哪个颜色块(我将在后面讨论使用另一种选择的替代项)。最多16个片段,最多需要4位。随着玩家输掉棋子,选择的数量会减少。同样,特定的游戏状态可能会限制棋子的选择。如果国王不能动弹不得,则选择的数量会减少一个。如果国王处于支配地位,那么任何无法使国王脱离支配地位的选择都不是可行的选择。

一旦指定了作品,它将只有一定数量的合法目的地。确切的数字高度取决于棋盘的布局和游戏历史,但我们可以算出某些最大值和期望值。对于除骑士以外的所有其他人,在铸造期间,一块都不能穿过另一块。这将是移动限制的重要来源,但很难量化。一块棋子不能移动,这也将在很大程度上限制目的地的数量。

我们通过按以下顺序沿线对正方形编号来编码大多数片段的目的地:W,NW,N,NE(黑色面为N)。一条线从给定方向上最远的正方形开始,可合法移动并朝该方向前进。对于不设防的国王,举动列表为W,E,NW,SE,N,S,NE,SW。对于骑士,我们从2W1N开始,然后顺时针方向前进;destination 0是此顺序中的第一个有效目的地。

  • 棋子:静止的棋子有2个目的地选择,因此需要1位。如果一个pawn可以捕获另一个正常的或随便的(由于解码器可以跟踪状态,所以它可以确定),它也可以选择2或3个动作。除此之外,典当只能有1个选择,不需要任何位。当典当排在第7 位时,我们也会选择晋升选择。由于典当通常被提升为皇后,随后是骑士,因此我们将选择编码为:
    • 皇后:0
    • 骑士10
    • 主教:110
    • 车队:111
  • 主教:最多{d,e} {4,5}时有13位,为4位。
  • Rook:最多14个目的地,4位。
  • 骑士:最多8个目的地,3位。
  • 国王:如果选择板球,国王会回到S,并且不能向下移动;这样一共有7个目的地。在其余时间中,国王最多有8步动作,最多给出3位。
  • 女王:与主教或菜鸟选项相同,共有27种选择,即5位

由于选择的数目并不总是2的幂,因此以上内容仍然浪费比特。假设选择的数量为C,特定的选择编号为c,并且n = ceil(lg(C))(编码选择所需的位数)。我们通过检查下一个选择的第一位来利用这些原本浪费的值。如果为0,则不执行任何操作。如果它是1并且c + C <2 n,则将C添加到c。解码数字会反过来:如果接收到的c > = C,则减去C并将下一个数字的第一位设置为1。如果 Ç<2n - C,然后将下一个数字的第一位设置为0。如果2 n - C <= c < C,则不执行任何操作。将此方案称为“饱和”。

可能会缩短编码的另一种潜在选择类型是选择要捕获的对手棋子。这样就增加了移动的第一部分的选择数量,最多只能多选一点(确切的数量取决于当前玩家可以移动多少块)。选择该选项后,选择了一个抓取件,这可能比任何给定玩家件的移动次数小得多。一件武器只能从任何基本方向上被一件东西攻击,再加上骑士最多只能攻击10件。尽管我希望平均可以得到7位,但这总共最多可以捕获9位。由于女王通常会拥有很多合法目的地,因此这对于女王捕获特别有利。

由于饱和,捕获编码可能无法提供优势。我们可以允许这两个选项,在初始状态中指定要使用的选项。如果不使用饱和度,则游戏编码也可以使用未使用的选择编号(C <= c <2 n)在游戏过程中更改选项。每当C是2的幂时,我们就无法更改选项。


1

托马斯(Thomas)有正确的方法来编码董事会。但是,这应该与ralu的移动存储方法相结合。列出所有可能的动作,写出表示此数字所需的位数。由于解码器正在执行相同的计算,因此它知道有多少种可能并且可以知道要读取多少位,因此不需要长度代码。

因此,我们获得164位棋子,获得4位投掷信息(假设我们正在存储游戏片段,否则可以对其进行重构),获得3位获得合格资格信息-只需存储发生移动的列(如果无法传递,则在不可能的地方存储一列(此类列必须存在),并为要移动的人存储1列。

移动通常需要5或6位,但范围从1到8。

另一个快捷方式-如果编码以12个1位开始(无效的情况-甚至一个片段的一侧都不会有两个国王),则中止解码,擦掉面板并建立新游戏。下一位将是移动位。


1

算法应确定性地列举每次移动时所有可能的目的地。目的地数:

  • 2个主教,每个13个目的地= 26
  • 2个小车,每个14个目的地= 28
  • 2名骑士,每8个目的地= 16
  • 皇后区,27个目的地
  • King,8个目的地

在最坏的情况(枚举方式)下,8个爪子都可能成为皇后,因此使最多的目的地数9 * 27 + 26 + 28 + 16 + 8 = 321。因此,任何移动的所有目的地都可以用9位数字来枚举。

双方的最大移动数为100(如果我没记错,则不是国际象棋棋手)。因此,任何游戏都可以以900位记录。加上初始位置,可以使用6位数字记录每段,总计32 * 6 = 192位。再加上一位“谁先走”的记录。因此,可以使用900 + 192 + 1 = 1093位来记录任何游戏。


1

存储板状态

我想到的最简单的方法也是,首先要有一个8 * 8位的数组来表示每个棋子的位置(因此,如果有一个棋子,则为1;否则,则为0)。由于这是固定长度,因此我们不需要任何终止符。

接下来,按其位置顺序代表每个棋子。每块使用4位,这需要32 * 4位(总共128位)。这真的很浪费。

使用二叉树,我们可以在一个字节中表示一个棋子,在3中表示一个骑士,白嘴鸦和主教,在4中表示一个国王和王后。由于我们还需要存储棋子的颜色,因此需要额外的字节来存储如(原谅我,如果这是错误的,我之前从未详细讨论过霍夫曼编码):

  • 典当:2
  • 白嘴鸦:4
  • 骑士4
  • 主教:4
  • 国王5
  • 女王:5

鉴于总数:

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

使用固定大小的位乘以28位拍子。

所以我发现最好的方法是将其存储在8 2 + 100位数组中

8*8 + 100 = 164



存储移动
我们需要了解的第一件事是哪一块正在移动到哪里。假设板上最多有32个零件,并且我们知道每个零件是什么,而不是整数代表正方形,我们可以有一个整数代表零件偏移,这意味着我们只需要拟合32个可能的值即可代表片。

不幸的是,有各种特殊的规则,例如cast割或推翻国王并形成共和国(特里·普拉切特参考),因此在存储要移动的棋子之前,我们需要一点一点来表明它是否是特殊的棋子。

因此,对于每个正常移动,我们都有一个必要的1 + 5 = 6位。(1位类型,一块为5位)

在对零件编号进行解码之后,我们便知道了零件的类型,并且每个零件都应以最有效的方式表示其移动。例如(如果我的国际象棋规则很严格),一个棋子总共有4种可能的动作(向左,向右,向前移动一个,向前移动两个)。
因此,代表典当动作,我们需要'6 + 2 = 8'位。(初始移动标头为6位,移动为2位)

为皇后移动会更复杂,因为最好有一个方向(8个可能的方向,所以3个位),每个方向总共有8个可能的正方形移动(所以又有3个位)。因此,要表示移动皇后区就需要6 + 3 + 3 = 12位。

对我而言,最后一件事情是我们需要存储哪些球员转过身。这应该是一点(白色或黑色接下来要移动)



结果格式
因此文件格式如下所示

[64位]初始棋子位置
[最多100位]初始棋子[1位]玩家回合
[n位]移动

移动为
[1位]的移动类型(特殊或常规)
[n位]移动的详细信息

如果“移动”是正常移动,则“移动细节”看起来类似于
[5位]片段
[n位]特定片段的片段(通常在2到6位的范围内)

如果是特殊动作
,则应为整数类型,然后为任何其他信息(例如是否为castling)。我记不清特殊举动的数量,因此仅表明这是一种特殊举动(如果只有一个)就可以了


1

在初始板加后续动作的基本情况下,请考虑以下事项。

使用国际象棋程序为所有可能的移动分配概率。例如,对于e2-e4为40%,对于d2-d4为20%,依此类推。如果某些举动是合法的,但未被该程序考虑,则给他们一些低概率。使用算术编码保存选择的位置,该位置在第一个动作中为0到0.4之间,在第二个动作中为0.4到0.6之间,以此类推。

在另一侧做同样的事情。例如,如果e7-e5对e2-e4的响应有50%的机会,则编码数字将在0到0.2之间。重复直到游戏结束。结果可能是非常小的范围。查找具有该范围的最小底数的二进制分数。那是算术编码。

这比霍夫曼更好,因为可以将其视为小数位编码(在游戏结束时再加上一些舍入为整位)。

结果应该比霍夫曼更为紧凑,并且没有晋级,传球,第50条规则动作和其他细节的特殊情况,因为它们由象棋评估程序处理。

要重播,请再次使用国际象棋程序评估棋盘并为每一步分配所有概率。使用算术编码值来确定实际演奏了哪一步。重复直到完成。

如果您的国际象棋程序足够好,则可以使用两态编码器获得更好的压缩,该编码器根据黑白的移动来定义概率。在大约200多个州的最极端情况下,这会编码所有可能的国际象棋游戏的整个集合,因此不可行。

这是说达里乌斯已经写的东西的一种完全不同的方式,只是举例说明了算术编码的工作方式,还有一个使用现有国际象棋程序来帮助评估下一步行动可能性的真实示例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.