Add Clue.removeForbiddenOptions (WIP)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
class UnsolvablePuzzleException(e: Exception) : Exception(e)
|
||||
@@ -2,6 +2,11 @@ package ch.dissem.yaep.domain
|
||||
|
||||
sealed class Clue {
|
||||
abstract fun isRuleViolated(grid: Grid): Boolean
|
||||
|
||||
/**
|
||||
* @return `true` if any option was removed
|
||||
*/
|
||||
abstract fun removeForbiddenOptions(grid: Grid): Boolean
|
||||
}
|
||||
|
||||
sealed class HorizontalClue : Clue()
|
||||
@@ -42,6 +47,37 @@ class NeighbourClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: I
|
||||
return true
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
val rowA: GameRow<A> = grid[aType.companion]
|
||||
val rowB: GameRow<B> = grid[bType.companion]
|
||||
|
||||
var removed = removeForbiddenOptions(a, b, rowA, rowB)
|
||||
removed = removeForbiddenOptions(b, a, rowB, rowA) || removed
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
private fun <X : ItemClass<X>, Y : ItemClass<Y>> removeForbiddenOptions(
|
||||
x: Item<X>,
|
||||
y: Item<Y>,
|
||||
rowX: GameRow<X>,
|
||||
rowY: GameRow<Y>
|
||||
): Boolean {
|
||||
var removed = false
|
||||
|
||||
for (iX in rowX.indices) {
|
||||
val cellX = rowX[iX]
|
||||
if (cellX.options.contains(x)) {
|
||||
if (!rowY.getOrNull(iX - 1).mayBe(y) && !rowY.getOrNull(iX + 1).mayBe(y)) {
|
||||
cellX.options.remove(x)
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun toString() = "$aType is next to $bType"
|
||||
}
|
||||
|
||||
@@ -69,6 +105,24 @@ class OrderClue<L : ItemClass<L>, R : ItemClass<R>>(val left: Item<L>, val right
|
||||
return rowLeft.indexOfFirst { it.mayBe(left) } >= rowRight.indexOfLast { it.mayBe(right) }
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
val rowL = grid[leftType.companion]
|
||||
val rowR = grid[rightType.companion]
|
||||
|
||||
val firstL = rowL.indexOfFirst { it.options.contains(left) }
|
||||
val lastR = rowR.indexOfLast { it.options.contains(right) }
|
||||
|
||||
var removed = false
|
||||
try {
|
||||
rowL.takeLast(rowL.size - lastR + 1)
|
||||
.forEach { removed = it.options.remove(left) || removed }
|
||||
rowR.take(firstL).forEach { removed = it.options.remove(right) || removed }
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw UnsolvablePuzzleException(e)
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun toString() = "$leftType is left of $rightType"
|
||||
}
|
||||
|
||||
@@ -161,6 +215,10 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
return true
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"$bType is between the neighbours $aType and $cType to both sides"
|
||||
}
|
||||
@@ -196,6 +254,25 @@ class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b:
|
||||
return true
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
val rowA = grid[aType.companion]
|
||||
val rowB = grid[bType.companion]
|
||||
|
||||
var removed = false
|
||||
for (i in rowA.indices) {
|
||||
val cellA = rowA[i]
|
||||
val cellB = rowB[i]
|
||||
|
||||
if (!cellA.mayBe(a)) {
|
||||
removed = cellB.options.remove(b) || removed
|
||||
}
|
||||
if (!cellB.mayBe(b)) {
|
||||
removed = cellA.options.remove(a) || removed
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun toString(): String = "$aType and $bType are in the same column"
|
||||
}
|
||||
|
||||
@@ -209,5 +286,18 @@ class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue()
|
||||
return grid[item].mayBe(item)
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
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
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun toString() = "$itemType is at position $index"
|
||||
}
|
||||
|
||||
@@ -44,23 +44,29 @@ internal fun solve(
|
||||
// Start with a grid where each cell is a list of all possible items.
|
||||
|
||||
// First, set the positions of the items that are already known.
|
||||
clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
||||
val row = grid[position.itemType.companion]
|
||||
val cell = row[position.index]
|
||||
cell.options.retainAll { it == position.item }
|
||||
row.cleanupOptions(cell)
|
||||
}
|
||||
// clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
||||
// position.removeForbiddenOptions(grid)
|
||||
// }
|
||||
// grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
|
||||
|
||||
// For each clue, remove any items that violate the clue.
|
||||
// If any cell has only one item left, remove that item from all other cells.
|
||||
// Repeat until no more items can be removed.
|
||||
val otherClues = clues.filter { it !is PositionClue<*> }
|
||||
// val otherClues = clues.filter { it !is PositionClue<*> }
|
||||
var removedOptions: Boolean
|
||||
do {
|
||||
removedOptions = false
|
||||
grid.forEach { row ->
|
||||
removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
||||
try {
|
||||
clues.forEach { clue ->
|
||||
removedOptions = clue.removeForbiddenOptions(grid) || removedOptions
|
||||
}
|
||||
} catch (e: UnsolvablePuzzleException) {
|
||||
return NO_SOLUTION
|
||||
}
|
||||
grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
|
||||
// grid.forEach { row ->
|
||||
// removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
||||
// }
|
||||
} while (removedOptions)
|
||||
|
||||
// If any cell has no items left, the puzzle has no solution.
|
||||
@@ -70,15 +76,16 @@ internal fun solve(
|
||||
if (grid.cells.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.
|
||||
return if (
|
||||
grid.cells
|
||||
.filter { it.selection == null }
|
||||
.any { it.countSolutions(grid, clues) == 1 }
|
||||
) {
|
||||
SOLVABLE
|
||||
} else {
|
||||
MULTIPLE_SOLUTIONS
|
||||
}
|
||||
// return if (
|
||||
// grid.cells
|
||||
// .filter { it.selection == null }
|
||||
// .any { it.countSolutions(grid, clues) == 1 }
|
||||
// ) {
|
||||
// SOLVABLE
|
||||
// } else {
|
||||
// MULTIPLE_SOLUTIONS
|
||||
// }
|
||||
return MULTIPLE_SOLUTIONS
|
||||
}
|
||||
|
||||
internal fun <C : ItemClass<C>> GameCell<C>.countSolutions(
|
||||
|
||||
Reference in New Issue
Block a user