Add tests

This commit is contained in:
Christian Basler
2024-06-24 18:42:39 +02:00
parent f76be158a0
commit 4c7cc68024
5 changed files with 66 additions and 33 deletions

View File

@@ -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
)
}
}

View File

@@ -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)

View File

@@ -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 ->

View File

@@ -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>>(

View File

@@ -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)
}
}