Sonar fixes
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 3m58s
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 3m58s
This commit is contained in:
@@ -39,4 +39,4 @@ jobs:
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST }}
|
||||
run: ./gradlew build sonar --info --build-cache
|
||||
run: ./gradlew sonar
|
||||
|
||||
90
android/ic_launcher_monochrome.svg
Normal file
90
android/ic_launcher_monochrome.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.4 KiB |
@@ -6,6 +6,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
<activity
|
||||
android:exported="true"
|
||||
|
||||
16
android/src/main/res/drawable/ic_launcher_monochrome.xml
Normal file
16
android/src/main/res/drawable/ic_launcher_monochrome.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<group
|
||||
android:scaleX="2.8"
|
||||
android:scaleY="2.8"
|
||||
android:translateX="18"
|
||||
android:translateY="18"
|
||||
>
|
||||
<path
|
||||
android:fillColor="#464c4f"
|
||||
android:pathData="m15.848,20.078c-0.34,2.82 -0.74,5.473 -1.078,8.299 -0.082,0.685 12.73,0.217 13.129,-0.182 0.22,-0.22 0.288,-1.278 0.143,-2.785C30.72,18.261 26.815,5.37 17.301,3.861 14.319,3.388 10.718,4.395 9.119,5.395c-0.432,0.27 2.131,2.26 3.264,3.248 -1.393,0.789 -2.973,2.554 -5.869,6.248 -0.435,0.555 -0.754,1.179 -1.18,1.586 -1.503,1.437 -2.891,3.476 -1.77,5.533 0.602,1.105 1.972,1.791 3.6,1.221 1.235,-0.432 1.905,-1.018 2.204,-1.54 3.218,0.401 5.569,-0.974 6.48,-1.613zM14.951,3.875 L15.354,5.457c-0.339,0.555 -0.651,1.288 -0.883,2.252L12.234,4.313c0.909,-0.242 1.814,-0.387 2.717,-0.438zM18.176,4.141c0.839,0.198 1.62,0.476 2.342,0.836L19.123,7.887C18.933,6.332 18.452,5.269 18.014,4.617ZM17.191,4.691c0.227,-0.012 1.371,1.696 1.283,3.701 -0.061,1.383 -0.51,1.544 -0.885,2.045 -0.702,0.936 -0.712,2.299 -0.305,2.594 0.972,0.194 1.433,-1.881 1.758,-2.504 0.679,0.254 0.832,0.325 1.209,0.662 -0.972,2.388 -2.752,7.249 -2.604,7.424 0.1,0.117 1.808,-0.833 5.93,-3.836 0.2,0.224 0.427,0.598 0.539,0.873C21.871,17.673 19.505,19.528 16.797,20.9 17.605,16.232 16.188,16.421 15.975,14.545 15.839,13.35 16.679,11.953 15.818,11.922c-0.999,0.288 -1.077,1.635 -1.291,2.48 -0.341,1.732 1.656,1.887 0.977,5.033l-0.744,0.346c0,0 1.139,-2.838 -1.576,-3.803 -2.015,-0.716 -3.539,0.345 -4.195,0.941 0.132,-0.444 0.169,-0.925 -0.062,-1.357 0.483,-2.284 2.414,-3.303 2.543,-4.719 0.153,-0.7 0.422,-1.248 1.127,-1.504 0,0 -0.042,0.871 0.07,1.719 0.087,0.646 0.9,0.764 1.398,0.387 0.498,-0.377 0.707,-2.645 0.707,-2.645l0.438,-0.162c0.207,-1.525 0.587,-3.062 1.982,-3.947zM22.289,6.064c0.432,0.325 0.825,0.657 1.178,0.996L20.771,10.627c-0.404,-0.283 -0.835,-0.531 -1.279,-0.758zM17.01,6.15c-0.528,-0.057 -1.844,3.645 -0.291,3.645 1.319,0 0.734,-3.596 0.291,-3.645zM24.773,8.498c0.466,0.59 0.887,1.208 1.266,1.854L23.252,12.949C22.867,12.483 22.46,12.039 22.02,11.631ZM9.289,12.07c0.365,0.524 -0.197,1.322 -1.295,2.693 -0.243,-0.105 -0.495,-0.164 -0.732,-0.162zM27.061,12.361c0.182,0.421 0.344,0.836 0.488,1.244l-2.729,1.611c-0.197,-0.338 -0.399,-0.672 -0.617,-0.996zM21.586,12.43c0.165,0.089 0.447,0.271 1.1,1.08 0,0 -2.18,1.749 -2.217,1.617 -0.037,-0.132 1.117,-2.697 1.117,-2.697zM12.967,12.664c-0.509,0 -0.92,0.493 -0.92,1.1 0,0.607 0.419,1.103 0.926,1.148 0.681,0.061 0.914,-0.542 0.914,-1.148 0,-0.607 -0.411,-1.1 -0.92,-1.1zM28.035,15.168c0.121,0.452 0.226,0.906 0.316,1.363l-2.301,1.162c-0.165,-0.389 -0.34,-0.773 -0.527,-1.156zM24.896,17.115c0.284,0.448 0.487,0.87 0.611,1.33 -0.278,0.41 -3.469,4.963 -9.748,7.367l0.182,-1.662C19.922,22.311 20.378,21.914 24.896,17.115ZM13.398,20.525c-0.448,0.227 -0.729,0.283 -1.18,0.361C12.542,18.162 11.139,18.184 9.018,19.371 8.838,19.169 8.657,19.017 8.514,18.885 11.008,16.534 13.703,16.865 13.398,20.525ZM28.65,18.596c0.041,0.457 0.065,0.882 0.074,1.277l-1.672,0.613c-0.114,-0.387 -0.243,-0.775 -0.379,-1.168zM5.965,19.559c0.242,0.744 -0.264,1.527 -0.66,1.455 -0.397,-0.072 -0.041,-0.684 -0.193,-0.998 -0.153,-0.315 -0.609,-0.253 -0.57,-0.59 0.066,-0.57 1.248,-0.409 1.424,0.133zM26.191,20.109c0.103,0.508 0.238,0.831 0.318,1.252 -1.821,2.749 -3.408,4.952 -6.684,6.637 -1.046,0.056 -2.087,0.012 -3.197,-0.078 1.058,-1.058 2.314,0.688 9.562,-7.811zM10.959,21.084c-0.512,0.033 -0.922,0.016 -1.449,0 0.017,-0.251 0.01,-0.473 -0.018,-0.666 0.872,-0.527 1.112,-0.04 1.467,0.666zM28.598,22.441c-0.101,0.856 -0.321,1.712 -0.617,2.568 -0.086,-0.749 -0.206,-1.559 -0.4,-2.461zM26.939,23.785c0.298,1.288 0.41,2.528 0.336,3.723 -0.776,0.117 -1.691,0.189 -2.746,0.213 1.162,-1.58 1.291,-1.953 2.41,-3.936z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -3,9 +3,13 @@ package ch.dissem.yaep.ui.common
|
||||
import ch.dissem.yaep.domain.Game
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
actual fun CoroutineScope.logGame(game: Game) {
|
||||
log.debug { "Game: $game" }
|
||||
actual fun CoroutineScope.logGame(game: Game, dispatcher: CoroutineContext) {
|
||||
launch(dispatcher) {
|
||||
log.debug { "Game: $game" }
|
||||
}
|
||||
}
|
||||
@@ -31,17 +31,23 @@ import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.dissem.yaep.domain.Clue
|
||||
import ch.dissem.yaep.domain.Game
|
||||
import ch.dissem.yaep.domain.GameCell
|
||||
import ch.dissem.yaep.domain.GameRow
|
||||
import ch.dissem.yaep.domain.Grid
|
||||
import ch.dissem.yaep.domain.HorizontalClue
|
||||
import ch.dissem.yaep.domain.Item
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import yaep.commonui.generated.resources.Res
|
||||
import yaep.commonui.generated.resources.neighbour
|
||||
import yaep.commonui.generated.resources.order
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
class DisplayClue<C : Clue>(val clue: C) {
|
||||
@@ -144,59 +150,89 @@ fun PuzzleGrid(
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
for (row in grid) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
val allOptions = row.options
|
||||
for (item in row) {
|
||||
var selection by remember(item) { mutableStateOf(item.selection) }
|
||||
val options = remember(item) {
|
||||
allOptions.map { Toggleable(it, item.options.contains(it)) }
|
||||
}
|
||||
LaunchedEffect(item) {
|
||||
item.optionsChangedListeners.add { enabled ->
|
||||
options.forEach { it.enabled = enabled.contains(it.item) }
|
||||
}
|
||||
item.selectionChangedListeners.add {
|
||||
selection = it
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
Selector(
|
||||
modifier = Modifier
|
||||
.padding(spacing)
|
||||
.weight(1f),
|
||||
spacing,
|
||||
selectDirectly = selectDirectly,
|
||||
options = options,
|
||||
onOptionRemoved = {
|
||||
grid.snapshot()
|
||||
item.options.remove(it)
|
||||
row.cleanupOptions()
|
||||
},
|
||||
onOptionAdded = {
|
||||
item.options.add(it)
|
||||
},
|
||||
selectedItem = selection,
|
||||
onSelectItem = { selectedItem ->
|
||||
if (selectedItem != null) {
|
||||
grid.snapshot()
|
||||
item.selection = selectedItem
|
||||
row.cleanupOptions()
|
||||
} else {
|
||||
while (item.selection != null) {
|
||||
if (!grid.undo()) break
|
||||
}
|
||||
options.forEach { option ->
|
||||
option.enabled = item.options.contains(option.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
PuzzleRow(
|
||||
row = row,
|
||||
onUpdate = onUpdate,
|
||||
onSnapshot = { grid.snapshot() },
|
||||
onUndo = { grid.undo() },
|
||||
spacing = spacing,
|
||||
selectDirectly = selectDirectly
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PuzzleRow(
|
||||
row: GameRow<ItemClass<*>>,
|
||||
onUpdate: () -> Unit,
|
||||
onSnapshot: () -> Unit,
|
||||
onUndo: () -> Boolean,
|
||||
spacing: Dp,
|
||||
selectDirectly: Boolean
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
val allOptions = row.options
|
||||
for (cell in row) {
|
||||
var selection by remember(cell) { mutableStateOf(cell.selection) }
|
||||
val options = remember(cell) {
|
||||
allOptions.map { Toggleable(it, cell.options.contains(it)) }
|
||||
}
|
||||
LaunchedEffect(cell) {
|
||||
cell.optionsChangedListeners.add { enabled ->
|
||||
options.forEach { it.enabled = enabled.contains(it.item) }
|
||||
}
|
||||
cell.selectionChangedListeners.add {
|
||||
selection = it
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
Selector(
|
||||
modifier = Modifier
|
||||
.padding(spacing)
|
||||
.weight(1f),
|
||||
spacing,
|
||||
selectDirectly = selectDirectly,
|
||||
options = options,
|
||||
onOptionRemoved = {
|
||||
onSnapshot()
|
||||
cell.options.remove(it)
|
||||
row.cleanupOptions()
|
||||
},
|
||||
onOptionAdded = {
|
||||
cell.options.add(it)
|
||||
},
|
||||
selectedItem = selection,
|
||||
onSelectItem = { selectedItem ->
|
||||
onSelectItem(row, cell, options, selectedItem, onSnapshot, onUndo)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSelectItem(
|
||||
row: GameRow<ItemClass<*>>,
|
||||
cell: GameCell<ItemClass<*>>,
|
||||
options: List<Toggleable<Item<ItemClass<*>>>>,
|
||||
selectedItem: Item<ItemClass<*>>?,
|
||||
onSnapshot: () -> Unit,
|
||||
onUndo: () -> Boolean
|
||||
) {
|
||||
if (selectedItem != null) {
|
||||
onSnapshot()
|
||||
cell.selection = selectedItem
|
||||
row.cleanupOptions()
|
||||
} else {
|
||||
while (cell.selection != null) {
|
||||
if (!onUndo()) break
|
||||
}
|
||||
options.forEach { option ->
|
||||
option.enabled = cell.options.contains(option.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +305,7 @@ fun VerticalClue(
|
||||
}
|
||||
}
|
||||
|
||||
expect fun CoroutineScope.logGame(game: Game)
|
||||
expect fun CoroutineScope.logGame(game: Game, dispatcher: CoroutineContext = Dispatchers.IO)
|
||||
|
||||
@Composable
|
||||
fun ClueCard(
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package ch.dissem.yaep.ui.common.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
private val lightScheme = lightColorScheme(
|
||||
primary = primaryLight,
|
||||
@@ -83,192 +82,19 @@ private val darkScheme = darkColorScheme(
|
||||
surfaceContainerHighest = surfaceContainerHighestDark,
|
||||
)
|
||||
|
||||
private val mediumContrastLightColorScheme = lightColorScheme(
|
||||
primary = primaryLightMediumContrast,
|
||||
onPrimary = onPrimaryLightMediumContrast,
|
||||
primaryContainer = primaryContainerLightMediumContrast,
|
||||
onPrimaryContainer = onPrimaryContainerLightMediumContrast,
|
||||
secondary = secondaryLightMediumContrast,
|
||||
onSecondary = onSecondaryLightMediumContrast,
|
||||
secondaryContainer = secondaryContainerLightMediumContrast,
|
||||
onSecondaryContainer = onSecondaryContainerLightMediumContrast,
|
||||
tertiary = tertiaryLightMediumContrast,
|
||||
onTertiary = onTertiaryLightMediumContrast,
|
||||
tertiaryContainer = tertiaryContainerLightMediumContrast,
|
||||
onTertiaryContainer = onTertiaryContainerLightMediumContrast,
|
||||
error = errorLightMediumContrast,
|
||||
onError = onErrorLightMediumContrast,
|
||||
errorContainer = errorContainerLightMediumContrast,
|
||||
onErrorContainer = onErrorContainerLightMediumContrast,
|
||||
background = backgroundLightMediumContrast,
|
||||
onBackground = onBackgroundLightMediumContrast,
|
||||
surface = surfaceLightMediumContrast,
|
||||
onSurface = onSurfaceLightMediumContrast,
|
||||
surfaceVariant = surfaceVariantLightMediumContrast,
|
||||
onSurfaceVariant = onSurfaceVariantLightMediumContrast,
|
||||
outline = outlineLightMediumContrast,
|
||||
outlineVariant = outlineVariantLightMediumContrast,
|
||||
scrim = scrimLightMediumContrast,
|
||||
inverseSurface = inverseSurfaceLightMediumContrast,
|
||||
inverseOnSurface = inverseOnSurfaceLightMediumContrast,
|
||||
inversePrimary = inversePrimaryLightMediumContrast,
|
||||
surfaceDim = surfaceDimLightMediumContrast,
|
||||
surfaceBright = surfaceBrightLightMediumContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
|
||||
surfaceContainerLow = surfaceContainerLowLightMediumContrast,
|
||||
surfaceContainer = surfaceContainerLightMediumContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
|
||||
)
|
||||
|
||||
private val highContrastLightColorScheme = lightColorScheme(
|
||||
primary = primaryLightHighContrast,
|
||||
onPrimary = onPrimaryLightHighContrast,
|
||||
primaryContainer = primaryContainerLightHighContrast,
|
||||
onPrimaryContainer = onPrimaryContainerLightHighContrast,
|
||||
secondary = secondaryLightHighContrast,
|
||||
onSecondary = onSecondaryLightHighContrast,
|
||||
secondaryContainer = secondaryContainerLightHighContrast,
|
||||
onSecondaryContainer = onSecondaryContainerLightHighContrast,
|
||||
tertiary = tertiaryLightHighContrast,
|
||||
onTertiary = onTertiaryLightHighContrast,
|
||||
tertiaryContainer = tertiaryContainerLightHighContrast,
|
||||
onTertiaryContainer = onTertiaryContainerLightHighContrast,
|
||||
error = errorLightHighContrast,
|
||||
onError = onErrorLightHighContrast,
|
||||
errorContainer = errorContainerLightHighContrast,
|
||||
onErrorContainer = onErrorContainerLightHighContrast,
|
||||
background = backgroundLightHighContrast,
|
||||
onBackground = onBackgroundLightHighContrast,
|
||||
surface = surfaceLightHighContrast,
|
||||
onSurface = onSurfaceLightHighContrast,
|
||||
surfaceVariant = surfaceVariantLightHighContrast,
|
||||
onSurfaceVariant = onSurfaceVariantLightHighContrast,
|
||||
outline = outlineLightHighContrast,
|
||||
outlineVariant = outlineVariantLightHighContrast,
|
||||
scrim = scrimLightHighContrast,
|
||||
inverseSurface = inverseSurfaceLightHighContrast,
|
||||
inverseOnSurface = inverseOnSurfaceLightHighContrast,
|
||||
inversePrimary = inversePrimaryLightHighContrast,
|
||||
surfaceDim = surfaceDimLightHighContrast,
|
||||
surfaceBright = surfaceBrightLightHighContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
|
||||
surfaceContainerLow = surfaceContainerLowLightHighContrast,
|
||||
surfaceContainer = surfaceContainerLightHighContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighLightHighContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
|
||||
)
|
||||
|
||||
private val mediumContrastDarkColorScheme = darkColorScheme(
|
||||
primary = primaryDarkMediumContrast,
|
||||
onPrimary = onPrimaryDarkMediumContrast,
|
||||
primaryContainer = primaryContainerDarkMediumContrast,
|
||||
onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
|
||||
secondary = secondaryDarkMediumContrast,
|
||||
onSecondary = onSecondaryDarkMediumContrast,
|
||||
secondaryContainer = secondaryContainerDarkMediumContrast,
|
||||
onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
|
||||
tertiary = tertiaryDarkMediumContrast,
|
||||
onTertiary = onTertiaryDarkMediumContrast,
|
||||
tertiaryContainer = tertiaryContainerDarkMediumContrast,
|
||||
onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
|
||||
error = errorDarkMediumContrast,
|
||||
onError = onErrorDarkMediumContrast,
|
||||
errorContainer = errorContainerDarkMediumContrast,
|
||||
onErrorContainer = onErrorContainerDarkMediumContrast,
|
||||
background = backgroundDarkMediumContrast,
|
||||
onBackground = onBackgroundDarkMediumContrast,
|
||||
surface = surfaceDarkMediumContrast,
|
||||
onSurface = onSurfaceDarkMediumContrast,
|
||||
surfaceVariant = surfaceVariantDarkMediumContrast,
|
||||
onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
|
||||
outline = outlineDarkMediumContrast,
|
||||
outlineVariant = outlineVariantDarkMediumContrast,
|
||||
scrim = scrimDarkMediumContrast,
|
||||
inverseSurface = inverseSurfaceDarkMediumContrast,
|
||||
inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
|
||||
inversePrimary = inversePrimaryDarkMediumContrast,
|
||||
surfaceDim = surfaceDimDarkMediumContrast,
|
||||
surfaceBright = surfaceBrightDarkMediumContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
|
||||
surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
|
||||
surfaceContainer = surfaceContainerDarkMediumContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
|
||||
)
|
||||
|
||||
private val highContrastDarkColorScheme = darkColorScheme(
|
||||
primary = primaryDarkHighContrast,
|
||||
onPrimary = onPrimaryDarkHighContrast,
|
||||
primaryContainer = primaryContainerDarkHighContrast,
|
||||
onPrimaryContainer = onPrimaryContainerDarkHighContrast,
|
||||
secondary = secondaryDarkHighContrast,
|
||||
onSecondary = onSecondaryDarkHighContrast,
|
||||
secondaryContainer = secondaryContainerDarkHighContrast,
|
||||
onSecondaryContainer = onSecondaryContainerDarkHighContrast,
|
||||
tertiary = tertiaryDarkHighContrast,
|
||||
onTertiary = onTertiaryDarkHighContrast,
|
||||
tertiaryContainer = tertiaryContainerDarkHighContrast,
|
||||
onTertiaryContainer = onTertiaryContainerDarkHighContrast,
|
||||
error = errorDarkHighContrast,
|
||||
onError = onErrorDarkHighContrast,
|
||||
errorContainer = errorContainerDarkHighContrast,
|
||||
onErrorContainer = onErrorContainerDarkHighContrast,
|
||||
background = backgroundDarkHighContrast,
|
||||
onBackground = onBackgroundDarkHighContrast,
|
||||
surface = surfaceDarkHighContrast,
|
||||
onSurface = onSurfaceDarkHighContrast,
|
||||
surfaceVariant = surfaceVariantDarkHighContrast,
|
||||
onSurfaceVariant = onSurfaceVariantDarkHighContrast,
|
||||
outline = outlineDarkHighContrast,
|
||||
outlineVariant = outlineVariantDarkHighContrast,
|
||||
scrim = scrimDarkHighContrast,
|
||||
inverseSurface = inverseSurfaceDarkHighContrast,
|
||||
inverseOnSurface = inverseOnSurfaceDarkHighContrast,
|
||||
inversePrimary = inversePrimaryDarkHighContrast,
|
||||
surfaceDim = surfaceDimDarkHighContrast,
|
||||
surfaceBright = surfaceBrightDarkHighContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
|
||||
surfaceContainerLow = surfaceContainerLowDarkHighContrast,
|
||||
surfaceContainer = surfaceContainerDarkHighContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class ColorFamily(
|
||||
val color: Color,
|
||||
val onColor: Color,
|
||||
val colorContainer: Color,
|
||||
val onColorContainer: Color
|
||||
)
|
||||
|
||||
val unspecified_scheme = ColorFamily(
|
||||
Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AppTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable() () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
darkTheme -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
// val view = LocalView.current
|
||||
// if (!view.isInEditMode) {
|
||||
// SideEffect {
|
||||
// val window = (view.context as Activity).window
|
||||
// window.statusBarColor = colorScheme.primary.toArgb()
|
||||
// WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
// }
|
||||
// }
|
||||
val colorScheme = when {
|
||||
darkTheme -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
// typography = AppTypography,
|
||||
content = content
|
||||
)
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package ch.dissem.yaep.ui.common
|
||||
import ch.dissem.yaep.domain.Game
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createFile
|
||||
import kotlin.io.path.isDirectory
|
||||
@@ -15,8 +15,8 @@ import kotlin.time.ExperimentalTime
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
actual fun CoroutineScope.logGame(game: Game) {
|
||||
launch(Dispatchers.IO) {
|
||||
actual fun CoroutineScope.logGame(game: Game, dispatcher: CoroutineContext) {
|
||||
launch(dispatcher) {
|
||||
val dirName = """${System.getProperty("user.home")}/.yaep"""
|
||||
val dir = Path(dirName)
|
||||
if (dir.isDirectory()) {
|
||||
|
||||
@@ -19,7 +19,7 @@ class GameCell<C : ItemClass<C>>(
|
||||
selectionChangedListeners.forEach { listener -> listener(value) }
|
||||
}
|
||||
}
|
||||
val options: ObservableSet<Item<C>> = ObservableSet(options) { before, after ->
|
||||
val options: ObservableSet<Item<C>> = ObservableSet(options) { _, after ->
|
||||
optionsChangedListeners.forEach { listener ->
|
||||
listener(after)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class GameRow<C : ItemClass<C>>(
|
||||
it.options.clear()
|
||||
it.options.add(it.selection!!)
|
||||
}
|
||||
cellsWithSelection.mapNotNull { it.selection }.toMutableSet()
|
||||
cellsWithSelection.mapNotNull { it.selection }.toSet()
|
||||
}
|
||||
filter { it.selection == null }
|
||||
.forEach { it.options.removeAll(selections) }
|
||||
|
||||
@@ -75,10 +75,12 @@ class NeighbourClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b: I
|
||||
|
||||
for (iX in rowX.indices) {
|
||||
val cellX = rowX[iX]
|
||||
if (cellX.mayBe(x, mayHaveSelection = false)) {
|
||||
if (!rowY.getOrNull(iX - 1).mayBe(y) && !rowY.getOrNull(iX + 1).mayBe(y)) {
|
||||
removed = cellX.options.remove(x) || removed
|
||||
}
|
||||
if (
|
||||
cellX.mayBe(x, mayHaveSelection = false)
|
||||
&& !rowY.getOrNull(iX - 1).mayBe(y)
|
||||
&& !rowY.getOrNull(iX + 1).mayBe(y)
|
||||
) {
|
||||
removed = cellX.options.remove(x) || removed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,51 +187,71 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
val ic by lazy { rowC.indexOf(cType) }
|
||||
|
||||
if (ia != -1) {
|
||||
return when (ib) {
|
||||
-1 -> when (ic) {
|
||||
-1 -> (rowB.getOrNull(ia - 1).hasNoSelection() && rowC.getOrNull(ia - 2)
|
||||
.hasNoSelection()) ||
|
||||
(rowB.getOrNull(ia + 1).hasNoSelection() && rowC.getOrNull(ia + 2)
|
||||
.hasNoSelection())
|
||||
|
||||
ia - 2 -> rowB.getOrNull(ia - 1).hasNoSelection()
|
||||
ia + 2 -> rowB.getOrNull(ia + 1).hasNoSelection()
|
||||
else -> false
|
||||
}
|
||||
|
||||
ia - 1 -> when (ic) {
|
||||
-1 -> rowC.getOrNull(ia - 2).hasNoSelection()
|
||||
ia - 2 -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
ia + 1 -> when (ic) {
|
||||
-1 -> rowC.getOrNull(ia + 2).hasNoSelection()
|
||||
ia + 2 -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
return isValidWithASet(ia, ib, ic, rowB, rowC)
|
||||
}
|
||||
if (ib != -1) {
|
||||
when (ic) {
|
||||
-1 -> return (rowA.getOrNull(ib - 1).hasNoSelection() && rowC.getOrNull(ib + 1)
|
||||
.hasNoSelection()) ||
|
||||
(rowA.getOrNull(ib + 1).hasNoSelection() && rowC.getOrNull(ib - 1)
|
||||
.hasNoSelection())
|
||||
|
||||
ib - 1 -> return rowA.getOrNull(ib + 1).hasNoSelection()
|
||||
ib + 1 -> return rowA.getOrNull(ib - 1).hasNoSelection()
|
||||
}
|
||||
return isValidWithBSet(ib, ic, rowA, rowC)
|
||||
}
|
||||
if (ic != -1) {
|
||||
return (rowB.getOrNull(ic - 1).hasNoSelection() && rowA.getOrNull(ic - 2)
|
||||
.hasNoSelection()) ||
|
||||
(rowB.getOrNull(ic + 1).hasNoSelection() && rowA.getOrNull(ic + 2)
|
||||
.hasNoSelection())
|
||||
return isValidWithCSet(ic, rowA, rowB)
|
||||
}
|
||||
return rowA.mapIndexed { index, gameCell -> if (gameCell.hasNoSelection()) index else null }
|
||||
return isValidWithNoneSet(rowA, rowB, rowC)
|
||||
}
|
||||
|
||||
private fun isValidWithASet(
|
||||
ia: Int,
|
||||
ib: Int,
|
||||
ic: Int,
|
||||
rowB: GameRow<B>,
|
||||
rowC: GameRow<C>
|
||||
): Boolean =
|
||||
when (ib) {
|
||||
-1 -> when (ic) {
|
||||
-1 -> (rowB.getOrNull(ia - 1).hasNoSelection() && rowC.getOrNull(ia - 2)
|
||||
.hasNoSelection()) ||
|
||||
(rowB.getOrNull(ia + 1).hasNoSelection() && rowC.getOrNull(ia + 2)
|
||||
.hasNoSelection())
|
||||
|
||||
ia - 2 -> rowB.getOrNull(ia - 1).hasNoSelection()
|
||||
ia + 2 -> rowB.getOrNull(ia + 1).hasNoSelection()
|
||||
else -> false
|
||||
}
|
||||
|
||||
ia - 1 -> when (ic) {
|
||||
-1 -> rowC.getOrNull(ia - 2).hasNoSelection()
|
||||
ia - 2 -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
ia + 1 -> when (ic) {
|
||||
-1 -> rowC.getOrNull(ia + 2).hasNoSelection()
|
||||
ia + 2 -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun isValidWithBSet(ib: Int, ic: Int, rowA: GameRow<A>, rowC: GameRow<C>): Boolean =
|
||||
when (ic) {
|
||||
-1 -> (rowA.getOrNull(ib - 1).hasNoSelection() && rowC.getOrNull(ib + 1)
|
||||
.hasNoSelection()) ||
|
||||
(rowA.getOrNull(ib + 1).hasNoSelection() && rowC.getOrNull(ib - 1)
|
||||
.hasNoSelection())
|
||||
|
||||
ib - 1 -> rowA.getOrNull(ib + 1).hasNoSelection()
|
||||
ib + 1 -> rowA.getOrNull(ib - 1).hasNoSelection()
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun isValidWithCSet(ic: Int, rowA: GameRow<A>, rowB: GameRow<B>): Boolean =
|
||||
(rowB.getOrNull(ic - 1).hasNoSelection() && rowA.getOrNull(ic - 2)
|
||||
.hasNoSelection()) ||
|
||||
(rowB.getOrNull(ic + 1).hasNoSelection() && rowA.getOrNull(ic + 2)
|
||||
.hasNoSelection())
|
||||
|
||||
private fun isValidWithNoneSet(rowA: GameRow<A>, rowB: GameRow<B>, rowC: GameRow<C>): Boolean =
|
||||
rowA.mapIndexed { index, gameCell -> if (gameCell.hasNoSelection()) index else null }
|
||||
.filterNotNull()
|
||||
.any { index ->
|
||||
(rowB.getOrNull(index - 1).hasNoSelection() && rowC.getOrNull(index - 2)
|
||||
@@ -237,7 +259,6 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
(rowB.getOrNull(index + 1).hasNoSelection() && rowC.getOrNull(index + 2)
|
||||
.hasNoSelection())
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeForbiddenOptions(grid: Grid): Boolean {
|
||||
val rowA = grid[aType.companion]
|
||||
@@ -247,49 +268,62 @@ class TripletClue<A : ItemClass<A>, B : ItemClass<B>, C : ItemClass<C>>(
|
||||
var removed = false
|
||||
|
||||
for (i in rowA.indices) {
|
||||
val cellA = rowA[i]
|
||||
if (cellA.mayBe(a, mayHaveSelection = false)) {
|
||||
val cellBR = rowB.getOrNull(i + 1)
|
||||
val cellCR = rowC.getOrNull(i + 2)
|
||||
val cellBL = rowB.getOrNull(i - 1)
|
||||
val cellCL = rowC.getOrNull(i - 2)
|
||||
|
||||
if (!(cellBR.mayBe(b) && cellCR.mayBe(c)) && !(cellBL.mayBe(b) && cellCL.mayBe(c))) {
|
||||
removed = cellA.options.remove(a) || removed
|
||||
}
|
||||
}
|
||||
removed = removeForbiddenOptionsGivenX(
|
||||
i = i,
|
||||
x = Group(a, rowA),
|
||||
y = Group(b, rowB, 1),
|
||||
z = Group(c, rowC, 2)
|
||||
) || removed
|
||||
}
|
||||
for (i in rowB.indices) {
|
||||
val cellB = rowB[i]
|
||||
if (cellB.mayBe(b, mayHaveSelection = false)) {
|
||||
val cellAL = rowA.getOrNull(i - 1)
|
||||
val cellAR = rowA.getOrNull(i + 1)
|
||||
val cellCL = rowC.getOrNull(i - 1)
|
||||
val cellCR = rowC.getOrNull(i + 1)
|
||||
|
||||
if (!(cellAL.mayBe(a) && cellCR.mayBe(c)) && !(cellCL.mayBe(c) && cellAR.mayBe(a))) {
|
||||
removed = cellB.options.remove(b) || removed
|
||||
}
|
||||
}
|
||||
removed = removeForbiddenOptionsGivenX(
|
||||
i = i,
|
||||
x = Group(b, rowB),
|
||||
y = Group(a, rowA, 1),
|
||||
z = Group(c, rowC, -1)
|
||||
) || removed
|
||||
}
|
||||
|
||||
for (i in rowC.indices) {
|
||||
val cellC = rowC[i]
|
||||
if (cellC.mayBe(c, mayHaveSelection = false)) {
|
||||
val cellBR = rowB.getOrNull(i + 1)
|
||||
val cellAR = rowA.getOrNull(i + 2)
|
||||
val cellBL = rowB.getOrNull(i - 1)
|
||||
val cellAL = rowA.getOrNull(i - 2)
|
||||
|
||||
if (!(cellBR.mayBe(b) && cellAR.mayBe(a)) && !(cellBL.mayBe(b) && cellAL.mayBe(a))) {
|
||||
removed = cellC.options.remove(c) || removed
|
||||
}
|
||||
}
|
||||
removed = removeForbiddenOptionsGivenX(
|
||||
i = i,
|
||||
x = Group(c, rowC),
|
||||
y = Group(b, rowB, 1),
|
||||
z = Group(a, rowA, 2)
|
||||
) || removed
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
private fun <X : ItemClass<X>, Y : ItemClass<Y>, Z : ItemClass<Z>> removeForbiddenOptionsGivenX(
|
||||
i: Int,
|
||||
x: Group<X>,
|
||||
y: Group<Y>,
|
||||
z: Group<Z>
|
||||
): Boolean {
|
||||
val cellX = x.row[i]
|
||||
if (cellX.mayBe(x.item, mayHaveSelection = false)) {
|
||||
val cellYR = y.row.getOrNull(i + y.offset)
|
||||
val cellZR = z.row.getOrNull(i + z.offset)
|
||||
val cellYL = y.row.getOrNull(i - y.offset)
|
||||
val cellZL = z.row.getOrNull(i - z.offset)
|
||||
|
||||
if (
|
||||
!(cellYR.mayBe(y.item) && cellZR.mayBe(z.item))
|
||||
&& !(cellYL.mayBe(y.item) && cellZL.mayBe(z.item))
|
||||
) {
|
||||
return cellX.options.remove(x.item)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private class Group<T : ItemClass<T>>(
|
||||
val item: Item<T>,
|
||||
val row: GameRow<T>,
|
||||
val offset: Int = 0
|
||||
)
|
||||
|
||||
override fun toString(): String =
|
||||
"$bType is between the neighbours $aType and $cType to both sides"
|
||||
|
||||
@@ -343,15 +377,11 @@ class SameColumnClue<A : ItemClass<A>, B : ItemClass<B>>(val a: Item<A>, val b:
|
||||
val cellA = rowA[i]
|
||||
val cellB = rowB[i]
|
||||
|
||||
if (cellB.hasNoSelection()) {
|
||||
if (!cellA.mayBe(a)) {
|
||||
removed = cellB.options.remove(b) || removed
|
||||
}
|
||||
if (cellB.hasNoSelection() && !cellA.mayBe(a)) {
|
||||
removed = cellB.options.remove(b) || removed
|
||||
}
|
||||
if (cellA.hasNoSelection()) {
|
||||
if (!cellB.mayBe(b)) {
|
||||
removed = cellA.options.remove(a) || removed
|
||||
}
|
||||
if (cellA.hasNoSelection() && !cellB.mayBe(b)) {
|
||||
removed = cellA.options.remove(a) || removed
|
||||
}
|
||||
}
|
||||
return removed
|
||||
|
||||
@@ -14,7 +14,7 @@ class GameSolverTest {
|
||||
fun `ensure there are no unnecessary clues`() {
|
||||
var game: Game
|
||||
var neighbours: List<NeighbourClue<*, *>>
|
||||
repeat(100) {
|
||||
repeat(10) {
|
||||
game = generateGame()
|
||||
val triplets = game.horizontalClues.filterIsInstance<TripletClue<*, *, *>>()
|
||||
neighbours = game.horizontalClues.filterIsInstance<NeighbourClue<*, *>>()
|
||||
|
||||
@@ -19,7 +19,7 @@ class GameTest {
|
||||
|
||||
@Test
|
||||
fun `ensure generated games are solvable`() {
|
||||
val tries = 1000
|
||||
val tries = 100
|
||||
var fastest = 500.milliseconds
|
||||
var slowest = 0.milliseconds
|
||||
var total = 0.milliseconds
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
app-version-code = "1"
|
||||
app-version-name = "1.0.0"
|
||||
agp = "8.11.1"
|
||||
agp = "8.12.0"
|
||||
jdk = "21"
|
||||
android-compileSdk = "36"
|
||||
android-minSdk = "26"
|
||||
@@ -22,7 +22,7 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl
|
||||
atrium = { module = "ch.tutteli.atrium:atrium-fluent", version.ref = "atrium" }
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" }
|
||||
logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version = "7.0.7" }
|
||||
logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version = "7.0.11" }
|
||||
logging-slf4j = { module = "org.slf4j:slf4j-simple", version = "2.0.17" }
|
||||
|
||||
[bundles]
|
||||
@@ -35,6 +35,6 @@ android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
sonarqube = { id = "org.sonarqube", version = "6.2.0.5505" }
|
||||
compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
sonarqube = { id = "org.sonarqube", version = "6.2.0.5505" }
|
||||
|
||||
Reference in New Issue
Block a user