Prolog: Bloxorz

Obiective

  • Aplicarea programării logice în limbajul Prolog.
  • Utilizarea mecanismelor de găsire automată a soluțiilor pentru un scop.

Observații preliminare

Tema propune implementarea câtorva elemente din mecanica jocului Bloxorz. Puteți juca Bloxorz aici.

Tema este împărțită în 2 etape:

  • una pe care o veți rezolva după laboratorul 10, cu deadline în ziua laboratorului 11.
  • una pe care o veți rezolva după laboratorul 11, cu deadline în ziua laboratorului 12.

Deadline-ul depinde de semigrupa în care sunteți repartizați. Restanțierii care refac tema și nu refac laboratorul beneficiază de ultimul deadline, și anume în zilele de 24.05, respectiv 31.05.

Rezolvările tuturor etapelor pot fi trimise până în ziua laboratorului 12 (deadline hard pentru toate etapele). Orice exercițiu trimis după un deadline soft se punctează la jumătate. Cu alte cuvinte, nota finală pe etapă se calculează conform formulei: n = (n1 + n2) / 2 (n1 = nota obținută înainte de deadline; n2 = nota obținută după deadline). Când toate submisiile preced deadline-ul, nota pe ultima submisie constituie nota finală (întrucât n1 = n2).

În fiecare etapă, veți valorifica ce ați învățat în săptămâna anterioară și veți avea la dispoziție un schelet de cod, cu toate că rezolvarea se bazează în mare măsură pe etapele anterioare. Enunțul caută să ofere o imagine de ansamblu atât la nivel conceptual, cât și în privința aspectelor care se doresc implementate, în timp ce detaliile se găsesc direct în schelet.

Perspectivă generală

Fiecare nivel (stage) din Bloxorz conține o suprafață de joc formată din pătrățele (tiles) care pot fi normale sau au diverse proprietăți. Jucătorul controloează un bloc care trebuie dus prin mișcări sus / jos / dreapta / stânga până la trecerea printr-o pătrățică lipsă din suprafața de joc (o numim scop sau gaură). Puteți urmări rezolvarea nivelurilor de Bloxorz aici.

Aveți în directorul ref un fișier cu codurile pentru fiecare nivel (1-6) și cu imagini ale stării intițiale pentru fiecare nivel.

În această temă, trebuie să acoperiți următoarele puncte:

  • să dezvoltați o reprezentare proprie pentru o stare a jocului.
  • să implementați predicate care să modifice și să acceseze o stare a jocului.
  • să implementați mecanica jocului.
  • să folosiți Prolog pentru a rezolva primele câteva niveluri din joc.

Etapa 1

În etapa 1 ne interesează reprezentarea stării și implementarea mecanicii principale de mișcare a blocului (fără partea de switches).

În ambele etape veți lucra numai în fișierul blox.pl.

  • nu folosiți predicatele intern_* și tt*, nu aveți nevoie de ele.

Faceți o implementare cât mai generală, nu hardcodați lucruri. De principiu, fiecare predicat ar trebui să aibă 1-2-3 reguli. De exemplu, nu faceți reguli separate pentru cele 4 direcții, ci folosiți neighbors din util.pl.

Reprezentare

În prima parte trebuie să dezvoltați o reprezentare a stării jocului. Această reprezentare poate fi construită cum doriți. Ea trebuie să conțină informații despre:

  • toate pătrățelele din suprafața de joc
  • poziția blocului
  • în etapa 2, va trebui să conțină și informații despre starea pătrățelelor mobile (bridges) declanșate de switches.

Reprezentarea va fi construită și accesată numai prin intermediul predicatelor pe care le scrieți voi. Testerul va considera reprezentarea unei stări ca fiind opacă și nu va parsa reprezentarea în niciun fel. Puteți alege ce reprezentare doriți, folosind liste, tupluri, sau alți compuși, acestea putând fi și imbricate.

În testarea unui nivel, vor exista două faze:

  1. checkerul va apela predicatele empty_state și set_* pentru a construi reprezentarea unei stări S (care poate să corespundă unui nivel din joc sau nu);
  2. checkerul va verifica corectitudinea reprezentării prin apelul, pe starea S rezultată, a predicatelor get_*
  3. checkerul va verifica implementarea corectă a mecanicii jocului prin apelul predicatului move care să transforme o stare S într-o altă stare, rezultată în urma realizării unei acțiuni.

Important: nu citiți direct reprezentarea nivelurilor din levels.pl. Stările corespunzătoare fiecărui nivel vor fi construite pătrățică cu pătrățică de către checker prin apeluri ale predicatelor set_*.

Aveți de implementat următoarele predicate în Prolog, documentate și în sursă:

  • empty_state(S) – leagă S la o reprezentare care nu conține încă nicio informație.
  • predicatele set_*(S, Pos, SN) – leagă SN la o reprezentare care conține aceleași informații ca și S, la care se adaugă faptul că la poziția Pos se află:
    • set_tile – o pătrățică normală;
    • set_fragile – o pătrățică pe care blocul nu poate sta în picioare;
    • set_target – pătrățica lipsă unde trebuie ca blocul să ajungă în picioare ca să se termine nivelul;
    • set_block_initial – o pătrățică normală, și în plus pe aceasta se află blocul inițial, poziționat vertical;
    • set_blank – [opțional] va fi apelat pentru toate coordonatele din interiorul dereptunghiului minim care conține toate pătrățelele (bounding box), unde nu se află pătrățele. În funcție de reprezentarea pe care o folosiți, acest predicat vă poate fi util. Dacă nu vă este util, este suficient să fie adevărat întotdeauna.
  • get_b_pos(S, B) – în funcție de poziția blocului:
    • dacă blocul este în poziție verticală, leagă B la poziția blocului.
    • dacă blocul este în poziție orizontală, leagă B la o listă de poziții, conținând pozițiile fiecărei jumătăți a blocului.
  • get_cell(S, Pos, Type) – leagă Type la tipul pătrățelei de la poziția Pos (vezi documentația predicatului) sau întoarce false dacă nu există o pătrățică la poziția respectivă.
  • get_bounds(S, Xmin, Xmax, Ymin, Ymax) – leagă ultimele 4 argumente la minimul/maximul coordonatelor X/Y pentru pătrățelele definite în starea S.
  • is_final(S) – este adevărat dacă blocul este în poziție verticală pe poziția găurii (target).
  • move(S, Move, SNext) – calculează SNext, care rezultă în urma acțiunii Move în starea S. Se disting 3 situații:
    • blocul este în picioare și va ajunge culcat, ocupând următoarele 2 poziții în direcția de mișcare (vezi neighbor și neighbor2 din util.pl). I.e. dacă ocupă o poziție P și mișcarea este r, va ocupa poziția la dreapta de poziția P și următoarea poziție la dreapta de poziția P.
    • blocul este culcat și urmează să ajungă tot culcat (se rostogolește culcat). I.e. va ocupa două poziții, fiecare pe direcția Move față de cele două poziții inițiale.
    • blocul este culcat și urmează să se ridice în picioare. I.e. dacă Move este d, și blocul ocupă două poziții P1 și P2, unde P2 este pe direcția d față de P1, blocul va ocupa, în picioare, poziția pe direcția d față de P2.

Partea de switches rămâne pentru etapa 2; switch-urile vor fi adăugate cu predicatul set_switch.

Coordonatele pozițiilor sunt perechi de forma (X, Y), cu X=0 pe cea mai din stânga coloană pe care există pătrățele și cu Y=0 pe cea mai de sus linie pe care există pătrățele. Predicatele set_* vor fi apelate în ordine pentru toate pătrățelele, de la stânga la dreapta, și de sus în jos. În imaginea de mai sus, am notat cu buline albe pozițiile pentru care se va apela, la construcția reprezentării set_blank.

De exemplu, pentru construcția reprezentării nivelului 3, testerul va apela următoarele predicate:

empty_state(S0), set_blank(S0, (0, 0), S1), set_blank(S1, (1, 0), S2), [...], set_blank(..., (5, 0), ...),
set_tile(..., (6, 0), ...), set_tile(..., (7, 0), ...), ..., set_tile(..., (12, 0), ...),
set_blank(..., (13, 0), ...), set_blank(..., (14, 0), ...), set_tile(..., (0, 1), ...),
set_tile(..., (1, 1), ...), ..., set_tile(..., (0, 3), ...), set_block_initial(..., (1, 3), ...), ...
set_target(..., (13, 3), ...), ..., set_tile(..., (14, 5), Level3).

După construcția unei reprezentări, putem utiliza predicate get_* pe această stare. De exemplu, pe starea de mai sus, putem apela get_cell(Level3, (8, 2), X) care ne așteptăm să lege X la tile.

După implementarea tuturor predicatelor set_* și get_* veți putea utiliza predicatul print_state/1 din util.pl pentru a afișa o stare. De exemplu, pentru Stage 03, starea va fi afișată ca

      +++++++   
++++  +++  ++   
+++++++++  ++++ 
+B++       ++$+ 
++++       ++++ 
            +++ 

Nu uitați că aveți predicate utile pentru calculul pozițiilor în fișierul util.pl.

Etapa 2

În această etapă trebuie să includeți funcționalitatea switch-urilor în mecanica jocului și să rezolvați nivelurile de Bloxorz date.

Un switch în Bloxorz permite activarea și/sau dezactivarea unor pătrățele din tabla de joc (numite și pod – bridge).

  • Avem 2 tipuri de switch-uri: un o-switch funcționează oricum ar fi apăsat de bloc; un x-switch funcționează doar dacă este apăsat de blocul așezat în picioare.
  • Avem 3 posibile funcționalități pentru switch-uri: doar activează un pod (uponly), doar dezactivează un pod (downolnly) sau comută starea podului (switch). Exemple din toate cele trei funcționalități pot fi văzute în nivelul 5.

Aveți de implementat:

  • predicatul set_switch(S, Pos, Switch, Func, Positions, SOut) care leagă SOut la o stare cu aceleași informații ca și S, având în plus un switch în poziția Pos. Switch este tipul (oswitch sau xswitch); Func este funcționalitatea (uponly, dnonly sau switch); Positions sunt pozițiile celulelor podului care este activat / dezactivat de switch. Odată implementat acest predicat, se așteaptă următorul comportament:
    • get_cell trebuie să lege ultimul argument la oswitch sau xswitch pentru poziția switch-urilor, pentru ca ele să fie afișate corect
    • pentru poduri, dacă podul este activat atunci get_cell trebuie să lege ultimul argument la tile pentru toate pozițiile podului, iar dacă podul este dezactivat get_cell trebuie să eșueze (să întoarcă false) pentru toate pozițiile podului
    • predicatul move va fi ajustat în așa fel încât să modifice starea tablei de joc în mod corespunzător cu funcționalitatea switch-urilor.

Pentru rezolvarea unui nivel, aveți de implementat predicatul:

  • solve(S, Moves) care leagă Moves la o listă de mutări necesare pentru a rezolva starea S.
    • Pentru solve, ar trebui să fie suficient să lăsați pe Prolog să facă backtracking, alegând din mutările disponibile și avansând starea corespunzător.
    • Pentru a nu trece prin aceeași poziție de două ori, folosiți o listă de poziții deja vizitate.
    • Observați că, de exemplu la nivelul 5, este necesar ca unele switch-uri să fie apăsate de 2 ori, ceea ce înseamnă că în acest caz trebiue să permiteți blocului să fie de două ori în aceeași poziție, dacă a apăsat pe un switch.
    • pentru ultimele puncte, încercați să obțineți o rezolvare în mai puține mutări. Hint: alegeți mai întâi o mutare care duce blocul mai aproape de scop. Puteți folosi predicatul msort/2 din Prolog.

Testare

Încărcați în Prolog fișierul blox.pl. Pentru testare folosiți predicatele check sau vmcheck. Pentru teste individuale folosiți vmtest(<nume_test>)., e.g. vmtest(s0). Vedeți și observația de la începutul fișierului testing.pl.

La trimiterea temei, este suficient să puneți în arhivă fișierul blox.pl.

Testele se află în fișierul testing.pl. Tipurile testelor sunt descrise succint la începutul fișierului. Pentru a verifica un test, puteți face o interogarea în consolă care conține scopurile plasate (de obicei) între ghilimele în primul argumental testului.

De exemplu, pentru a replica testul s0|b puteți interoga la consolă:

empty_state(S), ttSetCell(S, 0, 0, +, _, _)

iar pentru testul s0|c:

empty_state(S), ttSetCell(S, 0, 0, 'B', _, S1), get_cell(S1, (0, 0), X)

Pentru rezultate mai detaliate ale testării, puteți decomenta linia %detailed_mode_disabled :- !, fail. din fișierul testing.pl. Atenție! În acest caz, este posibil ca punctarea să fie diferită decât rezultatul de pe vmchecker, unde linia este comentată.

Pentru a face debug pe secvențe de mutări, puteți folosi predicatul ttSeqDbg din testing.pl, care afișează starea după fiecare mutare din secvență.

Resurse

Changelog

  • 14.05 – enunț.
  • 17.05 – teste, mai multe informații despre testare în enunț.
  • 20.05 – configurare vmchecker
  • 20.05 – corectare teste, adăugare condiții teste negative.
  • 22.05 – adăugare etapa 2, prime teste etapa 2
  • 22.05 – completare teste etapa 2, configurare vmchecker
pp/24/teme/prolog-bloxorz.txt · Last modified: 2024/05/22 17:39 by andrei.olaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0