Skip to content

Commit

Permalink
Cleaned up the second Board constructor and removed JSONBoard
Browse files Browse the repository at this point in the history
  • Loading branch information
henrykvdb committed May 10, 2019
1 parent 7c54169 commit 1133e0c
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 113 deletions.
67 changes: 28 additions & 39 deletions src/main/com/flaghacker/sttt/common/Board.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,38 +85,36 @@ class Board : Serializable {

/**
* Constructs a Board with a given state. Macro and main wins are calculated automatically.
* @param board 2 dimensional array containing who owns each tile. The format is `board[x][y]`
* @param nextPlayer the next player
* @param lastMove the last move played on the board, `null` means the next move is freeplay
* @param board 81 Char String containing the board. This string can be generated with toCompactString().
* */
constructor(board: Array<Array<Player>>, nextPlayer: Player, lastMove: Coord?) {
if (board.size != 9 && board.all { it.size != 9 })
throw IllegalArgumentException("Wrong board dimensions $board")
if (nextPlayer == Player.NEUTRAL)
throw IllegalArgumentException("nextPlayer can't be $nextPlayer")
if (lastMove != null && lastMove !in 0 until 81)
throw IllegalArgumentException("lastMove must be null or in range, was $lastMove")
constructor(board: String) {
if (board.length != 81 || board.toUpperCase().any { !Player.legalChar(it) } || board.count { it.isLowerCase() } > 1)
throw IllegalArgumentException("Illegal board string; $board")

val lastMove = board.indexOfFirst { it.isLowerCase() }
if (lastMove == -1 && board.any { it != Player.NEUTRAL.char })
throw IllegalArgumentException("No lastMove for a non-empty board; $board")
if (lastMove != -1 && (lastMove !in 0 until 81 || board[lastMove] == Player.NEUTRAL.char))
throw IllegalArgumentException("Illegal lastMove ($lastMove) for board; $board")

//val playCountDiff = board.count { it == Player.PLAYER.char } - board.count { it == Player.ENEMY.char }
//if (playCountDiff != 0 && playCountDiff != 1)
// throw IllegalArgumentException("The difference between Player and Enemy moves is too big; $playCountDiff")

this.openMacroMask = FULL_GRID
this.grids = IntArray(9)
this.mainGrid = 0
this.wonBy = null

this.grids = IntArray(10)
this.mainGrid = 0
for (i in 0 until 81) {
val owner = board[i.toPair().first][i.toPair().second]
if (owner != Player.NEUTRAL) {
val p = owner.ordinal
val om = i / 9
val os = i % 9

this.nextPlayer = owner
setTileAndUpdate(p, om, os)
}
val owner = Player.fromChar(board[i].toUpperCase())
if (owner != Player.NEUTRAL)
setTileAndUpdate(owner.ordinal, i / 9, i % 9)
}

this.lastMove = lastMove
this.nextPlayer = nextPlayer
this.macroMask = if (lastMove == null) openMacroMask else calcMacroMask(lastMove % 9)
this.lastMove = if (lastMove == -1) null else lastMove.toByte()
this.nextPlayer = if (lastMove == -1) Player.PLAYER else Player.fromChar(board[lastMove].toUpperCase()).other()
this.macroMask = if (lastMove == -1) openMacroMask else calcMacroMask(lastMove % 9)
}

/** Returns a copy of the current board. */
Expand All @@ -133,20 +131,6 @@ class Board : Serializable {
wonBy = board.wonBy
}

/**
* Returns a copy of the Board with the [Player]s swapped, including win
* @return A copy of the original [Board] with the [Player]s swapped
*/
fun flip(): Board {
val cpy = copy()
cpy.nextPlayer = nextPlayer.otherWithNeutral()
cpy.wonBy = wonBy?.otherWithNeutral()
for (i in 0 until 9)
cpy.grids[i] = (grids[i] shr 9) or ((grids[i] and FULL_GRID) shl 9)
cpy.mainGrid = (mainGrid shr 9) or ((mainGrid and FULL_GRID) shl 9)
return cpy
}

/**
* Returns which Player owns the requested macro.
* @param macroIndex the index of the macro (0-8)
Expand Down Expand Up @@ -300,8 +284,13 @@ class Board : Serializable {
if (openMacroMask.hasBit(os)) (1 shl os)
else openMacroMask

override fun toString() = toString(false)
fun toCompactString() = (0 until 81).map { toCoord(it % 9, it / 9) }.map {
if (it == lastMove)
tile(it).char.toLowerCase()
else tile(it).char
}.joinToString("")

override fun toString() = toString(false)
fun toString(showAvailableMoves: Boolean) = (0 until 81).joinToString("") {
val coord = toCoord(it % 9, it / 9)
when {
Expand Down
26 changes: 0 additions & 26 deletions src/main/com/flaghacker/sttt/common/JSONBoard.kt

This file was deleted.

18 changes: 9 additions & 9 deletions src/main/com/flaghacker/sttt/common/Player.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package com.flaghacker.sttt.common

enum class Player(val niceString: String) {
PLAYER("X"),
ENEMY("O"),
NEUTRAL(" ");
enum class Player(val char: Char) {
PLAYER('X'),
ENEMY('O'),
NEUTRAL(' ');

fun otherWithNeutral(): Player = if (this == NEUTRAL) NEUTRAL else this.other()
fun other(): Player = when (this) {
PLAYER -> ENEMY
ENEMY -> PLAYER
else -> throw IllegalArgumentException("player should be one of [PLAYER, ENEMY]; was " + this)
else -> throw IllegalArgumentException("player should be one of [PLAYER, ENEMY]; was $this")
}

fun otherWithNeutral(): Player = if (this == NEUTRAL) NEUTRAL else this.other()

companion object {
fun fromNiceString(string: String) = Player.values().find { it.niceString == string }
?: throw IllegalArgumentException("$string is not a valid Player")
fun legalChar(char: Char) = values().any { it.char == char }
fun fromChar(char: Char) = values().find { it.char == char }
?: throw IllegalArgumentException("$char is not a valid Player")
}
}
34 changes: 20 additions & 14 deletions src/test/com/flaghacker/sttt/BoardTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.junit.jupiter.api.assertThrows
import java.util.*
import kotlin.math.abs


fun randomBoard(rand: Random, moveCount: Int): Board {
return playRandom(Board(), rand, moveCount)
}
Expand All @@ -27,11 +28,20 @@ fun playRandom(startBoard: Board, rand: Random, moveCount: Int): Board {
}

private fun playedBoard(player: Player, playerMoves: IntArray, enemyMoves: IntArray, lastMove: Int): Board {
val board = Array(9) { Array(9) { Player.NEUTRAL } }
for (move in playerMoves) board[move.toPair().first][move.toPair().second] = player
for (move in enemyMoves) board[move.toPair().first][move.toPair().second] = player.other()

return Board(board, player, lastMove.toByte())
val board = (0 until 81).map {
when {
playerMoves.contains(it) -> player.char
enemyMoves.contains(it) -> player.other().char
else -> ' '
}
}.joinToString("")


//val lastMove = board.indexOfFirst { it.isUpperCase() }
val chars = board.toCharArray()
chars[lastMove] = chars[lastMove].toLowerCase()
return Board(String(chars))
}

private fun playedBoard(player: Player, moves: IntArray, lastMove: Int) =
Expand Down Expand Up @@ -73,12 +83,6 @@ class BoardTest {
assertBoardEquals(board, SerializationUtils.clone(board))
}

@Test
fun testDoubleFlip() {
val board = randomBoard(Random(0), 10)
assertBoardEquals(board, board.flip().flip())
}

@TestPlayersAndMacros
fun testManhattanWin(player: Player, om: Int) {
for (i in 0..2) {
Expand All @@ -103,7 +107,10 @@ class BoardTest {

@TestPlayers
fun testFullWin(player: Player) {
assertEquals(player, playedBoard(player, 0, 1, 2, 9, 10, 11, 18, 19, 20).wonBy) {
val board = playedBoard(player, 6, 7, 8, 15, 16, 17, 24, 25)
board.play(65)
board.play(26)
assertEquals(player, board.wonBy) {
"macro top line"
}
}
Expand All @@ -112,16 +119,15 @@ class BoardTest {
fun testFullMacro(player: Player, om: Int) {
//fill the macro and direct the next move to that macrp
val moves = ((9 * om) until (9 * om + 9)).toArray()
val lastMove = (8 - om) * 9 + om
val lastMove = om * 9 + om
val board = playedBoard(player, moves, lastMove)

assertEquals(81 - 9, board.availableMoves.size) { "freeplay" }
}

@TestPlayersAndMacros
fun testFullMixedMacro(player: Player, om: Int) {
val lastMove = (8 - om) * 9 + om

val lastMove = om * 9 + om
val board = playedBoard(
player,
P_MOVES.map { om * 9 + it }.toIntArray(),
Expand Down
25 changes: 0 additions & 25 deletions src/test/com/flaghacker/sttt/JSONBoardTest.kt

This file was deleted.

0 comments on commit 1133e0c

Please sign in to comment.