diff --git a/core/src/io/github/lonamiwebs/klooni/Klooni.java b/core/src/io/github/lonamiwebs/klooni/Klooni.java index 08476ff..2015950 100644 --- a/core/src/io/github/lonamiwebs/klooni/Klooni.java +++ b/core/src/io/github/lonamiwebs/klooni/Klooni.java @@ -91,6 +91,10 @@ public class Klooni extends Game { return prefs.getInteger("maxScore", 0); } + public static int getMaxTimeScore() { + return prefs.getInteger("maxTimeScore", 0); + } + public static void setMaxScore(int score) { prefs.putInteger("maxScore", score).flush(); } diff --git a/core/src/io/github/lonamiwebs/klooni/actors/Band.java b/core/src/io/github/lonamiwebs/klooni/actors/Band.java index 4636926..5e6ebbc 100644 --- a/core/src/io/github/lonamiwebs/klooni/actors/Band.java +++ b/core/src/io/github/lonamiwebs/klooni/actors/Band.java @@ -11,15 +11,15 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.utils.Align; import io.github.lonamiwebs.klooni.Klooni; +import io.github.lonamiwebs.klooni.game.BaseScorer; import io.github.lonamiwebs.klooni.game.GameLayout; -import io.github.lonamiwebs.klooni.game.Scorer; // Horizontal band, used to show the score on the pause menu public class Band extends Actor { //region Members - private final Scorer scorer; + private final BaseScorer scorer; private final Texture bandTexture; public final Rectangle scoreBounds; @@ -32,7 +32,7 @@ public class Band extends Actor { //region Constructor - public Band(final Klooni game, final GameLayout layout, final Scorer scorer, final Color bandColor) { + public Band(final Klooni game, final GameLayout layout, final BaseScorer scorer, final Color bandColor) { this.scorer = scorer; // A 1x1 pixel map will be enough since the band texture will then be expanded diff --git a/core/src/io/github/lonamiwebs/klooni/game/BaseScorer.java b/core/src/io/github/lonamiwebs/klooni/game/BaseScorer.java new file mode 100644 index 0000000..28b06c2 --- /dev/null +++ b/core/src/io/github/lonamiwebs/klooni/game/BaseScorer.java @@ -0,0 +1,97 @@ +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.Rectangle; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.utils.Align; + +import io.github.lonamiwebs.klooni.Klooni; + +public abstract class BaseScorer { + + //region Members + + final Label leftLabel; + final Label highScoreLabel; + + final Texture cupTexture; + final Rectangle cupArea; + + private final Color cupColor; + + //endregion + + //region Constructor + + // The board size is required when calculating the score + BaseScorer(final Klooni game, GameLayout layout, int highScore) { + 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"); + + leftLabel = new Label("0", labelStyle); + leftLabel.setColor(Klooni.theme.currentScore); + leftLabel.setAlignment(Align.right); + + highScoreLabel = new Label(Integer.toString(highScore), labelStyle); + highScoreLabel.setColor(Klooni.theme.highScore); + + layout.update(this); + } + + //endregion + + //region Private methods + + protected abstract void addScore(int score); + + // The original game seems to work as follows: + // If < 1 were cleared, score = 0 + // If = 1 was cleared, score = cells cleared + // If > 1 were cleared, score = cells cleared + score(cleared - 1) + 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); + } + + //endregion + + //region Public methods + + // Adds the score a given piece would give + public final void addPieceScore(int areaPut) { + addScore(areaPut); + } + + // Adds the score given by the board, this is, the count of cleared strips + public final void addBoardScore(int stripsCleared, int boardSize) { + addScore(calculateClearScore(stripsCleared, boardSize)); + } + + abstract public boolean isGameOver(); + + abstract public int getCurrentScore(); + + abstract public void saveScore(); + + abstract protected boolean isNewRecord(); + + public void draw(SpriteBatch batch) { + // If we beat a new record, the cup color will linear interpolate to the high score color + cupColor.lerp(isNewRecord() ? Klooni.theme.highScore : Klooni.theme.currentScore, 0.05f); + batch.setColor(cupColor); + batch.draw(cupTexture, cupArea.x, cupArea.y, cupArea.width, cupArea.height); + + leftLabel.draw(batch, 1f); + highScoreLabel.draw(batch, 1f); + } + + //endregion +} diff --git a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java index be09285..7330df3 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java +++ b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java @@ -56,7 +56,7 @@ public class GameLayout { // coordinates. Since these objects are not actors and we cannot // add them to a table (and would probably be harder), this approach // was used. Note that all these are using Y-up coordinates. - void update(Scorer scorer) { + void update(BaseScorer scorer) { float cupSize = Math.min(scoreHeight, scorer.cupTexture.getHeight()); final Rectangle area = new Rectangle( marginWidth, pieceHolderHeight + boardHeight, @@ -66,7 +66,7 @@ public class GameLayout { area.x + area.width * 0.5f - cupSize * 0.5f, area.y, cupSize, cupSize); - scorer.currentScoreLabel.setBounds( + scorer.leftLabel.setBounds( area.x, area.y, area.width * 0.5f - cupSize * 0.5f, area.height); @@ -75,25 +75,6 @@ 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/Scorer.java b/core/src/io/github/lonamiwebs/klooni/game/Scorer.java index 08ba6c5..9b3f54b 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/Scorer.java +++ b/core/src/io/github/lonamiwebs/klooni/game/Scorer.java @@ -1,34 +1,20 @@ 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.Interpolation; 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 io.github.lonamiwebs.klooni.Klooni; // Used to keep track of the current and maximum // score, and to also display it on the screen. // The maximum score is NOT saved automatically. -public class Scorer { +public class Scorer extends BaseScorer { //region Members private int currentScore, maxScore; - final Label currentScoreLabel; - final Label highScoreLabel; - - final Texture cupTexture; - final Rectangle cupArea; - - private final Color cupColor; - // If the currentScore beat the maxScore, then we have a new record private boolean newRecord; @@ -41,59 +27,26 @@ public class Scorer { // The board size is required when calculating the score public Scorer(final Klooni game, GameLayout layout) { + super(game, layout, Klooni.getMaxScore()); + currentScore = 0; maxScore = Klooni.getMaxScore(); - - 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"); - - currentScoreLabel = new Label("0", labelStyle); - currentScoreLabel.setColor(Klooni.theme.currentScore); - currentScoreLabel.setAlignment(Align.right); - - highScoreLabel = new Label(Integer.toString(maxScore), labelStyle); - highScoreLabel.setColor(Klooni.theme.highScore); - - layout.update(this); } //endregion //region Private methods - private void addScore(int score) { + @Override + protected void addScore(int score) { currentScore += score; newRecord = currentScore > maxScore; } - // The original game seems to work as follows: - // If < 1 were cleared, score = 0 - // If = 1 was cleared, score = cells cleared - // If > 1 were cleared, score = cells cleared + score(cleared - 1) - 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); - } - //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 int getCurrentScore() { return currentScore; } @@ -104,20 +57,23 @@ public class Scorer { } } + @Override + protected boolean isNewRecord() { + return newRecord; + } + + @Override + public boolean isGameOver() { + return false; + } + public void draw(SpriteBatch batch) { int roundShown = MathUtils.round(shownScore); if (roundShown != currentScore) { shownScore = Interpolation.linear.apply(shownScore, currentScore, 0.1f); - currentScoreLabel.setText(Integer.toString(MathUtils.round(shownScore))); + leftLabel.setText(Integer.toString(MathUtils.round(shownScore))); } - - // 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); - - currentScoreLabel.draw(batch, 1f); - highScoreLabel.draw(batch, 1f); + super.draw(batch); } //endregion diff --git a/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java b/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java index fa24c50..3465b41 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java +++ b/core/src/io/github/lonamiwebs/klooni/game/TimeScorer.java @@ -1,38 +1,21 @@ 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 { +public class TimeScorer extends BaseScorer { //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 @@ -41,40 +24,21 @@ public class TimeScorer { // The board size is required when calculating the score public TimeScorer(final Klooni game, GameLayout layout) { + super(game, layout, Klooni.getMaxTimeScore()); + 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) { + @Override + protected 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)); } @@ -84,6 +48,7 @@ public class TimeScorer { return (long)((score / 4.0) * 1e+09); } + @Override public boolean isGameOver() { return TimeUtils.nanoTime() > deadTime; } @@ -92,27 +57,28 @@ public class TimeScorer { //region Public methods - // Adds the score a given piece would give - public void addPieceScore(int areaPut) { - addScore(areaPut); + @Override + public int getCurrentScore() { + return nanosToSeconds(deadTime - startTime); } - // 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)); + @Override + public void saveScore() { + // TODO Save high time score } + @Override + protected boolean isNewRecord() { + // TODO Return true if it is a new record + return false; + } + + @Override public void draw(SpriteBatch batch) { int timeLeft = Math.max(nanosToSeconds(deadTime - TimeUtils.nanoTime()), 0); - timeLeftLabel.setText(Integer.toString(timeLeft)); + leftLabel.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); + super.draw(batch); } //endregion diff --git a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java index 4103d26..b473c93 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java @@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.SpriteBatch; 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.GameLayout; import io.github.lonamiwebs.klooni.game.Piece; @@ -21,8 +22,7 @@ class GameScreen implements Screen, InputProcessor { //region Members - private final Scorer scorer; - private final TimeScorer timeScorer; + private final BaseScorer scorerlol; private final Board board; private final PieceHolder holder; @@ -55,12 +55,20 @@ class GameScreen implements Screen, InputProcessor { this.gameMode = gameMode; final GameLayout layout = new GameLayout(); - scorer = new Scorer(game, layout); - timeScorer = new TimeScorer(game, layout); + switch (gameMode) { + case GAME_MODE_SCORE: + scorerlol = new Scorer(game, layout); + break; + case GAME_MODE_TIME: + scorerlol = new TimeScorer(game, layout); + break; + default: + throw new RuntimeException("Unknown game mode given: "+gameMode); + } board = new Board(layout, BOARD_SIZE); holder = new PieceHolder(layout, HOLDER_PIECE_COUNT, board.cellSize); - pauseMenu = new PauseMenuStage(layout, game, scorer); + pauseMenu = new PauseMenuStage(layout, game, scorerlol); gameOverSound = Gdx.audio.newSound(Gdx.files.internal("sound/game_over.mp3")); } @@ -78,6 +86,12 @@ class GameScreen implements Screen, InputProcessor { return true; } + private void doGameOver() { + pauseMenu.show(true); + if (Klooni.soundsEnabled()) + gameOverSound.play(); + } + //endregion //region Screen @@ -95,17 +109,13 @@ 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(); + if (scorerlol.isGameOver() && !pauseMenu.isShown()) { + doGameOver(); } batch.begin(); - //scorer.draw(batch); - timeScorer.draw(batch); + scorerlol.draw(batch); board.draw(batch); holder.update(); holder.draw(batch); @@ -148,16 +158,12 @@ class GameScreen implements Screen, InputProcessor { return false; if (action == PieceHolder.ON_BOARD_DROP) { - //scorer.addPieceScore(area); - //scorer.addBoardScore(board.clearComplete(), board.cellCount); - timeScorer.addPieceScore(area); - timeScorer.addBoardScore(board.clearComplete(), board.cellCount); + scorerlol.addPieceScore(area); + scorerlol.addBoardScore(board.clearComplete(), board.cellCount); // After the piece was put, check if it's game over if (isGameOver()) { - pauseMenu.show(true); - if (Klooni.soundsEnabled()) - gameOverSound.play(); + doGameOver(); } } return true; diff --git a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java index 063c58d..b5c9da4 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java @@ -66,6 +66,15 @@ public class MainMenuScreen extends InputListener implements Screen { // Stats button (high scores) final SoftButton statsButton = new SoftButton(2, "stats_texture"); + // TODO For testing purposes, open the time mode + statsButton.addListener(new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + MainMenuScreen.this.game.setScreen( + new GameScreen(MainMenuScreen.this.game, GameScreen.GAME_MODE_TIME)); + dispose(); + } + }); table.add(statsButton).space(16); // Palette button (buy colors) diff --git a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java index 4e93d2e..b4cbeda 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java @@ -17,8 +17,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import io.github.lonamiwebs.klooni.Klooni; import io.github.lonamiwebs.klooni.actors.Band; import io.github.lonamiwebs.klooni.actors.SoftButton; +import io.github.lonamiwebs.klooni.game.BaseScorer; import io.github.lonamiwebs.klooni.game.GameLayout; -import io.github.lonamiwebs.klooni.game.Scorer; // The pause stage is not a whole screen but rather a menu // which can be overlaid on top of another screen @@ -33,14 +33,14 @@ class PauseMenuStage extends Stage { private final ShapeRenderer shapeRenderer; private final Band band; - private final Scorer scorer; + private final BaseScorer scorer; //endregion //region Constructor // We need the score to save the maximum score if a new record was beaten - PauseMenuStage(final GameLayout layout, final Klooni game, final Scorer scorer) { + PauseMenuStage(final GameLayout layout, final Klooni game, final BaseScorer scorer) { this.scorer = scorer; shapeRenderer = new ShapeRenderer(20); // 20 vertex seems to be enough for a rectangle