/// <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)); } }
/// <summary> /// A player took an action! /// </summary> public void RecordPlayerTurn(Turn turn, int turnNumber) { // Increase the age of info foreach (var tile in m_infoLookup.Values) { tile.AgeInfo(); } switch (turn.Action.ActionType) { case PlayerAction.PlayerActionType.Info: { List <Guid> tiles = new List <Guid>(); // Go through the tiles included in the give and record what we now know about them foreach (Guid g in turn.TargetedTiles) { var lookup = Lookup(g); if (turn.Action.InfoType == PlayerAction.PlayerActionInfoType.Suit) { lookup.MustBe((Suit)turn.Action.Info, "Given Info"); lookup.GotInfo(); } else if (turn.Action.InfoType == PlayerAction.PlayerActionInfoType.Number) { lookup.MustBe(turn.Action.Info, "Given Info"); lookup.GotInfo(); } // If the targeted tile might be playable at all, remember it if (lookup.IsPossiblyPlayable(m_lastGameState)) { tiles.Add(g); } } if (m_allowFinesses) { if (turn.Action.TargetPlayer == PlayerIndex) { // Info was given to you, double check if it might be a finesse var finesseTargets = GetPossibleFinesseTargets(); // Ignore any finesse targets that are in the hand of the person giving the info, they can't see those finesseTargets = finesseTargets.Where(t => m_lastGameState.WhoHas(t.UniqueId) != turn.Action.ActingPlayer); // If there are possible finesses if (finesseTargets.Any()) { // Possible values our info could have if they're finessing each target var infoValueList = finesseTargets.Select(t => new TileValue(t.Suit, t.Number + 1)).ToList(); foreach (var infoTarget in turn.TargetedTiles) { foreach (var finesseTarget in finesseTargets) { var targetValueList = new TileValueList(); targetValueList.Add(new TileValue(finesseTarget.Suit, finesseTarget.Number)); var finesse = new Finesse(m_lastGameState, infoTarget, finesseTarget.UniqueId, infoValueList, targetValueList, turnNumber, turn.Action.ActingPlayer); m_activeFinesses.Add(finesse); } } } } else { // Info was given to someone else, see if you're being finessed var actualTiles = turn.TargetedTiles.Select(g => m_lastGameState.AllHands().First(t => t.UniqueId == g)); // If this info give is all unplayable tiles, it's gotta be a finesse if (!actualTiles.Any(t => m_lastGameState.IsPlayable(t))) { var finesseTargets = GetPossibleFinesseTargets(); var missingTargets = new List <Tile>(); foreach (var target in turn.TargetedTiles) { // Find this info tile var infoTile = m_lastGameState.AllHands().Where(t => t.UniqueId == target).First(); // See if the finesse targets list contains a corresponding tile var finesseTarget = finesseTargets.Where(t => t.Suit == infoTile.Suit && t.Number == infoTile.Number - 1).FirstOrDefault(); var infoValues = new TileValueList(); infoValues.Add(new TileValue(infoTile.Suit, infoTile.Number)); if (finesseTarget != null) { var targetValues = new TileValueList(); targetValues.Add(new TileValue(finesseTarget.Suit, finesseTarget.Number)); var finesse = new Finesse(m_lastGameState, infoTile.UniqueId, finesseTarget.UniqueId, infoValues, targetValues, turnNumber, turn.Action.ActingPlayer); m_activeFinesses.Add(finesse); } else { // We must have the target! var myNewest = m_lastGameState.YourHand.Last(); var targetValues = new TileValueList(); targetValues.Add(new TileValue(infoTile.Suit, infoTile.Number - 1)); var finesse = new Finesse(m_lastGameState, infoTile.UniqueId, myNewest, infoValues, targetValues, turnNumber, turn.Action.ActingPlayer); m_activeFinesses.Add(finesse); } } } } } if (tiles.Any()) { // Modify our strength value for all targeted tiles that could possibly be playable ModifyPlayStrength(turn.Action.TargetPlayer, tiles); } // Now go through all other tiles in that hand and mark the negative info we got foreach (Guid g in TilesInHand(turn.Action.TargetPlayer).Except(turn.TargetedTiles)) { var lookup = Lookup(g); if (turn.Action.InfoType == PlayerAction.PlayerActionInfoType.Suit) { lookup.CannotBe((Suit)turn.Action.Info, "Negative info"); } else if (turn.Action.InfoType == PlayerAction.PlayerActionInfoType.Number) { lookup.CannotBe(turn.Action.Info, "Negative info"); } } } break; } }