diff --git a/core/src/io/github/lonamiwebs/klooni/Klooni.java b/core/src/io/github/lonamiwebs/klooni/Klooni.java index 92a273b..3dce17f 100644 --- a/core/src/io/github/lonamiwebs/klooni/Klooni.java +++ b/core/src/io/github/lonamiwebs/klooni/Klooni.java @@ -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; diff --git a/core/src/io/github/lonamiwebs/klooni/Theme.java b/core/src/io/github/lonamiwebs/klooni/Theme.java index 3be1a46..cd34bff 100644 --- a/core/src/io/github/lonamiwebs/klooni/Theme.java +++ b/core/src/io/github/lonamiwebs/klooni/Theme.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/actors/Band.java b/core/src/io/github/lonamiwebs/klooni/actors/Band.java index 7e9016e..ff4e822 100644 --- a/core/src/io/github/lonamiwebs/klooni/actors/Band.java +++ b/core/src/io/github/lonamiwebs/klooni/actors/Band.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/actors/SoftButton.java b/core/src/io/github/lonamiwebs/klooni/actors/SoftButton.java index ff8efd4..101740b 100644 --- a/core/src/io/github/lonamiwebs/klooni/actors/SoftButton.java +++ b/core/src/io/github/lonamiwebs/klooni/actors/SoftButton.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/actors/ThemeCard.java b/core/src/io/github/lonamiwebs/klooni/actors/ThemeCard.java index 16e6599..7090c9b 100644 --- a/core/src/io/github/lonamiwebs/klooni/actors/ThemeCard.java +++ b/core/src/io/github/lonamiwebs/klooni/actors/ThemeCard.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/Board.java b/core/src/io/github/lonamiwebs/klooni/game/Board.java index 1bc12e2..ca17451 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/Board.java +++ b/core/src/io/github/lonamiwebs/klooni/game/Board.java @@ -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,39 +74,64 @@ 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)) { - return true; + // 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); } + // 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): + // + // 4x4 boardHeight piece + // _ _ * * * * + // _ * * * * + // * * _ _ + // * * _ _ + // + // 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() { - // 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): - // - // 4x4 boardHeight piece - // _ _ * * * * - // _ * * * * - // * * _ _ - // * * _ _ - // - // 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. 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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/Cell.java b/core/src/io/github/lonamiwebs/klooni/game/Cell.java index 6f92c94..afeed1c 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/Cell.java +++ b/core/src/io/github/lonamiwebs/klooni/game/Cell.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java index e980d56..a1119e9 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java +++ b/core/src/io/github/lonamiwebs/klooni/game/GameLayout.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/Piece.java b/core/src/io/github/lonamiwebs/klooni/game/Piece.java index 6929f1b..96241f0 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/Piece.java +++ b/core/src/io/github/lonamiwebs/klooni/game/Piece.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/PieceHolder.java b/core/src/io/github/lonamiwebs/klooni/game/PieceHolder.java index e510fe1..e1a84a7 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/PieceHolder.java +++ b/core/src/io/github/lonamiwebs/klooni/game/PieceHolder.java @@ -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 getAvailablePieces() { Array result = new Array(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 } diff --git a/core/src/io/github/lonamiwebs/klooni/game/Scorer.java b/core/src/io/github/lonamiwebs/klooni/game/Scorer.java index d6e8e78..3522b38 100644 --- a/core/src/io/github/lonamiwebs/klooni/game/Scorer.java +++ b/core/src/io/github/lonamiwebs/klooni/game/Scorer.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/screens/CustomizeScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/CustomizeScreen.java index 9b018ae..a1c1498 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/CustomizeScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/CustomizeScreen.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java index 7aa46f1..c2a03b8 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/GameScreen.java @@ -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; diff --git a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java index 2faebcc..fb3e768 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/MainMenuScreen.java @@ -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 } diff --git a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java index cd8e97a..8d53369 100644 --- a/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java +++ b/core/src/io/github/lonamiwebs/klooni/screens/PauseMenuStage.java @@ -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() { - // 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) { + // Draw an overlay rectangle with not all the opacity + // This is the only place where ShapeRenderer is OK because the batch hasn't started 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 }