/// <summary> /// Handles all the logic necessary for a declaration of mahjong, including scoring and setting up the next round, and, if necessary, delcaring the game finished. /// </summary> protected void Mahjong(int player_index, bool stolen_from_kong = false) { MahjongPlayer[] mp = new MahjongPlayer[4]; for (int i = 0; i < 4; i++) { mp[i] = players[i] as MahjongPlayer; } // Score the hand int fan = MahjongStaticFunctions.HandFan(mp[player_index].Melds, mp[player_index].SeatWind, PrevailingWind, player_moves[player_index].SelfPick, Deck.CountDrawPile == 0, OnReplacement > 0, OnReplacement > 1, stolen_from_kong, Heavenly, Earthly); int base_points = MahjongStaticFunctions.FanToBasePoints(fan); // Payout the score for (int i = 0; i < 4; i++) { if (i != player_index) { int factor = payment_factor(player_index, i); mp[i].Score -= factor * base_points; mp[player_index].Score += factor * base_points; } } // Go to the next hand if (mp[player_index].SeatWind == SuitIdentifier.EAST_WIND) { // East won, so we have a bonus hand BonusHand++; if (BonusHand > MaxBonusHand) { BonusHand = 0; Hand++; } } else { // East didn't win, so no bonus hand BonusHand = 0; Hand++; } if (!GameFinished) { InitHand(); } return; }
/// <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; }
/// <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 if (s.ActivePlayer != Player) { return(new MahjongMove()); } if (MahjongStaticFunctions.HasMahjong(ai.CardsInHand, mp.Melds)) { return(new MahjongMove(true)); } // Pick a random tile to discard int r = rand.Next(0, (int)ai.CardsInHand.CardsInHand); rc++; return(new MahjongMove(ai.CardsInHand.Cards[r])); }
/// <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()); }
/// <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> /// Given a move encoded in m, will perform all the necessary work to make the player reflect the move occuring. /// </summary> /// <param name="m">The move to make. It is assumed that the move is valid (and not null).</param> /// <returns>Returns true if the move is valid and false otherwise.</returns> public override bool MakePlay(MahjongMove m) { // We assume that the move we're given is valid, so let's jump right in if (m.Pass) { return(true); } // Discarding is simple to deal with if (m.Discard) { CardsInHand.PlayCard(m.DiscardedTile); return(true); } // Melding takes a bit of work // Note that if we're melding to declare mahjong, the meld is fixed and must happen, so we do it here always if (m.Meld) { // First decide if we're upgrading a pung bool upgrade = false; if (m.MeldTiles.Kong) { foreach (MahjongMeld meld in Melds) { if (meld.Pung && meld.Cards.Contains(m.DiscardedTile)) { upgrade = true; break; } } } if (upgrade) { // If we're adding to a pung to make a kong, we only need to play the tile we drew (and we HAVE to have drawn the tile from the wall) CardsInHand.PlayCard(m.DiscardedTile); // We do also have to update the meld we already have foreach (MahjongMeld meld in Melds) { if (meld.ConvertToKong(m.DiscardedTile)) // This will eventually work and fail all other times { break; } } } else { // Add the meld Melds.Add(m.MeldTiles.Clone()); // We're not expanding a pung, so we don't have to worry about that case now List <Card> meld = new List <Card>(m.MeldTiles.Cards); if (!m.ConcealedMeld) // If this is a concealed meld, then we drew the tile we're currently melding { meld.Remove(m.DiscardedTile); // We don't need to play the tile we don't technically have yet } foreach (Card c in meld) { CardsInHand.PlayCard(c); } } } // If we have Mahjong, we need to put whatever is left of the player's hand into melds // This can occur as a meld is declared, so we don't return early before to catch that case here if (m.Mahjong) { List <MahjongMeld> mm = MahjongStaticFunctions.BestMahjong(CardsInHand, Melds, SeatWind, PrevailingWind, m.SelfPick); if (mm != null) // This should always be the case, but let's be careful { Melds = mm; } } return(true); }