Biologie | Chimie | Didactica | Fizica | Geografie | Informatica | |
Istorie | Literatura | Matematica | Psihologie |
Implementarea aplicatiei - sah
Augustina1 Descriere generala
Aplicatia a fost inplementata in Microsoft Visual Studio 2008. Ea consta in 3 proiecte distincte. Dll-ul creat ca si tip de proiect Windows Forms Control Library si Web Part-ul creat ca si tip de proiect SharePoint Web Part in care a fost inclus dll-ul creat anterior si serverul de sah creat ca si tip de proiect Windows Forms Application. Limbajul folosit in toate cele trei parti este C#, un limbaj foarte popular orientat pe obiecte. La implementarea aplicatiei sau luat in considerare principiile programarii orientate pe obiecte prezentate in capitlolul doi.
Aspectul cel mai important al aplicatiei "ChessTer" este integrabilitatea lui in Share Point. De altfel el mai poate fi integrat in orice program care suporta adaugarea si rularea Dll-urilor. El poate comunica cu serverul de sah independent de aplicatia in care este inclus.
Clasele implementate reprezinta functiile de baza ale programului. Separandu-le devin mai usor de inteles si de modificat chiar inlocuit cu unele noi mai peformante cu conditia sa se pastreze numele si parametrii functiei apelate.
Dll-ul sau jocul propriuzis, partea cea mai mare si mai importanta a proiectului, este o parte functionala autonoma (nu apeleaza nici o functie din exterior). Ea contine atat partea logica a programului cat si partea grafica. Acesta cuprinde urmatoarele functii/facilitati:
Grafica (Meniu, Tabla de joc, Tabela de mutari)
Partea logica (Inteligenta artificiala, jocul in retea)
Web Part-ul este aplicatia efectiva ce vine incarcata pe un site Share Point. El include Dll-ul creat anterior. La rularea Web Part-ului el adauga automat noile modificari facute aplicatiei in poolul aplicatiei de share point.
Serverul de sah a fost creat special pentru ca doi utilizatori ai aplicatiei "ChessTer" sa poata juca in retea impreuna. El asigura comunicarea intre doua instante al aplicatiei. Pentru ca doi utilizatori sa poata comunica intre ei, ei trebuie sa transmita anumite date serverului, date ce vor fi prezentate mai amplu in acest capitol.
Augustina2 Jocul propriuzis de sah
Dll-ul, dupa cum sa mentionat si anterior, cuprinde partea grafica si partea logica a progrmaului de sah. El este structurat in mai multe clase dupa principiile programarii orientate pe obiecte. Aceasta modularizare face o eventuala imbunatatire a programului mult mai usoara. Structura claselor si interoperabilitatea dintre ele este prezentata in figura Augustinaceva. Sagetile indica instantierea unei clase de catre cea de la care pleaca sageata. Dupa cum se poate observa anumite clase sunt instantiate de mai multe ori . .
Fig. Augustinaceva
Augustina2.1. Decsrierea claselor importante
1. Clasa Chessboard
Aceasta este clasa principala din care ruleaza programul de sah. Ea contine partea grafica, tartarea de evenimente aparute la interactionarea clientului cu interfata si gestionarea conexiunii cu serverul. Functiile principale sunt :
private void ChessBoard_MouseClick(object sender, MouseEventArgs e);
private void ChessBoard_Paint(object sender, PaintEventArgs e);
void Connect();
private void OnDataReceived(IAsyncResult asyn);
private void Muta();
private void NewGame();
private void ProtocolComunicare(string szData);
+ functiile meniului de ex.:
private void sETNAMEToolStripMenuItem_Click(object sender, EventArgs e);
a.) ChessBoard_Paint(object sender, PaintEventArgs e) este evenimentul ce asigura desenarea suprafetei de joc si a pieselor de sah. El este declansat la pornirea aplicatiei pentru a desena pozitia initiala de pe tabla si de fiecare data cand se apeleaza functia Refresh(). Astfel, dupa fiecare schimbare ce are loc pe tabla de joc, se apeleaza functia Refresh() iar pozitia de pe tabla este reactualizata.
Desenul tablei este format din doua etape. Prima etapa este crearea celor 64 de patratele albe si negre alternativ. Ele sunt desenate incepand de la un punct fix dat de cele doua constante LEFT si DOWN. Modificand cele doua constante putem muta tabla pe suprafata controlului la o ulterioara modifcare a interfetei. Dimensiunea patratelor este data de o constanta SIZE egala cu 60. Modificarea ei mareste sau micsoreaza tabla. A doua etapa reprezinta desenarea pieselor pe tabla. Pozitionarea acestora depinde tot de cele trei constante mentionate mai sus astfel ca mutarea tablei sa aduca de la sine si deplasarea desenarii pieselor. Campurile ocupate de piese depinde de tabloul de doua dimensiuni de numere intregi ce reprezinta tabla de sah aflat in clasa Tabla. Acel tablou este parcurs camp cu camp iar cand se gaseste o piesa pe campul respectiv (valoarea campului din tablou este diferita de zero) se deseneaza pe campul corespondent al tablei calculat astfel : coloana*60 + LEFT, linie*60 + DOWN. La inceputul unui joc nou se deseneaza din nou pozitia, pozitie ce difera in functie de modul de joc in care se afla utilizatorul.
Aceasta functie mai este folosita si pentru a colora patratele pe care poate muta o anumita piesa pe care a facut click jucatorul daca variabila MUTA are valoarea 1. Atunci ea executa actiunile descrise anterior plus aceasta facilitate.
Variabilele ce determina rezultatul generat de aceasta funtie sunt MUTA (coloreaza sau nu patratele respective) si STARTJOC (daca deseneaza pozitia initiala sau copiaza starea tabloului aflat in clasa Tabla).
b.) ChessBoard_MouseClick(object sender, MouseEventArgs e) este evenimentul ce se declanseaza cand se face un click cu mouse-ul pe suprafata controlului. Daca acel click se face in exteriorul suprafetei de joc atunci el este ignorat. Cand se face un click in interiorul suprafetei de joc automat se calculeaza si se memoreaza pe ce camp a fost dat clickul (coordonatele celor doua clickuri transformate in cifre de la 0 la 7 ce compun o mutare se memoreaza). In functie de variabila MOVE care determina daca a fost primul click aferent mutarii (primul click selecteaza piesa sau mai bine zis campul de plecare al mutarii iar al doilea click campul de destinatie a piesei, astfel se efectueaza o mutare) sau al doilea au loc doua actiuni diferite.
Daca jucatorul a efctuat primul click atunci se apeleaza functia:
public int genereaza_mutari(int randul, int[,] tbl, int piesa, int i, int j);
din clasa ToateMutarilePosibile pentru a calcula toate mutarile posibile ale piesei aflate pe campul pe care a facut jucatorul click cu conditia ca in tabloul din clasa Tabla sa se afle o piesa pe acel camp. Astfel se apeleaza functia Refresh() care automat declanseaza functia eveniment ChessBoard_Paint(object sender, PaintEventArgs e) si astfel cauzeaza colorarea campurilor aferente.
Cand jucatorul efectueaza al doilea click mutarea este teoretic completa. Astfel se apeleaza functia Muta() care efectueaza muatrea in cazul in care este legala, dupa care este apelata din nou functia Refresh() pentru a reactualiza pozitia de pe tabla.
Acesta functie are efect doar cand utilizatorul este la mutare.
c.) Muta() este functia ce executa mutarea efectuata de catre utilizator si care apeleaza inteligenta artificiala daca este cazul. In momentul in care aceasta functie este apelata stim cu siguranta ca utilizatorul vrea sa efectueze o mutare si ca cele patru cifre care formeaza cele doua coordonate ale mutarii sunt stocate. Astfel apelam functia:
public int validare_mutare(int[,] tbl, int rand, int a, int b, int c, int d);
al clasei Mutare_valida pentru verifica daca mutarea este legala. Daca functia respectiva returneaza 1 atunci mutarea este legala, daca returneaza 0 atunci mutarea este ilegala si jucatorul trebuie sa efectueze alta mutare.
In cazul in care mutarea este valida se memoreaza mutarea pentru a servii la verificarea legalitatii mutarii "en passant" ce ar putea aparea eventual la mutarea urmatoare, se noteaza mutarea in panoul aferent mutarilor conform normelor FIDE, se efectueaza schimbarile aferente in tabloul din clasa Tabla ce va avea ca efect schimbarea pozitiei de pe tabla la urmatoarea apelare a functiei eveniment ChessBoard_Paint si la final se apeleaza functia genereaza_mutari din clasa ToateMutarilePosibile prezentata anterior pentru a verifica daca nu cumva acel jucator a dat mat sau a provocat o pozitie de pat caz in care aceasta functie returneaza valoarea 0 adica nu exista nici o mutare posibila pentru jucatorul aflat la rand. Daca functia returneaza 0 diferenta intre mat si pat este data de rezultatul apelului functiei:
public int sah_la_rege(int[,] tbl);
a clasei Sah_la_rege. In cazul in care functia returneaza 1 atunci inseamna ca regele advers este in sah, acest fapt coroborat cu faptul ca nu mai are nici o mutare pozibila rezulta ca jucatorul aflat la mutare a pierdut partida fiind dat mat de adversar. Daca functia returneaza 0 insemana ca regele nu se afla in sah, acest fapt coroborat cu faptul ca nu mai are nici o mutare posibila rezulta ca rezultatul partidei este remiza. Indiferent daca rezultatul este remiza sau victorie se apeleaza functia NewGame() pentru a incepe un joc nou.
In cazul in care jucatorul se afla in modurile de joc cu inteligenta artificiale (modurile intitulate WHITE si BLACK) dupa efectuarea pasilor anteriori se apeleaza inteligenta artificiala. Functia apelata este :
public int AlphaBetaPrune(int player, int[,] board, int alpha, int beta, int depth);
a clasei AI. Acesta functie calculeaza cea mai buna mutare din punctul de vedere al algoritmului si o memoreaza. Ea returneaza valoarea estimata a pozitiei finale calculate. Dupa ce aceasta functie a calculat mutarea calculatorului se efectueaza schimbarile aferente in tabloul din clasa Tabla. La urma se efctueaza apelul la functia sah_la_rege pentru a verifica daca sa terminat sau nu jocul de sah. Aceste verificari sunt identice cu cele prezentate anterior. In cazul terminarii jocului se apeleaza functia NewGame().
d.) NewGame() este functia resposabila cu resetarea variabilelor la valorile lor initiale, cu apelarea functiei initTabla() a clasei Tabla pentru a reseta tabloul clasei la pzozitia initiala, cu golirea tabelului de notare a mutarilor si pentru apelarea inteligentei artificiale daca programul se afla in modul BLACK deoarece calculatorul trebuie sa efectueze prima mutare.
Apelarea inteligentei artificiale se face la fel ca si in functia Muta() prezentata anterior, cu efectuarea mutarii in tabloul din clasa Tabla.
Modificarile ce au loc sunt urmatoarele: euSuntLaRand = 0; jocretea = 0; GameAnnotation.Items.Clear(); verificareMutare.NewGame(); startjoc = 1; rand = 0; tabla.initTabla(); numarMutare = 0; annotation = ''. In cazul in care nu se afla in modul MULTIPLAYER se reseteaza si variabila asteptare_raspuns cu valoarea 0.
e.) Connect() este functia responsabila cu conectarea la serverul de sah pentru a putea juca in retea. Aceasta functie este apelata cand utilizatorul da click pe modul MULTIPLAYER din meniu. In cazul in care serverul nu este disponibil el anunta clientul ca nu se poate conecta.
Pentru a se conecta la server el executa urmatoarele instructiuni:
IPAddress ip = IPAddress.Parse('ipul serverului');
int iPortNo = System.Convert.ToInt16( ); //portul serverului
// Create the end point
IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);
// Connect to the remote host
m_clientSocket.Connect(ipEnd);
f.) OnDataReceived(IAsyncResult asyn) este o functie eveniment ce se lanseaza cand soseste un mesaj de la server. Cu exceptia measjului de deconectare a serverului el decodeaza mesajul primit urmand apoi sa apeleze functia ProtocolComunicare(string szData) trimitandui mesajul receptionat de la server. Decodare se obtine in felul urmator:
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
int iRx = theSockId.thisSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
szData = new System.String(chars);
g.) ProtocolComunicare(string szData) este functia responsabila cu interpretarea mesajelor primite de la server (string szData). In functie de mesajele primite el apeleaza anumite functii ale clasei mama si seteaza anumite variabile specifice modului MULTIPLAYER. Descrierea in detaliu a protocolului de comunicare se va face in subcapitolul "Jocul in retea". Pe langa interpretarea mesajelor functia mai este responsabila cu afisarea lor in casuta de text INFO.
h.) Functiile meniului sunt acele functii care se executa cand utilizatorul da click pe o optiune a meniului. De exemplu functia: sETNAMEToolStripMenuItem_Click(object sender, EventArgs e) se declanseaza cand utilizatorul paleaza functia"SET NAME" din meniu. Ea lanseaza form-ul NumeSingleplayer daca aplicatia se afla in modurile BLACK sau WHITE sau lanseaza form-ul NumeMultiplayer daca aplicatia se afla in modul MULTIPLAYER. Aceste actiuni sunt urmate de catre un Refresh() pentru a actualiza informatiile aferente numelui sau numelor introduse.
2. Clasa Tabla
Aceasta clasa contine tabloul denumit tabla in care se memoreaza situatia de pe tabla. Ea este apelata doar de catre clasa Chessboard pentru a citimodifica tabloul. Variabila cea mai importanta este:
private static int[,] tabla = new int[8, 8];
Functiile principale sunt:
public void initTabla();
+ functiile de citire si modificare a tabloului, de ex:
public void SetNumber(int i, int j,int number);
a.) Tabla este poate cea mai importanta variabila a programului. Ea este un tablou de 8x8 campuri de numere intregi exact ca si tabla de sah. Campurile pot lua valori din multimea : 0, 1, 2, 3, 4, 5, 6, 11, 12, 13, 14, 15, 16. 0 reprezinta un camp liber(nu exista nici o piesa plasata pe el). Numerle mai mici decat 10 reprezinta piesele albului iar numerele mai mari decat 10 reprezinta piesele negrului. Corespondenta valoare piesa este urmatoarea:
1 si 11 rege alb respectiv negru
2 si 12 dama alba respectiv neagra
3 si 13 turn alb respectiv negru
4 si 14 nebun alb respectiv negru
5 si 15 cal alb respectiv negru
6 si 16 pion alb respectiv negru
b.) initTabla() este functia responsabila cu initializarea tablei la inceputul jocului. Tabla este initializata in functie de modul in care se afla. Daca se afla in modul normal, tabla se initializeaza cu pozitia initiala descrsia in capitolu 2. Daca in schimb se afla in modurile special create pentru incepatori ea se initializeaza conform pozitiilor prezentate in figurile ce descriu modurile respective din capitolul 3.
3. Clasa ParametriiSistem
Aceasta clasa contine parametrii care definesc starea jocului. Acesti parametrii sunt cititi de fiecare data cand se initializeaza o pozitie sau se apeleaza inteligenta artificiala. Pe langa acesti parametrii, clasa mai contine functiile create special pentru a citi si a seta acesti parametrii.
private static int culoare = 0;
private static int special = 0;
private static int caracter = 0;
private static int fortadejoc = 3;
Parametrii sunt declarati statici deoarece ei trebuie sa fie identici pentru toate instantierile acestei clase.
a.) Culoare este parametrul caracteristic modurilor de joc BLACK, WHITE si MULTIPLAYER. Valoarea lui initiala este 0 cea ce indica modul WHITE(utilizatorul joaca immpotriva calculatorului avand piesele albe). Folosind optiunile meniului aceasta valoare poate fi modificata, aceasta fiind si singura metoda prin care se poate schimba valoarea parametrului. Astfel se asigura ca parametrul va avea mereu o valoare in multimea : 0, 1, 2.
Valoarea corespunzatoare modului BLACK(utilizatorul joaca cu calculatorul avand piesele nerge) este 1. Pe cand valoarea 2 indica modul de joc MULTIPLAYER(utilizatorul joaca cu alt utilizator in retea).
b.) Special este parametrul caracteristic tipului de joc. Fie NORMAL fie unul din cele noua moduri create special pentru ajutorul incepatorilor. Folosind optiunile meniului aceasta valoare poate fi modificata, aceasta fiind si singura metoda prin care se poate schimba valoarea parametrului. Astfel se asigura ca parametrul va avea mereu valoari cuprinse intre 0 si 9. 0 fiind modul normal, de la 1 la 5 cele 5 moduri rege + pioni + o piesa si de la 6 la 9 cele 4 moduri denumite generic maturi elementare.
c.) Caracter este parametrul care decide stilul de joc al iteligentei artificiale. El poate avea 3 valori: 0, 1, 2. 0 reprezinta modul normal de joc, aceasta fiind si valoarea initiala a parametrului. 1 reprezinta modul de joc agresiv, computerulva incerca sa atace regele rocat al adversarului. 2 reprezinta un mod de joc defensiv, computerul va incerca sa isi apere regele de atacul adversarului. Folosind optiunile meniului aceasta valoare poate fi modificata, aceasta fiind si singura metoda prin care se poate schimba valoarea parametrului. Astfel se asigura ca parametrul va avea mereu valoari cuprinse intre 0 si 2.
d.) Fortadejoc este parametrul ce da forta de joc al inteligentei artificiale. Valoarea sa plus 1 da adancimea arborelui de cautare. Valoarea initiala este 3 reprezentatnd o forta de joc normala. 1 este valoarea atribuita unei forte de joc slabe iar 5 cea atribuita unei forte de joc puternice. Folosind optiunile meniului aceasta valoare poate fi modificata, aceasta fiind si singura metoda prin care se poate schimba valoarea parametrului. Astfel se asigura ca parametrul va avea mereu o valoare in multimea : 1, 3, 5.
Acetse valori au fost alese in asa fel incat raspunsul calculatorului sa vina in timp util pentru a nu se astepta mai mult de 2 minute in cazul unei forte dejoc puternice.
Augustina Clasa Mutare_valida
Aceasta clasa este responsabila cu verificarea muatrilor daca sunt sau nu valide. Astfel inainte de efectuarea propriuzisa a mutarii utilizatorului se verifica daca mutarea incercata de el este valida sau nu. In cazul in care nu este valida ea este anulata si se asteapta o noua incercare a utilizatorului. Aceasta clasa mai este utilizata si de clasa ToateMutarilePosiile pentru a determina toate mutarile legale intr-o pozitie anume. Parametrii si functiile cele mai importante sunt:
private int rocada_mica_alb = 0;
private int rocada_mare_alb = 0;
private int rocada_mica_negru = 0;
private int rocada_mare_negru = 0;
public void NewGame();
private void efectueaza_mutarea(int a, int b, int c, int d);
private void retracteaza_mutarea(int a, int b, int c, int d);
public int validare_mutare(int[,] tbl, int rand, int a, int b, int c, int d);
+ functiile de verificare pentru fiecare piesa in parte. De ex.:
private int mutare_nebun();
+ functiile de setare a variabilelor statice prezentate mai sus. De ex.:
public void set_rocada_mare_alb(int i);
a.) Cele patru variabile enumerate anterior au rolul de a memora daca albul sau negrul mai au dreptul de a face rocada mica sau mare. Valoarea lor initiala este 0 cea ce inseamna ca au dreptul de a efectua rocadele respective. In momentul in care ei efectueaza o mutare care conform regulamentului FIDE prezentat in capitolul 2 le interzice sa mai faca una dintre rocade pe parcursul partidei valoarea respectiva este pusa pe 1. Astfel orice incercare de a face rocada este anulata. Parametrii sunt statici pentru ca este important ca ei sa isi pastreze valorile pentru orice instantiere a clasei.
b.) NewGame() este functia care trebuie apelata de catre clasa principala Chessboard la inceperea unui joc nou pentru a pune din nou pe 0 variabilele prezentate la punctul a.) .
c.) Functia efectueaza_mutarea(int a, int b, int c, int d) primeste ca argumente patru numere intregi care reprezinta cele doua coordonate ce compun o mutare. Astfel functia efectueaza schimbarile aferente acestei muatri pe tabla transmisa ca parametru functiei validare_mutare. Ea tine cont cont daca mutarea este transformare sau rocada si seteaza variabilele respective astfel incat functia retracteaza_mutarea sa poata restaura tabla exact cum a fost inaintea efectuarii mutarii.
d.) Functia retracteaza_mutarea(int a, int b, int c, int d) primeste ca argumente la fel ca si functia efectueaza_mutarea patru numere intregi care reprezinta cele doua coordonate ce compun o mutare. Astfel in functie de variabilele de rocada sau transformare setate sau nu de catre functia efectueaza_mutarea el va restaura tabla la pozitia ei intiala (inaintea efectuarii mutarii).
e.) Functia validare_mutare(int[,] tbl, int rand, int a, int b, int c, int d) este functia de baza a acestei clase. Ea este apelata de catre clasele Chessboard si ToateMutarilePosibile cand se necesita verificarea validitatii unei mutari. Ea primeste ca intrare 5 argumente. Primul argument este un tablou ce reprezinta tabla de sah, pozitia ce se afla in acel moment pe tabla. Urmatorul argument determina culoarea pieselor care se afla la mutare, 0 reprezentand piesele albe si 1 reprezentand piesele negre. Ultimele 4 argumente sunt patru numere intregi care reprezinta cele doua coordonate ce compun o mutare.
Prima verificare facuta de functie este legata de variabila rand, ea fiind cea mai simpla si care poate da un verdict imediat asupra legalitatii mutarii. Verificam ce piesa se afla pe campul de plecare a mutarii, mai exact campul tabla[a,b]. Daca piesa este neagra(valoare strict mai mare decat 6) si "rand" este egal cu 0 inseamna ca albul este la rand si prin urmare mutarea este ilegala (trebuie mutata neaparat o piesa alba). Si situatia reciproca este valabila, daca negrul este la rand iar piesa care se afla pe campul de plecare a mutarii este alba. Astfel functia va returna imediat 0, valoare echivalente cu faptul ca mutarea nu este permisa.
Dupa ce trece de acest prim test urmeaza un switch al carui argument este piesa aflata pe campul de plecare al mutarii. In functie de tipul de piesa se apeleaza functia special creata pentru a verifica mutarea ei. Exista 12 functii create pentru a verifica mutarile feicarei piese in cauza. Necesitatea de a crea o functie diferita pentru turnul alb si pentru turnul negru de exemplu se datoreaza posibilitatii de a captura o piesa adversa , pentru simplitatea conditiilor din clauzele if am ales implementarea lor separata. La pioni este si mai evidenta necesitatea deoarece pionii de culori diferite muta mereu in directie opusa. Functiile respective vor fi descrise in urmatorul subpunct.
Daca functia corespunzatoare piesei si-a "dat acordul" atunci mai ramane un ultim pas. Aici intervin functiile se efectuare si de retractare a mutarilor. Se efectueaza mutarea dupa care se transmite noua pozitie printr-un tablou functiei sah_la_rege_alb() sau sah_la_rege_negru(), in functie de culoarea jucatorului care se afla la mutare, al clasei Sah_la_rege. Astfel se verifica daca mutarea nu cumva lasa regele in sah si prin urmare este ilegala. Aceste functii vor fi discutate cand vom aborda clasa respectiva. Daca functia returneaza 1 inseamna ca regele propriu se afla in sah si prin urmare mutarea este ilegala. In cazul in care returneaza 0 inseamna ca in sfarsit putem spune ca mutarea a trecut toate testele si este legala. In ambele cazuri (daca functia sah_la_rege returneaza 0 sau 1) se retrage mutarea efectuata dupa care se returneaza valoarea corespunzatoare legalitatii mutarii 0 sau 1. 0 daca este ilegala sau 1 daca este legala.
f.) Cele 12 functii sunt create special pentru cele 12 piese distincte de pe tabla de sah (calul negru este dictinct de calul alb, toti pionii negrii(sau albi) sunt considerati identici). Functiile sunt diferite datorita faptului ca fiecare piesa muta dupa alte reguli (vezi regulamentul FIDE din capitolul 2) si creearea unei functii unice ar conduce la verificari inutile si prin urmare la incetinirea raspunsului dat de inteligenta artificiala.
Sa luam ca exemplu functia responsabila verificarii mutarii nebunului care verifica daca punctul de pornire si cel de sosire se afla pe aceeasi diagonala, daca intre punctul de plecare si cel de sosire nu se afla nici un obstacol, daca nu se afla nici o piesa pe punctul de sosire sau daca se afla verifica sa nu fie de aceeasi culoare.
Toate aceste functii returneaza valoarea 1 daca mutarea este legala sau 0 daca mutarea este ilegala.
e.) Mai exista patru functii de setare a celor patru variabile, prezentate la inceput, rezervate rocadelor. Prin ele clasa Chessboard poate modifica valorile lor. Mai exact, cand un jucator efectueaza o mutare care ii anuleaza drepturile de a mai efectua rocada in acel joc, functia Muta() din clasa Chessboard intervine si anuleaza dreptul jucatorului de a efectua rocada prin setarea valorii pe 1 a variabilei corespunzatoare. Astfel de fiecare data cand un jucator va incerca totusi sa efectueze rocada el va fi impiedicat deoarece apelul catre functia validare_mutare va avea ca raspuns mutare ilegala.
5. Clasa Sah_la_rege
Acesta clasa verifica daca intr-o anume pozitie regele unei parti se afla in sah sau nu. Ea este folosita la validarea unei mutari de catre clasa Mutare_valida si la verificarea conditiei de terminare a jocului de catre clasa principala Chessboard. Functiile principale sunt:
public int sah_la_rege_alb(int [,] tbl);
public int sah_la_rege_negru(int [,] tbl);
Am ales implementarea a doua functii diferite pentru regele alb si pentru regele negru pentru a evita anumite verificari inutile si a complicarii apelului functie. Ambele functii au ca si parametru de intrare un tablou bidimensional ce reprezinta tabla de sah, care contine pozitia ce urmeazaa a fi inspectata.
Odata obtinuta tabla urmeaza cautarea regelui pe tabla. Dupa ce sa gasit pozitia regelui pe tabla se salveaza coordinatele acetsuia pentru verificarile urmatoare. In acest punct putem spune ca functia este impartita in patru parti. O parte dedicata exclusiv posibilitatii de a fi in sah de la calul adversarului, ceea ce inseamna verificarea celor posibile opt campuri (posibile opt deoarece regele s-ar putea afla la marginea tablei, acest fapt reducand considerabil numarul de campuri posibile) pe care daca s-ar afla un cal regele ar fi in sah. O alte parte exclusiva dedicata sahului de la pion, un posibil sah mai usor de verificat deoarece sunt maxim doua campuri de pe care un rege ar putea fi in sah de la un pion (maxim doua deoarece daca regele se afla pe colona 1 sau 8 ezista doar o singura posibilitate iar daca de exemplu regele alb se afla pe liniile 7 sau 8 nu exista nici o posibilitate).
Urmatoarele doua parti sunt mixte deoarece dama poate muta si ca turnul si ca nebunul in acelasi timp, prin urmare verificam impreuna acele posibilitati. Prima dintre aceste doua este cea de sah de la dama sau nebun. Aceasta parte parcurge cele posibile patru diagonalele (in cazul incare regele se afla pe una din marginile tablei numarul diagonalelor se reduce la doua) ce intersecteaza regele prin patru instructiuni for si se verifica prima piesa intalnita daca este nebun sau dama. A doua parte mixta verifica daca regele este in sah de la turn sau dama. La fel ca si in cazul precedent parcurgem, de aceasta data, cele posibil patru coloanele (din nou daca regele se afla la marginea tablei numarul se reduce la trei iar daca se afla in unul din colturi se reduce la doua) care intersecteaza regele prin patru instructiuni for si verificam prima piesa intalnita daca este turn sau dama.
In cazul incare regele se afla in sah, in concordanta cu regulamentul FIDE (vezi capitolul 2), functia returneaza 1 iar in caz contrar returneaza 0.
6. Clasa ToateMutarilePosibile
Acesta clasa returneaza toate mutarile posibile ale unui jucator in pozitia transmisa ca si argument functiei in cauza. Ea salveaza aceste mutari intr-un sir de clase Mutari. Functiilesi varibilele principale sunt:
Mutari [] mutari = new Mutari[100];
private int index = 0;
public void set();
public void gen();
public int GetIndex();
public int genereaza_mutari(int randul,int [,] tbl);
public int genereaza_mutari(int randul, int[,] tbl, int piesa, int i, int j);
+ functii pentru generarea mutarilor specifice fiecarei piese. De ex.:
private void mutare_rege(int a, int b);
+ functii de citire a mutarilor generate de functiile clasei. De ex.:
public int get_a(int i);
a.) In sirul "mutari" salvez muatrile generate de catre functiile de generare a muatrilor genereaza_mutari().
b.) Variabila index reprezinta indexul sirului de mutari generate de catre functiile genereaza_mutari. El va indica mereu numarul de mutari legale calculate din pozitia transmisa ca si argument celor doua functii.
c.) Functia set() trebuie sa fie apelata inaintea functiilor genereaza_mutari() pentru a reseta indexul, pentru ai atribui indexului valoarea 0.
d.) Functia gen() trebuie apelata doar o data pentru fiecare instanta a clasei ToateMutarilePosibile. Ea genereaza un sir de o suta de clase de tipul Mutari. Am ales aceasta varianta in detrimentul variantei de a genera un nou obiect al sirului doar in momentul cand am nevoie de el deoarece apeland de mai multe ori aceeasi instanta a clasei ToateMutarilePosibile am crea de fiecare data obiecte noi. Folosind aceasta metoda, obiectele vor fi reutilizate la un urmator apel al functiilor genereaza_mutari prin resetarea indexului.
e.) Functia GetIndex() returneaza valoarea indexului, util pentru parcurgerea sirului de mutari generate de catre functiile genereaza_mutari fara a depasii numarul lor si de a genera o eroare.
f.) Functia genereaza_mutari(int randul,int [,] tbl) genereaza toate mutarile posibile (legale bineinteles) din pozitia care ii este transmisa ca si argument. Aceasta functie, prima din cele doua functii generatoare de mutari, are doua argumente de intrare. Primul determina culoarea pieselor a jucatoului care se afla la mutare. Astfel daca variabila "rand" are valoarea 0 inseamna ca piesele albe sunt la mutare, iar daca valoarea lui este 1 atunci piesele negre sunt la mutare. Al doilea argument este un tablou bidimensional care reprezinta pozitia de pe tabla de sah.
Acesta functie este utilizata de catre inteligenta artificiala cu scopul formarii arborelui de cautare a mutarii optime si de catre clasa Chessboard mai precis functia Muta() a cestei clase pentru a determina cand se indeplinesc conditiile de terminarea jocului.
Avand culoarea pieselor si pozitia incepem parcurgerea tablei de sah camp cu camp. In momentul in care am gasit pe un camp o piesa de culoarea indicata de catre variabila "rand" apelam functia corespunzatoare piesei. In acest caz avem doar sapte functii, spre deosebire de clasa Mutare_valida, cinci pentru cai, nebuni, turnuri, dame si regi albi si negri si una pentru pioni albi si una pentru pioni negrii. In afara pionilor care muta in directie opusa, fapt ce justifica necesitatea celor doua functii separate, celelalte piese muta la fel indiferent de culoare.
Dupa generarea tuturor mutarilor posibile prin aceste functii special create pentru fiecare piesa in parte, functia genereaza_mutari se incheie prin returnarea valorii indexului.
g.) Functia genereaza_mutari(int randul, int[,] tbl, int piesa, int i, int j) a fost creata special pentru a calcula toate mutarile posibile ale unei singure piese de pe tabla de sah. Aceasta functie, spre deosebire de functia prezentata precedent, are cinci argumente de intrare. Pe langa "rand" si "tbl" care au aceeasi insemnatate ca si in functia precedenta, cea de determinare a culorii respectiv cea a pozitiei de pe tabla, mai sunt trei variabile de numere intregi. Variabila "piesa" reprezinta tipul piesei ce urmeaza a fi mutata (de ex. 3 pentru turn, 2 pentru dama). Variabilele "i" si "j" reprezinta coordinatele campului pe care este pozitionata piesa tabla.
Acesta functie este utilizata de catre functia eveniment ChessBoard_Paint() care apartine clasei Chessboard cu scopul colorarii campurilor pe care poate muta piesa selectata de catre utilizator.
Spre deosebire de functia anterioara nu se mai parcurge tabla deoarece este vorba doar de o singura piesa si se si stie locatia exacta la care se afla. Se verifica doar tipul piesei pentru a apela functia potrivita. Aceste functii sunt tot acelea pe care le apeleaza si functia precedenta.
Dupa generarea tuturor mutarilor posibile prin aceste functii special create pentru fiecare piesa in parte, functia genereaza_mutari se incheie prin returnarea valorii indexului. Diferenta majora fiind numarul de mutari generate, acela fiind considerabil mai mic decat cel generat de functia anterioara.
h.) Cele sapte functii create special pentru fiecare tip de piesa in parte au scopul de a calcula si memora fiecare mutare posibila a piesei in cauza. Nota aparte fac cele doua functii responsabile pentru muatrea pionilor. Una pentru pionii albi iar cealalta pentru pionii negrii, justificarea fiind faptul ca ei muta in directie opusa. In rest celelalte cinci functii nu tin cont de culoarea piesei deoarece ele muta dupa aceleasi reguli.
Toate cele 7 functii au urmaoarea structura:
private void mutare_rege(int a, int b);
Ele au doua numere intregi ca si argumente de intrare. Aceste numere compun coordonatele pe tabla de sah a piesei in cauza. Stiind coordonatele se calculeaza toate muatrile posibile ale acelei piese ca si cand ar fi pe o tabla goala. Validitatea mutarilor rezultate este verificata cu ajutorul functiei validare_mutare al clasei Mutare_valida. Mutarile validate de catre functia validare_mutare sunt memorate in sirul de clase de tip Mutari la valoarea indexului dupa care urmeaza incrementarea indexului.
i.) Pentru a putea extrage mutarile din sirul de clase create de catre functiile mentionate anterior avem nevoie de functiile urmatoare:
public int get_a(int i);
public int get_b(int i);
Cele cinci functii create pentru extragerea celor cinci atribute stocate (cele patru coordonate ce compun o mutare: a, b, c, d si tipul de piesa intitlat sugestiv : piesa) sunt structurate identic. Au doar un singur argument de intrare ce reprezinta indexul mutarii respective in sirul de mutari stocate de catre functiile anterioare. Ele returneaza valoarea atributului solicitat. Astfel se poate parcurge tot sirul de mutari generate pirntr-un for care sa aiba structura urmatoare: for(int i = 0; i < index; i++) . Indexul din for fiind chiar indexul acestei clase.
7. Clasa EfectuezaMutarea
Acesta clasa a fost creata special pentru efectuarea si retragerea mutarilor efctuate de catre inteligenta artificiala in cautarea mutarii optime. Datorita efectuarii a mai multor mutari consecutive inainte de retragerea unei mutari trebuiesc stocate mai multe informatii despre mutarea respectiva, astfel din considerente de economisire a spatiului de memorie am creat si o clasa speciala de retinere a acestor mutari MutariPtCalcul.
Functiile si variabilele cele mai importante sunt:
MutariPtCalcul[] mutari = new MutariPtCalcul[100000];
private int piesamut = 0;
private int transformare = 0;
private int rocada = 0;
private int piesa = 0;
private int enpassant = 0;
private int index = 0;
public int[,] efectMutare(int[,] tabla, int a, int b, int c, int d);
public int[,] retragMutare(int[,] tabla);
a.) In sirul "mutari" salvez muatrile efectuate de catre functia efectMutare() pentru a putea fi retrase ulterior prin functia retragMutare().
b.) Cu ajutorul lui "index" parcurg sirul de mutari. Valoarea lui initiala si finala este 0 deoarece algoritmul de inteligenta artificiala retrage pana la final toate mutarile efectuate. La efectuarea unei mutari el este incrementat iar la retractarea ei el este decrementat.
c.) Variabilele transformare, rocada, piesa si enpassant sunt folosite pentru stocarea informatiilor respective in sirul de mutari.
Variabila transformare anunta functia retragMutare() daca sa efectuat sau nu o transfromare deoarece daca a avut loc trebuie transformata dama inapoi in pion. Valoarea 1 indica faptul ca sa efectuat o transformare. 0 indica faptul ca nu sa efectuat nici o transformare.
Variabila rocada anunta functia retragMutare() daca sa efectuat sau nu o rocada deoarece daca sa efectuat trebuie avut grija sa se mute si turnul in cauza la locul sau initial. Este in grija functiei retragMutare() sa determine ce fel de rocada a avut loc (mare sau mica). Valoarea 1 indica faptul ca sa efectuat o rocada. 0 indica faptul ca nu sa efectuat nici o rocada.
Variabila enpassant anunta functia retragMutare() daca sa efectuat sau nu o mutare de tipul enpassant. In cazul in care sa efectuat functia retragMutare() trebuie sa aiba grija sa puna la locul initial pionul advers. Valoarea 1 indica faptul ca sa efectuat o mutare enpassant. 0 indica faptul ca nu sa efectuat nici o mutare enpassant.
Variabila piesa indica ce a fost pe campul de destinatie al muatrii inaintea efectuarii mutarii. Acest lucru este foarte util de retinut in cazul capturilor. Functia retragMutare() trebuie sa stie ce sa puna pe campul de unde a retras piesa mutata anterior. Ea pune mereu valoarea variabilei piesa in locul de destinatie al mutarii pe care o retracteaza. Variabila piesa poate lua orice valoare de piesa sau 0 in cazul in care campul era liber inaintea sosirii piesei mutate.
d.) Variabila piesamut este utilizata in salvarea tipului de piesa care a fost mutata. Salvarea mutarii ce tocmai a fost efectuata ajuta la calcularea corecta a regulii enpassant la mutarea imediat urmatoare.
e.) Functia efectMutare(int[,] tabla, int a, int b, int c, int d) are doua roluri, de a muta piesa pe tabla si de a salva mutarea respectiva in sirul "mutari". Ea are cinci argumente de intrare. Primul reprezinta pozitia de pe tabla, urmatoarele patru reprezinta cele doua coordonate ce compun o mutare. Ea returneaza dupa calculele aferente noua pozitie rezulatata dupa efectuarea mutarii.
In momentul efectuarii mutarii, functia efectMutare(), trebuie sa verifice daca nu cumva mutarea are un carecter "special", adica sa fie o mutare enpassant, o rocada sau o transformare, moment in care ea trebuie sa seteze variabilele metionate mai sus corespunzator. Dupa efectuarea mutarii ea trebuie sa memoreze mutarea efectuata impreuna cu variabilele mai sus mentionate in sirul de mutari urmat de incrementarea indexului si de memorare a mutarii in structura de mutare anterioara.
f.) Functia retragMutare(int[,] tabla) are ca scop doar retragerea ultimei mutarii efectuate. Ea are ca argument de intrare doar pozitia de pe tabla. Coordonatele si informatiile despre mutare le citeste din sirul; de mutari de la ultima pozitie indicata de valoarea indexului minus unu. La final returneaza noua pozitie rezulatata dupa retragerea mutarii.
Functia verifica daca mutarea are un caracter "special" sau nu pentru a retrage corespunzator mutarea. Dupa retragerea mutarii el decrementeaza indexul.
8. Clasa Mutari
In acesta clasa se stocheaza mutari de catre algoritmul de generare a mutarilor din calsa ToateMutarilePosibile. Clasa contine cele 5 variabile necesare pentru memorarea unei mutari:
private int a = 0;
private int b = 0;
private int c = 0;
private int d = 0;
private int piesa = 0;
Functia pentru memorarea mutarii:
public void set_mutare(int a, int b, int c, int d, int piesa);
Functiile de citire a variabilelor mutarii. De ex.:
public int get_a() ;
a.) Variabilele a, b, c si d compun cele doua coordonate ale unei mutari. Ele pot lua valori intre 0 si 7 echivalente cu coloana sau linia pe care o reprezinta daca se aduna un 1 la valoarea lor.
b.) Variabila piesa reprezinta tipul de piesa ce se afla pe campul de destinatie al mutarii. In cazul in care piesa a mutat pe un camp liber valoarea atribuita variabilei este 0. Altfel ea va lua valorile corespunzatoare piesei respective cuprinsa in intervalul 1 si 6 pentru alb si 11 si 16 pentru negru.
c.) Functia set_mutare(int a, int b, int c, int d, int piesa) are rolul de a memora mutarea prin copierea argumentelor de la intrare in variabilele corespunzatoare. Cele cinci argumente de intrare reprezinta cele doua coordonate ce compun o mutare in sah (primele patru argumente) si piesa "scoasa" de pe tabla la o eventuala captura.
d.) Pentru citirea celor cinci variabile am creat cinci functii care au aceessi structura: public int get_a() . Cand sunt apelate, ele returneaza valoarea variabilei in cauza.
9. Clasa MutariPtCalcul
Aceasta clasa a fost creata special pentru memorarea mutarilor efectuate de catre inteligenta artificiala pe parcursul cautarii mutarii optime. Ea contine mai multe informatii decat clasa Mutari necesare pentru stocarea unei mutari ce nu va fi retractata imediat dupa ce a fost efectuata cum este cazul in clasa Mutare_valida prezentata anterior. Singurele muatri ce se efectueaza in acest caz si sun retrase imediat sunt acelea care reprezinta o frunza a arborelui de cautare al algoritmului inteligentei artificiale.
Clasa EfectuezaMutarea utilizeaza aceasta clasa exclusiv pentru asi memora mutarile efectuate prin functia efectMutare() a clasei respective. Functia efectMutare() utilizeaza functia MutariPtCalcul() al acestei clase pentru a salva coordinatele muatrii si a variabilelor ce reprezinta muatri "speciale".
Cele opt variabile necesare memorari unei mutari sunt:
public int a = 0;
public int b = 0;
public int c = 0;
public int d = 0;
public int enpassant = 0;
public int rocada = 0;
public int transformare = 0;
public int piesa = 0;
functia de setare a variabilelor este:
public MutariPtCalcul(int a1, int b1, int c1, int d1, int enpassant1, int rocada1, int transformare1, int piesa1);
a.) Primele partru variabile a, b, c si d reprezinta ca si in cazul anterior cele doua coordonate ce compun o mutare in sah. Ele pot lua valori intre 0 si 7 echivalente cu coloana sau linia pe care o reprezinta daca se aduna un 1 la valoarea lor.
b.) Variabila piesa reprezinta tipul de piesa ce se afla pe campul de destinatie al mutarii. In cazul in care piesa a mutat pe un camp liber valoarea atribuita variabilei este 0. Altfel ea va lua valorile corespunzatoare piesei respective cuprinsa in intervalul 1 si 6 pentru alb si 11 si 16 pentru negru.
c.) Variabila enpassant este setata special de catre functia efectMutare() a clasei EfectuezaMutarea pentru a atentiona functia retragMutare() al aceleasi clase ca a vut loc o mutare enpassant. Valoarea 1 indica faptul ca sa efectuat o mutare enpassant, iar 0 (valoarea uzuala) indica faptul ca mutarea efectuata nu a fost enpassant.
d.) Variabila rocada este setata special de catre functia efectMutare() a clasei EfectuezaMutarea pentru a atentiona functia retragMutare() al aceleasi clase ca a vut loc o rocada. Ea precizeaza doar ca sa efectuat o rocada nu si tipul de rocada efectuata, acest lucru ramanand in grija functiei retragMutare().Valoarea 1 indica faptul ca sa efectuat o rocada, iar 0 (valoarea uzuala) indica faptul ca nu sa efectuat o rocada.
e.) Variabila transformare este setata special de catre functia efectMutare() a clasei EfectuezaMutarea pentru a atentiona functia retragMutare() al aceleasi clase ca a vut loc o transformare. Valoarea 1 indica faptul ca sa efectuat o transformare, iar 0 (valoarea uzuala) indica faptul ca nu sa efectuat o transformare.
f.) Functia prin intermediul careia se memoreaza mutarea este MutariPtCalcul(int a1, int b1, int c1, int d1, int enpassant1, int rocada1, int transformare1, int piesa1). Ea primeste opt argumente de intrare ce corespund celor opt variabile prezentate anterior. Valoarea argumentelor este atribuita variabilelor.
10. Clasa AI
Aceasta clasa contine algoritmul inteligentei artificiale. Ea este apelata de catre clasa Chessboard pentru a calcula cea mai buna mutare in pozitia aflata pe tabla la momentul respectiv.
Cea mai importanta variabila este : private int score = 0. Ea reprezinta coeficientul pozitiei, coeficient dupa care se ia decizia care este cea mai buna mutare.
O alta variabila foarte importanta este instantierea clasei MutareAI unde se salveaza mutarea aleasa ca fiind cea mai buna de catre algoritmul de intaligenta artificiala:
private MutareAI mutarea = new MutareAI();
Functiile clasei sunt:
public int AlphaBetaPrune(int player, int[,] board, int alpha, int beta, int depth);
private int Evalueaza(int player, int[,] board, int alpha, int beta, int depth);
private int otherplayer(int player);
a.) Functia otherplayer(int player) are rolul de a schimba jucatorul aflat la rand. Ea primeste ca argument un numar intreg ce simbolizeaza jucatorul aflat la rand si returneaza un numar intreg care il simbolizeaza pe oponentul sau. Efectiv aceasta functie returneaza 1 daca primeste ca si argumnet 0 sau returneaza 0 daca primeste ca si argumnet 1. 1 si 0 simbolizand ce-i doi jucatori aflati la tabla mai precis albul sau negrul. Aceasta functie este utilizata dupa cum vom observa imediat de catre algoritmul de inteligenta artificiala, mai precis de catre functiile AlphaBetaPrune() si Evalueaza().
b.) Functia AlphaBetaPrune(int player, int[,] board, int alpha, int beta, int depth) serveste ca si cadru pentru algoritmul efectiv al intelegintei artificiale reprezentat de catre functia Evalueaza(). Ea are cinci argumente de intrare. Primul reprezinta jucatorul sau mai bine zis culoarea pieselor ce se afla la mutare. Al doilea argument reprezinta pozitia actuala de pe tabla. Al treilea si al patrulea sunt valori de comparatie pentru scorul obtinut din calcularea pozitiei de pe tabla. Alpha, initializat cu -10000, reprezinta cel mai prost scor posibil pentru alb iar beta, initializat cu +10000, reprezinta cel mai prost scor posibil pentru negru. Ultimul argument da adancimea arborelui de cautare. Functia returneaza scorul pozitiei in urma mutarii efctuate de catre calculator.
In prima faza se calculeaza toate mutarile posibile din pozitia primita ca si argument. Numarul de mutari returnate de functia genereaza_mutari() il memoram in variabila parcurge. Dupa care incepem sa parcurgem sirul de mutari prin efectuarea primei mutari si apelam functia Evalueaza() pentru ca aceasta sa calculeza scorul obtinut prin efectuarea acestei mutari. Odata obtinut acel scor retractam mutarea efectuata si salvam muatarea in cazul in care este prima mutare calculata pentru a evita situatia in care calculatorul nu gaseste o solutie de scapare de mat si nu se salveaza nici o mutare deoarece scorul ei va fi egal cu cel mai slab posibil.
Dupa acesti pasi urmeaza compararea valorii obtinute in functie de piesele care se afla la rand. Astfel ea se compara cu -10000 pentru alb sau +10000 pentru negru. Daca aceasta valoare returnata de catre functia Evalueaza() este mai buna decat acea valoare prestabilita mutarea respectiva se salveaza iar valoarea cu care se vor compara urmatoarele mutari va fi cea a mutarii salvate: alpha = valoare; sau beta = valoare. Astfel se asigura ca mutarea ce va ramane salvata va fi cea mai buna gasita de catre algoritm.
Odata parcursi acesti pasi se continua parcurgerea sirului de mutari. Se extrage urmatoarea mutare din sir si se executa. Pasii executati sunt identici cu cei anteriori. La final vom sti care mutare din toate muatrile posibile in pozitia actuala are scorul cel mai bun din punct de vedere al inteligentei artificiale si mai exact al functiei Evalueaza(). Acea muatre este cea care se afla memorata in instanta clasei MutareAI. Acesta mutare va fi executata de catre functia Muta() aclasei Chessboard
Codul sursa al functiei AlphaBetaPrune:
public int AlphaBetaPrune(int player, int[,] board, int alpha, int beta, int depth)
if (player == 0)
}
if (player == 1)
}
}//end for
return valoare;
}
c..) Functia Evalueaza(int player, int[,] board, int alpha, int beta, int depth) este algoritmul de cautare al inteligentei artificiale. La baza lui sta algoritmul minimax cu Alpha-Beta pruning sau mai cautare Alpha-Beta. Termenul pruning vine de la evitarea cautarii pe anumite ramuri ale arborelui. Acest algoritm este o imbunatatire fata de algoritmul simplu MiniMax care parcurgea arborele exhaustiv.
Cu un factor de ramificatie b si o adnacime d numarul maxim de noduri frunza evaluate (cazul cel mai pesimist) este de O(b*b*.*b) = O( acelasi rezultat ca si MiniMax. Daca ordonarea muatrilor pentru cautare este optimala (cele mai bune mutari sunt calculate la inceput) , numarul de noduri frunza evaluate este aproximativ O(b*1*b*1.*b) pentru adancime impara si O(b*1*b*1.*1) pentru o adancime para sau O( ) = O( . In acest caz ramificarile se reduc la radacina lor, practic inseamna ca se poate merge la adancime dubla cu acelasi efort computational al unui algoritm simplu MiniMax. Daca b = 40 cum este incazul sahului, la o adancimea de 12, atunci raportul intre o sortare optima si un a pesimista este de sau aproximativ 4 miliarde.
Argumentele de intrare sunt identice cu cele ale functiei AlphaBetaPrune() si au acelasi rol. Functia Evalueaza() returneaza valoarea pozitiei cauzate de mutarea efectuata de functia AlphaBetaPrune() inaintea apelului. Astfel se poate lua o decizie privind cea mai buna mutare.
Prima verificare a functiei este daca sa ajuns la un nod frunza prin verificarea adancimii arborelui. Daca sa ajuns la 0 inseamna ca acel nod este o frunza si prin urmare se evalueaza pozitia respectiva apeland la functia EstimeazaTabla() al clasei Estimator si se returneaza valoare. Daca nodul nu este frunza se trece mai departe si se genereaza toate mutarile posibile din pozitia curenta. Dupa generarea mutarii se verifica care din jucatori este la rand, albul sau negrul, si se incepe parcurgerea sirului de mutari. Se extrage prima mutare se efectueaza mutarea si se face un apel recursiv la functie.
Cand functia ajunge la un nod frunza ea returneaza valoarea pozitiei respective. Astfel se revine in locul de unde sa initiat apelul recursiv si se continua executarea functiei prin retragerea mutarii efectuate cel mai recent. Se verifica mai apoi daca scorul pozitiei este mai bun decat cel reprezentat de coeficientul alpha pentru alb sau beta pentru negru. In cazul in care sa gasit o mutarea mai buna se inlocuieste valoarea lui alpha cu valoarea scorului obtinut. Astfel se ingusteaza fereastra formata de alpha si beta. Momentul in care alpha >= beta se opreste executia pe ramura respectiva nemaifiind necesara examinerea ei in continuare. Astfel functia returneaza alpha sau beta dupa caz.
Dupa terminarea evaluarilor sau a sirului de mutari functia returneaza "veridctul" final. Acesta fiind scorul minim garantat ce il poate obtine calculatorul in urma efectuarii acelei mutari.
Codul sursa al functiei Evalueaza:
private int Evalueaza(int player, int[,] board, int alpha, int beta, int depth)
return alpha;
}
else
return beta;
}
}
11.Clasa Estimator
Aceasta clasa este responsabila cu evaluarea pozitiilor de pe tabla de sah. Pe langa insumarea valorilor fixe a fiecarei piese in parte se mai adauga si bonusuri in functie de plasamentul piesei pe tabla. Valorile acestor bonusuri sunt atribuite dupa considernte de teorie sahista descrise in capitolul 3. O valoare pozitiva a coeficientului indica un avantaj al albului pe cand o valoare negativa indica un avantaj al negrului.
Functia de evaluare este:
public int EstimeazaTabla(int[,] tabla);
Aceasta functie primeste ca argument o pozitie spre analiza si reutrneaza valoarea ei. O valoare cat mai pozitiva favorizeaza albul iar una cat mai negativa favorizeaza negrul. Tabla de sah este parcursa camp cu camp si de fiecare data cand este gasita o piesa i se atribuie o valoare. Aceasta valoare este compusa din doua parti "valoarea cantitativa" care este mereu fixa si cea "calitativa" care incazul nostru depinde pozitionarea fiecarei piese in parte. Piesele au urmatoarea valoarea "cantitativa":
Rege = 0 puncte
Pion = 1 punct
Nebun = 3 puncte
Turn = 5 puncte
Dama = 9 puncte.
Valoarea "calitativa" a piesei este data de tabele de estimare ce vor fi prezentate ulterior. Alegerea tabelei de estimare potrivite se face in functie de tipul piesei (fiecare piesa are tabela ei), de modul de agresivitate ales de catre utilizator, de rocada efectuata, si de culoarea cu care joaca calculatorul. Astfel daca avem spre exemplu situatia urmatoare: utilizatorul a ales modul agresiv pentru inteligenta artificiala, utilizatorul a efectuat rocada mare si calculatorul joaca cu negrele. Cand gasim o piesa neagra, spre exemplu calul negru, adaugam valoarea aflata in tabela de estimare destinata cailor negrii in modul agresiv intitulata knightvaluenARMA (nARMA venind de la negru agresiv rocada mare). Cand gasim o piesa alba adaugam in schimb valoarea aflata in tabele defensive pentru a putea calcula cat mai realistic sansele de reusita ale atacului.
De precizat este faptul ca spre deosebire de anumite bonusuri negative care penalizeaza o proasta pozitionare a unei piese, toate valorile sunt pozitive. Coeficientul este calculat in felul urmator: valoarea lui initiala este 0 la care se aduna valorile cantitative si calitative ale pieselor albe si se scad valorile calitative si cantitative ale pieselor negre. Astfel se poate obtine un coeficient pozitiv sau negativ in functie de superioritatea pieselor albe sau a pieselor negre.
Clasa contine nu mai putin de 60 de tabele de estimare. De ex.:
private int[,] knightvaluea =,
,
,
,
,
,
,
,
};
Dupa cum se poate observa tabelele de estimare sunt bidimesionale de dimesiune 8x8. Ele simuleaza o tabla de sah cu deosebirea ca pe fiecare camp in loc de piese se afla bonusuri. Aceste bonusuri se acorda piesei ce se afla pe campul respectiv. Cand functia EstimeazaTabla() gaseste o piesa pe tabla interogheaza aceste tabele introducand coordonatele ce desemneaza campul pe care se afla piesa pe tabla. Spre exemplu utilizatorul se afla in modul normal de agresivitate si piesa in cauza este un cal alb aflat la e4 (coloana 5 randul 5 in acceptia tabelei) interogarea are ca rezultat valoarea 4 care se va aduna la valoarea cantitativa 3 specifica calului. Tabela folosita este chiar cea data ca si exemplu.
Cele 60 de tabele se impart in tabele destinate modului normal de agresivitate, modului agresiv si modului defensiv. Cele destinate modului normal sunt doar 12, o tabela pentru fiecare piesa tinundu-se cont de culoarea ei. Tabele destinate modului agresiv sunt 24 la numar impartite in doua grupe a cate 12. 12 pentru destinate rocadei mari, 12 destinate rocadei mici. Din nou fiecare piesa are tabela ei tinundu-se cont de culoare. Tabele destinate modului defensiv sunt tot 24 la numar impartite in doua grupe a cate 12. 12 pentru destinate rocadei mari, 12 destinate rocadei mici. Din nou fiecare piesa are tabela ei tinundu-se cont de culoare.
Justificarea creeari de tabele speciale pentru fiecare piesa in parte o da mobilitatea diferita a fiecarei piese. Un nebun este mai bun pe diagonale, in special pe marea diagonala unde este deobicei fianchetat. Calul de exemplu este o piesa care se spune ca "iubeste" centrul tablei datorita mobilitatii sporite de o pozitionare centrala.
Augustina3Jocul in retea
Jocul de sah ChessTer se poate juca si in retea. Pentru a se putea juca in retea el trebuie sa se conecteze la un server special creat pentru a gestiona conexiunea dintre doua instante ale jocului care vor sa comunice intre ele. Astfel am creat un server concurent bazat pe socluri in Microsoft Visual Studio 2008 ca si Windows Forms Application in limbajul orienatat pe obiecte C#. In continuare vom descrie serverul de sah si protocolul de comunicare intre server si client.
Augustina3.1 Serverul de sah
Serverul de sah are doar doua clase, programul efectiv si o clasa in care se tine evidenta utilizatorilor. Singurele functii la care are acces utilizitorul sunt cele de porinire si oprire a serverului. In interfata grafica pe langa butoanele de oprire si pornire mai sunt doua textboxuri pentru afisarea numarului de clienti si a mesajelor schimbate intre client si server din motive mai de graba de depanare. Pe interfata mai este afisat IP-ul serverului si portul pe care este inregistrat.
1. Clasa Users
Aceasta clasa a fost creata special pentru memorarea datelor fiecarui client in parte. Varibilele in care sunt stocate aceste date sunt:
public Socket socluclient = null;
public string nume = null;
public string oponent = null;
public int client_count = 0;
a.) Socluclient memoreaza scolul clientului conectat la server
b.) Client_count reprezinta numarul unic al clientului, el se utilizeaza cand dorim sa ii transmitem un mesaj
c.) Nume reprezinta numele clientului conectat
d.) Oponent reprezinta numele adversarului cu care doreste sa joace clientul respectiv
2. Clasa Form
Aceasta clasa contine toata logica programului. Contine functiile de pornire si oprire a serverului, functiile destinate conectarii clientilor si a primirii datelor de la ei precum si protocolul de comunicare. Functiile si variabilele cele mai importante sunt:
Users[] utilizatori = new Users[2000];
private int m_clientCount = 0;
private String GetIP();
private void START_Click(object sender, EventArgs e);
private void OnClientConnect(IAsyncResult asyn);
private void WaitForData(System.Net.Sockets.Socket soc, int clientNumber);
private void OnDataReceived(IAsyncResult asyn);
private void SendMsgToClient(string msg, int clientNumber);
private void remove_client(int clientnumber);
private void AppendToRichEditControl(string msg);
private void OnUpdateRichEdit(string msg);
private void UpdateClientListControl();
private void CloseSockets();
private void UpdateClientList();
private void STOP_Click(object sender, EventArgs e);
private void CLOSE_Click(object sender, EventArgs e);
a.) Utilizatori este sirul de clase Users folosit pentru memorarea clientilor conectati la server si a datelor necesare pentru jocul in retea ce caracterizeaza clientul respectiv.
b.) Variabila m_clinetCount tine evidenta numarul clientilor conectati, el functioneaza precum un index pentru sirul utilizatori.
c.) Functia GetIP() returneaza IP-ul calculatorului gazda necesar pentru creearea serverului. Acest IP se obtine utilizand functia GetHostByName() al clasei Dns. Secventa de cod pentru obtinerea Ip-ului este urmatoarea:
String strHostName = Dns.GetHostName();
IPHostEntry iphostentry = Dns.GetHostByName(strHostName);
foreach (IPAddress ipaddress in iphostentry.AddressList)
d.) Functia START_Click(object sender, EventArgs e) "porneste" serverul. Ea creaza soclul de "ascultare" al serverului, leaga soclul de adresa IP locala si apeleaza functis Listen() care semnifica inceperea ascultarii si porneste functia responsabila cu tratarea noilor conexiuni OnClientConnect(). Secventa de cod este umratoarea:
int port = System.Convert.ToInt32( );
// Creearea soclului de ascultare
m_mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);
// Legarea la adresa de IP locala
m_mainSocket.Bind(ipLocal);
// Incepe ascultarea
m_mainSocket.Listen(4);
// Creeaza functia de tratare a conexiunilor clientilor
m_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
e.) Functia OnClientConnect(IAsyncResult asyn) trateaza conectarea unui client nou la server. Ea este responsabila cu salvarea datelor clientului si mai apoi incrementarea variabilei m_clientCount:
if (m_clientCount < 2000)
// Invrementeaza variabila thread-safe
Interlocked.Increment(ref m_clientCount);
Urmeaza trimiterea unui mesaj de bun venit clientului si de updatarea listei de clienti:
string msg = 'Welcome client ' + m_clientCount + 'n';
SendMsgToClient(msg, m_clientCount);
UpdateClientListControl();
Acesti pasi fiind indepliniti urmeaza pornirea functiei de asteptare de mesaje de la clientul respectiv si de pornire din nou a acceptarii de conexiuni:
WaitForData(workerSocket, m_clientCount);
m_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
f.) Fiecare client conectat are desemnata o functie care astepta sa primeasca date de la ei: WaitForData(System.Net.Sockets.Socket soc, int clientNumber). In momentul in care sosesc date de la client, aceasta functie porneste functia OnDataReceived() responsabila cu prelucrarea mesajului:
pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
g.) Functia OnDataReceived(IAsyncResult asyn) este responsabila cu prelucrarea mesajelor primite de la client. Ea contine protocolul de comunicare ce va prezentat in paragraful urmator. Secventa de cod de prelucrare a mesajului si de afisare in textboxul aferent este urmatoarea:
int iRx = socketData.m_currentSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(socketData.dataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
string msg = + socketData.m_clientNumber + ;
AppendToRichEditControl(msg + szData);
Urmeaza protocolul de comunicare ce va discutat ulterior si apelarea functiei WaitForData pentru a astepta noi mesaje de la client:
WaitForData(socketData.m_currentSocket, socketData.m_clientNumber);
h.) Functia SendMsgToClient(string msg, int clientNumber) este apelata cand dorim sa transmitem un mesaj unui anumit client. Primul argument de intrare al functiei reprezinta mesajul iar al doilea argument reprezinta numar unic de client dupa care identificam clientul si astfel vom sti pe ce soclu sa trimitem mesajul. Sintaxa trimiterii mesajului este urmatoarea:
byte[] byData = System.Text.Encoding.ASCII.GetBytes(msg);
utilizatori[clientNumber].socluclient.Send(byData);
i.) Functia remove_client(int clientnumber) este responsabila cu stergerea din sirul de clienti a unui client care sa deconectat si trimite un mesaj adversarului, in caz ca avea un adversar, ca acesta sa deconectat.
j.) Functia AppendToRichEditControl(string msg) responsabil cu adaugarea mesajelor care circula intre server si clienti in textbox-ul aferent mesajelor.
k.) Functia OnUpdateRichEdit(string msg) este apelata de functia anteriora pentru a adauga mesajele thread-safe.
l.) Functia UpdateClientListControl() apeleaza functia UpdateClientList() intr-o maniera thread-safe pentru a updata lista clientilor afisata in textbox-ul aferent.
m.) Functia UpdateClientList() este apelata de catre functia anterioa pentru a modfica lisat clientilor afisata in textbox-ul aferent.
n.) Functia CloseSockets() este responsabila cu inchiderea soclurilor deschise in timpul rularii serverului. Aceasta functie este apelata de catre functiile STOP_Click si CLOSE_Click.
o.) Functia STOP_Click(object sender, EventArgs e) are rolul de a inchide serverul, ea apeleaza functia CloseSockets() si reseteaza valoarea variabilei m_clientCount.
p.) Functia CLOSE_Click(object sender, EventArgs e) inchide aplicatia nu inainte sa apeleze functia CloseSockets().
Augustina3.2 Protoculul de comunicare
Protocolul de comunicare cuprinde mesajele schimbate intre client si server si actiunile ce au loc cand apar aceste mesaje. Cuvintele cheie sunt : 'NUME', 'NUMELUAT', 'OPONENT', 'WAITING FOR OPONENT!', "CULOARERETEA", 'STARTJOC', 'ALTJOC', 'NO_OPONNENT'.
Cand serverul primeste un mesaj care incepe cu NUME el parcurge lista de clienti inregistrati pentru a verifica daca numele este deja inregistrat. Daca el este deja luat trimite mesajul NUMELUAT clientului. Astfel clientul stie ca trebuie sa isi schimbe numele. Daca numele este unic atunci el este inregistrat in variabila nume a clasei Users atribuite clientului respectiv.
if (szData.Substring(0, 4) == 'NUME')
}
if (numeexistent == 0)
}
else numeexistent = 0;
Cand sevreul primeste un mesaj care incepe cu OPONENT el salveaza numele oponentului in variabila oponent a clasei Users atribuite clientului respectiv. Dupa aceea parcurge lista sa vada daca exista deja un utilizator cu acest nume inregistrat. Daca gaseste un utilizator cu numele respectiv si acele are setat ca si nume de adversar numele clientului care a transmis mesajul, automat se trimit mesajele CULOARERETEA 0/1 pentru determinarea culorii cu care joaca fiecare si STARTJOC ambilor jucatori. Astfel se incepe partida de sah dintre cei doi. In cazul in care nu gaseste nici un utilizator care sa indeplineasca conditiile de mai sus el trimite mesajul "WAITING FOR OPONNENT!". Astfel clientul stie ca astepta conectarea adversarului.
else if (szData.Substring(0, 7) == 'OPONENT')
else break;
}
}
for (j = 0; j < m_clientCount; j++)
else break;
}
}
if (hailajoc == 0)
SendMsgToClient('WAITING FOR OPONENT!', utilizatori[i].client_count);
else
else
hailajoc = 0;
}
}
Cand serverul primeste mesajul ALTJOC inseamna ca jocul sa terminat si ca serverul trebuie sa trimita din nou culorile cu care vor juca data viitoare prin mesajul CULOARERETEA si mesajul de STARTJOC. In cazul in care unul dintre ei sa deconectat celalat primeste mesajul NO_OPONNENT.
else if (szData.Substring(0, 6) == 'ALTJOC')
}
else break;
}
}
if (aux == 0)
else aux = 0;
In cazul in care mesajul nu se potriveste cu mesajele anterioare inseamna ca este vorba de o mutare. Astfel serverul cauta adversarul celui care a trimis mesajul si ii trimite mesajul pentru a efectua mutarea. In cazul in care unul dintre ei se deconecteaza i se trimite celuilalt mesajul ca adversarul lui sa deconectat.
else
else break;
break;
}
}
if (aux == 0)
else aux = 0;
Augustina4 Web Part-ul
Web Part-ul este cadrul cu ajutorul caruia putem integra o aplicatie complexa precum jocul de sah ChessTer in Share Point. El este creat in ca si ti de proiect SharePoint Web Part in Microsoft Visual Studio 2008 in limbajul C#.
Primul pas in creearea si integrarea unui proiect complex in Share Point este creearea dll ce urmeaza sa fie integrat.
Pasul al doilea este creearea Web Partului. Astfel se foloseste template-ul SharePoint Web Part din Visula Studio. Am redenumit clasa WepPart1 in PlayChessTer. In aceasta clasa, singura de altfel, am modificat tipul de clasa extinsa de catre PlayChessTer:
public class PlayChessTer : Microsoft.SharePoint.WebPartPages.WebPart;
Si am introdus urmatoarea functie:
protected override void RenderWebPart(HtmlTextWriter output)
Prin varibila strToReplace inseram in Share Point cod html in care dll-ul nostru este inclus ca si obiect. Inaintea rularii proiectului trebuie sa intram in propiretatile proiectului in submeniul Debug. In acel submeniu la optiunea "Start browsre with URL" trebuie sa introducem adresa la care vrem sa includem acest Web Part. In cazul nostru adresa este:
https://chess:7777/
Acum putem rula aplicatia noastra, deoarece la rulare ei Visual Studio parcurge automat pasii necesari pentru includerea aplicatiei ca si Web Part pe un site Share Point. In cazul in care modificam dll-ul trebuie sa rulam din nou aplicatia pentru a efctutua schimbarile necesare.
Ultimul pas este creearea unui director in directorul wpresources cu urmatoarea denumire <WebPartAssemblyName>< WebPartAssemblyVersion> __<PublicKeyToken>bin In cazul nostru WebPartAssemblyName este numele aplicatiei si anume ChessAplication. Calea unde vine creat acest director este:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensionswpresources
Iar directorul ce trebuie creat la aceasta cale este:
ChessAplication1.0.0.0__9f4da00116c38ec5bin
In acest director vom copia dll-ul aplicatiei ce vrem sa ruleze ca si Web Part in Share Point. In cazul creeari unei noi versiuni a jocului vom copia noul dll peste cel vechi din acest director dupa care vom rula din nou aplicatia.
Acum nu ne mai ramane de facut decat sa deschidem pagina de Share Point respectiva in browserul nostru si sa adaugam Web Part-ul pe site. Rezultatul final al proiectului complet poate fi vazut in figura AugustinaAugustina1..
Fig. AugustinaAugustina1
Copyright © 2024 - Toate drepturile rezervate