Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step4 - 지뢰찾기 리팩터링 #495

Open
wants to merge 5 commits into
base: janghomoon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,16 @@
* 행, 열 선택 시 인접한 행, 열 C -> 숫자 표기
* 이미 오픈된 셀 표시(boolean)
* 지뢰 선택시 Lose Game 으로 출력
---
## step4 지뢰찾기(리팩토링)
### Todo 리스트
* 클래스간 책임 분리
* MineSweeper -> 게임 상태 혹은 게임 동작 담당 하도록 수정
* MineBoard -> 보드 관리 관련 연산
* MineRow -> 한 행의 셀 관리 및 상태 제공
* MineCell 셀의 상태, 행동 담당(Cell Open)
* MineRandomPlacer -> 폭탄 배치 로직 전담
* BoardCalculator -> 숫자 계산
* 책임 분리
* OpenCells - MineSweeper -> MineBoard
* getNeighbors - MineSweeper -> MineBoard
4 changes: 1 addition & 3 deletions src/main/kotlin/mine/controller/MineController.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package mine.controller

import mine.domain.BoardCalculator
import mine.domain.Minesweeper
import mine.view.InputView
import mine.view.OutputView
Expand All @@ -12,10 +11,9 @@ class MineController {
}

private fun gameProgress(mine: Minesweeper) {
val boardCalculator = BoardCalculator()
while (true) {
val coordinate = InputView.gameStart()
val isMineCell = boardCalculator.isMineCell(mine.mineBoard, coordinate)
val isMineCell = mine.mineBoard.isMine(coordinate)
val isAllMinedOpen = mine.areAllSafeCellsOpened()
when {
isMineCell -> {
Expand Down
32 changes: 0 additions & 32 deletions src/main/kotlin/mine/domain/AdjacentMineRow.kt

This file was deleted.

62 changes: 20 additions & 42 deletions src/main/kotlin/mine/domain/BoardCalculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,37 @@ import mine.dto.Coordinate
import mine.enums.MineCell

class BoardCalculator {
fun isMineCell(
mineBoard: List<MineRow>,
coordinate: Coordinate,
): Boolean {
require(coordinate.y in mineBoard.indices) { "지뢰찾기 보드의 크기를 초과할 수 없습니다." }
require(coordinate.x in mineBoard.first().mineCells.indices) { "지뢰찾기 보드의 크기를 초과할 수 없습니다." }
return mineBoard[coordinate.y].isMine(coordinate.x)
}

fun calculateBoard(mineBoard: List<MineRow>): List<MineRow> {
return mineBoard.mapIndexed { rowIndex, currentRow ->
val beforeRow = mineBoard.getOrNull(rowIndex - 1)
val afterRow = mineBoard.getOrNull(rowIndex + 1)

calculateRow(
AdjacentMineRow(
currentRow = currentRow,
beforeRow = beforeRow,
afterRow = afterRow,
),
)
fun calculateBoard(board: MineBoard): MineBoard {
board.rows.forEachIndexed { x, row ->
calculateRowValues(row, x, board)
}
return board
}

private fun calculateRow(adjacentMineRow: AdjacentMineRow): MineRow {
val updatedCells =
adjacentMineRow.currentRow.mineCells.mapIndexed { col, cell ->
calculateCell(cell, adjacentMineRow, col)
private fun calculateRowValues(
row: MineRow,
x: Int,
board: MineBoard,
) {
row.mineCells.forEachIndexed { y, cell ->
if (cell is MineCell.Number) {
cell.value = countAdjacentMines(x, y, board)
}
return MineRow(updatedCells)
}
}

private fun calculateCell(
cell: MineCell,
adjacentMineRow: AdjacentMineRow,
col: Int,
): MineCell {
return when (cell) {
is MineCell.MINE -> MineCell.MINE
is MineCell.Number -> {
val calculatedMines = adjacentMineRow.adjacentMineCalculate(col)
cell.copy(value = calculatedMines)
}
}
private fun countAdjacentMines(
x: Int,
y: Int,
board: MineBoard,
): Int {
return board.getNeighbors(Coordinate(x, y))
.count { (nx, ny) -> board.getCell(Coordinate(nx, ny)) is MineCell.MINE }
}

companion object {
const val MINE_ADD_VALUE = 1
const val MINE_NORMAL_VALUE = 0
const val CELL_NEGATIVE = -1
const val CELL_ZERO = 0
const val CELL_POSITIVE = 1
val directions =
listOf(CELL_NEGATIVE, CELL_ZERO, CELL_POSITIVE)
}
}
82 changes: 82 additions & 0 deletions src/main/kotlin/mine/domain/MineBoard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mine.domain

import mine.domain.BoardCalculator.Companion.CELL_NEGATIVE
import mine.domain.BoardCalculator.Companion.CELL_POSITIVE
import mine.domain.BoardCalculator.Companion.CELL_ZERO
import mine.dto.Coordinate
import mine.enums.MineCell

class MineBoard(val rows: List<MineRow>) {
fun isMine(coordinate: Coordinate): Boolean {
val (rowIndex, colIndex) = coordinate
return rows.getOrNull(rowIndex)?.mineCells?.getOrNull(colIndex) is MineCell.MINE
}

fun openCells(coordinate: Coordinate) {
val visited = mutableSetOf<Coordinate>()
val queue = ArrayDeque<Coordinate>()
queue.add(coordinate)

while (queue.isNotEmpty()) {
val (x, y) = queue.removeFirst()
val currentCell = getCell(Coordinate(x, y)) ?: continue

if (isOpenedCellOrVisitedCell(currentCell, visited, x, y)) continue
visited.add(Coordinate(x, y))

if (currentCell is MineCell.MINE) continue

currentCell.withOpen()

if (isCellValueZero(currentCell)) {
val neighbors = getNeighbors(Coordinate(x, y))
neighbors.filterNot { it in visited }.forEach(queue::add)
}
}
}

private fun isCellValueZero(cell: MineCell): Boolean {
if (cell is MineCell.Number) {
return cell.value == MINE_MIN_VALUE
}
return false
}

private fun isOpenedCellOrVisitedCell(
currentCell: MineCell,
visited: MutableSet<Coordinate>,
x: Int,
y: Int
) = currentCell.isOpen || visited.contains(Coordinate(x, y))

fun areAllSafeCellsOpened(): Boolean {
return rows.all { it.areAllNonMineCellsOpen() }
}

fun getCell(coordinate: Coordinate): MineCell? {
return rows.getOrNull(coordinate.x)?.mineCells?.getOrNull(coordinate.y)
}

fun getNeighbors(coordinate: Coordinate): List<Coordinate> {
return directions.map { (dx, dy) ->
Coordinate(coordinate.x + dx, coordinate.y + dy)
}.filter { (x, y) ->
x in rows.indices && y in rows[0].mineCells.indices
}
}

companion object {
const val MINE_MIN_VALUE = 0
private val directions =
listOf(
CELL_NEGATIVE to CELL_NEGATIVE,
CELL_NEGATIVE to CELL_ZERO,
CELL_NEGATIVE to CELL_POSITIVE,
CELL_ZERO to CELL_NEGATIVE,
CELL_ZERO to CELL_POSITIVE,
CELL_POSITIVE to CELL_NEGATIVE,
CELL_POSITIVE to CELL_ZERO,
CELL_POSITIVE to CELL_POSITIVE,
)
}
}
14 changes: 7 additions & 7 deletions src/main/kotlin/mine/domain/MineRandomPlacer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class MineRandomPlacer {

val minePositions = positions.take(mineCount).toSet()

val board =
(RANDOM_MINE_START_VALUE until height).map { row ->
(RANDOM_MINE_START_VALUE until width).map { col ->
if (row to col in minePositions) MineCell.MINE else MineCell.initial()
}
}
return BoardCalculator().calculateBoard(board.map { MineRow(it) })
return (RANDOM_MINE_START_VALUE until height).map { x ->
MineRow(
(RANDOM_MINE_START_VALUE until width).map { y ->
if (x to y in minePositions) MineCell.MINE else MineCell.Number(DEFAULT_MINE_NUMBER)
},
)
}
}

companion object {
Expand Down
15 changes: 7 additions & 8 deletions src/main/kotlin/mine/domain/MineRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import mine.enums.MineCell
import mine.enums.MineCell.MINE

data class MineRow(val mineCells: List<MineCell>) {
fun isValidCell(col: Int): Boolean {
return col in this.mineCells.indices
fun areAllNonMineCellsOpen(): Boolean {
return mineCells.all {
when (it) {
is MINE -> !it.isOpen
is MineCell.Number -> it.isOpen
}
}
}

fun isMine(index: Int): Boolean {
return mineCells.getOrNull(index) == MINE
}

fun areAllNonMineCellsOpen() = this.mineCells.all { cell -> cell !is MINE || cell.isOpen }
}
Loading