Improve and fix solver
This commit is contained in:
@@ -1,8 +1,45 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
import ch.dissem.yaep.domain.Animal.ANT
|
||||
import ch.dissem.yaep.domain.Animal.DOG
|
||||
import ch.dissem.yaep.domain.Animal.GOAT
|
||||
import ch.dissem.yaep.domain.Animal.SLOTH
|
||||
import ch.dissem.yaep.domain.Animal.SNAIL
|
||||
import ch.dissem.yaep.domain.Animal.ZEBRA
|
||||
import ch.dissem.yaep.domain.Dessert.CAKE
|
||||
import ch.dissem.yaep.domain.Dessert.CUPCAKE
|
||||
import ch.dissem.yaep.domain.Dessert.CUSTARD
|
||||
import ch.dissem.yaep.domain.Dessert.DOUGHNUT
|
||||
import ch.dissem.yaep.domain.Dessert.ICE_CREAM
|
||||
import ch.dissem.yaep.domain.Dessert.PIE
|
||||
import ch.dissem.yaep.domain.Drink.BEER
|
||||
import ch.dissem.yaep.domain.Drink.BEVERAGE
|
||||
import ch.dissem.yaep.domain.Drink.COFFEE
|
||||
import ch.dissem.yaep.domain.Drink.MILK
|
||||
import ch.dissem.yaep.domain.Drink.TEA
|
||||
import ch.dissem.yaep.domain.Drink.WINE
|
||||
import ch.dissem.yaep.domain.Fruit.BANANA
|
||||
import ch.dissem.yaep.domain.Fruit.GRAPES
|
||||
import ch.dissem.yaep.domain.Fruit.MANGO
|
||||
import ch.dissem.yaep.domain.Fruit.PEAR
|
||||
import ch.dissem.yaep.domain.Fruit.PINEAPPLE
|
||||
import ch.dissem.yaep.domain.Fruit.WATERMELON
|
||||
import ch.dissem.yaep.domain.Nationality.CANADA
|
||||
import ch.dissem.yaep.domain.Nationality.ENGLAND
|
||||
import ch.dissem.yaep.domain.Nationality.JAPAN
|
||||
import ch.dissem.yaep.domain.Nationality.SPAIN
|
||||
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.HEALTH_WORKER
|
||||
import ch.dissem.yaep.domain.Profession.ROCK_STAR
|
||||
import ch.dissem.yaep.domain.Profession.SCIENTIST
|
||||
import ch.dissem.yaep.domain.Profession.SOFTWARE_DEV
|
||||
import ch.dissem.yaep.domain.Profession.TEACHER
|
||||
|
||||
class Game(
|
||||
val grid: Grid,
|
||||
val clues: List<Clue>
|
||||
val clues: Collection<Clue>
|
||||
) {
|
||||
val horizontalClues = clues.filterIsInstance<HorizontalClue>()
|
||||
val verticalClues = clues.filterIsInstance<SameColumnClue<ItemClass<*>, ItemClass<*>>>()
|
||||
@@ -29,9 +66,61 @@ class Game(
|
||||
|
||||
fun isValid(): Boolean = areCategoriesValid() && clues.all { it.isValid(grid) }
|
||||
|
||||
fun isSolved(): Boolean = grid.cells.all { it.selection != null }
|
||||
val isSolved: Boolean
|
||||
get() = grid.rows.all { it.isSolved }
|
||||
|
||||
override fun toString(): String {
|
||||
return grid.toString() + "\n\n" + clues.joinToString("\n* ", prefix = "* ")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parse(description: String): Game {
|
||||
// TODO get options from string
|
||||
val rowOptions = listOf<List<Item<*>>>(
|
||||
listOf(
|
||||
HEALTH_WORKER,
|
||||
ROCK_STAR,
|
||||
SCIENTIST,
|
||||
SOFTWARE_DEV,
|
||||
ASTRONAUT,
|
||||
TEACHER
|
||||
).map { Item(it) },
|
||||
listOf(ANT, DOG, GOAT, SLOTH, SNAIL, ZEBRA).map { Item(it) },
|
||||
listOf(WATERMELON, MANGO, PEAR, GRAPES, PINEAPPLE, BANANA).map { Item(it) },
|
||||
listOf(CUPCAKE, ICE_CREAM, DOUGHNUT, CAKE, PIE, CUSTARD).map { Item(it) },
|
||||
listOf(SWITZERLAND, ENGLAND, JAPAN, UKRAINE, CANADA, SPAIN).map { Item(it) },
|
||||
listOf(WINE, BEVERAGE, BEER, COFFEE, TEA, MILK).map { Item(it) }
|
||||
)
|
||||
val optionMap = rowOptions.flatten().associateBy(
|
||||
keySelector = { it.itemType },
|
||||
valueTransform = { it }
|
||||
)
|
||||
val clues: Collection<Clue> = description.lines()
|
||||
.filter { it.startsWith("* ") }
|
||||
.map { line ->
|
||||
Clue.parse(line) {
|
||||
optionMap[it] ?: throw IllegalArgumentException("Unknown option $it")
|
||||
}
|
||||
}
|
||||
|
||||
return Game(
|
||||
grid = Grid(
|
||||
rows = rowOptions
|
||||
.map { options -> createRow<ItemClass<*>>(options) }
|
||||
.toList()
|
||||
),
|
||||
clues = clues
|
||||
)
|
||||
}
|
||||
|
||||
private fun <C : ItemClass<C>> createRow(options: List<Item<*>>): GameRow<*> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
options as List<Item<C>>
|
||||
return GameRow(
|
||||
category = options.first().itemType.companion,
|
||||
options = options,
|
||||
cells = options.map { GameCell(options = options.toMutableList()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
class GameCell<C : ItemClass<C>>(
|
||||
selection: Item<C>?,
|
||||
val solution: Item<C>,
|
||||
selection: Item<C>?=null,
|
||||
val solution: Item<C>? = null,
|
||||
options: Collection<Item<C>>
|
||||
) {
|
||||
val selectionChangedListeners = mutableListOf<(Item<C>?) -> Unit>()
|
||||
|
||||
@@ -5,52 +5,53 @@ class GameRow<C : ItemClass<C>>(
|
||||
val options: List<Item<C>>,
|
||||
val cells: List<GameCell<C>>
|
||||
) : List<GameCell<C>> by cells {
|
||||
var isSolved = false
|
||||
private set
|
||||
|
||||
fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element }
|
||||
|
||||
fun cleanupOptions() {
|
||||
cells.forEach {
|
||||
cleanupOptions(it)
|
||||
}
|
||||
// do {
|
||||
// var selectedSingleOption = false
|
||||
// cells.forEach { cleanupOptions(it) }
|
||||
// val selections = cells.mapNotNull { it.selection }
|
||||
// options
|
||||
// .filter { !selections.contains(it) }
|
||||
// .forEach { option ->
|
||||
// if (cells.count { cell -> cell.options.contains(option) } == 1) {
|
||||
// cells
|
||||
// .filter { it.selection == null }
|
||||
// .first { cell -> cell.options.contains(option) }
|
||||
// .let { it.selection = option }
|
||||
// selectedSingleOption = true
|
||||
// }
|
||||
// }
|
||||
// } while (selectedSingleOption)
|
||||
}
|
||||
|
||||
private fun cleanupOptions(cell: GameCell<C>, justSelected: Boolean = true) {
|
||||
if ((justSelected && cell.selection != null) || (cell.options.size == 1 && cell.selection == null)) {
|
||||
val selection = cell.selection
|
||||
if (selection == null) {
|
||||
cell.selection = cell.options.first()
|
||||
} else {
|
||||
cell.options.clear()
|
||||
cell.options.add(selection)
|
||||
if (
|
||||
isSolved && all {
|
||||
it.solution != null
|
||||
&& it.options.size == 1
|
||||
&& it.options.single() == it.selection
|
||||
}
|
||||
filter { otherCell -> otherCell != cell && otherCell.hasNoSelection() }.forEach { otherCell ->
|
||||
otherCell.options.remove(cell.selection)
|
||||
cleanupOptions(otherCell, false)
|
||||
) return
|
||||
do {
|
||||
var changed = false
|
||||
val selections = filter { it.selection != null }.let { cellsWithSelection ->
|
||||
cellsWithSelection
|
||||
.filter { it.options.size != 1 }
|
||||
.forEach {
|
||||
it.options.clear()
|
||||
it.options.add(it.selection!!)
|
||||
}
|
||||
cellsWithSelection.mapNotNull { it.selection }.toMutableSet()
|
||||
}
|
||||
filter { it.selection == null }
|
||||
.flatMap { c -> c.options.map { o -> o to c } }
|
||||
.groupBy { it.first }
|
||||
.filter { it.value.size == 1 }
|
||||
.forEach { it.options.removeAll(selections) }
|
||||
filter { it.selection == null && it.options.size == 1 }
|
||||
.forEach {
|
||||
val c = it.value.single().second
|
||||
c.selection = it.key
|
||||
cleanupOptions(c, true)
|
||||
changed = true
|
||||
it.selection = it.options.single()
|
||||
}
|
||||
if (!changed) {
|
||||
filter { it.selection == null }
|
||||
.flatMap { c -> c.options.map { o -> o to c } }
|
||||
.groupBy { it.first }
|
||||
.filter { it.value.size == 1 }
|
||||
.forEach {
|
||||
val c = it.value.single().second
|
||||
c.selection = it.key
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
} while (changed)
|
||||
isSolved = all {
|
||||
it.solution != null
|
||||
&& it.options.size == 1
|
||||
&& it.options.single() == it.selection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,26 @@ sealed class Clue {
|
||||
* @return `true` if any option was removed
|
||||
*/
|
||||
abstract fun removeForbiddenOptions(grid: Grid): Boolean
|
||||
|
||||
companion object {
|
||||
fun <T : ItemClass<T>> parse(line: String, mapper: (ItemClass<*>) -> Item<T>): Clue {
|
||||
return NeighbourClue.parse(line, mapper)
|
||||
?: OrderClue.parse(line, mapper)
|
||||
?: TripletClue.parse(line, mapper)
|
||||
?: SameColumnClue.parse(line, mapper)
|
||||
?: PositionClue.parse(line, mapper)
|
||||
?: throw IllegalStateException("Unknown clue: $line")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ClueParser {
|
||||
/**
|
||||
* Parses a string description of a clue into a clue.
|
||||
*
|
||||
* Returns `null` if `line` isn't a representation of that clue.
|
||||
*/
|
||||
fun <T : ItemClass<T>> parse(line: String, mapper: (ItemClass<*>) -> Item<T>): Clue?
|
||||
}
|
||||
|
||||
sealed class HorizontalClue : Clue()
|
||||
@@ -66,6 +86,21 @@ class NeighbourClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: I
|
||||
}
|
||||
|
||||
override fun toString() = "$aType is next to $bType"
|
||||
|
||||
companion object : ClueParser {
|
||||
override fun <T : ItemClass<T>> parse(
|
||||
line: String,
|
||||
mapper: (ItemClass<*>) -> Item<T>
|
||||
): Clue? {
|
||||
val regex = Regex("^(\\* )?(?<a>[A-Z_]+) is next to (?<b>[A-Z_]+)$")
|
||||
val matchResult = regex.matchEntire(line) ?: return null
|
||||
|
||||
val a = ItemClass.parse(matchResult.groups["a"]!!.value)
|
||||
val b = ItemClass.parse(matchResult.groups["b"]!!.value)
|
||||
|
||||
return NeighbourClue(mapper(a), mapper(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderClue<L : ItemClass<L>, R : ItemClass<R>>(val left: Item<L>, val right: Item<R>) :
|
||||
@@ -113,6 +148,21 @@ class OrderClue<L : ItemClass<L>, R : ItemClass<R>>(val left: Item<L>, val right
|
||||
}
|
||||
|
||||
override fun toString() = "$leftType is left of $rightType"
|
||||
|
||||
companion object : ClueParser {
|
||||
override fun <T : ItemClass<T>> parse(
|
||||
line: String,
|
||||
mapper: (ItemClass<*>) -> Item<T>
|
||||
): Clue? {
|
||||
val regex = Regex("^(\\* )?(?<left>[A-Z_]+) is left of (?<right>[A-Z_]+)$")
|
||||
val matchResult = regex.matchEntire(line) ?: return null
|
||||
|
||||
val left = ItemClass.parse(matchResult.groups["left"]!!.value)
|
||||
val right = ItemClass.parse(matchResult.groups["right"]!!.value)
|
||||
|
||||
return OrderClue(mapper(left), mapper(right))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
@@ -242,6 +292,23 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
|
||||
override fun toString(): String =
|
||||
"$bType is between the neighbours $aType and $cType to both sides"
|
||||
|
||||
companion object : ClueParser {
|
||||
override fun <T : ItemClass<T>> parse(
|
||||
line: String,
|
||||
mapper: (ItemClass<*>) -> Item<T>
|
||||
): Clue? {
|
||||
val regex =
|
||||
Regex("^(\\* )?(?<b>[A-Z_]+) is between the neighbours (?<a>[A-Z_]+) and (?<c>[A-Z_]+) to both sides$")
|
||||
val matchResult = regex.matchEntire(line) ?: return null
|
||||
|
||||
val a = ItemClass.parse(matchResult.groups["a"]!!.value)
|
||||
val b = ItemClass.parse(matchResult.groups["b"]!!.value)
|
||||
val c = ItemClass.parse(matchResult.groups["c"]!!.value)
|
||||
|
||||
return TripletClue(mapper(a), mapper(b), mapper(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: Item<B>) : Clue() {
|
||||
@@ -291,6 +358,22 @@ class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b:
|
||||
}
|
||||
|
||||
override fun toString(): String = "$aType and $bType are in the same column"
|
||||
|
||||
companion object : ClueParser {
|
||||
override fun <T : ItemClass<T>> parse(
|
||||
line: String,
|
||||
mapper: (ItemClass<*>) -> Item<T>
|
||||
): Clue? {
|
||||
val regex = Regex("^(\\* )?(?<a>[A-Z_]+) and (?<b>[A-Z_]+) are in the same column$")
|
||||
val matchResult = regex.matchEntire(line) ?: return null
|
||||
|
||||
val a = ItemClass.parse(matchResult.groups["a"]!!.value)
|
||||
val b = ItemClass.parse(matchResult.groups["b"]!!.value)
|
||||
|
||||
return SameColumnClue(mapper(a), mapper(b))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue() {
|
||||
@@ -307,14 +390,31 @@ class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue()
|
||||
val row = grid[itemType.companion]
|
||||
var removed = false
|
||||
row.forEachIndexed { i, cell ->
|
||||
if (i == index) {
|
||||
removed = cell.options.retainAll { it == item } || removed
|
||||
} else {
|
||||
removed = cell.options.remove(item) || removed
|
||||
if (cell.hasNoSelection()) {
|
||||
removed = if (i == index) {
|
||||
cell.options.retainAll { it == item } || removed
|
||||
} else {
|
||||
cell.options.remove(item) || removed
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun toString() = "$itemType is at position $index"
|
||||
|
||||
companion object : ClueParser {
|
||||
override fun <T : ItemClass<T>> parse(
|
||||
line: String,
|
||||
mapper: (ItemClass<*>) -> Item<T>
|
||||
): Clue? {
|
||||
val regex = Regex("^(\\* )?(?<type>[A-Z_]+) is at position (?<pos>\\d)$")
|
||||
val matchResult = regex.matchEntire(line) ?: return null
|
||||
|
||||
val type = ItemClass.parse(matchResult.groups["type"]!!.value)
|
||||
val pos = matchResult.groups["pos"]!!.value.toInt()
|
||||
|
||||
return PositionClue(mapper(type), pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,8 @@ private fun idic(symbol: String): Array<String> = Array(GENDERS.size * SKIN_TONE
|
||||
sealed interface ItemClass<out SELF : ItemClass<SELF>> {
|
||||
val symbols: Array<String>
|
||||
|
||||
val name: String
|
||||
|
||||
val companion: ItemClassCompanion<SELF>
|
||||
|
||||
companion object {
|
||||
@@ -168,6 +170,10 @@ sealed interface ItemClass<out SELF : ItemClass<SELF>> {
|
||||
fun randomClasses(n: Int): List<ItemClassCompanion<*>> {
|
||||
return classes.shuffled().take(n)
|
||||
}
|
||||
|
||||
fun parse(name: String): ItemClass<*> {
|
||||
return classes.mapNotNull { it.parse(name) }.single()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,4 +183,8 @@ sealed interface ItemClassCompanion<out C : ItemClass<C>> {
|
||||
fun randomItems(n: Int): List<C> {
|
||||
return items.shuffled().take(n)
|
||||
}
|
||||
|
||||
fun parse(name: String): C? {
|
||||
return items.firstOrNull { it.name == name }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ class GameTest {
|
||||
game = generateGame()
|
||||
}
|
||||
println("Generated game #$i in ${time.inWholeMilliseconds}ms")
|
||||
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
|
||||
val solvable = solve(game.grid, game.clues)
|
||||
if (solvable != SOLVABLE) {
|
||||
println("Puzzle:\n$game")
|
||||
}
|
||||
expect(solvable).toEqual(SOLVABLE)
|
||||
expect(time).toBeLessThan(500.milliseconds)
|
||||
if (time < fastest) {
|
||||
fastest = time
|
||||
@@ -67,8 +71,8 @@ class GameTest {
|
||||
feature(Game::areCategoriesValid).toEqual(true)
|
||||
feature(Game::isValid).toEqual(true)
|
||||
feature(Game::clues) {
|
||||
feature(List<Clue>::size).toBeGreaterThan(5)
|
||||
feature(List<Clue>::size).toBeLessThan(30)
|
||||
feature(Collection<Clue>::size).toBeGreaterThan(5)
|
||||
feature(Collection<Clue>::size).toBeLessThan(30)
|
||||
}
|
||||
}
|
||||
println("Clues: ${game.clues.size}")
|
||||
@@ -124,4 +128,42 @@ class GameTest {
|
||||
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure specific game is solvable`() {
|
||||
val game = Game.parse("""
|
||||
👩🏿⚕️👨🏽🎤👩🏿⚕️ 👩🏾🚀🧑🏿🏫
|
||||
🐜🐕 🐐 🐐
|
||||
🍉🥭🍐🍇🍍
|
||||
🧁🍨🍩🍰🥧
|
||||
🇨🇭🇬🇧🇯🇵🇺🇦🇬🇧🇨🇦
|
||||
🍷🧃🍺🧃
|
||||
|
||||
* ZEBRA is between the neighbours PIE and PEAR to both sides
|
||||
* WINE is at position 0
|
||||
* SLOTH is between the neighbours ZEBRA and COFFEE to both sides
|
||||
* ICE_CREAM is left of MANGO
|
||||
* SWITZERLAND is at position 0
|
||||
* PIE is at position 4
|
||||
* SCIENTIST is between the neighbours ASTRONAUT and PEAR to both sides
|
||||
* ROCK_STAR is between the neighbours SNAIL and ANT to both sides
|
||||
* SNAIL is between the neighbours ROCK_STAR and TEA to both sides
|
||||
* SOFTWARE_DEV is left of SLOTH
|
||||
* SOFTWARE_DEV is left of HEALTH_WORKER
|
||||
* MILK is between the neighbours CUSTARD and ZEBRA to both sides
|
||||
* SLOTH is between the neighbours CUSTARD and CAKE to both sides
|
||||
* SPAIN is between the neighbours CUSTARD and GRAPES to both sides
|
||||
* SCIENTIST is between the neighbours SNAIL and SLOTH to both sides
|
||||
* DOG is between the neighbours CUPCAKE and BEER to both sides
|
||||
* SNAIL is between the neighbours BANANA and GRAPES to both sides
|
||||
* SLOTH is between the neighbours GRAPES and CANADA to both sides
|
||||
* UKRAINE and SCIENTIST are in the same column
|
||||
* DOG is between the neighbours JAPAN and SWITZERLAND to both sides
|
||||
* SLOTH is between the neighbours GOAT and TEA to both sides
|
||||
* ROCK_STAR and ENGLAND are in the same column
|
||||
* ROCK_STAR is next to DOUGHNUT
|
||||
* PINEAPPLE is between the neighbours TEACHER and GRAPES to both sides
|
||||
""".trimIndent())
|
||||
|
||||
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ class NeighbourClueTest : ClueTest() {
|
||||
val a = grid[ia][j - 1]
|
||||
val b = grid[ib][j]
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,8 @@ class NeighbourClueTest : ClueTest() {
|
||||
val a = grid[ia][ja]
|
||||
val b = grid[ib][jb]
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,32 +58,32 @@ class NeighbourClueTest : ClueTest() {
|
||||
rowB.forEach { it.selection = null; it.options.clear() }
|
||||
|
||||
a.selection = null
|
||||
a.options.add(a.solution)
|
||||
a.options.add(a.solution!!)
|
||||
b.selection = b.solution
|
||||
b.options.clear()
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
|
||||
a.selection = a.solution
|
||||
a.options.clear()
|
||||
b.selection = null
|
||||
b.options.add(b.solution)
|
||||
b.options.add(b.solution!!)
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
|
||||
if (j < size - 1) {
|
||||
val notA = rowA[j + 1]
|
||||
|
||||
a.selection = null
|
||||
a.options.clear()
|
||||
notA.options.add(a.solution)
|
||||
notA.options.add(a.solution!!)
|
||||
b.selection = b.solution
|
||||
b.options.clear()
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,8 +102,8 @@ class NeighbourClueTest : ClueTest() {
|
||||
|
||||
rowB[3].selection = b.solution
|
||||
|
||||
expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class OrderClueTest : ClueTest() {
|
||||
val a = grid[ia][ja]
|
||||
val b = grid[ib][jb]
|
||||
|
||||
expect(OrderClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(OrderClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class OrderClueTest : ClueTest() {
|
||||
val a = grid[ia][ja]
|
||||
val b = grid[ib][jb]
|
||||
|
||||
expect(OrderClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(OrderClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class OrderClueTest : ClueTest() {
|
||||
for (rowA in grid.rows) {
|
||||
for (rowB in grid.rows) {
|
||||
for (i in 0 until size) {
|
||||
expect(OrderClue(rowA[i].solution, rowB[i].solution).isValid(grid)).toEqual(false)
|
||||
expect(OrderClue(rowA[i].solution!!, rowB[i].solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ class SameColumnClueTest : ClueTest() {
|
||||
val a = grid[ia][j]
|
||||
val b = grid[ib][j]
|
||||
|
||||
expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(true)
|
||||
expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,8 @@ class SameColumnClueTest : ClueTest() {
|
||||
val a = grid[ia][ja]
|
||||
val b = grid[ib][jb]
|
||||
|
||||
expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ class SameColumnClueTest : ClueTest() {
|
||||
a.selection = a.solution
|
||||
b.selection = b.solution
|
||||
|
||||
expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,8 +87,8 @@ class SameColumnClueTest : ClueTest() {
|
||||
}
|
||||
}
|
||||
|
||||
expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ class TripletClueTest : ClueTest() {
|
||||
val b = grid[ib][j - 1]
|
||||
val c = grid[ic][j]
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,14 +38,14 @@ class TripletClueTest : ClueTest() {
|
||||
|
||||
rowB[0].selection = b.solution
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
|
||||
rowB[0].selection = null
|
||||
rowB[grid.size - 1].selection = b.solution
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,10 +61,10 @@ class TripletClueTest : ClueTest() {
|
||||
val rowC = grid[1]
|
||||
val c = rowC[ic]
|
||||
|
||||
val clue = TripletClue(a.solution, b.solution, c.solution)
|
||||
val clue = TripletClue(a.solution!!, b.solution!!, c.solution!!)
|
||||
|
||||
b.selection = b.solution
|
||||
c.options.add(c.solution)
|
||||
c.options.add(c.solution!!)
|
||||
|
||||
|
||||
rowA.forEachIndexed { index, notA ->
|
||||
@@ -75,7 +75,7 @@ class TripletClueTest : ClueTest() {
|
||||
}
|
||||
|
||||
index == ic -> {
|
||||
rowC[ia].options.add(c.solution)
|
||||
rowC[ia].options.add(c.solution!!)
|
||||
expect(clue.isValid(grid)).toEqual(true)
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ class TripletClueTest : ClueTest() {
|
||||
a.selection = a.solution
|
||||
grid[1][4].selection = c.solution
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,11 +115,11 @@ class TripletClueTest : ClueTest() {
|
||||
b.options.clear()
|
||||
c.options.clear()
|
||||
|
||||
rowB[4].options.add(b.solution)
|
||||
rowC[5].options.add(c.solution)
|
||||
rowB[4].options.add(b.solution!!)
|
||||
rowC[5].options.add(c.solution!!)
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(true)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,7 +136,7 @@ class TripletClueTest : ClueTest() {
|
||||
rowB[4].selection = rowC[3].solution
|
||||
c.selection = rowC[3].solution
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
|
||||
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user