Fix solver
generator and/or solver still needs optimization
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ captures
|
|||||||
!*.xcodeproj/project.xcworkspace/
|
!*.xcodeproj/project.xcworkspace/
|
||||||
!*.xcworkspace/contents.xcworkspacedata
|
!*.xcworkspace/contents.xcworkspacedata
|
||||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||||
|
.kotlin
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ fun PuzzleGrid(
|
|||||||
grid: Grid
|
grid: Grid
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
for (row in grid.rows) {
|
for (row in grid) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package ch.dissem.yaep.domain
|
package ch.dissem.yaep.domain
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
class Grid(
|
class Grid(
|
||||||
val rows: List<GameRow<ItemClass<*>>>
|
val rows: List<GameRow<*>>
|
||||||
) : List<GameRow<ItemClass<*>>> by rows {
|
) : List<GameRow<ItemClass<*>>> by rows as List<GameRow<ItemClass<*>>> {
|
||||||
|
|
||||||
fun <C : ItemClass<C>> indexOf(element: C): Int {
|
fun <C : ItemClass<C>> indexOf(element: C): Int {
|
||||||
return this[element.companion]
|
return this[element.companion]
|
||||||
@@ -19,7 +20,7 @@ class Grid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return rows.joinToString("\n") { row ->
|
return joinToString("\n") { row ->
|
||||||
row.joinToString("") { it.selection?.symbol ?: " " }
|
row.joinToString("") { it.selection?.symbol ?: " " }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,8 +97,10 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
|||||||
ia - 1 -> {
|
ia - 1 -> {
|
||||||
return if (ic != -1) {
|
return if (ic != -1) {
|
||||||
ic != ia - 2
|
ic != ia - 2
|
||||||
} else {
|
} else if (ia - 2 >= 0) {
|
||||||
!rowC[ia - 2].mayBe(c)
|
!rowC[ia - 2].mayBe(c)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
fun generateGame(size: Int = 6): Game {
|
fun generateGame(size: Int = 6): Game {
|
||||||
// Generate a random puzzle instance.
|
// Generate a random puzzle instance.
|
||||||
// val classes = ItemClass.randomClasses(size)
|
val classes = ItemClass.randomClasses(size)
|
||||||
val classes = ItemClass.classes.take(size)
|
|
||||||
|
|
||||||
val grid: List<List<Item<ItemClass<*>>>> = classes.map {
|
val grid: List<List<Item<ItemClass<*>>>> = classes.map {
|
||||||
// it.randomItems(size).map { item -> Item(item) }
|
it.randomItems(size).map { item -> Item(item) }
|
||||||
it.items.take(size).map { item -> Item(item) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a set C of all possible clues that pertain to this puzzle instance.
|
// 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
|
// (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
|
// 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.)
|
// 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
|
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.)
|
// (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,
|
grid: Grid,
|
||||||
clues: Collection<Clue>
|
clues: Collection<Clue>
|
||||||
): PuzzleSolution {
|
): PuzzleSolution {
|
||||||
@@ -74,11 +72,26 @@ private fun solve(
|
|||||||
if (grid.flatMap { it }.all { it.selection != null }) return SOLVABLE
|
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.
|
// 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
|
return MULTIPLE_SOLUTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class PuzzleSolution {
|
internal enum class PuzzleSolution {
|
||||||
NO_SOLUTION,
|
NO_SOLUTION,
|
||||||
SOLVABLE,
|
SOLVABLE,
|
||||||
MULTIPLE_SOLUTIONS
|
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> {
|
fun getAllClues(rows: List<List<Item<ItemClass<*>>>>): MutableSet<Clue> {
|
||||||
val clues = mutableSetOf<Clue>()
|
val clues = mutableSetOf<Clue>()
|
||||||
// rows.forEach { row ->
|
rows.forEach { row ->
|
||||||
// row.forEachIndexed { i, item ->
|
row.forEachIndexed { i, item ->
|
||||||
// clues.add(PositionClue(item, i))
|
clues.add(PositionClue(item, i))
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
rows.forEach { row ->
|
rows.forEach { row ->
|
||||||
row.forEachIndexed { j, item ->
|
row.forEachIndexed { j, item ->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package ch.dissem.yaep.domain
|
package ch.dissem.yaep.domain
|
||||||
|
|
||||||
enum class Animals(symbol: String) : ItemClass<Animals> {
|
enum class Animal(symbol: String) : ItemClass<Animal> {
|
||||||
ZEBRA("🦓"),
|
ZEBRA("🦓"),
|
||||||
OCTOPUS("🐙"),
|
OCTOPUS("🐙"),
|
||||||
GOAT("🐐"),
|
GOAT("🐐"),
|
||||||
@@ -12,10 +12,10 @@ enum class Animals(symbol: String) : ItemClass<Animals> {
|
|||||||
override val symbols: Array<String> = arrayOf(symbol)
|
override val symbols: Array<String> = arrayOf(symbol)
|
||||||
|
|
||||||
override val companion
|
override val companion
|
||||||
get() = Animals
|
get() = Animal
|
||||||
|
|
||||||
companion object : ItemClassCompanion<Animals> {
|
companion object : ItemClassCompanion<Animal> {
|
||||||
override val items: List<Animals> = entries
|
override val items: List<Animal> = entries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ sealed interface ItemClass<out SELF : ItemClass<SELF>> {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val classes: List<ItemClassCompanion<*>> = listOf(
|
val classes: List<ItemClassCompanion<*>> = listOf(
|
||||||
Animals,
|
Animal,
|
||||||
Nationality,
|
Nationality,
|
||||||
Drink,
|
Drink,
|
||||||
Profession,
|
Profession,
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
package ch.dissem.yaep.domain
|
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.feature
|
||||||
import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan
|
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
|
||||||
@@ -8,28 +17,6 @@ import kotlin.test.Test
|
|||||||
|
|
||||||
class GameTest {
|
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
|
@Test
|
||||||
fun `ensure generated game is valid`() {
|
fun `ensure generated game is valid`() {
|
||||||
val game = generateGame()
|
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
|
@Test
|
||||||
fun `grid with a set and b and c as option on the same side is considered valid`() {
|
fun `grid with a set and b and c as option on the same side is considered valid`() {
|
||||||
val grid = createGrid { null }
|
val grid = createGrid { null }
|
||||||
val rowA = grid.rows.random()
|
val rowA = grid.random()
|
||||||
val rowB = grid.rows.random()
|
val rowB = grid.random()
|
||||||
val rowC = grid.rows.random()
|
val rowC = grid.random()
|
||||||
val a = rowA[3]
|
val a = rowA[3]
|
||||||
val b = rowB[2]
|
val b = rowB[2]
|
||||||
val c = rowC[1]
|
val c = rowC[1]
|
||||||
|
|||||||
Reference in New Issue
Block a user