Keset dengan kuda dan gajah. Basis keputusan

Ingin membuat teka-teki pemain catur pemula?
Mintalah dia untuk sekakmat dengan kuda dan gajah.

Ingin membuat puzzle seorang programmer pemula?
Minta dia menghitung tikar dengan kuda dan gajah.

gambar

Masalah catur membangkitkan imajinasi programmer, itulah
sebabnya untuk demonstrasi praktis kombinatorik
saya memilih masalah catur paling sulit dari siklus "sekakmat ke raja yang kesepian".

Pengaturan tujuan


Tujuan dari proyek ini adalah untuk menciptakan basis solusi, yaitu, daftar langkah yang tepat untuk semua lokasi yang mungkin dari raja putih, gajah, kuda dan raja hitam di papan catur.

Dalam publikasi ini saya akan memberi tahu Anda bagaimana saya memecahkan masalah ini, kesulitan apa yang harus saya hadapi, dan juga menunjukkan apa yang terjadi pada akhirnya. Teknologi yang digunakan: C #, JavaScript, PHP, HTML, CSS.

Menjadi pemain catur yang sangat biasa-biasa saja, saya tidak pernah belajar bagaimana dengan cepat skakmat dengan kuda dan gajah. Karena itu, saya memutuskan untuk mengkompensasi kekurangan ini dengan keterampilan pemrograman saya, memilah-milah semua posisi yang mungkin dan menemukan langkah yang tepat untuk masing-masing.

Sebelum menulis setidaknya satu baris kode, saya membuat rencana "Napoleon" untuk bagaimana saya akan melakukannya selama beberapa minggu. Saya benar-benar ingin mulai menyelesaikan masalah ini dari akhir, dengan memilah-milah semua kombinasi matte. Dan kemudian lakukan satu langkah mundur sampai semua opsi yang mungkin habis.

Ada berapa opsi?


Ada 64 sel di papan catur. Kami memiliki empat angka.
Jumlah kemungkinan kombinasi adalah 64 * 64 * 64 * 64 = 16.777.216.

Anda hanya dapat meninggalkan gajah berdada putih.
Jumlah opsi akan dibagi dua: 64 * 32 * 64 * 64 = 8.388.608.
Begitu banyak posisi akan ada dalam basis data solusi kami.

Bahkan, ada kombinasi lebih sedikit: dua potong tidak bisa berdiri di satu kotak, raja tidak bisa berdiri di kotak yang berdekatan, raja hitam tidak bisa di bawah cek, dan sebagainya. Ke depan, saya akan mengatakan bahwa basis data solusi ternyata menjadi 5.609.790 kombinasi, array akan diisi oleh 67%.

Namun, untuk menyederhanakan algoritma dan mempercepat akses ke data basis data, saya memutuskan untuk tidak "membuang waktu" dan membuat array empat dimensi untuk semua kombinasi.

Struktur berikut ini didefinisikan untuk menyimpan setiap kombinasi:

    struct Combo
    {
        public Coord whiteKing;
        public Coord whiteBishop;
        public Coord whiteKnight;
        public Coord blackKing;
    }

Di dalam, struktur Coord lain digunakan untuk merekam koordinat gambar, dengan kemampuan untuk menghitung indeks dari 0 hingga 63, serta dengan operator pembanding yang kelebihan beban.

    public struct Coord
    {
        public byte x; //    0  7 ( a  h)
        public byte y; //    0  7
        
        public int index
        {
            get { return x + y * 8; }
            set { x = (byte) (value % 8); 
                  y = (byte) (value / 8); }
        }
        
        public static bool operator == (Coord a, Coord b)
        {
            return a.x == b.x && a.y == b.y;
        }
    }

Struktur ini ternyata sangat nyaman untuk dilewatkan sebagai argumen ke berbagai fungsi tambahan, misalnya:

    bool isCheck (Combo combo); //    
    bool isCheckmate (Combo combo); //  
    bool isCheckByBishop (Combo combo); //     

Namun, untuk mencatat hasil dasar keputusan struktur ini tidak cukup, kita masih perlu ...

Kotak putih


Tujuan dari program kami adalah untuk menciptakan "kotak putih", di mana semua posisi di mana "bergerak adalah putih," dan yang diketahui langkah mana yang akan diambil, dan melalui berapa banyak gerakan itu akan dijamin untuk skakmat, akan ditambahkan.

Bagian integral dari "kotak putih" adalah struktur berikut:

    struct WhitesMove
    {
        public Combo combo;
        public byte moves;     //    
        public Coord moveFrom; //   - 
        public Coord moveTo;   // 
    }

Cara termudah untuk mengatur "kotak putih" adalah membuka matriks empat dimensi. Setiap dimensi dari matriks ini sesuai dengan posisi yang memungkinkan dari setiap gambar:

    WhitesMove [ , , , ] box = new WhitesMove [64, 32, 64, 64];

dimensi pertama adalah koordinat raja putih.
dimensi kedua adalah koordinat gajah putih / 2.
dimensi ketiga adalah koordinat kuda putih.
dimensi keempat adalah koordinat raja hitam.

Yang utama adalah tidak membingungkan pesanan mereka :) Array akan berubah menjadi 33% habis, tetapi sangat nyaman untuk diproses. Dalam array ini bahwa 8.388.608 entri akan disimpan untuk menyelesaikan kombinasi.

Ngomong-ngomong, sebelum mulai menulis semua algoritma pencarian, saya membuat proyek kosong dan menginisialisasi matriks empat dimensi ini, untuk memastikan bahwa ada cukup memori dan tidak perlu untuk menciptakan sesuatu yang tambahan. Rupanya, pengalaman berpartisipasi dalam olimpiade sains komputer selama milenium terakhir, di mana ukuran struktur tidak boleh melebihi 64 kilobyte, memengaruhi Turbo Pascal 7.0.

Ide algoritma


Jelaskan secara singkat ide utama pemecahan masalah ini. Ini didasarkan pada algoritma pencarian luas pertama, yang harus dimodifikasi sedikit, karena dua orang bermain catur dan gerakan dilakukan secara bergantian. Oleh karena itu, alih-alih satu baris, kita perlu dua - "hitam" dan "putih".

    Queue<BlacksMove> blackQueue = new Queue<BlacksMove>();
    Queue<WhitesMove> whiteQueue = new Queue<WhitesMove>();

Dengan struktur WhitesMove kita sudah bertemu. Struktur BlacksMove sedikit lebih sederhana, karena tidak perlu menyimpan langkah terakhir Black di dalamnya.

struct BlacksMove
    {
        public Combo combo;
        public byte moves; 
    }

Pertama, di "garis hitam" kita akan menempatkan semua posisi matte di mana langkahnya berwarna hitam. Kemudian dari setiap posisi seperti itu kita akan membuat langkah mundur untuk White dan membentuk "garis putih" - daftar posisi di mana Putih bergerak.

Langkah-langkah ini perlu diulang sampai semua kemungkinan kombinasi habis.

Algoritma utama dalam bentuk pseudo-code:

       " ", " ", " "
         
         " "

      
      {
           " "  
                 " "
                    
                      
                           " "
                             " "
                             " "

           " "  
                 " "
                      
                        
                      " "
                         " "

      }  " "  

       " "   


Posisi matte


Membuat dasar gerakan yang tepat dimulai dengan pencarian semua kombinasi matte. Penggunaan enumerator memungkinkan untuk menggambarkan proses ini dengan cukup efektif.

    foreach (Combo combo in AllCheckmates())
    {
        BlacksMove checkmate = new BlacksMove { combo = combo, moves = 0 };
        blackQueue.Enqueue(checkmate);
    }

Total ditemukan 232 posisi matte. Biarkan saya mengingatkan Anda bahwa kami membatasi diri hanya untuk gajah lapangan putih.

Beberapa dari mereka cukup eksotis, tidak ada dan "kooperatif", ini adalah ketika raja hitam sendiri naik ke bawah tikar.

Mat.  Apa langkah White?

Pemain catur sangat menyadari bahwa tikar dengan kuda dan gajah lapangan putih harus ditempatkan di sudut putih. Di sudut hitam, skakmat hanya mungkin jika hitam akan bermain bersama. Saya secara khusus memposting foto dengan pseudo-otomat pada awal artikel untuk memancing perhatian para pemain catur nyata :)

Mat dalam satu gerakan


Langkah selanjutnya adalah membalikkan Putih. Artinya, untuk setiap posisi matte yang ditemukan, buat semua kemungkinan gerakan putih kembali .

Bagaimana cara membuat gerakan mundur? Mengingat bahwa tidak ada tangkapan yang disediakan untuk posisi kami, algoritme ini cukup sederhana - lakukan gerakan apa pun oleh White, setelah itu tidak akan ada pemeriksaan ke raja hitam.

Semua posisi yang ditemukan dengan cara ini sudah dapat dimasukkan ke dalam "kotak putih", menunjukkan bahwa ada satu gerakan di depan matras, dan langkah apa yang harus dilakukan. Sepanjang jalan, kami menempatkan kombinasi yang ditemukan di "garis hitam".

Berikut bagian dari algoritma ini:

    //  " "  
    while (blackQueue.Count > 0)
    {
        //    " "
        BlacksMove black = blackQueue.Dequeue();
        //       
        foreach (WhitesMove white in AllWhiteBackMoves(black))
            //     
            if (!isCheck(white.combo))
                //      " "
                if (!whiteBox.Exists(white.combo))
                {
                    //    " "
                    whiteBox.Put (white);
                    //    " "
                    whiteQueue.Enqueue(white);
                }
    }

Ngomong-ngomong, tentang hasil
yield- , , :

        IEnumerable<WhitesMove> AllWhiteBackMoves(BlacksMove black)
        {
            foreach (WhitesMove white in AllWhiteKingMoves(black))
                yield return white;
            foreach (WhitesMove white in AllWhiteBishopMoves(black))
                yield return white;
            foreach (WhitesMove white in AllWhiteKnightMoves(black))
                yield return white;
        }


Sebanyak 920 posisi tersebut ditemukan, berikut adalah yang paling menarik: Gerakan

kuda
Ksatria 1 langkah ksatria 2 Ksatria 3

:
langkah gajah 1 langkah gajah 2

Gerakan gajah : Langkah Raja:
raja bergerak

Mat dalam satu setengah bergerak


Langkah selanjutnya adalah membalikkan Hitam. Dengan algoritma ini saya menghabiskan waktu paling lama, banyak kesalahan dibuat sebelum semuanya bekerja dengan benar.

Pada pandangan pertama, semuanya mirip dengan versi sebelumnya: untuk setiap posisi dari "garis putih" perlu untuk memilah semua kemungkinan langkah raja hitam. Dan tambahkan semua kombinasi yang ditemukan ke "garis hitam" - setelah semua, ini adalah skakmat dalam satu setengah langkah, dari mana Anda kemudian dapat membuat langkah mundur untuk Putih lagi - akan ada skakmat dalam dua langkah - dan terus sampai semua opsi direvisi.

Itu kesalahannya. Dengan langkah apa pun yang mungkin, Black mendapat sekakmat "kooperatif" dalam satu setengah langkah, tetapi dalam kenyataannya raja tidak harus pergi di bawah skakmat. Dmitry Grin menunjukkan kesalahan ini kepada saya, yang menghadiri semua webinar saya untuk membuat program ini, yang mana saya berterima kasih padanya secara terpisah.

Algoritme yang benar adalah sebagai berikut: untuk setiap posisi N setelah gerakan terbalik raja hitam, Anda harus melalui semua gerakan langsung yang mungkin untuk memastikan bahwa semuanya mengarah ke posisi yang familier dari "kotak putih", yaitu mengarah ke matras. Dan hanya setelah posisi itu N dapat ditambahkan ke "garis hitam". Dan jika raja hitam dapat "menyelinap" dari posisi N, maka kita lewati opsi ini. Dia akan bertemu di iterasi berikutnya, ketika akan ada posisi yang lebih akrab.

Berikut bagian dari algoritma ini:

    //  " "  
    while (whiteQueue.Count > 0)
    {
        //   N  " "
        WhitesMove white = whiteQueue.Dequeue();
        Combo whiteFigures = white.combo;
        //   N      
        foreach (BlacksMove black in AllBlackBackMoves(white))
        {
            bool solved = true;
            //      
            foreach (Coord blackKing in AllKingMoves(black.combo.blackKing))
            {
                whiteFigures.blackKing = blackKing; //   
                if (isCheck(whiteFigures)) //    
                    continue;
                if (box.Exists(whiteFigures)) //   
                    continue;
                solved = false; //    ""
                break;
            }
            //       
            //     " "
            if (solved)
                //    " "
                blackQueue.Enqueue(black);
        }
    }

Secara total, 156 kombinasi "Mat dan setengah bergerak" ditemukan.

Iteratif setengah jalan


Algoritma yang dijelaskan untuk membuat half-pass harus diulang. Dari "garis hitam" kita membentuk "garis putih", dan kemudian sebaliknya - dari garis "putih" kita membentuk "garis hitam". Demikian seterusnya sampai semua posisi baru habis. "Kotak putih" diisi pada tahap pembentukan "garis putih", karena menempatkan posisi di mana Putih bergerak.

Algoritme siap pakai untuk penghitungan semua opsi bekerja di suatu tempat dalam 12 menit dan berhenti bergerak 33. Itulah berapa banyak gerakan maksimum yang diperlukan untuk mengawinkan raja hitam dengan kuda dan gajah dari posisi apa pun.

Ngomong-ngomong, tidak ada begitu banyak posisi "paling sulit" seperti itu, hanya 156, di sini adalah salah satunya:

Mat di 33 bergerak

Mata tidak akan!


Ada banyak posisi di mana, bahkan setelah kepindahan White, raja hitam dapat makan seorang ksatria atau uskup dan mendapatkan hasil imbang. Ada juga opsi jalan buntu. Berikut adalah beberapa barang yang paling menarik.

Tidak ada teman Tidak ada teman

Tidak ada teman Tidak ada teman

Bagaimana cara menyimpan database solusi


Bagaimana cara menyimpan basis solusi yang ditemukan?
Cara termudah dan salah adalah dengan menggunakan serialisasi. Susunan empat dimensi serial dari struktur ini membutuhkan ruang disk sebesar 1,7 gigabytes (!). Proses serialisasi berlangsung sekitar enam menit, deserialisasi memakan waktu yang hampir sama.

Opsi ini, tentu saja, tidak cocok. Selain itu, dalam praktiknya tidak perlu menggunakan seluruh array empat dimensi. Hanya satu entri yang diperlukan untuk item baris tertentu.

Eureka! Untuk menghemat ruang, Anda masih bisa menghilangkan menyimpan koordinat angka untuk setiap kombinasi. Ketika kita memiliki array empat dimensi, posisi setiap gambar di papan ditentukan secara unik oleh indeksnya dalam array.

Diputuskan untuk menyimpan seluruh database solusi dalam satu file - sebagai pemindaian linier dari array empat dimensi. Untuk setiap posisi yang memungkinkan, alamat dihitung di mana jawaban yang benar dicatat.

Bagaimana cara mencatat jawaban yang kita butuhkan selengkap mungkin? Posisi gambar tidak perlu disimpan, jadi hanya tiga angka yang tersisa - berapa banyak yang bergerak ke matras, apa yang harus pergi dan ke mana harus pergi. Inilah bagaimana langkah yang tepat untuk Putih ditentukan secara unik.

6 bit Berapa banyak perpindahan ke mat adalah bilangan bulat dari 0 hingga 33,2
bit. Sosok apa yang berjalan - tiga opsi yang mungkin, seorang raja, gajah atau kuda.
6 bit Ke mana perginya - indeks bidang di papan tulis adalah dari 0 hingga 63.

Jadi, untuk setiap catatan keputusan, dua byte sudah cukup:
1 byte - berapa banyak yang bergerak ke matras, atau 0 jika posisinya tidak dikenal.
2 byte - FFNNNNNN
FF - jumlah gambar yang harus di berjalan (1 - raja, 2 - gajah, 3 - kuda)
NNNNNN - koordinat sel - ke mana harus pergi (dari 0 hingga 63).

Jadi, file database solusi mengambil 64 * 32 * 64 * 64 kata = tepat 16 megabyte. Posisi gambar diatur oleh koordinat setiap kata, dalam byte pertama - jumlah gerakan ke mat (atau 0 jika tidak ada solusi), langkah kedua menyimpan langkah yang benar.

Dimungkinkan untuk mengurangi ukuran file hingga setengahnya jika Anda tidak menyimpan jumlah gerakan ke matras, tetapi itu tidak akan menarik untuk dimainkan seperti itu.

Koordinat gajah putih kotak hitam


Saatnya membayar untuk optimasi. Kita perlu menerapkan algoritma perhitungan ulang koordinat untuk kombinasi dengan gajah "hitam dan putih".

Ini dilakukan sebagai berikut. Jika koordinat gajah jatuh di lapangan hitam, maka koordinat semua angka di papan harus "dibalik". Dalam hal ini, koordinat Y tetap tidak berubah, dan X berubah menjadi 7-X. Peragaan visual dari flip koordinat, lihat gambar.

Koordinasikan flip

Jika gajah berdiri di sangkar putih, maka pertama-tama Anda perlu "membalik" koordinat semua angka. Kemudian cari posisi dalam database solusi. Dan sekali lagi "balikkan" koordinat langkah yang benar dibaca dari sana.

Visualisasi basis solusi


Jadi, masalahnya selesai!
Database solusi telah dibuat.
Tetapi bagaimana cara menunjukkannya?

Cara yang paling jelas adalah menggunakan teknologi web sehingga Anda bisa memberikan tautan ke contoh yang berfungsi. Pada "formula programmer" saya, kursus foto " Nano-Chess " sudah dibuat , di mana menggunakan teknologi HTML, CSS, JavaScript dan PHP papan catur interaktif dibuat untuk bermain tanpa aturan untuk dua. Script ini diambil sebagai dasar.

Saya hanya menyisakan empat bagian, menghilangkan kemungkinan penangkapan, menambahkan fungsi PHP untuk membaca gerakan yang benar dari basis solusi, dan “menghembuskan kehidupan” melalui JavaScript.

Pada halaman www.videosharp.info/chess Anda dapat bereksperimen dengan basis data solusi.
Matras interaktif dengan kuda dan gajah
Untuk setiap posisi, gerakan dihitung untuk putih dan hitam.
Untuk White - langkah terbaik yang mengarah ke skakmat.
Untuk hitam - berapa banyak gerakan ke matras untuk setiap gerakan yang memungkinkan.

Anda dapat melakukan gerakan angka apa pun dengan mouse, tidak harus dengan aturan.
Skrip akan menghitung opsi untuk posisi apa pun, atau menulis bahwa tidak ada opsi.

Sangat menarik untuk bermain, melakukan gerakan yang diusulkan atau memindahkan potongan sesuai kebijaksanaan Anda.

Kesimpulan


Sebuah karya yang hebat dan menarik dilakukan untuk menyelesaikan masalah catur.
Jika Anda ingin mengulangi cara ini, Anda dapat menonton video tentang membuat program ini dari awal hingga hasilnya dengan penjelasan terperinci dan tugas-tugas independen.

Semoga beruntung

Source: https://habr.com/ru/post/id396453/


All Articles