Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix lint #478

Merged
merged 6 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ android {

defaultConfig {
// versionCode and versionName must be hardcoded to support F-droid
versionCode 1705091
versionName '17.5.9'
versionCode 1705101
versionName '17.5.10'
minSdk 21
targetSdk 34
compileSdk 34
Expand Down Expand Up @@ -77,7 +77,7 @@ android {
}

googleInstant {
versionCode 162
versionCode 163
dimension 'version'
applicationId 'dev.lucasnlm.antimine'
versionNameSuffix ' I'
Expand Down
26 changes: 18 additions & 8 deletions app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import dev.lucasnlm.external.ReviewWrapperImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -617,7 +618,7 @@ class GameActivity :
if (wasPlaying) {
gameAudioManager.resumeMusic()
}
gameViewModel.revealRandomMine(false)
revealRandomMineShowWarning(false)
gameViewModel.sendEvent(GameEvent.GiveMoreTip)
},
onFail = {
Expand Down Expand Up @@ -690,22 +691,31 @@ class GameActivity :
}
}

private fun revealRandomMine() {
private suspend fun revealRandomMine() {
analyticsManager.sentEvent(Analytics.UseHint)

val hintAmount = gameViewModel.getTips()
if (hintAmount > 0) {
val revealedId = gameViewModel.revealRandomMine()
if (revealedId == null) {
showGameWarning(i18n.string.cant_do_it_now)
} else {
showGameWarning(i18n.string.mine_revealed)
}
revealRandomMineShowWarning()
} else {
showGameWarning(i18n.string.help_win_a_game)
}
}

private fun revealRandomMineShowWarning(consume: Boolean = true) {
lifecycleScope.launch {
gameViewModel
.revealRandomMine(consume)
.collect { revealedId ->
if (revealedId == null) {
showGameWarning(i18n.string.cant_do_it_now)
} else {
showGameWarning(i18n.string.mine_revealed)
}
}
}
}

private fun startNewGameWithAds() {
if (!preferencesRepository.isPremiumEnabled()) {
if (featureFlagManager.useInterstitialAd) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ val ViewModelModule =
viewModel { LocalizationViewModel(get(), get()) }
viewModel {
GameViewModel(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.lucasnlm.antimine.common.level.di

import dev.lucasnlm.antimine.common.level.logic.VisibleMineStream
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepositoryImpl
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
Expand Down Expand Up @@ -33,4 +34,8 @@ val LevelModule =
single {
TipRepositoryImpl(get(), get())
} bind TipRepository::class

single {
VisibleMineStream()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,26 @@ class GameController {
}
}

fun revealRandomMine(): Int? {
/**
* Reveal a random mine near an uncovered area.
*
* @param visibleMines It will prioritize mines that are in the visible area.
* @return The id of the revealed mine.
*/
fun revealRandomMine(visibleMines: Set<Int>): Int? {
val resultId: Int?
field =
MinefieldHandler(
field = field.toMutableList(),
useQuestionMark = false,
individualActions = useIndividualActions(),
).run {
resultId = revealRandomMineNearUncoveredArea(lastIdInteractionX, lastIdInteractionY)
resultId =
revealRandomMineNearUncoveredArea(
visibleMines = visibleMines,
lastX = lastIdInteractionX,
lastY = lastIdInteractionY,
)
result()
}
return resultId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,35 +40,48 @@ class MinefieldHandler(
.forEach { field[it.id] = it.copy(mistake = false) }
}

private fun hasUncoveredNeighbor(area: Area): Boolean {
return area.neighborsIds.map { field[it] }.count { !it.isCovered } > 0
private fun Area.hasUncoveredNeighbor(): Boolean {
return neighborsIds.toArea().any { !it.isCovered }
}

private fun Area.potentialMineReveal(): Boolean {
return hasMine && mark.isNone() && !revealed && isCovered
}

fun revealRandomMineNearUncoveredArea(
visibleMines: Set<Int>,
lastX: Int? = null,
lastY: Int? = null,
): Int? {
val unrevealedMines = field.filter { it.hasMine && it.mark.isNone() && !it.revealed && it.isCovered }
val unrevealedMinesWithUncoveredNeighbors = unrevealedMines.filter(::hasUncoveredNeighbor)
val potentialTargets = unrevealedMinesWithUncoveredNeighbors.ifEmpty { unrevealedMines }
// / Prioritized mines are mines that are visible and have a potential to be revealed.
// / If there are no prioritized mines, then we get all mines that have a potential to be revealed.
val prioritizedMines =
visibleMines
.toArea()
.filter { it.potentialMineReveal() && it.hasUncoveredNeighbor() }

val unrevealedMines =
prioritizedMines.ifEmpty {
field.filter { it.potentialMineReveal() && it.hasUncoveredNeighbor() }
}

val nearestTarget =
if (lastX != null && lastY != null) {
potentialTargets.filter {
unrevealedMines.filter {
(lastX - it.posX).absoluteValue < NEAR_MINE_THRESHOLD &&
(lastY - it.posY).absoluteValue < NEAR_MINE_THRESHOLD
}.shuffled().firstOrNull()
} else {
null
}


return when {
nearestTarget != null -> {
field[nearestTarget.id] = nearestTarget.copy(revealed = true)
nearestTarget.id
}
else -> {
potentialTargets.shuffled().firstOrNull()?.run {
unrevealedMines.shuffled().firstOrNull()?.run {
field[this.id] = this.copy(revealed = true)
this.id
}
Expand Down Expand Up @@ -128,7 +141,7 @@ class MinefieldHandler(

if (!hasMine && minesAround == 0 && openNeighbors) {
neighborsIds
.map { field[it] }
.toArea()
.filter { it.isCovered }
.onEach {
openAt(it.id, openNeighbors = true, passive = true)
Expand All @@ -141,7 +154,7 @@ class MinefieldHandler(
fun openOrFlagNeighborsOf(index: Int) {
field.getOrNull(index)?.run {
if (!isCovered) {
val neighbors = neighborsIds.map { field[it] }
val neighbors = neighborsIds.toArea()
val flaggedCount = neighbors.count { it.mark.isFlag() || (!it.isCovered && it.hasMine) }
if (flaggedCount >= minesAround) {
neighbors
Expand All @@ -165,8 +178,8 @@ class MinefieldHandler(
fun openNeighborsOf(index: Int) {
field.getOrNull(index)?.run {
if (!isCovered) {
val neighbors = neighborsIds.map { field[it] }
neighbors
neighborsIds
.toArea()
.filter { it.isCovered && it.mark.isNone() }
.forEach { openAt(it.id, passive = false, openNeighbors = true) }
}
Expand All @@ -175,6 +188,8 @@ class MinefieldHandler(

fun result(): List<Area> = field.toList()

private fun Collection<Int>.toArea(): Collection<Area> = map { field[it] }

companion object {
const val NEAR_MINE_THRESHOLD = 5
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.lucasnlm.antimine.common.level.logic

import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow

class VisibleMineStream {
private val visibleMinesFlow =
MutableSharedFlow<Set<Int>>(
extraBufferCapacity = 1,
)
private val requestVisibleMinesFlow =
MutableSharedFlow<Unit>(
extraBufferCapacity = 1,
)

/** Returns a [SharedFlow] that emits the visible mines. */
fun observeVisibleMines() = visibleMinesFlow.asSharedFlow()

/** Updates the visible mines. */
fun update(visibleMines: Set<Int>) {
visibleMinesFlow.tryEmit(visibleMines)
}

/** Requests the visible mines. */
suspend fun requestVisibleMines() {
requestVisibleMinesFlow.emit(Unit)
}

/** Returns a [SharedFlow] that emits when the visible mines are requested. */
fun observeRequestVisibleMines() = requestVisibleMinesFlow.asSharedFlow()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
import com.badlogic.gdx.backends.android.AndroidFragmentApplication
import dev.lucasnlm.antimine.common.level.logic.VisibleMineStream
import dev.lucasnlm.antimine.common.level.viewmodel.GameEvent
import dev.lucasnlm.antimine.common.level.viewmodel.GameState
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
Expand All @@ -37,6 +38,7 @@ import dev.lucasnlm.antimine.preferences.models.Action
import dev.lucasnlm.antimine.preferences.models.ControlStyle
import dev.lucasnlm.antimine.ui.repository.ThemeRepository
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
Expand All @@ -52,6 +54,7 @@ open class GameRenderFragment : AndroidFragmentApplication() {
private val preferencesRepository: PreferencesRepository by inject()
private val appVersionManager: AppVersionManager by inject()
private val gameAudioManager: GameAudioManager by inject()
private val visibleMineStream: VisibleMineStream by inject()

private val layoutParent: FrameLayout? by lazy {
view?.parent as? FrameLayout
Expand Down Expand Up @@ -181,6 +184,13 @@ open class GameRenderFragment : AndroidFragmentApplication() {

Gdx.graphics.requestRendering()

lifecycleScope.launch {
visibleMineStream
.observeRequestVisibleMines()
.map { levelApplicationListener.getVisibleMineActors() }
.collect(visibleMineStream::update)
}

lifecycleScope.launch {
gameViewModel
.singleState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope
import dev.lucasnlm.antimine.common.io.models.FirstOpen
import dev.lucasnlm.antimine.common.io.models.Save
import dev.lucasnlm.antimine.common.level.logic.GameController
import dev.lucasnlm.antimine.common.level.logic.VisibleMineStream
import dev.lucasnlm.antimine.common.level.models.ActionCompleted
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
Expand All @@ -33,8 +34,12 @@ import dev.lucasnlm.external.Leaderboard
import dev.lucasnlm.external.PlayGamesManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale
Expand All @@ -52,6 +57,7 @@ open class GameViewModel(
private val playGamesManager: PlayGamesManager,
private val tipRepository: TipRepository,
private val clockManager: ClockManager,
private val visibleMineStream: VisibleMineStream,
) : IntentViewModel<GameEvent, GameState>() {
private lateinit var gameController: GameController
private var initialized = false
Expand Down Expand Up @@ -649,20 +655,30 @@ open class GameViewModel(
gameController.revealAllEmptyAreas()
}

fun revealRandomMine(consume: Boolean = true): Int? {
suspend fun revealRandomMine(consume: Boolean = true): Flow<Int?> {
return if (initialized) {
val result = gameController.revealRandomMine()

if (result != null) {
soundManager.playRevealBomb()

if (consume) {
sendEvent(GameEvent.ConsumeTip)
visibleMineStream
.observeVisibleMines()
.take(1)
.map {
gameController
.revealRandomMine(it)
.also { result ->
if (result != null) {
soundManager.playRevealBomb()

if (consume) {
sendEvent(GameEvent.ConsumeTip)
}
}
}
}
.also {
visibleMineStream
.requestVisibleMines()
}
}
result
} else {
null
flowOf(null)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ class GameApplicationListener(
minefieldStage.scaleZoom(delta)
}

fun getVisibleMineActors(): Set<Int> {
return minefieldStage.getVisibleMineActors()
}

fun refreshSettings() {
actionSettings =
with(preferencesRepository) {
Expand Down
3 changes: 3 additions & 0 deletions gdx/src/main/java/dev/lucasnlm/antimine/gdx/GameContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package dev.lucasnlm.antimine.gdx

import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import dev.lucasnlm.antimine.gdx.GdxExt.dim
import dev.lucasnlm.antimine.gdx.GdxExt.toGdxColor
import dev.lucasnlm.antimine.gdx.GdxExt.toInverseBackOrWhite
import dev.lucasnlm.antimine.gdx.models.GameTextures
import dev.lucasnlm.antimine.ui.model.AppTheme

Expand Down
Loading
Loading