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 지뢰찾기(그리기) #343

Open
wants to merge 14 commits into
base: qktlf789456
Choose a base branch
from
Empty file removed src/main/kotlin/.gitkeep
Empty file.
5 changes: 5 additions & 0 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import view.MineGameFlow

fun main() {
MineGameFlow().start()
}
15 changes: 15 additions & 0 deletions src/main/kotlin/domain/Cell.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package domain

data class Cell private constructor(val type: CellType, val position: Position) {
companion object {
fun ground(x: Int, y: Int): Cell = Cell(CellType.GROUND, position(x, y))

fun mine(x: Int, y: Int): Cell = Cell(CellType.MINE, position(x, y))

private fun position(x: Int, y: Int): Position = Position(x, y)
}
}

enum class CellType {
GROUND, MINE
}
37 changes: 37 additions & 0 deletions src/main/kotlin/domain/MineMatrix.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package domain

class MineMatrix(
private val minePositionGenerator: PositionGenerator? = null,
private val height: Int,
private val width: Int,
private val mineCount: Int
) {
private val minePositions: Set<Position> = generateMinePositions()

fun allCells(): List<Cell> {
return (0 until height).map { thisHeight ->
(0 until width).map { thisWidth ->
val position = Position(thisWidth, thisHeight)
if (position.isMinePosition()) Cell.mine(thisWidth, thisHeight) else Cell.ground(thisWidth, thisHeight)
}
}.flatten().sort()
}

private fun generateMinePositions() = minePositionGenerator().generate(mineCount).toSet()

private fun minePositionGenerator() = minePositionGenerator ?: randomPositionGenerator()

private fun randomPositionGenerator(): PositionGenerator = RandomPositionGenerator(DefaultRandomGenerator(), BASE_HEIGHT, height - 1, BASE_WIDTH, width - 1)

private fun Position.isMinePosition() = minePositions.contains(this)

private fun List<Cell>.sort(): List<Cell> = sortedWith { leftCell, rightCell ->
if (leftCell.position.y != rightCell.position.y) leftCell.position.y.compareTo(rightCell.position.y)
leftCell.position.x.compareTo(rightCell.position.x)
}

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

data class Position(val x: Int, val y: Int) {

fun move(movementBlock: MovementBlock): Position {
return PositionMovement(this).apply(movementBlock).position()
}

private class PositionMovement(private val position: Position) : Movement {
private var xPlusValue: Int = 0
private var yPlusValue: Int = 0

override fun up() {
yPlusValue++
}

override fun down() {
yPlusValue--
}

override fun left() {
xPlusValue--
}

override fun right() {
xPlusValue++
}

fun position() = Position(position.x + xPlusValue, position.y + yPlusValue)
}
}

typealias MovementBlock = Movement.() -> Unit

interface Movement {
fun up()

fun down()

fun left()

fun right()
}
35 changes: 35 additions & 0 deletions src/main/kotlin/domain/PositionGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package domain

import kotlin.math.abs

interface PositionGenerator {
fun generate(count: Int): List<Position>
}

class RandomPositionGenerator(
private val randomGenerator: RandomGenerator,
private val xFrom: Int,
private val xUntil: Int,
private val yFrom: Int,
private val yUntil: Int
) : PositionGenerator {
override fun generate(count: Int): List<Position> {
require(xLength() * yLength() >= count)

return mutableSetOf<Position>().apply {
while (size < count) {
add(randomPosition())
}
}.toList()
}

private fun xLength() = abs(xFrom - xUntil) + 1

private fun yLength() = abs(yFrom - yUntil) + 1

private fun randomPosition(): Position = Position(randomX(), randomY())

private fun randomX() = randomGenerator.random(xFrom, xUntil)

private fun randomY() = randomGenerator.random(yFrom, yUntil)
}
16 changes: 16 additions & 0 deletions src/main/kotlin/domain/RandomGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package domain

import kotlin.random.Random

interface RandomGenerator {
/**
* generate random integer [from] ~ [until](inclusive)
*/
fun random(from: Int, until: Int): Int
}

class DefaultRandomGenerator : RandomGenerator {
override fun random(from: Int, until: Int): Int {
return Random.nextInt(from, until + 1)
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package view

class InputView {
fun inputHeight(): Int {
println("높이를 입력하세요.")
return inputReadLine().toInt()
}

fun inputWidth(): Int {
println("너비를 입력하세요.")
return inputReadLine().toInt()
}

fun inputMineCount(): Int {
println("지뢰는 몇 개인가요?")
return inputReadLine().toInt()
}

private fun inputReadLine() = readLine()!!
}
26 changes: 26 additions & 0 deletions src/main/kotlin/view/MineGameFlow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package view

import domain.MineMatrix

class MineGameFlow {
private lateinit var mineMatrix: MineMatrix

private val inputView = InputView()
private val outputView = OutputView()

fun start() {
with(inputView) {
readyMineMatrix(inputHeight(), inputWidth(), inputMineCount())
}

with(outputView) {
outputGameStartMessage()
val cells = mineMatrix.allCells()
outputCells(cells)
}
}

private fun readyMineMatrix(height: Int, width: Int, mineCount: Int) {
mineMatrix = MineMatrix(height = height, width = width, mineCount = mineCount)
}
}
28 changes: 28 additions & 0 deletions src/main/kotlin/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package view

import domain.Cell
import domain.CellType

class OutputView {
fun outputGameStartMessage() {
println("지뢰찾기 게임 시작")
}

fun outputCells(cells: List<Cell>) {
val heightDividedCells = cells.groupBy { it.position.y }
heightDividedCells.keys.sorted().forEach { height ->
heightDividedCells.getValue(height).joinToString(separator = SPACING) { it.toView() }.also { println(it) }
}
}

private fun Cell.toView(): String = when (type) {
CellType.GROUND -> GROUND_VIEW
CellType.MINE -> MINE_VIEW
}

companion object {
private const val GROUND_VIEW = "C"
private const val MINE_VIEW = "*"
private const val SPACING = " "
}
}
Empty file removed src/test/kotlin/.gitkeep
Empty file.
46 changes: 46 additions & 0 deletions src/test/kotlin/domain/CellTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package domain

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class CellTest {
@Test
fun `GROUND Cell 을 생성할 수 있다`() {
with(Cell.ground(0, 0)) {
assertThat(type).isEqualTo(CellType.GROUND)
}
}

@Test
fun `MINE Cell 을 생성할 수 있다`() {
with(Cell.mine(0, 0)) {
assertThat(type).isEqualTo(CellType.MINE)
}
}

@Test
fun `Cell 은 x 좌표를 갖을 수 있다`() {
// given
val x = 10
val y = 11

// when
val cell = Cell.ground(x, y)

// then
assertThat(cell.position.x).isEqualTo(10)
}

@Test
fun `Cell 은 y 좌표를 갖을 수 있다`() {
// given
val x = 10
val y = 11

// when
val cell = Cell.ground(x, y)

// then
assertThat(cell.position.y).isEqualTo(11)
}
}
Loading