// Analyzes the most recent turn made in the game protected override IEnumerator AnalyzeTurn(int id) { Debug.Assert(TurnHistory != null && TurnHistory.Count >= id); Debug.Assert(id == NextTurnToAnalyze); // Get the actual turn MoveData turn = TurnHistory[id]; // Precompute some helpful booleans bool ItWasMyTurn = (turn.Player == MyController); bool IWasTheTarget = (turn.Target == MyController); bool ThereWasAKnockOut = (turn.KnockedOut != null); bool PlayerWasKnockedOut = (ThereWasAKnockOut && turn.KnockedOut == turn.Player); bool TargetWasKnockedOut = (ThereWasAKnockOut && turn.KnockedOut == turn.Target); // Wait for some random time, just to ease the computational load per frame yield return(new WaitForSecondsRealtime(UnityEngine.Random.Range(0, Time.fixedDeltaTime * PlayerCount))); // This flag will be set to true in certain cases if no renormalization is requires bool RenormalizationCommenced = false; // Basic precomputations before getting into the specifics of card effects if (ItWasMyTurn) { // Update my own hand distribution, just for simplicity UpdateOwnHandDistribution(); // Update my own play statistic UpdatePlayStatistics(turn); // Other players' knowledge of your hand becomes irrelevant after your own turn Array.Clear(PlayerKnowsThatMyHandIs, 0, PlayerCount); // We don't need to update unaccounted-for cards with the card just played and the card distribution, // because that was done at the end of the last analysis run (of the player immediately before us) } else { // When analyzing another player's turn, filter their hand first: FilterHiddenHandWithPlayedCard(turn.Player.SittingOrder, turn.Card.Value); // The card they just played lands on the discard pile, so update the unaccounted-for list AccountForCard(turn.Card.Value); // Also update this player's stats if (turn.Target == MyController) { PlayerHasTargetedMe[turn.Player.SittingOrder] += 1; } if (turn.KnockedOut != null && turn.KnockedOut != turn.Player) { PlayerHasKnockOuts[turn.Player.SittingOrder] += 1; } } // Wait some more yield return(new WaitForSecondsRealtime(UnityEngine.Random.Range(0, Time.fixedDeltaTime * PlayerCount))); // If there was a knock-out by something other than a Baron, then the filtering is simple if (ThereWasAKnockOut && turn.Card.Value != CardController.VALUE_BARON) { KnockOutFilter(turn.KnockedOut.SittingOrder, turn.AdditionalDiscard.Value); } else if (!turn.NoEffect) { // Otherwise, if the card effect wasn't blocked by a Handmaid, we check specific card effects switch (turn.Card.Value) { case CardController.VALUE_GUARD: // If the guard wasn't played against me and the target is still in the game, // I at least know what their hand is NOT if (!IWasTheTarget) { HiddenHandIsNot(turn.Target.SittingOrder, turn.TargetHandGuess); UpdateSameHands(turn.Target.SittingOrder); } break; case CardController.VALUE_PRIEST: // If it was my turn, I now know the target's hand if (ItWasMyTurn) { // With a Priest, I now know the target's hand Debug.Assert(turn.Target == lastLearnedHandOf); UpdateHandDistributionWithCertainty(lastLearnedHandOf.SittingOrder, lastLearnedCard.Value); UpdateSameHands(lastLearnedHandOf.SittingOrder); } else if (IWasTheTarget) { // But if I was the target, the other player now knows my hand! PlayerKnowsThatMyHandIs[turn.Player.SittingOrder] = myHand.Value; } break; case CardController.VALUE_BARON: // If either the player or the target were knocked out by Baron, we apply the Baron KO filter if (PlayerWasKnockedOut) { BaronEffectFilterWithKnockout(turn.Target.SittingOrder, turn.Player.SittingOrder, turn.AdditionalDiscard.Value); } else if (TargetWasKnockedOut) { BaronEffectFilterWithKnockout(turn.Player.SittingOrder, turn.Target.SittingOrder, turn.AdditionalDiscard.Value); } else { // On a draw between two other players, their hand distributions are now equal BaronEffectFilterWithDraw(turn.Player.SittingOrder, turn.Target.SittingOrder); } // If there was a draw involving me, the other player now knows my hand! if (!ThereWasAKnockOut && (ItWasMyTurn || IWasTheTarget)) { PlayerKnowsThatMyHandIs[ItWasMyTurn ? turn.Target.SittingOrder : turn.Player.SittingOrder] = myHand.Value; } break; case CardController.VALUE_PRINCE: // If I was the target of the Prince, I just need to update my hand if (IWasTheTarget) { // Even if it was my turn, I have already accounted for my old hand and the card I just drew (both of which I have discarded), // as well as have updated my own hand distribution, therefore I only need to account for the new card in my hand Debug.Assert(DrawnBecauseOfThePrince != null); UpdateHandDistributionWithCertainty(MyController.SittingOrder, DrawnBecauseOfThePrince.Value); AccountForCard(DrawnBecauseOfThePrince.Value); DrawnBecauseOfThePrince = null; } else { // Otherwise, the special Prince filter logic applies (the values are automatically renormalized) PrinceEffectFilterWithoutKnockout(turn.Target.SittingOrder, turn.AdditionalDiscard.Value); RenormalizationCommenced = true; ClearSameHands(turn.Target.SittingOrder); } break; case CardController.VALUE_KING: // If I was involved in the swap, I know quite a lot about the other player's hand now (and vice versa) if (ItWasMyTurn) { KingEffectFilterInvolvingMe(turn.Target.SittingOrder); } else if (IWasTheTarget) { KingEffectFilterInvolvingMe(turn.Player.SittingOrder); } else { // If I wasn't involved, just swap the hand distributions of both players who were SwapHandDistributions(turn.Player.SittingOrder, turn.Target.SittingOrder); } break; default: // Nothing to do when Handmaid, Countess, or Princess were played break; } } // Wait some more yield return(new WaitForSecondsRealtime(UnityEngine.Random.Range(0, Time.fixedDeltaTime * PlayerCount))); // If I'm up next, wait until I've drawn my next card before finishing the analysis if (turn.Player != MyController && MyController.Game.Deck.CountCardsLeft > 1 && NextPlayerIndex == MyController.SittingOrder) { yield return(new WaitUntil(() => ((myHand != null && justDrawn != null)) || MyController.Game.RoundOver)); if (!MyController.Game.RoundOver) { // And update the distributions accordingly AccountForCard(justDrawn.Value); RenormalizeDeckAndHandsDistributions(); } } else if (!RenormalizationCommenced) { // If a renormalization is still required, do it now (it is a very expensive call) RenormalizeDeckAndHandsDistributions(); } // Finally, finish the update and return NextTurnToAnalyze = id + 1; if (FullLogging) { displayCurrentBeliefs(); if (SameHands.Count > 0) { Debug.Log(name + " also knows following same hands: " + AIUtil.FormatDictionary(SameHands)); } } yield return(new WaitForFixedUpdate()); AnalysisOngoing = false; }