summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mines.kt118
-rw-r--r--minesweeper.kt91
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)
-}