From 4c7cc6802455b10e13569b593faf2077d90c8b92 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 24 Jun 2024 18:42:39 +0200 Subject: [PATCH] Add tests --- composeApp/src/commonMain/kotlin/App.kt | 27 +++++++++----- .../src/commonMain/kotlin/domain/clues.kt | 9 +++-- .../src/commonMain/kotlin/domain/generator.kt | 26 ++++++-------- .../src/commonMain/kotlin/domain/grid.kt | 2 +- .../kotlin/domain/TripletClueTest.kt | 35 ++++++++++++++++--- 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index 049a38d..3456daf 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -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(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, - verticalClues: List>> + horizontalClues: List>, + verticalClues: List>>> ) { 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 ) } } diff --git a/composeApp/src/commonMain/kotlin/domain/clues.kt b/composeApp/src/commonMain/kotlin/domain/clues.kt index e34ce49..a65558f 100644 --- a/composeApp/src/commonMain/kotlin/domain/clues.kt +++ b/composeApp/src/commonMain/kotlin/domain/clues.kt @@ -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>(val a: Item, val b: Item, 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) diff --git a/composeApp/src/commonMain/kotlin/domain/generator.kt b/composeApp/src/commonMain/kotlin/domain/generator.kt index 06a5852..7169e05 100644 --- a/composeApp/src/commonMain/kotlin/domain/generator.kt +++ b/composeApp/src/commonMain/kotlin/domain/generator.kt @@ -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>> = 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>>().forEach { position -> val row = grid[position.item.itemType.companion] + val newSelections = mutableListOf>>() 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()) { - return NO_SOLUTION - } + 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 > GameRow.cleanupOptions(cell: GameCell) { private fun getAllClues(rows: List>>>): MutableSet { val clues = mutableSetOf() - 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 -> diff --git a/composeApp/src/commonMain/kotlin/domain/grid.kt b/composeApp/src/commonMain/kotlin/domain/grid.kt index e345b67..e01fe7b 100644 --- a/composeApp/src/commonMain/kotlin/domain/grid.kt +++ b/composeApp/src/commonMain/kotlin/domain/grid.kt @@ -64,7 +64,7 @@ class GameCell>( val options = options.toMutableStateList() var selection by mutableStateOf(selection) - fun mayBe(item: Item) = selection == item || options.contains(item) + fun mayBe(item: Item) = selection == item || (selection == null && options.contains(item)) } class Item>( diff --git a/composeApp/src/commonTest/kotlin/domain/TripletClueTest.kt b/composeApp/src/commonTest/kotlin/domain/TripletClueTest.kt index 8793de4..bc349cf 100644 --- a/composeApp/src/commonTest/kotlin/domain/TripletClueTest.kt +++ b/composeApp/src/commonTest/kotlin/domain/TripletClueTest.kt @@ -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) } } \ No newline at end of file