package io.github.lonamiwebs.klooni.screens; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.Screen; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.GL20; 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.game.BaseScorer; import io.github.lonamiwebs.klooni.game.Board; import io.github.lonamiwebs.klooni.game.BonusParticleHandler; import io.github.lonamiwebs.klooni.game.GameLayout; import io.github.lonamiwebs.klooni.game.Piece; import io.github.lonamiwebs.klooni.game.PieceHolder; import io.github.lonamiwebs.klooni.game.Scorer; 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 class GameScreen implements Screen, InputProcessor, BinSerializable { //region Members private final BaseScorer scorer; private final BonusParticleHandler bonusParticleHandler; private final Board board; private final PieceHolder holder; private final SpriteBatch batch; private final Sound gameOverSound; private final PauseMenuStage pauseMenu; // TODO Perhaps make an abstract base class for the game screen and game modes // by implementing different "isGameOver" etc. logic instead using an integer? private final int gameMode; private boolean gameOverDone; // The last score that was saved when adding the money. // We use this so we don't add the same old score to the money twice, // but rather subtract it from the current score and then update it // with the current score to get the "increase" of money score. private int savedMoneyScore; //endregion //region Static members private final static int BOARD_SIZE = 10; private final static int HOLDER_PIECE_COUNT = 3; final static int GAME_MODE_SCORE = 0; final static int GAME_MODE_TIME = 1; private final static String SAVE_DAT_FILENAME = ".klooni.sav"; //endregion //region Constructor // Load any previously saved file by default GameScreen(final Klooni game, final int gameMode) { this(game, gameMode, true); } GameScreen(final Klooni game, final int gameMode, final boolean loadSave) { batch = new SpriteBatch(); this.gameMode = gameMode; final GameLayout layout = new GameLayout(); switch (gameMode) { case GAME_MODE_SCORE: scorer = new Scorer(game, layout); break; case GAME_MODE_TIME: scorer = new TimeScorer(game, layout); break; default: throw new RuntimeException("Unknown game mode given: "+gameMode); } board = new Board(layout, BOARD_SIZE); holder = new PieceHolder(layout, board, HOLDER_PIECE_COUNT, board.cellSize); pauseMenu = new PauseMenuStage(layout, game, scorer, gameMode); bonusParticleHandler = new BonusParticleHandler(game); gameOverSound = Gdx.audio.newSound(Gdx.files.internal("sound/game_over.mp3")); if (gameMode == GAME_MODE_SCORE) { 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 //region Private methods // If no piece can be put, then it is considered to be game over private boolean isGameOver() { for (Piece piece : holder.getAvailablePieces()) if (board.canPutPiece(piece)) return false; return true; } private void doGameOver(final String gameOverReason) { if (!gameOverDone) { gameOverDone = true; saveMoney(); holder.enabled = false; pauseMenu.showGameOver(gameOverReason, scorer instanceof TimeScorer); if (Klooni.soundsEnabled()) gameOverSound.play(); // The user should not be able to return to the game if its game over if (gameMode == GAME_MODE_SCORE) deleteSave(); } } //endregion //region Screen @Override public void show() { if (pauseMenu.isShown()) // Will happen if we go to the customize menu Gdx.input.setInputProcessor(pauseMenu); else Gdx.input.setInputProcessor(this); } // Save the state, the user might leave the game in any of the following 2 methods private void showPauseMenu() { saveMoney(); pauseMenu.show(); save(); } @Override public void pause() { save(); } @Override public void render(float delta) { Klooni.theme.glClearBackground(); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); if (scorer.isGameOver() && !pauseMenu.isShown()) { // TODO A bit hardcoded (timeOver = scorer instanceof TimeScorer) // Perhaps have a better mode to pass the required texture to overlay doGameOver(scorer.gameOverReason()); } batch.begin(); scorer.draw(batch); board.draw(batch); holder.update(); holder.draw(batch); bonusParticleHandler.run(batch); batch.end(); if (pauseMenu.isShown() || pauseMenu.isHiding()) { pauseMenu.act(delta); pauseMenu.draw(); } } @Override public void dispose() { pauseMenu.dispose(); } //endregion //region Input @Override public boolean keyUp(int keycode) { if (keycode == Input.Keys.P || keycode == Input.Keys.BACK) // Pause showPauseMenu(); return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { return holder.pickPiece(); } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { PieceHolder.DropResult result = holder.dropPiece(); if (!result.dropped) return false; if (result.onBoard) { scorer.addPieceScore(result.area); int bonus = scorer.addBoardScore(board.clearComplete(), board.cellCount); if (bonus > 0) bonusParticleHandler.addBonus(result.pieceCenter, bonus); // After the piece was put, check if it's game over if (isGameOver()) { doGameOver("no moves left"); } } return true; } //endregion //region Unused methods @Override public void resize(int width, int height) { } @Override public void resume() { } @Override public void hide() { /* Hide can only be called if the menu was shown. Place logic there. */ } @Override public boolean keyDown(int keycode) { return false; } @Override public boolean keyTyped(char character) { return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } @Override public boolean mouseMoved(int screenX, int screenY) { return false; } @Override public boolean scrolled(int amount) { return false; } //endregion //region Saving and loading private void saveMoney() { // Calculate new money since the previous saving int nowScore = scorer.getCurrentScore(); int newMoneyScore = nowScore - savedMoneyScore; savedMoneyScore = nowScore; Klooni.addMoneyFromScore(newMoneyScore); } private void save() { // Only save if the game is not over and the game mode is not the time mode. It // makes no sense to save the time game mode since it's supposed to be something quick. // Don't save either if the score is 0, which means the player did nothing. if (gameOverDone || gameMode != GAME_MODE_SCORE || scorer.getCurrentScore() == 0) 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(); } } private void deleteSave() { final FileHandle handle = Gdx.files.local(SAVE_DAT_FILENAME); if (handle.exists()) handle.delete(); } static boolean hasSavedData() { return Gdx.files.local(SAVE_DAT_FILENAME).exists(); } private boolean tryLoad() { final FileHandle handle = Gdx.files.local(SAVE_DAT_FILENAME); if (handle.exists()) { try { BinSerializer.deserialize(this, handle.read()); // No cheating! We need to load the previous money // or it would seem like we earned it on this game savedMoneyScore = scorer.getCurrentScore(); // After it's been loaded, delete the save file deleteSave(); 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 }