Algoritma untuk mengoptimalkan game pertandingan dengan antrian yang dikenal

10

Saya mencoba untuk menulis pemecah dalam C # .NET untuk permainan yang dikenal sebagai Flowerz. Untuk referensi Anda, Anda dapat memainkannya di MSN, di sini: http://zone.msn.com/gameplayer/gameplayer.aspx?game=flowerz . Saya menulisnya untuk bersenang-senang, bukan untuk semua jenis tugas atau pekerjaan apa pun yang terkait. Karena itu, satu-satunya batasan adalah komputer saya (intel i7 core, dengan 8GB RAM). Itu tidak perlu dijalankan di tempat lain, sejauh yang saya ketahui.

Singkatnya, aturannya seperti ini:

  • Ada antrian penuh dengan bunga berwarna. Panjangnya sewenang-wenang
    • Antrian tidak dapat dipengaruhi
    • Antrian dibuat di awal level
  • Bunga memiliki satu atau dua warna.
    • Jika ada dua warna, maka ada warna luar, dan warna dalam. Dalam kasus dua warna, warna luar digunakan untuk pencocokan.
    • Jika ada kecocokan, maka warna luar menghilang dan bunga sekarang menjadi bunga warna tunggal dengan warna yang sama dengan bunga dalam
  • Tujuan permainan ini adalah untuk membuat pertandingan tiga (atau lebih) dengan warna yang sama
    • Ketika bunga dengan warna tunggal adalah bagian dari korek api, bunga itu dihilangkan dari lapangan bermain, menciptakan ruang kosong
    • Anda dapat mencocokkan bunga warna tunggal dengan warna luar bunga dua warna. Dalam hal ini, warna bunga tunggal menghilang, warna luar bunga dua warna menghilang dan warna bagian dalam tetap ada
  • Anda memenangkan ronde ketika antrian kosong dan setidaknya ada satu ruang kosong yang tersisa
  • Pencocokan Cascading dimungkinkan. Kaskade adalah ketika tiga (atau lebih) bunga luar menghilang, dan ketika warna bagian dalam mereka membentuk rantai 3 (atau lebih banyak bunga) lainnya.
  • Lapangan bermain selalu 7x7
  • Beberapa ruang di lapangan ditutupi oleh batu
    • Anda tidak dapat menempatkan bunga di atas batu
  • Antrian juga dapat berisi sekop yang dapat Anda gunakan untuk memindahkan bunga yang ditempatkan ke ruang kosong
    • Anda harus menggunakan sekop, tetapi Anda tidak benar-benar harus memindahkan bunga: sangat legal untuk meletakkannya kembali dari tempat asalnya.
  • Antrian juga dapat berisi kupu-kupu berwarna. Saat Anda menggunakan kupu-kupu ini pada bunga, maka bunga itu mendapatkan warna kupu-kupu
    • Menerapkan kupu-kupu pada bunga dengan dua warna, menghasilkan bunga hanya mendapatkan satu warna, yaitu kupu-kupu
    • Anda dapat membuang kupu-kupu di ruang kosong atau bunga yang sudah memiliki warna ini
  • Membersihkan lapangan tidak memenangkan pertandingan

Tujuan dari solver itu sederhana: temukan cara untuk mengosongkan antrian, dengan sebanyak mungkin ruang sisa di lapangan bermain. Pada dasarnya, AI memainkan game untuk saya. Output dari solver adalah daftar dengan gerakan yang ditemukannya. Saya tidak tertarik dengan skor, tetapi bertahan selama mungkin, maka saya tertarik pada gerakan yang meninggalkan ruang terbuka sebanyak mungkin.

Tak perlu dikatakan, ruang pencarian tumbuh dengan cepat semakin besar antrian yang didapat, sehingga kekuatan kasar keluar dari pertanyaan. Antrian dimulai pada 15, dan tumbuh dengan 5 setiap dua atau tiga level, jika saya ingat benar. Dan, tentu saja, menempatkan bunga pertama pada (0,0) dan yang kedua pada (0,1) berbeda dari menempatkan bunga pertama pada (1,0) dan bunga kedua pada (0,0), terutama ketika lapangan sudah diisi dengan bunga dari babak sebelumnya. Keputusan sederhana seperti itu dapat membuat perbedaan dalam membuatnya atau tidak.

Pertanyaan yang saya miliki adalah sebagai berikut:

  • Masalah apa ini? (pikirkan penjual keliling, ransel, atau masalah kombinatorial lainnya). Mengetahui ini bisa membuat Google-fu saya sedikit lebih baik.
  • Algoritme apa yang bisa memberi saya hasil yang baik, cepat?

Mengenai yang terakhir: Pada awalnya, saya mencoba untuk menulis algoritma heuristik saya sendiri (pada dasarnya: bagaimana saya menyelesaikannya, jika saya tahu antrian?), Tetapi itu menghasilkan banyak kasus tepi dan pencocokan skor yang mungkin saya lewatkan.

Saya sedang berpikir untuk menggunakan algoritma genetika (karena saya setidaknya tahu bagaimana menggunakannya ...), tapi saya mengalami beberapa masalah dalam memutuskan representasi biner dari papan. Lalu ada masalah crossover, tapi itu bisa diselesaikan dengan operator crossover yang dipesan atau jenis operasi serupa.

Dugaan saya adalah bahwa pemecah harus selalu mengetahui konfigurasi papan dan antrian itu mencoba mengosongkan.

Saya tahu beberapa algoritma heuristik lain seperti jaringan saraf dan sistem logika fuzzy, tetapi saya tidak memiliki pengalaman untuk mengetahui mana yang paling cocok untuk diterapkan, atau jika ada yang lain yang lebih cocok untuk tugas yang dihadapi.

pengguna849924
sumber
Saya pernah bekerja bahwa ruang pencarian dari beberapa permainan kompleks yang sedang saya kerjakan adalah 32Gb. Pada saat itu (saya memiliki disk drive 20 MB) yang tidak mungkin dilakukan, tetapi hari ini hanya bisa dilakukan dalam RAM untuk beberapa komputer.
Jonathan
Apakah bunga dengan hanya satu warna hilang sepenuhnya saat dicocokkan? Dan bisakah bunga dengan dua warna cocok dengan lapisan luarnya terhadap satu warna bunga satu warna? Saya kira begitu pada kedua hal, tetapi ini tidak pernah secara eksplisit ditentukan dalam deskripsi masalah ...
Steven Stadnicki
@StevenStadnicki Terima kasih! Saya telah menambahkan informasi itu ke pertanyaan awal.
user849924
1
Sebagai catatan kecil, kebetulan, sangat mungkin bahwa versi 'boolean' dari masalah ini (adakah cara menempatkan bunga-bunga di antrian untuk meninggalkan papan benar-benar kosong di akhir?) Adalah NP-complete; itu memiliki kemiripan yang jelas dengan masalah Clickomania ( erikdemaine.org/clickomania ) yang merupakan NP-complete, dan masalahnya tidak lebih sulit daripada NP karena diberi solusi yang diakui (panjang polinomial) mudah untuk memverifikasi dengan hanya menjalankan simulasi. Ini berarti bahwa masalah optimasi mungkin dalam FP ^ NP.
Steven Stadnicki

Jawaban:

9

Pada pandangan pertama , ini bagi saya merupakan masalah pencarian agen tunggal . Yaitu: Anda memiliki satu agen ("pemain" AI). Ada status permainan yang mewakili kondisi papan permainan dan antrian, dan Anda memiliki fungsi penerus yang dapat menghasilkan status baru dari kondisi tertentu.

Ada juga kriteria tujuan yang memberi tahu Anda ketika negara bagian adalah negara "terpecahkan". Dan biaya jalur - biaya memajukan ke keadaan tertentu (selalu "1 bergerak" dalam kasus ini).

Salah satu puzzle prototipikal dari jenis ini adalah 15 Puzzle . Dan cara khas untuk mengatasinya adalah dengan pencarian informasi - misalnya, pencarian heuristik klasik A * dan variannya.


Namun ada masalah dengan pendekatan ini pada pandangan pertama. Algoritma seperti A * dirancang untuk memberi Anda jalur terpendek ke tujuan (misalnya: jumlah gerakan terkecil). Dalam kasus Anda, jumlah bergerak selalu tetap - tidak ada jalur terpendek - sehingga pencarian heuristik hanya akan memberikan sebuah jalan untuk sebuah pertandingan selesai.

Yang Anda inginkan adalah urutan gerakan yang memberi Anda kondisi permainan yang paling baik diselesaikan.

Jadi yang harus Anda lakukan adalah membalikkan masalahnya sedikit. Alih-alih papan permainan menjadi "negara", urutan gerakan menjadi "negara". (Yaitu: Tempatkan item dalam antrian di posisi "D2, A5, C7, B3, A3, ...")

Ini berarti kami tidak terlalu peduli bagaimana status tersebut dihasilkan. Dewan itu sendiri bersifat insidentil, hanya diperlukan untuk mengevaluasi kualitas suatu negara.

Ini mengubah masalah menjadi masalah optimisasi , yang dapat diselesaikan dengan algoritma pencarian lokal (yang pada dasarnya berarti membuat status di sekitar status yang diberikan, dan memilih status terbaik, tanpa peduli tentang jalur antara status.)

Teka-teki prototipe dari jenis ini adalah Puzzle Delapan Ratu .

Di kelas masalah ini, Anda mencari ruang keadaan untuk menemukan solusi yang baik, di mana "baik" dievaluasi oleh fungsi objektif (juga disebut fungsi evaluasi atau, untuk algoritma genetika, fungsi kebugaran ).

Untuk masalah Anda, fungsi objektif mungkin mengembalikan nilai antara 0 dan N, untuk jumlah item dalam antrian yang digunakan sebelum mencapai keadaan gagal (di mana N adalah panjang antrian). Dan, jika tidak, nilai N + M, di mana M adalah jumlah ruang kosong yang tersisa di papan setelah antrian kosong. Dengan demikian - semakin tinggi nilainya, solusi "lebih baik secara objektif".

(Perlu dicatat, pada titik ini, bahwa Anda harus mengoptimalkan omong kosong dari kode yang menjalankan permainan - yang mengubah keadaan menjadi papan jadi yang dapat digunakan untuk fungsi tujuan.)


Adapun contoh-contoh algoritma pencarian lokal : Pola dasar adalah pencarian mendaki bukit yang mengambil keadaan tertentu, memutasinya, dan bergerak menuju keadaan berikutnya yang memberikan hasil yang lebih baik.

Jelas ini bisa macet di maksimum lokal (dan sejenisnya). Dalam bentuk ini disebut pencarian lokal serakah . Ada banyak variasi untuk mengatasi hal ini dan masalah lainnya ( Wikipedia telah Anda bahas ). Beberapa di antaranya (misalnya: pencarian balok lokal ) melacak beberapa negara sekaligus.

Satu variasi khusus tentang ini adalah algoritma genetika ( Wikipedia ). Langkah-langkah dasar untuk algoritma genetika adalah:

  1. Tentukan beberapa cara untuk mengubah keadaan menjadi string. Dalam kasus Anda, ini mungkin rangkaian digit panjang antrian dari 1 hingga 49 (mewakili semua kemungkinan penempatan pada papan 7x7, mungkin masing-masing disimpan 1 byte). (Bagian "sekop" Anda dapat diwakili oleh dua entri antrian berikutnya, untuk setiap fase gerakan.)
  2. Pilih secara acak populasi pemuliaan, memberikan kemungkinan lebih tinggi untuk negara bagian yang memiliki kebugaran lebih baik . Populasi pengembangbiakan harus sama dengan populasi asli - Anda dapat memilih status dari populasi asli beberapa kali.
  3. Pasangan kondisi dalam populasi berkembang biak (pertama pergi dengan yang kedua, ketiga dengan yang keempat, dll)
  4. Pilih titik crossover secara acak untuk setiap pasangan (posisi dalam string).
  5. Buat dua keturunan untuk setiap pasangan dengan menukar bagian string setelah titik crossover.
  6. Secara acak, mutasikan setiap status keturunan. Sebagai contoh: secara acak memilih untuk mengubah posisi acak dalam string ke nilai acak.
  7. Ulangi proses dengan populasi baru sampai populasi bertemu pada satu atau lebih solusi (atau setelah beberapa generasi, atau solusi yang cukup baik ditemukan).

Sebuah solusi algoritma genetika merasa seperti itu mungkin sesuai untuk masalah Anda - dengan beberapa penyesuaian. Kesulitan terbesar yang saya lihat adalah bahwa, dengan representasi string di atas, Anda akan menemukan bahwa mengganti bagian ekor negara dengan bagian depan yang sangat berbeda cenderung menghasilkan keadaan "mati" (karena gerakan yang saling bertentangan antara kedua bagian, akibatnya adalah dalam skor kebugaran rendah).

Mungkin mungkin untuk mengatasi masalah ini. Satu ide yang muncul di benak adalah membuatnya lebih mungkin bagi negara-negara dengan bagian depan yang serupa untuk menjadi pasangan pengembangbiakan. Ini bisa sesederhana menyortir populasi negara berkembang, sebelum memasangkannya. Mungkin juga membantu untuk secara bertahap memindahkan posisi yang mungkin dari crossover, dari awal hingga akhir string, karena jumlah generasi meningkat.

Dimungkinkan juga untuk menghadirkan representasi gerakan dalam kondisi yang lebih tahan (bahkan mungkin sepenuhnya kebal) untuk menghadapi kondisi kegagalan "kuadrat penuh". Mungkin mewakili gerakan sebagai koordinat relatif dari langkah sebelumnya. Atau setelah bergerak pilih ruang kosong terdekat ke posisi yang diberikan.

Seperti dengan semua masalah AI non-sepele seperti ini, itu akan memerlukan beberapa bermain-main yang signifikan.

Dan, seperti yang saya sebutkan sebelumnya, tantangan utama lainnya hanyalah mengoptimalkan fungsi objektif Anda. Dengan mempercepat ini, Anda dapat mencari ruang dalam jumlah besar, dan mencari solusi untuk permainan dengan antrian yang lebih panjang.


Untuk jawaban ini, terutama untuk mendapatkan semua terminologi yang benar, saya harus menggali buku teks AI universitas saya, "Inteligensi Buatan: Pendekatan Modern" oleh Russell dan Norvig. Tidak yakin apakah itu "baik" (saya tidak memiliki teks AI lain untuk membandingkannya), tapi itu tidak buruk. Setidaknya itu cukup besar;)

Andrew Russell
sumber
Saya mengidentifikasi masalah dengan crossover juga: sangat mungkin bahwa seorang anak memiliki lebih banyak barang yang ditempatkan daripada yang tersedia dalam antrian (jenis kekurangan GA untuk TSP: ia mungkin mengunjungi kota dua kali atau lebih (atau tidak sama sekali!) Setelah crossover. Mungkin crossover yang dipesan ( permutationcity.co.uk/projects/mutants/tsp.html ) bisa berfungsi. Hal ini terutama berlaku ketika Anda membuat urutan gerakan negara.
user849924
Tidak yakin itu cukup benar - dalam pikiran saya, keadaan kegagalan adalah sepotong ditempatkan pada posisi yang sudah ditempati (sehingga mengakhiri permainan itu lebih awal, menghasilkan skor kebugaran rendah). Jadi panjang antrian cocok dengan panjang string genetik - itu tidak pernah menjadi panjang yang salah. Masih - Anda mungkin ke sesuatu dengan gagasan bertukar dan memesan. Jika pesanan yang diberikan menghasilkan permainan yang selesai, dan Anda bertukar dua gerakan, saya membayangkan bahwa ada peluang yang jauh lebih baik dari keadaan termutasi juga menjadi permainan yang selesai daripada jika Anda hanya mengatur satu (atau dua?) Posisi gerakan secara acak. .
Andrew Russell
Status kegagalan adalah ketika Anda tidak memiliki opsi lagi untuk menempatkan gerakan, yaitu ketika Anda kehabisan ruang kosong dan tidak ada kecocokan yang terjadi dengan gerakan itu. Mirip dengan apa yang Anda katakan: Anda harus meletakkannya pada posisi yang sudah ditempati (tapi itu hanya berlaku ketika tidak ada lagi tempat untuk memulai). Crossover yang saya posting bisa menarik. Kromosom A memiliki item yang ditempatkan pada A1, B1, ..., G1, A2, B2 dan C2, dan kromosom B pada G7 ... A7, G6, F6 dan E6. Pilih beberapa tebusan dari A dan simpan indeks mereka. Pilih komplemen A dari B dan simpan indeks mereka dan gabungkan untuk anak.
user849924
'Masalah' dengan crossover ini adalah bahwa beberapa gerakan di tempat yang sama diperbolehkan. Tapi itu harus dengan mudah dipecahkan dengan sesuatu yang mirip dengan SimulateOtomatisPerubahan dari solusi Stefan K: menerapkan gerakan / keadaan anak ke kondisi dasar (cukup menerapkan semua gerakan, satu per satu) dari lapangan bermain dan jika keadaan penerimaan (antrian kosong) ) tidak dapat dicapai (karena Anda harus meletakkan bunga di tempat yang diduduki), maka anak tersebut tidak valid dan kami harus berkembang biak lagi. Di sinilah kondisi kegagalan Anda muncul. Saya mendapatkannya sekarang, heh. : D
user849924
Saya menerima ini sebagai jawabannya, karena dua alasan. Pertama: Anda memberi saya ide yang saya butuhkan agar GA bekerja untuk masalah ini. Kedua: Anda yang pertama. ; p
user849924
2

Kategorisasi

Jawabannya tidak mudah. Teori gim memiliki beberapa klasifikasi untuk gim, tetapi tampaknya tidak ada kecocokan 1: 1 untuk gim tersebut dengan teori khusus. Ini adalah bentuk khusus dari masalah kombinatorial.

Ini bukan penjual keliling, yang akan memutuskan untuk memesan di mana Anda mengunjungi "node" dengan biaya untuk mencapai node berikutnya dari yang terakhir. Anda tidak dapat memesan ulang antrian, Anda juga tidak harus menggunakan semua bidang pada peta.

Knapsack tidak cocok karena beberapa bidang menjadi kosong saat memasukkan beberapa item ke dalam "knapsack". Jadi itu mungkin beberapa bentuk perpanjangan dari itu, tetapi kemungkinan besar algoritma tidak akan berlaku karena ini.

Wikipedia memberikan beberapa petunjuk tentang kategorisasi di sini: http://en.wikipedia.org/wiki/Game_theory#Types_of_games

Saya akan mengategorikannya sebagai "masalah kontrol optimal diskrit-waktu" ( http://en.wikipedia.org/wiki/Optimal_control ), tetapi saya tidak berpikir ini akan membantu Anda.

Algoritma

Jika Anda benar-benar mengetahui antrean lengkap, Anda dapat menerapkan algoritma penelusuran pohon. Seperti yang Anda katakan, kompleksitas masalah tumbuh sangat cepat dengan panjang antrian. Saya sarankan untuk menggunakan algoritme seperti "Pencarian kedalaman-pertama (DFS)", yang tidak memerlukan banyak memori. Karena skor tidak masalah bagi Anda, Anda bisa berhenti setelah menemukan solusi pertama. Untuk memutuskan cabang mana yang akan dicari terlebih dahulu, Anda harus menerapkan heuristik untuk pemesanan. Itu berarti Anda harus menulis fungsi evaluasi (misalnya: jumlah bidang kosong; semakin canggih ini, semakin baik), yang memberikan skor untuk membandingkan langkah selanjutnya yang paling menjanjikan.

Maka Anda hanya perlu bagian-bagian berikut:

  1. model status permainan, yang menyimpan semua informasi permainan (mis. status papan / peta, antrian, nomor / posisi bergerak dalam antrian)
  2. generator pemindahan, yang memberi Anda semua pemindahan yang valid untuk kondisi gim tertentu
  3. fungsi "do move" dan "undo move"; yang menerapkan / membatalkan langkah yang diberikan (valid) ke status permainan. Sedangkan fungsi "do move" harus menyimpan beberapa "undo information" untuk fungsi "undo". Menyalin kondisi permainan dan memodifikasinya di setiap iterasi akan memperlambat pencarian secara signifikan! Coba setidaknya untuk menyimpan status pada tumpukan (= variabel lokal, tidak ada alokasi dinamis menggunakan "baru").
  4. fungsi evaluasi, yang memberikan skor yang sebanding untuk setiap kondisi permainan
  5. fungsi pencarian

Berikut ini adalah implementasi referensi yang tidak lengkap untuk pencarian mendalam-pertama:

public class Item
{
    // TODO... represents queue items (FLOWER, SHOVEL, BUTTERFLY)
}

public class Field
{
    // TODO... represents field on the board (EMPTY or FLOWER)
}

public class Modification {
    int x, y;
    Field originalValue, newValue;

    public Modification(int x, int y, Field originalValue, newValue) {
        this.x = x;
        this.y = y;
        this.originalValue = originalValue;
        this.newValue = newValue;
    }

    public void Do(GameState state) {
        state.board[x,y] = newValue;
    }

    public void Undo(GameState state) {
        state.board[x,y] = originalValue;
    }
}

class Move : ICompareable {

    // score; from evaluation function
    public int score; 

    // List of modifications to do/undo to execute the move or to undo it
    Modification[] modifications;

    // Information for later knowing, what "control" action has been chosen
    public int x, y;   // target field chosen
    public int x2, y2; // secondary target field chosen (e.g. if moving a field)


    public Move(GameState state, Modification[] modifications, int score, int x, int y, int x2 = -1, int y2 = -1) {
        this.modifications = modifications;
        this.score = score;
        this.x = x;
        this.y = y;
        this.x2 = x2;
        this.y2 = y2;
    }

    public int CompareTo(Move other)
    {
        return other.score - this.score; // less than 0, if "this" precededs "other"...
    }

    public virtual void Do(GameState state)
    {
        foreach(Modification m in modifications) m.Do(state);
        state.queueindex++;
    }

    public virtual void Undo(GameState state)
    {
        --state.queueindex;
        for (int i = m.length - 1; i >= 0; --i) m.Undo(state); // undo modification in reversed order
    }
}

class GameState {
    public Item[] queue;
    public Field[][] board;
    public int queueindex;

    public GameState(Field[][] board, Item[] queue) {
        this.board = board;
        this.queue = queue;
        this.queueindex = 0;
    }

    private int Evaluate()
    {
        int value = 0;
        // TODO: Calculate some reasonable value for the game state...

        return value;
    }

    private List<Modification> SimulateAutomaticChanges(ref int score) {
        List<Modification> modifications = new List<Modification>();
        // TODO: estimate all "remove" flowers or recoler them according to game rules 
        // and store all changes into modifications...
        if (modifications.Count() > 0) {
            foreach(Modification modification in modifications) modification.Do(this);

            // Recursively call this function, for cases of chain reactions...
            List<Modification> moreModifications = SimulateAutomaticChanges();

            foreach(Modification modification in modifications) modification.Undo(this);

            // Add recursively generated moves...
            modifications.AddRange(moreModifications);
        } else {
            score = Evaluate();
        }

        return modifications;
    }

    // Helper function for move generator...
    private void MoveListAdd(List<Move> movelist, List<Modifications> modifications, int x, int y, int x2 = -1, int y2 = -1) {
        foreach(Modification modification in modifications) modification.Do(this);

        int score;
        List<Modification> autoChanges = SimulateAutomaticChanges(score);

        foreach(Modification modification in modifications) modification.Undo(this);

        modifications.AddRange(autoChanges);

        movelist.Add(new Move(this, modifications, score, x, y, x2, y2));
    }


    private List<Move> getValidMoves() {
        List<Move> movelist = new List<Move>();
        Item nextItem = queue[queueindex];
        const int MAX = board.length * board[0].length + 2;

        if (nextItem.ItemType == Item.SHOVEL)
        {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: Check if valid, else "continue;"

                    for (int x2 = 0; x2 < board.length; ++x2)
                    {
                        for(int y2 = 0; y2 < board[x].length; ++y2) {
                            List<Modifications> modifications = new List<Modifications>();

                            Item fromItem = board[x][y];
                            Item toItem = board[x2][y2];
                            modifications.Add(new Modification(x, y, fromItem, Item.NONE));
                            modifications.Add(new Modification(x2, y2, toItem, fromItem));

                            MoveListAdd(movelist, modifications, x, y, x2, y2);
                        }
                    }
                }
            }

        } else {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: check if nextItem may be applied here... if not "continue;"

                    List<Modifications> modifications = new List<Modifications>();
                    if (nextItem.ItemType == Item.FLOWER) {
                        // TODO: generate modifications for putting flower at x,y
                    } else {
                        // TODO: generate modifications for putting butterfly "nextItem" at x,y
                    }

                    MoveListAdd(movelist, modifications, x, y);
                }
            }
        }

        // Sort movelist...
        movelist.Sort();

        return movelist;
    }


    public List<Move> Search()
    {
        List<Move> validmoves = getValidMoves();

        foreach(Move move in validmoves) {
            move.Do(this);
            List<Move> solution = Search();
            if (solution != null)
            {
                solution.Prepend(move);
                return solution;
            }
            move.Undo(this);
        }

        // return "null" as no solution was found in this branch...
        // this will also happen if validmoves == empty (e.g. lost game)
        return null;
    }
}

Kode ini tidak diverifikasi untuk berfungsi, juga tidak dapat dikompilasi atau diselesaikan. Tapi itu harus memberi Anda ide bagaimana melakukannya. Pekerjaan yang paling penting adalah fungsi evaluasi. Semakin canggih, semakin salah "mencoba" algoritma akan mencoba (dan harus membatalkan) nanti. Ini sangat mengurangi kompleksitas.

Jika ini terlalu lambat, Anda juga dapat mencoba menerapkan beberapa metode permainan dua orang sebagai HashTables. Untuk itu Anda harus menghitung kunci hash (iteratif) untuk setiap status game yang Anda evaluasi dan tandai status yang tidak mengarah ke solusi. Misalnya setiap kali sebelum metode Pencarian () mengembalikan "null" entri HashTable harus dibuat dan ketika memasukkan Pencarian () Anda akan memeriksa apakah Negara ini telah mencapai sejauh ini tanpa hasil positif dan jika demikian mengembalikan "null" tanpa investigasi lebih lanjut. Untuk ini, Anda akan memerlukan tabel hash besar dan harus menerima "tabrakan hash" yang dapat menyebabkan Anda mungkin tidak menemukan solusi yang ada, tetapi yang sangat tidak mungkin, jika fungsi hash Anda cukup baik dan meja Anda cukup besar (ini risiko risiko yang dapat dihitung).

Saya pikir tidak ada algoritma lain untuk menyelesaikan masalah ini (seperti yang dijelaskan oleh Anda) lebih efisien, dengan asumsi fungsi evaluasi Anda optimal ...

SDwarfs
sumber
Ya, saya bisa tahu antrian lengkap. Apakah penerapan fungsi evaluasi juga mempertimbangkan penempatan yang valid, tetapi berpotensi buruk? Berpotensi buruk menjadi gerakan seperti menempatkannya di sebelah bunga dengan warna berbeda ketika sudah ada warna yang sama di lapangan? Atau menempatkan bunga di suatu tempat yang bloknya sama sekali berbeda karena kekurangan ruang?
user849924
Jawaban ini memberi saya ide untuk model dan cara bekerja dengan aturan permainan, jadi saya akan menjawabnya. Terima kasih atas masukan Anda!
user849924
@ user849924: Ya, tentu saja fungsi evaluasi harus menghitung "nilai" evaluasi untuk itu. Semakin banyak kondisi permainan saat ini menjadi lebih buruk (hampir kehilangan), semakin buruk nilai evaluasi yang dikembalikan. Evaluasi yang paling mudah adalah mengembalikan jumlah bidang kosong. Anda dapat meningkatkan ini dengan menambahkan 0,1 untuk setiap bunga ditempatkan di sebelah bunga dengan warna yang sama. Untuk memverifikasi fungsi Anda pilih beberapa status permainan acak, hitung nilainya dan bandingkan. Jika Anda berpikir negara A lebih baik daripada negara B, skor untuk A harus lebih baik daripada negara untuk B.
SDwarfs