yep
This commit is contained in:
@@ -182,16 +182,6 @@ public class BoardState
|
|||||||
return new MoveResult(true);
|
return new MoveResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the given path can be traversed without colliding into a piece.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPathBlocked(IEnumerable<Vector2> path)
|
|
||||||
{
|
|
||||||
return !path.Any()
|
|
||||||
|| path.SkipLast(1).Any(position => this[position] != null)
|
|
||||||
|| this[path.Last()]?.Owner == WhoseTurn;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsWithinPromotionZone(Vector2 position)
|
internal bool IsWithinPromotionZone(Vector2 position)
|
||||||
{
|
{
|
||||||
// TODO: Move this promotion zone logic into the StandardRules class.
|
// TODO: Move this promotion zone logic into the StandardRules class.
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ namespace Shogi.Domain.ValueObjects
|
|||||||
public WhichPiece WhichPiece { get; }
|
public WhichPiece WhichPiece { get; }
|
||||||
public WhichPlayer Owner { get; private set; }
|
public WhichPlayer Owner { get; private set; }
|
||||||
public bool IsPromoted { get; private set; }
|
public bool IsPromoted { get; private set; }
|
||||||
public bool IsUpsideDown => Owner == WhichPlayer.Player2;
|
|
||||||
|
|
||||||
protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
||||||
{
|
{
|
||||||
WhichPiece = piece;
|
WhichPiece = piece;
|
||||||
@@ -60,7 +58,7 @@ namespace Shogi.Domain.ValueObjects
|
|||||||
{
|
{
|
||||||
var steps = new List<Vector2>(10);
|
var steps = new List<Vector2>(10);
|
||||||
|
|
||||||
var path = MoveSet.GetNearestPath(start, end);
|
var path = GetNearestPath(MoveSet, start, end);
|
||||||
var position = start;
|
var position = start;
|
||||||
while (Vector2.Distance(start, position) < Vector2.Distance(start, end))
|
while (Vector2.Distance(start, position) < Vector2.Distance(start, end))
|
||||||
{
|
{
|
||||||
@@ -77,5 +75,25 @@ namespace Shogi.Domain.ValueObjects
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Path GetNearestPath(IEnumerable<Path> paths, Vector2 start, Vector2 end)
|
||||||
|
{
|
||||||
|
if (!paths.DefaultIfEmpty().Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("No paths to get nearest path from.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortestPath = paths.First();
|
||||||
|
foreach (var path in paths.Skip(1))
|
||||||
|
{
|
||||||
|
var distance = Vector2.Distance(start + path.Step, end);
|
||||||
|
var shortestDistance = Vector2.Distance(start + shortestPath.Step, end);
|
||||||
|
if (distance < shortestDistance)
|
||||||
|
{
|
||||||
|
shortestPath = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shortestPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
||||||
|
using System.Threading.Tasks;
|
||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.Domain.ValueObjects;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -8,18 +9,16 @@ namespace Shogi.Domain.ValueObjects;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ShogiBoard
|
public sealed class ShogiBoard
|
||||||
{
|
{
|
||||||
private readonly StandardRules rules;
|
private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||||
private static readonly Vector2 BoardSize = new(9, 9);
|
private static readonly Vector2 BoardSize = new(9, 9);
|
||||||
|
|
||||||
public ShogiBoard(BoardState initialState)
|
public ShogiBoard(BoardState initialState)
|
||||||
{
|
{
|
||||||
BoardState = initialState;
|
BoardState = initialState;
|
||||||
rules = new StandardRules(BoardState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoardState BoardState { get; }
|
public BoardState BoardState { get; }
|
||||||
|
|
||||||
private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
||||||
@@ -164,24 +163,101 @@ public sealed class ShogiBoard
|
|||||||
: WhichPlayer.Player1;
|
: WhichPlayer.Player1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (rules.IsOpponentInCheckAfterMove())
|
// A pawn, placed from the hand, cannot be the cause of checkmate.
|
||||||
//{
|
|
||||||
// BoardState.InCheck = otherPlayer;
|
|
||||||
// // A pawn, placed from the hand, cannot be the cause of checkmate.
|
|
||||||
// if (rules.IsOpponentInCheckMate() && pieceInHand != WhichPiece.Pawn)
|
// if (rules.IsOpponentInCheckMate() && pieceInHand != WhichPiece.Pawn)
|
||||||
// {
|
// {
|
||||||
// BoardState.IsCheckmate = true;
|
// BoardState.IsCheckmate = true;
|
||||||
// }
|
// }
|
||||||
//}
|
|
||||||
|
|
||||||
return new MoveResult(true);
|
return new MoveResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public GameOverResult EvaluateGameOver()
|
private async Task<GameOverResult> EvaluateGameOver()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!BoardState.InCheck.HasValue)
|
||||||
{
|
{
|
||||||
return GameOverResult.GameIsNotOver;
|
return GameOverResult.GameIsNotOver;
|
||||||
}
|
}
|
||||||
|
var kingInCheck = BoardState.State
|
||||||
|
.Single(kvp => kvp.Value?.WhichPiece == WhichPiece.King && kvp.Value?.Owner == BoardState.InCheck);
|
||||||
|
var kingInCheckVectorPosition = Notation.FromBoardNotation(kingInCheck.Key);
|
||||||
|
|
||||||
|
var piecesOfPlayerInCheck = BoardState.State
|
||||||
|
.Where(kvp => kvp.Value?.Owner == BoardState.InCheck)
|
||||||
|
.Cast<KeyValuePair<string, Piece>>();
|
||||||
|
|
||||||
|
foreach (var (notation, piece) in piecesOfPlayerInCheck)
|
||||||
|
{
|
||||||
|
// Get possible locations this piece could move to.
|
||||||
|
var allPossibleMoves = GetPossiblePositionsForPiece(Notation.FromBoardNotation(notation), piece);
|
||||||
|
// Try to make a legal move, disproving checkmate.
|
||||||
|
foreach (var move in allPossibleMoves)
|
||||||
|
{
|
||||||
|
var simState = new BoardState(BoardState);
|
||||||
|
simState.Move(notation, Notation.ToBoardNotation(move), false);
|
||||||
|
var inCheckResult = IsEitherPlayerInCheck(simState);
|
||||||
|
if (inCheckResult == InCheckResult.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// while (tasks.Any()) var result = Task.WhenAny(tasks); // Then check for GameIsNotOver and maybe return early.
|
||||||
|
|
||||||
|
|
||||||
|
var gameOverResult = BoardState.State
|
||||||
|
.Where(kvp => kvp.Value != null)
|
||||||
|
.Cast<KeyValuePair<string, Piece>>()
|
||||||
|
.Aggregate(GameOverResult.GameIsNotOver, (inCheckResult, kvp) =>
|
||||||
|
{
|
||||||
|
var newInCheckResult = inCheckResult;
|
||||||
|
var threatPiece = kvp.Value;
|
||||||
|
var opposingKingPosition = Notation.FromBoardNotation(kingInCheck.Single(king => king.Value.Owner != threatPiece.Owner).Key);
|
||||||
|
var positionsThreatened = threatPiece.GetPathFromStartToEnd(Notation.FromBoardNotation(kvp.Key), opposingKingPosition);
|
||||||
|
|
||||||
|
foreach (var position in positionsThreatened)
|
||||||
|
{
|
||||||
|
// No piece at this position, so pathing is unobstructed. Continue pathing.
|
||||||
|
if (simState[position] == null) continue;
|
||||||
|
|
||||||
|
var threatenedPiece = simState[position]!;
|
||||||
|
if (threatenedPiece.WhichPiece == WhichPiece.King && threatenedPiece.Owner != threatPiece.Owner)
|
||||||
|
{
|
||||||
|
newInCheckResult |= threatenedPiece.Owner == WhichPlayer.Player1 ? InCheckResult.Player1InCheck : InCheckResult.Player2InCheck;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newInCheckResult;
|
||||||
|
});
|
||||||
|
|
||||||
|
return GameOverResult.GameIsNotOver;
|
||||||
|
|
||||||
|
Vector2[] GetPossiblePositionsForPiece(Vector2 piecePosition, Piece piece)
|
||||||
|
{
|
||||||
|
var paths = piece.MoveSet;
|
||||||
|
return
|
||||||
|
paths
|
||||||
|
.SelectMany(path =>
|
||||||
|
{
|
||||||
|
var list = new List<Vector2>(10);
|
||||||
|
var position = path.Step + piecePosition;
|
||||||
|
while (position.IsInsideBoardBoundary())
|
||||||
|
{
|
||||||
|
list.Add(position);
|
||||||
|
position += path.Step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
})
|
||||||
|
// Where tile at position is empty, meaning the piece could move there.
|
||||||
|
.Where(newPosition => BoardState[newPosition] == null)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static InCheckResult IsEitherPlayerInCheck(BoardState simState)
|
private static InCheckResult IsEitherPlayerInCheck(BoardState simState)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,33 +21,3 @@ public record Path
|
|||||||
this.Distance = distance;
|
this.Distance = distance;
|
||||||
}
|
}
|
||||||
public Path Invert() => new(Vector2.Negate(Step), Distance);
|
public Path Invert() => new(Vector2.Negate(Step), Distance);
|
||||||
|
|
||||||
//public enum PathingResult
|
|
||||||
//{
|
|
||||||
// Obstructed,
|
|
||||||
// CompletedWithoutObstruction
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PathExtensions
|
|
||||||
{
|
|
||||||
public static Path GetNearestPath(this IEnumerable<Path> paths, Vector2 start, Vector2 end)
|
|
||||||
{
|
|
||||||
if (!paths.DefaultIfEmpty().Any())
|
|
||||||
{
|
|
||||||
throw new ArgumentException("No paths to get nearest path from.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var shortestPath = paths.First();
|
|
||||||
foreach (var path in paths.Skip(1))
|
|
||||||
{
|
|
||||||
var distance = Vector2.Distance(start + path.Step, end);
|
|
||||||
var shortestDistance = Vector2.Distance(start + shortestPath.Step, end);
|
|
||||||
if (distance < shortestDistance)
|
|
||||||
{
|
|
||||||
shortestPath = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shortestPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user