From 6356e0f4acf0afb9e9cbbdebc9f71c713cd24eee Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Thu, 18 Jul 2024 18:05:01 +0200 Subject: [PATCH] Change clues interface (WIP) --- .../main/kotlin/ch/dissem/yaep/domain/Game.kt | 17 +- .../kotlin/ch/dissem/yaep/domain/GameCell.kt | 9 +- .../kotlin/ch/dissem/yaep/domain/clues.kt | 159 ++++++------------ .../kotlin/ch/dissem/yaep/domain/GameTest.kt | 2 +- .../dissem/yaep/domain/NeighbourClueTest.kt | 38 ++--- .../ch/dissem/yaep/domain/OrderClueTest.kt | 9 +- .../dissem/yaep/domain/SameColumnClueTest.kt | 26 +-- .../ch/dissem/yaep/domain/TripletClueTest.kt | 42 ++--- 8 files changed, 107 insertions(+), 195 deletions(-) diff --git a/domain/src/main/kotlin/ch/dissem/yaep/domain/Game.kt b/domain/src/main/kotlin/ch/dissem/yaep/domain/Game.kt index 89adbcc..1f59c17 100644 --- a/domain/src/main/kotlin/ch/dissem/yaep/domain/Game.kt +++ b/domain/src/main/kotlin/ch/dissem/yaep/domain/Game.kt @@ -20,19 +20,16 @@ class Game( } } + /** + * Make sure that no category is used more than once. + */ fun areCategoriesValid(): Boolean { - val usedCategories = mutableSetOf>() - for (row in grid.rows) { - if (usedCategories.contains(row.category)) { - return false - } - usedCategories.add(row.category) - } - return true + return grid.rows.map { it.category }.distinct().size == grid.rows.size } - fun areRulesViolated(): Boolean = clues - .any { it.isRuleViolated(grid) } + fun isValid(): Boolean = areCategoriesValid() && clues.all { it.isValid(grid) } + + fun isSolved(): Boolean = grid.cells.all { it.selection != null } override fun toString(): String { return grid.toString() + "\n\n" + clues.joinToString("\n* ", prefix = "* ") diff --git a/domain/src/main/kotlin/ch/dissem/yaep/domain/GameCell.kt b/domain/src/main/kotlin/ch/dissem/yaep/domain/GameCell.kt index 7e36c85..a5b2b14 100644 --- a/domain/src/main/kotlin/ch/dissem/yaep/domain/GameCell.kt +++ b/domain/src/main/kotlin/ch/dissem/yaep/domain/GameCell.kt @@ -5,11 +5,12 @@ class GameCell>( val solution: Item, val options: MutableList> ) -//{ -// val options = options.toMutableStateList() -// var selection by mutableStateOf(selection) -//} fun > GameCell?.mayBe(item: Item, mayHaveSelection: Boolean = true) = this != null && ((mayHaveSelection && selection == item) || (selection == null && options.contains(item))) + +fun > GameCell?.isA(item: Item) = + this != null && selection == item + +fun > GameCell?.hasNoSelection() = this != null && this.selection == null diff --git a/domain/src/main/kotlin/ch/dissem/yaep/domain/clues.kt b/domain/src/main/kotlin/ch/dissem/yaep/domain/clues.kt index efa90c5..32975f0 100644 --- a/domain/src/main/kotlin/ch/dissem/yaep/domain/clues.kt +++ b/domain/src/main/kotlin/ch/dissem/yaep/domain/clues.kt @@ -1,7 +1,7 @@ package ch.dissem.yaep.domain sealed class Clue { - abstract fun isRuleViolated(grid: Grid): Boolean + abstract fun isValid(grid: Grid): Boolean /** * @return `true` if any option was removed @@ -16,33 +16,19 @@ class NeighbourClue, B : ItemClass>(val a: Item, val b: I private val aType = a.itemType private val bType = b.itemType - override fun isRuleViolated(grid: Grid): Boolean { + override fun isValid(grid: Grid): Boolean { val rowA = grid[aType.companion] val rowB = grid[bType.companion] val ia = rowA.indexOf(aType) - val ib by lazy { rowB.indexOf(bType) } + val ib = rowB.indexOf(bType) + if (ia != -1) { - if (ib != -1) return !(ib == ia - 1 || ib == ia + 1) - return !(rowB.getOrNull(ia - 1).mayBe(b) || - rowB.getOrNull(ia + 1).mayBe(b)) + if (ib != -1) return ib == ia - 1 || ib == ia + 1 + return rowB.getOrNull(ia - 1).hasNoSelection() || rowB.getOrNull(ia + 1).hasNoSelection() } - if (ib != -1) { - return !(rowA.getOrNull(ib - 1).mayBe(a) || - rowA.getOrNull(ib + 1).mayBe(a)) - } - - for (i in 1 until grid.size) { - if (rowA[i - 1].mayBe(a) && - rowB[i - 0].mayBe(b) - ) - return false - - if (rowA[i - 0].mayBe(a) && - rowB[i - 1].mayBe(b) - ) - return false + return rowA.getOrNull(ib - 1).hasNoSelection() || rowA.getOrNull(ib + 1).hasNoSelection() } return true } @@ -86,23 +72,22 @@ class OrderClue, R : ItemClass>(val left: Item, val right private val leftType = left.itemType private val rightType = right.itemType - override fun isRuleViolated(grid: Grid): Boolean { + override fun isValid(grid: Grid): Boolean { val rowLeft = grid[leftType.companion] val rowRight = grid[rightType.companion] val iLeft = rowLeft.indexOf(leftType) val iRight by lazy { rowRight.indexOf(rightType) } + if (iLeft != -1) { - if (iRight != -1) return iRight <= iLeft + if (iRight != -1) return iRight > iLeft - return rowRight.indexOfLast { it.mayBe(right) } in 0..iLeft + return rowRight.indexOfLast { it.hasNoSelection() } > iLeft } - if (iRight != -1) { - return rowLeft.indexOfFirst { it.mayBe(left) } >= iRight + return rowLeft.indexOfFirst { it.hasNoSelection() } in 0 until iRight } - - return rowLeft.indexOfFirst { it.mayBe(left) } >= rowRight.indexOfLast { it.mayBe(right) } + return rowLeft.indexOfFirst { it.hasNoSelection() } < rowRight.indexOfLast { it.hasNoSelection() } } override fun removeForbiddenOptions(grid: Grid): Boolean { @@ -136,7 +121,7 @@ class TripletClue, B : ItemClass, C : ItemClass>( private val bType = b.itemType private val cType = c.itemType - override fun isRuleViolated(grid: Grid): Boolean { + override fun isValid(grid: Grid): Boolean { val rowA = grid[aType.companion] val rowB by lazy { grid[bType.companion] } val rowC by lazy { grid[cType.companion] } @@ -146,73 +131,45 @@ class TripletClue, B : ItemClass, C : ItemClass>( val ic by lazy { rowC.indexOf(cType) } if (ia != -1) { - if (ib != -1) { - when (ib) { - ia - 1 -> { - return if (ic != -1) { - ic != ia - 2 - } else if (ia - 2 >= 0) { - !rowC[ia - 2].mayBe(c) - } else { - true - } - } - - ia + 1 -> { - return if (ic != -1) { - ic != ia + 2 - } else if (ia + 2 < grid.size) { - !rowC[ia + 2].mayBe(c) - } else { - true - } - } - - else -> return true + return when (ib) { + -1 -> when (ic) { + -1 -> (rowB[ia - 1].hasNoSelection() && rowC[ia - 2].hasNoSelection()) || (rowB[ia + 1].hasNoSelection() && rowC[ia + 2].hasNoSelection()) + ia - 2 -> rowB[ia - 1].hasNoSelection() + ia + 2 -> rowB[ia + 1].hasNoSelection() + else -> false } - } - return when (ic) { - -1 -> !(rowB.getOrNull(ia - 1).mayBe(b) && rowC.getOrNull(ia - 2).mayBe(c)) && - !(rowB.getOrNull(ia + 1).mayBe(b) && rowC.getOrNull(ia + 2).mayBe(c)) - ia - 2 -> !rowB[ia - 1].mayBe(b) - ia + 2 -> !rowB[ia + 1].mayBe(b) - else -> true - } - } + ia - 1 -> when (ic) { + -1 -> rowC[ia - 2].hasNoSelection() + ia - 2 -> true + else -> false + } - if (ib != -1) { - if (ib == 0 || ib == rowB.size - 1) return true + ia + 1 -> when (ic) { + -1 -> rowC[ia + 2].hasNoSelection() + ia + 2 -> true + else -> false + } - return when (ic) { - -1 -> !(rowA.getOrNull(ib - 1).mayBe(a) && rowC.getOrNull(ib + 1).mayBe(c)) && - !(rowA.getOrNull(ib + 1).mayBe(a) && rowC.getOrNull(ib - 1).mayBe(c)) - - ib - 1 -> !rowA[ib + 1].mayBe(a) - ib + 1 -> !rowA[ib - 1].mayBe(a) else -> false } } - + if (ib != -1) { + when (ic) { + -1 -> return (rowA[ib - 1].hasNoSelection() && rowC[ib + 1].hasNoSelection()) || (rowA[ib + 1].hasNoSelection() && rowC[ib - 1].hasNoSelection()) + ib - 1 -> return rowA[ib + 1].hasNoSelection() + ib + 1 -> return rowA[ib - 1].hasNoSelection() + } + } if (ic != -1) { - return !(rowB.getOrNull(ic - 1).mayBe(b) && rowA.getOrNull(ic - 2).mayBe(a)) && - !(rowB.getOrNull(ic + 1).mayBe(b) && rowA.getOrNull(ic + 2).mayBe(a)) + return (rowB[ic - 1].hasNoSelection() && rowA[ic - 2].hasNoSelection()) || (rowB[ic + 1].hasNoSelection() && rowA[ic + 2].hasNoSelection()) } - - 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 - } - return true + return rowA.mapIndexed { index, gameCell -> if (gameCell.hasNoSelection()) index else null } + .filterNotNull() + .any { index -> + (rowB.getOrNull(index - 1).hasNoSelection() && rowC.getOrNull(index - 2).hasNoSelection()) || + (rowB.getOrNull(index + 1).hasNoSelection() && rowC.getOrNull(index + 2).hasNoSelection()) + } } override fun removeForbiddenOptions(grid: Grid): Boolean { @@ -274,7 +231,7 @@ class SameColumnClue, B : ItemClass>(val a: Item, val b: private val aType = a.itemType private val bType = b.itemType - override fun isRuleViolated(grid: Grid): Boolean { + override fun isValid(grid: Grid): Boolean { val rowA = grid[aType.companion] val rowB = grid[bType.companion] @@ -282,23 +239,15 @@ class SameColumnClue, B : ItemClass>(val a: Item, val b: val ib by lazy { rowB.indexOf(bType) } if (ia != -1) { - return if (ib != -1) { - ib != ia - } else { - !rowB[ia].mayBe(b) - } + return if (ib == -1) rowB[ia].hasNoSelection() + else ib == ia } - if (ib != -1) { - return !rowA[ib].mayBe(a) + return rowA[ib].hasNoSelection() } - - for (i in 0 until grid.size) { - if (rowA[i].mayBe(a) && rowB[i].mayBe(b)) { - return false - } - } - return true + return rowA.mapIndexed { index, gameCell -> if (gameCell.hasNoSelection()) index else null } + .filterNotNull() + .any { index -> rowB[index].hasNoSelection() } } override fun removeForbiddenOptions(grid: Grid): Boolean { @@ -326,11 +275,11 @@ class SameColumnClue, B : ItemClass>(val a: Item, val b: class PositionClue>(val item: Item, val index: Int) : Clue() { val itemType = item.itemType - override fun isRuleViolated(grid: Grid): Boolean { + override fun isValid(grid: Grid): Boolean { val i = grid.indexOf(item.itemType) - if (i != -1) return i != index + if (i != -1) return i == index - return grid[item].mayBe(item) + return grid[item].hasNoSelection() } override fun removeForbiddenOptions(grid: Grid): Boolean { diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt index 7baa59c..93fbe81 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt @@ -64,7 +64,7 @@ class GameTest { } expect(game) { feature(Game::areCategoriesValid).toEqual(true) - feature(Game::areRulesViolated).toEqual(false) + feature(Game::isValid).toEqual(true) feature(Game::clues) { feature(List::size).toBeGreaterThan(5) feature(List::size).toBeLessThan(30) diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/NeighbourClueTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/NeighbourClueTest.kt index d16ac83..9c5684d 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/NeighbourClueTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/NeighbourClueTest.kt @@ -15,10 +15,8 @@ class NeighbourClueTest : ClueTest() { val a = grid[ia][j - 1] val b = grid[ib][j] - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true) + expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true) } } } @@ -37,10 +35,8 @@ class NeighbourClueTest : ClueTest() { val a = grid[ia][ja] val b = grid[ib][jb] - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(true) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(false) + expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(false) } } } @@ -66,20 +62,16 @@ class NeighbourClueTest : ClueTest() { b.selection = b.solution b.options.clear() - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + 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) - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + 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] @@ -90,10 +82,8 @@ class NeighbourClueTest : ClueTest() { b.selection = b.solution b.options.clear() - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(true) + expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(true) } } } @@ -112,10 +102,8 @@ class NeighbourClueTest : ClueTest() { rowB[3].selection = b.solution - expect(NeighbourClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(true) - expect(NeighbourClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(NeighbourClue(a.solution, b.solution).isValid(grid)).toEqual(false) + expect(NeighbourClue(b.solution, a.solution).isValid(grid)).toEqual(false) } -} \ No newline at end of file +} diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/OrderClueTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/OrderClueTest.kt index d2b8df8..faa2250 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/OrderClueTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/OrderClueTest.kt @@ -16,8 +16,7 @@ class OrderClueTest : ClueTest() { val a = grid[ia][ja] val b = grid[ib][jb] - expect(OrderClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) + expect(OrderClue(a.solution, b.solution).isValid(grid)).toEqual(true) } } } @@ -36,8 +35,7 @@ class OrderClueTest : ClueTest() { val a = grid[ia][ja] val b = grid[ib][jb] - expect(OrderClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(OrderClue(b.solution, a.solution).isValid(grid)).toEqual(false) } } } @@ -51,8 +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).isRuleViolated(grid)) - .toEqual(true) + expect(OrderClue(rowA[i].solution, rowB[i].solution).isValid(grid)).toEqual(false) } } } diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/SameColumnClueTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/SameColumnClueTest.kt index e8b5d71..fe9175c 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/SameColumnClueTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/SameColumnClueTest.kt @@ -14,10 +14,8 @@ class SameColumnClueTest : ClueTest() { val a = grid[ia][j] val b = grid[ib][j] - expect(SameColumnClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(false) - expect(SameColumnClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(true) + expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(true) } } } @@ -34,10 +32,8 @@ class SameColumnClueTest : ClueTest() { val a = grid[ia][ja] val b = grid[ib][jb] - expect(SameColumnClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(true) - expect(SameColumnClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false) + expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false) } } } @@ -59,10 +55,8 @@ class SameColumnClueTest : ClueTest() { b.selection = null b.options.remove(b.solution) - expect(SameColumnClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(true) - expect(SameColumnClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false) + expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false) } } } @@ -94,10 +88,8 @@ class SameColumnClueTest : ClueTest() { } } - expect(SameColumnClue(a.solution, b.solution).isRuleViolated(grid)) - .toEqual(true) - expect(SameColumnClue(b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(SameColumnClue(a.solution, b.solution).isValid(grid)).toEqual(false) + expect(SameColumnClue(b.solution, a.solution).isValid(grid)).toEqual(false) } } -} \ No newline at end of file +} diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/TripletClueTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/TripletClueTest.kt index bbb0970..fed4881 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/TripletClueTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/TripletClueTest.kt @@ -17,10 +17,8 @@ class TripletClueTest : ClueTest() { val b = grid[ib][j - 1] val c = grid[ic][j] - expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid)) - .toEqual(false) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(true) + expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(true) } } } @@ -40,18 +38,14 @@ class TripletClueTest : ClueTest() { rowB[0].selection = b.solution - expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid)) - .toEqual(true) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + 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).isRuleViolated(grid)) - .toEqual(true) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + 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 @@ -82,11 +76,11 @@ class TripletClueTest : ClueTest() { index == ic -> { rowC[ia].options.add(c.solution) - expect(clue.isRuleViolated(grid)).toEqual(false) + expect(clue.isValid(grid)).toEqual(true) } else -> { - expect(clue.isRuleViolated(grid)).toEqual(true) + expect(clue.isValid(grid)).toEqual(false) } } notA.selection = null @@ -103,10 +97,8 @@ class TripletClueTest : ClueTest() { a.selection = a.solution grid[1][4].selection = c.solution - expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid)) - .toEqual(true) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + 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 @@ -126,10 +118,8 @@ class TripletClueTest : ClueTest() { rowB[4].options.add(b.solution) rowC[5].options.add(c.solution) - expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid)) - .toEqual(false) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(false) + 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 @@ -146,9 +136,7 @@ class TripletClueTest : ClueTest() { grid[0][4].options.add(b.solution) grid[1][5].options.remove(c.solution) - expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid)) - .toEqual(true) - expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid)) - .toEqual(true) + expect(TripletClue(a.solution, b.solution, c.solution).isValid(grid)).toEqual(false) + expect(TripletClue(c.solution, b.solution, a.solution).isValid(grid)).toEqual(false) } -} \ No newline at end of file +}