Beispiel #1
0
        /// <summary>
        /// Creates a new player for a Mahjong game.
        /// </summary>
        /// <param name="cards">The cards this player starts with.</param>
        public MahjongHumanPlayer(IEnumerable <Card> cards) : base(cards)
        {
            Score      = 0;
            BonusTiles = new StandardHand(cards);

            SeatWind       = SuitIdentifier.EAST_WIND;
            PrevailingWind = SuitIdentifier.EAST_WIND;

            Melds = new List <MahjongMeld>();
            return;
        }
        /// <summary>
        /// Creates a new player for a Mahjong game.
        /// </summary>
        /// <param name="cards">The cards this player starts with.</param>
        public MahjongAIPlayer(IEnumerable <Card> cards, AIBehavior <MahjongMove> behaviour) : base(cards, behaviour)
        {
            Score      = 0;
            BonusTiles = new StandardHand(cards);

            SeatWind       = SuitIdentifier.EAST_WIND;
            PrevailingWind = SuitIdentifier.EAST_WIND;

            Melds = new List <MahjongMeld>();
            return;
        }
Beispiel #3
0
        /// <summary>
        /// Creates a deep copy in the manner expected.
        /// </summary>
        public static void CopyData(Hand hand, List <MahjongMeld> melds, out Hand h, out List <MahjongMeld> m)
        {
            h = new StandardHand(hand.Cards);
            m = new List <MahjongMeld>();

            foreach (MahjongMeld meld in melds)
            {
                m.Add(meld.Clone());
            }

            return;
        }
Beispiel #4
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());
        }
Beispiel #5
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]));
        }
Beispiel #6
0
 /// <summary>
 /// Creates a new player for a game.
 /// </summary>
 /// <param name="cards">The cards this player starts with.</param>
 public StandardPlayer(IEnumerable <Card> cards)
 {
     CardsInHand = new StandardHand(cards);
     return;
 }
Beispiel #7
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;
        }
Beispiel #8
0
        /// <summary>
        /// Creates a new meld from the given tiles.
        /// </summary>
        /// <param name="meld">The tiles to meld.</param>
        /// <param name="concealed">If true, the meld will be treated as concealed.</param>
        public MahjongMeld(IEnumerable <Card> meld, bool concealed = false) : base(meld)
        {
            Chow = false;
            Pung = false;
            Kong = false;
            Eye  = false;

            ThirteenOrphans = false;
            NineGates       = false;
            SevenPair       = false;

            Concealed          = concealed;
            ExposedFromExposed = false;

            // There are two special cases we need to handle
            // There's thirteen orphans and nine gates (the latter is a special case because it must be all concealed and has meaning beyond the melds it makes)
            if (CardsInHand == 14)
            {
                // Thirteen orphans is easier to check, so do that first
                Card[] orphans = { new StandardCard(new SimpleSuit(SuitIdentifier.BAMBOO),       new ValueN(1, "One")), new StandardCard(new SimpleSuit(SuitIdentifier.BAMBOO),    new ValueN(9,                                               "Nine")),
                                   new StandardCard(new SimpleSuit(SuitIdentifier.CHARACTER),    new ValueN(1, "One")), new StandardCard(new SimpleSuit(SuitIdentifier.CHARACTER), new ValueN(9,                                               "Nine")),
                                   new StandardCard(new SimpleSuit(SuitIdentifier.DOT),          new ValueN(1, "One")), new StandardCard(new SimpleSuit(SuitIdentifier.DOT),       new ValueN(9,                                               "Nine")),
                                   new StandardCard(new HonourSuit(SuitIdentifier.GREEN_DRAGON), new ValueN(0, null),   "Green Dragon"),                                           new StandardCard(new HonourSuit(SuitIdentifier.RED_DRAGON), new ValueN(0,null),  "Red Dragon"), new StandardCard(new HonourSuit(SuitIdentifier.WHITE_DRAGON), new ValueN(0, null), "White Dragon"),
                                   new StandardCard(new HonourSuit(SuitIdentifier.EAST_WIND),    new ValueN(0, null),   "East Wind"),                                              new StandardCard(new HonourSuit(SuitIdentifier.SOUTH_WIND), new ValueN(0,null),  "South Wind"), new StandardCard(new HonourSuit(SuitIdentifier.WEST_WIND),    new ValueN(0, null), "West Wind"),   new StandardCard(new HonourSuit(SuitIdentifier.NORTH_WIND),new ValueN(0, null), "North Wind") };

                for (int i = 0; i < 13; i++)
                {
                    if (!Cards.Contains(orphans[i]))
                    {
                        break;
                    }
                    else if (i == 12)
                    {
                        ThirteenOrphans = true;
                        return;
                    }
                }

                // We don't have thirteen orphans, so check for seven pairs
                Hand temp = new StandardHand(Cards);

                // Check for pairs by sheer brute force
                for (int i = 0; i < 7; i++)
                {
                    Card c = temp.PlayCard(0);

                    if (!temp.Cards.Contains(c))
                    {
                        break;
                    }

                    temp.PlayCard(c);

                    if (i == 6)
                    {
                        SevenPair = true;
                        return;
                    }
                }

                // We don't have thirteen orphans or seven pairs, so check for nine gates
                CardSuit suit = Cards[0].Suit;

                // For optimisation, we check this here rather than letting it be implicitly check later by asking for values
                if (suit.Color != SuitColor.SIMPLE)
                {
                    return;
                }

                // Each card must be the same suit
                for (int i = 1; i < 14; i++)
                {
                    if (!suit.Equals(Cards[i].Suit))
                    {
                        return;
                    }
                }

                // Now we need the tiles 1112345678999 and one extra
                temp = new StandardHand(Cards);
                string[] value_names = { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };

                // Check if all the cards are there by brute force
                for (int i = 1; i < 10; i++)
                {
                    for (int j = (i == 1 || i == 9 ? 0 : 2); j < 3; j++)                  // Ones and nines need to be played thrice
                    {
                        Card c = new StandardCard(suit, new ValueN(i, value_names[i - 1]));

                        if (!temp.Cards.Contains(c))
                        {
                            return;
                        }

                        temp.PlayCard(c);
                    }
                }

                // The last card doesn't matter as we know it's the right suit, so we're done
                NineGates = true;
                return;
            }

            if (CardsInHand < 2 || CardsInHand > 4)
            {
                return;
            }

            if (CardsInHand == 2)
            {
                if (Cards[0].Equals(Cards[1]))
                {
                    Eye = true;
                }

                return;
            }

            // If we have four tiles, our only option is a kong
            if (CardsInHand == 4)
            {
                // A kong must have four equal tiles
                for (int i = 0; i < 3; i++)
                {
                    if (!Cards[i].Equals(Cards[i + 1]))
                    {
                        return;
                    }
                }

                Kong = true;
                return;
            }

            // If we have three tiles and the first two are equal, we either have a pung or nothing
            if (Cards[0].Equals(Cards[1]))
            {
                if (!Cards[1].Equals(Cards[2]))
                {
                    return;
                }
                else
                {
                    Pung = true;
                    return;
                }
            }

            // We now have a chow or nothing, so make sure the tiles are of the same suit and are sequential
            if (!Cards[0].Suit.Equals(Cards[1].Suit) || !Cards[1].Suit.Equals(Cards[2].Suit))
            {
                return;
            }

            SortByValue();

            if (!ApproxEqual(Cards[0].Value.MaxValue + 1.0, Cards[1].Value.MaxValue) || !ApproxEqual(Cards[1].Value.MaxValue + 1.0, Cards[2].Value.MaxValue))
            {
                return;
            }

            Chow = true;
            return;
        }