From fcbebe802f159fe942acbf2df0501d064bc0fa13 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 19 Jun 2024 23:53:42 +0200 Subject: [PATCH] Render puzzle (WIP) --- .../composeResources/drawable/neighbour.xml | 10 + .../composeResources/drawable/order.xml | 10 + composeApp/src/commonMain/kotlin/App.kt | 171 +++++++++++++++--- .../src/commonMain/kotlin/domain/clues.kt | 15 +- .../src/commonMain/kotlin/domain/grid.kt | 18 +- .../src/commonMain/kotlin/domain/items.kt | 6 +- .../src/commonMain/kotlin/ui/selector.kt | 32 ++-- 7 files changed, 203 insertions(+), 59 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/neighbour.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/order.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/neighbour.xml b/composeApp/src/commonMain/composeResources/drawable/neighbour.xml new file mode 100644 index 0000000..6231d05 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/neighbour.xml @@ -0,0 +1,10 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/order.xml b/composeApp/src/commonMain/composeResources/drawable/order.xml new file mode 100644 index 0000000..1a72fd4 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/order.xml @@ -0,0 +1,10 @@ + + + diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index 4e821ee..049a38d 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -1,42 +1,163 @@ -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +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.LazyVerticalGrid +import androidx.compose.material3.OutlinedCard +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import domain.Animals -import domain.Item -import org.jetbrains.compose.resources.ExperimentalResourceApi +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp +import domain.Grid +import domain.HorizontalClue +import domain.ItemClass +import domain.NeighbourClue +import domain.OrderClue +import domain.SameRowClue +import domain.TripletClue +import domain.generateGame import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.ui.tooling.preview.Preview import ui.DrawItem import ui.Selector - import yaep.composeapp.generated.resources.Res -import yaep.composeapp.generated.resources.compose_multiplatform +import yaep.composeapp.generated.resources.neighbour +import yaep.composeapp.generated.resources.order @Composable -@Preview fun App(modifier: Modifier = Modifier) { - val size = 6 + val game = generateGame() + Row(modifier = modifier) { + PuzzleGrid(modifier = Modifier.weight(1f), game.grid) + PuzzleClues(modifier = Modifier.weight(1f), game.horizontalClues, game.verticalClues) + } +} + +@Composable +fun PuzzleGrid( + modifier: Modifier = Modifier, + grid: Grid +) { Column(modifier = modifier) { - Row { - val options = remember { Animals.items.shuffled().take(size) } - for (option in options) { - var selectedItem by remember { mutableStateOf?>(Item(option)) } - Selector( - category = Animals, - options = Animals.items.map { Item(it) }, - selectedItem = selectedItem, - onSelectItem = { selectedItem = it }, - modifier = Modifier.weight(1f) - ) + for (row in grid.rows) { + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + for (item in row) { + Selector( + modifier = Modifier + .padding(4.dp) + .weight(1f), + category = row.category, + options = item.options, + selectedItem = item.selection, + onSelectItem = { item.selection = it } + ) + } } } } +} + +@Composable +fun PuzzleClues( + modifier: Modifier = Modifier, + horizontalClues: List, + verticalClues: List>> +) { + Column(modifier = modifier) { + LazyVerticalGrid( + modifier = Modifier.fillMaxWidth().weight(1f), + columns = GridCells.Adaptive(32.dp) + ) { + for (clue in horizontalClues.filter { it.isActive }) { + item { + HorizontalClue( + modifier = Modifier.clickable { clue.isActive = false }, + clue = clue + ) + } + } + for (clue in horizontalClues.filter { !it.isActive }) { + item { + HorizontalClue( + modifier = Modifier + .alpha(0.5f) + .clickable { clue.isActive = true }, + clue = clue + ) + } + } + } + + LazyVerticalGrid( + modifier = Modifier.fillMaxWidth().weight(1f), + columns = GridCells.Adaptive(32.dp) + ) { + for (clue in verticalClues.filter { it.isActive }) { + item { + VerticalClue( + modifier = Modifier.clickable { clue.isActive = false }, + clue = clue + ) + } + } + for (clue in verticalClues.filter { !it.isActive }) { + item { + VerticalClue( + modifier = Modifier + .alpha(0.5f) + .clickable { clue.isActive = true }, + clue = clue + ) + } + } + } + } +} + +@Composable +fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue) { + Column { + when (clue) { + is NeighbourClue<*> -> { + DrawItem(modifier = Modifier.weight(1f), clue.a) + OutlinedCard(modifier = modifier.aspectRatio(1f).weight(1f)) { + Image( + painter = painterResource(Res.drawable.neighbour), + contentDescription = null + ) + } + DrawItem(modifier = Modifier.weight(1f), clue.b) + } + + is OrderClue<*> -> { + DrawItem(modifier = Modifier.weight(1f), clue.left) + OutlinedCard(modifier = modifier.aspectRatio(1f).weight(1f)) { + Image(painter = painterResource(Res.drawable.order), contentDescription = null) + } + DrawItem(modifier = Modifier.weight(1f), clue.right) + } + + is TripletClue<*> -> { + DrawItem(modifier = Modifier.weight(1f), clue.a) + DrawItem(modifier = Modifier.weight(1f), clue.b) + DrawItem(modifier = Modifier.weight(1f), clue.c) + } + } + } +} + +@Composable +fun VerticalClue(modifier: Modifier = Modifier, clue: SameRowClue<*>) { + Column(modifier = modifier) { + DrawItem(modifier = Modifier.weight(1f), clue.a) + DrawItem(modifier = Modifier.weight(1f), clue.b) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/domain/clues.kt b/composeApp/src/commonMain/kotlin/domain/clues.kt index 615de14..0ffe13d 100644 --- a/composeApp/src/commonMain/kotlin/domain/clues.kt +++ b/composeApp/src/commonMain/kotlin/domain/clues.kt @@ -1,14 +1,18 @@ package domain +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import kotlin.math.abs sealed class Clue { abstract fun isRuleViolated(grid: Grid): Boolean + var isActive: Boolean by mutableStateOf(true) } sealed class HorizontalClue : Clue() -class NeighbourClue>(val a: Item, val b: Item) : HorizontalClue() { +class NeighbourClue>(val a: Item, val b: Item) : HorizontalClue() { private val aType = a.itemType private val bType = b.itemType @@ -22,7 +26,7 @@ class NeighbourClue>(val a: Item, val b: Item) : Horizontal } } -class OrderClue>(val left: Item, val right: Item) : HorizontalClue() { +class OrderClue>(val left: Item, val right: Item) : HorizontalClue() { private val leftType = left.itemType private val rightType = right.itemType @@ -36,7 +40,8 @@ class OrderClue>(val left: Item, val right: Item) : Horizon } } -class TripletClue>(val a: Item, val b: Item, val c: Item) : HorizontalClue() { +class TripletClue>(val a: Item, val b: Item, val c: Item) : + HorizontalClue() { private val aType = a.itemType private val bType = b.itemType private val cType = c.itemType @@ -67,7 +72,7 @@ class TripletClue>(val a: Item, val b: Item, val c: Item } } -class SameRowClue>(val a: Item, val b: Item) : Clue() { +class SameRowClue>(val a: Item, val b: Item) : Clue() { private val aType = a.itemType private val bType = b.itemType @@ -81,7 +86,7 @@ class SameRowClue>(val a: Item, val b: Item) : Clue() { } } -class PositionClue>(val item: Item, val index: Int) : Clue() { +class PositionClue>(val item: Item, val index: Int) : Clue() { private val aType = item.itemType override fun isRuleViolated(grid: Grid): Boolean { diff --git a/composeApp/src/commonMain/kotlin/domain/grid.kt b/composeApp/src/commonMain/kotlin/domain/grid.kt index 578cd17..2df5914 100644 --- a/composeApp/src/commonMain/kotlin/domain/grid.kt +++ b/composeApp/src/commonMain/kotlin/domain/grid.kt @@ -1,5 +1,10 @@ package domain +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList + class GameRow>( val category: ItemClassCompanion, val options: List>, @@ -10,12 +15,12 @@ class Grid( val rows: List>> ) : List>> by rows { - fun > indexOf(element: C): Int { + fun > indexOf(element: C): Int { return this[element.companion] .indexOfFirst { it.selection?.itemType == element } } - operator fun > get(itemType: ItemClassCompanion): GameRow { + operator fun > get(itemType: ItemClassCompanion): GameRow { @Suppress("UNCHECKED_CAST") return rows.first { it.category == itemType } as GameRow } @@ -33,10 +38,13 @@ fun List>>>.toGrid() = Grid( ) class GameCell>( - var selection: Item?, + selection: Item?, val solution: Item, - val options: MutableList> -) + options: List> +) { + val options = options.toMutableStateList() + var selection by mutableStateOf(selection) +} class Item>( val itemType: C, diff --git a/composeApp/src/commonMain/kotlin/domain/items.kt b/composeApp/src/commonMain/kotlin/domain/items.kt index 5df4e3a..1514402 100644 --- a/composeApp/src/commonMain/kotlin/domain/items.kt +++ b/composeApp/src/commonMain/kotlin/domain/items.kt @@ -89,7 +89,7 @@ enum class Fruit(symbol: String) : ItemClass { PEAR("🍐"), MANGO("🥭"); - override val symbols: Array = idic(symbol) + override val symbols: Array = arrayOf(symbol) override val companion get() = Fruit @@ -110,7 +110,7 @@ enum class Dessert(symbol: String) : ItemClass { LOLLIPOP("🍭"), CUSTARD("🍮"); - override val symbols: Array = idic(symbol) + override val symbols: Array = arrayOf(symbol) override val companion get() = Dessert @@ -129,7 +129,7 @@ enum class Transportation(symbol: String) : ItemClass { TRAM_CAR("🚋"), BUS("🚌"); - override val symbols: Array = idic(symbol) + override val symbols: Array = arrayOf(symbol) override val companion get() = Transportation diff --git a/composeApp/src/commonMain/kotlin/ui/selector.kt b/composeApp/src/commonMain/kotlin/ui/selector.kt index ac0c1d4..ac7808b 100644 --- a/composeApp/src/commonMain/kotlin/ui/selector.kt +++ b/composeApp/src/commonMain/kotlin/ui/selector.kt @@ -2,10 +2,11 @@ package ui import androidx.compose.foundation.Canvas import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.OutlinedCard import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -14,7 +15,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer @@ -37,7 +37,11 @@ fun > Selector( DrawItem(item = selectedItem, modifier = modifier.clickable { onSelectItem(null) }) } else { OutlinedCard(modifier = modifier.aspectRatio(1f)) { - LazyHorizontalGrid(rows = GridCells.Fixed(3)) { + LazyVerticalGrid( + columns = GridCells.Fixed(3), + verticalArrangement = Arrangement.Center, + horizontalArrangement = Arrangement.Center + ) { for (option in options) { item { DrawItem( @@ -52,7 +56,10 @@ fun > Selector( } @Composable -fun > DrawItem(item: Item, modifier: Modifier = Modifier) { +fun > DrawItem( + modifier: Modifier = Modifier, + item: Item +) { OutlinedCard(modifier = modifier.aspectRatio(1f)) { val emoji = item.symbol @@ -61,7 +68,6 @@ fun > DrawItem(item: Item, modifier: Modifier = Modifier) { Canvas( modifier = Modifier.fillMaxSize(1f), onDraw = { - drawRect(Color.Red, size = size) val textSize = textMeasurer.measure(text = emoji) val minTextSizeDimension = min( textSize.size.width, @@ -87,19 +93,3 @@ fun > DrawItem(item: Item, modifier: Modifier = Modifier) { ) } } - -@Preview -@Composable -fun SelectorPreview() { - val size = 6 - val options = remember { Animals.items.shuffled().take(size) } - var selectedItem by remember { mutableStateOf?>(Item(options.random())) } -// var selectedItem by remember { mutableStateOf?>(null) } -// Selector( -// category = Animals, -// options = Animals.items.map { Item(it) }, -// selectedItem = selectedItem, -// onSelectItem = { selectedItem = it } -// ) - DrawItem(selectedItem!!) -} \ No newline at end of file