Add tests

This commit is contained in:
2024-06-23 22:27:45 +02:00
parent a598218cf2
commit f76be158a0
7 changed files with 258 additions and 57 deletions

View File

@@ -3,7 +3,6 @@ package domain
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlin.math.abs
sealed class Clue {
abstract fun isRuleViolated(grid: Grid): Boolean
@@ -17,12 +16,21 @@ class NeighbourClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Horizont
private val bType = b.itemType
override fun isRuleViolated(grid: Grid): Boolean {
val ia = grid.indexOf(aType)
val ib = grid.indexOf(bType)
val rowA = grid[aType.companion]
val rowB = grid[bType.companion]
if (ia == -1 || ib == -1) return false
for (i in 1 until grid.size) {
if (rowA[i - 1].mayBe(a) &&
rowB[i - 0].mayBe(b)
)
return false
return abs(ia - ib) != 1
if (rowA[i - 0].mayBe(a) &&
rowB[i - 1].mayBe(b)
)
return false
}
return true
}
}
@@ -31,12 +39,10 @@ class OrderClue<C : ItemClass<C>>(val left: Item<C>, val right: Item<C>) : Horiz
private val rightType = right.itemType
override fun isRuleViolated(grid: Grid): Boolean {
val il = grid.indexOf(leftType)
val ir = grid.indexOf(rightType)
val rowLeft = grid[leftType.companion]
val rowRight = grid[rightType.companion]
if (il == -1 || ir == -1) return false
return ir <= il
return rowLeft.indexOfFirst { it.mayBe(left) } >= rowRight.indexOfLast { it.mayBe(right) }
}
}
@@ -46,33 +52,25 @@ class TripletClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>, val c: Item<
private val bType = b.itemType
private val cType = c.itemType
private fun isNeighbourRuleViolated(ix: Int, iy: Int): Boolean {
if (ix == -1 || iy == -1) return false
return abs(ix - iy) != 1
}
override fun isRuleViolated(grid: Grid): Boolean {
val ia = grid.indexOf(aType)
val ib = grid.indexOf(bType)
val ic = grid.indexOf(cType)
val rowA = grid[aType.companion]
val rowB = grid[bType.companion]
val rowC = grid[cType.companion]
if (ib == 0 || ib == grid.size) {
return true
for (i in 2 until grid.size) {
if (rowA[i - 2].mayBe(a) &&
rowB[i - 1].mayBe(b) &&
rowC[i - 0].mayBe(c)
)
return false
if (rowA[i - 0].mayBe(a) &&
rowB[i - 1].mayBe(b) &&
rowC[i - 2].mayBe(c)
)
return false
}
if (ia == -1 && ic == -1) {
return false
}
if (ia != -1 && ic != -1) {
return !(ia + 2 == ic || ia == ic + 2)
}
if (isNeighbourRuleViolated(ia, ib) || isNeighbourRuleViolated(ib, ic)) {
return true
}
return false
return true
}
}
@@ -81,23 +79,20 @@ class SameRowClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Clue() {
private val bType = b.itemType
override fun isRuleViolated(grid: Grid): Boolean {
val ia = grid.indexOf(aType)
val ib = grid.indexOf(bType)
val rowA = grid[aType.companion]
val rowB = grid[bType.companion]
if (ia == -1 || ib == -1) return false
return ia != ib
for (i in 0 until grid.size) {
if (rowA[i].mayBe(a) && rowB[i].mayBe(b)) {
return false
}
}
return true
}
}
class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue() {
private val aType = item.itemType
override fun isRuleViolated(grid: Grid): Boolean {
val ia = grid.indexOf(aType)
if (ia == -1) return false
return ia != index
return grid[item].mayBe(item)
}
}

View File

@@ -41,6 +41,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.)
}
// FIXME: I need to better include the options into the solver (rule violations checks)
private fun solve(
grid: Grid,
clues: Collection<Clue>
@@ -121,13 +122,11 @@ fun <C : ItemClass<C>> GameRow<C>.cleanupOptions(cell: GameCell<C>) {
private 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))
// }
// }
clues.add(PositionClue(rows.random().first(), 0))
clues.add(PositionClue(rows.random()[3], 3))
rows.forEach { row ->
row.forEachIndexed { i, item ->
clues.add(PositionClue(item, i))
}
}
rows.forEach { columns ->
columns.forEachIndexed { j, item ->

View File

@@ -9,7 +9,12 @@ class GameRow<C : ItemClass<C>>(
val category: ItemClassCompanion<C>,
val options: List<Item<C>>,
val cells: List<GameCell<C>>
) : List<GameCell<C>> by cells
) : List<GameCell<C>> by cells {
fun updateOptions() {
val selections = mapNotNull { it.selection }
forEach { it.options.removeAll(selections) }
}
}
class Grid(
val rows: List<GameRow<ItemClass<*>>>
@@ -25,6 +30,14 @@ class Grid(
return rows.first { it.category == itemType } as GameRow<C>
}
operator fun <C : ItemClass<C>> get(item: Item<C>): GameCell<C> {
return this[item.itemType.companion].first { it.selection == item }
}
override fun toString(): String {
return rows.map { row -> row.map { it.selection?.symbol ?: " " }.joinToString("") }
.joinToString("\n")
}
}
fun List<List<Item<ItemClass<*>>>>.toGrid() = Grid(
@@ -50,6 +63,8 @@ class GameCell<C : ItemClass<C>>(
) {
val options = options.toMutableStateList()
var selection by mutableStateOf(selection)
fun mayBe(item: Item<C>) = selection == item || options.contains(item)
}
class Item<C : ItemClass<C>>(