-
Notifications
You must be signed in to change notification settings - Fork 199
Step2 #232
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
base: jpoh281
Are you sure you want to change the base?
Step2 #232
Changes from 27 commits
6ce91cc
288f98a
420c9f2
13926cd
d9b1a77
563b905
cdbdfb7
de0089e
90f6ef0
b196d6a
70b8fda
257b76b
4133782
e7e1a31
ada31aa
cb645f4
77a340b
19bdc2c
9820e71
3d033cd
f9e24c6
b9b28d3
eea4988
15dee1e
f5b8a13
e1aace3
b37a91c
943e4d5
0e68951
74acd9a
28edd9d
b8babef
322b8ca
001d298
75508fc
8e04e5f
520a042
1c4ee11
e95a24c
11ac2ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,7 @@ | ||
| package minesweeper.controller | ||
|
|
||
| import minesweeper.domain.Block | ||
| import minesweeper.domain.MineSweeperBoard | ||
| import minesweeper.domain.Point | ||
| import minesweeper.domain.MineSweeperException | ||
| import minesweeper.view.InputView | ||
| import minesweeper.view.InputViewImpl | ||
| import minesweeper.view.ResultView | ||
|
|
@@ -12,10 +11,18 @@ class GameController( | |
| private val inputView: InputView = InputViewImpl(), | ||
| private val resultView: ResultView = ResultViewImpl() | ||
| ) { | ||
| fun run(): Map<Point, Block> { | ||
| val gameBoard = setUp() | ||
| resultView.renderInitialBoard(gameBoard.state) | ||
| return gameBoard.state | ||
| fun run(): MineSweeperBoard? { | ||
| return try { | ||
| val gameBoard = setUp() | ||
| resultView.renderInitialBoard(gameBoard.state) | ||
| gameBoard | ||
| } catch (e: MineSweeperException) { | ||
| resultView.printKnownException(e) | ||
| null | ||
| } catch (e: Exception) { | ||
| resultView.printUnknownException(e) | ||
| null | ||
| } | ||
|
Comment on lines
+19
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 게임을 종료시킬 수 있는것을 컨트롤러에서 제어가 가능해지죠. |
||
| } | ||
|
|
||
| private fun setUp(): MineSweeperBoard { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,14 +4,29 @@ sealed class Block { | |
| abstract fun open() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 열 수 있는 블록과 없는 블록을 나누는건 어떨까요? |
||
| } | ||
|
|
||
| class EmptyBlock : Block() { | ||
| override fun open() { | ||
| TODO("Not yet implemented") | ||
| } | ||
| } | ||
|
|
||
| class MineBlock : Block() { | ||
| override fun open() { | ||
| TODO("Not yet implemented") | ||
| } | ||
| } | ||
|
|
||
| class SafeBlock : Block() { | ||
| class SafeBlock(val nearMineCount: Int = 0) : Block() { | ||
|
|
||
| init { | ||
| if (nearMineCount !in nearMineRange) throw MineSweeperException(ExceptionReason.ILLEGAL_NEAR_MINE_RANGE) | ||
| } | ||
|
|
||
| override fun open() { | ||
| TODO("Not yet implemented") | ||
| } | ||
|
|
||
| companion object { | ||
| private val nearMineRange = (0..8) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| package minesweeper.domain | ||
|
|
||
| enum class ExceptionReason(val message: String) { | ||
| NEGATIVE_POINT_VALUE("좌표 값은 음수가 될 수 없습니다."), | ||
| MINE_COUNT_OVER_BLOCKS("지뢰의 개수는 총 블록의 개수보다 많을 수 없습니다.") | ||
| enum class ExceptionReason { | ||
| ILLEGAL_POINT, | ||
| NEGATIVE_POINT_VALUE, | ||
| MINE_COUNT_OVER_BLOCKS, | ||
| ILLEGAL_NEAR_MINE_RANGE | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,36 +21,55 @@ class MineSweeperBoard(private val width: Int, private val height: Int, mineCoun | |
| _state = buildBoard(width, height).toMutableMap() | ||
| plantMines(mineCount) | ||
| _mineCount = countMine() | ||
| buildSafeBlock() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드가 응집이 되어보는건 어떨까요? |
||
| } | ||
|
|
||
| private fun buildBoard(maxXAxis: Int, maxYAxis: Int): Map<Point, Block> { | ||
| val pairs = mutableListOf<Pair<Point, Block>>() | ||
|
|
||
| (0 until maxXAxis).forEach { currentXAixs -> | ||
| pairs.addAll(buildLine(currentXAixs, maxYAxis)) | ||
| } | ||
|
|
||
| return pairs.toMap() | ||
| return (0 until maxXAxis).flatMap { currentXAixs -> | ||
| buildLine(currentXAixs, maxYAxis) | ||
| }.toMap() | ||
| } | ||
|
|
||
| private fun buildLine(currentXAxis: Int, maxYAxis: Int): List<Pair<Point, Block>> { | ||
| return (0 until maxYAxis).map { y -> | ||
| buildBlock(currentXAxis, y) | ||
| buildEmptyBlock(currentXAxis, y) | ||
| } | ||
| } | ||
|
|
||
| private fun buildBlock(currentXAxis: Int, currentYAxis: Int): Pair<Point, Block> { | ||
| return Point(currentXAxis, currentYAxis) to SafeBlock() | ||
| private fun buildEmptyBlock(currentXAxis: Int, currentYAxis: Int): Pair<Point, Block> { | ||
| return Point(currentXAxis, currentYAxis) to EmptyBlock() | ||
| } | ||
|
|
||
| private fun plantMines(plantingMineCount: Int) { | ||
| while (countMine() < plantingMineCount) { | ||
| val width = Random().nextInt(width) | ||
| val height = Random().nextInt(height) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 랜덤에 의존적인게 아쉽네요. 전략패턴 등을 이용해보면 어떤가요? |
||
| validatePoint(Point(width, height)) | ||
| _state[Point(width, height)] = MineBlock() | ||
| } | ||
| } | ||
|
|
||
| private fun validatePoint(point: Point) { | ||
| if (!_state.containsKey(point)) { | ||
| throw MineSweeperException(ExceptionReason.ILLEGAL_POINT) | ||
| } | ||
| } | ||
|
|
||
| private fun buildSafeBlock() { | ||
| val emptyBlockPoints = findEmptyBlockPoint() | ||
| emptyBlockPoints.forEach { currentPoint: Point -> | ||
| _state[currentPoint] = SafeBlock(countNearMine(currentPoint.getNearPoints())) | ||
| } | ||
| } | ||
|
|
||
| private fun findEmptyBlockPoint(): Set<Point> { | ||
| return _state.filterValues { block: Block -> block is EmptyBlock }.keys | ||
| } | ||
|
|
||
| private fun countNearMine(points: List<Point>): Int { | ||
| return points.filter { _state[it] is MineBlock }.size | ||
| } | ||
|
|
||
| private fun countMine(): Int { | ||
| return _state.values.count { block: Block -> block is MineBlock } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,5 +8,30 @@ data class Point(val x: Int, val y: Int) : Comparable<Point> { | |
| } | ||
| } | ||
|
|
||
| fun getNearPoints(): List<Point> { | ||
| return nearPoints.mapNotNull { plusOrNull(it.first, it.second) } | ||
| } | ||
|
|
||
| override fun compareTo(other: Point) = compareValuesBy(this, other, { it.y }, { it.x }) | ||
|
|
||
| private fun plusOrNull(x: Int, y: Int): Point? { | ||
| return try { | ||
| Point(this.x + x, this.y + y) | ||
| } catch (e: Exception) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| null | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| private val nearPoints = listOf( | ||
| -1 to -1, | ||
| -1 to 0, | ||
| -1 to 1, | ||
| 0 to -1, | ||
| 0 to 1, | ||
| 1 to -1, | ||
| 1 to 0, | ||
| 1 to 1 | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,12 @@ | ||
| package minesweeper.view | ||
|
|
||
| import minesweeper.domain.Block | ||
| import minesweeper.domain.MineSweeperException | ||
| import minesweeper.domain.Point | ||
|
|
||
| interface ResultView { | ||
| fun renderInitialBoard(state: Map<Point, Block>) | ||
|
|
||
| fun printKnownException(exception: MineSweeperException) | ||
| fun printUnknownException(exception: Exception) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package minesweeper.controller | ||
|
|
||
| import minesweeper.domain.MineBlock | ||
| import minesweeper.domain.MineSweeperBoard | ||
| import minesweeper.view.FakeInputView | ||
| import minesweeper.view.ResultViewImpl | ||
| import org.assertj.core.api.Assertions.assertThat | ||
| import org.junit.jupiter.api.Test | ||
|
|
||
| class GameControllerTest { | ||
|
|
||
| @Test | ||
| fun `게임 실행이 완료되면 마지막 상태의 유저 요구사항에 맞는 Board를 반환한다`() { | ||
|
|
||
| val userInputHeight = 2 | ||
| val userInputWidth = 2 | ||
| val userInputMineCount = 1 | ||
|
|
||
| val controller = | ||
| GameController(FakeInputView(userInputHeight, userInputWidth, userInputMineCount), ResultViewImpl()) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| val board = controller.run() | ||
| assertThat(board).isExactlyInstanceOf(MineSweeperBoard::class.java) | ||
| assertThat(board!!.state.size).isEqualTo(userInputWidth * userInputHeight) | ||
| assertThat(board.state.values.count { it is MineBlock }).isEqualTo(userInputMineCount) | ||
| } | ||
|
|
||
| @Test | ||
| fun `게임 실행 도중 예외가 발생하면 null을 반환한다`() { | ||
|
|
||
| val userInputHeight = 2 | ||
| val userInputWidth = 2 | ||
| val userInputMineCount = 5 | ||
|
|
||
| val controller = | ||
| GameController(FakeInputView(userInputHeight, userInputWidth, userInputMineCount), ResultViewImpl()) | ||
|
|
||
| val board = controller.run() | ||
| assertThat(board).isEqualTo(null) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package minesweeper.domain | ||
|
|
||
| import org.assertj.core.api.Assertions.assertThat | ||
| import org.junit.jupiter.api.DisplayName | ||
| import org.junit.jupiter.api.Test | ||
| import org.junit.jupiter.params.ParameterizedTest | ||
| import org.junit.jupiter.params.provider.ValueSource | ||
|
|
||
| class SafeBlockTest { | ||
|
|
||
| @Test | ||
| fun `0보다 작은 수는 생성될 수 없다`() { | ||
| val exception = org.junit.jupiter.api.assertThrows<MineSweeperException> { | ||
| SafeBlock(-1) | ||
| } | ||
|
|
||
| assertThat(exception.reason).isEqualTo(ExceptionReason.ILLEGAL_NEAR_MINE_RANGE) | ||
| } | ||
|
|
||
| @DisplayName("블록 생성 테스트") | ||
| @ParameterizedTest(name = "{0}는 생성된다") | ||
| @ValueSource(ints = [0, 1, 2, 3, 4, 5, 6, 7, 8]) | ||
| fun `0~8 까지 들어갈 수 있다`(nearMineCount: Int) { | ||
| assertThat(SafeBlock(nearMineCount)).isExactlyInstanceOf(SafeBlock::class.java) | ||
| } | ||
|
|
||
| @Test | ||
| fun `8보다 큰 수는 생성될 수 없다`() { | ||
| val exception = org.junit.jupiter.api.assertThrows<MineSweeperException> { | ||
| SafeBlock(9) | ||
| } | ||
|
|
||
| assertThat(exception.reason).isEqualTo(ExceptionReason.ILLEGAL_NEAR_MINE_RANGE) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package minesweeper.view | ||
|
|
||
| class FakeInputView(private val height: Int, private val width: Int, private val mineCount: Int) : InputView { | ||
| override fun readHeight(): Int = height | ||
|
|
||
| override fun readWidth(): Int = width | ||
|
|
||
| override fun readMineCount(): Int = mineCount | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타입이 나오면 좋겠네요~