//#nullable enable using DominionBase.Enums; using DominionBase.Players; using DominionBase.Utilities; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Xml; using System.Xml.Serialization; namespace DominionBase { public delegate void GameMessageEventHandler(object sender, GameMessageEventArgs e); public delegate void GameEndingEventHandler(object sender, GameEndingEventArgs e); public delegate void GameEndedEventHandler(object sender, GameEndedEventArgs e); public delegate void CostComputeEventHandler(object sender, CostComputeEventArgs e); public abstract class GameMessage { public virtual bool CheckEndGame => false; public string Message { get; set; } public WaitCallback WaitCallback { get; set; } public GameMessage() { } public GameMessage(string message) : this(null, message) { } public GameMessage(WaitCallback waitCallback) { WaitCallback = waitCallback; } public GameMessage(WaitCallback waitCallback, string message) : this(waitCallback) { Message = message; } public virtual void ActBefore(IGame game) { } public virtual Thread ActAfter(IGame game) { return null; } } public class GameResponseMessage : GameMessage { } public class GameBuyMessage : GameMessage { public IPlayer Player { get; set; } public IBuyable Supply { get; set; } public GameBuyMessage() { } public GameBuyMessage(IPlayer player, IBuyable supply) : this(null, player, supply) { } public GameBuyMessage(WaitCallback waitCallback, IPlayer player, IBuyable supply) : base(waitCallback) { Player = player; Supply = supply; } public override void ActBefore(IGame game) { Player.Buy(Supply); } } public class GamePlayMessage : GameMessage { public IPlayer Player { get; set; } public Cards.Card Card { get; set; } public GamePlayMessage() { } public GamePlayMessage(IPlayer player, Cards.Card card) : this(null, player, card) { } public GamePlayMessage(WaitCallback waitCallback, IPlayer player, Cards.Card card) : base(waitCallback) { Player = player; Card = card; } public override void ActBefore(IGame game) { Player.PlayCard(Card); } } public class GameUndoPlayMessage : GameMessage { public IPlayer Player { get; set; } public Cards.Card Card { get; set; } public PhaseEnum Phase { get; set; } public GameUndoPlayMessage() { } public GameUndoPlayMessage(IPlayer player, Cards.Card card) : this(null, player, card) { } public GameUndoPlayMessage(IPlayer player, PhaseEnum phase) : this(null, player, phase) { Player = player; Phase = phase; } public GameUndoPlayMessage(WaitCallback waitCallback, IPlayer player, Cards.Card card) : base(waitCallback) { Player = player; Card = card; } public GameUndoPlayMessage(WaitCallback waitCallback, IPlayer player, PhaseEnum phase) : base(waitCallback) { Player = player; Phase = phase; } public override void ActBefore(IGame game) { if (Card != null) Player.UndoPlayCard(Card); //if (this.Phase != PhaseEnum.Waiting) Player.UndoPhaseChange(Phase); } } public class GameEndTurnMessage : GameMessage { public override bool CheckEndGame => true; public IPlayer Player { get; set; } public GameEndTurnMessage() { } public GameEndTurnMessage(Player player) : this(null, player) { } public GameEndTurnMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Contract.Requires(game != null, "game cannot be null"); var playerEnumerator = game.GetPlayersStartingWithEnumerator(Player); while (playerEnumerator.MoveNext()) playerEnumerator.Current.Cleanup(); } public override Thread ActAfter(IGame game) { Contract.Requires(game != null, "game cannot be null"); Player.EndTurn(); return game.SetNextPlayer(); } } public class GamePlayTreasuresMessage : GameMessage { public IPlayer Player { get; set; } public GamePlayTreasuresMessage() { } public GamePlayTreasuresMessage(Player player) : this(null, player) { } public GamePlayTreasuresMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Player.PlayTreasures(game); } } public class GameGoToBuyTreasurePhaseMessage : GameMessage { public IPlayer Player { get; set; } public GameGoToBuyTreasurePhaseMessage() { } public GameGoToBuyTreasurePhaseMessage(IPlayer player) : this(null, player) { } public GameGoToBuyTreasurePhaseMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Player.GoToBuyTreasurePhase(); } } public class GameGoToBuyPhaseMessage : GameMessage { public IPlayer Player { get; set; } public GameGoToBuyPhaseMessage() { } public GameGoToBuyPhaseMessage(Player player) : this(null, player) { } public GameGoToBuyPhaseMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Player.GoToBuyPhase(); } } public class GameGoToNightPhaseMessage : GameMessage { public IPlayer Player { get; set; } public GameGoToNightPhaseMessage() { } public GameGoToNightPhaseMessage(Player player) : this(null, player) { } public GameGoToNightPhaseMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Player.GoToNightPhase(); } } public class GamePlayTokensMessage : GameMessage { public IPlayer Player { get; set; } public Type Token { get; set; } public int Count { get; set; } public GamePlayTokensMessage() { } public GamePlayTokensMessage(IPlayer player, Type token, int count) : this(null, player, token, count) { } public GamePlayTokensMessage(WaitCallback waitCallback, IPlayer player, Type token, int count) : base(waitCallback) { Player = player; Token = token; Count = count; } public override void ActBefore(IGame game) { Player.PlayTokens(game, Token, Count); } } public class GameEndMessage : GameMessage { public IPlayer Player { get; set; } public GameEndMessage() { } public GameEndMessage(Player player) : this(null, player) { } public GameEndMessage(WaitCallback waitCallback, IPlayer player) : base(waitCallback) { Player = player; } public override void ActBefore(IGame game) { Contract.Requires(game != null, "game cannot be null"); game.Abort(); } } public class GameEndingEventArgs : EventArgs { } public class GameEndedEventArgs : EventArgs { } public class GameMessageEventArgs : EventArgs { public IPlayer AffectedPlayer { get; } public List Cards { get; } = new List(); public int Count { get; } = 1; public Currency Currency { get; } public string MessageType { get; } public IPlayer Player { get; } public ICardBase SourceCard { get; } public Token SourceToken { get; } public List Tokens { get; } = new List(); public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, int count = 1) { Player = player; SourceCard = sourceCard; Count = count; } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, params IDisplayable[] cards) : this(player, sourceCard) { if (cards == null) return; Cards.AddRange(cards); } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, string messageType, int count) : this(player, sourceCard) { MessageType = messageType; Count = count; } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, string messageType, params IDisplayable[] cards) : this(player, sourceCard) { MessageType = messageType; if (cards == null) return; Cards.AddRange(cards); } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, int count, params IDisplayable[] cards) : this(player, sourceCard, count) { if (cards == null) return; Cards.AddRange(cards); } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, IDisplayable card, int count, params Token[] token) : this(player, sourceCard, card) { Count = count; Tokens.AddRange(token); } public GameMessageEventArgs(IPlayer player, IPlayer playerAffected, ICardBase sourceCard, params IDisplayable[] cards) : this(player, sourceCard, cards) { AffectedPlayer = playerAffected; } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, string messageType, Currency currency) : this(player, sourceCard) { MessageType = messageType; Currency = currency; } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard, Currency currency) : this(player, sourceCard) { Currency = currency; } public GameMessageEventArgs(IPlayer player, Token sourceToken, int count = 0) { Player = player; SourceToken = sourceToken; Count = count; } public GameMessageEventArgs(IPlayer player, ICardBase sourceCard) { Player = player; SourceCard = sourceCard; } } public class CostComputeEventArgs : EventArgs { public ICost Card { get; } public Cards.Cost Cost { get; set; } public CostComputeEventArgs(ICost card, Cards.Cost cost) { if (card == null || cost == (Cards.Cost)null) return; Card = card; Cost = cost.Clone(); } } [Serializable] public class GameCreationException : Exception { public GameCreationException() { } public GameCreationException(string message) : base(message) { } public GameCreationException(string message, Exception innerException) : base(message, innerException) { } protected GameCreationException(SerializationInfo info, StreamingContext context) : base(info, context) { } } public enum GameState { Unknown, Setup, CardSelection, CardSetup, NotStarted, Running, Ended, Aborted } public class Game : IDisposable, IGame { public event GameMessageEventHandler GameMessage; public event GameEndingEventHandler GameEndingEvent; public event GameEndedEventHandler GameEndedEvent; public event CostComputeEventHandler CostCompute; private readonly int _EndgameSupplies = 3; private bool _ShouldStop; public DateTime StartTime { get; private set; } = DateTime.Now; public Random RNG { get; private set; } = new Random(); public Game() { } public Game(int numHumanPlayers, IEnumerable playerNames, IEnumerable aiTypes, GameSettings settings) { State = GameState.Setup; Settings = settings; Players = new PlayerCollection(this); // Add human players for (var i = 0; i < numHumanPlayers; i++) Players.Add(new Human(this, playerNames.ElementAt(i))); // Add AI players for (var i = numHumanPlayers; i < playerNames.Count(); i++) try { Players.Add((Player)Activator.CreateInstance(aiTypes.ElementAt(i), this, playerNames.ElementAt(i))); } catch (TargetInvocationException tie) { if (tie.InnerException != null) throw tie.InnerException; } Players.Shuffle(); if (playerNames.Count() > 4) _EndgameSupplies = 4; State = GameState.CardSelection; } public void SelectCards() { if (State != GameState.CardSelection) throw new GameCreationException("This method can only be called during CardSelection!"); Table?.TearDown(this); CardsAvailable = new List(Cards.CardCollection.GetAllCards(c => IsCardAllowed(c, Location.Kingdom), Settings.EditionUsage)); SidewaysCardsAvailable = new List(Cards.CardCollection.GetAllCards(c => IsCardAllowed(c, Location.LandscapeCard), Settings.EditionUsage)); var _CardsChosen = new List(); if (Settings.Preset != null) { _CardsChosen = new List(Settings.Preset.Cards); Settings.ColonyPlatinumSelected = Settings.Preset.ColonyPlatinumSelected; if (Settings.ColonyPlatinumSelected == ColonyPlatinumSelected.Unknown) Settings.ColonyPlatinumSelected = DetermineColonyPlatnumSelection(_CardsChosen); Settings.ShelterSelected = Settings.Preset.ShelterSelected; if (Settings.ShelterSelected == ShelterSelected.Unknown) Settings.ShelterSelected = DetermineShelterSelection(_CardsChosen); } else { if (Settings.Constraints != null) { _CardsChosen.AddRange(Settings.Constraints.SelectCards(CardsAvailable.ToList(), 10)); } Settings.ColonyPlatinumSelected = DetermineColonyPlatnumSelection(_CardsChosen); Settings.ShelterSelected = DetermineShelterSelection(_CardsChosen); if (Settings.LandscapeCardConstraints != null) { var count = 0; switch (Settings.LandscapeCardUsage) { case LandscapeCardUsage.AlwaysOne: count = 1; break; case LandscapeCardUsage.AlwaysTwo: count = 2; break; case LandscapeCardUsage.AlwaysThree: count = 3; break; case LandscapeCardUsage.AlwaysOneIfAdventuresEmpiresRenaissance: if (_CardsChosen.Any(c => c.Source == Source.Adventures || c.Source == Source.Empires || c.Source == Source.Renaissance)) count = 1; break; case LandscapeCardUsage.AlwaysTwoIfAdventuresEmpiresRenaissance: if (_CardsChosen.Any(c => c.Source == Source.Adventures || c.Source == Source.Empires || c.Source == Source.Renaissance)) count = 2; break; case LandscapeCardUsage.AlwaysThreeIfAdventuresEmpiresRenaissance: if (_CardsChosen.Any(c => c.Source == Source.Adventures || c.Source == Source.Empires || c.Source == Source.Renaissance)) count = 3; break; case LandscapeCardUsage.Percentage: case LandscapeCardUsage.PercentageIfAdventuresEmpiresRenaissance: if (Settings.LandscapeCardUsage == LandscapeCardUsage.Percentage || _CardsChosen.Any(c => c.Source == Source.Adventures || c.Source == Source.Empires || c.Source == Source.Renaissance)) { var _cardIndicies = new List(); var availableCards = CardsAvailable.Count; var availableSidewaysCards = SidewaysCardsAvailable.Count; var r = new Random(); for (var i = 0; i < 12; i++) { int nextIndex; while (_cardIndicies.Contains(nextIndex = r.Next(availableCards + availableSidewaysCards)) || (nextIndex >= availableCards && _cardIndicies.Count(j => j >= availableCards) >= 2)) { }; _cardIndicies.Add(nextIndex); } count = _cardIndicies.Take(10).Count(j => j >= availableCards); } break; case LandscapeCardUsage.PercentageFromUsedSets: case LandscapeCardUsage.PercentageIfAdventuresEmpiresRenaissanceFromUsedSets: if (Settings.LandscapeCardUsage == LandscapeCardUsage.PercentageFromUsedSets || _CardsChosen.Any(c => c.Source == Source.Adventures || c.Source == Source.Empires || c.Source == Source.Renaissance)) { var _cardIndicies = new List(); var availableCards = CardsAvailable.Count(c => _CardsChosen.Any(cc => cc.Source == c.Source)); var availableSidewaysCards = SidewaysCardsAvailable.Count(c => _CardsChosen.Any(cc => cc.Source == c.Source)); var r = new Random(); for (var i = 0; i < 12; i++) { int nextIndex; while (_cardIndicies.Contains(nextIndex = r.Next(availableCards + availableSidewaysCards)) || (nextIndex >= availableCards && _cardIndicies.Count(j => j >= availableCards) >= 2)) { }; _cardIndicies.Add(nextIndex); } count = _cardIndicies.Take(10).Count(j => j >= availableCards); } break; } var selectedCards = Settings.LandscapeCardConstraints.SelectCards(SidewaysCardsAvailable.ToList(), count); _CardsChosen.AddRange(selectedCards); } } CardsAvailable.RemoveAll(card => _CardsChosen.Contains(card)); Table = new Table(this, Players.Count); _CardsChosen.ForEach(c => Table.AddTableItem(Players, c.Type)); // should now have a list of cards that can be drawn from for things like Bane supplies Table.SetupSupplies(this); } private ColonyPlatinumSelected DetermineColonyPlatnumSelection(IEnumerable cards) { switch (Settings.ColonyPlatinumUsage) { case ColonyPlatinumUsage.Always: return ColonyPlatinumSelected.Yes; case ColonyPlatinumUsage.AlwaysWithProsperity: if (cards.Any(c => c.Source == Source.Prosperity)) return ColonyPlatinumSelected.Yes; return ColonyPlatinumSelected.No; case ColonyPlatinumUsage.Never: return ColonyPlatinumSelected.No; case ColonyPlatinumUsage.FirstKingdomCard: if (cards.OfType().First().Source == Source.Prosperity) return ColonyPlatinumSelected.Yes; return ColonyPlatinumSelected.No; case ColonyPlatinumUsage.PercentageChance: if (RNG.Next(1, cards.OfType().Count() + 1) <= cards.Count(c => c.Location == Location.Kingdom && c.Source == Source.Prosperity)) // We have a winner! return ColonyPlatinumSelected.Yes; return ColonyPlatinumSelected.No; case ColonyPlatinumUsage.FiftyPercentChance: if (RNG.Next(1, 2) == 1) return ColonyPlatinumSelected.Yes; return ColonyPlatinumSelected.No; case ColonyPlatinumUsage.FiftyPercentChanceWithProsperity: if (cards.Any(c => c.Source == Source.Prosperity) && RNG.Next(1, 2) == 1) return ColonyPlatinumSelected.Yes; return ColonyPlatinumSelected.No; } throw new NotImplementedException(); } private ShelterSelected DetermineShelterSelection(IEnumerable cards) { switch (Settings.ShelterUsage) { case ShelterUsage.Always: return ShelterSelected.Yes; case ShelterUsage.AlwaysWithDarkAges: if (cards.Any(c => c.Source == Source.DarkAges)) return ShelterSelected.Yes; return ShelterSelected.No; case ShelterUsage.Never: return ShelterSelected.No; case ShelterUsage.LastKingdomCard: if (cards.Last().Source == Source.DarkAges) return ShelterSelected.Yes; return ShelterSelected.No; case ShelterUsage.PercentageChance: if (RNG.Next(1, cards.OfType().Count() + 1) <= cards.Count(c => c.Location == Location.Kingdom && c.Source == Source.DarkAges)) // We have a winner! return ShelterSelected.Yes; return ShelterSelected.No; case ShelterUsage.FiftyPercentChance: if (RNG.Next(1, 2) == 1) return ShelterSelected.Yes; return ShelterSelected.No; case ShelterUsage.FiftyPercentChanceWithDarkAges: if (cards.Any(c => c.Source == Source.DarkAges) && RNG.Next(1, 2) == 1) return ShelterSelected.Yes; return ShelterSelected.No; } throw new NotImplementedException(); } public void AcceptCards() { if (State != GameState.CardSelection) throw new GameCreationException("This method can only be called during CardSelection!"); Players.InitializeDecks(); Players.FinalizeDecks(); if (Settings.IdenticalStartingHands) foreach (var player in Players.Skip(1)) player.SetupDeckAs(Players.ElementAt(0)); State = GameState.CardSetup; } public void FinalizeSetup() { if (State != GameState.CardSetup) throw new GameCreationException("This method can only be called during CardSetup!"); Table.FinalizeSupplies(this); Players.Setup(this); State = GameState.NotStarted; } public void Clear() { Players?.TearDown(this); Table?.TearDown(this); foreach (var card in CardsAvailable) card.TearDown(this); var cardsAvailable = new List(CardsAvailable); var players = new List(); if (Players != null) players.AddRange(Players); Table?.Clear(); Players?.Clear(); TurnsTaken.Clear(); GameMessage ignored; while (MessageRequestQueue.TryDequeue(out ignored)) ; while (MessageResponseQueue.TryDequeue(out ignored)) ; CardsAvailable.Clear(); #if DEBUG foreach (var card in cardsAvailable.OfType()) card.TestFireAllEvents(); foreach (var p in players) p.TestFireAllEvents(); TestFireAllEvents(); #endif } public void TestFireAllEvents() { GameMessage?.Invoke(this, new GameMessageEventArgs(null, (Cards.Card)null)); GameEndingEvent?.Invoke(this, new GameEndingEventArgs()); GameEndedEvent?.Invoke(this, new GameEndedEventArgs()); CostCompute?.Invoke(this, new CostComputeEventArgs(null, null)); } #region IDisposable variables, properties, & methods // Track whether Dispose has been called. private bool disposed; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!disposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. ActivePlayer = null; CardsAvailable = null; foreach (var message in MessageRequestQueue) { } foreach (var message in MessageResponseQueue) { } Players = null; RNG = null; Table = null; TurnsTaken = null; } // Call the appropriate methods to clean up // unmanaged resources here. // If disposing is false, // only the following code is executed. // Note disposing has been done. disposed = true; } } ~Game() { Dispose(false); } #endregion public void SendMessage(IPlayer player, ICardBase sourceCard) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, int count) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, count); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, params IDisplayable[] cards) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, cards); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, string messageType, int count) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, messageType, count); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, string messageType, params IDisplayable[] cards) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, messageType, cards); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, int count, params IDisplayable[] cards) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, count, cards); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, IDisplayable card, int count = 1, Token token = null) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, card, count, token); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, IPlayer playerAffected, ICardBase sourceCard, IDisplayable card) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, playerAffected, sourceCard, card); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, string messageType, Currency currency) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, messageType, currency); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, ICardBase sourceCard, Currency currency) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceCard, currency); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, Token sourceToken) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceToken); GameMessage(this, gmea); } } public void SendMessage(IPlayer player, Token sourceToken, int count) { if (GameMessage != null) { var gmea = new GameMessageEventArgs(player, sourceToken, count); GameMessage(this, gmea); } } /// /// Returns True if the card is allowed to be in the game /// /// /// private bool IsCardAllowed(IDisplayable card, Location location) { if (card.Location == location) return true; return false; } internal IRandomizable GetNewCardPile(Func predicate) { var matchingCards = CardsAvailable.Where(predicate); if (!matchingCards.Any()) throw new GameCreationException("Cannot satisfy specified constraints! Please double-check and make sure it's possible to construct a Kingdom card setup with the constraints specified"); int index; // For some reason, Random.Next() actually sometimes returns the end point as an option -- Why? That seems really wrong. do index = RNG.Next(matchingCards.Count()); while (index == matchingCards.Count()); var toReturn = matchingCards.ElementAt(index); CardsAvailable.Remove(toReturn); return toReturn; } public GameState State { get; private set; } = GameState.Unknown; public List CardsAvailable { get; private set; } = new List(); internal List SidewaysCardsAvailable { get; private set; } = new List(); public ITable Table { get; private set; } public PlayerCollection Players { get; private set; } public IPlayer ActivePlayer { get; private set; } public ConcurrentQueue MessageRequestQueue { get; } = new ConcurrentQueue(); public ConcurrentQueue MessageResponseQueue { get; } = new ConcurrentQueue(); public IEnumerator GetPlayersStartingWithActiveEnumerator() { var p = Players.GetPlayersStartingWithEnumerator(ActivePlayer); return p; } public IEnumerator GetPlayersStartingWithEnumerator(IPlayer player) { var p = Players.GetPlayersStartingWithEnumerator(player); return p; } public TurnCollection TurnsTaken { get; private set; } = new TurnCollection(); public IEnumerable UnmodifiedTurnsTaken { get { return TurnsTaken.Where(t => !t.ModifiedTurn); } } public Turn CurrentTurn { get { if (TurnsTaken == null || TurnsTaken.Count == 0) return null; return TurnsTaken[TurnsTaken.Count - 1]; } } public GameSettings Settings { get; private set; } = new GameSettings(); public void Reset() { Table.Reset(); foreach (var player in Players) player.Reset(); } public IPlayer GetPlayerFromIndex(IPlayer currentPlayer, int index) { var newIndex = Players.IndexOf(currentPlayer) + index; // The (X ? Y : 0) clause deals with the case when newIndex < 0 return Players[newIndex % Players.Count + (newIndex < 0 ? Players.Count : 0)]; } public Thread SetNextPlayer() { var modifiedTurn = false; Turn turn = null; if (TurnsTaken.Any() && CurrentTurn.IsTurnFinished && CurrentTurn.NextPlayer != null) { ActivePlayer = CurrentTurn.NextPlayer; modifiedTurn = true; } else if (TurnsTaken.Any() && !CurrentTurn.IsTurnFinished) { //// I'm not dead yet! turn = CurrentTurn; //this.ActivePlayer = this.CurrentTurn.Player; //Thread startThread = new Thread(() => this.ActivePlayer.Start(this.CurrentTurn)); //return startThread; } else if (ActivePlayer != null) ActivePlayer = Players.Next(ActivePlayer); else ActivePlayer = Players[0]; Reset(); if (turn == null) { turn = new Turn(ActivePlayer) { ModifiedTurn = modifiedTurn }; if (CurrentTurn != null) turn.GrantedBy = CurrentTurn.NextGrantedBy; TurnsTaken.Add(turn); } var startThread = new Thread(() => ActivePlayer.Start(turn)); return startThread; } private void End() { GameEndingEvent?.Invoke(this, new GameEndingEventArgs()); if (State != GameState.Aborted) State = GameState.Ended; Reset(); ActivePlayer = null; foreach (var player in Players) { lock (player) { player.EndTurn(); player.End(); } } GameEndedEvent?.Invoke(this, new GameEndedEventArgs()); } public AutoResetEvent WaitEvent { get; } = new AutoResetEvent(false); private List AIThreads { get; set; } public void StartAsync() { try { AIThreads = new List(); foreach (var aiPlayer in Players.Where(p => p.PlayerType == PlayerType.Computer)) { var t = new Thread(aiPlayer.StartAsync); AIThreads.Add(t); t.Start(); var counter = 0; while (((Players.AI.IComputerAI)aiPlayer).State != DominionBase.Players.AI.AIState.Running) { counter++; if (counter > 20) throw new GameCreationException("AI player did not register soon enough!"); Thread.Sleep(250); } } Thread startingThread = null; _ShouldStop = true; if (State != GameState.Ended) { State = GameState.Running; _ShouldStop = false; startingThread = SetNextPlayer(); } while (!_ShouldStop) { startingThread?.Start(); WaitEvent.WaitOne(); startingThread = null; while (true) { // Making this lock section as small as possible if (!MessageRequestQueue.TryDequeue(out GameMessage message)) break; //System.Diagnostics.Trace.WriteLine(String.Format("Message: {0}", message.Message)); GameMessage response = new GameResponseMessage { Message = "ACK" }; try { message.ActBefore(this); } catch (NullReferenceException) { if (!_ShouldStop) throw; } MessageResponseQueue.Enqueue(response); message.WaitCallback?.Invoke(null); Thread t = null; if (message.CheckEndGame && IsEndgameTriggered) _ShouldStop = true; else t = message.ActAfter(this); if (t != null) startingThread = t; } } End(); } catch (Exception ex) { Logging.LogError(ex); throw; } } public bool IsEndgameTriggered { get { return (Table.TableEntities.Values.OfType().Any(s => s.IsEndgameTriggered) || Table.EmptySupplyPiles >= _EndgameSupplies); } } public PlayerCollection Winners { get { var playersWhoWon = new PlayerCollection(this); if (!IsEndgameTriggered) return playersWhoWon; foreach (var player in Players.OrderByDescending(p => p.VictoryPoints).ThenBy(p => UnmodifiedTurnsTaken.Count(t => t.IsTurnFinished && t.Player == p))) { if (playersWhoWon.Count == 0 || (playersWhoWon[0].VictoryPoints == player.VictoryPoints && UnmodifiedTurnsTaken.Count(t => t.IsTurnFinished && t.Player == playersWhoWon[0]) == UnmodifiedTurnsTaken.Count(t => t.IsTurnFinished && t.Player == player) )) playersWhoWon.Add(player); } return playersWhoWon; } } public void Abort() { State = GameState.Aborted; _ShouldStop = true; } public Cards.Cost ComputeCost(ICost card) { Contract.Requires(card != null, "card cannot be null"); var copy = CostCompute; if (copy != null) { var ccea = new CostComputeEventArgs(card, card.BaseCost); copy(this, ccea); return ccea.Cost; } return card.BaseCost; } private static List GetAllSerializingTypes(IEnumerable cards) { var typeDict = new List { typeof(Cards.Cost), typeof(Traits), typeof(Categories) }; foreach (var card in cards) typeDict.AddRange(card.GetSerializingTypes()); return typeDict; } public void Save(string filename) { var xdGame = new XmlDocument(); var xeGame = xdGame.CreateElement("game"); xdGame.AppendChild(xeGame); var msRandom = new MemoryStream(); var bf = new BinaryFormatter(); bf.Serialize(msRandom, RNG); msRandom.Close(); var xe = xdGame.CreateElement("rng"); xe.InnerText = Convert.ToBase64String(msRandom.ToArray()); xeGame.AppendChild(xe); xe = xdGame.CreateElement("start_time"); xe.InnerText = StartTime.ToString(CultureInfo.InvariantCulture); xeGame.AppendChild(xe); xe = xdGame.CreateElement("state"); xe.InnerText = State.ToString(); xeGame.AppendChild(xe); xe = xdGame.CreateElement("activeplayer"); if (ActivePlayer != null) xe.InnerText = ActivePlayer.UniqueId.ToString(); xeGame.AppendChild(xe); var xeSettings = xdGame.CreateElement("settings"); xeGame.AppendChild(xeSettings); var allCards = Cards.CardCollection.GetAllCards(c => true, EditionUsage.AllowDuplicates); var xsSettings = new XmlSerializer(typeof(GameSettings), GetAllSerializingTypes(allCards).ToArray()); var swSettings = new StringWriter(); xsSettings.Serialize(swSettings, Settings); swSettings.Close(); var xdSettings = new XmlDocument(); xdSettings.LoadXml(swSettings.ToString()); xeSettings.AppendChild(xdGame.ImportNode(xdSettings.DocumentElement, true)); xeGame.AppendChild(Table.GenerateXml(xdGame, "table")); xeGame.AppendChild(Players.GenerateXml(xdGame)); xeGame.AppendChild(TurnsTaken.GenerateXml(xdGame, "turns")); xdGame.Save("gamedump.xml"); using (var sw = new StringWriter()) using (var xw = XmlWriter.Create(sw)) { xdGame.WriteTo(xw); xw.Flush(); File.WriteAllBytes(filename, StringUtility.Zip(StringUtility.Encrypt(sw.GetStringBuilder().ToString()))); } } public void Load(string filename) { if (State != GameState.Unknown) return; //try //{ var xdGame = new XmlDocument(); xdGame.LoadXml(StringUtility.Decrypt(StringUtility.Unzip(File.ReadAllBytes(filename)))); //xdGame.Load("gamedump.xml"); var xn = xdGame.SelectSingleNode("game/rng"); if (xn != null) { var msRandom = new MemoryStream(Convert.FromBase64String(xn.InnerText)); var bf = new BinaryFormatter(); RNG = (Random)bf.Deserialize(msRandom); msRandom.Close(); } xn = xdGame.SelectSingleNode("game/start_time"); if (xn != null) StartTime = DateTime.Parse(xn.InnerText, CultureInfo.InvariantCulture); State = GameState.Setup; xn = xdGame.SelectSingleNode("game/settings/GameSettings"); if (xn != null) { var allCards = Cards.CardCollection.GetAllCards(c => true, EditionUsage.AllowDuplicates); var myDeserializer = new XmlSerializer(typeof(GameSettings), GetAllSerializingTypes(allCards).ToArray()); using (var sr = new StringReader(xn.OuterXml)) { Settings = (GameSettings)myDeserializer.Deserialize(sr); } } Table = new Table(this); var xnl = xdGame.SelectNodes("game/players/player"); Players = new PlayerCollection(this); foreach (XmlNode xnPlayer in xnl) { var player = Player.Load(this, xnPlayer); Players.Add(player); Table.AddPlayer(player); } xn = xdGame.SelectSingleNode("game/table"); if (xn == null) return; Table.Load(xn); Players.Setup(this); xn = xdGame.SelectSingleNode("game/state"); if (xn != null) State = (GameState)Enum.Parse(typeof(GameState), xn.InnerText, true); TurnsTaken.Load(this, xdGame.SelectSingleNode("game/turns")); xn = xdGame.SelectSingleNode("game/activeplayer"); if (!string.IsNullOrEmpty(xn?.InnerText)) { ActivePlayer = Players.FirstOrDefault(p => p.UniqueId == new Guid(xn.InnerText)); ActivePlayer.SetCurrentTurn(TurnsTaken.Last()); } //} //catch (Exception ex) //{ // throw ex; //} } } }