Esempio n. 1
0
        /// <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;
        }
Esempio n. 2
0
        /// <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;
        }
Esempio n. 3
0
        /// <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;
        }
Esempio n. 4
0
        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);
        }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 6
0
        /// <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);
        }