diff --git a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json
index 230be61..05ff5bf 100644
--- a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json
+++ b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json
@@ -1,13 +1,13 @@
{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
- "applicationUrl": "http://localhost:63676",
- "sslPort": 44396
+ "applicationUrl": "http://localhost:50728/",
+ "sslPort": 44315
}
},
- "$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
diff --git a/Shogi.Domain.UnitTests/ShogiShould.cs b/Shogi.Domain.UnitTests/ShogiShould.cs
index fc07cb5..745e28f 100644
--- a/Shogi.Domain.UnitTests/ShogiShould.cs
+++ b/Shogi.Domain.UnitTests/ShogiShould.cs
@@ -17,10 +17,12 @@ namespace Shogi.Domain.UnitTests
//[Fact]
//public void InitializeBoardStateWithMoves()
//{
+ // var board = new ShogiBoardState();
+ // var rules = new StandardRules(board);
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("A3", "A4")
+ // // P1 Pawn
+ // new Move("A3", "A4")
// };
// var shogi = new Shogi(moves);
// shogi.Board["A3"].Should().BeNull();
@@ -33,12 +35,12 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("G7", "G6"),
- // // P1 Bishop puts P2 in check
- // new Move("B2", "G7"),
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("G7", "G6"),
+ // // P1 Bishop puts P2 in check
+ // new Move("B2", "G7"),
// };
// var shogi = new Shogi(moves);
// shogi.InCheck.Should().Be(WhichPlayer.Player2);
@@ -158,12 +160,12 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("G7", "G6"),
- // // P1 Bishop puts P2 in check
- // new Move("B2", "G7")
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("G7", "G6"),
+ // // P1 Bishop puts P2 in check
+ // new Move("B2", "G7")
// };
// var shogi = new Shogi(moves);
// shogi.InCheck.Should().Be(WhichPlayer.Player2);
@@ -184,26 +186,26 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("I7", "I6"),
- // // P1 Bishop takes P2 Pawn.
- // new Move("B2", "G7"),
- // // P2 Gold, block check from P1 Bishop.
- // new Move("F9", "F8"),
- // // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance
- // new Move("G7", "H8", true),
- // // P2 Pawn again
- // new Move("I6", "I5"),
- // // P1 Bishop takes P2 Knight
- // new Move("H8", "H9"),
- // // P2 Pawn again
- // new Move("I5", "I4"),
- // // P1 Bishop takes P2 Lance
- // new Move("H9", "I9"),
- // // P2 Pawn captures P1 Pawn
- // new Move("I4", "I3")
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("I7", "I6"),
+ // // P1 Bishop takes P2 Pawn.
+ // new Move("B2", "G7"),
+ // // P2 Gold, block check from P1 Bishop.
+ // new Move("F9", "F8"),
+ // // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance
+ // new Move("G7", "H8", true),
+ // // P2 Pawn again
+ // new Move("I6", "I5"),
+ // // P1 Bishop takes P2 Knight
+ // new Move("H8", "H9"),
+ // // P2 Pawn again
+ // new Move("I5", "I4"),
+ // // P1 Bishop takes P2 Lance
+ // new Move("H9", "I9"),
+ // // P2 Pawn captures P1 Pawn
+ // new Move("I4", "I3")
// };
// var shogi = new Shogi(moves);
// shogi.Player1Hand.Count.Should().Be(4);
@@ -251,20 +253,20 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("G7", "G6"),
- // // P1 Pawn, arbitrary move.
- // new Move("A3", "A4"),
- // // P2 Bishop takes P1 Bishop
- // new Move("H8", "B2"),
- // // P1 Silver takes P2 Bishop
- // new Move("C1", "B2"),
- // // P2 Pawn, arbtrary move
- // new Move("A7", "A6"),
- // // P1 drop Bishop, place P2 in check
- // new Move(WhichPiece.Bishop, "G7")
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("G7", "G6"),
+ // // P1 Pawn, arbitrary move.
+ // new Move("A3", "A4"),
+ // // P2 Bishop takes P1 Bishop
+ // new Move("H8", "B2"),
+ // // P1 Silver takes P2 Bishop
+ // new Move("C1", "B2"),
+ // // P2 Pawn, arbtrary move
+ // new Move("A7", "A6"),
+ // // P1 drop Bishop, place P2 in check
+ // new Move(WhichPiece.Bishop, "G7")
// };
// var shogi = new Shogi(moves);
// shogi.InCheck.Should().Be(WhichPlayer.Player2);
@@ -287,14 +289,14 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("G7", "G6"),
- // // P1 Bishop capture P2 Bishop
- // new Move("B2", "H8"),
- // // P2 Pawn
- // new Move("G6", "G5")
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("G7", "G6"),
+ // // P1 Bishop capture P2 Bishop
+ // new Move("B2", "H8"),
+ // // P2 Pawn
+ // new Move("G6", "G5")
// };
// var shogi = new Shogi(moves);
// using (new AssertionScope())
@@ -325,10 +327,10 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4"),
- // // P2 Pawn
- // new Move("G7", "G6"),
+ // // P1 Pawn
+ // new Move("C3", "C4"),
+ // // P2 Pawn
+ // new Move("G7", "G6"),
// };
// var shogi = new Shogi(moves);
@@ -345,10 +347,10 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Pawn
- // new Move("C3", "C4" ),
- // // P2 Pawn
- // new Move("G7", "G6" )
+ // // P1 Pawn
+ // new Move("C3", "C4" ),
+ // // P2 Pawn
+ // new Move("G7", "G6" )
// };
// var shogi = new Shogi(moves);
@@ -373,26 +375,26 @@ namespace Shogi.Domain.UnitTests
// // Arrange
// var moves = new[]
// {
- // // P1 Rook
- // new Move("H2", "E2"),
- // // P2 Gold
- // new Move("F9", "G8"),
- // // P1 Pawn
- // new Move("E3", "E4"),
- // // P2 other Gold
- // new Move("D9", "C8"),
- // // P1 same Pawn
- // new Move("E4", "E5"),
- // // P2 Pawn
- // new Move("E7", "E6"),
- // // P1 Pawn takes P2 Pawn
- // new Move("E5", "E6"),
- // // P2 King
- // new Move("E9", "E8"),
- // // P1 Pawn promotes, threatens P2 King
- // new Move("E6", "E7", true),
- // // P2 King retreat
- // new Move("E8", "E9"),
+ // // P1 Rook
+ // new Move("H2", "E2"),
+ // // P2 Gold
+ // new Move("F9", "G8"),
+ // // P1 Pawn
+ // new Move("E3", "E4"),
+ // // P2 other Gold
+ // new Move("D9", "C8"),
+ // // P1 same Pawn
+ // new Move("E4", "E5"),
+ // // P2 Pawn
+ // new Move("E7", "E6"),
+ // // P1 Pawn takes P2 Pawn
+ // new Move("E5", "E6"),
+ // // P2 King
+ // new Move("E9", "E8"),
+ // // P1 Pawn promotes, threatens P2 King
+ // new Move("E6", "E7", true),
+ // // P2 King retreat
+ // new Move("E8", "E9"),
// };
// var shogi = new Shogi(moves);
// output.WriteLine(shogi.PrintStateAsAscii());
diff --git a/Shogi.Domain.UnitTests/StandardRulesShould.cs b/Shogi.Domain.UnitTests/StandardRulesShould.cs
new file mode 100644
index 0000000..7f132f3
--- /dev/null
+++ b/Shogi.Domain.UnitTests/StandardRulesShould.cs
@@ -0,0 +1,31 @@
+using FluentAssertions;
+using FluentAssertions.Execution;
+using Xunit;
+
+namespace Shogi.Domain.UnitTests
+{
+ public class StandardRulesShould
+ {
+ [Fact]
+ public void AllowValidMoves_AfterCheck()
+ {
+ // Arrange
+ var board = new ShogiBoardState();
+ var rules = new StandardRules(board);
+ rules.Move("C3", "C4"); // P1 Pawn
+ rules.Move("G7", "G6"); // P2 Pawn
+ rules.Move("B2", "G7"); // P1 Bishop puts P2 in check
+ board.InCheck.Should().Be(WhichPlayer.Player2);
+
+ // Act - P2 is able to un-check theirself.
+ /// P2 King moves out of check
+ var moveSuccess = rules.Move("E9", "E8");
+
+ // Assert
+ using var _ = new AssertionScope();
+ moveSuccess.Success.Should().BeTrue();
+ moveSuccess.Reason.Should().BeEmpty();
+ board.InCheck.Should().BeNull();
+ }
+ }
+}
diff --git a/Shogi.Domain/Shogi.cs b/Shogi.Domain/Shogi.cs
index cb0f990..1aca58a 100644
--- a/Shogi.Domain/Shogi.cs
+++ b/Shogi.Domain/Shogi.cs
@@ -31,25 +31,21 @@
public MoveResult CanMove(string from, string to, bool isPromotion)
{
- // TODO: ShogiBoardState.FromBoardNotation should not throw an execption in this query method.
- var fromVector = ShogiBoardState.FromBoardNotation(from);
- var toVector = ShogiBoardState.FromBoardNotation(to);
var simulator = new StandardRules(new ShogiBoardState(board));
- return simulator.Move(fromVector, toVector, isPromotion);
+ return simulator.Move(from, to, isPromotion);
}
public MoveResult CanMove(WhichPiece pieceInHand, string to)
{
- var toVector = ShogiBoardState.FromBoardNotation(to);
var simulator = new StandardRules(new ShogiBoardState(board));
- return simulator.Move(pieceInHand, toVector);
+ return simulator.Move(pieceInHand, to);
}
public void Move(string from, string to, bool isPromotion)
{
var fromVector = ShogiBoardState.FromBoardNotation(from);
var toVector = ShogiBoardState.FromBoardNotation(to);
- var moveResult = rules.Move(fromVector, toVector, isPromotion);
+ var moveResult = rules.Move(from, to, isPromotion);
if (!moveResult.Success)
{
throw new InvalidOperationException(moveResult.Reason);
diff --git a/Shogi.Domain/StandardRules.cs b/Shogi.Domain/StandardRules.cs
index aa9816c..8b89f5d 100644
--- a/Shogi.Domain/StandardRules.cs
+++ b/Shogi.Domain/StandardRules.cs
@@ -1,5 +1,7 @@
using System.Numerics;
+using System.Runtime.CompilerServices;
+[assembly: InternalsVisibleTo("Shogi.Domain.UnitTests")]
namespace Shogi.Domain
{
internal class StandardRules
@@ -39,11 +41,13 @@ namespace Shogi.Domain
///
/// Move a piece from a board tile to another board tile.
///
- /// The position of the piece being moved expressed in board notation.
- /// The target position expressed in board notation.
+ /// The position of the piece being moved expressed in board notation.
+ /// The target position expressed in board notation.
/// A describing the success or failure of the simulation.
- public MoveResult Move(Vector2 from, Vector2 to, bool isPromotion = false)
+ public MoveResult Move(string fromNotation, string toNotation, bool isPromotion = false)
{
+ var from = ShogiBoardState.FromBoardNotation(fromNotation);
+ var to = ShogiBoardState.FromBoardNotation(toNotation);
var fromPiece = board[from];
if (fromPiece == null)
{
@@ -55,7 +59,7 @@ namespace Shogi.Domain
return new MoveResult(false, "Not allowed to move the opponents piece");
}
- if (IsPathable(from, to) == false)
+ if (ShogiIsPathable(from, to) == false)
{
return new MoveResult(false, $"Proposed move is not part of the move-set for piece {fromPiece.WhichPiece}.");
}
@@ -106,8 +110,9 @@ namespace Shogi.Domain
///
/// The target position expressed in board notation.
/// A describing the success or failure of the simulation.
- public MoveResult Move(WhichPiece pieceInHand, Vector2 to)
+ public MoveResult Move(WhichPiece pieceInHand, string toNotation)
{
+ var to = ShogiBoardState.FromBoardNotation(toNotation);
var index = board.Hand.FindIndex(p => p.WhichPiece == pieceInHand);
if (index == -1)
{
@@ -150,7 +155,7 @@ namespace Shogi.Domain
return new MoveResult(true);
}
- private bool IsPathable(Vector2 from, Vector2 to)
+ private bool ShogiIsPathable(Vector2 from, Vector2 to)
{
var piece = board[from];
if (piece == null) return false;
@@ -255,7 +260,9 @@ namespace Shogi.Domain
PathEvery(from, (other, position) =>
{
var simulationBoard = new StandardRules(new ShogiBoardState(board));
- var simulationResult = simulationBoard.Move(from, position, false);
+ var fromNotation = ShogiBoardState.ToBoardNotation(from);
+ var toNotation = ShogiBoardState.ToBoardNotation(position);
+ var simulationResult = simulationBoard.Move(fromNotation, toNotation, false);
if (simulationResult.Success)
{
if (!EvaluateCheckAfterMove(from, position, board.InCheck.Value))
@@ -288,7 +295,7 @@ namespace Shogi.Domain
if (piece == null) return false;
var path = FindDirectionTowardsDestination(GetMoveSet(piece.WhichPiece).GetMoves(piece.IsUpsideDown), origin, destination);
- if (!IsPathable(origin, destination))
+ if (!IsPathable(origin, destination, path.Direction))
{
// Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen.
return false;
@@ -341,6 +348,25 @@ namespace Shogi.Domain
}
}
+ public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction)
+ {
+ var next = Vector2.Add(origin, direction);
+ if (Vector2.Distance(next, destination) >= Vector2.Distance(origin, destination)) return false;
+
+ var slope = (destination.Y - origin.Y) / (destination.X - origin.X);
+ if (float.IsInfinity(slope))
+ {
+ return next.X == destination.X;
+ }
+ else
+ {
+ // b = -mx + y
+ var yIntercept = -slope * origin.X + origin.Y;
+ // y = mx + b
+ return next.Y == slope * next.X + yIntercept;
+ }
+ }
+
///
/// Path the line from origin to destination, ignoring any Paths defined by the element at origin.
///