Tema urmărește implementarea unei variante simplificate a jocului Flappy Bird, utilizând biblioteca universe a limbajului Racket. Aceasta permite realizarea de aplicații vizuale și interactive, implementate în stil funcțional.
Jocul presupune ghidarea unei păsări, reprezentate de dreptunghiul galben, pentru a putea trece prin fantele dintre obstacolele verzi, fără a se lovi de acestea sau de solul maro:
Gravitația acționează constant asupra păsării, iar utilizatorul îi poate imprima un impuls în sus, prin apăsarea unei taste. De asemenea, pasărea poate dobândi anumite abilități pe parcursul jocului, în momentul intersectării cu anumite obiecte.
Deși veți avea libertate în alegerea modalității de reprezentare a entităților, putând recurge, spre exemplu, la listele standard, este puternic încurajată utilizarea structurilor din Racket. Acestea sunt similare structurilor din alte limbaje (vezi struct
din C), și simplifică destul de mult manipularea construcțiilor complexe.
Găsiți un tutorial pentru utilizarea structurilor la sfârșitul acestei secțiuni.
Biblioteca universe permite realizarea de aplicații vizuale și interactive, implementate în stil funcțional. Astfel, centrală este noțiunea de stare a aplicației, care poate fi desenată în forma unei imagini, sau transformată în starea următoare, în urma trecerii timpului sau a acțiunilor utilizatorului. Aceste prelucrări sunt surprinse, așa cum era de aștepat, prin funcții, care iau starea curentă ca parametru, și calculează rezultatele necesare.
Aveți la dispoziție un tutorial complet, în forma unei mici aplicații, care utilizează atât structuri Racket, cât și biblioteca universe. Parcurgeți-l cu atenție și observați valorile diverselor expresii din program.
Pornind la scheletul din secțiunea Resurse, implementați funcțiile din fișierele main.rkt
(cerințele de bază) și abilities.rkt
(pentru bonus), respectând indicațiile de mai jos și comentariile din fișiere, în ordinea dată de secvențele TODO
(TODO 1
, TODO 2
etc.).
Gravitația acționează constant asupra păsării. Cu alte cuvinte, la fiecare cadru care trece, vitezei pe y a păsării i se adaugă valoarea gravității.
Atunci când vrem să imprimăm un impuls păsării, viteza pe y a păsării va fi înlocuită complet cu o valoare dată.
Fiecare obiect din cadrul jocului (de la Flappy Bird, la pereți, abilități, chiar și pământ) are 2 coordonate - x și y - care îi descriu poziția pe ecran.
Evident, acestea depind de sistemul de referință ales, iar pentru cazul nostru, acesta este poziționat și orientat ca în următoarea imagine:
Astfel, creșterea valorii coordantei x va duce la apropierea de marginea dreaptă a ecranului, iar a coordonatei y-la apropierea de marginea de jos a ecranului.
Punctul în care ambele coordonate au valoarea 0 este colțul din stânga-sus al ecranului.
Pentru a simplifica reprezentarea obiectelor în starea jocului, vom salva poziția colțului din stânga sus a fiecărui obiect. După cum știți, când suprapunem o imagine peste o alta, coordonatele pe care le specificăm drept poziția imaginii reprezintă centrul acesteia. Pentru a face translația dintre reprezentarea noastră și imaginea actuală, va trebui să translatăm coordonatele salvate în jos și la dreapta (adică crescător atât pe axa x, cât și pe y), cu jumătate din înălțimea, respectiv lățimea obiectului. Întrucât fiecare pipe este alcătuit din două componente, cea superioară și cea inferioară, fiind despărțite de acea fanta, este suficient să salvăm coordonatele fantei, urmând ulterior să adăugăm la afișare și la verificarea coliziunilor cele două componente.
Starea descrisă mai sus poate fi reprezentată oricum, însă este necesar să implementați gettere pentru fiecare element descris în stare, în vederea testării automate.
Exemplu:
(get-bird state) ; -> va întoarce o reprezentare internă aleasă de voi (get-bird-y (get-bird state)) ; -> va întoarce un număr reprezentând poziția păsării
Oriunde folosiți funcția random trebuie sa includeți (require „random.rkt“) dacă nu este inclus deja, pentru a putea testa codul în checker.
Biblioteca de imagini din Racket folosește o structura pentru a reprezenta punctele numită `posn`. Aceasta contine doua campuri: x și y.
> (require lang/posn) > (make-posn 2 3) (posn 2 3) > (posn-x (make-posn 2 3)) 2 > (posn-y (make-posn 2 3)) 3
Această structură va apărea în schelet și în documentația pentru imagini, este recomandat sa o folosiți acolo unde este posibil.
Fișierul constants.rkt conține majoritatea constantelor globale folosite în construcția jocului. Mai jos se află o imagine care acopera ce reprezintă unele dintre acestea.
Observăm 3 X-uri roz, în colțurile din stânga sus ale pământului și păsări, și în centrul textului scorului. Punctul (bird-x
, bird-initial-y
) reprezintă coordonatele colțului din stânga sus al păsării, punctul (0, ground-y
), coordonatele colțului din stânga sus ale pământului, iar (text-x
, text-y
), coordonatele *centrului* textului. Nu va fi nevoie să translatati imaginea textului.
De asemenea, avem o constantă pentru înălțimea unui pipe, pipe-height
(aceeași atât pentru pipe-urile superioare, cât și pentru cele inferioare, chiar dacă depășesc dimensiunile scenei), înălțimea pământului, ground-height
, o constantă pentru gravitație, gravity
, una pentru momentum, cu acest nume, și una pentru viteza obiectelor, initial-scroll-speed
.
În cadrul implementării temei vă va fi utilă o funcție care să verifice dacă două dreptunghiuri se intersectează. O idee de implementare pleacă de la următoarea simulare. Oferim o implementare a acestei idei in functia check-collision-rectangles
, care primeste ca parametri cele 4 colturi ale dreptunghiurilor.
Pentru bonus ne dorim să implementăm un sistem generic de a introduce diverse abilități în joc. O abilitate va trebui să conțină:
La fiecare pas jocul trebuie sa aplica funcția next pentru fiecare abilitate activă și astfel să obțină variables. O abilitate este activă dacă a avut in trecut o coliziune cu pasărea și încă nu a expirat.
Abilitățile își vor produce efectul începând cu următorul cadru după ce au devenit active.
Pentru a poziționa abilitățile pe scenă puteți folosi funcția (random-position POSITION_RANGE) care întoarce centrul abilității.
let
. Un nivel foarte redus sau inexistent de utilizare a funcționalelor va conduce la o depunctare de 10p din 100.