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) { constructor(width: Int = 8, height: Int = 8, mines: Int = 10) : this(width, height, Array(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) { 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) } }