/// <summary> /// Creates a new melding move. /// </summary> /// <param name="meld">The meld being attempted.</param> /// <param name="new_tile">The tile taken from the discard, robbed from a kong, or a representative of a melded concealed kong.</param> /// <param name="mahjong">If true, this meld is to go out on.</param> public MahjongMove(MahjongMeld meld, Card new_tile, bool mahjong = false) { DiscardedTile = new_tile.Clone(); MeldTiles = meld.Clone(); Discard = false; Mahjong = mahjong; return; }
/// <summary> /// Determines if the given tiles forms an eye. /// </summary> /// <param name="tiles">The set of tiles to meld.</param> public static bool FormsEye(Hand tiles) { if (tiles.CardsInHand != 2) { return(false); // The meld construction catches this, of course, but this is a quick and dirty yes/no } MahjongMeld m = new MahjongMeld(tiles.Cards); return(m.Eye); }
/// <summary> /// Determines if the given tiles forms a chow, pong, or kong. /// </summary> /// <param name="tiles">The set of tiles to meld.</param> public static bool FormsCPK(Hand tiles) { if (tiles.CardsInHand < 3 || tiles.CardsInHand > 4) { return(false); // The meld construction catches this, of course, but this is a quick and dirty yes/no } MahjongMeld m = new MahjongMeld(tiles.Cards); return(m.Chow || m.Pung || m.Kong); }
/// <summary> /// Copies the data in hand and melds into new objects assigned to h and m. It then adds meld to m, add to h, and discards from h every tile in meld. /// Add can be null. /// </summary> private void CreateDuplicateMinusMeld(Hand hand, List <MahjongMeld> melds, MahjongMeld meld, Card add, out Hand h, out List <MahjongMeld> m) { MahjongStaticFunctions.CopyData(hand, melds, out h, out m); m.Add(meld); if (add != null) { h.DrawCard(add.Clone()); } foreach (Card c in meld.Cards) { h.PlayCard(c); } return; }
/// <summary> /// Creates a deep copy of this meld. /// </summary> /// <returns>Returns a copy of this meld.</returns> public MahjongMeld Clone() { MahjongMeld ret = new MahjongMeld(Cards); // We have to do this manually, because the above constructor could potentially clobber data ret.Chow = Chow; ret.Pung = Pung; ret.Kong = Kong; ret.Eye = Eye; ret.ThirteenOrphans = ThirteenOrphans; ret.NineGates = NineGates; ret.SevenPair = SevenPair; ret.Concealed = Concealed; ret.ExposedFromExposed = ExposedFromExposed; return(ret); }
/// <summary> /// Returns an enumerator of the possible moves at the given state. /// </summary> /// <param name="state">The state to enumerate the moves of.</param> private IEnumerator <MahjongMove> GetMoveEnumerator(MahjongGameState state) { List <MahjongMove> moves = new List <MahjongMove>(); // If the hand is done (or the game), then there's nothing left to enumerate. if (state.HandFinished || state.GameFinished) { return(moves.GetEnumerator()); } // Get the player we're considering MahjongAIPlayer ai = state.GetPlayer(state.SubActivePlayer) as MahjongAIPlayer; MahjongPlayer mp = ai as MahjongPlayer; // The active player has different choices available than the responding players if (state.ActivePlayer == state.SubActivePlayer) { // Add the kongs first foreach (Card c in ai.CardsInHand.Cards) { if (!ContainsKong(moves, c) && ai.CardsInHand.CountCard(c) == 4) { List <Card> meld = new List <Card>(); for (int i = 0; i < 4; i++) { meld.Add(c.Clone()); } moves.Add(new MahjongMove(new MahjongMeld(meld, true), c)); // We're melding a kong, so mahjong is definitely false here } } // We can also meld a fourth tile into an existing pung foreach (MahjongMeld meld in mp.Melds) { if (meld.Pung && ai.CardsInHand.Cards.Contains(meld.Cards[0])) { List <Card> n_meld = new List <Card>(); for (int i = 0; i < 4; i++) { n_meld.Add(meld.Cards[0].Clone()); } moves.Add(new MahjongMove(new MahjongMeld(n_meld, false), meld.Cards[0])); // We're melding a kong, so mahjong is definitely false here } } // Add all of the discards next foreach (Card c in ai.CardsInHand.Cards) { moves.Add(new MahjongMove(c)); } // Lastly, we'll add the ability to declare mahjong if (MahjongStaticFunctions.HasMahjong(ai.CardsInHand, mp.Melds)) { moves.Add(new MahjongMove(true)); } } else { // We need a tile available to do anything but pass (there are a few cases were this would not be available) if (state.AvailableTile != null) { // First, add chow moves if we can if (state.SubActivePlayer == state.NextPlayer) { List <MahjongMeld> chows = GetChows(ai.CardsInHand, state.AvailableTile); foreach (MahjongMeld m in chows) { Hand h; List <MahjongMeld> melds; CreateDuplicateMinusMeld(ai.CardsInHand, mp.Melds, m, state.AvailableTile, out h, out melds); moves.Add(new MahjongMove(m, state.AvailableTile, MahjongStaticFunctions.HasMahjong(h, melds))); } } // Add the ability to meld pungs and kongs int count = ai.CardsInHand.CountCard(state.AvailableTile); // We'll use this again after pung/kong checks, so this is extra fine to keep around if (count > 1) { // First, pungs are always possible List <Card> cards = new List <Card>(); cards.Add(state.AvailableTile.Clone()); cards.Add(state.AvailableTile.Clone()); cards.Add(state.AvailableTile.Clone()); MahjongMeld m = new MahjongMeld(cards); Hand h; List <MahjongMeld> melds; CreateDuplicateMinusMeld(ai.CardsInHand, mp.Melds, m, state.AvailableTile, out h, out melds); moves.Add(new MahjongMove(m, state.AvailableTile, MahjongStaticFunctions.HasMahjong(h, melds))); // Now create a kong move if possible if (count > 2) { cards.Add(state.AvailableTile.Clone()); m = new MahjongMeld(cards); CreateDuplicateMinusMeld(ai.CardsInHand, mp.Melds, m, state.AvailableTile, out h, out melds); moves.Add(new MahjongMove(m, state.AvailableTile, MahjongStaticFunctions.HasMahjong(h, melds))); } } // Now add the ability to declare mahjong through non-standard means // Nine gates can only be won on a self pick, so let's deal with thirteen orphans and seven pair first Hand htemp = new StandardHand(ai.CardsInHand.Cards); htemp.DrawCard(state.AvailableTile.Clone()); MahjongMeld mtemp = new MahjongMeld(htemp.Cards); if (mtemp.SevenPair || mtemp.ThirteenOrphans) { moves.Add(new MahjongMove(mtemp, state.AvailableTile, true)); } // The last mahjong we need to check now is if we can form an eye and win if (count > 0) { List <Card> cards = new List <Card>(); cards.Add(state.AvailableTile.Clone()); cards.Add(state.AvailableTile.Clone()); MahjongMeld m = new MahjongMeld(cards); Hand h; List <MahjongMeld> melds; CreateDuplicateMinusMeld(ai.CardsInHand, mp.Melds, m, state.AvailableTile, out h, out melds); if (MahjongStaticFunctions.HasMahjong(h, melds)) { moves.Add(new MahjongMove(m, state.AvailableTile, true)); } } } // Lastly, add the simple pass option moves.Add(new MahjongMove()); } return(moves.GetEnumerator()); }
/// <summary> /// Determines the next move for this AI to make. /// </summary> /// <param name="state">The state of the game. This will be cloned so that the AI does not affect the actual game state.</param> /// <returns>Returns the next move to make based on the current game state.</returns> public MahjongMove GetNextMove(GameState <MahjongMove> state) { MahjongGameState s = state as MahjongGameState; MahjongAIPlayer ai = state.GetPlayer(Player) as MahjongAIPlayer; MahjongPlayer mp = ai as MahjongPlayer; // Pass if it's not this AI's turn and it can't meld a pung or kong if (s.ActivePlayer != Player) { if (s.AvailableTile == null) { return(new MahjongMove()); // The active player is going out, so there's nothing we can do } else { switch (ai.CardsInHand.CountCard(s.AvailableTile)) { case 1: // If this is the last tile we needed to declare mahjong, go for it if (ai.CardsInHand.CardsInHand == 1) { List <Card> eye = new List <Card>(); eye.Add(s.AvailableTile.Clone()); eye.Add(s.AvailableTile.Clone()); return(new MahjongMove(new MahjongMeld(eye, false), s.AvailableTile, true)); } // Pass otherwise return(new MahjongMove()); case 2: List <Card> p2 = new List <Card>(); p2.Add(s.AvailableTile.Clone()); p2.Add(s.AvailableTile.Clone()); p2.Add(s.AvailableTile.Clone()); Hand htemp2 = new StandardHand(ai.CardsInHand.Cards); htemp2.PlayCard(s.AvailableTile); htemp2.PlayCard(s.AvailableTile); MahjongMeld m2 = new MahjongMeld(p2, false); List <MahjongMeld> melds2 = new List <MahjongMeld>(); foreach (MahjongMeld m22 in mp.Melds) { melds2.Add(m22.Clone()); } return(new MahjongMove(m2, s.AvailableTile, MahjongStaticFunctions.HasMahjong(htemp2, melds2))); case 3: List <Card> p3 = new List <Card>(); p3.Add(s.AvailableTile.Clone()); p3.Add(s.AvailableTile.Clone()); p3.Add(s.AvailableTile.Clone()); p3.Add(s.AvailableTile.Clone()); Hand htemp3 = new StandardHand(ai.CardsInHand.Cards); htemp3.PlayCard(s.AvailableTile); htemp3.PlayCard(s.AvailableTile); htemp3.PlayCard(s.AvailableTile); MahjongMeld m3 = new MahjongMeld(p3, false); List <MahjongMeld> melds3 = new List <MahjongMeld>(); foreach (MahjongMeld m33 in mp.Melds) { melds3.Add(m33.Clone()); } return(new MahjongMove(m3, s.AvailableTile, MahjongStaticFunctions.HasMahjong(htemp3, melds3))); default: return(new MahjongMove()); // If we naturally drew a kong, it'll stick around until we're forced to discard one of the tiles or just lose } } } // If it's our turn, check if we have mahjong if (MahjongStaticFunctions.HasMahjong(ai.CardsInHand, mp.Melds)) { return(new MahjongMove(true)); } // Find all the singletons List <int> singletons = new List <int>(); for (int i = 0; i < ai.CardsInHand.CardsInHand; i++) { if (ai.CardsInHand.CountCard(ai.CardsInHand.Cards[i]) == 1) { singletons.Add(i); } } // If we don't have a singleton tile, we'll have to part with another if (singletons.Count == 0) { singletons.Add(rand.Next(0, (int)ai.CardsInHand.CardsInHand)); rc++; } // Pick a random singleton tile to discard int r = rand.Next(0, singletons.Count); rc++; return(new MahjongMove(ai.CardsInHand.Cards[r])); }
/// <summary> /// The recursive version of the GetAllMahjongs function. /// This algorithm does not clobber data. /// </summary> /// <param name="hand">The set of tiles free to make melds as desired.</param> /// <param name="melds">The set of melds that cannot be changed.</param> /// <param name="ret">The set to add new winning hands to.</param> private static void GetAllMahjongs(Hand hand, List <MahjongMeld> melds, List <List <MahjongMeld> > ret) { // If we have less than 2 tiles left, something has gone horribly wrong if (hand.CardsInHand < 2) { return; } // If we have a limit hand, then get it out of the way right away if (hand.CardsInHand == 14) // Note that this can only happen if we have no fixed melds { MahjongMeld meld = new MahjongMeld(hand.Cards, true); if (meld.Valid) { List <MahjongMeld> temp = new List <MahjongMeld>(); temp.Add(meld); ret.Add(temp); } } // If we have two tiles left, then we need them to be an eye if (hand.CardsInHand == 2) { MahjongMeld meld = new MahjongMeld(hand.Cards, true); // If we don't have an eye, this won't form a winning hand if (!meld.Eye) { return; } // If we have an eye, then we need to make sure what we have is valid // Copying the data into a new list here is very important, because we're going to return a whole bunch of lists that need to all be distinct List <MahjongMeld> melds2 = new List <MahjongMeld>(); foreach (MahjongMeld m in melds) { melds2.Add(m.Clone()); } melds2.Add(meld); if (IsMahjong(melds2)) // It should be, but it's possible we were originally passed weird melds { ret.Add(melds2); } return; // We have nothing further to do, so return } // A simple check for failure // We must have an eye and melds of size 3 as we don't have extra tiles to make kongs if ((hand.CardsInHand - 2) % 3 != 0) { return; } // This call for BRUTE FORCE for (int i = 0; i < hand.CardsInHand - 2; i++) { for (int j = i + 1; j < hand.CardsInHand - 1; j++) { for (int k = j + 1; k < hand.CardsInHand; k++) { // Check if (i,j,k) forms a meld List <Card> l = new List <Card>(); l.Add(hand.Cards[i]); l.Add(hand.Cards[j]); l.Add(hand.Cards[k]); MahjongMeld meld = new MahjongMeld(l, true); // If we do have a meld, meld it and go deeper if (meld.Valid) { // Add the meld melds.Add(meld); // We want to preserve card order in the hand, and the easiest way to do that is to just make a new hand Hand temp = new StandardHand(hand.Cards); // i < j < k, so this is fine temp.PlayCard(k); temp.PlayCard(j); temp.PlayCard(i); // Go deeper GetAllMahjongs(temp, melds, ret); // Remove the meld melds.RemoveAt(melds.Count - 1); } } } } return; }
/// <summary> /// Counts the number of fan in the given hand. This only works on completed hands. /// Returns -1 if the melds do not form a valid hand. /// </summary> /// <param name="melds">The melds of the hand.</param> /// <param name="seat_wind">The player's seat wind.</param> /// <param name="prevailing_wind">The round's prevailing wind.</param> /// <param name="self_pick">If true, then the player won by drawing from the wall.</param> /// <param name="last_catch">If true, then the player won by drawing the last tile from the wall.</param> /// <param name="win_by_replacement">If true, then the player won by drawing a replacement tile from a kong or bonus tile.</param> /// <param name="double_replacement">If true, then the player won on a replacement tile for a replacement tile.</param> /// <param name="robbing_kong">If true, then the player won by robbing the kong.</param> /// <param name="heavenly_hand">If true, then the player won on their starting hand (only applies to east).</param> /// <param name="earthly_hand">If true, then the player won on east's discard.</param> /// <returns>Returns the fan of the provided hand.</returns> public static int HandFan(List <MahjongMeld> melds, SuitIdentifier seat_wind, SuitIdentifier prevailing_wind, bool self_pick = false, bool last_catch = false, bool win_by_replacement = false, bool double_replacement = false, bool robbing_kong = false, bool heavenly_hand = false, bool earthly_hand = false) { // If we don't have a hand, abandon ship if (!IsMahjong(melds)) { return(-1); } // We know we have a winning hand, so ensure we get the limit value if (melds.Count == 1 || double_replacement || heavenly_hand || earthly_hand) { return(int.MaxValue); } // For convenience MahjongMeld eye = null; List <MahjongMeld> leye = new List <MahjongMeld>(); List <MahjongMeld> cpk_melds = new List <MahjongMeld>(); foreach (MahjongMeld meld in melds) { if (meld.Eye) { eye = meld.Clone(); // There is exactly one of these since we know we have a winning hand that isn't a special hand } else { cpk_melds.Add(meld); // Since we have a winning hand, everything but the eye is a chow, pung, or kong } } leye.Add(eye); // Check for limit hands first int winds = ContainsSuit(cpk_melds, SuitIdentifier.EAST_WIND) + ContainsSuit(cpk_melds, SuitIdentifier.SOUTH_WIND) + ContainsSuit(cpk_melds, SuitIdentifier.WEST_WIND) + ContainsSuit(cpk_melds, SuitIdentifier.NORTH_WIND); // Great winds if (winds == 4) { return(int.MaxValue); } // Small winds if (winds == 3 && ContainsSuit(leye, SuitIdentifier.EAST_WIND) + ContainsSuit(leye, SuitIdentifier.SOUTH_WIND) + ContainsSuit(leye, SuitIdentifier.WEST_WIND) + ContainsSuit(leye, SuitIdentifier.NORTH_WIND) == 1) { return(int.MaxValue); } // All kongs and self triplets bool all_kongs = true; bool self_pks = self_pick; // Self triplets must be won by self pick foreach (MahjongMeld meld in cpk_melds) { if (!meld.Kong) { all_kongs = false; } if (!meld.Concealed || !(meld.Pung || meld.Kong)) { self_pks = false; } } if (all_kongs || self_pks) { return(int.MaxValue); } // All orphans bool ophans = true; foreach (MahjongMeld meld in cpk_melds) // Here we accept melds of ones and nines only { if (!meld.Pung && !meld.Kong || !ApproxEqual(meld.Cards[0].Value.MaxValue, 1.0) && !ApproxEqual(meld.Cards[0].Value.MaxValue, 9.0)) { ophans = false; } } if (ophans) { return(int.MaxValue); } // We have a boring hand, so calculate its points int ret = 0; // The below all have their points reduced so that the presence of the dragons/winds will bump up their point values as appropriate later if (ContainsSuit(cpk_melds, SuitIdentifier.GREEN_DRAGON) + ContainsSuit(cpk_melds, SuitIdentifier.RED_DRAGON) + ContainsSuit(cpk_melds, SuitIdentifier.WHITE_DRAGON) == 3) { ret = 5; // Great dragon: 8 (5 + 3) } else if (ContainsSuit(cpk_melds, SuitIdentifier.GREEN_DRAGON) + ContainsSuit(cpk_melds, SuitIdentifier.RED_DRAGON) + ContainsSuit(cpk_melds, SuitIdentifier.WHITE_DRAGON) == 2 && ContainsSuit(leye, SuitIdentifier.GREEN_DRAGON) + ContainsSuit(leye, SuitIdentifier.RED_DRAGON) + ContainsSuit(leye, SuitIdentifier.WHITE_DRAGON) == 1) { ret = 3; // Small dragon: 5 (3 + 2) } else if (ContainsTileType(cpk_melds, SuitColor.HONOURS) + ContainsTileType(leye, SuitColor.HONOURS) == 5) { ret = 10 - ContainsSuit(cpk_melds, SuitIdentifier.GREEN_DRAGON) - ContainsSuit(cpk_melds, SuitIdentifier.RED_DRAGON) - ContainsSuit(cpk_melds, SuitIdentifier.WHITE_DRAGON); // All honours: 10 (10 - drags + drags) } else if (ContainsSuit(cpk_melds, SuitIdentifier.BAMBOO) + ContainsSuit(leye, SuitIdentifier.BAMBOO) == 5 || ContainsSuit(cpk_melds, SuitIdentifier.CHARACTER) + ContainsSuit(leye, SuitIdentifier.CHARACTER) == 5 || ContainsSuit(cpk_melds, SuitIdentifier.DOT) + ContainsSuit(leye, SuitIdentifier.DOT) == 5) { ret = 7; // All one suit: 7 } else if (ContainsSuit(cpk_melds, SuitIdentifier.BAMBOO) + ContainsSuit(leye, SuitIdentifier.BAMBOO) + ContainsTileType(cpk_melds, SuitColor.HONOURS) + ContainsTileType(leye, SuitColor.HONOURS) == 5 || ContainsSuit(cpk_melds, SuitIdentifier.CHARACTER) + ContainsSuit(leye, SuitIdentifier.CHARACTER) + ContainsTileType(cpk_melds, SuitColor.HONOURS) + ContainsTileType(leye, SuitColor.HONOURS) == 5 || ContainsSuit(cpk_melds, SuitIdentifier.DOT) + ContainsSuit(leye, SuitIdentifier.DOT) + ContainsTileType(cpk_melds, SuitColor.HONOURS) + ContainsTileType(leye, SuitColor.HONOURS) == 5) { ret = 3; // Mixed one suit: 3 } else { bool all_chow = true; bool all_in_triplet = true; foreach (MahjongMeld meld in cpk_melds) { if (meld.Chow) { all_in_triplet = false; } else { all_chow = false; } } if (all_in_triplet) { ret = 3; // All in triplet } else if (all_chow) { ret = 1; // All chow } } // The above are calculated so that we can add these special points without worrying over what type of hand we had for double counting if (ContainsSuit(cpk_melds, seat_wind) > 0) { ret++; } if (ContainsSuit(cpk_melds, prevailing_wind) > 0) { ret++; } if (ContainsSuit(cpk_melds, SuitIdentifier.RED_DRAGON) > 0) { ret++; } if (ContainsSuit(cpk_melds, SuitIdentifier.GREEN_DRAGON) > 0) { ret++; } if (ContainsSuit(cpk_melds, SuitIdentifier.WHITE_DRAGON) > 0) { ret++; } // All oprhans is a limit hand, so the mixed orphans bonus is fine to put here like this bool mixed_ophans = true; foreach (MahjongMeld meld in cpk_melds) // Here we accept melds of honours, ones, and nines only { if (!meld.Pung && !meld.Kong || !ApproxEqual(meld.Cards[0].Value.MaxValue, 1.0) && !ApproxEqual(meld.Cards[0].Value.MaxValue, 9.0) && meld.Cards[0].Suit.Color != SuitColor.HONOURS) { mixed_ophans = false; } } if (mixed_ophans) { ret++; } // Self pick is drawing the winning tile if (self_pick) { ret++; } if (robbing_kong) { ret++; } if (last_catch) { ret++; } if (win_by_replacement) { ret++; } // Win from wall is if you never melded from a discard, that is all melds are concealed bool all_concealed = true; for (int i = 0; i < melds.Count; i++) { if (melds[i].Exposed) { all_concealed = false; break; } } if (all_concealed) { ret++; } return(ret); }