Skip to content

Commit

Permalink
🚀 2단계 - 지뢰 찾기(지뢰 개수) (#496)
Browse files Browse the repository at this point in the history
* feat: SafeCell 근처에 존재하는 LandMineCell개수를 세어 SafeCell에 할당해주는 로직 구현

* feat: SafeCell은 근처 지뢰 개수를 출력하도록 변경

* build: kotest-framework-datatest 추가

* study: kotest-framework-datatest 학습해보기

* test: withData를 사용하도록 변경

* refactor: LandMineCell -> Mine, Cells -> RowCells로 이름 변경

* refactor: 자기 자신은 제외하고 개수를 세도록 변경
  • Loading branch information
SeolYoungKim authored Jan 5, 2025
1 parent bca54a2 commit b382e2c
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 131 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,35 @@ C C C C C C C C C C
- [x] Cell 인터페이스 및 LandMineCell, SafeCell 클래스 생성
- [x] 지뢰는 랜덤으로 섞어야 한다
- 포지션을 갖게 할 지를 생각해보자.(추후에도 계속 생각. 어떤 방법이 좋을지?)

## 2단계 - 지뢰찾기(지뢰 개수)
### 기능 요구사항
- 각 사각형에 표시될 숫자는 자신을 제외한 주변 8개 사각형에 포함된 지뢰의 개수다.
```text
[실행 결과]
높이를 입력하세요.
10
너비를 입력하세요.
10
지뢰는 몇 개인가요?
10
지뢰찾기 게임 시작
0 1 2 * 2 1 1 * 1 0
0 1 * 3 * 1 1 1 1 0
0 1 1 2 1 1 0 0 0 0
1 1 0 0 0 0 0 0 0 0
* 1 0 0 0 1 1 1 0 0
1 2 1 1 0 2 * 2 0 0
0 1 * 1 0 3 * 3 1 1
0 1 1 1 0 2 * 2 1 *
0 0 0 0 0 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0
```

### 기능 목록
- 출력
- [x] SafeCell은 C가 아닌, 근처의 지뢰 개수를 표현
- [x] 지뢰가 아닌 Cell(SafeCell)은 주변 8개 사각형에 포함된 지뢰의 개수를 가진다
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies {
testImplementation("org.junit.jupiter", "junit-jupiter", "5.10.2")
testImplementation("org.assertj", "assertj-core", "3.25.3")
testImplementation("io.kotest", "kotest-runner-junit5", "5.8.0")
testImplementation("io.kotest", "kotest-framework-datatest", "5.8.0")

}

tasks {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/MineSweeper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fun main() {
val width = MineSweeperReader.readWidth()
val countOfLandMines = MineSweeperReader.readCountOfLandMine()

val board = Board(height = height, width = width, landMinesCount = countOfLandMines)
val board = Board(height = height, width = width, minesCount = countOfLandMines)

MineSweeperPrinter.printGameStartMessage()
MineSweeperPrinter.printBoard(board)
Expand Down
41 changes: 34 additions & 7 deletions src/main/kotlin/domain/Board.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
package domain

class Board(height: Int, width: Int, landMinesCount: Int) {
val matrix: List<Cells>
class Board(height: Int, width: Int, minesCount: Int) {
val matrix: List<RowCells>

init {
val totalCountOfCells = height * width
val cells =
Cells.create(
safeCellsCount = totalCountOfCells - landMinesCount,
landMinesCount = landMinesCount,
val rowCells =
RowCells.create(
safeCellsCount = totalCountOfCells - minesCount,
minesCount = minesCount,
shufflingStrategy = DefaultShufflingStrategy
)

matrix =
(1..height).map { number ->
val fromIndex = (number - 1) * width
val toIndex = number * width
cells.subList(fromIndex, toIndex)
rowCells.subList(fromIndex, toIndex)
}

countMineNearBySafeCell()
}

private fun countMineNearBySafeCell() {
matrix.forEachIndexed { index, rowCells ->
rowCells.fillMineCountNearBySafeCell(
previousRowCells = getPreviousCellsOf(index),
nextRowCells = getNextCellsOf(index),
)
}
}

private fun getPreviousCellsOf(index: Int): RowCells? {
return if (index == 0) {
null
} else {
matrix[index - 1]
}
}

private fun getNextCellsOf(index: Int): RowCells? {
return if (index == matrix.lastIndex) {
null
} else {
matrix[index + 1]
}
}
}
18 changes: 15 additions & 3 deletions src/main/kotlin/domain/Cell.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package domain

interface Cell
interface Cell {
fun isMine(): Boolean
}

class LandMineCell : Cell
class Mine : Cell {
override fun isMine(): Boolean {
return true
}
}

class SafeCell : Cell
class SafeCell : Cell {
var mineCountNearby: Int = 0

override fun isMine(): Boolean {
return false
}
}
26 changes: 0 additions & 26 deletions src/main/kotlin/domain/Cells.kt

This file was deleted.

83 changes: 83 additions & 0 deletions src/main/kotlin/domain/RowCells.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package domain

class RowCells(val elements: List<Cell>) {
/**
* [fromIndex] : 포함
* [toIndex] : 제외
*/
fun subList(
fromIndex: Int,
toIndex: Int,
): RowCells {
return RowCells(elements.subList(fromIndex, toIndex))
}

operator fun get(index: Int): Cell {
return elements[index]
}

fun fillMineCountNearBySafeCell(previousRowCells: RowCells?, nextRowCells: RowCells?) {
elements.forEachIndexed { index, cell ->
if (cell is SafeCell) {
val previousCount = countMines(previousRowCells, index)
val midCount = countMines(this, index)
val nextCount = countMines(nextRowCells, index)

cell.mineCountNearby = previousCount + midCount + nextCount
}
}
}

private fun countMines(otherRowCells: RowCells?, currentCellIndex: Int): Int {
return when (otherRowCells) {
null -> 0
this -> {
val previousIndex = getFromIndex(currentCellIndex)
val nextIndex = getToIndex(currentCellIndex)

listOf(elements[previousIndex], elements[nextIndex])
.count { cell -> cell.isMine() }
}
else -> {
val fromIndex = getFromIndex(currentCellIndex)
val toIndex = getToIndex(currentCellIndex)

otherRowCells.elements
.slice(fromIndex..toIndex)
.count { cell -> cell.isMine() }
}
}
}

private fun getFromIndex(currentCellIndex: Int): Int {
return if (currentCellIndex == 0) {
0
} else {
currentCellIndex - 1
}
}

private fun getToIndex(currentCellIndex: Int): Int {
return if (currentCellIndex == elements.lastIndex) {
currentCellIndex
} else {
currentCellIndex + 1
}
}

companion object {
fun create(
safeCellsCount: Int,
minesCount: Int,
shufflingStrategy: ShufflingStrategy
): RowCells {
require(safeCellsCount > 0 && minesCount > 0) { "safeCellsCount와 minesCount는 0보다 커야 합니다." }

val safeCells = (1..safeCellsCount).map { SafeCell() }
val mines = (1..minesCount).map { Mine() }

val cells = safeCells + mines
return RowCells(shufflingStrategy.shuffle(cells))
}
}
}
12 changes: 6 additions & 6 deletions src/main/kotlin/ui/MineSweeperPrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package ui

import domain.Board
import domain.Cell
import domain.Cells
import domain.LandMineCell
import domain.RowCells
import domain.Mine
import domain.SafeCell

object MineSweeperPrinter {
Expand All @@ -29,16 +29,16 @@ object MineSweeperPrinter {
}
}

private fun createCellsMessage(cells: Cells): String {
return cells.elements.joinToString(separator = " ") { cell ->
private fun createCellsMessage(rowCells: RowCells): String {
return rowCells.elements.joinToString(separator = " ") { cell ->
createCellMessage(cell)
}
}

private fun createCellMessage(cell: Cell): String {
return when (cell) {
is LandMineCell -> "*"
is SafeCell -> "C"
is Mine -> "*"
is SafeCell -> cell.mineCountNearby.toString()
else -> throw IllegalStateException()
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/domain/BoardTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ class BoardTest : FreeSpec({
val width = 5
val countOfLandMineCells = 5

val board = Board(height = height, width = width, landMinesCount = countOfLandMineCells)
val board = Board(height = height, width = width, minesCount = countOfLandMineCells)

board.matrix shouldHaveSize height
board.matrix.forEach { cells -> cells.elements shouldHaveSize width }

val landMineCells =
val mines =
board.matrix.flatMap { cells ->
cells.elements.filterIsInstance<LandMineCell>()
cells.elements.filterIsInstance<Mine>()
}
landMineCells.size shouldBe countOfLandMineCells
mines.size shouldBe countOfLandMineCells
}
})
84 changes: 0 additions & 84 deletions src/test/kotlin/domain/CellsTest.kt

This file was deleted.

Loading

0 comments on commit b382e2c

Please sign in to comment.