Papan C99 - 3x3 dalam ukuran 0,084
Sunting: Saya refactored kode saya dan melakukan beberapa analisis yang lebih dalam pada hasilnya.
Pengeditan Lebih Lanjut: Menambahkan pemangkasan oleh simetri. Ini membuat 4 konfigurasi algoritma: dengan atau tanpa simetri X dengan atau tanpa pemangkasan alpha-beta
Edit Terjauh: Menambahkan memoisasi menggunakan tabel hash, akhirnya mencapai hal yang mustahil: menyelesaikan papan 3x3!
Fitur utama:
- implementasi minimax langsung dengan pemangkasan alpha-beta
- sangat sedikit manajemen memori (mempertahankan dll dari langkah yang valid; O (1) pembaruan per cabang dalam pencarian pohon)
- file kedua dengan pemangkasan berdasarkan simetri. Masih mencapai pembaruan O (1) per cabang (secara teknis O (S) dengan S adalah jumlah simetri. Ini adalah 7 untuk papan persegi dan 3 untuk papan non-persegi)
- file ketiga dan keempat menambahkan memoisasi. Anda memiliki kontrol atas ukuran hashtable (
#define HASHTABLE_BITWIDTH
). Ketika ukuran ini lebih besar dari atau sama dengan jumlah dinding, itu tidak menjamin tabrakan dan pembaruan O (1). Hashtable yang lebih kecil akan memiliki lebih banyak tabrakan dan sedikit lebih lambat.
- kompilasi dengan
-DDEBUG
untuk cetakan
Peningkatan potensial:
memperbaiki kebocoran memori kecil yang diperbaiki pada edit pertama
pemangkasan alpha / beta ditambahkan di edit ke-2
simetri prune ditambahkan pada edit ke-3 (perhatikan bahwa simetri tidak ditangani oleh memoisasi, sehingga tetap merupakan optimasi yang terpisah.)
memoisasi ditambahkan di edit ke-4
- Saat ini memoisasi menggunakan bit indikator untuk setiap dinding. Papan 3x4 memiliki 31 dinding, sehingga metode ini tidak dapat menangani papan 4x4 terlepas dari kendala waktu. perbaikannya adalah meniru bilangan bulat X-bit, di mana X setidaknya sama besar dengan jumlah dinding.
Kode
Karena kurangnya pengorganisasian, jumlah file telah bertambah. Semua kode telah dipindahkan ke Gudang Github ini . Dalam edit memoisasi, saya menambahkan skrip makefile dan pengujian.
Hasil
Catatan tentang Kompleksitas
Pendekatan brutal terhadap titik dan kotak meledak dengan sangat cepat .
Pertimbangkan papan dengan R
baris dan C
kolom. Ada R*C
kotak, R*(C+1)
dinding vertikal, dan C*(R+1)
dinding horizontal. Itu total W = 2*R*C + R + C
.
Karena Lembik meminta kami untuk menyelesaikan permainan dengan minimax, kita perlu melintasi ke daun pohon permainan. Mari kita abaikan pemangkasan untuk saat ini, karena yang penting adalah urutan besarnya.
Ada W
opsi untuk langkah pertama. Untuk masing-masing, pemain berikutnya dapat memainkan salah satu dari W-1
dinding yang tersisa, dll. Itu memberi kita ruang pencarian SS = W * (W-1) * (W-2) * ... * 1
, atau SS = W!
. Faktorial sangat besar, tetapi itu baru permulaan. SS
adalah jumlah node daun di ruang pencarian. Lebih relevan dengan analisis kami adalah jumlah total keputusan yang harus dibuat (yaitu jumlah cabang B
di pohon). Lapisan pertama cabang memiliki W
opsi. Untuk masing-masing, level selanjutnya W-1
, dll.
B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!
B = SUM W!/(W-k)!
k=0..W-1
Mari kita lihat beberapa ukuran meja kecil:
Board Size Walls Leaves (SS) Branches (B)
---------------------------------------------------
1x1 04 24 64
1x2 07 5040 13699
2x2 12 479001600 1302061344
2x3 17 355687428096000 966858672404689
Angka-angka ini semakin konyol. Setidaknya mereka menjelaskan mengapa kode brute-force tampaknya menggantung selamanya di papan 2x3. Ruang pencarian papan 2x3 adalah 742560 kali lebih besar dari 2x2 . Jika 2x2 membutuhkan 20 detik untuk menyelesaikan, ekstrapolasi konservatif memperkirakan lebih dari 100 hari waktu eksekusi untuk 2x3. Jelas kita perlu memangkas.
Analisis Pemangkasan
Saya mulai dengan menambahkan pemangkasan sangat sederhana menggunakan algoritma alpha-beta. Pada dasarnya, ia berhenti mencari jika lawan yang ideal tidak akan pernah memberikannya kesempatan saat ini. "Hei, lihat - saya menang banyak jika lawan saya membiarkan saya mendapatkan setiap kotak!", Pikir tidak AI, pernah.
sunting Saya juga menambahkan pemangkasan berdasarkan papan simetris. Saya tidak menggunakan pendekatan memoisasi, kalau-kalau suatu hari saya menambahkan memoisasi dan ingin memisahkan analisis itu. Sebagai gantinya, ia bekerja seperti ini: sebagian besar garis memiliki "pasangan simetris" di tempat lain di grid. Ada hingga 7 simetri (horizontal, vertikal, 180 rotasi, 90 rotasi, 270 rotasi, diagonal, dan diagonal lainnya). Semua 7 berlaku untuk papan persegi, tetapi 4 terakhir tidak berlaku untuk papan non-persegi. Setiap dinding memiliki pointer ke "pasangan" untuk masing-masing simetri ini. Jika, setelah berbelok, papan itu simetris secara horizontal, maka hanya satu dari setiap pasangan horisontal yang perlu dimainkan.
sunting sunting Memoisasi! Setiap dinding mendapatkan id unik, yang saya setel dengan mudah sebagai indikator; dinding ke-n memiliki id 1 << n
. Hash papan, maka, hanya OR dari semua dinding yang dimainkan. Ini diperbarui di setiap cabang dalam waktu O (1). Ukuran hashtable diatur dalam a #define
. Semua tes dijalankan dengan ukuran 2 ^ 12, karena mengapa tidak? Ketika ada lebih banyak dinding daripada pengindeksan bit hashtable (12 bit dalam kasus ini), 12 paling signifikan ditutup dan digunakan sebagai indeks. Tabrakan ditangani dengan daftar tertaut di setiap indeks hashtable. Bagan berikut ini adalah analisis cepat dan kotor saya tentang bagaimana ukuran hashtable mempengaruhi kinerja. Pada komputer dengan RAM tak terbatas, kami akan selalu mengatur ukuran tabel dengan jumlah dinding. Papan 3x4 akan memiliki hashtable 2 ^ 31 panjang. Sayangnya kami tidak memiliki kemewahan itu.
Ok, kembali ke pemangkasan .. Dengan menghentikan pencarian tinggi di pohon, kita dapat menghemat banyak waktu dengan tidak turun untuk pergi. 'Faktor Pemangkasan' adalah fraksi dari semua cabang yang memungkinkan yang harus kami kunjungi. Brute-force memiliki faktor pemangkasan 1. Semakin kecil, semakin baik.