-
Notifications
You must be signed in to change notification settings - Fork 180
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
STEP 1 : 지뢰찾기(그리기) #386
base: yoonnyeong
Are you sure you want to change the base?
STEP 1 : 지뢰찾기(그리기) #386
Changes from all commits
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 +1,8 @@ | ||
# kotlin-minesweeper | ||
# kotlin-minesweeper | ||
|
||
## 지뢰 찾기(그리기) | ||
- [X] 높이, 너비, 지뢰 갯수를 입력받는다. | ||
- [X] 높이, 너비, 지뢰 갯수는 자연수이여야한다. | ||
- [X] 지뢰는 "*"로 표시되고 지뢰가 아닌 곳은 "C"로 표시된다. | ||
- [X] 지뢰는 랜덤으로 배치된다. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package minesweeper.domain | ||
|
||
class Cell(var isMine: Boolean = false) { | ||
override fun toString(): String { | ||
return when { | ||
isMine -> "*" | ||
else -> "C" | ||
Comment on lines
+6
to
+7
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. *와 C는 ResultView에서 출력하기 위해 존재하는 것 같아요. 어떻게 보면 핵심 도메인 로직 내부에 클라이언트쪽 요구사항이 결합되었다고 볼 수 있습니다. 이후에 * 대신 -로 출력한다거나 하는 요구사항이 생기는 경우 이 toString 결과를 가져다 쓰는 곳이 있다면 영향 범위 파악이 어려워 코드를 수정하기 어려울 수 있겠죠. 저는 이러한 관점에서 Mine과 아닌것을 구분하는 로직이 존재하는것은 괜찮지만 그에 따른 출력 관련 부분이 비즈니스 로직 내부에 포함되는 것은 지양하는것이 좋다고 생각합니다. |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package minesweeper.domain | ||
|
||
data class GameBoard(private val _height: Int, private val _width: Int) { | ||
val height get() = _height | ||
val width get() = _width | ||
Comment on lines
+3
to
+5
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로 정의된 값들은 setter를 통해 수정할 수 없습니다. getter를 통해 가져가는것은 열려있는 상태인데요. Backing Properties를 사용하지 않고 그냥 private val로 정의하는건 어떨까요? |
||
|
||
val board: Array<Array<Cell>> | ||
Comment on lines
+6
to
+7
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. Array가 아니라 조금 더 풍부한 표준 메서드를 제공하는 List를 쓰는 것은 어떨까요? |
||
|
||
init { | ||
require(_height > 0 && _width > 0) { | ||
"자연수를 입력해주세요." | ||
} | ||
Comment on lines
+10
to
+12
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. 둘 중 하나만 자연수를 벗어난 경우 어떤 값이 자연수가 아니어 실패했는지 파악이 어려울 것 같아요. height 및 width에 대한 값도 예외 메시지에 남기는건 어떨까요? |
||
board = Array(_height) { Array(_width) { Cell() } } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package minesweeper.domain | ||
|
||
import kotlin.random.Random | ||
|
||
class MineGenerator(private val gameBoard: GameBoard, private val mineCount: Int) { | ||
fun generateRandomPoints(): List<Point> { | ||
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 minePoints = mutableSetOf<Point>() | ||
|
||
while (minePoints.size < mineCount) { | ||
val randomPoint = Point(Random.nextInt(gameBoard.height), Random.nextInt(gameBoard.width)) | ||
minePoints.add(randomPoint) | ||
} | ||
|
||
return minePoints.toList() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package minesweeper.domain | ||
|
||
class MineSweeper(val gameBoard: GameBoard, mineCount: Int) { | ||
private val mineGenerator = MineGenerator(gameBoard, mineCount) | ||
private val minePoints = mineGenerator.generateRandomPoints() | ||
|
||
init { | ||
require(mineCount > 0) { | ||
"자연수를 입력해주세요." | ||
} | ||
placeMines(gameBoard) | ||
} | ||
|
||
private fun placeMines(gameBoard: GameBoard) { | ||
for (point in minePoints) { | ||
gameBoard.board[point.x][point.y].isMine = true | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package minesweeper.domain | ||
|
||
data class Point(val x: Int, val y: Int) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import minesweeper.view.InputView | ||
import minesweeper.view.ResultView | ||
|
||
fun main() { | ||
val mineSweeper = InputView.prepareMineSweeper() | ||
ResultView.startMineSweeper(mineSweeper) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package minesweeper.view | ||
|
||
import minesweeper.domain.GameBoard | ||
import minesweeper.domain.MineSweeper | ||
|
||
object InputView { | ||
|
||
fun prepareMineSweeper() : MineSweeper{ | ||
val gameBoard = GameBoard(getHeight(), getWidth()) | ||
return MineSweeper(gameBoard, getMine()) | ||
} | ||
|
||
private fun getHeight() : Int{ | ||
println("높이를 입력하세요.") | ||
return readln().toInt() | ||
} | ||
|
||
private fun getWidth() : Int{ | ||
println("너비를 입력하세요.") | ||
return readln().toInt() | ||
} | ||
|
||
|
||
private fun getMine() : Int{ | ||
println("지뢰는 몇 개인가요?") | ||
return readln().toInt() | ||
} | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package minesweeper.view | ||
|
||
import minesweeper.domain.GameBoard | ||
import minesweeper.domain.MineSweeper | ||
|
||
object ResultView { | ||
fun startMineSweeper(mineSweeper: MineSweeper){ | ||
println("지뢰찾기 게임 시작") | ||
printMineSweeper(mineSweeper.gameBoard) | ||
} | ||
|
||
private fun printMineSweeper(gameBoard : GameBoard) { | ||
for (row in gameBoard.board) { | ||
for (cell in row) { | ||
print("$cell ") | ||
} | ||
println() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package minesweeper.domain | ||
|
||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertThrows | ||
|
||
class GameBoardTest { | ||
@Test | ||
fun `자연수로 높이와 너비를 입력하면 게임보드를 만든다`() { | ||
val height = 3 | ||
val width = 4 | ||
val gameBoard = GameBoard(height, width) | ||
|
||
height shouldBe gameBoard.height | ||
width shouldBe gameBoard.width | ||
} | ||
|
||
@Test | ||
fun `지뢰찾기 보드의 높이와 너비는 자연수여야 한다`() { | ||
assertThrows<IllegalArgumentException> { | ||
GameBoard(0, 5) | ||
} | ||
|
||
assertThrows<IllegalArgumentException> { | ||
GameBoard(3, -1) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package minesweeper.domain | ||
|
||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.Test | ||
|
||
class MineGeneratorTest { | ||
|
||
@Test | ||
fun `입력받은 지뢰 개수 만큼 무작위로 지뢰가 위치할 좌표를 만든다`() { | ||
val mineCount = 5 | ||
val gameBoard = GameBoard(5, 5) | ||
val mineGenerator = MineGenerator(gameBoard, mineCount) | ||
|
||
val minePoints = mineGenerator.generateRandomPoints() | ||
|
||
mineCount shouldBe minePoints.size | ||
} | ||
} |
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.
이후에 지뢰와 아닌 블럭에 따라 분기 등 구현해야 할 책임이 늘어나면 Boolean 타입이 아니라 별도의 클래스로 분리하는것도 방법이 될 수 있을 것 같아요. 참고 부탁드립니다.