Skip to content
Open

Step2 #232

Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6ce91cc
docs: 요구사항 정리
Dec 22, 2022
288f98a
feat: 높이와 너비, 지뢰의 개수를 반영해 보드를 생성할 수 있다.
Dec 22, 2022
420c9f2
refactor: 블록 문자열을 블록 객체로 수정
Dec 22, 2022
13926cd
feat:유저로부터 요구사항을 입력받기 위한 InputView 생성
Dec 22, 2022
d9b1a77
feat: Main 및 컨트롤러를 생성해서 유저의 요구사항을 받아 보드 생성
Dec 22, 2022
563b905
feat: ResultView를 생성해서 생성된 보드 유저에게 보여주기
Dec 22, 2022
cdbdfb7
docs: 피드백 목록 정리
Dec 23, 2022
de0089e
refactor: View에서 처리해야할 로직들 이동
Dec 23, 2022
90f6ef0
refactor: OrNull 제거
Dec 26, 2022
b196d6a
feat: 좌표를 나타내는 Point 객체 생성
Dec 26, 2022
70b8fda
refactor: 이차원 배열 -> 맵으로 수정
Dec 26, 2022
257b76b
feat: CustomException 사용
Dec 27, 2022
4133782
refactor: View를 Interface로 변경
Dec 27, 2022
e7e1a31
refactor: 지뢰의 수를 측정하는 메소드와 변수 정리
Dec 27, 2022
ada31aa
refactor: ResultView의 매직 리터럴 companion object로 이동
Dec 29, 2022
cb645f4
refactor: forEach -> flatMap으로 변경
Dec 29, 2022
77a340b
refactor: 에러 문구 ResultView에게 책임 부여 및 Known, Unknown의 처리 분류
Dec 29, 2022
19bdc2c
refactor: 보드 자체를 반환하도록 변경
Dec 29, 2022
9820e71
docs: 2단계 요구사항 작성
Dec 29, 2022
3d033cd
feat: SafeBlock에 nearMineCount 프로퍼티 추가
Dec 29, 2022
f9e24c6
feat: 시작할 때 SAFEBLOCK -> EMPTYBLOCK으로 변경
Dec 30, 2022
b9b28d3
feat: InputView의 가짜 객체를 만들어서 컨트롤러 테스트 추가
Dec 30, 2022
eea4988
feat: Point - 근처의 포인트 객체들 받아오기 기능 추가
Dec 30, 2022
15dee1e
feat: - 지뢰를 심은 뒤에 지뢰의 수를 반영하는 메소드 생성
Dec 30, 2022
f5b8a13
feat: - SafeBlock 표기 방식 변경
Dec 30, 2022
e1aace3
refactor: 변수명 수정
Dec 30, 2022
b37a91c
Merge branch 'jpoh281' into step2
Jan 2, 2023
943e4d5
refactor: Controller에서 그대로 터뜨리기
jpoh281 Jan 3, 2023
0e68951
feat: 지뢰를 심는 전략패턴 인터페이스와 클래스들 생성
jpoh281 Jan 3, 2023
74acd9a
refactor: 열 수 있는 블록과 열 수 없는 블록 분리
Jan 9, 2023
28edd9d
refactor: 전략패턴 적용 및 응집도 높이기
Jan 9, 2023
b8babef
refactor: Empty Block 다시 삭제
Jan 9, 2023
322b8ca
refactor: try-catch를 안쓰고 로직을 변경
Jan 9, 2023
001d298
refactor: block, exception 패키지 생성
Jan 9, 2023
75508fc
refactor: 타입이 나오도록 변경 및 공백 삭제, 임포트 수정
Jan 16, 2023
8e04e5f
refactor: ResultViewImpl -> ConsoleResultView 변경
Jan 16, 2023
520a042
feat: 열린 블록과 열리지 않은 블록 클래스를 만들어 open 메소드를 필요한 클래스에만 할당
Jan 16, 2023
1c4ee11
refactor: 지뢰가 0일 때 0 나누기 에러 나오는 현상 수정
Jan 17, 2023
e95a24c
refactor: ResultViewImpl -> ConsoleReulstView로 더 명확하게변경
Jan 17, 2023
11ac2ce
refactor: 코드 정리
Jan 17, 2023
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
10 changes: 9 additions & 1 deletion src/main/kotlin/minesweeper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,12 @@
- [x] `View`를 `Interface`로 변경
- [x] `View` 에서 처리해야할 로직들 이동
- [x] `Blcok` 클래스의 `mark` 삭제 -> `View`로 이동
- [x] 모델들의 `toString()` -> `View`로 이동
- [x] 모델들의 `toString()` -> `View`로 이동


## 2단계
- [x] 주변의 지뢰 숫자를 보여주는 기능
- [x] `Point` - 근처의 포인트 객체들 받아오기
- [x] `SafeBlock` - 주변의 지뢰 수를 가지는 프로퍼티 생성
- [x] `MineSweeperBoard` - 지뢰를 심은 뒤에 지뢰의 수를 반영하는 메소드 생성
- [x] `ResultView` - SafeBlock 표기 방식 변경
22 changes: 15 additions & 7 deletions src/main/kotlin/minesweeper/controller/GameController.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package minesweeper.controller

import minesweeper.domain.Block
import minesweeper.domain.MineSweeperBoard
import minesweeper.domain.Point
import minesweeper.domain.exception.MineSweeperException
import minesweeper.domain.plant_strategy.RemainderPlantStrategy
import minesweeper.view.InputView
import minesweeper.view.InputViewImpl
import minesweeper.view.ResultView
Expand All @@ -12,17 +12,25 @@ 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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타입이 나오면 좋겠네요~

resultView.renderInitialBoard(gameBoard.state)
gameBoard
} catch (e: MineSweeperException) {
resultView.printKnownException(e)
throw e
} catch (e: Exception) {
resultView.printUnknownException(e)
throw e
}
Comment on lines +19 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception을 고스란히 터트려주는것도 좋은 방법이에요.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

게임을 종료시킬 수 있는것을 컨트롤러에서 제어가 가능해지죠.

}

private fun setUp(): MineSweeperBoard {
val height = inputView.readHeight()
val width = inputView.readWidth()
val mineCount = inputView.readMineCount()

return MineSweeperBoard(height, width, mineCount)
return MineSweeperBoard(height, width, mineCount, RemainderPlantStrategy())
}
}
17 changes: 0 additions & 17 deletions src/main/kotlin/minesweeper/domain/Block.kt

This file was deleted.

6 changes: 0 additions & 6 deletions src/main/kotlin/minesweeper/domain/ExceptionReason.kt

This file was deleted.

46 changes: 32 additions & 14 deletions src/main/kotlin/minesweeper/domain/MineSweeperBoard.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package minesweeper.domain

import java.util.Random
import minesweeper.domain.block.Block
import minesweeper.domain.block.MineBlock
import minesweeper.domain.block.SafeBlock
import minesweeper.domain.exception.ExceptionReason
import minesweeper.domain.exception.MineSweeperException
import minesweeper.domain.plant_strategy.PlantStrategy
import minesweeper.domain.plant_strategy.RemainderPlantStrategy

class MineSweeperBoard(private val width: Int, private val height: Int, mineCount: Int = 0) {
class MineSweeperBoard(
private val width: Int,
private val height: Int,
mineCount: Int = 0,
private val strategy: PlantStrategy = RemainderPlantStrategy()
) {

private val _state: MutableMap<Point, Block>

Expand All @@ -20,17 +31,12 @@ class MineSweeperBoard(private val width: Int, private val height: Int, mineCoun
}
_state = buildBoard(width, height).toMutableMap()
plantMines(mineCount)
_mineCount = countMine()
}

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 { currentXAxis ->
buildLine(currentXAxis, maxYAxis)
}.toMap()
}

private fun buildLine(currentXAxis: Int, maxYAxis: Int): List<Pair<Point, Block>> {
Expand All @@ -44,13 +50,25 @@ class MineSweeperBoard(private val width: Int, private val height: Int, mineCoun
}

private fun plantMines(plantingMineCount: Int) {
while (countMine() < plantingMineCount) {
val width = Random().nextInt(width)
val height = Random().nextInt(height)
_state[Point(width, height)] = MineBlock()
val minePoints = strategy.createMines(width, height, plantingMineCount)
minePoints.forEach {
_state[it] = MineBlock()
}
_mineCount = countMine()
updateSafeBlock()
Comment on lines +53 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전략을 바꿀 수 있으니, 여러 장점이 있을것 같아요.
테스트코드에서 전략을 한개 만들어서, 그 전략만큼 지뢰가 잘 심겼는지 등 테스트 할 수 있겠죠? 나중에 게임 실행하면서 원하는 위치에 지뢰를 심고, 터트리는 등 테스트를 더욱 쉽게 가능할거에요.

}

private fun updateSafeBlock() {
val safeBlockPoints = _state.filterValues { block: Block -> block is SafeBlock }.keys
safeBlockPoints.forEach { currentPoint: Point ->
_state[currentPoint] = SafeBlock(sumNearPointMines(currentPoint.getNearPoints(width, height)))
}
}

private fun sumNearPointMines(points: List<Point>): Int {
return points.count { _state[it] is MineBlock }
}

private fun countMine(): Int {
return _state.values.count { block: Block -> block is MineBlock }
}
Expand Down
36 changes: 36 additions & 0 deletions src/main/kotlin/minesweeper/domain/Point.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package minesweeper.domain

import minesweeper.domain.exception.ExceptionReason
import minesweeper.domain.exception.MineSweeperException

data class Point(val x: Int, val y: Int) : Comparable<Point> {

init {
Expand All @@ -8,5 +11,38 @@ data class Point(val x: Int, val y: Int) : Comparable<Point> {
}
}

fun getNearPoints(width: Int, height: Int): List<Point> {
val nearPoints = nearPoints.toList()
return nearPoints.mapNotNull { plusOrNull(it.first, it.second, width, height) }
}

override fun compareTo(other: Point) = compareValuesBy(this, other, { it.y }, { it.x })

private fun plusOrNull(x: Int, y: Int, maxX: Int, maxY: Int): Point? {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

연산자 오버로딩을 해도 괜찮겠네요~

if (isValidateX(x, maxX) && isValidateY(y, maxY)) {
return Point(this.x + x, this.y + y)
}
return null
}

private fun isValidateX(x: Int, maxX: Int): Boolean {
return (this.x + x) in 0 until maxX
}

private fun isValidateY(y: Int, maxY: Int): Boolean {
return (this.y + y) in 0 until maxY
}

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
)
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/minesweeper/domain/block/Block.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package minesweeper.domain.block

sealed class Block {
abstract fun open()
}
7 changes: 7 additions & 0 deletions src/main/kotlin/minesweeper/domain/block/MineBlock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package minesweeper.domain.block

class MineBlock : Block() {
override fun open() {
TODO("Not yet implemented")
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/minesweeper/domain/block/SafeBlock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package minesweeper.domain.block

import minesweeper.domain.exception.ExceptionReason
import minesweeper.domain.exception.MineSweeperException

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")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현이 안될 메서드를 만들어야 하는건 조금 아쉽네요. Block을 세분화 해보는건 어떨까요?


companion object {
private val nearMineRange = (0..8)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package minesweeper.domain.exception

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
@@ -1,3 +1,3 @@
package minesweeper.domain
package minesweeper.domain.exception

class MineSweeperException(val reason: ExceptionReason) : Exception()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package minesweeper.domain.plant_strategy

import minesweeper.domain.Point

interface PlantStrategy {
fun createMines(width: Int, height: Int, mineCount: Int): Set<Point>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package minesweeper.domain.plant_strategy

import minesweeper.domain.Point
import java.util.*

class RandomPlantStrategy : PlantStrategy {
override fun createMines(width: Int, height: Int, mineCount: Int): Set<Point> {
val minePoints = mutableSetOf<Point>()
while (minePoints.size < mineCount) {
val currentWidth = Random().nextInt(width)
val currentHeight = Random().nextInt(height)
minePoints.add(Point(currentWidth, currentHeight))
}
return minePoints
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package minesweeper.domain.plant_strategy

import minesweeper.domain.Point

class RemainderPlantStrategy : PlantStrategy {

override fun createMines(width: Int, height: Int, mineCount: Int): Set<Point> {
val minePoints = mutableSetOf<Point>()

val term = width * height / mineCount
val maxNumber = term * mineCount
for (i: Int in 0 until maxNumber step term) {
val currentWidth = i % width
val currentHeight = i / width

minePoints.add(Point(currentWidth, currentHeight))
}


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

린트 돌려주세요~

return minePoints
}
}
6 changes: 5 additions & 1 deletion src/main/kotlin/minesweeper/view/ResultView.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package minesweeper.view

import minesweeper.domain.Block
import minesweeper.domain.block.Block
import minesweeper.domain.exception.MineSweeperException
import minesweeper.domain.Point

interface ResultView {
fun renderInitialBoard(state: Map<Point, Block>)

fun printKnownException(exception: MineSweeperException)
fun printUnknownException(exception: Exception)
}
41 changes: 33 additions & 8 deletions src/main/kotlin/minesweeper/view/ResultViewImpl.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package minesweeper.view

import minesweeper.domain.Block
import minesweeper.domain.MineBlock
import minesweeper.domain.block.Block
import minesweeper.domain.exception.ExceptionReason
import minesweeper.domain.block.MineBlock
import minesweeper.domain.exception.MineSweeperException
import minesweeper.domain.Point

private const val DEFAULT_BLOCK_CHARACTER = "C"
private const val DEFAULT_MINE_CHARACTER = "*"
private const val BLANK = " "
import minesweeper.domain.block.SafeBlock

class ResultViewImpl : ResultView {
override fun renderInitialBoard(state: Map<Point, Block>) {
Expand All @@ -15,6 +14,23 @@ class ResultViewImpl : ResultView {
renderBoard(state)
}

override fun printKnownException(exception: MineSweeperException) {
println(parseException(exception))
}

private fun parseException(exception: MineSweeperException): String {
return when (exception.reason) {
ExceptionReason.NEGATIVE_POINT_VALUE -> NEGATIVE_POINT_VALUE_TEXT
ExceptionReason.MINE_COUNT_OVER_BLOCKS -> MINE_COUNT_OVER_BLOCKS_TEXT
ExceptionReason.ILLEGAL_NEAR_MINE_RANGE -> ILLEGAL_NEAR_MINE_RANGE_TEXT
ExceptionReason.ILLEGAL_POINT -> CANT_CREATE_BLOCK
}
}

override fun printUnknownException(exception: Exception) {
print(exception.message)
}

private fun renderBoard(state: Map<Point, Block>) {
val yList = state.toList().groupBy { it.first.y }
yList.forEach {
Expand All @@ -30,8 +46,17 @@ class ResultViewImpl : ResultView {
private fun renderBlock(block: Block) {
when (block) {
is MineBlock -> print(DEFAULT_MINE_CHARACTER)
else -> print(DEFAULT_BLOCK_CHARACTER)
is SafeBlock -> print(block.nearMineCount)
}
print(BLANK)
print(BLANK_CHARACTER)
}

companion object {
private const val DEFAULT_MINE_CHARACTER = "*"
private const val BLANK_CHARACTER = " "
private const val NEGATIVE_POINT_VALUE_TEXT = "좌표 값은 음수가 될 수 없습니다."
private const val MINE_COUNT_OVER_BLOCKS_TEXT = "지뢰의 개수는 총 블록의 개수보다 많을 수 없습니다."
private const val ILLEGAL_NEAR_MINE_RANGE_TEXT = "근처의 지뢰 범위는 0 이상 8 이하입니다."
private const val CANT_CREATE_BLOCK = "현재 보드에 없는 지점에 지뢰를 설치할 수 없습니다."
}
}
Loading