Minor improvements to the solver
cleanup code and improve performance
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
package ch.dissem.yaep.domain
|
package ch.dissem.yaep.domain
|
||||||
|
|
||||||
import ch.dissem.yaep.domain.PuzzleSolution.MULTIPLE_SOLUTIONS
|
import ch.dissem.yaep.domain.PuzzleSolution.*
|
||||||
import ch.dissem.yaep.domain.PuzzleSolution.NO_SOLUTION
|
|
||||||
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
fun generateGame(size: Int = 6): Game {
|
fun generateGame(size: Int = 6): Game {
|
||||||
@@ -21,10 +19,18 @@ fun generateGame(size: Int = 6): Game {
|
|||||||
|
|
||||||
var i = 0
|
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.
|
// If i >= n, we are done. The set of clues is minimal.
|
||||||
while (i < clues.size) {
|
while (i < clues.size) {
|
||||||
// Run your solver on the reduced set of clues and count the number of possible solutions.
|
// 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]
|
val temp = clues - clues[i]
|
||||||
if (solve(grid.toGrid(), temp) == SOLVABLE) {
|
if (solve(grid.toGrid(), temp) == SOLVABLE) {
|
||||||
// If there is exactly one solution, update clues and reset index.
|
// If there is exactly one solution, update clues and reset index.
|
||||||
@@ -33,27 +39,17 @@ fun generateGame(size: Int = 6): Game {
|
|||||||
}
|
}
|
||||||
i++
|
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(
|
internal fun solve(
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
clues: Collection<Clue>
|
clues: Collection<Clue>
|
||||||
): PuzzleSolution {
|
): 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.
|
// 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.
|
// If any cell has only one item left, remove that item from all other cells.
|
||||||
// Repeat until no more items can be removed.
|
// Repeat until no more items can be removed.
|
||||||
// val otherClues = clues.filter { it !is PositionClue<*> }
|
|
||||||
var removedOptions: Boolean
|
var removedOptions: Boolean
|
||||||
do {
|
do {
|
||||||
removedOptions = false
|
removedOptions = false
|
||||||
@@ -66,9 +62,6 @@ internal fun solve(
|
|||||||
}
|
}
|
||||||
// TODO clean up groups in rows (?)
|
// TODO clean up groups in rows (?)
|
||||||
grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
|
grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
|
||||||
// grid.forEach { row ->
|
|
||||||
// removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
|
||||||
// }
|
|
||||||
} while (removedOptions)
|
} while (removedOptions)
|
||||||
|
|
||||||
// If any cell has no items left, the puzzle has no solution.
|
// 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 all cells have exactly one item left, the puzzle is solved.
|
||||||
if (grid.cells.all { it.selection != null }) return SOLVABLE
|
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
|
return MULTIPLE_SOLUTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
package ch.dissem.yaep.domain
|
package ch.dissem.yaep.domain
|
||||||
|
|
||||||
import ch.dissem.yaep.domain.Animal.ANT
|
import ch.dissem.yaep.domain.Animal.*
|
||||||
import ch.dissem.yaep.domain.Animal.OCTOPUS
|
import ch.dissem.yaep.domain.Nationality.*
|
||||||
import ch.dissem.yaep.domain.Animal.SNAIL
|
import ch.dissem.yaep.domain.Profession.*
|
||||||
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.PuzzleSolution.SOLVABLE
|
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
|
||||||
import ch.tutteli.atrium.api.fluent.en_GB.feature
|
import ch.tutteli.atrium.api.fluent.en_GB.feature
|
||||||
import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThan
|
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.fluent.en_GB.toEqual
|
||||||
import ch.tutteli.atrium.api.verbs.expect
|
import ch.tutteli.atrium.api.verbs.expect
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.measureTime
|
import kotlin.time.measureTime
|
||||||
|
|
||||||
class GameTest {
|
class GameTest {
|
||||||
@@ -36,8 +30,9 @@ class GameTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("Clues: ${game.clues.size}")
|
println("Clues: ${game.clues.size}")
|
||||||
|
println("Time: $time")
|
||||||
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
|
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
|
||||||
expect(time).toBeLessThan(1.seconds)
|
expect(time).toBeLessThan(500.milliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user