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

STEP 1 : 지뢰찾기(그리기) #386

Open
wants to merge 1 commit into
base: yoonnyeong
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# kotlin-minesweeper
# kotlin-minesweeper

## 지뢰 찾기(그리기)
- [X] 높이, 너비, 지뢰 갯수를 입력받는다.
- [X] 높이, 너비, 지뢰 갯수는 자연수이여야한다.
- [X] 지뢰는 "*"로 표시되고 지뢰가 아닌 곳은 "C"로 표시된다.
- [X] 지뢰는 랜덤으로 배치된다.

Empty file removed src/main/kotlin/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions src/main/kotlin/minesweeper/domain/Cell.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package minesweeper.domain

class Cell(var isMine: Boolean = false) {

Choose a reason for hiding this comment

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

이후에 지뢰와 아닌 블럭에 따라 분기 등 구현해야 할 책임이 늘어나면 Boolean 타입이 아니라 별도의 클래스로 분리하는것도 방법이 될 수 있을 것 같아요. 참고 부탁드립니다.

override fun toString(): String {
return when {
isMine -> "*"
else -> "C"
Comment on lines +6 to +7

Choose a reason for hiding this comment

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

*와 C는 ResultView에서 출력하기 위해 존재하는 것 같아요. 어떻게 보면 핵심 도메인 로직 내부에 클라이언트쪽 요구사항이 결합되었다고 볼 수 있습니다. 이후에 * 대신 -로 출력한다거나 하는 요구사항이 생기는 경우 이 toString 결과를 가져다 쓰는 곳이 있다면 영향 범위 파악이 어려워 코드를 수정하기 어려울 수 있겠죠.

저는 이러한 관점에서 Mine과 아닌것을 구분하는 로직이 존재하는것은 괜찮지만 그에 따른 출력 관련 부분이 비즈니스 로직 내부에 포함되는 것은 지양하는것이 좋다고 생각합니다.

}
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/minesweeper/domain/GameBoard.kt
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

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

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

둘 중 하나만 자연수를 벗어난 경우 어떤 값이 자연수가 아니어 실패했는지 파악이 어려울 것 같아요. height 및 width에 대한 값도 예외 메시지에 남기는건 어떨까요?

board = Array(_height) { Array(_width) { Cell() } }
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/minesweeper/domain/MineGenerator.kt
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> {

Choose a reason for hiding this comment

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

여기서 반환된 좌표의 리스트가 마인을 나타내는건지를 알 수 있는 방법은 변수명 밖에 없을 것 같아요.

List<Point>를 묶어 지뢰의 좌표들을 나타낸다는 의도를 드러내고 생성 책임 또한 그쪽으로 옮기면 Generator의 책임을 그쪽으로 옮겨 클래스를 줄이고 지뢰의 좌표 리스트에 대한 의도를 명확히 나타낼 수 있지 않을까요? 한번 고민해보시면 좋겠습니다.

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()
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/minesweeper/domain/MineSweeper.kt
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
}
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/minesweeper/domain/Point.kt
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)
7 changes: 7 additions & 0 deletions src/main/kotlin/minesweeper/main.kt
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)
}
30 changes: 30 additions & 0 deletions src/main/kotlin/minesweeper/view/InputView.kt
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()
}


}
20 changes: 20 additions & 0 deletions src/main/kotlin/minesweeper/view/ResultView.kt
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()
}
}
}
Empty file removed src/test/kotlin/.gitkeep
Empty file.
28 changes: 28 additions & 0 deletions src/test/kotlin/minesweeper/domain/GameBoardTest.kt
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)
}
}
}
18 changes: 18 additions & 0 deletions src/test/kotlin/minesweeper/domain/MineGeneratorTest.kt
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
}
}