Fix solver
generator and/or solver still needs optimization
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Grid(
|
||||
val rows: List<GameRow<ItemClass<*>>>
|
||||
) : List<GameRow<ItemClass<*>>> by rows {
|
||||
val rows: List<GameRow<*>>
|
||||
) : List<GameRow<ItemClass<*>>> by rows as List<GameRow<ItemClass<*>>> {
|
||||
|
||||
fun <C : ItemClass<C>> indexOf(element: C): Int {
|
||||
return this[element.companion]
|
||||
@@ -19,7 +20,7 @@ class Grid(
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return rows.joinToString("\n") { row ->
|
||||
return joinToString("\n") { row ->
|
||||
row.joinToString("") { it.selection?.symbol ?: " " }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,10 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
ia - 1 -> {
|
||||
return if (ic != -1) {
|
||||
ic != ia - 2
|
||||
} else {
|
||||
} else if (ia - 2 >= 0) {
|
||||
!rowC[ia - 2].mayBe(c)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,19 +7,17 @@ import kotlin.random.Random
|
||||
|
||||
fun generateGame(size: Int = 6): Game {
|
||||
// Generate a random puzzle instance.
|
||||
// val classes = ItemClass.randomClasses(size)
|
||||
val classes = ItemClass.classes.take(size)
|
||||
val classes = ItemClass.randomClasses(size)
|
||||
|
||||
val grid: List<List<Item<ItemClass<*>>>> = classes.map {
|
||||
// it.randomItems(size).map { item -> Item(item) }
|
||||
it.items.take(size).map { item -> Item(item) }
|
||||
it.randomItems(size).map { item -> Item(item) }
|
||||
}
|
||||
|
||||
// Build a set C of all possible clues that pertain to this puzzle instance.
|
||||
// (There are a finite and in fact quite small number of possible clues: for example
|
||||
// if there are 5 houses, there are 5 possible clues of the form "Person A lives in
|
||||
// house B", 8 possible clues of the form "Person A lives next to house B", and so on.)
|
||||
var clues = getAllClues(grid).sortedBy { it.toString() } //.shuffled()
|
||||
var clues = getAllClues(grid).shuffled()
|
||||
|
||||
var i = 0
|
||||
|
||||
@@ -39,7 +37,7 @@ fun generateGame(size: Int = 6): Game {
|
||||
// (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.)
|
||||
}
|
||||
|
||||
private fun solve(
|
||||
internal fun solve(
|
||||
grid: Grid,
|
||||
clues: Collection<Clue>
|
||||
): PuzzleSolution {
|
||||
@@ -74,11 +72,26 @@ private fun solve(
|
||||
if (grid.flatMap { it }.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.
|
||||
// TODO: Does this need to be implemented? We would need to try all possible options to check if there are multiple solutions.
|
||||
for (i in 0 until grid.size) {
|
||||
for (j in 0 until grid.size) {
|
||||
val cell = grid[i][j]
|
||||
if (cell.selection == null) {
|
||||
val options = cell.options.toList()
|
||||
for (option in options) {
|
||||
cell.selection = option
|
||||
if (solve(grid, clues) == SOLVABLE) {
|
||||
return SOLVABLE
|
||||
}
|
||||
}
|
||||
cell.selection = null
|
||||
cell.options.addAll(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
return MULTIPLE_SOLUTIONS
|
||||
}
|
||||
|
||||
private enum class PuzzleSolution {
|
||||
internal enum class PuzzleSolution {
|
||||
NO_SOLUTION,
|
||||
SOLVABLE,
|
||||
MULTIPLE_SOLUTIONS
|
||||
@@ -112,11 +125,11 @@ fun <C : ItemClass<C>> GameRow<C>.cleanupOptions(cell: GameCell<C>) {
|
||||
|
||||
fun getAllClues(rows: List<List<Item<ItemClass<*>>>>): MutableSet<Clue> {
|
||||
val clues = mutableSetOf<Clue>()
|
||||
// rows.forEach { row ->
|
||||
// row.forEachIndexed { i, item ->
|
||||
// clues.add(PositionClue(item, i))
|
||||
// }
|
||||
// }
|
||||
rows.forEach { row ->
|
||||
row.forEachIndexed { i, item ->
|
||||
clues.add(PositionClue(item, i))
|
||||
}
|
||||
}
|
||||
|
||||
rows.forEach { row ->
|
||||
row.forEachIndexed { j, item ->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
enum class Animals(symbol: String) : ItemClass<Animals> {
|
||||
enum class Animal(symbol: String) : ItemClass<Animal> {
|
||||
ZEBRA("🦓"),
|
||||
OCTOPUS("🐙"),
|
||||
GOAT("🐐"),
|
||||
@@ -12,10 +12,10 @@ enum class Animals(symbol: String) : ItemClass<Animals> {
|
||||
override val symbols: Array<String> = arrayOf(symbol)
|
||||
|
||||
override val companion
|
||||
get() = Animals
|
||||
get() = Animal
|
||||
|
||||
companion object : ItemClassCompanion<Animals> {
|
||||
override val items: List<Animals> = entries
|
||||
companion object : ItemClassCompanion<Animal> {
|
||||
override val items: List<Animal> = entries
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ sealed interface ItemClass<out SELF : ItemClass<SELF>> {
|
||||
|
||||
companion object {
|
||||
val classes: List<ItemClassCompanion<*>> = listOf(
|
||||
Animals,
|
||||
Animal,
|
||||
Nationality,
|
||||
Drink,
|
||||
Profession,
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
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.tutteli.atrium.api.fluent.en_GB.feature
|
||||
import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan
|
||||
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
|
||||
@@ -8,28 +17,6 @@ import kotlin.test.Test
|
||||
|
||||
class GameTest {
|
||||
|
||||
// @Test
|
||||
// fun `try to find the error`() {
|
||||
// val size = 6
|
||||
// val classes = ItemClass.randomClasses(size)
|
||||
//
|
||||
// val grid: List<List<Item<ItemClass<*>>>> = classes.map {
|
||||
// it.randomItems(size).map { item -> Item(item) }
|
||||
// }
|
||||
//
|
||||
// // Build a set C of all possible clues that pertain to this puzzle instance.
|
||||
// // (There are a finite and in fact quite small number of possible clues: for example
|
||||
// // if there are 5 houses, there are 5 possible clues of the form "Person A lives in
|
||||
// // house B", 8 possible clues of the form "Person A lives next to house B", and so on.)
|
||||
// val clues = getAllClues(grid).shuffled()
|
||||
//
|
||||
// val gameGrid = grid.toGrid()
|
||||
// gameGrid.flatMap { it }.forEach { it.selection = it.solution }
|
||||
//
|
||||
// expect(clues).toHaveElementsAndAll {
|
||||
// feature { f(it::isRuleViolated, gameGrid) }.toEqual(false)
|
||||
// }
|
||||
// }
|
||||
@Test
|
||||
fun `ensure generated game is valid`() {
|
||||
val game = generateGame()
|
||||
@@ -42,4 +29,51 @@ class GameTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure game can be solved`() {
|
||||
val professions = listOf(
|
||||
Item(ASTRONAUT),
|
||||
Item(FARMER),
|
||||
Item(SOFTWARE_DEV)
|
||||
)
|
||||
val animals = listOf(
|
||||
Item(SNAIL),
|
||||
Item(ANT),
|
||||
Item(OCTOPUS)
|
||||
)
|
||||
val nationalities = listOf(
|
||||
Item(UKRAINE),
|
||||
Item(CANADA),
|
||||
Item(SWITZERLAND)
|
||||
)
|
||||
val game = Game(
|
||||
Grid(listOf(
|
||||
GameRow(
|
||||
Profession,
|
||||
professions,
|
||||
professions.map { GameCell(null, it, professions.toMutableList()) }
|
||||
),
|
||||
GameRow(
|
||||
Animal,
|
||||
animals,
|
||||
animals.map { GameCell(null, it, animals.toMutableList()) }
|
||||
),
|
||||
GameRow(
|
||||
Nationality,
|
||||
nationalities,
|
||||
nationalities.map { GameCell(null, it, nationalities.toMutableList()) }
|
||||
)
|
||||
)),
|
||||
listOf(
|
||||
TripletClue(professions[0], animals[1], nationalities[2]),
|
||||
OrderClue(professions[0], nationalities[1]),
|
||||
SameColumnClue(animals[1], nationalities[1]),
|
||||
NeighbourClue(professions[0], professions[1]),
|
||||
PositionClue(animals[0], 0)
|
||||
)
|
||||
)
|
||||
|
||||
expect(solve(game.grid, game.clues)).toEqual(PuzzleSolution.SOLVABLE)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -112,9 +112,9 @@ class TripletClueTest : ClueTest() {
|
||||
@Test
|
||||
fun `grid with a set and b and c as option on the same side is considered valid`() {
|
||||
val grid = createGrid { null }
|
||||
val rowA = grid.rows.random()
|
||||
val rowB = grid.rows.random()
|
||||
val rowC = grid.rows.random()
|
||||
val rowA = grid.random()
|
||||
val rowB = grid.random()
|
||||
val rowC = grid.random()
|
||||
val a = rowA[3]
|
||||
val b = rowB[2]
|
||||
val c = rowC[1]
|
||||
|
||||
Reference in New Issue
Block a user