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 {
|
sealed class Clue {
|
||||||
abstract fun isRuleViolated(grid: Grid): Boolean
|
abstract fun isRuleViolated(grid: Grid): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if any option was removed
|
||||||
|
*/
|
||||||
|
abstract fun removeForbiddenOptions(grid: Grid): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class HorizontalClue : Clue()
|
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
|
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"
|
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) }
|
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"
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"$bType is between the neighbours $aType and $cType to both sides"
|
"$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
|
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"
|
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)
|
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"
|
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.
|
// 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.
|
// First, set the positions of the items that are already known.
|
||||||
clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
// clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
||||||
val row = grid[position.itemType.companion]
|
// position.removeForbiddenOptions(grid)
|
||||||
val cell = row[position.index]
|
// }
|
||||||
cell.options.retainAll { it == position.item }
|
// grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
|
||||||
row.cleanupOptions(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each clue, remove any items that violate the clue.
|
// 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.
|
// If any cell has only one item left, remove that item from all other cells.
|
||||||
// Repeat until no more items can be removed.
|
// 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
|
var removedOptions: Boolean
|
||||||
do {
|
do {
|
||||||
removedOptions = false
|
removedOptions = false
|
||||||
grid.forEach { row ->
|
try {
|
||||||
removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
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)
|
} while (removedOptions)
|
||||||
|
|
||||||
// If any cell has no items left, the puzzle has no solution.
|
// 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 (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.
|
// If there are still cells with multiple items, pick one and try each item in turn, then go back to step 2.
|
||||||
return if (
|
// return if (
|
||||||
grid.cells
|
// grid.cells
|
||||||
.filter { it.selection == null }
|
// .filter { it.selection == null }
|
||||||
.any { it.countSolutions(grid, clues) == 1 }
|
// .any { it.countSolutions(grid, clues) == 1 }
|
||||||
) {
|
// ) {
|
||||||
SOLVABLE
|
// SOLVABLE
|
||||||
} else {
|
// } else {
|
||||||
MULTIPLE_SOLUTIONS
|
// MULTIPLE_SOLUTIONS
|
||||||
}
|
// }
|
||||||
|
return MULTIPLE_SOLUTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <C : ItemClass<C>> GameCell<C>.countSolutions(
|
internal fun <C : ItemClass<C>> GameCell<C>.countSolutions(
|
||||||
|
|||||||
Reference in New Issue
Block a user