Code clean up

This commit is contained in:
Lonami Exo 2017-01-30 21:36:45 +01:00
parent 6cff9d5968
commit 43f4ca68c6
15 changed files with 558 additions and 279 deletions

View file

@ -11,10 +11,16 @@ import io.github.lonamiwebs.klooni.screens.MainMenuScreen;
public class Klooni extends Game {
//region Members
// TODO Not sure whether the theme should be static or not since it might load textures
public static Theme theme;
public Skin skin;
//endregion
//region Creation
@Override
public void create() {
prefs = Gdx.app.getPreferences("io.github.lonamiwebs.klooni.game");
@ -51,11 +57,19 @@ public class Klooni extends Game {
setScreen(new MainMenuScreen(this));
}
//endregion
//region Screen
@Override
public void render() {
super.render();
}
//endregion
//region Disposing
@Override
public void dispose() {
super.dispose();
@ -63,6 +77,8 @@ public class Klooni extends Game {
theme.dispose();
}
//endregion
//region Settings
private static Preferences prefs;

View file

@ -10,8 +10,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.JsonReader;
import com.badlogic.gdx.utils.JsonValue;
// Represents a Theme for the current game.
// These are loaded from external files, so more
// can be easily added
public class Theme {
//region Members
private String displayName;
private String name;
private int price;
@ -23,19 +28,28 @@ public class Theme {
public NinePatch cellPatch;
// Save the button styles so the changes here get reflected
private ImageButton.ImageButtonStyle[] buttonStyles;
//endregion
//region Constructor
private Theme() {
buttonStyles = new ImageButton.ImageButtonStyle[4];
}
//endregion
//region Static methods
// Gets all the available themes on the available on the internal game storage
public static Theme[] getThemes() {
FileHandle[] handles = Gdx.files.internal("themes").list();
Theme[] result = new Theme[handles.length];
for (int i = 0; i < handles.length; i++) {
for (int i = 0; i < handles.length; i++)
result[i] = Theme.fromFile(handles[i]);
}
return result;
}
@ -48,6 +62,11 @@ public class Theme {
return new Theme().update(handle);
}
//endregion
//region Theme updating
// Updates the theme with all the values from the specified file or name
public Theme update(final String name) {
return update(Gdx.files.internal("themes/"+name+".theme"));
}
@ -93,6 +112,14 @@ public class Theme {
return this;
}
//endregion
//region Applying the theme
public String getName() {
return name;
}
public ImageButton.ImageButtonStyle getStyle(int button) {
return buttonStyles[button];
}
@ -101,20 +128,22 @@ public class Theme {
return cells[colorIndex];
}
void dispose() {
}
public void glClearBackground() {
Gdx.gl.glClearColor(background.r, background.g, background.b, background.a);
}
public String getName() {
return name;
}
public void updateStyle(ImageButton.ImageButtonStyle style, int styleIndex) {
style.imageUp = buttonStyles[styleIndex].imageUp;
style.imageDown = buttonStyles[styleIndex].imageDown;
}
//endregion
//region Disposal
void dispose() {
}
//endregion
}

View file

@ -15,21 +15,28 @@ import com.badlogic.gdx.utils.Align;
import io.github.lonamiwebs.klooni.game.GameLayout;
import io.github.lonamiwebs.klooni.game.Scorer;
// Score and pause menu band actually
// Horizontal band, used to show the score on the pause menu
public class Band extends Actor {
//region Members
private final Scorer scorer;
private final Texture bandTexture;
public final Rectangle scoreBounds;
public final Rectangle infoBounds;
public final Label infoLabel;
public final Label scoreLabel;
private final Label infoLabel;
private final Label scoreLabel;
public Band(final GameLayout layout, final Scorer aScorer, final Color bandColor) {
scorer = aScorer;
//endregion
//region Constructor
public Band(final GameLayout layout, final Scorer scorer, final Color bandColor) {
this.scorer = scorer;
// A 1x1 pixel map will be enough since the band texture will then be expanded
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pixmap.setColor(bandColor);
pixmap.fill();
@ -49,9 +56,9 @@ public class Band extends Actor {
layout.update(this);
}
public void setGameOver() {
infoLabel.setText("no moves left");
}
//endregion
//region Public methods
@Override
public void draw(Batch batch, float parentAlpha) {
@ -70,4 +77,11 @@ public class Band extends Actor {
infoLabel.setBounds(x + infoBounds.x, y + infoBounds.y, infoBounds.width, infoBounds.height);
infoLabel.draw(batch, parentAlpha);
}
// Once game over is set on the menu, it cannot be reverted
public void setGameOver() {
infoLabel.setText("no moves left");
}
//endregion
}

View file

@ -7,11 +7,18 @@ import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.Theme;
// Small wrapper to use themed image buttons more easily
public class SoftButton extends ImageButton {
//region Members
private int styleIndex;
private Drawable image;
//endregion
//region Constructor
public SoftButton(int styleIndex, String imageName) {
super(Klooni.theme.getStyle(styleIndex));
@ -19,13 +26,22 @@ public class SoftButton extends ImageButton {
image = Theme.skin.getDrawable(imageName);
}
//endregion
//region Public methods
@Override
public void draw(Batch batch, float parentAlpha) {
// Always update the style to make sure we're using the right colors
// Always update the style to make sure we're using the right image.
// This might not always be the case since two buttons can be using
// the "same" style (except for the image up, i.e. after coming from
// the customize menu), so make sure to update it always.
ImageButtonStyle style = getStyle();
Klooni.theme.updateStyle(style, styleIndex);
style.imageUp = image;
super.draw(batch, parentAlpha);
}
//endregion
}

View file

@ -1,6 +1,5 @@
package io.github.lonamiwebs.klooni.actors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
@ -11,12 +10,20 @@ import io.github.lonamiwebs.klooni.Theme;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.game.GameLayout;
// Card-like actor used to display information about a given theme
public class ThemeCard extends Actor {
//region Members
public final Theme theme;
private final Texture background;
//endregion
//region Constructor
public ThemeCard(final GameLayout layout, final Theme theme) {
// A 1x1 pixel map will be enough, the background texture will then be stretched accordingly
// TODO We could also use white color and then batch.setColor(theme.background)
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pixmap.setColor(theme.background);
@ -26,11 +33,12 @@ public class ThemeCard extends Actor {
this.theme = theme;
layout.update(this);
setWidth(Gdx.graphics.getWidth());
setScaleX(200);
}
//endregion
//region Public methods
@Override
public void draw(Batch batch, float parentAlpha) {
final float x = getX(), y = getY();
@ -55,4 +63,6 @@ public class ThemeCard extends Actor {
Cell.draw(theme.getCellColor(8), batch, x + cellSize * 2, y + cellSize * 3, cellSize);
Cell.draw(theme.getCellColor(3), batch, x + cellSize * 3, y + cellSize * 3, cellSize);
}
//endregion
}

View file

@ -8,18 +8,26 @@ import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.Klooni;
// Represents the on screen board, with all the put cells
// and functions to determine when it is game over given a PieceHolder
public class Board {
Cell[][] cells;
//region Members
public final int cellCount;
public float cellSize;
private final Vector2 lastPutPiecePos; // Used to animate cleared cells vanishing
private Cell[][] cells;
final Vector2 pos;
private final Sound stripClearSound;
// Used to animate cleared cells vanishing
private final Vector2 lastPutPiecePos;
//endregion
//region Constructor
public Board(final GameLayout layout, int cellCount) {
this.cellCount = cellCount;
@ -27,9 +35,9 @@ public class Board {
lastPutPiecePos = new Vector2();
pos = new Vector2();
layout.update(this);
// Cell size depends on the layout to be updated
// Cell size depends on the layout to be updated first
layout.update(this);
cells = new Cell[this.cellCount][this.cellCount];
for (int i = 0; i < this.cellCount; i++) {
for (int j = 0; j < this.cellCount; j++) {
@ -39,14 +47,21 @@ public class Board {
}
}
//endregion
//region Private methods
// True if the given cell coordinates are inside the bounds of the board
private boolean inBounds(int x, int y) {
return x >= 0 && x < cellCount && y >= 0 && y < cellCount;
}
// True if the given piece at the given coordinates is not outside the bounds of the board
private boolean inBounds(Piece piece, int x, int y) {
return inBounds(x, y) && inBounds(x + piece.cellCols - 1, y + piece.cellRows - 1);
}
// This only tests for the piece on the given coordinates, not the whole board
private boolean canPutPiece(Piece piece, int x, int y) {
if (!inBounds(piece, x, y))
return false;
@ -59,27 +74,51 @@ public class Board {
return true;
}
public boolean canPutPiece(Piece piece) {
for (int i = 0; i < cellCount; i++) {
for (int j = 0; j < cellCount; j++) {
if (canPutPiece(piece, j, i)) {
// Returns true iff the piece was put on the board
private boolean putPiece(Piece piece, int x, int y) {
if (!canPutPiece(piece, x, y))
return false;
lastPutPiecePos.set(piece.calculateGravityCenter());
for (int i = 0; i < piece.cellRows; i++) {
for (int j = 0; j < piece.cellCols; j++) {
if (piece.filled(i, j)) {
cells[y+i][x+j].set(piece.color);
}
}
}
return true;
}
//endregion
//region Public methods
public void draw(SpriteBatch batch) {
for (int i = 0; i < cellCount; i++)
for (int j = 0; j < cellCount; j++)
cells[i][j].draw(batch);
}
}
public boolean canPutPiece(Piece piece) {
for (int i = 0; i < cellCount; i++)
for (int j = 0; j < cellCount; j++)
if (canPutPiece(piece, j, i))
return true;
return false;
}
public boolean putScreenPiece(Piece piece) {
// Get the local piece coordinates
// TODO Works weird, it puts the piece like one too low
boolean putScreenPiece(Piece piece) {
// Convert the on screen coordinates of the piece to the local-board-space coordinates
// This is done by subtracting the piece coordinates from the board coordinates
Vector2 local = piece.pos.cpy().sub(pos);
int x = MathUtils.round(local.x / piece.cellSize);
int y = MathUtils.round(local.y / piece.cellSize);
return putPiece(piece, x, y);
}
public int clearComplete() {
// This will clear both complete rows and columns, all at once.
// The reason why we can't check first rows and then columns
// (or vice versa) is because the following case (* filled, _ empty):
@ -92,6 +131,7 @@ public class Board {
//
// If the piece is put on the top left corner, all the cells will be cleared.
// If we first cleared the columns, then the rows wouldn't have been cleared.
public int clearComplete() {
int clearCount = 0;
boolean[] clearedRows = new boolean[cellCount];
boolean[] clearedCols = new boolean[cellCount];
@ -123,13 +163,11 @@ public class Board {
float pan = 0;
// Do clear those rows and columns
for (int i = 0; i < cellCount; i++) {
if (clearedRows[i]) {
for (int j = 0; j < cellCount; j++) {
for (int i = 0; i < cellCount; i++)
if (clearedRows[i])
for (int j = 0; j < cellCount; j++)
cells[i][j].vanish(lastPutPiecePos);
}
}
}
for (int j = 0; j < cellCount; j++) {
if (clearedCols[j]) {
pan += 2f * (j - cellCount / 2) / (float)cellCount;
@ -150,27 +188,5 @@ public class Board {
return clearCount;
}
public boolean putPiece(Piece piece, int x, int y) {
if (!canPutPiece(piece, x, y))
return false;
lastPutPiecePos.set(piece.calculateGravityCenter());
for (int i = 0; i < piece.cellRows; i++) {
for (int j = 0; j < piece.cellCols; j++) {
if (piece.filled(i, j)) {
cells[y+i][x+j].set(piece.color);
}
}
}
return true;
}
public void draw(SpriteBatch batch) {
for (int i = 0; i < cellCount; i++) {
for (int j = 0; j < cellCount; j++) {
cells[i][j].draw(batch);
}
}
}
//endregion
}

View file

@ -9,19 +9,27 @@ import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.Klooni;
// Represents a single cell, with a position, size and color.
// Instances will use the cell texture provided by the currently used skin.
public class Cell {
//region Members
private boolean empty;
private Color color;
Vector2 pos;
float size;
private Vector2 pos;
private float size;
private Color vanishColor;
private float vanishSize;
private float vanishElapsed;
private float vanishLifetime;
//endregion
//region Constructor
Cell(float x, float y, float cellSize) {
pos = new Vector2(x, y);
size = cellSize;
@ -31,6 +39,11 @@ public class Cell {
vanishElapsed = Float.POSITIVE_INFINITY;
}
//endregion
//region Package local methods
// Sets the cell to be non-empty and of the specified color
void set(Color c) {
empty = false;
color = c;
@ -54,20 +67,9 @@ public class Cell {
}
}
// TODO Use skin atlas
public static void draw(Color color, Batch batch,
float x, float y, float size) {
batch.setColor(color);
Klooni.theme.cellPatch.draw(batch, x, y, size, size);
}
boolean isEmpty() {
return empty;
}
// Vanish from indicates the point which caused the vanishing to happen,
// in this case, a piece was put. The closer it was put, the faster
// this piece will vanish.
// this piece will vanish. This immediately marks the piece as empty.
void vanish(Vector2 vanishFrom) {
if (empty) // We cannot vanish twice
return;
@ -90,4 +92,21 @@ public class Cell {
color = Color.WHITE;
}
boolean isEmpty() {
return empty;
}
//endregion
//region Static methods
// TODO Use skin atlas
public static void draw(Color color, Batch batch,
float x, float y, float size) {
batch.setColor(color);
Klooni.theme.cellPatch.draw(batch, x, y, size, size);
}
//endregion
}

View file

@ -13,32 +13,47 @@ import io.github.lonamiwebs.klooni.actors.ThemeCard;
// For example, the boardHeight on the left and the piece holder on the right
public class GameLayout {
// Widths
private float screenWidth, marginWidth, availableWidth;
//region Members
// Heights
private float screenHeight, logoHeight, scoreHeight, boardHeight, pieceHolderHeight;
private float screenWidth, marginWidth, availableWidth;
private float scoreHeight, boardHeight, pieceHolderHeight;
//endregion
//region Constructor
public GameLayout() {
calculate();
}
//endregion
//region Private methods
private void calculate() {
screenWidth = Gdx.graphics.getWidth();
screenHeight = Gdx.graphics.getHeight();
float screenHeight = Gdx.graphics.getHeight();
// Widths
marginWidth = screenWidth * 0.05f;
availableWidth = screenWidth - marginWidth * 2f;
// Heights
logoHeight = screenHeight * 0.10f;
// logoHeight = screenHeight * 0.10f; // Unused
scoreHeight = screenHeight * 0.15f;
boardHeight = screenHeight * 0.50f;
pieceHolderHeight = screenHeight * 0.25f;
}
// Note that we're now using Y-up coordinates
//endregion
//region Update layout methods
// These methods take any of the custom objects used in the game
// and positions them accordingly on the screen, by using relative
// 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) {
float cupSize = Math.min(scoreHeight, scorer.cupTexture.getHeight());
final Rectangle area = new Rectangle(
@ -94,4 +109,6 @@ public class GameLayout {
public void update(ThemeCard card) {
card.setSize(availableWidth - marginWidth, scoreHeight);
}
//endregion
}

View file

@ -8,18 +8,31 @@ import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.Klooni;
// Pieces can be L shaped and be rotated 0 to 3 times to make it random
// Maximum cellSize = 4
// Represents a piece with an arbitrary shape, which
// can be either rectangles (squares too) or L shaped
// with any rotation.
public class Piece {
//region Members
final Vector2 pos;
float cellSize = 10f; // Default
final Color color;
final int cellCols, cellRows;
private boolean shape[][];
final Color color;
// Default arbitrary value
float cellSize = 10f;
//endregion
//region Constructors
// Rectangle-shaped constructor
//
// If swapSize is true, the rows and columns will be swapped.
// colorIndex represents a random index that will be used
// to determine the color of this piece when drawn on the screen.
private Piece(int cols, int rows, boolean swapSize, int colorIndex) {
color = Klooni.theme.getCellColor(colorIndex);
@ -34,6 +47,7 @@ public class Piece {
}
}
// L-shaped constructor
private Piece(int lSize, int rotateCount, int colorIndex) {
color = Klooni.theme.getCellColor(colorIndex);
@ -68,11 +82,12 @@ public class Piece {
}
}
boolean filled(int i, int j) {
return shape[i][j];
}
//endregion
public static Piece random() {
//region Static methods
// Generates a random piece with always the same color for the generated shape
static Piece random() {
int color = MathUtils.random(8); // 9 pieces
switch (color) {
// Squares
@ -93,10 +108,28 @@ public class Piece {
throw new RuntimeException("Random function is broken.");
}
//endregion
//region Package local methods
void draw(SpriteBatch batch) {
for (int i = 0; i < cellRows; i++)
for (int j = 0; j < cellCols; j++)
if (shape[i][j])
Cell.draw(color, batch, pos.x + j * cellSize, pos.y + i * cellSize, cellSize);
}
// Calculates the rectangle of the piece with screen coordinates
Rectangle getRectangle() {
return new Rectangle(pos.x, pos.y, cellCols * cellSize, cellRows * cellSize);
}
// Determines whether the shape is filled on the given row and column
boolean filled(int i, int j) {
return shape[i][j];
}
// Calculates the area occupied by the shape
int calculateArea() {
int area = 0;
for (int i = 0; i < cellRows; i++) {
@ -109,6 +142,7 @@ public class Piece {
return area;
}
// Calculates the gravity center of the piece shape
Vector2 calculateGravityCenter() {
int filledCount = 0;
Vector2 result = new Vector2();
@ -125,14 +159,5 @@ public class Piece {
return result.scl(1f / filledCount);
}
void draw(SpriteBatch batch) {
for (int i = 0; i < cellRows; i++) {
for (int j = 0; j < cellCols; j++) {
if (shape[i][j]) {
Cell.draw(color, batch,
pos.x + j * cellSize, pos.y + i * cellSize, cellSize);
}
}
}
}
//endregion
}

View file

@ -11,28 +11,43 @@ import com.badlogic.gdx.utils.Array;
import io.github.lonamiwebs.klooni.Klooni;
// A holder of pieces that can be drawn on screen.
// Pieces can be picked up from it and dropped on a board.
public class PieceHolder {
//region Members
final Rectangle area;
private final Piece[] pieces;
private final Rectangle[] originalPositions; // Needed after a piece is dropped
private final Sound pieceDropSound;
private final Sound invalidPieceDropSound;
private final Sound takePiecesSound;
// Count of pieces to be shown
private final int count;
// Currently held piece index (picked by the user)
private int heldPiece;
final Rectangle area;
// Needed after a piece is dropped, so it can go back
private final Rectangle[] originalPositions;
// The size the cells will adopt once picked
private final float pickedCellSize;
//endregion
//region Static members
public static final int NO_DROP = 0;
public static final int NORMAL_DROP = 1;
public static final int ON_BOARD_DROP = 2;
//endregion
//region Constructor
public PieceHolder(final GameLayout layout, final int pieceCount, final float pickedCellSize) {
count = pieceCount;
pieces = new Piece[count];
@ -53,7 +68,21 @@ public class PieceHolder {
takeMore();
}
void takeMore() {
//endregion
//region Private methods
// Determines whether all the pieces have been put (and the "hand" is finished)
private boolean handFinished() {
for (int i = 0; i < count; i++)
if (pieces[i] != null)
return false;
return true;
}
// Takes a new set of pieces. Should be called when there are no more piece left
private void takeMore() {
float perPieceWidth = area.width / count;
for (int i = 0; i < count; i++) {
pieces[i] = Piece.random();
@ -85,15 +114,11 @@ public class PieceHolder {
}
}
boolean handFinished() {
for (int i = 0; i < count; i++)
if (pieces[i] != null)
return false;
//endregion
return true;
}
//region Public methods
// Pick the piece below the finger/mouse
// Picks the piece below the finger/mouse, returning true if any was picked
public boolean pickPiece() {
Vector2 mouse = new Vector2(
Gdx.input.getX(),
@ -112,23 +137,20 @@ public class PieceHolder {
public Array<Piece> getAvailablePieces() {
Array<Piece> result = new Array<Piece>(count);
for (int i = 0; i < count; i++) {
if (pieces[i] != null) {
for (int i = 0; i < count; i++)
if (pieces[i] != null)
result.add(pieces[i]);
}
}
return result;
}
// If no piece is currently being held, the area will be 0
public int calculateHeldPieceArea() {
if (heldPiece > -1) {
return pieces[heldPiece].calculateArea();
} else {
return 0;
}
return heldPiece > -1 ? pieces[heldPiece].calculateArea() : 0;
}
// Returns one of the following: NO_DROP, NORMAL_DROP, ON_BOARD_DROP
// Tries to drop the piece on the given board. As a result, it
// returns one of the following: NO_DROP, NORMAL_DROP, ON_BOARD_DROP
public int dropPiece(Board board) {
if (heldPiece > -1) {
boolean put = board.putScreenPiece(pieces[heldPiece]);
@ -154,6 +176,7 @@ public class PieceHolder {
return NO_DROP;
}
// Updates the state of the piece holder (and the held piece)
public void update() {
Piece piece;
if (heldPiece > -1) {
@ -163,7 +186,7 @@ public class PieceHolder {
Gdx.input.getX(),
Gdx.graphics.getHeight() - Gdx.input.getY()); // Y axis is inverted
// Center the piece
// Center the new piece position
mouse.sub(piece.getRectangle().width / 2, piece.getRectangle().height / 2);
piece.pos.lerp(mouse, 0.4f);
@ -194,4 +217,6 @@ public class PieceHolder {
}
}
}
//endregion
}

View file

@ -13,13 +13,14 @@ 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 {
private int currentScore, maxScore;
private boolean newRecord;
//region Members
private float shownScore; // To interpolate between shown score -> real score
private final int boardSize;
private int currentScore, maxScore;
final Label currentScoreLabel;
final Label maxScoreLabel;
@ -27,10 +28,20 @@ public class Scorer {
final Texture cupTexture;
final Rectangle cupArea;
public Scorer(GameLayout layout, int boardSize) {
// If the currentScore beat the maxScore, then we have a new record
private boolean newRecord;
// To interpolate between shown score -> real score
private float shownScore;
//endregion
//region Constructor
// The board size is required when calculating the score
public Scorer(GameLayout layout) {
currentScore = 0;
maxScore = Klooni.getMaxScore();
this.boardSize = boardSize;
cupTexture = new Texture(Gdx.files.internal("ui/cup.png"));
cupArea = new Rectangle();
@ -48,19 +59,39 @@ public class Scorer {
layout.update(this);
}
public void addPieceScore(int areaPut) {
addScore(areaPut);
}
//endregion
public void addBoardScore(int stripsCleared) {
addScore(calculateClearScore(stripsCleared));
}
//region Private methods
private 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;
}
@ -71,16 +102,6 @@ public class Scorer {
}
}
int calculateClearScore(int stripsCleared) {
// 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)
if (stripsCleared < 1) return 0;
if (stripsCleared == 1) return boardSize;
else return boardSize * stripsCleared + calculateClearScore(stripsCleared - 1);
}
public void draw(SpriteBatch batch) {
int roundShown = MathUtils.round(shownScore);
if (roundShown != currentScore) {
@ -93,4 +114,6 @@ public class Scorer {
currentScoreLabel.draw(batch, 1f);
maxScoreLabel.draw(batch, 1f);
}
//endregion
}

View file

@ -22,15 +22,29 @@ import io.github.lonamiwebs.klooni.actors.SoftButton;
import io.github.lonamiwebs.klooni.actors.ThemeCard;
import io.github.lonamiwebs.klooni.game.GameLayout;
public class CustomizeScreen implements Screen {
private Klooni game;
// Screen where the user can customize the look and feel of the game
class CustomizeScreen implements Screen {
//region Members
private Klooni game;
private Stage stage;
public CustomizeScreen(Klooni aGame, final Screen lastScreen) {
//endregion
//region Static members
// As the examples show on the LibGdx wiki
private static final float minDelta = 1/30f;
//endregion
//region Constructor
CustomizeScreen(Klooni game, final Screen lastScreen) {
final GameLayout layout = new GameLayout();
game = aGame;
this.game = game;
stage = new Stage();
Table table = new Table();
@ -45,7 +59,7 @@ public class CustomizeScreen implements Screen {
backButton.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
game.setScreen(lastScreen);
CustomizeScreen.this.game.setScreen(lastScreen);
dispose();
}
});
@ -59,7 +73,7 @@ public class CustomizeScreen implements Screen {
@Override
public void changed(ChangeEvent event, Actor actor) {
Klooni.toggleSound();
soundButton.getStyle().imageUp = game.skin.getDrawable(
soundButton.getStyle().imageUp = CustomizeScreen.this.game.skin.getDrawable(
Klooni.soundsEnabled() ? "sound_on_texture" : "sound_off_texture");
}
});
@ -86,8 +100,9 @@ public class CustomizeScreen implements Screen {
optionsGroup.addActor(webButton);
table.add(new ScrollPane(optionsGroup)).pad(20, 4, 12, 4);
table.row();
// Load all the available themes
table.row();
VerticalGroup themesGroup = new VerticalGroup();
for (Theme theme : Theme.getThemes()) {
final ThemeCard card = new ThemeCard(layout, theme);
@ -105,13 +120,15 @@ public class CustomizeScreen implements Screen {
table.add(new ScrollPane(themesGroup)).expand().fill();
}
//endregion
//region Public methods
@Override
public void show() {
Gdx.input.setInputProcessor(stage);
}
private static final float minDelta = 1/30f;
@Override
public void render(float delta) {
Klooni.theme.glClearBackground();
@ -130,23 +147,23 @@ public class CustomizeScreen implements Screen {
stage.getViewport().update(width, height, true);
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void hide() {
}
@Override
public void dispose() {
stage.dispose();
}
//endregion
//region Empty methods
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void hide() { }
//endregion
}

View file

@ -15,41 +15,58 @@ import io.github.lonamiwebs.klooni.game.Piece;
import io.github.lonamiwebs.klooni.game.PieceHolder;
import io.github.lonamiwebs.klooni.game.Scorer;
public class GameScreen implements Screen, InputProcessor {
// Main game screen. Here the board, piece holder and score are shown
class GameScreen implements Screen, InputProcessor {
//region Members
private final Scorer scorer;
private Board board;
private PieceHolder holder;
private final Board board;
private final PieceHolder holder;
private final GameLayout layout;
private final SpriteBatch batch;
private final Sound gameOverSound;
private SpriteBatch batch;
private final PauseMenuStage pauseMenu;
//endregion
//region Static members
private final static int BOARD_SIZE = 10;
private final static int HOLDER_PIECE_COUNT = 3;
//endregion
//region Constructor
GameScreen(final Klooni game) {
batch = new SpriteBatch();
layout = new GameLayout();
scorer = new Scorer(layout, 10);
board = new Board(layout, 10);
holder = new PieceHolder(layout, 3, board.cellSize);
final GameLayout layout = new GameLayout();
scorer = new Scorer(layout);
board = new Board(layout, BOARD_SIZE);
holder = new PieceHolder(layout, HOLDER_PIECE_COUNT, board.cellSize);
pauseMenu = new PauseMenuStage(layout, game, scorer);
gameOverSound = Gdx.audio.newSound(Gdx.files.internal("sound/game_over.mp3"));
}
//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)) {
for (Piece piece : holder.getAvailablePieces())
if (board.canPutPiece(piece))
return false;
}
}
return true;
}
//endregion
//region Screen
@Override
@ -80,26 +97,6 @@ public class GameScreen implements Screen, InputProcessor {
}
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void hide() {
}
@Override
public void dispose() {
pauseMenu.dispose();
@ -109,11 +106,6 @@ public class GameScreen implements Screen, InputProcessor {
//region Input
@Override
public boolean keyDown(int keycode) {
return false;
}
@Override
public boolean keyUp(int keycode) {
if (keycode == Input.Keys.P || keycode == Input.Keys.BACK) // Pause
@ -122,11 +114,6 @@ public class GameScreen implements Screen, InputProcessor {
return false;
}
@Override
public boolean keyTyped(char character) {
return false;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
return holder.pickPiece();
@ -141,7 +128,7 @@ public class GameScreen implements Screen, InputProcessor {
if (action == PieceHolder.ON_BOARD_DROP) {
scorer.addPieceScore(area);
scorer.addBoardScore(board.clearComplete());
scorer.addBoardScore(board.clearComplete(), board.cellCount);
// After the piece was put, check if it's game over
if (isGameOver()) {
@ -153,6 +140,32 @@ public class GameScreen implements Screen, InputProcessor {
return true;
}
//endregion
//region Unused methods
@Override
public void resize(int width, int height) { }
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void hide() { }
@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;

View file

@ -4,7 +4,6 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
@ -15,16 +14,28 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.actors.SoftButton;
// Main menu screen, presenting some options (play, customize)
public class MainMenuScreen extends InputListener implements Screen {
private Klooni game;
Stage stage;
SpriteBatch batch;
//region Members
public MainMenuScreen(Klooni aGame) {
game = aGame;
private final Klooni game;
private final Stage stage;
//endregion
//region Static members
// As the examples show on the LibGdx wiki
private static final float minDelta = 1/30f;
//endregion
//region Constructor
public MainMenuScreen(Klooni game) {
this.game = game;
batch = new SpriteBatch();
stage = new Stage();
Table table = new Table();
@ -35,7 +46,7 @@ public class MainMenuScreen extends InputListener implements Screen {
final ImageButton playButton = new SoftButton(0, "play_texture");
playButton.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
game.setScreen(new GameScreen(game));
MainMenuScreen.this.game.setScreen(new GameScreen(MainMenuScreen.this.game));
dispose();
}
});
@ -55,20 +66,22 @@ public class MainMenuScreen extends InputListener implements Screen {
final ImageButton paletteButton = new SoftButton(3, "palette_texture");
paletteButton.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
game.setScreen(new CustomizeScreen(game, game.getScreen()));
MainMenuScreen.this.game.setScreen(new CustomizeScreen(MainMenuScreen.this.game, MainMenuScreen.this.game.getScreen()));
// Don't dispose because then it needs to take us to the previous screen
}
});
table.add(paletteButton).space(16);
}
//endregion
//region Screen
@Override
public void show() {
Gdx.input.setInputProcessor(stage);
}
private static final float minDelta = 1/30f;
@Override
public void render(float delta) {
Klooni.theme.glClearBackground();
@ -86,23 +99,23 @@ public class MainMenuScreen extends InputListener implements Screen {
stage.getViewport().update(width, height, true);
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void hide() {
}
@Override
public void dispose() {
stage.dispose();
}
//endregion
//region Unused methods
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void hide() { }
//endregion
}

View file

@ -21,7 +21,11 @@ import io.github.lonamiwebs.klooni.actors.SoftButton;
import io.github.lonamiwebs.klooni.game.GameLayout;
import io.github.lonamiwebs.klooni.game.Scorer;
public class PauseMenuStage extends Stage {
// The pause stage is not a whole screen but rather a menu
// which can be overlaid on top of another screen
class PauseMenuStage extends Stage {
//region Members
private InputProcessor lastInputProcessor;
private boolean shown;
@ -32,8 +36,13 @@ public class PauseMenuStage extends Stage {
private final Band band;
private final Scorer scorer;
public PauseMenuStage(final GameLayout layout, final Klooni game, final Scorer aScorer) {
scorer = aScorer;
//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) {
this.scorer = scorer;
shapeRenderer = new ShapeRenderer(20); // 20 vertex seems to be enough for a rectangle
@ -43,7 +52,7 @@ public class PauseMenuStage extends Stage {
// Current and maximum score band.
// Do not add it to the table not to over-complicate things.
band = new Band(layout, scorer, Color.SKY);
band = new Band(layout, this.scorer, Color.SKY);
addActor(band);
// Home screen button
@ -84,7 +93,7 @@ public class PauseMenuStage extends Stage {
});
// Continue playing OR share (if game over) button
// TODO Enable both actions for this button
// TODO Enable both actions for this button? Or leave play?
final ImageButton playButton = new SoftButton(2, "play_texture");
table.add(playButton).space(16);
@ -95,22 +104,12 @@ public class PauseMenuStage extends Stage {
});
}
void show(final boolean gameOver) {
scorer.saveScore();
//endregion
lastInputProcessor = Gdx.input.getInputProcessor();
Gdx.input.setInputProcessor(this);
shown = true;
hiding = false;
//region Private methods
if (gameOver)
band.setGameOver();
addAction(Actions.moveTo(0, Gdx.graphics.getHeight()));
addAction(Actions.moveTo(0, 0, 0.75f, Interpolation.swingOut));
}
public void hide() {
// Hides the pause menu, setting back the previous input processor
private void hide() {
shown = false;
hiding = true;
Gdx.input.setInputProcessor(lastInputProcessor);
@ -126,19 +125,44 @@ public class PauseMenuStage extends Stage {
));
}
public boolean isShown() {
//endregion
//region Package local methods
// Shows the pause menu, indicating whether it's game over or not
void show(final boolean gameOver) {
scorer.saveScore();
// Save the last input processor so then we can return the handle to it
lastInputProcessor = Gdx.input.getInputProcessor();
Gdx.input.setInputProcessor(this);
shown = true;
hiding = false;
if (gameOver)
band.setGameOver();
addAction(Actions.moveTo(0, Gdx.graphics.getHeight()));
addAction(Actions.moveTo(0, 0, 0.75f, Interpolation.swingOut));
}
boolean isShown() {
return shown;
}
public boolean isHiding() {
boolean isHiding() {
return hiding;
}
//endregion
//region Public methods
@Override
public void draw() {
if (shown) {
// Draw an overlay rectangle with not all the opacity
// This is the only place where ShapeRenderer is OK because the batch hasn't started
if (shown) {
Gdx.gl.glEnable(GL20.GL_BLEND);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(1f, 1f, 1f, 0.3f);
@ -156,4 +180,6 @@ public class PauseMenuStage extends Stage {
return super.keyUp(keyCode);
}
//endregion
}