Render puzzle (WIP)
This commit is contained in:
5
composeApp/src/androidMain/kotlin/ui/theme/emoji.kt
Normal file
5
composeApp/src/androidMain/kotlin/ui/theme/emoji.kt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
|
||||||
|
actual val emojiFontFamily: FontFamily? = null
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastAny
|
||||||
|
|
||||||
class Game(
|
class Game(
|
||||||
val grid: Grid,
|
val grid: Grid,
|
||||||
val clues: List<Clue>
|
val clues: List<Clue>
|
||||||
@@ -18,7 +20,6 @@ class Game(
|
|||||||
gameCell.options.remove(position.item)
|
gameCell.options.remove(position.item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,5 @@ class Game(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun areRulesViolated(): Boolean = clues
|
fun areRulesViolated(): Boolean = clues
|
||||||
.map { it.isRuleViolated(grid) }
|
.any { it.isRuleViolated(grid) }
|
||||||
.reduce { a, b -> a || b }
|
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ fun generateGame(size: Int = 6): Game {
|
|||||||
// Generate a random puzzle instance.
|
// Generate a random puzzle instance.
|
||||||
val classes = ItemClass.randomClasses(size)
|
val classes = ItemClass.randomClasses(size)
|
||||||
|
|
||||||
val grid: List<List<Item<ItemClass<*>>>> = classes.map { it ->
|
val grid: List<List<Item<ItemClass<*>>>> = classes.map {
|
||||||
it.randomItems(size).map { item -> Item(item) }
|
it.randomItems(size).map { item -> Item(item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,10 @@ fun generateGame(size: Int = 6): Game {
|
|||||||
// if there are 5 houses, there are 5 possible clues of the form "Person A lives in
|
// 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.)
|
// house B", 8 possible clues of the form "Person A lives next to house B", and so on.)
|
||||||
var clues = getAllClues(grid).shuffled()
|
var clues = getAllClues(grid).shuffled()
|
||||||
|
// var positionClues: MutableSet<PositionClue<out ItemClass<*>>> = grid.flatMap { row ->
|
||||||
|
// row.mapIndexed { i, item -> PositionClue(item, i) }
|
||||||
|
// }.toMutableSet()
|
||||||
|
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
@@ -37,12 +41,14 @@ fun generateGame(size: Int = 6): Game {
|
|||||||
// (You can speed this up by removing clues in batches rather than one at a time, but it makes the algorithm more complicated to describe.)
|
// (You can speed this up by removing clues in batches rather than one at a time, but it makes the algorithm more complicated to describe.)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun solve(grid: Grid, clues: List<Clue>): PuzzleSolution {
|
private fun solve(
|
||||||
|
grid: Grid,
|
||||||
|
clues: Collection<Clue>
|
||||||
|
): PuzzleSolution {
|
||||||
// Start with a grid where each cell is a list of all possible items.
|
// Start with a grid where each cell is a list of all possible items.
|
||||||
|
|
||||||
// First, set the positions of the items that are already known.
|
// First, set the positions of the items that are already known.
|
||||||
val positionClues = clues.filterIsInstance<PositionClue<ItemClass<*>>>().toSet()
|
clues.filterIsInstance<PositionClue<ItemClass<*>>>().forEach { position ->
|
||||||
positionClues.forEach { position ->
|
|
||||||
val row = grid[position.item.itemType.companion]
|
val row = grid[position.item.itemType.companion]
|
||||||
row.forEachIndexed { index, gameCell ->
|
row.forEachIndexed { index, gameCell ->
|
||||||
if (index == position.index) {
|
if (index == position.index) {
|
||||||
@@ -56,9 +62,10 @@ private fun solve(grid: Grid, clues: List<Clue>): PuzzleSolution {
|
|||||||
// For each clue, remove any items that violate the clue.
|
// For each clue, remove any items that violate the clue.
|
||||||
// If any cell has only one item left, remove that item from all other cells.
|
// If any cell has only one item left, remove that item from all other cells.
|
||||||
// Repeat until no more items can be removed.
|
// Repeat until no more items can be removed.
|
||||||
val otherClues = clues - positionClues
|
val otherClues = clues.filter { it !is PositionClue<*> }
|
||||||
var removedOptions = false
|
var removedOptions: Boolean
|
||||||
do {
|
do {
|
||||||
|
removedOptions = false
|
||||||
grid.forEach { row ->
|
grid.forEach { row ->
|
||||||
removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
|
||||||
}
|
}
|
||||||
@@ -112,15 +119,17 @@ fun <C : ItemClass<C>> GameRow<C>.cleanupOptions(cell: GameCell<C>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAllClues(rows: List<List<Item<ItemClass<*>>>>): MutableList<Clue> {
|
private fun getAllClues(rows: List<List<Item<ItemClass<*>>>>): MutableSet<Clue> {
|
||||||
val clues = mutableListOf<Clue>()
|
val clues = mutableSetOf<Clue>()
|
||||||
// For optimization reasons we want the positional clues first
|
// rows.forEach { row ->
|
||||||
rows.forEach { row ->
|
// row.forEachIndexed { i, item ->
|
||||||
row.forEachIndexed { i, item ->
|
// clues.add(PositionClue(item, i))
|
||||||
clues.add(PositionClue(item, i))
|
// }
|
||||||
}
|
// }
|
||||||
}
|
clues.add(PositionClue(rows.random().first(), 0))
|
||||||
rows.forEachIndexed { i, columns ->
|
clues.add(PositionClue(rows.random()[3], 3))
|
||||||
|
|
||||||
|
rows.forEach { columns ->
|
||||||
columns.forEachIndexed { j, item ->
|
columns.forEachIndexed { j, item ->
|
||||||
// Clue: Neighbours
|
// Clue: Neighbours
|
||||||
if (j > 0) {
|
if (j > 0) {
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ class Grid(
|
|||||||
fun List<List<Item<ItemClass<*>>>>.toGrid() = Grid(
|
fun List<List<Item<ItemClass<*>>>>.toGrid() = Grid(
|
||||||
map { row ->
|
map { row ->
|
||||||
GameRow(
|
GameRow(
|
||||||
row.first().itemType.companion,
|
category = row.first().itemType.companion,
|
||||||
row,
|
options = row,
|
||||||
row.map { GameCell(selection = null, solution = it, options = row.toMutableList()) }
|
cells = row.map { GameCell(selection = null, solution = it, options = row.toMutableList()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import domain.Item
|
|||||||
import domain.ItemClass
|
import domain.ItemClass
|
||||||
import domain.ItemClassCompanion
|
import domain.ItemClassCompanion
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
import ui.theme.emojiFontFamily
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -86,7 +87,7 @@ fun <C : ItemClass<C>> DrawItem(
|
|||||||
drawText(
|
drawText(
|
||||||
textMeasurer = textMeasurer,
|
textMeasurer = textMeasurer,
|
||||||
text = emoji,
|
text = emoji,
|
||||||
style = TextStyle(fontSize = fontSize),
|
style = TextStyle(fontSize = fontSize, fontFamily = emojiFontFamily ?: TextStyle.Default.fontFamily),
|
||||||
topLeft = offset
|
topLeft = offset
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
5
composeApp/src/commonMain/kotlin/ui/theme/emoji.kt
Normal file
5
composeApp/src/commonMain/kotlin/ui/theme/emoji.kt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
|
||||||
|
expect val emojiFontFamily: FontFamily?
|
||||||
14
composeApp/src/desktopMain/kotlin/ui/theme/emoji.kt
Normal file
14
composeApp/src/desktopMain/kotlin/ui/theme/emoji.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.platform.Font
|
||||||
|
|
||||||
|
actual val emojiFontFamily: FontFamily? = FontFamily(
|
||||||
|
Font(
|
||||||
|
resource = "NotoColorEmoji-Regular.ttf",
|
||||||
|
weight = FontWeight.W400,
|
||||||
|
style = FontStyle.Normal
|
||||||
|
)
|
||||||
|
)
|
||||||
BIN
composeApp/src/desktopMain/resources/NotoColorEmoji-Regular.ttf
Normal file
BIN
composeApp/src/desktopMain/resources/NotoColorEmoji-Regular.ttf
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.3.2"
|
agp = "8.5.0"
|
||||||
android-compileSdk = "34"
|
android-compileSdk = "34"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "34"
|
android-targetSdk = "34"
|
||||||
|
|||||||
13
proguard-rules.pro
vendored
Normal file
13
proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-libraryjars <java.home>/jmods/java.base.jmod(!**.jar;!module-info.class)
|
||||||
|
|
||||||
|
-keepclasseswithmembers public class MainKt {
|
||||||
|
public static void main(java.lang.String[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn kotlinx.coroutines.debug.*
|
||||||
|
|
||||||
|
-keep class kotlin.** { *; }
|
||||||
|
-keep class kotlinx.coroutines.** { *; }
|
||||||
|
-keep class org.jetbrains.skia.** { *; }
|
||||||
|
-keep class org.jetbrains.skiko.** { *; }
|
||||||
|
-dontwarn kotlinx.datetime.**
|
||||||
Reference in New Issue
Block a user