diff options
| -rw-r--r-- | mines.kt | 118 | ||||
| -rw-r--r-- | minesweeper.kt | 91 |
2 files changed, 118 insertions, 91 deletions
diff --git a/mines.kt b/mines.kt new file mode 100644 index 0000000..b4d7d77 --- /dev/null +++ b/mines.kt @@ -0,0 +1,118 @@ +data class Tile(val adjacentMines: Int = 0, val mine: Boolean = false, val masked: Boolean = true) + +class Grid(val width: Int, val height: Int, val tiles: Array<Tile>) { + constructor(width: Int = 8, height: Int = 8, mines: Int = 10) + : this(width, height, Array<Tile>(width * height) { Tile() }) { + for (i in 0 until mines) { + val x = (0 until width).random() + val y = (0 until height).random() + placeMine(x, y) + } + } + + private fun index(x: Int, y: Int) = y * width + x + private fun coordinatesValid(x: Int, y: Int) = y >= 0 && y < height && x >= 0 && x < width + + private fun place(x: Int, y: Int, tile: Tile) { + tiles[index(x, y)] = tile + } + + /** + * Returns the tile at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun tile(x: Int, y: Int) = if (coordinatesValid(x, y)) { + tiles[index(x, y)] + } else { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + /** + * Places a mine at the given coordinates. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun placeMine(x: Int, y: Int) { + if (!coordinatesValid(x, y)) { + throw IllegalArgumentException("Invalid coordinates (${x}, ${y})") + } + + place(x, y, Tile(mine = true)) + + val xMin = if (x - 1 < 0) { x } else { x - 1 } + val xMax = if (x + 1 >= width) { x } else { x + 1 } + val yMin = if (y - 1 < 0) { y } else { y - 1 } + val yMax = if (y + 1 >= height) { y } else { y + 1 } + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + with (tiles[index(x, y)]) { + if (!mine) { + place(x, y, copy(adjacentMines + 1)) + } + } + } + } + } + + /** + * Reveals a tile at the given coordinates according to the game rules. + * + * @throws IllegalArgumentException if the X coordinate is outside + * the range of [0, width), or if the Y coordinate is outside the + * range of [0, height). + */ + fun reveal(x: Int, y: Int) { + if (!coordinatesValid(x, y) || !tile(x, y).masked) { + return; + } + + with (tile(x, y)) { + place(x, y, copy(masked = false)) + + if (!mine && adjacentMines == 0) { + val xMin = if (x - 1 < 0) { x } else { x - 1 } + val xMax = if (x + 1 >= width) { x } else { x + 1 } + val yMin = if (y - 1 < 0) { y } else { y - 1 } + val yMax = if (y + 1 >= height) { y } else { y + 1 } + + for (y in yMin..yMax) { + for (x in xMin..xMax) { + reveal(x, y) + } + } + } + } + } + + override fun toString(): String = buildString { + for (y in 0 until height) { + for (x in 0 until width) { + val tile = tile(x, y) + append(when { + tile.masked -> "." + tile.mine -> "M" + tile.adjacentMines == 0 -> " " + else -> tile.adjacentMines.toString() + } + " ") + } + append("\n") + } + } +} + +fun main(args: Array<String>) { + val grid = Grid() + + for (i in 0 until 9) { + val x = (0 until 8).random() + val y = (0 until 8).random() + grid.reveal(x, y) + println(grid) + } +} diff --git a/minesweeper.kt b/minesweeper.kt deleted file mode 100644 index ee4c575..0000000 --- a/minesweeper.kt +++ /dev/null @@ -1,91 +0,0 @@ -data class Tile(val minesAdjacent: Int, val mine: Boolean, val masked: Boolean) - -data class Grid(val tiles: Array<Tile>, val width: Int, val height: Int) - -fun Grid.valid(x: Int, y: Int) = x >= 0 && x < width && y >= 0 && y < height -fun Grid.index(x: Int, y: Int) = y * width + x -fun Grid.tile(x: Int, y: Int) = tiles[index(x, y)] -fun Grid.get(x: Int, y: Int) = Pair(tile(x, y), index(x, y)) - -fun Grid.put(x: Int, y: Int, tile: Tile) { - tiles[index(x, y)] = tile -} - -fun addMine(grid: Grid, x: Int, y: Int) { - if (!grid.valid(x, y)) { - throw IllegalArgumentException() - } - - grid.put(x, y, Tile(minesAdjacent = 0, mine = true, masked = true)) - - for (i in -1..1) { - for (j in -1..1) { - if (!(i == 0 && j == 0) && grid.valid(x + i, y + j)) { - val old = grid.tile(x + i, y + j) - val minesAdjacent = if (old.mine) { 0 } else { old.minesAdjacent + 1 } - grid.put(x + i, y + j, Tile(minesAdjacent, old.mine, old.masked)) - } - } - } -} - -fun revealTile(grid: Grid, x: Int, y: Int) { - if (!grid.valid(x, y)) { - return - } - - val tile = grid.tile(x, y) - - if (!tile.masked) { - return - } - - grid.put(x, y, Tile(tile.minesAdjacent, tile.mine, masked = false)) - - if (tile.minesAdjacent == 0) { - revealTile(grid, x - 1, y - 1) - revealTile(grid, x, y - 1) - revealTile(grid, x + 1, y - 1) - revealTile(grid, x - 1, y ) - revealTile(grid, x + 1, y ) - revealTile(grid, x - 1, y + 1) - revealTile(grid, x, y + 1) - revealTile(grid, x + 1, y + 1) - } -} - -// TODO: Turn this into a toString? - -fun printTiles(grid: Grid) { - for (y in 0..grid.height-1) { - for (x in 0..grid.width-1) { - val tile = grid.tile(x, y) - val display = when { - tile.masked -> "." - tile.mine -> "M" - tile.minesAdjacent == 0 -> " " - else -> tile.minesAdjacent.toString() - } - - print("${display} ") - } - println() - println() - } -} - -fun main(args: Array<String>) { - val width = 10 - val height = 10 - val tiles: Array<Tile> = Array(width * height, { Tile(0, mine = false, masked = true) }) - - val grid = Grid(tiles, width, height) - - addMine(grid, 0, 0) - addMine(grid, 1, 0) - addMine(grid, 2, 0) - addMine(grid, 3, 0) - revealTile(grid, 5, 5) - - printTiles(grid) -} |