Add improvements
There is an issue related to the new code finding single options
This commit is contained in:
@@ -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,11 +117,16 @@ 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
|
||||||
|
if (it != null) {
|
||||||
row.cleanupOptions()
|
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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
if (option.enabled) {
|
||||||
option.enabled = false
|
option.enabled = false
|
||||||
onOptionRemoved(option.item)
|
onOptionRemoved(option.item)
|
||||||
|
} else {
|
||||||
|
option.enabled = true
|
||||||
|
onOptionAdded(option.item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,13 +276,17 @@ 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 (cellB.hasNoSelection()) {
|
||||||
if (!cellA.mayBe(a)) {
|
if (!cellA.mayBe(a)) {
|
||||||
removed = cellB.options.remove(b) || removed
|
removed = cellB.options.remove(b) || removed
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (cellA.hasNoSelection()) {
|
||||||
if (!cellB.mayBe(b)) {
|
if (!cellB.mayBe(b)) {
|
||||||
removed = cellA.options.remove(a) || removed
|
removed = cellA.options.remove(a) || removed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user