using System.Collections; using System.Collections.Generic; using System.Linq; using GameFramework.AI; using GameFramework.CardZones; using GameFramework.Data; using GameFramework.Utilities; using UnityEngine; namespace GameFramework.GamePhases { public class PlayHandsPhase : GamePhaseBase { private static readonly WaitForSeconds _pauseOpponentThink = new(0.9f); private static readonly WaitForSeconds _pauseShowGo = new(1.2f); private static readonly WaitForSeconds _pauseShowLastCard = new(1.5f); protected override IEnumerator RunPhaseLogic() { // Starts or Continues the Play Hands Phase -- // This phase has two additional phases with two sub-phase event triggered functions // following the base phase, for selecting cards or clicking go button // This phase naturally transitions to/from CountHands phase when all cards have been played while peg score < 131 // This phase may also transition to/from the MovingPegs phase (and its additional phase MovePeg) // When in a PlayerOut phase, only one player has cards and board had been reset - no go points, just play out TrySetActiveState(Context.TableScoreTotalPanel, true); // ---- PLAYER GO BUTTON ---- if (Context.PlayPanelGoButton.activeSelf) { TrySetActiveState(Context.PlayPanelGoButton, false); } else { if (CurrentPhase == GamePhase.PlayHands) { SetPhase(GamePhase.PlayCard); } } // ---- PLAYER OUT PHASE ---- if (CurrentPhase == GamePhase.PlayerOut) { yield return HandleOpponentPlayOut(); yield break; } // ---- PLAYER CARD SELECTED ---- if (SelectedCard != null) { yield return HandlePlayerMove(SelectedCard); yield return HandlePlayerPlayOut(); yield break; } // ---- WHOSE TURN? ---- var isNewRound = IsNewRound(); var isPlayerCribOwner = IsPlayerCribOwner(); var playerHand = Context.PlayerHandManager.GetCards(); var opponentHand = Context.OpponentHandManager.GetCards(); // Player or opponent leads? var isOpponentsMove = (LastPlayer == GamePlayer.Player1) || !HasPlayableCard(playerHand); var isPlayersMove = (isNewRound && !isPlayerCribOwner) || (!isNewRound && !isOpponentsMove); // ---- NEW ROUND SCENARIO: Need to ensure table score is zeroed ---- if (isNewRound) { UpdateRunningScore(0); } // ---- GO SCENARIO: No one can play, but table <= 31 ---- var tableScore = GetCurrentTableScore(); if (!IsCountPhaseReady() && !HasPlayableCard(playerHand) && !HasPlayableCard(opponentHand)) { if (tableScore != 31 && LastPlayer == GamePlayer.Player1) { SetPhase(GamePhase.PlayHands); yield return ShowGoMessage(); } if (tableScore == 31) { if (LastPlayer != GamePlayer.Opponent) { SetPhase(GamePhase.PlayHands); } } yield return MovePlayedCardsToPlayedZone(); ResetTableScore(); if (!CheckAndEnterPlayOut()) { if (LastPlayer == GamePlayer.Opponent) { SetPhase(GamePhase.PlayCard); } } yield break; } // ---- LAST CARD/COUNT PHASE ---- if (IsCountPhaseReady()) { if (tableScore < 31) { yield return ShowLastCardMessage(); } yield break; } // ---- PLAYER MOVE CHECK ---- if (isPlayersMove) { // Wait for player input (card select or Go button) // Defensive: if player can't play, show Go button if (!HasPlayableCard(playerHand) && HasPlayableCard(opponentHand)) { TrySetActiveState(Context.PlayPanelGoButton, true); } if (!CheckAndEnterPlayOut()) { SetPhase(GamePhase.PlayCard); } yield break; } // ---- OPPONENT MOVE CHECK ---- if (HasPlayableCard(opponentHand)) { yield return HandleOpponentMove(); tableScore = GetCurrentTableScore(); if (tableScore == 31) { // TODO: Opponent scores 2 points, implement scoring event later yield return MovePlayedCardsToPlayedZone(); ResetTableScore(); yield break; } // If EITHER player has cards in PlayHands/PlayCard phase, it is still not over and go button should be conditionally shown to player var isRoundStillGoing = CurrentPhase != GamePhase.PlayerOut && CurrentPhase != GamePhase.CountHands; var canStillPlay = playerHand.Count > 0 || Context.OpponentHandManager.GetCards().Count > 0; if (isRoundStillGoing && canStillPlay) { TrySetActiveState(Context.PlayPanelGoButton, !HasPlayableCard(playerHand)); } if (!canStillPlay) { if (IsCountPhaseReady() && tableScore < 31) { yield return ShowLastCardMessage(); } } yield break; } else { if (tableScore != 31 && LastPlayer == GamePlayer.Player1) { // TODO: Player scores a go point yield return ShowGoMessage(); } } } // ------------------- HELPERS & HANDLERS ------------------- private IEnumerator HandlePlayerMove(CardData card) { var playerHand = Context.PlayerHandManager.GetCards(); if (!playerHand.Contains(card) || !CheckCanPlay(card)) { yield break; } // Move card ZonesCardTransfer.TransferCard( Context.PlayerHandManager, Context.CardPlayPanelManager, card, Context.CardSprites, Context.CardSprites[card.SpriteName]); SetLastPlayer(GamePlayer.Player1); Context.PlayerLowestCardManager.AddCard(card, Context.CardSprites); UpdateRunningScore(card.Value); // Check for playout state (if PlayerOut phase, clear table if needed) if (CurrentPhase == GamePhase.OpponentOut && IsCountPhaseReady()) { if (GetCurrentTableScore() == 31) { // TODO: 2 points to this player here, fire event when scoring system implemented } } // Normal flow: let opponent move else wait for next player action on playout if (CurrentPhase != GamePhase.OpponentOut) { SetPhase(GamePhase.PlayHands); } } private IEnumerator HandleOpponentMove() { yield return _pauseOpponentThink; var opponentHand = Context.OpponentHandManager.GetCards(); var playZone = Context.CardPlayPanelManager.GetCards(); var card = OpponentCardSelection.GetPlayCard(opponentHand, playZone, GetCurrentTableScore()); if (card == null) { if (CurrentPhase != GamePhase.PlayerOut) { SetPhase(GamePhase.PlayHands); } yield break; } ZonesCardTransfer.TransferCard( Context.OpponentHandManager, Context.CardPlayPanelManager, card, Context.CardSprites, Context.CardSprites[card.SpriteName]); SetLastPlayer(GamePlayer.Opponent); Context.OpponentLowestCardManager.AddCard(card, Context.CardSprites); UpdateRunningScore(card.Value); if (CurrentPhase == GamePhase.PlayerOut && IsCountPhaseReady()) { if (GetCurrentTableScore() == 31) { // TODO: 2 points to this player here, fire event when scoring system implemented } else { yield return ShowLastCardMessage(); } } } private IEnumerator HandlePlayerPlayOut() { // ---- PHASE OUT LOGIC ---- if (CurrentPhase != GamePhase.OpponentOut) { yield break; } var playerHand = Context.PlayerHandManager.GetCards(); if (playerHand.Count == 0) { yield return MovePlayedCardsToPlayedZone(); SetPhase(GamePhase.CountHands); yield break; } } private IEnumerator HandleOpponentPlayOut() { var opponentHand = Context.OpponentHandManager.GetCards(); if (opponentHand.Count == 0) { yield return MovePlayedCardsToPlayedZone(); SetPhase(GamePhase.CountHands); yield break; } yield return HandleOpponentMove(); } private IEnumerator ShowGoMessage() { TrySetActiveState(Context.PlayPanelGoMessage, true); // TODO: Player1 earns 1 point for go, once scoring is implemented yield return _pauseShowGo; TrySetActiveState(Context.PlayPanelGoMessage, false); } private IEnumerator ShowLastCardMessage() { var isActiveSelf = Context.PlayerLastCardMessage.activeSelf || Context.OpponentLastCardMessage.activeSelf; if (isActiveSelf) { yield break; // Should not happen, but defensive check } var lastPlayerIsPlayer = (LastPlayer == GamePlayer.Player1); TrySetActiveState(Context.PlayerLastCardMessage, lastPlayerIsPlayer); TrySetActiveState(Context.OpponentLastCardMessage, !lastPlayerIsPlayer); yield return _pauseShowLastCard; TrySetActiveState(Context.PlayerLastCardMessage, false); TrySetActiveState(Context.OpponentLastCardMessage, false); } private IEnumerator MovePlayedCardsToPlayedZone() { var playZone = Context.CardPlayPanelManager; var playedZone = Context.CardsPlayedPanelManager; var currentCards = playZone.GetCards().ToList(); foreach (var card in currentCards) { ZonesCardTransfer.TransferCard(playZone, playedZone, card, Context.CardSprites); yield return new WaitForSeconds(0.15f); } playZone.ClearZone(); yield return new WaitForSeconds(0.4f); } private void ResetTableScore() { UpdateRunningScore(0); TrySetActiveState(Context.PlayPanelGoButton, false); TrySetActiveState(Context.PlayPanelGoMessage, false); } // --------------- Utility Logic --------------- private bool HasPlayableCard(IReadOnlyList hand) { foreach (var card in hand) { if (CheckCanPlay(card)) { return true; } } return false; } private bool IsNewRound() { var isPlayerHandFull = Context.PlayerHandManager.GetCards().Count == 4; var isOpponentHandFull = Context.OpponentHandManager.GetCards().Count == 4; return isPlayerHandFull && isOpponentHandFull; } private bool IsPlayerCribOwner() { return Context.PlayerCribPanelManager.GetCards().Count > 0; } private bool CheckAndEnterPlayOut() { var playerHand = Context.PlayerHandManager.GetCards(); var opponentHand = Context.OpponentHandManager.GetCards(); var playerOut = playerHand.Count == 0; var opponentOut = opponentHand.Count == 0; if (playerOut && !opponentOut) { SetPhase(GamePhase.PlayerOut); return true; } if (opponentOut && !playerOut) { SetPhase(GamePhase.OpponentOut); return true; } return false; } private int GetCurrentTableScore() { var text = Context.TableScoreValueTextMesh.text ?? "0"; return int.TryParse(text, out int score) ? score : 0; } private void UpdateRunningScore(int value) { var current = GetCurrentTableScore(); current = value == 0 ? 0 : current + GetNormalizedValue(value); Context.TableScoreValueTextMesh.text = current.ToString(); // TODO: Check for 15-2 and 31 for 2 here when scoring events implemented (??) } private bool IsCountPhaseReady() { var playerCardsCount = Context.PlayerHandManager.GetCards().Count; var opponentCardsCount = Context.OpponentHandManager.GetCards().Count; if (playerCardsCount == 0 && opponentCardsCount == 0) { TrySetActiveState(Context.PlayPanelGoButton, false); // TEST: Commented out ResetTablescore, using this now TrySetActiveState(Context.PlayPanelGoMessage, false); // TEST: Commented out ResetTablescore, using this now SetPhase(GamePhase.CountHands); return true; } return false; } // NOTE: now repeated this method in AI class OpponentCardSelection - consider util later private int GetNormalizedValue(int value) => value > 10 ? 10 : value; private bool CheckCanPlay(CardData card) { var cardValue = GetNormalizedValue(card.Value); var currentScore = GetCurrentCardsCount(); return currentScore + cardValue <= 31; } private int GetCurrentCardsCount() { var playZone = Context.CardPlayPanelManager; var currentCards = playZone.GetCards(); return currentCards.Sum(card => GetNormalizedValue(card.Value)); } } }