feat: visible AI turns with 800ms delay between each move

This commit is contained in:
2026-08-02 18:33:35 +02:00
parent 2b2816de3c
commit 2187bf2f47
3 changed files with 89 additions and 76 deletions
@@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
import com.uno.game.GameEngine import com.uno.game.GameEngine
import com.uno.game.model.* import com.uno.game.model.*
import com.uno.shared.UnoGameScreen import com.uno.shared.UnoGameScreen
import kotlinx.coroutines.delay
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -33,6 +34,23 @@ fun UnoApp() {
val engine = remember { GameEngine() } val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) } var gameState by remember { mutableStateOf(engine.startGame()) }
var gameStarted by remember { mutableStateOf(false) } var gameStarted by remember { mutableStateOf(false) }
var isAiThinking by remember { mutableStateOf(false) }
LaunchedEffect(gameState.currentPlayerIndex, gameState.phase) {
if (gameState.currentPlayerIndex != 0
&& gameState.phase == GamePhase.PLAYING
&& !gameState.isFinished
) {
isAiThinking = true
var currentState = gameState
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
delay(800)
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
gameState = currentState
}
isAiThinking = false
}
}
if (!gameStarted) { if (!gameStarted) {
StartScreen(onStartGame = { playerCount -> StartScreen(onStartGame = { playerCount ->
@@ -44,29 +62,22 @@ fun UnoApp() {
state = gameState, state = gameState,
onPlayCard = { card, color -> onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color) gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
}, },
onDrawCard = { onDrawCard = {
gameState = engine.drawCard(gameState, 0) gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
}, },
onCallUno = { gameState = engine.callUno(gameState, 0) } onCallUno = { gameState = engine.callUno(gameState, 0) }
) )
if (gameState.isFinished) { if (gameState.isFinished) {
GameOverScreen(winner = gameState.winner, onPlayAgain = { gameState = engine.startGame() }) GameOverScreen(winner = gameState.winner, onPlayAgain = {
gameState = engine.startGame()
gameStarted = false
})
} }
} }
} }
private fun runAiTurns(engine: GameEngine, state: GameState): GameState {
var currentState = state
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
}
return currentState
}
@Composable @Composable
fun StartScreen(onStartGame: (Int) -> Unit) { fun StartScreen(onStartGame: (Int) -> Unit) {
Column( Column(
@@ -13,6 +13,7 @@ import androidx.compose.ui.window.application
import com.uno.game.GameEngine import com.uno.game.GameEngine
import com.uno.game.model.* import com.uno.game.model.*
import com.uno.shared.UnoGameScreen import com.uno.shared.UnoGameScreen
import kotlinx.coroutines.delay
fun main() = application { fun main() = application {
Window( Window(
@@ -29,6 +30,24 @@ fun UnoDesktopApp() {
val engine = remember { GameEngine() } val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) } var gameState by remember { mutableStateOf(engine.startGame()) }
var gameStarted by remember { mutableStateOf(false) } var gameStarted by remember { mutableStateOf(false) }
var isAiThinking by remember { mutableStateOf(false) }
// KI-Züge mit Verzögerung
LaunchedEffect(gameState.currentPlayerIndex, gameState.phase) {
if (gameState.currentPlayerIndex != 0
&& gameState.phase == GamePhase.PLAYING
&& !gameState.isFinished
) {
isAiThinking = true
var currentState = gameState
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
delay(800)
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
gameState = currentState
}
isAiThinking = false
}
}
if (!gameStarted) { if (!gameStarted) {
DesktopStartScreen(onStartGame = { playerCount -> DesktopStartScreen(onStartGame = { playerCount ->
@@ -40,11 +59,9 @@ fun UnoDesktopApp() {
state = gameState, state = gameState,
onPlayCard = { card, color -> onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color) gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
}, },
onDrawCard = { onDrawCard = {
gameState = engine.drawCard(gameState, 0) gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
}, },
onCallUno = { gameState = engine.callUno(gameState, 0) } onCallUno = { gameState = engine.callUno(gameState, 0) }
) )
@@ -52,20 +69,15 @@ fun UnoDesktopApp() {
if (gameState.isFinished) { if (gameState.isFinished) {
DesktopGameOverScreen( DesktopGameOverScreen(
winner = gameState.winner, winner = gameState.winner,
onPlayAgain = { gameState = engine.startGame() } onPlayAgain = {
gameState = engine.startGame()
gameStarted = false
}
) )
} }
} }
} }
private fun runAiTurns(engine: GameEngine, state: GameState): GameState {
var currentState = state
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
}
return currentState
}
@Composable @Composable
fun DesktopStartScreen(onStartGame: (Int) -> Unit) { fun DesktopStartScreen(onStartGame: (Int) -> Unit) {
Column( Column(
@@ -1,51 +1,65 @@
package com.uno.web package com.uno.web
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.uno.game.GameEngine import com.uno.game.GameEngine
import com.uno.game.model.* import com.uno.game.model.*
import com.uno.shared.UnoGameScreen import com.uno.shared.UnoGameScreen
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.delay
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
fun main() { fun main() {
val root = document.getElementById("root") as HTMLElement val root = document.getElementById("root") as HTMLElement
root.innerHTML = "" root.innerHTML = ""
val container = document.createElement("div") val container = document.createElement("div")
container.id = "app" container.id = "app"
root.appendChild(container) root.appendChild(container)
composeWebRoot()
composeWebRoot(container)
} }
@Composable @Composable
fun composeWebRoot(container: HTMLElement) { fun composeWebRoot() {
val engine = remember { GameEngine() } val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) } var gameState by remember { mutableStateOf(engine.startGame()) }
var gameStarted by remember { mutableStateOf(false) } var gameStarted by remember { mutableStateOf(false) }
var isAiThinking by remember { mutableStateOf(false) }
LaunchedEffect(gameState.currentPlayerIndex, gameState.phase) {
if (gameState.currentPlayerIndex != 0
&& gameState.phase == GamePhase.PLAYING
&& !gameState.isFinished
) {
isAiThinking = true
var currentState = gameState
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
delay(800)
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
gameState = currentState
}
isAiThinking = false
}
}
if (!gameStarted) { if (!gameStarted) {
WebStartScreen( WebStartScreen(onStartGame = { playerCount ->
onStartGame = { playerCount ->
gameState = engine.startGame(playerCount) gameState = engine.startGame(playerCount)
gameStarted = true gameStarted = true
} })
)
} else { } else {
UnoGameScreen( UnoGameScreen(
state = gameState, state = gameState,
onPlayCard = { card, color -> onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color) gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
}, },
onDrawCard = { onDrawCard = {
gameState = engine.drawCard(gameState, 0) gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
}, },
onCallUno = { onCallUno = { gameState = engine.callUno(gameState, 0) }
gameState = engine.callUno(gameState, 0)
}
) )
if (gameState.isFinished) { if (gameState.isFinished) {
@@ -53,60 +67,36 @@ fun composeWebRoot(container: HTMLElement) {
winner = gameState.winner, winner = gameState.winner,
onPlayAgain = { onPlayAgain = {
gameState = engine.startGame() gameState = engine.startGame()
gameStarted = false
} }
) )
} }
} }
} }
private fun runAiTurns(engine: GameEngine, state: GameState): GameState {
var currentState = state
while (currentState.currentPlayerIndex != 0 && !currentState.isFinished) {
currentState = engine.aiPlay(currentState, currentState.currentPlayerIndex)
}
return currentState
}
@Composable @Composable
fun WebStartScreen(onStartGame: (Int) -> Unit) { fun WebStartScreen(onStartGame: (Int) -> Unit) {
androidx.compose.foundation.layout.Column( Column(
modifier = androidx.compose.foundation.layout.fillMaxSize().padding(32.dp), modifier = Modifier.fillMaxSize().padding(32.dp),
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
androidx.compose.material3.Text( Text("UNO", style = MaterialTheme.typography.displayLarge, color = MaterialTheme.colorScheme.primary)
text = "UNO", Spacer(modifier = Modifier.height(8.dp))
style = androidx.compose.material3.MaterialTheme.typography.displayLarge, Text("Web Edition", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
color = androidx.compose.material3.MaterialTheme.colorScheme.primary Spacer(modifier = Modifier.height(24.dp))
) Button(onClick = { onStartGame(4) }) { Text("Spiel starten (4 Spieler)") }
androidx.compose.foundation.layout.Spacer(modifier = Modifier)
androidx.compose.material3.Text(
text = "Web Edition",
style = androidx.compose.material3.MaterialTheme.typography.titleMedium,
color = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant
)
androidx.compose.foundation.layout.Spacer(modifier = Modifier)
androidx.compose.material3.Button(onClick = { onStartGame(4) }) {
androidx.compose.material3.Text("Spiel starten (4 Spieler)")
}
} }
} }
@Composable @Composable
fun WebGameOverScreen(winner: Player?, onPlayAgain: () -> Unit) { fun WebGameOverScreen(winner: Player?, onPlayAgain: () -> Unit) {
androidx.compose.material3.AlertDialog( AlertDialog(
onDismissRequest = onPlayAgain, onDismissRequest = onPlayAgain,
title = { androidx.compose.material3.Text("Spiel vorbei!") }, title = { Text("Spiel vorbei!") },
text = { text = { Text(if (winner != null) "${winner.name} hat gewonnen!" else "Unentschieden!") },
androidx.compose.material3.Text(
text = if (winner != null) "${winner.name} hat gewonnen!"
else "Unentschieden!"
)
},
confirmButton = { confirmButton = {
androidx.compose.material3.Button(onClick = onPlayAgain) { Button(onClick = onPlayAgain) { Text("Nochmal spielen") }
androidx.compose.material3.Text("Nochmal spielen")
}
} }
) )
} }