Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,6 @@ fun Keypad(
}
}
}
/*Spacer(modifier = Modifier.Companion.height(8.dp))
Text(
text = stringResource(id = R.string.current_input, inputValue.ifEmpty { "-" }),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
)*/
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import eu.indiewalkabout.mathbrainer.feat_credits.presentation.ui.GameCreditsScr
import eu.indiewalkabout.mathbrainer.feat_games.feat_count_items.presentation.ui.CountObjectsGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_enigma.presentation.ui.EnigmaGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_falling_op.presentation.ui.FallingOpsGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.ui.MemoryFlashGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_math_op_double.presentation.ui.DoubleNumberGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_math_op_write.presentation.ui.MathWriteGameScreen
import eu.indiewalkabout.mathbrainer.feat_games.feat_math_random_operation.presentation.ui.RandomOperationGameScreen
Expand Down Expand Up @@ -120,6 +121,21 @@ fun NavGraph(
)
}

// --- Memory Flash Game ---
composable(
route = ScreenRoutes.MemoryFlashGame.route,
arguments = listOf(
navArgument("highScore") { type = NavType.IntType }
)
) { backStackEntry ->
val highScore = backStackEntry.arguments?.getInt("highScore") ?: 0

MemoryFlashGameScreen(
initialHighScore = highScore,
onBack = { navController.popBackStack() }
)
}

// --- Number Order Game ---
composable(
route = ScreenRoutes.NumberOrderGame.route,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ sealed class ScreenRoutes(val route: String) {
object RandomOperationGame : ScreenRoutes("random_op_game/{highScore}") {
fun createRoute(highScore: Int = 0): String = "random_op_game/$highScore"
}
object MemoryFlashGame : ScreenRoutes("memory_flash_game/{highScore}") {
fun createRoute(highScore: Int = 0): String = "memory_flash_game/$highScore"
}
object CountObjectsGame : ScreenRoutes("count_objects_game/{highScore}") {
fun createRoute(highScore: Int = 0): String {
return "count_objects_game/$highScore"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.model

data class MemoryFlashChallenge(
val sequence: List<Int>
) {
val answer: String = sequence.joinToString(separator = "")
val displaySequence: String = sequence.joinToString(separator = " ")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.model

data class MemoryFlashConfig(
val level: Int,
val length: Int,
val maxDigit: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.use_cases

import eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.model.MemoryFlashChallenge
import eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.model.MemoryFlashConfig
import javax.inject.Inject
import kotlin.random.Random

class GenerateMemoryFlashChallengeUseCase @Inject constructor() {
operator fun invoke(config: MemoryFlashConfig): MemoryFlashChallenge {
val digits = MutableList(config.length) { Random.nextInt(0, config.maxDigit + 1) }
return MemoryFlashChallenge(sequence = digits)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.domain.use_cases

import eu.indiewalkabout.mathbrainer.feat_statistics.domain.model.GameScores.Companion.emptyScores
import eu.indiewalkabout.mathbrainer.feat_statistics.domain.repository.MathBrainerRepository
import javax.inject.Inject
import kotlin.math.max
import kotlinx.coroutines.flow.firstOrNull

class UpdateMemoryFlashScoreUseCase @Inject constructor(
private val repository: MathBrainerRepository
) {
suspend operator fun invoke(sessionScore: Int) {
val currentScores = repository.observeGameScores().firstOrNull() ?: emptyScores
val updatedScores = currentScores.copy(
memory_flash_game_score = max(currentScores.memory_flash_game_score, sessionScore),
id = 0,
global_score = currentScores.global_score + sessionScore
)
repository.insertGameScores(updatedScores)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.components

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import eu.indiewalkabout.mathbrainer.R
import eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.state.MemoryFlashUiState

@Composable
fun MemoryFlashChallengeCard(state: MemoryFlashUiState) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
) {
// Define height constants at the top of the Column scope
val textFieldHeight = 50.dp

Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.memory_flash_instruction),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)

val sequenceText = when {
state.isSequenceVisible -> state.visibleSequence
state.revealedSequence != null -> state.revealedSequence
else -> stringResource(id = R.string.memory_flash_hidden_sequence)
}

Text(
text = sequenceText,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold
)

if (!state.isSequenceVisible && state.revealedSequence == null) {
// Input field for the answer
TextField(
value = state.inputValue,
onValueChange = {},
readOnly = true,
modifier = Modifier
.fillMaxWidth()
.height(textFieldHeight),
textStyle = MaterialTheme.typography.titleMedium.copy(
textAlign = TextAlign.Center,
color = if (state.feedback != null) {
when (state.feedback) {
MemoryFlashUiState.Feedback.SUCCESS -> MaterialTheme.colorScheme.primary
MemoryFlashUiState.Feedback.FAILURE -> MaterialTheme.colorScheme.error
}
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
),
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
disabledContainerColor = MaterialTheme.colorScheme.surface,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
cursorColor = Color.Transparent
),
visualTransformation = VisualTransformation.None,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.None
),
placeholder = {
Text(
text = stringResource(id = R.string.memory_flash_prompt),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
)
} else {
// Invisible placeholder with the same height as TextField
Spacer(modifier = Modifier.height(textFieldHeight))
}
Spacer(modifier = Modifier.height(4.dp))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.indiewalkabout.mathbrainer.R
import eu.indiewalkabout.mathbrainer.feat_games.feat_math_op_write.presentation.components.LevelProgressBar
import eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.state.MemoryFlashUiState

@Composable
fun MemoryFlashHeader(state: MemoryFlashUiState) {
Card(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.level_label, state.level),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)

LevelProgressBar(
currentProgress = state.challengesCompleted,
totalSegments = state.challengesPerLevel,
segmentColor = MaterialTheme.colorScheme.primary,
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp)
.height(8.dp)
)

Text(
text = stringResource(id = R.string.lives_label, state.lives),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
}

Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(id = R.string.score_label, state.score),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = stringResource(
id = R.string.high_score_label,
state.highScore ?: 0
),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package eu.indiewalkabout.mathbrainer.feat_games.feat_memory_flash.presentation.state

data class MemoryFlashUiState(
val score: Int = 0,
val highScore: Int? = null,
val level: Int = 1,
val lives: Int = 3,
val visibleSequence: String = "",
val isSequenceVisible: Boolean = true,
val inputValue: String = "",
val feedback: Feedback? = null,
val isGameOver: Boolean = false,
val challengesCompleted: Int = 0,
val challengesPerLevel: Int = INITIAL_CHALLENGES_PER_LEVEL,
val revealedSequence: String? = null,
val isReadyForNext: Boolean = false
) {
enum class Feedback { SUCCESS, FAILURE }

companion object {
const val INITIAL_CHALLENGES_PER_LEVEL = 3
const val MAX_CHALLENGES_PER_LEVEL = 6
}
}
Loading