厳密な質問ではなく、より多くのパズル...
長年にわたり、私は新入社員の技術面接に数回携わってきました。 「Xテクノロジーを知っていますか」という標準的な質問をする以外に、私は彼らがどのように問題に取り組んでいるかについての感触を得ることも試みました。通常は、インタビューの前日に質問をメールで送信し、翌日には解決策が見つかることを期待しています。
多くの場合、結果は非常に興味深い-間違っているが興味深い-であり、特定のアプローチをとった理由を説明できるなら、その人はまだ私の推薦を受けるでしょう。
だから、Stack Overflowの視聴者に質問を投げかけると思った。
質問:チェスゲーム(またはそのサブセット)の状態をエンコードするために考えられる最もスペース効率の高い方法は何ですか?つまり、チェスボードにピースが合法的に配置されている場合、この初期状態の両方をエンコードしますそして、ゲーム内のプレイヤーによって行われたその後のすべての法的動き。
答えにコードは必要ありません。使用するアルゴリズムの説明だけです。
編集:ポスターの1つが指摘したように、私は移動の時間間隔を考慮しませんでした。オプションの追加として、それも考慮してください:)
EDIT2:追加説明のためだけに...エンコーダー/デコーダーはルールに対応していることを忘れないでください。本当に保存する必要があるのは、プレイヤーの選択だけです。それ以外はエンコーダー/デコーダーが認識しているとみなすことができます。
EDIT3:ここで勝者を選ぶのは難しいでしょう:)たくさんの素晴らしい答え!
更新:このトピックが大好きだったので、 プログラミングパズル、チェスポジション、ハフマンコーディング を書きました。これを読み通せば、ゲームの完全な状態を保存するonlyの方法は、完全な移動リストを保存することだと判断しました。理由を読んでください。そのため、ピースのレイアウトには問題を少し簡略化したバージョンを使用します。
この画像は、チェスの開始位置を示しています。チェスは8x8ボードで行われ、各プレイヤーは8個のポーン、2個のルーク、2人の騎士、2人の司教、1人のクイーン、1人の王で構成される16ピースの同一セットで始まります。
位置は通常、列の文字と行の番号として記録され、ホワイトの女王はd1になります。移動はほとんどの場合、 代数表記 で保存されます。これは明確であり、一般に必要な最小限の情報のみを指定します。このオープニングを検討してください:
次のように変換されます:
ボードは次のようになります。
プログラマーにとって重要な能力は、問題を正確かつ明確に特定するができることです。
それでは、何が欠けているか、あいまいなのでしょうか?結局のところ、たくさん。
最初に判断する必要があるのは、ゲームの状態を保存しているか、ボード上のピースの位置を保存しているかです。単にピースの位置をエンコードすることは一つのことですが、問題は「その後のすべての合法的な動き」です。この問題は、この時点までの動きを知ることについても何も述べていません。それは実際に私が説明するように問題です。
ゲームは次のように進行しました。
ボードは次のようになります。
白には castling のオプションがあります。この要件の一部は、キングと関連するルークが決して移動できないことです。そのため、キングまたは各サイドのルークが移動したかどうかを保存する必要があります。明らかに彼らが開始位置にいない場合、彼らはそれ以外の場合は指定する必要がある移動しました。
この問題に対処するために使用できるいくつかの戦略があります。
まず、6個の追加情報(ルークとキングごとに1個)を保存して、そのピースが移動したかどうかを示します。適切なピースがその中にある場合、これらの6つの正方形の1つにビットを保存するだけで、これを合理化できます。代わりに、移動していない各ピースを別のピースタイプとして扱うことができるため、各側(ポーン、ルーク、ナイト、ビショップ、クイーン、キング)の6ピースタイプではなく、8(移動していないルークと非移動キングを追加)があります。
チェスのもう1つの独特でしばしば無視されるルールは、 En Passant です。
ゲームが進行しました。
B4の黒のポーンには、b4のポーンをc3に移動して、c4の白のポーンを取るオプションがあります。これは最初の機会でのみ発生します。つまり、ブラックがオプションをパスした場合、次の行動を取ることはできません。したがって、これを保存する必要があります。
前の動きを知っていれば、En Passantが可能かどうかは間違いなく答えることができます。あるいは、4ランクの各ポーンが前方に2回移動してそこに移動したかどうかを保存できます。または、ボード上のEn Passantの可能性のある各位置を確認し、可能かどうかを示すフラグを付けることができます。
ホワイトの動きです。ホワイトがポーンをh7からh8に移動した場合、それは他のピースに昇格できます(キングは昇格できません)。 99%の確率で女王に昇格しますが、そうでない場合があります。通常、そうしないと勝ち目が止まってしまいます。これは次のように書かれています。
これは私たちの問題で重要です。なぜなら、それぞれの側に固定された数のピースがあるとは思えないからです。 8つのポーンがすべて昇格した場合、一方の側が9人のクイーン、10人のルーク、10人の司教、または10人の騎士になることは完全に可能です(しかし、信じられないほどありません)。
あなたが最高の戦術に勝てない立場にいるときは、 stalemate を試すことです。最も可能性の高いバリアントは、合法的な動きができない場合です(通常、キングをチェックするときに動きがあるため)。この場合、引き分けを要求できます。これは簡単に対応できます。
2番目のバリエーションは、 threefold repeat によるものです。ゲーム内で同じボード位置が3回発生した場合(または次の動きで3回目に発生する場合)、引き分けを請求できます。位置は特定の順序で発生する必要はありません(同じ動きのシーケンスを3回繰り返す必要がないことを意味します)。これは、以前のボードの位置をすべて覚えておく必要があるため、問題を非常に複雑にします。 これが問題の要件である場合、問題に対する唯一の可能な解決策は、以前のすべての動きを保存することです。
最後に、 50移動ルール があります。プレーヤーは、ポーンが移動せず、前の50の連続した動きで駒が取られなかった場合、引き分けを要求できます。したがって、ポーンが動かされてからの動きの数または駒が取られた後(2つのうちの最新のもの) 6ビット(0-63)。
もちろん、誰の番であるかを知る必要もあり、これはほんの少しの情報です。
膠着状態のケースのため、ゲームの状態を保存する唯一の実行可能または賢明な方法は、この位置に至ったすべての動きを保存することです。その1つの問題に取り組みます。ボードの状態の問題は、これに単純化されます。キャスリング、一時停止、膠着状態、およびターンが誰であるかを無視して、ボード上のすべてのピースの現在の位置を保存。
ピースのレイアウトは、2つの方法のいずれかで幅広く処理できます。各正方形の内容を保存するか、各ピースの位置を保存します。
6つのピースタイプ(ポーン、ルーク、ナイト、ビショップ、クイーン、キング)があります。各ピースは白または黒であるため、正方形には12個の可能性のあるピースのいずれかが含まれるか、空の場合は13個の可能性があります。 13は4ビット(0〜15)で保存できます。したがって、最も簡単な解決策は、各平方時間64平方または256ビットの情報に対して4ビットを保存することです。
この方法の利点は、操作が信じられないほど簡単で速いことです。これは、ストレージ要件を増やすことなく、さらに3つの可能性を追加することで拡張できます。最後のターンに2スペースを移動したポーン、移動していないキング、移動していないルーク、多くに対応します前述の問題の。
しかし、私たちはもっとうまくやることができます。
ベース13エンコーディング
多くの場合、ボードの位置を非常に大きな数字と考えると役立ちます。これはコンピューターサイエンスでよく行われます。たとえば、 halting problem は、コンピュータープログラムを(正しい)多数として扱います。
最初のソリューションでは、位置を64桁の基数16として扱いますが、実証されているように、この情報には冗長性があり(「桁」ごとに3つの未使用の可能性があるため)、数値スペースを64基の13桁に減らすことができます。もちろん、これはベース16ほど効率的に行うことはできませんが、ストレージ要件を節約できます(そしてストレージスペースを最小限にすることが目標です)。
10を基数とする234は2 x 10に相当します2 + 3 x 101 + 4 x 10。
ベース16では、数値0xA50は10 x 16に相当します2 + 5 x 161 + 0 x 16 = 2640(10進数)。
したがって、位置をpとしてエンコードできます。 x 1363 + p1 x 1362 + ... + p63 x 13 ここで、p私 正方形の内容を表しますi。
2256 約1.16e77です。 1364 約1.96e71に相当し、237ビットのストレージスペースが必要です。わずか7.5%の節約では、大幅に操作コストが増加します。
法律上の委員会では、特定の正方形に特定のピースを表示できません。たとえば、ポーンは1番目または8番目のランクでは発生できないため、これらのスクエアの可能性は11に減少します。16 x 1348 = 1.35e70(概算)、233ビットのストレージスペースが必要です。
実際、このような値を10進数(または2進数)との間でエンコードおよびデコードすることはもう少し複雑ですが、確実に行うことができ、読者の練習として残されています。
前の2つの方法は、両方とも固定幅のアルファベットエンコーディングとして説明できます。アルファベットの11、13、または16の各メンバーは、別の値に置き換えられます。各「文字」は同じ幅ですが、各文字が等しくない可能性があると考えると、効率を改善できます。
モールス符号 (上図)を検討してください。メッセージ内の文字は、ダッシュとドットのシーケンスとしてエンコードされます。これらのダッシュとドットは、無線で(通常)転送され、それらの間に区切りが置かれます。
文字E( 英語 で最も一般的な文字)が単一のドットであり、最短のシーケンスであるのに対し、Z(最低頻度)は2つのダッシュと2つのビープ音であることに注意してください。
このようなスキームは、expectedメッセージのサイズを大幅に削減できますが、ランダムな文字シーケンスのサイズを増やすという代償を伴います。
モールス符号には別の組み込み機能があることに注意してください:ダッシュは3ドットまでなので、上記のコードはダッシュの使用を最小限に抑えるために作成されています。 1と0(ビルディングブロック)にはこの問題がないため、複製する必要のある機能ではありません。
最後に、モールス符号には2種類の休符があります。短い休符(ドットの長さ)は、ドットとダッシュを区別するために使用されます。より長いギャップ(ダッシュの長さ)は、文字を区切るために使用されます。
では、これは私たちの問題にどのように当てはまりますか?
Huffman coding と呼ばれる可変長コードを処理するアルゴリズムがあります。ハフマン符号化は、可変長のコード置換を作成します。通常、シンボルの予想される頻度を使用して、より一般的なシンボルに短い値を割り当てます。
上記のツリーでは、文字Eは000(または左から左)としてエンコードされ、Sは1011です。このエンコードスキームがnambiguousであることは明らかです。
これは、モールス信号との重要な違いです。モールス符号には文字区切り記号があるため、あいまいな置換(たとえば、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(), "");
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);
}
}
}
1つの可能な出力は次のとおりです。
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ビットに相当します。
もう1つの可能なアプローチは、最初のアプローチとハフマンコーディングを組み合わせることです。これは、(ランダムに生成されたものではなく)最も予想されるチェス盤が、少なくとも部分的には開始位置に似ている可能性が高いという仮定に基づいています。
だからあなたはXOR 256ビットの開始位置を持つ256ビットの現在のボード位置とそれをエンコードします(ハフマンコーディングまたは ラン長encoding )。明らかにこれは非常に効率的に開始できます(おそらく64ビットに対応する64 0)が、ゲームの進行に応じて必要なストレージが増加します。
前述のように、この問題を攻撃する別の方法は、プレイヤーが持っている各ピースの位置を代わりに保存することです。これは、ほとんどの正方形が空になるエンドゲームの位置で特にうまく機能します(ただし、ハフマンコーディングアプローチでは、空の正方形はとにかく1ビットしか使用しません)。
各サイドにはキングと0〜15個のピースがあります。プロモーションのため、これらのピースの正確な構成は十分に異なる可能性があるため、開始位置に基づく数値が最大であるとは想定できません。
これを論理的に分割するには、2つのサイド(白と黒)で構成されるポジションを保存します。各サイドには次のものがあります。
ポーンの場所については、ポーンは48個の可能な正方形(他のような64個ではない)にのみ配置できます。そのため、ポーンごとに6ビットを使用する場合に使用される余分な16個の値を無駄にしない方が良いでしょう。したがって、8つのポーンがある場合は488 28,179,280,429,056に等しい可能性。その数の値をエンコードするには45ビットが必要です。
片側105ビット、または合計210ビットです。ただし、この方法の場合、開始位置は最悪の場合であり、ピースを削除すると大幅に改善されます。
48未満であることに注意してください8 ポーンがすべて同じ正方形にあるわけではないため、可能性があります最初は48の可能性、2番目は47などです。 48 x 47 x…x 41 = 1.52e13 = 44ビットストレージ。
これをさらに改善するには、他のピース(反対側を含む)が占める正方形を削除して、最初に白い非ポーン、次に黒い非ポーン、次に白いポーン、最後に黒いポーンを配置できるようにします。開始位置では、これによりストレージ要件が白で44ビット、黒で42ビットに削減されます。
別の可能な最適化は、これらのアプローチのそれぞれに長所と短所があるということです。たとえば、最適な4を選択し、最初の2ビットでスキームセレクターをエンコードし、その後にスキーム固有のストレージをエンコードできます。
オーバーヘッドが小さいため、これが断然最良のアプローチです。
positionではなくgameを保存する問題に戻ります。 3回繰り返されるため、この時点までに発生した移動のリストを保存する必要があります。
判断しなければならないことの1つは、単に移動のリストを保存するだけなのか、それともゲームに注釈を付けるのかということです。チェスのゲームには、多くの場合、注釈が付けられています。例:
白の動きは2つの感嘆符によって華麗であるとマークされますが、黒の動きは間違いと見なされます。 チェスの句読点 を参照してください。
さらに、動きを説明するときにフリーテキストを保存する必要もあります。
私は、動きが十分であるので、注釈がないと仮定しています。
ここに移動のテキスト(「e4」、「Bxb5」など)を保存するだけです。終了バイトを含めると、1回の動きにつき約6バイト(48ビット)を見ています(最悪の場合)。それは特に効率的ではありません。
2つ目は、開始位置(6ビット)と終了位置(6ビット)を保存して、1移動あたり12ビットにすることです。それはかなり良いです。
あるいは、現在の位置からのすべての合法的な動きを、予測可能で決定的な方法で決定し、選択した状態にすることができます。これは、上記の変数ベースエンコーディングに戻ります。白と黒には、最初の動きでそれぞれ20の可能な動きがあり、2番目の動きではさらに多くの動きがあります。
この質問に対する絶対的な正しい答えはありません。上記はほんの数例ですが、多くの可能なアプローチがあります。
この問題や類似の問題で気に入っているのは、使用パターンの検討、要件の正確な決定、コーナーケースの検討など、プログラマーにとって重要な能力が要求されることです。
人間が読める標準形式でチェスゲームを保存するのが最善です。
Portable Game Notation は、標準の開始位置( する必要はありません ですが)を想定し、順番に順番に動きをリストします。人間が読めるコンパクトな標準形式。
例えば。
[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
小さくしたい場合は、 zipするだけ です。仕事完了!
素晴らしいパズル!
ほとんどの人が各作品の位置を保存しているようです。もっと単純なアプローチを取り、各正方形の内容を保存してください?これにより、プロモーションとキャプチャされたピースが自動的に処理されます。
そして、ハフマン符号化が可能です。実際、ボード上のピースの最初の頻度はほぼ完璧です。正方形の半分は空で、残りの正方形の半分はポーンなどです。
各作品の頻度を考慮して、紙に ハフマンツリー を作成しましたが、ここでは繰り返しません。結果、c
は色(白= 0、黒= 1)を表します。
当初の状況における取締役会全体については、
合計:164ビットinitialボード状態の場合現在投票されている最高回答の235ビットよりも大幅に少ない。そして、ゲームが進行するにつれて小さくなります(プロモーションの後を除く)。
ボード上のピースの位置だけを見ました。追加の状態(誰が転向したか、誰がキャスティングしたか、通行人、動きを繰り返すかなど)は、個別にエンコードする必要があります。おそらく最大16ビットなので、ゲームの状態全体で180ビットです。可能な最適化:
c
ビットを持たない追加のシンボルを導入することでエンコードできます。 (司教に昇格したポーンはこのスキームを混乱させます...)位置-18バイト
法定ポジションの推定数は 1043
それらをすべて列挙するだけで、位置はわずか143ビットで保存できます。どちらの側が次にプレイするかを示すには、もう1ビット必要です。
列挙はもちろん実用的ではありませんが、少なくとも144ビットが必要であることを示しています。
Moves-1バイト
通常、各ポジションには約30〜40のリーガルムーブがありますが、その数は218に達する場合があります。各ポジションのリーガルムーブをすべて列挙しましょう。これで、各動きを1バイトにエンコードできます。
辞任を表す0xFFなどの特別な移動の余地はまだ十分にあります。
初期位置がエンコードされた後のステップをエンコードするサブ問題を攻撃します。アプローチは、ステップの「リンクリスト」を作成することです。
ゲームの各ステップは、「古い位置->新しい位置」のペアとしてエンコードされます。チェスゲームの開始時の初期位置を知っています。リンクされたステップのリストを走査することにより、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ビット+(移動されるもののタイプに基づいた可変ビット数)。
各位置で、可能なすべての動きの数を取得します。
次の動きは
index_current_move =n % num_of_moves //this is best space efficiency
n=n/num_of_moves
ランダムに生成されたゲームを保存するための最適なスペース効率。30〜40の可能な動きがあるため、平均で約5ビット/移動が必要です。ストレージの組み立ては、逆の順序でnを生成するだけです。
保管場所は、冗長性が高いため、割れにくいです。 (1つのサイトに最大9人のクイーンを乗船させることができますが、その場合ポーンはなく、ボード上にある場合は司教は反対の色の正方形にいます)が、一般的に残りの正方形に同じピースの組み合わせを保存するようなものです)
編集:
移動の保存のポイントは、移動のインデックスのみを保存することです。 Kc1-c2を保存してこの情報を削減しようとする代わりに、決定論的なmovegenerator(position)から生成された移動のインデックスのみを追加する必要があります。
移動するたびに、サイズの情報を追加します
num_of_moves = get_number_of_possible_moves(postion) ;
プール内でこの数を減らすことはできません
情報プールの生成は
n=n*num_of_moves+ index_current_move
追加
最終位置で利用可能な動きが1つしかない場合は、以前に実行した強制的な動きの数として保存します。例:開始位置に各サイドに1つの強制移動(2つの移動)があり、これを1つの移動ゲームとして保存する場合、プールnに1を格納します。
情報プールに保存する例
既知の開始位置があり、3つの動きをすると仮定します。
最初の移動では5つの使用可能な移動があり、移動インデックス4が使用されます。2番目の移動では6つの使用可能な移動があり、位置インデックス3が使用されます。 2。
ベクトル形式;インデックス= [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) ansdスペースの複雑さはO(n)です。編集:時間の複雑さは実際にはO(n ^ 2)です。ただし、10,000ムーブまでのゲームの保存に問題はないはずです。
保存位置
最適に近い状態で行うことができます。
情報について知り、情報を保存するとき、それについてもっと話させてください。一般的な考え方は、冗長性を減らすことです(これについては後で説明します)。プロモーションもテイクもなかったと仮定して、ポーンが8つ、ルークが2つ、騎士が2つ、司教が2人、キングが1人、クイーンが1人です。
私たちは何を救わなければならないのか:1.各平和の位置2.キャスティングの可能性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)など。
両方のサイトでこれを乗算すると、4634726695587809641192045982323285670400000の約142ビットが得られます。1つの可能性のある通行人に8を追加する必要があります(通行人のポーンは8か所のいずれかにあります)、キャスティングの制限には16(4ビット)移動したサイト用に1ビット。最終的に142 + 3 + 4 + 1 = 150bitsになります
しかし、32個のピースを持ち、テイクなしでボード上の冗長性を探しましょう。
黒と白のポーンは同じ列にあり、互いに向き合っています。各ポーンは他のポーンに面しているため、白いポーンは最大で6位になります。これにより、情報が56ビット減少するC(64/8)* C(48/8)ではなく、8 * C(6/2)が得られます。
キャスティングの可能性も冗長です。ルークが出発地にない場合、そのルークのキャスティングの可能性はありません。したがって、このルークが可能であればキャスティングが可能であれば追加の情報を得るためにボード上に4つの正方形を追加して、4つのキャスティングビットを削除できます。したがって、C(56/2)* C(40/2)* 16の代わりにC(58/2)* C(42/2)があり、3.76ビット(ほぼ4ビットすべて)が失われました。
en-passant:8つのen passant possibilitesの1つを格納するとき、黒のポーンの位置を知って情報の冗長性を減らします(白い動きで、3番目のポーンen-passantがある場合は、c5に黒のポーンがあり、白のポーンはどちらかですc2、c3またはc4)なので、C(6/2)が3ビットあり、2.3ビットが失われました。 (3つの可能性->左、右、両方)行われ、私たちは通り過ぎることができるポーンの位置を知っています(たとえば、左、右、または両方にあることができるc5に黒である前の横道の例から)。 C(6/2)であり、1.3ビット削減する)また、両側で4.2ビット削減する場合は、通過ごとに2.3 + 1.3 = 3.6ビット削減できます。
ビショップ:ビソップはオポタイト広場にのみ配置できます。これにより、サイトごとに冗長性が1ビット削減されます。
まとめると、150-56-4-3.6-2 = テイキングがなかった場合、チェスの位置を保存するための85ビットが必要です。
そして、アカウントに撮影やプロモーションがある場合はおそらくそれほど多くはありません(しかし、誰かがこの長い投稿が便利だと思うなら、私は後でそれについて書きます)
最悪のケースではなく、人間がプレイする典型的なゲームのaverage-caseサイズに最適化することに関心を追加します。 (問題文はどちらを言っているのか、ほとんどの応答は最悪のケースを想定している。)
移動シーケンスでは、優れたチェスエンジンに各位置からの移動を生成させます。品質のランキング順に並べられたkの可能な動きのリストを作成します。一般に、人々はランダムな動きよりも良い動きを選ぶことが多いため、リスト内の各位置から、人々が「良い」動きを選ぶ確率へのマッピングを学習する必要があります。これらの確率(インターネットチェスデータベースのゲームのコーパスに基づく)を使用して、動きを 算術コーディング でエンコードします。 (デコーダーは同じチェスエンジンとマッピングを使用する必要があります。)
開始位置については、ラルのアプローチが機能します。選択肢を確率で重み付けする方法があれば、そこに算術コーディングを使用してそれを改良することもできます。断片は、ランダムではなく、お互いを防御する構成で表示されることがよくあります。その知識を組み込む簡単な方法を見つけるのは困難です。 1つのアイデア:代わりに、上記のムーブエンコーディングに戻って、標準の開始位置から開始し、目的のボードで終わるシーケンスを見つけます。 (最終位置からのピースの距離の合計に等しいヒューリスティック距離、またはそれらの線に沿った何かでA *検索を試してみてください。)これは、移動シーケンスを過剰に指定することによる非効率とチェスプレイの利用による効率のトレードオフです。知識。 (A *検索で以前に探索された位置につながる移動の選択肢を排除することにより、非効率性の一部を引き戻すことができます。これらは算術コードで重み0を取得できます。)
また、実際のコーパスから統計を収集することなく、平均的なケースの複雑さの中でどれだけの節約が得られるかを見積もることも少し難しいです。しかし、すべての動きの出発点は、おそらくここでの提案の大部分をすでに打ち負かしていると思います:算術コーディングは、動きごとに整数のビット数を必要としません。
昨夜この質問を見て、興味をそそられたので、解決策を考えてベッドに座りました。私の最終的な答えは、実際にはint3によく似ています。
標準のチェスゲームで、ルールをエンコードしないと仮定すると(ホワイトが常に最初に行くように)、各ピースの動きだけをエンコードすることで大幅に節約できます。
合計32個の駒がありますが、移動するたびにどの色が動いているかがわかるので、心配する必要があるのは16マスだけです。これは4ビットです。
各ピースには限定されたムーブセットのみがあり、何らかの方法で列挙できます。
プロモーションには、4つのピース(ルーク、ビショップ、ナイト、クイーン)から選択できるため、その動きに2ビットを追加して指定します。他のすべてのルールは自動的にカバーされると思います(例:一時的)。
最初に、1つの色の8個がキャプチャされた後、ピースエンコードを3ビットに減らし、次に4個のピースに対して2ビットを減らすことができます。
ただし、主な最適化は、ゲームの各ポイントで可能な動きを列挙するだけですonly。 Pawnの動きを{00, 01, 10, 11}
として保存し、左に1歩、左に2歩、右に斜め2歩それぞれ格納するとします。一部の移動が不可能な場合は、このターンのエンコードからそれらを削除できます。
私たちはすべての段階でゲームの状態を知っているので(すべての動きを追うことにより)、どのピースが動くのかを読んだ後、いつ読む必要があるかをいつでも判断できます。この時点でポーンの唯一の動きが右斜めにキャプチャされているか、前方に移動していることに気付いた場合、1ビットしか読み取らないことがわかります。
つまり、各ピースの上記のビットストレージは、maximumのみです。ほぼすべての動きにオプションが少なくなり、多くの場合ビットが少なくなります。
ボード上の位置は7ビットで定義できます(0〜63、およびボード上にないことを指定する1つの値)。したがって、ボード上のすべてのピースに対して、それが配置されている場所を指定します。
32個* 7ビット= 224ビット
編集:カドリアンが指摘したように...我々はまた、「クイーンへのポーンの促進」のケースを持っています。どのポーンがプロモートされたかを示すために、最後に余分なビットを追加することをお勧めします。
したがって、プロモートされたポーンごとに、プロモートされたポーンのインデックスを示す5ビットで224ビットに従い、リストの最後の場合は11111に従います。
したがって、最小限のケース(プロモーションなし)は224ビット+ 5(プロモーションなし)です。プロモートされたポーンごとに5ビットを追加します。
編集:毛むくじゃらのカエルが指摘しているように、最後にもう1つビットが必要です。
ほとんどの人はボードの状態をエンコードしていますが、動き自体については..ここにビットエンコードの説明があります。
1個あたりのビット:
すべてのピースがボード上にあると仮定すると、これらは動きごとのビットです。ポーン-最初の動きで6ビット、続いて5ビット。昇格した場合は7。ビショップ:9ビット(最大)、ナイト:7、ルーク:9、キング:7、クイーン:11(最大)。
問題は、典型的なチェスのゲームに最も効率的なエンコーディングを与えることですか、それとも最悪の場合のエンコーディングを最短にするエンコーディングですか?
後者の場合、最も効率的な方法は最も不透明です:可能なすべてのペア(初期ボード、正当な一連の動き)の列挙を作成します。 -pawn-move-or-captureルール以降の-fifty-movesは再帰的です。次に、この有限シーケンス内の位置のインデックスは、最短の最悪の場合のエンコーディングを提供しますが、典型的な場合は同様に長いエンコーディングを提供します。可能な限り長いチェスゲームは5000を超える動きであると想定されており、通常、各プレイヤーは各ポジションで20〜30の動きを利用できます(残りのピースが少ない場合は少なくなります)-これにより、このエンコードに必要な40000ビットのようなものが得られます.
上記のHenk Holtermanのエンコードの動きに関する提案で説明されているように、列挙の考え方を適用して、より扱いやすいソリューションを提供できます。私の提案:最小限ではなく、私が見た上記の例よりも短く、合理的な扱いやすい:
占有されている正方形を表す64ビット(占有マトリックス)、および占有されている各正方形に含まれるピースのリスト(ポーンに3ビット、他のピースに4ビットを使用可能):これにより、開始位置に190ビットが与えられます。ボードには32個を超えることはできないため、占有マトリックスのエンコードは冗長です。したがって、一般的なボード位置のようなものをエンコードできます。たとえば、33セットのビットと共通ボードリストのボードのインデックスです。
誰が先手を打ったかを言う1ビット
Henkの提案によるコードの移動:通常、白/黒の移動のペアごとに10ビット。ただし、プレーヤーに代替の移動がない場合、一部の移動には0ビットがかかります。
これは、典型的な30移動ゲームをコーディングするために490ビットを示唆しており、典型的なゲームでは合理的に効率的な表現となります。
最後のポーンの移動またはキャプチャのルール以降、3回繰り返しの描画位置と50を超えない移動のエンコードについて:過去の移動を最後のポーンの移動またはキャプチャにエンコードする場合、これらのルールが適用されるかどうかを判断するのに十分な情報があります。ゲーム全体の履歴は不要です。
計算時間が問題にならない場合は、特定の位置に一意のIDを割り当てるために、決定論的な可能な位置ジェネレーターを使用できます。
与えられた位置から、まず決定論的マナーで可能な位置の数を生成します。左下から右上に移動します。これにより、次の移動に必要なビット数が決まります。状況によっては、1ビットでもかまいません。次に、移動が行われると、その移動の一意のIDのみが格納されます。
プロモーションやその他のルールは、確定的な方法で処理されている限り、有効な動きとしてカウントされます。女王に、ルークに、司教にそれぞれの動きとしてカウントします。
最初の位置は最も難しく、約2億5千万の可能性のある位置(私が思うに)を生成する可能性があります。
ターンの順番がわかっていると仮定すると(各ターンは白から黒に反転します)、決定論的ジェネレーターは次のようになります。
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の動きのリスト」はそれぞれ、チェック状況を変更する有効なムーブのみを返します。
書籍や論文でゲームをエンコードするように、すべてのピースにはシンボルがあります。 「合法的な」ゲームなので、白が最初に動きます-白または黒を別々にエンコードする必要はなく、動きの数を数えるだけで誰が動いたかを判断します。また、すべての動きは(ピース、終了位置)としてエンコードされ、「終了位置」は、あいまいさを識別できるようにするシンボルの最小量に削減されます(ゼロにすることもできます)。ゲームの長さは、動きの数を決定します。また、すべてのステップで(最後の移動から)分単位で時間をエンコードすることもできます。
ピースのエンコードは、それぞれ(合計32)にシンボルを割り当てるか、クラスにシンボルを割り当てて、終了位置を使用して、どのピースが移動されたかを理解することによって実行できます。たとえば、ポーンには6つの可能な終了位置があります。しかし、平均して、毎ターンに数人しか利用できません。したがって、統計的には、このシナリオには終了位置によるエンコードが最適です。
同様のエンコーディングは、計算神経科学(AER)のスパイク列に使用されます。
欠点:現在の状態に到達してサブセットを生成するには、リンクリストをたどるのと同じように、ゲーム全体をリプレイする必要があります。
Huffman encoding を使用しようとします。これの背後にある理論は-すべてのチェスのゲームで、たくさん動き回るいくつかのピースがあり、いくつかはあまり動かないか、早く排除されます。開始位置の一部が既に削除されている場合-いっそう良い。同じことが正方形についても言えます-いくつかの正方形はすべてのアクションを見ることができますが、いくつかはあまり触れられません。
したがって、2つのハフマンテーブルがあります。1つはピース用、もう1つは正方形用です。実際のゲームを見ることで生成されます。ピースとスクエアのペアごとに1つの大きなテーブルを作成することもできますが、同じピースのインスタンスが同じスクエアを再び移動することはあまりないため、これは非常に効率が悪いと思います。
すべてのピースにはIDが割り当てられます。 32個の異なるピースがあるため、ピースIDには5ビットしか必要ありません。ピースIDはゲームごとに変わりません。同じことがスクエアIDにも当てはまります。これには6ビットが必要です。
ハフマンツリーは、各ノードが順番にトラバースされるときに各ノードを書き留めることによってエンコードされます(つまり、最初にノードが出力され、次にその子が左から右に出力されます)。すべてのノードには、リーフノードかブランチノードかを指定する1ビットがあります。リーフノードの場合、IDを示すビットが続きます。
開始位置は、一連のピースとロケーションのペアによって単純に指定されます。その後、移動ごとに1つのピースとロケーションのペアがあります。開始位置記述子の終わり(および移動記述子の始まり)は、2回言及されている最初の部分を見つけるだけで見つけることができます。ポーンがプロモートされる場合、それが何になるかを指定する余分な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:もう1つの特殊なケース。ゲームの状態に動きがない場合、次に動く人を区別することが重要になります。最後にもう1ビット追加します。 :)
ボードの位置は64個あるため、位置ごとに6ビットが必要です。初期ピースは32個あるため、これまでのところ合計192ビットで、6ビットごとに特定のピースの位置を示しています。ピースが表示される順序を事前に決定できるため、どちらがどちらであるかを言う必要はありません。
ピースがボードから外れている場合はどうなりますか?さて、別のピースと同じ場所にピースを配置して、ボードから外れていることを示すことができます。しかし、最初の作品がボードに載るかどうかもわかりません。したがって、最初の断片を示す5ビットを追加します(32個の可能性=最初の断片を表す5ビット)。次に、そのスポットを使用して、ボード外の後続のピースに使用できます。これにより、合計で197ビットになります。ボードには少なくとも1つのピースが必要です。
次に、その順番に1ビットが必要です。198ビットになります。
ポーンプロモーションはどうですか?ポーンごとに3ビットを追加し、42ビットを追加することで、悪い方法を実現できます。しかし、その後、ほとんどの場合、ポーンは昇格されないことに気付くことができます。
したがって、ボード上にあるポーンごとに、ビット「0」は昇格されていないことを示します。ポーンがボード上にない場合、少しは必要ありません。次に、彼が持っているプロモーションの可変長ビット文字列を使用できます。ほとんどの場合、それは女王になるので、「10」はQUEENを意味します。 「110」はルーク、「1110」は司教、「1111」は騎士を意味します。
16個のポーンはすべてボード上にあり、プロモーションされていないため、初期状態には198 + 16 = 214ビットが必要です。 2つのプローンポーンクイーンのあるエンドゲームは、198 + 4 + 4のようなものを取ります。つまり、4つのポーンはプロモートされていないが、2つのクイーンポーンは206ビット合計です。かなり堅牢なようです!
===
ハフマン符号化は、他の人が指摘したように、次のステップです。数百万のゲームを観察すると、各ピースが特定の正方形にある可能性がはるかに高いことに気付くでしょう。たとえば、ほとんどの場合、ポーンは直線のままか、左に1つ、右に1つです。王は通常、本拠地の周りに固執します。
したがって、個別の位置ごとにハフマン符号化方式を考案します。ポーンはおそらく6ではなく平均3〜4ビットしか取りません。キングも同様に数ビットを取ります。
また、このスキームでは、可能な位置として「とられた」を含めます。これにより、キャスティングも非常に堅牢に処理できます。ルークとキングにはそれぞれ、「元の位置に移動した」状態が追加されます。この方法でポーンにパッサントをエンコードすることもできます-「元の位置、パッサンができます」。
十分なデータがあれば、このアプローチは本当に良い結果をもたらすはずです。
回答のほとんどは3倍の繰り返しを見落としていました。残念ながら、3回繰り返しを行うには、これまでにプレイしたすべてのポジションを保存する必要があります...
この質問では、スペースの効率的な方法で保存する必要があったため、移動のリストから位置を構築できる限り、実際に位置を保存する必要はありません(標準の開始位置がある場合)。 PGNを最適化することができ、それで完了です。ベローはシンプルなスキームです。
ボードには64個の正方形があります。64= 2 ^ 6です。12ビットかかる各動きの最初と最後の正方形のみを保存する場合(プロモーションは後で取り組まれます)。このスキームは、プレーヤーの移動、注意喚起、駒の捕獲、キャスリングなどをすでにカバーしていることに注意してください。これらは、移動リストを再生するだけで構築できます。
プロモーションのために、「移動NでピースXYZにプロモート」と言うベクトルの別個の配列を保持できます。 (int、byte)のベクトルを保持できます。
これらの(To、From)ベクトルの多くはチェスでは使用できないため、(To、From)ベクトルも最適化するのは魅力的です。例えば。 e1からd8への移行などはありません。しかし、どのようなスキームも思いつきませんでした。さらにアイデアがあれば歓迎します。
[質問を適切に読んだ後に編集]すべての正当な位置に初期位置から到達できると仮定した場合(これは「合法」の定義である可能性があります)、任意の位置は最初からの一連の動きとして表現できます。非標準の位置から開始するプレイのスニペットは、開始に到達するために必要な一連の動き、カメラをオンにするスイッチ、それに続く動きとして表現できます。
それで、初期ボード状態をシングルビット「0」と呼びましょう。
任意の位置からの移動は、正方形に番号を付け、移動を(開始、終了)で順序付けることで列挙できます。従来の2つの正方形ジャンプはキャスティングを示します。ボードの位置とルールは常に既知であるため、違法な動きをエンコードする必要はありません。カメラの電源をオンにするフラグは、特別な帯域内移動として表現することも、帯域外移動番号としてより賢明に表現することもできます。
どちらの側にも24のオープニングムーブがあり、それぞれ5ビットに収まります。後続の移動には多少のビットが必要になる場合がありますが、正当な移動は常に列挙可能であるため、各移動の幅は喜んで拡大または拡大できます。計算していませんが、7ビットの位置はまれだと思います。
このシステムを使用すると、100個の半移動ゲームを約500ビットでエンコードできます。ただし、オープニングブックを使用するのが賢明かもしれません。 100万個のシーケンスが含まれているとします。次に、最初の0は標準ボードからの開始を示し、1に20ビットの数字が続くと、そのオープニングシーケンスからの開始を示します。多少慣習的な開口部を持つゲームは、たとえば20半動き、つまり100ビット短縮される可能性があります。
これは可能な限り最大の圧縮ではありませんが、(最初の本がなければ)チェスモデルが既にある場合は実装が非常に簡単です。
さらに圧縮するには、任意の順序ではなく尤度に従って動きを順序付けし、可能性のあるシーケンスをより少ないビットでエンコードします(たとえば、人々が言及したようにハフマントークンを使用します)。
ランレングスエンコーディングを使用します。一部のピースは一意である(または2回しか存在しない)ため、それらの後の長さは省略できます。クレタスのように、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つのポーンがある場合、「ポーン、1、空、16、空、16、空16、空、15」= 10ニブル= 40ビットになります。
最悪のケースは、各ピースの間に空の正方形がある場合です。しかし、ピースのエンコードには、16個の値のうち13個が必要なだけなので、別の値を使用して「Empty1」と言うことができます。次に、64ニブル== 128ビットが必要です。
動きについては、ピースに3ビット(白は常に最初に動くという事実によって与えられる)に加えて、新しい位置=動きごとに1バイトの5ビット(0..63)が必要です。ほとんどの場合、範囲内にあるのは1つのピースだけなので、古い位置は必要ありません。奇妙な場合、単一の未使用コード(ピースをエンコードするのに7つのコードが必要です)を使用し、古い位置に5ビット、新しい位置に5ビットを使用する必要があります。
これにより、キャスティングを13バイトでエンコードできます(キングをルークの方に移動できます。これで、意図を伝えることができます)。
[編集]スマートエンコーダーを許可する場合、初期設定に0ビットが必要です(何らかの方法でエンコードする必要がないため、静的です)。
[EDIT2]ポーン変換を残します。ポーンが最後の行に到達したら、それを「変換」と言う位置に移動し、置き換えられるピースに3ビットを追加できます(クイーンを使用する必要はありません。ポーンを何でも置き換えることができます)しかし、王)。
私は長い間(+-2時間)そのことについて考えてきました。そして、明らかな答えはありません。
想定:
...最新の最新ルールです。最初は繰り返しに関係なく、移動制限を移動します。
-C 25バイトを丸めた(64b + 32 * 4b + 5b = 325b)
= 64ビット(something/nothing)+ 32 * 4ビット[1bit = color {black/withe} + 3bit = pieceの種類{King、Queen、Bishop、kNight、Rook、Pawn、MovedPawn} NB:Moved pawn ...たとえば、前のターンで最後に移動したポーンで、「通行人」が実行可能であることを示している場合。 ]実際の状態は+5ビット(誰が順番を回っているか、通行人か、両側でルークするかどうか)
ここまでは順調ですね。おそらく強化することができますが、可変長とプロモーションを考慮する必要があります!?
現在、以下のルールは、プレイヤーが引き分けに応じる場合にのみ適用されます。ITIS自動ではありません!引き取りを呼び出さないプレイヤーがいない場合、この90の動きまたはポーンの動きは実現可能です。 !すべての動きを記録する必要があるという意味...そして利用可能。
-D位置の繰り返し...例上記のボードの状態(Cを参照)またはしない(FIDEルールに関する以下を参照)-Eキャプチャまたはポーンの移動なしで50の移動許容量という複雑な問題が残るため、カウンターが必要です...ただし。
それではどう対処しますか?...まあ、本当に方法はありません。どちらのプレイヤーも絵を描くことを望まないかもしれないし、それが起こったことに気付かないかもしれないからです。 Eの場合、カウンターで十分かもしれませんが、ここにトリックがあり、FIDEルール(http://www.fide.com/component/handbook/?id=124&view=article)を読むこともできません。答え...ルーキングの能力の喪失についてはどうですか。それは繰り返しですか?私はそうは思いませんが、これは明確ではない、対処されていないぼやけた主題です。
エンコードしようとしても2つの複雑な、または未定義の2つのルールがあります...乾杯。
したがって、ゲームを真にエンコードする唯一の方法は、最初からすべてを記録することです...それは、「ボード状態」の質問と競合する(またはしない?)ことです。
この助けを願っています...あまり数学ではありません:-)いくつかの質問がそれほど簡単ではなく、解釈や事前知識が正確で効率的であるためにあまりにも開かれていないことを示すだけです。ワームの缶を開きすぎるので、私はインタビューの対象とは考えません。
Yacobyのソリューションの開始位置の改善の可能性
各色の16個を超える法的地位はありません。 64個の正方形に最大16個の黒と16個の白のピースを配置する方法の数は、約3.63e27です。 Log2(3.63e27)= 91.55。これは、92ビットですべてのピースの位置と色をエンコードできることを意味します。これは、Yacobyのソリューションが必要とする位置の64ビット+色の最大32ビットよりも小さいです。エンコードがかなり複雑になる代わりに、最悪の場合は4ビット節約できます。
一方、5つ以上のピースが欠落しているポジションのサイズが大きくなります。これらの位置は、すべての位置の4%未満にすぎませんが、おそらく初期位置とは異なる開始位置を記録したい場合の大半です。
これにより完全なソリューションが得られます
ボードには32個があります。各ピースには位置があります(64マスに1マス)。したがって、必要なのは32個の正の整数だけです。
64ビットのポジションが6ビットで保持されることは知っていますが、それはしません。いくつかのフラグの最後のビットを保持します(ドロップされたピース、クイーンのポーン)
アルゴリズムは、各移動ですべての可能な宛先を確定的に列挙する必要があります。目的地の数:
8つの足はすべて最悪の(列挙型)場合に女王になる可能性があり、したがって、可能な最大数の宛先を9 * 27 + 26 + 28 + 16 + 8 = 321にします。したがって、すべての移動のすべての宛先は、9ビットの数値で列挙できます。
両当事者の移動の最大数は100です(私が間違っていなければ、チェスプレーヤーではありません)。したがって、任意のゲームを900ビットで記録できます。さらに、各ピースは6ビットの数値を使用して記録でき、合計で32 * 6 = 192ビットになります。さらに、「誰が最初に移動するか」レコード用の1ビット。したがって、900 + 192 + 1 = 1093ビットを使用して任意のゲームを記録できます。
cletus '答えは良いが、彼は誰の番であるかをエンコードするのを忘れた。これは現在の状態の一部であり、その状態を使用して検索アルゴリズム(アルファ-ベータ微分など)を駆動する場合に必要です。
私はチェスプレイヤーではありませんが、もう一つのコーナーケースがあると思います:何回の動きが繰り返されたか。各プレイヤーが同じ動きを3回行うと、ゲームは引き分けになりますよね?その場合、3回目の繰り返しの後、状態は最終状態であるため、その状態でその情報を保存する必要があります。
他のいくつかの人が言及したように、32個のピースのそれぞれについて、それらがどこにあるかを保存でき、ボード上にあるかどうかにかかわらず、これは32 *(log2(64)+ 1)= 224ビットになります。
ただし、ビショップは黒または白の正方形のみを占有できるため、これらの場合は位置にlog2(32)ビットのみが必要で、28 * 7 + 4 * 6 = 220ビットになります。
また、ポーンは後方からは開始せず、前方にしか移動できないため、56にしか配置できないため、この制限を使用してポーンに必要なビット数を減らすことができるはずです。
ボードの状態の保存
私が考えた最も簡単な方法は、最初に各ピースの位置を表す8 * 8ビットの配列を持つことです(チェスのピースがある場合は1、ない場合は0)。これは固定長なので、ターミネーターは必要ありません。
次に、その位置の順にすべてのチェスの駒を表します。ピースごとに4ビットを使用すると、32 * 4ビット(合計128)かかります。これは本当に無駄です。
バイナリツリーを使用すると、ポーンを1バイト、ナイトとルークとビショップを3、キングとクイーンを4で表すことができます。また、ピースの色を保存する必要があるため、余分なバイトが必要になりますas(これが間違っている場合はご容赦ください、以前に詳細に ハフマンコーディング を見たことはありません):
合計を考えると:
2*16 + 4*4 + 4*4 + 4*4 + 2*5 + 2*5 = 100
固定サイズのビットセットを使用して28ビットずつ勝ちます。
だから私が見つけた最良の方法は、8に保存することです2 + 100ビット配列
8*8 + 100 = 164
移動の保存
最初に知っておくべきことは、どのピースがどこに移動しているかです。ボードには最大32個のピースがあり、正方形を表す整数ではなく、各ピースが何であるかを知っているので、ピースオフセットを表す整数を持つことができます。つまり、ピース。
残念ながら、王を投げたり倒したりして共和国を形成するなど、さまざまな特別なルールがあります(テリープラチェットのリファレンス)。
したがって、通常の動きごとに必要な1 + 5 = 6
ビット。 (1ビットタイプ、ピース用に5ビット)
ピース番号がデコードされた後、ピースのタイプがわかり、各ピースは最も効率的な方法でその動きを表す必要があります。たとえば(チェスのルールがスクラッチの場合)、ポーンには合計4つの可能な動きがあります(左に移動、右に移動、1つ前方に移動、2つ前方に移動)。
したがって、ポーンの動きを表すには、「6 + 2 = 8」ビットが必要です。 (最初の移動ヘッダーには6ビット、移動には2ビット)
女王への移動はより複雑になります。方向(8つの可能な方向、つまり3ビット)と、各方向に移動する合計8つの可能な正方形(さらに3ビット)を持つことが最善であるという点です。したがって、女王の移動を表すには、6 + 3 + 3 = 12
ビット。
私が最後に思い浮かぶのは、どのプレイヤーがそれを有効にするかを保存する必要があるということです。これは単一ビットである必要があります(次に移動するには白または黒)
結果の形式
したがって、ファイル形式は次のようになります
[64ビット]初期ピースの場所
[最大100ビット]初期ピース[1ビット]プレイヤーのターン
[nビット]移動
移動の場所
[1ビット]移動タイプ(特殊または通常)
[nビット]詳細の移動
移動が通常の移動である場合、移動の詳細は次のようになります
[5ビット]個
[nビット]特定のピースの移動(通常2〜6ビットの範囲)
特別な動きの場合
整数型があり、その後に追加情報が必要です(キャスティングの場合など)。私は特別な動きの数を覚えていないので、それが特別な動きであることを示すだけでいいかもしれません(1つしかない場合)
これは、ゲームステップをエンコードする方法です。 40ステップのゲームの場合、これには約180ビット程度かかります。
最初に、すべてのチェスルールを知っているエンジンを使用して、すべての選択肢のリストを作成します。各ステップでこれを実行します。
これにより、次のようなリストが表示されます。
[[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
...
]
等々。エンコードするには、可能な動きの数ではなく、選択肢を保存するだけです。保存する1つの方法は、各選択に必要なビット数を調べることです。
[[10, 3], # 10 choices => 4 bits
[2, 0], # 2 choices => 1 bit
[10, 2], # 10 choices => 4 bits
[2, 1], # 2 choices => 1 bit
...
]
合計4+1+4+1=10
最初の2つの動きのビット。ただし、10ビットの選択に4ビットを使用すると、6ビットの選択が無駄になり、数ビットが無駄になります。
より良いことは可能です:リストを逆にし、可能な選択肢と選択された選択肢に基づいて数を計算します:
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
これで番号243
、バイナリ11110011
、上記のすべてのステップをわずか8ビットでエンコードします。
デコードするために、最初のオープニングポジションには10の選択肢があることがわかっています。計算する
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
エンコードは非常に効率的です。特に可能なのは選択肢があまりないためです。また、可能な移動が1つしか残っていない場合、その移動にストレージはまったく必要ありません。
各ピースは4ビット(キングにポーン、6種類)、黒/白= 12の値で表すことができます
ボード上の各正方形は、6ビット(x座標、y座標)で表すことができます。
初期位置には最大320ビット(32個、4 + 6ビット)が必要です
後続の各移動は、16ビット(from-position、to-position、piece)で表すことができます。
キャスリングは二重移動なので、追加の16ビットが必要になります。
クイーンのポーンは、4ビットのうちの4つのスペア値のいずれかで表すことができます。
詳細な計算を行わずに、32 * 7ビット(事前定義されたピースの配列)または64 * 4ビット(事前定義された正方形の割り当て)と比較して、最初の移動後にスペースを節約し始めます
両側で10回移動した後、必要な最大スペースは640ビットです
...しかし、各ピースを一意に識別し(5ビット)、クイーン化されたポーンにフラグを立てるために6ビット目を追加する場合は、各移動にpiece-id + to-positionのみが必要です。これにより計算が変更されます...
初期位置=最大384ビット(32個、6 + 6ビット)各移動= 12ビット(to-position、piece-id)
次に、各側で10回移動した後、必要な最大スペースは624ビットです
Thomasには、ボードをエンコードするための適切なアプローチがあります。ただし、これは、移動を保存するためのraluのアプローチと組み合わせる必要があります。すべての可能な動きのリストを作成し、この数を表現するために必要なビット数を書きます。デコーダは同じ計算を行っているため、可能なビット数と読み取るビット数を知ることができるため、長さコードは不要です。
したがって、ピースには164ビット、キャスリング情報には4ビット(ゲームのフラグメントを保存すると仮定し、そうでなければ再構築可能)、一時的な資格情報には3ビットを取得します-移動が発生した列を単に保存します(通行人が不可能な場合は、不可能な場所に列を保存します(そのような列が存在している必要があります)および1が移動する場所です。
移動には通常5または6ビットがかかりますが、1から8まで変化する可能性があります。
追加のショートカット-エンコードが12 1ビットで始まる場合(無効な状況-片側に2つのキングが存在しない場合でも)、デコードを中止し、ボードをワイプし、新しいゲームをセットアップします。次のビットは移動ビットになります。
ボードには64個の正方形があり、正方形が空かどうかを示す64ビットで表すことができます。正方形にピースがある場合にのみピース情報が必要です。プレーヤー+ピースは4ビットかかるため(前述のとおり)、現在の状態を64 + 4 * 32 = 192ビットで取得できます。現在のターンで投げると、193ビットがあります。
ただし、各ピースの法的動きもエンコードする必要があります。最初に、各ピースの正当な動きの数を計算し、完全な正方形のピース識別子の後にその数のビットを追加します。私は次のように計算しました:
Pawn:フォワード、最初のターン2フォワード、パッサント* 2、プロモーション= 7ビット。同じ位置からは発生しないため、最初のターンフォワードとプロモーションを1ビットに組み合わせることができます。したがって、6があります。ルーク:7垂直正方形、7水平正方形= 14ビットナイト:8正方形= 8ビットビショップ: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ビット)。
Robert Gのように、PGNは標準であり、幅広いツールで使用できるため、PGNを使用する傾向があります。
ただし、遠方の宇宙探査機に搭載されているチェスAIをプレイしているため、すべてのビットが貴重な場合は、これがムーブメントのために行うことです。後で初期状態をエンコードすることに夢中になります。
動きは状態を記録する必要はありません。デコーダーは、状態と、どの時点でも有効な動きを追跡できます。記録する必要があるすべての動きは、さまざまな法的選択肢のどれが選択されるかです。プレイヤーは交代するため、ムーブではプレイヤーの色を記録する必要はありません。プレイヤーは自分の色のピースのみを移動できるため、最初の選択肢はプレイヤーが移動するピースです(後で別の選択肢を使用する代替手段に戻ります)。最大16個の場合、これには最大4ビットが必要です。プレイヤーがピースを失うと、選択肢の数が減ります。また、特定のゲームの状態によって、ピースの選択が制限される場合があります。王が自分自身をチェックせずに移動できない場合、選択肢の数は1つ減ります。王がチェックインしている場合、キングをチェックアウトできないピースは実行可能な選択ではありません。 a1から始まる行の主要な順序でピースに番号を付けます(h1はa2の前に来ます)。
ピースが指定されると、一定数の正当な宛先のみが含まれます。正確な数は、ボードレイアウトとゲーム履歴に大きく依存しますが、特定の最大値と期待値を把握できます。騎士以外、キャスティング中は、駒は他の駒を通過できません。これは移動制限の大きな原因になりますが、定量化するのは困難です。ピースはボードから移動できません。これにより、宛先の数も大幅に制限されます。
次の順序で線に沿って正方形に番号を付けることにより、ほとんどのピースの宛先をエンコードします:W、NW、N、NE(黒側はN)。行は、指定された方向の最も遠い正方形から始まり、それに向かって移動し、それに向かって進むことができます。邪魔されない王の場合、動きのリストはW、E、NW、SE、N、S、NE、SWです。ナイトの場合、2W1Nから始めて時計回りに進みます。宛先0は、この順序で最初の有効な宛先です。
選択肢の数が常に2のべき乗であるとは限らないため、上記はまだビットを浪費しています。選択肢の数が[〜#〜] c [〜#〜]であり、特定の選択肢がcおよびn = ceilと番号付けされているとします(lg([〜#〜] c [〜#〜]))(選択肢をエンコードするのに必要なビット数)。次の選択肢の最初のビットを調べることにより、これらの無駄な値を利用します。 0の場合、何もしません。 1でc + [〜#〜] c [〜#〜] <2の場合n、次に[〜#〜] c [〜#〜]をcに追加します。数値をデコードすると、これが逆になります。受信したc> = [〜#〜] c [〜#〜]の場合、[〜#〜] c [ 〜#〜]そして次の番号の最初のビットを1に設定します。c <2の場合n-[〜#〜] c [〜#〜]、次の番号の最初のビットを0に設定します。2の場合n-[〜#〜] c [〜#〜] <= c <[〜#〜] c [〜#〜]何もしない。このスキームを「彩度」と呼びます。
エンコーディングを短縮する可能性のある別の潜在的な選択のタイプは、キャプチャする相手のピースを選択することです。これにより、移動の最初の部分の選択数が増加し、最大で1ビット追加されます(正確な数は、現在のプレーヤーが移動できる部分の数によって異なります)。この選択の後に、キャプチャピースの選択が続きます。これは、特定のプレイヤーピースのいずれかのムーブの数よりもはるかに小さい可能性があります。ピースは、任意の基本的な方向からの1ピースと、最大で10個の攻撃ピースのナイトによってのみ攻撃できます。これにより、キャプチャの移動に合計9ビットの最大値が与えられますが、平均で7ビットが期待されます。これは多くの場合、かなりの数の合法的な目的地があるため、女王による捕獲には特に有利です。
飽和状態では、キャプチャエンコードはおそらく利点がありません。両方のオプションを許可し、使用される初期状態で指定することができます。飽和が使用されない場合、ゲームのエンコーディングは未使用の選択肢番号も使用できます([〜#〜] c [〜#〜] <= c <2n)ゲーム中にオプションを変更します。 [〜#〜] c [〜#〜]は2のべき乗であるため、オプションを変更できませんでした。
最初のボードとそれに続く移動の基本ケースでは、次のことを考慮してください。
チェスプログラムを使用して、可能性のあるすべての動きに確率を割り当てます。たとえば、e2-e4の場合は40%、d2-d4の場合は20%などです。いくつかの動きが合法であるが、そのプログラムで考慮されていない場合、それらに低い確率を与えます。算術コーディングを使用して、選択した内容を保存します。これは、最初の動きでは0〜0.4、2番目の動きでは0.4および0.6のようになります。
反対側にも同じことを行います。たとえば、e2-e4への応答としてe7-e5の可能性が50%ある場合、エンコードされた数値は0〜0.2になります。ゲームが完了するまで繰り返します。結果は、潜在的に非常に小さな範囲です。その範囲に収まる最小のベースを持つバイナリ分数を見つけます。それが算術コーディングです。
フラクショナルビットエンコーディングと考えることができるため、これはハフマンよりも優れています(さらに、ゲームの最後にビット全体に切り上げるための一部もあります)。
結果はハフマンよりもコンパクトになり、チェス評価プログラムで処理されるため、プロモーション、パッサン、50ルールの移動、その他の詳細に関する特別なケースはありません。
再生するには、チェスプログラムを再度使用してボードを評価し、すべての確率を各動きに割り当てます。算術エンコード値を使用して、実際にプレイされた動きを判断します。完了するまで繰り返します。
チェスプログラムが十分であれば、2ステートエンコーダーでより良い圧縮を得ることができます。この場合、確率は黒と白の両方の動きに基づいて定義されます。約200以上の状態の最も極端な場合、これはすべての可能なチェスゲームのセット全体をエンコードするため、実行不可能です。
これは、算術コーディングがどのように機能するかのほんの少しの例と、次の動きの確率を評価するために既存のチェスプログラムを使用する実際の例でのみ、Dariusがすでに書いたものを言うためのかなり異なる方法です。