Minor improvements to the solver

cleanup code and improve performance
This commit is contained in:
Christian Basler
2024-07-16 17:02:44 +02:00
parent 7190e4faef
commit 40250ebeae
2 changed files with 18 additions and 40 deletions

View File

@@ -1,8 +1,6 @@
package ch.dissem.yaep.domain
import ch.dissem.yaep.domain.PuzzleSolution.MULTIPLE_SOLUTIONS
import ch.dissem.yaep.domain.PuzzleSolution.NO_SOLUTION
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
import ch.dissem.yaep.domain.PuzzleSolution.*
import kotlin.random.Random
fun generateGame(size: Int = 6): Game {
@@ -21,10 +19,18 @@ fun generateGame(size: Int = 6): Game {
var i = 0
// Run your solver on the reduced set of clues and count the number of possible solutions.
// First do a binary search until the game isn't solvable anymore for the first time.
var halvedClues = clues.take(clues.size / 2)
while (solve(grid.toGrid(), halvedClues) == SOLVABLE) {
clues = halvedClues
halvedClues = clues.take(clues.size / 2)
}
// With the last solvable clues set, see what clues can be removed while the game is still solvable.
// If i >= n, we are done. The set of clues is minimal.
while (i < clues.size) {
// Run your solver on the reduced set of clues and count the number of possible solutions.
// TODO: first do binary search
val temp = clues - clues[i]
if (solve(grid.toGrid(), temp) == SOLVABLE) {
// If there is exactly one solution, update clues and reset index.
@@ -33,27 +39,17 @@ fun generateGame(size: Int = 6): Game {
}
i++
}
return Game(grid.toGrid(), clues)
// (You can speed this up by removing clues in batches rather than one at a time, but it makes the algorithm more complicated to describe.)
return Game(grid.toGrid(), clues)
}
internal fun solve(
grid: Grid,
clues: Collection<Clue>
): PuzzleSolution {
// Start with a grid where each cell is a list of all possible items.
// First, set the positions of the items that are already known.
// clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
// position.removeForbiddenOptions(grid)
// }
// grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
// For each clue, remove any items that violate the clue.
// If any cell has only one item left, remove that item from all other cells.
// Repeat until no more items can be removed.
// val otherClues = clues.filter { it !is PositionClue<*> }
var removedOptions: Boolean
do {
removedOptions = false
@@ -66,9 +62,6 @@ internal fun solve(
}
// TODO clean up groups in rows (?)
grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
// grid.forEach { row ->
// removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
// }
} while (removedOptions)
// If any cell has no items left, the puzzle has no solution.
@@ -77,16 +70,6 @@ internal fun solve(
// If all cells have exactly one item left, the puzzle is solved.
if (grid.cells.all { it.selection != null }) return SOLVABLE
// If there are still cells with multiple items, pick one and try each item in turn, then go back to step 2.
// return if (
// grid.cells
// .filter { it.selection == null }
// .any { it.countSolutions(grid, clues) == 1 }
// ) {
// SOLVABLE
// } else {
// MULTIPLE_SOLUTIONS
// }
return MULTIPLE_SOLUTIONS
}

View File

@@ -1,14 +1,8 @@
package ch.dissem.yaep.domain
import ch.dissem.yaep.domain.Animal.ANT
import ch.dissem.yaep.domain.Animal.OCTOPUS
import ch.dissem.yaep.domain.Animal.SNAIL
import ch.dissem.yaep.domain.Nationality.CANADA
import ch.dissem.yaep.domain.Nationality.SWITZERLAND
import ch.dissem.yaep.domain.Nationality.UKRAINE
import ch.dissem.yaep.domain.Profession.ASTRONAUT
import ch.dissem.yaep.domain.Profession.FARMER
import ch.dissem.yaep.domain.Profession.SOFTWARE_DEV
import ch.dissem.yaep.domain.Animal.*
import ch.dissem.yaep.domain.Nationality.*
import ch.dissem.yaep.domain.Profession.*
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
import ch.tutteli.atrium.api.fluent.en_GB.feature
import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThan
@@ -16,7 +10,7 @@ import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.measureTime
class GameTest {
@@ -36,8 +30,9 @@ class GameTest {
}
}
println("Clues: ${game.clues.size}")
println("Time: $time")
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
expect(time).toBeLessThan(1.seconds)
expect(time).toBeLessThan(500.milliseconds)
}
@Test