Step3: 지뢰 찾기(게임 실행)#394
Conversation
| /** | ||
| * 테스트 내에서만 쓰는, 특정 형태의 string을 입력 시 MineMap을 뽑아주는 메소드 | ||
| * | ||
| * ex) | ||
| * ...* | ||
| * .... | ||
| * .*.. | ||
| * .... | ||
| * | ||
| * -> 4x4 맵에 (1열 4행), (3열 2행) 지뢰가 있음 | ||
| */ | ||
| private fun createMap(mapString: String): MineMap { | ||
| val rows = mapString.split("\n") | ||
|
|
||
| val mines = mutableListOf<Point>() | ||
| var mineCount = 0 | ||
| rows.forEachIndexed { row, str -> | ||
| val columnList = str.withIndex().filter { it.value == '*' }.map { it.index } | ||
| for (col in columnList) { | ||
| mines.add(Point(row + 1, col + 1)) | ||
| mineCount++ | ||
| } | ||
| } |
There was a problem hiding this comment.
테스트 코드 내에서만 쓰이는, 가짜 생성자를 만들었습니다. 이를 통해 시각적인 효과를 보여줄 수 있을 거라 생각합니다.
| ClickResult.CONTINUE | ||
| } | ||
|
|
||
| else -> ClickResult.ERROR |
There was a problem hiding this comment.
사실 여기 들어가면 안되는데, Map 특성상 null이 될 확률을 컴파일러 내에서 배제하기 힘들어서 ERROR를 따로 넣었습니다. 이를 없앨 수 있는 좋은 방법이 있을까요?
There was a problem hiding this comment.
mineMap.mineMap[point]이 null이 되는 건 게임을 진행하면서 결코 피할 수 없을 거라 생각해요.
오히려 게임 진행에 있어서 필수적인 흐름이란 생각이 드는데요,
이런 경우가 어떨 때 생기는지 ClickResult.ERROR의 이름을 변경함으로써 표현하고, 게임 진행에서 어떻게 처리하면 좋을지 고민해보셔도 좋겠어요!
| private fun setAdjacentTilesClicked(point: Point) { | ||
| val adjacent = point.getAdjacentPoints(mineMap.mapInfo.mapSize) | ||
| for (adj in adjacent) { | ||
| if (clickedSet.contains(adj)) continue | ||
|
|
||
| adjacentTileDfs(adj) | ||
| } | ||
| } | ||
|
|
||
| private fun adjacentTileDfs(adj: Point) { | ||
| val info = mineMap.mineMap[adj] | ||
| if (info is MapTile.Blank) { | ||
| setClickedPoint(adj) | ||
| if (info.isNoMineNear) setAdjacentTilesClicked(adj) | ||
| } | ||
| } |
There was a problem hiding this comment.
여기 DFS 구현한다고 했는데... 사실 이름이 크게 떠오르지 않아서 이상한 메소드를 쓴 것 같습니다. 혹시 네이밍 관련 좋은 이름이 있을까요?
There was a problem hiding this comment.
수행하고자 하는 것이 무엇인지를 고민해서 메서드명에 녹여내보면 좋겠어요 :)
정답은 없으니 충분히 고민해보는 시간을 가져보셔도 좋겠어요.
| package minesweeper | ||
|
|
||
| class AdjacentPoints private constructor(val points: List<Point>) { | ||
| companion object { | ||
| fun create(center: Point, mapRow: Int, mapCol: Int) = | ||
| AdjacentPoints( | ||
| buildList { | ||
| for (dir in Direction.values()) { | ||
| val nearPoint = Point(center.row + dir.row, center.col + dir.col) | ||
| if (nearPoint.isOutOfBound(mapRow, mapCol)) continue | ||
| add(nearPoint) | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| private fun Point.isOutOfBound(mapRow: Int, mapCol: Int): Boolean = | ||
| this.row < 0 || this.col < 0 || this.row >= mapRow || this.col >= mapCol | ||
| } | ||
|
|
||
| enum class Direction(val row: Int, val col: Int) { | ||
| NORTH(-1, 0), | ||
| NORTHEAST(-1, 1), | ||
| EAST(0, 1), | ||
| SOUTHEAST(1, 1), | ||
| SOUTH(1, 0), | ||
| SOUTHWEST(1, -1), | ||
| WEST(0, -1), | ||
| NORTHWEST(-1, -1) | ||
| } | ||
| } |
There was a problem hiding this comment.
Point 내에 메시지를 던지게 만듦으로써 필요없어진 클래스를 삭제했습니다.
| value class MineCount(val count: Int) { | ||
| init { | ||
| require(count > 0) { | ||
| require(count >= 0) { |
There was a problem hiding this comment.
이건 지뢰가 0개일 수도 있을거 같다는 생각에 바꿨습니다.
| return mutableMapOf<Point, MapTile>().apply { | ||
| for (i in 1..mapSize.row.count) { | ||
| for (j in 1..mapSize.column.count) { | ||
| put(Point(i, j), MapTile.Blank(0)) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Blank(0)인 Map을 초기화 시 넣게 했습니다.
| private fun Int.toPoint(rowNum: Int): Point { | ||
| val rowIdx = this / rowNum | ||
| val colIdx = this % rowNum | ||
| return Point(rowIdx + 1, colIdx + 1) |
There was a problem hiding this comment.
게임 실행을 살펴보니 1,1부터 시작하는 걸로 보여서 +1 붙여줬습니다.
malibinYun
left a comment
There was a problem hiding this comment.
3단계 미션 고생 많으셨어요 :)
고민해보면 좋을 점들에 코멘트 남겨두었어요.
피드백 반영 후 다시 리뷰 요청 부탁드려요!
| class MineMap( | ||
| val mineMapInfo: MineMapInfo, | ||
| createStrategy: MinePointCreateStrategy = RandomPointCreateStrategy() | ||
| val mineMap: Map<Point, MapTile>, | ||
| val mapInfo: MineMapInfo | ||
| ) { |
There was a problem hiding this comment.
MineMap이 단순히 Map 컬렉션을 가지고있는 것 말고 큰 역할을 수행하고 있지 않네요!
이 객체가 수행해야할 역할을 모아보는건 어떨까요?
| private fun createNear(map: MutableMap<Point, MapTile>, mine: Point, mapSize: MapSize) { | ||
| val adjacentPoints = mine.getAdjacentPoints(mapSize) | ||
| for (adj in adjacentPoints) { | ||
| val nearInfo = map[adj] | ||
| if (nearInfo is MapTile.Blank) map[adj] = nearInfo + 1 | ||
| } |
There was a problem hiding this comment.
지뢰 찾기를 진행하면서, 칸을 눌렀을 때, 칸의 숫자를 계산하도록 만드는 건 어떨까요?
게임을 진행하면서 열리지 않을 칸들에 대해, 생성 시점에서 주변 지뢰 수를 계산하는 것은 낭비라는 생각이 드네요.
| clickedSet: Set<Point> = setOf(), | ||
| ) { | ||
| var clickedSet: Set<Point> = clickedSet | ||
| private set | ||
|
|
There was a problem hiding this comment.
MineTile의 하위 구현체로, 열린 칸과 닫힌 칸을 표현해보는 건 어떨까요?
| ClickResult.CONTINUE | ||
| } | ||
|
|
||
| else -> ClickResult.ERROR |
There was a problem hiding this comment.
mineMap.mineMap[point]이 null이 되는 건 게임을 진행하면서 결코 피할 수 없을 거라 생각해요.
오히려 게임 진행에 있어서 필수적인 흐름이란 생각이 드는데요,
이런 경우가 어떨 때 생기는지 ClickResult.ERROR의 이름을 변경함으로써 표현하고, 게임 진행에서 어떻게 처리하면 좋을지 고민해보셔도 좋겠어요!
| private fun setAdjacentTilesClicked(point: Point) { | ||
| val adjacent = point.getAdjacentPoints(mineMap.mapInfo.mapSize) | ||
| for (adj in adjacent) { | ||
| if (clickedSet.contains(adj)) continue | ||
|
|
||
| adjacentTileDfs(adj) | ||
| } | ||
| } | ||
|
|
||
| private fun adjacentTileDfs(adj: Point) { | ||
| val info = mineMap.mineMap[adj] | ||
| if (info is MapTile.Blank) { | ||
| setClickedPoint(adj) | ||
| if (info.isNoMineNear) setAdjacentTilesClicked(adj) | ||
| } | ||
| } |
There was a problem hiding this comment.
수행하고자 하는 것이 무엇인지를 고민해서 메서드명에 녹여내보면 좋겠어요 :)
정답은 없으니 충분히 고민해보는 시간을 가져보셔도 좋겠어요.
| data class Point(val row: Int, val col: Int) { | ||
| fun getAdjacentPoints(mapSize: MapSize): List<Point> { |
There was a problem hiding this comment.
Point객체 자체는 지뢰판의 영향 없이 그대로 주위 point 객체를 모두 반환하는 것이 더 자연스러워 보여요.
지뢰판에서 유효하지 않은 Point를 빼내는 작업은 다른 객체의 역할로 분리해보는 건 어떨까요?
3단계 진행했습니다. 코멘트를 달겠습니다!