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

import minesweeper.domain.Height
import minesweeper.domain.MineIndexes
import minesweeper.domain.MineMap
import minesweeper.domain.MineMapSize
import minesweeper.domain.RandomIndexesGenerator
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 mineMapSize = MineMapSize(width, height)
val mineCount = InputView.receiveMineCount()
val mineIndexes = MineIndexes(RandomIndexesGenerator.generate(mineCount, mineMapSize.size()))

val mineMap = MineMap(mineMapSize, mineIndexes)

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

class MineIndexes(private val indexes: List<Int>) {
init {
require(indexes.size >= MIN_MINE_COUNT_VALUE) { "지뢰 개수는 $MIN_MINE_COUNT_VALUE 이상만 허용됩니다." }
}

fun contains(index: Int): Boolean {
return indexes.contains(index)
}

fun size(): Int {
return indexes.size
}

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

class MineMap(private val mineMapSize: MineMapSize, mineIndexes: MineIndexes) {
private val size: Int = mineMapSize.size()
val map: List<Point>

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

val height = mineMapSize.height()
val width = mineMapSize.width()

map = points(height, width, mineIndexes)
}

fun width(): Int {
return mineMapSize.width()
}

fun getPoint(index: Int): Point {
return map[index]
}

private fun points(
height: Int,
width: Int,
mineIndexes: MineIndexes
): List<Point> {
return (1..height).flatMap { y ->
generateRow(width, mineIndexes, y)
}
}

private fun generateRow(
width: Int,
mineIndexes: MineIndexes,
y: Int
) = (1..width).map { x ->
point(mineIndexes.contains(mineMapSize.getIndex(y, x)), x, y)
}

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

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

class MineMapSize(private val width: Width, private val height: Height) {
fun size(): Int {
return width.value * height.value
}

fun width(): Int {
return width.value
}

fun height(): Int {
return height.value
}

fun getIndex(heightIndex: Int, widthIndex: Int): Int {
return (heightIndex - 1) * width() + (widthIndex - 1)
}
}
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"
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/minesweeper/domain/RandomIndexesGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package minesweeper.domain

object RandomIndexesGenerator {
fun generate(size: Int, maximum: Int): List<Int> {
return (0..maximum)
.shuffled()
.take(size)
}
}
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 receiveNotNullNumber()
}

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

return receiveNotNullNumber()
}

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

return receiveNotNullNumber()
}

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

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

return input
}

private fun receiveNotNullNumber(): Int {
var input: Int? = receiveString().toIntOrNull()

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

return input
}
}
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())
.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) }
}
}
18 changes: 18 additions & 0 deletions src/test/kotlin/minesweeper/MineMapSizeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package minesweeper

import io.kotest.matchers.shouldBe
import minesweeper.domain.Height
import minesweeper.domain.MineMapSize
import minesweeper.domain.Width
import org.junit.jupiter.api.Test

class MineMapSizeTest {
@Test
fun `size는 width와 height의 value의 곱이다`() {
val width = Width(5)
val height = Height(5)
val mineMapSize = MineMapSize(width, height)

mineMapSize.size() shouldBe 25
}
}
36 changes: 36 additions & 0 deletions src/test/kotlin/minesweeper/MineMapTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package minesweeper

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.types.shouldBeTypeOf
import minesweeper.domain.Height
import minesweeper.domain.MineIndexes
import minesweeper.domain.MineMap
import minesweeper.domain.MineMapSize
import minesweeper.domain.MinePoint
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 mineMapSize = MineMapSize(width, height)
val mineIndexes = MineIndexes(listOf(1, 2, 3))

shouldThrow<IllegalArgumentException> { MineMap(mineMapSize, mineIndexes) }
}

@Test
fun `mineIndexes에 표시된 위치에 지뢰가 심어진다`() {
val height = Height(10)
val width = Width(10)
val mineMapSize = MineMapSize(width, height)
val mineIndexes = MineIndexes(listOf(1, 2, 3))
val mineMap = MineMap(mineMapSize, mineIndexes)

mineMap.getPoint(1).shouldBeTypeOf<MinePoint>()
mineMap.getPoint(2).shouldBeTypeOf<MinePoint>()
mineMap.getPoint(3).shouldBeTypeOf<MinePoint>()
}
}
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) }
}
}