PPB I - Tugas Pert 11
Tugas Pert 11
Nama : Rachel Anggieuli AP
NRP : 5025201263
Tahun : 2023/2024
Kelas
: PPB I
ViewModel and State
Pada tugas kali ini, ditugaskan untuk membuat Aplikasi Unsramble Words pada android studio. Aplikasi tersembut akan memberikan kata dalam bahasa inggris secara acak, dan kita sebagai pemain harus memasukkan kata dalam bentuk benar.
Link TutorialLink Tutorial-Github
Dokumentasi Aplikasi
GameUiState.kt
<> package com.example.unscramble.ui /** * Data class that represents the game UI state */ data class GameUiState( val currentScrambledWord: String = "", val currentWordCount: Int = 1, val score: Int = 0, val isGuessedWordWrong: Boolean = false, val isGameOver: Boolean = false ) <>
GameViewModel.kt
<> package com.example.unscramble.ui import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.example.unscramble.data.MAX_NO_OF_WORDS import com.example.unscramble.data.SCORE_INCREASE import com.example.unscramble.data.allWords import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update /** * ViewModel containing the app data and methods to process the data */ class GameViewModel : ViewModel() { // Game UI state private val _uiState = MutableStateFlow(GameUiState()) val uiState: StateFlow<GameUiState< = _uiState.asStateFlow() var userGuess by mutableStateOf("") private set // Set of words used in the game private var usedWords: MutableSet<String< = mutableSetOf() private lateinit var currentWord: String init { resetGame() } /* * Re-initializes the game data to restart the game. */ fun resetGame() { usedWords.clear() _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle()) } /* * Update the user's guess */ fun updateUserGuess(guessedWord: String){ userGuess = guessedWord } /* * Checks if the user's guess is correct. * Increases the score accordingly. */ fun checkUserGuess() { if (userGuess.equals(currentWord, ignoreCase = true)) { // User's guess is correct, increase the score // and call updateGameState() to prepare the game for next round val updatedScore = _uiState.value.score.plus(SCORE_INCREASE) updateGameState(updatedScore) } else { // User's guess is wrong, show an error _uiState.update { currentState -> currentState.copy(isGuessedWordWrong = true) } } // Reset user guess updateUserGuess("") } /* * Skip to next word */ fun skipWord() { updateGameState(_uiState.value.score) // Reset user guess updateUserGuess("") } /* * Picks a new currentWord and currentScrambledWord and updates UiState according to * current game state. */ private fun updateGameState(updatedScore: Int) { if (usedWords.size == MAX_NO_OF_WORDS){ //Last round in the game, update isGameOver to true, don't pick a new word _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, score = updatedScore, isGameOver = true ) } } else{ // Normal round in the game _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), currentWordCount = currentState.currentWordCount.inc(), score = updatedScore ) } } } private fun shuffleCurrentWord(word: String): String { val tempWord = word.toCharArray() // Scramble the word tempWord.shuffle() while (String(tempWord) == word) { tempWord.shuffle() } return String(tempWord) } private fun pickRandomWordAndShuffle(): String { // Continue picking up a new random word until you get one that hasn't been used before currentWord = allWords.random() return if (usedWords.contains(currentWord)) { pickRandomWordAndShuffle() } else { usedWords.add(currentWord) shuffleCurrentWord(currentWord) } } } <>
MainActivity.kt
<> package com.example.unscramble.ui import android.app.Activity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.unscramble.R import com.example.unscramble.ui.theme.UnscrambleTheme @Composable fun GameScreen(gameViewModel: GameViewModel = viewModel()) { val gameUiState by gameViewModel.uiState.collectAsState() val mediumPadding = dimensionResource(R.dimen.padding_medium) Column( modifier = Modifier .statusBarsPadding() .verticalScroll(rememberScrollState()) .safeDrawingPadding() .padding(mediumPadding), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = stringResource(R.string.app_name), style = typography.titleLarge, ) GameLayout( onUserGuessChanged = { gameViewModel.updateUserGuess(it) }, wordCount = gameUiState.currentWordCount, userGuess = gameViewModel.userGuess, onKeyboardDone = { gameViewModel.checkUserGuess() }, currentScrambledWord = gameUiState.currentScrambledWord, isGuessWrong = gameUiState.isGuessedWordWrong, modifier = Modifier .fillMaxWidth() .wrapContentHeight() .padding(mediumPadding) ) Column( modifier = Modifier .fillMaxWidth() .padding(mediumPadding), verticalArrangement = Arrangement.spacedBy(mediumPadding), horizontalAlignment = Alignment.CenterHorizontally ) { Button( modifier = Modifier.fillMaxWidth(), onClick = { gameViewModel.checkUserGuess() } ) { Text( text = stringResource(R.string.submit), fontSize = 16.sp ) } OutlinedButton( onClick = { gameViewModel.skipWord() }, modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(R.string.skip), fontSize = 16.sp ) } } GameStatus(score = gameUiState.score, modifier = Modifier.padding(20.dp)) if (gameUiState.isGameOver) { FinalScoreDialog( score = gameUiState.score, onPlayAgain = { gameViewModel.resetGame() } ) } } } @Composable fun GameStatus(score: Int, modifier: Modifier = Modifier) { Card( modifier = modifier ) { Text( text = stringResource(R.string.score, score), style = typography.headlineMedium, modifier = Modifier.padding(8.dp) ) } } @Composable fun GameLayout( currentScrambledWord: String, wordCount: Int, isGuessWrong: Boolean, userGuess: String, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, modifier: Modifier = Modifier ) { val mediumPadding = dimensionResource(R.dimen.padding_medium) Card( modifier = modifier, elevation = CardDefaults.cardElevation(defaultElevation = 5.dp) ) { Column( verticalArrangement = Arrangement.spacedBy(mediumPadding), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(mediumPadding) ) { Text( modifier = Modifier .clip(shapes.medium) .background(colorScheme.surfaceTint) .padding(horizontal = 10.dp, vertical = 4.dp) .align(alignment = Alignment.End), text = stringResource(R.string.word_count, wordCount), style = typography.titleMedium, color = colorScheme.onPrimary ) Text( text = currentScrambledWord, style = typography.displayMedium ) Text( text = stringResource(R.string.instructions), textAlign = TextAlign.Center, style = typography.titleMedium ) OutlinedTextField( value = userGuess, singleLine = true, shape = shapes.large, modifier = Modifier.fillMaxWidth(), colors = TextFieldDefaults.colors( focusedContainerColor = colorScheme.surface, unfocusedContainerColor = colorScheme.surface, disabledContainerColor = colorScheme.surface, ), onValueChange = onUserGuessChanged, label = { if (isGuessWrong) { Text(stringResource(R.string.wrong_guess)) } else { Text(stringResource(R.string.enter_your_word)) } }, isError = isGuessWrong, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( onDone = { onKeyboardDone() } ) ) } } } /* * Creates and shows an AlertDialog with final score. */ @Composable private fun FinalScoreDialog( score: Int, onPlayAgain: () -> Unit, modifier: Modifier = Modifier ) { val activity = (LocalContext.current as Activity) AlertDialog( onDismissRequest = { // Dismiss the dialog when the user clicks outside the dialog or on the back // button. If you want to disable that functionality, simply use an empty // onCloseRequest. }, title = { Text(text = stringResource(R.string.congratulations)) }, text = { Text(text = stringResource(R.string.you_scored, score)) }, modifier = modifier, dismissButton = { TextButton( onClick = { activity.finish() } ) { Text(text = stringResource(R.string.exit)) } }, confirmButton = { TextButton(onClick = onPlayAgain) { Text(text = stringResource(R.string.play_again)) } } ) } @Preview(showBackground = true) @Composable fun GameScreenPreview() { UnscrambleTheme { GameScreen() } } <>
Comments
Post a Comment