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