Pengenalan Algoritma Minimax dengan Pelaksanaan Java

1. Gambaran keseluruhan

Dalam artikel ini, kita akan membincangkan algoritma Minimax dan aplikasinya dalam AI. Oleh kerana ia adalah algoritma teori permainan, kami akan melaksanakan permainan sederhana yang menggunakannya.

Kami juga akan membincangkan kelebihan menggunakan algoritma dan melihat bagaimana ia dapat diperbaiki.

2. Pengenalan

Minimax adalah algoritma pembuatan keputusan, biasanya digunakan dalam permainan dua pemain berdasarkan giliran . Matlamat algoritma adalah untuk mencari langkah seterusnya yang optimum.

Dalam algoritma, satu pemain disebut pemaksimum, dan pemain lain adalah peminimum. Sekiranya kita memberikan skor penilaian ke papan permainan, satu pemain akan memilih keadaan permainan dengan skor maksimum, sementara yang lain memilih keadaan dengan skor minimum.

Dengan kata lain, yang pemaksimal berfungsi untuk mendapatkan skor tertinggi, sementara cuba minimizer mendapatkan mata terendah dengan cuba untuk bergerak balas .

Ia berdasarkan konsep permainan sifar-jumlah. Dalam permainan sifar-jumlah, jumlah skor utiliti dibahagikan di antara pemain. Peningkatan skor satu pemain mengakibatkan penurunan skor pemain lain. Jadi, jumlah skor selalu sifar. Untuk satu pemain menang, yang lain harus kalah. Contoh permainan seperti itu adalah catur, poker, pemeriksa, tic-tac-toe.

Fakta menarik - pada tahun 1997, komputer bermain catur IBM Deep Blue (dibina dengan Minimax) mengalahkan Garry Kasparov (juara dunia catur).

3. Algoritma Minimax

Matlamat kami adalah untuk mencari langkah terbaik untuk pemain. Untuk melakukannya, kita boleh memilih simpul dengan skor penilaian terbaik. Untuk menjadikan proses lebih pintar, kita juga dapat melihat ke depan dan menilai pergerakan lawan yang berpotensi.

Untuk setiap pergerakan, kita dapat melihat ke depan seberapa banyak pergerakan yang diizinkan oleh kekuatan pengkomputeran kita. Algoritma menganggap bahawa lawan bermain dengan optimum.

Secara teknikal, kita mulakan dengan simpul akar dan memilih simpul yang terbaik. Kami menilai nod berdasarkan skor penilaiannya. Dalam kes kami, fungsi penilaian dapat memberikan skor hanya pada node hasil (daun). Oleh itu, kita secara berulang-ulang mencapai daun dengan skor dan kembali menyebarkan skor.

Pertimbangkan pokok permainan di bawah:

Maximizer bermula dengan simpul akar dan memilih langkah dengan skor maksimum. Malangnya, hanya daun yang mempunyai skor penilaian dengannya, dan oleh itu algoritma harus mencapai simpul daun secara berulang. Di pohon permainan yang diberikan, kini giliran peminum untuk memilih perpindahan dari simpul daun , jadi simpul dengan skor minimum (di sini, simpul 3 dan 4) akan dipilih. Ia tetap memilih node terbaik dengan cara yang sama, sehingga mencapai simpul akar.

Sekarang, mari kita tentukan langkah algoritma secara rasmi:

  1. Bentukkan pokok permainan yang lengkap
  2. Nilai skor untuk daun menggunakan fungsi penilaian
  3. Markah sandaran dari daun ke akar, dengan mempertimbangkan jenis pemain:
    • Untuk pemain maks, pilih anak dengan skor maksimum
    • Untuk pemain min, pilih anak dengan skor minimum
  4. Pada simpul akar, pilih simpul dengan nilai maksimum dan lakukan pergerakan yang sesuai

4. Pelaksanaan

Sekarang, mari kita laksanakan permainan.

Dalam permainan ini, kita mempunyai timbunan dengan bila n beberapa tulang . Kedua-dua pemain harus mengambil 1,2 atau 3 tulang pada gilirannya. Pemain yang tidak dapat mengambil tulang kehilangan permainan. Setiap pemain bermain dengan optimum. Memandangkan nilai n , mari tulis AI.

Untuk menentukan peraturan permainan, kami akan menerapkan kelas GameOfBones :

class GameOfBones { static List getPossibleStates(int noOfBonesInHeap) { return IntStream.rangeClosed(1, 3).boxed() .map(i -> noOfBonesInHeap - i) .filter(newHeapCount -> newHeapCount >= 0) .collect(Collectors.toList()); } }

Selain itu, kami juga memerlukan pelaksanaan untuk kelas Node dan Tree juga:

public class Node { int noOfBones; boolean isMaxPlayer; int score; List children; // setters and getters } public class Tree { Node root; // setters and getters }

Sekarang kita akan melaksanakan algoritma. Ia memerlukan pokok permainan untuk melihat ke depan dan mencari langkah terbaik. Mari kita laksanakan:

public class MiniMax { Tree tree; public void constructTree(int noOfBones) { tree = new Tree(); Node root = new Node(noOfBones, true); tree.setRoot(root); constructTree(root); } private void constructTree(Node parentNode) { List listofPossibleHeaps = GameOfBones.getPossibleStates(parentNode.getNoOfBones()); boolean isChildMaxPlayer = !parentNode.isMaxPlayer(); listofPossibleHeaps.forEach(n -> { Node newNode = new Node(n, isChildMaxPlayer); parentNode.addChild(newNode); if (newNode.getNoOfBones() > 0) { constructTree(newNode); } }); } }

Sekarang, kami akan melaksanakan kaedah checkWin yang akan mensimulasikan permainan, dengan memilih pergerakan yang optimum untuk kedua pemain. Ini menetapkan skor untuk:

  • +1, jika pemaksimum menang
  • -1, jika peminimum menang

The checkWin akan kembali benar jika pemain pertama (dalam kes kami - pemaksimal) menang:

public boolean checkWin() { Node root = tree.getRoot(); checkWin(root); return root.getScore() == 1; } private void checkWin(Node node) { List children = node.getChildren(); boolean isMaxPlayer = node.isMaxPlayer(); children.forEach(child -> { if (child.getNoOfBones() == 0) { child.setScore(isMaxPlayer ? 1 : -1); } else { checkWin(child); } }); Node bestChild = findBestChild(isMaxPlayer, children); node.setScore(bestChild.getScore()); }

Di sini, kaedah findBestChild menemui node dengan skor maksimum jika pemain adalah pemaksimum. Jika tidak, ia mengembalikan anak dengan skor minimum:

private Node findBestChild(boolean isMaxPlayer, List children) { Comparator byScoreComparator = Comparator.comparing(Node::getScore); return children.stream() .max(isMaxPlayer ? byScoreComparator : byScoreComparator.reversed()) .orElseThrow(NoSuchElementException::new); }

Akhirnya, mari kita laksanakan kes ujian dengan beberapa nilai n (bilangan tulang dalam timbunan):

@Test public void givenMiniMax_whenCheckWin_thenComputeOptimal() { miniMax.constructTree(6); boolean result = miniMax.checkWin(); assertTrue(result); miniMax.constructTree(8); result = miniMax.checkWin(); assertFalse(result); }

5. Penambahbaikan

Bagi kebanyakan masalah, tidak mustahil untuk membina keseluruhan pokok permainan. Dalam praktiknya, kita dapat mengembangkan separa pokok (membina pokok itu sehingga jumlah tingkat yang ditentukan sahaja) .

Kemudian, kita harus melaksanakan fungsi penilaian, yang seharusnya dapat menentukan seberapa baik keadaan semasa, untuk pemain.

Walaupun kita tidak membina pokok permainan yang lengkap, memerlukan masa untuk menghitung pergerakan permainan dengan faktor percabangan yang tinggi.

Nasib baik, ada pilihan untuk mencari langkah yang optimum, tanpa meneroka setiap simpul pokok permainan. Kami boleh melangkau beberapa cabang dengan mengikuti beberapa peraturan, dan itu tidak akan mempengaruhi hasil akhir. Proses ini dipanggil pemangkasan . Pemangkasan alpha-beta adalah varian algoritma minimax yang lazim.

6. Kesimpulannya

Algoritma Minimax adalah salah satu algoritma yang paling popular untuk permainan papan komputer. Ia digunakan secara meluas dalam permainan berdasarkan giliran. Ini boleh menjadi pilihan yang baik apabila pemain mempunyai maklumat lengkap mengenai permainan.

Ini mungkin bukan pilihan terbaik untuk permainan dengan faktor percabangan yang sangat tinggi (contohnya permainan GO). Walaupun begitu, dengan implementasi yang tepat, ia dapat menjadi AI yang cukup pintar.

Seperti biasa, kod lengkap untuk algoritma boleh didapati di GitHub.