fix: 出牌记录改为米白背景+黑色文字+彩色底色、万能牌→颜色整体着色、自动换行

This commit is contained in:
flykhan 2026-04-26 20:41:42 +08:00
parent 347e01b5ee
commit 13a1e9432a
2 changed files with 116 additions and 22 deletions

View File

@ -218,12 +218,12 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
} }
CardType.WILD -> { CardType.WILD -> {
wildColor = chosenColor ?: CardColor.RED wildColor = chosenColor ?: CardColor.RED
message += ",选择 ${wildColor!!.displayName}" message += " ${wildColor!!.displayName}"
} }
CardType.WILD_DRAW_FOUR -> { CardType.WILD_DRAW_FOUR -> {
wildColor = chosenColor ?: CardColor.RED wildColor = chosenColor ?: CardColor.RED
pendingDraw = if (isStacking) pendingDraw + 4 else 4 pendingDraw = if (isStacking) pendingDraw + 4 else 4
message += ",下家摸${pendingDraw}张牌,选${wildColor!!.displayName}" message += "${wildColor!!.displayName},下家摸${pendingDraw}张牌"
} }
// Flip mode dark side effects // Flip mode dark side effects
CardType.SKIP_ALL -> { CardType.SKIP_ALL -> {
@ -238,7 +238,7 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
CardType.WILD_DRAW_TWO -> { CardType.WILD_DRAW_TWO -> {
wildColor = chosenColor ?: CardColor.RED wildColor = chosenColor ?: CardColor.RED
pendingDraw = 2 pendingDraw = 2
message += "下家摸2张${wildColor!!.displayName}" message += "${wildColor!!.displayName}下家摸2张"
} }
CardType.FLIP -> { CardType.FLIP -> {
flipped = !flipped flipped = !flipped
@ -260,7 +260,7 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
pendingDraw = 4 pendingDraw = 4
val tempState = state.copy(direction = direction) val tempState = state.copy(direction = direction)
nextIndex = tempState.nextPlayerIndex() nextIndex = tempState.nextPlayerIndex()
message += "+4反转${wildColor!!.displayName}" message += "${wildColor!!.displayName}+4反转"
} }
CardType.DISCARD_COLOR -> { CardType.DISCARD_COLOR -> {
val discardColor = card.activeCard(flipped).color val discardColor = card.activeCard(flipped).color
@ -274,7 +274,7 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
CardType.WILD_DRAW_COLOR -> { CardType.WILD_DRAW_COLOR -> {
wildColor = chosenColor ?: CardColor.RED wildColor = chosenColor ?: CardColor.RED
nextIndex = advanceIndex(state, nextIndex) nextIndex = advanceIndex(state, nextIndex)
message += ",跳过下家并选${wildColor!!.displayName}" message += "${wildColor!!.displayName},跳过下家"
} }
CardType.NUMBER -> { CardType.NUMBER -> {
// 7-0 rules // 7-0 rules

View File

@ -14,7 +14,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.google.gson.Gson import com.google.gson.Gson
@ -26,6 +29,7 @@ import com.unogame.game.GameRules
import com.unogame.game.SimpleAI import com.unogame.game.SimpleAI
import com.unogame.model.* import com.unogame.model.*
import com.unogame.ui.components.ColorPickerDialog import com.unogame.ui.components.ColorPickerDialog
import com.unogame.ui.components.getBotAvatar
import com.unogame.ui.theme.* import com.unogame.ui.theme.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -149,6 +153,8 @@ fun LocalGameScreen(
var pendingDrawStateForWild by remember { mutableStateOf<GameState?>(null) } var pendingDrawStateForWild by remember { mutableStateOf<GameState?>(null) }
var pendingDrawCardIdxForWild by remember { mutableIntStateOf(-1) } var pendingDrawCardIdxForWild by remember { mutableIntStateOf(-1) }
var gameLog by remember { mutableStateOf(listOf<String>()) } var gameLog by remember { mutableStateOf(listOf<String>()) }
// 记录每条日志对应的玩家名,用于行着色
var gameLogPlayers by remember { mutableStateOf(listOf<String>()) }
var showLogDialog by remember { mutableStateOf(false) } var showLogDialog by remember { mutableStateOf(false) }
var showSwapTargetPicker by remember { mutableStateOf(false) } var showSwapTargetPicker by remember { mutableStateOf(false) }
var pendingSwapState by remember { mutableStateOf<GameState?>(null) } var pendingSwapState by remember { mutableStateOf<GameState?>(null) }
@ -162,7 +168,10 @@ fun LocalGameScreen(
myCards = state.players.find { it.id == myPlayerId }?.cards ?: myCards myCards = state.players.find { it.id == myPlayerId }?.cards ?: myCards
errorMessage = "" errorMessage = ""
if (state.message.isNotEmpty()) { if (state.message.isNotEmpty()) {
// 倒序插入,最新在上
gameLog = listOf(state.message) + gameLog gameLog = listOf(state.message) + gameLog
// 记录该消息对应的操作玩家(消息格式: "玩家名 出了..." 或 "玩家名 摸了..."
gameLogPlayers = listOf(state.currentPlayer.name) + gameLogPlayers
} }
if (state.isGameOver) { if (state.isGameOver) {
isGameOver = true isGameOver = true
@ -329,42 +338,127 @@ fun LocalGameScreen(
) )
} }
// Game log dialog // 出牌记录弹窗
if (showLogDialog) { if (showLogDialog) {
// 卡牌颜色名映射
val cardColorMap = mapOf(
"" to UnoRed, "" to UnoBlue,
"" to Color(0xFFFDD835), "绿" to UnoGreen,
"" to Color(0xFFE91E63), "" to Color(0xFF9C27B0),
"" to Color(0xFF009688), "" to Color(0xFFFF9800),
"万能" to Color(0xFF616161), "" to Color(0xFF424242)
)
fun parsePlayerName(msg: String): String = msg.substringBefore(" 出了").substringBefore(" 摸了").substringBefore(" 选择").substringBefore(" 无法")
fun parseColorText(msg: String): String {
val after = msg.substringAfter(" 出了 ", "")
if (after.isEmpty()) return ""
val colorNames = listOf("万能", "", "", "", "绿", "", "", "", "", "")
return colorNames.firstOrNull { after.startsWith(it) } ?: ""
}
fun parseArrowColor(msg: String): Pair<String, Color>? {
val arrowIdx = msg.indexOf("")
if (arrowIdx < 0) return null
val afterArrow = msg.substring(arrowIdx + 3).trimStart()
val cn = listOf("", "", "", "绿", "", "", "", "", "", "万能")
for (name in cn) {
if (afterArrow.startsWith(name)) {
return name to (cardColorMap[name] ?: Color.White)
}
}
return null
}
val creamBg = Color(0xFFFFF8E1) // 护眼米白
val dimText = Color(0xFF555555) // 浅色底上的暗灰文字
// 关闭
AlertDialog( AlertDialog(
onDismissRequest = { showLogDialog = false }, onDismissRequest = { showLogDialog = false },
title = { Text("出牌记录", color = GoldAccent, fontWeight = FontWeight.Bold) }, title = { Text("出牌记录", color = Color(0xFF333333), fontWeight = FontWeight.Bold) },
text = { text = {
Column( Column(
modifier = Modifier modifier = Modifier
.heightIn(max = 400.dp) .heightIn(min = 300.dp, max = 420.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
if (gameLog.isEmpty()) { if (gameLog.isEmpty()) {
Text("暂无记录", color = Color.White.copy(alpha = 0.5f)) Text("暂无记录", color = dimText, fontSize = 13.sp)
} }
gameLog.forEachIndexed { index, msg -> gameLog.forEachIndexed { index, msg ->
Row(modifier = Modifier.padding(vertical = 2.dp)) { val playerName = gameLogPlayers.getOrElse(index) { parsePlayerName(msg) }
Text( val avatar = getBotAvatar(playerName)
"${gameLog.size - index}. ", val colorText = parseColorText(msg)
color = Color.White.copy(alpha = 0.3f), val cardColor = cardColorMap[colorText]
fontSize = 12.sp val isPlay = " 出了 " in msg
) val arrowColor = parseArrowColor(msg)
Text( val afterPlayer = msg.substringAfter(playerName)
msg,
color = Color.White.copy(alpha = 0.8f), // 用 AnnotatedString 构建整行,支持自动换行
fontSize = 12.sp val annotated = buildAnnotatedString {
) // 序号
withStyle(SpanStyle(color = Color(0xFF999999), fontSize = 13.sp)) {
append("${gameLog.size - index}. ")
}
// 玩家名
withStyle(SpanStyle(color = avatar.color, fontWeight = FontWeight.Bold, fontSize = 13.sp)) {
append(playerName)
}
if (isPlay && (colorText.isNotEmpty() || arrowColor != null)) {
if (arrowColor != null) {
val prefix = afterPlayer.substringBefore(" 万能")
val cardBlock = afterPlayer.substringAfter(" 出了 ").substringBefore("")
val remain = afterPlayer.substringAfter(prefix + " 出了 " + cardBlock)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix)
append(" 出了 ")
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = arrowColor.second.copy(alpha = 0.9f))) {
append(cardBlock)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
} else {
val prefix = afterPlayer.substringBefore(colorText)
val cardId = colorText + afterPlayer.substringAfter(colorText).substringBefore("")
val remain = afterPlayer.substringAfter(prefix + cardId)
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(prefix)
}
withStyle(SpanStyle(color = Color.Black, fontWeight = FontWeight.Black,
fontSize = 13.sp, background = (cardColor ?: Color.Gray).copy(alpha = 0.9f))) {
append(cardId)
}
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = Color(0xFF777777), fontSize = 12.sp)) {
append(remain)
}
}
}
} else {
withStyle(SpanStyle(color = dimText, fontSize = 13.sp)) {
append(afterPlayer)
}
}
}
Text(annotated, modifier = Modifier.padding(vertical = 5.dp))
// 条目间加细分割线
if (index < gameLog.size - 1) {
Spacer(modifier = Modifier.height(1.dp))
} }
} }
} }
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { showLogDialog = false }) { TextButton(onClick = { showLogDialog = false }) {
Text("关闭", color = GoldAccent) Text("关闭", color = Color(0xFF333333))
} }
}, },
containerColor = DarkSurface containerColor = creamBg
) )
} }