Last active
April 21, 2023 14:11
-
-
Save jayrambhia/8260e059ec3c4e287acdedc3ebf322a7 to your computer and use it in GitHub Desktop.
Battleship game in Kotlin
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
<?xml version="1.0" encoding="utf-8"?> | |
<FrameLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context="com.fenchtose.battleship.GameActivity"> | |
<android.support.v7.widget.RecyclerView | |
android:id="@+id/recyclerview" | |
android:background="@color/cell_background" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
tools:listitem="@layout/square_cell_itemview"/> | |
<TextView | |
android:id="@+id/gamestate_info" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_gravity="bottom" | |
android:layout_margin="16dp" | |
android:textSize="21sp" | |
android:gravity="center" | |
tools:text="Your turn!"/> | |
</FrameLayout> |
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
class BattleshipActivity: AppCompatActivity() { | |
private lateinit var gridAdpater: UiCellAdapter | |
private lateinit var myBoard: Board | |
private lateinit var otherBoard: Board | |
private val width: Int = 10 | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_game); | |
gridAdapter = UiCellAdapter(this, { playMove(it.point) }) | |
gridAdapter.setHasStableIds(true) | |
val recyclerview = findViewById<RecyclerView>(R.id.recyclerview).apply { | |
layoutManager = GridLayoutManager(this@BattleshipActivity, width) | |
adapter = gridAdpater | |
} | |
setupGame() | |
updateUI() | |
} | |
private fun updateUI() { | |
val cells = convert(myBoard) | |
adapter.cells.clear() | |
adapter.cells.addAll(cells) | |
adapter.cells.notifyDataSetChanged() | |
} | |
private fun playMove(p: Point) { | |
// check if already played | |
if (p in otherBoard.hits || p in otherBoard.misses) { | |
return | |
} | |
var missed = true | |
var hitShip: Ship? = null | |
for (ship in otherBoard.ships) { | |
if (p in ship) { | |
// we hit the ship | |
hitShip = ship.copy(hits=ship.hits.plus(p)) | |
missed = false | |
break | |
} | |
} | |
if (missed) { | |
myBoard = myBoard.copy(misses = myBoard.misses.plus(p)) | |
otherBoard = otherBoard.copy( | |
ships = if (hitShip) != null ? otherBoard.ships.update(hitShip), | |
opponentMisses = otherBoard.opponentMisses.plus(p)) | |
} | |
updateUI() | |
} | |
private fun convert(board: Board): List<Cell> { | |
val cells = ArrayList<Cell>(board.width * board.height) | |
for (i in 0 until board.height) { | |
for (j in 0 until board.width) { | |
val point = Point(j, i) | |
cells.add(Cell( | |
point = point, | |
hasShip = board.ships.contains(point), | |
direction = board.ships.getShipDirection(point), | |
userHit = board.hits.contains(point), | |
userMissed = board.misses.contains(point), | |
opponentMissed = board.opponentMisses.contains(point), | |
opponentHit = board.opponentHits.contains(point) | |
)) | |
} | |
} | |
return cells | |
} | |
private fun setupGame() { | |
myBoard = Board(0, User("Player 1"), width, width, getRandomShips(0)) | |
otherBoard = Board(1, User("Player 2"), width, width, getRandomShips(1)) | |
} | |
private fun getRandomShips(startId: Int) { | |
val ships = ArrayList<Ship>() | |
for (i in 1..5) { | |
ships.add(Ship(startId + i, i, Point(i, i), (if (i%2 == 0) Direction.HORIZONTAL else Direction.VERTICAL ))) | |
} | |
return ships | |
} | |
} |
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
data class Point(val col: Int, val row: Int) { | |
fun isValid() = col >= 0 && row >= 0 | |
operator fun plus(wd: WeighedDirection) = when (wd.d) { | |
Direction.HORIZONTAL -> Point(col + wd.len, row) | |
Direction.VERTICAL -> Point(col, row + wd.len) | |
} | |
} | |
enum class Direction { | |
HORIZONTAL, VERTICAL; | |
operator fun times(n: Int): WeighedDirection { | |
return WeighedDirection(this, n) | |
} | |
} | |
data class WeighedDirection(val d: Direction, val len: Int) | |
data class Ship( | |
val id: Int, | |
val size: Int, val start: Point, val direction: Direction, | |
val hits: Set<Point> = setOf()) { | |
val end = start + direction*(size-1) | |
val destroyed = hits.size == size | |
// check for overlaps | |
operator fun contains(other: Ship): Boolean { | |
if (other.direction == direction) { | |
return other.start in this || other.end in this | |
} | |
val vertical = if (other.direction == Direction.VERTICAL) other else this | |
val horizontal = if (other.direction == Direction.HORIZONTAL) other else this | |
return horizontal.start.row in vertical.start.row..vertical.end.row | |
&& vertical.start.col in horizontal.start.col..horizontal.end.col | |
} | |
operator fun contains(p: Point): Boolean { | |
return when(direction) { | |
Direction.HORIZONTAL -> start.row == p.row && start.col <= p.col && end.col >= p.col | |
Direction.VERTICAL -> start.col == p.col && start.row <= p.row && end.row >= p.row | |
} | |
} | |
} | |
data class Board( | |
val id: Int, val user: User, | |
val width: Int, val height: Int, | |
val ships: List<Ship> = listOf(), | |
val hits: Set<Point> = setOf(), | |
val misses: Set<Point> = setOf(), | |
val opponentHits: Set<Point> = setOf(), | |
val opponentMisses: Set<Point> = setOf()) { | |
val activeShips = ships.filter { !it.destroyed }.map { it.id } | |
val destroyedShips = ships.filter { it.destroyed }.map { it.id } | |
val lost = ships.isNotEmpty() && activeShips.isEmpty() | |
fun fits(ship: Ship) = ship.start in this && ship.end in this | |
fun isOverlap(ship: Ship): Boolean { | |
return ships.any { ship in it } | |
} | |
operator fun contains(p: Point) = p.isValid() && p.col < width && p.row < height | |
} | |
// Extension function to update the ship (based on id) and return new list | |
fun List<Ship>.update(ship: Ship): List<Ship> { | |
var index = -1 | |
forEachIndexed { i, s -> | |
if (s.id == ship.id) { | |
index = i | |
return@forEachIndexed | |
} | |
} | |
if (index != -1) { | |
val mutable = ArrayList(this) | |
mutable.removeAt(index) | |
mutable.add(index, ship) | |
return mutable | |
} | |
return this | |
} | |
// Extension function to check if any of the ships are at the given point | |
fun List<Ship>.contains(p: Point): Boolean { | |
forEach { | |
if (p in it) { | |
return true | |
} | |
} | |
return false | |
} | |
// Extension function to check the direction of the ship at the given point | |
fun List<Ship>.getShipDirection(p: Point): Direction? { | |
forEach { | |
if (p in it) { | |
return it.direction | |
} | |
} | |
return null | |
} |
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
data class Cell( | |
val point: Point, | |
val hasShip: Boolean = false, | |
val direction: Direction? = null, | |
val userHit: Boolean = false, | |
val userMissed: Boolean = false, | |
val opponentHit: Boolean = false, | |
val opponentMissed: Boolean = false) | |
class SquareCell: View { | |
private var cell: Cell? = null | |
private val shipPaint: Paint | |
private val shipHitPaint: Paint | |
private val hitPaint: Paint | |
private val missedPaint: Paint | |
private val opponentHitPaint: Paint | |
private val opponentMissedPaint: Paint | |
private val borderPaint: Paint | |
private val rect: Rect = Rect() | |
constructor(context: Context) : this(context, null) | |
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) | |
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { | |
shipPaint = initPaint(R.color.cell_my_ship, cap = Paint.Cap.ROUND) | |
shipHitPaint = initPaint(R.color.cell_my_ship_hit) | |
hitPaint = initPaint(R.color.cell_ship_hit) | |
opponentMissedPaint = initPaint(R.color.cell_opponent_miss) | |
opponentHitPaint = initPaint(R.color.cell_ship_hit) | |
missedPaint = initPaint(R.color.cell_ship_miss) | |
borderPaint = initPaint(R.color.cell_border, Paint.Style.STROKE, 2f) | |
if (isInEditMode) { | |
hasShip = true | |
shipDirection = Direction.HORIZONTAL | |
} | |
} | |
private fun initPaint(@ColorRes color: Int, style: Paint.Style = Paint.Style.FILL, width: Float? = null, cap: Paint.Cap? = null): Paint { | |
return Paint(Paint.ANTI_ALIAS_FLAG).apply { | |
this.color = ContextCompat.getColor(context, color) | |
this.style = style | |
width?.let { this.strokeWidth = it } | |
cap?.let { this.strokeCap = it } | |
} | |
} | |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec) | |
} | |
override fun onDraw(canvas: Canvas) { | |
rect.left = 0 | |
rect.top = 0 | |
rect.bottom = canvas.height | |
rect.right = canvas.width | |
canvas.drawRect(rect, borderPaint) | |
cell?.apply { | |
if (hasShip && direction != null) { | |
when (direction) { | |
Direction.HORIZONTAL -> { | |
rect.bottom = canvas.height - 64 | |
rect.right = canvas.width | |
} | |
Direction.VERTICAL -> { | |
rect.bottom = canvas.height | |
rect.right = canvas.width - 64 | |
} | |
} | |
val paint = if (!opponentHit) shipPaint else shipHitPaint | |
canvas.drawRect(rect, paint) | |
} | |
if (userHit) { | |
canvas.drawCircle((canvas.width - 24).toFloat(), (canvas.height - 24).toFloat(), 16f, hitPaint) | |
} else if (userMissed) { | |
canvas.drawCircle((canvas.width - 24).toFloat(), (canvas.height - 24).toFloat(), 16f, missedPaint) | |
} | |
if (opponentHit) { | |
canvas.drawCircle(24f, 24f, 16f, opponentHitPaint) | |
} else if (opponentMissed) { | |
canvas.drawCircle(24f, 24f, 16f, opponentMissedPaint) | |
} | |
} | |
} | |
fun bind(cell: Cell) { | |
this.cell = cell | |
invalidate() | |
} | |
} | |
class UiCellAdapter(context: Context, private val onClick: ((Cell) -> Unit)): RecyclerView.Adapter<RecyclerView.ViewHolder>() { | |
val cells = ArrayList<Cell>() | |
private val inflater = LayoutInflater.from(context) | |
override fun getItemCount(): Int { | |
return cells.size | |
} | |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { | |
return UiCellViewHolder(inflater.inflate(R.layout.square_cell_itemview, parent, false), onClick) | |
} | |
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { | |
holder as UiCellViewHolder | |
holder.bind(cells[position]) | |
} | |
override fun getItemId(position: Int): Long { | |
return cells[position].hashCode().toLong() | |
} | |
class UiCellViewHolder(itemView: View, onClick: (Cell) -> Unit) : RecyclerView.ViewHolder(itemView) { | |
private val view = itemView as SquareCell | |
private var cell: Cell? = null | |
init { | |
view.setOnClickListener { | |
cell?.let(onClick) | |
} | |
} | |
fun bind(cell: Cell) { | |
this.cell = cell | |
view.bind(cell) | |
} | |
} | |
} |
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
fun Board.reduceOffense(action: Action): Board { | |
return when(action) { | |
is GeneratedAction.MissedMove -> copy(misses = misses.plus(action.point)) | |
is GeneratedAction.DefinitiveAction -> copy(hits = hits.plus(action.point)) | |
else -> this | |
} | |
} | |
fun Board.reduceDefense(action: Action): Board { | |
return when(action) { | |
is GeneratedAction.MissedMove -> copy(opponentMisses = opponentMisses.plus(action.point)) | |
is GeneratedAction.DefinitiveAction -> { | |
val ship = ships.getById(action.ship.id) | |
if (ship != null) { | |
val updated = ship.reduce(action) | |
copy(opponentHits = opponentHits.plus(action.point), ships = ships.update(updated)) | |
} else { | |
this | |
} | |
} | |
else -> this | |
} | |
} | |
fun Board.reduceSetup(action: Action): Board { | |
return when(action) { | |
is AddShip -> copy(ships = ships.add(action.ship)) | |
else -> this | |
} | |
} | |
fun Ship.reduce(action: Action): Ship { | |
return when(action) { | |
is GeneratedAction.DefinitiveAction -> copy(hits = hits.plus(action.point)) | |
else -> this | |
} | |
} |
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
fun destroyMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is GeneratedAction.DefinitiveAction.HitMove) { | |
if (action.ship.hits.size + 1 == action.ship.size) { | |
return next(state, GeneratedAction.DefinitiveAction.DestroyShip(action.offense, action.defense, action.point, action.ship), dispatch) | |
} | |
} | |
return next(state, action, dispatch) | |
} | |
fun lostMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is GeneratedAction.DefinitiveAction.DestroyShip) { | |
if (state.boardById(action.defense).activeShips.size == 1) { | |
return next(state, GeneratedAction.DefinitiveAction.LostGame(action.offense, action.defense, action.point, action.ship), dispatch) | |
} | |
} | |
return next(state, action, dispatch) | |
} |
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
class GameActivity : AppCompatActivity() { | |
private lateinit var adapter: UiCellAdapter | |
private lateinit var gamestateInfo: TextView | |
private lateinit var handler: Handler | |
private lateinit var store: Gamestore | |
private lateinit var myBoard: Board | |
private lateinit var otherBoard: Board | |
private var unsubscribe: Unsubscribe? = null | |
private var dispatch: Dispatch? = null | |
private var gameOver: Boolean = false | |
private val width: Int = 10 | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_game) | |
handler = Handler() | |
gamestateInfo = findViewById(R.id.gamestate_info) | |
val recyclerview = findViewById<RecyclerView>(R.id.recyclerview) | |
adapter = UiCellAdapter(this, { | |
if (gameOver) { | |
return@UiCellAdapter | |
} | |
dispatch?.invoke(Move(myBoard.id, otherBoard.id, it.point)) | |
}) | |
adapter.setHasStableIds(true) | |
recyclerview.layoutManager = GridLayoutManager(this, width) | |
recyclerview.adapter = adapter | |
adapter.notifyDataSetChanged() | |
myBoard = Board(0, User("Player 1"), width, width) | |
otherBoard = Board(1, User("Player 2"), width, width) | |
val initState = GameState( | |
board1 = myBoard, | |
board2 = otherBoard, | |
lastPlayed = otherBoard.id | |
) | |
store = Gamestore(initState) | |
setupGame() | |
} | |
override fun onResume() { | |
super.onResume() | |
unsubscribe = store.subscribe({ state, dispatch -> | |
this.dispatch = dispatch | |
myBoard = state.board1 | |
otherBoard = state.board2 | |
val cells = generateCells(myBoard) | |
adapter.cells.clear() | |
adapter.cells.addAll(cells) | |
adapter.notifyDataSetChanged() | |
gamestateInfo.text = if (state.lastPlayed == otherBoard.id) "Your turn" else "Player 2 turn" | |
gameOver = state.gameOver | |
if (state.gameOver) { | |
gamestateInfo.text = "Game over!" | |
} | |
if(state.lastPlayed == myBoard.id && !state.gameOver) { | |
handler.postDelayed({ | |
playOther() | |
}, 300) | |
} | |
}) | |
} | |
override fun onPause() { | |
super.onPause() | |
unsubscribe?.invoke() | |
} | |
private fun setupGame() { | |
for (i in 1..5) { | |
store.dispatch(AddShip(myBoard.id, Ship(i, i, Point(i, i), (if (i%2 == 0) Direction.HORIZONTAL else Direction.VERTICAL )))) | |
store.dispatch(AddShip(otherBoard.id, Ship(10 + i, i, Point(i, i), (if (i%2 == 0) Direction.HORIZONTAL else Direction.VERTICAL )))) | |
} | |
} | |
private fun playOther() { | |
val random = Random() | |
while(true) { | |
val point = Point(random.nextInt(width), random.nextInt(width)) | |
if (point !in otherBoard.hits && point !in otherBoard.misses) { | |
dispatch?.invoke(Move(otherBoard.id, myBoard.id, point)) | |
break | |
} | |
} | |
} | |
private fun generateCells(board: Board): ArrayList<Cell> { | |
val cells = ArrayList<Cell>(board.width * board.height) | |
for (i in 0 until board.height) { | |
for (j in 0 until board.width) { | |
val point = Point(j, i) | |
cells.add(Cell( | |
point = point, | |
hasShip = board.ships.contains(point), | |
direction = board.ships.getShipDirection(point), | |
userHit = board.hits.contains(point), | |
userMissed = board.misses.contains(point), | |
opponentMissed = board.opponentMisses.contains(point), | |
opponentHit = board.opponentHits.contains(point) | |
)) | |
} | |
} | |
return cells | |
} | |
} |
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
fun GameState.reduceSetup(action: Action): GameState { | |
return when(action) { | |
is AddShip -> reduceChildState(this, boardById(action.offense), action, Board::reduceSetup, { state, board -> state.updateBoard(board) }) | |
else -> this | |
} | |
} | |
fun GameState.reduceGameplay(action: Action): GameState { | |
return when(action) { | |
is GeneratedAction -> { | |
val offense = boardById(action.offense).reduceOffense(action) | |
val defense = boardById(action.defense).reduceDefense(action) | |
copy(board1 = whichBoard(board1, offense, defense), | |
board2 = whichBoard(board2, offense, defense), | |
gameOver = action is GeneratedAction.DefinitiveAction.LostGame) | |
} | |
is SwitchAction -> { | |
reduceGameplay(action.last).copy(lastPlayed = action.offense) | |
} | |
else -> this | |
} | |
} | |
fun<State, Child> reduceChildState( | |
state: State, | |
child: Child, | |
action: Action, | |
reducer: Reducer<Child>, | |
onReduced: (State, Child) -> State): State { | |
val reduced = reducer.invoke(child, action) | |
if (reduced === child) { | |
return state | |
} | |
return onReduced(state, reduced) | |
} | |
private fun whichBoard(board: Board, offense: Board, defense: Board): Board { | |
return when(board.id) { | |
offense.id -> offense | |
defense.id -> defense | |
else -> board | |
} | |
} |
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
fun gameSetupMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is AddShip) { | |
val board = state.boardById(action.offense) | |
if (!board.fits(action.ship) || board.isOverlap(action.ship)) { | |
return next(state, AddShipInvalid(action.offense, action.ship), dispatch) | |
} | |
} | |
return next(state, action, dispatch) | |
} |
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
data class GameState( | |
val board1: Board, | |
val board2: Board, | |
val lastPlayed: Int, | |
val gameOver: Boolean = false) { | |
fun hasBoardById(id: Int): Boolean { | |
return when(id) { | |
board1.id, board2.id -> true | |
else -> false | |
} | |
} | |
fun boardById(id: Int): Board { | |
return when (id) { | |
board1.id -> board1 | |
board2.id -> board2 | |
else -> throw RuntimeException("No such board with id: $id") | |
} | |
} | |
fun updateBoard(board: Board): GameState { | |
return when(board.id) { | |
board1.id -> copy(board1 = board) | |
board2.id -> copy(board2 = board) | |
else -> this | |
} | |
} | |
} |
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
class Gamestore(initialSate: GameState): SimpleStore<GameState>( | |
initialState = initialSate, | |
middlewares = listOf( | |
::stateValidityMiddleware, | |
::gameSetupMiddleware, | |
::moveMiddleware, | |
::hitMiddleware, | |
::destroyMiddleware, | |
::lostMiddleware, | |
::switcherMiddleware | |
), | |
reducers = listOf( | |
GameState::reduceSetup, | |
GameState::reduceGameplay | |
) | |
) |
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
data class AddShip(val offense: Int, val ship: Ship): Action | |
data class AddShipInvalid(val offense: Int, val ship: Ship): Action | |
sealed class GeneratedAction(val offense: Int, val defense: Int, val point: Point): Action { | |
class InvalidMove(offense: Int, defense: Int, point: Point): GeneratedAction(offense, defense, point) | |
class PlayMove(offense: Int, defense: Int, point: Point): GeneratedAction(offense, defense, point) | |
class MissedMove(offense: Int, defense: Int, point: Point): GeneratedAction(offense, defense, point) | |
sealed class DefinitiveAction(offense: Int, defense: Int, point: Point, val ship: Ship): GeneratedAction(offense, defense, point) { | |
class HitMove(offense: Int, defense: Int, point: Point, ship: Ship) : DefinitiveAction(offense, defense, point, ship) | |
class DestroyShip(offense: Int, defense: Int, point: Point, ship: Ship) : DefinitiveAction(offense, defense, point, ship) | |
class LostGame(offense: Int, defense: Int, point: Point, ship: Ship) : DefinitiveAction(offense, defense, point, ship) | |
} | |
} | |
data class SwitchAction(val offense: Int, val defense: Int, val last: GeneratedAction): Action | |
object InvalidState: Action |
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
fun hitMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is GeneratedAction.PlayMove) { | |
val defense = state.boardById(action.defense) | |
for (id in defense.activeShips) { | |
defense.ships.getById(id)?.let { | |
if (action.point in it) { | |
return next(state, GeneratedAction.DefinitiveAction.HitMove(action.offense, action.defense, action.point, it), dispatch) | |
} | |
} | |
} | |
return next(state, GeneratedAction.MissedMove(action.offense, action.defense, action.point), dispatch) | |
} | |
return next(state, action, dispatch) | |
} |
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
fun moveMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is Move) { | |
val offense = state.boardById(action.offense) | |
if (offense.hits.contains(action.point) || offense.misses.contains(action.point)) { | |
return next(state, GeneratedAction.InvalidMove(action.offense, action.defense, action.point), dispatch) | |
} | |
return next(state, GeneratedAction.PlayMove(action.offense, action.defense, action.point), dispatch) | |
} | |
return next(state, action, dispatch) | |
} |
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
<?xml version="1.0" encoding="utf-8"?> | |
<com.fenchtose.battleship.ui.SquareCell | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
android:orientation="vertical" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
</com.fenchtose.battleship.ui.SquareCell> |
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
fun stateValidityMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
when(action) { | |
is Move -> { | |
if (!state.hasBoardById(action.offense) || !state.hasBoardById(action.defense)) { | |
return InvalidState | |
} | |
} | |
is GeneratedAction -> { | |
if (!state.hasBoardById(action.offense) || !state.hasBoardById(action.defense)) { | |
return InvalidState | |
} | |
} | |
is AddShip -> { | |
if (!state.hasBoardById(action.offense)) { | |
return InvalidState | |
} | |
} | |
} | |
return next(state, action, dispatch) | |
} |
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
fun switcherMiddleware(state: GameState, action: Action, dispatch: Dispatch, next: Next<GameState>): Action { | |
if (action is GeneratedAction && action !is GeneratedAction.InvalidMove) { | |
return next(state, SwitchAction(action.offense, action.defense, action), dispatch) | |
} | |
return next(state, action, dispatch) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment