Make mines explode individually
This commit is contained in:
parent
ca33bc7caf
commit
0e226eb620
5 changed files with 75 additions and 30 deletions
|
@ -43,6 +43,7 @@ import dev.lucasnlm.antimine.level.view.LevelFragment
|
|||
import dev.lucasnlm.antimine.preferences.PreferencesActivity
|
||||
import dev.lucasnlm.antimine.share.viewmodel.ShareViewModel
|
||||
import kotlinx.android.synthetic.main.activity_game.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
@ -99,8 +100,13 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun bindViewModel() = viewModel.apply {
|
||||
var lastEvent: Event? = null // TODO use distinctUntilChanged
|
||||
|
||||
eventObserver.observe(this@GameActivity, Observer {
|
||||
onGameEvent(it)
|
||||
if (lastEvent != it) {
|
||||
onGameEvent(it)
|
||||
lastEvent = it
|
||||
}
|
||||
})
|
||||
|
||||
elapsedTimeSeconds.observe(this@GameActivity, Observer {
|
||||
|
@ -459,9 +465,11 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
status = Status.Over(currentTime, score)
|
||||
invalidateOptionsMenu()
|
||||
viewModel.stopClock()
|
||||
viewModel.gameOver()
|
||||
|
||||
waitAndShowEndGameDialog(false)
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
viewModel.gameOver()
|
||||
waitAndShowEndGameDialog(false)
|
||||
}
|
||||
}
|
||||
Event.ResumeVictory -> {
|
||||
val score = Score(
|
||||
|
|
|
@ -32,6 +32,7 @@ import dev.lucasnlm.antimine.level.view.CustomLevelDialogFragment
|
|||
import dev.lucasnlm.antimine.level.view.LevelFragment
|
||||
import dev.lucasnlm.antimine.preferences.PreferencesActivity
|
||||
import kotlinx.android.synthetic.main.activity_tv_game.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
@ -302,9 +303,11 @@ class TvGameActivity : DaggerAppCompatActivity() {
|
|||
status = Status.Over(currentTime, score)
|
||||
invalidateOptionsMenu()
|
||||
viewModel.stopClock()
|
||||
viewModel.gameOver()
|
||||
|
||||
waitAndShowGameOverConfirmNewGame()
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
viewModel.gameOver()
|
||||
waitAndShowGameOverConfirmNewGame()
|
||||
}
|
||||
}
|
||||
Event.ResumeVictory, Event.ResumeGameOver -> {
|
||||
val score = Score(
|
||||
|
|
|
@ -82,6 +82,8 @@ class LevelFacade {
|
|||
|
||||
fun hasMarkOn(index: Int): Boolean = getArea(index).mark.isNotNone()
|
||||
|
||||
fun isEmpty(index: Int): Boolean = getArea(index).let { it.minesAround == 0 && !it.hasMine && !it.isCovered }
|
||||
|
||||
fun plantMinesExcept(index: Int, includeSafeArea: Boolean = false) {
|
||||
plantRandomMines(index, includeSafeArea)
|
||||
putMinesTips()
|
||||
|
@ -187,9 +189,11 @@ class LevelFacade {
|
|||
it.id
|
||||
}
|
||||
|
||||
fun runFlagAssistant() {
|
||||
fun runFlagAssistant(): Sequence<Int> {
|
||||
// Must not select Mark.PurposefulNone, only Mark.None. Otherwise, it will flag
|
||||
// a square that was previously unflagged by player.
|
||||
val assists = mutableListOf<Int>()
|
||||
|
||||
mines.filter { it.mark.isPureNone() }.forEach { field ->
|
||||
val neighbors = field.findNeighbors()
|
||||
val neighborsCount = neighbors.count()
|
||||
|
@ -197,8 +201,15 @@ class LevelFacade {
|
|||
!neighbor.isCovered || (neighbor.hasMine && neighbor.mark.isFlag())
|
||||
}.count()
|
||||
|
||||
field.mark = if (revealedNeighborsCount == neighborsCount) Mark.Flag else Mark.None
|
||||
if (revealedNeighborsCount == neighborsCount) {
|
||||
assists.add(field.id)
|
||||
field.mark = Mark.Flag
|
||||
} else {
|
||||
field.mark = Mark.None
|
||||
}
|
||||
}
|
||||
|
||||
return assists.asSequence()
|
||||
}
|
||||
|
||||
fun getStats() = Score(
|
||||
|
@ -207,21 +218,25 @@ class LevelFacade {
|
|||
field.count()
|
||||
)
|
||||
|
||||
fun showAllMines() {
|
||||
fun showAllMines() =
|
||||
mines.filter { it.mark != Mark.Flag }.forEach { it.isCovered = false }
|
||||
}
|
||||
|
||||
fun flagAllMines() {
|
||||
mines.forEach { it.mark = Mark.Flag }
|
||||
}
|
||||
fun findExplodedMine() = mines.filter { it.mistake }.firstOrNull()
|
||||
|
||||
fun showWrongFlags() {
|
||||
fun takeExplosionRadius(target: Area, take: Int = 20): Sequence<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
|
||||
}.take(take)
|
||||
|
||||
fun flagAllMines() = mines.forEach { it.mark = Mark.Flag }
|
||||
|
||||
fun showWrongFlags() =
|
||||
field.filter { it.mark.isNotNone() && !it.hasMine }.forEach { it.mistake = true }
|
||||
}
|
||||
|
||||
fun revealAllEmptyAreas() {
|
||||
fun revealAllEmptyAreas() =
|
||||
field.filter { !it.hasMine }.forEach { it.isCovered = false }
|
||||
}
|
||||
|
||||
fun hasAnyMineExploded(): Boolean = mines.firstOrNull { it.mistake } != null
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.lucasnlm.antimine.common.level.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||
|
@ -19,6 +20,7 @@ import dev.lucasnlm.antimine.core.analytics.models.Analytics
|
|||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
@ -58,7 +60,7 @@ class GameViewModel(
|
|||
mineCount.postValue(minefield.mines)
|
||||
this.difficulty.postValue(difficulty)
|
||||
levelSetup.postValue(minefield)
|
||||
field.postValue(levelFacade.field.toList())
|
||||
refreshField()
|
||||
|
||||
eventObserver.postValue(Event.StartNewGame)
|
||||
|
||||
|
@ -83,7 +85,7 @@ class GameViewModel(
|
|||
mineCount.postValue(setup.mines)
|
||||
difficulty.postValue(save.difficulty)
|
||||
levelSetup.postValue(setup)
|
||||
field.postValue(levelFacade.field.toList())
|
||||
refreshField()
|
||||
|
||||
when {
|
||||
levelFacade.hasAnyMineExploded() -> eventObserver.postValue(Event.ResumeGameOver)
|
||||
|
@ -155,8 +157,7 @@ class GameViewModel(
|
|||
analyticsManager.sentEvent(Analytics.LongPressMultipleArea(index))
|
||||
}
|
||||
|
||||
field.postValue(levelFacade.field.toList())
|
||||
|
||||
refreshField()
|
||||
refreshGame()
|
||||
}
|
||||
|
||||
|
@ -173,12 +174,15 @@ class GameViewModel(
|
|||
}
|
||||
|
||||
levelFacade.clickArea(index)
|
||||
|
||||
field.postValue(levelFacade.field.toList())
|
||||
refreshField(index)
|
||||
}
|
||||
|
||||
if (preferencesRepository.useFlagAssistant() && !levelFacade.hasAnyMineExploded()) {
|
||||
levelFacade.runFlagAssistant()
|
||||
levelFacade.runFlagAssistant().forEach {
|
||||
Handler().post {
|
||||
refreshField(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshGame()
|
||||
|
@ -223,11 +227,20 @@ class GameViewModel(
|
|||
levelFacade.revealAllEmptyAreas()
|
||||
}
|
||||
|
||||
fun gameOver() {
|
||||
suspend fun gameOver() {
|
||||
levelFacade.run {
|
||||
analyticsManager.sentEvent(Analytics.GameOver(clock.time(), getStats()))
|
||||
showAllMines()
|
||||
|
||||
findExplodedMine()?.let { exploded ->
|
||||
takeExplosionRadius(exploded).forEach {
|
||||
it.isCovered = false
|
||||
refreshField(it.id)
|
||||
delay(75L)
|
||||
}
|
||||
}
|
||||
|
||||
showWrongFlags()
|
||||
refreshGame()
|
||||
}
|
||||
|
||||
GlobalScope.launch {
|
||||
|
@ -255,7 +268,11 @@ class GameViewModel(
|
|||
|
||||
fun useAccessibilityMode() = preferencesRepository.useLargeAreas()
|
||||
|
||||
private fun refreshField(index: Int) {
|
||||
fieldRefresh.postValue(index)
|
||||
private fun refreshField(index: Int? = null) {
|
||||
if (index == null || levelFacade.isEmpty(index)) {
|
||||
field.postValue(levelFacade.field.toList())
|
||||
} else {
|
||||
fieldRefresh.postValue(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,10 +171,12 @@ class WatchGameActivity : DaggerAppCompatActivity(), AmbientModeSupport.AmbientC
|
|||
Event.GameOver -> {
|
||||
status = Status.Over()
|
||||
viewModel.stopClock()
|
||||
viewModel.gameOver()
|
||||
|
||||
messageText.text = getString(R.string.game_over)
|
||||
waitAndShowNewGameButton()
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
viewModel.gameOver()
|
||||
messageText.text = getString(R.string.game_over)
|
||||
waitAndShowNewGameButton()
|
||||
}
|
||||
}
|
||||
Event.ResumeVictory -> {
|
||||
status = Status.Over()
|
||||
|
|
Loading…
Reference in a new issue