Jocuri de sumă 0 (wikipedia.org/zero-sum-game) sunt jocurile care de obicei se joacă în doi jucători și în care o mutare bună a unui jucător este în dezavantajul celuilalt jucător în mod direct. Aplicațiile din cadrul acestui laborator sunt în orice joc de sumă 0: șah, table, X și 0, dame, go etc. Orice joc în care se poate acorda un scor / punctaj pentru anumite evenimente - de exemplu la șah pentru capturarea unor piese sau la X și 0 pentru plasarea unui X pe o anumite poziție).
Algoritmul Minimax reprezintă una din cele mai cunoscute strategii pentru a juca jocurile de sumă 0. Minimax este un algoritm ce permite minimizarea pierderii de puncte într-o mutare următoare a jucătorului advers. Concret, pentru o alegere într-un joc oarecare se preferă cea care aduce un risc minim dintre viitoarele mutări bune ale adversarului.
De asemenea, algoritmul Minimax este folosit în diverse domenii precum teoria jocurilor (Game Theory), teoria jocurilor combinatorice (Combinatorial Game Theory - CGT), teoria deciziei (Decision Theory) și statistică.
Ideea pe care se bazează algoritmul este că jucătorii adoptă următoarele strategii:
De ce merge o astfel de abordare? După cum se preciza la început, discuția se axează pe jocuri de sumă zero (zero-sum game). Acest lucru garantează, printre altele, că orice câștig al Jucătorului 1 este egal cu modulul sumei pierdute de Jucătorul 2. Cu alte cuvinte, cât pierde Jucătorul 2, atât câștigă Jucător 1. Invers, cât pierde Jucător 1, atât câștigă Jucator 2. Sau:
$$ Câștig_{Jucător_1} = | Pierdere_{Jucător_2} |$$ $$ Câștig_{Jucător_2} = | Pierdere_{Jucător_1} | $$
În general spațiul soluțiilor pentru un joc în doi de tip zero-sum se reprezintă ca un arbore, fiecărui nod fiindu-i asociată o stare a jocului în desfășurare (game state). De exemplul, putem considera jocul de X și O ce are următorul arbore (parțial) de soluții. Acesta corespunde primelor mutări ale lui X, respectiv O:
Metodele de reprezentare a arborelui variază în funcție de paradigma de programare aleasă, de limbaj, precum și de gradul de optimizare avut în vedere.
Având noțiunile de bază asupra strategiei celor doi jucători, precum și a reprezentării spațiului soluțiilor problemei, o primă implementare a algoritmului Minimax ar folosi două funcții maxi() și mini(), care ambele calculează cel mai bun scor pe care îl poate obține jucătorul menționat. Intuitiv, cele 2 funcții au implementare aproape identică, astfel că dorim o organizam a codului fără porțiuni duplicate. De aceea singura variantă de implementare pe care o recomandăm, este varianta Negamax:
Datorită spațiului de soluții mare, de multe ori copleșitor ca volum de date de analizat, o inspectare completă a acestuia nu este fezabilă și devine impracticabilă din punctul de vedere al timpului consumat sau chiar a memoriei alocate (se vor discuta aceste aspecte în paragraful legat de complexitate).
Astfel, de cele mai multe ori este preferată o abordare care parcurge arborele numai până la o anumită adâncime maximă („depth”). Aceasta abordare permite examinarea arborelui destul de mult pentru a putea lua decizii minimalist coerente in deșfăsurarea jocului.
Totuși, dezavantajul major este că pe termen lung se poate dovedi ca decizia luată la adâncimea depth nu este global favorabilă jucătorului în cauză (s-a ales o valoare maxim local, iar dacă s-ar fi continuat în arborele de explorare s-ar fi constatat că este o decizie ce avantajează celălălt jucător).
De asemenea, se observă recursivitatea indirectă. Prin convenție acceptăm ca începutul algoritmului să fie cu jucătorul max. Astfel, se analizează succesiv diferite stări ale jocului din punctul de vedere al celor doi jucatori până la adâncimea depth. Rezultatul întors este scorul final al mișcării celei mai bune pentru un jucător din perspectiva următoarelor depth mutări în joc.
Se observă că arborele se completează prin parcugere în adâncime (stanga-dreapta, sus-jos).
* Pe nivel Max/Maxi se alege maximul dintre valorile primite de la copii.
* Pe nivel Min/Mini se alege minimul dintre valorile primite de la copii.
Obervație: Toate nodurile sunt evaluate.
Aceleași explicații ca la Minimax, cu câteva observații adiționale:
* NU mai este nevoie să numerotăm nivelele. Pe fiecare nivel, jucătorul curent alege maximul dintre scorurile negate ale adeversarului (venite din copii).
Până acum s-a discutat despre algoritmii Minimax / Negamax. Aceștia sunt algoritmi exhaustivi (exhausting search algorithms). Cu alte cuvinte, ei găsesc soluția optima examinând întreg spațiul de soluții al problemei. Acest mod de abordare este extrem de ineficient în ceea ce privește efortul de calcul necesar, mai ales considerând că extrem de multe stări de joc inutile sunt explorate (este vorba de acele stări care nu pot fi atinse datorită încălcării principului că fiecare jucător joacă optim la fiecare rundă).
O îmbunățațire substanțială a Minimax/Negamax este Alpha-beta pruning (tăiere alfa-beta). Acest algoritm încearcă să optimizeze Minimax/Negamax profitând de o observație importantă: pe parcursul examinării arborelui de soluții se pot elimina întregi subarbori, corespunzători unei mișcări m, dacă pe parcursul analizei găsim că mișcarea m este mai slabă calitativ decât cea mai bună mișcare curentă.
Astfel, considerăm că pornim cu o primă mișcare M1. După ce analizăm această mișcare în totalitate și îi atribuim un scor, continuăm să analizăm mișcarea M2. Dacă în analiza ulterioară găsim că adversarul are cel puțin o mișcare care transformă M2 într-o mișcare mai slabă decât M1 atunci orice alte variante ce corespund mișcării M2 (subarbori) nu mai trebuie analizate.
În continuare prezentăm o implementare conceptuală a Alpha-beta pentru varianta Negamax:
În cazul ideal în care cea mai bună mișcare a jucătorului curent este analizată prima, toate celelalte mișcări, fiind mai slabe, vor fi eliminate din căutare timpuriu.
În cazul cel mai defavorabil însă, în care mișcările sunt ordonate crescător după câștigul furnizat, Alpha-beta are aceeași complexitate cu Minimax/ Negamax, îmbunătățirea fiind nulă.
În medie se constată o eficiență sporită in practică pentru Alpha-beta.
Pe scurt, iterative deepening reprezintă o strategie de optimizare a timpului prin căutarea progresivă în adâncime. Uneori, există posibilitatea pentru anumite stări să nu fie explorate complet, iar în acest caz rezultatul este aproximat printr-o euristică. Tehnica presupune salvarea în memorie a rezultatelor neexplorate complet (toate sau cele mai “promițătoare” stări), iar la următoarea rundă se vor refolosi o parte din aceste rezultate (cele care mai sunt posibile din noul moment al jocului). Un video cu un exemplu explicat se găsește pe link-ul youtube.com/iterative_deepening
Pentru a vedea complexitatea algoritmilor prezentați anterior, se vor introduce câteva noțiuni:
Dintre cele mai importante jocuri în care putem aplica direct strategia minimax, menționăm:
În lab06 nu există exerciții propriu-zise.
Task-uri:
* Parcugeți teoria din acest laborator împreună cu asistentul.
* Discutați și comparați euristici pentru: