Fotbal
Proiect AA
Ivascu Mihai, 334 CA
miahi@home.ro


  1. Jocul Fotbal
  2. Applet-ul Fotbal
  3. Programul Fotbal
  4. Algoritmul Fotbal
  5. Rularea Fotbal


1. Jocul Fotbal

Fotbal se joaca pe hartie, desenand un teren ca in imaginea urmatoare:


Imaginea 1: Terenul

Fiecare jucator are cate o poarta, pe care trebuie sa o apere de adversar, in acelasi timp incercand sa dea gol in poarta adversarului. Jocul incepe mutand mingea din centrul terenului (exemplul 0). O mutare inseamna miscarea mingii o casuta in oricare din cele 8 directii posibile, catre cele 8 puncte vecine punctului in care se afla mingea. Cand se muta, se uneste cu o linie punctul in care era mingea cu punctul in care ajunge mingea (exemplul 1).


Imaginea 2: Exemplu de mutari corecte

Daca mutarea pe care o face jucatorul este catre un punct nefolosit (prin care nu s-a mai trecut in timpul jocului), mingea ramane in acel loc si este randul adversarului sa mute (exemplele 1 si 2). Daca mingea se muta intr-un punct care a mai fost folosit, mingea ricoseaza si jucatorul poate continua mutarea mingii din pozitia in care a ajuns mingea in oricare alt punct vecin, continuand mutarile pana cand mingea ajunge intr-un punct nefolosit. Nu se poate trece de doua ori pe acelasi traseu, adica nu se poate muta pe o linie care a fost deja trasa.

Scopul jocului este de a da gol adversarului, adica de a muta mingea in poarta adversa. O alta posibilitate de castig este ca adversarul sa se blocheze; se poate intampla ca in timpul jocului toate posibilitatile de iesire dintr-o anumita zona a terenului sa fie blocate, iar in interiorul zonei izolate sa nu mai existe nici un punct nefolosit; in acest caz jucatorul care trebuie sa mute nu are cum sa-si finalizeze mutarea, si pierde jocul.

Sus | Jocul | Applet-ul | Programul | Algoritmul | Rularea Fotbal

2. Applet-ul Fotbal


Imaginea 3; Screenshot al applet-ului

Fereastra applet-ului arata ca mai sus. In stanga se afla desenat terenul si mutarile facute pe teren, jucatorul (sus) avand culoarea rosie, iar calculatorul (jos) avand culoarea albastra. Sub desen este scrisa starea terenului (jucatorul care trebuie sa mute sau o finalitate a jocului - cine a castigat si de ce). Mai jos este scris numarul de mutari posibile in momentul actual, asa cum sunt gasite de algoritmul de joc (23 in exemplu). Pe ultima linie este scrisa versiunea actuala a applet-ului.

In dreapta se afla controalele jocului:

  • Restart - reporneste jocul de la inceput
  • Undo - revine la penultima mutare
  • Best - muta calculatorul (atunci cand este randul lui) in cea mai buna pozitie la care poate sa ajunga. Mai multe despre calculul pozitiilor in capitolul algoritm
  • automat - daca optiunea este bifata, calculatorul muta automat atunci cand este randul lui; daca nu, calculatorul asteapta apasarea butonului Best pentru a muta
  • posibilitati - afiseaza sau nu punctele la care se poate ajunge din pozitia curenta; punctele la care se poate ajunge vor fi incercuite cu un cerculet rosu, asa cum se observa in imaginea 4
  • 2 jucatori - bifarea acestei optiuni permite jucatorului sa mute in locul calculatorului, atunci cand se joaca 2 jucatori sau pentru a seta o anumita pozitie a terenului in vederea testarii algoritmului de cautare a solutiilor.
  • adancimea - de aici se poate modifica adancimea maxima din arbore la care sa caute algoritmul solutii; optiunile sunt: 5, 10, 15, 20, 25. O optiune speciala este "adaptiv", cand adancimea se modifica automat o data cu mutarile, folosind ca adancime distanta pe verticala fata de poarta adversa + 6.
  • random - modifica coeficientii random ai alegerii unei solutii; cu cat random este mai mare, cu atat diversitatea alegerilor este mai mare, insa calculatorul joaca mai prost. Un inconvenient al acestei setari este ca atunci cand calculatorul joaca mai random, terenul devine mult mai imprastiat, crescand numarul de solutii posibile si adancimea solutiilor, deci crescand timpul de joc.

Imaginea 4: Optiunea posibilitati activata

Jucatorul muta dand click in interiorul terenului pe punctul urmator in care vrea sa mute; daca ajunge la o mutare terminala (intr-un punct nefolosit), si este activata optiunea automat, calculatorul muta singur si cedeaza controlul jucatorului atunci cand ii este randul.

De fiecare data cand jucatorul face o mutare este repornit algoritmul de cautare a mutarilor posibile din acea pozitie; daca este activata optiunea posibilitati, punctele finale unde poate ajunge mingea sunt desenate pe teren. Optiunea este utila pentru studierea algoritmului de alegere a celei mai bune mutari (impreuna cu butonul Undo), si pentru incepatori, pentru sublinierea unor posibilitati de mutare mai greu observabile.

Sus | Jocul | Applet-ul | Programul | Algoritmul | Rularea Fotbal

3. Programul Fotbal

Programul a fost scris in Java si programat pentru a fi rulat intr-un browser HTTP care suporta integrarea cu masina virtuala Java (JRE). Este format din 3 clase:

  • fotbal.java, care implementeaza interfata grafica si dialogul cu utilizatorul
  • teren.java, care implementeaza modul de memorare a mutarilor din teren, precum si mutarile propriu-zise si calculul starii terenului
  • back.java, care implementeaza algoritmii de cautare si alegere a celei mai bune mutari

O mica documentatie a celor trei clase, in format de documentatie Java (javadoc), cu explicarea variabilelor si functiilor, gasiti aici

Sus | Jocul | Applet-ul | Programul | Algoritmul | Rularea Fotbal

4. Algoritmul Fotbal

In applet-ul fotbal sunt folositi doi algoritmi, implementati in clasa back. Primul algoritm face cautarea posibilitatilor de mutare, iar al doilea algoritm alege dintre posibilitatile generate de primul cea mai buna mutare, cautand maximul unei functii care atribuie fiecarei solutii gasite cate un punctaj.

Algoritmul de generare a mutarilor este un algoritm Backtracking, cu structura urmatoare

   void CautSolutie(int adancime){
        int i,sv;
        teren t1;
        for(i=0;i<=7;i++){ //pentru fiecare directie
            if (posibil(adancime, i)){ //daca directia este posibila
                t1=new teren(intoarcere[adancime-1]); //facem un backup
                sv=t1.status;
                t1.muta(i); //si mutam in directia aleasa
                if(t1.status!=sv){ //daca se schimba starea terenului (i.e. se schimba jucatorul)
                    if(t1.status!=teren.ST_BL1&&t1.status!=teren.ST_BL2){ //daca nu se blocheaza
                         if((adancime > punctajSolutie[t1.mingex][t1.mingey])&&((((int)Math.random()*nivelRandomAdancime)==0)||(punctajSolutie[t1.mingex][t1.mingey])==0)) {
                            if(t1.status == teren.ST_GOL2 && cautare==1) gasitGol=true;
                            if(t1.status == teren.ST_GOL1 && cautare==2) gasitGol=true; 
                            solutii[t1.mingex][t1.mingey]=new teren(t1); //salveaza solutia
                            punctajSolutie[t1.mingex][t1.mingey] = adancime; //precum si punctajul ei
                        }
                    toateSolutiile++; //am mai gasit o solutie
                    }
                }
                else{ //daca nu s-a ajuns intr-o pozitie finala
                    intoarcere[adancime]=new teren(t1);
                     CautSolutie(adancime+1); //se continua cautarea din punctul in care am ajuns
                   }
            }
        }      
    }

Functia nu intoarce toate solutiile posibile, ci pentru fiecare punct de pe teren in care se poate ajunge se pastreaza o singura solutie, solutia cu cel mai bun punctaj. La nivelul cautarii, punctajul inseamna adancimea la care a fost gasita solutia respectiva (adica numarul de linii duse de la pozitia initiala pana la pozitia curenta). Cu cat o mutare este mai lunga, cu atat se ocupa mai mult din teren, iar sansele ca oponentul sa se intoarca pe acelasi drum scad.

Pentru fiecare pozitie in care se ajunge, pentru fiecare directie in care se poate merge, se muta si se evalueaza noua stare a terenului; daca starea nu s-a modificat, inseamna ca s-a mutat intr-un punct folosit si se continua cautarea, adica se apeleaza recursiv functia de cautare pentru noua configuratie. Daca starea terenului s-a modificat inseamna ca este o mutare finala si se evalueaza starea. Daca starea nu este de blocare, se compara punctajul solutiei curente (adancimea la care ne aflam) cu punctajul solutiei precedente din acelasi punct. Daca punctajul este mai mare, atunci solutia noua o inlocuieste pe cea veche. Exista totusi o sansa ca o solutie de adancime mai mare sa nu o inlocuiasca pe alta de adancime mai mica; sansa se poate modifica prin variabila nivelRandomAdancime, probabilitatea fiind 1 la nivelRandomAdancime.

Complexitatea algoritmului de cautare este teoretic 8n, unde n este adancimea la care se poate ajunge. In program, adancimea este limitata la 15, 815=35.184.372.088.832. De fapt, pentru a se putea trece printr-un punct, acesta trebuie sa fi fost deja parcurs, deci trebuie sa existe cel putin doua linii duse deja din acel punct, si mai exista si o a treia linie, cea prin care se ajunge la punct prin mutarea curenta, rezultand doar 515=30.517.578.125 posibilitati. In practica lucrurile stau mult mai bine, deoarece adancimea este strict limitata de liniile care au fost duse deja, numarul maxim de posibilitati atins de mine intr-un joc ilogic (cu mutari aleatoare) este de 200.000, iar intr-un joc logic (cu mutari care corespund scopului jocului) numarul de mutari nu creste decat rar peste 50.000, la adancimea 15.

Consumul de memorie este limitat, applet-ul lucrand intr-un spatiu de memorie de 3 MB, din care ocupa cam 1.5-2 MB. Limitarea este facuta prin folosirea unei memorari a terenului care ocupa aproximativ 1000 de biti, adica 125 Bytes, dar care permite accesul rapid la elementele terenului. Pentru fiecare linie de pe teren se pastreaza 2 biti, unul da existenta liniei (true daca este linie dintr-un punct intr-o anumita directie), iar celalalt da culoarea liniei (jucator 1/jucator 2). In algoritmul de cautare, limitarea se face pastrand o singura solutie pentru fiecare punct din teren.

Performanta algoritmului pe un sistem Intel Celeron II 1.3 GHz este de 5000 de solutii pe secunda

Algoritmul de alegere a solutiei optime are complexitate liniara, facand doar o cautare in solutiile date de functia de cautare

    public teren ceaBuna(){
        int i,j,max=0,p;
        teren best=new teren();
        boolean gasit=false; //rezolva bugul resetarii
        for(i=0;i<13;i++){
            for(j=0;j<10;j++){
                if(punctajSolutie[i][j]!=0){
                    p=punctaj(i,j,punctajSolutie[i][j],solutii[i][j]);
                    if(p==max){ //daca au acelasi punctaj, exista 50% sanse ca a doua sa fie aleasa
                         if(Math.random()<0.5f){ best=new teren(solutii[i][j]); gasit=true;}
                    }
                    else if(p>max){
                         best=new teren(solutii[i][j]);
                         gasit=true;
                         max=p;
                    }
                }
            }            
        }
        if(gasit) return best;
        else{
            t.status=teren.ST_BL1; //finalitate
            return t; //bug-ul blocarii este rezolvat
        }
    }

Functia ia fiecare solutie posibila si ii aplica o functie de punctaj. Daca doua solutii au acelasi punctaj, exista sansa de 50% ca solutia noua sa o inlocuiasca pe cea veche. Punctajul poate depinde de pozitia punctului final, lungimea drumului pana in acel punct si configuratia terenului. In aceasta versiune am optat pentru o functie care foloseste numai pozitia punctului final.

    public int punctaj(int x,int y,int adancime,teren t1){
        //x este vertical si y orizontal!
        return sgn(x-6)*x*(Math.abs(y-5)+1)+(12-x)*43 + (int)(Math.random()*nivelRandom) + ((x==6)?-2:0);
    }

Functia atribuie punctelor de pe teren urmatoarele punctaje:

          516  516  516

469  470  471  472  471  470  469

422  424  426  428  426  424  422

375  378  381  384  381  378  375

328  332  336  340  336  332  328

281  286  291  296  291  286  281

280  274  268  262  268  274  280

243  236  229  222  229  236  243

204  196  188  180  188  196  204

165  156  147  138  147  156  165

126  116  106   96  106  116  126

 87   76   65   54   65   76   87

           24   12   24

Se observa ca aceasta formula de punctaj corespunde destul de bine scopului jocului, adica indreapta calculatorul catre poarta adversarului si apara poarta proprie. Cu cat pozitia verticala este mai aproape de poarta adversa, punctajul creste pe pozitia de mijloc orizontala. Calculatorul se va apropia de poarta adversa pe mijlocul terenului. Cu cat pozitia verticala se apropie de poarta calculatorului, punctajul orizontal creste spre marginea terenului, permitand astfel departarea de poarta, adica apararea ei.

Si in functia de punctaj apare un element random, pentru a varia raspunsul calculatorului atunci cand jocul ajunge in aceeasi pozitie.

Deoarece uneori timpul de calcul este mare, nu am putut implementa un algoritm MinMax, deoarece timpul de calcul al fiecarei mutari ar fi crescut de cel putin 6-7 ori. Din pacate, nu am gasit nici o euristica suficient de buna pentru a reduce arborele de cautare fara a pierde uneori solutii importante. Singura optimizare din cautarea solutiilor este descoperirea unui gol din partea calculatorului, moment in care algoritmul de cautare este intrerupt.

Sus | Jocul | Applet-ul | Programul | Algoritmul | Rularea Fotbal

5. Rularea Fotbal

Prezenta versiune a fost compilata cu Java SDK 1.4.1_01 si testata cu aceeasi veriune de masina virtuala. Teoretic functioneaza si cu alte versiuni ale JVM.

Ruleaza programul

Sus | Jocul | Applet-ul | Programul | Algoritmul | Rularea Fotbal




Acest document, impreuna cu applet-ul si sursele Fotbal, pot fi gasite la
http://users.pcnet.ro/miahi
http://miahi.3x.ro