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.model.*
import com.uno.shared.UnoGameScreen
import kotlinx.coroutines.delay
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -33,6 +34,23 @@ fun UnoApp() {
val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) }
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) {
StartScreen(onStartGame = { playerCount ->
@@ -44,29 +62,22 @@ fun UnoApp() {
state = gameState,
onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
},
onDrawCard = {
gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
},
onCallUno = { gameState = engine.callUno(gameState, 0) }
)
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
fun StartScreen(onStartGame: (Int) -> Unit) {
Column(
@@ -13,6 +13,7 @@ import androidx.compose.ui.window.application
import com.uno.game.GameEngine
import com.uno.game.model.*
import com.uno.shared.UnoGameScreen
import kotlinx.coroutines.delay
fun main() = application {
Window(
@@ -29,6 +30,24 @@ fun UnoDesktopApp() {
val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) }
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) {
DesktopStartScreen(onStartGame = { playerCount ->
@@ -40,11 +59,9 @@ fun UnoDesktopApp() {
state = gameState,
onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
},
onDrawCard = {
gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
},
onCallUno = { gameState = engine.callUno(gameState, 0) }
)
@@ -52,20 +69,15 @@ fun UnoDesktopApp() {
if (gameState.isFinished) {
DesktopGameOverScreen(
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
fun DesktopStartScreen(onStartGame: (Int) -> Unit) {
Column(
@@ -1,51 +1,65 @@
package com.uno.web
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
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.model.*
import com.uno.shared.UnoGameScreen
import kotlinx.browser.document
import kotlinx.coroutines.delay
import org.w3c.dom.HTMLElement
fun main() {
val root = document.getElementById("root") as HTMLElement
root.innerHTML = ""
val container = document.createElement("div")
container.id = "app"
root.appendChild(container)
composeWebRoot(container)
composeWebRoot()
}
@Composable
fun composeWebRoot(container: HTMLElement) {
fun composeWebRoot() {
val engine = remember { GameEngine() }
var gameState by remember { mutableStateOf(engine.startGame()) }
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) {
WebStartScreen(
onStartGame = { playerCount ->
gameState = engine.startGame(playerCount)
gameStarted = true
}
)
WebStartScreen(onStartGame = { playerCount ->
gameState = engine.startGame(playerCount)
gameStarted = true
})
} else {
UnoGameScreen(
state = gameState,
onPlayCard = { card, color ->
gameState = engine.playCard(gameState, 0, card, color)
gameState = runAiTurns(engine, gameState)
},
onDrawCard = {
gameState = engine.drawCard(gameState, 0)
gameState = runAiTurns(engine, gameState)
},
onCallUno = {
gameState = engine.callUno(gameState, 0)
}
onCallUno = { gameState = engine.callUno(gameState, 0) }
)
if (gameState.isFinished) {
@@ -53,60 +67,36 @@ fun composeWebRoot(container: HTMLElement) {
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
fun WebStartScreen(onStartGame: (Int) -> Unit) {
androidx.compose.foundation.layout.Column(
modifier = androidx.compose.foundation.layout.fillMaxSize().padding(32.dp),
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center
Column(
modifier = Modifier.fillMaxSize().padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
androidx.compose.material3.Text(
text = "UNO",
style = androidx.compose.material3.MaterialTheme.typography.displayLarge,
color = androidx.compose.material3.MaterialTheme.colorScheme.primary
)
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)")
}
Text("UNO", style = MaterialTheme.typography.displayLarge, color = MaterialTheme.colorScheme.primary)
Spacer(modifier = Modifier.height(8.dp))
Text("Web Edition", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
Spacer(modifier = Modifier.height(24.dp))
Button(onClick = { onStartGame(4) }) { Text("Spiel starten (4 Spieler)") }
}
}
@Composable
fun WebGameOverScreen(winner: Player?, onPlayAgain: () -> Unit) {
androidx.compose.material3.AlertDialog(
AlertDialog(
onDismissRequest = onPlayAgain,
title = { androidx.compose.material3.Text("Spiel vorbei!") },
text = {
androidx.compose.material3.Text(
text = if (winner != null) "${winner.name} hat gewonnen!"
else "Unentschieden!"
)
},
title = { Text("Spiel vorbei!") },
text = { Text(if (winner != null) "${winner.name} hat gewonnen!" else "Unentschieden!") },
confirmButton = {
androidx.compose.material3.Button(onClick = onPlayAgain) {
androidx.compose.material3.Text("Nochmal spielen")
}
Button(onClick = onPlayAgain) { Text("Nochmal spielen") }
}
)
}