fix: 7-0自由选择交换对象、解除死锁、出牌记录优化、万能牌摸牌选色
This commit is contained in:
parent
8ac938cb2b
commit
548fbef889
@ -432,6 +432,7 @@ fun UnoApp() {
|
|||||||
myPlayerId = myPlayerId,
|
myPlayerId = myPlayerId,
|
||||||
isMyTurn = state.currentPlayer.id == myPlayerId,
|
isMyTurn = state.currentPlayer.id == myPlayerId,
|
||||||
errorMessage = errorMessage,
|
errorMessage = errorMessage,
|
||||||
|
isSevenZeroMode = false,
|
||||||
onPlayCard = { index ->
|
onPlayCard = { index ->
|
||||||
errorMessage = ""
|
errorMessage = ""
|
||||||
val colorToSend = if (index >= 0 && index < myCards.size && myCards[index].type.isWild) {
|
val colorToSend = if (index >= 0 && index < myCards.size && myCards[index].type.isWild) {
|
||||||
|
|||||||
@ -124,7 +124,8 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
sealed class PlayResult {
|
sealed class PlayResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val state: GameState,
|
val state: GameState,
|
||||||
val drawnCardPlayableIndex: Int = -1
|
val drawnCardPlayableIndex: Int = -1,
|
||||||
|
val needsSwapTarget: Boolean = false
|
||||||
) : PlayResult()
|
) : PlayResult()
|
||||||
data class Error(val message: String) : PlayResult()
|
data class Error(val message: String) : PlayResult()
|
||||||
}
|
}
|
||||||
@ -133,7 +134,8 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
state: GameState,
|
state: GameState,
|
||||||
playerId: String,
|
playerId: String,
|
||||||
cardIndex: Int,
|
cardIndex: Int,
|
||||||
chosenColor: CardColor? = null
|
chosenColor: CardColor? = null,
|
||||||
|
swapTargetId: String? = null
|
||||||
): PlayResult {
|
): PlayResult {
|
||||||
val playerIndex = state.players.indexOfFirst { it.id == playerId }
|
val playerIndex = state.players.indexOfFirst { it.id == playerId }
|
||||||
if (playerIndex != state.currentPlayerIndex)
|
if (playerIndex != state.currentPlayerIndex)
|
||||||
@ -162,7 +164,7 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
!card.matches(topCard, state.currentWildColor, state.flipped))
|
!card.matches(topCard, state.currentWildColor, state.flipped))
|
||||||
return PlayResult.Error("不能出这张牌")
|
return PlayResult.Error("不能出这张牌")
|
||||||
|
|
||||||
return executeCardPlay(state, player, playerIndex, cardIndex, card, chosenColor, canStack)
|
return executeCardPlay(state, player, playerIndex, cardIndex, card, chosenColor, canStack, swapTargetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun executeCardPlay(
|
private fun executeCardPlay(
|
||||||
@ -172,7 +174,8 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
cardIndex: Int,
|
cardIndex: Int,
|
||||||
card: Card,
|
card: Card,
|
||||||
chosenColor: CardColor?,
|
chosenColor: CardColor?,
|
||||||
isStacking: Boolean
|
isStacking: Boolean,
|
||||||
|
swapTargetId: String? = null
|
||||||
): PlayResult {
|
): PlayResult {
|
||||||
val newDiscard = discardPile.toMutableList()
|
val newDiscard = discardPile.toMutableList()
|
||||||
newDiscard.add(card)
|
newDiscard.add(card)
|
||||||
@ -187,7 +190,8 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
var nextIndex = state.nextPlayerIndex()
|
var nextIndex = state.nextPlayerIndex()
|
||||||
var direction = state.direction
|
var direction = state.direction
|
||||||
var flipped = state.flipped
|
var flipped = state.flipped
|
||||||
var message = "${player.name} 出了 ${card.displayText}"
|
val cardColorText = if (card.color == CardColor.WILD) "万能" else card.color.displayName
|
||||||
|
var message = "${player.name} 出了 ${cardColorText} ${card.displayText}"
|
||||||
|
|
||||||
val activeCard = card.activeCard(flipped)
|
val activeCard = card.activeCard(flipped)
|
||||||
|
|
||||||
@ -303,13 +307,38 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
pendingDraw = 0
|
pendingDraw = 0
|
||||||
sevenZeroDone = true
|
sevenZeroDone = true
|
||||||
} else if (pendingDraw == -2) {
|
} else if (pendingDraw == -2) {
|
||||||
// 7: swap hand with the next player
|
// 7: swap hand with player of choice
|
||||||
val nextIdx = state.nextPlayerIndex()
|
val targetIdx = if (swapTargetId != null) {
|
||||||
val nextCards = state.players[nextIdx].cards
|
state.players.indexOfFirst { it.id == swapTargetId }
|
||||||
|
} else {
|
||||||
|
state.nextPlayerIndex()
|
||||||
|
}
|
||||||
|
// If target not specified and more than 2 players, need UI to choose
|
||||||
|
if (swapTargetId == null && state.players.size > 2) {
|
||||||
|
// Return state with 7 removed but no swap yet, flag for target selection
|
||||||
|
val partialPlayers = state.players.mapIndexed { i, p ->
|
||||||
|
if (i == playerIndex) p.copy(cards = newCards, cardCount = newCards.size)
|
||||||
|
else p.copy(isCurrentTurn = false)
|
||||||
|
}
|
||||||
|
return PlayResult.Success(
|
||||||
|
state.copy(
|
||||||
|
players = partialPlayers,
|
||||||
|
currentPlayerIndex = playerIndex,
|
||||||
|
discardPile = discardPile.toList(),
|
||||||
|
drawPileCount = drawPile.size,
|
||||||
|
pendingDrawCount = 0,
|
||||||
|
flipped = flipped,
|
||||||
|
turnNumber = state.turnNumber + 1,
|
||||||
|
message = message + ",选择交换对象"
|
||||||
|
),
|
||||||
|
needsSwapTarget = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val nextCards = state.players[targetIdx].cards
|
||||||
modifiedPlayers = state.players.mapIndexed { i, p ->
|
modifiedPlayers = state.players.mapIndexed { i, p ->
|
||||||
when (i) {
|
when (i) {
|
||||||
playerIndex -> p.copy(cards = nextCards, cardCount = nextCards.size)
|
playerIndex -> p.copy(cards = nextCards, cardCount = nextCards.size)
|
||||||
nextIdx -> p.copy(cards = newCards, cardCount = newCards.size)
|
targetIdx -> p.copy(cards = newCards, cardCount = newCards.size)
|
||||||
else -> p
|
else -> p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,8 +453,28 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
if (drawPile.isNotEmpty()) drawPile.removeAt(0) else null
|
if (drawPile.isNotEmpty()) drawPile.removeAt(0) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawnCards.isEmpty())
|
if (drawnCards.isEmpty()) {
|
||||||
return PlayResult.Error("牌堆已空,无法摸牌")
|
// Can't draw - just pass turn
|
||||||
|
val nextIndex = state.nextPlayerIndex()
|
||||||
|
val updatedPlayers = state.players.mapIndexed { i, p ->
|
||||||
|
when (i) {
|
||||||
|
playerIndex -> p.copy(isCurrentTurn = false)
|
||||||
|
else -> if (i == nextIndex) p.copy(isCurrentTurn = true) else p.copy(isCurrentTurn = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PlayResult.Success(
|
||||||
|
state.copy(
|
||||||
|
players = updatedPlayers,
|
||||||
|
currentPlayerIndex = nextIndex,
|
||||||
|
discardPile = discardPile.toList(),
|
||||||
|
drawPileCount = 0,
|
||||||
|
pendingDrawCount = if (state.pendingDrawCount > 0) 0 else state.pendingDrawCount,
|
||||||
|
flipped = state.flipped,
|
||||||
|
turnNumber = state.turnNumber + 1,
|
||||||
|
message = "${player.name} 无法摸牌,跳过回合"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val newCards = player.cards.toMutableList()
|
val newCards = player.cards.toMutableList()
|
||||||
newCards.addAll(drawnCards)
|
newCards.addAll(drawnCards)
|
||||||
@ -489,6 +538,69 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun finishSwap(state: GameState, playerId: String, swapTargetId: String): PlayResult {
|
||||||
|
val playerIndex = state.players.indexOfFirst { it.id == playerId }
|
||||||
|
val targetIdx = state.players.indexOfFirst { it.id == swapTargetId }
|
||||||
|
if (playerIndex < 0 || targetIdx < 0) return PlayResult.Error("玩家不存在")
|
||||||
|
if (playerIndex == targetIdx) return PlayResult.Error("不能和自己交换")
|
||||||
|
|
||||||
|
val currentPlayer = state.players[playerIndex]
|
||||||
|
val targetPlayer = state.players[targetIdx]
|
||||||
|
|
||||||
|
val updatedPlayers = state.players.mapIndexed { i, p ->
|
||||||
|
when (i) {
|
||||||
|
playerIndex -> p.copy(
|
||||||
|
cards = targetPlayer.cards,
|
||||||
|
cardCount = targetPlayer.cards.size,
|
||||||
|
isCurrentTurn = false
|
||||||
|
)
|
||||||
|
targetIdx -> p.copy(
|
||||||
|
cards = currentPlayer.cards,
|
||||||
|
cardCount = currentPlayer.cards.size
|
||||||
|
)
|
||||||
|
else -> p.copy(isCurrentTurn = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextIndex = state.nextPlayerIndex()
|
||||||
|
val updatedWithTurn = updatedPlayers.mapIndexed { i, p ->
|
||||||
|
if (i == nextIndex) p.copy(isCurrentTurn = true) else p
|
||||||
|
}
|
||||||
|
|
||||||
|
val isWinner = currentPlayer.cards.isEmpty()
|
||||||
|
if (isWinner) {
|
||||||
|
return PlayResult.Success(
|
||||||
|
state.copy(
|
||||||
|
players = updatedWithTurn,
|
||||||
|
isGameOver = true,
|
||||||
|
winner = currentPlayer.copy(cards = emptyList(), cardCount = 0),
|
||||||
|
discardPile = discardPile.toList(),
|
||||||
|
drawPileCount = drawPile.size,
|
||||||
|
turnNumber = state.turnNumber + 1,
|
||||||
|
message = "${currentPlayer.name} 与 ${targetPlayer.name} 交换手牌,${currentPlayer.name} 赢了!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val calledUno = currentPlayer.cards.size == 1
|
||||||
|
val finalPlayers = updatedWithTurn.mapIndexed { i, p ->
|
||||||
|
if (i == playerIndex) p.copy(calledUno = calledUno) else p
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayResult.Success(
|
||||||
|
state.copy(
|
||||||
|
players = finalPlayers,
|
||||||
|
currentPlayerIndex = nextIndex,
|
||||||
|
discardPile = discardPile.toList(),
|
||||||
|
drawPileCount = drawPile.size,
|
||||||
|
pendingDrawCount = 0,
|
||||||
|
flipped = state.flipped,
|
||||||
|
turnNumber = state.turnNumber + 1,
|
||||||
|
message = "${currentPlayer.name} 与 ${targetPlayer.name} 交换手牌"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun skipAfterDraw(state: GameState): PlayResult {
|
fun skipAfterDraw(state: GameState): PlayResult {
|
||||||
val nextIndex = state.nextPlayerIndex()
|
val nextIndex = state.nextPlayerIndex()
|
||||||
val updatedPlayers = state.players.mapIndexed { i, p ->
|
val updatedPlayers = state.players.mapIndexed { i, p ->
|
||||||
@ -521,7 +633,13 @@ class GameEngine(private val rules: GameRules = GameRules.forMode(GameMode.NORMA
|
|||||||
|
|
||||||
private fun reshuffleDiscard() {
|
private fun reshuffleDiscard() {
|
||||||
if (discardPile.size <= 1 && drawPile.isNotEmpty()) return
|
if (discardPile.size <= 1 && drawPile.isNotEmpty()) return
|
||||||
|
if (discardPile.isEmpty()) return
|
||||||
val topCard = discardPile.removeAt(discardPile.size - 1)
|
val topCard = discardPile.removeAt(discardPile.size - 1)
|
||||||
|
if (discardPile.isEmpty()) {
|
||||||
|
// Only the top card exists - put it back, can't reshuffle
|
||||||
|
discardPile.add(topCard)
|
||||||
|
return
|
||||||
|
}
|
||||||
drawPile.addAll(discardPile)
|
drawPile.addAll(discardPile)
|
||||||
shuffle(drawPile)
|
shuffle(drawPile)
|
||||||
discardPile.clear()
|
discardPile.clear()
|
||||||
|
|||||||
@ -30,17 +30,26 @@ object SimpleAI {
|
|||||||
data class AIMove(
|
data class AIMove(
|
||||||
val type: MoveType,
|
val type: MoveType,
|
||||||
val cardIndex: Int = -1,
|
val cardIndex: Int = -1,
|
||||||
val chosenColor: CardColor? = null
|
val chosenColor: CardColor? = null,
|
||||||
|
val swapTargetId: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class MoveType { PLAY, DRAW }
|
enum class MoveType { PLAY, DRAW }
|
||||||
|
|
||||||
|
fun pickSwapTarget(players: List<Player>, selfId: String): String? {
|
||||||
|
val others = players.filter { it.id != selfId }
|
||||||
|
if (others.isEmpty()) return null
|
||||||
|
return others.random(Random).id
|
||||||
|
}
|
||||||
|
|
||||||
fun decideMove(
|
fun decideMove(
|
||||||
player: Player,
|
player: Player,
|
||||||
topCard: Card?,
|
topCard: Card?,
|
||||||
wildColor: CardColor?,
|
wildColor: CardColor?,
|
||||||
flipped: Boolean = false,
|
flipped: Boolean = false,
|
||||||
difficulty: AIDifficulty = AIDifficulty.NORMAL
|
difficulty: AIDifficulty = AIDifficulty.NORMAL,
|
||||||
|
otherPlayers: List<Player> = emptyList(),
|
||||||
|
isSevenZero: Boolean = false
|
||||||
): AIMove {
|
): AIMove {
|
||||||
val hand = player.cards
|
val hand = player.cards
|
||||||
if (hand.isEmpty()) return AIMove(MoveType.DRAW)
|
if (hand.isEmpty()) return AIMove(MoveType.DRAW)
|
||||||
@ -62,11 +71,15 @@ object SimpleAI {
|
|||||||
// Easy: play random card
|
// Easy: play random card
|
||||||
if (difficulty == AIDifficulty.EASY) {
|
if (difficulty == AIDifficulty.EASY) {
|
||||||
val chosen = playable.random()
|
val chosen = playable.random()
|
||||||
if (chosen.second.type.isWild || chosen.second.activeCard(flipped).type.isWild) {
|
val idx = hand.indexOf(chosen.second)
|
||||||
|
return if (chosen.second.type.isWild || chosen.second.activeCard(flipped).type.isWild) {
|
||||||
val color = pickBestColor(hand, flipped)
|
val color = pickBestColor(hand, flipped)
|
||||||
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second), color)
|
AIMove(MoveType.PLAY, idx, color)
|
||||||
|
} else if (isSevenZero && chosen.second.type == CardType.NUMBER && chosen.second.number == 7) {
|
||||||
|
AIMove(MoveType.PLAY, idx, swapTargetId = pickSwapTarget(otherPlayers, player.id))
|
||||||
|
} else {
|
||||||
|
AIMove(MoveType.PLAY, idx)
|
||||||
}
|
}
|
||||||
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hard: prefer cards that hurt the opponent most
|
// Hard: prefer cards that hurt the opponent most
|
||||||
@ -106,6 +119,10 @@ object SimpleAI {
|
|||||||
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second), color)
|
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second), color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSevenZero && chosen.second.type == CardType.NUMBER && chosen.second.number == 7) {
|
||||||
|
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second), swapTargetId = pickSwapTarget(otherPlayers, player.id))
|
||||||
|
}
|
||||||
|
|
||||||
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second))
|
return AIMove(MoveType.PLAY, hand.indexOf(chosen.second))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@ fun GameScreen(
|
|||||||
onCallUno: () -> Unit = {},
|
onCallUno: () -> Unit = {},
|
||||||
onChallengeUno: (String) -> Unit = {},
|
onChallengeUno: (String) -> Unit = {},
|
||||||
onShowLog: () -> Unit = {},
|
onShowLog: () -> Unit = {},
|
||||||
|
onPlaySeven: (Int) -> Unit = {},
|
||||||
|
isSevenZeroMode: Boolean = false,
|
||||||
errorMessage: String
|
errorMessage: String
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
@ -289,6 +291,8 @@ fun GameScreen(
|
|||||||
if (realCard.type.isWild) {
|
if (realCard.type.isWild) {
|
||||||
selectedAutoCard = sortedCards.indexOf(realCard)
|
selectedAutoCard = sortedCards.indexOf(realCard)
|
||||||
showColorPicker = true
|
showColorPicker = true
|
||||||
|
} else if (isSevenZeroMode && realCard.type == CardType.NUMBER && realCard.number == 7 && gameState.players.size > 2) {
|
||||||
|
onPlaySeven(myCards.indexOf(realCard))
|
||||||
} else {
|
} else {
|
||||||
selectedCardIndex = index
|
selectedCardIndex = index
|
||||||
onPlayCard(myCards.indexOf(realCard))
|
onPlayCard(myCards.indexOf(realCard))
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import com.unogame.game.GameMode
|
|||||||
import com.unogame.game.GameRules
|
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.theme.*
|
import com.unogame.ui.theme.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -122,8 +123,14 @@ fun LocalGameScreen(
|
|||||||
var showDrawPlayPopup by remember { mutableStateOf(false) }
|
var showDrawPlayPopup by remember { mutableStateOf(false) }
|
||||||
var pendingDrawState by remember { mutableStateOf<GameState?>(null) }
|
var pendingDrawState by remember { mutableStateOf<GameState?>(null) }
|
||||||
var pendingDrawnCardIndex by remember { mutableIntStateOf(-1) }
|
var pendingDrawnCardIndex by remember { mutableIntStateOf(-1) }
|
||||||
|
var pendingDrawIsWild by remember { mutableStateOf(false) }
|
||||||
|
var showDrawPlayWildPicker by remember { mutableStateOf(false) }
|
||||||
|
var pendingDrawStateForWild by remember { mutableStateOf<GameState?>(null) }
|
||||||
|
var pendingDrawCardIdxForWild by remember { mutableIntStateOf(-1) }
|
||||||
var gameLog by remember { mutableStateOf(listOf<String>()) }
|
var gameLog by remember { mutableStateOf(listOf<String>()) }
|
||||||
var showLogDialog by remember { mutableStateOf(false) }
|
var showLogDialog by remember { mutableStateOf(false) }
|
||||||
|
var showSwapTargetPicker by remember { mutableStateOf(false) }
|
||||||
|
var pendingSwapState by remember { mutableStateOf<GameState?>(null) }
|
||||||
|
|
||||||
val myPlayerId = "human"
|
val myPlayerId = "human"
|
||||||
val currentPlayer = gameState.currentPlayer
|
val currentPlayer = gameState.currentPlayer
|
||||||
@ -134,7 +141,7 @@ 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 = gameLog + state.message
|
gameLog = listOf(state.message) + gameLog
|
||||||
}
|
}
|
||||||
if (state.isGameOver) {
|
if (state.isGameOver) {
|
||||||
isGameOver = true
|
isGameOver = true
|
||||||
@ -165,10 +172,17 @@ fun LocalGameScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executePlay(cardIndex: Int, chosenColor: CardColor? = null) {
|
fun executePlay(cardIndex: Int, chosenColor: CardColor? = null, swapTargetId: String? = null) {
|
||||||
val result = engine.playCard(gameState, myPlayerId, cardIndex, chosenColor)
|
val result = engine.playCard(gameState, myPlayerId, cardIndex, chosenColor, swapTargetId)
|
||||||
when (result) {
|
when (result) {
|
||||||
is GameEngine.PlayResult.Success -> updateState(result.state)
|
is GameEngine.PlayResult.Success -> {
|
||||||
|
if (result.needsSwapTarget) {
|
||||||
|
pendingSwapState = result.state
|
||||||
|
showSwapTargetPicker = true
|
||||||
|
} else {
|
||||||
|
updateState(result.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,6 +194,8 @@ fun LocalGameScreen(
|
|||||||
if (result.drawnCardPlayableIndex >= 0) {
|
if (result.drawnCardPlayableIndex >= 0) {
|
||||||
pendingDrawState = result.state
|
pendingDrawState = result.state
|
||||||
pendingDrawnCardIndex = result.drawnCardPlayableIndex
|
pendingDrawnCardIndex = result.drawnCardPlayableIndex
|
||||||
|
val drawnCard = result.state.players.find { it.id == myPlayerId }?.cards?.getOrNull(result.drawnCardPlayableIndex)
|
||||||
|
pendingDrawIsWild = drawnCard?.type?.isWild == true
|
||||||
showDrawPlayPopup = true
|
showDrawPlayPopup = true
|
||||||
} else {
|
} else {
|
||||||
updateState(result.state)
|
updateState(result.state)
|
||||||
@ -195,10 +211,14 @@ fun LocalGameScreen(
|
|||||||
if (current.id != myPlayerId) {
|
if (current.id != myPlayerId) {
|
||||||
delay(1200L)
|
delay(1200L)
|
||||||
|
|
||||||
val move = SimpleAI.decideMove(current, gameState.topCard, gameState.currentWildColor, gameState.flipped, aiDiff)
|
val move = SimpleAI.decideMove(
|
||||||
|
current, gameState.topCard, gameState.currentWildColor, gameState.flipped, aiDiff,
|
||||||
|
otherPlayers = gameState.players,
|
||||||
|
isSevenZero = mode == GameMode.SEVEN_ZERO
|
||||||
|
)
|
||||||
when (move.type) {
|
when (move.type) {
|
||||||
SimpleAI.MoveType.PLAY -> {
|
SimpleAI.MoveType.PLAY -> {
|
||||||
val result = engine.playCard(gameState, current.id, move.cardIndex, move.chosenColor)
|
val result = engine.playCard(gameState, current.id, move.cardIndex, move.chosenColor, move.swapTargetId)
|
||||||
if (result is GameEngine.PlayResult.Success) {
|
if (result is GameEngine.PlayResult.Success) {
|
||||||
updateState(result.state)
|
updateState(result.state)
|
||||||
} else {
|
} else {
|
||||||
@ -246,6 +266,14 @@ fun LocalGameScreen(
|
|||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
val state = pendingDrawState!!
|
val state = pendingDrawState!!
|
||||||
val cardIdx = pendingDrawnCardIndex
|
val cardIdx = pendingDrawnCardIndex
|
||||||
|
val isWild = pendingDrawIsWild
|
||||||
|
if (isWild) {
|
||||||
|
showDrawPlayPopup = false
|
||||||
|
selectedWildColor = null
|
||||||
|
showDrawPlayWildPicker = true
|
||||||
|
pendingDrawStateForWild = state
|
||||||
|
pendingDrawCardIdxForWild = cardIdx
|
||||||
|
} else {
|
||||||
showDrawPlayPopup = false
|
showDrawPlayPopup = false
|
||||||
pendingDrawState = null
|
pendingDrawState = null
|
||||||
if (cardIdx >= 0) {
|
if (cardIdx >= 0) {
|
||||||
@ -255,6 +283,7 @@ fun LocalGameScreen(
|
|||||||
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}) { Text("出牌", color = GoldAccent) }
|
}) { Text("出牌", color = GoldAccent) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
@ -308,6 +337,85 @@ fun LocalGameScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Color picker for draw-then-play wild card
|
||||||
|
if (showDrawPlayWildPicker) {
|
||||||
|
ColorPickerDialog(
|
||||||
|
flipped = gameState.flipped,
|
||||||
|
onColorSelected = { color ->
|
||||||
|
showDrawPlayWildPicker = false
|
||||||
|
val state = pendingDrawStateForWild!!
|
||||||
|
val cardIdx = pendingDrawCardIdxForWild
|
||||||
|
pendingDrawStateForWild = null
|
||||||
|
if (cardIdx >= 0) {
|
||||||
|
val result = engine.playCard(state, myPlayerId, cardIdx, color)
|
||||||
|
when (result) {
|
||||||
|
is GameEngine.PlayResult.Success -> updateState(result.state)
|
||||||
|
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showDrawPlayWildPicker = false
|
||||||
|
// Fall back to skip on dismiss
|
||||||
|
val state = pendingDrawStateForWild!!
|
||||||
|
val result = engine.skipAfterDraw(state)
|
||||||
|
if (result is GameEngine.PlayResult.Success) updateState(result.state)
|
||||||
|
pendingDrawStateForWild = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7-0 swap target picker
|
||||||
|
if (showSwapTargetPicker && pendingSwapState != null) {
|
||||||
|
val otherPlayers = gameState.players.filter { it.id != myPlayerId }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
// Cancel swap, just finish the turn
|
||||||
|
if (pendingSwapState != null) {
|
||||||
|
gameState = pendingSwapState!!
|
||||||
|
updateState(gameState)
|
||||||
|
showSwapTargetPicker = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text("选择交换对象", color = GoldAccent) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text("选择要交换手牌的玩家:", color = Color.White.copy(alpha = 0.7f), fontSize = 14.sp)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
otherPlayers.forEach { p ->
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
val state = pendingSwapState!!
|
||||||
|
showSwapTargetPicker = false
|
||||||
|
pendingSwapState = null
|
||||||
|
val result = engine.finishSwap(state, myPlayerId, p.id)
|
||||||
|
when (result) {
|
||||||
|
is GameEngine.PlayResult.Success -> updateState(result.state)
|
||||||
|
is GameEngine.PlayResult.Error -> errorMessage = result.message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Person, null, tint = GoldAccent, modifier = Modifier.size(18.dp))
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(p.name, color = Color.White, fontSize = 14.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
if (pendingSwapState != null) {
|
||||||
|
gameState = pendingSwapState!!
|
||||||
|
updateState(gameState)
|
||||||
|
}
|
||||||
|
showSwapTargetPicker = false
|
||||||
|
}) { Text("取消", color = Color.White.copy(alpha = 0.5f)) }
|
||||||
|
},
|
||||||
|
containerColor = DarkSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (isGameOver) {
|
if (isGameOver) {
|
||||||
GameOverScreen(
|
GameOverScreen(
|
||||||
winnerName = winnerName,
|
winnerName = winnerName,
|
||||||
@ -326,7 +434,9 @@ fun LocalGameScreen(
|
|||||||
myPlayerId = myPlayerId,
|
myPlayerId = myPlayerId,
|
||||||
isMyTurn = isMyTurn,
|
isMyTurn = isMyTurn,
|
||||||
errorMessage = errorMessage,
|
errorMessage = errorMessage,
|
||||||
|
isSevenZeroMode = mode == GameMode.SEVEN_ZERO,
|
||||||
onPlayCard = { index -> executePlay(index, selectedWildColor) },
|
onPlayCard = { index -> executePlay(index, selectedWildColor) },
|
||||||
|
onPlaySeven = { index -> executePlay(index, null) },
|
||||||
onDrawCard = { executeDraw() },
|
onDrawCard = { executeDraw() },
|
||||||
onChooseColor = { selectedWildColor = it },
|
onChooseColor = { selectedWildColor = it },
|
||||||
onShowLog = { showLogDialog = true },
|
onShowLog = { showLogDialog = true },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user