/// <summary> /// Creates a new honours suit. /// <param name="type">The type of simple suit to make. Only valid if the type is simple. Defaults to east wind otherwise.</param> /// </summary> public HonourSuit(SuitIdentifier type) : base() { if (((int)type & (int)SuitColor.HONOURS) > 0) { ID = type; } else { ID = SuitIdentifier.EAST_WIND; } return; }
/// <summary> /// Creates a new simple suit. /// <param name="type">The type of simple suit to make. Only valid if the type is simple. Defaults to bamboo otherwise.</param> /// </summary> public SimpleSuit(SuitIdentifier type) : base() { if (((int)type & (int)SuitColor.SIMPLE) > 0) { ID = type; } else { ID = SuitIdentifier.BAMBOO; } return; }
/// <summary> /// Creates a new bonus suit. /// <param name="type">The type of simple suit to make. Only valid if the type is simple. Defaults to flowers otherwise.</param> /// </summary> public BonusSuit(SuitIdentifier type) : base() { if (((int)type & (int)SuitColor.BONUS) > 0) { ID = type; } else { ID = SuitIdentifier.FLOWER; } return; }
private static int ContainsSuit(List <MahjongMeld> melds, SuitIdentifier suit) { int ret = 0; foreach (MahjongMeld meld in melds) { if (meld.Cards[0].Suit.ID == suit) // Melds can only be made of uniform suits, so this is fine { ret++; } } return(ret); }
/// <summary> /// Takes a hand and a set of fixed melds and returns the highest scoring winning hand possible. /// 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="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> /// <returns>The best mahjong that can be formed from the given tiles or null if no mahjong can be formed.</returns> public static List <MahjongMeld> BestMahjong(Hand hand, List <MahjongMeld> melds, SuitIdentifier seat_wind, SuitIdentifier prevailing_wind, bool self_pick = false) { List <MahjongMeld> ret = null; int max = -1; // Note that if the list is empty, we default to returning null as desired foreach (List <MahjongMeld> mahjong in GetAllMahjongs(hand, melds)) { int fan = HandFan(mahjong, seat_wind, prevailing_wind, self_pick); if (fan > max) { ret = mahjong; max = fan; } } return(ret); }
/// <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); }