Tugas Pertemuan 11 - Game Unscramble
Tugas Pertemuan 11
Game Unscramble
Nama : Rycahaya Sri Hutomo
NRP : 5025201046
Kelas : Pemrograman Perangkat Bergerak B
Tahun : 2024
Mendownload Kode Awal
Kondisi awal aplikasi:
- Kata acak tidak ditampilkan, tetapi di-hardcode ke "scrambleun"
- Tidak ada yang terjadi saat tombol diklik.
File Kode Awal
WordsData.kt
File ini berisi daftar kata yang digunakan dalam game, konstanta untuk jumlah maksimum kata per game, dan jumlah poin skor pemain untuk setiap kata yang benar.
MainActivity.kt
File ini sebagian besar berisi kode yang dihasilkan oleh template. Anda menampilkan composable GameScreen di blok setContent{}.
GameScreen.kt
Semua composable UI ditentukan dalam file GameScreen.kt. Bagian berikut memberikan panduan tentang beberapa fungsi composable.
GameStatus
GameStatus adalah fungsi composable yang menampilkan skor game di bagian bawah layar. Fungsi composable berisi composable teks di Card. Untuk saat ini, skor di-hardcode menjadi 0.
GameLayout
GameLayout adalah fungsi composable yang menampilkan fungsi game utama, yang mencakup kata acak, petunjuk game, dan kolom teks yang menerima tebakan pengguna.
GameScreen
Composable GameScreen berisi fungsi composable GameStatus dan GameLayout, judul game, jumlah kata, dan composable untuk tombol Submit dan Skip.
FinalScoreDialog
Composable FinalScoreDialog menampilkan dialog, yaitu jendela kecil yang memberikan permintaan kepada pengguna, dengan opsi untuk Play Again atau Exit game. Nanti dalam codelab ini, Anda akan menerapkan logika untuk menampilkan dialog ini di akhir game.
Menambahkan ViewModel
1. Buka build.gradle.kts (Module :app), scroll ke blok dependencies, lalu tambahkan dependensi berikut untuk ViewModel.
dependencies { // other dependencies implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") //... }
import androidx.lifecycle.ViewModel class GameViewModel : ViewModel() { }
import kotlinx.coroutines.flow.MutableStateFlow // Game UI state private val _uiState = MutableStateFlow(GameUiState())
Properti Pendukung
1. Di file GameViewModel.kt, tambahkan properti pendukung ke uiState yang bernama _uiState. Beri nama properti uiState dan berjenis StateFlow<GameUiState>.
import kotlinx.coroutines.flow.StateFlow // Game UI state private val _uiState = MutableStateFlow(GameUiState()) val uiState: StateFlow<GameUiState>
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow // Game UI state private val _uiState = MutableStateFlow(GameUiState()) val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()
Menampilkan kata acak tanpa pola
1. Di GameViewModel, tambahkan properti bernama currentWord dari jenis String untuk menyimpan kata acak saat ini.private lateinit var currentWord: String
import com.example.unscramble.data.allWords private fun pickRandomWordAndShuffle(): String { // Continue picking up a new random word until you get one that hasn't been used before currentWord = allWords.random() if (usedWords.contains(currentWord)) { return pickRandomWordAndShuffle() } else { usedWords.add(currentWord) return shuffleCurrentWord(currentWord) } }
// Set of words used in the game private var usedWords: MutableSet<String> = mutableSetOf()
private fun shuffleCurrentWord(word: String): String { val tempWord = word.toCharArray() // Scramble the word tempWord.shuffle() while (String(tempWord).equals(word)) { tempWord.shuffle() } return String(tempWord) }
fun resetGame() { usedWords.clear() _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle()) }
init { resetGame() }
Merancang Compose UI
Meneruskan Data
1. Pada fungsi GameScreen, teruskan argumen kedua dari jenis GameViewModel dengan nilai default viewModel().import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun GameScreen( gameViewModel: GameViewModel = viewModel() ) { // ... }
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @Composable fun GameScreen( // ... ) { val gameUiState by gameViewModel.uiState.collectAsState() // ... }
GameLayout( currentScrambledWord = gameUiState.currentScrambledWord, modifier = Modifier .fillMaxWidth() .wrapContentHeight() .padding(mediumPadding) )
@Composable fun GameLayout( currentScrambledWord: String, modifier: Modifier = Modifier ) { }
@Composable fun GameLayout( // ... ) { Column( verticalArrangement = Arrangement.spacedBy(24.dp) ) { Text( text = currentScrambledWord, ... ) //... } }
Menampilkan kata tebakan
1. Di file GameScreen.kt, dalam composable GameLayout(), setel onValueChange ke onUserGuessChanged dan onKeyboardDone() ke tindakan keyboard onDone. Anda dapat memperbaiki error tersebut di langkah berikutnya.
OutlinedTextField( value = "", singleLine = true, modifier = Modifier.fillMaxWidth(), onValueChange = onUserGuessChanged, label = { Text(stringResource(R.string.enter_your_word)) }, isError = false, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( onDone = { onKeyboardDone() } ),
@Composable fun GameLayout( currentScrambledWord: String, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, modifier: Modifier = Modifier ) { }
GameLayout( currentScrambledWord = gameUiState.currentScrambledWord, onUserGuessChanged = { gameViewModel.updateUserGuess(it) }, onKeyboardDone = { }, )
fun updateUserGuess(guessedWord: String){ userGuess = guessedWord }
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue var userGuess by mutableStateOf("") private set
fun GameLayout( currentScrambledWord: String, userGuess: String, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, modifier: Modifier = Modifier ) { Column( verticalArrangement = Arrangement.spacedBy(24.dp) ) { //... OutlinedTextField( value = userGuess, //.. ) } }
GameLayout(
currentScrambledWord = gameUiState.currentScrambledWord,
userGuess = gameViewModel.userGuess,
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { },
//...
)
1. Di GameViewModel, tambahkan metode lain bernama checkUserGuess().
2. Pada fungsi checkUserGuess(), tambahkan blok if else untuk memverifikasi apakah tebakan pengguna sama dengan currentWord. Reset userGuess ke string yang kosong.
fun checkUserGuess() { if (userGuess.equals(currentWord, ignoreCase = true)) { } else { } // Reset user guess updateUserGuess("") }
fun checkUserGuess() { if (userGuess.equals(currentWord, ignoreCase = true)) { } else { // User's guess is wrong, show an error _uiState.update { currentState -> currentState.copy(isGuessedWordWrong = true) } } // Reset user guess updateUserGuess("") }
data class GameUiState( val currentScrambledWord: String = "", val isGuessedWordWrong: Boolean = false, )
Meneruskan callback peristiwa checkUserGuess() ke atas dari GameScreen ke ViewModel saat pengguna mengklik tombol Submit atau tombol selesai di keyboard.
1. Di file GameScreen.kt, di akhir fungsi composable GameScreen(), panggil gameViewModel.checkUserGuess() di dalam ekspresi lambda onClick dari tombol Submit.Button(
modifier = Modifier.fillMaxWidth(),
onClick = { gameViewModel.checkUserGuess() }
) {
Text(
text = stringResource(R.string.submit),
fontSize = 16.sp
)
}
GameLayout( currentScrambledWord = gameUiState.currentScrambledWord, userGuess = gameViewModel.userGuess, onUserGuessChanged = { gameViewModel.updateUserGuess(it) }, onKeyboardDone = { gameViewModel.checkUserGuess() }, )
fun GameLayout( currentScrambledWord: String, isGuessWrong: Boolean, userGuess: String, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, modifier: Modifier = Modifier ) { Column( // ,... OutlinedTextField( // ... isError = isGuessWrong, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( onDone = { onKeyboardDone() } ), ) } }
fun GameLayout( currentScrambledWord: String, isGuessWrong: Boolean, userGuess: String, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, modifier: Modifier = Modifier ) { Column( // ,... OutlinedTextField( // ... isError = isGuessWrong, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( onDone = { onKeyboardDone() } ), ) } }
OutlinedTextField( // ... label = { if (isGuessWrong) { Text(stringResource(R.string.wrong_guess)) } else { Text(stringResource(R.string.enter_your_word)) } }, // ... )
<string name="wrong_guess">Wrong Guess!</string>
Memperbarui skor dan jumlah kata
1. Di GameUiState, tambahkan variabel score dan lakukan inisialisasi ke nol.
data class GameUiState( val currentScrambledWord: String = "", val isGuessedWordWrong: Boolean = false, val score: Int = 0 )
import com.example.unscramble.data.SCORE_INCREASE fun checkUserGuess() { if (userGuess.equals(currentWord, ignoreCase = true)) { // User's guess is correct, increase the score val updatedScore = _uiState.value.score.plus(SCORE_INCREASE) } else { //... } }
private fun updateGameState(updatedScore: Int) { _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), score = updatedScore ) } }
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 { //... } }
data class GameUiState( val currentScrambledWord: String = "", val currentWordCount: Int = 1, val score: Int = 0, val isGuessedWordWrong: Boolean = false, )
private fun updateGameState(updatedScore: Int) { _uiState.update { currentState -> currentState.copy( //... currentWordCount = currentState.currentWordCount.inc(), ) } }
Skor kelulusan dan jumlah kata
1. Di file GameScreen.kt, pada fungsi composable GameLayout(), tambahkan jumlah kata sebagai argumen dan teruskan argumen format wordCount ke elemen teks.
fun GameLayout( onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, wordCount: Int, //... ) { //... Card( //... ) { Column( // ... ) { Text( //.. text = stringResource(R.string.word_count, wordCount), style = typography.titleMedium, color = colorScheme.onPrimary ) // ... }
GameLayout(
userGuess = gameViewModel.userGuess,
wordCount = gameUiState.currentWordCount,
//...
)
GameStatus(score = gameUiState.score, modifier = Modifier.padding(20.dp))
5. Masukkan kata tebakan, lalu klik Submit. Perhatikan skor dan jumlah kata yang diperbarui.
6. Klik Skip, dan perhatikan bahwa tidak ada yang terjadi.
7. Di file GameScreen.kt, pada fungsi composable GameScreen(), lakukan panggilan ke gameViewModel.skipWord() dalam ekspresi lambda onClick.
OutlinedButton(
onClick = { gameViewModel.skipWord() },
modifier = Modifier.fillMaxWidth()
) {
//...
}
9. Di dalam fungsi skipWord(), lakukan panggilan ke updateGameState(), dengan meneruskan skor dan mereset tebakan pengguna.
fun skipWord() { updateGameState(_uiState.value.score) // Reset user guess updateUserGuess("") }
Menangani putaran terakhir game
1. Di GameViewModel, tambahkan blok if-else dan pindahkan isi fungsi yang ada ke dalam blok else.
2. Tambahkan kondisi if untuk memastikan ukuran usedWords sama dengan MAX_NO_OF_WORDS.
import com.example.android.unscramble.data.MAX_NO_OF_WORDS private fun updateGameState(updatedScore: Int) { if (usedWords.size == MAX_NO_OF_WORDS){ //Last round in the game } else{ // Normal round in the game _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), currentWordCount = currentState.currentWordCount.inc(), score = updatedScore ) } } }
4. Perbarui score dan reset isGuessedWordWrong di dalam blok if. Kode berikut menunjukkan tampilan fungsi Anda:
import com.example.android.unscramble.data.MAX_NO_OF_WORDS private fun updateGameState(updatedScore: Int) { if (usedWords.size == MAX_NO_OF_WORDS){ //Last round in the game } else{ // Normal round in the game _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), currentWordCount = currentState.currentWordCount.inc(), score = updatedScore ) } } }
import com.example.android.unscramble.data.MAX_NO_OF_WORDS private fun updateGameState(updatedScore: Int) { if (usedWords.size == MAX_NO_OF_WORDS){ //Last round in the game } else{ // Normal round in the game _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), currentWordCount = currentState.currentWordCount.inc(), score = updatedScore ) } } }
Menampilkan dialog akhir game
1. Di file GameScreen.kt, di akhir fungsi composable GameScreen(), setelah blok Column, tambahkan kondisi if untuk memeriksa gameUiState.isGameOver.
2. Di blok if, tampilkan dialog pemberitahuan. Lakukan panggilan ke FinalScoreDialog() dengan meneruskan score dan gameViewModel.resetGame() untuk callback peristiwa onPlayAgain.
if (gameUiState.isGameOver) {
FinalScoreDialog(
score = gameUiState.score,
onPlayAgain = { gameViewModel.resetGame() }
)
}
fun resetGame() { usedWords.clear() _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle()) }
5. Mainkan game hingga selesai, dan amati dialog pemberitahuan dengan opsi untuk Exit dari game atau Play Again. Coba opsi yang ditampilkan di dialog pemberitahuan.
Komentar
Posting Komentar