엄격하게 질문이 아니라 퍼즐에 가깝습니다 …
수년에 걸쳐 저는 신입 사원에 대한 몇 가지 기술 인터뷰에 참여했습니다. 표준 “X 기술을 알고 계십니까”질문을하는 것 외에, 그들이 문제에 어떻게 접근하는지에 대한 느낌을 얻으려고 노력했습니다. 일반적으로 인터뷰 전날 이메일로 질문을 보내고 다음 날까지 해결책을 찾을 수 있기를 기대합니다.
종종 결과는 매우 흥미 롭습니다. 틀렸지 만 흥미로울 것입니다. 그리고 그 사람이 왜 특정한 접근 방식을 취했는지 설명 할 수 있다면 제 추천을받을 것입니다.
그래서 저는 Stack Overflow 청중을 위해 제 질문 중 하나를 던질 것이라고 생각했습니다.
질문 : 체스 게임 (또는 그 하위 집합)의 상태를 인코딩하기 위해 생각할 수있는 가장 공간 효율적인 방법은 무엇입니까? 즉, 합법적으로 배열 된 조각이있는 체스 판이 주어지면이 초기 상태와 게임에서 플레이어가 취하는 모든 후속 법적 동작을 모두 인코딩합니다.
답변에 필요한 코드는 없으며 사용할 알고리즘에 대한 설명 만 있으면됩니다.
편집 : 포스터 중 하나가 지적했듯이 나는 움직임 사이의 시간 간격을 고려하지 않았습니다. 추가 옵션으로도 고려하십시오. 🙂
EDIT2 : 추가 설명을 위해 … 기억하십시오, 인코더 / 디코더는 규칙을 인식합니다. 실제로 저장해야하는 유일한 것은 플레이어의 선택입니다. 다른 것은 인코더 / 디코더가 알고 있다고 가정 할 수 있습니다.
EDIT3 : 여기서 우승자를 선택하는 것은 어려울 것입니다. 🙂 많은 훌륭한 답변!
답변
업데이트 : 저는 프로그래밍 퍼즐, 체스 포지션, 허프만 코딩을 썼습니다 . 이 글을 읽어 보면 완전한 게임 상태를 저장하는 유일한 방법은 전체 동작 목록을 저장하는 것이라고 판단했습니다 . 이유는 계속 읽어보십시오. 그래서 저는 조각 레이아웃에 대해 약간 단순화 된 버전의 문제를 사용합니다.
문제
이 이미지는 시작 체스 위치를 보여줍니다. 체스는 8×8 보드에서 발생하며 각 플레이어는 8 개의 폰, 2 개의 루크, 2 개의 기사, 2 개의 비숍, 1 개의 퀸, 1 개의 킹으로 구성된 동일한 16 개 조각 세트로 시작합니다.
위치는 일반적으로 열의 문자와 행의 숫자로 기록되므로 White의 여왕은 d1에 있습니다. 이동은 대부분의 경우 대수 표기법으로 저장되며 , 이는 모호하지 않으며 일반적으로 필요한 최소한의 정보 만 지정합니다. 이 오프닝을 고려하십시오.
- e4 e5
- Nf3 Nc6
- …
다음으로 번역됩니다.
- White는 king ‘s pawn을 e2에서 e4로 이동합니다 (e4에 도달 할 수있는 유일한 조각이므로 “e4”).
- Black은 왕의 폰을 e7에서 e5로 이동합니다.
- 흰색은 기사 (N)를 f3으로 이동합니다.
- 검은 색은 기사를 c6로 이동합니다.
- …
보드는 다음과 같습니다.
모든 프로그래머에게 중요한 능력 은 문제 를 정확하고 명확하게 지정할 수 있다는 것 입니다.
그래서 무엇이 누락되었거나 모호합니까? 밝혀진대로 많이.
보드 상태 대 게임 상태
가장 먼저 결정해야 할 것은 게임의 상태를 저장할 것인지 아니면 보드에있는 조각의 위치를 저장할 것인지입니다. 단순히 조각의 위치를 인코딩하는 것은 하나이지만 문제는 “모든 후속 법적 이동”이라고 말합니다. 문제는 또한이 시점까지의 움직임을 아는 것에 대해 아무것도 말하지 않습니다. 제가 설명 하겠지만 실제로 문제입니다.
캐슬 링
게임은 다음과 같이 진행되었습니다.
- e4 e5
- Nf3 Nc6
- Bb5 a6
- Ba4 Bc5
보드는 다음과 같이 보입니다.
흰색에는 캐슬 링 옵션이 있습니다. 이것에 대한 요구 사항 중 일부는 왕과 관련 루크가 결코 움직일 수 없기 때문에 왕이나 양쪽의 루크가 이동했는지 여부를 저장해야한다는 것입니다. 분명히 그들이 그들의 시작 위치에 있지 않다면, 그들은 움직이지 않았다면 그것을 지정해야합니다.
이 문제를 처리하는 데 사용할 수있는 몇 가지 전략이 있습니다.
첫째, 그 조각이 움직 였는지 여부를 나타 내기 위해 6 비트의 추가 정보 (각 루크와 킹에 대해 1 개)를 저장할 수 있습니다. 올바른 조각이있는 경우 6 개의 사각형 중 하나에 대해 약간만 저장하여이를 간소화 할 수 있습니다. 또는 움직이지 않은 각 조각을 다른 조각 유형으로 취급 할 수 있으므로 각면에 6 개의 조각 유형 (폰, 루크, 나이트, 비숍, 퀸 및 킹) 대신 8 개 (움직이지 않은 루크 및 움직이지 않은 킹 추가)가 있습니다.
En Passant
Chess에서 독특하고 자주 무시되는 또 다른 규칙은 En Passant 입니다.
게임이 진행되었습니다.
- e4 e5
- Nf3 Nc6
- Bb5 a6
- Ba4 Bc5
- OO b5
- Bb3 b4
- c4
b4에있는 Black의 폰은 이제 b4에있는 그의 폰을 c4에있는 White 폰을 c3으로 옮기는 옵션이 있습니다. 이것은 첫 번째 기회에서만 발생합니다. 즉, Black이 옵션을 통과하면 다음 이동을 수행 할 수 없습니다. 그래서 우리는 이것을 저장해야합니다.
이전 움직임을 알고 있다면 En Passant가 가능한지 확실히 대답 할 수 있습니다. 또는 4 순위에있는 각 폰이 두 번 앞으로 이동하여 방금 이동했는지 여부를 저장할 수 있습니다. 또는 우리는 보드에서 가능한 각 En Passant 위치를보고 가능한지 여부를 나타내는 플래그를 가질 수 있습니다.
프로모션
화이트의 움직임입니다. White가 자신의 폰을 h7에서 h8로 옮기면 다른 말로 승격 될 수 있습니다 (왕은 아님). 99 %의 경우 여왕으로 승진되지만 때로는 그렇지 않은 경우도 있습니다. 일반적으로 그렇지 않으면 승리 할 때 교착 상태가 될 수 있기 때문입니다. 이것은 다음과 같이 작성됩니다.
- h8 = Q
이것은 우리가 각면에 고정 된 수의 조각이 있다는 것을 믿을 수 없다는 것을 의미하기 때문에 우리 문제에서 중요합니다. 8 개의 폰이 모두 승격되면 한 쪽이 9 명의 퀸, 10 명의 루크, 10 명의 비숍 또는 10 명의 기사로 끝날 가능성은 전적으로 가능합니다.
수가 막히게 하다
당신이 이길 수없는 위치에있을 때 당신의 최선의 전술은 교착 상태 를 시도하는 것 입니다. 가장 가능성이 높은 변형은 합법적 인 이동을 할 수없는 경우입니다 (일반적으로 킹을 체크 할 때 이동하기 때문에). 이 경우 무승부를 청구 할 수 있습니다. 이것은 음식을 제공하기 쉽습니다.
두 번째 변형은 3 중 반복 입니다. 같은 보드 위치가 게임에서 세 번 발생하면 (또는 다음 이동에서 세 번 발생) 무승부가 청구될 수 있습니다. 위치는 특정 순서로 발생할 필요가 없습니다 (즉, 동일한 순서의 동작을 세 번 반복 할 필요가 없음). 이것은 이전의 모든 보드 위치를 기억해야하기 때문에 문제를 크게 복잡하게 만듭니다. 이것이 문제의 요구 사항 인 경우 문제에 대한 유일한 해결책은 이전의 모든 이동을 저장하는 것입니다.
마지막으로 50 개의 이동 규칙이 있습니다. 플레이어는 폰이 움직이지 않았고 이전 50 개 연속 이동에서 조각을 가져 오지 않은 경우 무승부를 청구 할 수 있으므로 폰이 이동 된 이후 또는 조각을 가져온 이후에 몇 개의 움직임을 저장해야합니다 (두 개 중 가장 최근의 것). 6 비트 (0-63).
누구의 차례인가?
물론 우리는 누구의 차례인지 알 필요가 있으며 이것은 하나의 정보입니다.
두 가지 문제
교착 상태로 인해 게임 상태를 저장하는 유일하고 현명한 방법은이 위치로 이어진 모든 동작을 저장하는 것입니다. 그 한 가지 문제를 해결하겠습니다. 보드 상태 문제는 다음과 같이 단순화됩니다. 캐슬 링, en passant, 교착 상태 및 차례를 무시하고 보드에있는 모든 조각의 현재 위치를 저장합니다 .
조각 레이아웃은 각 사각형의 내용을 저장하거나 각 조각의 위치를 저장하는 두 가지 방법 중 하나로 광범위하게 처리 할 수 있습니다.
간단한 내용
6 가지 유형 (폰, 루크, 나이트, 비숍, 퀸, 킹)이 있습니다. 각 조각은 흰색 또는 검은 색일 수 있으므로 사각형에는 12 개의 가능한 조각 중 하나가 포함될 수 있거나 비어있어 13 개의 가능성이 있습니다. 13은 4 비트 (0-15)로 저장 될 수 있으므로 가장 간단한 해결책은 각 제곱에 대해 4 비트를 64 제곱 또는 256 비트 정보로 저장하는 것입니다.
이 방법의 장점은 조작이 매우 쉽고 빠르다는 것입니다. 저장 공간 요구 사항을 늘리지 않고 3 개의 가능성을 더 추가하여 확장 할 수도 있습니다. 이전에 언급 된 문제의
하지만 우리는 더 잘할 수 있습니다.
Base 13 인코딩
이사회 위치를 매우 많은 수로 생각하면 도움이됩니다. 이것은 종종 컴퓨터 과학에서 수행됩니다. 예를 들어, 중지 문제 는 컴퓨터 프로그램을 (올바르게) 많은 수로 취급합니다.
첫 번째 솔루션은 위치를 64 자리 16 진수로 처리하지만이 정보에 중복성이 있으므로 ( “숫자”당 사용되지 않은 3 가지 가능성) 숫자 공간을 64 진수 13 자리로 줄일 수 있습니다. 물론 이것은 기본 16만큼 효율적으로 수행 할 수 없지만 저장 요구 사항을 절약 할 수 있습니다 (저장 공간 최소화가 목표입니다).
베이스 (10)의 수 (234)는 2 × 동등 2 + 3 × 10 1 + 4 × 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 (최소 빈도)는 대시 2 개와 경고음 2 개입니다.
이러한 방식은 예상되는 메시지 의 크기를 크게 줄일 수 있지만 무작위 문자 시퀀스의 크기를 증가시키는 비용이 발생합니다.
모스 코드에는 또 다른 내장 기능이 있다는 점에 유의해야합니다. 대시는 점 3 개만큼 길기 때문에 대시 사용을 최소화하기 위해이를 염두에두고 생성 된 코드입니다. 1과 0 (빌딩 블록)에는이 문제가 없기 때문에 복제해야 할 기능이 아닙니다.
마지막으로 모스 부호에는 두 가지 종류의 쉼표가 있습니다. 짧은 쉼표 (점의 길이)는 점과 대시를 구분하는 데 사용됩니다. 더 긴 간격 (대시 길이)은 문자를 구분하는 데 사용됩니다.
그렇다면 이것이 우리 문제에 어떻게 적용됩니까?
허프만 코딩
Huffman 코딩 이라는 가변 길이 코드를 처리하는 알고리즘이 있습니다 . Huffman 코딩은 일반적으로 기호의 예상 빈도를 사용하여 더 일반적인 기호에 더 짧은 값을 할당하는 가변 길이 코드 대체를 생성합니다.
위의 트리에서 문자 E는 000 (또는 left-left-left)으로 인코딩되고 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(), "");
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 비트와 같습니다.
국가 차이
또 다른 가능한 접근 방식은 첫 번째 접근 방식을 Huffman 코딩과 결합하는 것입니다. 이것은 (무작위로 생성 된 것이 아니라) 대부분의 예상 체스 보드가 적어도 부분적으로는 시작 위치와 비슷하지 않을 가능성이 더 높다는 가정을 기반으로합니다.
그래서 여러분이하는 일은 256 비트 시작 위치로 256 비트 현재 보드 위치를 XOR 한 다음이를 인코딩합니다 (Huffman 코딩 또는 실행 길이 인코딩 방법 사용 ). 분명히 이것은 (64 비트에 해당하는 64 0s) 시작하는 데 매우 효율적이지만 게임이 진행됨에 따라 필요한 저장 공간이 증가합니다.
조각 위치
앞서 언급했듯이이 문제를 해결하는 또 다른 방법은 플레이어가 가지고있는 각 조각의 위치를 저장하는 것입니다. 이것은 대부분의 사각형이 비어있는 엔드 게임 위치에서 특히 잘 작동합니다 (그러나 Huffman 코딩 방식에서는 빈 사각형이 어쨌든 1 비트 만 사용합니다).
각면에는 킹과 0-15 개의 다른 조각이 있습니다. 승진으로 인해 이러한 조각의 정확한 구성은 시작 위치를 기반으로 한 숫자가 최대라고 가정 할 수 없을 정도로 다양 할 수 있습니다.
이것을 분할하는 논리적 방법은 두 개의면 (흰색과 검은 색)으로 구성된 위치를 저장하는 것입니다. 각 측면에는 다음이 있습니다.
- 킹 : 위치에 대해 6 비트;
- 폰 있음 : 1 (예), 0 (아니오)
- 그렇다면 폰 수 : 3 비트 (0-7 + 1 = 1-8);
- 그렇다면, 각 폰의 위치는 다음과 같이 인코딩됩니다 : 45 비트 (아래 참조);
- 비 폰의 수 : 4 비트 (0-15);
- 각 조각 : 유형 (퀸, 루크, 나이트, 비숍의 경우 2 비트) 및 위치 (6 비트)
폰 위치와 관련하여 폰은 48 개의 가능한 사각형에있을 수 있습니다 (다른 것과 같은 64 개가 아닌). 따라서 폰당 6 비트를 사용하는 경우 사용할 추가 16 개의 값을 낭비하지 않는 것이 좋습니다. 따라서 폰이 8 개인 경우 28,179,280,429,056에 해당 하는 48 8 개의 가능성 이 있습니다 . 많은 값을 인코딩하려면 45 비트가 필요합니다.
이는 측면 당 105 비트 또는 총 210 비트입니다. 시작 위치는이 방법의 경우 최악의 경우이며 조각을 제거할수록 훨씬 좋아집니다.
폰이 모두 같은 사각형에있을 수 없기 때문에 48 8 개 미만의 가능성 이 있다는 점을 지적해야합니다 . 첫 번째는 48 개, 두 번째는 47 개 등입니다. 48 x 47 x… x 41 = 1.52e13 = 44 비트 저장.
다른 조각 (다른면 포함)이 차지하는 사각형을 제거하여이를 더욱 개선 할 수 있으므로 먼저 흰색 비폰을 배치 한 다음 검정색 비폰을 배치 한 다음 흰색 폰을 배치하고 마지막으로 검은 폰을 배치 할 수 있습니다. 시작 위치에서 이것은 저장 요구 사항을 흰색의 경우 44 비트, 검은 색의 경우 42 비트로 줄입니다.
결합 된 접근 방식
또 다른 가능한 최적화는 이러한 접근 방식 각각에 강점과 약점이 있다는 것입니다. 예를 들어, 가장 좋은 4 개를 선택한 다음 처음 2 비트에서 체계 선택기를 인코딩 한 다음 그 이후에 체계 별 저장소를 인코딩 할 수 있습니다.
오버 헤드가 너무 작기 때문에 이것이 최고의 접근 방식이 될 것입니다.
게임 상태
나는 위치 보다는 게임 을 저장하는 문제로 돌아 간다 . 3 중 반복 때문에이 지점까지 발생한 동작 목록을 저장해야합니다.
주석
결정해야 할 한 가지는 단순히 동작 목록을 저장하는 것입니까, 아니면 게임에 주석을 달고 있습니까? 체스 게임에는 종종 주석이 추가됩니다. 예를 들면 다음과 같습니다.
- Bb5 !! Nc4?
White의 움직임은 두 개의 느낌표가 훌륭하다고 표시되는 반면 Black은 실수로 간주됩니다. Chess 구두점을 참조하십시오 .
또한 이동이 설명 된대로 자유 텍스트를 저장해야 할 수도 있습니다.
나는 움직임이 충분하여 주석이 없을 것이라고 가정합니다.
대수 표기법
여기에 이동 텍스트 (“e4”,“Bxb5”등)를 간단히 저장할 수 있습니다. 종료 바이트를 포함하여 이동 당 약 6 바이트 (48 비트)를보고 있습니다 (최악의 경우). 특히 효율적이지 않습니다.
두 번째 시도는 시작 위치 (6 비트)와 끝 위치 (6 비트)를 저장하여 이동 당 12 비트를 저장하는 것입니다. 훨씬 낫습니다.
또는 우리가 선택한 예측 가능하고 결정적인 방식과 상태로 현재 위치에서 모든 법적 이동을 결정할 수 있습니다. 그런 다음 위에서 언급 한 가변 기본 인코딩으로 돌아갑니다. 화이트와 블랙은 첫 번째 이동에서 각각 20 개의 이동이 가능하고 두 번째 이동에서 더 많은 이동이 가능합니다.
결론
이 질문에 절대적으로 정답은 없습니다. 위의 몇 가지 가능한 접근 방식이 많이 있습니다.
이 문제와 유사한 문제에 대해 내가 좋아하는 점은 사용 패턴을 고려하고 요구 사항을 정확하게 결정하고 코너 케이스에 대해 생각하는 것과 같이 모든 프로그래머에게 중요한 능력이 필요하다는 것입니다.
Chess Position Trainer 에서 스크린 샷으로 찍은 체스 위치 .
답변
체스 게임을 사람이 읽을 수있는 표준 형식으로 저장하는 것이 가장 좋습니다.
휴대용 게임 표기법은 (는 있지만 표준 시작 위치를 가정 할 필요가 없습니다 ) 그냥 움직임을 나열 회전에 의해 전원을 켭니다. 사람이 읽을 수있는 컴팩트 한 표준 형식입니다.
예
[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
더 작게 만들고 싶다면 압축 하세요. 완료되었습니다!
답변
멋진 퍼즐!
나는 대부분의 사람들이 각 조각의 위치를 저장하고있는 것을 봅니다. 좀 더 단순한 접근 방식을 취하고 각 사각형의 내용을 저장하는 것은 어떻습니까? 승진을 처리하고 조각을 자동으로 캡처합니다.
그리고 그것은 Huffman 인코딩을 허용합니다 . 실제로 보드에있는 조각의 초기 주파수는 거의 완벽합니다. 사각형의 절반은 비어 있고 나머지 사각형의 절반은 폰입니다.
각 조각의 빈도를 고려하여 종이에 허프만 나무 를 만들었는데 여기서는 반복하지 않겠습니다. 결과 c
는 색상을 나타냅니다 (흰색 = 0, 검은 색 = 1).
- 빈 사각형의 경우 0
- 폰의 경우 1c0
- 루크의 경우 1c100
- 기 사용 1c101
- 비숍 용 1c110
- 여왕의 경우 1c1110
- 왕을위한 1c1111
초기 상황의 전체 이사회에 대해
- 빈 사각형 : 32 * 1 비트 = 32 비트
- 폰 : 16 * 3 비트 = 48 비트
- 루크 / 나이츠 / 비숍 : 12 * 5 비트 = 60 비트
- queens / kings : 4 * 6 비트 = 24 비트
전체 : 초기 보드 상태의 경우 164 비트 . 현재 가장 많이 투표 된 답변의 235 비트보다 훨씬 적습니다. 그리고 게임이 진행됨에 따라 더 작아 질 것입니다 (프로모션 이후 제외).
나는 칠판에있는 조각들의 위치만을 보았다. 추가 상태 (턴, 캐슬 링 한 사람, 패전트, 반복 동작 등)는 별도로 인코딩해야합니다. 최대 16 비트가 추가 될 수 있으므로 전체 게임 상태에 대해 180 비트 입니다. 가능한 최적화 :
- 자주 사용하지 않는 부분은 제외하고 위치를 별도로 저장합니다. 그러나 그것은 도움이되지 않을 것입니다. 왕과 여왕을 빈 사각형으로 바꾸면 5 비트를 절약 할 수 있습니다. 이는 다른 방식으로 위치를 인코딩하는 데 필요한 정확히 5 비트입니다.
- “뒷줄에 폰 없음”은 뒷줄에 다른 Huffman 테이블을 사용하여 쉽게 인코딩 할 수 있지만 많은 도움이되지는 않습니다. 당신은 아마도 여전히 같은 Huffman 나무로 끝날 것입니다.
- “하나의 흰색, 하나의 검정 비숍”은
c
비트 가없는 추가 기호를 도입하여 인코딩 할 수 있으며 , 그런 다음 비숍이있는 사각형에서 추론 할 수 있습니다. (주교로 승진 한 폰은이 계획을 방해합니다 …) - 빈 사각형의 반복은 “행에 빈 사각형 2 개”및 “행에 빈 사각형 4 개”에 대한 추가 기호를 도입하여 실행 길이로 인코딩 될 수 있습니다. 그러나 그 빈도를 추정하는 것은 그리 쉽지 않으며, 잘못 이해하면 도움이되기보다는 상처를 입을 것입니다.
답변
정말 큰 조회 테이블 접근 방식
Position -18 bytes
합법적 인 위치의 예상 수는 10입니다. 43
단순히 모든 위치를 열거하면 위치가 143 비트에 저장 될 수 있습니다. 다음에 플레이 할 팀을 표시하려면 1 비트가 더 필요합니다.
물론 열거 형은 실용적이지 않지만 최소한 144 비트가 필요하다는 것을 보여줍니다.
이동 -1 바이트
일반적으로 각 위치에 대해 약 30-40 개의 합법적 인 이동이 있지만 그 수는 218 개까지있을 수 있습니다. 각 위치에 대한 모든 합법적 인 이동을 열거합니다. 이제 각 이동을 1 바이트로 인코딩 할 수 있습니다.
우리는 여전히 사임을 나타 내기 위해 0xFF와 같은 특별한 움직임을위한 충분한 여지를 가지고 있습니다.
답변
최악의 경우 대신 인간이 플레이하는 일반적인 게임의 평균 케이스 크기 를 최적화하는 데 관심이 추가 됩니다. (문제 진술은 어느 것을 말하지 않습니다. 대부분의 응답은 최악의 경우를 가정합니다.)
이동 시퀀스의 경우 좋은 체스 엔진이 각 위치에서 이동을 생성하도록합니다. 퀄리티 순위에 따라 순서대로 k 개의 가능한 이동 목록을 생성합니다. 사람들은 일반적으로 무작위 동작보다 좋은 동작을 더 자주 선택하므로 목록의 각 위치에서 사람들이 ‘좋은’동작을 선택할 확률에 대한 매핑을 배워야합니다. 이러한 확률 (일부 인터넷 체스 데이터베이스의 게임 코퍼스 기반)을 사용하여 산술 코딩으로 동작을 인코딩합니다 . (디코더는 동일한 체스 엔진과 매핑을 사용해야합니다.)
시작 위치의 경우 ralu의 접근 방식이 효과적입니다. 확률에 따라 선택에 가중치를 부여하는 방법이 있다면 산술 코딩으로이를 다듬을 수 있습니다. 예를 들어, 조각은 종종 무작위가 아닌 서로를 방어하는 구성에 나타납니다. 그 지식을 통합하는 쉬운 방법을 찾기가 더 어렵습니다. 한 가지 아이디어는 위의 이동 인코딩으로 돌아가서 표준 개방 위치에서 시작하여 원하는 보드에서 끝나는 시퀀스를 찾는 것입니다. (마지막 위치에서 조각의 거리를 합한 것과 같은 휴리스틱 거리로 A * 검색을 시도 할 수 있습니다.) 이것은 이동 순서를 과도하게 지정하는 것과 체스 플레이를 활용하는 효율성의 비 효율성을 교환합니다. 지식.
실제 말뭉치에서 일부 통계를 수집하지 않고 평균 사례 복잡성에서 얼마나 많은 절감 효과를 얻을 수 있는지 추정하는 것도 어렵습니다. 그러나 모든 움직임의 시작점은 이미 여기에서 대부분의 제안을 능가 할 것이라고 생각합니다. 산술 코딩에는 움직임 당 정수 비트 수가 필요하지 않습니다.
답변
초기 위치가 인코딩 된 후 단계 인코딩의 하위 문제를 공격합니다. 접근 방식은 단계의 “연결된 목록”을 만드는 것입니다.
게임의 각 단계는 “이전 위치-> 새 위치”쌍으로 인코딩됩니다. 당신은 체스 게임이 시작될 때의 초기 위치를 알고 있습니다. 연결된 단계 목록을 순회하여 X가 이동 한 후 상태에 도달 할 수 있습니다.
각 단계를 인코딩하려면 시작 위치를 인코딩하는 데 64 개의 값이 필요합니다 (보드의 64 개 사각형의 경우 6 비트-8×8 사각형), 끝 위치의 경우 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 비트.
답변
나는 어제 밤에이 질문을 보았고 그것이 나를 흥미롭게해서 해결책을 생각하면서 침대에 앉았다. 내 최종 답변은 실제로 int3와 매우 유사합니다.
기본 솔루션
표준 체스 게임을 가정하고 규칙을 인코딩하지 않는다고 가정하면 (예 : White가 항상 먼저 진행됨) 각 조각이 만드는 동작 만 인코딩하여 많은 비용을 절약 할 수 있습니다.
총 32 개의 조각이 있지만 각 이동에서 어떤 색이 움직이는 지 알 수 있으므로 걱정할 사각형은 16 개뿐입니다 .이 조각은 이번 턴에 이동하는 4 비트 입니다.
각 조각에는 제한된 이동 세트 만 있으며 어떤 식 으로든 열거 할 수 있습니다.
- 폰 : 4 개 옵션, 2 비트 (1 단계 앞으로, 2 단계 앞으로, 각 대각선 1 개)
- 루크 : 14 개 옵션, 4 비트 (각 방향으로 최대 7 개)
- Bishop : 13 개 옵션, 4 비트 (하나의 대각선에 7 개가있는 경우 다른 대각선에 6 개만 있음)
- Knight : 8 가지 옵션, 3 비트
- 퀸 : 27 개 옵션, 5 비트 (Rook + Bishop)
- King : 9 개 옵션, 4 비트 (8 개의 한 단계 이동 및 캐슬 링 옵션)
승진을 위해 선택할 수있는 4 개의 조각 (Rook, Bishop, Knight, Queen)이 있으므로 해당 이동에 2 비트 를 추가 하여 지정합니다. 다른 모든 규칙은 자동으로 적용됩니다 (예 : en passant).
추가 최적화
먼저 한 가지 색상의 8 개 조각을 캡처 한 후 조각 인코딩을 3 비트로 줄인 다음 4 조각에 대해 2 비트 등으로 줄일 수 있습니다.
그러나 주요 최적화 는 게임의 각 지점에서 가능한 이동 만 열거 하는 것입니다. Pawn의 움직임 {00, 01, 10, 11}
을 각각 1 단계 앞으로, 2 단계 앞으로, 대각선 왼쪽 및 대각선 오른쪽으로 저장한다고 가정합니다 . 일부 이동이 불가능한 경우 이번 턴에 인코딩에서 제거 할 수 있습니다.
우리는 모든 단계의 게임 상태를 알고 있으므로 (모든 동작을 따라 가면서), 어떤 조각이 움직일 것인지 읽은 후 항상 읽어야하는 비트 수를 결정할 수 있습니다. 이 시점에서 폰의 유일한 움직임이 대각선으로 오른쪽으로 캡처되거나 앞으로 이동하는 것을 인식하면 1 비트 만 읽는다는 것을 알고 있습니다.
요컨대, 각 조각에 대해 위에 나열된 비트 스토리지는 최대 값 입니다. 거의 모든 움직임에는 더 적은 수의 옵션과 종종 더 적은 비트가 있습니다.