/// <summary> /// Given a tile we want to give info on, evaluate the relative strengths of suit vs number info for that tile /// and return whichever is better /// </summary> private Tuple <int, PlayerAction> ChooseBestInfoForTile(int playerIndex, Tile tile, string log) { // Only give info if this tile hasn't had info given on it recently var unknownTile = Lookup(tile); bool noRecentInfo = (unknownTile.InfoAge == -1 || unknownTile.InfoAge > 20); // Only give this info if no other player has already been told to play their copy of this tile var otherCopies = m_lastGameState.AllHands().Where(t => t.Same(tile) && t.UniqueId != tile.UniqueId); bool otherCopiesInfoAlready = otherCopies.Any(t => Lookup(t).InfoAge >= 0); Tuple <int, PlayerAction> bestAction = null; if (noRecentInfo && !otherCopiesInfoAlready) { // Construct an action for giving number info for this tile var numAction = PlayerAction.GiveInfo(PlayerIndex, PlayerAction.PlayerActionInfoType.Number, playerIndex, tile.Number, log); var numTiles = m_lastGameState.Hands[playerIndex].Where(t => t.Number == tile.Number); var numPlayable = numTiles.Count(t => m_lastGameState.IsPlayable(t)); var numBad = numTiles.Count() - numPlayable; var numFitness = (INFO_PLAYABLE_FITNESS * numPlayable + INFO_UNPLAYABLE_FITNESS * numBad); // Construct an action for giving suit info for this tile var suitAction = PlayerAction.GiveInfo(PlayerIndex, PlayerAction.PlayerActionInfoType.Suit, playerIndex, (int)tile.Suit, log); var suitTiles = m_lastGameState.Hands[playerIndex].Where(t => t.Suit == tile.Suit); var suitPlayable = suitTiles.Count(t => m_lastGameState.IsPlayable(t)); var suitBad = suitTiles.Count() - suitPlayable; var suitFitness = (INFO_PLAYABLE_FITNESS * suitPlayable + INFO_UNPLAYABLE_FITNESS * suitBad); if (numFitness > suitFitness) { bestAction = new Tuple <int, PlayerAction>(numFitness, numAction); } else { bestAction = new Tuple <int, PlayerAction>(suitFitness, suitAction); } } return(bestAction); }
/// <summary> /// Get all the reasonable actions along with their "strength" /// </summary> private IEnumerable <Tuple <int, PlayerAction> > GetAllActions() { /////////////////////////////////////////////////// // PLAY ACTIONS /////////////////////////////////////////////////// foreach (Guid g in TilesInHand(PlayerIndex)) { if (m_allowFinesses) { // Finesses var finesseTargetsInOurHand = m_activeFinesses.Where(f => m_lastGameState.YourHand.Contains(f.TargetTileId)); if (finesseTargetsInOurHand.Any()) { var finesse = finesseTargetsInOurHand.First(); if (finesse.PossibleTargetValues.All(v => m_lastGameState.IsPlayable(v.Item1, v.Item2))) { var infoString = finesse.PossibleInfoValues.Aggregate("", (s, t) => s += t.Item1 + " " + t.Item2 + ", "); var playAction = PlayerAction.PlayTile(PlayerIndex, finesse.TargetTileId, String.Format("Finessed by {0}", infoString)); yield return(new Tuple <int, PlayerAction>(80, playAction)); } } } // Other Info var lookup = Lookup(g); // If this tile has been strongly messages OR we know it's definitely playable bool playStrength = lookup.PlayStrength >= GetCurrentPlayStrength(); bool isUnplayable = lookup.IsUnplayable(m_lastGameState); bool isPlayable = lookup.IsDefinitelyPlayable(m_lastGameState); if (isPlayable || (playStrength && !isUnplayable)) { string log = ""; if (isPlayable) { log = String.Format("This tile should be playable ({0})", lookup.ToString()); } else { log = String.Format("PlayStrength is {0}", lookup.PlayStrength); } if (m_activeFinesses.Where(f => f.InfoTileId == g).Any()) { // Check to see if this has play strength because it's the info part of a finesse Finesse finesse = m_activeFinesses.Where(f => f.InfoTileId == g).First(); int currTurn = m_lastGameState.History.Count() + 1; // If the target has had a chance to act if (currTurn > finesse.TurnTargetShouldAct) { var target = finesse.PossibleTargetValues.First(); // If the target tile of this finesse was played if (m_lastGameState.Play.Where(t => t.UniqueId == finesse.TargetTileId).Any()) { // Awesome! This is probably playable! log = String.Format("Was used to finesse the {0} {1} which was played!", target.Item1, target.Item2); } else { // There's been enough time but the finessed person didn't play // Therefore this is probably not a finesse // Remove the finesse from our list and keep assuming this tile is playable m_activeFinesses.Remove(finesse); log = String.Format("Thought this was finessing the {0} {1} but apparently not!", target.Item1, target.Item2); } } else { // Hasn't been long enough for the finesse target to act yet // So delay - skip considering this tile playable for now, but don't change any bookkeeping continue; } } var playAction = PlayerAction.PlayTile(PlayerIndex, g, log); yield return(new Tuple <int, PlayerAction>(lookup.PlayStrength, playAction)); } } /////////////////////////////////////////////////// // PURPOSEFUL DISCARD ACTIONS /////////////////////////////////////////////////// // Next see if we need to discard // TODO /////////////////////////////////////////////////// // INFO ACTIONS /////////////////////////////////////////////////// // Only give info if we have a token // TODO: don't wait until 0 tokens, maybe if (m_lastGameState.Tokens > 0) { if (m_allowFinesses) { var finesses = GetSimpleFinesses().OrderBy(t => t.Item2.Number); foreach (var combo in finesses) { var info = ChooseBestInfoForTile(combo.Item1, combo.Item2, "Finesse!"); if (info != null) { // Add bonus strength for this being a finesse var newInfo = new Tuple <int, PlayerAction>(info.Item1 + 20, info.Item2); yield return(newInfo); } } } // Next see if there's valid info to give // This is pretty naive var playable = GetAllPlayableTiles().OrderBy(t => t.Item2.Number); foreach (var combo in playable) { var info = ChooseBestInfoForTile(combo.Item1, combo.Item2, "Playable"); if (info != null) { yield return(info); } } } /////////////////////////////////////////////////// // NOTHING BETTER TO DO DISCARD /////////////////////////////////////////////////// if (m_lastGameState.Tokens < 8) { // TODO: account for don't-discard info var discardOldestAction = PlayerAction.DiscardTile(PlayerIndex, m_lastGameState.YourHand.First(), "Discard oldest"); yield return(new Tuple <int, PlayerAction>(1, discardOldestAction)); } else { // Give totally arbitrary info // This should maybe try to at least indicate a definitely-unplayable tile or something // This logic is currently responsible for most of our 3-Fuse endgames var leastPlayable = m_lastGameState.AllHands().OrderBy(t => t.Number).Last(); var owner = m_lastGameState.WhoHas(leastPlayable.UniqueId); var arbitraryInfoAction = PlayerAction.GiveInfo(PlayerIndex, PlayerAction.PlayerActionInfoType.Number, owner, leastPlayable.Number, "Least playable thing I could find"); yield return(new Tuple <int, PlayerAction>(0, arbitraryInfoAction)); } }