Home Reguli Joc Algoritm Sursa
Home
Reguli
Demo
Joc
Algoritm
Sursa
Download
Autori
Scrie-ne
Links
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Prezentarea algoritmului

      Algoritmul minimax reprezinta o metoda de a selecta cel mai bun mod de a actiona intr-o anumita situatie, sau joc, cand doua forte opuse, sau doi jucatori incearca fiecare sa obtina victoria, jucand dupa aceleasi reguli. Este folosit cel mai adesea pentru a determina cea mai buna mutare pe care un jucator o are la dispozitie intr-un joc. Are la baza principiul ca la fiecare moment jucatorul aflat la mutare va alege solutia cea mai buna.

      Solutia este reprezentata sub forma unui arbore. Acest arbore contine toate mutarile pe care un jucator le are la dispozitie sub forma unor succesori ai radacinii, arborele avand un numar de nivele egal cu numarul de mutari prevazute ale adversarului. Fiecare ramura a arborelui reprezinta o mutare posibila a jucatorului la un moment dat. Prin evaluarea frunzelor arborelui se poate prevedea cum va evolua jocul pana in acel moment.

      Cu cat arborele are o adancime mai mare cu atat se poate prevedea mutarea cea mai benefica la un moment dat.

      In situatia jocului Puissance4, cu doi jucatori, rosu si verde, rosul cauta in arbore nodurile cu valori cat mai mari, iar verdele pe cele cu valori cat mai mici. Un algoritm de tip minimax determina toate continuarile posibile ale unui joc pana la nivelul dorit, evaluand fiecare mutare posibila la o anumita valoare. Cautarea se reduce apoi la a parcurge arborele si a cauta pe rand valorile cea mai mare si cea mai mica. Acest lucru este echivalent cu a alterna cautarea celei mai bune mutari atat pentru rosu cat si pentru verde.

      Figura de mai jos arata un arbore care simuleaza jocul pe patru nivele, avand rosu la mutare.Cum am aratat mai sus, in timp ce rosul poate obtine valori mai mari alegand mutari care duc in stanga sau in drepta arborelui, in acelasi timp verdele poate obtine valori foarte mici. De aceea valoarea obtinuta de rosu nu este cea mai buna per total, ci numai avand in vedere conditiile actuale.


(Algoritmul in pseudocod)



      Deoarece algoritmul minimax are un cost care creste exponential cu adancimea de cautare, pentru a reduce timpul necesar unei mutari, cautarea trebuie restrictionata astfel inca sa nu se ia in considerare mutarile care dezavantajeaza evident jucatorul aflat la mutare. Un mod de a face acest lucru este de a implementa limitarea alfa beta. Aceasta metoda functioneaza pe principiul prezentat anterior ca rosul nu va face o mutare daca prin aceasta va permite verdelui sa faca o mutare si mai buna. Astfel, daca este intalnita o mutare care are un scor mai mic decat cea mai buna mutare de pana atunci nu se mai continua pe acea cale. Astfel, daca o mutare este evaluata ca fiind avantajoasa pentru jucatorul curent, dar nu si pentru adversar, nu se mai continua pe aceasta cale doarece celalalt jucator nu ar permite asa ceva.


(Algoritmul in pseudocod)



Exemplificarea algoritmului minimax:

      Rezolvarea problemelor care presupun 2 adversari este des asociata cu jocurile. Lumea noastra este plina de astfel de probleme de exemplu: probleme politice, diplomatice, negocieri,afaceri, batalii militare, strategie. Rezolvarea problemelor atunci cand ai de a face cu adversari necesita multa inteligenta.
      Vom avea nevoie de un evaluator static care sa caute in spatiul starilor pentru a selecta cea mai buna mutare posibila. In functie de joc, putem avea o multitudine de stari in care cel care se afla la mutare sa aiba jocul ca si castigat. Telul este de a obtine starile de mai sus. Avem de asemenea si stari perdante, din care jucatorul nu mai poate evolua spre a castiga jocul. O stare castigatoare pentru unul dintre jucatori inseamna o stare perdanta pentru celalalt. Jucatorul care se afla la mutare incearca sa identifice modul de joc cel mai profitabil.
      Presupunand ca avem urmatorul arbore al jocului sa urmarim evolutia algoritmului minimax


      Cea mai buna mutare este de a juca la d, apoi la 1.In termeni de programare expresia echivalenta este:
      max( min(l,k,m), min(f,g,e), min(i,j,h) )
      Pentru a calcula valoarea acestei expresii, s-ar putea sa nu fie nevoie de evaluarea tuturor argumentelor. Sa presupunem ca am evaluat l,k,m si f astfel:
      max( min(l,k,m), min(f,g,e), min(i,j,h) )
                    2 3 4           1
      max( min(2,3,4), min(1,g,e), min(i,j,h) )
      max( 2, min(1,g,e), min(i,j,h) )
      Pentru ca se calculeaza maximul si s-a intalnit deja un 2, orice minim intalnit ulterior si care are o valoare mai mica decat 2 nu este luat in calcul. De exemplu min(1,g,e) trebuie sa fie 1 sau mai putin. Stim ca max(2,1 sau mai putin) trebuie sa fie 2, de aceea nu mai este necesar sa evaluam 'g' sau 'e'. Similar, cand 'i' este evaluat la 0, avem max (2, 1 sau mai putin, 0 sau mai putin), care este evaluat la 2. Aceasta ordine a nodurilor a fost aleasa intamplator.
      Sa ne intoarcem la pasii algoritmului minimax si sa vedem cum acesta decide nodurile care trebuie luate in considerare.
      Cautarea minimax cu limitare alfa/beta permite evitarea evaluarii anumitor noduri irelevante. Pseudocodul ar fi urmatorul:
double maximize(node *n, action *a, double alpha, double beta)
{
generate children of n
for each child
     evaluate it as a leaf, or recursively via minimize()
     modify alpha locally to be the max of children so far
     break out of this loop if alpha >= beta
return(alpha)
}
      Acesta trebuie initial apelat in modul urmator:
      v = maximize(root,&op,-99999.0,99999.0);
      Functia de minimizare este similara:
double minimize(node *n, action *a, double alpha, double beta)
{
generate children of n
for each child
     evaluate it as a leaf, or recursively via maximize()
     modify beta locally to be the min of children so far
     break out of this loop if alpha >= beta
return(beta)
}
      Jucatorul care se minimizeaza salveaza valoarea nodului evaluat ca minim in variabila beta. Functiile de maximizare si minimizare sunt recursive.
      De exemplu pe urmatorul arbore de joc:

      Sa presupunem ca evaluarea fiecarui succesor este realizata cu functia de minimizare. De exeplu pentru 'b':
     

      Alfa si beta sunt copiate de mai sus. Acum sa presupunem ca 'g' este evaluat de catre functie la valoarea 2. Pentru ca functia de evaluare este cea de minimizare, si pentru ca 2 este mai mic decat beta actual, acesta din urma va lua valoarea 2, rezultand:
     

      Sa presupunem acum ca 'f' este evaluat la 1. Valoarea lui beta este coborata de la 2 la 1.
     

      Faptul ca 'e' este evaluat la 4 nu afecteaza valoarea lui beta, rezultand:
     

      Rezulta ca aceasta apelare a minimizarii returneaza beta=1, ceea ce inseamna ca nodului 'b' ii este data valoarea 1. Avem acum urmatorul arbore:
     

      De observat este faptul ca valoarea lui alfa este acum 1. Urmeaza o apelare recursiva a minimizarii pentru nodul 'd'. Initial avem:
     

      Observati ca alfa este initializat la 1 pentru ca este copiata valoarea de mai sus. Dupa evaluarea nodurilor 'k','l','m' avem urmatorul arbore:
     

      Deoarece beta=2 functia de minimizare returneaza 2, rezultand:
     

      Acum, minimizarea pentru 'c' incepe astfel:
     

      Frunza 'j' este evaluata la valoarea 3, rezultand:
     

      Frunza 'i' este evaluata la valoarea 0, rezultand:
     

      In acest moment, deoarece 2>=0, nu mai este nevoie sa evaluam 'h'. Faptul ca alfa este 2 inseamna ca o alta mutare poate atinge acest scor. Deci nu are rost sa continuam evaluarea aici, unde jucatorul care se maximizeaza s-ar descurca mai prost (<=0).
      In continuare vom urmari pasii unei variante oarecum diferite de minimax denumita negamax. O problema care foloseste minimax in varianta de mai sus trebuie sa aiba cod si pentru jucatorul care se maximizeaza si pentru cel care se minimizeaza. Negamax realizeaza tocmai reunirea celor doua coduri intr-unul singur.
      Se observa ca min(x,y,z)=-max(-x,-y,-z). Denumirea acestui algoritm vine de la acest '-max'. Prin complementarea starii algoritmului la fiecare nivel, intotdeauna este randul jucatorului care se maximizeaza. Bineinteles ca in continuare exista 2 jucatori care alterneaza rundele, dar prin complementarea starii, amandoi pot folosi codul pentru maximizare de fiecare data.
(Algoritmul in pseudocod)


      Sa luam exemplul unui joc mai simplu decat cel implementat de noi, X si 0. Daca 'x' joaca primul, se poate schimba valoarea lui 'x' in '0', si lasa ca x sa joace in continuare. Dupa aceasta, se complementeaza din nou starea, 'x' revenind in '0' si invers. Cand este evaluata starea complementara, se returneaza o valoare din punctul de vedere al celuilalt jucator. Deci, evaluand o stare din punctul de vedere al jucatorului care se minimzeaza, folosind functIA de maximizare, se obtine o valoare complementara (adica valoarea opusa celei reale). Astfel se pot maximiza aceste valori folosind max(-x,-y,-z) si returnand pur si simplu -max. Sa luam exemplul de mai sus:
     

      Acum sa adaugam la acest algoritm si limitarea alfa/beta. Este evident ca in loc sa transmitem ca pa rametrii alfa si beta functiei max, ii interschimbam si ii negam. Aceasta inseamna a nega si a schimba punctul de vedere.
(Algoritmul in pseudocod)


      Vom lucra pe exemplul precedent:
     

      Sa presupunem ca evaluarea fiecarui succesor a fost realizata folosind negamax. Pentru 'b':
     

      Alfa si beta sunt luate de mai sus. Interschimbarea si negarea sunt dificil de observat, deoarece perechea [-beta,alfa] = [-alfa,beta] in cazul de fata. Acum sa presupunem ca 'g' este evaluat la -2. Deoarece -2 este mai mare decat valoarea actuala a lui alfa, alfa va lua valoarea -2, rezultand arborele:
     

      Acum sa presupunem ca 'f' este evaluat la -1. Alfa creste de la -2 la -1, rezultand:
     

      Deoarece 'e' este evaluat la -4 (<-1) el nu va afecta valoarea lui alfa:
     
Aceasta apelare a lui negamax returneaza -alfa (-(-1)), ceea ce inseamna ca lui 'b' i s-a atribuit valoarea 1:
     

      Sa observam ca alfa creste la 1. Acum urmeaza o noua apelare recursiva a lui negamax pentru nodul 'd'. Initial avem:
     

      Beta este initializat la -1 pentru ca este obtinut prin negarea valorii lui alfa. Dupa evaluarea nodurilor 'k', 'l', 'm':
     

      Deoarece alfa este 2, avem:
     

      Cautand valoarea lui 'c' cu negamax, rezulta:
     

      Frunza 'j' este evaluata la -3:
     

      Frunza `i' este evaluata la 0, rezultand:
     

      Sa observam acum ca deoarece 0>=-2 nu mai este nevoie de evaluarea lui 'h'.
      Sa ne intoarcem la modul de ordonare al nodurilor. Am vazut mai sus ca unele metode de ordonare produc mai multe apelari recursive decat altele. In arborele din stanga au loc patru apelari, in timp ce in cel din dreapta nu are loc nici una:
     

      Scaderea numarului de apelari recursive printr-o buna ordonare a nodurilor poate duce la o imbunatatire a timpului de cautare. Aceasta ordonare a nodurilor poate fi realizata prin metoda numita `static node ordering'. La fiecare nod, se genereaza succesorii, se evalueaza fiecare (chiar daca nu sunt frunze), sunt sortati descrescator, si se cauta in aceasta ordine. Astfel, cautand mutarile mai bune la inceput, alfa si beta se apropie una de cealalta mai rapid, ceea ce inseamna ca vom avea mai putine apelari recursive.

     
 
Copyright (c) 2003, <Puissance4> All right reserved.