Skip to content

Commit

Permalink
Merge pull request #478 from lucasnlm/new-build
Browse files Browse the repository at this point in the history
Fix lint
  • Loading branch information
lucasnlm authored Nov 12, 2023
2 parents cc9451d + 0a757c0 commit 5354543
Show file tree
Hide file tree
Showing 17 changed files with 226 additions and 136 deletions.
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

0 comments on commit 5354543

Please sign in to comment.