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

Step1 - 지뢰 찾기(그리기) #333

Open
wants to merge 10 commits into
base: sumniy
Choose a base branch
from
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# kotlin-minesweeper
# kotlin-minesweeper

- [x] 지뢰 찾기에 필요한 정보(높이, 너비, 지뢰 개수)를 입력 받는다
- [x] 높이 * 너비 만큼의 좌표 정보를 생성한다
- [x] 지뢰 개수만큼 랜덤한 지뢰의 위치를 구해 해당 좌표에 지뢰를 설치한다
- [x] 지뢰 찾기 게임을 출력한다
Empty file removed src/main/kotlin/.gitkeep
Empty file.
18 changes: 18 additions & 0 deletions src/main/kotlin/minesweeper/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package minesweeper

import minesweeper.domain.Height
import minesweeper.domain.MineCount
import minesweeper.domain.MineMap
import minesweeper.domain.Width
import minesweeper.view.InputView
import minesweeper.view.ResultView

fun main() {
val height = Height(InputView.receiveHeight())
val width = Width(InputView.receiveWidth())
val mineCount = MineCount(InputView.receiveMineCount())

val mineMap = MineMap(height, width, mineCount)

ResultView.printMineGame(mineMap)
}
11 changes: 11 additions & 0 deletions src/main/kotlin/minesweeper/domain/Height.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package minesweeper.domain

class Height(val value: Int) {
init {
require(value >= MIN_HEIGHT_VALUE) { "height는 $MIN_HEIGHT_VALUE 이상만 허용됩니다." }
}

companion object {
const val MIN_HEIGHT_VALUE = 1
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/minesweeper/domain/MineCount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package minesweeper.domain

class MineCount(val value: Int) {
init {
require(value >= MIN_MINE_COUNT_VALUE) { "지뢰 개수는 $MIN_MINE_COUNT_VALUE 이상만 허용됩니다." }
}

companion object {
private const val MIN_MINE_COUNT_VALUE = 0
}
}
33 changes: 33 additions & 0 deletions src/main/kotlin/minesweeper/domain/MineMap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package minesweeper.domain

class MineMap(height: Height, val width: Width, mineCount: MineCount) {
Copy link

Choose a reason for hiding this comment

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

한 가지 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. 이 규칙을 MineMap 클래스에 적용하려고 했는데, 좋은 방법이 떠오르지 않아서.. 3개의 인스턴스를 받게 유지했습니다. 좋은 방법이 있다면 피드백 부탁드리겠습니다!

Width와 Height를 묶을 수 있지 않을까요? 🤔

private val size: Int = height.value * width.value
val map: List<Point>

init {
require(size >= mineCount.value) { "지도 크기는 지뢰 개수이상이어야 합니다." }

val mineIndexes = (0..size)
.shuffled()
.take(mineCount.value)

val mutableList = mutableListOf<Point>()
(1..height.value).forEach { y ->
(1..width.value).forEach { x ->
val index = (y - 1) * width.value + (x - 1)

mutableList.add(point(mineIndexes.contains(index), x, y))
}
}
Copy link

Choose a reason for hiding this comment

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

객체지향 생활 체조 원칙에는 아래와 같은 항목이 있습니다.

한 메서드에 오직 한 단계의 들여쓰기만 한다.

이 요구사항을 적용해보면 어떨까요?


map = mutableList.toList()
}

private fun point(isMine: Boolean, x: Int, y: Int): Point {
if (isMine) {
return MinePoint(x, y)
}

return Point(x, y)
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/minesweeper/domain/MinePoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package minesweeper.domain

class MinePoint(x: Int, y: Int) : Point(x, y) {
override val symbol = MINE_SYMBOL

companion object {
private const val MINE_SYMBOL = "*"
}
}
18 changes: 18 additions & 0 deletions src/main/kotlin/minesweeper/domain/Point.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package minesweeper.domain

open class Point(x: Int, y: Int) {
open val symbol: String = POINT_SYMBOL

init {
require(x >= Width.MIN_WIDTH_VALUE) { "x 좌표 값은 width의 최소 값 이상이어야합니다." }
require(y >= Height.MIN_HEIGHT_VALUE) { "y 좌표 값은 height의 최소 값 이상이어야합니다." }
}

fun symbol(): String {
return symbol
}

companion object {
private const val POINT_SYMBOL = "C"
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/minesweeper/domain/Width.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package minesweeper.domain

class Width(val value: Int) {
init {
require(value >= MIN_WIDTH_VALUE) { "width는 $MIN_WIDTH_VALUE 이상만 허용됩니다." }
}

companion object {
const val MIN_WIDTH_VALUE = 1
}
}
43 changes: 43 additions & 0 deletions src/main/kotlin/minesweeper/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package minesweeper.view

object InputView {
fun receiveHeight(): Int {
println("높이를 입력하세요.")

return receiveInt()
}

fun receiveWidth(): Int {
println()
println("너비를 입력하세요.")

return receiveInt()
}

fun receiveMineCount(): Int {
println()
println("지뢰는 몇 개인가요?")

return receiveInt()
}

private fun receiveString(): String {
var input: String? = null

do {
input = readlnOrNull()
} while (input.isNullOrBlank())

return input
}

private fun receiveInt(): Int {
Copy link

Choose a reason for hiding this comment

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

함수명만 봤을 때에는 "Int를 받는다" 라고 이해가 되는데요.
여기서 Int는 어떤 것을 받는 걸까요?
함수명을 좀 더 구체적으로 작성해보면 어떨까요?

var int: Int? = receiveString().toIntOrNull()
Copy link

Choose a reason for hiding this comment

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

변수는 상태를 나타냅니다.
"Int"와 같은 자료형은 상태를 나타내기 어렵습니다.
변수명만 봤을 때 어떤 상태를 가지는지 알 수 있도록 이름을 변경해보면 어떨까요?


while (int == null) {
int = receiveString().toIntOrNull()
}

return int
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/minesweeper/view/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package minesweeper.view

import minesweeper.domain.MineMap

object ResultView {
fun printMineGame(mineMap: MineMap) {
println()
println("지뢰찾기 게임 시작")

mineMap.map
.chunked(mineMap.width.value)
.map { line ->
println(line.joinToString(" ") { it.symbol })
}
}
}
Empty file removed src/test/kotlin/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions src/test/kotlin/minesweeper/HeightTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import minesweeper.domain.Height
import org.junit.jupiter.api.Test

class HeightTest {
@Test
fun `height는 음수 값으로 생성 시 예외가 발생한다`() {
shouldThrow<IllegalArgumentException> { Height(0) }
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/minesweeper/MineCountTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import minesweeper.domain.MineCount
import org.junit.jupiter.api.Test

class MineCountTest {
@Test
fun `mineCount는 지도 크기를 초과하면 예외가 발생한다`() {
shouldThrow<IllegalArgumentException> { MineCount(-1) }
}
}
19 changes: 19 additions & 0 deletions src/test/kotlin/minesweeper/MineMapTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import minesweeper.domain.Height
import minesweeper.domain.MineCount
import minesweeper.domain.MineMap
import minesweeper.domain.Width
import org.junit.jupiter.api.Test

class MineMapTest {
@Test
fun `지뢰 개수가 지도 크기를 초과하면 예외가 발생한다`() {
Copy link

Choose a reason for hiding this comment

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

내가 원하는 좌표에 지뢰를 심어보고, 그 좌표에 정말 지뢰가 있는지 확인해볼 수 있도록 테스트 코드를 작성해보면 어떨까요?

val height = Height(1)
val width = Width(1)
val mineCount = MineCount(2)

shouldThrow<IllegalArgumentException> { MineMap(height, width, mineCount) }
}
}
13 changes: 13 additions & 0 deletions src/test/kotlin/minesweeper/PointTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import minesweeper.domain.Point
import org.junit.jupiter.api.Test

class PointTest {
@Test
fun `좌표정보는 height와 width 의 최소 값 조건을 따른다`() {
shouldThrow<IllegalArgumentException> { Point(0, 1) }
shouldThrow<IllegalArgumentException> { Point(1, 0) }
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/minesweeper/WidthTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import minesweeper.domain.Width
import org.junit.jupiter.api.Test

class WidthTest {
@Test
fun `width는 음수 값으로 생성 시 예외가 발생한다`() {
shouldThrow<IllegalArgumentException> { Width(0) }
}
}