Permiteți jucătorilor tăi să-și retragă greșelile în joc cu modelul de comandă

Multe jocuri pe bază de turnuri includ un Anula pentru a permite jucătorilor să reverse greșelile pe care le fac în timpul jocului. Această caracteristică devine relevantă în special pentru dezvoltarea jocurilor mobile, în care atingerea poate avea o recunoaștere clară a atingerii. Mai degrabă decât să vă bazați pe un sistem în care cereți utilizatorului "sunteți sigur că doriți să faceți această sarcină?" pentru fiecare acțiune pe care o ia, este mult mai eficient să-i lași să facă greșeli și să aibă opțiunea de a-și inversa cu ușurință acțiunea. În acest tutorial, vom examina modul în care vom implementa acest lucru utilizând Model de comandă, folosind exemplul unui joc tic-tac-toe.

Notă: Deși acest tutorial este scris folosind Java, ar trebui să puteți folosi aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului. (Nu este limitat la jocurile tic-tac-toe!


Rezultatul final al rezultatelor

Rezultatul final al acestui tutorial este un joc tic-tac-toe care oferă operații nelimitate de anulare și redo.

Această demonstrație necesită executarea Java.

Nu se poate încărca aplicația? Urmăriți videoclipul de joc pe YouTube:

De asemenea, puteți rula demonstrația pe linia de comandă utilizând TicTacToeMain ca clasă principală de executat. După extragerea sursei executați următoarele comenzi:

 javac * .java java TicTacToeMain

Pasul 1: Creați o implementare de bază a Tic-Tac-Toe

Pentru acest tutorial, veți lua în considerare o implementare a tic-tac-toe. Deși jocul este extrem de banal, conceptele oferite în acest tutorial se pot aplica jocurilor mult mai complexe.

Următoarea descărcare (care diferă de descărcarea sursei finale) conține codul de bază pentru un model de joc tic-tac-toe care nu nu conține o caracteristică anulare sau redo. Va fi sarcina dvs. să urmați acest tutorial și să adăugați aceste caracteristici. Descărcați baza TicTacToeModel.java.

Ar trebui să țineți seama, în special, de următoarele metode:

public void placeX (int rând, int col) assert (playerXTurn); afirmați (spații [rând] [col] == 0); spații [rând] [col] = 1; playerXTurn = false; 
public void placeO (int rând, int col) assert (! playerXTurn); afirmați (spații [rând] [col] == 0); spații [rând] [col] = 2; playerXTurn = adevărat; 

Aceste metode sunt singurele metode pentru acest joc care schimbă starea jocului. Vor fi ceea ce se va schimba.

Dacă nu sunteți dezvoltator Java, probabil veți fi capabil să înțelegeți codul. Este copiat aici dacă vrei să te referi la el:

 / ** Logica jocului pentru un joc Tic-Tac-Toe. Acest model nu are * o interfață de utilizator asociată: este doar logica jocului. * * Jocul este reprezentat de o simplă matrice 3x3. O valoare de * 0 înseamnă că spațiul este gol, 1 înseamnă că este un X, 2 înseamnă că este O. * * @author aarnott * * / clasa publică TicTacToeModel // Adevărat dacă este rândul jucătorului X, fals dacă este rândul jucatorului O privat boolean playerXTurn; // setul de spații de pe gridul privat int [] []; / ** Initializeaza un nou model de joc. În jocul tradițional Tic-Tac-Toe *, X merge mai întâi. * * / public TicTacToeModel () spaces = new int [3] [3]; playerXTurn = adevărat;  / ** Returnează true dacă este rândul jucătorului X. * * @return * / boolean public estePlayerXTurn () return playerXTurn;  / ** Returnează true dacă este rândul jucătorului O. * * @return * / boolean public estePlayerOTurn () return! playerXTurn;  / ** Plasează un X într-un spațiu specificat de parametrii rândului și coloanei *. * * Precondiții: * -> Trebuie să fie rândul jucatorului X * -> Spațiul trebuie să fie gol * * @ param line rândul pentru a plasa X pe * @ param col Coloana pentru a plasa X pe * / public void placeX (int rând, int col) assert (playerXTurn); afirmați (spații [rând] [col] == 0); spații [rând] [col] = 1; playerXTurn = false;  / ** Pune un O într-un spațiu specificat de parametrii rândului și coloanei *. * * Precondiții: * -> Trebuie să fie rândul jucătorului O * -> Spațiul trebuie să fie gol * * @ param line rândul pentru a plasa O pe * @ param col Coloana pentru a plasa O pe * / public void placeO (int rând, int col) assert (! playerXTurn); afirmați (spații [rând] [col] == 0); spații [rând] [col] = 2; playerXTurn = adevărat;  / ** Returnează adevărat dacă un spațiu pe grilă este gol (nu Xs sau Os) * * @param rând * @param col * @return * / boolean public esteSpaceEmpty (int rând, int col) return (spații [rând ] [col] == 0);  / ** Returnează adevărat dacă un spațiu pe grilă este un rând X. * * @param * @param col * @return * / boolean public isSpaceX (int rând, int col) retur (spații [rând] [col] == 1);  / ** Returnează adevărat dacă un spațiu pe grilă este o linie O. * * @param * @param col * @return * / boolean public isSpaceO (int rând, int col) return [spații [rând] == 2);  / ** Returnează true dacă jucătorul X a câștigat jocul. Aceasta înseamnă că dacă jucătorul * X a terminat o linie de trei X-uri. * * @return * / boolean public hasPlayerXWon () // Verificați rândurile dacă (spații [0] [0] == 1 && spații [0] [1] == 1 && spații [0] [2] == 1 ) return true; dacă [spațiile [1] [0] == 1 && spațiile [1] [1] == 1 && spațiile [1] [2] == 1) return true; dacă [spații [2] [0] == 1 && spații [2] [1] == 1 && spații [2] [2] == 1) return true; // Verificați coloanele dacă [spații [0] [0] == 1 && spații [1] [0] == 1 && spații [2] [0] == 1) return true; dacă [spațiile [0] [1] == 1 && spațiile [1] [1] == 1 && spațiile [2] [1] == 1) return true; dacă [spații [0] [2] == 1 && spații [1] [2] == 1 && spații [2] [2] == 1) return true; // Verificați diagonalele dacă [spații [0] [0] == 1 && spații [1] [1] == 1 && spații [2] [2] == 1) return true; dacă spațiile [0] [2] == 1 && spații [1] [1] == 1 && spații [2] [0] == 1) return true; // În caz contrar, nu există nicio returnare a liniei false;  / ** Returneaza true daca jucatorul O a castigat jocul. Asta este, dacă jucătorul * O a terminat o linie de trei Os. * * @return * / boolean public hasPlayerOWon () // Verificați rândurile dacă (spații [0] [0] == 2 && spații [0] [1] == 2 && spații [0] [2] == 2 ) return true; dacă [spații [1] [0] == 2 && spații [1] [1] == 2 && spații [1] [2] == 2) return true; dacă [spații [2] [0] == 2 && spații [2] [1] == 2 && spații [2] [2] == 2) return true; // Verificați coloanele dacă [spații [0] [0] == 2 && spații [1] [0] == 2 && spații [2] [0] == 2) return true; dacă [spații [0] [1] == 2 && spații [1] [1] == 2 && spații [2] [1] == 2) return true; dacă [spații [0] [2] == 2 && spații [1] [2] == 2 && spații [2] [2] == 2) return true; // Verificați diagonalele dacă (spații [0] [0] == 2 && spații [1] [1] == 2 && spații [2] [2] == 2) return true; dacă [spații [0] [2] == 2 && spații [1] [1] == 2 && spații [2] [0] == 2) return true; // În caz contrar, nu există nicio returnare a liniei false;  / ** Returneaza true daca toate spatiile sunt umplut sau unul dintre jucatori * a castigat jocul. * * @return * / booleanul public esteGameOver () if (hasPlayerXWon () || hasPlayerOWon ()) return true; // Verificați dacă toate spațiile sunt umplute. Dacă unul nu este jocul nu sa terminat pentru (int rând = 0; rând < 3; row++)  for(int col = 0; col < 3; col++)  if(spaces[row][col] == 0) return false;   //Otherwise, it is a “cat's game” return true;  

Pasul 2: Înțelegerea modelului de comandă

Comanda modelul este un model de design care este utilizat în mod obișnuit cu interfețele utilizatorilor pentru a separa acțiunile efectuate de butoane, meniuri sau alte widgeturi de definițiile codului interfeței utilizator pentru aceste obiecte. Acest concept de cod de acțiune separator poate fi folosit pentru a urmări fiecare schimbare care se întâmplă cu starea jocului și puteți utiliza aceste informații pentru a inversa modificările.

Versiunea cea mai de bază a Comanda modelul este următoarea interfață:

interfață publică Command public void execute (); 

Orice acțiunea care este luată de program care schimbă starea jocului - cum ar fi plasarea unui X într-un spațiu specific - va implementa Comanda interfață. Când se ia acțiunea, a executa() se numește metoda.

Acum, probabil ați observat că această interfață nu oferă posibilitatea de a anula acțiunile; tot ce face este să ia jocul de la o stare la alta. Următoarea îmbunătățire va permite acțiunilor de implementare pentru a oferi capacități de anulare.

interfață publică Command public void execute (); public void undo (); 

Scopul implementării a Comanda va fi de a avea Anula() metoda inversă orice acțiune luată de a executa metodă. În consecință, a executa() metoda va fi, de asemenea, capabilă să ofere capacitatea de a repeta o acțiune.

Aceasta este ideea de bază. Va deveni mai clară în timp ce punem în aplicare anumite comenzi pentru acest joc.


Pasul 3: Creați un Manager de comandă

Pentru a adăuga o caracteristică de anulare, veți crea un CommandManager clasă. CommandManager este responsabil pentru urmărirea, executarea și anularea Comanda implementările.

(Rețineți că Comanda interfața oferă metodele de a face schimbări de la o stare a unui program la altul și, de asemenea, să o inverseze.)

clasa publică CommandManager Command privat lastCommand; public CommandManager ()  public void executeCommand (comanda c) c.execute (); lastCommand = c;  ...

Pentru a executa a Comanda, CommandManager este trecut a Comanda exemplu, și va executa Comanda apoi stocați cel mai recent executat Comanda pentru referință ulterioară.

Adăugând caracteristica anulare la CommandManager trebuie doar să-i spui să anuleze cele mai recente Comanda care a fost executat.

booleanul public esteUndoAvailable () return lastCommand! = null;  public void undo () assert (lastCommand! = null); lastCommand.undo (); lastCommand = null; 

Acest cod este tot ceea ce este necesar pentru a avea o funcționalitate CommandManager. Pentru ca aceasta să funcționeze corect, va trebui să creați câteva implementări ale programului Comanda interfață.


Pasul 4: Creați implementări ale Comanda Interfață

Scopul Comanda modelul pentru acest tutorial este de a muta orice cod care modifică starea jocului tic-tac-toe într-o Comanda instanță. Anume, codul în metodele placeX () și placeO () sunt ceea ce veți schimba.

În interiorul TicTacToeModel clasa, adăugați două noi clase interioare numite PlaceXCommand și PlaceOCommand, respectiv, care pun în aplicare fiecare Comanda interfață.

public class TicTacToeModel ... private class PlaceXCommand implementează Comandă public void execute () ... public void undo () ... clasă privată PlaceOCommand implementează Command public void execute () ... public void undo 

Funcția de a Comanda implementarea este aceea de a stoca un stat și de a avea logică fie la trecerea la un nou stat care rezultă din execuția lui Comanda sau de a reveni la starea inițială înainte de Comanda a fost executat. Există două modalități clare de realizare a acestei sarcini.

  1. Stocați întreaga stare anterioară și starea următoare. Setați starea curentă a jocului la următoarea stare atunci când a executa() se numește și setează starea actuală a jocului la starea anterioară stocată atunci când Anula() se numește.
  2. Stocați numai informațiile care se schimbă între stări. Schimbați numai această informație stocată când a executa() sau Anula() se numește.
// Opțiunea 1: Stocarea claselor private anterioare și următoare PlaceXCommand implementează Command private TicTacToeModel model; / / int int [] [] anteriorGridState; private boolean previousTurnState; int int [] [] nextGridState; privat boolean nextTurnState; // privat PlaceXCommand (modelul TicTacToeModel, int rând, int col) this.model = model; // previousTurnState = model.playerXTurn; // Copiați întreaga rețea pentru ambele state previousGridState = new int [3] [3]; nextGridState = int int [3] [3]; pentru (int i = 0; i < 3; i++)  for(int j = 0; j < 3; j++)  //This is allowed because this class is an inner //class. Otherwise, the model would need to //provide array access somehow. previousGridState[i][j] = m.spaces[i][j]; nextGridState[i][j] = m.spaces[i][j];   //Figure out the next state by applying the placeX logic nextGridState[row][col] = 1; nextTurnState = false;  // public void execute()  model.spaces = nextGridState; model.playerXTurn = nextTurnState;  // public void undo()  model.spaces = previousGridState; model.playerXTurn = previousTurnState;  

Prima opțiune este puțin risipitoare, dar asta nu înseamnă că este un design rău. Codul este simplu și, cu excepția cazului în care informațiile de stat sunt extrem de mari, cantitatea de deșeuri nu va fi ceva de care să vă faceți griji.

Veți vedea că, în cazul acestui tutorial, a doua opțiune este mai bună, dar această abordare nu va fi întotdeauna cea mai bună pentru fiecare program. De cele mai multe ori, însă, a doua opțiune va fi calea de urmat.

// Opțiunea 2: Stocarea numai a schimbărilor între state clasa privată PlaceXCommand implementează Command private TicTacToeModel model; private int previousValue; privat boolean anteriorTurn; rând privat int; private int col; // privat PlaceXCommand (modelul TicTacToeModel, int rând, int col) this.model = model; this.row = rând; this.col = col; // Copiați valoarea anterioară din grilă this.previousValue = model.space [rând] [col]; this.previousTurn = model.playerXTurn;  // void public execute () model.space [rând] [col] = 1; model.playerXTurn = fals;  // public void undo () model.spații [rând] [col] = precedentValue; model.playerXTurn = precedentTurn; 

A doua opțiune stochează doar schimbările care se produc, mai degrabă decât întreaga stare. În cazul tic-tac-toe, este mai eficient și nu este mai complex decât utilizarea acestei opțiuni.

PlaceOCommand clasa interioară este scrisă într-un mod similar - aveți de gând să o scrieți singur!


Pasul 5: puneți totul împreună

Pentru a vă folosi de dvs. Comanda implementările, PlaceXCommand și PlaceOCommand, va trebui să modificați TicTacToeModel clasă. Clasa trebuie să facă uz de a CommandManager și trebuie să o folosească Comanda în locul aplicării direct a acțiunilor.

clasa publică TicTacToeModel CommandManager privat commandManager; // ... // public TicTacToeModel () ... // commandManager = nou CommandManager ();  // // // public void placeX (int rând, int col) assert (playerXTurn); afirmați (spații [rând] [col] == 0); commandManager.executeCommand (nou placeXCommand (acest rând, coloană));  // public void placeO (int rând, int col) assert (! playerXTurn); afirmați (spații [rând] [col] == 0); commandManager.executeCommand (nou placeOCommand (acest rând, rând, col));  // ...

TicTacToeModel clasa va funcționa exact așa cum a procedat înainte de modificările dvs. acum, dar puteți expune și caracteristica anulare. Adăugați un Anula() la model și adăugați, de asemenea, o metodă de verificare canUndo pentru ca interfața cu utilizatorul să fie utilizată la un moment dat.

clasa publică TicTacToeModel // ... // public boolean canUndo () return commandManager.isUndoAvailable ();  // public void undo () commandManager.undo (); 

Acum aveți un model complet de joc tic-tac-toe care acceptă anularea!


Pasul 6: Luați-o mai departe

Cu câteva mici modificări la CommandManager, puteți adăuga suport pentru operațiunile de redoire, precum și un număr nelimitat de recuperări și retrageri.

Conceptul din spatele unei caracteristici de redo este aproape la fel ca o caracteristică de anulare. În plus față de stocarea ultimului Comanda executate, stocați și ultima Comanda care a fost anulată. Păstrează asta Comanda când se cheamă o anulare și o șterge când a Comanda este executat.

class public CommandManager comandă privată lastCommandUndone; ... public void executeCommand (comanda c) c.execute (); lastCommand = c; lastCommandUndone = null;  public void undo () assert (lastCommand! = null); lastCommand.undo (); lastCommandUndone = lastCommand; lastCommand = null;  boolean public esteRedoAvailable () return lastCommandUndone! = null;  public void redo () assert (lastCommandUndone! = null); lastCommandUndone.execute (); lastCommand = lastCommandUndone; lastCommandUndone = null; 

Adăugarea în mai multe anteturi și redos este o chestiune de stocare a grămadă de acțiuni nedorite și redoibile. Atunci când se execută o nouă acțiune, aceasta se adaugă la stiva de dezangajare, iar stiva de redirecționare este șters. Când o acțiune este anulată, ea este adăugată la stiva de redo și este scoasă din stiva de anulare. Atunci când o acțiune este redone, este eliminată din stiva de redo și adăugată în stivă de anulare.

Imaginea de mai sus arată un exemplu de stive în acțiune. Stackul de redare are două elemente din comenzi care au fost deja anulate. Când sunt comenzi noi, PlaceX (0,0) și PlaceO (0,1), sunt executate, stiva de redirecționare este șters și acestea sunt adăugate la stiva de dezangajare. Când un PlaceO (0,1) este anulată, este îndepărtată din partea superioară a coșului de anulare și este plasată pe stiva de redo.

Iată cum arată acest lucru în cod:

clasa publică CommandManager private Stack undos = Stack nou(); Stack privat redos = stack nou(); public void executeCommand (comanda c) c.execute (); undos.push (c); redos.clear ();  boolean public esteUndoAvailable () return! undos.empty ();  public void undo () assert (! undos.empty ()); Command comandă = undos.pop (); command.undo (); redos.push (comandă);  boolean public esteRedoAvailable () return! redos.empty ();  public void redo () assert (! redos.empty ()); Command comandă = redos.pop (); command.execute (); undos.push (comandă); 

Acum aveți un model de joc tic-tac-toe care poate anula acțiunile până la începutul jocului și le poate repune din nou.

Dacă doriți să vedeți cum toate acestea se potrivesc împreună, apucați descărcarea sursei finale, care conține codul completat din acest tutorial.


Concluzie

S-ar putea să fi observat că finala CommandManager pentru care ați scris va lucra pentru orice Comanda implementări. Acest lucru înseamnă că puteți codifica o CommandManager în limba dvs. preferată, creați câteva instanțe ale Comanda și aveți un sistem complet pregătit pentru dezactivare / redoire. Caracteristica de anulare poate fi o modalitate excelentă de a permite utilizatorilor să vă exploreze jocul și să facă greșeli fără a se simți angajați în deciziile nepotrivite.

Vă mulțumim că v-ați interesat de acest tutorial!

Ca alte mâncare pentru gândire, ia în considerare următoarele: Comanda model împreună cu CommandManager vă permite să urmăriți fiecare schimbare de stat în timpul executării jocului. Dacă salvați aceste informații, puteți crea replici ale executării programului.