Skip to content

Commit 6296e4d

Browse files
committed
Baseline reached 5-9. Invalid position implemented for when a user enters an invalid number at an invalid, row, column or subgrid.
1 parent 7497383 commit 6296e4d

File tree

10 files changed

+165
-73
lines changed

10 files changed

+165
-73
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ koin_annotation_version = "2.0.0"
88

99
ui_components_version = "0.92.3"
1010
ui_navigation_version = "0.3.0"
11-
ui_helper_version = "0.5.9"
11+
ui_helper_version = "0.6.0"
1212
ui_canvas_version = "0.1.9"
1313

1414
data_result_version = "0.0.26"

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/component/SudokuBoard.kt

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,16 @@ import androidx.compose.foundation.layout.BoxWithConstraintsScope
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.Modifier
66
import androidx.compose.ui.draw.clip
7-
import androidx.compose.ui.geometry.Offset
8-
import androidx.compose.ui.graphics.Color
97
import androidx.compose.ui.graphics.RectangleShape
108
import androidx.compose.ui.text.TextStyle
11-
import androidx.compose.ui.text.drawText
129
import androidx.compose.ui.text.rememberTextMeasurer
1310
import com.cerve.development.ui.canvas.component.CerveCanvasWithDrawScope
1411
import com.cerve.development.ui.canvas.model.CerveCanvasColors
15-
import com.cerve.development.ui.canvas.model.CerveSize
1612
import com.cerve.development.ui.canvas.operators.CerveCanvasDefaults
1713
import com.cerve.development.ui.canvas.operators.CerveCanvasDefaults.canvasGridConfigurations
1814
import com.cerve.development.ui.canvas.operators.rememberCanvasGridProperties
15+
import com.cerve.development.ui.component.theme.ExtendedTheme
1916
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.BoardState
20-
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.PlacementOrigin
2117

2218
@Composable
2319
fun BoxWithConstraintsScope.SudokuBoard(
@@ -28,9 +24,7 @@ fun BoxWithConstraintsScope.SudokuBoard(
2824
val textMeasurer = rememberTextMeasurer()
2925
val canvasGridProperties = rememberCanvasGridProperties(state.canvasState.gridLineCount)
3026

31-
val style = TextStyle(
32-
color = Color.Black
33-
)
27+
val style = TextStyle(color = ExtendedTheme.colors.onSurface)
3428

3529
CerveCanvasWithDrawScope(
3630
modifier = modifier
@@ -43,49 +37,25 @@ fun BoxWithConstraintsScope.SudokuBoard(
4337
) {
4438

4539
state.board.forEach { tile ->
46-
val valueSize = textMeasurer.measure(tile.text, style).size
4740

48-
val topLeft = tile.centered(
49-
spacing = canvasGridProperties.spacing,
50-
valueOffset = Offset(
51-
x = valueSize.width.toFloat(),
52-
y = valueSize.height.toFloat()
53-
)
41+
drawSolverAdjacent(
42+
state = state,
43+
tile = tile,
44+
color = colors.gridCellAltColor.copy(0.5f),
45+
canvasGridProperties = canvasGridProperties
5446
)
5547

56-
if (tile.origin == PlacementOrigin.User) {
57-
drawRect(
58-
color = colors.gridCellAltColor,
59-
topLeft = tile.topLeft(canvasGridProperties.spacing),
60-
size = CerveSize(canvasGridProperties.spacing).toSize
61-
)
62-
}
63-
64-
val adjacent = state.canvasState.selectedCell?.let { cell ->
65-
val position = cell.position(subgridSize = state.dimensions.multiplier)
66-
67-
when {
68-
position == tile.position -> false
69-
position.row == tile.position.row -> true
70-
position.column == tile.position.column -> true
71-
position.subgrid == tile.position.subgrid -> true
72-
else -> false
73-
}
74-
} ?: false
75-
76-
if (adjacent) {
77-
drawRect(
78-
color = colors.gridCellAltColor,
79-
topLeft = tile.topLeft(canvasGridProperties.spacing),
80-
size = CerveSize(canvasGridProperties.spacing).toSize
81-
)
82-
}
48+
drawSolverSelected(
49+
tile = tile,
50+
color = colors.gridCellAltColor.copy(0.5f),
51+
canvasGridProperties = canvasGridProperties
52+
)
8353

84-
drawText(
85-
textMeasurer = textMeasurer,
86-
text = tile.text,
54+
drawSolverText(
55+
tile = tile,
8756
style = style,
88-
topLeft = topLeft
57+
textMeasurer = textMeasurer,
58+
canvasGridProperties = canvasGridProperties
8959
)
9060

9161
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.stsd.selftaughtsoftwaredevelopers.shared.ui.component
2+
3+
import androidx.compose.ui.geometry.Offset
4+
import androidx.compose.ui.graphics.Color
5+
import androidx.compose.ui.graphics.drawscope.DrawScope
6+
import androidx.compose.ui.text.TextMeasurer
7+
import androidx.compose.ui.text.TextStyle
8+
import androidx.compose.ui.text.drawText
9+
import com.cerve.development.ui.canvas.model.CerveCanvasGridProperties
10+
import com.cerve.development.ui.canvas.model.CerveSize
11+
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.BoardState
12+
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.PlacementOrigin
13+
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.TileState
14+
15+
fun DrawScope.drawSolverAdjacent(
16+
state: BoardState,
17+
tile: TileState,
18+
color: Color,
19+
canvasGridProperties: CerveCanvasGridProperties
20+
) {
21+
val adjacent = state.canvasState.selectedCell?.let { cell ->
22+
val position = cell.position(subgridSize = state.dimensions.multiplier)
23+
24+
when {
25+
position == tile.position -> false
26+
position.row == tile.position.row -> true
27+
position.column == tile.position.column -> true
28+
position.subgrid == tile.position.subgrid -> true
29+
else -> false
30+
}
31+
} ?: false
32+
33+
if (adjacent) {
34+
when(tile.origin) {
35+
PlacementOrigin.UserError -> {
36+
drawRect(
37+
color = Color.Red,
38+
topLeft = tile.topLeft(canvasGridProperties.spacing),
39+
size = CerveSize(canvasGridProperties.spacing).toSize
40+
)
41+
}
42+
else -> {
43+
drawRect(
44+
color = color,
45+
topLeft = tile.topLeft(canvasGridProperties.spacing),
46+
size = CerveSize(canvasGridProperties.spacing).toSize
47+
)
48+
}
49+
}
50+
}
51+
}
52+
53+
fun DrawScope.drawSolverSelected(
54+
tile: TileState,
55+
color: Color,
56+
canvasGridProperties: CerveCanvasGridProperties
57+
) {
58+
when(tile.origin) {
59+
PlacementOrigin.User -> {
60+
drawRect(
61+
color = color,
62+
topLeft = tile.topLeft(canvasGridProperties.spacing),
63+
size = CerveSize(canvasGridProperties.spacing).toSize
64+
)
65+
}
66+
PlacementOrigin.UserError -> {
67+
drawRect(
68+
color = Color.Red,
69+
topLeft = tile.topLeft(canvasGridProperties.spacing),
70+
size = CerveSize(canvasGridProperties.spacing).toSize
71+
)
72+
}
73+
else -> Unit
74+
}
75+
}
76+
77+
fun DrawScope.drawSolverText(
78+
tile: TileState,
79+
style: TextStyle,
80+
textMeasurer: TextMeasurer,
81+
canvasGridProperties: CerveCanvasGridProperties
82+
) {
83+
84+
val valueSize = textMeasurer.measure(tile.text, style).size
85+
86+
val topLeft = tile.centered(
87+
spacing = canvasGridProperties.spacing,
88+
valueOffset = Offset(
89+
x = valueSize.width.toFloat(),
90+
y = valueSize.height.toFloat()
91+
)
92+
)
93+
94+
drawText(
95+
textMeasurer = textMeasurer,
96+
text = tile.text,
97+
style = style,
98+
topLeft = topLeft
99+
)
100+
}

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/component/SudokuBottomBar.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ fun SudokuBottomBar(
5353
) {
5454
TileValue.entries.forEach { tile ->
5555
CerveButton(
56+
enabled = enabled,
5657
text = tile.value.toString(),
5758
) { onEnterValue(tile.value) }
5859
}
@@ -66,11 +67,13 @@ fun SudokuBottomBar(
6667
)
6768
) {
6869
CerveIconButton(
70+
enabled = enabled,
6971
containerColor = colors.secondaryContainer,
7072
imageVector = Icons.Default.RestartAlt
7173
) { onResetClick() }
7274

7375
CerveIconButton(
76+
enabled = enabled,
7477
containerColor = colors.secondaryContainer,
7578
imageVector = Icons.Default.Delete
7679
) { onDeleteClick() }

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/model/board/BoardState.kt

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board
22

3+
import com.cerve.development.data.result.CerveResult
34
import com.cerve.development.ui.canvas.model.CerveCanvasState
4-
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.component.findEmptyPosition
5-
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.component.isValidPlacement
6-
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.TimeState
7-
import kotlinx.coroutines.delay
85

96
data class BoardState(
107
val dimensions: GridDimensions = GridDimensions.GRID_3X3,
@@ -37,11 +34,12 @@ data class BoardState(
3734
}
3835
}
3936

40-
suspend fun findSolution(board: MutableList<TileState>): List<TileState> {
41-
delay(TimeState.INSTANT_SPEED.time)
42-
findEmptyPosition(board).also { position ->
37+
fun findSolution(board: MutableList<TileState>): CerveResult<List<TileState>?> {
38+
return if (board.none { tile -> tile.origin == PlacementOrigin.UserError }) {
39+
val position = findEmptyPosition(board)
40+
4341
if (position == null) {
44-
return board
42+
return CerveResult.Success(board)
4543
} else {
4644
(1..9).forEach { value ->
4745
val candidate = TileState(
@@ -54,16 +52,26 @@ data class BoardState(
5452
val index = board.indexOfFirst { it.position == position }
5553
upsert(index, candidate)
5654

57-
if (findEmptyPosition(findSolution(board)) == null) {
58-
return board
59-
}
55+
findSolution(board).data?.let { board ->
56+
if (findEmptyPosition(board) == null) {
57+
return CerveResult.Success(board)
58+
}
59+
} ?: CerveResult.Error(
60+
value = board,
61+
errorMessage = ""
62+
)
6063

6164
upsert(index, candidate.copy(value = 0))
6265

6366
}
6467
}
65-
return board
68+
return CerveResult.Success(board)
6669
}
70+
} else {
71+
CerveResult.Error(
72+
value = board,
73+
errorMessage = ""
74+
)
6775
}
6876
}
6977

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board
22

33
enum class PlacementOrigin {
4-
User, Solver
4+
User, Solver, UserError
55
}

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/component/Utils.kt renamed to shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/model/board/Utils.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
package com.stsd.selftaughtsoftwaredevelopers.shared.ui.component
1+
package com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board
22

33
import com.cerve.development.ui.canvas.model.CervePosition
4-
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.TileState
54

65
fun findEmptyPosition(board: List<TileState>): CervePosition? {
76
return board.find { tile -> tile.value == 0 }?.position

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/navigation/graph/sudokuSolverHubNavigationGraph.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ fun NavGraphBuilder.sudokuSolverHubNavigationGraph(
2525
val vm = koinViewModel<SudokuSolverViewModel>()
2626
val uiState by vm.uiState.collectAsState()
2727

28-
uiState.StateWrapper { state ->
28+
uiState.StateWrapper { state, isLoading ->
2929
SudokuSolverScreen(
3030
state = state,
31+
isLoading = isLoading,
3132
onSolveBoardClick = vm::solveBoard,
3233
onUpdateValueClick = vm::changeValue,
3334
onDeleteClick = vm::delete,

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/screen/SudokuSolverScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.BoardState
1111
@Composable
1212
fun SudokuSolverScreen(
1313
state: BoardState,
14+
isLoading: Boolean,
1415
modifier: Modifier = Modifier,
1516
onSolveBoardClick: () -> Unit = { },
1617
onUpdateValueClick: (Int) -> Unit = { },
@@ -27,6 +28,7 @@ fun SudokuSolverScreen(
2728
},
2829
bottomBar = {
2930
SudokuBottomBar(
31+
enabled = !isLoading,
3032
onResetClick = onResetClick,
3133
onDeleteClick = onDeleteClick,
3234
onSolveClick = onSolveBoardClick,

shared/src/commonMain/kotlin/com/stsd/selftaughtsoftwaredevelopers/shared/ui/state/SudokuSolverViewModel.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import com.cerve.development.ui.canvas.model.CerveCanvasState
77
import com.cerve.development.ui.state.helper.UIInitStateInstance.Companion.InitMutableUIStateFlow
88
import com.cerve.development.ui.state.helper.UIInitStateInstance.Companion.asStateFlow
99
import com.cerve.development.ui.state.helper.UIInitStateInstance.Companion.getState
10+
import com.cerve.development.ui.state.observer.UIInitStateInstanceUpdate.stateOrNullScope
1011
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.BoardState
1112
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.PlacementOrigin
1213
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.TileState
14+
import com.stsd.selftaughtsoftwaredevelopers.shared.ui.model.board.isValidPlacement
1315
import kotlinx.coroutines.Dispatchers
1416
import kotlinx.coroutines.IO
1517
import kotlinx.coroutines.launch
@@ -33,14 +35,22 @@ class SudokuSolverViewModel : ViewModel() {
3335
state.canvasState.selectedCell?.let { cell ->
3436

3537
val position = cell.position(state.dimensions.multiplier)
38+
val candidate = TileState(
39+
value = value,
40+
position = position,
41+
origin = PlacementOrigin.User
42+
)
3643

37-
state.upsert(
38-
tile = TileState(
39-
value = value,
40-
position = position,
41-
origin = PlacementOrigin.User
42-
)
44+
val isValidPlacement = isValidPlacement(
45+
candidate = candidate,
46+
board = state.board
4347
)
48+
49+
val tileState = if (isValidPlacement) {
50+
candidate
51+
} else candidate.copy(origin = PlacementOrigin.UserError)
52+
53+
state.upsert(tile = tileState)
4454
}
4555
}
4656
}
@@ -53,9 +63,8 @@ class SudokuSolverViewModel : ViewModel() {
5363
_uiState.getState?.reset()
5464
}
5565

56-
fun solveBoard() = viewModelScope.launch(Dispatchers.IO) {
57-
_uiState.getState?.let { state ->
58-
state.findSolution(state.board)
59-
}
66+
fun solveBoard() = _uiState.stateOrNullScope {
67+
findSolution(board)
68+
this
6069
}
6170
}

0 commit comments

Comments
 (0)