diff --git a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs b/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs
index b7c58fe..f9ce7f9 100644
--- a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs
+++ b/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs
@@ -1,11 +1,10 @@
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
using System.Text;
using System.Text.RegularExpressions;
-using Domain = Shogi.Domain;
namespace Gameboard.ShogiUI.Sockets.Extensions
{
- public static class ModelExtensions
+ public static class ModelExtensions
{
public static string GetShortName(this Models.Piece self)
{
diff --git a/Gameboard.ShogiUI.Sockets/Startup.cs b/Gameboard.ShogiUI.Sockets/Startup.cs
index cb857d0..e0c7407 100644
--- a/Gameboard.ShogiUI.Sockets/Startup.cs
+++ b/Gameboard.ShogiUI.Sockets/Startup.cs
@@ -24,9 +24,7 @@ using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Security.Claims;
using System.Text;
-using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets
{
diff --git a/Shogi.Domain/BoardTile.cs b/Shogi.Domain/BoardTile.cs
deleted file mode 100644
index 4662e01..0000000
--- a/Shogi.Domain/BoardTile.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Shogi.Domain.Pieces;
-
-namespace Shogi.Domain
-{
- internal class BoardTile
- {
- public BoardTile(Piece piece, Vector2 position)
- {
- Piece = piece;
- Position = position;
- }
-
- public Piece Piece { get; }
-
- public Vector2 Position { get; }
- }
-}
diff --git a/Shogi.Domain/DomainExtensions.cs b/Shogi.Domain/DomainExtensions.cs
new file mode 100644
index 0000000..7471221
--- /dev/null
+++ b/Shogi.Domain/DomainExtensions.cs
@@ -0,0 +1,10 @@
+using Shogi.Domain.Pieces;
+
+namespace Shogi.Domain
+{
+ internal static class DomainExtensions
+ {
+ public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King;
+
+ }
+}
diff --git a/Shogi.Domain/Pieces/Piece.cs b/Shogi.Domain/Pieces/Piece.cs
index 832aefb..6a54cfe 100644
--- a/Shogi.Domain/Pieces/Piece.cs
+++ b/Shogi.Domain/Pieces/Piece.cs
@@ -55,6 +55,13 @@ namespace Shogi.Domain.Pieces
IsPromoted = false;
}
+ ///
+ /// Respecting the move-set of the Piece, collect all positions from start to end.
+ /// Useful if you need to iterate a move-set.
+ ///
+ ///
+ ///
+ /// An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions.
public IEnumerable GetPathFromStartToEnd(Vector2 start, Vector2 end)
{
var steps = new List(10);
diff --git a/Shogi.Domain/Shogi.cs b/Shogi.Domain/Shogi.cs
index 5b16827..f4e6bd1 100644
--- a/Shogi.Domain/Shogi.cs
+++ b/Shogi.Domain/Shogi.cs
@@ -1,5 +1,4 @@
using Shogi.Domain.Pieces;
-using System;
using System.Text;
namespace Shogi.Domain
@@ -20,8 +19,8 @@ namespace Shogi.Domain
public Shogi(ShogiBoardState board)
{
+ this.rules = new StandardRules(board);
this.boardState = board;
- rules = new StandardRules(this.boardState);
}
///
diff --git a/Shogi.Domain/ShogiBoardState.cs b/Shogi.Domain/ShogiBoardState.cs
index a678ff7..69c1645 100644
--- a/Shogi.Domain/ShogiBoardState.cs
+++ b/Shogi.Domain/ShogiBoardState.cs
@@ -1,5 +1,6 @@
using Shogi.Domain.Pieces;
using System.Text.RegularExpressions;
+using BoardTile = System.Collections.Generic.KeyValuePair;
namespace Shogi.Domain
{
@@ -15,25 +16,6 @@ namespace Shogi.Domain
///
private readonly Dictionary board;
- public List ActivePlayerHand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand;
- ///
- /// "Active Player" means the player whose turn it is.
- ///
- public Vector2 ActivePlayerKingPosition => WhoseTurn == WhichPlayer.Player1 ? Player1KingPosition : Player2KingPosition;
- ///
- /// "Opposing Player" means the player whose turn it isn't.
- ///
- public Vector2 OpposingPlayerKingPosition => WhoseTurn == WhichPlayer.Player1 ? Player2KingPosition : Player1KingPosition;
- public Vector2 Player1KingPosition { get; set; }
- public Vector2 Player2KingPosition { get; set; }
- public List Player1Hand { get; }
- public List Player2Hand { get; }
- public Vector2 PreviousMoveFrom { get; private set; }
- public Vector2 PreviousMoveTo { get; private set; }
- public WhichPlayer WhoseTurn { get; set; }
- public WhichPlayer? InCheck { get; set; }
- public bool IsCheckmate { get; set; }
-
public ShogiBoardState()
{
board = new Dictionary(81, StringComparer.OrdinalIgnoreCase);
@@ -41,9 +23,26 @@ namespace Shogi.Domain
Player1Hand = new List();
Player2Hand = new List();
PreviousMoveTo = Vector2.Zero;
- CacheKingPositions();
}
+ public List ActivePlayerHand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand;
+ public Vector2 Player1KingPosition => FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
+ {
+ var piece = kvp.Value;
+ return piece!.IsKing() && piece!.Owner == WhichPlayer.Player1;
+ }).Key);
+ public Vector2 Player2KingPosition => FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
+ {
+ var piece = kvp.Value;
+ return piece!.IsKing() && piece!.Owner == WhichPlayer.Player2;
+ }).Key);
+ public List Player1Hand { get; }
+ public List Player2Hand { get; }
+ public Vector2 PreviousMoveFrom { get; private set; }
+ public Vector2 PreviousMoveTo { get; private set; }
+ public WhichPlayer WhoseTurn { get; set; }
+ public WhichPlayer? InCheck { get; set; }
+ public bool IsCheckmate { get; set; }
///
/// Copy constructor.
@@ -61,30 +60,13 @@ namespace Shogi.Domain
PreviousMoveTo = other.PreviousMoveTo;
Player1Hand.AddRange(other.Player1Hand);
Player2Hand.AddRange(other.Player2Hand);
- Player1KingPosition = other.Player1KingPosition;
- Player2KingPosition = other.Player2KingPosition;
}
public Piece? this[string notation]
{
// TODO: Validate "notation" here and throw an exception if invalid.
get => board[notation];
- set
- {
- if (value?.WhichPiece == WhichPiece.King)
- {
- if (value.Owner == WhichPlayer.Player1)
- {
- // TODO: This FromBoardNotation() is a waste if the Vector2 indexer was called. :(
- Player1KingPosition = FromBoardNotation(notation);
- }
- else if (value.Owner == WhichPlayer.Player2)
- {
- Player2KingPosition = FromBoardNotation(notation);
- }
- }
- board[notation] = value;
- }
+ set => board[notation] = value;
}
public Piece? this[Vector2 vector]
@@ -112,21 +94,7 @@ namespace Shogi.Domain
{
return !path.Any()
|| path.SkipLast(1).Any(position => this[position] != null)
- || this[path.Last()]?.Owner == WhoseTurn;
- }
-
- public void ForEachNotNull(ForEachDelegate callback)
- {
- for (var x = 0; x < 9; x++)
- {
- for (var y = 0; y < 9; y++)
- {
- var position = new Vector2(x, y);
- var elem = this[position];
- if (elem != null)
- callback(elem, position);
- }
- }
+ || this[path.Last()]?.Owner == WhoseTurn;
}
internal bool IsWithinPromotionZone(Vector2 position)
@@ -141,9 +109,10 @@ namespace Shogi.Domain
&& position.Y <= 8 && position.Y >= 0;
}
- internal IEnumerable GetTilesOccupiedBy(WhichPlayer whichPlayer) => board
+ internal List GetTilesOccupiedBy(WhichPlayer whichPlayer) => board
.Where(kvp => kvp.Value?.Owner == whichPlayer)
- .Select(kvp => new BoardTile(kvp.Value!, FromBoardNotation(kvp.Key)));
+ .Select(kvp => new BoardTile(FromBoardNotation(kvp.Key), kvp.Value!))
+ .ToList();
internal void Capture(Vector2 to)
{
@@ -167,7 +136,7 @@ namespace Shogi.Domain
}
}
- internal Piece? GetFirstPieceAlongPath(IEnumerable path)
+ internal Piece? QueryFirstPieceInPath(IEnumerable path)
{
foreach (var step in path)
{
@@ -197,24 +166,6 @@ namespace Shogi.Domain
throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
}
- private void CacheKingPositions()
- {
- ForEachNotNull((tile, position) =>
- {
- if (tile.WhichPiece == WhichPiece.King)
- {
- if (tile.Owner == WhichPlayer.Player1)
- {
- Player1KingPosition = position;
- }
- else if (tile.Owner == WhichPlayer.Player2)
- {
- Player2KingPosition = position;
- }
- }
- });
- }
-
private void InitializeBoardState()
{
this["A1"] = new Lance(WhichPlayer.Player1);
diff --git a/Shogi.Domain/StandardRules.cs b/Shogi.Domain/StandardRules.cs
index ac11ff7..3352424 100644
--- a/Shogi.Domain/StandardRules.cs
+++ b/Shogi.Domain/StandardRules.cs
@@ -1,6 +1,5 @@
-using System.Runtime.CompilerServices;
+using BoardTile = System.Collections.Generic.KeyValuePair;
-[assembly: InternalsVisibleTo("Shogi.Domain.UnitTests")]
namespace Shogi.Domain
{
internal class StandardRules
@@ -130,7 +129,7 @@ namespace Shogi.Domain
var direction = Vector2.Subtract(kingPosition, boardState.PreviousMoveFrom);
var slope = Math.Abs(direction.Y / direction.X);
var path = ShogiBoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMoveFrom, Vector2.Normalize(direction));
- var threat = boardState.GetFirstPieceAlongPath(path);
+ var threat = boardState.QueryFirstPieceInPath(path);
if (threat == null || threat.Owner == previousMovedPiece.Owner) return false;
// If absolute slope is 45°, look for a bishop along the line.
// If absolute slope is 0° or 90°, look for a rook along the line.
@@ -173,7 +172,7 @@ namespace Shogi.Domain
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition;
var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition);
- var threatenedPiece = boardState.GetFirstPieceAlongPath(path);
+ var threatenedPiece = boardState.QueryFirstPieceInPath(path);
if (!path.Any() || threatenedPiece == null) return false;
return threatenedPiece.WhichPiece == WhichPiece.King;
@@ -183,32 +182,44 @@ namespace Shogi.Domain
{
if (!boardState.InCheck.HasValue) return false;
-
// Get all pieces from "other player" who threaten the king in question.
var otherPlayer = whichPlayer == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
var tilesOccupiedByOtherPlayer = boardState.GetTilesOccupiedBy(otherPlayer);
+ var kingPosition = whichPlayer == WhichPlayer.Player1
+ ? boardState.Player1KingPosition
+ : boardState.Player2KingPosition;
+ var threats = tilesOccupiedByOtherPlayer.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList();
- if (tilesOccupiedByOtherPlayer.SingleOrDefault() != default)
+
+ if (threats.Count == 1)
{
/* If there is exactly one threat it is possible to block the check.
* Foreach piece owned by whichPlayer
* if piece can intercept check, return false;
*/
- var threat = tilesOccupiedByOtherPlayer.Single();
- var kingPosition = whichPlayer == WhichPlayer.Player1
- ? boardState.Player1KingPosition
- : boardState.Player2KingPosition;
- var tiles = boardState.GetTilesOccupiedBy(whichPlayer);
- var line = Vector2.Subtract(kingPosition, threat.Position);
- var slope = line.Y / line.X;
- foreach (var tile in tiles)
+ var threat = threats.Single();
+ var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition);
+ var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(whichPlayer);
+ foreach (var threatBlockingPosition in pathFromThreatToKing)
{
- // y = mx + b; slope intercept
- // b = -mx + y;
- var b = -slope * tile.Position.X + tile.Position.Y;
- //if (tile.Position.Y = slope * tile.Position.X + )
- }
+ var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat
+ .Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition))
+ .ToList();
+ if (tilesThatCouldBlockTheThreat.Any())
+ {
+ return false; // Cannot be check-mate if a piece can intercept the threat.
+ }
+ }
+ //var line = Vector2.Subtract(kingPosition, threat.Position);
+ //var slope = line.Y / line.X;
+ //foreach (var tile in tilesThatCouldBlockTheThreat)
+ //{
+ // // y = mx + b; slope intercept
+ // // b = -mx + y;
+ // var b = -slope * tile.Position.X + tile.Position.Y;
+ // //if (tile.Position.Y = slope * tile.Position.X + )
+ //}
}
/* If no ability to block the check, maybe the king can evade check by moving.
@@ -217,34 +228,15 @@ namespace Shogi.Domain
* Foreach piece owned by "other player", check if piece threatens king position.
*/
- //
return false;
+ }
-
-
-
-
-
- //foreach (var kvp in boardState)
- //{
- // if (kvp.Value == null) continue;
- // var position = ShogiBoardState.FromBoardNotation(kvp.Key);
- // var piece = kvp.Value;
- // foreach (var path in piece.MoveSet)
- // {
- // if (path.Distance == Distance.OneStep)
- // {
- // var move = path.Direction + position;
- // var simulationState = new ShogiBoardState(boardState);
- // var simulation = new Shogi(simulationState);
- // simulation.Move(position, move);
- // }
- // else if (path.Distance == Distance.MultiStep)
- // {
-
- // }
- // }
- //}
+ private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget)
+ {
+ var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget);
+ return path
+ .SkipLast(1)
+ .All(position => this.boardState[ShogiBoardState.ToBoardNotation(position)] == null);
}
public bool EvaluateCheckmate_Old()
@@ -253,31 +245,31 @@ namespace Shogi.Domain
// Assume true and try to disprove.
var isCheckmate = true;
- boardState.ForEachNotNull((piece, from) => // For each piece...
- {
- // Short circuit
- if (!isCheckmate) return;
+ //boardState.ForEachNotNull((piece, from) => // For each piece...
+ //{
+ // Short circuit
+ //if (!isCheckmate) return;
- if (piece.Owner == boardState.InCheck) // ...owned by the player in check...
- {
- // ...evaluate if any move gets the player out of check.
- //PathEvery(from, (other, position) =>
- //{
- // var simulationBoard = new StandardRules(new ShogiBoardState(board));
- // var fromNotation = ShogiBoardState.ToBoardNotation(from);
- // var toNotation = ShogiBoardState.ToBoardNotation(position);
- // var simulationResult = simulationBoard.Move(fromNotation, toNotation, false);
- // if (simulationResult.Success)
- // {
- // //if (!IsPlayerInCheckAfterMove(from, position, board.InCheck.Value))
- // //{
- // // isCheckmate = false;
- // //}
- // }
- //});
- }
- // TODO: Assert that a player could not place a piece from their hand to avoid check.
- });
+ //if (piece.Owner == boardState.InCheck) // ...owned by the player in check...
+ //{
+ // ...evaluate if any move gets the player out of check.
+ //PathEvery(from, (other, position) =>
+ //{
+ // var simulationBoard = new StandardRules(new ShogiBoardState(board));
+ // var fromNotation = ShogiBoardState.ToBoardNotation(from);
+ // var toNotation = ShogiBoardState.ToBoardNotation(position);
+ // var simulationResult = simulationBoard.Move(fromNotation, toNotation, false);
+ // if (simulationResult.Success)
+ // {
+ // //if (!IsPlayerInCheckAfterMove(from, position, board.InCheck.Value))
+ // //{
+ // // isCheckmate = false;
+ // //}
+ // }
+ //});
+ //}
+ // TODO: Assert that a player could not place a piece from their hand to avoid check.
+ //});
return isCheckmate;
}
}