fix: 修复机器人名预览与局内不一致bug,开局前可手动选择机器人名

This commit is contained in:
flykhan 2026-04-26 18:38:00 +08:00
parent 6ad256216e
commit d7e3be548a
4 changed files with 74 additions and 16 deletions

View File

@ -354,13 +354,14 @@ fun UnoApp() {
playerName = savedName, playerName = savedName,
modeDisplayName = mode.displayName, modeDisplayName = mode.displayName,
mode = mode, mode = mode,
onStartGame = { totalPlayers, name -> onStartGame = { totalPlayers, name, botNames ->
if (name.isNotEmpty()) { if (name.isNotEmpty()) {
savedName = name savedName = name
prefs.edit().putString("player_name", name).apply() prefs.edit().putString("player_name", name).apply()
} }
val encoded = java.net.URLEncoder.encode(botNames.joinToString(","), "UTF-8")
navController.navigate( navController.navigate(
Screen.LocalGame.createRoute(modeName, totalPlayers, name) Screen.LocalGame.createRoute(modeName, totalPlayers, name, encoded)
) )
}, },
onBack = { navController.popBackStack() } onBack = { navController.popBackStack() }
@ -372,18 +373,23 @@ fun UnoApp() {
arguments = listOf( arguments = listOf(
navArgument("modeName") { type = NavType.StringType }, navArgument("modeName") { type = NavType.StringType },
navArgument("totalPlayers") { type = NavType.IntType }, navArgument("totalPlayers") { type = NavType.IntType },
navArgument("humanPlayerName") { type = NavType.StringType } navArgument("humanPlayerName") { type = NavType.StringType },
navArgument("botNames") { type = NavType.StringType }
) )
) { backStackEntry -> ) { backStackEntry ->
val modeName = backStackEntry.arguments?.getString("modeName") ?: "NORMAL" val modeName = backStackEntry.arguments?.getString("modeName") ?: "NORMAL"
val mode = try { GameMode.valueOf(modeName) } catch (_: Exception) { GameMode.NORMAL } val mode = try { GameMode.valueOf(modeName) } catch (_: Exception) { GameMode.NORMAL }
val totalPlayers = backStackEntry.arguments?.getInt("totalPlayers") ?: 2 val totalPlayers = backStackEntry.arguments?.getInt("totalPlayers") ?: 2
val humanPlayerName = backStackEntry.arguments?.getString("humanPlayerName") ?: "玩家" val humanPlayerName = backStackEntry.arguments?.getString("humanPlayerName") ?: "玩家"
val botNames = java.net.URLDecoder.decode(
backStackEntry.arguments?.getString("botNames") ?: "", "UTF-8"
).split(",").filter { it.isNotEmpty() }
LocalGameScreen( LocalGameScreen(
mode = mode, mode = mode,
totalPlayers = totalPlayers, totalPlayers = totalPlayers,
humanPlayerName = humanPlayerName, humanPlayerName = humanPlayerName,
botNames = botNames,
onBackToMenu = { onBackToMenu = {
navController.navigate(Screen.MainMenu.route) { navController.navigate(Screen.MainMenu.route) {
popUpTo(0) { inclusive = true } popUpTo(0) { inclusive = true }

View File

@ -10,9 +10,9 @@ sealed class Screen(val route: String) {
data object LocalSetup : Screen("local_setup/{modeName}") { data object LocalSetup : Screen("local_setup/{modeName}") {
fun createRoute(mode: String) = "local_setup/$mode" fun createRoute(mode: String) = "local_setup/$mode"
} }
data object LocalGame : Screen("local_game/{modeName}/{totalPlayers}/{humanPlayerName}") { data object LocalGame : Screen("local_game/{modeName}/{totalPlayers}/{humanPlayerName}/{botNames}") {
fun createRoute(mode: String, totalPlayers: Int, humanPlayerName: String) = fun createRoute(mode: String, totalPlayers: Int, humanPlayerName: String, botNames: String) =
"local_game/$mode/$totalPlayers/$humanPlayerName" "local_game/$mode/$totalPlayers/$humanPlayerName/$botNames"
} }
data object Scoreboard : Screen("scoreboard") data object Scoreboard : Screen("scoreboard")
data object Rules : Screen("rules") data object Rules : Screen("rules")

View File

@ -109,6 +109,7 @@ fun LocalGameScreen(
totalPlayers: Int, totalPlayers: Int,
humanPlayerName: String, humanPlayerName: String,
mode: GameMode, mode: GameMode,
botNames: List<String> = emptyList(),
onBackToMenu: () -> Unit onBackToMenu: () -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -119,11 +120,11 @@ fun LocalGameScreen(
val gameStartTime = remember { System.currentTimeMillis() } val gameStartTime = remember { System.currentTimeMillis() }
val players = remember { val players = remember {
val botNames = pickUniqueBotNames(totalPlayers - 1) val names = if (botNames.isEmpty()) pickUniqueBotNames(totalPlayers - 1) else botNames
val list = mutableListOf<Player>() val list = mutableListOf<Player>()
list.add(Player(id = "human", name = humanPlayerName, isCurrentTurn = true)) list.add(Player(id = "human", name = humanPlayerName, isCurrentTurn = true))
for (i in 0 until totalPlayers - 1) { for (i in 0 until totalPlayers - 1) {
list.add(Player(id = "bot_${i + 2}", name = botNames[i], isCurrentTurn = false)) list.add(Player(id = "bot_${i + 2}", name = names.getOrElse(i) { "机器人${i + 1}" }, isCurrentTurn = false))
} }
list list
} }

View File

@ -29,7 +29,7 @@ fun LocalSetupScreen(
playerName: String, playerName: String,
modeDisplayName: String, modeDisplayName: String,
mode: com.unogame.game.GameMode, mode: com.unogame.game.GameMode,
onStartGame: (Int, String) -> Unit, onStartGame: (Int, String, List<String>) -> Unit,
onBack: () -> Unit onBack: () -> Unit
) { ) {
var totalPlayers by remember { mutableIntStateOf(2) } var totalPlayers by remember { mutableIntStateOf(2) }
@ -39,7 +39,7 @@ fun LocalSetupScreen(
val initMaxHandSize = remember { com.unogame.game.GameRules.loadMaxHandSize(context, mode) } val initMaxHandSize = remember { com.unogame.game.GameRules.loadMaxHandSize(context, mode) }
var maxHandSize by remember { mutableIntStateOf(initMaxHandSize) } var maxHandSize by remember { mutableIntStateOf(initMaxHandSize) }
val botNames = remember { pickUniqueBotNames(3) } var botNames by remember { mutableStateOf(pickUniqueBotNames(3).toMutableList()) }
Box( Box(
modifier = Modifier modifier = Modifier
@ -241,12 +241,63 @@ fun LocalSetupScreen(
// Bots // Bots
for (i in 1 until totalPlayers) { for (i in 1 until totalPlayers) {
val botIdx = i - 1
var botDropdownExpanded by remember { mutableStateOf(false) }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
PlayerSlot( Card(
name = botNames[i - 1], modifier = Modifier.fillMaxWidth(),
isHuman = false, shape = RoundedCornerShape(12.dp),
isYou = false colors = CardDefaults.cardColors(containerColor = DarkSurface)
) ) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.Gray),
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.SmartToy, null, tint = Color.White, modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
botNames.getOrElse(botIdx) { "机器人${botIdx + 1}" },
color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp
)
Text("AI机器人", color = Color.White.copy(alpha = 0.5f), fontSize = 12.sp)
}
Box {
IconButton(onClick = { botDropdownExpanded = true }) {
Icon(Icons.Default.ArrowDropDown, "改名", tint = GoldAccent)
}
DropdownMenu(
expanded = botDropdownExpanded,
onDismissRequest = { botDropdownExpanded = false }
) {
BOT_NAMES.forEach { preset ->
val alreadyUsed = (0 until botIdx).any { botNames.getOrNull(it) == preset } ||
(botIdx + 1 until botNames.size).any { botNames.getOrNull(it) == preset }
DropdownMenuItem(
text = { Text(preset, color = if (alreadyUsed) Color.Gray else Color.White) },
onClick = {
if (!alreadyUsed) {
val newList = botNames.toMutableList()
newList[botIdx] = preset
botNames = newList
}
botDropdownExpanded = false
},
enabled = !alreadyUsed
)
}
}
}
}
}
} }
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
@ -255,7 +306,7 @@ fun LocalSetupScreen(
onClick = { onClick = {
com.unogame.game.AIDifficulty.save(context, difficulty) com.unogame.game.AIDifficulty.save(context, difficulty)
com.unogame.game.GameRules.saveMaxHandSize(context, mode, maxHandSize) com.unogame.game.GameRules.saveMaxHandSize(context, mode, maxHandSize)
onStartGame(totalPlayers, name.ifBlank { "玩家" }) onStartGame(totalPlayers, name.ifBlank { "玩家" }, botNames.take(totalPlayers - 1))
}, },
modifier = Modifier.fillMaxWidth().height(56.dp), modifier = Modifier.fillMaxWidth().height(56.dp),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),