Tugas 10 Aplikasi Unscramble
Nama: Dafarel Fatih Wirayudha
NRP: 5025211120
Kelas: PPB A
Pada tugas kali ini saya membuat aplikasi permain unscramble.
Berikut adalah penjelasan fungsi fungsi penting dari kode aplikasi nya:
class GameViewModel : ViewModel() {
private val _uiState = MutableStateFlow(GameUiState())
val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()
var userGuess by mutableStateOf("")
private set
private var usedWords: MutableSet<String> = mutableSetOf()
private lateinit var currentWord: String
init {
resetGame()
}
fun resetGame() {
usedWords.clear()
_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
}
fun updateUserGuess(guessedWord: String){
userGuess = guessedWord
}
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
updateGameState(updatedScore)
} else {
_uiState.update { currentState ->
currentState.copy(isGuessedWordWrong = true)
}
}
updateUserGuess("")
}
fun skipWord() {
updateGameState(_uiState.value.score)
// Reset user guess
updateUserGuess("")
}
private fun updateGameState(updatedScore: Int) {
if (usedWords.size == MAX_NO_OF_WORDS){
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
score = updatedScore,
isGameOver = true
)
}
} else{
_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()
tempWord.shuffle()
while (String(tempWord) == word) {
tempWord.shuffle()
}
return String(tempWord)
}
private fun pickRandomWordAndShuffle(): String {
currentWord = allWords.random()
return if (usedWords.contains(currentWord)) {
pickRandomWordAndShuffle()
} else {
usedWords.add(currentWord)
shuffleCurrentWord(currentWord)
}
}
}
- _uiState: Ini adalah MutableStateFlow yang menyimpan status terkini dari game dalam sebuah objek GameUiState. StateFlow adalah holder data yang dapat diobservasi, yang secara efisien memberi tahu UI ketika ada perubahan data.
- uiState: Ini adalah versi StateFlow yang hanya bisa dibaca (read-only) dari _uiState. UI akan "mendengarkan" StateFlow ini untuk memperbarui dirinya sendiri.
- userGuess: Variabel ini menggunakan mutableStateOf dari Compose untuk menyimpan tebakan kata dari pengguna.
- usedWords: Sebuah MutableSet untuk melacak kata-kata yang sudah digunakan dalam sesi permainan saat ini untuk menghindari pengulangan.
- init: Blok init akan dieksekusi saat GameViewModel pertama kali dibuat, dan di sini kita memanggil resetGame() untuk memulai permainan baru.
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
updateGameState(updatedScore)
} else {
_uiState.update { currentState ->
currentState.copy(isGuessedWordWrong = true)
}
}
updateUserGuess("")
}
- Fungsi ini membandingkan userGuess dengan currentWord (kata yang benar), dengan mengabaikan huruf besar/kecil.
- Jika tebakan benar, skor akan diperbarui (updatedScore), dan updateGameState() dipanggil untuk menyiapkan putaran berikutnya.
- Jika salah, _uiState diperbarui untuk menandai bahwa tebakan salah (isGuessedWordWrong = true), yang akan memicu perubahan di UI untuk menampilkan pesan kesalahan.
- Terakhir, updateUserGuess("") dipanggil untuk mengosongkan kolom input setelah setiap tebakan.
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() }
)
)
}
}
}
@Composable
private fun FinalScoreDialog(
score: Int,
onPlayAgain: () -> Unit,
modifier: Modifier = Modifier
) {
val activity = (LocalContext.current as Activity)
AlertDialog(
onDismissRequest = {},
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()
}
}
- @Composable: Anotasi ini menandakan bahwa ini adalah fungsi Jetpack Compose yang dapat digunakan untuk membangun UI.
- gameViewModel: GameViewModel = viewModel(): Ini adalah cara standar di Compose untuk mendapatkan instance dari GameViewModel. Compose akan secara otomatis mengelola siklus hidup ViewModel ini.
- val gameUiState by gameViewModel.uiState.collectAsState(): Baris ini sangat penting. collectAsState() mengumpulkan data dari StateFlow di ViewModel dan mengubahnya menjadi State yang dapat dibaca oleh Compose. Setiap kali gameUiState di ViewModel berubah, GameScreen akan secara otomatis di-recompose (digambar ulang) dengan data baru.
- GameLayout(...): Ini adalah composable lain yang dipanggil untuk menampilkan tata letak utama permainan. Perhatikan bagaimana event seperti onUserGuessChanged dan onKeyboardDone diteruskan sebagai lambda yang memanggil fungsi di gameViewModel.
- if (gameUiState.isGameOver): UI di dalam blok if ini (yaitu FinalScoreDialog) hanya akan ditampilkan jika kondisi isGameOver dari gameUiState adalah true. Ini menunjukkan sifat deklaratif dari Compose.
Hasil:
Comments
Post a Comment