/// <summary> /// Creates a deep copy of this player. /// </summary> /// <returns>Returns a deep copy of this player.</returns> public override Player <MahjongMove> Clone() { MahjongAIPlayer ret = new MahjongAIPlayer(CardsInHand.Cards, AI); ret.CopyData(this); return(ret); }
public static void Main(string[] args) { // Setup output information string fout = "Game " + DEPTH + "-" + SAMPLES + ".txt"; string header = "Minimax Search Depth: " + DEPTH + "\nMonte Carlo Sample Size: " + SAMPLES + "\n\nFinal Scores\n------------\n"; string results = ""; string game_text = ""; // Create the game MahjongGameState state = new MahjongGameState(); // Add the AI state.PlayerLeft(0, new NaiveAI(0)); state.PlayerLeft(1, new GreedyAI(1)); state.PlayerLeft(2, new MiniMaxAI(2, DEPTH)); state.PlayerLeft(3, new MonteCarloAI(3, SAMPLES)); int i = 4; // Run the game to completion while (!state.GameFinished) { MahjongAIPlayer ai = state.GetPlayer(state.SubActivePlayer) as MahjongAIPlayer; MahjongMove move = ai.AI.GetNextMove(state); Log(PlayerToName(state.SubActivePlayer) + " " + move + ".\n", ref game_text); if (--i == 0) { Log("\n", ref game_text); i = 4; } if (!state.ApplyMove(move)) { Log("An invalid move has been provided.\n", ref game_text); } if (state.HandFinished && !state.GameFinished) { Print(state, ref game_text); state.StartHand(); } } // Output the final scores PrintLight(state, ref results); Print(state, ref game_text, true); File.WriteAllText(fout, header + results + "\nGame Log\n--------\n" + game_text); return; }
/// <summary> /// If a player leaves the game then they are replaced with an AI player. /// </summary> /// <param name="index">The index of the player that left.</param> /// <param name="replacement">The replacement AI. If null then a default behavior will be used.</param> /// <remarks>An AI player can leave the game to be replaced by a new AI player.</remarks> public void PlayerLeft(int index, AIBehavior <MahjongMove> replacement = null) { if (index < 0 || index > 4) { return; } MahjongPlayer old = players[index] as MahjongPlayer; players[index] = new MahjongAIPlayer(players[index].CardsInHand.Cards, replacement); (players[index] as MahjongPlayer).CopyData(old); 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])); }