Add tests
This commit is contained in:
@@ -10,9 +10,14 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.unit.dp
|
||||
import domain.Clue
|
||||
import domain.Grid
|
||||
import domain.HorizontalClue
|
||||
import domain.ItemClass
|
||||
@@ -28,12 +33,18 @@ import yaep.composeapp.generated.resources.Res
|
||||
import yaep.composeapp.generated.resources.neighbour
|
||||
import yaep.composeapp.generated.resources.order
|
||||
|
||||
class DisplayClue<C : Clue>(val clue: C) {
|
||||
var isActive by mutableStateOf(true)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun App(modifier: Modifier = Modifier) {
|
||||
val game = generateGame()
|
||||
val game = remember { generateGame() }
|
||||
val horizontalClues = remember { game.horizontalClues.map { DisplayClue(it) } }
|
||||
val verticalClues = remember { game.verticalClues.map { DisplayClue(it) } }
|
||||
Row(modifier = modifier) {
|
||||
PuzzleGrid(modifier = Modifier.weight(1f), game.grid)
|
||||
PuzzleClues(modifier = Modifier.weight(1f), game.horizontalClues, game.verticalClues)
|
||||
PuzzleClues(modifier = Modifier.weight(1f), horizontalClues, verticalClues)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +79,8 @@ fun PuzzleGrid(
|
||||
@Composable
|
||||
fun PuzzleClues(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalClues: List<HorizontalClue>,
|
||||
verticalClues: List<SameRowClue<ItemClass<*>>>
|
||||
horizontalClues: List<DisplayClue<HorizontalClue>>,
|
||||
verticalClues: List<DisplayClue<SameRowClue<ItemClass<*>>>>
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
LazyVerticalGrid(
|
||||
@@ -80,7 +91,7 @@ fun PuzzleClues(
|
||||
item {
|
||||
HorizontalClue(
|
||||
modifier = Modifier.clickable { clue.isActive = false },
|
||||
clue = clue
|
||||
clue = clue.clue
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +101,7 @@ fun PuzzleClues(
|
||||
modifier = Modifier
|
||||
.alpha(0.5f)
|
||||
.clickable { clue.isActive = true },
|
||||
clue = clue
|
||||
clue = clue.clue
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -104,7 +115,7 @@ fun PuzzleClues(
|
||||
item {
|
||||
VerticalClue(
|
||||
modifier = Modifier.clickable { clue.isActive = false },
|
||||
clue = clue
|
||||
clue = clue.clue
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +125,7 @@ fun PuzzleClues(
|
||||
modifier = Modifier
|
||||
.alpha(0.5f)
|
||||
.clickable { clue.isActive = true },
|
||||
clue = clue
|
||||
clue = clue.clue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
package domain
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
|
||||
sealed class Clue {
|
||||
abstract fun isRuleViolated(grid: Grid): Boolean
|
||||
var isActive: Boolean by mutableStateOf(true)
|
||||
}
|
||||
|
||||
sealed class HorizontalClue : Clue()
|
||||
@@ -58,6 +53,10 @@ class TripletClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>, val c: Item<
|
||||
val rowC = grid[cType.companion]
|
||||
|
||||
for (i in 2 until grid.size) {
|
||||
if (rowA[i-2].selection == a){
|
||||
return rowB[i - 1].mayBe(b) &&
|
||||
rowC[i - 0].mayBe(c)
|
||||
}
|
||||
if (rowA[i - 2].mayBe(a) &&
|
||||
rowB[i - 1].mayBe(b) &&
|
||||
rowC[i - 0].mayBe(c)
|
||||
|
||||
@@ -18,10 +18,6 @@ fun generateGame(size: Int = 6): Game {
|
||||
// if there are 5 houses, there are 5 possible clues of the form "Person A lives in
|
||||
// house B", 8 possible clues of the form "Person A lives next to house B", and so on.)
|
||||
var clues = getAllClues(grid).shuffled()
|
||||
// var positionClues: MutableSet<PositionClue<out ItemClass<*>>> = grid.flatMap { row ->
|
||||
// row.mapIndexed { i, item -> PositionClue(item, i) }
|
||||
// }.toMutableSet()
|
||||
|
||||
|
||||
var i = 0
|
||||
|
||||
@@ -51,13 +47,14 @@ private fun solve(
|
||||
// First, set the positions of the items that are already known.
|
||||
clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
||||
val row = grid[position.item.itemType.companion]
|
||||
val newSelections = mutableListOf<GameCell<ItemClass<*>>>()
|
||||
row.forEachIndexed { index, gameCell ->
|
||||
if (index == position.index) {
|
||||
gameCell.selection = position.item
|
||||
} else {
|
||||
gameCell.options.remove(position.item)
|
||||
newSelections.add(gameCell)
|
||||
}
|
||||
}
|
||||
newSelections.forEach { row.cleanupOptions(it) }
|
||||
}
|
||||
|
||||
// For each clue, remove any items that violate the clue.
|
||||
@@ -73,11 +70,10 @@ private fun solve(
|
||||
} while (removedOptions)
|
||||
|
||||
// If any cell has no items left, the puzzle has no solution.
|
||||
grid.flatMap { it }.forEach { cell ->
|
||||
if (cell.options.isEmpty()) {
|
||||
if (grid.flatMap { it }.any { cell -> cell.options.isEmpty() }) {
|
||||
return NO_SOLUTION
|
||||
}
|
||||
}
|
||||
|
||||
// If all cells have exactly one item left, the puzzle is solved.
|
||||
if (grid.flatMap { it }.all { it.selection != null }) return SOLVABLE
|
||||
|
||||
@@ -122,11 +118,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))
|
||||
}
|
||||
}
|
||||
// rows.forEach { row ->
|
||||
// row.forEachIndexed { i, item ->
|
||||
// clues.add(PositionClue(item, i))
|
||||
// }
|
||||
// }
|
||||
|
||||
rows.forEach { columns ->
|
||||
columns.forEachIndexed { j, item ->
|
||||
|
||||
@@ -64,7 +64,7 @@ class GameCell<C : ItemClass<C>>(
|
||||
val options = options.toMutableStateList()
|
||||
var selection by mutableStateOf(selection)
|
||||
|
||||
fun mayBe(item: Item<C>) = selection == item || options.contains(item)
|
||||
fun mayBe(item: Item<C>) = selection == item || (selection == null && options.contains(item))
|
||||
}
|
||||
|
||||
class Item<C : ItemClass<C>>(
|
||||
|
||||
@@ -19,7 +19,7 @@ class TripletClueTest : ClueTest() {
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
.toEqual(false)
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid))
|
||||
.toEqual(false)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,33 @@ class TripletClueTest : ClueTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure that middle might not be at the corners`() {
|
||||
val grid = createGrid { null }
|
||||
|
||||
val rowA = grid.random()
|
||||
val rowB = grid.random()
|
||||
val rowC = grid.random()
|
||||
val a = rowA[1]
|
||||
val b = rowB[2]
|
||||
val c = rowC[3]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure grid where a or c and b are not neighbours is invalid`() {
|
||||
val grid = createGrid { null }
|
||||
@@ -78,7 +105,7 @@ class TripletClueTest : ClueTest() {
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
.toEqual(true)
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid))
|
||||
.toEqual(true)
|
||||
}
|
||||
|
||||
@@ -98,7 +125,7 @@ class TripletClueTest : ClueTest() {
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
.toEqual(false)
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid))
|
||||
.toEqual(false)
|
||||
}
|
||||
|
||||
@@ -118,7 +145,7 @@ class TripletClueTest : ClueTest() {
|
||||
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
.toEqual(true)
|
||||
expect(TripletClue(a.solution, b.solution, c.solution).isRuleViolated(grid))
|
||||
expect(TripletClue(c.solution, b.solution, a.solution).isRuleViolated(grid))
|
||||
.toEqual(true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user