diff --git a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/BottomAppBarItemClickListener.kt b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/BottomAppBarItemClickListener.kt index 33a9e9b5..3c58f693 100644 --- a/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/BottomAppBarItemClickListener.kt +++ b/gauguin-app/src/main/kotlin/org/piepmeyer/gauguin/ui/main/BottomAppBarItemClickListener.kt @@ -5,12 +5,14 @@ import androidx.appcompat.widget.Toolbar import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.piepmeyer.gauguin.R +import org.piepmeyer.gauguin.difficulty.human.HumanSolver import org.piepmeyer.gauguin.game.Game import org.piepmeyer.gauguin.game.GameSolveService class BottomAppBarItemClickListener( private val mainActivity: MainActivity, -) : Toolbar.OnMenuItemClickListener, KoinComponent { +) : Toolbar.OnMenuItemClickListener, + KoinComponent { private val game: Game by inject() private val gameSolveService: GameSolveService by inject() @@ -23,6 +25,19 @@ class BottomAppBarItemClickListener( R.id.menu_reveal_cell -> gameSolveService.revealSelectedCell() R.id.menu_reveal_cage -> gameSolveService.revealSelectedCage() R.id.menu_show_solution -> gameSolveService.solveGrid() + R.id.menu_debug_solve_by_human_solver_from_start -> { + val solver = HumanSolver(game.grid) + solver.prepareGrid() + solver.solveAndCalculateDifficulty() + + game.gridUI.invalidate() + } + R.id.menu_debug_solve_by_human_solver_from_here -> { + val solver = HumanSolver(game.grid) + solver.solveAndCalculateDifficulty() + + game.gridUI.invalidate() + } } return true 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 a7555782..349594e4 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 @@ -10,6 +10,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -19,9 +20,12 @@ 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.HumanSolver import org.piepmeyer.gauguin.game.Game import org.piepmeyer.gauguin.game.GameLifecycle import org.piepmeyer.gauguin.game.PlayTimeListener +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage import org.piepmeyer.gauguin.preferences.ApplicationPreferences import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelBalloon import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelFragment @@ -142,6 +146,45 @@ class GameTopFragment : binding.playtime.text = Utils.displayableGameDuration(game.grid.playTime) } + + if (resources.getBoolean(R.bool.debuggable)) { + lifecycleScope.launch(Dispatchers.Default) { + val grid = Grid(game.grid.variant) + + game.grid.cages.forEach { + val newCage = GridCage(it.id, grid.options.showOperators, it.action, it.cageType) + + it.cells.forEach { newCage.addCell(grid.getCell(it.cellNumber)) } + + newCage.result = it.result + + grid.addCage(newCage) + } + + game.grid.cells.forEach { + val newCell = grid.getCell(it.cellNumber) + + newCell.value = it.value + } + + val solver = HumanSolver(grid) + solver.prepareGrid() + val solverResult = solver.solveAndCalculateDifficulty() + + var text = binding.difficulty.text as String + " (${solverResult.difficulty}" + + if (!solverResult.success) { + text += "!" + } + text += ")" + + launch(Dispatchers.Main) { + if (!binding.difficulty.text.contains(' ')) { + binding.difficulty.text = text + } + } + } + } } private fun setStarsByDifficulty(difficulty: GameDifficulty?) { diff --git a/gauguin-app/src/main/res/menu/bottom_app_bar.xml b/gauguin-app/src/main/res/menu/bottom_app_bar.xml index 5e9a2716..370410be 100644 --- a/gauguin-app/src/main/res/menu/bottom_app_bar.xml +++ b/gauguin-app/src/main/res/menu/bottom_app_bar.xml @@ -1,5 +1,6 @@ - + + + + \ No newline at end of file diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/FillSingleCage.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/FillSingleCage.kt new file mode 100644 index 00000000..87722a1c --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/FillSingleCage.kt @@ -0,0 +1,20 @@ +package org.piepmeyer.gauguin.difficulty.human + +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.grid.Grid + +class FillSingleCage { + fun fillCells(grid: Grid): Int { + val cagesToBeFilled = grid.cages.filter { it.cageType == GridCageType.SINGLE && !it.getCell(0).isUserValueSet } + + var filledCells = 0 + + cagesToBeFilled.forEach { + grid.setUserValueAndRemovePossibles(it.getCell(0), it.result) + + filledCells++ + } + + return filledCells + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLine.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLine.kt new file mode 100644 index 00000000..7424cfe3 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLine.kt @@ -0,0 +1,27 @@ +package org.piepmeyer.gauguin.difficulty.human + +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage +import org.piepmeyer.gauguin.grid.GridCell + +data class GridLine( + private val grid: Grid, + val type: GridLineType, + val lineNumber: Int, +) { + fun contains(cell: GridCell): Boolean = + when (type) { + GridLineType.COLUMN -> cell.column == lineNumber + GridLineType.ROW -> cell.row == lineNumber + } + + fun cells(): List = grid.cells.filter { contains(it) } + + fun cages(): Set = + grid.cells + .filter { contains(it) } + .map { it.cage!! } + .toSet() + + override fun toString(): String = "GridLine type=$type, lineNumber=$lineNumber" +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLineType.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLineType.kt new file mode 100644 index 00000000..651e8c1c --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLineType.kt @@ -0,0 +1,6 @@ +package org.piepmeyer.gauguin.difficulty.human + +enum class GridLineType { + COLUMN, + ROW, +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLines.kt new file mode 100644 index 00000000..47951ebb --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/GridLines.kt @@ -0,0 +1,79 @@ +package org.piepmeyer.gauguin.difficulty.human + +import org.piepmeyer.gauguin.grid.Grid + +class GridLines( + private val grid: Grid, +) { + fun allLines(): Set { + val lines = mutableSetOf() + + for (column in 0.. { + val lines = mutableSetOf() + + if (grid.gridSize.height == grid.gridSize.largestSide()) { + for (column in 0..> { + val lines = mutableSetOf>() + + if (grid.gridSize.height == grid.gridSize.largestSide()) { + for (column in 0..> { + val lines = mutableSetOf>() + + for (column in 0..>() + + fun initialize() { + cageToPossibles += + grid.cages.associateWith { + val creator = GridSingleCageCreator(grid.variant, it) + creator.possibleCombinations + } + } + + fun validateEntries() { + cageToPossibles.forEach { (cage, possibles) -> + val possiblesToDelete = + possibles.filterNot { + cage.cells.withIndex().all { cell -> + if (cell.value.isUserValueSet) { + cell.value.userValue == it[cell.index] + } else { + cell.value.possibles.contains(it[cell.index]) + } + } + } + + if (possiblesToDelete.isNotEmpty()) { + cageToPossibles[cage] = possibles - possiblesToDelete.toSet() + } + } + } + + fun possibles(cage: GridCage): List = cageToPossibles[cage]!! +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/PossiblesReducer.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/PossiblesReducer.kt new file mode 100644 index 00000000..48f224df --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/PossiblesReducer.kt @@ -0,0 +1,25 @@ +package org.piepmeyer.gauguin.difficulty.human + +import org.piepmeyer.gauguin.grid.GridCage + +class PossiblesReducer( + private val cage: GridCage, +) { + fun reduceToPossibleCombinations(possibleCombinations: List): Boolean { + var foundPossibles = false + + cage.cells.forEachIndexed { cellIndex, cell -> + val differentPossibles = possibleCombinations.map { it[cellIndex] }.toSet() + + for (possible in cell.possibles) { + if (!differentPossibles.contains(possible)) { + cage.getCell(cellIndex).possibles -= possible + + foundPossibles = true + } + } + } + + return foundPossibles + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesOddEvenCheckSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesOddEvenCheckSum.kt new file mode 100644 index 00000000..a1ed4b6d --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesOddEvenCheckSum.kt @@ -0,0 +1,88 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +/** + * Scans two adjacent lines to detect if each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated and enforced by deleting + * deviant possibles. + */ +abstract class AbstractLinesOddEvenCheckSum( + private val numberOfLines: Int, +) : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val linePairs = GridLines(grid).adjacentlinesWithEachPossibleValue(numberOfLines) + + linePairs.forEach { linePair -> + val (singleCageNotCoveredByLines, remainingSumIsEven) = calculateSingleCageCoveredByLines(grid, linePair, cache) + + singleCageNotCoveredByLines?.let { cage -> + val validPossibles = cache.possibles(cage) + + val indexesInLines = + cage.cells.mapIndexedNotNull { index, cell -> + if (linePair.any { line -> line.contains(cell) }) { + index + } else { + null + } + } + + val validPossiblesWithNeededSum = + validPossibles + .filter { + it + .filterIndexed { index, _ -> + indexesInLines.contains(index) + }.sum() + .mod(2) == if (remainingSumIsEven) 0 else 1 + } + + if (validPossiblesWithNeededSum.isNotEmpty() && validPossiblesWithNeededSum.size < validPossibles.size) { + val reducedPossibles = PossiblesReducer(cage).reduceToPossibleCombinations(validPossiblesWithNeededSum) + + if (reducedPossibles) { + return true + } + } + } + } + + return false + } + + private fun calculateSingleCageCoveredByLines( + grid: Grid, + lines: Set, + cache: PossiblesCache, + ): Pair { + val cages = lines.map { it.cages() }.flatten().toSet() + val lineCells = lines.map { it.cells() }.flatten().toSet() + + var cageEvenAndOddSums: GridCage? = null + var remainingSumIsEven = (grid.variant.possibleDigits.sum() * numberOfLines).mod(2) == 0 + + cages.forEach { cage -> + if (EvenOddSumUtils.hasOnlyEvenOrOddSumsInCells(grid, cage, lineCells, cache)) { + val even = EvenOddSumUtils.hasEvenSumsOnlyInCells(grid, cage, lineCells, cache) + + remainingSumIsEven = !remainingSumIsEven.xor(even) + } else if (cageEvenAndOddSums == null) { + cageEvenAndOddSums = cage + } else { + return Pair(null, true) + } + } + + return Pair(cageEvenAndOddSums, remainingSumIsEven) + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesSingleCagePossiblesSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesSingleCagePossiblesSum.kt new file mode 100644 index 00000000..ce3f32fd --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractLinesSingleCagePossiblesSum.kt @@ -0,0 +1,88 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +abstract class AbstractLinesSingleCagePossiblesSum( + private val numberOfLines: Int, +) : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val adjacentLinesSet = GridLines(grid).adjacentlinesWithEachPossibleValue(numberOfLines) + + adjacentLinesSet.forEach { adjacentLines -> + val (singleCageNotCoveredByLines, staticGridSum) = calculateSingleCageCoveredByLines(grid, adjacentLines, cache) + + singleCageNotCoveredByLines?.let { cage -> + val neededSumOfLines = grid.variant.possibleDigits.sum() * numberOfLines - staticGridSum + + val indexesInLines = + cage.cells.mapIndexedNotNull { index, cell -> + if (adjacentLines.any { line -> line.contains(cell) }) { + index + } else { + null + } + } + + val validPossibles = cache.possibles(cage) + val validPossiblesWithNeededSum = + validPossibles.filter { + it + .filterIndexed { index, _ -> + indexesInLines.contains(index) + }.sum() == neededSumOfLines + } + + if (validPossiblesWithNeededSum.isNotEmpty() && validPossiblesWithNeededSum.size < validPossibles.size) { + val reducedPossibles = PossiblesReducer(cage).reduceToPossibleCombinations(validPossiblesWithNeededSum) + + if (reducedPossibles) { + return true + } + } + } + } + + return false + } + + private fun calculateSingleCageCoveredByLines( + grid: Grid, + lines: Set, + cache: PossiblesCache, + ): Pair { + val cages = lines.map { it.cages() }.flatten().toSet() + val lineCells = lines.map { it.cells() }.flatten().toSet() + + var singleCageNotCoveredByLines: GridCage? = null + var staticGridSum = 0 + + cages.forEach { cage -> + val hasAtLeastOnePossibleInLines = + cage.cells + .filter { + lines.any { line -> line.contains(it) } + }.any { !it.isUserValueSet } + + if (!StaticSumUtils.hasStaticSumInCells(grid, cage, lineCells, cache)) { + if (singleCageNotCoveredByLines != null && hasAtLeastOnePossibleInLines) { + return Pair(null, 0) + } + + singleCageNotCoveredByLines = cage + } else { + staticGridSum += StaticSumUtils.staticSumInCells(grid, cage, lineCells, cache) + } + } + + return Pair(singleCageNotCoveredByLines, staticGridSum) + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractMinMaxSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractMinMaxSum.kt new file mode 100644 index 00000000..ab9beafe --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractMinMaxSum.kt @@ -0,0 +1,114 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +abstract class AbstractMinMaxSum( + private val numberOfLines: Int, +) : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val adjacentLinesSet = GridLines(grid).adjacentlinesWithEachPossibleValue(numberOfLines) + + val sumOfAdjacentLines = grid.variant.possibleDigits.sum() * numberOfLines + + adjacentLinesSet.forEach { lines -> + val lineCages = lines.flatMap { it.cages() }.toSet() + + lineCages.forEach { cage -> + + val otherCages = lineCages - cage + + val (minSum, maxSum) = minAndMaxSum(otherCages, lines, cache) + + val possibles = possiblesInLines(cage, lines, cache) + + val possiblesWithinSum = + possibles.filter { + it.sum() + minSum <= sumOfAdjacentLines && it.sum() + maxSum >= sumOfAdjacentLines + } + + if (possiblesWithinSum.size < possibles.size) { + val cellIndexes = cellIndexesInLine(cage, lines) + + val validPossibles = + cache.possibles(cage).filter { possibles -> + possiblesWithinSum.any { + it.contentEquals( + possibles.filterIndexed { index, _ -> cellIndexes.contains(index) }.toIntArray(), + ) + } + } + + val reduced = PossiblesReducer(cage).reduceToPossibleCombinations(validPossibles) + + if (reduced) { + return true + } + } + } + } + + return false + } + + private fun minAndMaxSum( + otherCages: Set, + lines: Set, + cache: PossiblesCache, + ): Pair { + var minSum = 0 + var maxSum = 0 + + otherCages.forEach { otherCage -> + val possiblesInLines = possiblesInLines(otherCage, lines, cache) + + minSum += requireNotNull(possiblesInLines.minByOrNull { it.sum() }).sum() + maxSum += requireNotNull(possiblesInLines.maxByOrNull { it.sum() }).sum() + } + + return Pair(minSum, maxSum) + } + + private fun possiblesInLines( + cage: GridCage, + lines: Set, + cache: PossiblesCache, + ): List { + val cellIndexesInLine = + cellIndexesInLine(cage, lines) + + val possiblesInLines = + cache.possibles(cage).map { + it + .filterIndexed { index, _ -> + cellIndexesInLine.contains(index) + }.toIntArray() + } + + return possiblesInLines + } + + private fun cellIndexesInLine( + cage: GridCage, + lines: Set, + ): List { + val cellIndexesInLine = + cage.cells + .mapIndexed { index, cell -> + if (lines.any { line -> line.contains(cell) }) { + index + } else { + null + } + }.filterNotNull() + return cellIndexesInLine + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractTwoCellsPossiblesSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractTwoCellsPossiblesSum.kt new file mode 100644 index 00000000..841b9eee --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/AbstractTwoCellsPossiblesSum.kt @@ -0,0 +1,85 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCell + +abstract class AbstractTwoCellsPossiblesSum( + private val numberOfLines: Int, +) : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val adjacentLinesSet = GridLines(grid).adjacentlinesWithEachPossibleValue(numberOfLines) + + adjacentLinesSet.forEach { adjacentLines -> + val (cellsNotCoveredByLines, staticGridSum) = calculateTwoCellsCoveredByLines(grid, adjacentLines, cache) + + if (cellsNotCoveredByLines.size == 2) { + val neededSumOfLines = grid.variant.possibleDigits.sum() * numberOfLines - staticGridSum + + var found = false + + cellsNotCoveredByLines.forEach { cell -> + val otherCell = (cellsNotCoveredByLines - cell).first() + + cell.possibles.forEach { possible -> + if (!otherCell.possibles.contains(neededSumOfLines - possible)) { + found = true + cell.possibles -= possible + } + } + } + + if (found) { + return true + } + } + } + + return false + } + + private fun calculateTwoCellsCoveredByLines( + grid: Grid, + lines: Set, + cache: PossiblesCache, + ): Pair, Int> { + val cages = lines.map { it.cages() }.flatten().toSet() + val lineCells = lines.map { it.cells() }.flatten().toSet() + + val cellsNotCoveredByLines = mutableListOf() + var staticGridSum = 0 + + cages.forEach { cage -> + val dynamicSumCells = + cage.cells + .filter { + lines.any { line -> line.contains(it) } + }.filter { !it.isUserValueSet } + + if (!StaticSumUtils.hasStaticSumInCells(grid, cage, lineCells, cache)) { + cellsNotCoveredByLines += dynamicSumCells + staticGridSum += + cage.cells + .filter { + lines.any { line -> line.contains(it) } + }.filter { it.isUserValueSet } + .map { it.userValue } + .sum() + + if (cellsNotCoveredByLines.size > 2) { + return Pair(emptyList(), 0) + } + } else { + staticGridSum += StaticSumUtils.staticSumInCells(grid, cage, lineCells, cache) + } + } + + return Pair(cellsNotCoveredByLines, staticGridSum) + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossibleUsedInLinesByOtherCagesDualLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossibleUsedInLinesByOtherCagesDualLines.kt new file mode 100644 index 00000000..64f064a1 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossibleUsedInLinesByOtherCagesDualLines.kt @@ -0,0 +1,70 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid + +class DetectPossibleUsedInLinesByOtherCagesDualLines : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val lines = GridLines(grid).adjacentlines(2) + + lines.forEach { dualLines -> + + val cellsOfLines = + dualLines.map { it.cells() }.flatten() + + val (cagesIntersectingWithLines, possiblesInLines) = GridLineHelper.getIntersectingCagesAndPossibles(dualLines, cache) + + cagesIntersectingWithLines.forEach { cage -> + val possiblesForFirstCage = possiblesInLines[cage]!! + + val possibleInEachFirstCageCombination = + grid.variant.possibleDigits.filter { possible -> + possiblesForFirstCage.all { it.contains(possible) } + } + + if (possibleInEachFirstCageCombination.isNotEmpty()) { + cagesIntersectingWithLines + .filter { it.id > cage.id } + .forEach { otherCage -> + val possiblesForOtherCage = possiblesInLines[otherCage]!! + + val possibleInEachOtherCageCombination = + grid.variant.possibleDigits.filter { possible -> + possiblesForOtherCage.all { it.contains(possible) } + } + + val possiblesInBothCages = + possibleInEachFirstCageCombination.intersect( + possibleInEachOtherCageCombination, + ) + + if (possiblesInBothCages.isNotEmpty()) { + val foreignCells = cellsOfLines - cage.cells - otherCage.cells + + var found = false + + possiblesInBothCages.forEach { possible -> + if (foreignCells.any { it.possibles.contains(possible) }) { + found = true + + foreignCells.forEach { it.possibles -= possible } + } + } + + if (found) { + return true + } + } + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossiblesBreakingOtherCagesPossiblesDualLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossiblesBreakingOtherCagesPossiblesDualLines.kt new file mode 100644 index 00000000..7243447e --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/DetectPossiblesBreakingOtherCagesPossiblesDualLines.kt @@ -0,0 +1,69 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid + +class DetectPossiblesBreakingOtherCagesPossiblesDualLines : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val lines = GridLines(grid).adjacentlines(2) + + lines.forEach { dualLines -> + + val cellsOfLines = + dualLines.map { it.cells() }.flatten() + + val cagesContainedInBothLines = + dualLines + .map { it.cages() } + .flatten() + .filter { it.cells.all { it.isUserValueSet || cellsOfLines.contains(it) } } + .filter { it.cells.any { !it.isUserValueSet } } + .toSet() + + cagesContainedInBothLines.forEach { cage -> + val combinations = cache.possibles(cage) + + combinations.forEach { combination -> + combination + .groupBy { it } + .filter { groupedSize -> + groupedSize.value.size == 2 && combinations.none { it.count { it == groupedSize.key } == 1 } + }.map { it.key } + .filter { doublePossible -> + cage.cells.none { it.userValue == doublePossible } + }.forEach { doublePossible -> + val otherCages = cagesContainedInBothLines - cage + + otherCages + .filter { it.cells.none { it.userValue == doublePossible } } + .forEach { otherCage -> + val eachPossibleEnforcesDoublePossible = + cache + .possibles(otherCage) + .all { it.contains(doublePossible) } + + if (eachPossibleEnforcesDoublePossible) { + val reducing = + PossiblesReducer(cage).reduceToPossibleCombinations( + combinations.filterNot { it.count { it == doublePossible } == 2 }, + ) + + if (reducing) { + return true + } + } + } + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/EvenOddSumUtils.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/EvenOddSumUtils.kt new file mode 100644 index 00000000..0bb4f5e9 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/EvenOddSumUtils.kt @@ -0,0 +1,115 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage +import org.piepmeyer.gauguin.grid.GridCageAction +import org.piepmeyer.gauguin.grid.GridCell + +object EvenOddSumUtils { + fun hasEvenSumsOnly( + grid: Grid, + cage: GridCage, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE) { + return cage.cells + .first() + .value + .mod(2) == 0 + } + + if (cage.action == GridCageAction.ACTION_ADD) { + return cage.result.mod(2) == 0 + } + + if (cage.cells.all { it.isUserValueSet }) { + return cage.cells + .map { it.userValue } + .sum() + .mod(2) == 0 + } + + return cache + .possibles(cage) + .map { it.sum() } + .distinct() + .all { it.mod(2) == 0 } + } + + fun hasEvenSumsOnlyInCells( + grid: Grid, + cage: GridCage, + cells: Set, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE) { + return if (cage.cells.first() in cells) { + cage.cells + .first() + .value + .mod(2) == 0 + } else { + false + } + } + + val filteredCells = cage.cells.filter { it in cells } + + if (filteredCells.all { it.isUserValueSet }) { + return filteredCells.sumOf { it.userValue }.mod(2) == 0 + } + + return cache + .possibles(cage) + .map { it.filterIndexed { index, _ -> cage.cells[index] in cells } } + .map { it.sum() } + .distinct() + .all { it.mod(2) == 0 } + } + + fun hasOnlyEvenOrOddSums( + grid: Grid, + cage: GridCage, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE || cage.action == GridCageAction.ACTION_ADD) { + return true + } + + if (cage.cells.all { it.isUserValueSet }) { + return true + } + + val validPossiblesSums = + cache + .possibles(cage) + .map { it.sum() } + .map { it.mod(2) == 0 } + .distinct() + + return validPossiblesSums.size == 1 + } + + fun hasOnlyEvenOrOddSumsInCells( + grid: Grid, + cage: GridCage, + cells: Set, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE && cage.cells.first() in cells) { + return true + } + + val validPossiblesSums = + cache + .possibles(cage) + .map { it.filterIndexed { index, _ -> cage.cells[index] in cells } } + .map { it.sum() } + .map { it.mod(2) == 0 } + .distinct() + + return validPossiblesSums.size == 1 + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridLineHelper.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridLineHelper.kt new file mode 100644 index 00000000..aef12f6e --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridLineHelper.kt @@ -0,0 +1,38 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCage + +object GridLineHelper { + fun getIntersectingCagesAndPossibles( + dualLines: Set, + cache: PossiblesCache, + ): Pair, Map>>> { + val cellsOfLines = + dualLines.map { it.cells() }.flatten() + + val cagesIntersectingWithLines = + dualLines + .map { it.cages() } + .flatten() + .filter { it.cells.any { !it.isUserValueSet && cellsOfLines.contains(it) } } + .toSet() + + val possiblesInLines = + cagesIntersectingWithLines.associateWith { cage -> + cache + .possibles(cage) + .map { + it.filterIndexed { + index, + _, + -> + !cage.cells[index].isUserValueSet && cellsOfLines.contains(cage.cells[index]) + } + }.toSet() + } + + return Pair(cagesIntersectingWithLines, possiblesInLines) + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridSumEnforcesCageSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridSumEnforcesCageSum.kt new file mode 100644 index 00000000..5ade926d --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/GridSumEnforcesCageSum.kt @@ -0,0 +1,49 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +/* + * Calculates the sum of all cages having a static cage sum. If there is exactly one cage with a + * dynamic sum, calculate the remaining sum of it and delete all possibles which do not lead to this + * sum. + */ +class GridSumEnforcesCageSum : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + var cageWithDynamicSum: GridCage? = null + var staticGridSum = 0 + + grid.cages.forEach { cage -> + if (StaticSumUtils.hasStaticSum(grid, cage, cache)) { + staticGridSum += StaticSumUtils.staticSum(grid, cage, cache) + } else if (cageWithDynamicSum == null) { + cageWithDynamicSum = cage + } else { + return false + } + } + + cageWithDynamicSum?.let { cage -> + val neededSumOfCage = grid.variant.possibleDigits.sum() * grid.gridSize.smallestSide() - staticGridSum + + val validPossibles = cache.possibles(cage) + val validPossiblesWithNeededSum = validPossibles.filter { it.sum() == neededSumOfCage } + + if (validPossiblesWithNeededSum.size < validPossibles.size) { + val reducedPossibles = PossiblesReducer(cage).reduceToPossibleCombinations(validPossiblesWithNeededSum) + + if (reducedPossibles) { + return true + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/ImpossibleCombinationInLineDetector.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/ImpossibleCombinationInLineDetector.kt new file mode 100644 index 00000000..6f717a73 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/ImpossibleCombinationInLineDetector.kt @@ -0,0 +1,72 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +private val logger = KotlinLogging.logger {} + +object ImpossibleCombinationInLineDetector { + fun fillCells( + grid: Grid, + cache: PossiblesCache, + isImpossible: (GridLine, GridCage, cache: PossiblesCache, List) -> Boolean, + ): Boolean { + val lines = GridLines(grid).linesWithEachPossibleValue() + + lines.forEach { line -> + line.cages().forEach { cage -> + val validPossibles = + cache.possibles(cage) + + val lineCageCells = + cage.cells + .filter { line.contains(it) && !it.isUserValueSet } + + lineCageCells.forEach { cell -> + val cellIndex = cage.cells.indexOf(cell) + + logger.trace { "analysing $line, $cage, $cell" } + + val validPossiblesOfCell = validPossibles.map { it[cellIndex] } + + val validPossiblesSetOfCell = validPossiblesOfCell.distinct() + + val possiblesWithSingleCombination = + validPossiblesSetOfCell.filter { validPossible -> + validPossiblesOfCell.count { it == validPossible } == 1 + } + + logger.trace { "set of possible cell values: $validPossiblesSetOfCell" } + logger.trace { "set of possible cell values with single combination: $possiblesWithSingleCombination" } + + possiblesWithSingleCombination.forEach { singleCombinationPossible -> + val lineCageCellsIndexes = + lineCageCells + .map { cage.cells.indexOf(it) } + + val singlePossible = + validPossibles + .first { it[cellIndex] == singleCombinationPossible } + .filterIndexed { index, _ -> + lineCageCellsIndexes.contains(index) + } + + logger.trace { "Relevant line indexes: $lineCageCellsIndexes" } + logger.trace { "Single possible: $singlePossible" } + + if (singlePossible.isNotEmpty() && isImpossible.invoke(line, cage, cache, singlePossible)) { + cell.removePossible(singleCombinationPossible) + return true + } + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LineSingleCagePossiblesSumSingle.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LineSingleCagePossiblesSumSingle.kt new file mode 100644 index 00000000..d7cdaf61 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LineSingleCagePossiblesSumSingle.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans a single line to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class LineSingleCagePossiblesSumSingle : AbstractLinesSingleCagePossiblesSum(1) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumDual.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumDual.kt new file mode 100644 index 00000000..c6439bb6 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumDual.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans two adjacent lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class LinesSingleCagePossiblesSumDual : AbstractLinesSingleCagePossiblesSum(2) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumTriple.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumTriple.kt new file mode 100644 index 00000000..12481f4e --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/LinesSingleCagePossiblesSumTriple.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans three adjacent lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class LinesSingleCagePossiblesSumTriple : AbstractLinesSingleCagePossiblesSum(3) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumOneLine.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumOneLine.kt new file mode 100644 index 00000000..1e43be28 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumOneLine.kt @@ -0,0 +1,3 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +class MinMaxSumOneLine : AbstractMinMaxSum(1) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumThreeLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumThreeLines.kt new file mode 100644 index 00000000..d34bd92b --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumThreeLines.kt @@ -0,0 +1,3 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +class MinMaxSumThreeLines : AbstractMinMaxSum(3) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumTwoLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumTwoLines.kt new file mode 100644 index 00000000..e9f93cba --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/MinMaxSumTwoLines.kt @@ -0,0 +1,3 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +class MinMaxSumTwoLines : AbstractMinMaxSum(2) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPair.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPair.kt new file mode 100644 index 00000000..014b7058 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPair.kt @@ -0,0 +1,59 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCell + +/** + * Finds a naked pair, that is two cells in the same row or column which have to same list of + * exactly two possible values. As these values could not occur in any other cells beside these + * two, these values get deleted from the other cages possibles. + */ +class NakedPair : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val cellsWithoutUserValue = grid.cells.filter { !it.isUserValueSet } + + cellsWithoutUserValue.forEach { cell -> + cellsWithoutUserValue.forEach { otherCell -> + if (isNakedPair(cell, otherCell)) { + val possibles = cell.possibles + + val cellsOfSameRowOrColumn = + if (cell.row == otherCell.row) { + grid.getCellsAtSameRow(cell) - otherCell + } else { + grid.getCellsAtSameColumn(cell) - otherCell + } + + val cellsWithPossibles = + cellsOfSameRowOrColumn + .filter { !it.isUserValueSet } + .filter { it.possibles.intersect(possibles).isNotEmpty() } + + if (cellsWithPossibles.isNotEmpty()) { + cellsWithPossibles.forEach { + it.possibles -= possibles + } + + return true + } + } + } + } + + return false + } + + private fun isNakedPair( + cell: GridCell, + otherCell: GridCell, + ) = cell != otherCell && + (cell.row == otherCell.row || cell.column == otherCell.column) && + cell.possibles.size == 2 && + otherCell.possibles.size == 2 && + cell.possibles.containsAll(otherCell.possibles) +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTriple.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTriple.kt new file mode 100644 index 00000000..59d0e3e2 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTriple.kt @@ -0,0 +1,59 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid + +private val logger = KotlinLogging.logger {} + +/** + * Finds a naked triple, that is three cells in the same row or column which have to same list of + * exactly two possible values. As these values could not occur in any other cells beside these + * two, these values get deletes from the other cages possibles. + */ +class NakedTriple : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + GridLines(grid) + .allLines() + .map { it.cells() } + .forEach { lineCells -> + val relevantCells = lineCells.filter { !it.isUserValueSet && it.possibles.size <= 3 } + + if (relevantCells.size >= 3) { + relevantCells.forEach { cellOne -> + (relevantCells - cellOne).forEach { cellTwo -> + (relevantCells - cellOne - cellTwo).forEach { cellThree -> + val possibles = cellOne.possibles + cellTwo.possibles + cellThree.possibles + + if (possibles.size == 3) { + val otherCellsWithPossibles = + (lineCells - cellOne - cellTwo - cellThree) + .filter { !it.isUserValueSet } + .filter { it.possibles.intersect(possibles).isNotEmpty() } + + if (otherCellsWithPossibles.isNotEmpty()) { + otherCellsWithPossibles.forEach { + it.possibles -= possibles + } + + logger.debug { + "Naked triple found: ${cellOne.cellNumber}, ${cellTwo.cellNumber}, ${cellThree.cellNumber}" + } + + return true + } + } + } + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NumberOfCagesWithPossibleForcesPossibleInCage.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NumberOfCagesWithPossibleForcesPossibleInCage.kt new file mode 100644 index 00000000..b4119ece --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NumberOfCagesWithPossibleForcesPossibleInCage.kt @@ -0,0 +1,110 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid + +/** + * Scans the whole grid for each possible value analysing if the number of possibles contained in + * the cages: + * + * - Calculates the occurrences of the possible which must be fulfilled via undecided cells. + * - Calculates the set of cages with a static count of possibles in each combination. + * - Calculates the set of cages with a dynamic count of possibles. + * - If the static set already fulfills the needed amount of possible, delete all possible + * combinations of the dynamic cages which contain this possible. + * - If the missing count of occurrences 1 and there is exactly one dynamic cage, delete all + * possible combinations from this cage that do not contain the needed possible. + */ +class NumberOfCagesWithPossibleForcesPossibleInCage : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + grid.variant.possibleDigits.forEach { possible -> + val numberOfPossiblesLeft = + grid.variant.gridSize.smallestSide() - grid.cells.count { it.userValue == possible } + + val cagesWithPossible = + grid.cages + .filter { it.cells.any { !it.isUserValueSet } } + .filter { it.cells.any { it.possibles.contains(possible) } } + + if (cagesWithPossible.isNotEmpty()) { + val cagesWithStaticNumberOfPossible = + cagesWithPossible.filter { cage -> + val staticPossibleCount = + cache + .possibles(cage) + .first() + .filterIndexed { index, _ -> !cage.cells[index].isUserValueSet } + .count { it == possible } + + cache + .possibles(cage) + .map { + it.filterIndexed { index, _ -> !cage.cells[index].isUserValueSet } + }.all { possibleCombination -> + possibleCombination.count { it == possible } == staticPossibleCount + } + } + + val staticNumberOfPossibles = + cagesWithStaticNumberOfPossible.sumOf { cage -> + cache + .possibles(cage) + .first() + .filterIndexed { index, _ -> !cage.cells[index].isUserValueSet } + .count { it == possible } + } + + val cagesWithDynamicNumberOfPossible = + cagesWithPossible - cagesWithStaticNumberOfPossible.toSet() + + if (staticNumberOfPossibles == numberOfPossiblesLeft) { + /* + * All possibles are already contained in the static cages, so we delete + * the possible from all other cages. + */ + cagesWithDynamicNumberOfPossible.forEach { dynamicCage -> + val reduced = + PossiblesReducer(dynamicCage).reduceToPossibleCombinations( + cache + .possibles(dynamicCage) + .filter { + it + .filterIndexed { index, value -> + !dynamicCage.cells[index].isUserValueSet && value == possible + }.isEmpty() + }, + ) + + if (reduced) { + return true + } + } + } else if (cagesWithDynamicNumberOfPossible.size == 1 && staticNumberOfPossibles == numberOfPossiblesLeft - 1) { + val dynamicCage = cagesWithDynamicNumberOfPossible.first() + + val reduced = + PossiblesReducer(dynamicCage).reduceToPossibleCombinations( + cache + .possibles(dynamicCage) + .filter { + it + .filterIndexed { index, _ -> !dynamicCage.cells[index].isUserValueSet } + .count { it == possible } == 1 + }, + ) + + if (reduced) { + return true + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckGridSum.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckGridSum.kt new file mode 100644 index 00000000..fb852f38 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckGridSum.kt @@ -0,0 +1,49 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +/* + * Calculates the even/odd sum of all cages having a static cage sum. If there is exactly one cage + * with a dynamic even/odd sum, the even/odd state of the remaining cage gets calculated. All + * combinations which do not lead to such a even or odd sum get deleted. + */ +class OddEvenCheckGridSum : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + var cageEvenAndOddSums: GridCage? = null + var remainingSumIsEven = (grid.variant.possibleDigits.sum() * grid.gridSize.smallestSide()).mod(2) == 0 + + grid.cages.forEach { cage -> + if (EvenOddSumUtils.hasOnlyEvenOrOddSums(grid, cage, cache)) { + val even = EvenOddSumUtils.hasEvenSumsOnly(grid, cage, cache) + + remainingSumIsEven = !remainingSumIsEven.xor(even) + } else if (cageEvenAndOddSums == null) { + cageEvenAndOddSums = cage + } else { + return false + } + } + + cageEvenAndOddSums?.let { cage -> + val validPossibles = cache.possibles(cage) + val validPossiblesWithNeededSum = validPossibles.filter { it.sum().mod(2) == if (remainingSumIsEven) 0 else 1 } + + if (validPossiblesWithNeededSum.size < validPossibles.size) { + val reducedPossibles = PossiblesReducer(cage).reduceToPossibleCombinations(validPossiblesWithNeededSum) + + if (reducedPossibles) { + return true + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumDual.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumDual.kt new file mode 100644 index 00000000..af2060c4 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumDual.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans two adjacent lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class OddEvenCheckSumDual : AbstractLinesOddEvenCheckSum(2) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumSingle.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumSingle.kt new file mode 100644 index 00000000..635ecffb --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumSingle.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans one line to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class OddEvenCheckSumSingle : AbstractLinesOddEvenCheckSum(1) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumTriple.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumTriple.kt new file mode 100644 index 00000000..aaa31fa8 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/OddEvenCheckSumTriple.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans three adjacent lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class OddEvenCheckSumTriple : AbstractLinesOddEvenCheckSum(3) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLine.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLine.kt new file mode 100644 index 00000000..db75cadc --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLine.kt @@ -0,0 +1,66 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLines +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +class PossibleMustBeContainedInSingleCageInLine : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + val lines = GridLines(grid).linesWithEachPossibleValue() + + lines.forEach { line -> + for (singlePossible in grid.variant.possibleDigits) { + val cagesWithPossible = + line + .cells() + .filter { it.possibles.contains(singlePossible) } + .map { it.cage!! } + .toSet() + + if (cagesWithPossible.size == 1) { + val cage = cagesWithPossible.first() + + val validPossibles = + cache + .possibles(cage) + .filter { + it.withIndex().any { possibleWithIndex -> + possibleWithIndex.value == singlePossible && + line.contains(cage.cells[possibleWithIndex.index]) + } + } + + if (validPossibles.isNotEmpty()) { + if (deletePossibleInSingleCage(cage, validPossibles)) return true + } + } + } + } + + return false + } + + private fun deletePossibleInSingleCage( + cage: GridCage, + validPossibles: List, + ): Boolean { + for (cellNumber in 0.. + line + .cages() + .filter { it.cells.any { !it.isUserValueSet } } + .forEach { cage -> + + val validPossibles = + cache + .possibles(cage) + .map { + it.filterIndexed { index, _ -> + line.contains(cage.cells[index]) + } + } + + if (validPossibles.isNotEmpty()) { + val possibleDigitsAlwaysInLine = + grid.variant.possibleDigits.filter { possible -> + validPossibles.all { it.contains(possible) } + } + + if (deletePossibleInSingleCage( + line, + cage, + possibleDigitsAlwaysInLine, + ) + ) { + return true + } + } + } + } + + return false + } + + private fun deletePossibleInSingleCage( + line: GridLine, + cage: GridCage, + possiblesToBeDeleted: List, + ): Boolean { + line + .cells() + .filter { it.cage != cage && !it.isUserValueSet } + .forEach { cell -> + possiblesToBeDeleted.forEach { possibleToBeDeleted -> + if (cell.possibles.contains(possibleToBeDeleted)) { + println("In line deletion: $line, cage to ignore $cage, $possibleToBeDeleted") + cell.removePossible(possibleToBeDeleted) + + return true + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCageCombinations.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCageCombinations.kt new file mode 100644 index 00000000..284fe5c0 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCageCombinations.kt @@ -0,0 +1,30 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.difficulty.human.PossiblesReducer +import org.piepmeyer.gauguin.grid.Grid + +/** + * Looks out if a cage's cells contain possibles which are not included in any + * valid combination. If so, deletes these possibles out of all the cage's + * cells. + */ +class RemoveImpossibleCageCombinations : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + grid.cages + .filter { it.cells.any { !it.isUserValueSet } } + .forEach { cage -> + val reducedPossibles = PossiblesReducer(cage).reduceToPossibleCombinations(cache.possibles(cage)) + + if (reducedPossibles) { + return true + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfPossiblesOfOtherCage.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfPossiblesOfOtherCage.kt new file mode 100644 index 00000000..d3403dbd --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfPossiblesOfOtherCage.kt @@ -0,0 +1,54 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +/* + * Detects and deletes possibles if a possible is included in a single combination + * of the cage and that combination may not be chosen because there is another cell + * in the line which only has possibles left contained in the single combination + */ +class RemoveImpossibleCombinationInLineBecauseOfPossiblesOfOtherCage : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean = ImpossibleCombinationInLineDetector.fillCells(grid, cache, this::isImpossible) + + private fun isImpossible( + line: GridLine, + cage: GridCage, + cache: PossiblesCache, + singlePossible: List, + ): Boolean { + line + .cages() + .filter { it != cage } + .forEach { otherCage -> + val validPossiblesOtherCage = + cache.possibles(otherCage) + + val otherCageLineCellsIndexes = + otherCage.cells + .filter { line.contains(it) && !it.isUserValueSet } + .map { otherCage.cells.indexOf(it) } + + val allPossiblesInvalid = + validPossiblesOtherCage.all { validPossibles -> + validPossibles + .filterIndexed { index, _ -> + otherCageLineCellsIndexes.contains(index) + }.intersect(singlePossible.toSet()) + .isNotEmpty() + } + + if (allPossiblesInvalid) { + return true + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfSingleCell.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfSingleCell.kt new file mode 100644 index 00000000..3d5872f4 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineBecauseOfSingleCell.kt @@ -0,0 +1,40 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.piepmeyer.gauguin.difficulty.human.GridLine +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage + +private val logger = KotlinLogging.logger {} + +/* + * Detects and deletes possibles if a possible is included in a single combination + * of the cage and that combination may not be chosen because there is another cell + * in the line which only has possibles left contained in the single combination + */ +class RemoveImpossibleCombinationInLineBecauseOfSingleCell : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean = ImpossibleCombinationInLineDetector.fillCells(grid, cache, this::isImpossible) + + private fun isImpossible( + line: GridLine, + cage: GridCage, + cache: PossiblesCache, + singlePossible: List, + ): Boolean { + line + .cells() + .filter { it.cage() != cage && !it.isUserValueSet } + .forEach { otherCell -> + if (singlePossible.containsAll(otherCell.possibles)) { + return true + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemovePossibleWithoutCombination.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemovePossibleWithoutCombination.kt new file mode 100644 index 00000000..e825cf62 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemovePossibleWithoutCombination.kt @@ -0,0 +1,36 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid + +/** + * Calculates all possible combinations per cage and deletes one possible that is not contained + * in one of the combinations. + */ +class RemovePossibleWithoutCombination : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + grid.cages + .filter { it.cells.any { !it.isUserValueSet } } + .forEach { cage -> + val possibles = cache.possibles(cage) + + cage.cells.forEachIndexed { index, cageCell -> + if (!cageCell.isUserValueSet) { + cageCell.possibles.forEach { possibleValue -> + if (possibles.none { it[index] == possibleValue }) { + cageCell.removePossible(possibleValue) + + return true + } + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/SinglePossibleInCage.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/SinglePossibleInCage.kt new file mode 100644 index 00000000..7c8b9707 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/SinglePossibleInCage.kt @@ -0,0 +1,33 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid + +class SinglePossibleInCage : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + grid.cages + .filter { it.cells.any { !it.isUserValueSet } } + .forEach { cage -> + val validPossibles = cache.possibles(cage) + + for (cellNumber in 0.. + line + .cells() + .filter { !it.isUserValueSet } + .forEach { cell -> + val otherCellsInLine = line.cells() - cell + + cell.possibles.forEach { possible -> + if (otherCellsInLine + .map { it.possibles } + .none { it.contains(possible) } + ) { + grid.setUserValueAndRemovePossibles(cell, possible) + + return true + } + } + } + } + + return false + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/StaticSumUtils.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/StaticSumUtils.kt new file mode 100644 index 00000000..2807a66b --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/StaticSumUtils.kt @@ -0,0 +1,94 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid +import org.piepmeyer.gauguin.grid.GridCage +import org.piepmeyer.gauguin.grid.GridCageAction +import org.piepmeyer.gauguin.grid.GridCell + +object StaticSumUtils { + fun staticSum( + grid: Grid, + cage: GridCage, + cache: PossiblesCache, + ): Int { + if (cage.cageType == GridCageType.SINGLE) { + return cage.cells.first().value + } + + if (cage.cells.all { it.isUserValueSet }) { + return cage.cells.map { it.userValue }.sum() + } + + return cache + .possibles(cage) + .first() + .sum() + } + + fun staticSumInCells( + grid: Grid, + cage: GridCage, + cells: Set, + cache: PossiblesCache, + ): Int { + if (cage.cageType == GridCageType.SINGLE) { + return if (cage.cells.first() in cells) { + cage.cells.first().value + } else { + 0 + } + } + + val filteredCells = cage.cells.filter { it in cells } + + if (filteredCells.all { it.isUserValueSet }) { + return filteredCells.sumOf { it.userValue } + } + + return cache + .possibles(cage) + .map { it.filterIndexed { index, _ -> cage.cells[index] in cells } } + .first() + .sum() + } + + fun hasStaticSum( + grid: Grid, + cage: GridCage, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE || cage.action == GridCageAction.ACTION_ADD) { + return true + } + + val validPossiblesSums = + cache + .possibles(cage) + .map { it.sum() } + .distinct() + + return validPossiblesSums.size == 1 + } + + fun hasStaticSumInCells( + grid: Grid, + cage: GridCage, + cells: Set, + cache: PossiblesCache, + ): Boolean { + if (cage.cageType == GridCageType.SINGLE && cage.cells.first() in cells) { + return true + } + + val validPossiblesSums = + cache + .possibles(cage) + .map { it.filterIndexed { index, _ -> cage.cells[index] in cells } } + .map { it.sum() } + .distinct() + + return validPossiblesSums.size == 1 + } +} diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumSingleLine.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumSingleLine.kt new file mode 100644 index 00000000..7dd2dcd2 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumSingleLine.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans a single line to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class TwoCellsPossiblesSumSingleLine : AbstractTwoCellsPossiblesSum(1) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumThreeLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumThreeLines.kt new file mode 100644 index 00000000..1d544f21 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumThreeLines.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans three lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class TwoCellsPossiblesSumThreeLines : AbstractTwoCellsPossiblesSum(3) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumTwoLines.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumTwoLines.kt new file mode 100644 index 00000000..202d09d1 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/TwoCellsPossiblesSumTwoLines.kt @@ -0,0 +1,8 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +/** + * Scans two lines to find that each part of cage contained in this lines has a static sum + * excluding one part of cage. The sum of this part of cages is calculated all enforced by deleting + * deviant possibles. + */ +class TwoCellsPossiblesSumTwoLines : AbstractTwoCellsPossiblesSum(2) diff --git a/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/XWing.kt b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/XWing.kt new file mode 100644 index 00000000..ddb37719 --- /dev/null +++ b/gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/XWing.kt @@ -0,0 +1,84 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import org.piepmeyer.gauguin.difficulty.human.HumanSolverStrategy +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.Grid + +class XWing : HumanSolverStrategy { + override fun fillCells( + grid: Grid, + cache: PossiblesCache, + ): Boolean { + for (x in 0.. { + return cells.filter { it.row == cell.row && it != cell } + } + + fun getCellsAtSameColumn(cell: GridCell): List { + return cells.filter { it.column == cell.column && it != cell } + } + val options: GameOptionsVariant get() = variant.options diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultyBalanceTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultyBalanceTest.kt new file mode 100644 index 00000000..4c66e3be --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultyBalanceTest.kt @@ -0,0 +1,54 @@ +package org.piepmeyer.gauguin.difficulty.human + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.game.save.SaveGame +import java.io.File +import java.nio.file.Files +import java.util.stream.Collectors +import kotlin.io.path.isDirectory +import kotlin.io.path.name + +private val logger = KotlinLogging.logger {} + +class HumanDifficultyBalanceTest : + FunSpec({ + test("balancing") { + val savedGames = + Files + .list(File("src/test/resources/difficulty-balancing").toPath()) + .collect(Collectors.toList()) + .filter { !it.isDirectory() } + + val namesToGrids = + savedGames + .associateWith { + val grid = SaveGame.createWithFile(it.toFile()).restore()!! + grid.clearUserValues() + grid.addPossiblesAtNewGame() + + grid + }.mapKeys { it.key.name } + + val namesToDifficulties = + namesToGrids.mapValues { + logger.info { it.key + "..." } + + val result = HumanSolver(it.value).solveAndCalculateDifficulty() + + withClue(it.key) { + result.success shouldBe true + } + + result.difficulty + } + + namesToDifficulties.entries + .sortedBy { it.value } + .forEach { + logger.info { "${it.value} -> ${it.key}" } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverHandpickedTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverHandpickedTest.kt new file mode 100644 index 00000000..d03dadf4 --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverHandpickedTest.kt @@ -0,0 +1,88 @@ +package org.piepmeyer.gauguin.difficulty.human + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.MergingCageGridCalculator +import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler +import org.piepmeyer.gauguin.creation.SeedRandomizerMock +import org.piepmeyer.gauguin.grid.GridSize +import org.piepmeyer.gauguin.options.GameOptionsVariant +import org.piepmeyer.gauguin.options.GameVariant + +class HumanDifficultySolverHandpickedTest : + FunSpec({ + test("seed random grid should be solved") { + val randomizer = SeedRandomizerMock(16) + + val calculator = + MergingCageGridCalculator( + GameVariant( + GridSize(3, 6), + GameOptionsVariant.createClassic(), + ), + randomizer, + RandomPossibleDigitsShuffler(randomizer.random), + ) + + val grid = calculator.calculate() + grid.cells.forEach { it.possibles = grid.variant.possibleDigits } + + val solver = HumanSolver(grid, true) + + solver.solveAndCalculateDifficulty() + + println(grid.toString()) + + grid.isSolved() shouldBe true + } + + test("merging algo 4x4 wrong solution") { + val randomizer = SeedRandomizerMock(6096) + + val calculator = + MergingCageGridCalculator( + GameVariant( + GridSize(4, 4), + GameOptionsVariant.createClassic(), + ), + randomizer, + RandomPossibleDigitsShuffler(randomizer.random), + ) + + val grid = calculator.calculate() + grid.cells.forEach { it.possibles = grid.variant.possibleDigits } + + val solver = HumanSolver(grid) + + solver.solveAndCalculateDifficulty() + + println(grid.toString()) + + grid.isSolved() shouldBe true + } + + test("merging algo 4x4 DetectPossiblesBreakingOtherCagesPossiblesDualLines bug") { + val randomizer = SeedRandomizerMock(36) + + val calculator = + MergingCageGridCalculator( + GameVariant( + GridSize(5, 5), + GameOptionsVariant.createClassic(), + ), + randomizer, + RandomPossibleDigitsShuffler(randomizer.random), + ) + + val grid = calculator.calculate() + grid.cells.forEach { it.possibles = grid.variant.possibleDigits } + + val solver = HumanSolver(grid) + + solver.solveAndCalculateDifficulty() + + println(grid.toString()) + + grid.isSolved() shouldBe true + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverPerformanceTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverPerformanceTest.kt new file mode 100644 index 00000000..a51d3376 --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverPerformanceTest.kt @@ -0,0 +1,53 @@ +package org.piepmeyer.gauguin.difficulty.human + +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.RandomCageGridCalculator +import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler +import org.piepmeyer.gauguin.creation.SeedRandomizerMock +import org.piepmeyer.gauguin.grid.GridSize +import org.piepmeyer.gauguin.options.GameOptionsVariant +import org.piepmeyer.gauguin.options.GameVariant + +class HumanDifficultySolverPerformanceTest : + FunSpec({ + for (seed in 0..9) { + withClue("seed $seed") { + + test("seed random grid should be solved") { + val randomizer = SeedRandomizerMock(1) + + val calculator = + RandomCageGridCalculator( + GameVariant( + GridSize(11, 11), + GameOptionsVariant.createClassic(), + ), + randomizer, + RandomPossibleDigitsShuffler(randomizer.random), + ) + + val grid = calculator.calculate() + grid.cells.forEach { it.possibles = grid.variant.possibleDigits } + + val solver = HumanSolver(grid) + + val solverResult = solver.solveAndCalculateDifficulty() + + println(grid.toString()) + + if (!grid.isSolved()) { + if (grid.numberOfMistakes() != 0) { + throw IllegalStateException("Found a grid with wrong values.") + } + } + + grid.isSolved() shouldBe true + + solverResult.difficulty shouldBeGreaterThan 0 + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverTest.kt new file mode 100644 index 00000000..d5fbe93e --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/HumanDifficultySolverTest.kt @@ -0,0 +1,70 @@ +package org.piepmeyer.gauguin.difficulty.human + +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.MergingCageGridCalculator +import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler +import org.piepmeyer.gauguin.creation.SeedRandomizerMock +import org.piepmeyer.gauguin.grid.GridSize +import org.piepmeyer.gauguin.options.GameOptionsVariant +import org.piepmeyer.gauguin.options.GameVariant + +class HumanDifficultySolverTest : + FunSpec({ + for (seed in 0..999) { + // 10_000 of 4x4, random: 4 left unsolved + // 10_000 of 4x4, merge: 19 left unsolved + // 10_000 of 5x5, merge: 134 left unsolved + // 10_000 of 2x4, merge: no (!) left unsolved + // 1_000 of 3x6, merge: 119 left unsolved + // 100 of 9x9, merge: 51 left unsolved + withClue("seed $seed") { + test("seed random grid should be solved") { + val randomizer = SeedRandomizerMock(seed) + + val calculator = + MergingCageGridCalculator( + GameVariant( + GridSize(3, 6), + GameOptionsVariant.createClassic(), + ), + randomizer, + RandomPossibleDigitsShuffler(randomizer.random), + ) + + val grid = calculator.calculate() + grid.cells.forEach { it.possibles = grid.variant.possibleDigits } + + val solver = HumanSolver(grid, true) + + val solverResult = solver.solveAndCalculateDifficulty() + + println(grid.toString()) + + if (!grid.isSolved()) { + if (grid.numberOfMistakes() != 0) { + throw IllegalStateException("Found a grid with wrong values.") + } + grid.isActive = true + grid.startedToBePlayed = true + grid.description = "${grid.gridSize.width}x${grid.gridSize.height}-$seed" + /*val saveGame = + SaveGame.createWithFile( + File( + SaveGame.SAVEGAME_NAME_PREFIX + + "${grid.numberOfMistakes()}-${grid.gridSize.width}x${grid.gridSize.height}-$seed.yml", + ), + ) + + saveGame.save(grid)*/ + } + + grid.isSolved() shouldBe true + + solverResult.difficulty shouldBeGreaterThan 0 + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/LinesPossiblesSumDualTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/LinesPossiblesSumDualTest.kt new file mode 100644 index 00000000..8757bf1e --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/LinesPossiblesSumDualTest.kt @@ -0,0 +1,80 @@ +package org.piepmeyer.gauguin.difficulty.human + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.strategy.LinesSingleCagePossiblesSumDual +import org.piepmeyer.gauguin.grid.GridCageAction + +class LinesPossiblesSumDualTest : + FunSpec({ + + test("4x4 grid") { + val grid = + GridBuilder(4, 4) + .addSingleCage(3, 3) + .addSingleCage(2, 14) + .addCage( + 24, + GridCageAction.ACTION_MULTIPLY, + GridCageType.L_HORIZONTAL_SHORT_RIGHT_BOTTOM, + 0, + ).addCage( + 1, + GridCageAction.ACTION_SUBTRACT, + GridCageType.DOUBLE_VERTICAL, + 4, + ).addCage( + 9, + GridCageAction.ACTION_ADD, + GridCageType.ANGLE_RIGHT_TOP, + 5, + ).addCage( + 8, + GridCageAction.ACTION_MULTIPLY, + GridCageType.TRIPLE_VERTICAL, + 7, + ).addCage( + 3, + GridCageAction.ACTION_DIVIDE, + GridCageType.DOUBLE_HORIZONTAL, + 12, + ).createGrid() + + grid.cells[0].possibles = setOf(2, 4) + grid.cells[1].possibles = setOf(2, 4) + grid.cells[2].userValue = 1 + grid.cells[3].userValue = 3 + grid.cells[4].possibles = setOf(1, 2, 4) + grid.cells[5].possibles = setOf(2, 4) + grid.cells[6].userValue = 3 + grid.cells[7].possibles = setOf(1, 2) + grid.cells[8].possibles = setOf(1, 2, 3) + grid.cells[9].possibles = setOf(1, 3) + grid.cells[10].userValue = 4 + grid.cells[11].possibles = setOf(1, 2) + grid.cells[12].possibles = setOf(1, 3) + grid.cells[13].possibles = setOf(1, 3) + grid.cells[14].userValue = 2 + grid.cells[15].userValue = 4 + + val solver = LinesSingleCagePossiblesSumDual() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("cell 4") { + grid.cells[4].possibles shouldContainExactly setOf(2, 4) + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPairTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPairTest.kt new file mode 100644 index 00000000..8e45a138 --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedPairTest.kt @@ -0,0 +1,49 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCageAction + +class NakedPairTest : + FunSpec({ + + test("4x1 grid") { + val grid = + GridBuilder(4, 1) + .addSingleCage(2, 2) + .addSingleCage(4, 3) + .addCage( + 3, + GridCageAction.ACTION_MULTIPLY, + GridCageType.DOUBLE_HORIZONTAL, + 0, + ).createGrid() + + grid.cells[0].possibles = setOf(1, 3) + grid.cells[1].possibles = setOf(1, 3) + grid.cells[2].possibles = setOf(1, 2, 3, 4) + grid.cells[3].possibles = setOf(1, 2, 3, 4) + + val solver = NakedPair() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("possibles should be deleted from other cells") { + grid.cells[2].possibles shouldContainExactly setOf(2, 4) + grid.cells[3].possibles shouldContainExactly setOf(2, 4) + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTripleTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTripleTest.kt new file mode 100644 index 00000000..ac988b42 --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/NakedTripleTest.kt @@ -0,0 +1,85 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCageAction + +class NakedTripleTest : + FunSpec({ + + test("naked triple with all three possibles in each cell") { + val grid = + GridBuilder(5, 1) + .addSingleCage(4, 3) + .addSingleCage(5, 4) + .addCage( + 6, + GridCageAction.ACTION_MULTIPLY, + GridCageType.TRIPLE_HORIZONTAL, + 0, + ).createGrid() + + grid.cells[0].possibles = setOf(1, 2, 3) + grid.cells[1].possibles = setOf(1, 2, 3) + grid.cells[2].possibles = setOf(1, 2, 3) + grid.cells[3].possibles = setOf(1, 2, 3, 4, 5) + grid.cells[4].possibles = setOf(1, 2, 3, 4, 5) + + val solver = NakedTriple() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("possibles should be deleted from other cells") { + grid.cells[3].possibles shouldContainExactly setOf(4, 5) + grid.cells[4].possibles shouldContainExactly setOf(4, 5) + } + } + } + + test("naked triple with different possible combination in each cell") { + val grid = + GridBuilder(5, 1) + .addSingleCage(4, 3) + .addSingleCage(5, 4) + .addCage( + 6, + GridCageAction.ACTION_MULTIPLY, + GridCageType.TRIPLE_HORIZONTAL, + 0, + ).createGrid() + + grid.cells[0].possibles = setOf(1, 2) + grid.cells[1].possibles = setOf(2, 3) + grid.cells[2].possibles = setOf(1, 3) + grid.cells[3].possibles = setOf(1, 2, 3, 4, 5) + grid.cells[4].possibles = setOf(1, 2, 3, 4, 5) + + val solver = NakedTriple() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("possibles should be deleted from other cells") { + grid.cells[3].possibles shouldContainExactly setOf(4, 5) + grid.cells[4].possibles shouldContainExactly setOf(4, 5) + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineDeleteFromOtherCagesTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineDeleteFromOtherCagesTest.kt new file mode 100644 index 00000000..9db0e1a5 --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineDeleteFromOtherCagesTest.kt @@ -0,0 +1,79 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCageAction + +class PossibleMustBeContainedInSingleCageInLineDeleteFromOtherCagesTest : + FunSpec({ + + test("2x6 grid") { + val grid = + GridBuilder(2, 6) + .addCage( + 72, + GridCageAction.ACTION_MULTIPLY, + GridCageType.ANGLE_RIGHT_BOTTOM, + 0, + ).addCage( + 9, + GridCageAction.ACTION_ADD, + GridCageType.ANGLE_LEFT_TOP, + 3, + ).addCage( + 10, + GridCageAction.ACTION_MULTIPLY, + GridCageType.TRIPLE_VERTICAL, + 6, + ).addCage( + 12, + GridCageAction.ACTION_ADD, + GridCageType.TRIPLE_VERTICAL, + 7, + ).createGrid() + + // first column + grid.cells[0].possibles = setOf(2, 3, 4, 6) + grid.cells[2].possibles = setOf(4, 6) + grid.cells[4].possibles = setOf(2, 3, 4, 5, 6) + grid.cells[6].possibles = setOf(1, 2, 5) + grid.cells[8].possibles = setOf(1, 2, 5) + grid.cells[10].possibles = setOf(1, 2) + // second column + grid.cells[1].possibles = setOf(3, 4, 6) + grid.cells[3].possibles = setOf(1, 2, 4) + grid.cells[5].possibles = setOf(2, 3, 4, 5, 6) + grid.cells[7].possibles = setOf(1, 2, 3, 4, 5, 6) + grid.cells[9].possibles = setOf(1, 2, 3, 4, 5, 6) + grid.cells[11].possibles = setOf(1, 2, 3, 4, 6) + + val solver = PossibleMustBeContainedInSingleCageInLineDeleteFromOtherCages() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("cell 0") { + grid.cells[0].possibles shouldContainExactly setOf(3, 4, 6) + } + withClue("cell 2") { + grid.cells[2].possibles shouldContainExactly setOf(4, 6) + } + withClue("cell 4") { + grid.cells[4].possibles shouldContainExactly setOf(3, 4, 6) + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineTest.kt new file mode 100644 index 00000000..a8344b6d --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/PossibleMustBeContainedInSingleCageInLineTest.kt @@ -0,0 +1,48 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCageAction + +class PossibleMustBeContainedInSingleCageInLineTest : + FunSpec({ + + test("1x5 grid and 5x1 grid with same data") { + for ((width, height) in mapOf(1 to 5, 5 to 1)) { + withClue("width $width, height $height") { + val grid = + GridBuilder(width, height) + .addSingleCage(2, 0) + .addCage( + 10, + GridCageAction.ACTION_ADD, + if (width == 1) GridCageType.TRIPLE_VERTICAL else GridCageType.TRIPLE_HORIZONTAL, + 1, + ).addSingleCage(2, 4) + .createGrid() + + grid.cells[0].possibles = setOf(3, 4) + grid.cells[1].possibles = setOf(2, 3, 4) + grid.cells[2].userValue = 5 + grid.cells[3].possibles = setOf(1, 2, 3) + grid.cells[4].possibles = setOf(1, 4) + + println(grid) + + val solver = PossibleMustBeContainedInSingleCageInLine() + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + grid.cells[1].possibles shouldContainExactly setOf(2, 3) + grid.cells[3].possibles shouldContainExactly setOf(2, 3) + } + } + } + }) diff --git a/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineTest.kt b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineTest.kt new file mode 100644 index 00000000..9c698f4b --- /dev/null +++ b/gauguin-core/src/test/kotlin/org/piepmeyer/gauguin/difficulty/human/strategy/RemoveImpossibleCombinationInLineTest.kt @@ -0,0 +1,150 @@ +package org.piepmeyer.gauguin.difficulty.human.strategy + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.piepmeyer.gauguin.creation.GridBuilder +import org.piepmeyer.gauguin.creation.cage.GridCageType +import org.piepmeyer.gauguin.difficulty.human.PossiblesCache +import org.piepmeyer.gauguin.grid.GridCageAction + +class RemoveImpossibleCombinationInLineTest : + FunSpec({ + + test("4x4 grid") { + val grid = + GridBuilder(4, 4) + .addSingleCage(1, 3) + .addSingleCage(4, 10) + .addCage( + 24, + GridCageAction.ACTION_MULTIPLY, + GridCageType.TRIPLE_HORIZONTAL, + 0, + ).addCage( + 24, + GridCageAction.ACTION_MULTIPLY, + GridCageType.L_VERTICAL_SHORT_RIGHT_TOP, + 4, + ).addCage( + 8, + GridCageAction.ACTION_ADD, + GridCageType.ANGLE_LEFT_BOTTOM, + 6, + ).addCage( + 2, + GridCageAction.ACTION_DIVIDE, + GridCageType.DOUBLE_VERTICAL, + 9, + ).addCage( + 1, + GridCageAction.ACTION_SUBTRACT, + GridCageType.DOUBLE_HORIZONTAL, + 14, + ).createGrid() + + grid.cells[0].possibles = setOf(3, 4) + grid.cells[1].possibles = setOf(3, 4) + grid.cells[2].userValue = 2 + grid.cells[3].userValue = 1 + grid.cells[4].possibles = setOf(1, 2, 3, 4) + grid.cells[5].possibles = setOf(3, 4) + grid.cells[6].possibles = setOf(1, 3) + grid.cells[7].possibles = setOf(2, 4) + grid.cells[8].possibles = setOf(1, 2) + grid.cells[9].possibles = setOf(1, 2) + grid.cells[10].userValue = 4 + grid.cells[11].userValue = 3 + grid.cells[12].possibles = setOf(1, 2, 3, 4) + grid.cells[13].possibles = setOf(1, 2) + grid.cells[14].possibles = setOf(1, 3) + grid.cells[15].possibles = setOf(2, 4) + + val solver = RemoveImpossibleCombinationInLineBecauseOfSingleCell() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("cell 14") { + grid.cells[14].possibles shouldContainExactly setOf(3) + } + } + } + + test("4x4 grid impossible combinations") { + val grid = + GridBuilder(4, 4) + .addSingleCage(2, 3) + .addSingleCage(1, 13) + .addCage( + 3, + GridCageAction.ACTION_DIVIDE, + GridCageType.DOUBLE_VERTICAL, + 0, + ).addCage( + 9, + GridCageAction.ACTION_ADD, + GridCageType.ANGLE_LEFT_BOTTOM, + 1, + ).addCage( + 6, + GridCageAction.ACTION_MULTIPLY, + GridCageType.DOUBLE_VERTICAL, + 5, + ).addCage( + 8, + GridCageAction.ACTION_ADD, + GridCageType.TRIPLE_VERTICAL, + 7, + ).addCage( + 2, + GridCageAction.ACTION_DIVIDE, + GridCageType.DOUBLE_VERTICAL, + 8, + ).addCage( + 1, + GridCageAction.ACTION_SUBTRACT, + GridCageType.DOUBLE_VERTICAL, + 10, + ).createGrid() + + grid.cells[0].possibles = setOf(1, 3) + grid.cells[1].userValue = 4 + grid.cells[2].possibles = setOf(1, 3) + grid.cells[3].userValue = 2 + grid.cells[4].possibles = setOf(1, 3) + grid.cells[5].possibles = setOf(2, 3) + grid.cells[6].possibles = setOf(2, 4) + grid.cells[7].possibles = setOf(1, 3, 4) + grid.cells[8].possibles = setOf(2, 4) + grid.cells[9].possibles = setOf(2, 3) + grid.cells[10].possibles = setOf(1, 2, 3, 4) + grid.cells[11].possibles = setOf(1, 3, 4) + grid.cells[12].possibles = setOf(2, 4) + grid.cells[13].userValue = 1 + grid.cells[14].possibles = setOf(2, 3, 4) + grid.cells[15].possibles = setOf(3, 4) + + val solver = RemoveImpossibleCombinationInLineBecauseOfPossiblesOfOtherCage() + + println(grid) + + // solver should find two possibles and delete one of them for each run + solver.fillCells(grid, PossiblesCache(grid)) shouldBe true + + println(grid) + + assertSoftly { + withClue("cell 2") { + grid.cells[2].possibles shouldContainExactly setOf(1) + } + } + } + }) diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-easy-but-not-so-simple.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-easy-but-not-so-simple.yml new file mode 100644 index 00000000..b5d17f4d --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-easy-but-not-so-simple.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":3,"height":3},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"NO_SINGLE_CAGES","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723225807344,"playTimeInMilliseconds":26563,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":0,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":6,"cellNumbers":[0,3,4]},{"id":1,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":6,"cellNumbers":[1,2]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[5,8]},{"id":3,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[6,7]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-simple.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-simple.yml new file mode 100644 index 00000000..fd9a7b3c --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_3x3-simple.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":3,"height":3},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723225631857,"playTimeInMilliseconds":27047,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":8,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":8,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"TRIPLE_VERTICAL","result":6,"cellNumbers":[2,5,8]},{"id":1,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":4,"cellNumbers":[3,6,7]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":7,"cellNumbers":[0,1,4]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_3x5-5-possibles-should-be-eliminated.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_3x5-5-possibles-should-be-eliminated.yml new file mode 100644 index 00000000..c270e33a --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_3x5-5-possibles-should-be-eliminated.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":3,"height":5},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723272996505,"playTimeInMilliseconds":3527,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":3,"row":1,"column":0,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":4,"row":1,"column":1,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":5,"row":1,"column":2,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":6,"row":2,"column":0,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":7,"row":2,"column":1,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":8,"row":2,"column":2,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":9,"row":3,"column":0,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":10,"row":3,"column":1,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":11,"row":3,"column":2,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":12,"row":4,"column":0,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":13,"row":4,"column":1,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":14,"row":4,"column":2,"value":1,"userValue":2147483647,"possibles":[]}],"selectedCellNumber":null,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":3,"cellNumbers":[4]},{"id":1,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[3,6]},{"id":2,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":20,"cellNumbers":[7,8]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_TOP","result":15,"cellNumbers":[11,14,13]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":10,"cellNumbers":[9,10,12]},{"id":5,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":48,"cellNumbers":[0,1,2,5]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-0-4x4-82.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-0-4x4-82.yml new file mode 100644 index 00000000..67453cd5 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-0-4x4-82.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722159495623,"playTimeInMilliseconds":0,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":2147483647,"possibles":[2,3,4]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":3,"row":0,"column":3,"value":4,"userValue":2147483647,"possibles":[1,2,3,4]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2147483647,"possibles":[2,3,4]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":2147483647,"possibles":[1,2,3]}],"selectedCellNumber":null,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[6]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":1,"cellNumbers":[9]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":6,"cellNumbers":[0,4,5]},{"id":3,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":10,"cellNumbers":[1,2,3,7]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":10,"cellNumbers":[8,12,13]},{"id":5,"action":"ACTION_ADD","type":"SQUARE","result":9,"cellNumbers":[10,11,14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-2712-sum-of-to-lines-with-incomplete-frist-cage-gets-to-value-of-minus-one.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-2712-sum-of-to-lines-with-incomplete-frist-cage-gets-to-value-of-minus-one.yml new file mode 100644 index 00000000..9b4fbbe2 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-2712-sum-of-to-lines-with-incomplete-frist-cage-gets-to-value-of-minus-one.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722159539505,"playTimeInMilliseconds":0,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2147483647,"possibles":[1,2,4]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2147483647,"possibles":[2,4]}],"selectedCellNumber":null,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":1,"cellNumbers":[13]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[4]},{"id":2,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":8,"cellNumbers":[0,1,2]},{"id":3,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":9,"cellNumbers":[3,7,11,10]},{"id":4,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[5,6]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":8,"cellNumbers":[8,9,12]},{"id":6,"action":"ACTION_ADD","type":"DOUBLE_HORIZONTAL","result":6,"cellNumbers":[14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-a-tetris-t-brainfuck-but-solvable.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-a-tetris-t-brainfuck-but-solvable.yml new file mode 100644 index 00000000..c5105a27 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-a-tetris-t-brainfuck-but-solvable.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722160870079,"playTimeInMilliseconds":103732,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":2147483647,"possibles":[3]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":2147483647,"possibles":[3]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":2147483647,"possibles":[3]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":2147483647,"possibles":[3]}],"selectedCellNumber":13,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"TETRIS_T_LEFT_UP","result":9,"cellNumbers":[4,8,9,12]},{"id":1,"action":"ACTION_ADD","type":"TETRIS_T","result":8,"cellNumbers":[0,1,2,5]},{"id":2,"action":"ACTION_ADD","type":"TETRIS_T_RIGHT_UP","result":11,"cellNumbers":[3,6,7,11]},{"id":3,"action":"ACTION_MULTIPLY","type":"TETRIS_T_BOTTOM_UP","result":48,"cellNumbers":[10,13,14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-doppelte-zwei-nicht-moeglich.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-doppelte-zwei-nicht-moeglich.yml new file mode 100644 index 00000000..0ba23c1a --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-doppelte-zwei-nicht-moeglich.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723385002222,"playTimeInMilliseconds":116955,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2147483647,"possibles":[1,2,4]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":12,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[6]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":3,"cellNumbers":[8]},{"id":2,"action":"ACTION_MULTIPLY","type":"SQUARE","result":12,"cellNumbers":[0,1,4,5]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_BOTTOM","result":12,"cellNumbers":[2,3,7]},{"id":4,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":8,"cellNumbers":[9,10,11,15]},{"id":5,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":24,"cellNumbers":[12,13,14]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-and-smooth.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-and-smooth.yml new file mode 100644 index 00000000..30a143d7 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-and-smooth.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723319581539,"playTimeInMilliseconds":56239,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":4,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[12,13]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":7,"cellNumbers":[11,15,14]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":5,"cellNumbers":[6,10,9]},{"id":3,"action":"ACTION_MULTIPLY","type":"TETRIS_T_LEFT_UP","result":72,"cellNumbers":[0,4,5,8]},{"id":4,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":32,"cellNumbers":[1,2,3,7]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-may-be-missing-xwing.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-may-be-missing-xwing.yml new file mode 100644 index 00000000..0754379b --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-may-be-missing-xwing.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723716757705,"playTimeInMilliseconds":47244,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":14,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":5,"cellNumbers":[0,4]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":9,"cellNumbers":[11,15,14]},{"id":2,"action":"ACTION_MULTIPLY","type":"SQUARE","result":18,"cellNumbers":[8,9,12,13]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":7,"cellNumbers":[6,7,10]},{"id":4,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":24,"cellNumbers":[1,2,3,5]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-much-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-much-too-high.yml new file mode 100644 index 00000000..92a6acf7 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-much-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723717884585,"playTimeInMilliseconds":55612,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":6,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"DOUBLE_HORIZONTAL","result":5,"cellNumbers":[2,3]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":8,"cellNumbers":[8,12,13]},{"id":2,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_BOTTOM","result":4,"cellNumbers":[0,1,4]},{"id":3,"action":"ACTION_ADD","type":"SQUARE","result":9,"cellNumbers":[5,6,9,10]},{"id":4,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":12,"cellNumbers":[7,11,15,14]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-rating-matches.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-rating-matches.yml new file mode 100644 index 00000000..0c8db59a --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-rating-matches.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723800783333,"playTimeInMilliseconds":50719,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":4,"userValue":4,"possibles":[]}],"selectedCellNumber":15,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[1]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":3,"cellNumbers":[7]},{"id":2,"action":"ACTION_MULTIPLY","type":"TRIPLE_VERTICAL","result":12,"cellNumbers":[0,4,8]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_BOTTOM","result":12,"cellNumbers":[2,3,6]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":7,"cellNumbers":[5,9,10]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":6,"cellNumbers":[11,15,14]},{"id":6,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[12,13]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium-very-smooth.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium-very-smooth.yml new file mode 100644 index 00000000..8c2dce77 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium-very-smooth.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722086532646,"playTimeInMilliseconds":52039,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":1,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[6]},{"id":1,"action":"ACTION_DIVIDE","type":"DOUBLE_VERTICAL","result":3,"cellNumbers":[8,12]},{"id":2,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":8,"cellNumbers":[0,4]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_TOP","result":8,"cellNumbers":[7,11,10]},{"id":4,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":9,"cellNumbers":[1,2,3,5]},{"id":5,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_TOP","result":10,"cellNumbers":[9,13,14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium.yml new file mode 100644 index 00000000..767bbb6a --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-to-medium.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723274372399,"playTimeInMilliseconds":47345,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":4,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[3,7]},{"id":1,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[4,8]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":7,"cellNumbers":[11,15,14]},{"id":3,"action":"ACTION_ADD","type":"TRIPLE_VERTICAL","result":8,"cellNumbers":[2,6,10]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":4,"cellNumbers":[0,1,5]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":10,"cellNumbers":[9,13,12]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-too-high.yml new file mode 100644 index 00000000..ce892ba8 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-easy-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723716870483,"playTimeInMilliseconds":37638,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":13,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":3,"cellNumbers":[8,12]},{"id":1,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":10,"cellNumbers":[0,1,2,4]},{"id":2,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":8,"cellNumbers":[5,6]},{"id":3,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_TOP","result":10,"cellNumbers":[9,13,14,15]},{"id":4,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":11,"cellNumbers":[3,7,11,10]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-but-no-question-mark-rated-too-strong.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-but-no-question-mark-rated-too-strong.yml new file mode 100644 index 00000000..50532178 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-but-no-question-mark-rated-too-strong.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722084385333,"playTimeInMilliseconds":108051,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":15,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[14,15]},{"id":1,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[2,3]},{"id":2,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":5,"cellNumbers":[8,12]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_BOTTOM","result":8,"cellNumbers":[6,7,11]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":6,"cellNumbers":[9,10,13]},{"id":5,"action":"ACTION_ADD","type":"SQUARE","result":12,"cellNumbers":[0,1,4,5]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-not-too-hard.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-not-too-hard.yml new file mode 100644 index 00000000..c4bf49e8 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard-not-too-hard.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722086060839,"playTimeInMilliseconds":65174,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":11,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[2,3]},{"id":1,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":8,"cellNumbers":[13,14,15]},{"id":2,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_TOP","result":24,"cellNumbers":[7,11,10]},{"id":3,"action":"ACTION_MULTIPLY","type":"TETRIS_T_LEFT_UP","result":12,"cellNumbers":[4,8,9,12]},{"id":4,"action":"ACTION_MULTIPLY","type":"TETRIS_HORIZONTAL_LEFT_TOP","result":16,"cellNumbers":[0,1,5,6]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard.yml new file mode 100644 index 00000000..1d022563 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-hard.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"NO_SINGLE_CAGES","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722087482831,"playTimeInMilliseconds":379500,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":4,"userValue":4,"possibles":[]}],"selectedCellNumber":8,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_DIVIDE","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[0,4]},{"id":1,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":24,"cellNumbers":[1,2,3,5]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[6,7]},{"id":3,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":9,"cellNumbers":[8,9,10,14]},{"id":4,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[11,15]},{"id":5,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[12,13]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-is-really-hard.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-is-really-hard.yml new file mode 100644 index 00000000..fbdccab4 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-is-really-hard.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723735921614,"playTimeInMilliseconds":836385,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":0,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[11,15]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":7,"cellNumbers":[2,3,7]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":9,"cellNumbers":[0,1,4]},{"id":3,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_RIGHT_TOP","result":24,"cellNumbers":[10,12,13,14]},{"id":4,"action":"ACTION_ADD","type":"TETRIS_HORIZONTAL_RIGHT_TOP","result":9,"cellNumbers":[5,6,8,9]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-in-flow-no-breaks-fast-to-play.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-in-flow-no-breaks-fast-to-play.yml new file mode 100644 index 00000000..9790fa78 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-in-flow-no-breaks-fast-to-play.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722086673656,"playTimeInMilliseconds":52167,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":4,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":1,"cellNumbers":[9]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":3,"cellNumbers":[15]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[0,4]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":9,"cellNumbers":[1,5,6]},{"id":4,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[2,3]},{"id":5,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_TOP","result":12,"cellNumbers":[7,11,10]},{"id":6,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[8,12]},{"id":7,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[13,14]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-rated-much-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-rated-much-too-high.yml new file mode 100644 index 00000000..1d022563 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-rated-much-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"NO_SINGLE_CAGES","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722087482831,"playTimeInMilliseconds":379500,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":4,"userValue":4,"possibles":[]}],"selectedCellNumber":8,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_DIVIDE","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[0,4]},{"id":1,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":24,"cellNumbers":[1,2,3,5]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[6,7]},{"id":3,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":9,"cellNumbers":[8,9,10,14]},{"id":4,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[11,15]},{"id":5,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[12,13]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-too-high.yml new file mode 100644 index 00000000..5bac67f0 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723319765246,"playTimeInMilliseconds":93631,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":4,"userValue":4,"possibles":[]}],"selectedCellNumber":3,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":3,"cellNumbers":[3,7]},{"id":1,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":12,"cellNumbers":[8,12,13]},{"id":2,"action":"ACTION_MULTIPLY","type":"SQUARE","result":48,"cellNumbers":[10,11,14,15]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":8,"cellNumbers":[0,1,4]},{"id":4,"action":"ACTION_ADD","type":"TETRIS_VERTICAL_RIGHT_TOP","result":10,"cellNumbers":[2,5,6,9]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium_question-mark.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium_question-mark.yml new file mode 100644 index 00000000..6501103f --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-medium_question-mark.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722084196546,"playTimeInMilliseconds":133591,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":13,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[8,12]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":5,"cellNumbers":[9,10,13]},{"id":2,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":13,"cellNumbers":[7,11,15,14]},{"id":3,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":6,"cellNumbers":[4,5,6]},{"id":4,"action":"ACTION_ADD","type":"FOUR_HORIZONTAL","result":10,"cellNumbers":[0,1,2,3]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-no-brainer.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-no-brainer.yml new file mode 100644 index 00000000..c7afea68 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-no-brainer.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722083776533,"playTimeInMilliseconds":84884,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":12,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":2,"cellNumbers":[5]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[0]},{"id":2,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":7,"cellNumbers":[1,2,3,7]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":9,"cellNumbers":[4,8,9]},{"id":4,"action":"ACTION_MULTIPLY","type":"TRIPLE_VERTICAL","result":8,"cellNumbers":[6,10,14]},{"id":5,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":12,"cellNumbers":[11,15]},{"id":6,"action":"ACTION_ADD","type":"DOUBLE_HORIZONTAL","result":6,"cellNumbers":[12,13]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-not-easy-rated-too-low.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-not-easy-rated-too-low.yml new file mode 100644 index 00000000..1bf0f92d --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-not-easy-rated-too-low.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"DYNAMIC","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723802131326,"playTimeInMilliseconds":356071,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":3,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":5,"cellNumbers":[0,4]},{"id":1,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":7,"cellNumbers":[1,2,3]},{"id":2,"action":"ACTION_DIVIDE","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[5,9]},{"id":3,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_RIGHT_TOP","result":11,"cellNumbers":[6,10,14,7]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":8,"cellNumbers":[8,12,13]},{"id":5,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[11,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-overall_sum_but_rest_easy.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-overall_sum_but_rest_easy.yml new file mode 100644 index 00000000..623930e5 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-overall_sum_but_rest_easy.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722767174418,"playTimeInMilliseconds":85591,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":11,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":2,"cellNumbers":[2]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[9]},{"id":2,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_RIGHT_TOP","result":11,"cellNumbers":[0,4,8,1]},{"id":3,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":10,"cellNumbers":[3,7,11,10]},{"id":4,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[5,6]},{"id":5,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[12,13]},{"id":6,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-schwierig-herausfordernd-gut-zu-spielen.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-schwierig-herausfordernd-gut-zu-spielen.yml new file mode 100644 index 00000000..6c72604b --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-schwierig-herausfordernd-gut-zu-spielen.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723710949433,"playTimeInMilliseconds":150954,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":4,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[3,7]},{"id":1,"action":"ACTION_DIVIDE","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[9,10]},{"id":2,"action":"ACTION_MULTIPLY","type":"L_VERTICAL_SHORT_RIGHT_TOP","result":12,"cellNumbers":[4,8,12,5]},{"id":3,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_BOTTOM","result":8,"cellNumbers":[0,1,2,6]},{"id":4,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_RIGHT_TOP","result":24,"cellNumbers":[11,13,14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-seems-hard-but-is-easy-to-medium-rated-much-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-seems-hard-but-is-easy-to-medium-rated-much-too-high.yml new file mode 100644 index 00000000..1212a202 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-seems-hard-but-is-easy-to-medium-rated-much-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722086306583,"playTimeInMilliseconds":92151,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":4,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[12,13]},{"id":1,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[14,15]},{"id":2,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":3,"cellNumbers":[3,7]},{"id":3,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":12,"cellNumbers":[0,1,2]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":8,"cellNumbers":[4,5,8]},{"id":5,"action":"ACTION_MULTIPLY","type":"TETRIS_T_BOTTOM_UP","result":32,"cellNumbers":[6,9,10,11]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-simple-hopping-from-cage-to-cage.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-simple-hopping-from-cage-to-cage.yml new file mode 100644 index 00000000..42a636b0 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-simple-hopping-from-cage-to-cage.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722086172613,"playTimeInMilliseconds":59390,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":5,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":1,"cellNumbers":[12]},{"id":1,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[2,3]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[0,1]},{"id":3,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":4,"cellNumbers":[11,15]},{"id":4,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_TOP","result":32,"cellNumbers":[10,14,13]},{"id":5,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":12,"cellNumbers":[4,8,9]},{"id":6,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":12,"cellNumbers":[5,6,7]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-smooth-playble-easy-rated-too-high.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-smooth-playble-easy-rated-too-high.yml new file mode 100644 index 00000000..4aec7726 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-smooth-playble-easy-rated-too-high.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723273099272,"playTimeInMilliseconds":47162,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":12,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":12,"cellNumbers":[3,7]},{"id":1,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_BOTTOM","result":8,"cellNumbers":[0,1,4]},{"id":2,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":4,"cellNumbers":[5,9]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":8,"cellNumbers":[8,12,13]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":7,"cellNumbers":[11,15,14]},{"id":5,"action":"ACTION_MULTIPLY","type":"TRIPLE_VERTICAL","result":6,"cellNumbers":[2,6,10]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-start-of-hard-no-question-mark.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-start-of-hard-no-question-mark.yml new file mode 100644 index 00000000..3686a591 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-start-of-hard-no-question-mark.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1722085401750,"playTimeInMilliseconds":77929,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":12,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[0]},{"id":1,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[3,7]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":4,"cellNumbers":[1,2,6]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":7,"cellNumbers":[4,5,8]},{"id":4,"action":"ACTION_MULTIPLY","type":"TETRIS_T_BOTTOM_UP","result":36,"cellNumbers":[9,12,13,14]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":7,"cellNumbers":[10,11,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-sum-of-lower-two-lines.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-sum-of-lower-two-lines.yml new file mode 100644 index 00000000..d34dd74d --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-sum-of-lower-two-lines.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723479252940,"playTimeInMilliseconds":77642,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":4,"row":1,"column":0,"value":3,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":6,"row":1,"column":2,"value":4,"userValue":2147483647,"possibles":[1,4]},{"cellNumber":7,"row":1,"column":3,"value":2,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":2147483647,"possibles":[1,4]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":2147483647,"possibles":[1,3,4]},{"cellNumber":12,"row":3,"column":0,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":1,"userValue":2147483647,"possibles":[1,2,4]}],"selectedCellNumber":10,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[8,12]},{"id":1,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":5,"cellNumbers":[3,7]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":8,"cellNumbers":[11,15,14]},{"id":3,"action":"ACTION_ADD","type":"DOUBLE_VERTICAL","result":5,"cellNumbers":[6,10]},{"id":4,"action":"ACTION_MULTIPLY","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":24,"cellNumbers":[0,1,2,4]},{"id":5,"action":"ACTION_MULTIPLY","type":"TRIPLE_VERTICAL","result":6,"cellNumbers":[5,9,13]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-summe-spalten-links.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-summe-spalten-links.yml new file mode 100644 index 00000000..6eb84762 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-summe-spalten-links.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723385339079,"playTimeInMilliseconds":86708,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":2,"row":0,"column":2,"value":2,"userValue":2147483647,"possibles":[1,2,4]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":2,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2147483647,"possibles":[1,2]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":11,"row":2,"column":3,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2147483647,"possibles":[2,4]}],"selectedCellNumber":15,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":1,"cellNumbers":[13]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":4,"cellNumbers":[4]},{"id":2,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":8,"cellNumbers":[0,1,2]},{"id":3,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":9,"cellNumbers":[3,7,11,10]},{"id":4,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[5,6]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":8,"cellNumbers":[8,9,12]},{"id":6,"action":"ACTION_ADD","type":"DOUBLE_HORIZONTAL","result":6,"cellNumbers":[14,15]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-super-fast.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-super-fast.yml new file mode 100644 index 00000000..744c54cb --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-super-fast.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723273237838,"playTimeInMilliseconds":47789,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":4,"userValue":4,"possibles":[]}],"selectedCellNumber":10,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":6,"cellNumbers":[2,3]},{"id":1,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":12,"cellNumbers":[11,15]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":3,"cellNumbers":[0,1]},{"id":3,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":6,"cellNumbers":[12,13,14]},{"id":4,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":6,"cellNumbers":[5,6,7]},{"id":5,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_TOP","result":11,"cellNumbers":[4,8,9,10]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-very-easy.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-very-easy.yml new file mode 100644 index 00000000..f7be646a --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-very-easy.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723716673190,"playTimeInMilliseconds":46304,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":3,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_BOTTOM","result":48,"cellNumbers":[2,3,7]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":5,"cellNumbers":[0,1,4]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":11,"cellNumbers":[8,12,13]},{"id":3,"action":"ACTION_ADD","type":"SQUARE","result":9,"cellNumbers":[5,6,9,10]},{"id":4,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":4,"cellNumbers":[11,15,14]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-viel-einfacher-als-angegeben.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-viel-einfacher-als-angegeben.yml new file mode 100644 index 00000000..af73095f --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4-viel-einfacher-als-angegeben.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723729469983,"playTimeInMilliseconds":51955,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":9,"row":2,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":12,"row":3,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":3,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":15,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":0,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[0,1]},{"id":1,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":12,"cellNumbers":[14,15]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_HORIZONTAL","result":1,"cellNumbers":[10,11]},{"id":3,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":8,"cellNumbers":[2,3,7]},{"id":4,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":6,"cellNumbers":[8,12,13]},{"id":5,"action":"ACTION_ADD","type":"TETRIS_T","result":10,"cellNumbers":[4,5,6,9]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_4x4_sum_of_two_right_column_eliminates_possible_at_12x.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4_sum_of_two_right_column_eliminates_possible_at_12x.yml new file mode 100644 index 00000000..77456702 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_4x4_sum_of_two_right_column_eliminates_possible_at_12x.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":4,"height":4},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1728039415968,"playTimeInMilliseconds":998120,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":3,"userValue":2147483647,"possibles":[2,3,4]},{"cellNumber":1,"row":0,"column":1,"value":2,"userValue":2147483647,"possibles":[1,2,3]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":4,"row":1,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":5,"row":1,"column":1,"value":3,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":6,"row":1,"column":2,"value":2,"userValue":2147483647,"possibles":[2,3]},{"cellNumber":7,"row":1,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":8,"row":2,"column":0,"value":2,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":9,"row":2,"column":1,"value":4,"userValue":2147483647,"possibles":[2,4]},{"cellNumber":10,"row":2,"column":2,"value":1,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":11,"row":2,"column":3,"value":3,"userValue":2147483647,"possibles":[1,3]},{"cellNumber":12,"row":3,"column":0,"value":4,"userValue":2147483647,"possibles":[3,4]},{"cellNumber":13,"row":3,"column":1,"value":1,"userValue":2147483647,"possibles":[1,3,4]},{"cellNumber":14,"row":3,"column":2,"value":3,"userValue":2147483647,"possibles":[1,3,4]},{"cellNumber":15,"row":3,"column":3,"value":2,"userValue":2,"possibles":[]}],"selectedCellNumber":null,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_ADD","type":"ANGLE_LEFT_BOTTOM","result":9,"cellNumbers":[2,3,7]},{"id":1,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":6,"cellNumbers":[0,1,4]},{"id":2,"action":"ACTION_MULTIPLY","type":"TRIPLE_HORIZONTAL","result":12,"cellNumbers":[12,13,14]},{"id":3,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_BOTTOM","result":6,"cellNumbers":[10,11,15]},{"id":4,"action":"ACTION_MULTIPLY","type":"TETRIS_HORIZONTAL_RIGHT_TOP","result":48,"cellNumbers":[5,6,8,9]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-702.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-702.yml new file mode 100644 index 00000000..ea7e095b --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-702.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":5,"height":5},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"DYNAMIC","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723981472850,"playTimeInMilliseconds":269753,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":5,"userValue":5,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":4,"row":0,"column":4,"value":3,"userValue":3,"possibles":[]},{"cellNumber":5,"row":1,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":6,"row":1,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":7,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":1,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":9,"row":1,"column":4,"value":5,"userValue":5,"possibles":[]},{"cellNumber":10,"row":2,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":11,"row":2,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":12,"row":2,"column":2,"value":5,"userValue":5,"possibles":[]},{"cellNumber":13,"row":2,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":14,"row":2,"column":4,"value":2,"userValue":2,"possibles":[]},{"cellNumber":15,"row":3,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":16,"row":3,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":17,"row":3,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":18,"row":3,"column":3,"value":5,"userValue":5,"possibles":[]},{"cellNumber":19,"row":3,"column":4,"value":4,"userValue":4,"possibles":[]},{"cellNumber":20,"row":4,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":21,"row":4,"column":1,"value":5,"userValue":5,"possibles":[]},{"cellNumber":22,"row":4,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":23,"row":4,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":24,"row":4,"column":4,"value":1,"userValue":1,"possibles":[]}],"selectedCellNumber":19,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"DOUBLE_VERTICAL","result":15,"cellNumbers":[4,9]},{"id":1,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":1,"cellNumbers":[0,5]},{"id":2,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[17,22]},{"id":3,"action":"ACTION_DIVIDE","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[1,6]},{"id":4,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_BOTTOM","result":6,"cellNumbers":[2,3,7]},{"id":5,"action":"ACTION_ADD","type":"ANGLE_RIGHT_TOP","result":7,"cellNumbers":[8,13,14]},{"id":6,"action":"ACTION_ADD","type":"ANGLE_LEFT_TOP","result":8,"cellNumbers":[16,21,20]},{"id":7,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":12,"cellNumbers":[10,11,12,15]},{"id":8,"action":"ACTION_ADD","type":"SQUARE","result":13,"cellNumbers":[18,19,23,24]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-929-very-difficult.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-929-very-difficult.yml new file mode 100644 index 00000000..1c30a7a7 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-929-very-difficult.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":5,"height":5},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"EXTREME","singleCageUsage":"DYNAMIC","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723967043372,"playTimeInMilliseconds":160949,"startedToBePlayed":true,"isActive":false,"cells":[{"cellNumber":0,"row":0,"column":0,"value":1,"userValue":1,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":3,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":4,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":5,"userValue":5,"possibles":[]},{"cellNumber":4,"row":0,"column":4,"value":2,"userValue":2,"possibles":[]},{"cellNumber":5,"row":1,"column":0,"value":5,"userValue":5,"possibles":[]},{"cellNumber":6,"row":1,"column":1,"value":1,"userValue":1,"possibles":[]},{"cellNumber":7,"row":1,"column":2,"value":3,"userValue":3,"possibles":[]},{"cellNumber":8,"row":1,"column":3,"value":2,"userValue":2,"possibles":[]},{"cellNumber":9,"row":1,"column":4,"value":4,"userValue":4,"possibles":[]},{"cellNumber":10,"row":2,"column":0,"value":3,"userValue":3,"possibles":[]},{"cellNumber":11,"row":2,"column":1,"value":4,"userValue":4,"possibles":[]},{"cellNumber":12,"row":2,"column":2,"value":2,"userValue":2,"possibles":[]},{"cellNumber":13,"row":2,"column":3,"value":1,"userValue":1,"possibles":[]},{"cellNumber":14,"row":2,"column":4,"value":5,"userValue":5,"possibles":[]},{"cellNumber":15,"row":3,"column":0,"value":4,"userValue":4,"possibles":[]},{"cellNumber":16,"row":3,"column":1,"value":2,"userValue":2,"possibles":[]},{"cellNumber":17,"row":3,"column":2,"value":5,"userValue":5,"possibles":[]},{"cellNumber":18,"row":3,"column":3,"value":3,"userValue":3,"possibles":[]},{"cellNumber":19,"row":3,"column":4,"value":1,"userValue":1,"possibles":[]},{"cellNumber":20,"row":4,"column":0,"value":2,"userValue":2,"possibles":[]},{"cellNumber":21,"row":4,"column":1,"value":5,"userValue":5,"possibles":[]},{"cellNumber":22,"row":4,"column":2,"value":1,"userValue":1,"possibles":[]},{"cellNumber":23,"row":4,"column":3,"value":4,"userValue":4,"possibles":[]},{"cellNumber":24,"row":4,"column":4,"value":3,"userValue":3,"possibles":[]}],"selectedCellNumber":24,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_MULTIPLY","type":"ANGLE_RIGHT_TOP","result":40,"cellNumbers":[15,20,21]},{"id":1,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_RIGHT_TOP","result":9,"cellNumbers":[19,22,23,24]},{"id":2,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":9,"cellNumbers":[13,14,18]},{"id":3,"action":"ACTION_SUBTRACT","type":"DOUBLE_VERTICAL","result":2,"cellNumbers":[11,16]},{"id":4,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":8,"cellNumbers":[0,1,2]},{"id":5,"action":"ACTION_MULTIPLY","type":"ANGLE_LEFT_BOTTOM","result":40,"cellNumbers":[3,4,9]},{"id":6,"action":"ACTION_ADD","type":"ANGLE_RIGHT_BOTTOM","result":9,"cellNumbers":[5,6,10]},{"id":7,"action":"ACTION_MULTIPLY","type":"L_VERTICAL_SHORT_RIGHT_TOP","result":60,"cellNumbers":[7,12,17,8]}]} \ No newline at end of file diff --git a/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-xwing.yml b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-xwing.yml new file mode 100644 index 00000000..7b9b5b13 --- /dev/null +++ b/gauguin-core/src/test/resources/difficulty-balancing/game_5x5-xwing.yml @@ -0,0 +1 @@ +{"variant":{"gridSize":{"width":5,"height":5},"options":{"showOperators":true,"cageOperation":"OPERATIONS_ALL","digitSetting":"FIRST_DIGIT_ONE","difficultySetting":"ANY","singleCageUsage":"FIXED_NUMBER","numeralSystem":"Decimal"}},"savedAtInMilliseconds":1723381453769,"playTimeInMilliseconds":2756133,"startedToBePlayed":true,"isActive":true,"cells":[{"cellNumber":0,"row":0,"column":0,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":1,"row":0,"column":1,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":2,"row":0,"column":2,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":3,"row":0,"column":3,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":4,"row":0,"column":4,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":5,"row":1,"column":0,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":6,"row":1,"column":1,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":7,"row":1,"column":2,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":8,"row":1,"column":3,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":9,"row":1,"column":4,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":10,"row":2,"column":0,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":11,"row":2,"column":1,"value":4,"userValue":2147483647,"possibles":[]},{"cellNumber":12,"row":2,"column":2,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":13,"row":2,"column":3,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":14,"row":2,"column":4,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":15,"row":3,"column":0,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":16,"row":3,"column":1,"value":2,"userValue":2147483647,"possibles":[]},{"cellNumber":17,"row":3,"column":2,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":18,"row":3,"column":3,"value":5,"userValue":2147483647,"possibles":[4]},{"cellNumber":19,"row":3,"column":4,"value":4,"userValue":2147483647,"possibles":[4]},{"cellNumber":20,"row":4,"column":0,"value":5,"userValue":2147483647,"possibles":[]},{"cellNumber":21,"row":4,"column":1,"value":1,"userValue":2147483647,"possibles":[]},{"cellNumber":22,"row":4,"column":2,"value":3,"userValue":2147483647,"possibles":[]},{"cellNumber":23,"row":4,"column":3,"value":4,"userValue":2147483647,"possibles":[4]},{"cellNumber":24,"row":4,"column":4,"value":2,"userValue":2147483647,"possibles":[4]}],"selectedCellNumber":13,"invalidCellNumbers":[],"cheatedCellNumbers":[],"cages":[{"id":0,"action":"ACTION_NONE","type":"SINGLE","result":3,"cellNumbers":[15]},{"id":1,"action":"ACTION_NONE","type":"SINGLE","result":5,"cellNumbers":[12]},{"id":2,"action":"ACTION_ADD","type":"TRIPLE_VERTICAL","result":7,"cellNumbers":[0,5,10]},{"id":3,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":8,"cellNumbers":[1,2,3]},{"id":4,"action":"ACTION_ADD","type":"L_VERTICAL_SHORT_LEFT_BOTTOM","result":11,"cellNumbers":[4,9,14,13]},{"id":5,"action":"ACTION_ADD","type":"L_HORIZONTAL_SHORT_LEFT_BOTTOM","result":14,"cellNumbers":[6,7,8,11]},{"id":6,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":2,"cellNumbers":[16,17]},{"id":7,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":20,"cellNumbers":[18,19]},{"id":8,"action":"ACTION_ADD","type":"TRIPLE_HORIZONTAL","result":9,"cellNumbers":[20,21,22]},{"id":9,"action":"ACTION_MULTIPLY","type":"DOUBLE_HORIZONTAL","result":8,"cellNumbers":[23,24]}]} \ No newline at end of file