Skip to content

Commit

Permalink
Merge pull request #4 from arkivanov/local-icons
Browse files Browse the repository at this point in the history
Pass game icons via local
  • Loading branch information
arkivanov authored Feb 5, 2024
2 parents 8782cd1 + 6e6414e commit e726b09
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -41,36 +43,43 @@ private val cellSize = 16.dp
@Composable
internal fun GameContent(component: GameComponent, modifier: Modifier = Modifier) {
val state by component.state.subscribeAsState()

Box(modifier = modifier, contentAlignment = Alignment.Center) {
Column {
RestartButton(
isWin = state.gameStatus == GameStatus.WIN,
isFailed = state.gameStatus == GameStatus.FAILED,
isTrying = state.pressMode != PressMode.NONE,
onClick = component::onRestartClicked,
modifier = Modifier.align(Alignment.CenterHorizontally),
)

Spacer(modifier = Modifier.height(16.dp))

Row(
modifier = Modifier.touchHandler(
gridWidth = state.width,
gridHeight = state.height,
onPrimaryTouched = component::onCellTouchedPrimary,
onSecondaryPressed = component::onCellPressedSecondary,
onTertiaryTouched = component::onCellTouchedTertiary,
onReleased = component::onCellReleased,
),
) {
repeat(state.width) { x ->
Column(modifier = Modifier.width(cellSize)) {
repeat(state.height) { y ->
CellContent(
cell = state.grid.getValue(x by y),
modifier = Modifier.fillMaxWidth().height(cellSize),
)
val gameStatus by derivedStateOf { state.gameStatus }
val pressMode by derivedStateOf { state.pressMode }
val gridWidth by derivedStateOf { state.width }
val gridHeight by derivedStateOf { state.height }
val grid by derivedStateOf { state.grid }

CompositionLocalProvider(LocalGameIcons provides gameIcons()) {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
Column {
RestartButton(
isWin = gameStatus == GameStatus.WIN,
isFailed = gameStatus == GameStatus.FAILED,
isTrying = pressMode != PressMode.NONE,
onClick = component::onRestartClicked,
modifier = Modifier.align(Alignment.CenterHorizontally),
)

Spacer(modifier = Modifier.height(16.dp))

Row(
modifier = Modifier.touchHandler(
gridWidth = gridWidth,
gridHeight = gridHeight,
onPrimaryTouched = component::onCellTouchedPrimary,
onSecondaryPressed = component::onCellPressedSecondary,
onTertiaryTouched = component::onCellTouchedTertiary,
onReleased = component::onCellReleased,
),
) {
repeat(gridWidth) { x ->
Column(modifier = Modifier.width(cellSize)) {
repeat(gridHeight) { y ->
CellContent(
cell = grid.getValue(x by y),
modifier = Modifier.fillMaxWidth().height(cellSize),
)
}
}
}
}
Expand Down Expand Up @@ -108,11 +117,11 @@ private fun RestartButton(

Image(
painter = when {
isWin -> GameIcons.smileWin
isPressed -> GameIcons.smilePressed
isFailed -> GameIcons.smileFailed
isTrying -> GameIcons.smileTrying
else -> GameIcons.smileNormal
isWin -> LocalGameIcons.icons.smileWin
isPressed -> LocalGameIcons.icons.smilePressed
isFailed -> LocalGameIcons.icons.smileFailed
isTrying -> LocalGameIcons.icons.smileTrying
else -> LocalGameIcons.icons.smileNormal
},
contentDescription = "Restart",
modifier = modifier
Expand All @@ -124,24 +133,23 @@ private fun RestartButton(
onClick = onClick,
),
)

}

@Composable
private fun Cell.painter(): Painter =
when (status) {
is CellStatus.Closed ->
when {
status.isFlagged -> GameIcons.cellClosedFlag
status.isPressed -> GameIcons.cellOpen
else -> GameIcons.cellClosed
status.isFlagged -> LocalGameIcons.icons.cellClosedFlag
status.isPressed -> LocalGameIcons.icons.cellOpen
else -> LocalGameIcons.icons.cellClosed
}

is CellStatus.Open ->
when (value) {
is CellValue.None -> GameIcons.cellOpen
is CellValue.Mine -> GameIcons.cellOpenMine
is CellValue.Number -> GameIcons.cellOpen(value.number)
is CellValue.None -> LocalGameIcons.icons.cellOpen
is CellValue.Mine -> LocalGameIcons.icons.cellOpenMine
is CellValue.Number -> LocalGameIcons.icons.cellOpenNumbers.getValue(value.number)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@
package com.arkivanov.minesweeper.game

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.painter.Painter
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource

@OptIn(ExperimentalResourceApi::class)
internal object GameIcons {

val cellClosed: Painter
@Composable
get() = painterResource("cell_closed.png")

val cellClosedFlag: Painter
@Composable
get() = painterResource("cell_closed_flag.png")

val cellOpen: Painter
@Composable
get() = painterResource("cell_open.png")

val cellOpenMine: Painter
@Composable
get() = painterResource("cell_open_mine.png")

val smileFailed: Painter
@Composable
get() = painterResource("smile_failed.png")

val smileNormal: Painter
@Composable
get() = painterResource("smile_normal.png")

val smilePressed: Painter
@Composable
get() = painterResource("smile_pressed.png")

val smileWin: Painter
@Composable
get() = painterResource("smile_win.png")

val smileTrying: Painter
@Composable
get() = painterResource("smile_trying.png")
internal data class GameIcons(
val cellClosed: Painter,
val cellClosedFlag: Painter,
val cellOpen: Painter,
val cellOpenMine: Painter,
val cellOpenNumbers: Map<Int, Painter>,
val smileFailed: Painter,
val smileNormal: Painter,
val smilePressed: Painter,
val smileWin: Painter,
val smileTrying: Painter,
)

@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun gameIcons(): GameIcons =
GameIcons(
cellClosed = painterResource("cell_closed.png"),
cellClosedFlag = painterResource("cell_closed_flag.png"),
cellOpen = painterResource("cell_open.png"),
cellOpenMine = painterResource("cell_open_mine.png"),
cellOpenNumbers = buildMap {
for (i in 1..8) {
put(i, painterResource("cell_open_$i.png"))
}
},
smileFailed = painterResource("smile_failed.png"),
smileNormal = painterResource("smile_normal.png"),
smilePressed = painterResource("smile_pressed.png"),
smileWin = painterResource("smile_win.png"),
smileTrying = painterResource("smile_trying.png"),
)

internal val LocalGameIcons: ProvidableCompositionLocal<GameIcons?> =
compositionLocalOf { null }

internal val CompositionLocal<GameIcons?>.icons: GameIcons
@Composable
fun cellOpen(number: Int): Painter =
painterResource("cell_open_$number.png")
}
get() = requireNotNull(current)

0 comments on commit e726b09

Please sign in to comment.