Improve and fix solver

This commit is contained in:
2024-07-30 23:11:45 +02:00
parent a019ea406d
commit 3a2865f199
10 changed files with 334 additions and 92 deletions

View File

@@ -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()) }
)
}
}
}

View File

@@ -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>()

View File

@@ -5,43 +5,38 @@ 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)
if (
isSolved && all {
it.solution != null
&& it.options.size == 1
&& it.options.single() == it.selection
}
// 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)
) 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!!)
}
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)
cellsWithSelection.mapNotNull { it.selection }.toMutableSet()
}
filter { otherCell -> otherCell != cell && otherCell.hasNoSelection() }.forEach { otherCell ->
otherCell.options.remove(cell.selection)
cleanupOptions(otherCell, false)
filter { it.selection == null }
.forEach { it.options.removeAll(selections) }
filter { it.selection == null && it.options.size == 1 }
.forEach {
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 }
@@ -49,9 +44,15 @@ class GameRow<C : ItemClass<C>>(
.forEach {
val c = it.value.single().second
c.selection = it.key
cleanupOptions(c, true)
changed = true
}
}
} while (changed)
isSolved = all {
it.solution != null
&& it.options.size == 1
&& it.options.single() == it.selection
}
}
}

View File

@@ -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
if (cell.hasNoSelection()) {
removed = if (i == index) {
cell.options.retainAll { it == item } || removed
} else {
removed = cell.options.remove(item) || removed
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)
}
}
}

View File

@@ -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 }
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}