Mange turnbaserte spill inkluderer en angre knappen for å la spillerne reversere feilene de gjør under spillet. Denne funksjonen blir spesielt relevant for mobil spillutvikling hvor kontakten kan ha klumpet berøringskjenning. Snarere enn å stole på et system der du spør brukeren "er du sikker på at du vil gjøre denne oppgaven?" For hver handling de tar, er det mye mer effektivt å la dem gjøre feil og få muligheten til å enkelt reversere sin handling. I denne opplæringen ser vi på hvordan du implementerer dette ved hjelp av Kommando Mønster, bruker eksempelet på et tic-tac-toe spill.
Merk: Selv om denne opplæringen er skrevet ved hjelp av Java, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø. (Det er ikke begrenset til tic-tac-toe-spill, heller!)
Det endelige resultatet av denne opplæringen er et tic-tac-toe-spill som tilbyr ubegrenset angre og gjenta operasjoner.
Kan ikke laste appleten? Se spillvideoen på YouTube:
Du kan også kjøre demoen på kommandolinjen ved hjelp av TicTacToeMain
som hovedklassen å utføre fra. Etter å ha hentet kilden, kjør følgende kommandoer:
javac * .java java TicTacToeMain
For denne opplæringen skal du vurdere en implementering av tic-tac-toe. Selv om spillet er ekstremt trivielt, kan konseptene i denne opplæringen gjelde for mye mer komplekse spill.
Følgende nedlastning (som er forskjellig fra den endelige kildedownloaden) inneholder grunnleggende kode for en tic-tac-toe spillmodell som gjør ikke inneholde en angre eller gjenta funksjon. Det blir din jobb å følge denne opplæringen og legge til disse funksjonene. Last ned basen TicTacToeModel.java.
Du bør spesielt merke seg følgende metoder:
offentlig tomrom x (int rad, int kol) assert (playerXTurn); assert (mellomrom [rad] [col] == 0); mellomrom [rad] [col] = 1; playerXTurn = false;
offentlig tomrom sted (int rad, int kol) assert (! playerXTurn); assert (mellomrom [rad] [col] == 0); mellomrom [rad] [col] = 2; playerXTurn = true;
Disse metodene er de eneste metodene for dette spillet som endrer tilstanden til spillruten. De vil bli det du vil forandre.
Hvis du ikke er en Java-utvikler, vil du nok fortsatt kunne forstå koden. Det er kopiert her hvis du bare vil referere til det:
/ ** Spilllogikken for et Tic-Tac-Toe-spill. Denne modellen har ikke * et tilhørende brukergrensesnitt: det er bare spilllogikken. * * Spillet er representert av et enkelt 3x3 heltall array. En verdi på * 0 betyr at rommet er tomt, 1 betyr at det er en X, 2 betyr at det er en O. * * @author aarnott * * / public class TicTacToeModel // True hvis det er X-spilleren snu, falsk hvis det er O-spilleren sin private boolske spillerXTurn; // Settet med mellomrom på spillruten private int [] [] mellomrom; / ** Initialiser en ny spillmodell. I det tradisjonelle Tic-Tac-Toe * -spillet går X først. * * / public TicTacToeModel () spaces = new int [3] [3]; playerXTurn = true; / ** Returnerer sant hvis det er X-spilleren snu. * * @return * / offentlig boolean isPlayerXTurn () return playerXTurn; / ** Returnerer sant hvis det er O-spillerens sving. * * @return * / offentlig boolean isPlayerOTurn () return! playerXTurn; / ** Plasser en X på et mellomrom angitt av rad og kolonne * parametere. * * Forutsetninger: * -> Det må være X-spillerens sving * -> Plassen må være tom * * @param rad Rækken for å plassere X på * @param kol Kolonnen for å plassere X på * / offentlig tomrom (int rad, int col) assert (playerXTurn); assert (mellomrom [rad] [col] == 0); mellomrom [rad] [col] = 1; playerXTurn = false; / ** Plasser en O på et mellomrom angitt av rad og kolonne * parametere. * * Forutsetninger: * -> Det må være O-spillerens sving * -> Plassen må være tom * * @param rad Rækken for å plassere O på * @param col Kolonnen for å plassere O på * / offentlig tomrom (int rad, int col) assert (! playerXTurn); assert (mellomrom [rad] [col] == 0); mellomrom [rad] [col] = 2; playerXTurn = true; / ** Returnerer sant hvis et mellomrom på rutenettet er tomt (nei Xs eller Os) * * @param rad * @param col * @return * / offentlig boolean isSpaceEmpty (int rad, int col) return ] [col] == 0); / ** Returnerer sant hvis en plass på rutenettet er en X. * * @param rad * @param col * @return * / offentlig boolean isSpaceX (int rad, int col) retur (mellomrom [rad] == 1); / ** Returnerer sant hvis en plass på rutenettet er en O. * * @param rad * @param col * @return * / offentlig boolean isSpaceO (int rad, int col) retur (mellomrom [rad] == 2); / ** Returnerer sant hvis X-spilleren vant spillet. Det vil si at hvis * X-spilleren har fullført en linje på tre Xs. * * @return * / offentlig booleansk harPlayerXWon () // Kontroller rader hvis (mellomrom [0] [0] == 1 && mellomrom [0] [1] == 1 && mellomrom [0] [2] == 1 ) returnere sant; hvis (mellomrom [1] [0] == 1 && mellomrom [1] [1] == 1 && mellomrom [1] [2] == 1) returner sann; hvis (mellomrom [2] [0] == 1 && mellomrom [2] [1] == 1 && mellomrom [2] [2] == 1) returner sann; // Sjekk kolonner hvis (mellomrom [0] [0] == 1 && mellomrom [1] [0] == 1 && mellomrom [2] [0] == 1) returner sann; hvis (mellomrom [0] [1] == 1 && mellomrom [1] [1] == 1 && mellomrom [2] [1] == 1) returner sann; hvis (mellomrom [0] [2] == 1 && mellomrom [1] [2] == 1 && mellomrom [2] [2] == 1) returner sann; // Kontroller diagonaler hvis (mellomrom [0] [0] == 1 && mellomrom [1] [1] == 1 && mellomrom [2] [2] == 1) returner sann; hvis (mellomrom [0] [2] == 1 && mellomrom [1] [1] == 1 && mellomrom [2] [0] == 1) returner sann; // Ellers er det ingen returlinje på linjen. / ** Returnerer sant hvis O-spilleren vant spillet. Det vil si at hvis * O-spilleren har fullført en linje på tre Os. * * @return * / offentlig boolsk harPlayerOWon () // Kontroller rader hvis (mellomrom [0] [0] == 2 && mellomrom [0] [1] == 2 && mellomrom [0] [2] == 2 ) returnere sant; hvis (mellomrom [1] [0] == 2 && mellomrom [1] [1] == 2 && mellomrom [1] [2] == 2) returner sann; hvis (mellomrom [2] [0] == 2 && mellomrom [2] [1] == 2 && mellomrom [2] [2] == 2) returner sant; // Sjekk kolonner hvis (mellomrom [0] [0] == 2 && mellomrom [1] [0] == 2 && mellomrom [2] [0] == 2) returner sann; hvis (mellomrom [0] [1] == 2 && mellomrom [1] [1] == 2 && mellomrom [2] [1] == 2) returner sann; hvis (mellomrom [0] [2] == 2 && mellomrom [1] [2] == 2 && mellomrom [2] [2] == 2) returner sann; // Kontroller diagonaler hvis (mellomrom [0] [0] == 2 && mellomrom [1] [1] == 2 && mellomrom [2] [2] == 2) returner sann; hvis (mellomrom [0] [2] == 2 && mellomrom [1] [1] == 2 && mellomrom [2] [0] == 2) returner sann; // Ellers er det ingen returlinje på linjen. / ** Returnerer sant hvis alle mellomrom er fylt eller en av spillerne har * vant spillet. * * @return * / offentlig boolean isGameOver () hvis (harPlayerXWon () || harPlayerOWon ()) returnere true; // Sjekk om alle mellomrom er fylt. Hvis man ikke er spillet, er ikke over for (int rad = 0; rad < 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;
De Kommando
mønster er et mønster som vanligvis brukes med brukergrensesnitt for å skille mellom handlinger utført av knapper, menyer eller andre widgets fra definisjonene for brukergrensesnittkoden for disse objektene. Dette konseptet med separerende handlingskode kan brukes til å spore alle endringer som skjer med tilstanden til et spill, og du kan bruke denne informasjonen til å reversere endringene.
Den mest grunnleggende versjonen av Kommando
mønster er følgende grensesnitt:
offentlige grensesnitt kommando public void execute ();
Noen handling som tas av programmet som endrer tilstanden til spillet - for eksempel å plassere en X i en bestemt plass - vil implementere Kommando
grensesnitt. Når handlingen er tatt, vil henrette()
Metoden kalles.
Nå har du sannsynligvis lagt merke til at dette grensesnittet ikke gir mulighet til å angre handlinger. alt det gjør er å ta spillet fra en stat til en annen. Følgende forbedringer vil tillate implementeringshandlinger å tilby angre evnen.
offentlige grensesnitt kommando public void execute (); offentlig ugyldig angre ();
Målet når man implementerer en Kommando
vil være å ha angre ()
metode reversere alle handlinger tatt av henrette
metode. Som en følge av dette henrette()
Metoden vil også kunne gi muligheten til å gjenta en handling.
Det er den grunnleggende ideen. Det blir tydeligere da vi implementerer bestemte kommandoer for dette spillet.
For å legge til en angrepsfunksjon, vil du opprette en Command
klasse. De Command
er ansvarlig for å spore, utføre og angre Kommando
implementeringer.
(Husk at Kommando
grensesnitt gir metoder for å gjøre endringer fra en tilstand av et program til et annet og også reversere det.)
offentlig klasse CommandManager private Command lastCommand; offentlig CommandManager () public void executeCommand (Command c) c.execute (); siste kommando = c; ...
For å utføre en Kommando
, de Command
er bestått a Kommando
eksempel, og det vil utføre Kommando
og lagre deretter den nylig utførte Kommando
for senere referanse.
Legge til å angre funksjonen til Command
krever bare å fortelle det å angre det siste Kommando
som henrettet.
offentlig boolean erUndoAvailable () return lastCommand! = null; offentlig ugyldig angre () assert (lastCommand! = null); lastCommand.undo (); lastCommand = null;
Denne koden er alt som kreves for å være funksjonell Command
. For at det skal fungere ordentlig, må du opprette noen implementeringer av Kommando
grensesnitt.
Kommando
InterfaceMålet med Kommando
mønster for denne opplæringen er å flytte noen kode som endrer tilstanden til tic-tac-toe-spillet i en Kommando
forekomst. Nemlig koden i metodene placeX ()
og placeO ()
er det du vil endre.
Inne i TicTacToeModel
klasse, legg til to nye indre klasser kalt PlaceXCommand
og PlaceOCommand
, henholdsvis, som hver implementerer Kommando
grensesnitt.
offentlig klasse TicTacToeModel ... privat klasse PlaceXCommand implementerer Command public void execute () ... offentlig ugyldig angre () ... privat klasse PlaceCommand implementerer Command public void execute () ... offentlig ugyldig ugyldig () ...
Jobben av a Kommando
implementering er å lagre en stat og ha logikk til enten overgang til en ny stat som følge av utførelsen av Kommando
eller å overgå tilbake til opprinnelig tilstand før Kommando
ble henrettet. Det er to enkle måter å oppnå denne oppgaven på.
henrette()
kalles og angir spillets nåværende tilstand til den lagrede tidligere tilstanden når angre ()
er kalt.henrette()
eller angre ()
er kalt.// Alternativ 1: Lagre forrige og neste tilstander privat klasse PlaceXCommand implementerer kommando privat TicTacToeModel-modell; // private int [] [] previousGridState; privat boolsk tidligereTurnState; privat int [] [] nextGridState; privat booleansk nextTurnState; // privat PlaceXCommand (TicTacToeModel modell, int rad, int col) this.model = model; // previousTurnState = model.playerXTurn; // Kopier hele rutenettet for begge statene previousGridState = new int [3] [3]; nextGridState = new int [3] [3]; for (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;
Det første alternativet er litt sløsing, men det betyr ikke at det er dårlig design. Koden er enkel og med mindre statlig informasjon er ekstremt stor, vil mengden avfall ikke være noe å bekymre seg for.
Du vil se at i tilfelle denne opplæringen er det andre alternativet bedre, men denne tilnærmingen vil ikke alltid være best for hvert program. Oftere enn ikke, det andre alternativet vil imidlertid være veien å gå.
// Alternativ 2: Lagrer bare endringene mellom tilstandene privat klasse PlaceXCommand implementerer kommandoen private TicTacToeModel-modellen; privat int tidligereValue; privat boolsk forrige tur; privat int rad; privat int col; // privat PlaceXCommand (TicTacToeModel modell, int rad, int col) this.model = model; this.row = rad; this.col = col; // Kopier den forrige verdien fra rutenettet this.previousValue = model.spaces [rad] [col]; this.previousTurn = model.playerXTurn; // public void execute () model.spaces [rad] [col] = 1; model.playerXTurn = false; // offentlig ugyldig angre () model.spaces [rad] [col] = previousValue; model.playerXTurn = previousTurn;
Det andre alternativet lagrer bare endringene som skjer, i stedet for hele staten. Når det gjelder tic-tac-toe, er det mer effektivt og ikke spesielt mer komplekst å bruke dette alternativet.
De PlaceOCommand
indre klasse er skrevet på en lignende måte - ta en tur til å skrive det selv!
For å gjøre bruk av din Kommando
implementeringer, PlaceXCommand
og PlaceOCommand
, du må endre TicTacToeModel
klasse. Klassen må gjøre bruk av a Command
og den må bruke Kommando
forekomster i stedet for å søke handlinger direkte.
offentlig klasse TicTacToeModel private CommandManager commandManager; // ... // offentlige TicTacToeModel () ... // commandManager = ny CommandManager (); // ... // offentlig tomt stedX (int rad, int kol) assert (playerXTurn); assert (mellomrom [rad] [col] == 0); commandManager.executeCommand (nytt stedXCommand (dette, rad, kol)); // offentlig tomrom sted (int rad, int kol) assert (! playerXTurn); assert (mellomrom [rad] [col] == 0); commandManager.executeCommand (nytt stedOCommand (dette, rad, kol)); // //
De TicTacToeModel
klassen vil fungere akkurat som det gjorde før endringene dine nå, men du kan også avsløre å angre funksjonen. Legg til en angre ()
metode til modellen og også legge til en sjekk metode canUndo
for brukergrensesnittet å bruke på et tidspunkt.
offentlig klasse TicTacToeModel // ... // offentlige boolean canUndo () return commandManager.isUndoAvailable (); // offentlig ugyldig angre () commandManager.undo ();
Du har nå en helt funksjonell tic-tac-toe spillmodell som støtter angre!
Med noen få små modifikasjoner til Command
, Du kan legge til støtte for gjenta operasjoner, samt et ubegrenset antall undos og redos.
Konseptet bak en omformingsfunksjon er stort sett det samme som en angrepsfunksjon. I tillegg til å lagre sist Kommando
utført, lagrer du også sist Kommando
det var utelatt. Du lagrer det Kommando
når et angrepet kalles og slettes når a Kommando
er utført.
offentlig klasse CommandManager Private Command lastCommandUndone; ... public void executeCommand (Command c) c.execute (); siste kommando = c; lastCommandUndone = null; offentlig ugyldig angre () assert (lastCommand! = null); lastCommand.undo (); lastCommandUndone = lastCommand; lastCommand = null; offentlige boolean isRedoAvailable () return lastCommandUndone! = null; offentlig ugyldig gjeld () assert (lastCommandUndone! = null); lastCommandUndone.execute (); lastCommand = lastCommandUndone; lastCommandUndone = null;
Å legge til i flere undos og redos er et spørsmål om lagring av en stable av uopprettelige og omsettelige handlinger. Når en ny handling utføres, legges den til å angre stabelen, og gjenopprettingsstakken slettes. Når en handling er slettet, legges den til gjenta stakken og fjernes fra stryk av stakkord. Når en handling er omgjort, blir den fjernet fra gjenta-stakken og lagt til for å angre stakken.
Ovennevnte bilde viser et eksempel på stablene i aksjon. Gjenopprettingsstakken har to elementer fra kommandoer som allerede er utelatt. Når nye kommandoer, PlaceX (0,0)
og PlaceO (0,1)
, blir utført, gjenta stakken blir ryddet og de legges til å angre stakken. Når en PlaceO (0,1)
er utelatt, blir den fjernet fra toppen av å angre stakken og plassert på gjenta stakken.
Slik ser det ut i kode:
offentlig klasse CommandManager private Stackundos = ny Stack (); privat stabling redos = ny Stack (); Offentlig ugyldig utfør kommandoen (Kommando c) c.execute (); undos.push (c); redos.clear (); offentlige boolean erUndoAvailable () return! undos.empty (); offentlig ugyldig angre () assert (! undos.empty ()); Kommando kommando = undos.pop (); command.undo (); redos.push (kommando); offentlig boolsk isRedoAvailable () return! redos.empty (); offentlig ugyldig omgang () assert (! redos.empty ()); Kommando kommando = redos.pop (); command.execute (); undos.push (kommando);
Nå har du en tic-tac-toe spillmodell som kan angre handlinger helt tilbake til begynnelsen av spillet og gjenta dem igjen.
Hvis du vil se hvordan alt dette passer sammen, ta den endelige kildedownloaden, som inneholder den fullførte koden fra denne opplæringen.
Du har kanskje lagt merke til at den endelige Command
du skrev vil jobbe for noen Kommando
implementeringer. Dette betyr at du kan kode opp en Command
i favorittspråket ditt, opprett noen forekomster av Kommando
grensesnitt, og har et fullt system forberedt for å angre / gjenta. Å angre funksjonen kan være en fin måte å tillate brukere å utforske spillet ditt og gjøre feil uten å være forpliktet til dårlige beslutninger.
Takk for at du har interesse for denne opplæringen!
Som noen ytterligere mat for tanker, vurder følgende: Kommando
mønster sammen med Command
lar deg spore alle statlige endringer under utførelsen av spillet ditt. Hvis du lagrer denne informasjonen, kan du opprette replays av utførelsen av programmet.