Render puzzle (WIP)

This commit is contained in:
2024-06-19 23:53:42 +02:00
parent 755c3de295
commit fcbebe802f
7 changed files with 203 additions and 59 deletions

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M280,680L80,480L280,280L336,336L233,440L727,440L624,336L680,280L880,480L680,680L624,624L727,520L233,520L336,624L280,680Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Z"/>
</vector>

View File

@@ -1,42 +1,163 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Text import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.runtime.* import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.ui.Alignment import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import domain.Animals import androidx.compose.ui.draw.alpha
import domain.Item import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.ExperimentalResourceApi 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.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import ui.DrawItem import ui.DrawItem
import ui.Selector import ui.Selector
import yaep.composeapp.generated.resources.Res 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 @Composable
@Preview
fun App(modifier: Modifier = Modifier) { 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) { Column(modifier = modifier) {
Row { for (row in grid.rows) {
val options = remember { Animals.items.shuffled().take(size) } Row(
for (option in options) { modifier = Modifier
var selectedItem by remember { mutableStateOf<Item<Animals>?>(Item(option)) } .fillMaxWidth()
.wrapContentHeight()
) {
for (item in row) {
Selector( Selector(
category = Animals, modifier = Modifier
options = Animals.items.map { Item(it) }, .padding(4.dp)
selectedItem = selectedItem, .weight(1f),
onSelectItem = { selectedItem = it }, category = row.category,
modifier = Modifier.weight(1f) options = item.options,
selectedItem = item.selection,
onSelectItem = { item.selection = it }
) )
} }
} }
} }
}
}
@Composable
fun PuzzleClues(
modifier: Modifier = Modifier,
horizontalClues: List<HorizontalClue>,
verticalClues: List<SameRowClue<ItemClass<*>>>
) {
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)
}
} }

View File

@@ -1,14 +1,18 @@
package domain package domain
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlin.math.abs import kotlin.math.abs
sealed class Clue { sealed class Clue {
abstract fun isRuleViolated(grid: Grid): Boolean abstract fun isRuleViolated(grid: Grid): Boolean
var isActive: Boolean by mutableStateOf(true)
} }
sealed class HorizontalClue : Clue() sealed class HorizontalClue : Clue()
class NeighbourClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>) : HorizontalClue() { class NeighbourClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>) : HorizontalClue() {
private val aType = a.itemType private val aType = a.itemType
private val bType = b.itemType private val bType = b.itemType
@@ -22,7 +26,7 @@ class NeighbourClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Horizontal
} }
} }
class OrderClue<C:ItemClass<C>>(val left: Item<C>, val right: Item<C>) : HorizontalClue() { class OrderClue<C : ItemClass<C>>(val left: Item<C>, val right: Item<C>) : HorizontalClue() {
private val leftType = left.itemType private val leftType = left.itemType
private val rightType = right.itemType private val rightType = right.itemType
@@ -36,7 +40,8 @@ class OrderClue<C:ItemClass<C>>(val left: Item<C>, val right: Item<C>) : Horizon
} }
} }
class TripletClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>, val c: Item<C>) : HorizontalClue() { class TripletClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>, val c: Item<C>) :
HorizontalClue() {
private val aType = a.itemType private val aType = a.itemType
private val bType = b.itemType private val bType = b.itemType
private val cType = c.itemType private val cType = c.itemType
@@ -67,7 +72,7 @@ class TripletClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>, val c: Item<C>
} }
} }
class SameRowClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Clue() { class SameRowClue<C : ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Clue() {
private val aType = a.itemType private val aType = a.itemType
private val bType = b.itemType private val bType = b.itemType
@@ -81,7 +86,7 @@ class SameRowClue<C:ItemClass<C>>(val a: Item<C>, val b: Item<C>) : Clue() {
} }
} }
class PositionClue<C:ItemClass<C>>(val item: Item<C>, val index: Int) : Clue() { class PositionClue<C : ItemClass<C>>(val item: Item<C>, val index: Int) : Clue() {
private val aType = item.itemType private val aType = item.itemType
override fun isRuleViolated(grid: Grid): Boolean { override fun isRuleViolated(grid: Grid): Boolean {

View File

@@ -1,5 +1,10 @@
package domain package domain
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
class GameRow<C : ItemClass<C>>( class GameRow<C : ItemClass<C>>(
val category: ItemClassCompanion<C>, val category: ItemClassCompanion<C>,
val options: List<Item<C>>, val options: List<Item<C>>,
@@ -10,12 +15,12 @@ class Grid(
val rows: List<GameRow<ItemClass<*>>> val rows: List<GameRow<ItemClass<*>>>
) : List<GameRow<ItemClass<*>>> by rows { ) : List<GameRow<ItemClass<*>>> by rows {
fun <C: ItemClass<C>> indexOf(element: C): Int { fun <C : ItemClass<C>> indexOf(element: C): Int {
return this[element.companion] return this[element.companion]
.indexOfFirst { it.selection?.itemType == element } .indexOfFirst { it.selection?.itemType == element }
} }
operator fun <C: ItemClass<C>> get(itemType: ItemClassCompanion<C>): GameRow<C> { operator fun <C : ItemClass<C>> get(itemType: ItemClassCompanion<C>): GameRow<C> {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return rows.first { it.category == itemType } as GameRow<C> return rows.first { it.category == itemType } as GameRow<C>
} }
@@ -33,10 +38,13 @@ fun List<List<Item<ItemClass<*>>>>.toGrid() = Grid(
) )
class GameCell<C : ItemClass<C>>( class GameCell<C : ItemClass<C>>(
var selection: Item<C>?, selection: Item<C>?,
val solution: Item<C>, val solution: Item<C>,
val options: MutableList<Item<C>> options: List<Item<C>>
) ) {
val options = options.toMutableStateList()
var selection by mutableStateOf(selection)
}
class Item<C : ItemClass<C>>( class Item<C : ItemClass<C>>(
val itemType: C, val itemType: C,

View File

@@ -89,7 +89,7 @@ enum class Fruit(symbol: String) : ItemClass<Fruit> {
PEAR("🍐"), PEAR("🍐"),
MANGO("🥭"); MANGO("🥭");
override val symbols: Array<String> = idic(symbol) override val symbols: Array<String> = arrayOf(symbol)
override val companion override val companion
get() = Fruit get() = Fruit
@@ -110,7 +110,7 @@ enum class Dessert(symbol: String) : ItemClass<Dessert> {
LOLLIPOP("🍭"), LOLLIPOP("🍭"),
CUSTARD("🍮"); CUSTARD("🍮");
override val symbols: Array<String> = idic(symbol) override val symbols: Array<String> = arrayOf(symbol)
override val companion override val companion
get() = Dessert get() = Dessert
@@ -129,7 +129,7 @@ enum class Transportation(symbol: String) : ItemClass<Transportation> {
TRAM_CAR("🚋"), TRAM_CAR("🚋"),
BUS("🚌"); BUS("🚌");
override val symbols: Array<String> = idic(symbol) override val symbols: Array<String> = arrayOf(symbol)
override val companion override val companion
get() = Transportation get() = Transportation

View File

@@ -2,10 +2,11 @@ package ui
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridCells 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.material3.OutlinedCard
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -14,7 +15,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
@@ -37,7 +37,11 @@ fun <C : ItemClass<C>> Selector(
DrawItem(item = selectedItem, modifier = modifier.clickable { onSelectItem(null) }) DrawItem(item = selectedItem, modifier = modifier.clickable { onSelectItem(null) })
} else { } else {
OutlinedCard(modifier = modifier.aspectRatio(1f)) { 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) { for (option in options) {
item { item {
DrawItem( DrawItem(
@@ -52,7 +56,10 @@ fun <C : ItemClass<C>> Selector(
} }
@Composable @Composable
fun <C : ItemClass<C>> DrawItem(item: Item<C>, modifier: Modifier = Modifier) { fun <C : ItemClass<C>> DrawItem(
modifier: Modifier = Modifier,
item: Item<C>
) {
OutlinedCard(modifier = modifier.aspectRatio(1f)) { OutlinedCard(modifier = modifier.aspectRatio(1f)) {
val emoji = item.symbol val emoji = item.symbol
@@ -61,7 +68,6 @@ fun <C : ItemClass<C>> DrawItem(item: Item<C>, modifier: Modifier = Modifier) {
Canvas( Canvas(
modifier = Modifier.fillMaxSize(1f), modifier = Modifier.fillMaxSize(1f),
onDraw = { onDraw = {
drawRect(Color.Red, size = size)
val textSize = textMeasurer.measure(text = emoji) val textSize = textMeasurer.measure(text = emoji)
val minTextSizeDimension = min( val minTextSizeDimension = min(
textSize.size.width, textSize.size.width,
@@ -87,19 +93,3 @@ fun <C : ItemClass<C>> DrawItem(item: Item<C>, modifier: Modifier = Modifier) {
) )
} }
} }
@Preview
@Composable
fun SelectorPreview() {
val size = 6
val options = remember { Animals.items.shuffled().take(size) }
var selectedItem by remember { mutableStateOf<Item<Animals>?>(Item(options.random())) }
// var selectedItem by remember { mutableStateOf<Item<Animals>?>(null) }
// Selector(
// category = Animals,
// options = Animals.items.map { Item(it) },
// selectedItem = selectedItem,
// onSelectItem = { selectedItem = it }
// )
DrawItem(selectedItem!!)
}