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

πŸš€ 1단계 - 지뒰 μ°ΎκΈ°(그리기) #361

Open
wants to merge 3 commits into
base: mins99
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
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,52 @@
# kotlin-minesweeper
# kotlin-minesweeper

## πŸš€ 1단계 - 지뒰 μ°ΎκΈ°(그리기)
### κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­
지뒰 μ°ΎκΈ°λ₯Ό λ³€ν˜•ν•œ ν”„λ‘œκ·Έλž¨μ„ κ΅¬ν˜„ν•œλ‹€.

- 높이와 λ„ˆλΉ„, 지뒰 개수λ₯Ό μž…λ ₯받을 수 μžˆλ‹€.
- μ§€λ’°λŠ” λˆˆμ— 잘 λ„λŠ” κ²ƒμœΌλ‘œ ν‘œκΈ°ν•œλ‹€.
- μ§€λ’°λŠ” 가급적 λžœλ€μ— κ°€κΉκ²Œ λ°°μΉ˜ν•œλ‹€.

### μ‹€ν–‰ κ²°κ³Ό
```
높이λ₯Ό μž…λ ₯ν•˜μ„Έμš”.
10

λ„ˆλΉ„λ₯Ό μž…λ ₯ν•˜μ„Έμš”.
10

μ§€λ’°λŠ” λͺ‡ κ°œμΈκ°€μš”?
10

지뒰찾기 κ²Œμž„ μ‹œμž‘
C C C * C C C * C C
C C * C * C C C C C
C C C C C C C C C C
C C C C C C C C C C
* C C C C C C C C C
C C C C C C * C C C
C C * C C C * C C C
C C C C C C * C C *
C C C C C C C C C C
C C C C C C C C C C
```

### ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬ 사항
객체지ν–₯ μƒν™œ 체쑰 원칙을 μ§€ν‚€λ©΄μ„œ ν”„λ‘œκ·Έλž˜λ°ν•œλ‹€.
#### 객체지ν–₯ μƒν™œ 체쑰 원칙
1. ν•œ λ©”μ„œλ“œμ— 였직 ν•œ λ‹¨κ³„μ˜ λ“€μ—¬μ“°κΈ°λ§Œ ν•œλ‹€.
2. else μ˜ˆμ•½μ–΄λ₯Ό 쓰지 μ•ŠλŠ”λ‹€.
3. λͺ¨λ“  μ›μ‹œ κ°’κ³Ό λ¬Έμžμ—΄μ„ 포μž₯ν•œλ‹€.
4. ν•œ 쀄에 점을 ν•˜λ‚˜λ§Œ μ°λŠ”λ‹€.
5. 쀄여 쓰지 μ•ŠλŠ”λ‹€(μΆ•μ•½ κΈˆμ§€).
6. λͺ¨λ“  μ—”ν‹°ν‹°λ₯Ό μž‘κ²Œ μœ μ§€ν•œλ‹€.
7. 3개 μ΄μƒμ˜ μΈμŠ€ν„΄μŠ€ λ³€μˆ˜λ₯Ό 가진 클래슀λ₯Ό 쓰지 μ•ŠλŠ”λ‹€.
8. 일급 μ»¬λ ‰μ…˜μ„ μ“΄λ‹€.
9. getter/setter/ν”„λ‘œνΌν‹°λ₯Ό 쓰지 μ•ŠλŠ”λ‹€.

### κ΅¬ν˜„ 사항
- [x] 높이λ₯Ό μž…λ ₯λ°›λŠ”λ‹€
- [x] λ„ˆλΉ„λ₯Ό μž…λ ₯λ°›λŠ”λ‹€
- [x] 지뒰 갯수λ₯Ό μž…λ ₯λ°›λŠ”λ‹€
- [x] μž…λ ₯ κ²°κ³Όλ₯Ό 좜λ ₯ν•œλ‹€
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repositories {
dependencies {
testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2")
testImplementation("org.assertj", "assertj-core", "3.22.0")
testImplementation("io.kotest", "kotest-runner-junit5", "5.2.3")
testImplementation("io.kotest", "kotest-runner-junit5", "5.5.3")
}

tasks {
Expand Down
16 changes: 16 additions & 0 deletions src/main/kotlin/minesweeper/controller/MineSweeperController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package minesweeper.controller

import minesweeper.model.MineSweeperGame
import minesweeper.view.InputView
import minesweeper.view.OutputView

class MineSweeperController {
fun start() {
val game = MineSweeperGame(InputView.inputHeight(), InputView.inputWidth(), InputView.inputCountOfMine())
OutputView.printMap(game.minefield())
}
}

fun main() {
MineSweeperController().start()
}
22 changes: 22 additions & 0 deletions src/main/kotlin/minesweeper/model/MineSweeperGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package minesweeper.model

import minesweeper.model.board.Minefield
import minesweeper.model.cell.Opening

class MineSweeperGame(
private val countOfMine: Int,
private val minefield: Minefield
) {
constructor(rows: Int, cols: Int, countOfMine: Int) : this(
countOfMine = countOfMine,
minefield = Minefield(rows, cols)
)

init {
plantingMines()
}

fun minefield(): Array<Array<Opening>> = minefield.minefield

Choose a reason for hiding this comment

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

μΌκΈ‰μ»¬λ ‰μ…˜ μ‚¬μš©μ‹œ view μ˜μ—­μ— 데이터λ₯Ό κ°€μ Έμ˜¬ λ•Œ
fun minefield(): Array<Array> = minefield.minefield 와 같이 ν•œ 번 더 κΊΌλ‚΄κ²Œ λ˜λŠ”λ°,
ν˜Ήμ‹œ 객체지ν–₯적으둜 더 쒋은 방법이 μžˆλ‹€λ©΄ μ•Œλ €μ£Όμ‹œλ©΄ κ°μ‚¬ν•˜κ² μŠ΅λ‹ˆλ‹€! πŸ™‡

κ²°κ³Ό(λͺ¨λ“  셀을 좜λ ₯)μ—λ§Œ μ§‘μ€‘ν•˜λŠ” λŒ€μ‹  ν•„μš”ν•œ κΈ°λŠ₯을 μž‘κ²Œ μ ‘κ·Ό ν•΄ λ³Ό 수둝 μ’‹μŠ΅λ‹ˆλ‹€.
κ°€λ Ή ν–‰κ³Ό μ—΄μ˜ 크기λ₯Ό μ•Œκ³  μžˆμ„ λ•Œ μš°λ¦¬κ°€ μ›ν•˜λŠ” 것은, "ν•΄λ‹Ή μœ„μΉ˜μ— μžˆλŠ” 셀을 μ•Œλ €μ€˜" μž…λ‹ˆλ‹€.

fun getCellByPosition(position: Position): Cell { ... }

val position = Position(x, y)
val cell: Cell = getCellByPosition(position)


private fun plantingMines() = minefield.plantingMine(countOfMine)
}
11 changes: 11 additions & 0 deletions src/main/kotlin/minesweeper/model/board/Cols.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package minesweeper.model.board

import kotlin.random.Random

data class Cols(val value: Int) {
init {
require(value > 0) { "μž…λ ₯ 값은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€." }
}

fun getPosition() = Random.nextInt(value)
}
42 changes: 42 additions & 0 deletions src/main/kotlin/minesweeper/model/board/Minefield.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package minesweeper.model.board

import minesweeper.model.cell.Island
import minesweeper.model.cell.Mine
import minesweeper.model.cell.Opening

class Minefield(
private val rows: Rows,
private val cols: Cols,
val minefield: Array<Array<Opening>>

Choose a reason for hiding this comment

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

Array<Array<Opening>> νƒ€μž…μ΄λΌλ©΄, μ–΄λ– ν•œ μœ„μΉ˜μ˜ 셀에 μ ‘κ·Όν•˜κΈ° μœ„ν•΄ λ‹€μ†Œ λΆˆν•„μš”ν•œ μˆœνšŒκ°€ 많이 ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

νŠΉμ • μœ„μΉ˜λ₯Ό 보닀 효율적으둜 μ ‘κ·Όν•˜κΈ° μœ„ν•œ 자료ꡬ쑰λ₯Ό κ³ λ € ν•΄ λ³Ό 수 μžˆμ§€ μ•Šμ„κΉŒμš”?

) {
constructor(rows: Int, cols: Int) : this(
rows = Rows(rows),
cols = Cols(cols),
minefield = Array(rows) { Array(cols) { Island() } }
)

constructor(rows: Int, cols: Int, minefield: Array<Array<Opening>>) : this(
rows = Rows(rows),
cols = Cols(cols),
minefield = minefield
)

fun plantingMine(countOfMine: Int) {
var mineCount = 0

while (mineCount < countOfMine) {
val row = rows.getPosition()
val col = cols.getPosition()

mineCount = plantingResult(row, col, mineCount)
}
}
Comment on lines +24 to +33

Choose a reason for hiding this comment

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

일반적으둜 μƒκ°ν–ˆμ„ λ•Œ, getPosition() μ΄λΌλŠ” ν•¨μˆ˜κ°€ 무엇을 λ°˜ν™˜ν•  것이라고 μ˜ˆμΈ‘ν• κΉŒμš”?
κ³Όμ—° λ¬΄μž‘μœ„ 값을 λ°˜ν™˜ν•  κ²ƒμœΌλ‘œ κΈ°λŒ€ν•  수 μžˆμ„κΉŒμš”?

μ €λŠ” κ·Έ 이전에 ν–‰, μ—΄ μ΄λΌλŠ” κ°’ 객체가 Position ? μ΄λΌλŠ” μœ„μΉ˜λΌλŠ” 것이 무엇을 μ˜λ―Έν•˜λŠ” 것인지 λΆ€ν„° 이해가 잘 λ˜μ§€ μ•Šμ„ 것 κ°™μ•„μš”.

μ–΄λ– ν•œ x, y μ’Œν‘œμ— 지뒰λ₯Ό μ‹¬λŠ”λ‹€λŠ” μ—­ν• λ‘œ λ§Œλ“€μ–΄ λ³΄λŠ”κ±΄ μ–΄λ–¨κΉŒμš”?

// position μœ„μΉ˜μ— 지뒰λ₯Ό μ‹¬λŠ”λ‹€.
fun mine(position: Position) { ... }


private fun plantingResult(row: Int, col: Int, mineCount: Int): Int {
if (minefield[row][col] is Island) {
minefield[row][col] = Mine()
return mineCount + 1
}
return mineCount
}
Comment on lines +35 to +41

Choose a reason for hiding this comment

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

μ΄μ–΄μ„œ, 지뒰λ₯Ό μ‹¬λŠ”λ‹€λŠ” λ™μž‘μ΄ ν˜„μž¬λŠ” λ¬΄μž‘μœ„ 값에 의쑴이 λ˜μ–΄ μžˆμ–΄μš”.
κ·Έλ™μ•ˆ λ―Έμ…˜μ—μ„œ ν•΄μ™”λ˜ 것 처럼 λ¬΄μž‘μœ„ 값은 ν…ŒμŠ€νŠΈλ₯Ό 맀우 νž˜λ“€κ²Œ ν•˜κ³  객체가 자율적으둜 λ™μž‘μ„ μˆ˜ν–‰ν•˜λŠ” 것을 μ‹ λ’°ν•  수 없도둝 λ§Œλ“­λ‹ˆλ‹€.

μ•žμ„œ μœ„μΉ˜λΌλŠ” κ°œλ…μ„ μ‚¬μš©ν•΄λ³Έλ‹€λ©΄, μ•„λž˜μ™€ 같은 μˆœμ„œλ₯Ό μ°Έκ³  ν•΄ λ³΄μ‹œλ©΄ 도움이 될 것 κ°™μŠ΅λ‹ˆλ‹€.

1. 5 x 5 크기의 λ³΄λ“œλ₯Ό μ€€λΉ„ν•œλ‹€ // Minefield
2. μ§€λ’°μ˜ μœ„μΉ˜λ₯Ό νŒλ‹¨ν•œλ‹€. // MineSweeperGame
3. μ§€λ’°μ˜ μœ„μΉ˜λŠ” 랜덀으둜 받을 μˆ˜λ„ 있고, λͺ…μ‹œμ μœΌλ‘œ 받을 μˆ˜λ„ μžˆλ‹€ (interface)
4. 주어진 μ§€λ’°μ˜ μœ„μΉ˜λ§ŒνΌ 지뒰λ₯Ό μ‹¬λŠ”λ‹€. ( ??? )

}
11 changes: 11 additions & 0 deletions src/main/kotlin/minesweeper/model/board/Rows.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package minesweeper.model.board

import kotlin.random.Random

data class Rows(val value: Int) {
init {
require(value > 0) { "μž…λ ₯ 값은 μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€." }
}

fun getPosition() = Random.nextInt(value)
}
3 changes: 3 additions & 0 deletions src/main/kotlin/minesweeper/model/cell/Island.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package minesweeper.model.cell

data class Island(override val value: String = "C") : Opening
3 changes: 3 additions & 0 deletions src/main/kotlin/minesweeper/model/cell/Mine.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package minesweeper.model.cell

data class Mine(override val value: String = "*") : Opening

Choose a reason for hiding this comment

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

"*" 을 좜λ ₯ν•˜λŠ” 것은 UI의 관심사 이지 μ•Šμ„κΉŒμš”?
κ°€λ Ή 지뒰λ₯Ό 별이 μ•„λ‹Œ κ·Έλž˜ν”½ λͺ¨μ–‘μ˜ μ§€λ’°λ‘œ 좜λ ₯ν•œλ‹€κ³  ν•˜λ©΄, 도메인 λͺ¨λΈμ˜ valueλŠ” μ•„λ¬΄λŸ° 역할을 ν•˜μ§€ λͺ»ν•˜κ²Œ λ κ±°μ—μš”.

5 changes: 5 additions & 0 deletions src/main/kotlin/minesweeper/model/cell/Opening.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package minesweeper.model.cell

interface Opening {
val value: String
}
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.

cell의 경우 κ°€λŠ₯ν•œ μΌ€μ΄μŠ€κ°€ 빈칸 λ˜λŠ” 지뒰 둜 λ˜μ–΄μžˆλ‹€ λ³΄λ‹ˆ μΈν„°νŽ˜μ΄μŠ€(Opening)λ₯Ό μ‚¬μš©ν•˜μ—¬ μœ μ—°ν•˜κ²Œ ꡬ성

빈칸, λ˜λŠ” μ§€λ’°λΌλŠ” μ˜λ―Έκ°€ Opening μ΄λΌλŠ” νƒ€μž…μœΌλ‘œ 잘 이루어져 μžˆμ„κΉŒμš”?
Opening μ΄λΌλŠ” 것은 μ–΄λ– ν•œ μƒνƒœ? 처럼 λŠκ»΄μ§€μ§€ μ•Šμ„κΉŒμš”?
isOpening() ...

μ§€λ’°μ°ΎκΈ°μ˜ κΈ°λ³Έ κ²Œμž„ κ΅¬μ„±μœΌλ‘œλŠ” λ³΄λ“œ νŒμ—λŠ” N개의 μ…€(Cell)이 μ‘΄μž¬ν•œλ‹€ 을 ν‘œν˜„ν•˜κ³ λŠ” ν•©λ‹ˆλ‹€.
κ·Έλ ‡λ‹€λ©΄ 이λ₯Ό λŒ€μž…ν•΄ 봀을 λ•Œ λ³΄λ“œ νŒμ—λŠ” N개의 Opening이 μ‘΄μž¬ν•œλ‹€ 둜 비ꡐ ν•΄ 보싀 수 μžˆμŠ΅λ‹ˆλ‹€.

19 changes: 19 additions & 0 deletions src/main/kotlin/minesweeper/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package minesweeper.view

object InputView {

fun inputHeight(): Int {
println("높이λ₯Ό μž…λ ₯ν•˜μ„Έμš”.")
return readln().toInt()
}

fun inputWidth(): Int {
println("\nλ„ˆλΉ„λ₯Ό μž…λ ₯ν•˜μ„Έμš”.")
return readln().toInt()
}

fun inputCountOfMine(): Int {
println("\nμ§€λ’°λŠ” λͺ‡ 개 μΈκ°€μš”?")
return readln().toInt()
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/minesweeper/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package minesweeper.view

import minesweeper.model.cell.Opening

object OutputView {
fun printMap(minefield: Array<Array<Opening>>) {
println("\n지뒰찾기 κ²Œμž„ μ‹œμž‘")
for (row in minefield) {
printCols(row)
}
}

private fun printCols(row: Array<Opening>) {
for (it in row) {
print("${it.value} ")
}
println()
}
}
22 changes: 22 additions & 0 deletions src/test/kotlin/minesweeper/model/MineSweeperGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package minesweeper.model

import io.kotest.matchers.types.shouldBeInstanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import org.junit.jupiter.api.Test

class MineSweeperGameTest {
@Test
fun `지뒰찾기 κ²Œμž„μ˜ λ„ˆλΉ„μ™€ 높이, 지뒰 갯수λ₯Ό λ°›μ•„ κ²Œμž„μ„ μƒμ„±ν•œλ‹€`() {
// given
val cols = 10
val rows = 10
val countOfMine = 10

// when
val mineSweeperGame = MineSweeperGame(cols = cols, rows = rows, countOfMine = countOfMine)

// then
mineSweeperGame.shouldBeInstanceOf<MineSweeperGame>()
mineSweeperGame.minefield().size shouldBeSameInstanceAs cols
}
}
18 changes: 18 additions & 0 deletions src/test/kotlin/minesweeper/model/board/ColsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package minesweeper.model.board

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.ints.shouldBeBetween

class ColsTest : StringSpec({
"ν–‰μ˜ 길이 λ²”μœ„ λ‚΄μ˜ λžœλ€ν•œ μœ„μΉ˜κ°’μ„ λ°˜ν™˜λ°›λŠ”λ‹€" {
// given
val size = 10
val col = Cols(size)

// when
val result = col.getPosition()

// then
result.shouldBeBetween(0, size)
}
})
27 changes: 27 additions & 0 deletions src/test/kotlin/minesweeper/model/board/MinefieldTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package minesweeper.model.board

import io.kotest.matchers.shouldBe
import minesweeper.model.cell.Island
import minesweeper.model.cell.Mine
import minesweeper.model.cell.Opening
import org.junit.jupiter.api.Test

class MinefieldTest {
@Test
fun `지뒰찾기 맡 생성 κ²°κ³Ό 지뒰 갯수만큼 지뒰가 심어진닀`() {
// given
val openings: Array<Array<Opening>> = arrayOf(
arrayOf(Island(), Mine(), Island()),
arrayOf(Mine(), Island(), Mine()),
arrayOf(Mine(), Island(), Island())
)
val minefield = Minefield(3, 3, openings)

// when
val (islandCount, mineCount) = openings.flatten().partition { it is Island }

// then
islandCount.size shouldBe 5
mineCount.size shouldBe 4
}
}
18 changes: 18 additions & 0 deletions src/test/kotlin/minesweeper/model/board/RowsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package minesweeper.model.board

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.ints.shouldBeBetween

class RowsTest : StringSpec({
"μ—΄μ˜ 길이 λ²”μœ„ λ‚΄μ˜ λžœλ€ν•œ μœ„μΉ˜κ°’μ„ λ°˜ν™˜λ°›λŠ”λ‹€" {
// given
val size = 10
val row = Rows(size)

// when
val result = row.getPosition()

// then
result.shouldBeBetween(0, size)
}
})