Make Area immutable
This commit is contained in:
parent
c35c050f93
commit
c68e172946
12 changed files with 327 additions and 306 deletions
|
@ -13,7 +13,6 @@ import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
import dev.lucasnlm.antimine.common.level.models.Mark
|
import dev.lucasnlm.antimine.common.level.models.Mark
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.models.Score
|
import dev.lucasnlm.antimine.common.level.models.Score
|
||||||
import dev.lucasnlm.antimine.common.level.models.StateUpdate
|
|
||||||
import dev.lucasnlm.antimine.common.level.solver.LimitedBruteForceSolver
|
import dev.lucasnlm.antimine.common.level.solver.LimitedBruteForceSolver
|
||||||
import dev.lucasnlm.antimine.core.control.ActionResponse
|
import dev.lucasnlm.antimine.core.control.ActionResponse
|
||||||
import dev.lucasnlm.antimine.core.control.GameControl
|
import dev.lucasnlm.antimine.core.control.GameControl
|
||||||
|
@ -27,17 +26,12 @@ class GameController {
|
||||||
private var saveId = 0
|
private var saveId = 0
|
||||||
private var firstOpen: FirstOpen = FirstOpen.Unknown
|
private var firstOpen: FirstOpen = FirstOpen.Unknown
|
||||||
private var gameControl: GameControl = GameControl.Standard
|
private var gameControl: GameControl = GameControl.Standard
|
||||||
private var mines: Sequence<Area> = emptySequence()
|
|
||||||
private var useQuestionMark = true
|
private var useQuestionMark = true
|
||||||
|
|
||||||
var hasMines = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
val seed: Long
|
val seed: Long
|
||||||
|
|
||||||
private val minefieldCreator: MinefieldCreator
|
private val minefieldCreator: MinefieldCreator
|
||||||
var field: List<Area>
|
private var field: List<Area>
|
||||||
private set
|
|
||||||
|
|
||||||
constructor(minefield: Minefield, seed: Long, saveId: Int? = null) {
|
constructor(minefield: Minefield, seed: Long, saveId: Int? = null) {
|
||||||
this.minefieldCreator = MinefieldCreator(minefield, Random(seed))
|
this.minefieldCreator = MinefieldCreator(minefield, Random(seed))
|
||||||
|
@ -54,12 +48,15 @@ class GameController {
|
||||||
this.saveId = save.uid
|
this.saveId = save.uid
|
||||||
this.seed = save.seed
|
this.seed = save.seed
|
||||||
this.firstOpen = save.firstOpen
|
this.firstOpen = save.firstOpen
|
||||||
|
|
||||||
this.field = save.field
|
this.field = save.field
|
||||||
this.mines = this.field.filter { it.hasMine }.asSequence()
|
|
||||||
this.hasMines = this.mines.count() != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun field() = field
|
||||||
|
|
||||||
|
fun mines() = field.filter { it.hasMine }
|
||||||
|
|
||||||
|
fun hasMines() = field.firstOrNull { it.hasMine } != null
|
||||||
|
|
||||||
private fun getArea(id: Int) = field.first { it.id == id }
|
private fun getArea(id: Int) = field.first { it.id == id }
|
||||||
|
|
||||||
private fun plantMinesExcept(safeId: Int) {
|
private fun plantMinesExcept(safeId: Int) {
|
||||||
|
@ -69,24 +66,21 @@ class GameController {
|
||||||
field = minefieldCreator.create(safeId, useSafeZone)
|
field = minefieldCreator.create(safeId, useSafeZone)
|
||||||
val fieldCopy = field.map { it.copy() }.toMutableList()
|
val fieldCopy = field.map { it.copy() }.toMutableList()
|
||||||
val minefieldHandler = MinefieldHandler(fieldCopy, false)
|
val minefieldHandler = MinefieldHandler(fieldCopy, false)
|
||||||
minefieldHandler.openAt(safeId)
|
minefieldHandler.openAt(safeId, false)
|
||||||
} while (solver.keepTrying() && !solver.trySolve(minefieldHandler.result().toMutableList()))
|
} while (solver.keepTrying() && !solver.trySolve(minefieldHandler.result().toMutableList()))
|
||||||
|
|
||||||
mines = field.filter { it.hasMine }.asSequence()
|
|
||||||
firstOpen = FirstOpen.Position(safeId)
|
firstOpen = FirstOpen.Position(safeId)
|
||||||
hasMines = mines.count() != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAction(target: Area, actionResponse: ActionResponse?) = flow {
|
private fun handleAction(target: Area, actionResponse: ActionResponse?) {
|
||||||
val mustPlantMines = !hasMines
|
val mustPlantMines = !hasMines()
|
||||||
|
|
||||||
val minefieldHandler: MinefieldHandler
|
val minefieldHandler: MinefieldHandler
|
||||||
|
|
||||||
if (mustPlantMines) {
|
if (mustPlantMines) {
|
||||||
plantMinesExcept(target.id)
|
plantMinesExcept(target.id)
|
||||||
minefieldHandler = MinefieldHandler(field.toMutableList(), useQuestionMark)
|
minefieldHandler = MinefieldHandler(field.toMutableList(), useQuestionMark)
|
||||||
minefieldHandler.openAt(target.id)
|
minefieldHandler.openAt(target.id, false)
|
||||||
emit(StateUpdate.Multiple)
|
|
||||||
} else {
|
} else {
|
||||||
minefieldHandler = MinefieldHandler(field.toMutableList(), useQuestionMark)
|
minefieldHandler = MinefieldHandler(field.toMutableList(), useQuestionMark)
|
||||||
minefieldHandler.turnOffAllHighlighted()
|
minefieldHandler.turnOffAllHighlighted()
|
||||||
|
@ -96,15 +90,15 @@ class GameController {
|
||||||
if (target.mark.isNotNone()) {
|
if (target.mark.isNotNone()) {
|
||||||
minefieldHandler.removeMarkAt(target.id)
|
minefieldHandler.removeMarkAt(target.id)
|
||||||
} else {
|
} else {
|
||||||
minefieldHandler.openAt(target.id)
|
minefieldHandler.openAt(target.id, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionResponse.SwitchMark -> {
|
ActionResponse.SwitchMark -> {
|
||||||
if (!hasMines) {
|
if (!hasMines()) {
|
||||||
if (target.mark.isNotNone()) {
|
if (target.mark.isNotNone()) {
|
||||||
minefieldHandler.removeMarkAt(target.id)
|
minefieldHandler.removeMarkAt(target.id)
|
||||||
} else {
|
} else {
|
||||||
minefieldHandler.openAt(target.id)
|
minefieldHandler.openAt(target.id, false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
minefieldHandler.switchMarkAt(target.id)
|
minefieldHandler.switchMarkAt(target.id)
|
||||||
|
@ -119,17 +113,17 @@ class GameController {
|
||||||
minefieldHandler.openOrFlagNeighborsOf(target.id)
|
minefieldHandler.openOrFlagNeighborsOf(target.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
field = minefieldHandler.result()
|
field = minefieldHandler.result()
|
||||||
emit(minefieldHandler.getStateUpdate())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun singleClick(index: Int) = flow {
|
fun singleClick(index: Int) = flow {
|
||||||
val target = getArea(index)
|
val target = getArea(index)
|
||||||
val action = if (target.isCovered) gameControl.onCovered.singleClick else gameControl.onOpen.singleClick
|
val action = if (target.isCovered) gameControl.onCovered.singleClick else gameControl.onOpen.singleClick
|
||||||
action?.let {
|
action?.let {
|
||||||
emit(action to handleAction(target, action))
|
handleAction(target, action)
|
||||||
|
emit(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +131,8 @@ class GameController {
|
||||||
val target = getArea(index)
|
val target = getArea(index)
|
||||||
val action = if (target.isCovered) gameControl.onCovered.doubleClick else gameControl.onOpen.doubleClick
|
val action = if (target.isCovered) gameControl.onCovered.doubleClick else gameControl.onOpen.doubleClick
|
||||||
action?.let {
|
action?.let {
|
||||||
emit(action to handleAction(target, action))
|
handleAction(target, action)
|
||||||
|
emit(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,46 +140,79 @@ class GameController {
|
||||||
val target = getArea(index)
|
val target = getArea(index)
|
||||||
val action = if (target.isCovered) gameControl.onCovered.longPress else gameControl.onOpen.longPress
|
val action = if (target.isCovered) gameControl.onCovered.longPress else gameControl.onOpen.longPress
|
||||||
action?.let {
|
action?.let {
|
||||||
emit(action to handleAction(target, action))
|
handleAction(target, action)
|
||||||
|
emit(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runFlagAssistant(): Flow<Int> {
|
fun runFlagAssistant() {
|
||||||
return FlagAssistant(field.toMutableList()).runFlagAssistant()
|
field = FlagAssistant(field.toMutableList()).run {
|
||||||
|
runFlagAssistant()
|
||||||
|
result()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getScore() = Score(
|
fun getScore() = Score(
|
||||||
mines.count { !it.mistake && it.mark.isFlag() },
|
mines().count { !it.mistake && it.mark.isFlag() },
|
||||||
mines.count(),
|
getMinesCount(),
|
||||||
field.count()
|
field.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getMinesCount() = mines.count()
|
fun getMinesCount() = mines().count()
|
||||||
|
|
||||||
fun showAllMines() =
|
fun showAllMines() {
|
||||||
mines.filter { it.mark != Mark.Flag }.forEach { it.isCovered = false }
|
field = MinefieldHandler(field.toMutableList(), false).run {
|
||||||
|
showAllMines()
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun findExplodedMine() = mines.filter { it.mistake }.firstOrNull()
|
fun findExplodedMine() = mines().filter { it.mistake }.firstOrNull()
|
||||||
|
|
||||||
fun takeExplosionRadius(target: Area): Sequence<Area> =
|
fun takeExplosionRadius(target: Area): List<Area> =
|
||||||
mines.filter { it.isCovered && it.mark.isNone() }.sortedBy {
|
mines().filter { it.isCovered && it.mark.isNone() }.sortedBy {
|
||||||
val dx1 = (it.posX - target.posX)
|
val dx1 = (it.posX - target.posX)
|
||||||
val dy1 = (it.posY - target.posY)
|
val dy1 = (it.posY - target.posY)
|
||||||
dx1 * dx1 + dy1 * dy1
|
dx1 * dx1 + dy1 * dy1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun flagAllMines() = mines.forEach { it.mark = Mark.Flag }
|
fun revealArea(id: Int) {
|
||||||
|
field = MinefieldHandler(field.toMutableList(), false).run {
|
||||||
|
openAt(id, passive = true, openNeighbors = false)
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun showWrongFlags() = field.filter { it.mark.isNotNone() && !it.hasMine }.forEach { it.mistake = true }
|
fun flagAllMines() {
|
||||||
|
field = MinefieldHandler(field.toMutableList(), false).run {
|
||||||
|
flagAllMines()
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun revealAllEmptyAreas() = field.filterNot { it.hasMine }.forEach { it.isCovered = false }
|
fun showWrongFlags() {
|
||||||
|
field = field.map {
|
||||||
|
if (it.mark.isNotNone() && !it.hasMine) {
|
||||||
|
it.copy(mistake = true)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun hasAnyMineExploded(): Boolean = mines.firstOrNull { it.mistake } != null
|
fun revealAllEmptyAreas() {
|
||||||
|
field = MinefieldHandler(field.toMutableList(), false).run {
|
||||||
|
revealAllEmptyAreas()
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasAnyMineExploded(): Boolean = mines().firstOrNull { it.mistake } != null
|
||||||
|
|
||||||
fun hasFlaggedAllMines(): Boolean = rightFlags() == minefield.mines
|
fun hasFlaggedAllMines(): Boolean = rightFlags() == minefield.mines
|
||||||
|
|
||||||
fun hasIsolatedAllMines() =
|
fun hasIsolatedAllMines() =
|
||||||
mines.map {
|
mines().map {
|
||||||
val neighbors = field.filterNeighborsOf(it)
|
val neighbors = field.filterNeighborsOf(it)
|
||||||
val neighborsCount = neighbors.count()
|
val neighborsCount = neighbors.count()
|
||||||
val isolatedNeighborsCount = neighbors.count { neighbor ->
|
val isolatedNeighborsCount = neighbors.count { neighbor ->
|
||||||
|
@ -193,17 +221,17 @@ class GameController {
|
||||||
neighborsCount != isolatedNeighborsCount
|
neighborsCount != isolatedNeighborsCount
|
||||||
}.count { it } == 0
|
}.count { it } == 0
|
||||||
|
|
||||||
private fun rightFlags() = mines.count { it.mark.isFlag() }
|
private fun rightFlags() = mines().count { it.mark.isFlag() }
|
||||||
|
|
||||||
fun checkVictory(): Boolean =
|
fun checkVictory(): Boolean =
|
||||||
hasMines && hasIsolatedAllMines() && !hasAnyMineExploded()
|
hasMines() && hasIsolatedAllMines() && !hasAnyMineExploded()
|
||||||
|
|
||||||
fun isGameOver(): Boolean =
|
fun isGameOver(): Boolean =
|
||||||
checkVictory() || hasAnyMineExploded()
|
checkVictory() || hasAnyMineExploded()
|
||||||
|
|
||||||
fun remainingMines(): Int {
|
fun remainingMines(): Int {
|
||||||
val flagsCount = field.count { it.mark.isFlag() }
|
val flagsCount = field.count { it.mark.isFlag() }
|
||||||
val minesCount = mines.count()
|
val minesCount = mines().count()
|
||||||
return (minesCount - flagsCount).coerceAtLeast(0)
|
return (minesCount - flagsCount).coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,11 +266,11 @@ class GameController {
|
||||||
Stats(
|
Stats(
|
||||||
0,
|
0,
|
||||||
duration,
|
duration,
|
||||||
mines.count(),
|
getMinesCount(),
|
||||||
if (gameStatus == SaveStatus.VICTORY) 1 else 0,
|
if (gameStatus == SaveStatus.VICTORY) 1 else 0,
|
||||||
minefield.width,
|
minefield.width,
|
||||||
minefield.height,
|
minefield.height,
|
||||||
mines.count { !it.isCovered }
|
mines().count { !it.isCovered }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,36 +2,29 @@ package dev.lucasnlm.antimine.common.level.logic
|
||||||
|
|
||||||
import dev.lucasnlm.antimine.common.level.models.Area
|
import dev.lucasnlm.antimine.common.level.models.Area
|
||||||
import dev.lucasnlm.antimine.common.level.models.Mark
|
import dev.lucasnlm.antimine.common.level.models.Mark
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import kotlinx.coroutines.flow.emitAll
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
|
|
||||||
class FlagAssistant(
|
class FlagAssistant(
|
||||||
private val field: MutableList<Area>
|
private val field: MutableList<Area>
|
||||||
) {
|
) {
|
||||||
fun runFlagAssistant() = flow {
|
fun runFlagAssistant() {
|
||||||
// Must not select Mark.PurposefulNone, only Mark.None. Otherwise, it will flag
|
// Must not select Mark.PurposefulNone, only Mark.None. Otherwise, it will flag
|
||||||
// a square that was previously unflagged by player.
|
// a square that was previously unflagged by player.
|
||||||
val flaggedIds = field
|
field
|
||||||
.filter { it.hasMine && it.mark.isPureNone() }
|
.filter { it.hasMine && it.mark.isPureNone() }
|
||||||
.mapNotNull(::putFlagIfIsolated)
|
.forEach(::putFlagIfIsolated)
|
||||||
.asFlow()
|
|
||||||
|
|
||||||
emitAll(flaggedIds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun putFlagIfIsolated(it: Area): Int? {
|
fun result(): List<Area> = field.toList()
|
||||||
|
|
||||||
|
private fun putFlagIfIsolated(it: Area) {
|
||||||
val neighbors = field.filterNeighborsOf(it)
|
val neighbors = field.filterNeighborsOf(it)
|
||||||
val neighborsCount = neighbors.count()
|
val neighborsCount = neighbors.count()
|
||||||
val revealedNeighborsCount = neighbors.count { neighbor ->
|
val revealedNeighborsCount = neighbors.count { neighbor ->
|
||||||
!neighbor.isCovered || (neighbor.hasMine && neighbor.mark.isFlag())
|
!neighbor.isCovered || (neighbor.hasMine && neighbor.mark.isFlag())
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (revealedNeighborsCount == neighborsCount) {
|
if (revealedNeighborsCount == neighborsCount) {
|
||||||
it.mark = Mark.Flag
|
field[it.id] = it.copy(mark = Mark.Flag)
|
||||||
it.id
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ class MinefieldCreator(
|
||||||
private val minefield: Minefield,
|
private val minefield: Minefield,
|
||||||
private val randomGenerator: Random
|
private val randomGenerator: Random
|
||||||
) {
|
) {
|
||||||
private fun createMutableEmpty(): MutableList<Area> {
|
private fun createMutableEmpty(): List<Area> {
|
||||||
val width = minefield.width
|
val width = minefield.width
|
||||||
val height = minefield.height
|
val height = minefield.height
|
||||||
val fieldSize = width * height
|
val fieldLength = width * height
|
||||||
|
|
||||||
return (0 until fieldSize).map { index ->
|
return (0 until fieldLength).map { index ->
|
||||||
val yPosition = floor((index / width).toDouble()).toInt()
|
val yPosition = floor((index / width).toDouble()).toInt()
|
||||||
val xPosition = (index % width)
|
val xPosition = (index % width)
|
||||||
Area(
|
Area(
|
||||||
|
@ -24,15 +24,15 @@ class MinefieldCreator(
|
||||||
0,
|
0,
|
||||||
hasMine = false
|
hasMine = false
|
||||||
)
|
)
|
||||||
}.toMutableList()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createEmpty(): List<Area> {
|
fun createEmpty(): List<Area> {
|
||||||
return createMutableEmpty().toList()
|
return createMutableEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(safeIndex: Int, safeZone: Boolean): List<Area> {
|
fun create(safeIndex: Int, safeZone: Boolean): List<Area> {
|
||||||
return createMutableEmpty().apply {
|
return createMutableEmpty().toMutableList().apply {
|
||||||
// Plant mines and setup number tips
|
// Plant mines and setup number tips
|
||||||
if (safeZone) { filterNotNeighborsOf(safeIndex) } else { filterNot { it.id == safeIndex } }
|
if (safeZone) { filterNotNeighborsOf(safeIndex) } else { filterNot { it.id == safeIndex } }
|
||||||
.shuffled(randomGenerator)
|
.shuffled(randomGenerator)
|
||||||
|
|
|
@ -2,57 +2,64 @@ package dev.lucasnlm.antimine.common.level.logic
|
||||||
|
|
||||||
import dev.lucasnlm.antimine.common.level.models.Area
|
import dev.lucasnlm.antimine.common.level.models.Area
|
||||||
import dev.lucasnlm.antimine.common.level.models.Mark
|
import dev.lucasnlm.antimine.common.level.models.Mark
|
||||||
import dev.lucasnlm.antimine.common.level.models.StateUpdate
|
|
||||||
|
|
||||||
class MinefieldHandler(
|
class MinefieldHandler(
|
||||||
private val field: MutableList<Area>,
|
private val field: MutableList<Area>,
|
||||||
private val useQuestionMark: Boolean
|
private val useQuestionMark: Boolean
|
||||||
) {
|
) {
|
||||||
private var changedIndex: Int? = null
|
fun showAllMines() {
|
||||||
private var changes = 0
|
field.filter { it.hasMine && it.mark != Mark.Flag}
|
||||||
|
.forEach { field[it.id] = it.copy(isCovered = false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flagAllMines() {
|
||||||
|
field.filter { it.hasMine }
|
||||||
|
.forEach { field[it.id] = it.copy(mark = Mark.Flag) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun revealAllEmptyAreas() {
|
||||||
|
field.filterNot { it.hasMine }
|
||||||
|
.forEach { field[it.id] = it.copy(isCovered = false) }
|
||||||
|
}
|
||||||
|
|
||||||
fun turnOffAllHighlighted() {
|
fun turnOffAllHighlighted() {
|
||||||
changes += field.filter { it.highlighted }.onEach { it.highlighted = false }.count()
|
field.filter { it.highlighted }
|
||||||
|
.forEach { field[it.id] = it.copy(highlighted = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeMarkAt(index: Int) {
|
fun removeMarkAt(index: Int) {
|
||||||
field.getOrNull(index)?.let {
|
field.getOrNull(index)?.let {
|
||||||
changes++
|
field[it.id] = it.copy(mark = Mark.PurposefulNone)
|
||||||
changedIndex = index
|
|
||||||
it.mark = Mark.PurposefulNone
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchMarkAt(index: Int) {
|
fun switchMarkAt(index: Int) {
|
||||||
field.getOrNull(index)?.run {
|
field.getOrNull(index)?.let {
|
||||||
if (isCovered) {
|
if (it.isCovered) {
|
||||||
changes++
|
field[index] = it.copy(mark = when (it.mark) {
|
||||||
changedIndex = index
|
|
||||||
mark = when (mark) {
|
|
||||||
Mark.PurposefulNone, Mark.None -> Mark.Flag
|
Mark.PurposefulNone, Mark.None -> Mark.Flag
|
||||||
Mark.Flag -> if (useQuestionMark) Mark.Question else Mark.None
|
Mark.Flag -> if (useQuestionMark) Mark.Question else Mark.None
|
||||||
Mark.Question -> Mark.None
|
Mark.Question -> Mark.None
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openAt(index: Int) {
|
fun openAt(index: Int, passive: Boolean, openNeighbors: Boolean = true) {
|
||||||
field.getOrNull(index)?.run {
|
field.getOrNull(index)?.run {
|
||||||
if (isCovered) {
|
if (isCovered) {
|
||||||
changedIndex = index
|
field[index] = copy(
|
||||||
changes++
|
isCovered = false,
|
||||||
isCovered = false
|
mark = Mark.None,
|
||||||
mark = Mark.None
|
mistake = (!passive && hasMine) || (!hasMine && mark.isFlag())
|
||||||
|
)
|
||||||
|
|
||||||
if (hasMine) {
|
if (!hasMine && minesAround == 0 && openNeighbors) {
|
||||||
mistake = true
|
|
||||||
} else if (minesAround == 0) {
|
|
||||||
changes +=
|
|
||||||
field.filterNeighborsOf(this)
|
field.filterNeighborsOf(this)
|
||||||
.filter { it.isCovered }
|
.filter { it.isCovered }
|
||||||
.onEach {
|
.onEach {
|
||||||
openAt(it.id)
|
openAt(it.id, openNeighbors = true, passive = true)
|
||||||
}.count()
|
}.count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,18 +67,13 @@ class MinefieldHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun highlightAt(index: Int) {
|
fun highlightAt(index: Int) {
|
||||||
field.getOrNull(index)?.run {
|
field.getOrNull(index)?.let {
|
||||||
when {
|
field[index] = it.copy(highlighted = it.minesAround != 0 && !it.highlighted)
|
||||||
minesAround != 0 -> {
|
}.also {
|
||||||
changes++
|
field.filterNeighborsOf(field[index])
|
||||||
changedIndex = index
|
|
||||||
highlighted = !highlighted
|
|
||||||
changes += field.filterNeighborsOf(this)
|
|
||||||
.filter { it.mark.isNone() && it.isCovered }
|
.filter { it.mark.isNone() && it.isCovered }
|
||||||
.onEach { it.highlighted = !it.highlighted }
|
.onEach { neighbor ->
|
||||||
.count()
|
field[neighbor.id] = neighbor.copy(highlighted = true)
|
||||||
}
|
|
||||||
else -> 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,16 +84,14 @@ class MinefieldHandler(
|
||||||
val neighbors = field.filterNeighborsOf(this)
|
val neighbors = field.filterNeighborsOf(this)
|
||||||
val flaggedCount = neighbors.count { it.mark.isFlag() }
|
val flaggedCount = neighbors.count { it.mark.isFlag() }
|
||||||
if (flaggedCount >= minesAround) {
|
if (flaggedCount >= minesAround) {
|
||||||
changes++
|
neighbors
|
||||||
changes += neighbors
|
|
||||||
.filter { it.isCovered && it.mark.isNone() }
|
.filter { it.isCovered && it.mark.isNone() }
|
||||||
.onEach { openAt(it.id) }
|
.onEach { openAt(it.id, passive = false, openNeighbors = true) }
|
||||||
.count()
|
.count()
|
||||||
} else {
|
} else {
|
||||||
val coveredNeighbors = neighbors.filter { it.isCovered }
|
val coveredNeighbors = neighbors.filter { it.isCovered }
|
||||||
if (coveredNeighbors.count() == minesAround) {
|
if (coveredNeighbors.count() == minesAround) {
|
||||||
changes++
|
coveredNeighbors.filter {
|
||||||
changes += coveredNeighbors.filter {
|
|
||||||
it.mark.isNone()
|
it.mark.isNone()
|
||||||
}.onEach {
|
}.onEach {
|
||||||
switchMarkAt(it.id)
|
switchMarkAt(it.id)
|
||||||
|
@ -103,18 +103,4 @@ class MinefieldHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun result(): List<Area> = field.toList()
|
fun result(): List<Area> = field.toList()
|
||||||
|
|
||||||
fun getStateUpdate(): StateUpdate {
|
|
||||||
return when (changes) {
|
|
||||||
0 -> {
|
|
||||||
StateUpdate.None
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
changedIndex?.let(StateUpdate::Single) ?: StateUpdate.Multiple
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
StateUpdate.Multiple
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ data class Area(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val posX: Int,
|
val posX: Int,
|
||||||
val posY: Int,
|
val posY: Int,
|
||||||
var minesAround: Int = 0,
|
val minesAround: Int = 0,
|
||||||
var hasMine: Boolean = false,
|
val hasMine: Boolean = false,
|
||||||
var mistake: Boolean = false,
|
val mistake: Boolean = false,
|
||||||
var isCovered: Boolean = true,
|
val isCovered: Boolean = true,
|
||||||
var mark: Mark = Mark.None,
|
val mark: Mark = Mark.None,
|
||||||
var highlighted: Boolean = false
|
val highlighted: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package dev.lucasnlm.antimine.common.level.models
|
|
||||||
|
|
||||||
sealed class StateUpdate {
|
|
||||||
class Single(val index: Int) : StateUpdate()
|
|
||||||
|
|
||||||
object Multiple : StateUpdate()
|
|
||||||
|
|
||||||
object None : StateUpdate()
|
|
||||||
}
|
|
|
@ -7,11 +7,11 @@ import dev.lucasnlm.antimine.common.R
|
||||||
import dev.lucasnlm.antimine.common.level.GameController
|
import dev.lucasnlm.antimine.common.level.GameController
|
||||||
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
||||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||||
|
import dev.lucasnlm.antimine.common.level.logic.MinefieldHandler
|
||||||
import dev.lucasnlm.antimine.common.level.models.Area
|
import dev.lucasnlm.antimine.common.level.models.Area
|
||||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
import dev.lucasnlm.antimine.common.level.models.Event
|
import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.models.StateUpdate
|
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
|
@ -30,7 +30,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.flatMapConcat
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
mineCount.postValue(minefield.mines)
|
mineCount.postValue(minefield.mines)
|
||||||
difficulty.postValue(newDifficulty)
|
difficulty.postValue(newDifficulty)
|
||||||
levelSetup.postValue(minefield)
|
levelSetup.postValue(minefield)
|
||||||
refreshAll()
|
refreshField()
|
||||||
|
|
||||||
eventObserver.postValue(Event.StartNewGame)
|
eventObserver.postValue(Event.StartNewGame)
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
mineCount.postValue(setup.mines)
|
mineCount.postValue(setup.mines)
|
||||||
difficulty.postValue(save.difficulty)
|
difficulty.postValue(save.difficulty)
|
||||||
levelSetup.postValue(setup)
|
levelSetup.postValue(setup)
|
||||||
refreshAll()
|
refreshField()
|
||||||
refreshMineCount()
|
refreshMineCount()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
|
@ -132,7 +132,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
mineCount.postValue(setup.mines)
|
mineCount.postValue(setup.mines)
|
||||||
difficulty.postValue(save.difficulty)
|
difficulty.postValue(save.difficulty)
|
||||||
levelSetup.postValue(setup)
|
levelSetup.postValue(setup)
|
||||||
refreshAll()
|
refreshField()
|
||||||
|
|
||||||
eventObserver.postValue(Event.StartNewGame)
|
eventObserver.postValue(Event.StartNewGame)
|
||||||
|
|
||||||
|
@ -172,9 +172,10 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (save.firstOpen is FirstOpen.Position) {
|
if (save.firstOpen is FirstOpen.Position) {
|
||||||
gameController.singleClick(save.firstOpen.value).flatMapConcat { it.second }.collect {
|
gameController
|
||||||
refreshAll()
|
.singleClick(save.firstOpen.value)
|
||||||
}
|
.filterNotNull()
|
||||||
|
.collect { refreshField() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
|
|
||||||
fun pauseGame() {
|
fun pauseGame() {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
if (gameController.hasMines) {
|
if (gameController.hasMines()) {
|
||||||
eventObserver.postValue(Event.Pause)
|
eventObserver.postValue(Event.Pause)
|
||||||
}
|
}
|
||||||
clock.stop()
|
clock.stop()
|
||||||
|
@ -208,7 +209,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveGame() {
|
suspend fun saveGame() {
|
||||||
if (gameController.hasMines) {
|
if (gameController.hasMines()) {
|
||||||
val id = savesRepository.saveGame(
|
val id = savesRepository.saveGame(
|
||||||
gameController.getSaveState(elapsedTimeSeconds.value ?: 0L, currentDifficulty)
|
gameController.getSaveState(elapsedTimeSeconds.value ?: 0L, currentDifficulty)
|
||||||
)
|
)
|
||||||
|
@ -218,7 +219,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveStats() {
|
private suspend fun saveStats() {
|
||||||
if (initialized && gameController.hasMines) {
|
if (initialized && gameController.hasMines()) {
|
||||||
gameController.getStats(elapsedTimeSeconds.value ?: 0L)?.let {
|
gameController.getStats(elapsedTimeSeconds.value ?: 0L)?.let {
|
||||||
statsRepository.addStats(it)
|
statsRepository.addStats(it)
|
||||||
}
|
}
|
||||||
|
@ -226,23 +227,19 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resumeGame() {
|
fun resumeGame() {
|
||||||
if (initialized && gameController.hasMines && !gameController.isGameOver()) {
|
if (initialized && gameController.hasMines() && !gameController.isGameOver()) {
|
||||||
eventObserver.postValue(Event.Resume)
|
eventObserver.postValue(Event.Resume)
|
||||||
runClock()
|
runClock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onLongClick(index: Int) {
|
suspend fun onLongClick(index: Int) {
|
||||||
gameController.longPress(index).flatMapConcat { (action, flow) ->
|
gameController
|
||||||
|
.longPress(index)
|
||||||
|
.filterNotNull()
|
||||||
|
.collect { action ->
|
||||||
onFeedbackAnalytics(action, index)
|
onFeedbackAnalytics(action, index)
|
||||||
flow
|
refreshField()
|
||||||
}.collect {
|
|
||||||
if (it is StateUpdate.Multiple) {
|
|
||||||
refreshAll()
|
|
||||||
} else if (it is StateUpdate.Single) {
|
|
||||||
refreshIndex(it.index, false)
|
|
||||||
}
|
|
||||||
}.also {
|
|
||||||
onPostAction()
|
onPostAction()
|
||||||
|
|
||||||
if (preferencesRepository.useHapticFeedback()) {
|
if (preferencesRepository.useHapticFeedback()) {
|
||||||
|
@ -252,16 +249,12 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onDoubleClick(index: Int) {
|
suspend fun onDoubleClick(index: Int) {
|
||||||
gameController.doubleClick(index).flatMapConcat { (action, flow) ->
|
gameController
|
||||||
|
.doubleClick(index)
|
||||||
|
.filterNotNull()
|
||||||
|
.collect { action ->
|
||||||
onFeedbackAnalytics(action, index)
|
onFeedbackAnalytics(action, index)
|
||||||
flow
|
refreshField()
|
||||||
}.collect {
|
|
||||||
if (it is StateUpdate.Multiple) {
|
|
||||||
refreshAll()
|
|
||||||
} else if (it is StateUpdate.Single) {
|
|
||||||
refreshIndex(it.index, false)
|
|
||||||
}
|
|
||||||
}.also {
|
|
||||||
onPostAction()
|
onPostAction()
|
||||||
|
|
||||||
if (preferencesRepository.useHapticFeedback()) {
|
if (preferencesRepository.useHapticFeedback()) {
|
||||||
|
@ -271,25 +264,20 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onSingleClick(index: Int) {
|
suspend fun onSingleClick(index: Int) {
|
||||||
gameController.singleClick(index).flatMapConcat { (action, flow) ->
|
gameController
|
||||||
|
.singleClick(index)
|
||||||
|
.filterNotNull()
|
||||||
|
.collect { action ->
|
||||||
onFeedbackAnalytics(action, index)
|
onFeedbackAnalytics(action, index)
|
||||||
flow
|
refreshField()
|
||||||
}.collect {
|
|
||||||
if (it is StateUpdate.Multiple) {
|
|
||||||
refreshAll()
|
|
||||||
} else if (it is StateUpdate.Single) {
|
|
||||||
refreshIndex(it.index, false)
|
|
||||||
}
|
|
||||||
}.also {
|
|
||||||
onPostAction()
|
onPostAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onPostAction() {
|
private fun onPostAction() {
|
||||||
if (preferencesRepository.useFlagAssistant() && !gameController.hasAnyMineExploded()) {
|
if (preferencesRepository.useFlagAssistant() && !gameController.hasAnyMineExploded()) {
|
||||||
gameController.runFlagAssistant().collect {
|
gameController.runFlagAssistant()
|
||||||
refreshIndex(it)
|
refreshField()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameState()
|
updateGameState()
|
||||||
|
@ -324,12 +312,12 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameController.hasMines) {
|
if (gameController.hasMines()) {
|
||||||
refreshMineCount()
|
refreshMineCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameController.checkVictory()) {
|
if (gameController.checkVictory()) {
|
||||||
refreshAll()
|
refreshField()
|
||||||
eventObserver.postValue(Event.Victory)
|
eventObserver.postValue(Event.Victory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,16 +366,18 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showWrongFlags()
|
||||||
|
refreshField()
|
||||||
|
|
||||||
findExplodedMine()?.let { exploded ->
|
findExplodedMine()?.let { exploded ->
|
||||||
takeExplosionRadius(exploded).forEach {
|
takeExplosionRadius(exploded).forEach {
|
||||||
it.isCovered = false
|
revealArea(it.id)
|
||||||
refreshIndex(it.id)
|
refreshField()
|
||||||
delay(delayMillis)
|
delay(delayMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showWrongFlags()
|
refreshField()
|
||||||
refreshAll()
|
|
||||||
updateGameState()
|
updateGameState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,6 +402,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
)
|
)
|
||||||
flagAllMines()
|
flagAllMines()
|
||||||
showWrongFlags()
|
showWrongFlags()
|
||||||
|
refreshField()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDifficulty == Difficulty.Standard) {
|
if (currentDifficulty == Difficulty.Standard) {
|
||||||
|
@ -428,15 +419,7 @@ class GameViewModel @ViewModelInject constructor(
|
||||||
|
|
||||||
private fun getAreaSizeMultiplier() = preferencesRepository.areaSizeMultiplier()
|
private fun getAreaSizeMultiplier() = preferencesRepository.areaSizeMultiplier()
|
||||||
|
|
||||||
private fun refreshIndex(targetIndex: Int, multipleChanges: Boolean = false) {
|
private fun refreshField() {
|
||||||
if (!preferencesRepository.useAnimations() || multipleChanges) {
|
field.postValue(gameController.field())
|
||||||
field.postValue(gameController.field)
|
|
||||||
} else {
|
|
||||||
fieldRefresh.postValue(targetIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshAll() {
|
|
||||||
field.postValue(gameController.field)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dev.lucasnlm.antimine.common.level.logic
|
package dev.lucasnlm.antimine.common.level.logic
|
||||||
|
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import kotlinx.coroutines.flow.toCollection
|
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -12,14 +11,21 @@ class FlagAssistantTest {
|
||||||
fun testRunAssistant() = runBlockingTest {
|
fun testRunAssistant() = runBlockingTest {
|
||||||
repeat(20) { takeMines ->
|
repeat(20) { takeMines ->
|
||||||
val creator = MinefieldCreator(Minefield(8, 8, 25), Random(200))
|
val creator = MinefieldCreator(Minefield(8, 8, 25), Random(200))
|
||||||
val map = creator.create(50, false)
|
val map = creator.create(50, false).toMutableList()
|
||||||
|
|
||||||
map.filter { it.hasMine }
|
map.filter { it.hasMine }
|
||||||
.take(takeMines)
|
.take(takeMines)
|
||||||
.forEach { map.filterNeighborsOf(it).forEach { neighbor -> neighbor.isCovered = false } }
|
.forEach {
|
||||||
|
map.filterNeighborsOf(it)
|
||||||
|
.forEach { neighbor ->
|
||||||
|
map[neighbor.id] = neighbor.copy(isCovered = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val actual = mutableListOf<Int>()
|
val actual = FlagAssistant(map.toMutableList()).run {
|
||||||
FlagAssistant(map.toMutableList()).runFlagAssistant().toCollection(actual)
|
runFlagAssistant()
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
|
||||||
val expected = map
|
val expected = map
|
||||||
.filter { it.hasMine }
|
.filter { it.hasMine }
|
||||||
|
@ -38,14 +44,21 @@ class FlagAssistantTest {
|
||||||
repeat(20) { takeMines ->
|
repeat(20) { takeMines ->
|
||||||
val seed = 10 * takeMines
|
val seed = 10 * takeMines
|
||||||
val creator = MinefieldCreator(Minefield(8, 8, 25), Random(seed))
|
val creator = MinefieldCreator(Minefield(8, 8, 25), Random(seed))
|
||||||
val map = creator.create(50, false)
|
val map = creator.create(50, false).toMutableList()
|
||||||
|
|
||||||
map.filter { it.hasMine }
|
map.filter { it.hasMine }
|
||||||
.take(takeMines)
|
.take(takeMines)
|
||||||
.forEach { map.filterNeighborsOf(it).forEach { neighbor -> neighbor.isCovered = false } }
|
.forEach {
|
||||||
|
map.filterNeighborsOf(it)
|
||||||
|
.forEach { neighbor ->
|
||||||
|
map[neighbor.id] = neighbor.copy(isCovered = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val actual = mutableListOf<Int>()
|
val actual = FlagAssistant(map.toMutableList()).run {
|
||||||
FlagAssistant(map.toMutableList()).runFlagAssistant().toCollection(actual)
|
runFlagAssistant()
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
|
||||||
val expected = map
|
val expected = map
|
||||||
.filter { it.hasMine }
|
.filter { it.hasMine }
|
||||||
|
|
|
@ -2,17 +2,17 @@ package dev.lucasnlm.antimine.common.level.logic
|
||||||
|
|
||||||
import dev.lucasnlm.antimine.common.level.GameController
|
import dev.lucasnlm.antimine.common.level.GameController
|
||||||
import dev.lucasnlm.antimine.common.level.models.Area
|
import dev.lucasnlm.antimine.common.level.models.Area
|
||||||
import dev.lucasnlm.antimine.common.level.models.Mark
|
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.models.Score
|
import dev.lucasnlm.antimine.common.level.models.Score
|
||||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||||
import dev.lucasnlm.antimine.core.control.GameControl
|
import dev.lucasnlm.antimine.core.control.GameControl
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -42,9 +42,17 @@ class GameControllerTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertEquals(Score(0, 20, 100), controller.getScore())
|
assertEquals(Score(0, 20, 100), controller.getScore())
|
||||||
|
|
||||||
repeat(20) { right ->
|
repeat(20) { markedMines ->
|
||||||
controller.field.filter { it.hasMine }.take(right).forEach { it.mark = Mark.Flag }
|
controller
|
||||||
assertEquals(Score(right, 20, 100), controller.getScore())
|
.mines()
|
||||||
|
.take(markedMines)
|
||||||
|
.filter { it.mark.isNone() }
|
||||||
|
.forEach {
|
||||||
|
// Put a flag.
|
||||||
|
controller.fakeLongPress(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(Score(markedMines, 20, 100), controller.getScore())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +61,17 @@ class GameControllerTest {
|
||||||
fun testGetScoreWithQuestion() = runBlockingTest {
|
fun testGetScoreWithQuestion() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertEquals(Score(0, 20, 100), controller.getScore())
|
assertEquals(Score(0, 20, 100), controller.getScore())
|
||||||
|
controller.useQuestionMark(true)
|
||||||
|
|
||||||
|
controller
|
||||||
|
.mines()
|
||||||
|
.take(5)
|
||||||
|
.forEach {
|
||||||
|
// Put Question Mark
|
||||||
|
controller.fakeLongPress(it.id)
|
||||||
|
controller.fakeLongPress(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
controller.field.filter { it.hasMine }.take(5).forEach { it.mark = Mark.Question }
|
|
||||||
assertEquals(Score(0, 20, 100), controller.getScore())
|
assertEquals(Score(0, 20, 100), controller.getScore())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,9 +79,12 @@ class GameControllerTest {
|
||||||
@Test
|
@Test
|
||||||
fun testFlagAllMines() = runBlockingTest {
|
fun testFlagAllMines() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
|
val minesCount = controller.mines().count()
|
||||||
|
|
||||||
controller.flagAllMines()
|
controller.flagAllMines()
|
||||||
val actual = controller.field.filter { it.hasMine }.count { it.isCovered && it.mark.isFlag() }
|
val actualFlaggedMines = controller.mines().count { it.isCovered && it.mark.isFlag() }
|
||||||
assertEquals(20, actual)
|
|
||||||
|
assertEquals(minesCount, actualFlaggedMines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,30 +92,29 @@ class GameControllerTest {
|
||||||
fun testFindExplodedMine() = runBlockingTest {
|
fun testFindExplodedMine() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertNull(controller.findExplodedMine())
|
assertNull(controller.findExplodedMine())
|
||||||
val target = controller.field.first { it.hasMine }
|
val target = controller.mines().first()
|
||||||
launch {
|
controller.fakeSingleClick(target.id)
|
||||||
controller.singleClick(target.id).collect { it.second.collect() }
|
assertNotNull(controller.findExplodedMine())
|
||||||
}
|
assertEquals(target.id, controller.findExplodedMine()!!.id)
|
||||||
assertEquals(target.id, controller.findExplodedMine()?.id ?: - 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTakeExplosionRadius() = runBlockingTest {
|
fun testTakeExplosionRadius() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
val lastMine = controller.field.last { it.hasMine }
|
val lastMine = controller.mines().last()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(95, 85, 74, 73, 65, 88, 55, 91, 45, 52, 90, 47, 59, 42, 36, 32, 39, 28, 4, 3),
|
listOf(95, 85, 74, 73, 65, 88, 55, 91, 45, 52, 90, 47, 59, 42, 36, 32, 39, 28, 4, 3),
|
||||||
controller.takeExplosionRadius(lastMine).map { it.id }.toList()
|
controller.takeExplosionRadius(lastMine).map { it.id }.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
val firstMine = controller.field.first { it.hasMine }
|
val firstMine = controller.mines().first()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(3, 4, 32, 42, 36, 45, 52, 28, 55, 47, 65, 39, 73, 74, 59, 85, 91, 95, 88, 90),
|
listOf(3, 4, 32, 42, 36, 45, 52, 28, 55, 47, 65, 39, 73, 74, 59, 85, 91, 95, 88, 90),
|
||||||
controller.takeExplosionRadius(firstMine).map { it.id }.toList()
|
controller.takeExplosionRadius(firstMine).map { it.id }.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
val midMine = controller.field.filter { it.hasMine }.take(controller.getMinesCount() / 2).last()
|
val midMine = controller.mines().take(controller.getMinesCount() / 2).last()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(52, 42, 32, 73, 74, 55, 45, 65, 91, 85, 36, 90, 95, 3, 47, 4, 28, 88, 59, 39),
|
listOf(52, 42, 32, 73, 74, 55, 45, 65, 91, 85, 36, 90, 95, 3, 47, 4, 28, 88, 59, 39),
|
||||||
controller.takeExplosionRadius(midMine).map { it.id }.toList()
|
controller.takeExplosionRadius(midMine).map { it.id }.toList()
|
||||||
|
@ -107,10 +126,10 @@ class GameControllerTest {
|
||||||
fun testShowAllMines() = runBlockingTest {
|
fun testShowAllMines() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
controller.showAllMines()
|
controller.showAllMines()
|
||||||
controller.field.filter { it.hasMine && it.mistake }.forEach {
|
controller.mines().filter { it.mistake }.forEach {
|
||||||
assertEquals(it.isCovered, false)
|
assertEquals(it.isCovered, false)
|
||||||
}
|
}
|
||||||
controller.field.filter { it.hasMine && it.mark.isFlag() }.forEach {
|
controller.mines().filter { it.mark.isFlag() }.forEach {
|
||||||
assertEquals(it.isCovered, true)
|
assertEquals(it.isCovered, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,25 +138,25 @@ class GameControllerTest {
|
||||||
@Test
|
@Test
|
||||||
fun testShowWrongFlags() = runBlockingTest {
|
fun testShowWrongFlags() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
val wrongFlag = controller.field.first { !it.hasMine }.apply {
|
controller.field().first { !it.hasMine }
|
||||||
mark = Mark.Flag
|
.also { controller.fakeLongPress(it.id) }
|
||||||
}
|
|
||||||
val rightFlag = controller.field.first { it.hasMine }.apply {
|
val wrongFlag = controller.field().first { !it.hasMine }
|
||||||
mark = Mark.Flag
|
|
||||||
}
|
//val rightFlag = controller.mines().first().apply { controller.fakeLongPress(id) }
|
||||||
controller.showWrongFlags()
|
controller.showWrongFlags()
|
||||||
assertTrue(wrongFlag.mistake)
|
assertTrue(wrongFlag.mistake)
|
||||||
assertFalse(rightFlag.mistake)
|
//assertFalse(rightFlag.mistake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRevealAllEmptyAreas() = runBlockingTest {
|
fun testRevealAllEmptyAreas() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
val covered = controller.field.filter { it.isCovered }
|
val covered = controller.field().filter { it.isCovered }
|
||||||
assertTrue(covered.isNotEmpty())
|
assertTrue(covered.isNotEmpty())
|
||||||
controller.revealAllEmptyAreas()
|
controller.revealAllEmptyAreas()
|
||||||
assertEquals(controller.field.filter { it.hasMine }, controller.field.filter { it.isCovered })
|
assertEquals(controller.field().filter { it.hasMine }, controller.field().filter { it.isCovered })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,9 +164,19 @@ class GameControllerTest {
|
||||||
fun testFlaggedAllMines() = runBlockingTest {
|
fun testFlaggedAllMines() = runBlockingTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertFalse(controller.hasFlaggedAllMines())
|
assertFalse(controller.hasFlaggedAllMines())
|
||||||
controller.field.filter { it.hasMine }.take(10).forEach { it.mark = Mark.Flag }
|
|
||||||
|
controller.field()
|
||||||
|
.filter { it.hasMine }
|
||||||
|
.take(10)
|
||||||
|
.forEach { controller.fakeLongPress(it.id) }
|
||||||
|
|
||||||
assertFalse(controller.hasFlaggedAllMines())
|
assertFalse(controller.hasFlaggedAllMines())
|
||||||
controller.field.filter { it.hasMine }.forEach { it.mark = Mark.Flag }
|
|
||||||
|
controller.field()
|
||||||
|
.filter { it.hasMine }
|
||||||
|
.filter { it.mark.isNone() }
|
||||||
|
.forEach { controller.fakeLongPress(it.id) }
|
||||||
|
|
||||||
assertTrue(controller.hasFlaggedAllMines())
|
assertTrue(controller.hasFlaggedAllMines())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,7 +187,7 @@ class GameControllerTest {
|
||||||
assertEquals(20, controller.remainingMines())
|
assertEquals(20, controller.remainingMines())
|
||||||
|
|
||||||
repeat(20) { flagCount ->
|
repeat(20) { flagCount ->
|
||||||
controller.field.filter { it.hasMine }.take(flagCount).forEach { it.mark = Mark.Flag }
|
controller.field().filter { it.hasMine }.take(flagCount).forEach { controller.fakeLongPress(it.id) }
|
||||||
assertEquals("flagging $flagCount mines", 20 - flagCount, controller.remainingMines())
|
assertEquals("flagging $flagCount mines", 20 - flagCount, controller.remainingMines())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,9 +199,9 @@ class GameControllerTest {
|
||||||
assertFalse(controller.hasIsolatedAllMines())
|
assertFalse(controller.hasIsolatedAllMines())
|
||||||
assertFalse(controller.isGameOver())
|
assertFalse(controller.isGameOver())
|
||||||
|
|
||||||
controller.field.filter { !it.hasMine }.forEach {
|
controller.field()
|
||||||
it.isCovered = false
|
.filter { !it.hasMine }
|
||||||
}
|
.forEach { controller.fakeSingleClick(it.id) }
|
||||||
|
|
||||||
assertTrue(controller.hasIsolatedAllMines())
|
assertTrue(controller.hasIsolatedAllMines())
|
||||||
assertTrue(controller.isGameOver())
|
assertTrue(controller.isGameOver())
|
||||||
|
@ -184,10 +213,7 @@ class GameControllerTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertFalse(controller.hasAnyMineExploded())
|
assertFalse(controller.hasAnyMineExploded())
|
||||||
|
|
||||||
controller.field.first { it.hasMine }.also {
|
controller.field().first { it.hasMine }.also { controller.fakeSingleClick(it.id) }
|
||||||
it.isCovered = false
|
|
||||||
it.mistake = true
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(controller.hasAnyMineExploded())
|
assertTrue(controller.hasAnyMineExploded())
|
||||||
}
|
}
|
||||||
|
@ -198,10 +224,7 @@ class GameControllerTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertFalse(controller.isGameOver())
|
assertFalse(controller.isGameOver())
|
||||||
|
|
||||||
controller.field.first { it.hasMine }.also {
|
controller.field().first { it.hasMine }.also { controller.fakeSingleClick(it.id) }
|
||||||
it.isCovered = false
|
|
||||||
it.mistake = true
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(controller.isGameOver())
|
assertTrue(controller.isGameOver())
|
||||||
}
|
}
|
||||||
|
@ -212,13 +235,15 @@ class GameControllerTest {
|
||||||
withGameController { controller ->
|
withGameController { controller ->
|
||||||
assertFalse(controller.checkVictory())
|
assertFalse(controller.checkVictory())
|
||||||
|
|
||||||
controller.field.filter { it.hasMine }.forEach { it.mark = Mark.Flag }
|
controller.field()
|
||||||
|
.filter { it.hasMine }
|
||||||
|
.forEach { controller.fakeLongPress(it.id) }
|
||||||
assertFalse(controller.checkVictory())
|
assertFalse(controller.checkVictory())
|
||||||
|
|
||||||
controller.field.filterNot { it.hasMine }.forEach { it.isCovered = false }
|
controller.field().filterNot { it.hasMine }.forEach { controller.fakeSingleClick(it.id) }
|
||||||
assertTrue(controller.checkVictory())
|
assertTrue(controller.checkVictory())
|
||||||
|
|
||||||
controller.field.first { it.hasMine }.mistake = true
|
controller.field().first { it.hasMine }.also { controller.fakeSingleClick(it.id) }
|
||||||
assertFalse(controller.checkVictory())
|
assertFalse(controller.checkVictory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,17 +300,17 @@ class GameControllerTest {
|
||||||
updateGameControl(GameControl.fromControlType(ControlStyle.Standard))
|
updateGameControl(GameControl.fromControlType(ControlStyle.Standard))
|
||||||
fakeSingleClick(14)
|
fakeSingleClick(14)
|
||||||
assertFalse(at(14).isCovered)
|
assertFalse(at(14).isCovered)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
field.filter { it.hasMine }.forEach {
|
field().filter { it.hasMine }.forEach {
|
||||||
fakeLongPress(it.id)
|
fakeLongPress(it.id)
|
||||||
assertTrue(it.mark.isFlag())
|
assertTrue(it.mark.isFlag())
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeLongPress(14)
|
fakeLongPress(14)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
if (it.hasMine) {
|
if (it.hasMine) {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
} else {
|
} else {
|
||||||
|
@ -348,17 +373,17 @@ class GameControllerTest {
|
||||||
updateGameControl(GameControl.fromControlType(ControlStyle.FastFlag))
|
updateGameControl(GameControl.fromControlType(ControlStyle.FastFlag))
|
||||||
fakeLongPress(14)
|
fakeLongPress(14)
|
||||||
assertFalse(at(14).isCovered)
|
assertFalse(at(14).isCovered)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
field.filter { it.hasMine }.forEach {
|
field().filter { it.hasMine }.forEach {
|
||||||
fakeSingleClick(it.id)
|
fakeSingleClick(it.id)
|
||||||
assertTrue(it.mark.isFlag())
|
assertTrue(it.mark.isFlag())
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeSingleClick(14)
|
fakeSingleClick(14)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
if (it.hasMine) {
|
if (it.hasMine) {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
} else {
|
} else {
|
||||||
|
@ -427,17 +452,16 @@ class GameControllerTest {
|
||||||
updateGameControl(GameControl.fromControlType(ControlStyle.DoubleClick))
|
updateGameControl(GameControl.fromControlType(ControlStyle.DoubleClick))
|
||||||
fakeDoubleClick(14)
|
fakeDoubleClick(14)
|
||||||
assertFalse(at(14).isCovered)
|
assertFalse(at(14).isCovered)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
field.filter { it.hasMine }.forEach {
|
field().filter { it.hasMine }
|
||||||
fakeSingleClick(it.id)
|
.onEach { fakeSingleClick(it.id) }
|
||||||
assertTrue(it.mark.isFlag())
|
.onEach { assertTrue(it.mark.isFlag()) }
|
||||||
}
|
|
||||||
|
|
||||||
fakeDoubleClick(14)
|
fakeDoubleClick(14)
|
||||||
field.filterNeighborsOf(at(14)).forEach {
|
field().filterNeighborsOf(at(14)).forEach {
|
||||||
if (it.hasMine) {
|
if (it.hasMine) {
|
||||||
assertTrue(it.isCovered)
|
assertTrue(it.isCovered)
|
||||||
} else {
|
} else {
|
||||||
|
@ -453,11 +477,11 @@ class GameControllerTest {
|
||||||
withGameController(clickOnCreate = false) { controller ->
|
withGameController(clickOnCreate = false) { controller ->
|
||||||
controller.run {
|
controller.run {
|
||||||
updateGameControl(GameControl.fromControlType(ControlStyle.DoubleClick))
|
updateGameControl(GameControl.fromControlType(ControlStyle.DoubleClick))
|
||||||
assertFalse(hasMines)
|
assertFalse(hasMines())
|
||||||
assertEquals(0, field.filterNot { it.isCovered }.count())
|
assertEquals(0, field().filterNot { it.isCovered }.count())
|
||||||
fakeSingleClick(40)
|
fakeSingleClick(40)
|
||||||
assertTrue(hasMines)
|
assertTrue(hasMines())
|
||||||
field.filterNeighborsOf(at(40)).forEach { assertFalse(it.isCovered) }
|
field().filterNeighborsOf(at(40)).forEach { assertFalse(it.isCovered) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,25 +491,23 @@ class GameControllerTest {
|
||||||
withGameController(clickOnCreate = false) { controller ->
|
withGameController(clickOnCreate = false) { controller ->
|
||||||
controller.run {
|
controller.run {
|
||||||
updateGameControl(GameControl.fromControlType(ControlStyle.FastFlag))
|
updateGameControl(GameControl.fromControlType(ControlStyle.FastFlag))
|
||||||
assertFalse(hasMines)
|
assertFalse(hasMines())
|
||||||
assertEquals(0, field.filterNot { it.isCovered }.count())
|
assertEquals(0, field().filterNot { it.isCovered }.count())
|
||||||
fakeSingleClick(40)
|
fakeSingleClick(40)
|
||||||
assertTrue(hasMines)
|
assertTrue(hasMines())
|
||||||
field.filterNeighborsOf(at(40)).forEach { assertFalse(it.isCovered) }
|
field().filterNeighborsOf(at(40)).forEach { assertFalse(it.isCovered) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun GameController.at(index: Int): Area {
|
private fun GameController.at(index: Int): Area {
|
||||||
return this.field.first { it.id == index }
|
return this.field().first { it.id == index }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun GameController.fakeSingleClick(index: Int) {
|
private fun GameController.fakeSingleClick(index: Int) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
launch {
|
launch {
|
||||||
singleClick(index).collect {
|
singleClick(index).single()
|
||||||
it.second.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -493,7 +515,7 @@ class GameControllerTest {
|
||||||
private fun GameController.fakeLongPress(index: Int) {
|
private fun GameController.fakeLongPress(index: Int) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
launch {
|
launch {
|
||||||
longPress(index).collect { it.second.collect() }
|
longPress(index).single()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,7 +523,7 @@ class GameControllerTest {
|
||||||
private fun GameController.fakeDoubleClick(index: Int) {
|
private fun GameController.fakeDoubleClick(index: Int) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
launch {
|
launch {
|
||||||
doubleClick(index).collect { it.second.collect() }
|
doubleClick(index).single()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class MinefieldHandlerTest {
|
||||||
fun testOpenArea() {
|
fun testOpenArea() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
assertTrue(minefield[3].isCovered)
|
assertTrue(minefield[3].isCovered)
|
||||||
handler.openAt(3)
|
handler.openAt(3, false, openNeighbors = false)
|
||||||
assertFalse(minefield[3].isCovered)
|
assertFalse(minefield[3].isCovered)
|
||||||
assertEquals(Mark.None, minefield[3].mark)
|
assertEquals(Mark.None, minefield[3].mark)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class MinefieldHandlerTest {
|
||||||
fun testOpenAreaWithSafeZone() {
|
fun testOpenAreaWithSafeZone() {
|
||||||
handleMinefield(useSafeZone = true) { handler, minefield ->
|
handleMinefield(useSafeZone = true) { handler, minefield ->
|
||||||
assertTrue(minefield[3].isCovered)
|
assertTrue(minefield[3].isCovered)
|
||||||
handler.openAt(3)
|
handler.openAt(3, false, openNeighbors = false)
|
||||||
assertFalse(minefield[3].isCovered)
|
assertFalse(minefield[3].isCovered)
|
||||||
assertEquals(Mark.None, minefield[3].mark)
|
assertEquals(Mark.None, minefield[3].mark)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ class MinefieldHandlerTest {
|
||||||
assertEquals(0, minefield.count { it.highlighted })
|
assertEquals(0, minefield.count { it.highlighted })
|
||||||
|
|
||||||
// After Open
|
// After Open
|
||||||
handler.openAt(5)
|
handler.openAt(5, false, openNeighbors = false)
|
||||||
val target = minefield.first { it.minesAround != 0 }
|
val target = minefield.first { it.minesAround != 0 }
|
||||||
handler.highlightAt(target.id)
|
handler.highlightAt(target.id)
|
||||||
assertEquals(5, minefield.count { it.highlighted })
|
assertEquals(5, minefield.count { it.highlighted })
|
||||||
|
@ -113,7 +113,7 @@ class MinefieldHandlerTest {
|
||||||
@Test
|
@Test
|
||||||
fun testOpenNeighbors() {
|
fun testOpenNeighbors() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(5)
|
handler.openAt(5, false, openNeighbors = true)
|
||||||
handler.openOrFlagNeighborsOf(5)
|
handler.openOrFlagNeighborsOf(5)
|
||||||
assertEquals(9, minefield.count { !it.isCovered })
|
assertEquals(9, minefield.count { !it.isCovered })
|
||||||
}
|
}
|
||||||
|
@ -122,9 +122,9 @@ class MinefieldHandlerTest {
|
||||||
@Test
|
@Test
|
||||||
fun testOpenNeighborsWithFlags() {
|
fun testOpenNeighborsWithFlags() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(5)
|
handler.openAt(5, false, openNeighbors = true)
|
||||||
val neighbors = minefield.filterNeighborsOf(minefield.first { it.id == 5 })
|
val neighbors = minefield.filterNeighborsOf(minefield.first { it.id == 5 })
|
||||||
neighbors.filter { it.hasMine }.forEach { it.mark = Mark.Flag }
|
neighbors.filter { it.hasMine }.forEach { handler.switchMarkAt(it.id) }
|
||||||
handler.openOrFlagNeighborsOf(5)
|
handler.openOrFlagNeighborsOf(5)
|
||||||
assertEquals(4, minefield.count { !it.isCovered })
|
assertEquals(4, minefield.count { !it.isCovered })
|
||||||
assertEquals(3, neighbors.count { !it.isCovered })
|
assertEquals(3, neighbors.count { !it.isCovered })
|
||||||
|
@ -133,10 +133,15 @@ class MinefieldHandlerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOpenNeighborsWithQuestionMarks() {
|
fun testOpenNeighborsWithQuestionMarks() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield(useQuestionMark = true) { handler, minefield ->
|
||||||
handler.openAt(5)
|
handler.openAt(5, false, openNeighbors = true)
|
||||||
val neighbors = minefield.filterNeighborsOf(minefield.first { it.id == 5 })
|
val neighbors = minefield.filterNeighborsOf(minefield.first { it.id == 5 })
|
||||||
neighbors.filter { it.hasMine }.forEach { it.mark = Mark.Question }
|
neighbors
|
||||||
|
.filter { it.hasMine }
|
||||||
|
.forEach {
|
||||||
|
handler.switchMarkAt(it.id)
|
||||||
|
handler.switchMarkAt(it.id)
|
||||||
|
}
|
||||||
handler.openOrFlagNeighborsOf(5)
|
handler.openOrFlagNeighborsOf(5)
|
||||||
assertEquals(4, minefield.count { !it.isCovered })
|
assertEquals(4, minefield.count { !it.isCovered })
|
||||||
assertEquals(3, neighbors.count { !it.isCovered })
|
assertEquals(3, neighbors.count { !it.isCovered })
|
||||||
|
|
|
@ -25,13 +25,13 @@ class BruteForceSolverTest {
|
||||||
@Test
|
@Test
|
||||||
fun isSolvable() {
|
fun isSolvable() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(40)
|
handler.openAt(40, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = BruteForceSolver()
|
val bruteForceSolver = BruteForceSolver()
|
||||||
assertTrue(bruteForceSolver.trySolve(minefield.toMutableList()))
|
assertTrue(bruteForceSolver.trySolve(minefield.toMutableList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(0)
|
handler.openAt(0, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = BruteForceSolver()
|
val bruteForceSolver = BruteForceSolver()
|
||||||
assertFalse(bruteForceSolver.trySolve(minefield.toMutableList()))
|
assertFalse(bruteForceSolver.trySolve(minefield.toMutableList()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,28 +25,28 @@ class LimitedBruteForceSolverTest {
|
||||||
@Test
|
@Test
|
||||||
fun isSolvable() {
|
fun isSolvable() {
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(40)
|
handler.openAt(40, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = LimitedBruteForceSolver()
|
val bruteForceSolver = LimitedBruteForceSolver()
|
||||||
assertTrue(bruteForceSolver.trySolve(minefield.toMutableList()))
|
assertTrue(bruteForceSolver.trySolve(minefield.toMutableList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMinefield { handler, minefield ->
|
handleMinefield { handler, minefield ->
|
||||||
handler.openAt(0)
|
handler.openAt(0, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = LimitedBruteForceSolver()
|
val bruteForceSolver = LimitedBruteForceSolver()
|
||||||
assertFalse(bruteForceSolver.trySolve(minefield.toMutableList()))
|
assertFalse(bruteForceSolver.trySolve(minefield.toMutableList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldntKeepTryingAfterTimout() {
|
fun shouldntKeepTryingAfterTimeout() {
|
||||||
handleMinefield { handler, _ ->
|
handleMinefield { handler, _ ->
|
||||||
handler.openAt(40)
|
handler.openAt(40, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = LimitedBruteForceSolver(1000L)
|
val bruteForceSolver = LimitedBruteForceSolver(1000L)
|
||||||
assertTrue(bruteForceSolver.keepTrying())
|
assertTrue(bruteForceSolver.keepTrying())
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMinefield { handler, _ ->
|
handleMinefield { handler, _ ->
|
||||||
handler.openAt(0)
|
handler.openAt(0, passive = false, openNeighbors = false)
|
||||||
val bruteForceSolver = LimitedBruteForceSolver(50)
|
val bruteForceSolver = LimitedBruteForceSolver(50)
|
||||||
sleep(100)
|
sleep(100)
|
||||||
assertFalse(bruteForceSolver.keepTrying())
|
assertFalse(bruteForceSolver.keepTrying())
|
||||||
|
|
Loading…
Reference in a new issue