/// <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> /// 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> /// 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; }