Pembaruan: Saya sangat menyukai topik ini sehingga saya menulis Teka-teki Pemrograman, Posisi Catur, dan Pengkodean Huffman . Jika Anda membaca ini, saya telah menentukan bahwa satu - satunya cara untuk menyimpan status permainan lengkap adalah dengan menyimpan daftar lengkap gerakan. Baca terus untuk alasannya. Jadi saya menggunakan versi masalah yang sedikit disederhanakan untuk tata letak bagian.
Masalah
Gambar ini mengilustrasikan posisi awal Catur. Catur terjadi pada papan 8x8 dengan setiap pemain dimulai dengan set identik 16 buah yang terdiri dari 8 pion, 2 benteng, 2 ksatria, 2 uskup, 1 ratu dan 1 raja seperti yang diilustrasikan di sini:
Posisi umumnya dicatat sebagai huruf untuk kolom diikuti dengan nomor baris sehingga ratu Putih berada di d1. Gerakan paling sering disimpan dalam notasi aljabar , yang tidak ambigu dan umumnya hanya menentukan informasi minimal yang diperlukan. Pertimbangkan pembukaan ini:
- e4 e5
- Nf3 Nc6
- …
yang diterjemahkan menjadi:
- Putih memindahkan pion raja dari e2 ke e4 (itu adalah satu-satunya bidak yang bisa sampai ke e4 maka “e4”);
- Hitam memindahkan pion raja dari e7 ke e5;
- Putih memindahkan ksatria (N) ke f3;
- Hitam memindahkan ksatria ke c6.
- …
Papannya terlihat seperti ini:
Kemampuan penting bagi setiap programmer adalah dapat menentukan masalah dengan benar dan jelas .
Jadi, apa yang kurang atau ambigu? Ternyata banyak.
Status Papan vs Status Game
Hal pertama yang perlu Anda tentukan adalah apakah Anda menyimpan status permainan atau posisi bidak di papan. Mengkodekan posisi potongan adalah satu hal tetapi masalahnya mengatakan "semua langkah hukum berikutnya". Masalahnya juga tidak mengatakan apa-apa tentang mengetahui pergerakan ke titik ini. Itu sebenarnya masalah seperti yang akan saya jelaskan.
Castling
Permainan telah berjalan sebagai berikut:
- e4 e5
- Nf3 Nc6
- Bb5 a6
- Ba4 Bc5
Papan tersebut terlihat sebagai berikut:
Putih memiliki opsi rokade . Sebagian dari persyaratan untuk ini adalah bahwa raja dan benteng yang bersangkutan tidak akan pernah bisa pindah, jadi apakah raja atau benteng dari masing-masing sisi telah berpindah perlu disimpan. Jelas jika mereka tidak berada di posisi awal, mereka telah pindah jika tidak maka perlu ditentukan.
Ada beberapa strategi yang bisa digunakan untuk mengatasi masalah ini.
Pertama, kami dapat menyimpan 6 bit informasi tambahan (1 untuk setiap benteng dan raja) untuk menunjukkan apakah bidak itu telah pindah. Kita dapat merampingkan ini dengan hanya menyimpan sedikit untuk salah satu dari enam kotak ini jika bagian yang tepat kebetulan ada di dalamnya. Sebagai alternatif, kita dapat memperlakukan setiap bidak yang tidak digerakkan sebagai jenis bidak lain sehingga alih-alih 6 jenis bidak di setiap sisi (bidak, benteng, ksatria, uskup, ratu dan raja) ada 8 (menambahkan benteng yang tidak digerakkan dan raja yang tidak digerakkan).
Sambil lalu
Aturan lain yang aneh dan sering diabaikan dalam Catur adalah En Passant .
Permainan telah berkembang.
- e4 e5
- Nf3 Nc6
- Bb5 a6
- Ba4 Bc5
- OO b5
- Bb3 b4
- c4
Bidak Hitam di b4 sekarang memiliki opsi untuk memindahkan pionnya di b4 ke c3 mengambil bidak Putih di c4. Ini hanya terjadi pada kesempatan pertama yang berarti jika Black meneruskan opsi tersebut sekarang dia tidak dapat mengambil langkah selanjutnya. Jadi kita perlu menyimpan ini.
Jika kita tahu langkah sebelumnya, kita pasti bisa menjawab jika En Passant memungkinkan. Alternatifnya kita bisa menyimpan apakah setiap bidak di peringkat ke-4 baru saja pindah ke sana dengan gerakan maju ganda. Atau kita dapat melihat setiap kemungkinan posisi En Passant di papan tulis dan memiliki bendera untuk menunjukkan apakah itu memungkinkan atau tidak.
Promosi
Ini adalah langkah Putih. Jika Putih memindahkan pionnya di h7 ke h8, itu bisa dipromosikan ke bidak lain (tapi bukan raja). 99% dari waktu itu dipromosikan menjadi Ratu tetapi terkadang tidak, biasanya karena itu dapat memaksa jalan buntu ketika jika tidak Anda akan menang. Ini ditulis sebagai:
- h8 = Q
Ini penting dalam masalah kita karena itu berarti kita tidak dapat mengandalkan jumlah keping yang tetap di setiap sisi. Sangat mungkin (tetapi sangat tidak mungkin) untuk satu pihak berakhir dengan 9 ratu, 10 benteng, 10 uskup atau 10 ksatria jika semua 8 pion dipromosikan.
Jalan buntu
Ketika dalam posisi di mana Anda tidak bisa menang, taktik terbaik Anda adalah mencoba jalan buntu . Varian yang paling mungkin adalah di mana Anda tidak dapat melakukan langkah hukum (biasanya karena tindakan apa pun ketika membuat raja Anda di cek). Dalam hal ini Anda dapat mengklaim hasil imbang. Yang ini mudah dipenuhi.
Varian kedua adalah dengan pengulangan tiga kali lipat . Jika posisi papan yang sama terjadi tiga kali dalam permainan (atau akan terjadi untuk ketiga kalinya pada langkah berikutnya), hasil imbang dapat diklaim. Posisi tidak perlu terjadi dalam urutan tertentu (artinya tidak harus urutan gerakan yang sama yang diulang tiga kali). Yang ini sangat memperumit masalah karena Anda harus mengingat setiap posisi papan sebelumnya. Jika ini merupakan persyaratan masalah, satu-satunya solusi yang mungkin untuk masalah tersebut adalah menyimpan setiap langkah sebelumnya.
Terakhir, ada aturan lima puluh langkah . Seorang pemain dapat mengklaim hasil imbang jika tidak ada bidak yang bergerak dan tidak ada bidak yang diambil dalam lima puluh langkah berturut-turut sebelumnya, jadi kami perlu menyimpan berapa banyak gerakan sejak bidak dipindahkan atau bidak diambil (yang terbaru dari dua bidak. Ini membutuhkan 6 bit (0-63).
Giliran siapa sekarang?
Tentu saja kita juga perlu tahu giliran siapa dan ini sedikit informasi.
Dua Masalah
Karena kasus buntu, satu-satunya cara yang layak atau masuk akal untuk menyimpan status game adalah menyimpan semua gerakan yang mengarah ke posisi ini. Saya akan mengatasi satu masalah itu. Masalah status papan akan disederhanakan menjadi ini: simpan posisi saat ini dari semua bagian di papan dengan mengabaikan kondisi rokade, en passant, jalan buntu dan giliran siapa .
Tata letak satuan dapat ditangani secara luas dengan salah satu dari dua cara berikut: dengan menyimpan isi setiap kotak atau dengan menyimpan posisi setiap bagian.
Isi Sederhana
Ada enam jenis bidak (pion, benteng, ksatria, uskup, ratu dan raja). Tiap bidak bisa Putih atau Hitam jadi bujur sangkar bisa berisi salah satu dari 12 kemungkinan keping atau mungkin kosong jadi ada 13 kemungkinan. 13 dapat disimpan dalam 4 bit (0-15) Jadi solusi paling sederhana adalah dengan menyimpan 4 bit untuk setiap kuadrat dikalikan 64 kotak atau 256 bit informasi.
Keuntungan dari metode ini adalah manipulasi sangat mudah dan cepat. Ini bahkan dapat diperpanjang dengan menambahkan 3 kemungkinan lagi tanpa menambah persyaratan penyimpanan: bidak yang telah berpindah 2 ruang pada belokan terakhir, raja yang belum bergerak dan benteng yang belum bergerak, yang akan melayani banyak orang. masalah yang disebutkan sebelumnya.
Tapi kita bisa lebih baik.
Pengkodean Basis 13
Seringkali membantu untuk memikirkan posisi papan sebagai angka yang sangat besar. Ini sering dilakukan dalam ilmu komputer. Misalnya, masalah penghentian memperlakukan program komputer (dengan benar) sebagai angka yang besar.
Solusi pertama memperlakukan posisi sebagai 64 digit bilangan basis 16 tetapi seperti yang ditunjukkan ada redundansi dalam informasi ini (menjadi 3 kemungkinan yang tidak digunakan per "digit") sehingga kita dapat mengurangi ruang bilangan menjadi 64 basis 13 digit. Tentu saja ini tidak dapat dilakukan seefisien basis 16 tetapi akan menghemat kebutuhan penyimpanan (dan meminimalkan ruang penyimpanan adalah tujuan kami).
Dalam basis 10 angka 234 sama dengan 2 x 10 2 + 3 x 10 1 + 4 x 10 0 .
Dalam basis 16 angka 0xA50 setara dengan 10 x 16 2 + 5 x 16 1 + 0 x 16 0 = 2640 (desimal).
Jadi kita dapat menyandikan posisi kita sebagai p 0 x 13 63 + p 1 x 13 62 + ... + p 63 x 13 0 dimana p i mewakili isi dari kuadrat i .
2 256 sama dengan sekitar 1,16e77. 13 64 sama dengan kira-kira 1.96e71, yang membutuhkan ruang penyimpanan 237 bit. Penghematan hanya 7,5% itu menimbulkan biaya manipulasi yang meningkat secara signifikan .
Pengkodean Basis Variabel
Di papan hukum, bagian tertentu tidak dapat muncul di kotak tertentu. Misalnya, bidak tidak dapat muncul di peringkat pertama atau kedelapan, mengurangi kemungkinan untuk kotak tersebut menjadi 11. Hal itu mengurangi kemungkinan papan menjadi 11 16 x 13 48 = 1.35e70 (kurang-lebih), membutuhkan ruang penyimpanan 233 bit.
Sebenarnya encoding dan decoding nilai-nilai seperti itu ke dan dari desimal (atau biner) sedikit lebih berbelit-belit tetapi dapat dilakukan dengan andal dan dibiarkan sebagai latihan bagi pembaca.
Huruf Lebar Variabel
Kedua metode sebelumnya dapat dideskripsikan sebagai pengkodean alfabet dengan lebar tetap . Masing-masing dari 11, 13 atau 16 anggota alfabet diganti dengan nilai lain. Setiap "karakter" memiliki lebar yang sama tetapi efisiensinya dapat ditingkatkan jika Anda mempertimbangkan bahwa setiap karakter tidak mungkin sama.
Pertimbangkan kode Morse (digambarkan di atas). Karakter dalam pesan dikodekan sebagai urutan tanda hubung dan titik. Tanda hubung dan titik tersebut ditransfer melalui radio (biasanya) dengan jeda di antara keduanya untuk membatasinya.
Perhatikan bagaimana huruf E (huruf yang paling umum dalam bahasa Inggris ) adalah titik tunggal, urutan yang paling pendek, sedangkan Z (paling jarang) adalah dua tanda hubung dan dua bip.
Skema seperti itu dapat secara signifikan mengurangi ukuran pesan yang diharapkan, tetapi mengakibatkan peningkatan ukuran urutan karakter acak.
Perlu dicatat bahwa kode Morse memiliki fitur bawaan lainnya: tanda hubung sepanjang tiga titik sehingga kode di atas dibuat dengan pemikiran ini untuk meminimalkan penggunaan tanda hubung. Karena 1s dan 0s (blok penyusun kami) tidak memiliki masalah ini, ini bukan fitur yang perlu kami tiru.
Terakhir, ada dua jenis sandaran dalam kode Morse. Istirahat singkat (panjang sebuah titik) digunakan untuk membedakan antara titik dan garis. Celah yang lebih panjang (panjang tanda hubung) digunakan untuk membatasi karakter.
Jadi, bagaimana ini berlaku untuk masalah kita?
Huffman Coding
Ada algoritma untuk menangani kode panjang variabel yang disebut pengkodean Huffman . Pengkodean Huffman membuat substitusi kode panjang variabel, biasanya menggunakan frekuensi simbol yang diharapkan untuk menetapkan nilai yang lebih pendek ke simbol yang lebih umum.
Pada pohon di atas, huruf E dikodekan sebagai 000 (atau kiri-kiri-kiri) dan S adalah 1011. Seharusnya jelas bahwa skema pengkodean ini tidak ambigu .
Ini adalah perbedaan penting dari kode Morse. Kode Morse memiliki pemisah karakter sehingga dapat melakukan substitusi yang ambigu (misalnya 4 titik bisa H atau 2 Is) tetapi kita hanya memiliki 1 dan 0 jadi kita memilih substitusi yang tidak ambigu.
Di bawah ini adalah implementasi sederhana:
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; }
}
dengan data statis:
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);
}
dan:
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);
}
}
}
Salah satu kemungkinan keluarannya adalah:
White Black
Empty 0
Pawn 110 100
Rook 11111 11110
Knight 10110 10101
Bishop 10100 11100
Queen 111010 111011
King 101110 101111
Untuk posisi awal, ini setara dengan 32 x 1 + 16 x 3 + 12 x 5 + 4 x 6 = 164 bit.
Perbedaan Negara
Pendekatan lain yang mungkin adalah menggabungkan pendekatan pertama dengan pengkodean Huffman. Ini didasarkan pada asumsi bahwa papan Catur yang paling diharapkan (daripada yang dihasilkan secara acak) lebih mungkin daripada tidak, setidaknya sebagian, menyerupai posisi awal.
Jadi yang Anda lakukan adalah XOR posisi papan arus 256 bit dengan posisi awal 256 bit dan kemudian menyandikannya (menggunakan pengkodean Huffman atau, katakanlah, beberapa metode pengkodean panjang jalan ). Jelas ini akan sangat efisien untuk memulai (64 0 mungkin sesuai dengan 64 bit) tetapi peningkatan penyimpanan diperlukan saat permainan berlangsung.
Posisi Bidak
Seperti disebutkan, cara lain untuk mengatasi masalah ini adalah dengan menyimpan posisi setiap bidak yang dimiliki pemain. Ini bekerja sangat baik dengan posisi permainan akhir di mana sebagian besar kotak akan kosong (tetapi dalam pendekatan pengkodean Huffman kotak kosong hanya menggunakan 1 bit).
Setiap sisi akan memiliki raja dan 0-15 bidak lainnya. Karena promosi, susunan yang tepat dari potongan-potongan itu dapat cukup bervariasi sehingga Anda tidak dapat menganggap angka berdasarkan posisi awal adalah maksimal.
Cara logis untuk membagi ini adalah menyimpan Posisi yang terdiri dari dua Sisi (Putih dan Hitam). Setiap Sisi memiliki:
- Seorang raja: 6 bit untuk lokasi;
- Memiliki bidak: 1 (ya), 0 (tidak);
- Jika ya, jumlah bidak: 3 bidak (0-7 + 1 = 1-8);
- Jika ya, lokasi setiap bidak dikodekan: 45 bit (lihat di bawah);
- Jumlah bukan bidak: 4 bidak (0-15);
- Untuk setiap bidak: jenis (2 bit untuk ratu, benteng, ksatria, bishop) dan lokasi (6 bit)
Sedangkan untuk lokasi bidak, bidak hanya dapat berada di 48 petak yang memungkinkan (bukan 64 petak lainnya). Karena itu, lebih baik tidak menyia-nyiakan 16 nilai ekstra yang akan digunakan menggunakan 6 bit per bidak. Jadi jika Anda memiliki 8 pion ada 48 8 kemungkinan, sama dengan 28.179.280.429.056. Anda membutuhkan 45 bit untuk menyandikan banyak nilai itu.
Itu 105 bit per sisi atau total 210 bit. Posisi awal adalah kasus terburuk untuk metode ini dan itu akan menjadi jauh lebih baik saat Anda menghapus potongan.
Harus dijelaskan bahwa ada kurang dari 48 8 kemungkinan karena bidak tidak bisa semuanya berada di kotak yang sama. Yang pertama memiliki 48 kemungkinan, yang kedua 47 dan seterusnya. 48 x 47 x… x 41 = 1,52e13 = penyimpanan 44 bit.
Anda dapat meningkatkan ini lebih jauh dengan menghilangkan petak yang ditempati oleh bidak lain (termasuk sisi lain) sehingga Anda dapat menempatkan bidak putih terlebih dahulu, kemudian bidak hitam, lalu bidak putih, dan terakhir bidak hitam. Pada posisi awal, ini mengurangi persyaratan penyimpanan menjadi 44 bit untuk Putih dan 42 bit untuk Hitam.
Pendekatan Gabungan
Pengoptimalan lain yang mungkin adalah bahwa masing-masing pendekatan ini memiliki kekuatan dan kelemahan. Anda dapat, katakanlah, memilih 4 terbaik dan kemudian menyandikan pemilih skema dalam dua bit pertama dan kemudian penyimpanan khusus skema setelah itu.
Dengan overhead sekecil itu, sejauh ini ini akan menjadi pendekatan terbaik.
Status Game
Saya kembali ke masalah menyimpan permainan daripada posisi . Karena pengulangan tiga kali lipat kami harus menyimpan daftar gerakan yang telah terjadi hingga saat ini.
Anotasi
Satu hal yang harus Anda tentukan adalah apakah Anda hanya menyimpan daftar gerakan atau Anda membuat anotasi permainan? Permainan catur sering kali diberi anotasi, misalnya:
- Bb5 !! Nc4?
Langkah Putih ditandai dengan dua tanda seru sebagai brilian sedangkan Hitam dipandang sebagai kesalahan. Lihat tanda baca catur .
Selain itu, Anda juga perlu menyimpan teks bebas saat gerakan dijelaskan.
Saya berasumsi bahwa pergerakannya cukup sehingga tidak akan ada anotasi.
Notasi Aljabar
Kita cukup menyimpan teks perpindahan di sini ("e4", "Bxb5", dll). Termasuk byte pengakhiran yang Anda lihat sekitar 6 byte (48 bit) per gerakan (kasus terburuk). Itu tidak terlalu efisien.
Hal kedua yang harus dicoba adalah menyimpan lokasi awal (6 bit) dan lokasi akhir (6 bit) jadi 12 bit per gerakan. Itu jauh lebih baik.
Atau kita dapat menentukan semua langkah hukum dari posisi saat ini dengan cara dan keadaan yang dapat diprediksi dan deterministik yang telah kita pilih. Ini kemudian kembali ke pengkodean basis variabel yang disebutkan di atas. Putih dan Hitam memiliki 20 kemungkinan gerakan masing-masing pada langkah pertama mereka, lebih banyak pada langkah kedua dan seterusnya.
Kesimpulan
Tidak ada jawaban yang benar-benar tepat untuk pertanyaan ini. Ada banyak kemungkinan pendekatan yang di atas hanyalah beberapa di antaranya.
Apa yang saya suka tentang ini dan masalah serupa adalah bahwa ia menuntut kemampuan yang penting bagi programmer mana pun seperti mempertimbangkan pola penggunaan, menentukan persyaratan secara akurat, dan memikirkan kasus sudut.
Posisi catur diambil sebagai tangkapan layar dari Chess Position Trainer .