protected override IEnumerator PickAMove() { // Wait until the perceptor has finished working yield return(new WaitUntil(() => MyPerceptor.READY)); // Get available moves List <MoveData> availableMoves = myHand.GetLegalMoves(Game, this); availableMoves.AddRange(justDrawn.GetLegalMoves(Game, this)); // Find the move wit the highest utility MoveData.DualUtility highestUtility = MoveData.DualUtility.MinValue; MoveData bestMove = availableMoves[0]; foreach (MoveData move in availableMoves) { MoveData.DualUtility utility = GetMoveUtility(move); // Compare and discard if (utility.CompareTo(highestUtility) > 0) { bestMove = move; highestUtility = utility; } } // Return the highest-utility move myNextMove = bestMove; }
protected override IEnumerator PickAMove() { // Wait until the perceptor has finished working yield return(new WaitUntil(() => MyPerceptor.READY)); // Get available moves List <MoveData> availableMoves = myHand.GetLegalMoves(Game, this); availableMoves.AddRange(justDrawn.GetLegalMoves(Game, this)); // Calculate utilities of every move MoveData.DualUtility[] moveUtility = new MoveData.DualUtility[availableMoves.Count]; MoveData.DualUtility highestUtility = MoveData.DualUtility.MinValue; for (int i = 0; i < availableMoves.Count; i++) { moveUtility[i] = GetMoveUtility(availableMoves[i]); if (moveUtility[i].CompareTo(highestUtility) > 0) { highestUtility = moveUtility[i]; } } // Calculate cutoff for random selection float cutoff = Mathf.Max(0, RELATIVE_CUTOFF_POINT * highestUtility.Utility); // Return a weighted random of the resulting moves myNextMove = availableMoves[AIUtil.GetWeightedRandom(moveUtility, highestUtility.Rank, cutoff)]; }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(move.Target != null); // No point in playing against protected opponents MoveData.DualUtility result = MoveData.DualUtility.Default; if (move.Target.Protected) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } // The utility of playing a Priest is the measure of the current player's uncertainty regarding the target player's hand, // but only if this player will have another turn, otherwise this knowledge is worthless (in which case the utility stays 0) if (perceptorData.WillThisPlayerHaveAnotherTurn(move.Player)) { float highestProbability = 0; float[] TargetHandProbabilities = perceptorData.GetCardProbabilitiesInHand(move.Target); for (int i = CardController.VALUE_GUARD; i <= CardController.VALUE_PRINCESS; i++) { if (TargetHandProbabilities[i] > highestProbability) { highestProbability = TargetHandProbabilities[i]; } } // The uncertainty score is the inverse of certainty result.Utility = 1f - highestProbability; } return(result); }
protected MoveData.DualUtility GetMoveUtility(MoveData move) { CardController remainingHand = (move.Card == justDrawn) ? myHand : justDrawn; // Get basic move utility MoveData.DualUtility moveUtility = move.Card.EstimateMoveUtility(move, remainingHand, MyPerceptor); // Get remaining hand value utility float marginalHandValueUtility = GetMarginalHandValueUtility(Game, remainingHand.Value); // Get the utility of playing the current hand given that the opponent knows it float opponentKnowledgeUtility = GetOpponentKnowledgeUtility(); // Weighted sum of utilities MoveData.DualUtility result = new MoveData.DualUtility(moveUtility.Rank, CONSIDERATION_WEIGHT_MOVE_UTILITY * moveUtility.Utility + CONSIDERATION_WEIGHT_MARGINAL_HAND_VALUE * marginalHandValueUtility + CONSIDERATION_WEIGHT_OPPONENT_KNOWLEDGE * opponentKnowledgeUtility + CONSIDERATION_WEIGHT_TACTICAL_DANGER * GetTacticalDanger(move.Target, remainingHand) + CONSIDERATION_WEIGHT_STRATEGIC_DANGER * GetStrategicDanger(move.Target) + CONSIDERATION_WEIGHT_GRUDGE * GetGrudgeAgainst(move.Target)); // Drop the rank on new game to avoid knocking out the player immediately if (move.Target == Game.HumanPlayer && !MyPerceptor.HumanPlayerHasActed) { result.Rank = Mathf.Min(result.Rank, MoveData.RANK_ONLY_IF_NECESSARY); } return(result); }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(move.Target != null); Debug.Assert(otherCard != null); // No point in playing against protected opponents MoveData.DualUtility result = MoveData.DualUtility.Default; if (otherCard.Value == CardController.VALUE_COUNTESS) { result.Rank = MoveData.RANK_NEVER_EVER; return(result); } else if (move.Target.Protected || otherCard.Value == CardController.VALUE_GUARD || (perceptorData.GetCardProbabilityInHand(move.Target, CardController.VALUE_GUARD) > 0) && perceptorData.WillThisPlayerHaveAnotherTurn(move.Target)) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } // The Kings's utility is the difference between the marginal utilities of own hand and the (estimated) target's hand float ownMarginalHandValueUtility = PlayerController.GetMarginalHandValueUtility(move.Player.Game, otherCard.Value); // Estimate the target player's hand and marginal utility float targetsMarginalHandValueUtility = PlayerController.GetMarginalHandValueUtility(move.Player.Game, perceptorData.GetExpectedHandValue(move.Target)); // Normalize the difference before returning result.Utility = (targetsMarginalHandValueUtility - ownMarginalHandValueUtility + 1f) / 2; return(result); }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(move.Target != null); Debug.Assert(move.TargetHandGuess >= VALUE_PRIEST && move.TargetHandGuess <= VALUE_PRINCESS); // No point in playing against protected opponents, or when the likelihoood of guessing right is too low MoveData.DualUtility result = MoveData.DualUtility.Default; float LikelihoodOfCorrectGuess = perceptorData.GetCardProbabilityInHand(move.Target, move.TargetHandGuess); if (LikelihoodOfCorrectGuess < REASONABLE_DOUBT_CUTOFF || perceptorData.GetRevealedCardCount(move.TargetHandGuess) >= GameController.CARD_COUNT[move.TargetHandGuess]) { result.Rank = MoveData.RANK_NEVER_EVER; return(result); } else if (move.Target.Protected) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } else if (perceptorData.GetCertainHandValue(move.Target) == move.TargetHandGuess) { result.Rank = MoveData.RANK_PARAMOUNT; } // Utility of playing a Guard is just the certainty that the other player has the card you were going to guess result.Utility = LikelihoodOfCorrectGuess; return(result); }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(otherCard != null); // The Countess' utility is very high if the other card is a Prince or a King (i.e. the utility of not losing the round) MoveData.DualUtility result = MoveData.DualUtility.Default; if (IsKnockOutByCountess(otherCard.Value, move.Card.Value)) { result.Rank = MoveData.RANK_MUST_PLAY; } else if (!move.Player.Game.TheKingAndBothPrincesAreDiscarded) { // Otherwise, it's the utility of potential misdirection (unless the King and both Princes are already discarded) result.Utility = Mathf.Max(0, (5f - otherCard.Value) / 4); } return(result); }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(move.Target != null); // No point in playing against protected opponents MoveData.DualUtility result = MoveData.DualUtility.Default; if (otherCard.Value == CardController.VALUE_COUNTESS) { result.Rank = MoveData.RANK_NEVER_EVER; return(result); } else if (move.Target.Protected) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } // The Prince's utility is the marginal utility of the (estimated) target's hand, // compared to the estimate marginal utility of a draw from deck float targetHandMarginalUtility = PlayerController.GetMarginalHandValueUtility(move.Player.Game, perceptorData.GetExpectedHandValue(move.Target)); float deckDrawMarginalUtility = PlayerController.GetMarginalHandValueUtility(move.Player.Game, perceptorData.GetExpectedDeckValue()); // The resulting utility against opponents and against oneself is reversed if (move.Target == move.Player) { result.Utility = Mathf.Clamp01(deckDrawMarginalUtility - targetHandMarginalUtility); if (otherCard.Value == CardController.VALUE_PRINCESS) { result.Rank = MoveData.RANK_NEVER_EVER; return(result); } } else { result.Utility = Mathf.Clamp01(targetHandMarginalUtility - deckDrawMarginalUtility); // If you are certain the other player has the Princess, playing the Prince against them is paramount if (perceptorData.GetCertainHandValue(move.Target) == CardController.VALUE_PRINCESS) { result.Rank = MoveData.RANK_PARAMOUNT; } } return(result); }
public override MoveData.DualUtility EstimateMoveUtility(MoveData move, CardController otherCard, AIGenericPerceptor perceptorData) { Debug.Assert(move.Card == this); Debug.Assert(move.Target != null); Debug.Assert(otherCard != null); // No point in playing against protected opponents MoveData.DualUtility result = MoveData.DualUtility.Default; if (move.Target.Protected || otherCard.Value == CardController.VALUE_GUARD) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } // The Baron's utility is the certainty that the other player's card is lower than your own for (int i = CardController.VALUE_GUARD; i < otherCard.Value; i++) { result.Utility += perceptorData.GetCardProbabilityInHand(move.Target, i); } // However, if the chance of tying or losing is above the certainty threshold, also drop the rank if ((1f - result.Utility) > AIGenericPerceptor.CERTAINTY_THRESHOLD) { result.Rank = MoveData.RANK_BARELY_SENSIBLE; } return(result); }