Esempio n. 1
0
        public void GameHistoryTest()
        {
            GameHistory gameHistory = new GameHistory();
            Game        game        = new Game(new StartMenu());

            game.GenerateNewGame();

            var robotEnegry = game.Robot.BatteryCharge;

            game.State = new GameProcess();

            Assert.AreEqual(game.Robot.BatteryCharge, robotEnegry);

            gameHistory.Add(new GameMemento(game.Robot.SaveState(), game.MoveCounter, (Map)game.Map.Clone()));
            var res = game.Turn("next");

            Assert.AreEqual(res, "In fronr of you lies: " + game.Map.Stones[1].GetInfo() + "\r\nWhat will U do?\r\n\r\n"
                            + (game.Robot is Cyborg ? "Turns harm: 0, battery charge: " : "Battery charge: ") +
                            +(robotEnegry - 1) + ", battery lost: " + 1 + ", ");
            Assert.AreEqual(game.Robot.BatteryCharge, robotEnegry - 1);

            game.Robot.RestoreState(gameHistory.GetLastState().GetRobotMemento());

            Assert.AreEqual(game.Robot.BatteryCharge, robotEnegry);
        }
Esempio n. 2
0
        /// <summary>
        /// Startet - abhängig davon, ob die KI zufällig oder lernend ist- die Funktion MakeTurn des Objektes AI.
        /// Im Anschluss werden die Funktionen für das Spielende, Spielerwechsel und die Aktualisierung der View aufgerufen.
        ///
        /// Speichert die Ausgangslage (GameHistory) und die erfolgte Entscheidung(DecisionHistory) in den entsprechenden Variablen.
        /// </summary>
        /// <param name="learning_AI"> Ist die KI eine lernende KI?</param>
        private void MakeTurn_AI(Boolean learning_AI)
        {
            //aktuelle Spielsituation wird gespeichert
            GameHistory.Add(GameState.Board.Clone() as int[]);

            if (!learning_AI)
            {
                GameState.Board = AI.Random_MakeTurn(GameState.Board, GameState.ActivePlayer);
            }
            else if (learning_AI)
            {
                GameState.Board = AI.Learning_MakeTurn(GameState.Board, GameState.ActivePlayer);
            }

            //Entscheidung der KI wird gespeichert, nachdem der letzte Zug der KI ermittelt wurde.
            for (int i = 0; i < 9; i++)
            {
                if (GameState.Board[i] != GameHistory.Last()[i] &&
                    GameState.Board[i] == GameState.ActivePlayer)
                {
                    DecisionHistory.Add(i);
                }
            }
            TestForEndCondition();
            PlayerChange();
            SendInformationToVM();
        }
        public bool MakeMove(TicTacToeMove move)
        {
            var(player, i, j) = move;

            if (!CheckBounds(i, j))
            {
                return(false);
            }

            if (player.Mark == CurrentMove && Board[i, j] == TicTacToeMarkEnum.NaN)
            {
                Board[i, j] = player.Mark;
                CurrentMove = CurrentMove == TicTacToeMarkEnum.O ? TicTacToeMarkEnum.X : TicTacToeMarkEnum.O;
            }
            else
            {
                return(false);
            }

            // move is done
            GameHistory.Add(move);


            if (GameEnd())
            {
                OnGameEnd?.Invoke();
            }

            return(true);
        }
Esempio n. 4
0
        private void GetGameUpdates(UdpClient client)
        {
            Log("Receiving GameSteps");

            try {
                while (!_shutdown)
                {
                    IPEndPoint endPoint = null;
                    var        msg      = client.Receive(ref endPoint);

                    using (var br = new BinaryReader(new MemoryStream(msg))) {
                        if ((SC_Message)msg[0] == SC_Message.GameStepCollection)
                        {
                            GameStepCollection gsc = new GameStepCollection(br);

                            foreach (var gs in gsc.GameSteps)
                            {
                                if (gs.StepNumber > _highestGameStepNum + 8)
                                {
                                    Log("Disconnected!");
                                    throw new Exception("Disconnected!");
                                }

                                _highestGameStepNum = Math.Max(gs.StepNumber, _highestGameStepNum);

                                lock (_gameHistory) {
                                    _gameHistory.Add(gs);
                                }

                                if (gs.PlayerInputs.Any())
                                {
                                    var msgs = gs.PlayerInputs
                                               .Select(pi =>
                                                       pi.Key +
                                                       ":" +
                                                       String.Join("|", pi.Value.Inputs.Select(k => k.Key + ":" + k.Value).ToArray()))
                                               .Aggregate((a, e) => a + "," + e);

                                    //Log("[" + gs.StepNumber + "] " + msgs);
                                }
                            }
                        }
                        else
                        {
                            Log("Invalid Message!");
                        }
                    }
                }
            }
            catch (Exception e) {
                Log(e.ToString());
            }

            Log("GetGameSteps Exit");
        }
Esempio n. 5
0
        public void Notify(string eventDetail)
        {
            if (GameHistory is null)
            {
                GameHistory = new List <GameEvent>();
            }

            GameHistory.Add(new GameEvent
            {
                EventDetail = eventDetail
            });
        }
Esempio n. 6
0
 /// <summary>
 /// Testet, ob das angegebene Feld frei war. Sollte es nicht frei gewesen sein, so passiert nichts.
 /// Ist es ein Freies Feld, so wird es mit der Nummer des Spieler überschrieben,
 ///
 ///
 /// Die AusgabeVariable gibt dem ViewModel die Informationen, ob der Zug gültig war und welcher Spieler diesen durchgeführt hat.
 ///     Ausgabe = 0 : Zug ungültig
 ///               1 : Spieler "X" hat gezogen
 ///               5 : Spieler "X" hat gewonnen
 ///              -1 : Spieler "O" hat gezogen
 ///              -5 : Spieler "O" hat gewonnen
 ///             100 : Spiel mit Unendschieden beendet
 ///
 /// Speichert die Ausgangslage (GameHistory) und die erfolgte Entscheidung(DecisionHistory) in den entsprechenden Variablen.
 /// </summary>
 /// <param name="point"></param>
 /// <returns></returns>
 public void MakeTurn(int point)
 {
     if (GameState.Board[point] == 0)
     {
         GameHistory.Add(GameState.Board.Clone() as int[]);
         GameState.Board[point] = GameState.ActivePlayer;
         DecisionHistory.Add(point);
         TestForEndCondition();
         PlayerChange();
         ChooseAI();
         SendInformationToVM();
     }
 }
Esempio n. 7
0
        /// <summary>
        /// IParson行動通知イベントハンドラ
        /// </summary>
        /// <param name="parson">通知元</param>
        /// <param name="action">行動</param>
        private void OutputParsonAction(IParson parson, string action)
        {
            if (Application.Current == null || Application.Current.Dispatcher == null)
            {
                return;
            }

            Application.Current.Dispatcher.Invoke(() =>
            {
                GameHistory.Add(new GameHistoryItem()
                {
                    Parson = parson, Action = action
                });
            }, System.Windows.Threading.DispatcherPriority.Background);
        }
Esempio n. 8
0
 public Anwsers Anwser(int proposal)
 {
     if (randomized > proposal)
     {
         GameHistory.Add(new Move(proposal, Anwsers.TooLittle));
         return(Anwsers.TooLittle);
     }
     else if (randomized < proposal)
     {
         GameHistory.Add(new Move(proposal, Anwsers.TooMuch));
         return(Anwsers.TooMuch);
     }
     else
     {
         Status = GameStatus.Finished;
         return(Anwsers.Score);
     }
 } //publiczna metoda typu Anwsers (ten typ to enum)
Esempio n. 9
0
        public sealed override void ReceiveMove(IMove opponentMove)
        {
            LatestOpponentMove = opponentMove;

            if (!_connectionTestOverride)
            {
                // Basic validation
                var move = new SingleMove(opponentMove);
                if (Board.ValueAt(move.PrevPos) == null)
                {
                    throw new ArgumentException($"Player [isWhite={!IsPlayerWhite}] Tried to move a from position that is empty");
                }

                if (Board.ValueAt(move.PrevPos) is PieceBase opponentPiece)
                {
                    if (opponentPiece.IsWhite == IsPlayerWhite)
                    {
                        throw new ArgumentException($"Opponent tried to move player piece");
                    }
                }

                // TODO intelligent analyzing what actually happened

                if (Board.ValueAt(move.NewPos) is PieceBase playerPiece)
                {
                    // Opponent captures player targetpiece
                    if (playerPiece.IsWhite == IsPlayerWhite)
                    {
                        move.Capture = true;
                    }
                    else
                    {
                        throw new ArgumentException("Opponent tried to capture own piece.");
                    }
                }

                Board.ExecuteMove(move);
                GameHistory.Add(opponentMove);
                TurnCount++;
            }
        }
Esempio n. 10
0
        public async Task PlayNextGameAsync()
        {
            var nextCompetitors = Elimination.GetNextCompetitors();

            GameOverTextVisibility = Visibility.Collapsed;

            if (nextCompetitors != null)
            {
                var gameHistoryEntry = new GameHistoryEntryViewModel()
                {
                    GameDescription = Elimination.GetGameDescription(),
                    History         = new ObservableCollection <RoundPartialHistory>()
                };

                Game.SetupNewGame(nextCompetitors);
                GameHistory.Add(gameHistoryEntry);

                OutputText += "Game starting: " + gameHistoryEntry.GameDescription + "\n";

                await PlayGameAsync(gameHistoryEntry);
            }
        }
Esempio n. 11
0
        public async Task PlayNextGameAsync()
        {
            var nextCompetitors = Elimination.GetNextCompetitors();

            if (nextCompetitors != null)
            {
                await SetArenaMessage(ArenaMessageHelper.GetInitialMessage(nextCompetitors));

                var gameHistoryEntry = new GameHistoryEntryViewModel()
                {
                    GameDescription = Elimination.GetGameDescription(),
                    History         = new ObservableCollection <RoundPartialHistory>()
                };

                Game.SetupNewGame(nextCompetitors);
                GameHistory.Add(gameHistoryEntry);

                OutputText += "Game starting: " + gameHistoryEntry.GameDescription + "\n";

                await PlayGameAsync(gameHistoryEntry);
            }
        }
        public async Task <GameState> LoadHistoryGetState()
        {
            var       tokens    = accountHandler.FindTokenByMetadata(Player1Address, "GameId", Id.ToString());
            GameState lastState = null;

            GameHistory.Clear();
            GameHistoryTxIds.Clear();
            //var tok = tokens.Values.OrderBy(t => t.TimeStamp);

            foreach (var t in tokens)
            {
                try
                {
                    if (t.Value.Metadata != null)
                    {
                        if (t.Value.Metadata.TryGetValue("GameData", out var gameData))
                        {
                            var parsedData = JsonConvert.DeserializeObject <ChessGameDto>(gameData);

                            if (parsedData != null)
                            {
                                if (parsedData.GameId == Id.ToString() && parsedData.Type == GameDtoTypes.ChessGame)
                                {
                                    //GameHistoryTxIds.Add(t.Key);

                                    if (parsedData.Players.Count > 0)
                                    {
                                        Players = parsedData.Players;
                                    }

                                    parsedData.TxId      = t.Key;
                                    parsedData.TimeStamp = t.Value.TimeStamp;
                                    GameHistory.Add(parsedData);
                                }

                                lastState = JsonConvert.DeserializeObject <GameState>(parsedData.GameState);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.Error("Chess Game - wrong format of gameData in token metadata. Cannot load history", ex);
                    return(null);
                }
            }

            var ghist = GameHistory.OrderBy(t => t.TimeStamp);

            GameHistory = ghist.ToList <ChessGameDto>();
            foreach (var gh in GameHistory)
            {
                GameHistoryTxIds.Add(gh.TxId);
            }

            var last = GameHistoryTxIds.LastOrDefault();

            if (last != null)
            {
                ActualStateTxId = last;
            }

            return(await Task.FromResult(lastState));
        }
        public async Task <string> WriteMove(string stateString, string onMoveAddress, string player2Address, string password = "")
        {
            GameState state = null;

            if (!string.IsNullOrEmpty(stateString))
            {
                state = GetGameStateFromString(stateString);
            }
            else
            {
                return(await Task.FromResult("Cannot load state String. It is empty!"));
            }

            if (!Players.ContainsKey(onMoveAddress) || !Players.ContainsKey(player2Address))
            {
                return(await Task.FromResult("Player address is not in the list of players!"));
            }

            try
            {
                state.LastMovePlayer = onMoveAddress;
                var dto = new ChessGameDto()
                {
                    GameId             = Id.ToString(),
                    GameState          = JsonConvert.SerializeObject(state),
                    Type               = GameDtoTypes.ChessGame,
                    LastGameRecordTxId = ActualStateTxId
                };

                dto.Players = new Dictionary <string, ChessPlayer>();

                foreach (var pl in Players)
                {
                    dto.Players.TryAdd(pl.Key, pl.Value);
                }

                GameHistory.Add(dto);

                var moveString = JsonConvert.SerializeObject(dto);

                var tkData = new SendTokenTxData()
                {
                    Amount          = 1,
                    Symbol          = TokenSymbol,
                    ReceiverAddress = player2Address,
                    SenderAddress   = onMoveAddress,
                    Id = TokenId
                };

                tkData.Metadata.TryAdd("GameId", Id.ToString());
                tkData.Metadata.TryAdd("GameData", moveString);
                tkData.Password = password;

                var res = await NeblioTransactionHelpers.SendNTP1TokenAPI(tkData, 30000);

                if (!string.IsNullOrEmpty(res))
                {
                    ActualStateTxId = res;
                }

                return(await Task.FromResult(res));
            }
            catch (Exception ex)
            {
                log.Error("Chess Game - Cannot write move!", ex);
                return(await Task.FromResult("ERROR"));
            }
        }
        public async Task <string> LoadHistoryFromActualTx()
        {
            try
            {
                var allLoaded       = false;
                var actualTxLoading = ActualStateTxId;
                while (!allLoaded)
                {
                    var info = await NeblioTransactionHelpers.TransactionInfoAsync(null, TransactionTypes.Neblio, actualTxLoading);

                    if (info == null)
                    {
                        return(await Task.FromResult("OK - No History yet."));
                    }

                    if (info.VoutTokens != null)
                    {
                        if (info.VoutTokens.Count > 0)
                        {
                            var token = info.VoutTokens[0];
                            if (token.Metadata != null)
                            {
                                if (token.Metadata.Count > 0)
                                {
                                    if (token.Metadata.TryGetValue("GameId", out var gameid))
                                    {
                                        if (gameid == Id.ToString())
                                        {
                                            if (token.Metadata.TryGetValue("GameData", out var gameData))
                                            {
                                                if (!string.IsNullOrEmpty(gameData))
                                                {
                                                    try
                                                    {
                                                        var parsedData = JsonConvert.DeserializeObject <ChessGameDto>(gameData);
                                                        if (parsedData != null)
                                                        {
                                                            if (parsedData.GameId == Id.ToString() && parsedData.Type == GameDtoTypes.ChessGame)
                                                            {
                                                                GameHistoryTxIds.Add(actualTxLoading);

                                                                if (Players.Count == 0 && parsedData.Players.Count > 0)
                                                                {
                                                                    Players = parsedData.Players;
                                                                }

                                                                GameHistory.Add(parsedData);

                                                                if (parsedData.LastGameRecordTxId != "StartOfNewGame")
                                                                {
                                                                    actualTxLoading = parsedData.LastGameRecordTxId;
                                                                }
                                                                else
                                                                {
                                                                    allLoaded = true;
                                                                }
                                                            }
                                                        }
                                                    }
                                                    catch (Exception ex)
                                                    {
                                                        log.Error("Chess Game - wrong format of gameData in token metadata. Cannot load history", ex);
                                                        return(null);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // loading is going from newset to oldest one, so it is important to reverse whole list
                GameHistoryTxIds.Reverse();
                GameHistory.Reverse();

                return(await Task.FromResult("OK"));
            }
            catch (Exception ex)
            {
                log.Error("Chess Game - Cannot load game History!", ex);
                return(await Task.FromResult("ERROR"));
            }
        }
Esempio n. 15
0
        private IPlayerMove CreateNewMove(bool checkOpenings, int?overrideSearchDepth = null)
        {
            var isMaximizing = IsPlayerWhite;

            Diagnostics.StartMoveCalculations();

            // Common start measures
            if (Settings.UseTranspositionTables)
            {
                // Delete old entries from tables
                var transpositions = Board.Shared.Transpositions.Tables;
                if (transpositions.Any())
                {
                    var toBeDeleted      = new List <ulong>();
                    var currentTurnCount = Board.Shared.GameTurnCount;

                    foreach (var transposition in transpositions)
                    {
                        // If transposition.turn 20 < current 25 - 4
                        if (transposition.Value.GameTurnCount <
                            currentTurnCount - Settings.ClearSavedTranspositionsAfterTurnsPassed)
                        {
                            toBeDeleted.Add(transposition.Key);
                        }
                    }

                    foreach (var hash in toBeDeleted)
                    {
                        transpositions.Remove(hash);
                    }

                    if (Settings.UseFullDiagnostics)
                    {
                        if (toBeDeleted.Any())
                        {
                            Diagnostics.AddMessage($"Deleted {toBeDeleted.Count} old transposition entries.");
                        }
                        Diagnostics.AddMessage($"Total transpositions: {transpositions.Count}.");
                    }
                }
            }


            // Opening
            if (GameHistory.Count < 10 && checkOpenings)
            {
                var previousMoves = GetPreviousMoves();
                var openingMove   = Openings.NextMove(previousMoves);

                if (openingMove != null)
                {
                    var openingMoveWithData = Board.CollectMoveProperties(openingMove);
                    Board.ExecuteMove(openingMoveWithData);
                    Board.Shared.GameTurnCount++;
                    PreviousData = Diagnostics.CollectAndClear();

                    var result = new PlayerMoveImplementation(
                        openingMove.ToInterfaceMove(), PreviousData.ToString());
                    GameHistory.Add(result.Move);
                    return(result);
                }
            }

            // Get all available moves and do necessary filtering
            List <SingleMove> allMoves = Board.Moves(isMaximizing, true, true).ToList();

            if (allMoves.Count == 0)
            {
                // Game ended to stalemate

                throw new ArgumentException(
                          $"No possible moves for player [isWhite={IsPlayerWhite}]. Game should have ended to draw (stalemate).");
            }

            if (MoveHistory.IsLeaningToDraw(GameHistory))
            {
                // Take 4th from the end of list
                var repetionMove = GameHistory[^ 4];
Esempio n. 16
0
        private void SendPlayerUpdates()
        {
            Console.WriteLine("Starting Broadcasts");
            _lastStepBroadcast = DateTime.UtcNow;

            DateTime start           = DateTime.UtcNow;
            int      sleepIntervalMs = GameStepIntervalMs;

            try {
                while (true)
                {
                    if (sleepIntervalMs > 0)
                    {
                        Thread.Sleep(sleepIntervalMs);
                    }

                    start = DateTime.UtcNow;

                    GameStep gameStep;
                    lock (_gameLock) {
                        byte ellapsed = (byte)Math.Min((DateTime.UtcNow - _lastStepBroadcast).TotalMilliseconds, byte.MaxValue);

                        // Create new GameStep to broadcast
                        gameStep = new GameStep(_stepNumber, ellapsed);

                        GameStep lastGameStep;
                        // If previous GameSteps exit
                        if (_gameHistory.Count > 0 && _gameHistory.TryGet((UInt16)(_stepNumber - 1), out lastGameStep))
                        {
                            foreach (var currentInput in _curPlayerInputs)
                            {
                                // If Player input exists from last game update, take XOR of last player input and current player input
                                PlayerInputs lastInput;
                                if (lastGameStep.PlayerInputs.TryGetValue(currentInput.Key, out lastInput))
                                {
                                    var changed = currentInput.Value.Inputs
                                                  .Where(i => !lastInput.Inputs.ContainsKey(i.Key) || lastInput.Inputs[i.Key] != i.Value)
                                                  .ToDictionary(k => k.Key, k => k.Value);

                                    PlayerInputs pi = new PlayerInputs()
                                    {
                                        PlayerId = currentInput.Key,
                                        Inputs   = changed
                                    };

                                    gameStep.PlayerInputs.Add(pi.PlayerId, pi);
                                }
                                // If no last game update for this player, just send current inputs
                                else
                                {
                                    gameStep.PlayerInputs.Add(currentInput.Key, currentInput.Value);
                                }
                            }

                            _curPlayerInputs.Clear();
                        }
                        // If no previous GameSteps exist
                        else
                        {
                            gameStep.PlayerInputs = _curPlayerInputs;
                        }

                        _gameHistory.Add(gameStep);
                    }

                    GameStepCollection gsc = new GameStepCollection(3);
                    gsc.GameSteps.Add(gameStep);

                    // Send Redundant Step #1
                    if (_stepNumber >= 1)
                    {
                        UInt16   idx = (UInt16)Math.Max(_stepNumber - 2, 0);
                        GameStep redundant;
                        if (_gameHistory.TryGet(idx, out redundant))
                        {
                            gsc.GameSteps.Add(redundant);
                        }
                    }

                    // Send Redundant Step #2
                    if (_stepNumber >= 3)
                    {
                        UInt16   idx = (UInt16)Math.Max(_stepNumber - 8, 0);
                        GameStep redundant;
                        if (_gameHistory.TryGet(idx, out redundant))
                        {
                            gsc.GameSteps.Add(redundant);
                        }
                    }

                    BroadcastGameStepCollection(gsc);

                    // Calculate next Sleep Interval
                    _lastStepBroadcast = DateTime.UtcNow;
                    _stepNumber++;
                    TimeSpan broadcastDuration = (_lastStepBroadcast - start);
                    sleepIntervalMs = Math.Max(GameStepIntervalMs - (int)broadcastDuration.TotalMilliseconds, 0);
                }
            }
            catch (Exception e) {
                Console.Write(e);
            }
        }
Esempio n. 17
0
 void OnGameUpdate(Stone.StoneState state) => gameHistory.Add(game.Board, state);
Esempio n. 18
0
        public override IPlayerMove CreateMove()
        {
            if (_connectionTestOverride)
            {
                var diagnostics = Diagnostics.CollectAndClear();
                // Dummy moves for connection testing
                var move = new PlayerMoveImplementation()
                {
                    Move = new MoveImplementation()
                    {
                        StartPosition   = $"a{_connectionTestIndex--}",
                        EndPosition     = $"a{_connectionTestIndex}",
                        PromotionResult = PromotionPieceType.NoPromotion
                    },
                    Diagnostics = diagnostics.ToString()
                };

                return(move);
            }
            else
            {
                var isMaximizing = IsPlayerWhite;
                Diagnostics.StartMoveCalculations();

                // Get all available moves and do necessary filtering
                List <SingleMove> allMoves = Board.Moves(isMaximizing, true, true).ToList();
                if (allMoves.Count == 0)
                {
                    throw new ArgumentException($"No possible moves for player [isWhite={IsPlayerWhite}]. Game should have ended to draw (stalemate).");
                }

                // Reorder moves to improve alpha-beta cutoffs
                // allMoves = MoveResearch.OrderMoves(allMoves, Board, isMaximizing);

                if (MoveHistory.IsLeaningToDraw(GameHistory))
                {
                    var repetionMove = GameHistory[GameHistory.Count - 4];
                    allMoves.RemoveAll(m =>
                                       m.PrevPos.ToAlgebraic() == repetionMove.StartPosition &&
                                       m.NewPos.ToAlgebraic() == repetionMove.EndPosition);
                }
                Diagnostics.AddMessage($"Available moves found: {allMoves.Count}. ");

                Strategy.Update(PreviousData, TurnCount);
                var strategyResult = Strategy.DecideSearchDepth(PreviousData, allMoves, Board);
                SearchDepth = strategyResult.searchDepth;
                Phase       = strategyResult.gamePhase;
                var bestMove = AnalyzeBestMove(allMoves);

                if (bestMove == null)
                {
                    throw new ArgumentException($"Board didn't contain any possible move for player [isWhite={IsPlayerWhite}].");
                }

                // Update local
                Board.ExecuteMove(bestMove);
                TurnCount++;

                // Endgame checks
                // TODO should be now read from singlemove
                var castling = false;
                var check    = Board.IsCheck(IsPlayerWhite);
                //var checkMate = false;
                //if(check) checkMate = Board.IsCheckMate(IsPlayerWhite, true);
                if (bestMove.Promotion)
                {
                    Diagnostics.AddMessage($"Promotion occured at {bestMove.NewPos.ToAlgebraic()}. ");
                }

                PreviousData = Diagnostics.CollectAndClear();

                var move = new PlayerMoveImplementation()
                {
                    Move        = bestMove.ToInterfaceMove(castling, check),
                    Diagnostics = PreviousData.ToString()
                };
                GameHistory.Add(move.Move);
                return(move);
            }
        }