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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ jobs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Build project and run local and device tests
- name: Build project
run: ./gradlew :app:assembleDebug
4 changes: 1 addition & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,8 @@ dependencies {
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")


debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.compose.ui:ui-tooling")

testImplementation("junit:junit:4.13.2")
}
4 changes: 2 additions & 2 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
Expand All @@ -18,4 +18,4 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
3 changes: 2 additions & 1 deletion app/src/main/java/com/example/unscramble/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.unscramble.ui.GameScreen
import com.example.unscramble.ui.GameViewModel
import com.example.unscramble.ui.theme.UnscrambleTheme

class MainActivity : ComponentActivity() {
Expand All @@ -35,7 +36,7 @@ class MainActivity : ComponentActivity() {
Surface(
modifier = Modifier.fillMaxSize(),
) {
GameScreen()
GameScreen(GameViewModel)
}
}
}
Expand Down
53 changes: 25 additions & 28 deletions app/src/main/java/com/example/unscramble/ui/GameScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ 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
Expand All @@ -54,15 +53,15 @@ 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()
fun GameScreen(
gameViewModel: GameViewModel
) {
val mediumPadding = dimensionResource(R.dimen.padding_medium)

val gameUiState = gameViewModel.uiState.collectAsState()
Column(
modifier = Modifier
.statusBarsPadding()
Expand All @@ -78,16 +77,16 @@ fun GameScreen(gameViewModel: GameViewModel = viewModel()) {
style = typography.titleLarge,
)
GameLayout(
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
wordCount = gameUiState.currentWordCount,
userGuess = gameViewModel.userGuess,
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { gameViewModel.checkUserGuess() },
currentScrambledWord = gameUiState.currentScrambledWord,
isGuessWrong = gameUiState.isGuessedWordWrong,
currentScrambledWord = gameUiState.value.currentScrambledWord,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(mediumPadding)
.padding(mediumPadding),
isGuessWrong = gameUiState.value.isGuessedWordWrong,
wordCount = gameUiState.value.currentWordCount,
)
Column(
modifier = Modifier
Expand Down Expand Up @@ -116,16 +115,15 @@ fun GameScreen(gameViewModel: GameViewModel = viewModel()) {
fontSize = 16.sp
)
}
if (gameUiState.value.isGameOver) {
FinalScoreDialog(
score = gameUiState.value.score,
onPlayAgain = { gameViewModel.resetGame() }
)
}
}

GameStatus(score = gameUiState.score, modifier = Modifier.padding(20.dp))

if (gameUiState.isGameOver) {
FinalScoreDialog(
score = gameUiState.score,
onPlayAgain = { gameViewModel.resetGame() }
)
}
GameStatus(score = gameUiState.value.score, modifier = Modifier.padding(20.dp))
}
}

Expand All @@ -139,22 +137,19 @@ fun GameStatus(score: Int, modifier: Modifier = Modifier) {
style = typography.headlineMedium,
modifier = Modifier.padding(8.dp)
)

}
}

@Composable
fun GameLayout(
currentScrambledWord: String,
wordCount: Int,
isGuessWrong: Boolean,
isGuessWrong : Boolean,
userGuess: String,
onUserGuessChanged: (String) -> Unit,
onKeyboardDone: () -> Unit,
modifier: Modifier = Modifier
) {
currentScrambledWord: String,
modifier: Modifier = Modifier) {
val mediumPadding = dimensionResource(R.dimen.padding_medium)

Card(
modifier = modifier,
elevation = CardDefaults.cardElevation(defaultElevation = 5.dp)
Expand All @@ -170,13 +165,15 @@ fun GameLayout(
.background(colorScheme.surfaceTint)
.padding(horizontal = 10.dp, vertical = 4.dp)
.align(alignment = Alignment.End),
text = stringResource(R.string.word_count, wordCount),
text = stringResource(R.string.word_count,wordCount ),
style = typography.titleMedium,
color = colorScheme.onPrimary
)
Text(
text = currentScrambledWord,
style = typography.displayMedium
style = typography.displayMedium,
fontSize = 45.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = stringResource(R.string.instructions),
Expand Down Expand Up @@ -206,7 +203,7 @@ fun GameLayout(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { onKeyboardDone() }
onDone = { onKeyboardDone }
)
)
}
Expand Down Expand Up @@ -254,6 +251,6 @@ private fun FinalScoreDialog(
@Composable
fun GameScreenPreview() {
UnscrambleTheme {
GameScreen()
GameScreen(GameViewModel)
}
}
22 changes: 2 additions & 20 deletions app/src/main/java/com/example/unscramble/ui/GameUiState.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 score: Int = 0,
val currentWordCount: Int = 1,
val isGameOver: Boolean = false
)
107 changes: 31 additions & 76 deletions app/src/main/java/com/example/unscramble/ui/GameViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.unscramble.ui

import androidx.compose.runtime.getValue
Expand All @@ -28,49 +12,45 @@ 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
object GameViewModel : ViewModel() {
var userGuess by mutableStateOf("")
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
private var usedWords: MutableSet<String> = mutableSetOf()

init {
resetGame()
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)
}
}

private fun shuffleCurrentWord(word: String): String {
val tempWord = word.toCharArray()
// Scramble the word
tempWord.shuffle()
while (String(tempWord) == word) {
tempWord.shuffle()
}
return String(tempWord)
}

/*
* 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){
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 {
Expand All @@ -82,23 +62,9 @@ class GameViewModel : ViewModel() {
// 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
//Last round in the game
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
Expand All @@ -119,24 +85,13 @@ class GameViewModel : ViewModel() {
}
}

private fun shuffleCurrentWord(word: String): String {
val tempWord = word.toCharArray()
// Scramble the word
tempWord.shuffle()
while (String(tempWord) == word) {
tempWord.shuffle()
}
return String(tempWord)
fun skipWord() {
updateGameState(_uiState.value.score)
// Reset user guess
updateUserGuess("")
}

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)
}
init {
resetGame()
}
}
}
Loading