public KnowledgeState(int ThisPlayerSittingOrder, PlayerController[] AllPlayers) { // Save the player data PlayerCount = AllPlayers.Length; MySittingOrder = ThisPlayerSittingOrder; HiddenHands = new int[PlayerCount - 1]; // Initialize distributions DeckDistribution = new Distribution1D(CARD_VECTOR_LENGTH); SingleOpponentHandDistribution = new Distribution1D(CARD_VECTOR_LENGTH); TwoOpponentsHandsDistribution = new Distribution2D(CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH); ThreeOpponentsHandsDistribution = new Distribution3D(CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH); HandDistribution = new Distribution1D[PlayerCount]; for (int p = 0; p < PlayerCount; p++) { HandDistribution[p] = new Distribution1D(CARD_VECTOR_LENGTH); } CountUnaccountedForCards = new int[GameController.CARD_COUNT.Length]; TheDeck = AllPlayers[ThisPlayerSittingOrder].Game.Deck; // Perform precomputations of static variables if necessary if (!PrecomputationsComplete) { PrecomputationsComplete = true; PrecomputeStaticArrays(); } // Other opponent data PlayerIsKnockedOut = new bool[PlayerCount]; PlayerKnowsThatMyHandIs = new int[PlayerCount]; PlayerHasTargetedMe = new int[PlayerCount]; PlayerHasKnockouts = new int[PlayerCount]; // Initialize memory Reset(); }
/// <summary> /// Adds another matrix of same size to the current one, optionally weighting them. /// </summary> /// <param name="other">The matrix to be added to this one.</param> /// <param name="ownWeight">A factor to multiply own values before addion (optional, default: 1).</param> /// <param name="otherWeight">A factor to multiply the other's values before addion (optional, default: 1).</param> public void Add(ProbabilityDistribution3D other, float ownWeight = 1f, float otherWeight = 1f) { if (other.GetLength(0) != GetLength(0) || other.GetLength(1) != GetLength(1) || other.GetLength(2) != GetLength(2)) { throw new ArgumentException("Cannot add distributions of different sizes!"); } if (ownWeight < 0) { throw new ArgumentOutOfRangeException("ownWeight", ownWeight, "Cannot weight elements by a negative factor!"); } if (otherWeight < 0) { throw new ArgumentOutOfRangeException("otherWeight", otherWeight, "Cannot multiply elements by a negative factor!"); } for (int x = 0; x < GetLength(0); x++) { for (int y = 0; y < GetLength(1); y++) { for (int z = 0; z < GetLength(2); z++) { this[x, y, z] = this[x, y, z] * ownWeight + other[x, y, z] * otherWeight; } } } }
// Precomputes the static arrays for quicker access protected static void PrecomputeStaticArrays() { // Initialized base deck distribution BaseDeckDistribution = new Distribution1D(CARD_VECTOR_LENGTH); for (int CardIndex = 0; CardIndex < CARD_VECTOR_LENGTH; CardIndex++) { BaseDeckDistribution[CardIndex] = ((float)GameController.CARD_COUNT[CardIndex + 1]) / GameController.TOTAL_CARD_COUNT; } // Initialize base joint hand distribution BaseThreeOpponentsHandsDistribution = new Distribution3D(CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH); // Optimization for the following calculation: int[] tempRemainingCards = new int[CARD_VECTOR_LENGTH]; // We can't use virtualRemainingCards in a static method... Array.Copy(GameController.CARD_COUNT, 1, tempRemainingCards, 0, CARD_VECTOR_LENGTH); float SumOfArray = 0, ScalingFactor = 1f / GameController.TOTAL_CARD_COUNT / (GameController.TOTAL_CARD_COUNT - 1) / (GameController.TOTAL_CARD_COUNT - 2); // Calculate base joint hand distribution for (int Hand1Index = 0; Hand1Index < CARD_VECTOR_LENGTH; Hand1Index++) { // Account for this value of the first hand float Hand1Prob = ScalingFactor * tempRemainingCards[Hand1Index]; tempRemainingCards[Hand1Index] -= 1; // And loop through all possible second and third hidden hands for (int Hand2Index = 0; Hand2Index < CARD_VECTOR_LENGTH; Hand2Index++) { // Account for this value of the second hand float Hand2JointProb = Hand1Prob * tempRemainingCards[Hand2Index]; tempRemainingCards[Hand2Index] -= 1; // And loop through all possible third hidden hands for (int Hand3Index = 0; Hand3Index < CARD_VECTOR_LENGTH; Hand3Index++) { // If this is an impossible case, just jump to the next iteration if (Hand2JointProb <= 0 || tempRemainingCards[Hand3Index] <= 0) { continue; } // Otherwise, calculate the joint probability of this case and store it float jointProb = Hand2JointProb * tempRemainingCards[Hand3Index]; BaseThreeOpponentsHandsDistribution[Hand1Index, Hand2Index, Hand3Index] = jointProb; SumOfArray += jointProb; } // Reset card counts for the next iteration tempRemainingCards[Hand2Index] += 1; } // Reset card counts for the next iteration tempRemainingCards[Hand1Index] += 1; } Debug.Assert(AIUtil.Approx(SumOfArray, 1f)); }
/// <summary> /// Returns a tensor product of this matrix (on the X and Y axes) with another vector (on the Y axis). /// </summary> /// <param name="other">The vector to multiply with.</param> /// <returns>A 3D probability distribution representing the tensor product of the matrix and the vector.</returns> public ProbabilityDistribution3D GetTensorProduct(ProbabilityDistribution1D other) { ProbabilityDistribution3D result = new ProbabilityDistribution3D(GetLength(0), GetLength(1), other.Length); for (int x = 0; x < GetLength(0); x++) { for (int y = 0; y < GetLength(1); y++) { for (int z = 0; z < other.Length; z++) { result[x, y, z] = this[x, y] * other[z]; } } } return(result); }
/// <summary> /// Computes a partial hand distribution of the specified player's new hand after being targeted by the Prince, /// for an assumed world state and the observation of which card they previously discarded. /// </summary> /// <param name="PlayerIndex">Index of the player for whom the computation is performed.</param> /// <param name="Hand0">The index of the card the first of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand1">The index of the card the second of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand2">The index of the card the third of the three remaining opponents is asssumed to hold.</param> /// <param name="DiscardedIndex">The observed index of the card that the player had discarded.</param> /// <param name="OutDistribution">A reference to a probability distribution that containts return values.</param> protected void PrinceDiscardAndDrawPartialUpdate(int PlayerIndex, int Hand0, int Hand1, int Hand2, int DiscardedIndex, ref Distribution3D OutDistribution) { // Don't continue if priori probability is zero float PrioriProbability = ThreeOpponentsHandsDistribution[Hand0, Hand1, Hand2]; if (PrioriProbability <= 0) { return; } // Determine which card in the current data belongs to PlayerIndex int[] idx = new int[] { Hand0, Hand1, Hand2 }; int playerHand = idx[PlayerIndex]; if (playerHand != DiscardedIndex) { return; } // Make a copy of the unaccounted card counters and update it with the "virtually" accounted-for cards Array.Copy(CountUnaccountedForCards, 1, virtualRemainingCards, 0, CARD_VECTOR_LENGTH); if (--virtualRemainingCards[Hand0] < 0 || --virtualRemainingCards[Hand1] < 0 || --virtualRemainingCards[Hand2] < 0) { return; // If a counter goes below zero, this is an impossible case, so return without an update } // Prepare computation int remainingDeckSize = AIUtil.SumUpArray(virtualRemainingCards); if (remainingDeckSize < 1) { return; // The game is over, anyway. } Debug.Assert(remainingDeckSize > 0); // Now loop through each deck card and see if the card that was played could have been played from hand or from deck for (int dc = 0; dc < CARD_VECTOR_LENGTH; dc++) { if (virtualRemainingCards[dc] > 0) { // Calculate the joint probability of such play and increment the output array idx[PlayerIndex] = dc; OutDistribution[idx[0], idx[1], idx[2]] += PrioriProbability * virtualRemainingCards[dc] / remainingDeckSize; } } }
/// <summary> /// Computes a partial hand distribution of the specified player, /// for an assumed world state and the observation of which card they played. /// </summary> /// <param name="TurnData">Complete data of the turn being analyzed.</param> /// <param name="Hand0">The index of the card the first of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand1">The index of the card the second of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand2">The index of the card the third of the three remaining opponents is asssumed to hold.</param> /// <param name="OutDistribution">A reference to a probability distribution that containts return values.</param> protected void UpdatePartialHandDistribution(MoveData TurnData, int Hand0, int Hand1, int Hand2, ref Distribution3D OutDistribution) { // Don't continue if priori probability is zero float PrioriProbability = ThreeOpponentsHandsDistribution[Hand0, Hand1, Hand2]; if (PrioriProbability <= 0) { return; } // Make a copy of the unaccounted card counters and update it with the "virtually" accounted-for cards Array.Copy(CountUnaccountedForCards, 1, virtualRemainingCards, 0, CARD_VECTOR_LENGTH); if (--virtualRemainingCards[Hand0] < 0 || --virtualRemainingCards[Hand1] < 0 || --virtualRemainingCards[Hand2] < 0) { return; // If a counter goes below zero, this is an impossible case, so return without an update } // Parse the turn data int PlayerIndex = GetHiddenHandIndex(TurnData.Player.SittingOrder), PlayedCardIndex = TurnData.Card.Value - 1, TargetIndex = (TurnData.Target == null || TurnData.Target.SittingOrder == MySittingOrder) ? -1 : GetHiddenHandIndex(TurnData.Target.SittingOrder); // Determine which card in the current data belongs to PlayerIndex int[] idx = new int[] { Hand0, Hand1, Hand2 }; int playerHand = idx[PlayerIndex], targetHand = TargetIndex != -1 ? idx[TargetIndex] : -1; // Prepare computation int remainingDeckSize = AIUtil.SumUpArray(virtualRemainingCards); Debug.Assert(remainingDeckSize > 0); // Now loop through each deck card and see if the card that was played could have been played from hand or from deck for (int dc = 0; dc < CARD_VECTOR_LENGTH; dc++) { if (virtualRemainingCards[dc] > 0 && (PlayedCardIndex == playerHand || PlayedCardIndex == dc)) { // Compute which card the player has left in their hand, given PlayedCardIndex int otherCard = (PlayedCardIndex == playerHand) ? dc : playerHand; // Prepare the index values for the update idx[PlayerIndex] = otherCard; // Calculate the joint probability of such play and increment the output array OutDistribution[idx[0], idx[1], idx[2]] += PrioriProbability * virtualRemainingCards[dc] / remainingDeckSize * GetLikelihoodOfPlay(TurnData, otherCard + 1, targetHand + 1); } } }