Add improvements

There is an issue related to the new code finding single options
This commit is contained in:
2024-07-28 10:15:58 +02:00
parent e673b3cb4d
commit a019ea406d
5 changed files with 112 additions and 25 deletions

View File

@@ -1,21 +1,45 @@
package ch.dissem.yaep.ui.common package ch.dissem.yaep.ui.common
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.layout.* import androidx.compose.foundation.PointerMatcher.Companion.mouse
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.onClick
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.input.pointer.PointerButton.Companion.Secondary
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ch.dissem.yaep.domain.* import ch.dissem.yaep.domain.Clue
import ch.dissem.yaep.domain.Game
import ch.dissem.yaep.domain.Grid
import ch.dissem.yaep.domain.HorizontalClue
import ch.dissem.yaep.domain.ItemClass
import ch.dissem.yaep.domain.NeighbourClue
import ch.dissem.yaep.domain.OrderClue
import ch.dissem.yaep.domain.SameColumnClue
import ch.dissem.yaep.domain.TripletClue
import ch.dissem.yaep.domain.generateGame
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import yaep.commonui.generated.resources.Res import yaep.commonui.generated.resources.Res
import yaep.commonui.generated.resources.neighbour import yaep.commonui.generated.resources.neighbour
@@ -93,10 +117,15 @@ fun PuzzleGrid(
item.options.remove(it) item.options.remove(it)
row.cleanupOptions() row.cleanupOptions()
}, },
onOptionAdded = {
item.options.add(it)
},
selectedItem = selection, selectedItem = selection,
onSelectItem = { onSelectItem = {
item.selection = it item.selection = it
row.cleanupOptions() if (it != null) {
row.cleanupOptions()
}
} }
) )
} }
@@ -147,10 +176,11 @@ fun PuzzleClues(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
private fun Modifier.forClue(clue: DisplayClue<out Clue>) = this private fun Modifier.forClue(clue: DisplayClue<out Clue>) = this
.alpha(if (clue.isActive) 1f else 0.2f) .alpha(if (clue.isActive) 1f else 0.2f)
.padding(8.dp) .padding(8.dp)
.clickable { clue.isActive = false } .onClick(matcher = PointerMatcher.Primary + mouse(Secondary)) { clue.isActive = !clue.isActive }
@Composable @Composable
fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueViolated: Boolean) { fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueViolated: Boolean) {
@@ -191,7 +221,11 @@ fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueVi
} }
@Composable @Composable
fun VerticalClue(modifier: Modifier = Modifier, clue: SameColumnClue<*, *>, isClueViolated: Boolean = false) { fun VerticalClue(
modifier: Modifier = Modifier,
clue: SameColumnClue<*, *>,
isClueViolated: Boolean = false
) {
ClueCard( ClueCard(
modifier = modifier.aspectRatio(0.5f), modifier = modifier.aspectRatio(0.5f),
isClueViolated = isClueViolated isClueViolated = isClueViolated
@@ -204,11 +238,20 @@ fun VerticalClue(modifier: Modifier = Modifier, clue: SameColumnClue<*, *>, isCl
} }
@Composable @Composable
fun ClueCard(modifier: Modifier = Modifier, isClueViolated: Boolean, content: @Composable () -> Unit) { fun ClueCard(
modifier: Modifier = Modifier,
isClueViolated: Boolean,
content: @Composable () -> Unit
) {
val colors = MaterialTheme.colorScheme val colors = MaterialTheme.colorScheme
OutlinedCard( OutlinedCard(
modifier = if (isClueViolated) { modifier = if (isClueViolated) {
modifier.shadow(8.dp, shape = CardDefaults.outlinedShape, ambientColor = colors.error, spotColor = colors.error) modifier.shadow(
8.dp,
shape = CardDefaults.outlinedShape,
ambientColor = colors.error,
spotColor = colors.error
)
} else { } else {
modifier modifier
}, },

View File

@@ -28,7 +28,8 @@ import kotlin.math.min
fun <C : ItemClass<C>> Selector( fun <C : ItemClass<C>> Selector(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
options: List<Toggleable<Item<C>>>, options: List<Toggleable<Item<C>>>,
onOptionRemoved: (Item<C>?) -> Unit, onOptionRemoved: (Item<C>) -> Unit,
onOptionAdded: (Item<C>) -> Unit,
selectedItem: Item<C>?, selectedItem: Item<C>?,
onSelectItem: (Item<C>?) -> Unit, onSelectItem: (Item<C>?) -> Unit,
) { ) {
@@ -54,8 +55,13 @@ fun <C : ItemClass<C>> Selector(
.onClick( .onClick(
matcher = PointerMatcher.mouse(PointerButton.Secondary), matcher = PointerMatcher.mouse(PointerButton.Secondary),
onClick = { onClick = {
option.enabled = false if (option.enabled) {
onOptionRemoved(option.item) option.enabled = false
onOptionRemoved(option.item)
} else {
option.enabled = true
onOptionAdded(option.item)
}
} }
) )
) )

View File

@@ -8,18 +8,49 @@ class GameRow<C : ItemClass<C>>(
fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element } fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element }
fun cleanupOptions() { fun cleanupOptions() {
cells.forEach { cleanupOptions(it) } cells.forEach {
cleanupOptions(it)
}
// do {
// var selectedSingleOption = false
// cells.forEach { cleanupOptions(it) }
// val selections = cells.mapNotNull { it.selection }
// options
// .filter { !selections.contains(it) }
// .forEach { option ->
// if (cells.count { cell -> cell.options.contains(option) } == 1) {
// cells
// .filter { it.selection == null }
// .first { cell -> cell.options.contains(option) }
// .let { it.selection = option }
// selectedSingleOption = true
// }
// }
// } while (selectedSingleOption)
} }
private fun cleanupOptions(cell: GameCell<C>, firstCall: Boolean = true) { private fun cleanupOptions(cell: GameCell<C>, justSelected: Boolean = true) {
if ((firstCall && cell.selection != null) || (cell.options.size == 1 && cell.selection == null)) { if ((justSelected && cell.selection != null) || (cell.options.size == 1 && cell.selection == null)) {
if (cell.selection == null) { val selection = cell.selection
if (selection == null) {
cell.selection = cell.options.first() cell.selection = cell.options.first()
} else {
cell.options.clear()
cell.options.add(selection)
} }
filter { otherCell -> otherCell != cell && otherCell.selection == null }.forEach { otherCell -> filter { otherCell -> otherCell != cell && otherCell.hasNoSelection() }.forEach { otherCell ->
otherCell.options.remove(cell.selection) otherCell.options.remove(cell.selection)
cleanupOptions(otherCell, false) cleanupOptions(otherCell, false)
} }
filter { it.selection == null }
.flatMap { c -> c.options.map { o -> o to c } }
.groupBy { it.first }
.filter { it.value.size == 1 }
.forEach {
val c = it.value.single().second
c.selection = it.key
cleanupOptions(c, true)
}
} }
} }

View File

@@ -55,10 +55,9 @@ class NeighbourClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: I
for (iX in rowX.indices) { for (iX in rowX.indices) {
val cellX = rowX[iX] val cellX = rowX[iX]
if (cellX.options.contains(x)) { if (cellX.mayBe(x, mayHaveSelection = false)) {
if (!rowY.getOrNull(iX - 1).mayBe(y) && !rowY.getOrNull(iX + 1).mayBe(y)) { if (!rowY.getOrNull(iX - 1).mayBe(y) && !rowY.getOrNull(iX + 1).mayBe(y)) {
cellX.options.remove(x) removed = cellX.options.remove(x) || removed
removed = true
} }
} }
} }
@@ -102,8 +101,11 @@ class OrderClue<L : ItemClass<L>, R : ItemClass<R>>(val left: Item<L>, val right
var removed = false var removed = false
try { try {
rowL.takeLast(rowL.size - lastR) rowL.takeLast(rowL.size - lastR)
.filter { it.selection == null }
.forEach { removed = it.options.remove(left) || removed } .forEach { removed = it.options.remove(left) || removed }
rowR.take(firstL + 1).forEach { removed = it.options.remove(right) || removed } rowR.take(firstL + 1)
.filter { it.selection == null }
.forEach { removed = it.options.remove(right) || removed }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw UnsolvablePuzzleException(e) throw UnsolvablePuzzleException(e)
} }
@@ -274,11 +276,15 @@ class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b:
val cellA = rowA[i] val cellA = rowA[i]
val cellB = rowB[i] val cellB = rowB[i]
if (!cellA.mayBe(a)) { if (cellB.hasNoSelection()) {
removed = cellB.options.remove(b) || removed if (!cellA.mayBe(a)) {
removed = cellB.options.remove(b) || removed
}
} }
if (!cellB.mayBe(b)) { if (cellA.hasNoSelection()) {
removed = cellA.options.remove(a) || removed if (!cellB.mayBe(b)) {
removed = cellA.options.remove(a) || removed
}
} }
} }
return removed return removed

View File

@@ -29,6 +29,7 @@ class GameTest {
val time = measureTime { val time = measureTime {
game = generateGame() game = generateGame()
} }
println("Generated game #$i in ${time.inWholeMilliseconds}ms")
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE) expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
expect(time).toBeLessThan(500.milliseconds) expect(time).toBeLessThan(500.milliseconds)
if (time < fastest) { if (time < fastest) {