before remove validation board

This commit is contained in:
2021-02-25 19:55:43 -06:00
parent f644795cd3
commit 640db4f4a2
8 changed files with 172 additions and 58 deletions

View File

@@ -7,7 +7,7 @@ namespace Benchmarking
{ {
public class Benchmarks public class Benchmarks
{ {
private Move[] moves; private readonly Move[] moves;
public Benchmarks() public Benchmarks()
{ {
@@ -37,7 +37,7 @@ namespace Benchmarking
} }
[Benchmark] [Benchmark]
public void OnlyValidMoves_NewBoard() public void One()
{ {
var board = new ShogiBoard(); var board = new ShogiBoard();
foreach (var move in moves) foreach (var move in moves)
@@ -45,6 +45,17 @@ namespace Benchmarking
board.TryMove(move); board.TryMove(move);
} }
} }
[Benchmark]
public void Two()
{
var board = new ShogiBoard();
foreach (var move in moves)
{
//board.TryMove2(move);
}
}
} }
public class Program public class Program

View File

@@ -1,20 +1,21 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Gameboard.ShogiUI.BoardState namespace Gameboard.ShogiUI.BoardState
{ {
public class Array2D<T> : IEnumerable public class Array2D<T> : IEnumerable<T>
{ {
/// <returns>False to stop iterating.</returns>
public delegate void ForEachDelegate(T element, int x, int y);
private readonly T[] array; private readonly T[] array;
private readonly int width; private readonly int width;
private readonly int height;
public Array2D(int width, int height) public Array2D(int width, int height)
{ {
this.width = width; this.width = width;
this.height = height;
array = new T[width * height]; array = new T[width * height];
} }
@@ -24,6 +25,48 @@ namespace Gameboard.ShogiUI.BoardState
set => array[y * width + x] = value; set => array[y * width + x] = value;
} }
public void ForEach(ForEachDelegate callback)
{
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
callback(this[x, y], x, y);
}
}
}
public void ForEachNotNull(ForEachDelegate callback)
{
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
if (this[x, y] != null)
callback(this[x, y], x, y);
}
}
}
// TODO: Figure out a better return type, or make this class specific to ShogiBoard.
public BoardVector IndexOf(Predicate<T> predicate)
{
for (var x = 0; x < width; x++)
for (var y = 0; y < height; y++)
{
if (this[x, y] != null && predicate(this[x, y]))
{
return new BoardVector(x, y);
}
}
return null;
}
public IEnumerator<T> GetEnumerator()
{
foreach (var item in array) yield return item;
}
IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator();
} }
} }

View File

@@ -33,6 +33,14 @@ namespace Gameboard.ShogiUI.BoardState
_ => " ? ", _ => " ? ",
}; };
public bool IsRanged => WhichPiece switch
{
WhichPiece.Bishop => true,
WhichPiece.Rook => true,
WhichPiece.Lance => !IsPromoted,
_ => false,
};
public void ToggleOwnership() public void ToggleOwnership()
{ {
Owner = Owner == WhichPlayer.Player1 Owner = Owner == WhichPlayer.Player1

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using System.Text; using System.Text;
namespace Gameboard.ShogiUI.BoardState namespace Gameboard.ShogiUI.BoardState
@@ -13,9 +14,10 @@ namespace Gameboard.ShogiUI.BoardState
{ {
private delegate void MoveSetCallback(Piece piece, BoardVector position); private delegate void MoveSetCallback(Piece piece, BoardVector position);
private ShogiBoard validationBoard; private ShogiBoard validationBoard;
private Vector2 player1King;
private Vector2 player2King;
public IReadOnlyDictionary<WhichPlayer, List<Piece>> Hands { get; } public IReadOnlyDictionary<WhichPlayer, List<Piece>> Hands { get; }
public Piece[,] Board { get; } public Array2D<Piece> Board { get; }
public List<Move> MoveHistory { get; } public List<Move> MoveHistory { get; }
public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2; public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2;
public WhichPlayer? InCheck { get; private set; } public WhichPlayer? InCheck { get; private set; }
@@ -23,13 +25,15 @@ namespace Gameboard.ShogiUI.BoardState
public ShogiBoard() public ShogiBoard()
{ {
Board = new Piece[9, 9]; Board = new Array2D<Piece>(9, 9);
MoveHistory = new List<Move>(20); MoveHistory = new List<Move>(20);
Hands = new Dictionary<WhichPlayer, List<Piece>> { Hands = new Dictionary<WhichPlayer, List<Piece>> {
{ WhichPlayer.Player1, new List<Piece>()}, { WhichPlayer.Player1, new List<Piece>()},
{ WhichPlayer.Player2, new List<Piece>()}, { WhichPlayer.Player2, new List<Piece>()},
}; };
InitializeBoardState(); InitializeBoardState();
player1King = new Vector2(4, 0);
player2King = new Vector2(4, 8);
} }
public ShogiBoard(IList<Move> moves) : this() public ShogiBoard(IList<Move> moves) : this()
{ {
@@ -45,7 +49,40 @@ namespace Gameboard.ShogiUI.BoardState
/// <summary> /// <summary>
/// Attempts a given move. Returns false if the move is illegal. /// Attempts a given move. Returns false if the move is illegal.
/// </summary> /// </summary>
public bool TryMove(Move move) //public bool TryMove2(Move move)
//{
// // Try making the move in a "throw away" board.
// if (validationBoard == null)
// {
// validationBoard = new ShogiBoard(MoveHistory);
// }
// var isValid = move.PieceFromCaptured.HasValue
// ? validationBoard.PlaceFromHand(move)
// : validationBoard.PlaceFromBoard(move);
// if (!isValid)
// {
// // Invalidate the "throw away" board.
// validationBoard = null;
// return false;
// }
// // Assert that this move does not put the moving player in check.
// if (validationBoard.EvaluateCheck(WhoseTurn)) return false;
// var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
// // The move is valid and legal; update board state.
// if (move.PieceFromCaptured.HasValue) PlaceFromHand(move);
// else PlaceFromBoard(move);
// // Evaluate check
// InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null;
// if (InCheck.HasValue)
// {
// //IsCheckmate = EvaluateCheckmate();
// }
// return true;
//}
public bool TryMove(Move move, bool shouldEvaluateCheck = true)
{ {
// Try making the move in a "throw away" board. // Try making the move in a "throw away" board.
if (validationBoard == null) if (validationBoard == null)
@@ -70,14 +107,18 @@ namespace Gameboard.ShogiUI.BoardState
else PlaceFromBoard(move); else PlaceFromBoard(move);
// Evaluate check // Evaluate check
if (shouldEvaluateCheck)
{
InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null; InCheck = EvaluateCheck(otherPlayer) ? otherPlayer : null;
if (InCheck.HasValue) if (InCheck.HasValue)
{ {
//IsCheckmate = EvaluateCheckmate(); IsCheckmate = EvaluateCheckmate();
}
} }
return true; return true;
} }
private bool EvaluateCheckmate() private bool EvaluateCheckmate()
{ {
if (!InCheck.HasValue) return false; if (!InCheck.HasValue) return false;
@@ -100,10 +141,9 @@ namespace Gameboard.ShogiUI.BoardState
// And evaluate if any move gets the player out of check. // And evaluate if any move gets the player out of check.
foreach (var position in positionsToCheck) foreach (var position in positionsToCheck)
{ {
var moveSuccess = validationBoard.TryMove(new Move { From = from, To = position }); var moveSuccess = validationBoard.TryMove(new Move { From = from, To = position }, false);
if (moveSuccess) if (moveSuccess)
{ {
Console.WriteLine($"Not check mate");
isCheckmate &= validationBoard.EvaluateCheck(InCheck.Value); isCheckmate &= validationBoard.EvaluateCheck(InCheck.Value);
validationBoard = null; validationBoard = null;
} }
@@ -148,7 +188,7 @@ namespace Gameboard.ShogiUI.BoardState
var fromPiece = Board[move.From.X, move.From.Y]; var fromPiece = Board[move.From.X, move.From.Y];
if (fromPiece == null) return false; // Invalid move if (fromPiece == null) return false; // Invalid move
if (fromPiece.Owner != WhoseTurn) return false; // Invalid move; cannot move other players pieces. if (fromPiece.Owner != WhoseTurn) return false; // Invalid move; cannot move other players pieces.
if (ValidateMoveAgainstMoveSet(move) == false) return false; // Invalid move; move not part of move-set. if (ValidateMoveAgainstMoveSet(move.From, move.To) == false) return false; // Invalid move; move not part of move-set.
var captured = Board[move.To.X, move.To.Y]; var captured = Board[move.To.X, move.To.Y];
if (captured != null) if (captured != null)
@@ -172,6 +212,19 @@ namespace Gameboard.ShogiUI.BoardState
} }
Board[move.To.X, move.To.Y] = fromPiece; Board[move.To.X, move.To.Y] = fromPiece;
Board[move.From.X, move.From.Y] = null; Board[move.From.X, move.From.Y] = null;
if (fromPiece.WhichPiece == WhichPiece.King)
{
if (fromPiece.Owner == WhichPlayer.Player1)
{
player1King.X = move.To.X;
player1King.Y = move.To.Y;
}
else if (fromPiece.Owner == WhichPlayer.Player2)
{
player2King.X = move.To.X;
player2King.Y = move.To.Y;
}
}
MoveHistory.Add(move); MoveHistory.Add(move);
return true; return true;
} }
@@ -215,13 +268,16 @@ namespace Gameboard.ShogiUI.BoardState
/// </summary> /// </summary>
private bool EvaluateCheck(WhichPlayer whichPlayer) private bool EvaluateCheck(WhichPlayer whichPlayer)
{ {
var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King;
var inCheck = false; var inCheck = false;
// Iterate every board piece... // Iterate every board piece...
Board.ForEachNotNull((piece, x, y) => Board.ForEachNotNull((piece, x, y) =>
{ {
// ...that belongs to the opponent... var v = new Vector2(x, y);
if (piece.Owner != whichPlayer) // ...that belongs to the opponent within range...
if (piece.Owner != whichPlayer && (piece.IsRanged || Vector2.Distance(kingPosition, v) < 3))
{ {
Console.WriteLine($"Evaluating {piece.WhichPiece}");
IterateMoveSet(new BoardVector(x, y), (threatenedPiece, position) => IterateMoveSet(new BoardVector(x, y), (threatenedPiece, position) =>
{ {
// ...and threatens the player's king. // ...and threatens the player's king.
@@ -233,47 +289,13 @@ namespace Gameboard.ShogiUI.BoardState
}); });
return inCheck; return inCheck;
} }
private bool EvaluateCheck2(WhichPlayer whichPlayer)
{
var inCheck = false;
MoveSetCallback checkKingThreat = (piece, position) =>
{
inCheck |=
piece?.WhichPiece == WhichPiece.King
&& piece?.Owner == whichPlayer;
};
// Find interesting pieces
var longRangePiecePositions = new List<BoardVector>(8);
Board.ForEachNotNull((piece, x, y) =>
{
if (piece.Owner != whichPlayer)
{
switch (piece.WhichPiece)
{
case WhichPiece.Bishop:
case WhichPiece.Rook:
longRangePiecePositions.Add(new BoardVector(x, y));
break;
case WhichPiece.Lance:
if (!piece.IsPromoted) longRangePiecePositions.Add(new BoardVector(x, y));
break;
}
}
});
foreach(var position in longRangePiecePositions) private bool ValidateMoveAgainstMoveSet(BoardVector from, BoardVector to)
{
IterateMoveSet(position, checkKingThreat);
}
return inCheck;
}
private bool ValidateMoveAgainstMoveSet(Move move)
{ {
var isValid = false; var isValid = false;
IterateMoveSet(move.From, (piece, position) => IterateMoveSet(from, (piece, position) =>
{ {
if (piece?.Owner != WhoseTurn && position == move.To) if (piece?.Owner != WhoseTurn && position == to)
{ {
isValid = true; isValid = true;
} }
@@ -289,7 +311,7 @@ namespace Gameboard.ShogiUI.BoardState
// TODO: Make these are of the move To, so only possible moves towards the move To are iterated. // TODO: Make these are of the move To, so only possible moves towards the move To are iterated.
// Maybe separate functions? Sometimes I need to iterate the whole move-set, sometimes I need to iterate only the move-set towards the move To. // Maybe separate functions? Sometimes I need to iterate the whole move-set, sometimes I need to iterate only the move-set towards the move To.
var piece = Board[from.X, from.Y]; var piece = Board[from.X, from.Y];
switch (piece.WhichPiece) switch (piece?.WhichPiece)
{ {
case WhichPiece.King: case WhichPiece.King:
IterateKingMoveSet(from, callback); IterateKingMoveSet(from, callback);

View File

@@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.BoardStat
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests", "Gameboard.ShogiUI.UnitTests\Gameboard.ShogiUI.UnitTests.csproj", "{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests", "Gameboard.ShogiUI.UnitTests\Gameboard.ShogiUI.UnitTests.csproj", "{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -41,6 +43,10 @@ Global
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU {DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -0,0 +1,7 @@
namespace PathFinding
{
interface IPlanarCollection<T>
{
T this[int x, int y] { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace PathFinding
{
public class PathFinder2D
{
public PathFinder2D()
{
}
}
}

View File

@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>