diff --git a/Chase.Engine/Game.cs b/Chase.Engine/Game.cs index 98e2452..d6b6f99 100644 --- a/Chase.Engine/Game.cs +++ b/Chase.Engine/Game.cs @@ -20,6 +20,8 @@ public class Game private List MoveHistory; private ISearchAlgorithm search; + + private string loadedCgn; public delegate void SearchProgress(SearchStatus status); @@ -68,6 +70,8 @@ public void StartNew(Position position) BoardHistory.Add(Board.Clone()); MoveHistory = new List(); + + loadedCgn = ""; } private void Search_OnNewResult(object sender, SearchStatus e) @@ -125,8 +129,9 @@ public bool IsValidMove(Move move) public void MakeMove(string move) { - Board.MakeMove(move); + Move parsed = Board.MakeMove(move); BoardHistory.Add(Board.Clone()); + MoveHistory.Add(parsed); } public void MakeMove(Move move) @@ -192,6 +197,55 @@ public List GetMoveHistory() return history; } + public Move RecallState(int move) + { + // TODO: there's got to be a better way to load a state than to re-parse and re-play all past moves from a clean state... + LoadFromGameNotationString(loadedCgn, move); + + if (MoveHistory.Count >= 1) + { + return MoveHistory[MoveHistory.Count - 1]; + } + else + { + return null; + } + } + + public int LoadFromGameNotationString(string cgn) + { + return LoadFromGameNotationString(cgn, int.MaxValue); + } + + public int LoadFromGameNotationString(string cgn, int stopOnMove) + { + StartNew(); + + int moveNum = 0; + foreach (string line in cgn.Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + if (!line.StartsWith("[")) + { + string[] parts = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 1; i < parts.Length; i++) + { + if (moveNum < stopOnMove) + { + MakeMove(parts[i]); + moveNum++; + } + } + } + } + + if (string.IsNullOrEmpty(loadedCgn)) + { + loadedCgn = cgn; + } + + return moveNum; + } + public string GetGameNotationString() { StringBuilder sb = new StringBuilder(); diff --git a/Chase.Engine/Move.cs b/Chase.Engine/Move.cs index 5ee090e..9665d31 100644 --- a/Chase.Engine/Move.cs +++ b/Chase.Engine/Move.cs @@ -25,6 +25,8 @@ public class Move public int Evaluation { get; set; } + public bool IsValid { get { return FromIndex != -1 || ToIndex != -1 || Increment != -1; } } + /// /// The direction the piece was moving when it stopped /// @@ -46,6 +48,67 @@ public static int GetIndexFromTile(string tile) return IndexLookup.ContainsKey(tile) ? IndexLookup[tile] : Constants.InvalidTile; } + public static Move ParseMove(string move) + { + // There are three types of moves... + // Move from one tile to another: A1-A2 + // Add points after a capture: A1+=1 + // Add points to an adjacent piece: A1-A2+=1 + + try + { + string[] parts = move.ToUpper().Split(new string[] { "+=" }, StringSplitOptions.None); + if (parts.Length == 2) + { + int increment = int.Parse(parts[1]); + string[] moves = parts[0].Split('-'); + int fromTile = GetIndexFromTile(moves[0]); + if (moves.Length == 2) + { + int toTile = GetIndexFromTile(moves[1]); + + return new Move() + { + FromIndex = fromTile, + ToIndex = toTile, + Increment = increment + }; + } + else + { + return new Move() + { + FromIndex = -1, + ToIndex = fromTile, + Increment = increment + }; + } + } + else + { + string[] moves = parts[0].Split('-'); + int fromTile = GetIndexFromTile(moves[0]); + int toTile = GetIndexFromTile(moves[1]); + + return new Move() + { + FromIndex = fromTile, + ToIndex = toTile, + Increment = 0 + }; + } + } + catch (Exception) + { + return new Move() + { + FromIndex = -1, + ToIndex = -1, + Increment = -1 + }; + } + } + public static string GetTileFromIndex(int index) { // Indexes of each piece on the board... diff --git a/Chase.Engine/Position.cs b/Chase.Engine/Position.cs index c36a645..836212e 100644 --- a/Chase.Engine/Position.cs +++ b/Chase.Engine/Position.cs @@ -43,9 +43,23 @@ public Position() } } - public void MakeMove(string move) + public Move MakeMove(string move) { - // TODO: parse string moves and validate before making the move + Move parse = Move.ParseMove(move); + if (parse.IsValid) + { + foreach (Move m in GetValidMoves()) + { + if (m.FromIndex == parse.FromIndex && m.ToIndex == parse.ToIndex && m.Increment == parse.Increment) + { + // We can't use the parsed move since it doesn't contain the final direction information + MakeMove(m); + return m; + } + } + } + + return null; } public void MakeMove(Move move) diff --git a/Chase.GUI/GameForm.Designer.cs b/Chase.GUI/GameForm.Designer.cs index c180dec..c9c5ccc 100644 --- a/Chase.GUI/GameForm.Designer.cs +++ b/Chase.GUI/GameForm.Designer.cs @@ -63,6 +63,9 @@ private void InitializeComponent() this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.loadPositionFromCSNToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyCSNFromPositionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.saveGameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.loadGameFromCGNToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.computerPlaysBlueToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); @@ -72,8 +75,11 @@ private void InitializeComponent() this.showComputerAnalysisToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.showTileLabelsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.testToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveGameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.saveCgn = new System.Windows.Forms.SaveFileDialog(); + this.openCgn = new System.Windows.Forms.OpenFileDialog(); + this.nextMove = new System.Windows.Forms.Button(); + this.previousMove = new System.Windows.Forms.Button(); + this.analysisLabel = new System.Windows.Forms.Label(); this.gamePanel.SuspendLayout(); this.csnPanel.SuspendLayout(); this.addPanel.SuspendLayout(); @@ -276,6 +282,9 @@ private void InitializeComponent() // // splitContainer1.Panel2 // + this.splitContainer1.Panel2.Controls.Add(this.analysisLabel); + this.splitContainer1.Panel2.Controls.Add(this.previousMove); + this.splitContainer1.Panel2.Controls.Add(this.nextMove); this.splitContainer1.Panel2.Controls.Add(this.moveHistory); this.splitContainer1.Panel2.Controls.Add(this.infoLabel); this.splitContainer1.Size = new System.Drawing.Size(1095, 662); @@ -306,8 +315,9 @@ private void InitializeComponent() this.moveHistory.ReadOnly = true; this.moveHistory.RowHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.None; this.moveHistory.RowHeadersVisible = false; - this.moveHistory.Size = new System.Drawing.Size(294, 629); + this.moveHistory.Size = new System.Drawing.Size(294, 600); this.moveHistory.TabIndex = 1; + //this.moveHistory.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.moveHistory_CellClick); // // MoveNumber // @@ -369,7 +379,9 @@ private void InitializeComponent() this.toolStripSeparator1, this.loadPositionFromCSNToolStripMenuItem, this.copyCSNFromPositionToolStripMenuItem, - this.saveGameToolStripMenuItem}); + this.toolStripSeparator4, + this.saveGameToolStripMenuItem, + this.loadGameFromCGNToolStripMenuItem}); this.gameToolStripMenuItem.Name = "gameToolStripMenuItem"; this.gameToolStripMenuItem.Size = new System.Drawing.Size(50, 20); this.gameToolStripMenuItem.Text = "&Game"; @@ -452,6 +464,25 @@ private void InitializeComponent() this.copyCSNFromPositionToolStripMenuItem.Text = "&Copy CSN from Position"; this.copyCSNFromPositionToolStripMenuItem.Click += new System.EventHandler(this.copyCSNFromPositionToolStripMenuItem_Click); // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(200, 6); + // + // saveGameToolStripMenuItem + // + this.saveGameToolStripMenuItem.Name = "saveGameToolStripMenuItem"; + this.saveGameToolStripMenuItem.Size = new System.Drawing.Size(203, 22); + this.saveGameToolStripMenuItem.Text = "&Save Game as CGN"; + this.saveGameToolStripMenuItem.Click += new System.EventHandler(this.saveGameToolStripMenuItem_Click); + // + // loadGameFromCGNToolStripMenuItem + // + this.loadGameFromCGNToolStripMenuItem.Name = "loadGameFromCGNToolStripMenuItem"; + this.loadGameFromCGNToolStripMenuItem.Size = new System.Drawing.Size(203, 22); + this.loadGameFromCGNToolStripMenuItem.Text = "Load &Game from CGN"; + this.loadGameFromCGNToolStripMenuItem.Click += new System.EventHandler(this.loadGameFromCGNToolStripMenuItem_Click); + // // optionsToolStripMenuItem // this.optionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -530,13 +561,6 @@ private void InitializeComponent() this.testToolStripMenuItem.Visible = false; this.testToolStripMenuItem.Click += new System.EventHandler(this.testToolStripMenuItem_Click); // - // saveGameToolStripMenuItem - // - this.saveGameToolStripMenuItem.Name = "saveGameToolStripMenuItem"; - this.saveGameToolStripMenuItem.Size = new System.Drawing.Size(203, 22); - this.saveGameToolStripMenuItem.Text = "&Save Game as CGN"; - this.saveGameToolStripMenuItem.Click += new System.EventHandler(this.saveGameToolStripMenuItem_Click); - // // saveCgn // this.saveCgn.DefaultExt = "cgn"; @@ -544,6 +568,42 @@ private void InitializeComponent() this.saveCgn.Filter = "Chase Game Notation|*.cgn"; this.saveCgn.Title = "Save Game"; // + // openCgn + // + this.openCgn.DefaultExt = "cgn"; + this.openCgn.FileName = "game.cgn"; + this.openCgn.Filter = "Chase Game Notation|*.cgn"; + this.openCgn.Title = "Load a saved game"; + // + // nextMove + // + this.nextMove.Location = new System.Drawing.Point(226, 636); + this.nextMove.Name = "nextMove"; + this.nextMove.Size = new System.Drawing.Size(75, 23); + this.nextMove.TabIndex = 2; + this.nextMove.Text = ">"; + this.nextMove.UseVisualStyleBackColor = true; + this.nextMove.Click += new System.EventHandler(this.nextMove_Click); + // + // previousMove + // + this.previousMove.Location = new System.Drawing.Point(7, 636); + this.previousMove.Name = "previousMove"; + this.previousMove.Size = new System.Drawing.Size(75, 23); + this.previousMove.TabIndex = 3; + this.previousMove.Text = "<"; + this.previousMove.UseVisualStyleBackColor = true; + this.previousMove.Click += new System.EventHandler(this.previousMove_Click); + // + // analysisLabel + // + this.analysisLabel.Location = new System.Drawing.Point(88, 636); + this.analysisLabel.Name = "analysisLabel"; + this.analysisLabel.Size = new System.Drawing.Size(132, 23); + this.analysisLabel.TabIndex = 4; + this.analysisLabel.Text = "CGN"; + this.analysisLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // // GameForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -626,6 +686,12 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem twentySecondsMove; private System.Windows.Forms.ToolStripMenuItem saveGameToolStripMenuItem; private System.Windows.Forms.SaveFileDialog saveCgn; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem loadGameFromCGNToolStripMenuItem; + private System.Windows.Forms.OpenFileDialog openCgn; + private System.Windows.Forms.Button previousMove; + private System.Windows.Forms.Button nextMove; + private System.Windows.Forms.Label analysisLabel; } } diff --git a/Chase.GUI/GameForm.cs b/Chase.GUI/GameForm.cs index 731ac43..7125a27 100644 --- a/Chase.GUI/GameForm.cs +++ b/Chase.GUI/GameForm.cs @@ -21,6 +21,9 @@ public partial class GameForm : Form int selectedFromTile = -1; int selectedToTile = -1; + int analysisMaxMoves = 0; + int analysisMoveNum = 0; + public GameForm() { InitializeComponent(); @@ -203,6 +206,19 @@ private void RefreshBoard(Move lastmove) { infoLabel.Text = game.PlayerToMove.ToString() + "'s Turn"; } + + if (type == GameType.Loaded) + { + nextMove.Enabled = true; + previousMove.Enabled = true; + analysisLabel.Text = analysisMoveNum + "/" + analysisMaxMoves; + } + else + { + nextMove.Enabled = false; + previousMove.Enabled = false; + analysisLabel.Text = "CGN"; + } } private void ClickTile(int index) @@ -503,5 +519,53 @@ private void saveGameToolStripMenuItem_Click(object sender, EventArgs e) } } } + + private void loadGameFromCGNToolStripMenuItem_Click(object sender, EventArgs e) + { + DialogResult dr = openCgn.ShowDialog(); + if (dr == DialogResult.OK) + { + using (StreamReader r = new StreamReader(openCgn.FileName)) + { + analysisMaxMoves = game.LoadFromGameNotationString(r.ReadToEnd()); + } + + type = GameType.Loaded; + analysisMoveNum = analysisMaxMoves; + + Move lastMove = game.RecallState(analysisMoveNum); + RefreshBoard(lastMove); + } + } + + private void previousMove_Click(object sender, EventArgs e) + { + if (type == GameType.Loaded) + { + analysisMoveNum--; + if (analysisMoveNum < 0) + { + analysisMoveNum = 0; + } + + Move lastMove = game.RecallState(analysisMoveNum); + RefreshBoard(lastMove); + } + } + + private void nextMove_Click(object sender, EventArgs e) + { + if (type == GameType.Loaded) + { + analysisMoveNum++; + if (analysisMoveNum > analysisMaxMoves) + { + analysisMoveNum = analysisMaxMoves; + } + + Move lastMove = game.RecallState(analysisMoveNum); + RefreshBoard(lastMove); + } + } } } diff --git a/Chase.GUI/GameForm.resx b/Chase.GUI/GameForm.resx index ed9af71..512e091 100644 --- a/Chase.GUI/GameForm.resx +++ b/Chase.GUI/GameForm.resx @@ -135,6 +135,9 @@ 248, 17 + + 345, 17 + diff --git a/Chase.GUI/GameType.cs b/Chase.GUI/GameType.cs index f1ccf1d..a03f7f0 100644 --- a/Chase.GUI/GameType.cs +++ b/Chase.GUI/GameType.cs @@ -11,6 +11,7 @@ enum GameType ComputerSelfPlay, ComputerVsHuman, NotStarted, - Analysis + Analysis, + Loaded } } diff --git a/Chase.Tests/Chase.Tests.csproj b/Chase.Tests/Chase.Tests.csproj index 476061a..3b51190 100644 --- a/Chase.Tests/Chase.Tests.csproj +++ b/Chase.Tests/Chase.Tests.csproj @@ -52,6 +52,7 @@ + diff --git a/Chase.Tests/MoveTests.cs b/Chase.Tests/MoveTests.cs new file mode 100644 index 0000000..b0443a5 --- /dev/null +++ b/Chase.Tests/MoveTests.cs @@ -0,0 +1,48 @@ +using Chase.Engine; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Chase.Tests +{ + [TestClass] + public class MoveTests + { + [TestMethod] + public void TestParseMove() + { + // 0, 1, 2, 3, 4, 5, 6, 7, 8, // i + // 9, 10, 11, 12, 13, 14, 15, 16, 17, // h + // 18, 19, 20, 21, 22, 23, 24, 25, 26, // g + // 27, 28, 29, 30, 31, 32, 33, 34, 35, // f + // 36, 37, 38, 39, 40, 41, 42, 43, 44, // e + // 45, 46, 47, 48, 49, 50, 51, 52, 53, // d + // 54, 55, 56, 57, 58, 59, 60, 61, 62, // c + // 63, 64, 65, 66, 67, 68, 69, 70, 71, // b + // 72, 73, 74, 75, 76, 77, 78, 79, 80 // a + + Move move; + + move = Move.ParseMove("CH-e4"); + Assert.AreEqual(40, move.FromIndex); + Assert.AreEqual(39, move.ToIndex); + Assert.AreEqual(0, move.Increment); + + move = Move.ParseMove("A1+=3"); + Assert.AreEqual(-1, move.FromIndex); + Assert.AreEqual(72, move.ToIndex); + Assert.AreEqual(3, move.Increment); + + move = Move.ParseMove("b1-b2+=2"); + Assert.AreEqual(63, move.FromIndex); + Assert.AreEqual(64, move.ToIndex); + Assert.AreEqual(2, move.Increment); + + move = Move.ParseMove("boom"); + Assert.IsFalse(move.IsValid); + } + } +}