diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/MainApplication.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/MainApplication.kt index 8fc07254..4f2a1b13 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/MainApplication.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/MainApplication.kt @@ -31,6 +31,7 @@ class MainApplication : Application() { val applicationPreferences = ApplicationPreferencesImpl(this) val preferenceMigrations = ApplicationPreferencesMigrations(applicationPreferences) preferenceMigrations.migrateThemeToNightModeIfNecessary() + preferenceMigrations.migrateDifficultySettingIfNecessary() val options = DynamicColorsOptions diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesImpl.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesImpl.kt index 88190365..9e0461f9 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesImpl.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesImpl.kt @@ -130,14 +130,14 @@ class ApplicationPreferencesImpl( } } - override var difficultySetting: DifficultySetting + override var difficultiesSetting: Set get() { - val usage = preferences.getString("difficulty", DifficultySetting.ANY.name)!! - return enumValueOf(usage) + val difficulties = preferences.getStringSet("difficulties", setOf(DifficultySetting.EASY.name))!! + return difficulties.map { enumValueOf(it) }.toSet() } - set(difficultySetting) { + set(difficultiesSetting) { preferences.edit { - putString("difficulty", difficultySetting.name) + putStringSet("difficulties", difficultiesSetting.map { it.name }.toSet()) } } @@ -227,7 +227,7 @@ class ApplicationPreferencesImpl( showOperators(), operations, digitSetting, - difficultySetting, + difficultiesSetting, singleCageUsage, numeralSystem, ) diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelBalloon.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelBalloon.kt index b6a5e45c..9e1e138e 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelBalloon.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelBalloon.kt @@ -9,11 +9,11 @@ import com.skydoves.balloon.ArrowPositionRules import com.skydoves.balloon.BalloonAnimation import com.skydoves.balloon.BalloonSizeSpec import com.skydoves.balloon.createBalloon -import org.piepmeyer.gauguin.difficulty.GameDifficulty +import org.piepmeyer.gauguin.options.DifficultySetting import org.piepmeyer.gauguin.options.GameVariant class MainGameDifficultyLevelBalloon( - private val difficulty: GameDifficulty?, + private val difficulty: DifficultySetting?, private val variant: GameVariant, ) { fun showBalloon( diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelFragment.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelFragment.kt index 05e23bd3..13749a1e 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelFragment.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/difficulty/MainGameDifficultyLevelFragment.kt @@ -16,15 +16,15 @@ import com.google.android.material.textview.MaterialTextView import org.piepmeyer.gauguin.R import org.piepmeyer.gauguin.databinding.FragmentGameDifficultyLevelBinding import org.piepmeyer.gauguin.difficulty.DisplayableGameDifficultyThreshold -import org.piepmeyer.gauguin.difficulty.GameDifficulty import org.piepmeyer.gauguin.difficulty.GameDifficultyRater import org.piepmeyer.gauguin.difficulty.GameDifficultyRating +import org.piepmeyer.gauguin.options.DifficultySetting import org.piepmeyer.gauguin.options.GameVariant import java.math.BigDecimal import java.text.DecimalFormat class MainGameDifficultyLevelFragment( - private val difficulty: GameDifficulty?, + private val difficulty: DifficultySetting?, private val variant: GameVariant, ) : Fragment(R.layout.fragment_game_difficulty_level) { private lateinit var binding: FragmentGameDifficultyLevelBinding @@ -71,34 +71,39 @@ class MainGameDifficultyLevelFragment( private fun layoutWithRating(rating: GameDifficultyRating) { val uiRating = DisplayableGameDifficultyThreshold(rating) - binding.veryEasyMaximumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.EASY)) - binding.easyMinimumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.EASY)) - binding.easyMaximumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.MEDIUM)) - binding.mediumMinimumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.MEDIUM)) - binding.mediumMaximumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.HARD)) - binding.hardMinimumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.HARD)) - binding.hardMaximumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.EXTREME)) - binding.extremeMinimumValue.text = formatDifficulty(uiRating.thresholdText(GameDifficulty.EXTREME)) + binding.veryEasyMaximumValue.text = + formatDifficulty( + uiRating.thresholdText( + DifficultySetting.EASY, + ), + ) + binding.easyMinimumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.EASY)) + binding.easyMaximumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.MEDIUM)) + binding.mediumMinimumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.MEDIUM)) + binding.mediumMaximumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.HARD)) + binding.hardMinimumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.HARD)) + binding.hardMaximumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.EXTREME)) + binding.extremeMinimumValue.text = formatDifficulty(uiRating.thresholdText(DifficultySetting.EXTREME)) } private fun layoutWithDifficulty( - difficulty: GameDifficulty, + difficulty: DifficultySetting, parent: ViewGroup?, ) { val referenceId = when (difficulty) { - GameDifficulty.VERY_EASY -> R.id.veryEasy - GameDifficulty.EASY -> R.id.easy - GameDifficulty.MEDIUM -> R.id.medium - GameDifficulty.HARD -> R.id.hard - GameDifficulty.EXTREME -> R.id.extreme + DifficultySetting.VERY_EASY -> R.id.veryEasy + DifficultySetting.EASY -> R.id.easy + DifficultySetting.MEDIUM -> R.id.medium + DifficultySetting.HARD -> R.id.hard + DifficultySetting.EXTREME -> R.id.extreme } setHighlighterConstraintsToMatch(difficulty, referenceId, parent) } private fun setHighlighterConstraintsToMatch( - difficulty: GameDifficulty?, + difficulty: DifficultySetting?, referenceId: Int, parent: ViewGroup?, ) { @@ -141,46 +146,46 @@ class MainGameDifficultyLevelFragment( constraintSet.applyTo(binding.mainGameDifficultyLevelConstaintLayout) } - private fun hightlightedTextViews(difficulty: GameDifficulty): List = + private fun hightlightedTextViews(difficulty: DifficultySetting): List = when (difficulty) { - GameDifficulty.VERY_EASY -> listOf(binding.veryEasy, binding.veryEasyMinimumValue, binding.veryEasyMaximumValue) - GameDifficulty.EASY -> listOf(binding.easy, binding.easyMinimumValue, binding.easyMaximumValue) - GameDifficulty.MEDIUM -> listOf(binding.medium, binding.mediumMinimumValue, binding.mediumMaximumValue) - GameDifficulty.HARD -> listOf(binding.hard, binding.hardMinimumValue, binding.hardMaximumValue) - GameDifficulty.EXTREME -> listOf(binding.extreme, binding.extremeMinimumValue, binding.extremeMaximumValue) + DifficultySetting.VERY_EASY -> listOf(binding.veryEasy, binding.veryEasyMinimumValue, binding.veryEasyMaximumValue) + DifficultySetting.EASY -> listOf(binding.easy, binding.easyMinimumValue, binding.easyMaximumValue) + DifficultySetting.MEDIUM -> listOf(binding.medium, binding.mediumMinimumValue, binding.mediumMaximumValue) + DifficultySetting.HARD -> listOf(binding.hard, binding.hardMinimumValue, binding.hardMaximumValue) + DifficultySetting.EXTREME -> listOf(binding.extreme, binding.extremeMinimumValue, binding.extremeMaximumValue) } - private fun hightlightedImageViews(difficulty: GameDifficulty): List = + private fun hightlightedImageViews(difficulty: DifficultySetting): List = when (difficulty) { - GameDifficulty.VERY_EASY -> + DifficultySetting.VERY_EASY -> listOf( binding.ratingStarVeryEasyOne, binding.ratingStarVeryEasyTwo, binding.ratingStarVeryEasyThree, binding.ratingStarVeryEasyFour, ) - GameDifficulty.EASY -> + DifficultySetting.EASY -> listOf( binding.ratingStarEasyOne, binding.ratingStarEasyTwo, binding.ratingStarEasyThree, binding.ratingStarEasyFour, ) - GameDifficulty.MEDIUM -> + DifficultySetting.MEDIUM -> listOf( binding.ratingStarMediumOne, binding.ratingStarMediumTwo, binding.ratingStarMediumThree, binding.ratingStarMediumFour, ) - GameDifficulty.HARD -> + DifficultySetting.HARD -> listOf( binding.ratingStarHardOne, binding.ratingStarHardTwo, binding.ratingStarHardThree, binding.ratingStarHardFour, ) - GameDifficulty.EXTREME -> + DifficultySetting.EXTREME -> listOf( binding.ratingStarExtremeOne, binding.ratingStarExtremeTwo, diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/GameTopFragment.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/GameTopFragment.kt index 984a21ae..8b57122d 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/GameTopFragment.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/GameTopFragment.kt @@ -18,12 +18,12 @@ import org.piepmeyer.gauguin.R import org.piepmeyer.gauguin.Utils import org.piepmeyer.gauguin.databinding.FragmentMainGameTopBinding import org.piepmeyer.gauguin.difficulty.DisplayableGameDifficulty -import org.piepmeyer.gauguin.difficulty.GameDifficulty import org.piepmeyer.gauguin.difficulty.GameDifficultyRater import org.piepmeyer.gauguin.difficulty.human.HumanDifficultyCalculator import org.piepmeyer.gauguin.game.Game import org.piepmeyer.gauguin.game.GameLifecycle import org.piepmeyer.gauguin.game.PlayTimeListener +import org.piepmeyer.gauguin.options.DifficultySetting import org.piepmeyer.gauguin.preferences.ApplicationPreferences import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelBalloon import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelFragment @@ -172,35 +172,35 @@ class GameTopFragment : } } - private fun setStarsByDifficulty(difficulty: GameDifficulty?) { + private fun setStarsByDifficulty(difficulty: DifficultySetting?) { if (difficulty == null) return setStarByDifficulty( binding.ratingStarOne, difficulty, - GameDifficulty.EASY, + DifficultySetting.EASY, ) setStarByDifficulty( binding.ratingStarTwo, difficulty, - GameDifficulty.MEDIUM, + DifficultySetting.MEDIUM, ) setStarByDifficulty( binding.ratingStarThree, difficulty, - GameDifficulty.HARD, + DifficultySetting.HARD, ) setStarByDifficulty( binding.ratingStarFour, difficulty, - GameDifficulty.EXTREME, + DifficultySetting.EXTREME, ) } private fun setStarByDifficulty( view: ImageView, - difficulty: GameDifficulty, - minimumDifficulty: GameDifficulty, + difficulty: DifficultySetting, + minimumDifficulty: DifficultySetting, ) { if (difficulty >= minimumDifficulty) { view.setImageResource(R.drawable.filled_star_20) diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/GridCellOptionsFragment.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/GridCellOptionsFragment.kt index 48cb9672..fd1e3d1a 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/GridCellOptionsFragment.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/GridCellOptionsFragment.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.chip.ChipGroup import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import kotlinx.coroutines.launch @@ -34,6 +35,8 @@ class GridCellOptionsFragment : private lateinit var binding: FragmentNewGameOptionsBinding + private lateinit var singleDifficultyIdMap: Map> + override fun onCreateView( inflater: LayoutInflater, parent: ViewGroup?, @@ -48,7 +51,7 @@ class GridCellOptionsFragment : view: View, savedInstanceState: Bundle?, ) { - viewModel = ViewModelProvider(requireActivity()).get(NewGameViewModel::class.java) + viewModel = ViewModelProvider(requireActivity())[NewGameViewModel::class.java] createDifficultyChips() createSingleCellUsageChips() @@ -59,14 +62,11 @@ class GridCellOptionsFragment : binding.difficultyInfoIcon.setOnClickListener { val variant = viewModel.gameVariantState.value - val difficultyOrNull = - if (variant.variant.options.difficultySetting != DifficultySetting.ANY) { - variant.variant.options.difficultySetting.gameDifficulty - } else { - null - } + val difficulty = + variant.variant.options.difficultiesSetting + .singleOrNull() - MainGameDifficultyLevelBalloon(difficultyOrNull, variant.variant).showBalloon( + MainGameDifficultyLevelBalloon(difficulty, variant.variant).showBalloon( baseView = binding.difficultyInfoIcon, inflater = this.layoutInflater, parent = binding.root, @@ -105,31 +105,28 @@ class GridCellOptionsFragment : } } } + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.difficultySelectionState.collect { + updateDifficultyMultiSelection() + } + } + } } private fun updateVisibility(tab: TabLayout.Tab) { - val basicMode = - if (tab.position == 0) { - View.VISIBLE - } else { - View.GONE - } - val numbersMode = - if (tab.position == 1) { - View.VISIBLE - } else { - View.GONE - } - val advancedMode = - if (tab.position == 2) { - View.VISIBLE - } else { - View.GONE - } + binding.newGameOptionsBasicScrollView.visibility = visibleIfTabPositionActive(tab, 0) + binding.newGameOptionsNumbersScrollView.visibility = visibleIfTabPositionActive(tab, 1) + binding.newGameOptionsAdvancedScrollView.visibility = visibleIfTabPositionActive(tab, 2) + } - binding.newGameOptionsBasicScrollView.visibility = basicMode - binding.newGameOptionsNumbersScrollView.visibility = numbersMode - binding.newGameOptionsAdvancedScrollView.visibility = advancedMode + private fun visibleIfTabPositionActive( + tab: TabLayout.Tab, + position: Int, + ) = if (tab.position == position) { + View.VISIBLE + } else { + View.GONE } private fun createSingleCellUsageChips() { @@ -210,30 +207,63 @@ class GridCellOptionsFragment : } private fun createDifficultyChips() { - val difficultyIdMap = + singleDifficultyIdMap = mapOf( - binding.chipDifficultyAny.id to DifficultySetting.ANY, - binding.chipDifficultyVeryEasy.id to DifficultySetting.VERY_EASY, - binding.chipDifficultyEasy.id to DifficultySetting.EASY, - binding.chipDifficultyMedium.id to DifficultySetting.MEDIUM, - binding.chipDifficultyHard.id to DifficultySetting.HARD, - binding.chipDifficultyVeryHard.id to DifficultySetting.EXTREME, + binding.chipDifficultyAny.id to DifficultySetting.all(), + binding.chipDifficultyVeryEasy.id to setOf(DifficultySetting.VERY_EASY), + binding.chipDifficultyEasy.id to setOf(DifficultySetting.EASY), + binding.chipDifficultyMedium.id to setOf(DifficultySetting.MEDIUM), + binding.chipDifficultyHard.id to setOf(DifficultySetting.HARD), + binding.chipDifficultyVeryHard.id to setOf(DifficultySetting.EXTREME), ) - binding.difficultyChipGroup.setOnCheckedStateChangeListener { _, _ -> - val digitdifficulty = difficultyIdMap[binding.difficultyChipGroup.checkedChipId]!! + binding.difficultyChipGroup.setOnCheckedStateChangeListener(difficultyChangelistener()) + + binding.difficultyMultiSelectionSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateDifficultyMultiSelection( + if (isChecked) DifficultySelectionState.MULTI_SELECTION else DifficultySelectionState.SINGLE_SELECTION, + ) + } + } + + private fun difficultyChangelistener() = + ChipGroup.OnCheckedStateChangeListener { _, newCheckedChipIds -> + val digitdifficulties = + (binding.difficultyChipGroup.checkedChipIds + newCheckedChipIds) + .flatMap { singleDifficultyIdMap[it]!! } + .toSet() + + applicationPreferences.difficultiesSetting = digitdifficulties - applicationPreferences.difficultySetting = digitdifficulty viewModel.calculateGrid() } - binding.difficultyChipGroup.check( - difficultyIdMap - .filterValues { - it == applicationPreferences.difficultySetting - }.keys - .first(), - ) + private fun updateDifficultyMultiSelection() { + val multiSelection = viewModel.difficultySelectionState.value == DifficultySelectionState.MULTI_SELECTION + + binding.difficultyChipGroup.setOnCheckedStateChangeListener(null) + + binding.difficultyChipGroup.isSingleSelection = !multiSelection + binding.chipDifficultyAny.visibility = if (multiSelection) View.GONE else View.VISIBLE + binding.difficultyMultiSelectionSwitch.isChecked = multiSelection + + if (multiSelection) { + binding.chipDifficultyVeryEasy.isChecked = DifficultySetting.VERY_EASY in applicationPreferences.difficultiesSetting + binding.chipDifficultyEasy.isChecked = DifficultySetting.EASY in applicationPreferences.difficultiesSetting + binding.chipDifficultyMedium.isChecked = DifficultySetting.MEDIUM in applicationPreferences.difficultiesSetting + binding.chipDifficultyHard.isChecked = DifficultySetting.HARD in applicationPreferences.difficultiesSetting + binding.chipDifficultyVeryHard.isChecked = DifficultySetting.EXTREME in applicationPreferences.difficultiesSetting + } else { + val chipId = + singleDifficultyIdMap + .filterValues { it == applicationPreferences.difficultiesSetting } + .keys + .first() + + binding.difficultyChipGroup.check(chipId) + } + + binding.difficultyChipGroup.setOnCheckedStateChangeListener(difficultyChangelistener()) } private fun createNumeralSystemChips() { diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/NewGameViewModel.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/NewGameViewModel.kt index 9c69c06f..18d34d33 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/NewGameViewModel.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/newgame/NewGameViewModel.kt @@ -14,6 +14,7 @@ import org.piepmeyer.gauguin.creation.GridCalculatorFactory import org.piepmeyer.gauguin.game.GameLifecycle import org.piepmeyer.gauguin.grid.Grid import org.piepmeyer.gauguin.grid.GridSize +import org.piepmeyer.gauguin.options.DifficultySetting import org.piepmeyer.gauguin.options.GameVariant import org.piepmeyer.gauguin.preferences.ApplicationPreferences @@ -38,6 +39,11 @@ enum class GridCalculationAlgorithm { } } +enum class DifficultySelectionState { + SINGLE_SELECTION, + MULTI_SELECTION, +} + data class GridVariantState( val variant: GameVariant, val calculationAlgorithm: GridCalculationAlgorithm, @@ -55,9 +61,11 @@ class NewGameViewModel : private val mutablePreviewGridState = MutableStateFlow(initialPreviewService()) private val mutableGameVariantState = MutableStateFlow(gridVariantState()) + private val mutableDifficultySelectionState = MutableStateFlow(initialDifficultySelectionState()) val previewGridState: StateFlow = mutablePreviewGridState.asStateFlow() val gameVariantState: StateFlow = mutableGameVariantState.asStateFlow() + val difficultySelectionState: StateFlow = mutableDifficultySelectionState.asStateFlow() init { GridCalculatorFactory.alwaysUseNewAlgorithm = applicationPreferences.mergingCageAlgorithm @@ -86,6 +94,13 @@ class NewGameViewModel : return GridVariantState(gameVariant, GridCalculationAlgorithm.fromMerging(useMergingAlgorithm)) } + private fun initialDifficultySelectionState(): DifficultySelectionState = + if (DifficultySetting.isApplicableToSingleSelection(applicationPreferences.difficultiesSetting)) { + DifficultySelectionState.SINGLE_SELECTION + } else { + DifficultySelectionState.MULTI_SELECTION + } + private fun gameVariant(): GameVariant = GameVariant( GridSize( @@ -153,4 +168,15 @@ class NewGameViewModel : } fun singleCellOptionsAvailable(): Boolean = mutableGameVariantState.value.calculationAlgorithm == GridCalculationAlgorithm.RandomGrid + + fun updateDifficultyMultiSelection(value: DifficultySelectionState) { + if (value == DifficultySelectionState.SINGLE_SELECTION) { + if (!DifficultySetting.isApplicableToSingleSelection(applicationPreferences.difficultiesSetting)) { + applicationPreferences.difficultiesSetting = DifficultySetting.all() + } + } + + mutableDifficultySelectionState.value = value + calculateGrid() + } } diff --git a/gauguin-app/src/main/res/layout/fragment_new_game_options.xml b/gauguin-app/src/main/res/layout/fragment_new_game_options.xml index 50a221a1..d5586b14 100644 --- a/gauguin-app/src/main/res/layout/fragment_new_game_options.xml +++ b/gauguin-app/src/main/res/layout/fragment_new_game_options.xml @@ -82,7 +82,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/difficulty_label" - app:singleSelection="true" app:selectionRequired="true" > @@ -130,12 +129,22 @@ + + diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestGridCreator.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestGridCreator.kt index fac47b0e..5ccb316e 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestGridCreator.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestGridCreator.kt @@ -12,51 +12,52 @@ import org.piepmeyer.gauguin.options.GridCageOperation import org.piepmeyer.gauguin.options.NumeralSystem import org.piepmeyer.gauguin.options.SingleCageUsage -class TestGridCreator : FunSpec({ - test("3x3GridCreationWithoutRandomValues") { - val creator = - GridCreator( - GameVariant( - GridSize(3, 3), - GameOptionsVariant( - true, - GridCageOperation.OPERATIONS_ALL, - DigitSetting.FIRST_DIGIT_ONE, - DifficultySetting.ANY, - SingleCageUsage.FIXED_NUMBER, - NumeralSystem.Decimal, +class TestGridCreator : + FunSpec({ + test("3x3GridCreationWithoutRandomValues") { + val creator = + GridCreator( + GameVariant( + GridSize(3, 3), + GameOptionsVariant( + true, + GridCageOperation.OPERATIONS_ALL, + DigitSetting.FIRST_DIGIT_ONE, + DifficultySetting.all(), + SingleCageUsage.FIXED_NUMBER, + NumeralSystem.Decimal, + ), ), - ), - OnlyZeroRandomizerMock(), - ShufflerStub(), - ) + OnlyZeroRandomizerMock(), + ShufflerStub(), + ) - val grid = creator.createRandomizedGridWithCages() + val grid = creator.createRandomizedGridWithCages() - grid.getValidCellAt(0, 0).value shouldBe 1 - grid.getValidCellAt(0, 1).value shouldBe 2 - grid.getValidCellAt(0, 2).value shouldBe 3 - grid.getValidCellAt(1, 0).value shouldBe 2 - grid.getValidCellAt(1, 1).value shouldBe 3 - grid.getValidCellAt(1, 2).value shouldBe 1 - grid.getValidCellAt(2, 0).value shouldBe 3 - grid.getValidCellAt(2, 1).value shouldBe 1 - grid.getValidCellAt(2, 2).value shouldBe 2 - } + grid.getValidCellAt(0, 0).value shouldBe 1 + grid.getValidCellAt(0, 1).value shouldBe 2 + grid.getValidCellAt(0, 2).value shouldBe 3 + grid.getValidCellAt(1, 0).value shouldBe 2 + grid.getValidCellAt(1, 1).value shouldBe 3 + grid.getValidCellAt(1, 2).value shouldBe 1 + grid.getValidCellAt(2, 0).value shouldBe 3 + grid.getValidCellAt(2, 1).value shouldBe 1 + grid.getValidCellAt(2, 2).value shouldBe 2 + } - test("deterministic random number generator leads to deterministic grids") { - val variant = - GameVariant( - GridSize(10, 10), - GameOptionsVariant.createClassic(), - ) + test("deterministic random number generator leads to deterministic grids") { + val variant = + GameVariant( + GridSize(10, 10), + GameOptionsVariant.createClassic(), + ) - val gridOne = calculateGrid(variant) - val gridTwo = calculateGrid(variant) + val gridOne = calculateGrid(variant) + val gridTwo = calculateGrid(variant) - gridOne.toString() shouldBe gridTwo.toString() - } -}) + gridOne.toString() shouldBe gridTwo.toString() + } + }) private suspend fun calculateGrid(variant: GameVariant): Grid { val randomizer = SeedRandomizerMock(1) diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestMergingCageGridCalculatorDistribution.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestMergingCageGridCalculatorDistribution.kt index 964a2f00..55ecea31 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestMergingCageGridCalculatorDistribution.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/creation/TestMergingCageGridCalculatorDistribution.kt @@ -19,15 +19,16 @@ import org.piepmeyer.gauguin.options.SingleCageUsage private val logger = KotlinLogging.logger {} -class TestMergingCageGridCalculatorDistribution : FunSpec({ - xtest("calculateValues 6x6") { - testHundredGrids(6) - } +class TestMergingCageGridCalculatorDistribution : + FunSpec({ + xtest("calculateValues 6x6") { + testHundredGrids(6) + } - xtest("calculateValues 9x9") { - testHundredGrids(9) - } -}) { + xtest("calculateValues 9x9") { + testHundredGrids(9) + } + }) { companion object { private fun testHundredGrids(size: Int) { val difficultiesAndSingles = @@ -59,7 +60,9 @@ class TestMergingCageGridCalculatorDistribution : FunSpec({ "maximum ${sortedSingles.max()}" } - sortedSingles.groupingBy { it }.eachCount() + sortedSingles + .groupingBy { it } + .eachCount() .forEach { (singles, count) -> logger.info { "singles $singles: $count" } } @@ -76,7 +79,7 @@ class TestMergingCageGridCalculatorDistribution : FunSpec({ true, GridCageOperation.OPERATIONS_ALL, DigitSetting.FIRST_DIGIT_ONE, - DifficultySetting.ANY, + DifficultySetting.all(), SingleCageUsage.FIXED_NUMBER, NumeralSystem.Decimal, ), diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossible.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossible.kt index bff54d84..396eb710 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossible.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossible.kt @@ -24,37 +24,37 @@ import kotlin.time.Duration.Companion.minutes private val logger = KotlinLogging.logger {} -class TestGridDifficultyCalculationPossible : FunSpec({ - xtest("calculateValues") { - runBlocking(Dispatchers.Default) { - - val groupedItems = - calculateDifficulties() - .map { - logger.info { "waiting for $it" } - val value = it.await() - logger.info { "finished: $it" } - value - } - .groupBy({ it.first }, { it.second }) - .map { - val success = it.value.count { it } +class TestGridDifficultyCalculationPossible : + FunSpec({ + xtest("calculateValues") { + runBlocking(Dispatchers.Default) { + + val groupedItems = + calculateDifficulties() + .map { + logger.info { "waiting for $it" } + val value = it.await() + logger.info { "finished: $it" } + value + }.groupBy({ it.first }, { it.second }) + .map { + val success = it.value.count { it } - val item = GameVariantPossibleItem(it.key, success) + val item = GameVariantPossibleItem(it.key, success) - logger.info { "Possible: $success, ${it.key}" } + logger.info { "Possible: $success, ${it.key}" } - item - }.sortedBy { it.calculatedDifficulties } + item + }.sortedBy { it.calculatedDifficulties } - logger.info { "calculated difficulties ${groupedItems.size}." } + logger.info { "calculated difficulties ${groupedItems.size}." } - val result = Json { prettyPrint = true }.encodeToString(groupedItems) + val result = Json { prettyPrint = true }.encodeToString(groupedItems) - File("possibles.yml").writeText(result) + File("possibles.yml").writeText(result) + } } - } -}) { + }) { companion object { suspend fun calculateDifficulties(): List>> = kotlinx.coroutines.coroutineScope { @@ -72,7 +72,7 @@ class TestGridDifficultyCalculationPossible : FunSpec({ showOperators, cageOperation, digitSetting, - DifficultySetting.ANY, + DifficultySetting.all(), singleCageUsage, NumeralSystem.Decimal, ), diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossibleTwo.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossibleTwo.kt index e2a24bac..7add646e 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossibleTwo.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyCalculationPossibleTwo.kt @@ -16,40 +16,39 @@ import org.piepmeyer.gauguin.options.GameVariant import org.piepmeyer.gauguin.options.NumeralSystem import java.io.File -class TestGridDifficultyCalculationPossibleTwo : FunSpec({ - xtest("calculateValues") { - runBlocking(Dispatchers.Default) { - - val fileData = - this::class.java - .getResource("/org/piepmeyer/gauguin/difficulty/possibles-to-10.yml")!! - .readText() - - val possibles = Json.decodeFromString>(fileData) - - val groupedItems = - calculateDifficulties( - possibles.filter { it.calculatedDifficulties == 10 }.map { it.variant }, - ) - .map { +class TestGridDifficultyCalculationPossibleTwo : + FunSpec({ + xtest("calculateValues") { + runBlocking(Dispatchers.Default) { + + val fileData = + this::class.java + .getResource("/org/piepmeyer/gauguin/difficulty/possibles-to-10.yml")!! + .readText() + + val possibles = Json.decodeFromString>(fileData) + + val groupedItems = + calculateDifficulties( + possibles.filter { it.calculatedDifficulties == 10 }.map { it.variant }, + ).map { println("waiting for $it") val value = it.await() println("finished: $it") value - } - .groupBy({ it.first }, { it.second }) - .map { - GameVariantMassDifficultyItem(it.key, it.value.sorted()) - } + }.groupBy({ it.first }, { it.second }) + .map { + GameVariantMassDifficultyItem(it.key, it.value.sorted()) + } - println("calculated difficulties ${groupedItems.size}.") + println("calculated difficulties ${groupedItems.size}.") - val result = Json { prettyPrint = true }.encodeToString(groupedItems) + val result = Json { prettyPrint = true }.encodeToString(groupedItems) - File("mass-difficulties-10-10.yml").writeText(result) + File("mass-difficulties-10-10.yml").writeText(result) + } } - } -}) { + }) { companion object { suspend fun calculateDifficulties(variants: List): List>> = kotlinx.coroutines.coroutineScope { @@ -63,7 +62,7 @@ class TestGridDifficultyCalculationPossibleTwo : FunSpec({ it.showOperators, it.cageOperation, it.digitSetting, - DifficultySetting.ANY, + DifficultySetting.all(), it.singleCageUsage, NumeralSystem.Decimal, ), diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyMassCalculation.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyMassCalculation.kt index efc9a83a..5c955834 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyMassCalculation.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridDifficultyMassCalculation.kt @@ -20,31 +20,31 @@ import org.piepmeyer.gauguin.options.NumeralSystem import org.piepmeyer.gauguin.options.SingleCageUsage import java.io.File -class TestGridDifficultyMassCalculation : FunSpec({ - xtest("calculateValues") { - runBlocking(Dispatchers.Default) { +class TestGridDifficultyMassCalculation : + FunSpec({ + xtest("calculateValues") { + runBlocking(Dispatchers.Default) { - val groupedItems = - calculateDifficulties() - .map { - println("waiting for $it") - val value = it.await() - println("finished: $it") - value - } - .groupBy({ it.first }, { it.second }) - .map { - GameVariantMassDifficultyItem(it.key, it.value.sorted()) - } + val groupedItems = + calculateDifficulties() + .map { + println("waiting for $it") + val value = it.await() + println("finished: $it") + value + }.groupBy({ it.first }, { it.second }) + .map { + GameVariantMassDifficultyItem(it.key, it.value.sorted()) + } - println("calculated difficulties ${groupedItems.size}.") + println("calculated difficulties ${groupedItems.size}.") - val result = Json { prettyPrint = true }.encodeToString(groupedItems) + val result = Json { prettyPrint = true }.encodeToString(groupedItems) - File("mass-difficulties.yml").writeText(result) + File("mass-difficulties.yml").writeText(result) + } } - } -}) { + }) { companion object { suspend fun calculateDifficulties(): List>> = kotlinx.coroutines.coroutineScope { @@ -62,7 +62,7 @@ class TestGridDifficultyMassCalculation : FunSpec({ showOperators, cageOperation, digitSetting, - DifficultySetting.ANY, + DifficultySetting.all(), singleCageUsage, NumeralSystem.Decimal, ), diff --git a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridMostDifficultOnes.kt b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridMostDifficultOnes.kt index 41ac8a7f..2e8bb6e6 100644 --- a/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridMostDifficultOnes.kt +++ b/gauguin-core/src/integrationTest/kotlin/org/piepmeyer/gauguin/difficulty/TestGridMostDifficultOnes.kt @@ -21,14 +21,15 @@ import org.piepmeyer.gauguin.options.GridCageOperation import org.piepmeyer.gauguin.options.NumeralSystem import org.piepmeyer.gauguin.options.SingleCageUsage -class TestGridMostDifficultOnes : FunSpec({ - xtest("calculateValues") { - runBlocking(Dispatchers.Default) { +class TestGridMostDifficultOnes : + FunSpec({ + xtest("calculateValues") { + runBlocking(Dispatchers.Default) { - calculateDifficulties() + calculateDifficulties() + } } - } -}) { + }) { companion object { suspend fun calculateDifficulties(): List>> = kotlinx.coroutines.coroutineScope { @@ -43,7 +44,7 @@ class TestGridMostDifficultOnes : FunSpec({ true, GridCageOperation.OPERATIONS_ALL, DigitSetting.FIRST_DIGIT_ONE, - DifficultySetting.EXTREME, + DifficultySetting.all(), SingleCageUsage.DYNAMIC, NumeralSystem.Decimal, ), diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/calculation/GridPreviewCalculationService.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/calculation/GridPreviewCalculationService.kt index f70abd9e..af26062b 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/calculation/GridPreviewCalculationService.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/calculation/GridPreviewCalculationService.kt @@ -62,7 +62,7 @@ class GridPreviewCalculationService( logger.info { "Generating pseudo grid..." } val variantWithoutDifficulty = variant.copy( - options = variant.options.copy(difficultySetting = DifficultySetting.ANY), + options = variant.options.copy(difficultiesSetting = DifficultySetting.all()), ) grid = GridCreator(variantWithoutDifficulty).createRandomizedGridWithCages() diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/GridCreator.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/GridCreator.kt index 4bfff471..73479bc8 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/GridCreator.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/GridCreator.kt @@ -30,13 +30,13 @@ class GridCreator( } private fun isWantedDifficulty(grid: Grid): Boolean { - if (variant.options.difficultySetting == DifficultySetting.ANY) { + if (variant.options.difficultiesSetting == DifficultySetting.all()) { return true } return if (!rater.isSupported(grid.variant)) { true } else { - rater.difficulty(variantRating, grid) == variant.options.difficultySetting.gameDifficulty + rater.difficulty(variantRating, grid) in variant.options.difficultiesSetting } } diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/MergingCageGridCalculator.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/MergingCageGridCalculator.kt index 52a309ed..9294b986 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/MergingCageGridCalculator.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/creation/MergingCageGridCalculator.kt @@ -116,13 +116,12 @@ class MergingCageGridCalculator( private fun minimumCageSize(): Int { val maximumAverageCageCells = - when (variant.options.difficultySetting) { + when (variant.options.difficultiesSetting.random(randomizer.random())) { DifficultySetting.VERY_EASY -> 2.025 DifficultySetting.EASY -> 2.31 DifficultySetting.MEDIUM -> 2.61 DifficultySetting.HARD -> 3.0 DifficultySetting.EXTREME -> Double.MAX_VALUE - DifficultySetting.ANY -> 2.025 + ((3.375 - 2.025) * randomizer.nextDouble()) } return round(variant.surfaceArea.toDouble() / maximumAverageCageCells).toInt() diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/DisplayableGameDifficultyThreshold.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/DisplayableGameDifficultyThreshold.kt index b4e68dde..0f9c40eb 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/DisplayableGameDifficultyThreshold.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/DisplayableGameDifficultyThreshold.kt @@ -1,11 +1,12 @@ package org.piepmeyer.gauguin.difficulty +import org.piepmeyer.gauguin.options.DifficultySetting import java.math.BigDecimal class DisplayableGameDifficultyThreshold( private val rating: GameDifficultyRating, ) { - fun thresholdText(difficulty: GameDifficulty): BigDecimal { + fun thresholdText(difficulty: DifficultySetting): BigDecimal { val threshold = rating.threshold(difficulty) return DisplayableGameDifficulty(rating).displayableDifficultyValue(threshold) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficulty.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficulty.kt deleted file mode 100644 index 0330f69e..00000000 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficulty.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.piepmeyer.gauguin.difficulty - -enum class GameDifficulty { - VERY_EASY, - EASY, - MEDIUM, - HARD, - EXTREME, -} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRater.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRater.kt index f7cc50ed..247665aa 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRater.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRater.kt @@ -1,41 +1,30 @@ package org.piepmeyer.gauguin.difficulty import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.options.DifficultySetting import org.piepmeyer.gauguin.options.GameVariant class GameDifficultyRater { private val difficultyLoader = GameDifficultyLoader.loadDifficulties() - fun difficulty(grid: Grid): GameDifficulty? { - return difficulty(grid, GridDifficultyCalculator(grid).calculate()) - } + fun difficulty(grid: Grid): DifficultySetting? = difficulty(grid, GridDifficultyCalculator(grid).calculate()) fun difficulty( gameRating: GameDifficultyRating?, grid: Grid, - ): GameDifficulty? { - return difficulty(gameRating, GridDifficultyCalculator(grid).calculate()) - } + ): DifficultySetting? = difficulty(gameRating, GridDifficultyCalculator(grid).calculate()) private fun difficulty( grid: Grid, difficultyValue: Double, - ): GameDifficulty? { - return difficulty(difficultyLoader.byVariant(grid.variant), difficultyValue) - } + ): DifficultySetting? = difficulty(difficultyLoader.byVariant(grid.variant), difficultyValue) private fun difficulty( gameRating: GameDifficultyRating?, difficultyValue: Double, - ): GameDifficulty? { - return gameRating?.getDifficulty(difficultyValue) - } + ): DifficultySetting? = gameRating?.getDifficulty(difficultyValue) - fun isSupported(variant: GameVariant): Boolean { - return difficultyLoader.isSupported(variant) - } + fun isSupported(variant: GameVariant): Boolean = difficultyLoader.isSupported(variant) - fun byVariant(variant: GameVariant): GameDifficultyRating? { - return difficultyLoader.byVariant(variant) - } + fun byVariant(variant: GameVariant): GameDifficultyRating? = difficultyLoader.byVariant(variant) } diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRating.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRating.kt index 747bbd56..40051402 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRating.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/GameDifficultyRating.kt @@ -1,6 +1,7 @@ package org.piepmeyer.gauguin.difficulty import kotlinx.serialization.Serializable +import org.piepmeyer.gauguin.options.DifficultySetting @Serializable data class GameDifficultyRating( @@ -10,30 +11,29 @@ data class GameDifficultyRating( val thresholdHard: Double, val thresholdExtreme: Double, ) { - fun threshold(difficulty: GameDifficulty): Double { - return when (difficulty) { - GameDifficulty.EASY -> thresholdEasy - GameDifficulty.MEDIUM -> thresholdMedium - GameDifficulty.HARD -> thresholdHard - GameDifficulty.EXTREME -> thresholdExtreme + fun threshold(difficulty: DifficultySetting): Double = + when (difficulty) { + DifficultySetting.EASY -> thresholdEasy + DifficultySetting.MEDIUM -> thresholdMedium + DifficultySetting.HARD -> thresholdHard + DifficultySetting.EXTREME -> thresholdExtreme else -> throw IllegalArgumentException("Threshold of difficulty $difficulty is not implemented.") } - } - fun getDifficulty(difficultyValue: Double): GameDifficulty { + fun getDifficulty(difficultyValue: Double): DifficultySetting { if (difficultyValue >= thresholdExtreme) { - return GameDifficulty.EXTREME + return DifficultySetting.EXTREME } if (difficultyValue >= thresholdHard) { - return GameDifficulty.HARD + return DifficultySetting.HARD } if (difficultyValue >= thresholdMedium) { - return GameDifficulty.MEDIUM + return DifficultySetting.MEDIUM } return if (difficultyValue >= thresholdEasy) { - GameDifficulty.EASY + DifficultySetting.EASY } else { - GameDifficulty.VERY_EASY + DifficultySetting.VERY_EASY } } } diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedDifficultySetting.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedDifficultySetting.kt new file mode 100644 index 00000000..e3f867c4 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedDifficultySetting.kt @@ -0,0 +1,22 @@ +package org.piepmeyer.gauguin.game.save + +import org.piepmeyer.gauguin.options.DifficultySetting + +enum class SavedDifficultySetting { + ANY, + VERY_EASY, + EASY, + MEDIUM, + HARD, + EXTREME, ; + + fun toDifficultySetting(): Set = + when (this) { + ANY -> DifficultySetting.all() + VERY_EASY -> setOf(DifficultySetting.VERY_EASY) + EASY -> setOf(DifficultySetting.EASY) + MEDIUM -> setOf(DifficultySetting.MEDIUM) + HARD -> setOf(DifficultySetting.HARD) + EXTREME -> setOf(DifficultySetting.EXTREME) + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedGameOptionsVariant.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedGameOptionsVariant.kt index f963c36c..aa16a9e9 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedGameOptionsVariant.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/game/save/SavedGameOptionsVariant.kt @@ -13,7 +13,8 @@ data class SavedGameOptionsVariant( var showOperators: Boolean, var cageOperation: GridCageOperation, var digitSetting: DigitSetting, - var difficultySetting: DifficultySetting, + var difficultySetting: SavedDifficultySetting?, + var difficultiesSetting: Set = emptySet(), var singleCageUsage: SingleCageUsage, var numeralSystem: NumeralSystem, ) { @@ -22,7 +23,7 @@ data class SavedGameOptionsVariant( showOperators = showOperators, cageOperation = cageOperation, digitSetting = digitSetting, - difficultySetting = difficultySetting, + difficultiesSetting = difficultiesSetting.ifEmpty { difficultySetting!!.toDifficultySetting() }, singleCageUsage = singleCageUsage, numeralSystem = numeralSystem, ) @@ -33,7 +34,8 @@ data class SavedGameOptionsVariant( showOperators = options.showOperators, cageOperation = options.cageOperation, digitSetting = options.digitSetting, - difficultySetting = options.difficultySetting, + difficultySetting = null, + difficultiesSetting = options.difficultiesSetting, singleCageUsage = options.singleCageUsage, numeralSystem = options.numeralSystem, ) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/DifficultySetting.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/DifficultySetting.kt index 1252a1fd..10c53c7d 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/DifficultySetting.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/DifficultySetting.kt @@ -1,12 +1,16 @@ package org.piepmeyer.gauguin.options -import org.piepmeyer.gauguin.difficulty.GameDifficulty +enum class DifficultySetting { + VERY_EASY, + EASY, + MEDIUM, + HARD, + EXTREME, ; -enum class DifficultySetting(val gameDifficulty: GameDifficulty?) { - ANY(null), - VERY_EASY(GameDifficulty.VERY_EASY), - EASY(GameDifficulty.EASY), - MEDIUM(GameDifficulty.MEDIUM), - HARD(GameDifficulty.HARD), - EXTREME(GameDifficulty.EXTREME), + companion object { + fun all(): Set = entries.toSet() + + fun isApplicableToSingleSelection(difficultiesSetting: Set): Boolean = + difficultiesSetting == all() || difficultiesSetting.size == 1 + } } diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/GameOptionsVariant.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/GameOptionsVariant.kt index aeae0787..92bcd640 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/GameOptionsVariant.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/options/GameOptionsVariant.kt @@ -4,7 +4,7 @@ data class GameOptionsVariant( var showOperators: Boolean, var cageOperation: GridCageOperation, var digitSetting: DigitSetting, - var difficultySetting: DifficultySetting, + var difficultiesSetting: Set, var singleCageUsage: SingleCageUsage, var numeralSystem: NumeralSystem, ) { @@ -17,7 +17,7 @@ data class GameOptionsVariant( showOperators = true, digitSetting = digitSetting, singleCageUsage = SingleCageUsage.FIXED_NUMBER, - difficultySetting = DifficultySetting.ANY, + difficultiesSetting = DifficultySetting.all(), numeralSystem = NumeralSystem.Decimal, ) } diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferences.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferences.kt index 1f4cf9f6..3cd67837 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferences.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferences.kt @@ -29,7 +29,7 @@ interface ApplicationPreferences { var operations: GridCageOperation var singleCageUsage: SingleCageUsage - var difficultySetting: DifficultySetting + var difficultiesSetting: Set var digitSetting: DigitSetting var numeralSystem: NumeralSystem diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrations.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrations.kt index c7adb56a..50392441 100644 --- a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrations.kt +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrations.kt @@ -6,6 +6,32 @@ import org.piepmeyer.gauguin.Theme class ApplicationPreferencesMigrations( private val applicationPreferences: ApplicationPreferences, ) { + fun migrateDifficultySettingIfNecessary() { + if (applicationPreferences.getStringSet("difficulties", null) != null) { + return + } + + val oldDifficultyValue = applicationPreferences.getString("difficulty", null) + /* + * Possible values: + * ANY + * VERY_EASY + * EASY + * MEDIUM + * HARD + * EXTREME + */ + + if (oldDifficultyValue != null) { + applicationPreferences.difficultiesSetting = + if (oldDifficultyValue == "ANY") { + DifficultySetting.all() + } else { + setOf(DifficultySetting.valueOf(oldDifficultyValue)) + } + } + } + fun migrateThemeToNightModeIfNecessary() { if (applicationPreferences.getString("nightMode", null) != null) { return diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrationsTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrationsTest.kt index 0cd11fb4..13f5a4ed 100644 --- a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrationsTest.kt +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/preferences/ApplicationPreferencesMigrationsTest.kt @@ -9,6 +9,7 @@ import io.mockk.runs import io.mockk.verify import org.piepmeyer.gauguin.NightMode import org.piepmeyer.gauguin.Theme +import org.piepmeyer.gauguin.options.DifficultySetting class ApplicationPreferencesMigrationsTest : FunSpec({ @@ -55,4 +56,55 @@ class ApplicationPreferencesMigrationsTest : preferences.nightMode = testData.expectedNightMode } } + + test("difficulty migration gets not triggered if difficulties are already in use") { + val preferences = + mockk { + every { getStringSet("difficulties", null) } returns setOf(DifficultySetting.EASY.name) + // no setter of 'difficulties' allowed here + } + + val migrations = ApplicationPreferencesMigrations(preferences) + + migrations.migrateDifficultySettingIfNecessary() + } + + test("difficulty migration gets not triggered if no difficulty preference is used yet") { + val preferences = + mockk { + every { getStringSet("difficulties", null) } returns null + every { getString("difficulty", null) } returns null + // no setter of 'difficulties' allowed here + } + + val migrations = ApplicationPreferencesMigrations(preferences) + + migrations.migrateDifficultySettingIfNecessary() + } + + data class DifficultyMigrationTestData( + val sharedPreferenceDifficultyValue: String?, + val expectedDifficultiesValue: Set, + ) + + withData( + DifficultyMigrationTestData("EASY", setOf(DifficultySetting.EASY)), + DifficultyMigrationTestData("EXTREME", setOf(DifficultySetting.EXTREME)), + DifficultyMigrationTestData("ANY", DifficultySetting.all()), + ) { testData -> + val preferences = + mockk { + every { getStringSet("difficulties", null) } returns null + every { getString("difficulty", null) } returns testData.sharedPreferenceDifficultyValue + every { difficultiesSetting = testData.expectedDifficultiesValue } just runs + } + + val migrations = ApplicationPreferencesMigrations(preferences) + + migrations.migrateDifficultySettingIfNecessary() + + verify { + preferences.difficultiesSetting = testData.expectedDifficultiesValue + } + } })