Tema este împărțită în 2 etape:
Așa cum se poate observa, ziua deadline-ului variază în funcție de semigrupa în care sunteți repartizați. Restanțierii care refac tema și nu refac laboratorul beneficiază de ultimul deadline (deci vor avea deadline-uri în zilele de 25.05, 01.06).
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ă cu jumătate din punctaj. 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 sunt înainte de deadline, nota pe ultima submisie este și nota finală (întrucât n1 = n2).
În fiecare etapă, veți folosi ce ați învățat în săptămâna anterioară pentru a dezvolta aplicația.
Veți implementa în Prolog anumite componente dintr-un joc de Ultimate Tic-Tac-Toe. Vom utiliza aceste reguli. Puteți juca jocul aici. Vom numi jocul de Ultimate Tic Tac Toe “UTTT” și un joc obișnuit de Tic-Tac-Toe (X și 0) “TTT”. Vom face referire mai jos la următorul exemplu:
Vom identifica tablele individuale, ca și pozițiile din tablele individuale, prin “pozițiile” (în ordinea parcurgerii de la stânga la dreapta și de sus în jos): NW, N, NE, W, C, E, SW, S, SE:
nw | n | ne ---+---+--- w | c | e ---+---+--- sw | s | se
Tabla de UTTT (o vom mai numi și “U-board”) este formată 9 table obișnuite de TTT (numite în cod “board”). O mutare a unui jucător va fi o mutare obișnuită de TTT pe una dintre tablele disponibile pentru mutări (despre asta mai târziu). În exemplul de mai sus, ultima mutare a fost o jucătorului 0
în poziția indicată de săgeata verde, deci jucătorul 0
a mutat în poziția w
din tabla individuală care este în poziția n
din tabla e UTTT.
Tablele individuale de TTT și tabla de UTTT se câștigă după regulile obișnuite din X și 0. Câștigarea unei table individuale de TTT reprezintă o mutare (un X sau 0) pe tabla mare de UTTT. În exemplul de mai sus, tablele din pozițiile nw
, c
și se
din U-board au fost deja câștigate, de jucătorul x
, x
și 0
, respectiv. Jucătorul 0
este, de asemenea, aproape de a câștiga tablele n
și ne
.
Când un jucător câștigă pe tabla mare de UTTT, este câștigător al jocului și jocul se termină. În exemplul de mai sus, dacă jucătorul 0
câștigă și tablele ne
și e
atunci câștigă jocul (dacă nu îl câștigă x
înainte).
Tabla sau tablele individuale de TTT pe care poate muta un jucător se decid(e) astfel:
0
tocmai a mutat pe poziția w
a unei table individuale, jucătorul x
va trebui acum să mute într-una din pozițiile libere din tabla w
.0
ar fi mutat în centrul tablei (tabla individuală din poziția n
a U-board), ar fi câștigat tabla n
, dar, cum tabla c
este deja finalizată, jucătorul x
ar fi putut la următoarea mutare să mute în oricare dintre tablele ne
, w
, e
, sw
, s
, ceea ce ar fi fost un avantaj pentru x
.În această etapă vom implementa câteva predicate care lucrează cu liste. Trebuie implementate construcția și accesul la o stare a jocului și efectuare unei mutări în joc. Pentru bonus, se vor implementa două strategii foarte simple.
Reprezentarea concretă a unei stări este la alegerea fiecăruia. Puteți folosi liste, perechi, sau structuri (compounds).
Vom avea 3 grupuri de predicate (ordinea recomandată de implementare a predicatelor este cea din fișierul sursă):
initialState
construiește reprezentarea stării inițiale a jocului.buildState
construiește reprezentarea unei stări pe baza configurației tablei de joc și pe baza poziției în care a mutat jucătorul anterior.getBoards
, getBoard
, getPos/3
, getPos/4
obțin informații despre tablele de joc individuale. Tablele individuale sunt văzute ca liste de 9 celule, puse în ordinea pozițiilor dată mai sus. Fiecare celulă poate avea ca valoare atomul (literalul) x
, numărul 0
, sau atomul vid ''
. Pentru starea din exemplul de mai sus, getBoard(State, n, B)
trebuie să lege B
la lista [x,'','', 0,'',0, '',0,'']
.getUBoard
obține configurația tablei de UTTT, văzută ca o tablă individuală. În plus față de tablele individuale, tabla de UTTT poate avea și celule cu valoarea r
, pentru tablele individuale remizate. Pentru starea din exemplul de mai sus, getUBoard(State, UBoard)
trebuie să lege UBoard
la lista [x,'','', '',x,'', '','',0]
.getNextPlayer
obține jucătorul care urmează la rând (x
sau 0
). Acesta poate fi determinat numărând celulele cu x
și cu 0
din tablele individuale. Primul jucător este x
. În exemplul de mai sus, următorul jucător este x
pentru că sunt 14 mutări ale lui x
și 14 mutări ale lui 0
.getNextAvailableBoards
obține tablele individuale (ca poziții în tabla de UTTT) disponibile pentru următoarea mutare.initialState(S0), getNextAvailableBoards(S0, Boards)
, Boards
trebuie legat la lista completă de poziții: [nw, n, ne, w, c, e, sw, s, se]
;0
tocmai a mutat în poziția w
a tablei n
, jucătorul x
trebuie să mute obligatoriu în tabla w
, deci getNextAvailableBoards(State, Boards)
trebuie să lege Boards
la lista [w]
;x
mută în centrul tablei w
, pentru următoarea mutare, a lui 0
vor fi disponibile tablele [n, ne, w, e, sw, s]
.getBoardResult
obține rezultatul pentru configurația unei table individuale. Rezultatul poate fi x
, 0
, r
, sau ''
, acesta din urmă pentru cazul în care jocul pe această tablă individuală continuă.(UPos, Pos)
între poziția tablei individuale (în cadrul U-board) unde se va face mutarea, și poziția din tablă unde se va pune x sau 0.validMove
verifică validitatea unei mutări. O mutare este validă dacă:makeMove
determină starea următoare după efectuarea unei mutări.Pentru BONUS în această etapă se vor implementa două strategii foarte simple:
dummy_first
, care alege întotdeauna prima (în ordinea pozițiilor) mutare disponibilă. În exemplul de mai sus, strategia va întoarce n
, prima poziție disponibilă din tabla w
unde trebuie să mute x
. Dacă din starea din exemplu, x
ar muta, conform unei alte strategii, în c
, pentru mutarea lui 0
strategia dummy_first
ar întoarce (n, n)
, pentru că n
este prima tablă disponibilă, și n
este prima poziție disponibilă din acea tablă.dummy_last
, care alege întotdeauna ultima (în ordinea pozițiilor) mutare disponibilă. În exemplul de mai sus, strategia va întoarce we
, ultima poziție disponibilă din tabla w
unde trebuie să mute x
. Dacă din starea din exemplu, x
ar muta, conform unei alte strategii, în c
, pentru mutarea lui 0
strategia last
ar întoarce (s, se)
, pentru că s
este ultima tablă disponibilă, și se
este ultima poziție disponibilă din acea tablă.
NOTĂ: pentru majoritatea testelor de la predicatele de acces este necesar ca buildState
să fie implementat. Pentru restul (mai puține), este necesar ca initialState
să fie implementat. Ideal este să implementați predicatele de acces la reprezentarea stării în același timp cu părțile corespunzătoare din predicatele care construiesc reprezentarea stării.
Pe parcursul implementării temei, veți găsi foarte utile predicatele nth0/3 și nth0/4.
Pentru a afișa o stare a jocului, folosiți predicatul printBoards/1
, iar pentru a afișa o tablă individuală folosiți predicatul printBoard/1
. De exemplu, pentru a vizualiza starea inițială (odată ce ați implementat construcția sa), puteți folosi interogarea:
initialState(S), printBoards(S).
Iar pentru a afișa o stare utilizată în teste, puteți folosi, de exemplu (odată ce ați implementat predicatul buildState
:
uttt(2, S), printBoards(S).
Testele sunt disponibile în fișierul checker.pl
, iar jocurile și listele de mutări folosite în teste sunt disponibile în fișierul input.pl
.
Pentru testele de validMove
care ies din timp, vedeți acest thread.
În această etapă vom evalua calitatea mutărilor după un algoritm dat și vom implementa două strategii care folosesc aceste măsuri de calitate.
Într-o tablă individuală, vom avea următoarele priorități pentru mutări (mutarea cu cea mai mare calitate va fi cea de prioritate minimă):
Predicatul movePriority/4
evaluează prioritatea unei mutări, pentru un jucător, pentru o tablă individuală.
Predicatul bestIndividualMoves/3
ordonează mutările disponibile într-o tablă individuală în funcție de prioritatea lor pentru jucătorul curent. Ordinea apriori a mutărilor este cea din lista positions. Două mutări cu prioritate egală își păstrează ordinea apriori. Folosiți pentru sortare sortMoves/2
.
În exemplu, pentru tabla w, cele mai bune mutări sunt, în ordine, [c, ne, sw, se, n, w, e, s]
, pentru că:
Strategia narrowGreedy
va întoarce o mutare bazată pe următorul algoritm: dacă este o singură tablă disponibilă, se va alege mutarea cu cea mai mică prioritate (sau prima mutare cu cea mai mică prioritate); dacă sunt mai multe table disponibile, se alege tabla cu cea mai mică prioritate, și în ea mutarea cu cea mai ică prioritate.
Pentru a evalua mutările la nivelul întregului joc de UTTT, vom considera următoarea ordonare a priorităților:
Predicatul bestMoves/2
ordonează mutările disponibile în ordinea prezentată mai sus. Pentru stările în care sunt mai multe table disponibile pentru jucătorul curent, mutările vor fi luate apriori (înainte de sortarea după priorități) după ordonearea tablelor individuale conform cu bestIndividualMoves/3
.
NOTĂ: când calculați cele mai bune mutări, în moemntul evaluării unei anumite mutări M, atunci când facem referire la ce va face mai departe un alt jucător (sau același jucător), faceți evaluarea pe starea în care mutarea M s-ar fi aplicat deja.
În exemplu, pentru x, cele mai bune mutări (după această strategie) sunt, în ordine, [sw, w, e, s, n, ne, c, se]
, pentru că:
Strategia greedy
va întoarce cea mai bună mutare (prima) din mutările întoarse de bestMoves/2
.
Pentru puncte bonus, implementați cât mai elegant, folosind findall
, forall
, și având reguli diferite doar atunci când este neapărat nevoie (e.g. pentru calculul priorităților pe diferite cazuri).
findall/3
sortMoves/2
din utils.pl
uttt.pl
vmcheck.
)vmtest(<nume_test>)
, e.g. vmtest(narrowGreedy)
detailed_mode_disabled :- !, fail.
din fișierul checker.pl
. Modul detaliat (unde este posibil să primiți câteva puncte în plus cu implementările implicite) nu este cel folosit pe vmchecker, dar în acest mod testerul oferă mai mult detalii despre testele eșuate.input.pl
există și starea uttt(enunt, S)
, care este exemplul de mai sus din acest enunț.uttt.pl
trebuie inclus files.pl
în loc de checker.pl
printBoards
(în scheletul pentru etapa 2) a afișării jucătorului curent și a tablelor disponibile pentru următoarea mutare.bestIndividualMoves
uttt(enunt, S)
în input.pl
și mențiune în enunțbestMoves
play_strategies
în utils.pl