Save the game state on pause (closes #1)

This commit is contained in:
Lonami Exo 2017-02-09 20:34:23 +01:00
parent 67d74936d2
commit 8a103f9533
4 changed files with 104 additions and 21 deletions

BIN
android/assets/.klooni.sav Normal file

Binary file not shown.

View file

@ -5,9 +5,14 @@ import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.Screen; import com.badlogic.gdx.Screen;
import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import io.github.lonamiwebs.klooni.Klooni; import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.game.BaseScorer; import io.github.lonamiwebs.klooni.game.BaseScorer;
import io.github.lonamiwebs.klooni.game.Board; import io.github.lonamiwebs.klooni.game.Board;
@ -16,9 +21,11 @@ import io.github.lonamiwebs.klooni.game.Piece;
import io.github.lonamiwebs.klooni.game.PieceHolder; import io.github.lonamiwebs.klooni.game.PieceHolder;
import io.github.lonamiwebs.klooni.game.Scorer; import io.github.lonamiwebs.klooni.game.Scorer;
import io.github.lonamiwebs.klooni.game.TimeScorer; import io.github.lonamiwebs.klooni.game.TimeScorer;
import io.github.lonamiwebs.klooni.serializer.BinSerializable;
import io.github.lonamiwebs.klooni.serializer.BinSerializer;
// Main game screen. Here the board, piece holder and score are shown // Main game screen. Here the board, piece holder and score are shown
class GameScreen implements Screen, InputProcessor { class GameScreen implements Screen, InputProcessor, BinSerializable {
//region Members //region Members
@ -48,11 +55,18 @@ class GameScreen implements Screen, InputProcessor {
final static int GAME_MODE_SCORE = 0; final static int GAME_MODE_SCORE = 0;
final static int GAME_MODE_TIME = 1; final static int GAME_MODE_TIME = 1;
private final static String SAVE_DAT_FILENAME = ".klooni.sav";
//endregion //endregion
//region Constructor //region Constructor
// Load any previously saved file by default
GameScreen(final Klooni game, final int gameMode) { GameScreen(final Klooni game, final int gameMode) {
this(game, gameMode, true);
}
GameScreen(final Klooni game, final int gameMode, final boolean loadSave) {
batch = new SpriteBatch(); batch = new SpriteBatch();
this.gameMode = gameMode; this.gameMode = gameMode;
@ -73,6 +87,15 @@ class GameScreen implements Screen, InputProcessor {
pauseMenu = new PauseMenuStage(layout, game, scorer, gameMode); pauseMenu = new PauseMenuStage(layout, game, scorer, gameMode);
gameOverSound = Gdx.audio.newSound(Gdx.files.internal("sound/game_over.mp3")); gameOverSound = Gdx.audio.newSound(Gdx.files.internal("sound/game_over.mp3"));
if (loadSave) {
// The user might have a previous game. If this is the case, load it
tryLoad();
}
else {
// Ensure that there is no old save, we don't want to load it, thus delete it
deleteSave();
}
} }
//endregion //endregion
@ -90,12 +113,15 @@ class GameScreen implements Screen, InputProcessor {
private void doGameOver() { private void doGameOver() {
if (!gameOverDone) { if (!gameOverDone) {
gameOverDone = true;
holder.enabled = false; holder.enabled = false;
pauseMenu.show(true); pauseMenu.show(true);
if (Klooni.soundsEnabled()) if (Klooni.soundsEnabled())
gameOverSound.play(); gameOverSound.play();
gameOverDone = true; // The user should not be able to return to the game if its game over
deleteSave();
} }
} }
@ -111,6 +137,11 @@ class GameScreen implements Screen, InputProcessor {
Gdx.input.setInputProcessor(this); Gdx.input.setInputProcessor(this);
} }
private void showPauseMenu() {
pauseMenu.show(false);
save(); // Save the state, the user might leave the game
}
@Override @Override
public void render(float delta) { public void render(float delta) {
Klooni.theme.glClearBackground(); Klooni.theme.glClearBackground();
@ -147,7 +178,7 @@ class GameScreen implements Screen, InputProcessor {
@Override @Override
public boolean keyUp(int keycode) { public boolean keyUp(int keycode) {
if (keycode == Input.Keys.P || keycode == Input.Keys.BACK) // Pause if (keycode == Input.Keys.P || keycode == Input.Keys.BACK) // Pause
pauseMenu.show(false); showPauseMenu();
return false; return false;
} }
@ -218,4 +249,60 @@ class GameScreen implements Screen, InputProcessor {
} }
//endregion //endregion
//region Saving and loading
private void save() {
// Only save if the game is not over
if (gameOverDone)
return;
final FileHandle handle = Gdx.files.local(SAVE_DAT_FILENAME);
try {
BinSerializer.serialize(this, handle.write(false));
} catch (IOException e) {
// Should never happen but what else could be done if the game wasn't saved?
e.printStackTrace();
}
}
static void deleteSave() {
final FileHandle handle = Gdx.files.local(SAVE_DAT_FILENAME);
if (handle.exists())
handle.delete();
}
private boolean tryLoad() {
// Load will fail if the game modes differ, but that's okay
final FileHandle handle = Gdx.files.local(SAVE_DAT_FILENAME);
if (handle.exists()) {
try {
BinSerializer.deserialize(this, handle.read());
return true;
} catch (IOException ignored) { }
}
return false;
}
@Override
public void write(DataOutputStream out) throws IOException {
// gameMode, board, holder, scorer
out.writeInt(gameMode);
board.write(out);
holder.write(out);
scorer.write(out);
}
@Override
public void read(DataInputStream in) throws IOException {
int savedGameMode = in.readInt();
if (savedGameMode != gameMode)
throw new IOException("A different game mode was saved. Cannot load the save data.");
board.read(in);
holder.read(in);
scorer.read(in);
}
//endregion
} }

View file

@ -71,7 +71,8 @@ class PauseMenuStage extends Stage {
replayButton.addListener(new ChangeListener() { replayButton.addListener(new ChangeListener() {
@Override @Override
public void changed(ChangeEvent event, Actor actor) { public void changed(ChangeEvent event, Actor actor) {
game.setScreen(new GameScreen(game, gameMode)); // false, don't load the saved game state; we do want to replay
game.setScreen(new GameScreen(game, gameMode, false));
dispose(); dispose();
} }
}); });

View file

@ -1,7 +1,9 @@
package io.github.lonamiwebs.klooni.serializer; package io.github.lonamiwebs.klooni.serializer;
import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public class BinSerializer { public class BinSerializer {
@ -15,24 +17,17 @@ public class BinSerializer {
out.close(); out.close();
} catch (IOException ignored) { } } catch (IOException ignored) { }
} }
// todo uhm maybe make the classes serializable? like telethon, kinda, idk, bye.
/*
DataInputStream d = new DataInputStream(new FileInputStream("test.txt"));
DataOutputStream out = new DataOutputStream(new FileOutputStream("test1.txt"));
String count;
d.readFully();
while((count = d.readLine()) != null){
String u = count.toUpperCase();
System.out.println(u);
out.writeBytes(u + " ,");
} }
d.close();
out.close();
*/
public static void deserialize(final BinSerializable serializable, final InputStream input)
throws IOException {
DataInputStream in = new DataInputStream(input);
try {
serializable.read(in);
} finally {
try {
in.close();
} catch (IOException ignored) { }
}
} }
} }