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