zum Inhalt springen
Java lernen durch Ausprobieren!
Benutzer-Werkzeuge
Anmelden
Webseiten-Werkzeuge
Suche
Werkzeuge
Seite anzeigen
Ältere Versionen
Links hierher
Letzte Änderungen
Medien-Manager
Übersicht
Anmelden
>
Letzte Änderungen
Medien-Manager
Übersicht
Zuletzt angesehen:
api:projects:mover:start
====== Mover-Game ====== Das Mover-Game ist dem Spiel [[https://de.wikipedia.org/wiki/Sokoban|Sokoban]] nachempfunden. Ziel des Spiel ist es, alle Kisten an die Zielpositionen zu schieben. <WRAP center round tip 60%> **Nützliche Design-Patterns in diesem Spiel:** * Verwendung von String-Feldern zum Speichern der Levels (siehe die Klassen ''Levels'' und ''Level'') * Speicherung der vergangenen Spielzüge, um **undo** und **redo** von Spielzügen zu ermöglichen * Animation der Bewegung der Spielerfigur und der Kisten durch Setzen der destination-Position und automatisches "Daraufzubewegen" (siehe die act-Methode der Klasse ''MyTile'') </WRAP> <HTML> <div class="java-online" style="height: 500px; width: 100%" data-java-online="{'withBottomPanel': true, 'id': 'Mover-Game'}"> <script type="text/plain" title="Mover-Main.java"> SystemTools.setSpeed(-1); new World(800, 800); new MoverMain(); /** * Hauptklasse des Mover-Spiels * * Die Klasse ist eine Unterklasse von Actor. Sie erbt damit Methoden zum Abfragen der Tastatur. * Zudem wird ihre Methode act 30-mal pro Sekunde aufgerufen. */ class MoverMain extends Actor { Player player; // Spielfigur Level currentLevel; // Aktuelles Level int currentLevelNumber; // Nummber des aktuellen Levels; 0 => Startbildschirm // Textfelder Text bigText; Text smallText; Text levelIndicator; // Wann wurde zuletzt eine Taste gedrückt (Zeitpunkt in Millisekunden)? int lastTimeKeyPressed = 0; /** * Aktueller Programmzustand; besitzt einen dieser Werte: * "beforeGame", "playing", "betweenLevels" */ String state; /** * Laufrichtung der Spielerfigur auf dem Startbildschirm */ boolean playerMovesLeft = false; /** * Klasse zum Speichern der vergangenen Spielzüge; Sie ermöglicht das undo/redo der Spielzüge. */ History history = new History(); public MoverMain() { player = new Player(); bigText = new Text(400, 200, 80, ""); bigText.setFillColor(Color.white); bigText.setAlignment(Alignment.center); bigText.setBorderColor(Color.darkblue); smallText = new Text(400, 400, 50, ""); smallText.setFillColor(Color.white); smallText.setAlignment(Alignment.center); smallText.setBorderColor(Color.darkblue); levelIndicator = new Text(780, 20, 50, ""); levelIndicator.setFillColor(Color.white); levelIndicator.setAlignment(Alignment.right); levelIndicator.setBorderColor(Color.darkblue); // Die Textfelder sollen sich nicht mit der Spielfigur mitbewegen (scrollen), sondern immer am selben Platz bleiben: bigText.setStatic(true); smallText.setStatic(true); levelIndicator.setStatic(true); currentLevelNumber = 0; setState("beforeGame"); // Anzeige der Tastenbelegung: println("User Arrow-Keys to move.", Color.lightblue); println(); println("n: Next level"); println("p: previous level"); println("u: undo last step"); println("r: redo last step"); } /** * Diese Methode wird bei jeder Zustandsänderung aufgerufen */ void setState(String newState) { switch(newState) { case "beforeGame" : showLevel(0); // Zeigt das künstliche 0-te Level mit dem Schriftzug "Mover" bigText.setVisible(true); smallText.setVisible(true); bigText.setText("Mover-Game"); smallText.setText("Press space to start."); break; case "playing" : history.clear(); // Löscht die gespeicherten Spielzüge // Anzeige des nächsten Levels: this.currentLevelNumber++; showLevel(this.currentLevelNumber); bigText.setVisible(false); smallText.setVisible(false); break; case "betweenLevels" : bigText.setVisible(true); smallText.setVisible(true); bigText.setText("Level " + currentLevelNumber + " done!"); smallText.setText("Press space to start next level."); break; } state = newState; } /** * Anzeige des Levels mit der gegebenen Levelnummer */ void showLevel(int level) { // aktuell sichtbares Level zerstören if(currentLevel != null) { currentLevel.destroy(); } // neues Level instanzieren... currentLevel = new Level(Levels.levels[level], this); // ... und anzeigen. currentLevel.render(); // Texte und Spielerfigur wieder nach vorne bringen, da sie sonst vom neuen Level verdeckt werden: bigText.bringToFront(); smallText.bringToFront(); levelIndicator.bringToFront(); player.bringToFront(); // Nummer des Levels anzeigen: if(currentLevelNumber != 0) { levelIndicator.setText("Level " + this.currentLevelNumber); } } /** * Setzt den Player - ohne Animation - auf die angegebene Position im Kachelfeld */ void setPlayerPosition(int column, int row) { player.setPosition(column, row); } /** * Bewegt den Player animiert zur angegebenen Kachelposition hin */ void movePlayer(int column, int row) { player.moveTo(column, row); } /** * Diese Methode wird 30-mal pro Sekunde aufgerufen. Sie analysiert, welche Taste gerade gedrückt ist * und sorgt für entsprechende Aktionen. */ public void act() { /** * Tastendrucke nur auswerten, wenn lange genug keine Taste gedrückt wurde. * 64.0 / player.v * 30.0 ist die Zeit in ms, die die Spielerfigur benötigt, * um eine Kachel weit in x- bzw. y-Richtung zu gehen. */ if(currentLevel != null && state == "playing" && (System.currentTimeMillis() - lastTimeKeyPressed > 64.0 / player.v * 30.0 || !player.isCurrentlyMoving) ) { lastTimeKeyPressed = System.currentTimeMillis(); // Pfeiltasten gedrückt? if(isKeyDown(Key.ArrowRight)) { currentLevel.movePlayerTo(player.column, player.row, 1, 0); } else if(isKeyDown(Key.ArrowLeft)) { currentLevel.movePlayerTo(player.column, player.row, -1, 0); } else if(isKeyDown(Key.ArrowUp)) { currentLevel.movePlayerTo(player.column, player.row, 0, -1); } else if(isKeyDown(Key.ArrowDown)) { currentLevel.movePlayerTo(player.column, player.row, 0, 1); } } // Leertaste gedrückt? if(isKeyDown(" ")) { if(state == "beforeGame") { setState("playing"); } else if(state == "betweenLevels") { setState("playing"); } } // Hin- und Herbewegen der Spielerfigur auf dem Startbildschirm: if(state == "beforeGame" && !player.isCurrentlyMoving) { if(playerMovesLeft) { movePlayer(30, 1); } else { movePlayer(1, 1); } playerMovesLeft = !playerMovesLeft; } /** * Dieser Methodenaufruf verschiebt das Welt-Koordinatensystem so, dass sich die Spielerfigur * im sichtbaren Bereich der Grafikausgabe (sogar innerhalb eines 200 Pixel breiten Rahmens) * befindet. */ player.getWorld().follow(player, 200, -10000, 10000, -10000, 10000); } /** * Diese Methode wird vom System immer dann aufgerufen, wenn eine Taste losgelassen wurde. * Wir benutzen sie, um auf das Drücken der Tasten n, p, u und r zu reagieren. */ public void onKeyTyped(String key) { if(state == "beforeGame") { return; } switch(key) { case "n" : // next level if(currentLevelNumber < Levels.levels.length - 1) { setState("playing"); } break; case "p" : // previous level currentLevelNumber -= 2; if(currentLevelNumber < 0) { currentLevelNumber = 0; } setState("playing"); break; case "u" : // undo last step if(state == "playing") { history.undo(currentLevel); } break; case "r" : // redo step if(state == "playing") { history.redo(currentLevel); } break; } } } </script> <script type="text/plain" title="Tile, Box, Player.java"> class MyTile extends Sprite { // Position im Tile-Raster: int column; // Wievieltes Tile von links her (0 bedeutet: ganz links) int row; // Wievieltes Tile von obern her (0 bedeutet: ganz oben) /** * Solange sich die Position (destinationX/destinationY) von der aktuellen Position * (getCenterX()/getCenterY()) unterscheidet, bewegt sich das Tile mit der Geschwindigkeit v in * beiden Richtungen (x bzw. y) darauf zu. Die Methode moveTo setzt die Werte von destinationX bzw. * destinationY und startet den periodischen Aufruf der act-Methode. Dadurch kann * die Bewegung animiert dargestellt werden. * * Solange das Tilesprite noch nicht bei (destinationX/destinationY) angekommen ist, ist * isCurrentlyMoving == true. */ double destinationX; double destinationY; double v = 15; boolean isCurrentlyMoving = false; /** * isMirrored == true genau dann, wenn das Tile-Sprite in X-Richtung gespiegelt dargestellt wird. */ boolean isMirrored = false; /** * Erzeugt ein neues Tile-Sprite an der Position (column/row) im Tile-Raster. */ public MyTile(int column, int row, int spriteIndex) { super(columnToX(column), rowToY(row), SpriteLibrary.Soko, spriteIndex); this.column = column; this.row = row; this.stopActing(); } /** * Wandelt die Spaltenposition im Tile-Raster in die X-Koordinate um. */ public double columnToX(int column) { return 64 * column; } /** * Wandelt die Zeilenposition im Tile-Raster in die X-Koordinate um. */ public double rowToY(int row) { return 64 * row; } /** * Die act-Methode wird vom System 30-mal in der Sekunde aufgerufen. * Sie wird genutzt, um die Bewegung des Tiles animiert darzustellen. Ausgangspunkt * für die Animation ist immer der Aufruf der Methode moveTo (s.u.). */ public void act() { // Entfernung zum Ziel in x- bzw. y-Richtung: double dx = destinationX - this.getCenterX(); double dy = destinationY - this.getCenterY(); boolean done = true; // Falls das Ziel in x-Richtung noch nicht erreicht ist... if(Math.abs(dx) > 0.1) { // Gehe höchstens um v darauf zu: move(Math.min(Math.abs(dx), v) * Math.signum(dx), 0); done = false; } // Falls das Ziel in y-Richtung noch nicht erreicht ist... if(Math.abs(dy) > 0.1) { // Gehe höchstens um v darauf zu: move(0, Math.min(Math.abs(dy), v) * Math.signum(dy)); done = false; } // Falls das Ziel erreicht ist... if(done) { this.stopActing(); // unterbindet weitere Aufrufe der act-Methode isCurrentlyMoving = false; callbackAfterMoving(); // wird in der Unterklasse Box benutzt um herauszufinden, ob die Box auf einem Zielpunkt angekommen ist. } } /** * Wird in der Unterklasse Box benutzt um herauszufinden, ob die Box auf einem Zielpunkt angekommen ist. */ void callbackAfterMoving() { } /** * Sorgt dafür, dass das Tile langsam zur Tile-Position (column/row) bewegt wird. */ public void moveTo(int column, int row) { this.destinationX = columnToX(column); this.destinationY = rowToY(row); this.isCurrentlyMoving = true; this.restartActing(); // Sorgt dafür, dass die act-Methode ab jetzt 30-mal pro Sekunde aufgerufen wird // Das Spiel verhält sich im weiteren Verlauf bereits so, als wäre das Tile schon angekommen: this.column = column; this.row = row; } /** * Verschiebt das Tile ohne Animation sofort zur Tile-Position (column, row). */ public void setPosition(int column, int row) { this.stopActing(); moveTo(columnToX(column), rowToY(row)); this.column = column; this.row = row; } } /** * Kiste */ class Box extends MyTile { // isAtDestination == true genau dann, wenn sich die Box auf einem Zielpunkt befindet boolean isAtDestination; public Box(int column, int row, boolean isAtDestination) { super(column, row, 9); setIsAtDestination(isAtDestination); } /** * Stellt die Box in grüner Farbe dar, falls sie sich auf einem Zielpunkt befindet und in * brauner Farbe, falls nicht. */ void setIsAtDestination(boolean isAtDestination) { if(isAtDestination) { setImageIndex(10); } else { setImageIndex(9); } this.isAtDestination = isAtDestination; } boolean isAtDestination() { return isAtDestination; } /** * Falls die Bewegung der Box abgeschlossen ist und sie sich auf einem Zielpunkt befindet, * wird sie grün dargestellt. Diese Methode wird aus der act-Methode heraus nach Abschluss der * animierten Bewegung aufgerufen. */ void callbackAfterMoving() { setIsAtDestination(isAtDestination); } } /** * Spielerfigur */ class Player extends MyTile { public Player() { super(5, 2, 0); } /** * Ergänzend zur Methode moveTo der Basisklasse sorgt diese Methode zusätzlich dafür, dass * je nach Bewegungsrichtung eine in die entsprechende Richtung blickende Spielfigur angezeigt wird. */ public void moveTo(int column, int row) { /** * Bewegungsrichtung nach rechts? */ if(column - this.column > 0) { setImageIndex(0); setMirrorX(false); } /** * Bewegungsrichtung nach links? */ if(column - this.column < 0) { setImageIndex(0); setMirrorX(true); } /** * Bewegungsrichtung nach oben? */ if(row - this.row > 0) { setImageIndex(3); setMirrorX(false); } /** * Bewegungsrichtung nach unten? */ if(row - this.row < 0) { setImageIndex(7); setMirrorX(false); } super.moveTo(column, row); } void setMirrorX(boolean mirrorX) { if(mirrorX != isMirrored) { this.mirrorX(); isMirrored = mirrorX; } } } </script> <script type="text/plain" title="Level.java"> /** * Diese Klasse stellt ein Level dar. */ class Level { /** * Angezeigte Kacheln. Zur Kodierung siehe * http://sokobano.de/wiki/index.php?title=Level_format * Jeder String-Wert des Arrays repräsentiert eine Kachel-Zeile. */ String[] rows; // Referenz aufs Hauptprogramm MoverMain moverMain; /** * Wir speichern alle angezeigten Kacheln (außer Kisten) in dieser Gruppe, damit wir sie * bei Bedarf auf einfache Art gemeinsam vernichten können. */ Group tiles = new Group(); /** * In dieser Gruppe werden alle Kisten des Levels gespeichert. */ Group boxes = new Group(); public Level(String[] rows, MoverMain moverMain) { this.rows = rows; this.moverMain = moverMain; } /** * Zerstört das Level, d.h. alle angezeigten Tiles und Kisten */ public void destroy() { tiles.destroy(); boxes.destroy(); } /** * Erzeugt aus dem String-kodierten Level-Format (siehe http://sokobano.de/wiki/index.php?title=Level_format) * alle benötigten Kachel-Sprites und die Kisten und setzt die Spielerfigur auf die Startposition (Zeichen @). */ public void render() { // Gehe von oben her alle Zeilen durch: for(int line = 0; line < rows.length; line++) { // Hole den String-Wert, in dem die aktuelle Zeile kodiert ist String row = rows[line]; // Gehe von links her alle Spalten durch: for(int column = 0; column < row.length(); column++) { // Zeichen, das die aktuelle Kachel repräsentiert: char tilechar = row.charAt(column); switch(tilechar) { case '#' : // wall tiles.add(new MyTile(column, line, 12)); break; case '.' : // destination tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen tiles.add(new MyTile(column, line, 13)); // Zielfeld-Symbol break; case ' ' : // empty tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen break; case '$' : // box tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen boxes.add(new Box(column, line, false)); break; case '*' : // destination with box tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen tiles.add(new MyTile(column, line, 13)); // Zielfeld-Symbol boxes.add(new Box(column, line, true)); break; case '@' : // man tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen moverMain.setPlayerPosition(column, line); // setze die Spielerfigur auf die Startposition break; case '+' : // man on destination tiles.add(new MyTile(column, line, 11)); // grauer Quadratrahmen tiles.add(new MyTile(column, line, 13)); // Zielfeld-Symbol moverMain.setPlayerPosition(column, line); // setze die Spielerfigur auf die Startposition break; } } } } /** * Gibt genau dann true zurück, wenn sich an der Position (column/row) ein Zielfeld befindet. */ public boolean isDestination(int column, int row) { char code = rows[row].charAt(column); return code == '*' || code == '+' || code == '.'; } /** * Prüft, ob eine Bewegung er Spielerfigur hin zur Position (column + dx/row + dy) zulässig ist. * * Falls "ja", wird die Spielerfigur in animierter Weise dort hinbewegt und ggf. eine davor * befindliche Kiste mitgeschoben. * * column/row: Aktuelle Kachel-Position der Spielerfigur * dx: Um so viele Kacheln soll die Spielerfigur nach rechts bewegt werden. * dy: Um so viele Kacheln soll die Spielerfigur nach unten bewegt werden. */ public void movePlayerTo(int column, int row, int dx, int dy) { // Berechnung der neuen Position int newColumn = column + dx; int newRow = row + dy; // Was befindet sich dort? char code = rows[newRow].charAt(newColumn); if(code == '#') { return; // Wall! => nichts zu tun. } // Befindet sich eine Kiste an der neuen Spielerposition? Box box = getBox(newColumn, newRow); if(box == null) { // nein => einfach Spielerfigur verschieben. moverMain.movePlayer(newColumn, newRow); return; } // Ja! => neue Kistenposition berechnen (nach Verschiebung durch die Spielerfigur) int columnAfterBox = newColumn + dx; int rowAfterBox = newRow + dy; // Was befindet sich an der neuen Kistenposition? char codeAfterBox = rows[rowAfterBox].charAt(columnAfterBox); // Befindet sich dort eine andere Kiste? Box boxAfterBox = getBox(columnAfterBox, rowAfterBox); if(boxAfterBox != null || codeAfterBox == '#') { return; // An der neuen Kistenposition befindet sich eine Box oder eine Mauer => nichts zu tun. } // Befindet sich an der neuen Kistenposition ein Zielfeld? box.isAtDestination = codeAfterBox == '.' || codeAfterBox == '+' || codeAfterBox == '*'; // Kiste animiert zur neuen Kistenposition verschieben box.moveTo(columnAfterBox, rowAfterBox); // Spielzug speichern (für spätere Möglichkeit zum undo/redo) moverMain.history.pushState(new HistoryStep(dx, dy, box, moverMain.player.column, moverMain.player.row)); // Spielerfigur animiert zur neuen Position bewegen moverMain.movePlayer(newColumn, newRow); /** * Falls die Kiste auf eine Zielposition verschoben wurde: Befinden sich vielleicht sogar * schon ALLE Kiste auf einer Zielposition? */ if(box.isAtDestination) { boolean levelCompleted = true; // Annahme: "ja" // Gehe alle Kisten durch... for(Shape b : boxes) { Box box1 = (Box)b; if(!box1.isAtDestination) { // Es wurde eine Kiste gefunden, die sich nicht an einer Zielposition befindet => Das Level ist noch nicht geschafft! levelCompleted = false; break; // Wir brauchen nicht weitersuchen, daher brechen wir die Wiederholung ab. } } // Level geschafft? if(levelCompleted) { moverMain.setState("betweenLevels"); } } } /** * Falls sich an der Kachelposition (column, row) eine Kiste befindet, wird diese zurückgeliefert. * Falls nicht, gibt die Methode den Wert null zurück. */ public Box getBox(int column, int row) { for(Shape b : boxes) { Box box = (Box) b; if(box.column == column && box.row == row) { return box; } } return null; } } </script> <script type="text/plain" title="Levels.java"> /** * Levels aus Jorge Glorias "classic" compilation, siehe https://www.sourcecode.se/sokoban/levels * * Zur Kodierung der Levels siehe http://sokobano.de/wiki/index.php?title=Level_format */ class Levels { static String[][] levels = { { "################################", "#@ #", "# $ $ $$$$$ $ $ $$$$$ $$$ #", "# $$ $$ $ $ $ $ $ $ $ #", "# $ $ $ $ $ $ $ $$$$ $$$ #", "# $ $ $ $ $ $ $ $ $ #", "# $ $ $$$$$ $ $$$$$ $ $ #", "# #", "################################" }, { "##########", "#@ $ .#", "##########" }, { "#################", "##########.######", "########## ######", "########## ######", "#@ $ #", "########## #### #", "########## #### #", "########## #", "#################" }, { "##############", "# # # #", "# # #$$ # + #", "# # # $ # #.#", "# $ #. . #", "# # #$$ # #.#", "# # # # . #", "# # ###### ###", "# #", "########## # #", " # #", " #####" }, // #01", { " ############# ", " #...# @ #...# ", " #. # # # .# ", " # # ", " # ## # ## # ", "###### # ######", "# # # # #", "# $ # $ #", "# $$#$ # $#$$ #", "# # # #", "###############" }, // #02", { " #####", "####### #", "# #", "# $$#$#*# ###", "# #..... #", "# $$#$#.### #", "# # @ #", "#############" }, // #03", { "####", "# @#", "# ######", "# # #.#", "# $#$ * #", "# $.. . #", "# $## . #", "# ##$##", "# $.#", "#########" }, // #04", { " #####", " # #", " # $ ######", "## # $ #", "# ...$#$ #", "# @.. #", "## # ######", " # $ #", " # #", " #####" }, // #05", { " ######", "## .+#", "# **##", "# #.$ #", "# $ $ #", "### ###", " # #", " ####" }, // #06", { " #######", " # . #", "###.$#$ #", "# . .$ #", "#@#.$$*##", "# #", "########" }, //#07", { " ####", " ##. ###", "## . $ #", "# .**@#", "# $ $ #", "##### #", " ####" }, //#08", { "###########", "# @ #", "# $#*#.#$ #", "# * ..* #", "# $# # #$ #", "# *.. * #", "# $#.#*#$ #", "# #", "###########" }, //#09", { " ####", " # #", " # #", " #$ #", " ## $##", " # $ #", " #... #", "##$ ###", "# . #", "# @#", "#####" }, //#10", { "#######", "# .* .#####", "# $@ #", "#### $* #", " # #####", " ####" }, //#11", { " #####", " # @ #", " #$#$###", "## . $ #", "# .*# #", "# . #", "########" }, //#12", { " ####", " ####@ #", "##### . #", "# $*$ * #", "# . ####", "##### #", " ####" }, //#13", { " #####", " ## #", "#### $ #", "# $ ##", "# @$###", "###.$.####", " # ..#", " ########" }, //#14", { " #######", "##### . #", "# $@.## #", "# ## ## #", "## # #", " ### $. # #", " # $ #", " # ## #", " # ######", " ####" }, //#15", { "#####", "#.. ####", "# .$ #", "## $ #", " ###@$ #", " ## #", " ####" }, //#16", { " #######", "##### .. #", "# #", "# $.### #", "###$ ######", " # #@#", " ## $ #", " ###$$ #", " #. . #", " #######" }, //#17", { "######", "#.# ####", "#.... ##", "# # ## #", "# # $ #", "# ##$$ $ #", "# $ #####", "# #@#", "######" }, //#18", { " #########", "###@ .....#", "# $$## # #", "# $ # #", "## $ $## #", " ##### #", " ### #", " ####" }, //#19", { " #####", " #####.#.#", "### ....#", "# $ ## # #", "# $ # #", "## $## #", " # # $@$ #", " ## ##$# #", " # ####", " ######" }, //#20", { "### #####", "#.### #", "# # * $ ##", "# * # .#", "# #$ . #", "#@$ ## ##", "### $ .#", " #######" }, //#21", { " #####", "### .#", "# . $.#", "# $$$#", "###$+ #", " # $##", " ## #", " #. .#", " #####" }, //#22", { "#########", "#. . #@ #", "# *$$$*#", "# $ #", "##.$###.#", " # # ###", " ##.#", " ###" }, //#23", { "######", "# . #", "#.$# ##", "#*$ $ ####", "#@* . ..#", "# ## $ ###", "# $ # #", "### *$.#", " ## #", " #####" }, //#24", { "######", "# .##", "# . *.#", "# $$.##", "### .#", " # $ #", " # #", " ##$$##", " #@ $.#", " ######" }, //#25", { "#########", "# . .$.###", "# ##$. $@#", "# # $ ###", "# ###", "# ##", "######" }, //#26", { " #####", " ## @#", " ## $#", " ## #", " ## $#", " # . ## ##", "## .# #", "#. # $ #", "######## #", " ####" }, //#27", { " ####", " # #", " ### #", "## $#####", "#.. .## #", "# $ $@#", "#### ## ##", " ##*# #", " # #", " #######" }, //#28", { " #######", " #. #", " ##$# #", "#### .## ##", "# $ #", "#.. ## #", "## $## #", " ###$ #####", " #@ #", " ####" }, //#29", { " ###", " ####.#", " #..$ #", " # $$ #", " #.$ #", " # #$.#", "### ##", "# *$*###", "#. . $@#", "########" }, //#30", { " #####", " ## @ #", " # $#.#", "## $ #", "# $ # #", "# .. #", "#######" }, //#31", { "#######", "# #", "# $ $ #", "#...# #", "#.#$$ #", "# * @##", "# ###", "####" }, //#32", { " ####", " ### ###", " # @ #", " #$#.$. #", "### $ ###", "# . .#$#", "# #", "### ###", " ####" }, //#33", { " #######", "## @ ##", "# .* ##", "# $*$ * #", "# #.*#$ #", "## .$ . ##", " ## ##", " ######" }, //#34", { " ########", "########.. #", "# ###@## #", "# $$$$ .. .. #", "# ### ####", "# $$$$ # #", "# # . . #", "# $$$$ # .... #", "# # #", "###############" }, //#35", { "################", "# #", "# $ $$$##$$$ $ #", "### # . # ###", " ## . * ##", " ## #**# ##", " ##....##", " ##.+##", " ####", " ##" }, //#36", { "#########", "# #", "# $ $ $ #", "# ### $ #", "# $.$ # #", "# .*. $ #", "# #*# ###", "#.$.$.#", "#. + .#", "#######" }, //#37", { "#####", "# ##", "# ######", "#.# $## #", "## $ #", "# $ #.. ##", "#@$.### #", "## # ####", " ####" }, //#38", { " #########", " ##.#. $ #", " # # $ #", " # ### #", "##. $ ### #", "# # ### #", "# # $ +# #", "## $# #", " # # #", " ## # .#", " #########" }, //#39", { "#######", "# #", "# .@.#", "## ####", "### . #.#", "# ## ###", "# . $ #", "##.# $$ #", " # # $$ $##", " ## #", " #########" }, //#40", { "########", "# # #####", "# .# $### #", "# ### #@$ # #", "# # $ $ #", "#. $ #### ###", "# . ### # #", "# .# # $ #", "# $ # ##### $ #", "### ## $ #", " #### ## $ #", " #### #", " #... ###", " #... ###", " ######" }, //#41", { " #######", " #.+$ .#", " ##$$$###", " ## # #", "## * #", "#. .####", "###$ #", " # #", " # #", " #####" }, //#42", { " ######", " # .. #", " # *@$###", " ##$# #", " # * #", " # $####", " #. #", " # #", "###$ #", "#. #", "#######" }, //#43", { " ###", "########.#", "# . $@#", "# * $ # $#", "## ###.#", " #.# # ###", "##$ #", "# $ ##", "# . #", "#####" }, //#44", { " ########", " #. .. .#", " # . ##", " ##. ##", "##### $ #", "# $ ###", "#@$$$ ##", "## $ #", " ## #", " ######" }, //#45", { " ####", " # ########", "##.. . $ #", "# $ $ $ $@ #", "# . ######.#", "##### ###" }, //#46", { "#### ####", "# #####@.#", "# * **##", "# $$ #", "#######.$. #", " ### #", " ####" }, //#47", { " #####", " ## ..##", " # *.$ #", " ## #", "### $$ ##", "#. ###", "###*$##", " # #", " #@ #", " ####" }, //#48", { " ######", " #.. ###", " ### $@ #", " # $ #", " ### ####", " #. ####", "###$$ #", "# $ #$ #", "#. . . #", "##########" }, //#49", { "####", "# ##", "# $ ##", "# .#", "##$ $#", " # ##", " ## $@#", " # .$##", " ##$..#", " # .#", " ## .#", " ####" }, //#50", { " ####", " ####### #", "### ## $ #", "# . # $ #", "# # $##", "#### ## $$ #", "#### # $ #", "# #@#", "#. ..######", "#.#####", "#.#", "###" }, //#51", { "#########", "#..## #", "# ..$$ $##", "# .$ #", "#####$.$@#", " # ###", " ####" }, //#52", { " ######", "#### # #", "# @# # $ #", "# ##### ###", "# # $ $ ###", "# $ #", "##$###.# ##", "# # #", "# $.## ..#", "# ##.##.###", "##########" }, //#53", { " #####", " #. +#", "#####$*#", "#. . #", "## $$ #", " ### ###", " #. $ #", " # #", " # $ #", " ### #", " ####" }, //#54", { " ####", " # #", " # #", " # #", " ## #", " ##### #", " ## # ####", " # #$ ...#", "## $ ####", "# $$ #*$####", "# #@ ..#", "############" }, //#55", { " #####", " # .####", " # *. #", " # $ # #", "### $.#", "# ### #", "# $$#.#*##", "### $ +#", " ## ###", " #####" }, //#56", { "###", "#.####", "#.##.####", "#+$ .# .#", "# $* #$ #", "### # #", " # $ $#", " # .#$$ #", " ## #", " # #", " #######" }, //#57", { "##########", "# $ .#", "# $ $ ##", "# $ ####", "# $$# .#", "###. $$.##", " # # .##", " #### .+.#", " ######" }, //#58", { " ######", " ## . ####", " #@$ $ . #", " # * # $# #", " ##* # $ #", "### # $###", "#. *. .#", "############" }, //#59", { " ######", " #.# .##", " #. . .#", " ##$ $$ ##", "## $#. #", "#@$$ # #", "# $. # #", "### #####", " ####" }, //#60", { " ######", " # ..#", " # $ ## #####", " ###.##### #", " # ## #", "#### $## #*###", "#@* $$. #", "#$$ .## ##", "#. #########", "####" }, //#61", { " ####", " ####+ #", " # #$$#", "####$ # .#", "# $ #", "# *$ *.* #", "#. . # #", "##########" }, //#62", { " ######", " # .#", " #*$@##", "##.$####", "# .#", "#$.$$$ #", "# # .#", "#. #.$ #", "########" }, //#63", { " ###", " #.#", "### #", "#. $###", "#.* $@##", "# $.$ #", "#.$$# #", "# . #$ #", "## . ##", " ######" }, //#64", { "#####", "# @ #####", "# $$#.. #", "## #", "#. $#..##", "##$ #$ ##", " # # #", " # # #", " ########" }, //#65", { "########", "# # ##", "# $ #", "## $ #", " #$ ## #", " # #####", " #*$ @ .#", " # ...#", " ########" }, //#66", { "###", "#.########", "# ... #@ #", "# # $$$#", "## # $ #", " #. .# .#", "## # $ #", "# $$ ###", "# # #", "########" }, //#67", { "#########", "# # #", "# $ $#", "# $ # #", "# # * .#", "####$$*##", " #...+#", " ######" }, //#68", { "#####", "#. #", "#. ####", "##. #@ #", " # .#$ #", " # # $##", " # #$ $ #", "##$# # ##", "# .$ ##", "#. ## $ .#", "###########" }, //#69", { " ######", "#### #", "# # #", "#.*$ ####", "#@ # $ #", "#.$# $ #", "#. ### $##", "#. # #$.#", "#.## # #", "### # #", " ####" }, //#70", { "###", "#.#########", "# .#...#", "# $ ###. ##", "## # #", "# $$# $ #", "# $ $###", "# @# $. #", "##### #", " #####" }, //#71", { " #####", " # . ##", "#### ###", "# $.$$ #", "# . $# $ #", "# ## $ #", "#@ .# $###", "### .#", " # ..###", " #####" }, //#72", { "#### #####", "# ### ##", "# $ $*...#", "# $**+$ #", "# #####", "# ##", "######" }, //#73", { " #####", " # . #", " # .$##", " #$. #", " # . #", " #$.###", "### .#", "#@$$ ##", "# $ #", "### #", " #####" }, //#74", { "##########", "# # #", "#.$# #", "#.*.. ####", "#.$## # #", "#. # $ $##", "# $# #", "# #### # #", "# $# # # #", "# @# # #", "#### #####" }, //#75", { "####", "# #####", "# $@$ ###", "# $# $. #", "# #$...#", "# $* #", "#######..#", " # #", " ####" }, //#76", { "#########", "#@*.... #", "# $ *# ##", "#### .#$ #", " ## $ #", " # $#$ #", " # ##", " ### ##", " #####" }, //#77", { " #####", " #### . #", " #. ...#", "###$$## #", "# #@**#", "# $ $ #", "# # ####", "### $ #", " # #", " #####" }, //#78", { " ####", " # ###", " ## * ##", " # $.@#", " ##*$#.. #", "## ##.#", "# $$ ##", "# * #", "#########" }, //#79", { " #####", " # ###", "### $ #", "# # $ .#", "# $##$$@#", "#.. $###", "#####. . #", " # $ #", " ## ..#", " #####" }, //#80", { " #####", " # ###", " # $ #", " # ..# #", "##$#*..#", "# $$#", "# #@ #", "########" } //#81", }; } </script> <script type="text/plain" title="History.java"> /** * Ein Objekt der Klasse HistoryStep speichert genau einen Spielzug. */ class HistoryStep { int playerColumnDelta; // Um wie viele Kacheln wurde die Spielerfigur nach rechts verschoben? int playerRowDelta; // Um wie viele Kakckheln wurde die Spieherfigur nach unten verschoben? Box movedBox; // Falls eine Kiste mitbewegt wurde, wird hier eine Referenz auf das entsprechende Kisten-Objekt gespeichert. // Kachel-Position der Spielerfigur vor diesem Spielzug: int playerColumnBefore; int playerRowBefore; HistoryStep(int playerColumnDelta, int playerRowDelta, Box movedBox, int playerColumnBefore, int playerRowBefore) { this.playerColumnDelta = playerColumnDelta; this.playerRowDelta = playerRowDelta; this.movedBox = movedBox; this.playerColumnBefore = playerColumnBefore; this.playerRowBefore = playerRowBefore; } } /** * Ein Objekt der Klasse History kann bis zu 10000 Spielzüge speichern und verfügt über Methoden * zum Undo bzw. Redo von Spielzügen. */ class History { // Feld zum Speichern der Spielzüge HistoryStep[] steps = new HistoryStep[10000]; // Index des letzten gespeicherten Spielzuges innerhalb des steps-Feldes: int lastStepIndex = -1; /** * Speichert einen Spielzug */ void pushState(HistoryStep state) { // Noch Platz im Feld steps? if(lastStepIndex < steps.length - 1) { lastStepIndex++; steps[lastStepIndex] = state; } // redo history nach dem gespeicherten Spielzug löschen int index = lastStepIndex + 1; while(index < steps.length && steps[index] != null) { steps[index] = null; } } /** * Macht den letzten Spielzug rückgängig */ void undo(Level level) { // Ist überhaupt ein Spielzug gespeichert? if(lastStepIndex >= 0) { // Ja => hole ihn! HistoryStep step = steps[lastStepIndex]; // Spieler zur vorhergehenden Position bewegen: Player player = level.moverMain.player; player.moveTo(step.playerColumnBefore, step.playerRowBefore); if(step.movedBox != null) { int newBoxColumn = step.movedBox.column - step.playerColumnDelta; int newBoxRow = step.movedBox.row - step.playerRowDelta; step.movedBox.moveTo(newBoxColumn, newBoxRow); step.movedBox.isAtDestination = level.isDestination(newBoxColumn, newBoxRow); } /** * Wir verändern nur den Zeiger auf den letzten gespeicherten Spielzug. Der gerade rückgängig * gemachte Spielzug bleibt rechts davon im Feld steps gespeichert für den Fall einer eventuellen * nachfolgenden redo-Anforderung. */ lastStepIndex--; } } /** * Führt ein Redo aus, d.h.: * Falls vorher ein Spielzug rückgängig gemacht wurde, wird dieser dieser wiederholt. * * Sobald der Spieler mit der Spielfigur eine Kiste bewegt, wird die Redo-History gelöscht, siehe * die letzte Anweisung in der Methode pushState. */ void redo(Level level) { if(lastStepIndex < steps.length - 1) { // Hole den Spielzug, der sich im Feld steps HINTER dem letzten gespeicherten Spielzug befindet. // N.B.: Dort befindet sich nur dann etwas, wenn vorher Spielzüge rückgängig gemacht wurden. HistoryStep step = steps[lastStepIndex + 1]; if(step != null) { // Spielzug ausführen: Spielerfigur verschieben Player player = level.moverMain.player; player.moveTo(step.playerColumnBefore + step.playerColumnDelta, step.playerRowBefore + step.playerRowDelta); // Ggf. Kiste bewegen if(step.movedBox != null) { int newBoxColumn = step.movedBox.column + step.playerColumnDelta; int newBoxRow = step.movedBox.row + step.playerRowDelta; step.movedBox.moveTo(newBoxColumn, newBoxRow); step.movedBox.isAtDestination = level.isDestination(newBoxColumn, newBoxRow); } // Der Spielzug bleibt im Feld steps gespeichert. Wir ändern nur den Zeiger auf den letzten gespeicherten Spielzug. lastStepIndex++; } } } /** * Lösche alle gespeicherten Spielzüge aus der History. */ void clear() { lastStepIndex = -1; for(int i = 0; i < steps.length && steps[i] != null; i++) { steps[i] = null; } } } </script> </div> </HTML>
api/projects/mover/start.txt
· Zuletzt geändert:
2024/08/31 10:03
von
127.0.0.1
Seiten-Werkzeuge
Seite anzeigen
Ältere Versionen
Links hierher
Nach oben