-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ghost)!: implement ghost block game feature
BREAKING CHANGE: Facade class now accepts a game constructor for allowing a new instance of GhostGame. this closes #4
- Loading branch information
1 parent
0d3b289
commit b4a1840
Showing
18 changed files
with
348 additions
and
115 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,25 @@ | ||
package game | ||
|
||
import block_factory.BlockCreator | ||
import blocks.Block | ||
import board.Board | ||
import game.exceptions.BlockCanMoveDownException | ||
import game.exceptions.EmptyCurrentBlockException | ||
import movements.Direction | ||
import movements.Rotation | ||
import score.Points | ||
import score.ScoreCalculator | ||
|
||
internal const val GAME_COLUMNS = 10 | ||
internal const val GAME_ROWS = 20 | ||
internal const val DEFAULT_POINTS = 0 | ||
interface Game { | ||
|
||
class Game(private val creator: BlockCreator, private val scoreCalculator: ScoreCalculator) { | ||
fun getGrid(): List<List<GameCell>> | ||
|
||
private val board: Board = Board(GAME_ROWS, GAME_COLUMNS) | ||
private var block: Block? = null | ||
private var points: Points = Points(DEFAULT_POINTS) | ||
fun generateNextBlock() | ||
|
||
fun getGrid(): List<List<GameCell>> { | ||
val result = getGameCellGridFromBoard() | ||
if (block != null) | ||
block!!.getNeededPositions() | ||
.forEach { result[it.row][it.column] = GameCell(block!!.getCell(), isCurrentBlockCell = true) } | ||
return result | ||
} | ||
fun moveBlock(direction: Direction) | ||
|
||
fun generateNextBlock() { | ||
block = creator.getBlock() | ||
} | ||
fun rotateBlock(rotation: Rotation) | ||
|
||
fun moveBlock(direction: Direction) { | ||
assertBlockNotNull() | ||
if (blockIsInValidPosition(block!!.move(direction))) | ||
block = block!!.move(direction) | ||
} | ||
fun blockCanMoveDownNext(): Boolean | ||
|
||
fun rotateBlock(rotation: Rotation) { | ||
assertBlockNotNull() | ||
if (blockIsInValidPosition(block!!.rotate(rotation))) | ||
block = block!!.rotate(rotation) | ||
} | ||
fun writeBlockToBoard() | ||
|
||
fun blockCanMoveDownNext(): Boolean { | ||
assertBlockNotNull() | ||
return blockIsInValidPosition(block!!.move(Direction.DOWN)) | ||
} | ||
fun hasFinished(): Boolean | ||
|
||
fun writeBlockToBoard() { | ||
assertBlockNotNull() | ||
if (blockCanMoveDownNext()) | ||
throw BlockCanMoveDownException("Block can move down next, so it's impossible to write it to the board") | ||
block!!.getNeededPositions().forEach { board.writePosition(block!!.getCell(), it) } | ||
checkIfCompletedRows() | ||
} | ||
fun getScore(): Points | ||
|
||
fun hasFinished(): Boolean { | ||
assertBlockNotNull() | ||
return !blockIsInValidPosition(block!!) | ||
} | ||
|
||
fun getScore(): Points { | ||
return points | ||
} | ||
|
||
private fun assertBlockNotNull() { | ||
if (block == null) | ||
throw EmptyCurrentBlockException("Did you initialize a block with getNextBlock()?") | ||
} | ||
|
||
private fun getGameCellGridFromBoard(): MutableList<MutableList<GameCell>> { | ||
val immutableBoardGameCell = board.board.map { row -> row.map { cell -> GameCell(cell) } } | ||
return immutableBoardGameCell.map { it.toMutableList() }.toMutableList() | ||
} | ||
|
||
private fun blockIsInValidPosition(block: Block) = | ||
block.getNeededPositions().all { board.isInside(it) && board.isEmpty(it) } | ||
|
||
private fun checkIfCompletedRows() { | ||
if (hasCompletedRows()) { | ||
updateScore() | ||
board.redoBoardWithClearedCells() | ||
} | ||
} | ||
|
||
private fun hasCompletedRows() = (0 until GAME_ROWS).any { row -> board.isFull(row) } | ||
|
||
private fun getCompletedRows() = (0 until GAME_ROWS).count { row -> board.isFull(row) } | ||
|
||
private fun updateScore() { | ||
points = points.add(scoreCalculator.getScore(getCompletedRows())) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,5 @@ | ||
@file:Suppress("unused", "unused") | ||
|
||
package game | ||
|
||
import board.Cell | ||
|
||
data class GameCell(val cell: Cell, val isCurrentBlockCell: Boolean = false) | ||
data class GameCell(val cell: Cell, val isCurrentBlockCell: Boolean = false, val isGhostBlockCell: Boolean = false) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package game.ghost | ||
|
||
import block_factory.BlockCreator | ||
import game.Game | ||
import game.GameCell | ||
import game.normal.NormalGame | ||
import movements.Direction | ||
import movements.Position | ||
import movements.Rotation | ||
import score.Points | ||
import score.ScoreCalculator | ||
|
||
class GhostGame(creator: BlockCreator, scoreCalculator: ScoreCalculator) : Game { | ||
|
||
private val game = NormalGame(creator, scoreCalculator) | ||
|
||
override fun getGrid(): List<List<GameCell>> { | ||
val grid = game.getGrid().toMutableList().map { it.toMutableList() } | ||
return getGridWithGhostBlock(grid) | ||
} | ||
|
||
override fun generateNextBlock() = game.generateNextBlock() | ||
|
||
override fun moveBlock(direction: Direction) = game.moveBlock(direction) | ||
|
||
override fun rotateBlock(rotation: Rotation) = game.rotateBlock(rotation) | ||
|
||
override fun blockCanMoveDownNext(): Boolean = game.blockCanMoveDownNext() | ||
|
||
override fun writeBlockToBoard() = game.writeBlockToBoard() | ||
|
||
override fun hasFinished(): Boolean = game.hasFinished() | ||
|
||
override fun getScore(): Points = game.getScore() | ||
|
||
private fun getGridWithGhostBlock(grid: List<MutableList<GameCell>>): List<List<GameCell>> { | ||
// get current cells of the block | ||
val result = mutableListOf<Position>() | ||
for ((rowI, row) in grid.withIndex()) { | ||
for (column in row.indices) { | ||
if (grid[rowI][column].isCurrentBlockCell) | ||
result.add(Position(rowI, column)) | ||
} | ||
} | ||
if (result.isEmpty()) | ||
return grid | ||
// get minimum row of current block | ||
val distance = result.getRowOffset(grid) | ||
// Write Result | ||
for (position in result) { | ||
val currentGhostBlock = GameCell(grid[position.row][position.column].cell, false, true) | ||
grid[position.row + distance][position.column] = currentGhostBlock | ||
} | ||
return grid | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package game.ghost | ||
|
||
import board.Cell | ||
import game.GameCell | ||
import movements.Position | ||
|
||
internal fun Position.getLastEmptyBlock(grid: List<List<GameCell>>): Position { | ||
var varRow = this.row | ||
while (varRow < grid.size && (grid[varRow][column].cell == Cell.EMPTY || grid[varRow][column].isCurrentBlockCell)) | ||
varRow += 1 | ||
return Position(varRow - 1, column) | ||
} | ||
|
||
internal fun List<Position>.getRowOffset(grid: List<List<GameCell>>): Int { | ||
val minimum = this.minByOrNull{ it.getLastEmptyBlock(grid).row - it.row } | ||
return minimum!!.getLastEmptyBlock(grid).row - minimum.row | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package game.normal | ||
|
||
import block_factory.BlockCreator | ||
import blocks.Block | ||
import board.Board | ||
import game.Game | ||
import game.GameCell | ||
import game.exceptions.BlockCanMoveDownException | ||
import game.exceptions.EmptyCurrentBlockException | ||
import movements.Direction | ||
import movements.Rotation | ||
import score.Points | ||
import score.ScoreCalculator | ||
|
||
internal const val GAME_COLUMNS = 10 | ||
internal const val GAME_ROWS = 20 | ||
internal const val DEFAULT_POINTS = 0 | ||
|
||
class NormalGame(private val creator: BlockCreator, private val scoreCalculator: ScoreCalculator): Game { | ||
|
||
private val board: Board = Board(GAME_ROWS, GAME_COLUMNS) | ||
private var block: Block? = null | ||
private var points: Points = Points(DEFAULT_POINTS) | ||
|
||
override fun getGrid(): List<List<GameCell>> { | ||
val result = getGameCellGridFromBoard() | ||
if (block != null) | ||
block!!.getNeededPositions() | ||
.forEach { result[it.row][it.column] = GameCell(block!!.getCell(), isCurrentBlockCell = true) } | ||
return result | ||
} | ||
|
||
override fun generateNextBlock() { | ||
block = creator.getBlock() | ||
} | ||
|
||
override fun moveBlock(direction: Direction) { | ||
assertBlockNotNull() | ||
if (blockIsInValidPosition(block!!.move(direction))) | ||
block = block!!.move(direction) | ||
} | ||
|
||
override fun rotateBlock(rotation: Rotation) { | ||
assertBlockNotNull() | ||
if (blockIsInValidPosition(block!!.rotate(rotation))) | ||
block = block!!.rotate(rotation) | ||
} | ||
|
||
override fun blockCanMoveDownNext(): Boolean { | ||
assertBlockNotNull() | ||
return blockIsInValidPosition(block!!.move(Direction.DOWN)) | ||
} | ||
|
||
override fun writeBlockToBoard() { | ||
assertBlockNotNull() | ||
if (blockCanMoveDownNext()) | ||
throw BlockCanMoveDownException("Block can move down next, so it's impossible to write it to the board") | ||
block!!.getNeededPositions().forEach { board.writePosition(block!!.getCell(), it) } | ||
checkIfCompletedRows() | ||
} | ||
|
||
override fun hasFinished(): Boolean { | ||
assertBlockNotNull() | ||
return !blockIsInValidPosition(block!!) | ||
} | ||
|
||
override fun getScore(): Points { | ||
return points | ||
} | ||
|
||
private fun assertBlockNotNull() { | ||
if (block == null) | ||
throw EmptyCurrentBlockException("Did you initialize a block with getNextBlock()?") | ||
} | ||
|
||
private fun getGameCellGridFromBoard(): MutableList<MutableList<GameCell>> { | ||
val immutableBoardGameCell = board.board.map { row -> row.map { cell -> GameCell(cell) } } | ||
return immutableBoardGameCell.map { it.toMutableList() }.toMutableList() | ||
} | ||
|
||
private fun blockIsInValidPosition(block: Block) = | ||
block.getNeededPositions().all { board.isInside(it) && board.isEmpty(it) } | ||
|
||
private fun checkIfCompletedRows() { | ||
if (hasCompletedRows()) { | ||
updateScore() | ||
board.redoBoardWithClearedCells() | ||
} | ||
} | ||
|
||
private fun hasCompletedRows() = (0 until GAME_ROWS).any { row -> board.isFull(row) } | ||
|
||
private fun getCompletedRows() = (0 until GAME_ROWS).count { row -> board.isFull(row) } | ||
|
||
private fun updateScore() { | ||
points = points.add(scoreCalculator.getScore(getCompletedRows())) | ||
} | ||
} |
Oops, something went wrong.