Tugas 10 - Membuat Aplikasi Unscramble Word App
Membuat Aplikasi Unscramble Word App
Ricardo Supriyanto - 5025221218
Pada tugas 10, saya melanjutkan eksplorasi pengembangan aplikasi Android dengan fokus pada implementasi dan penambahan fitur pada sebuah aplikasi game kata yang saya namakan "Aplikasi Unscramble Word". Proyek ini merupakan pengembangan lebih lanjut dari panduan codelab resmi Android yang menekankan pada penggunaan Jetpack Compose untuk membangun antarmuka pengguna deklaratif dan ViewModel untuk pengelolaan status UI yang efisien dan siklus hidup aplikasi yang robust. Tujuan utama dari pengembangan ini adalah menciptakan sebuah permainan tebak kata yang interaktif dan menarik, di mana pengguna diberikan serangkaian huruf acak dan harus menyusunnya menjadi kata yang benar.
Aplikasi "Unscramble Word" ini dirancang sebagai permainan klik sederhana yang menantang kemampuan kosakata dan kecepatan berpikir pengguna. Setiap putaran menampilkan kata yang hurufnya diacak, dan pemain harus memasukkan tebakan mereka. Skor akan bertambah untuk setiap tebakan yang benar, mendorong pemain untuk terus berpartisipasi dan meningkatkan performa mereka. Arsitektur aplikasi ini dibangun di atas prinsip Unidirectional Data Flow (UDF), memastikan data mengalir secara konsisten dari ViewModel ke UI, sehingga UI selalu merepresentasikan status aplikasi terbaru dengan akurat.
Fitur-fitur utama yang diimplementasikan meliputi:
- Mekanisme Game Tebak Kata: Pengguna memasukkan tebakan kata dari huruf yang diacak.
- Pembaruan Skor Dinamis: Skor pemain meningkat untuk setiap tebakan yang benar, dengan pelacakan jumlah kata yang telah dimainkan.
- Pelestarian Status Game: Menggunakan ViewModel, progres permainan (skor, kata saat ini, dll.) dipertahankan bahkan saat terjadi perubahan konfigurasi perangkat seperti rotasi layar.
- Struktur Proyek Modular: Pemisahan data, ViewModel, dan komponen UI yang jelas untuk kode yang bersih dan mudah dikelola.
Selain fungsionalitas dasar dari codelab, saya juga menambahkan beberapa modifikasi signifikan untuk meningkatkan pengalaman pengguna dan menambahkan kompleksitas pada gameplay:
- Fitur Petunjuk (Hint): Sebuah opsi "Hint" memungkinkan pemain untuk melihat huruf pertama dari kata yang benar. Penggunaan petunjuk ini akan mengurangi skor pemain, dan hanya dapat digunakan sekali per kata untuk menjaga tantangan permainan.
- Pengatur Waktu Per Kata (Timer): Setiap kata kini dilengkapi dengan batas waktu (misalnya, 30 detik). Jika pemain gagal menebak kata dalam batas waktu, kata tersebut akan dilewati secara otomatis, memberikan penalti skor kecil, dan game akan melanjutkan ke kata berikutnya, menambah elemen tekanan dan kecepatan.
- Tombol Reset Game: Sebuah tombol "Reset Game" telah diimplementasikan, memungkinkan pemain untuk mengatur ulang seluruh progres permainan (skor, hitungan kata, dan memulai kata baru) kapan saja, memberikan fleksibilitas untuk memulai sesi baru.
Berikut merupakan sedikit demo dari aplikasi "Unscramble Word" yang telah saya buat:
Kode Program Modifikasi:
package com.example.unscramble.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
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.sizeIn
import androidx.compose.foundation.rememberScrollState
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
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
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
import androidx.compose.animation.SizeTransform
@Composable
fun GameScreen(
gameViewModel: GameViewModel = viewModel()
) {
val gameUiState by gameViewModel.uiState.collectAsState()
val mediumPadding = dimensionResource(R.dimen.padding_medium)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(mediumPadding)
.safeDrawingPadding()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.unscramble_the_word),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
GameStatus(gameUiState.currentWordCount, gameUiState.score, modifier = Modifier.padding(mediumPadding))
Text(
text = "Time Left: ${gameUiState.timeLeft}s",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 8.dp)
)
GameLayout(
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
userGuess = gameViewModel.userGuess,
isGuessWrong = gameUiState.isGuessedWordWrong,
currentScrambledWord = gameUiState.currentScrambledWord,
onUseHint = { gameViewModel.useHint() },
isHintUsed = gameUiState.isHintUsedForCurrentWord,
hintLetter = gameUiState.hintLetter,
onResetGame = { gameViewModel.resetGame() }
)
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
)
}
}
if (gameUiState.isGameOver) {
FinalScoreDialog(
score = gameUiState.score,
onPlayAgain = { gameViewModel.resetGame() }
)
}
}
}
@Composable
fun GameStatus(wordCount: Int, score: Int, modifier: Modifier = Modifier) {
Card(
modifier = modifier
.fillMaxWidth()
.sizeIn(minHeight = 80.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
) {
Column(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.word_count, wordCount),
style = MaterialTheme.typography.titleMedium
)
Text(
text = stringResource(R.string.score, score),
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
}
}
}
@Composable
fun GameLayout(
onUserGuessChanged: (String) -> Unit,
userGuess: String,
isGuessWrong: Boolean,
currentScrambledWord: String,
onUseHint: () -> Unit,
isHintUsed: Boolean,
hintLetter: Char?,
onResetGame: () -> Unit,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium))
) {
Text(
text = stringResource(R.string.instructions),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleMedium
)
AnimatedContent(
targetState = currentScrambledWord,
transitionSpec = {
slideInVertically { height -> height } + fadeIn() togetherWith
slideOutVertically { height -> -height } + fadeOut() using
SizeTransform(clip = false)
}, label = "ScrambledWordAnimation"
) { scrambledWord ->
Text(
text = scrambledWord,
style = MaterialTheme.typography.displayMedium,
fontWeight = FontWeight.Bold
)
}
if (isHintUsed && hintLetter != null) {
Text(
text = "Hint: $hintLetter",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
}
OutlinedTextField(
value = userGuess,
onValueChange = onUserGuessChanged,
label = {
if (isGuessWrong) {
Text(stringResource(R.string.wrong_guess))
} else {
Text(stringResource(R.string.enter_your_word))
}
},
isError = isGuessWrong,
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = onUseHint,
enabled = !isHintUsed,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Use Hint (-5 pts)")
}
OutlinedButton(
onClick = onResetGame,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Reset Game")
}
}
}
}
}
@Composable
private fun FinalScoreDialog(
score: Int,
onPlayAgain: () -> Unit,
modifier: Modifier = Modifier
) {
val openDialog = true
if (openDialog) {
AlertDialog(
onDismissRequest = {
},
title = { Text(text = stringResource(R.string.congratulations)) },
text = { Text(text = stringResource(R.string.you_scored, score)) },
modifier = modifier,
dismissButton = {
TextButton(
onClick = {
onPlayAgain()
}
) {
Text(text = stringResource(R.string.play_again))
}
},
confirmButton = {
}
)
}
}
@Preview(showBackground = true)
@Composable
fun GameScreenPreview() {
UnscrambleTheme {
GameScreen()
}
}

Komentar
Posting Komentar