Skip to content

Commit

Permalink
wip MinMaxSum
Browse files Browse the repository at this point in the history
  • Loading branch information
meikpiep committed Nov 3, 2024
1 parent be812d3 commit 6972f87
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import org.piepmeyer.gauguin.difficulty.human.strategy.GridSumEnforcesCageSum
import org.piepmeyer.gauguin.difficulty.human.strategy.LineSingleCagePossiblesSumSingle
import org.piepmeyer.gauguin.difficulty.human.strategy.LinesSingleCagePossiblesSumDual
import org.piepmeyer.gauguin.difficulty.human.strategy.LinesSingleCagePossiblesSumTriple
import org.piepmeyer.gauguin.difficulty.human.strategy.MinMaxSumOneLine
import org.piepmeyer.gauguin.difficulty.human.strategy.MinMaxSumThreeLines
import org.piepmeyer.gauguin.difficulty.human.strategy.MinMaxSumTwoLines
import org.piepmeyer.gauguin.difficulty.human.strategy.NakedPair
import org.piepmeyer.gauguin.difficulty.human.strategy.NakedTriple
import org.piepmeyer.gauguin.difficulty.human.strategy.NumberOfCagesWithPossibleForcesPossibleInCage
Expand Down Expand Up @@ -49,15 +52,17 @@ enum class HumanSolverStrategies(
ATwoCellsPossiblesSumThreeLines(87, TwoCellsPossiblesSumThreeLines()),

ANumberOfCagesWithPossibleForcesPossibleInCage(89, NumberOfCagesWithPossibleForcesPossibleInCage()),

AMinMaxSumOneLine(89, MinMaxSumOneLine()),
AOddEvenCheckSumSingle(90, OddEvenCheckSumSingle()),
ADetectPossiblesBreakingOtherCagesPossiblesDualLines(95, DetectPossiblesBreakingOtherCagesPossiblesDualLines()),
ADetectPossibleUsedInLinesByOtherCagesDualLines(98, DetectPossibleUsedInLinesByOtherCagesDualLines()),
ADualLinesPossiblesSum(100, LinesSingleCagePossiblesSumDual()),
AOddEvenCheckSumDual(110, OddEvenCheckSumDual()),
AXWing(120, XWing()),
AMinMaxSumTwoLines(130, MinMaxSumTwoLines()),
ATripleLinesPossiblesSum(140, LinesSingleCagePossiblesSumTriple()),
AOddEvenCheckSumTriple(150, OddEvenCheckSumTriple()),
AGridSumEnforcesCageSum(160, GridSumEnforcesCageSum()),
AGridSumOddEvenCheck(200, OddEvenCheckGridSum()),
AMinMaxSumThreeLines(210, MinMaxSumThreeLines()),
}
Original file line number Diff line number Diff line change
@@ -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.calculatePossibles(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<GridCage>,
lines: Set<GridLine>,
cache: PossiblesCache,
): Pair<Int, Int> {
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<GridLine>,
cache: PossiblesCache,
): List<IntArray> {
val cellIndexesInLine =
cellIndexesInLine(cage, lines)

val possiblesInLines =
cache.calculatePossibles(cage).map {
it
.filterIndexed { index, _ ->
cellIndexesInLine.contains(index)
}.toIntArray()
}

return possiblesInLines
}

private fun cellIndexesInLine(
cage: GridCage,
lines: Set<GridLine>,
): List<Int> {
val cellIndexesInLine =
cage.cells
.mapIndexed { index, cell ->
if (lines.any { line -> line.contains(cell) }) {
index
} else {
null
}
}.filterNotNull()
return cellIndexesInLine
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.piepmeyer.gauguin.difficulty.human.strategy

class MinMaxSumOneLine : AbstractMinMaxSum(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.piepmeyer.gauguin.difficulty.human.strategy

class MinMaxSumThreeLines : AbstractMinMaxSum(3)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.piepmeyer.gauguin.difficulty.human.strategy

class MinMaxSumTwoLines : AbstractMinMaxSum(2)
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import org.piepmeyer.gauguin.options.GameVariant
class HumanDifficultySolverHandpickedTest :
FunSpec({
test("seed random grid should be solved") {
val randomizer = SeedRandomizerMock(3252) // 1495, 2281
val randomizer = SeedRandomizerMock(3) // 1495, 2281

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(4, 4),
GridSize(3, 6),
GameOptionsVariant.createClassic(),
),
randomizer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ 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.game.save.SaveGame
import org.piepmeyer.gauguin.grid.GridSize
import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant
import java.io.File

class HumanDifficultySolverTest :
FunSpec({
for (seed in 0..9999) {
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: 153 left unsolved
// 10_000 of 5x5, merge: 134 left unsolved
// 10_000 of 2x4, merge: no (!) left unsolved
// 1_000 of 3x6, merge: 125 left unsolved
withClue("seed $seed") {
test("seed random grid should be solved") {
val randomizer = SeedRandomizerMock(seed)

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(4, 4),
GridSize(3, 6),
GameOptionsVariant.createClassic(),
),
randomizer,
Expand All @@ -46,7 +46,7 @@ class HumanDifficultySolverTest :
if (grid.numberOfMistakes() != 0) {
throw IllegalStateException("Found a grid with wrong values.")
}
grid.isActive = true
/*grid.isActive = true
grid.startedToBePlayed = true
val saveGame =
SaveGame.createWithFile(
Expand All @@ -56,7 +56,7 @@ class HumanDifficultySolverTest :
),
)
saveGame.save(grid)
saveGame.save(grid)*/
}

grid.isSolved() shouldBe true
Expand Down

0 comments on commit 6972f87

Please sign in to comment.