diff --git a/src/main/kotlin/minesweeper/MineSweeper.kt b/src/main/kotlin/minesweeper/MineSweeper.kt index 59444691b..f05dd35e0 100644 --- a/src/main/kotlin/minesweeper/MineSweeper.kt +++ b/src/main/kotlin/minesweeper/MineSweeper.kt @@ -1,23 +1,14 @@ package minesweeper -import minesweeper.domain.MineMapGenerator -import minesweeper.domain.MineMapMeta +import minesweeper.domain.MineCountMapFactory import minesweeper.domain.PositionGenerator import minesweeper.view.InputView import minesweeper.view.OutputView object MineSweeper { fun drawMap() { - val mineMapMeta = MineMapMeta( - height = InputView.getHeight(), - width = InputView.getWidth(), - mineCount = InputView.getMineCount() - ) - val positionGenerator = PositionGenerator(mineMapMeta) - val minePositions = positionGenerator.generateMinePositions() - val emptyPositions = positionGenerator.generateEmptyPositions(minePositions) - val mineMap = MineMapGenerator.generate(minePositions, emptyPositions) - + val mineMapMeta = InputView.readMineMapMeta() + val mineMap = MineCountMapFactory(PositionGenerator(mineMapMeta)).create() OutputView.printGameStartMsg() OutputView.printMineMap(mineMapMeta, mineMap) } diff --git a/src/main/kotlin/minesweeper/domain/Cell.kt b/src/main/kotlin/minesweeper/domain/Cell.kt index 9644b2437..b6b40311c 100644 --- a/src/main/kotlin/minesweeper/domain/Cell.kt +++ b/src/main/kotlin/minesweeper/domain/Cell.kt @@ -1,9 +1,6 @@ package minesweeper.domain -data class Cell( - val state: CellState -) +sealed interface Cell -fun Cell.getStateSymbol(): String { - return this.state.symbol -} +object Mine : Cell +data class Empty(val mineCount: Int = 0) : Cell diff --git a/src/main/kotlin/minesweeper/domain/CellState.kt b/src/main/kotlin/minesweeper/domain/CellState.kt deleted file mode 100644 index 3757adc7e..000000000 --- a/src/main/kotlin/minesweeper/domain/CellState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package minesweeper.domain - -enum class CellState(val symbol: String = "") { - MINE("*"), - EMPTY("C"); -} diff --git a/src/main/kotlin/minesweeper/domain/MineCountMapFactory.kt b/src/main/kotlin/minesweeper/domain/MineCountMapFactory.kt new file mode 100644 index 000000000..f0fa6ebf6 --- /dev/null +++ b/src/main/kotlin/minesweeper/domain/MineCountMapFactory.kt @@ -0,0 +1,24 @@ +package minesweeper.domain + +class MineCountMapFactory( + private val positionGenerator: PositionGenerator +) : MineMapFactory { + override fun create(): MineMap { + val minePositions = positionGenerator.generateMinePositions() + val emptyPositions = positionGenerator.generateEmptyPositions(minePositions) + val cells = (minePositions + emptyPositions) + .getValues() + .associateWith { createCell(it, minePositions) } + return MineMap(cells) + } + + private fun createCell(position: Position, minePositions: Positions): Cell { + val aroundPositions = position.aroundPositions() + return if (minePositions.contains(position)) { + Mine + } else { + val mineCount = aroundPositions.count { minePositions.contains(it) } + Empty(mineCount) + } + } +} diff --git a/src/main/kotlin/minesweeper/domain/MineMap.kt b/src/main/kotlin/minesweeper/domain/MineMap.kt index c15cf5b6e..6c3e0646d 100644 --- a/src/main/kotlin/minesweeper/domain/MineMap.kt +++ b/src/main/kotlin/minesweeper/domain/MineMap.kt @@ -1,18 +1,12 @@ package minesweeper.domain class MineMap( - private val _values: MutableMap = mutableMapOf() + private val values: Map ) { - val values: Map - get() = _values val size: Int - get() = _values.keys.size - - fun plantCell(position: Position, cell: Cell) { - _values[position] = cell - } + get() = values.keys.size fun getCell(position: Position): Cell { - return _values[position] ?: throw IllegalArgumentException("해당 위치에 셀이 없습니다") + return values[position] ?: throw IllegalArgumentException("해당 위치에 셀이 없습니다") } } diff --git a/src/main/kotlin/minesweeper/domain/MineMapFactory.kt b/src/main/kotlin/minesweeper/domain/MineMapFactory.kt new file mode 100644 index 000000000..4a90be909 --- /dev/null +++ b/src/main/kotlin/minesweeper/domain/MineMapFactory.kt @@ -0,0 +1,5 @@ +package minesweeper.domain + +interface MineMapFactory { + fun create(): MineMap +} diff --git a/src/main/kotlin/minesweeper/domain/MineMapGenerator.kt b/src/main/kotlin/minesweeper/domain/MineMapGenerator.kt deleted file mode 100644 index 9175779f4..000000000 --- a/src/main/kotlin/minesweeper/domain/MineMapGenerator.kt +++ /dev/null @@ -1,10 +0,0 @@ -package minesweeper.domain - -object MineMapGenerator { - fun generate(minePositions: Positions, emptyPositions: Positions): MineMap { - val mineMap = MineMap() - minePositions.forEach { mineMap.plantCell(it, Cell(CellState.MINE)) } - emptyPositions.forEach { mineMap.plantCell(it, Cell(CellState.EMPTY)) } - return mineMap - } -} diff --git a/src/main/kotlin/minesweeper/domain/MineMapMeta.kt b/src/main/kotlin/minesweeper/domain/MineMapMeta.kt index ff159a25f..da12748d0 100644 --- a/src/main/kotlin/minesweeper/domain/MineMapMeta.kt +++ b/src/main/kotlin/minesweeper/domain/MineMapMeta.kt @@ -6,10 +6,10 @@ data class MineMapMeta( val mineCount: Int ) { init { - require(height > 0) { "높이는 0 이거나 음수일 수 없습니다" } - require(width > 0) { "너비는 0 이거나 음수일 수 없습니다" } - require(mineCount > 0) { "지뢰 개수는 0 이거나 음수일 수 없습니다" } - require(height * width >= mineCount) { "지뢰 개수는 (높이 x 너비) 개수를 초과할 수 없습니다" } + require(height > 0) { "입력값: $height, 높이는 0 이거나 음수일 수 없습니다" } + require(width > 0) { "입력값: $width, 너비는 0 이거나 음수일 수 없습니다" } + require(mineCount > 0) { "입력값: $mineCount, 지뢰 개수는 0 이거나 음수일 수 없습니다" } + require(height * width >= mineCount) { "입력값: ${height * width}, 지뢰 개수는 (높이 x 너비) 개수를 초과할 수 없습니다" } } fun getCellCount(): Int { diff --git a/src/main/kotlin/minesweeper/domain/Position.kt b/src/main/kotlin/minesweeper/domain/Position.kt index 27ca26655..e5fc4e992 100644 --- a/src/main/kotlin/minesweeper/domain/Position.kt +++ b/src/main/kotlin/minesweeper/domain/Position.kt @@ -5,7 +5,25 @@ data class Position( val x: Int ) { init { - require(y > 0) { "y는 0이거나 음수일 수 없습니다" } - require(x > 0) { "x는 0이거나 음수일 수 없습니다" } + require(y > 0) { "입력값: $y, y는 0이거나 음수일 수 없습니다" } + require(x > 0) { "입력값: $x, x는 0이거나 음수일 수 없습니다" } } + + fun aroundPositions(): List { + return listOfNotNull( + topOrNull(), + bottomOrNull(), + leftOrNull(), + rightOrNull(), + topOrNull()?.leftOrNull(), + topOrNull()?.rightOrNull(), + bottomOrNull()?.leftOrNull(), + bottomOrNull()?.rightOrNull() + ) + } + + private fun leftOrNull(): Position? = runCatching { Position(y, x - 1) }.getOrNull() + private fun rightOrNull(): Position? = runCatching { Position(y, x + 1) }.getOrNull() + private fun topOrNull(): Position? = runCatching { Position(y - 1, x) }.getOrNull() + private fun bottomOrNull(): Position? = runCatching { Position(y + 1, x) }.getOrNull() } diff --git a/src/main/kotlin/minesweeper/domain/PositionGenerator.kt b/src/main/kotlin/minesweeper/domain/PositionGenerator.kt index 2f9a081fa..1ba3f370f 100644 --- a/src/main/kotlin/minesweeper/domain/PositionGenerator.kt +++ b/src/main/kotlin/minesweeper/domain/PositionGenerator.kt @@ -4,32 +4,28 @@ class PositionGenerator( private val mineMapMeta: MineMapMeta, private val positionSelector: PositionSelector = RandomPositionSelector ) { - tailrec fun generateMinePositions( - minePositions: Positions = Positions() - ): Positions { - if (minePositions.size == mineMapMeta.mineCount) { return minePositions } - val randomMinePosition = positionSelector.select(mineMapMeta) - return if (randomMinePosition in minePositions) { - generateMinePositions(minePositions) - } else { - generateMinePositions(minePositions + randomMinePosition) - } + private val allPositions = generateAllPositions() + + private fun generateAllPositions(): Positions { + val allPositions = (1..mineMapMeta.height) + .flatMap { y -> (1..mineMapMeta.width).map { x -> Position(y, x) } } + .toSet() + .toPositions() + require(allPositions.size == mineMapMeta.getCellCount()) { "모든 위치를 생성하지 못했습니다" } + return allPositions + } + + fun generateMinePositions(): Positions { + val minePositions = positionSelector.select(allPositions, mineMapMeta.mineCount) + require(minePositions.size == mineMapMeta.mineCount) { "지뢰의 개수가 맞지 않습니다." } + return minePositions } fun generateEmptyPositions( minePositions: Positions ): Positions { - val allPositions = generateAllPositions() - require(allPositions.size == mineMapMeta.getCellCount()) { "모든 위치를 생성하지 못했습니다" } val emptyPositions = allPositions - minePositions require(!emptyPositions.containSamePosition(minePositions)) { "지뢰와 빈 공간은 겹칠 수 없습니다." } return emptyPositions } - - private fun generateAllPositions(): Positions { - return (1..mineMapMeta.height) - .flatMap { y -> (1..mineMapMeta.width).map { x -> Position(y, x) } } - .toSet() - .toPositions() - } } diff --git a/src/main/kotlin/minesweeper/domain/PositionSelector.kt b/src/main/kotlin/minesweeper/domain/PositionSelector.kt index b180ddbf1..52630a8a8 100644 --- a/src/main/kotlin/minesweeper/domain/PositionSelector.kt +++ b/src/main/kotlin/minesweeper/domain/PositionSelector.kt @@ -1,5 +1,5 @@ package minesweeper.domain interface PositionSelector { - fun select(mineMapMeta: MineMapMeta): Position + fun select(positions: Positions, selectNum: Int): Positions } diff --git a/src/main/kotlin/minesweeper/domain/Positions.kt b/src/main/kotlin/minesweeper/domain/Positions.kt index 7901e5b8b..f7cf15579 100644 --- a/src/main/kotlin/minesweeper/domain/Positions.kt +++ b/src/main/kotlin/minesweeper/domain/Positions.kt @@ -2,8 +2,14 @@ package minesweeper.domain class Positions( private val positions: Set = emptySet() -) : Set by positions { - infix fun containSamePosition(otherPositions: Positions): Boolean = positions.intersect(otherPositions).isNotEmpty() +) { + val size + get() = positions.size + + fun getValues(): Set = positions + fun contains(position: Position): Boolean = positions.contains(position) + + infix fun containSamePosition(otherPositions: Positions): Boolean = positions.intersect(otherPositions.getValues()).isNotEmpty() operator fun plus(position: Position): Positions = Positions(this.positions + position) operator fun plus(positions: Positions): Positions = Positions(this.positions + positions.positions) diff --git a/src/main/kotlin/minesweeper/domain/RandomPositionSelector.kt b/src/main/kotlin/minesweeper/domain/RandomPositionSelector.kt index 5350829cb..06a564cb6 100644 --- a/src/main/kotlin/minesweeper/domain/RandomPositionSelector.kt +++ b/src/main/kotlin/minesweeper/domain/RandomPositionSelector.kt @@ -1,13 +1,12 @@ package minesweeper.domain -import kotlin.random.Random - object RandomPositionSelector : PositionSelector { - override fun select( - mineMapMeta: MineMapMeta - ): Position { - val x = Random.nextInt(1, mineMapMeta.width + 1) - val y = Random.nextInt(1, mineMapMeta.height + 1) - return Position(y, x) + override fun select(positions: Positions, selectNum: Int): Positions { + return positions + .getValues() + .shuffled() + .take(selectNum) + .toSet() + .toPositions() } } diff --git a/src/main/kotlin/minesweeper/view/InputView.kt b/src/main/kotlin/minesweeper/view/InputView.kt index 0cd0f9262..64e5c79e8 100644 --- a/src/main/kotlin/minesweeper/view/InputView.kt +++ b/src/main/kotlin/minesweeper/view/InputView.kt @@ -1,16 +1,17 @@ package minesweeper.view -object InputView { - fun getHeight(): Int { - return getNumber("높이를 입력하세요.") - } +import minesweeper.domain.MineMapMeta - fun getWidth(): Int { - return getNumber("너비를 입력하세요.") - } - - fun getMineCount(): Int { - return getNumber("지뢰는 몇 개인가요?") +object InputView { + fun readMineMapMeta(): MineMapMeta { + val height = getNumber("높이를 입력하세요.") + val width = getNumber("너비를 입력하세요.") + val mineCount = getNumber("지뢰는 몇 개인가요?") + return MineMapMeta( + height = height, + width = width, + mineCount = mineCount + ) } private fun getNumber(consoleMsg: String): Int { diff --git a/src/main/kotlin/minesweeper/view/OutputView.kt b/src/main/kotlin/minesweeper/view/OutputView.kt index 8585b5724..8af968daf 100644 --- a/src/main/kotlin/minesweeper/view/OutputView.kt +++ b/src/main/kotlin/minesweeper/view/OutputView.kt @@ -1,11 +1,14 @@ package minesweeper.view +import minesweeper.domain.Empty +import minesweeper.domain.Mine import minesweeper.domain.MineMap import minesweeper.domain.MineMapMeta import minesweeper.domain.Position -import minesweeper.domain.getStateSymbol object OutputView { + private const val MINE_CHAR = "*" + fun printGameStartMsg() { println("\n지뢰 찾기 게임 시작") } @@ -18,7 +21,10 @@ object OutputView { private fun printRowCells(mineMapMeta: MineMapMeta, mineMap: MineMap, row: Int) { for (col in 1 until mineMapMeta.width + 1) { - print(mineMap.getCell(Position(row, col)).getStateSymbol() + " ") + when (val cell = mineMap.getCell(Position(row, col))) { + is Mine -> print("$MINE_CHAR ") + is Empty -> print("${cell.mineCount} ") + } } println() } diff --git a/src/test/kotlin/minesweeper/domain/MineCountMapFactoryTest.kt b/src/test/kotlin/minesweeper/domain/MineCountMapFactoryTest.kt new file mode 100644 index 000000000..25f3f3862 --- /dev/null +++ b/src/test/kotlin/minesweeper/domain/MineCountMapFactoryTest.kt @@ -0,0 +1,36 @@ +package minesweeper.domain + +import io.kotest.assertions.assertSoftly +import minesweeper.domain.support.FixedPositionSelector +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class MineCountMapFactoryTest { + private val positionGenerator = PositionGenerator( + MineMapMeta(3, 3, 3), + FixedPositionSelector + ) + + @Test + fun `주변 지뢰 개수를 담고 있는 cell을 생성해 Map을 구성한다`() { + // given + val mineCountMapFactory = MineCountMapFactory(positionGenerator) + + // when + val mineMap = mineCountMapFactory.create() + + // then + assertSoftly { + assertThat(mineMap.size).isEqualTo(9) + assertThat(mineMap.getCell(Position(1, 1))).usingRecursiveComparison().isEqualTo(Mine) + assertThat(mineMap.getCell(Position(1, 2))).usingRecursiveComparison().isEqualTo(Mine) + assertThat(mineMap.getCell(Position(1, 3))).usingRecursiveComparison().isEqualTo(Mine) + assertThat(mineMap.getCell(Position(2, 1))).usingRecursiveComparison().isEqualTo(Empty(2)) + assertThat(mineMap.getCell(Position(2, 2))).usingRecursiveComparison().isEqualTo(Empty(3)) + assertThat(mineMap.getCell(Position(2, 3))).usingRecursiveComparison().isEqualTo(Empty(2)) + assertThat(mineMap.getCell(Position(3, 1))).usingRecursiveComparison().isEqualTo(Empty(0)) + assertThat(mineMap.getCell(Position(3, 2))).usingRecursiveComparison().isEqualTo(Empty(0)) + assertThat(mineMap.getCell(Position(3, 3))).usingRecursiveComparison().isEqualTo(Empty(0)) + } + } +} diff --git a/src/test/kotlin/minesweeper/domain/MineMapGeneratorTest.kt b/src/test/kotlin/minesweeper/domain/MineMapGeneratorTest.kt deleted file mode 100644 index 254074821..000000000 --- a/src/test/kotlin/minesweeper/domain/MineMapGeneratorTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package minesweeper.domain - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class MineMapGeneratorTest { - @Test - fun `지뢰 맵을 구성할 위치 정보가 주어졌을 때 MineMap을 생성할 수 있다`() { - // given - val mineMapMeta = MineMapMeta(3, 3, 3) - val minePositions = setOf( - Position(1, 1), - Position(2, 2), - Position(3, 3) - ).toPositions() - val emptyPositions = setOf( - Position(1, 2), - Position(1, 3), - Position(2, 1), - Position(2, 3), - Position(3, 1), - Position(3, 2) - ).toPositions() - - // when - val mineMap = MineMapGenerator.generate(minePositions, emptyPositions) - - // then - assertEquals(mineMapMeta.getCellCount(), mineMap.values.size) - } -} diff --git a/src/test/kotlin/minesweeper/domain/MineMapMetaTest.kt b/src/test/kotlin/minesweeper/domain/MineMapMetaTest.kt index 1661e2e3b..77e4deb63 100644 --- a/src/test/kotlin/minesweeper/domain/MineMapMetaTest.kt +++ b/src/test/kotlin/minesweeper/domain/MineMapMetaTest.kt @@ -1,11 +1,12 @@ package minesweeper.domain -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.assertThrows +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource class MineMapMetaTest { + @ParameterizedTest @CsvSource( "0 0 0", @@ -17,7 +18,7 @@ class MineMapMetaTest { // given val properties = input.split(" ").map { it.toInt() } - assertThrows { // then + assertThatIllegalArgumentException().isThrownBy { // then MineMapMeta( // when height = properties[0], width = properties[1], @@ -36,7 +37,7 @@ class MineMapMetaTest { // given val properties = input.split(" ").map { it.toInt() } - assertThrows { // then + assertThatIllegalArgumentException().isThrownBy { // then MineMapMeta( // when height = properties[0], width = properties[1], @@ -64,6 +65,6 @@ class MineMapMetaTest { val cellCount = mineMapMeta.getCellCount() // then - assertEquals(expected, cellCount) + assertThat(cellCount).isEqualTo(expected) } } diff --git a/src/test/kotlin/minesweeper/domain/MineMapTest.kt b/src/test/kotlin/minesweeper/domain/MineMapTest.kt index 762a11aba..fa8f4e691 100644 --- a/src/test/kotlin/minesweeper/domain/MineMapTest.kt +++ b/src/test/kotlin/minesweeper/domain/MineMapTest.kt @@ -1,59 +1,45 @@ package minesweeper.domain -import org.junit.jupiter.api.Assertions.assertEquals +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows class MineMapTest { - @Test - fun `위치 정보를 전달해 MineMap에 지뢰를 심는다`() { - // given - val mineMap = MineMap() - - // when - mineMap.plantCell(Position(1, 1), Cell(CellState.MINE)) - - // then - assertEquals(1, mineMap.size) - assertEquals(CellState.MINE, mineMap.values[Position(1, 1)]?.state) - } + private val mapValues = mapOf( + Position(1, 1) to Mine, + Position(1, 2) to Empty(), + Position(2, 1) to Empty(), + Position(2, 2) to Mine + ) @Test - fun `위치 정보를 전달해 MineMap에 빈 상태를 심는다`() { - // given - val mineMap = MineMap() - - // when - mineMap.plantCell(Position(1, 1), Cell(CellState.EMPTY)) - mineMap.plantCell(Position(2, 2), Cell(CellState.EMPTY)) + fun `Position(key)와 Cell(value)로 구쉉된 map을 받으면 MineMap을 생성할 수 있다`() { + // given, when + val mineMap = MineMap(mapValues) // then - assertEquals(2, mineMap.size) - assertEquals(CellState.EMPTY, mineMap.values[Position(1, 1)]?.state) - assertEquals(CellState.EMPTY, mineMap.values[Position(2, 2)]?.state) + assertThat(mineMap.size).isEqualTo(4) } @Test - fun `주어진 위치 정보에 놓인 Cell 객체를 가져온다`() { - // given - val mineMap = MineMap() - val position = Position(1, 1) - mineMap.plantCell(position, Cell(CellState.EMPTY)) + fun `주어진 위치 정보에 놓인 Cell 객체를 가져올 수 있다`() { + // given, when + val mineMap = MineMap(mapValues) // when - val cell = mineMap.getCell(position) + val cell = mineMap.getCell(Position(1, 1)) // then - assertEquals(CellState.EMPTY, cell.state) + assertThat(cell).isEqualTo(Mine) } @Test fun `MineMap에 없는 위치 정보를 통해 Cell 객체를 가져온다면 IllegalArgumentException이 발생한다`() { - // given - val mineMap = MineMap() + // given, when + val mineMap = MineMap(mapValues) - assertThrows { // then - mineMap.getCell(Position(1, 1)) // when - } + assertThatIllegalArgumentException().isThrownBy { // then + mineMap.getCell(Position(3, 3)) // when + }.withMessage("해당 위치에 셀이 없습니다") } } diff --git a/src/test/kotlin/minesweeper/domain/PositionGeneratorTest.kt b/src/test/kotlin/minesweeper/domain/PositionGeneratorTest.kt index f3cbbe4f6..004e69e58 100644 --- a/src/test/kotlin/minesweeper/domain/PositionGeneratorTest.kt +++ b/src/test/kotlin/minesweeper/domain/PositionGeneratorTest.kt @@ -1,39 +1,35 @@ package minesweeper.domain -import org.junit.jupiter.api.Assertions.assertEquals +import io.kotest.assertions.assertSoftly +import minesweeper.domain.support.FixedPositionSelector +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class PositionGeneratorTest { - @Test - fun `지뢰 메타 정보(지뢰 수, 지뢰맵 크기)가 주어질 때 mineCount 만큼의 지뢰 위치 정보 Set을 생성하고 반환한다`() { - // given - val mineMapMeta = MineMapMeta(10, 10, 10) - val positionGenerator = PositionGenerator(mineMapMeta) - - // when - val minePositions = positionGenerator.generateMinePositions() - - // then - assertEquals(mineMapMeta.mineCount, minePositions.size) - assertEquals(true, minePositions.all { it.x in 1..10 && it.y in 1..10 }) - } + private val mineMapMeta = MineMapMeta(5, 5, 5) + private val positionGenerator = PositionGenerator( + mineMapMeta = mineMapMeta, + positionSelector = FixedPositionSelector + ) @Test - fun `지뢰 메타 정보와 지뢰 위치 Set이 주어질 때 빈칸의 위치 정보 Set을 반환한다`() { - // given - val mineMapMeta = MineMapMeta(10, 10, 10) - val minePositions = setOf( - Position(1, 1), - Position(5, 5), - Position(10, 10) - ).toPositions() - val positionGenerator = PositionGenerator(mineMapMeta) - - // when + fun `지뢰 위치와 지뢰가 아닌 위치를 생성한다`() { + // given, when + val minePositions = positionGenerator.generateMinePositions() val emptyPositions = positionGenerator.generateEmptyPositions(minePositions) - // then - assertEquals(mineMapMeta.getCellCount() - minePositions.size, emptyPositions.size) - assertEquals(true, emptyPositions.all { it.x in 1..10 && it.y in 1..10 }) + assertSoftly { // then + assertThat(minePositions.getValues()) + .containsExactly( + Position(1, 1), Position(1, 2), Position(1, 3), Position(1, 4), Position(1, 5) + ) + assertThat(emptyPositions.getValues()) + .containsExactly( + Position(2, 1), Position(2, 2), Position(2, 3), Position(2, 4), Position(2, 5), + Position(3, 1), Position(3, 2), Position(3, 3), Position(3, 4), Position(3, 5), + Position(4, 1), Position(4, 2), Position(4, 3), Position(4, 4), Position(4, 5), + Position(5, 1), Position(5, 2), Position(5, 3), Position(5, 4), Position(5, 5) + ) + } } } diff --git a/src/test/kotlin/minesweeper/domain/PositionTest.kt b/src/test/kotlin/minesweeper/domain/PositionTest.kt index a43819cc3..2edabad10 100644 --- a/src/test/kotlin/minesweeper/domain/PositionTest.kt +++ b/src/test/kotlin/minesweeper/domain/PositionTest.kt @@ -1,6 +1,8 @@ package minesweeper.domain -import org.junit.jupiter.api.assertThrows +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException +import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource @@ -16,8 +18,22 @@ class PositionTest { // given val properties = input.split(" ").map { it.toInt() } - assertThrows { // then + assertThatIllegalArgumentException().isThrownBy { // then Position(properties[0], properties[1]) // when } } + + @Test + fun `현재 위치의 상대 좌표(상, 하, 좌, 우, 좌상, 우상, 좌하, 우하)를 반환한다(단, 0 이하의 좌표는 제외)`() { + // given + val position = Position(1, 1) + // when + val aroundPositions = position.aroundPositions() + // then + assertThat(aroundPositions).containsExactlyInAnyOrder( + Position(1, 2), + Position(2, 1), + Position(2, 2) + ) + } } diff --git a/src/test/kotlin/minesweeper/domain/PositionsTest.kt b/src/test/kotlin/minesweeper/domain/PositionsTest.kt index d0a2b9d0f..5d872c3e9 100644 --- a/src/test/kotlin/minesweeper/domain/PositionsTest.kt +++ b/src/test/kotlin/minesweeper/domain/PositionsTest.kt @@ -1,7 +1,7 @@ package minesweeper.domain +import io.kotest.assertions.assertSoftly import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class PositionsTest { @@ -12,7 +12,7 @@ class PositionsTest { ).toPositions() @Test - fun `두 Positions 중 중복 Position 포함 여부를 알 수 있다`() { + fun `두 Positions중 중복 Position 포함 여부를 알 수 있다`() { // given val anotherPosition = setOf( Position(1, 1), @@ -24,7 +24,7 @@ class PositionsTest { val isContainSamePosition = positions containSamePosition anotherPosition // then - assertEquals(true, isContainSamePosition) + assertThat(isContainSamePosition).isEqualTo(true) } @Test @@ -36,13 +36,17 @@ class PositionsTest { val newPositions = positions + position // then - assertEquals(4, newPositions.size) - assertThat(newPositions == setOf( - Position(1, 1), - Position(2, 2), - Position(3, 3), - Position(100, 100) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(4) + assertThat( + newPositions == setOf( + Position(1, 1), + Position(2, 2), + Position(3, 3), + Position(100, 100) + ).toPositions() + ) + } } @Test @@ -54,12 +58,16 @@ class PositionsTest { val newPositions = positions + position // then - assertEquals(3, newPositions.size) - assertThat(newPositions == setOf( - Position(1, 1), - Position(2, 2), - Position(3, 3) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(3) + assertThat( + newPositions == setOf( + Position(1, 1), + Position(2, 2), + Position(3, 3) + ).toPositions() + ) + } } @Test @@ -75,15 +83,19 @@ class PositionsTest { val newPositions = positions + anotherPositions // then - assertEquals(6, newPositions.size) - assertThat(newPositions == setOf( - Position(1, 1), - Position(2, 2), - Position(3, 3), - Position(100, 100), - Position(200, 200), - Position(300, 300) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(6) + assertThat( + newPositions == setOf( + Position(1, 1), + Position(2, 2), + Position(3, 3), + Position(100, 100), + Position(200, 200), + Position(300, 300) + ).toPositions() + ) + } } @Test @@ -99,14 +111,18 @@ class PositionsTest { val newPositions = positions + anotherPositions // then - assertEquals(5, newPositions.size) - assertThat(newPositions == setOf( - Position(1, 1), - Position(2, 2), - Position(3, 3), - Position(20, 20), - Position(30, 30) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(5) + assertThat( + newPositions == setOf( + Position(1, 1), + Position(2, 2), + Position(3, 3), + Position(20, 20), + Position(30, 30) + ).toPositions() + ) + } } @Test @@ -118,11 +134,15 @@ class PositionsTest { val newPositions = positions - position // then - assertEquals(2, newPositions.size) - assertThat(newPositions == setOf( - Position(2, 2), - Position(3, 3) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(2) + assertThat( + newPositions == setOf( + Position(2, 2), + Position(3, 3) + ).toPositions() + ) + } } @Test @@ -138,9 +158,9 @@ class PositionsTest { val newPositions = positions - anotherPositions // then - assertEquals(1, newPositions.size) - assertThat(newPositions == setOf( - Position(3, 3) - ).toPositions()) + assertSoftly { + assertThat(newPositions.size).isEqualTo(1) + assertThat(newPositions == setOf(Position(3, 3)).toPositions()) + } } } diff --git a/src/test/kotlin/minesweeper/domain/support/FixedPositionSelector.kt b/src/test/kotlin/minesweeper/domain/support/FixedPositionSelector.kt new file mode 100644 index 000000000..4b907b8ac --- /dev/null +++ b/src/test/kotlin/minesweeper/domain/support/FixedPositionSelector.kt @@ -0,0 +1,16 @@ +package minesweeper.domain.support + +import minesweeper.domain.PositionSelector +import minesweeper.domain.Positions +import minesweeper.domain.toPositions + +object FixedPositionSelector : PositionSelector { + override fun select(positions: Positions, selectNum: Int): Positions { + val values = positions.getValues() + return values + .sortedWith(compareBy({ it.y }, { it.x })) + .take(selectNum) + .toSet() + .toPositions() + } +}