diff --git a/README.md b/README.md index 4c354962f..c7b515b12 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# kotlin-minesweeper \ No newline at end of file +# kotlin-minesweeper + +## ๐Ÿš€ 1๋‹จ๊ณ„ - ์ง€๋ขฐ ์ฐพ๊ธฐ(๊ทธ๋ฆฌ๊ธฐ) +### ๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ +์ง€๋ขฐ ์ฐพ๊ธฐ๋ฅผ ๋ณ€ํ˜•ํ•œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌํ˜„ํ•œ๋‹ค. + +- ๋†’์ด์™€ ๋„ˆ๋น„, ์ง€๋ขฐ ๊ฐœ์ˆ˜๋ฅผ ์ž…๋ ฅ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. +- ์ง€๋ขฐ๋Š” ๋ˆˆ์— ์ž˜ ๋„๋Š” ๊ฒƒ์œผ๋กœ ํ‘œ๊ธฐํ•œ๋‹ค. +- ์ง€๋ขฐ๋Š” ๊ฐ€๊ธ‰์  ๋žœ๋ค์— ๊ฐ€๊น๊ฒŒ ๋ฐฐ์น˜ํ•œ๋‹ค. + +### ์‹คํ–‰ ๊ฒฐ๊ณผ +``` +๋†’์ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. +10 + +๋„ˆ๋น„๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. +10 + +์ง€๋ขฐ๋Š” ๋ช‡ ๊ฐœ์ธ๊ฐ€์š”? +10 + +์ง€๋ขฐ์ฐพ๊ธฐ ๊ฒŒ์ž„ ์‹œ์ž‘ +C C C * C C C * C C +C C * C * C C C C C +C C C C C C C C C C +C C C C C C C C C C +* C C C C C C C C C +C C C C C C * C C C +C C * C C C * C C C +C C C C C C * C C * +C C C C C C C C C C +C C C C C C C C C C +``` + +### ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ +๊ฐ์ฒด์ง€ํ–ฅ ์ƒํ™œ ์ฒด์กฐ ์›์น™์„ ์ง€ํ‚ค๋ฉด์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ๋‹ค. +#### ๊ฐ์ฒด์ง€ํ–ฅ ์ƒํ™œ ์ฒด์กฐ ์›์น™ +1. ํ•œ ๋ฉ”์„œ๋“œ์— ์˜ค์ง ํ•œ ๋‹จ๊ณ„์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๋งŒ ํ•œ๋‹ค. +2. else ์˜ˆ์•ฝ์–ด๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. +3. ๋ชจ๋“  ์›์‹œ ๊ฐ’๊ณผ ๋ฌธ์ž์—ด์„ ํฌ์žฅํ•œ๋‹ค. +4. ํ•œ ์ค„์— ์ ์„ ํ•˜๋‚˜๋งŒ ์ฐ๋Š”๋‹ค. +5. ์ค„์—ฌ ์“ฐ์ง€ ์•Š๋Š”๋‹ค(์ถ•์•ฝ ๊ธˆ์ง€). +6. ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ž‘๊ฒŒ ์œ ์ง€ํ•œ๋‹ค. +7. 3๊ฐœ ์ด์ƒ์˜ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ํด๋ž˜์Šค๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. +8. ์ผ๊ธ‰ ์ปฌ๋ ‰์…˜์„ ์“ด๋‹ค. +9. getter/setter/ํ”„๋กœํผํ‹ฐ๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. + +### ๊ตฌํ˜„ ์‚ฌํ•ญ +- [x] ๋†’์ด๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค +- [x] ๋„ˆ๋น„๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค +- [x] ์ง€๋ขฐ ๊ฐฏ์ˆ˜๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค +- [x] ์ž…๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e78e72956..4a0fdf936 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ repositories { dependencies { testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2") testImplementation("org.assertj", "assertj-core", "3.22.0") - testImplementation("io.kotest", "kotest-runner-junit5", "5.2.3") + testImplementation("io.kotest", "kotest-runner-junit5", "5.5.3") } tasks { diff --git a/src/main/kotlin/minesweeper/controller/MineSweeperController.kt b/src/main/kotlin/minesweeper/controller/MineSweeperController.kt new file mode 100644 index 000000000..2c94cafe7 --- /dev/null +++ b/src/main/kotlin/minesweeper/controller/MineSweeperController.kt @@ -0,0 +1,16 @@ +package minesweeper.controller + +import minesweeper.model.MineSweeperGame +import minesweeper.view.InputView +import minesweeper.view.OutputView + +class MineSweeperController { + fun start() { + val game = MineSweeperGame(InputView.inputHeight(), InputView.inputWidth(), InputView.inputCountOfMine()) + OutputView.printMap(game.minefield()) + } +} + +fun main() { + MineSweeperController().start() +} diff --git a/src/main/kotlin/minesweeper/model/MineSweeperGame.kt b/src/main/kotlin/minesweeper/model/MineSweeperGame.kt new file mode 100644 index 000000000..b5b9d44ad --- /dev/null +++ b/src/main/kotlin/minesweeper/model/MineSweeperGame.kt @@ -0,0 +1,22 @@ +package minesweeper.model + +import minesweeper.model.board.Minefield +import minesweeper.model.cell.Opening + +class MineSweeperGame( + private val countOfMine: Int, + private val minefield: Minefield +) { + constructor(rows: Int, cols: Int, countOfMine: Int) : this( + countOfMine = countOfMine, + minefield = Minefield(rows, cols) + ) + + init { + plantingMines() + } + + fun minefield(): Array> = minefield.minefield + + private fun plantingMines() = minefield.plantingMine(countOfMine) +} diff --git a/src/main/kotlin/minesweeper/model/board/Cols.kt b/src/main/kotlin/minesweeper/model/board/Cols.kt new file mode 100644 index 000000000..abd59e294 --- /dev/null +++ b/src/main/kotlin/minesweeper/model/board/Cols.kt @@ -0,0 +1,11 @@ +package minesweeper.model.board + +import kotlin.random.Random + +data class Cols(val value: Int) { + init { + require(value > 0) { "์ž…๋ ฅ ๊ฐ’์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค." } + } + + fun getPosition() = Random.nextInt(value) +} diff --git a/src/main/kotlin/minesweeper/model/board/Minefield.kt b/src/main/kotlin/minesweeper/model/board/Minefield.kt new file mode 100644 index 000000000..db4dfad7d --- /dev/null +++ b/src/main/kotlin/minesweeper/model/board/Minefield.kt @@ -0,0 +1,42 @@ +package minesweeper.model.board + +import minesweeper.model.cell.Island +import minesweeper.model.cell.Mine +import minesweeper.model.cell.Opening + +class Minefield( + private val rows: Rows, + private val cols: Cols, + val minefield: Array> +) { + constructor(rows: Int, cols: Int) : this( + rows = Rows(rows), + cols = Cols(cols), + minefield = Array(rows) { Array(cols) { Island() } } + ) + + constructor(rows: Int, cols: Int, minefield: Array>) : this( + rows = Rows(rows), + cols = Cols(cols), + minefield = minefield + ) + + fun plantingMine(countOfMine: Int) { + var mineCount = 0 + + while (mineCount < countOfMine) { + val row = rows.getPosition() + val col = cols.getPosition() + + mineCount = plantingResult(row, col, mineCount) + } + } + + private fun plantingResult(row: Int, col: Int, mineCount: Int): Int { + if (minefield[row][col] is Island) { + minefield[row][col] = Mine() + return mineCount + 1 + } + return mineCount + } +} diff --git a/src/main/kotlin/minesweeper/model/board/Rows.kt b/src/main/kotlin/minesweeper/model/board/Rows.kt new file mode 100644 index 000000000..acb808b71 --- /dev/null +++ b/src/main/kotlin/minesweeper/model/board/Rows.kt @@ -0,0 +1,11 @@ +package minesweeper.model.board + +import kotlin.random.Random + +data class Rows(val value: Int) { + init { + require(value > 0) { "์ž…๋ ฅ ๊ฐ’์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค." } + } + + fun getPosition() = Random.nextInt(value) +} diff --git a/src/main/kotlin/minesweeper/model/cell/Island.kt b/src/main/kotlin/minesweeper/model/cell/Island.kt new file mode 100644 index 000000000..050f12d21 --- /dev/null +++ b/src/main/kotlin/minesweeper/model/cell/Island.kt @@ -0,0 +1,3 @@ +package minesweeper.model.cell + +data class Island(override val value: String = "C") : Opening diff --git a/src/main/kotlin/minesweeper/model/cell/Mine.kt b/src/main/kotlin/minesweeper/model/cell/Mine.kt new file mode 100644 index 000000000..c8bc86d49 --- /dev/null +++ b/src/main/kotlin/minesweeper/model/cell/Mine.kt @@ -0,0 +1,3 @@ +package minesweeper.model.cell + +data class Mine(override val value: String = "*") : Opening diff --git a/src/main/kotlin/minesweeper/model/cell/Opening.kt b/src/main/kotlin/minesweeper/model/cell/Opening.kt new file mode 100644 index 000000000..2d919b4ff --- /dev/null +++ b/src/main/kotlin/minesweeper/model/cell/Opening.kt @@ -0,0 +1,5 @@ +package minesweeper.model.cell + +interface Opening { + val value: String +} diff --git a/src/main/kotlin/minesweeper/view/InputView.kt b/src/main/kotlin/minesweeper/view/InputView.kt new file mode 100644 index 000000000..23942cc60 --- /dev/null +++ b/src/main/kotlin/minesweeper/view/InputView.kt @@ -0,0 +1,19 @@ +package minesweeper.view + +object InputView { + + fun inputHeight(): Int { + println("๋†’์ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.") + return readln().toInt() + } + + fun inputWidth(): Int { + println("\n๋„ˆ๋น„๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.") + return readln().toInt() + } + + fun inputCountOfMine(): Int { + println("\n์ง€๋ขฐ๋Š” ๋ช‡ ๊ฐœ ์ธ๊ฐ€์š”?") + return readln().toInt() + } +} diff --git a/src/main/kotlin/minesweeper/view/OutputView.kt b/src/main/kotlin/minesweeper/view/OutputView.kt new file mode 100644 index 000000000..99aef54dd --- /dev/null +++ b/src/main/kotlin/minesweeper/view/OutputView.kt @@ -0,0 +1,19 @@ +package minesweeper.view + +import minesweeper.model.cell.Opening + +object OutputView { + fun printMap(minefield: Array>) { + println("\n์ง€๋ขฐ์ฐพ๊ธฐ ๊ฒŒ์ž„ ์‹œ์ž‘") + for (row in minefield) { + printCols(row) + } + } + + private fun printCols(row: Array) { + for (it in row) { + print("${it.value} ") + } + println() + } +} diff --git a/src/test/kotlin/minesweeper/model/MineSweeperGameTest.kt b/src/test/kotlin/minesweeper/model/MineSweeperGameTest.kt new file mode 100644 index 000000000..f003f1198 --- /dev/null +++ b/src/test/kotlin/minesweeper/model/MineSweeperGameTest.kt @@ -0,0 +1,22 @@ +package minesweeper.model + +import io.kotest.matchers.types.shouldBeInstanceOf +import io.kotest.matchers.types.shouldBeSameInstanceAs +import org.junit.jupiter.api.Test + +class MineSweeperGameTest { + @Test + fun `์ง€๋ขฐ์ฐพ๊ธฐ ๊ฒŒ์ž„์˜ ๋„ˆ๋น„์™€ ๋†’์ด, ์ง€๋ขฐ ๊ฐฏ์ˆ˜๋ฅผ ๋ฐ›์•„ ๊ฒŒ์ž„์„ ์ƒ์„ฑํ•œ๋‹ค`() { + // given + val cols = 10 + val rows = 10 + val countOfMine = 10 + + // when + val mineSweeperGame = MineSweeperGame(cols = cols, rows = rows, countOfMine = countOfMine) + + // then + mineSweeperGame.shouldBeInstanceOf() + mineSweeperGame.minefield().size shouldBeSameInstanceAs cols + } +} diff --git a/src/test/kotlin/minesweeper/model/board/ColsTest.kt b/src/test/kotlin/minesweeper/model/board/ColsTest.kt new file mode 100644 index 000000000..38e9966e1 --- /dev/null +++ b/src/test/kotlin/minesweeper/model/board/ColsTest.kt @@ -0,0 +1,18 @@ +package minesweeper.model.board + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.ints.shouldBeBetween + +class ColsTest : StringSpec({ + "ํ–‰์˜ ๊ธธ์ด ๋ฒ”์œ„ ๋‚ด์˜ ๋žœ๋คํ•œ ์œ„์น˜๊ฐ’์„ ๋ฐ˜ํ™˜๋ฐ›๋Š”๋‹ค" { + // given + val size = 10 + val col = Cols(size) + + // when + val result = col.getPosition() + + // then + result.shouldBeBetween(0, size) + } +}) diff --git a/src/test/kotlin/minesweeper/model/board/MinefieldTest.kt b/src/test/kotlin/minesweeper/model/board/MinefieldTest.kt new file mode 100644 index 000000000..af32f51a4 --- /dev/null +++ b/src/test/kotlin/minesweeper/model/board/MinefieldTest.kt @@ -0,0 +1,27 @@ +package minesweeper.model.board + +import io.kotest.matchers.shouldBe +import minesweeper.model.cell.Island +import minesweeper.model.cell.Mine +import minesweeper.model.cell.Opening +import org.junit.jupiter.api.Test + +class MinefieldTest { + @Test + fun `์ง€๋ขฐ์ฐพ๊ธฐ ๋งต ์ƒ์„ฑ ๊ฒฐ๊ณผ ์ง€๋ขฐ ๊ฐฏ์ˆ˜๋งŒํผ ์ง€๋ขฐ๊ฐ€ ์‹ฌ์–ด์ง„๋‹ค`() { + // given + val openings: Array> = arrayOf( + arrayOf(Island(), Mine(), Island()), + arrayOf(Mine(), Island(), Mine()), + arrayOf(Mine(), Island(), Island()) + ) + val minefield = Minefield(3, 3, openings) + + // when + val (islandCount, mineCount) = openings.flatten().partition { it is Island } + + // then + islandCount.size shouldBe 5 + mineCount.size shouldBe 4 + } +} diff --git a/src/test/kotlin/minesweeper/model/board/RowsTest.kt b/src/test/kotlin/minesweeper/model/board/RowsTest.kt new file mode 100644 index 000000000..cda161612 --- /dev/null +++ b/src/test/kotlin/minesweeper/model/board/RowsTest.kt @@ -0,0 +1,18 @@ +package minesweeper.model.board + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.ints.shouldBeBetween + +class RowsTest : StringSpec({ + "์—ด์˜ ๊ธธ์ด ๋ฒ”์œ„ ๋‚ด์˜ ๋žœ๋คํ•œ ์œ„์น˜๊ฐ’์„ ๋ฐ˜ํ™˜๋ฐ›๋Š”๋‹ค" { + // given + val size = 10 + val row = Rows(size) + + // when + val result = row.getPosition() + + // then + result.shouldBeBetween(0, size) + } +})