Code smells
This commit is contained in:
@@ -12,7 +12,7 @@ namespace Benchmarking
|
|||||||
{
|
{
|
||||||
private readonly Move[] moves;
|
private readonly Move[] moves;
|
||||||
private readonly Vector2[] directions;
|
private readonly Vector2[] directions;
|
||||||
private readonly Consumer consumer = new Consumer();
|
private readonly Consumer consumer = new();
|
||||||
|
|
||||||
public Benchmarks()
|
public Benchmarks()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState
|
|
||||||
{
|
|
||||||
public static class Direction
|
|
||||||
{
|
|
||||||
public static readonly Vector2 Up = new Vector2(0, 1);
|
|
||||||
public static readonly Vector2 Down = new Vector2(0, -1);
|
|
||||||
public static readonly Vector2 Left = new Vector2(-1, 0);
|
|
||||||
public static readonly Vector2 Right = new Vector2(1, 0);
|
|
||||||
public static readonly Vector2 UpLeft = new Vector2(-1, 1);
|
|
||||||
public static readonly Vector2 UpRight = new Vector2(1, 1);
|
|
||||||
public static readonly Vector2 DownLeft = new Vector2(-1, -1);
|
|
||||||
public static readonly Vector2 DownRight = new Vector2(1, -1);
|
|
||||||
public static readonly Vector2 KnightLeft = new Vector2(-1, 2);
|
|
||||||
public static readonly Vector2 KnightRight = new Vector2(1, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
namespace Gameboard.ShogiUI.BoardState
|
|
||||||
{
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
public static void ForEachNotNull(this Piece[,] array, Action<Piece, int, int> action)
|
|
||||||
{
|
|
||||||
for (var x = 0; x < array.GetLength(0); x++)
|
|
||||||
for (var y = 0; y < array.GetLength(1); y++)
|
|
||||||
{
|
|
||||||
var piece = array[x, y];
|
|
||||||
if (piece != null)
|
|
||||||
{
|
|
||||||
action(piece, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,34 +1,32 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class Bishop : Piece
|
public class Bishop : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(4)
|
private static readonly List<PathFinding.Move> Moves = new(4)
|
||||||
{
|
{
|
||||||
new Path(Direction.UpLeft, Distance.MultiStep),
|
new PathFinding.Move(Direction.UpLeft, Distance.MultiStep),
|
||||||
new Path(Direction.UpRight, Distance.MultiStep),
|
new PathFinding.Move(Direction.UpRight, Distance.MultiStep),
|
||||||
new Path(Direction.DownLeft, Distance.MultiStep),
|
new PathFinding.Move(Direction.DownLeft, Distance.MultiStep),
|
||||||
new Path(Direction.DownRight, Distance.MultiStep)
|
new PathFinding.Move(Direction.DownRight, Distance.MultiStep)
|
||||||
};
|
};
|
||||||
private static readonly List<Path> PromotedMoveSet = new List<Path>(8)
|
private static readonly List<PathFinding.Move> PromotedMoves = new(8)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up),
|
new PathFinding.Move(Direction.Up),
|
||||||
new Path(Direction.Left),
|
new PathFinding.Move(Direction.Left),
|
||||||
new Path(Direction.Right),
|
new PathFinding.Move(Direction.Right),
|
||||||
new Path(Direction.Down),
|
new PathFinding.Move(Direction.Down),
|
||||||
new Path(Direction.UpLeft, Distance.MultiStep),
|
new PathFinding.Move(Direction.UpLeft, Distance.MultiStep),
|
||||||
new Path(Direction.UpRight, Distance.MultiStep),
|
new PathFinding.Move(Direction.UpRight, Distance.MultiStep),
|
||||||
new Path(Direction.DownLeft, Distance.MultiStep),
|
new PathFinding.Move(Direction.DownLeft, Distance.MultiStep),
|
||||||
new Path(Direction.DownRight, Distance.MultiStep)
|
new PathFinding.Move(Direction.DownRight, Distance.MultiStep)
|
||||||
};
|
};
|
||||||
public Bishop(WhichPlayer owner) : base(WhichPiece.Bishop, owner)
|
public Bishop(WhichPlayer owner) : base(WhichPiece.Bishop, owner)
|
||||||
{
|
{
|
||||||
// TODO: If this strat works out, we can do away with the Direction class entirely.
|
moveSet = new MoveSet(this, Moves);
|
||||||
PromotedMoveSet.AddRange(MoveSet);
|
promotedMoveSet = new MoveSet(this, PromotedMoves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -37,11 +35,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? PromotedMoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class GoldenGeneral : Piece
|
public class GoldenGeneral : Piece
|
||||||
{
|
{
|
||||||
public static readonly List<Path> MoveSet = new List<Path>(6)
|
public static readonly List<PathFinding.Move> Moves = new(6)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up),
|
new PathFinding.Move(Direction.Up),
|
||||||
new Path(Direction.UpLeft),
|
new PathFinding.Move(Direction.UpLeft),
|
||||||
new Path(Direction.UpRight),
|
new PathFinding.Move(Direction.UpRight),
|
||||||
new Path(Direction.Left),
|
new PathFinding.Move(Direction.Left),
|
||||||
new Path(Direction.Right),
|
new PathFinding.Move(Direction.Right),
|
||||||
new Path(Direction.Down)
|
new PathFinding.Move(Direction.Down)
|
||||||
};
|
};
|
||||||
public GoldenGeneral(WhichPlayer owner) : base(WhichPiece.GoldenGeneral, owner)
|
public GoldenGeneral(WhichPlayer owner) : base(WhichPiece.GoldenGeneral, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -26,9 +26,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths() => Owner == WhichPlayer.Player1
|
|
||||||
? MoveSet
|
|
||||||
: MoveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class King : Piece
|
public class King : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(8)
|
private static readonly List<PathFinding.Move> Moves = new(8)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up),
|
new PathFinding.Move(Direction.Up),
|
||||||
new Path(Direction.Left),
|
new PathFinding.Move(Direction.Left),
|
||||||
new Path(Direction.Right),
|
new PathFinding.Move(Direction.Right),
|
||||||
new Path(Direction.Down),
|
new PathFinding.Move(Direction.Down),
|
||||||
new Path(Direction.UpLeft),
|
new PathFinding.Move(Direction.UpLeft),
|
||||||
new Path(Direction.UpRight),
|
new PathFinding.Move(Direction.UpRight),
|
||||||
new Path(Direction.DownLeft),
|
new PathFinding.Move(Direction.DownLeft),
|
||||||
new Path(Direction.DownRight)
|
new PathFinding.Move(Direction.DownRight)
|
||||||
};
|
};
|
||||||
public King(WhichPlayer owner) : base(WhichPiece.King, owner)
|
public King(WhichPlayer owner) : base(WhichPiece.King, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -28,7 +28,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
// The move set for a King is the same regardless of orientation.
|
|
||||||
public override ICollection<Path> GetPaths() => MoveSet;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
{
|
{
|
||||||
public class Knight : Piece
|
public class Knight : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(2)
|
private static readonly List<PathFinding.Move> Moves = new(2)
|
||||||
{
|
{
|
||||||
new Path(Direction.KnightLeft),
|
new PathFinding.Move(Direction.KnightLeft),
|
||||||
new Path(Direction.KnightRight)
|
new PathFinding.Move(Direction.KnightRight)
|
||||||
};
|
};
|
||||||
|
|
||||||
public Knight(WhichPlayer owner) : base(WhichPiece.Knight, owner)
|
public Knight(WhichPlayer owner) : base(WhichPiece.Knight, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -23,11 +25,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class Lance : Piece
|
public class Lance : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(1)
|
private static readonly List<PathFinding.Move> Moves = new(1)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up, Distance.MultiStep),
|
new PathFinding.Move(Direction.Up, Distance.MultiStep),
|
||||||
};
|
};
|
||||||
|
|
||||||
public Lance(WhichPlayer owner) : base(WhichPiece.Lance, owner)
|
public Lance(WhichPlayer owner) : base(WhichPiece.Lance, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -22,11 +22,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class Pawn : Piece
|
public class Pawn : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(1)
|
private static readonly List<PathFinding.Move> Moves = new(1)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up)
|
new PathFinding.Move(Direction.Up)
|
||||||
};
|
};
|
||||||
|
|
||||||
public Pawn(WhichPlayer owner) : base(WhichPiece.Pawn, owner)
|
public Pawn(WhichPlayer owner) : base(WhichPiece.Pawn, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -22,11 +22,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
[DebuggerDisplay("{WhichPiece} {Owner}")]
|
[DebuggerDisplay("{WhichPiece} {Owner}")]
|
||||||
public abstract class Piece : IPlanarElement
|
public abstract class Piece : IPlanarElement
|
||||||
{
|
{
|
||||||
|
protected MoveSet promotedMoveSet;
|
||||||
|
protected MoveSet moveSet;
|
||||||
|
|
||||||
|
public MoveSet MoveSet => IsPromoted ? promotedMoveSet : moveSet;
|
||||||
|
public abstract Piece DeepClone();
|
||||||
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;
|
||||||
|
|
||||||
public Piece(WhichPiece piece, WhichPlayer owner)
|
public Piece(WhichPiece piece, WhichPlayer owner)
|
||||||
{
|
{
|
||||||
@@ -22,27 +27,6 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
&& WhichPiece != WhichPiece.King
|
&& WhichPiece != WhichPiece.King
|
||||||
&& WhichPiece != WhichPiece.GoldenGeneral;
|
&& WhichPiece != WhichPiece.GoldenGeneral;
|
||||||
|
|
||||||
public string ShortName => WhichPiece switch
|
|
||||||
{
|
|
||||||
WhichPiece.King => " K ",
|
|
||||||
WhichPiece.GoldenGeneral => " G ",
|
|
||||||
WhichPiece.SilverGeneral => IsPromoted ? "^S^" : " S ",
|
|
||||||
WhichPiece.Bishop => IsPromoted ? "^B^" : " B ",
|
|
||||||
WhichPiece.Rook => IsPromoted ? "^R^" : " R ",
|
|
||||||
WhichPiece.Knight => IsPromoted ? "^k^" : " k ",
|
|
||||||
WhichPiece.Lance => IsPromoted ? "^L^" : " L ",
|
|
||||||
WhichPiece.Pawn => IsPromoted ? "^P^" : " P ",
|
|
||||||
_ => " ? ",
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
||||||
@@ -59,11 +43,5 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
ToggleOwnership();
|
ToggleOwnership();
|
||||||
Demote();
|
Demote();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ICollection<Path> GetPaths();
|
|
||||||
|
|
||||||
public abstract Piece DeepClone();
|
|
||||||
|
|
||||||
public bool IsUpsideDown => Owner == WhichPlayer.Player2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class Rook : Piece
|
public class Rook : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(4)
|
private static readonly List<PathFinding.Move> Moves = new(4)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up, Distance.MultiStep),
|
new PathFinding.Move(Direction.Up, Distance.MultiStep),
|
||||||
new Path(Direction.Left, Distance.MultiStep),
|
new PathFinding.Move(Direction.Left, Distance.MultiStep),
|
||||||
new Path(Direction.Right, Distance.MultiStep),
|
new PathFinding.Move(Direction.Right, Distance.MultiStep),
|
||||||
new Path(Direction.Down, Distance.MultiStep)
|
new PathFinding.Move(Direction.Down, Distance.MultiStep)
|
||||||
};
|
};
|
||||||
private static readonly List<Path> PromotedMoveSet = new List<Path>(8)
|
private static readonly List<PathFinding.Move> PromotedMoves = new(8)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up, Distance.MultiStep),
|
new PathFinding.Move(Direction.Up, Distance.MultiStep),
|
||||||
new Path(Direction.Left, Distance.MultiStep),
|
new PathFinding.Move(Direction.Left, Distance.MultiStep),
|
||||||
new Path(Direction.Right, Distance.MultiStep),
|
new PathFinding.Move(Direction.Right, Distance.MultiStep),
|
||||||
new Path(Direction.Down, Distance.MultiStep),
|
new PathFinding.Move(Direction.Down, Distance.MultiStep),
|
||||||
new Path(Direction.UpLeft),
|
new PathFinding.Move(Direction.UpLeft),
|
||||||
new Path(Direction.UpRight),
|
new PathFinding.Move(Direction.UpRight),
|
||||||
new Path(Direction.DownLeft),
|
new PathFinding.Move(Direction.DownLeft),
|
||||||
new Path(Direction.DownRight)
|
new PathFinding.Move(Direction.DownRight)
|
||||||
};
|
};
|
||||||
public Rook(WhichPlayer owner) : base(WhichPiece.Rook, owner)
|
public Rook(WhichPlayer owner) : base(WhichPiece.Rook, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, PromotedMoves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -35,10 +35,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? PromotedMoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState.Pieces
|
namespace Gameboard.ShogiUI.BoardState.Pieces
|
||||||
{
|
{
|
||||||
public class SilverGeneral : Piece
|
public class SilverGeneral : Piece
|
||||||
{
|
{
|
||||||
private static readonly List<Path> MoveSet = new List<Path>(4)
|
private static readonly List<PathFinding.Move> Moves = new(4)
|
||||||
{
|
{
|
||||||
new Path(Direction.Up),
|
new PathFinding.Move(Direction.Up),
|
||||||
new Path(Direction.UpLeft),
|
new PathFinding.Move(Direction.UpLeft),
|
||||||
new Path(Direction.UpRight),
|
new PathFinding.Move(Direction.UpRight),
|
||||||
new Path(Direction.DownLeft),
|
new PathFinding.Move(Direction.DownLeft),
|
||||||
new Path(Direction.DownRight)
|
new PathFinding.Move(Direction.DownRight)
|
||||||
};
|
};
|
||||||
public SilverGeneral(WhichPlayer owner) : base(WhichPiece.SilverGeneral, owner)
|
public SilverGeneral(WhichPlayer owner) : base(WhichPiece.SilverGeneral, owner)
|
||||||
{
|
{
|
||||||
|
moveSet = new MoveSet(this, Moves);
|
||||||
|
promotedMoveSet = new MoveSet(this, GoldenGeneral.Moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Piece DeepClone()
|
public override Piece DeepClone()
|
||||||
@@ -25,11 +25,5 @@ namespace Gameboard.ShogiUI.BoardState.Pieces
|
|||||||
if (IsPromoted) clone.Promote();
|
if (IsPromoted) clone.Promote();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICollection<Path> GetPaths()
|
|
||||||
{
|
|
||||||
var moveSet = IsPromoted ? GoldenGeneral.MoveSet : MoveSet;
|
|
||||||
return Owner == WhichPlayer.Player1 ? moveSet : moveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState
|
namespace Gameboard.ShogiUI.BoardState
|
||||||
{
|
{
|
||||||
public class Array2D<T> : IPlanarCollection<T>, IEnumerable<T>
|
public class PlanarCollection<T> : IPlanarCollection<T>, IEnumerable<T> where T : IPlanarElement
|
||||||
{
|
{
|
||||||
/// <returns>False to stop iterating.</returns>
|
|
||||||
public delegate void ForEachDelegate(T element, int x, int y);
|
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;
|
private readonly int height;
|
||||||
|
|
||||||
public Array2D(int width, int height)
|
public PlanarCollection(int width, int height)
|
||||||
{
|
{
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
@@ -39,17 +37,6 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
_ => throw new IndexOutOfRangeException()
|
_ => throw new IndexOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
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)
|
public void ForEachNotNull(ForEachDelegate callback)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < width; x++)
|
for (var x = 0; x < width; x++)
|
||||||
@@ -62,19 +49,6 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2? 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 Vector2(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
{
|
{
|
||||||
foreach (var item in array) yield return item;
|
foreach (var item in array) yield return item;
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.BoardState
|
|
||||||
{
|
|
||||||
public class Position
|
|
||||||
{
|
|
||||||
private int x;
|
|
||||||
private int y;
|
|
||||||
|
|
||||||
public int X
|
|
||||||
{
|
|
||||||
get => x;
|
|
||||||
set {
|
|
||||||
if (value > 8 || value < 0) throw new ArgumentOutOfRangeException();
|
|
||||||
x = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Y
|
|
||||||
{
|
|
||||||
get => y;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value > 8 || value < 0) throw new ArgumentOutOfRangeException();
|
|
||||||
y = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Position(int x, int y)
|
|
||||||
{
|
|
||||||
X = x;
|
|
||||||
Y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
private Vector2 player1King;
|
private Vector2 player1King;
|
||||||
private Vector2 player2King;
|
private Vector2 player2King;
|
||||||
public IReadOnlyDictionary<WhichPlayer, List<Piece>> Hands { get; }
|
public IReadOnlyDictionary<WhichPlayer, List<Piece>> Hands { get; }
|
||||||
public Array2D<Piece> Board { get; }
|
public PlanarCollection<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; }
|
||||||
@@ -28,7 +28,7 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
|
|
||||||
public ShogiBoard()
|
public ShogiBoard()
|
||||||
{
|
{
|
||||||
Board = new Array2D<Piece>(9, 9);
|
Board = new PlanarCollection<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>()},
|
||||||
@@ -54,7 +54,7 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
|
|
||||||
private ShogiBoard(ShogiBoard toCopy)
|
private ShogiBoard(ShogiBoard toCopy)
|
||||||
{
|
{
|
||||||
Board = new Array2D<Piece>(9, 9);
|
Board = new PlanarCollection<Piece>(9, 9);
|
||||||
for (var x = 0; x < 9; x++)
|
for (var x = 0; x < 9; x++)
|
||||||
for (var y = 0; y < 9; y++)
|
for (var y = 0; y < 9; y++)
|
||||||
Board[x, y] = toCopy.Board[x, y]?.DeepClone();
|
Board[x, y] = toCopy.Board[x, y]?.DeepClone();
|
||||||
@@ -213,40 +213,6 @@ namespace Gameboard.ShogiUI.BoardState
|
|||||||
return !isObstructed && isPathable;
|
return !isObstructed && isPathable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintStateAsAscii()
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.Append(" Player 2");
|
|
||||||
builder.AppendLine();
|
|
||||||
for (var y = 8; y > -1; y--)
|
|
||||||
{
|
|
||||||
builder.Append("- ");
|
|
||||||
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
|
||||||
builder.Append("- -");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.Append('|');
|
|
||||||
for (var x = 0; x < 9; x++)
|
|
||||||
{
|
|
||||||
var piece = Board[x, y];
|
|
||||||
if (piece == null)
|
|
||||||
{
|
|
||||||
builder.Append(" ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.AppendFormat("{0}", piece.ShortName);
|
|
||||||
}
|
|
||||||
builder.Append('|');
|
|
||||||
}
|
|
||||||
builder.AppendLine();
|
|
||||||
}
|
|
||||||
builder.Append("- ");
|
|
||||||
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
|
||||||
builder.Append("- -");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.Append(" Player 1");
|
|
||||||
Console.WriteLine(builder.ToString());
|
|
||||||
}
|
|
||||||
#region Rules Validation
|
#region Rules Validation
|
||||||
private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer)
|
private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
IsPromotion = move.IsPromotion;
|
IsPromotion = move.IsPromotion;
|
||||||
PieceFromCaptured = pieceFromCaptured;
|
PieceFromCaptured = pieceFromCaptured;
|
||||||
}
|
}
|
||||||
public ServiceModels.Socket.Types.Move ToServiceModel() => new ServiceModels.Socket.Types.Move
|
public ServiceModels.Socket.Types.Move ToServiceModel() => new()
|
||||||
{
|
{
|
||||||
From = From.ToBoardNotation(),
|
From = From.ToBoardNotation(),
|
||||||
IsPromotion = IsPromotion,
|
IsPromotion = IsPromotion,
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Gameboard.ShogiUI.BoardState;
|
||||||
|
using Gameboard.ShogiUI.BoardState.Pieces;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.UnitTests.BoardState
|
||||||
|
{
|
||||||
|
public static class BoardStateExtensions
|
||||||
|
{
|
||||||
|
public static string GetShortName(this Piece self)
|
||||||
|
{
|
||||||
|
var name = self.WhichPiece switch
|
||||||
|
{
|
||||||
|
WhichPiece.King => " K ",
|
||||||
|
WhichPiece.GoldenGeneral => " G ",
|
||||||
|
WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ",
|
||||||
|
WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ",
|
||||||
|
WhichPiece.Rook => self.IsPromoted ? "^R " : " R ",
|
||||||
|
WhichPiece.Knight => self.IsPromoted ? "^k " : " k ",
|
||||||
|
WhichPiece.Lance => self.IsPromoted ? "^L " : " L ",
|
||||||
|
WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ",
|
||||||
|
_ => " ? ",
|
||||||
|
};
|
||||||
|
if (self.Owner == WhichPlayer.Player2)
|
||||||
|
name = Regex.Replace(name, @"([^\s]+)\s", "$1.");
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PrintStateAsAscii(this ShogiBoard self)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(" Player 2(.)");
|
||||||
|
builder.AppendLine();
|
||||||
|
for (var y = 8; y > -1; y--)
|
||||||
|
{
|
||||||
|
builder.Append("- ");
|
||||||
|
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
||||||
|
builder.Append("- -");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.Append('|');
|
||||||
|
for (var x = 0; x < 9; x++)
|
||||||
|
{
|
||||||
|
var piece = self.Board[x, y];
|
||||||
|
if (piece == null)
|
||||||
|
{
|
||||||
|
builder.Append(" ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendFormat("{0}", piece.GetShortName());
|
||||||
|
}
|
||||||
|
builder.Append('|');
|
||||||
|
}
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
builder.Append("- ");
|
||||||
|
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
||||||
|
builder.Append("- -");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.Append(" Player 1");
|
||||||
|
Console.WriteLine(builder.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Gameboard.ShogiUI.BoardState;
|
using Gameboard.ShogiUI.BoardState;
|
||||||
|
using Gameboard.ShogiUI.BoardState.Pieces;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -81,7 +82,6 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState
|
|||||||
shogi.Board[0, 3].WhichPiece.Should().Be(WhichPiece.Pawn);
|
shogi.Board[0, 3].WhichPiece.Should().Be(WhichPiece.Pawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void PreventInvalidMoves_MoveFromEmptyPosition()
|
public void PreventInvalidMoves_MoveFromEmptyPosition()
|
||||||
{
|
{
|
||||||
@@ -245,30 +245,110 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState
|
|||||||
var shogi = new ShogiBoard(moves);
|
var shogi = new ShogiBoard(moves);
|
||||||
shogi.PrintStateAsAscii();
|
shogi.PrintStateAsAscii();
|
||||||
|
|
||||||
// Prerequisite
|
// Prerequisites
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight);
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
|
||||||
|
|
||||||
// Act | Assert - It is P1 turn
|
// Act | Assert - It is P1 turn
|
||||||
// try illegally placing Knight from the hand.
|
/// try illegally placing Knight from the hand.
|
||||||
shogi.Board[7, 8].Should().BeNull();
|
shogi.Board[7, 8].Should().BeNull();
|
||||||
var moveSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 8) });
|
var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 8) });
|
||||||
shogi.PrintStateAsAscii();
|
dropSuccess.Should().BeFalse();
|
||||||
moveSuccess.Should().BeFalse();
|
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
shogi.Board[7, 8].Should().BeNull();
|
shogi.Board[7, 8].Should().BeNull();
|
||||||
|
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 7) });
|
||||||
|
dropSuccess.Should().BeFalse();
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
|
shogi.Board[7, 7].Should().BeNull();
|
||||||
|
|
||||||
// Assert
|
/// try illegally placing Pawn from the hand
|
||||||
//var pawnDropSuccess = shogi.Move(new Move)
|
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Pawn, To = new Vector2(7, 8) });
|
||||||
|
dropSuccess.Should().BeFalse();
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
|
||||||
|
shogi.Board[7, 8].Should().BeNull();
|
||||||
|
|
||||||
// Assert
|
/// try illegally placing Lance from the hand
|
||||||
|
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(7, 8) });
|
||||||
|
dropSuccess.Should().BeFalse();
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
|
shogi.Board[7, 8].Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void PreventInvalidDrop_Check()
|
public void PreventInvalidDrop_Check()
|
||||||
{
|
{
|
||||||
|
// Arrange
|
||||||
|
var moves = new[]
|
||||||
|
{
|
||||||
|
// P1 Pawn
|
||||||
|
new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) },
|
||||||
|
// P2 Pawn
|
||||||
|
new Move { From = new Vector2(8, 6), To = new Vector2(8, 5) },
|
||||||
|
// P1 Bishop, check
|
||||||
|
new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) },
|
||||||
|
// P2 Gold, block check
|
||||||
|
new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) },
|
||||||
|
// P1 arbitrary move
|
||||||
|
new Move { From = new Vector2(0, 2), To = new Vector2(0, 3) },
|
||||||
|
// P2 Bishop
|
||||||
|
new Move { From = new Vector2(7, 7), To = new Vector2(8, 6) },
|
||||||
|
// P1 Bishop takes P2 Lance
|
||||||
|
new Move { From = new Vector2(6, 6), To = new Vector2(8, 8) },
|
||||||
|
// P2 Bishop
|
||||||
|
new Move { From = new Vector2(8, 6), To = new Vector2(7, 7) },
|
||||||
|
// P1 arbitrary move
|
||||||
|
new Move { From = new Vector2(0, 3), To = new Vector2(0, 4) },
|
||||||
|
// P2 Bishop, check
|
||||||
|
new Move { From = new Vector2(7, 7), To = new Vector2(2, 2) },
|
||||||
|
};
|
||||||
|
var shogi = new ShogiBoard(moves);
|
||||||
|
|
||||||
|
// Prerequisites
|
||||||
|
shogi.InCheck.Should().Be(WhichPlayer.Player1);
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
|
|
||||||
|
// Act - P1 tries to place a Lance while in check.
|
||||||
|
var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(4, 4) });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dropSuccess.Should().BeFalse();
|
||||||
|
shogi.Board[4, 4].Should().BeNull();
|
||||||
|
shogi.InCheck.Should().Be(WhichPlayer.Player1);
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void PreventInvalidDrop_Capture()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var moves = new[]
|
||||||
|
{
|
||||||
|
// P1 Pawn
|
||||||
|
new Move { From = new Vector2(2, 2), To = new Vector2(2, 3) },
|
||||||
|
// P2 Pawn
|
||||||
|
new Move { From = new Vector2(6, 6), To = new Vector2(6, 5) },
|
||||||
|
// P1 Bishop, capture P2 Pawn, check
|
||||||
|
new Move { From = new Vector2(1, 1), To = new Vector2(6, 6) },
|
||||||
|
// P2 Gold, block check
|
||||||
|
new Move { From = new Vector2(5, 8), To = new Vector2(5, 7) },
|
||||||
|
// P1 Bishop capture P2 Bishop
|
||||||
|
new Move { From = new Vector2(6, 6), To = new Vector2(7, 7) },
|
||||||
|
// P2 arbitrary move
|
||||||
|
new Move { From = new Vector2(0, 8), To = new Vector2(0, 7) },
|
||||||
|
};
|
||||||
|
var shogi = new ShogiBoard(moves);
|
||||||
|
|
||||||
|
// Prerequisites
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
|
||||||
|
|
||||||
|
// Act - P1 tries to place Bishop from hand to an already-occupied position
|
||||||
|
var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Bishop, To = new Vector2(4, 0) });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dropSuccess.Should().BeFalse();
|
||||||
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
|
||||||
|
shogi.Board[4, 0].WhichPiece.Should().Be(WhichPiece.King);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Gameboard.ShogiUI.BoardState;
|
using Gameboard.ShogiUI.BoardState.Pieces;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using PathFinding;
|
using PathFinding;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
||||||
@@ -10,32 +9,25 @@ namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class PathFinder2DShould
|
public class PathFinder2DShould
|
||||||
{
|
{
|
||||||
class TestElement : IPlanarElement
|
|
||||||
{
|
|
||||||
public ICollection<Path> GetPaths() => throw new System.NotImplementedException();
|
|
||||||
public bool IsUpsideDown => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Maths()
|
public void Maths()
|
||||||
{
|
{
|
||||||
var finder = new PathFinder2D<TestElement>(new Array2D<TestElement>(5, 5));
|
|
||||||
|
|
||||||
var result = finder.IsPathable(
|
var result = PathFinder2D<Piece>.IsPathable(
|
||||||
new Vector2(2, 2),
|
new Vector2(2, 2),
|
||||||
new Vector2(7, 7),
|
new Vector2(7, 7),
|
||||||
new Vector2(1, 1)
|
new Vector2(1, 1)
|
||||||
);
|
);
|
||||||
result.Should().BeTrue();
|
result.Should().BeTrue();
|
||||||
|
|
||||||
result = finder.IsPathable(
|
result = PathFinder2D<Piece>.IsPathable(
|
||||||
new Vector2(2, 2),
|
new Vector2(2, 2),
|
||||||
new Vector2(7, 7),
|
new Vector2(7, 7),
|
||||||
new Vector2(0, 0)
|
new Vector2(0, 0)
|
||||||
);
|
);
|
||||||
result.Should().BeFalse();
|
result.Should().BeFalse();
|
||||||
|
|
||||||
result = finder.IsPathable(
|
result = PathFinder2D<Piece>.IsPathable(
|
||||||
new Vector2(2, 2),
|
new Vector2(2, 2),
|
||||||
new Vector2(7, 7),
|
new Vector2(7, 7),
|
||||||
new Vector2(-1, 1)
|
new Vector2(-1, 1)
|
||||||
|
|||||||
18
PathFinding/Direction.cs
Normal file
18
PathFinding/Direction.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace PathFinding
|
||||||
|
{
|
||||||
|
public static class Direction
|
||||||
|
{
|
||||||
|
public static readonly Vector2 Up = new(0, 1);
|
||||||
|
public static readonly Vector2 Down = new(0, -1);
|
||||||
|
public static readonly Vector2 Left = new(-1, 0);
|
||||||
|
public static readonly Vector2 Right = new(1, 0);
|
||||||
|
public static readonly Vector2 UpLeft = new(-1, 1);
|
||||||
|
public static readonly Vector2 UpRight = new(1, 1);
|
||||||
|
public static readonly Vector2 DownLeft = new(-1, -1);
|
||||||
|
public static readonly Vector2 DownRight = new(1, -1);
|
||||||
|
public static readonly Vector2 KnightLeft = new(-1, 2);
|
||||||
|
public static readonly Vector2 KnightRight = new(1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
PathFinding/Distance.cs
Normal file
8
PathFinding/Distance.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PathFinding
|
||||||
|
{
|
||||||
|
public enum Distance
|
||||||
|
{
|
||||||
|
OneStep,
|
||||||
|
MultiStep
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public enum HaltCondition
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Do not stop until you reach the collection boundary.
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
/// <summary>
|
|
||||||
/// Halt after encountering a non-null element.
|
|
||||||
/// </summary>
|
|
||||||
AfterCollide
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public enum Distance
|
|
||||||
{
|
|
||||||
OneStep,
|
|
||||||
MultiStep
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace PathFinding
|
namespace PathFinding
|
||||||
{
|
{
|
||||||
public interface IPlanarCollection<T> : IEnumerable<T>
|
public interface IPlanarCollection<T> : IEnumerable<T> where T : IPlanarElement
|
||||||
{
|
{
|
||||||
T this[float x, float y] { get; set; }
|
T this[float x, float y] { get; set; }
|
||||||
int GetLength(int dimension);
|
int GetLength(int dimension);
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace PathFinding
|
namespace PathFinding
|
||||||
{
|
{
|
||||||
public interface IPlanarElement
|
public interface IPlanarElement
|
||||||
{
|
{
|
||||||
ICollection<Path> GetPaths();
|
MoveSet MoveSet { get; }
|
||||||
|
|
||||||
bool IsUpsideDown { get; }
|
bool IsUpsideDown { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ using System.Numerics;
|
|||||||
namespace PathFinding
|
namespace PathFinding
|
||||||
{
|
{
|
||||||
[DebuggerDisplay("{Direction} - {Distance}")]
|
[DebuggerDisplay("{Direction} - {Distance}")]
|
||||||
public class Path
|
public class Move
|
||||||
{
|
{
|
||||||
public Vector2 Direction { get; }
|
public Vector2 Direction { get; }
|
||||||
public Distance Distance { get; }
|
public Distance Distance { get; }
|
||||||
public Path(Vector2 direction, Distance distance = Distance.OneStep)
|
public Move(Vector2 direction, Distance distance = Distance.OneStep)
|
||||||
{
|
{
|
||||||
Direction = direction;
|
Direction = direction;
|
||||||
Distance = distance;
|
Distance = distance;
|
||||||
23
PathFinding/MoveSet.cs
Normal file
23
PathFinding/MoveSet.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace PathFinding
|
||||||
|
{
|
||||||
|
public class MoveSet
|
||||||
|
{
|
||||||
|
private readonly IPlanarElement element;
|
||||||
|
private readonly ICollection<Move> moves;
|
||||||
|
private readonly ICollection<Move> upsidedownMoves;
|
||||||
|
|
||||||
|
public MoveSet(IPlanarElement element, ICollection<Move> moves)
|
||||||
|
{
|
||||||
|
this.element = element;
|
||||||
|
this.moves = moves;
|
||||||
|
upsidedownMoves = moves.Select(_ => new Move(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICollection<Move> GetMoves() => element.IsUpsideDown ? upsidedownMoves : moves;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ namespace PathFinding
|
|||||||
/// <param name="position"></param>
|
/// <param name="position"></param>
|
||||||
public delegate void Callback(T collider, Vector2 position);
|
public delegate void Callback(T collider, Vector2 position);
|
||||||
|
|
||||||
|
|
||||||
private readonly IPlanarCollection<T> collection;
|
private readonly IPlanarCollection<T> collection;
|
||||||
private readonly int width;
|
private readonly int width;
|
||||||
private readonly int height;
|
private readonly int height;
|
||||||
@@ -39,8 +37,9 @@ namespace PathFinding
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var element = collection[origin.X, origin.Y];
|
var element = collection[origin.X, origin.Y];
|
||||||
var path = FindDirectionTowardsDestination(element.GetPaths(), origin, destination);
|
if (element == null) return false;
|
||||||
|
|
||||||
|
var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination);
|
||||||
if (!IsPathable(origin, destination, path.Direction))
|
if (!IsPathable(origin, destination, path.Direction))
|
||||||
{
|
{
|
||||||
// Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen.
|
// Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen.
|
||||||
@@ -69,7 +68,7 @@ namespace PathFinding
|
|||||||
public void PathEvery(Vector2 from, Callback callback)
|
public void PathEvery(Vector2 from, Callback callback)
|
||||||
{
|
{
|
||||||
var element = collection[from.X, from.Y];
|
var element = collection[from.X, from.Y];
|
||||||
foreach (var path in element.GetPaths())
|
foreach (var path in element.MoveSet.GetMoves())
|
||||||
{
|
{
|
||||||
var shouldPath = true;
|
var shouldPath = true;
|
||||||
var next = Vector2.Add(from, path.Direction); ;
|
var next = Vector2.Add(from, path.Direction); ;
|
||||||
@@ -106,16 +105,15 @@ namespace PathFinding
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path FindDirectionTowardsDestination(ICollection<Path> paths, Vector2 origin, Vector2 destination) =>
|
public static Move FindDirectionTowardsDestination(ICollection<Move> paths, Vector2 origin, Vector2 destination) =>
|
||||||
paths.Aggregate((a, b) => Vector2.Distance(destination, Vector2.Add(origin, a.Direction)) < Vector2.Distance(destination, Vector2.Add(origin, b.Direction)) ? a : b);
|
paths.Aggregate((a, b) => Vector2.Distance(destination, Vector2.Add(origin, a.Direction)) < Vector2.Distance(destination, Vector2.Add(origin, b.Direction)) ? a : b);
|
||||||
|
|
||||||
|
public static bool IsPathable(Vector2 origin, Vector2 destination, T element)
|
||||||
public bool IsPathable(Vector2 origin, Vector2 destination, T element)
|
|
||||||
{
|
{
|
||||||
var path = FindDirectionTowardsDestination(element.GetPaths(), origin, destination);
|
var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination);
|
||||||
return IsPathable(origin, destination, path.Direction);
|
return IsPathable(origin, destination, path.Direction);
|
||||||
}
|
}
|
||||||
public bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction)
|
public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction)
|
||||||
{
|
{
|
||||||
direction = Vector2.Normalize(direction);
|
direction = Vector2.Normalize(direction);
|
||||||
var next = Vector2.Add(origin, direction);
|
var next = Vector2.Add(origin, direction);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user