Cum sa faci un joc de X si 0
Crezi ca e greu sa faci un joc de X si 0? Dupa ce am facut impreuna jocul de labirint, cel mai probabil lucrurile deja nu ti se mai par de domeniul science-fiction. Totusi, poate ca inca e prea devreme sa iti iei singur zborul pe acest taram inca destul de incetosat, asa ca hai sa dam impreuna ceata la o parte si sa vedem cat de simplu e sa programezi un joc de X si 0.
Daca ai citit articolul trecut, atunci stii ca pentru a face un joc iti trebuiesc doar doi pasi si trei miscari. Ai vazut ca abordarea asta functioneaza foarte bine pentru jocul de labirint. Vei vedea in articolul acesta ca ea functioneaza la fel de bine si pentru X si 0.
Bun, hai sa ne apucam de treaba!
Proiectarea jocului de X si 0
Care e pasul 1? Proiectarea jocului. Adica acea parte in care practic realizezi jocul fara sa ai nevoie de programare. Este pasul in care faci jocul pe hartie.
Si cum acesta este pasul esential, l-am impartit la randul lui in 3 miscari: (1) interfata, (2) variabilele si (3) schita.
(1) Interfata jocului
Asta e partea aceea foarte frumoasa in care te joci cu creionul pe hartie si iti transformi gandurile in imagini. Avand in vedere limitarile destul de serioase ale ecranului de care dispunem (si anume doar 10×10 puncte ce pot avea doar 8 culori distincte, inclusiv alb), interfata proiectata de mine arata ca cea din prima imagine din acest articol.
Am ales ca bordura patratelor sa fie desenata cu gri, X-ul cu albastru si 0-ul cu verde. Si ce-i cu punctul rosu? El indica pozitia cursorului pe tabla.
Cu ajutorul tastelor A, D, S, si W (stanga, dreapta, jos si sus) voi putea deplasa cursorul pe oricare din cele 9 pozitii de pe tabla. Iar cu tasta P voi putea plasa o piesa (de culoarea jucatorului curent) pe tabla la pozitia curenta a cursorului. (Bineinteles, dupa ce voi verifica in prealabil ca respectiva pozitie de pe tabla sa nu fie deja ocupata de o piesa.)
Dupa oricare plasare de piesa pe tabla voi verifica daca respectivul jucator a facut trei piese in linie (caz in care a castigat jocul), sau daca nu se mai poate face nicio mutare (caz in care jocul s-a incheiat cu remiza).
In momentul incheierii jocului, cursorul nu va mai aparea pe ecran si programul va inceta sa mai raspunda la apasarea tastelor, iar liniile tablei se vor colora cu negru in cazul remizei, cu albastru in cazul victoriei lui X, sau cu verde in cazul victoriei lui 0.
(2) Variabilele jocului
Super. Am stabilit clar cum vreau sa arate si ce vreau sa faca jocul. Acum trebuie sa vad ce obiecte si ce actiuni pot identifica in descrierea jocului facuta pana aici.
Mai intai obiectele. Daca sparg jocul (avand in minte interfata prezentata in sectiunea anterioara) in bucatele, atunci obtin urmatoarele obiecte:
– tabla de joc (goala) <— liniile ce delimiteaza patratele de pe tabla.
– piesele de pe tabla <— 3×3 piese, fiecare dintre ele putand avea una dintre valorile: gol, X, sau 0.
– cursorul <— coordonatele lui pe ecran.
– jucatorul curent <— X, sau 0.
– starea jocului <— in desfasurare, terminat egalitate, terminat victorie X, sau terminat victorie 0.
Iar ca actiuni pe care jocul trebuie sa le indeplineasca, ar fi urmatoarele:
– porneste jocul <— la initializare.
– muta cursoul <— cu tastele A, D, S, W.
– pune piesa <— cu tasta P.
Stiind obiectele din joc, e usor acuma sa vad ce variabile mi-ar trebui pentru a putea obtine un model abstract al jocului. Adica pentru a putea obtine o memorare a elementelor esentiale ale jocului, fara a pastra toate detaliile legate de interfata.
Imi va trebui, deci, o variabila piesa in care sa memorez piesele de pe tabla. Si cum am 3×3 piese, cel mai indicat e sa folosesc o matrice.
Pentru a memora jucatorul care este la mutare imi trebuie o variabila juc_crt.
Pentru a memora pozitia curenta a cursorului pe tabla de joc pot folosi doua variabile, cx si cy.
Iar pentru a memora starea curenta a jocului pot folosi o alta variabila, stare_joc.
Acestea sunt, asadar, variabilele necesare pentru a memora intreaga stare a jocului la un moment dat. Pentru a putea face programul, insa, e posibil sa mai am nevoie si de alte variabile in afara de acestea, dar asta voi vedea pe parcurs. In caz ca va aparea necesitatea unei alte variabile, voi reveni si o voi adauga aici.
Actiunile jocului, pe de alta parte, imi vor fi necesare in sectiunea urmatoare.
(3) Schita programului
Tinand cont de cunostintele de programare deprinse pana aici, precum si de actiunile jocului identificate in sectiunea anterioara, programul va avea urmatoarea structura:
– definire variabile (globale)
– definire functii
– programul propriu-zis.
La partea de definire functii as pune in primul rand doua functii: una care sa deseneze interfata grafica a jocului pe ecran (si pe care o pot denumi DeseneazaEcranJoc), si una in care sa specific reactiile jocului la apasarea tastelor (si pe care o pot denumi FunctieTaste).
Iar in partea de program propriu-zis va trebui sa am o parte de initializare a variabilelor (in care sa le dau acestora valori adecvate pornirii jocului), urmata de apelul DeseneazaEcranJoc(), si apoi de apelul AscultaTaste(FunctieTaste).
Mai ramane acuma sa detaliez (tot la nivelul de schita, bineinteles) cele doua functii de care am pomenit.
Functia DeseneazaEcranJoc. Aici voi avea grija sa desenez intreaga interfata a jocului, tinand cont de starea curenta a jocului, asa cum e ea memorata in variabilele piesa, juc_crt, cx, cy si stare_joc. Functia asta va cuprinde:
– desenarea liniilor tablei.
– desenarea pieselor pe tabla.
– desenarea cursorului.
Si functia FunctieTaste. Ea va trebui sa cuprinda urmatoarele:
– deplasarea cursorului la apasarea tastelor A, D, S, W.
– plasarea unei piese pe tabla la apasarea tastei P.
– redesenarea interfetei jocului.
– incetarea ascultarii tastelor in cazul incheierii jocului (prin remiza sau victorie X sau victorie 0).
Programarea jocului de X si 0
Deja ajuns in acest punct am un plan suficient de bun incat sa pot trece fara teama la pasul al doilea, si anume programarea propriu-zisa. Bineinteles ca fiecare dintre punctele mentionate mai pot fi detaliate. (Ba chiar este indicat s-o faci, la inceput. Nu are sens sa te arunci direct in scrierea de cod sursa, caci nu acolo vei gasi programarea adevarata. Programarea adevarata nu se produce sub degetele tale atunci cand scrii cod sursa de programe, ci se produce in creierul tau, atunci cand proiectezi si rulezi in minte secvente de programe.)
Eu nu voi detalia aici pasii mentionati, ci te voi lasa sa descoperi singur ce solutii am ales pentru implementarea lor.
O singura remarca mai fac: Vei observa, desigur, ca pe langa variabilele mentionate de mine am folosit in program si variabila nr_mutare. Nu era absolut necesara (caci numarul mutarii curente il puteam obtine parcurgand matricea piesa si numarand cate elemente egale cu valorile asociate pentru X sau pentru 0 gasesc acolo). Insa prin folosirea ei am simplificat mult verificarea situatiei in care jocul se incheie prin remiza (asa cum vei putea vedea spre finalul functiei FunctieTaste). In mod similar, vei observa ca pentru a simplifica functiile DeseneazaEcranJoc si FunctieTaste am mai definit alte functii. Cu ajutorul acestor noi functii definite am crescut mult claritatea celor doua functii mari din program. (Iti reamintesc faptul ca o functie trebuie definita mai inainte de locul din program in care o apelezi (pentru prima oara), la fel cum o variabila trebuie declarata mai inainte de locul din program in care o folosesti pentru prima oara.) Vei putea remarca, de asemenea, ca in cazul unora dintre functii am avut nevoie temporar de niste variabile pe care le-am definit acolo, ca variabile locale acelor functii.
Ia uite si codul sursa complet al programului:
// Joc de X si 0 (Tic Tac Toe Game)
// —> Definesc variabilele jocului:
var piesa; // {0: gol, 1: X, 2: 0}
var juc_crt; // {1: X, 2: 0}
var cx; // {1, 2, 3, …, 10}
var cy; // {1, 2, 3, …, 10}
var stare_joc; // {1: in desfasurare, 2: terminat egal, 3: terminat victorie X, 4: terminat victorie 0}
var nr_mutare; // {1, 2, 3, …, 10}
// —> Definesc functiile necesare:
function AprindeNrCul(x, y, nc)
{
if (nc == 0)
Aprinde(x, y, ALB);
else if (nc == 1)
Aprinde(x, y, NEGRU);
else if (nc == 2)
Aprinde(x, y, ROSU);
else if (nc == 3)
Aprinde(x, y, VERDE);
else if (nc == 4)
Aprinde(x, y, ALBASTRU);
else if (nc == 5)
Aprinde(x, y, GRI);
else if (nc == 6)
Aprinde(x, y, GALBEN);
else if (nc == 7)
Aprinde(x, y, TURCOAZ);
}
function DeseneazaEcranJoc()
{
// Deseneaza tabla
var nr_culoare = 0;
if (stare_joc == 1)
nr_culoare = 5;
else if (stare_joc == 2)
nr_culoare = 1;
else if (stare_joc == 3)
nr_culoare = 4;
else if (stare_joc == 4)
nr_culoare = 3;
var i;
i = 1;
while (i<=10)
{
// liniile orizontale
AprindeNrCul(i, 1, nr_culoare);
AprindeNrCul(i, 4, nr_culoare);
AprindeNrCul(i, 7, nr_culoare);
AprindeNrCul(i, 10, nr_culoare);
// liniile verticale
AprindeNrCul(1, i, nr_culoare);
AprindeNrCul(4, i, nr_culoare);
AprindeNrCul(7, i, nr_culoare);
AprindeNrCul(10, i, nr_culoare);
i = i+1;
}
// Deseneaza piesele
var l;
var c;
l = 0;
while (l < 3)
{
c = 0;
while (c < 3)
{
nr_culoare = 0;
if (piesa[l][c] == 0)
nr_culoare = 0;
else if (piesa[l][c] == 1)
nr_culoare = 4;
else if (piesa[l][c] == 2)
nr_culoare = 3;
AprindeNrCul(2+c*3, 9-l*3, nr_culoare);
AprindeNrCul(2+c*3+1, 9-l*3, nr_culoare);
AprindeNrCul(2+c*3, 9-l*3-1, nr_culoare);
AprindeNrCul(2+c*3+1, 9-l*3-1, nr_culoare);
c = c+1;
}
l = l+1;
}
// Deseneaza cursorul
if (stare_joc == 1)
{
Aprinde(cx, cy, ROSU);
}
}
function CalcDiv(A, B)
{
var rest = A % B;
var cat = (A-rest)/B;
return cat;
}
function PunePiesa()
{
var c = CalcDiv(cx-2, 3);
var l = CalcDiv(9-cy, 3);
if (piesa[l][c] == 0)
{
piesa[l][c] = juc_crt;
return 1;
}
else
{
return 0;
}
}
function VerificaVictorie()
{
if ( ( (piesa[0][0]==1)&&(piesa[0][1]==1)&&(piesa[0][2]==1) ) ||
( (piesa[1][0]==1)&&(piesa[1][1]==1)&&(piesa[1][2]==1) ) ||
( (piesa[2][0]==1)&&(piesa[2][1]==1)&&(piesa[2][2]==1) ) ||
( (piesa[0][0]==1)&&(piesa[1][0]==1)&&(piesa[2][0]==1) ) ||
( (piesa[0][1]==1)&&(piesa[1][1]==1)&&(piesa[2][1]==1) ) ||
( (piesa[0][2]==1)&&(piesa[1][2]==1)&&(piesa[2][2]==1) ) ||
( (piesa[0][0]==1)&&(piesa[1][1]==1)&&(piesa[2][2]==1) ) ||
( (piesa[2][0]==1)&&(piesa[1][1]==1)&&(piesa[0][2]==1) ) )
{
stare_joc = 3;
}
if ( ( (piesa[0][0]==2)&&(piesa[0][1]==2)&&(piesa[0][2]==2) ) ||
( (piesa[1][0]==2)&&(piesa[1][1]==2)&&(piesa[1][2]==2) ) ||
( (piesa[2][0]==2)&&(piesa[2][1]==2)&&(piesa[2][2]==2) ) ||
( (piesa[0][0]==2)&&(piesa[1][0]==2)&&(piesa[2][0]==2) ) ||
( (piesa[0][1]==2)&&(piesa[1][1]==2)&&(piesa[2][1]==2) ) ||
( (piesa[0][2]==2)&&(piesa[1][2]==2)&&(piesa[2][2]==2) ) ||
( (piesa[0][0]==2)&&(piesa[1][1]==2)&&(piesa[2][2]==2) ) ||
( (piesa[2][0]==2)&&(piesa[1][1]==2)&&(piesa[0][2]==2) ) )
{
stare_joc = 4;
}
}
function FunctieTaste(ev)
{
var tasta = TastaApasata(ev);
// mutare cursor
if ( (tasta == 'a') && (cx >= 5) )
cx = cx-3;
if ( (tasta == 'd') && (cx <= 5) )
cx = cx+3;
if ( (tasta == 's') && (cy >= 6) )
cy = cy-3;
if ( (tasta == 'w') && (cy <= 6) )
cy = cy+3;
// plasare piesa
if (tasta == 'p')
{
var rez = PunePiesa();
// efectueaza mutare
if (rez == 1)
{
juc_crt = 3-juc_crt;
nr_mutare = nr_mutare+1;
VerificaVictorie();
// daca n-a fost victorie, verifica remiza
if ( (stare_joc < 3) && (nr_mutare == 10) )
{
stare_joc = 2;
}
}
}
// reimprospatare ecran
DeseneazaEcranJoc();
if (stare_joc != 1)
{
AscultaTaste();
}
}
// —> Programul propriu-zis:
// Initializez variabilele
piesa = Matrice(3, 3);
piesa[0][0] = 0; piesa[0][1] = 0; piesa[0][2] = 0;
piesa[1][0] = 0; piesa[1][1] = 0; piesa[1][2] = 0;
piesa[2][0] = 0; piesa[2][1] = 0; piesa[2][2] = 0;
juc_crt = 1;
cx = 2; cy = 9;
stare_joc = 1;
nr_mutare = 1;
// Desenez ecranul initial
DeseneazaEcranJoc();
// Asculta apasarea tastelor
AscultaTaste(FunctieTaste);
Il poti testa cu usurinta in simulatorul de mai jos.
Ce ar fi sa iti aduci aportul la acest joc? De exemplu, ti-ar fi dificil sa modifici culorile elementelor? Sau forma tablei?
Iar daca cerintele acestea sunt prea simple, atunci gandeste-te, te rog, cum ai face ca in loc de un punct rosu cursorul sa fie un patrat (gol) rosu care incadreaza pe tabla pozitia in care se gaseste.
Prea dificil pentru tine? Nu cred.
In cartea Abecedar de programare mi-am propus ca programarea sa ajunga un subiect facil pentru oricine. Provocarea la care am incercat sa raspund prin ea a fost aceea de a explica programarea calculatoarelor pe intelesul bunicii. Te astept sa-mi spui daca am reusit s-o explic si pe intelesul tau.