From 47301864ebb8db15a0e3bb6ef73bbfd5842c651d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 4 Feb 2017 19:35:02 +0100 Subject: [PATCH] Add time mode scorer --- .../lonamiwebs/klooni/game/GameLayout.java | 19 +++ .../lonamiwebs/klooni/game/TimeScorer.java | 119 ++++++++++++++++++ .../lonamiwebs/klooni/screens/GameScreen.java | 31 ++++- .../klooni/screens/MainMenuScreen.java | 4 +- .../klooni/screens/PauseMenuStage.java | 3 +- 5 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java diff --git a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java index da68f73..be09285 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java +++ b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java @@ -75,6 +75,25 @@ public class GameLayout { area.width * 0.5f - cupSize * 0.5f, area.height); } + void update(TimeScorer scorer) { + float cupSize = Math.min(scoreHeight, scorer.cupTexture.getHeight()); + final Rectangle area = new Rectangle( + marginWidth, pieceHolderHeight + boardHeight, + availableWidth, scoreHeight); + + scorer.cupArea.set( + area.x + area.width * 0.5f - cupSize * 0.5f, area.y, + cupSize, cupSize); + + scorer.timeLeftLabel.setBounds( + area.x, area.y, + area.width * 0.5f - cupSize * 0.5f, area.height); + + scorer.highTimeLabel.setBounds( + area.x + area.width * 0.5f + cupSize * 0.5f, area.y, + area.width * 0.5f - cupSize * 0.5f, area.height); + } + void update(Board board) { // We can't leave our area, so pick the minimum between available // height and width to determine an appropriated cell size diff --git a/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java b/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java new file mode 100644 index 0000000..fa24c50 --- /dev/null +++ b/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java @@ -0,0 +1,119 @@ +package io.github.lonamiwebs.klooni.game; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.TimeUtils; + +import io.github.lonamiwebs.klooni.Klooni; + +public class TimeScorer { + + //region Members + + private final long startTime; + + // Maximum time alive, in seconds + private int maxTimeScore; + + // Indicates where we would die in time. Score adds to this, so we take + // longer to die. To get the "score" we simply calculate `deadTime - startTime` + private long deadTime; + + final Label timeLeftLabel; + final Label highTimeLabel; + + final Texture cupTexture; + final Rectangle cupArea; + + private final Color cupColor; + + private static final long START_TIME = 20 * 1000000000L; + + //endregion + + //region Constructor + + // The board size is required when calculating the score + public TimeScorer(final Klooni game, GameLayout layout) { + startTime = TimeUtils.nanoTime(); + deadTime = startTime + START_TIME; + + cupTexture = new Texture(Gdx.files.internal("ui/cup.png")); + cupColor = Klooni.theme.currentScore.cpy(); + cupArea = new Rectangle(); + + Label.LabelStyle labelStyle = new Label.LabelStyle(); + labelStyle.font = game.skin.getFont("font"); + + timeLeftLabel = new Label("0", labelStyle); + timeLeftLabel.setColor(Klooni.theme.currentScore); + timeLeftLabel.setAlignment(Align.right); + + highTimeLabel = new Label(Integer.toString(nanosToSeconds(maxTimeScore)), labelStyle); + highTimeLabel.setColor(Klooni.theme.highScore); + + layout.update(this); + } + + //endregion + + //region Private methods + + private void addScore(int score) { + deadTime += scoreToNanos(score); + } + + private int calculateClearScore(int stripsCleared, int boardSize) { + if (stripsCleared < 1) return 0; + if (stripsCleared == 1) return boardSize; + else return boardSize * stripsCleared + calculateClearScore(stripsCleared - 1, boardSize); + } + + private int nanosToSeconds(long nano) { + return MathUtils.ceil((float)(nano * 1e-09)); + } + + private long scoreToNanos(int score) { + // 1s/4p seems fair enough + return (long)((score / 4.0) * 1e+09); + } + + public boolean isGameOver() { + return TimeUtils.nanoTime() > deadTime; + } + + //endregion + + //region Public methods + + // Adds the score a given piece would give + public void addPieceScore(int areaPut) { + addScore(areaPut); + } + + // Adds the score given by the board, this is, the count of cleared strips + public void addBoardScore(int stripsCleared, int boardSize) { + addScore(calculateClearScore(stripsCleared, boardSize)); + } + + public void draw(SpriteBatch batch) { + int timeLeft = Math.max(nanosToSeconds(deadTime - TimeUtils.nanoTime()), 0); + timeLeftLabel.setText(Integer.toString(timeLeft)); + + // If we beat a new record, the cup color will linear interpolate to the high score color + //cupColor.lerp(newRecord ? Klooni.theme.highScore : Klooni.theme.currentScore, 0.05f); + batch.setColor(cupColor); + batch.draw(cupTexture, cupArea.x, cupArea.y, cupArea.width, cupArea.height); + + timeLeftLabel.draw(batch, 1f); + highTimeLabel.draw(batch, 1f); + } + + //endregion +} diff --git a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java index c612e81..4103d26 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java @@ -14,6 +14,7 @@ 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; // Main game screen. Here the board, piece holder and score are shown class GameScreen implements Screen, InputProcessor { @@ -21,6 +22,8 @@ class GameScreen implements Screen, InputProcessor { //region Members private final Scorer scorer; + private final TimeScorer timeScorer; + private final Board board; private final PieceHolder holder; @@ -29,6 +32,10 @@ class GameScreen implements Screen, InputProcessor { 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; + //endregion //region Static members @@ -36,15 +43,21 @@ class GameScreen implements Screen, InputProcessor { 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; + //endregion //region Constructor - GameScreen(final Klooni game) { + GameScreen(final Klooni game, final int gameMode) { batch = new SpriteBatch(); + this.gameMode = gameMode; final GameLayout layout = new GameLayout(); scorer = new Scorer(game, layout); + timeScorer = new TimeScorer(game, layout); + board = new Board(layout, BOARD_SIZE); holder = new PieceHolder(layout, HOLDER_PIECE_COUNT, board.cellSize); pauseMenu = new PauseMenuStage(layout, game, scorer); @@ -82,9 +95,17 @@ class GameScreen implements Screen, InputProcessor { Klooni.theme.glClearBackground(); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + // With the time mode, we always need to check whether it's game over or not + if (timeScorer.isGameOver() && !pauseMenu.isShown()) { + pauseMenu.show(true); + if (Klooni.soundsEnabled()) + gameOverSound.play(); + } + batch.begin(); - scorer.draw(batch); + //scorer.draw(batch); + timeScorer.draw(batch); board.draw(batch); holder.update(); holder.draw(batch); @@ -127,8 +148,10 @@ class GameScreen implements Screen, InputProcessor { return false; if (action == PieceHolder.ON_BOARD_DROP) { - scorer.addPieceScore(area); - scorer.addBoardScore(board.clearComplete(), board.cellCount); + //scorer.addPieceScore(area); + //scorer.addBoardScore(board.clearComplete(), board.cellCount); + timeScorer.addPieceScore(area); + timeScorer.addBoardScore(board.clearComplete(), board.cellCount); // After the piece was put, check if it's game over if (isGameOver()) { diff --git a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java index 8d5cdd4..063c58d 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java @@ -7,7 +7,6 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; -import com.badlogic.gdx.scenes.scene2d.ui.ImageButton; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; @@ -46,7 +45,8 @@ public class MainMenuScreen extends InputListener implements Screen { final SoftButton playButton = new SoftButton(0, "play_texture"); playButton.addListener(new ChangeListener() { public void changed (ChangeEvent event, Actor actor) { - MainMenuScreen.this.game.setScreen(new GameScreen(MainMenuScreen.this.game)); + MainMenuScreen.this.game.setScreen( + new GameScreen(MainMenuScreen.this.game, GameScreen.GAME_MODE_SCORE)); dispose(); } }); diff --git a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java index 2413c0a..4e93d2e 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java @@ -11,7 +11,6 @@ import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.actions.RunnableAction; -import com.badlogic.gdx.scenes.scene2d.ui.ImageButton; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; @@ -73,7 +72,7 @@ class PauseMenuStage extends Stage { replayButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { - game.setScreen(new GameScreen(game)); + game.setScreen(new GameScreen(game, GameScreen.GAME_MODE_SCORE)); dispose(); } });