before deleting Rules
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.BoardState\Gameboard.ShogiUI.Rules.csproj" />
|
<ProjectReference Include="..\Gameboard.ShogiUI.Rules\Gameboard.ShogiUI.Rules.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -16,33 +16,33 @@ namespace Benchmarking
|
|||||||
|
|
||||||
public Benchmarks()
|
public Benchmarks()
|
||||||
{
|
{
|
||||||
moves = new[]
|
//moves = new[]
|
||||||
{
|
//{
|
||||||
// P1 Rook
|
// // P1 Rook
|
||||||
new Move { From = new Vector2(7, 1), To = new Vector2(4, 1) },
|
// new Move { From = new Vector2(7, 1), To = new Vector2(4, 1) },
|
||||||
// P2 Gold
|
// // P2 Gold
|
||||||
new Move { From = new Vector2(3, 8), To = new Vector2(2, 7) },
|
// new Move { From = new Vector2(3, 8), To = new Vector2(2, 7) },
|
||||||
// P1 Pawn
|
// // P1 Pawn
|
||||||
new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) },
|
// new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) },
|
||||||
// P2 other Gold
|
// // P2 other Gold
|
||||||
new Move { From = new Vector2(5, 8), To = new Vector2(6, 7) },
|
// new Move { From = new Vector2(5, 8), To = new Vector2(6, 7) },
|
||||||
// P1 same Pawn
|
// // P1 same Pawn
|
||||||
new Move { From = new Vector2(4, 3), To = new Vector2(4, 4) },
|
// new Move { From = new Vector2(4, 3), To = new Vector2(4, 4) },
|
||||||
// P2 Pawn
|
// // P2 Pawn
|
||||||
new Move { From = new Vector2(4, 6), To = new Vector2(4, 5) },
|
// new Move { From = new Vector2(4, 6), To = new Vector2(4, 5) },
|
||||||
// P1 Pawn takes P2 Pawn
|
// // P1 Pawn takes P2 Pawn
|
||||||
new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) },
|
// new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) },
|
||||||
// P2 King
|
// // P2 King
|
||||||
new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) },
|
// new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) },
|
||||||
// P1 Pawn promotes
|
// // P1 Pawn promotes
|
||||||
new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true },
|
// new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true },
|
||||||
// P2 King retreat
|
// // P2 King retreat
|
||||||
new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) },
|
// new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) },
|
||||||
};
|
//};
|
||||||
var rand = new Random();
|
//var rand = new Random();
|
||||||
|
|
||||||
directions = new Vector2[10];
|
//directions = new Vector2[10];
|
||||||
for (var n = 0; n < 10; n++) directions[n] = new Vector2(rand.Next(-2, 2), rand.Next(-2, 2));
|
//for (var n = 0; n < 10; n++) directions[n] = new Vector2(rand.Next(-2, 2), rand.Next(-2, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Benchmark]
|
//[Benchmark]
|
||||||
|
|||||||
@@ -4,4 +4,8 @@
|
|||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Results\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
16
CouchDB/CouchDocument.cs
Normal file
16
CouchDB/CouchDocument.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace CouchDB
|
||||||
|
{
|
||||||
|
public class CouchDocument<T>
|
||||||
|
{
|
||||||
|
public readonly string _id;
|
||||||
|
public readonly string type;
|
||||||
|
public readonly T model;
|
||||||
|
|
||||||
|
public CouchDocument(string id, T model)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
this.model = model;
|
||||||
|
type = nameof(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
CouchDB/Selectors/CouchQuery.cs
Normal file
27
CouchDB/Selectors/CouchQuery.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CouchDB.Selectors
|
||||||
|
{
|
||||||
|
public class CouchQuery
|
||||||
|
{
|
||||||
|
public static CouchQuery Select => new();
|
||||||
|
|
||||||
|
private readonly List<Equals> equals;
|
||||||
|
protected CouchQuery()
|
||||||
|
{
|
||||||
|
equals = new List<Equals>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CouchQuery WithEqual(string key, string value)
|
||||||
|
{
|
||||||
|
equals.Add(new Equals(key, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var selector = string.Join(",", equals);
|
||||||
|
return $"{{ \"selector\": {selector}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
CouchDB/Selectors/Equals.cs
Normal file
18
CouchDB/Selectors/Equals.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace CouchDB.Selectors
|
||||||
|
{
|
||||||
|
public class Equals
|
||||||
|
{
|
||||||
|
private readonly string key;
|
||||||
|
private readonly string value;
|
||||||
|
internal Equals(string key, string value)
|
||||||
|
{
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{{ \"{key}\": {{ \"$eq\": {value}}} }}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Rules
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("{From} - {To}")]
|
|
||||||
public class Move
|
|
||||||
{
|
|
||||||
public WhichPiece? PieceFromCaptured { get; set; }
|
|
||||||
public Vector2 From { get; set; }
|
|
||||||
public Vector2 To { get; set; }
|
|
||||||
public bool IsPromotion { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Gameboard.ShogiUI.Domain
|
|
||||||
{
|
|
||||||
public class Board
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
namespace Gameboard.ShogiUI.Domain
|
|
||||||
{
|
|
||||||
public class Match
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string Player1 { get; }
|
|
||||||
public string Player2 { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize pre-existing Match.
|
|
||||||
/// </summary>
|
|
||||||
public Match(MatchMeta meta, Board board)
|
|
||||||
{
|
|
||||||
Name = meta.Name;
|
|
||||||
Player1 = meta.Player1;
|
|
||||||
Player2 = meta.Player2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new Match.
|
|
||||||
/// </summary>
|
|
||||||
public Match(string name, string player1)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Player1 = player1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Domain
|
|
||||||
{
|
|
||||||
public class MatchMeta
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string Player1 { get; }
|
|
||||||
public string Player2 { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
27
Gameboard.ShogiUI.Rules/Move.cs
Normal file
27
Gameboard.ShogiUI.Rules/Move.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Rules
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{From} - {To}")]
|
||||||
|
public class Move
|
||||||
|
{
|
||||||
|
public WhichPiece? PieceFromHand { get; }
|
||||||
|
public Vector2? From { get; }
|
||||||
|
public Vector2 To { get; }
|
||||||
|
public bool IsPromotion { get; }
|
||||||
|
|
||||||
|
public Move(Vector2 from, Vector2 to, bool isPromotion)
|
||||||
|
{
|
||||||
|
From = from;
|
||||||
|
To = to;
|
||||||
|
IsPromotion = isPromotion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Move(WhichPiece pieceFromHand, Vector2 to)
|
||||||
|
{
|
||||||
|
PieceFromHand = pieceFromHand;
|
||||||
|
To = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ namespace Gameboard.ShogiUI.Rules.Pieces
|
|||||||
new PathFinding.Move(Direction.Right),
|
new PathFinding.Move(Direction.Right),
|
||||||
new PathFinding.Move(Direction.Down)
|
new PathFinding.Move(Direction.Down)
|
||||||
};
|
};
|
||||||
public GoldenGeneral(WhichPlayer owner) : base(WhichPiece.GoldenGeneral, owner)
|
public GoldenGeneral(WhichPlayer owner) : base(WhichPiece.GoldGeneral, owner)
|
||||||
{
|
{
|
||||||
moveSet = new MoveSet(this, Moves);
|
moveSet = new MoveSet(this, Moves);
|
||||||
promotedMoveSet = new MoveSet(this, Moves);
|
promotedMoveSet = new MoveSet(this, Moves);
|
||||||
@@ -25,7 +25,7 @@ namespace Gameboard.ShogiUI.Rules.Pieces
|
|||||||
|
|
||||||
public bool CanPromote => !IsPromoted
|
public bool CanPromote => !IsPromoted
|
||||||
&& WhichPiece != WhichPiece.King
|
&& WhichPiece != WhichPiece.King
|
||||||
&& WhichPiece != WhichPiece.GoldenGeneral;
|
&& WhichPiece != WhichPiece.GoldGeneral;
|
||||||
|
|
||||||
public void ToggleOwnership()
|
public void ToggleOwnership()
|
||||||
{
|
{
|
||||||
@@ -8,7 +8,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
public class PlanarCollection<T> : IPlanarCollection<T>, IEnumerable<T> where T : IPlanarElement
|
public class PlanarCollection<T> : IPlanarCollection<T>, IEnumerable<T> where T : IPlanarElement
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
@@ -19,12 +19,12 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
array = new T[width * height];
|
array = new T[width * height];
|
||||||
}
|
}
|
||||||
|
|
||||||
public T this[int x, int y]
|
public T? this[int x, int y]
|
||||||
{
|
{
|
||||||
get => array[y * width + x];
|
get => array[y * width + x];
|
||||||
set => array[y * width + x] = value;
|
set => array[y * width + x] = value;
|
||||||
}
|
}
|
||||||
public T this[float x, float y]
|
public T? this[float x, float y]
|
||||||
{
|
{
|
||||||
get => array[(int)y * width + (int)x];
|
get => array[(int)y * width + (int)x];
|
||||||
set => array[(int)y * width + (int)x] = value;
|
set => array[(int)y * width + (int)x] = value;
|
||||||
@@ -14,9 +14,8 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
public class ShogiBoard
|
public class ShogiBoard
|
||||||
{
|
{
|
||||||
private delegate void MoveSetCallback(Piece piece, Vector2 position);
|
private delegate void MoveSetCallback(Piece piece, Vector2 position);
|
||||||
private readonly bool isValidationBoard;
|
|
||||||
private readonly PathFinder2D<Piece> pathFinder;
|
private readonly PathFinder2D<Piece> pathFinder;
|
||||||
private ShogiBoard validationBoard;
|
private ShogiBoard? validationBoard;
|
||||||
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; }
|
||||||
@@ -26,7 +25,6 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
public WhichPlayer? InCheck { get; private set; }
|
public WhichPlayer? InCheck { get; private set; }
|
||||||
public bool IsCheckmate { get; private set; }
|
public bool IsCheckmate { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public string Error { get; private set; }
|
public string Error { get; private set; }
|
||||||
|
|
||||||
public ShogiBoard()
|
public ShogiBoard()
|
||||||
@@ -41,6 +39,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
InitializeBoardState();
|
InitializeBoardState();
|
||||||
player1King = new Vector2(4, 8);
|
player1King = new Vector2(4, 8);
|
||||||
player2King = new Vector2(4, 0);
|
player2King = new Vector2(4, 0);
|
||||||
|
Error = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShogiBoard(IList<Move> moves) : this()
|
public ShogiBoard(IList<Move> moves) : this()
|
||||||
@@ -57,11 +56,16 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
|
|
||||||
private ShogiBoard(ShogiBoard toCopy)
|
private ShogiBoard(ShogiBoard toCopy)
|
||||||
{
|
{
|
||||||
isValidationBoard = true;
|
|
||||||
Board = new PlanarCollection<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();
|
{
|
||||||
|
var piece = toCopy.Board[x, y];
|
||||||
|
if (piece != null)
|
||||||
|
{
|
||||||
|
Board[x, y] = piece.DeepClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pathFinder = new PathFinder2D<Piece>(Board);
|
pathFinder = new PathFinder2D<Piece>(Board);
|
||||||
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
||||||
@@ -72,6 +76,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
};
|
};
|
||||||
player1King = toCopy.player1King;
|
player1King = toCopy.player1King;
|
||||||
player2King = toCopy.player2King;
|
player2King = toCopy.player2King;
|
||||||
|
Error = toCopy.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Move(Move move)
|
public bool Move(Move move)
|
||||||
@@ -103,7 +108,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
validationBoard = new ShogiBoard(this);
|
validationBoard = new ShogiBoard(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var isValid = move.PieceFromCaptured.HasValue
|
var isValid = move.PieceFromHand.HasValue
|
||||||
? validationBoard.PlaceFromHand(move)
|
? validationBoard.PlaceFromHand(move)
|
||||||
: validationBoard.PlaceFromBoard(move);
|
: validationBoard.PlaceFromBoard(move);
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
@@ -123,20 +128,20 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The move is valid and legal; update board state.
|
// The move is valid and legal; update board state.
|
||||||
if (move.PieceFromCaptured.HasValue) PlaceFromHand(move);
|
if (move.PieceFromHand.HasValue) PlaceFromHand(move);
|
||||||
else PlaceFromBoard(move);
|
else PlaceFromBoard(move);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/// <returns>True if the move was successful.</returns>
|
/// <returns>True if the move was successful.</returns>
|
||||||
private bool PlaceFromHand(Move move)
|
private bool PlaceFromHand(Move move)
|
||||||
{
|
{
|
||||||
if (move.PieceFromCaptured.HasValue == false) return false; //Invalid move
|
if (move.PieceFromHand.HasValue == false) return false; //Invalid move
|
||||||
var index = Hands[WhoseTurn].FindIndex(p => p.WhichPiece == move.PieceFromCaptured);
|
var index = Hands[WhoseTurn].FindIndex(p => p.WhichPiece == move.PieceFromHand);
|
||||||
if (index < 0) return false; // Invalid move
|
if (index < 0) return false; // Invalid move
|
||||||
if (Board[move.To.X, move.To.Y] != null) return false; // Invalid move; cannot capture while playing from the hand.
|
if (Board[move.To.X, move.To.Y] != null) return false; // Invalid move; cannot capture while playing from the hand.
|
||||||
|
|
||||||
var minimumY = 0;
|
var minimumY = 0;
|
||||||
switch (move.PieceFromCaptured.Value)
|
switch (move.PieceFromHand.Value)
|
||||||
{
|
{
|
||||||
case WhichPiece.Knight:
|
case WhichPiece.Knight:
|
||||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||||
@@ -160,7 +165,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
/// <returns>True if the move was successful.</returns>
|
/// <returns>True if the move was successful.</returns>
|
||||||
private bool PlaceFromBoard(Move move)
|
private bool PlaceFromBoard(Move move)
|
||||||
{
|
{
|
||||||
var fromPiece = Board[move.From.X, move.From.Y];
|
var fromPiece = Board[move.From.Value.X, move.From.Value.Y];
|
||||||
if (fromPiece == null)
|
if (fromPiece == null)
|
||||||
{
|
{
|
||||||
Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
|
Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
|
||||||
@@ -171,7 +176,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
Error = "Not allowed to move the opponents piece";
|
Error = "Not allowed to move the opponents piece";
|
||||||
return false; // Invalid move; cannot move other players pieces.
|
return false; // Invalid move; cannot move other players pieces.
|
||||||
}
|
}
|
||||||
if (IsPathable(move.From, move.To) == false)
|
if (IsPathable(move.From.Value, move.To) == false)
|
||||||
{
|
{
|
||||||
Error = $"Illegal move for {fromPiece.WhichPiece}. {nameof(move)}.{nameof(move.To)} is not part of the move-set.";
|
Error = $"Illegal move for {fromPiece.WhichPiece}. {nameof(move)}.{nameof(move.To)} is not part of the move-set.";
|
||||||
return false; // Invalid move; move not part of move-set.
|
return false; // Invalid move; move not part of move-set.
|
||||||
@@ -188,17 +193,17 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
//Mutate the board.
|
//Mutate the board.
|
||||||
if (move.IsPromotion)
|
if (move.IsPromotion)
|
||||||
{
|
{
|
||||||
if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y < 3 || move.From.Y < 3))
|
if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y < 3 || move.From.Value.Y < 3))
|
||||||
{
|
{
|
||||||
fromPiece.Promote();
|
fromPiece.Promote();
|
||||||
}
|
}
|
||||||
else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y > 5 || move.From.Y > 5))
|
else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y > 5 || move.From.Value.Y > 5))
|
||||||
{
|
{
|
||||||
fromPiece.Promote();
|
fromPiece.Promote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.Value.X, move.From.Value.Y] = null;
|
||||||
if (fromPiece.WhichPiece == WhichPiece.King)
|
if (fromPiece.WhichPiece == WhichPiece.King)
|
||||||
{
|
{
|
||||||
if (fromPiece.Owner == WhichPlayer.Player1)
|
if (fromPiece.Owner == WhichPlayer.Player1)
|
||||||
@@ -239,7 +244,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
if (pathFinder.PathTo(move.To, kingPosition)) return true;
|
if (pathFinder.PathTo(move.To, kingPosition)) return true;
|
||||||
|
|
||||||
// Get line equation from king through the now-unoccupied location.
|
// Get line equation from king through the now-unoccupied location.
|
||||||
var direction = Vector2.Subtract(kingPosition, move.From);
|
var direction = Vector2.Subtract(kingPosition, move.From.Value);
|
||||||
var slope = Math.Abs(direction.Y / direction.X);
|
var slope = Math.Abs(direction.Y / direction.X);
|
||||||
// If absolute slope is 45°, look for a bishop along the line.
|
// 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.
|
// If absolute slope is 0° or 90°, look for a rook along the line.
|
||||||
@@ -304,7 +309,7 @@ namespace Gameboard.ShogiUI.Rules
|
|||||||
pathFinder.PathEvery(from, (other, position) =>
|
pathFinder.PathEvery(from, (other, position) =>
|
||||||
{
|
{
|
||||||
if (validationBoard == null) validationBoard = new ShogiBoard(this);
|
if (validationBoard == null) validationBoard = new ShogiBoard(this);
|
||||||
var moveToTry = new Move { From = from, To = position };
|
var moveToTry = new Move(from, position, false);
|
||||||
var moveSuccess = validationBoard.TryMove(moveToTry);
|
var moveSuccess = validationBoard.TryMove(moveToTry);
|
||||||
if (moveSuccess)
|
if (moveSuccess)
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
public enum WhichPiece
|
public enum WhichPiece
|
||||||
{
|
{
|
||||||
King,
|
King,
|
||||||
GoldenGeneral,
|
GoldGeneral,
|
||||||
SilverGeneral,
|
SilverGeneral,
|
||||||
Bishop,
|
Bishop,
|
||||||
Rook,
|
Rook,
|
||||||
@@ -4,7 +4,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages
|
|||||||
{
|
{
|
||||||
public class GetGuestToken
|
public class GetGuestToken
|
||||||
{
|
{
|
||||||
public string ClientId { get; set; }
|
public string? ClientId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetGuestTokenResponse
|
public class GetGuestTokenResponse
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages
|
||||||
|
{
|
||||||
|
public class PostSession
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Player1 { get; set; }
|
||||||
|
public string Player2 { get; set; }
|
||||||
|
public bool IsPrivate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces
|
|||||||
{
|
{
|
||||||
public interface IRequest
|
public interface IRequest
|
||||||
{
|
{
|
||||||
ClientAction Action { get; set; }
|
ClientAction Action { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
public class CreateGameRequest : IRequest
|
public class CreateGameRequest : IRequest
|
||||||
{
|
{
|
||||||
public ClientAction Action { get; set; }
|
public ClientAction Action { get; set; }
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; } = string.Empty;
|
||||||
public bool IsPrivate { get; set; }
|
public bool IsPrivate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateGameResponse : IResponse
|
public class CreateGameResponse : IResponse
|
||||||
{
|
{
|
||||||
public string Action { get; private set; }
|
public string Action { get; }
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
public Game Game { get; set; }
|
public Game Game { get; set; }
|
||||||
public string PlayerName { get; set; }
|
public string PlayerName { get; set; }
|
||||||
@@ -20,6 +20,9 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
public CreateGameResponse(ClientAction action)
|
public CreateGameResponse(ClientAction action)
|
||||||
{
|
{
|
||||||
Action = action.ToString();
|
Action = action.ToString();
|
||||||
|
Error = string.Empty;
|
||||||
|
Game = new Game();
|
||||||
|
PlayerName = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|
||||||
{
|
|
||||||
public class ErrorResponse : IResponse
|
|
||||||
{
|
|
||||||
public string Action { get; private set; }
|
|
||||||
public string Error { get; set; }
|
|
||||||
|
|
||||||
public ErrorResponse(ClientAction action)
|
|
||||||
{
|
|
||||||
Action = action.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|
||||||
{
|
|
||||||
public class JoinByCode : IRequest
|
|
||||||
{
|
|
||||||
public ClientAction Action { get; set; }
|
|
||||||
public string JoinCode { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,12 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
||||||
{
|
{
|
||||||
|
public class JoinByCodeRequest : IRequest
|
||||||
|
{
|
||||||
|
public ClientAction Action { get; set; }
|
||||||
|
public string JoinCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class JoinGameRequest : IRequest
|
public class JoinGameRequest : IRequest
|
||||||
{
|
{
|
||||||
public ClientAction Action { get; set; }
|
public ClientAction Action { get; set; }
|
||||||
@@ -11,7 +17,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
|
|
||||||
public class JoinGameResponse : IResponse
|
public class JoinGameResponse : IResponse
|
||||||
{
|
{
|
||||||
public string Action { get; private set; }
|
public string Action { get; }
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; }
|
||||||
public string PlayerName { get; set; }
|
public string PlayerName { get; set; }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
|
|
||||||
public class ListGamesResponse : IResponse
|
public class ListGamesResponse : IResponse
|
||||||
{
|
{
|
||||||
public string Action { get; private set; }
|
public string Action { get; }
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
public ICollection<Game> Games { get; set; }
|
public ICollection<Game> Games { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
||||||
{
|
{
|
||||||
@@ -12,7 +11,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
|
|
||||||
public class LoadGameResponse : IResponse
|
public class LoadGameResponse : IResponse
|
||||||
{
|
{
|
||||||
public string Action { get; private set; }
|
public string Action { get; }
|
||||||
public Game Game { get; set; }
|
public Game Game { get; set; }
|
||||||
public BoardState BoardState { get; set; }
|
public BoardState BoardState { get; set; }
|
||||||
public string Error { get; set; }
|
public string Error { get; set; }
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
public class MoveRequest : IRequest
|
public class MoveRequest : IRequest
|
||||||
{
|
{
|
||||||
public ClientAction Action { get; set; }
|
public ClientAction Action { get; set; }
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; } = string.Empty;
|
||||||
public Move Move { get; set; }
|
public Move Move { get; set; } = new Move();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MoveResponse : IResponse
|
public class MoveResponse : IResponse
|
||||||
@@ -21,6 +21,10 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
|
|||||||
public MoveResponse(ClientAction action)
|
public MoveResponse(ClientAction action)
|
||||||
{
|
{
|
||||||
Action = action.ToString();
|
Action = action.ToString();
|
||||||
|
Error = string.Empty;
|
||||||
|
GameName = string.Empty;
|
||||||
|
BoardState = new BoardState();
|
||||||
|
PlayerName = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
|
||||||
{
|
{
|
||||||
public class BoardState
|
public class BoardState
|
||||||
{
|
{
|
||||||
public Piece[,] Board { get; set; }
|
public Piece[,] Board { get; set; } = new Piece[0, 0];
|
||||||
public IReadOnlyCollection<Piece> Player1Hand { get; set; }
|
public IReadOnlyCollection<Piece> Player1Hand { get; set; } = Array.Empty<Piece>();
|
||||||
public IReadOnlyCollection<Piece> Player2Hand { get; set; }
|
public IReadOnlyCollection<Piece> Player2Hand { get; set; } = Array.Empty<Piece>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
JoinGame,
|
JoinGame,
|
||||||
JoinByCode,
|
JoinByCode,
|
||||||
LoadGame,
|
LoadGame,
|
||||||
Move,
|
Move
|
||||||
KeepAlive
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
|
||||||
{
|
{
|
||||||
public class Game
|
public class Game
|
||||||
{
|
{
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; } = string.Empty;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Players[0] is the session owner, Players[1] is the other guy
|
/// Players[0] is the session owner, Players[1] is the other guy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<string> Players { get; set; }
|
public IReadOnlyList<string> Players { get; set; } = Array.Empty<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
{
|
{
|
||||||
public class Move
|
public class Move
|
||||||
{
|
{
|
||||||
public string PieceFromCaptured { get; set; }
|
public WhichPiece? PieceFromCaptured { get; set; }
|
||||||
/// <summary>Board position notation, like A3 or G1</summary>
|
/// <summary>Board position notation, like A3 or G1</summary>
|
||||||
public string From { get; set; }
|
public string? From { get; set; }
|
||||||
/// <summary>Board position notation, like A3 or G1</summary>
|
/// <summary>Board position notation, like A3 or G1</summary>
|
||||||
public string To { get; set; }
|
public string To { get; set; } = string.Empty;
|
||||||
public bool IsPromotion { get; set; }
|
public bool IsPromotion { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
public class Piece
|
public class Piece
|
||||||
{
|
{
|
||||||
public WhichPiece WhichPiece { get; set; }
|
|
||||||
|
|
||||||
public bool IsPromoted { get; set; }
|
public bool IsPromoted { get; set; }
|
||||||
|
public WhichPiece WhichPiece { get; set; }
|
||||||
|
public WhichPlayer Owner { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
|
||||||
|
{
|
||||||
|
public enum WhichPlayer
|
||||||
|
{
|
||||||
|
Player1,
|
||||||
|
Player2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,15 +9,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets.S
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Rules", "Gameboard.ShogiUI.BoardState\Gameboard.ShogiUI.Rules.csproj", "{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}"
|
|
||||||
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gameboard.ShogiUI.Domain", "Gameboard.ShogiUI.Domain\Gameboard.ShogiUI.Domain.csproj", "{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CouchDB", "CouchDB\CouchDB.csproj", "{EDFED1DF-253D-463B-842A-0B66F95214A7}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Rules", "Gameboard.ShogiUI.Rules\Gameboard.ShogiUI.Rules.csproj", "{D7130FAF-CEC4-4567-A9F0-22C060E9B508}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -33,10 +33,6 @@ Global
|
|||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C5A7C4EF-549F-40A8-A0BD-DA2C7C0A6CF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -49,10 +45,14 @@ Global
|
|||||||
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{EDFED1DF-253D-463B-842A-0B66F95214A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EDFED1DF-253D-463B-842A-0B66F95214A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EDFED1DF-253D-463B-842A-0B66F95214A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EDFED1DF-253D-463B-842A-0B66F95214A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D7130FAF-CEC4-4567-A9F0-22C060E9B508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D7130FAF-CEC4-4567-A9F0-22C060E9B508}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D7130FAF-CEC4-4567-A9F0-22C060E9B508}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D7130FAF-CEC4-4567-A9F0-22C060E9B508}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
using Gameboard.ShogiUI.Sockets.Managers;
|
||||||
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -14,17 +15,20 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
public class GameController : ControllerBase
|
public class GameController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IGameboardRepositoryManager manager;
|
private readonly IGameboardRepositoryManager manager;
|
||||||
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
private readonly IGameboardRepository repository;
|
private readonly IGameboardRepository repository;
|
||||||
|
|
||||||
public GameController(
|
public GameController(
|
||||||
IGameboardRepository repository,
|
IGameboardRepository repository,
|
||||||
IGameboardRepositoryManager manager)
|
IGameboardRepositoryManager manager,
|
||||||
|
ISocketCommunicationManager communicationManager)
|
||||||
{
|
{
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
this.communicationManager = communicationManager;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("JoinCode")]
|
[HttpPost("JoinCode")]
|
||||||
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
|
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
|
||||||
{
|
{
|
||||||
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
|
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
|
||||||
@@ -41,7 +45,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[Route("GuestJoinCode")]
|
[HttpPost("GuestJoinCode")]
|
||||||
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
|
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -57,5 +61,26 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
return new UnauthorizedResult();
|
return new UnauthorizedResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
|
||||||
|
//[Route("")]
|
||||||
|
//public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
||||||
|
//{
|
||||||
|
// var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2);
|
||||||
|
// var success = await repository.CreateSession(model);
|
||||||
|
// if (success)
|
||||||
|
// {
|
||||||
|
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Socket.Types.ClientAction.CreateGame)
|
||||||
|
// {
|
||||||
|
// Game = model.ToServiceModel(),
|
||||||
|
// PlayerName =
|
||||||
|
// }
|
||||||
|
// var task = request.IsPrivate
|
||||||
|
// ? communicationManager.BroadcastToPlayers(response, userName)
|
||||||
|
// : communicationManager.BroadcastToAll(response);
|
||||||
|
// return new CreatedResult("", null);
|
||||||
|
// }
|
||||||
|
// return new ConflictResult();
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.Managers;
|
using Gameboard.ShogiUI.Sockets.Managers;
|
||||||
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -13,18 +15,24 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class SocketController : ControllerBase
|
public class SocketController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<SocketController> logger;
|
||||||
private readonly ISocketTokenManager tokenManager;
|
private readonly ISocketTokenManager tokenManager;
|
||||||
private readonly IGameboardRepositoryManager gameboardManager;
|
private readonly IGameboardRepositoryManager gameboardManager;
|
||||||
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
|
|
||||||
public SocketController(
|
public SocketController(
|
||||||
|
ILogger<SocketController> logger,
|
||||||
ISocketTokenManager tokenManager,
|
ISocketTokenManager tokenManager,
|
||||||
IGameboardRepositoryManager gameboardManager)
|
IGameboardRepositoryManager gameboardManager,
|
||||||
|
IGameboardRepository gameboardRepository)
|
||||||
{
|
{
|
||||||
|
this.logger = logger;
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
this.gameboardManager = gameboardManager;
|
this.gameboardManager = gameboardManager;
|
||||||
|
this.gameboardRepository = gameboardRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("Token")]
|
[HttpGet("Token")]
|
||||||
public IActionResult GetToken()
|
public IActionResult GetToken()
|
||||||
{
|
{
|
||||||
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
|
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
|
||||||
@@ -33,7 +41,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[Route("GuestToken")]
|
[HttpGet("GuestToken")]
|
||||||
public async Task<IActionResult> GetGuestToken([FromQuery] GetGuestToken request)
|
public async Task<IActionResult> GetGuestToken([FromQuery] GetGuestToken request)
|
||||||
{
|
{
|
||||||
if (request.ClientId == null)
|
if (request.ClientId == null)
|
||||||
@@ -44,7 +52,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (await gameboardManager.PlayerExists(request.ClientId))
|
if (await gameboardRepository.IsGuestUser(request.ClientId))
|
||||||
{
|
{
|
||||||
var token = tokenManager.GenerateToken(request.ClientId);
|
var token = tokenManager.GenerateToken(request.ClientId);
|
||||||
return new JsonResult(new GetGuestTokenResponse(request.ClientId, token));
|
return new JsonResult(new GetGuestTokenResponse(request.ClientId, token));
|
||||||
|
|||||||
@@ -4,10 +4,18 @@
|
|||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Gameboard.Shogi.Api.ServiceModels" Version="2.13.0" />
|
<None Remove="Repositories\CouchModels\Readme.md" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Repositories\CouchModels\Readme.md" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
<PackageReference Include="IdentityModel" Version="5.0.0" />
|
<PackageReference Include="IdentityModel" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="5.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="5.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.2" />
|
||||||
@@ -17,7 +25,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.BoardState\Gameboard.ShogiUI.Rules.csproj" />
|
<ProjectReference Include="..\CouchDB\CouchDB.csproj" />
|
||||||
|
<ProjectReference Include="..\Gameboard.ShogiUI.Rules\Gameboard.ShogiUI.Rules.csproj" />
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets.ServiceModels\Gameboard.ShogiUI.Sockets.ServiceModels.csproj" />
|
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets.ServiceModels\Gameboard.ShogiUI.Sockets.ServiceModels.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
public interface IBoardManager
|
public interface IBoardManager
|
||||||
{
|
{
|
||||||
void Add(string sessionName, ShogiBoard board);
|
void Add(string sessionName, ShogiBoard board);
|
||||||
ShogiBoard Get(string sessionName);
|
ShogiBoard? Get(string sessionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BoardManager : IBoardManager
|
public class BoardManager : IBoardManager
|
||||||
@@ -20,10 +20,12 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
|
|
||||||
public void Add(string sessionName, ShogiBoard board) => Boards.TryAdd(sessionName, board);
|
public void Add(string sessionName, ShogiBoard board) => Boards.TryAdd(sessionName, board);
|
||||||
|
|
||||||
public ShogiBoard Get(string sessionName)
|
public ShogiBoard? Get(string sessionName)
|
||||||
{
|
{
|
||||||
if (Boards.TryGetValue(sessionName, out var board))
|
if (Boards.TryGetValue(sessionName, out var board))
|
||||||
|
{
|
||||||
return board;
|
return board;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,55 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Models;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
|
public interface ICreateGameHandler
|
||||||
|
{
|
||||||
|
Task Handle(CreateGameRequest request, string userName);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This doesn't need to be a socket action.
|
// TODO: This doesn't need to be a socket action.
|
||||||
// It can be an API route and still tell socket connections about the new session.
|
// It can be an API route and still tell socket connections about the new session.
|
||||||
public class CreateGameHandler : IActionHandler
|
public class CreateGameHandler : ICreateGameHandler
|
||||||
{
|
{
|
||||||
private readonly IGameboardRepository repository;
|
private readonly IGameboardRepositoryManager manager;
|
||||||
private readonly ISocketCommunicationManager communicationManager;
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
|
|
||||||
public CreateGameHandler(
|
public CreateGameHandler(
|
||||||
ISocketCommunicationManager communicationManager,
|
ISocketCommunicationManager communicationManager,
|
||||||
IGameboardRepository repository)
|
IGameboardRepositoryManager manager)
|
||||||
{
|
{
|
||||||
this.repository = repository;
|
this.manager = manager;
|
||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(CreateGameRequest request, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<CreateGameRequest>(json);
|
var model = new Session(request.GameName, request.IsPrivate, userName);
|
||||||
var sessionName = await repository.PostSession(new PostSession
|
var success = await manager.CreateSession(model);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
{
|
{
|
||||||
SessionName = request.GameName,
|
var error = new CreateGameResponse(request.Action)
|
||||||
PlayerName = userName,
|
{
|
||||||
IsPrivate = request.IsPrivate
|
Error = "Unable to create game with this name."
|
||||||
});
|
};
|
||||||
|
await communicationManager.BroadcastToPlayers(error, userName);
|
||||||
|
}
|
||||||
|
|
||||||
var response = new CreateGameResponse(request.Action)
|
var response = new CreateGameResponse(request.Action)
|
||||||
{
|
{
|
||||||
PlayerName = userName,
|
PlayerName = userName,
|
||||||
Game = new Game
|
Game = model.ToServiceModel()
|
||||||
{
|
|
||||||
GameName = sessionName,
|
|
||||||
Players = new[] { userName }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(sessionName))
|
var task = request.IsPrivate
|
||||||
{
|
? communicationManager.BroadcastToPlayers(response, userName)
|
||||||
response.Error = "Game already exists.";
|
: communicationManager.BroadcastToAll(response);
|
||||||
}
|
|
||||||
|
|
||||||
if (request.IsPrivate)
|
await task;
|
||||||
{
|
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await communicationManager.BroadcastToAll(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|
||||||
{
|
|
||||||
public interface IActionHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Responsible for parsing json and handling the request.
|
|
||||||
/// </summary>
|
|
||||||
Task Handle(string json, string userName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate IActionHandler ActionHandlerResolver(ClientAction action);
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
public class JoinByCodeHandler : IActionHandler
|
public interface IJoinByCodeHandler
|
||||||
|
{
|
||||||
|
Task Handle(JoinByCodeRequest request, string userName);
|
||||||
|
}
|
||||||
|
public class JoinByCodeHandler : IJoinByCodeHandler
|
||||||
{
|
{
|
||||||
private readonly IGameboardRepository repository;
|
private readonly IGameboardRepository repository;
|
||||||
private readonly ISocketCommunicationManager communicationManager;
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
@@ -20,44 +21,44 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(JoinByCodeRequest request, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<JoinByCode>(json);
|
//var request = JsonConvert.DeserializeObject<JoinByCode>(json);
|
||||||
var sessionName = await repository.PostJoinPrivateSession(new PostJoinPrivateSession
|
//var sessionName = await repository.PostJoinPrivateSession(new PostJoinPrivateSession
|
||||||
{
|
//{
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
JoinCode = request.JoinCode
|
// JoinCode = request.JoinCode
|
||||||
});
|
//});
|
||||||
|
|
||||||
if (sessionName == null)
|
//if (sessionName == null)
|
||||||
{
|
//{
|
||||||
var response = new JoinGameResponse(ClientAction.JoinByCode)
|
// var response = new JoinGameResponse(ClientAction.JoinByCode)
|
||||||
{
|
// {
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
GameName = sessionName,
|
// GameName = sessionName,
|
||||||
Error = "Error joining game."
|
// Error = "Error joining game."
|
||||||
};
|
// };
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||||
}
|
//}
|
||||||
else
|
//else
|
||||||
{
|
//{
|
||||||
// Other members of the game see a regular JoinGame occur.
|
// // Other members of the game see a regular JoinGame occur.
|
||||||
var response = new JoinGameResponse(ClientAction.JoinGame)
|
// var response = new JoinGameResponse(ClientAction.JoinGame)
|
||||||
{
|
// {
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
GameName = sessionName
|
// GameName = sessionName
|
||||||
};
|
// };
|
||||||
// At this time, userName hasn't subscribed and won't receive this message.
|
// // At this time, userName hasn't subscribed and won't receive this message.
|
||||||
await communicationManager.BroadcastToGame(sessionName, response);
|
// await communicationManager.BroadcastToGame(sessionName, response);
|
||||||
|
|
||||||
// The player joining sees the JoinByCode occur.
|
// // The player joining sees the JoinByCode occur.
|
||||||
response = new JoinGameResponse(ClientAction.JoinByCode)
|
// response = new JoinGameResponse(ClientAction.JoinByCode)
|
||||||
{
|
// {
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
GameName = sessionName
|
// GameName = sessionName
|
||||||
};
|
// };
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
public class JoinGameHandler : IActionHandler
|
public interface IJoinGameHandler
|
||||||
|
{
|
||||||
|
Task Handle(JoinGameRequest request, string userName);
|
||||||
|
}
|
||||||
|
public class JoinGameHandler : IJoinGameHandler
|
||||||
{
|
{
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
private readonly ISocketCommunicationManager communicationManager;
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
@@ -19,30 +20,30 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(JoinGameRequest request, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<JoinGameRequest>(json);
|
//var request = JsonConvert.DeserializeObject<JoinGameRequest>(json);
|
||||||
|
|
||||||
var joinSucceeded = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession
|
//var joinSucceeded = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession
|
||||||
{
|
//{
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
SessionName = request.GameName
|
// SessionName = request.GameName
|
||||||
});
|
//});
|
||||||
|
|
||||||
var response = new JoinGameResponse(ClientAction.JoinGame)
|
//var response = new JoinGameResponse(ClientAction.JoinGame)
|
||||||
{
|
//{
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
GameName = request.GameName
|
// GameName = request.GameName
|
||||||
};
|
//};
|
||||||
if (joinSucceeded)
|
//if (joinSucceeded)
|
||||||
{
|
//{
|
||||||
await communicationManager.BroadcastToAll(response);
|
// await communicationManager.BroadcastToAll(response);
|
||||||
}
|
//}
|
||||||
else
|
//else
|
||||||
{
|
//{
|
||||||
response.Error = "Game is full.";
|
// response.Error = "Game is full.";
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.Models;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
|
public interface IListGamesHandler
|
||||||
|
{
|
||||||
|
Task Handle(ListGamesRequest request, string userName);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This doesn't need to be a socket action.
|
// TODO: This doesn't need to be a socket action.
|
||||||
// It can be an HTTP route.
|
// It can be an HTTP route.
|
||||||
public class ListGamesHandler : IActionHandler
|
public class ListGamesHandler : IListGamesHandler
|
||||||
{
|
{
|
||||||
private readonly ISocketCommunicationManager communicationManager;
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
private readonly IGameboardRepository repository;
|
private readonly IGameboardRepository repository;
|
||||||
@@ -23,16 +26,10 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(ListGamesRequest _, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<ListGamesRequest>(json);
|
var sessions = await repository.ReadSessions();
|
||||||
var getGamesResponse = string.IsNullOrWhiteSpace(userName)
|
var games = sessions.Select(s => s.ToServiceModel()); // yuck
|
||||||
? await repository.GetGames()
|
|
||||||
: await repository.GetGames(userName);
|
|
||||||
|
|
||||||
var games = getGamesResponse.Sessions
|
|
||||||
.OrderBy(s => s.Player1 == userName || s.Player2 == userName)
|
|
||||||
.Select(s => new Session(s).ToServiceModel()); // yuck
|
|
||||||
|
|
||||||
var response = new ListGamesResponse(ClientAction.ListGames)
|
var response = new ListGamesResponse(ClientAction.ListGames)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,16 +3,20 @@ using Gameboard.ShogiUI.Sockets.Repositories;
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
|
public interface ILoadGameHandler
|
||||||
|
{
|
||||||
|
Task Handle(LoadGameRequest request, string userName);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscribes a user to messages for a session and loads that session into the BoardManager for playing.
|
/// Subscribes a user to messages for a session and loads that session into the BoardManager for playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoadGameHandler : IActionHandler
|
public class LoadGameHandler : ILoadGameHandler
|
||||||
{
|
{
|
||||||
private readonly ILogger<LoadGameHandler> logger;
|
private readonly ILogger<LoadGameHandler> logger;
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
@@ -31,26 +35,27 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
this.boardManager = boardManager;
|
this.boardManager = boardManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(LoadGameRequest request, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<LoadGameRequest>(json);
|
var readSession = gameboardRepository.ReadSession(request.GameName);
|
||||||
var gameTask = gameboardRepository.GetGame(request.GameName);
|
var readStates = gameboardRepository.ReadBoardStates(request.GameName);
|
||||||
var moveTask = gameboardRepository.GetMoves(request.GameName);
|
|
||||||
|
|
||||||
var sessionModel = await gameTask;
|
var sessionModel = await readSession;
|
||||||
if (sessionModel == null)
|
if (sessionModel == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("{action} - {user} was unable to load session named {session}.", ClientAction.LoadGame, userName, request.GameName);
|
logger.LogWarning("{action} - {user} was unable to load session named {session}.", ClientAction.LoadGame, userName, request.GameName);
|
||||||
var response = new LoadGameResponse(ClientAction.LoadGame) { Error = "Game not found." };
|
var error = new LoadGameResponse(ClientAction.LoadGame) { Error = "Game not found." };
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
await communicationManager.BroadcastToPlayers(error, userName);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var moveModels = await moveTask;
|
|
||||||
|
|
||||||
communicationManager.SubscribeToGame(sessionModel, userName);
|
communicationManager.SubscribeToGame(sessionModel, userName);
|
||||||
var boardMoves = moveModels.Select(_ => _.ToBoardModel()).ToList();
|
var boardStates = await readStates;
|
||||||
var shogiBoard = new ShogiBoard(boardMoves);
|
var moveModels = boardStates
|
||||||
|
.Where(_ => _.Move != null)
|
||||||
|
.Select(_ => _.Move!.ToRulesModel())
|
||||||
|
.ToList();
|
||||||
|
var shogiBoard = new ShogiBoard(moveModels);
|
||||||
boardManager.Add(sessionModel.Name, shogiBoard);
|
boardManager.Add(sessionModel.Name, shogiBoard);
|
||||||
|
|
||||||
var response = new LoadGameResponse(ClientAction.LoadGame)
|
var response = new LoadGameResponse(ClientAction.LoadGame)
|
||||||
@@ -62,4 +67,3 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Models;
|
||||||
using Gameboard.ShogiUI.Sockets.Models;
|
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Service = Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||||
{
|
{
|
||||||
public class MoveHandler : IActionHandler
|
public interface IMoveHandler
|
||||||
|
{
|
||||||
|
Task Handle(MoveRequest request, string userName);
|
||||||
|
}
|
||||||
|
public class MoveHandler : IMoveHandler
|
||||||
{
|
{
|
||||||
private readonly IBoardManager boardManager;
|
private readonly IBoardManager boardManager;
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
@@ -23,43 +26,43 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
|||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(string json, string userName)
|
public async Task Handle(MoveRequest request, string userName)
|
||||||
{
|
{
|
||||||
var request = JsonConvert.DeserializeObject<Service.Messages.MoveRequest>(json);
|
//var request = JsonConvert.DeserializeObject<Service.Messages.MoveRequest>(json);
|
||||||
var moveModel = new Move(request.Move);
|
//var moveModel = new Move(request.Move);
|
||||||
var board = boardManager.Get(request.GameName);
|
//var board = boardManager.Get(request.GameName);
|
||||||
if (board == null)
|
//if (board == null)
|
||||||
{
|
//{
|
||||||
// TODO: Find a flow for this
|
// // TODO: Find a flow for this
|
||||||
var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
// var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
||||||
{
|
// {
|
||||||
Error = $"Game isn't loaded. Send a message with the {Service.Types.ClientAction.LoadGame} action first."
|
// Error = $"Game isn't loaded. Send a message with the {Service.Types.ClientAction.LoadGame} action first."
|
||||||
};
|
// };
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||||
|
|
||||||
}
|
//}
|
||||||
var boardMove = moveModel.ToBoardModel();
|
//var boardMove = moveModel.ToBoardModel();
|
||||||
var moveSuccess = board.Move(boardMove);
|
//var moveSuccess = board.Move(boardMove);
|
||||||
if (moveSuccess)
|
//if (moveSuccess)
|
||||||
{
|
//{
|
||||||
await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel()));
|
// await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel()));
|
||||||
var boardState = new BoardState(board);
|
// var boardState = new BoardState(board);
|
||||||
var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
// var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
||||||
{
|
// {
|
||||||
GameName = request.GameName,
|
// GameName = request.GameName,
|
||||||
PlayerName = userName,
|
// PlayerName = userName,
|
||||||
BoardState = boardState.ToServiceModel()
|
// BoardState = boardState.ToServiceModel()
|
||||||
};
|
// };
|
||||||
await communicationManager.BroadcastToGame(request.GameName, response);
|
// await communicationManager.BroadcastToGame(request.GameName, response);
|
||||||
}
|
//}
|
||||||
else
|
//else
|
||||||
{
|
//{
|
||||||
var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
// var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
||||||
{
|
// {
|
||||||
Error = "Invalid move."
|
// Error = "Invalid move."
|
||||||
};
|
// };
|
||||||
await communicationManager.BroadcastToPlayers(response, userName);
|
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
|||||||
using Gameboard.ShogiUI.Sockets.Managers.Utility;
|
using Gameboard.ShogiUI.Sockets.Managers.Utility;
|
||||||
using Gameboard.ShogiUI.Sockets.Models;
|
using Gameboard.ShogiUI.Sockets.Models;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -16,10 +17,9 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
{
|
{
|
||||||
public interface ISocketCommunicationManager
|
public interface ISocketCommunicationManager
|
||||||
{
|
{
|
||||||
Task CommunicateWith(WebSocket w, string s);
|
|
||||||
Task BroadcastToAll(IResponse response);
|
Task BroadcastToAll(IResponse response);
|
||||||
Task BroadcastToGame(string gameName, IResponse response);
|
//Task BroadcastToGame(string gameName, IResponse response);
|
||||||
Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
|
//Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
|
||||||
void SubscribeToGame(Session session, string playerName);
|
void SubscribeToGame(Session session, string playerName);
|
||||||
void SubscribeToBroadcast(WebSocket socket, string playerName);
|
void SubscribeToBroadcast(WebSocket socket, string playerName);
|
||||||
void UnsubscribeFromBroadcastAndGames(string playerName);
|
void UnsubscribeFromBroadcastAndGames(string playerName);
|
||||||
@@ -34,54 +34,14 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
/// <summary>Dictionary key is game name.</summary>
|
/// <summary>Dictionary key is game name.</summary>
|
||||||
private readonly ConcurrentDictionary<string, Session> sessions;
|
private readonly ConcurrentDictionary<string, Session> sessions;
|
||||||
private readonly ILogger<SocketCommunicationManager> logger;
|
private readonly ILogger<SocketCommunicationManager> logger;
|
||||||
private readonly ActionHandlerResolver handlerResolver;
|
|
||||||
|
|
||||||
public SocketCommunicationManager(
|
public SocketCommunicationManager(ILogger<SocketCommunicationManager> logger)
|
||||||
ILogger<SocketCommunicationManager> logger,
|
|
||||||
ActionHandlerResolver handlerResolver)
|
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.handlerResolver = handlerResolver;
|
|
||||||
connections = new ConcurrentDictionary<string, WebSocket>();
|
connections = new ConcurrentDictionary<string, WebSocket>();
|
||||||
sessions = new ConcurrentDictionary<string, Session>();
|
sessions = new ConcurrentDictionary<string, Session>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CommunicateWith(WebSocket socket, string userName)
|
|
||||||
{
|
|
||||||
SubscribeToBroadcast(socket, userName);
|
|
||||||
|
|
||||||
while (!socket.CloseStatus.HasValue)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var message = await socket.ReceiveTextAsync();
|
|
||||||
if (string.IsNullOrWhiteSpace(message)) continue;
|
|
||||||
logger.LogInformation("Request \n{0}\n", message);
|
|
||||||
var request = JsonConvert.DeserializeObject<Request>(message);
|
|
||||||
if (!Enum.IsDefined(typeof(ClientAction), request.Action))
|
|
||||||
{
|
|
||||||
await socket.SendTextAsync("Error: Action not recognized.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var handler = handlerResolver(request.Action);
|
|
||||||
await handler.Handle(message, userName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex.Message);
|
|
||||||
}
|
|
||||||
catch (WebSocketException ex)
|
|
||||||
{
|
|
||||||
logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketCommunicationManager)}.");
|
|
||||||
logger.LogInformation("Probably tried writing to a closed socket.");
|
|
||||||
logger.LogError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UnsubscribeFromBroadcastAndGames(userName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SubscribeToBroadcast(WebSocket socket, string playerName)
|
public void SubscribeToBroadcast(WebSocket socket, string playerName)
|
||||||
{
|
{
|
||||||
connections.TryAdd(playerName, socket);
|
connections.TryAdd(playerName, socket);
|
||||||
@@ -154,27 +114,27 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
return Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
|
//public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
|
||||||
{
|
//{
|
||||||
if (sessions.TryGetValue(gameName, out var session))
|
// if (sessions.TryGetValue(gameName, out var session))
|
||||||
{
|
// {
|
||||||
var serialized1 = JsonConvert.SerializeObject(forPlayer1);
|
// var serialized1 = JsonConvert.SerializeObject(forPlayer1);
|
||||||
var serialized2 = JsonConvert.SerializeObject(forPlayer2);
|
// var serialized2 = JsonConvert.SerializeObject(forPlayer2);
|
||||||
return Task.WhenAll(
|
// return Task.WhenAll(
|
||||||
session.SendToPlayer1(serialized1),
|
// session.SendToPlayer1(serialized1),
|
||||||
session.SendToPlayer2(serialized2));
|
// session.SendToPlayer2(serialized2));
|
||||||
}
|
// }
|
||||||
return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
}
|
//}
|
||||||
|
|
||||||
public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
|
//public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
|
||||||
{
|
//{
|
||||||
if (sessions.TryGetValue(gameName, out var session))
|
// if (sessions.TryGetValue(gameName, out var session))
|
||||||
{
|
// {
|
||||||
var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
|
// var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
|
||||||
return session.Broadcast(serialized);
|
// return session.Broadcast(serialized);
|
||||||
}
|
// }
|
||||||
return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Gameboard.ShogiUI.Sockets.Extensions;
|
||||||
|
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
||||||
|
using Gameboard.ShogiUI.Sockets.Managers.Utility;
|
||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers
|
namespace Gameboard.ShogiUI.Sockets.Managers
|
||||||
@@ -12,14 +20,36 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
|
|
||||||
public class SocketConnectionManager : ISocketConnectionManager
|
public class SocketConnectionManager : ISocketConnectionManager
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<SocketConnectionManager> logger;
|
||||||
private readonly ISocketCommunicationManager communicationManager;
|
private readonly ISocketCommunicationManager communicationManager;
|
||||||
private readonly ISocketTokenManager tokenManager;
|
private readonly ISocketTokenManager tokenManager;
|
||||||
|
private readonly ICreateGameHandler createGameHandler;
|
||||||
|
private readonly IJoinByCodeHandler joinByCodeHandler;
|
||||||
|
private readonly IJoinGameHandler joinGameHandler;
|
||||||
|
private readonly IListGamesHandler listGamesHandler;
|
||||||
|
private readonly ILoadGameHandler loadGameHandler;
|
||||||
|
private readonly IMoveHandler moveHandler;
|
||||||
|
|
||||||
public SocketConnectionManager(ISocketCommunicationManager communicationManager, ISocketTokenManager tokenManager) : base()
|
public SocketConnectionManager(
|
||||||
|
ILogger<SocketConnectionManager> logger,
|
||||||
|
ISocketCommunicationManager communicationManager,
|
||||||
|
ISocketTokenManager tokenManager,
|
||||||
|
ICreateGameHandler createGameHandler,
|
||||||
|
IJoinByCodeHandler joinByCodeHandler,
|
||||||
|
IJoinGameHandler joinGameHandler,
|
||||||
|
IListGamesHandler listGamesHandler,
|
||||||
|
ILoadGameHandler loadGameHandler,
|
||||||
|
IMoveHandler moveHandler) : base()
|
||||||
{
|
{
|
||||||
|
this.logger = logger;
|
||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
|
this.createGameHandler = createGameHandler;
|
||||||
|
this.joinByCodeHandler = joinByCodeHandler;
|
||||||
|
this.joinGameHandler = joinGameHandler;
|
||||||
|
this.listGamesHandler = listGamesHandler;
|
||||||
|
this.loadGameHandler = loadGameHandler;
|
||||||
|
this.moveHandler = moveHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleSocketRequest(HttpContext context)
|
public async Task HandleSocketRequest(HttpContext context)
|
||||||
@@ -33,7 +63,74 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
if (userName != null)
|
if (userName != null)
|
||||||
{
|
{
|
||||||
var socket = await context.WebSockets.AcceptWebSocketAsync();
|
var socket = await context.WebSockets.AcceptWebSocketAsync();
|
||||||
await communicationManager.CommunicateWith(socket, userName);
|
|
||||||
|
communicationManager.SubscribeToBroadcast(socket, userName);
|
||||||
|
while (!socket.CloseStatus.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var message = await socket.ReceiveTextAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(message)) continue;
|
||||||
|
logger.LogInformation("Request \n{0}\n", message);
|
||||||
|
var request = JsonConvert.DeserializeObject<Request>(message);
|
||||||
|
if (!Enum.IsDefined(typeof(ClientAction), request.Action))
|
||||||
|
{
|
||||||
|
await socket.SendTextAsync("Error: Action not recognized.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (request.Action)
|
||||||
|
{
|
||||||
|
case ClientAction.ListGames:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<ListGamesRequest>(message);
|
||||||
|
await listGamesHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientAction.CreateGame:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<CreateGameRequest>(message);
|
||||||
|
await createGameHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientAction.JoinGame:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<JoinGameRequest>(message);
|
||||||
|
await joinGameHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientAction.JoinByCode:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<JoinByCodeRequest>(message);
|
||||||
|
await joinByCodeHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientAction.LoadGame:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<LoadGameRequest>(message);
|
||||||
|
await loadGameHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ClientAction.Move:
|
||||||
|
{
|
||||||
|
var req = JsonConvert.DeserializeObject<MoveRequest>(message);
|
||||||
|
await moveHandler.Handle(req, userName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex.Message);
|
||||||
|
}
|
||||||
|
catch (WebSocketException ex)
|
||||||
|
{
|
||||||
|
logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketCommunicationManager)}.");
|
||||||
|
logger.LogInformation("Probably tried writing to a closed socket.");
|
||||||
|
logger.LogError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
communicationManager.UnsubscribeFromBroadcastAndGames(userName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -16,27 +17,24 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Key is userName
|
/// Key is userName
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<string, Guid> Tokens;
|
private readonly ConcurrentDictionary<string, Guid> Tokens;
|
||||||
|
|
||||||
public SocketTokenManager()
|
public SocketTokenManager()
|
||||||
{
|
{
|
||||||
Tokens = new Dictionary<string, Guid>();
|
Tokens = new ConcurrentDictionary<string, Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid GenerateToken(string userName)
|
public Guid GenerateToken(string userName)
|
||||||
{
|
{
|
||||||
var guid = Guid.NewGuid();
|
Tokens.Remove(userName, out _);
|
||||||
|
|
||||||
if (Tokens.ContainsKey(userName))
|
var guid = Guid.NewGuid();
|
||||||
{
|
Tokens.TryAdd(userName, guid);
|
||||||
Tokens.Remove(userName);
|
|
||||||
}
|
|
||||||
Tokens.Add(userName, guid);
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||||
Tokens.Remove(userName);
|
Tokens.Remove(userName, out _);
|
||||||
});
|
});
|
||||||
|
|
||||||
return guid;
|
return guid;
|
||||||
@@ -45,13 +43,12 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
/// <returns>User name associated to the guid or null.</returns>
|
/// <returns>User name associated to the guid or null.</returns>
|
||||||
public string GetUsername(Guid guid)
|
public string GetUsername(Guid guid)
|
||||||
{
|
{
|
||||||
if (Tokens.ContainsValue(guid))
|
var userName = Tokens.FirstOrDefault(kvp => kvp.Value == guid).Key;
|
||||||
|
if (userName != null)
|
||||||
{
|
{
|
||||||
var username = Tokens.First(kvp => kvp.Value == guid).Key;
|
Tokens.Remove(userName, out _);
|
||||||
Tokens.Remove(username);
|
|
||||||
return username;
|
|
||||||
}
|
}
|
||||||
return null;
|
return userName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ namespace Gameboard.ShogiUI.Sockets.Managers.Utility
|
|||||||
public class Request : IRequest
|
public class Request : IRequest
|
||||||
{
|
{
|
||||||
public ClientAction Action { get; set; }
|
public ClientAction Action { get; set; }
|
||||||
public string PlayerName { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Gameboard.ShogiUI.Rules;
|
using Gameboard.ShogiUI.Rules;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ServiceTypes = Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using ServiceTypes = Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
|
|
||||||
@@ -7,28 +8,53 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
{
|
{
|
||||||
public class BoardState
|
public class BoardState
|
||||||
{
|
{
|
||||||
public Piece[,] Board { get; set; }
|
// TODO: Create a custom 2D array implementation which removes the (x,y) or (y,x) ambiguity.
|
||||||
public IReadOnlyCollection<Piece> Player1Hand { get; set; }
|
public Piece?[,] Board { get; }
|
||||||
public IReadOnlyCollection<Piece> Player2Hand { get; set; }
|
public IReadOnlyCollection<Piece> Player1Hand { get; }
|
||||||
|
public IReadOnlyCollection<Piece> Player2Hand { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Move is null in the first BoardState of a Session, before any moves have been made.
|
||||||
|
/// </summary>
|
||||||
|
public Move? Move { get; }
|
||||||
|
|
||||||
|
public BoardState() : this(new ShogiBoard()) { }
|
||||||
|
|
||||||
|
public BoardState(Piece?[,] board, IList<Piece> player1Hand, ICollection<Piece> player2Hand, Move move)
|
||||||
|
{
|
||||||
|
Board = board;
|
||||||
|
Player1Hand = new ReadOnlyCollection<Piece>(player1Hand);
|
||||||
|
}
|
||||||
|
|
||||||
public BoardState(ShogiBoard shogi)
|
public BoardState(ShogiBoard shogi)
|
||||||
{
|
{
|
||||||
Board = new Piece[9, 9];
|
Board = new 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] = new Piece(shogi.Board[x, y]);
|
{
|
||||||
|
var piece = shogi.Board[x, y];
|
||||||
|
if (piece != null)
|
||||||
|
{
|
||||||
|
Board[x, y] = new Piece(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Player1Hand = shogi.Hands[WhichPlayer.Player1].Select(_ => new Piece(_)).ToList();
|
Player1Hand = shogi.Hands[WhichPlayer.Player1].Select(_ => new Piece(_)).ToList();
|
||||||
Player2Hand = shogi.Hands[WhichPlayer.Player2].Select(_ => new Piece(_)).ToList();
|
Player2Hand = shogi.Hands[WhichPlayer.Player2].Select(_ => new Piece(_)).ToList();
|
||||||
|
Move = new Move(shogi.MoveHistory[^1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceTypes.BoardState ToServiceModel()
|
public ServiceTypes.BoardState ToServiceModel()
|
||||||
{
|
{
|
||||||
var board = new ServiceTypes.Piece[9, 9];
|
var board = new ServiceTypes.Piece[9, 9];
|
||||||
Board = new 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] = Board[x, y].ToServiceModel();
|
{
|
||||||
|
var piece = Board[x, y];
|
||||||
|
if (piece != null)
|
||||||
|
{
|
||||||
|
board[x, y] = piece.ToServiceModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
return new ServiceTypes.BoardState
|
return new ServiceTypes.BoardState
|
||||||
{
|
{
|
||||||
Board = board,
|
Board = board,
|
||||||
|
|||||||
@@ -1,88 +1,41 @@
|
|||||||
using Gameboard.ShogiUI.Rules;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using Microsoft.FSharp.Core;
|
|
||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using BoardStateMove = Gameboard.ShogiUI.Rules.Move;
|
|
||||||
using ShogiApi = Gameboard.Shogi.Api.ServiceModels.Types;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
public class Move
|
public class Move
|
||||||
{
|
{
|
||||||
public string PieceFromCaptured { get; set; }
|
public Coords? From { get; set; }
|
||||||
public Coords From { get; set; }
|
|
||||||
public Coords To { get; set; }
|
|
||||||
public bool IsPromotion { get; set; }
|
public bool IsPromotion { get; set; }
|
||||||
|
public WhichPiece? PieceFromHand { get; set; }
|
||||||
|
public Coords To { get; set; }
|
||||||
|
|
||||||
public Move(ServiceModels.Socket.Types.Move move)
|
public Move(Coords from, Coords to, bool isPromotion)
|
||||||
{
|
{
|
||||||
From = Coords.FromBoardNotation(move.From);
|
From = from;
|
||||||
To = Coords.FromBoardNotation(move.To);
|
To = to;
|
||||||
PieceFromCaptured = move.PieceFromCaptured;
|
IsPromotion = isPromotion;
|
||||||
IsPromotion = move.IsPromotion;
|
|
||||||
}
|
}
|
||||||
public Move(ShogiApi.Move move)
|
|
||||||
|
public Move(WhichPiece pieceFromHand, Coords to)
|
||||||
{
|
{
|
||||||
string pieceFromCaptured = null;
|
PieceFromHand = pieceFromHand;
|
||||||
if (move.PieceFromCaptured != null)
|
To = to;
|
||||||
{
|
|
||||||
pieceFromCaptured = move.PieceFromCaptured.Value switch
|
|
||||||
{
|
|
||||||
ShogiApi.WhichPieceName.Bishop => "",
|
|
||||||
ShogiApi.WhichPieceName.GoldenGeneral => "G",
|
|
||||||
ShogiApi.WhichPieceName.King => "K",
|
|
||||||
ShogiApi.WhichPieceName.Knight => "k",
|
|
||||||
ShogiApi.WhichPieceName.Lance => "L",
|
|
||||||
ShogiApi.WhichPieceName.Pawn => "P",
|
|
||||||
ShogiApi.WhichPieceName.Rook => "R",
|
|
||||||
ShogiApi.WhichPieceName.SilverGeneral => "S",
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
From = new Coords(move.Origin.X, move.Origin.Y);
|
|
||||||
To = new Coords(move.Destination.X, move.Destination.Y);
|
|
||||||
IsPromotion = move.IsPromotion;
|
|
||||||
PieceFromCaptured = pieceFromCaptured;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceModels.Socket.Types.Move ToServiceModel() => new()
|
public ServiceModels.Socket.Types.Move ToServiceModel() => new()
|
||||||
{
|
{
|
||||||
From = From.ToBoardNotation(),
|
From = From?.ToBoardNotation(),
|
||||||
IsPromotion = IsPromotion,
|
IsPromotion = IsPromotion,
|
||||||
PieceFromCaptured = PieceFromCaptured,
|
To = To.ToBoardNotation(),
|
||||||
To = To.ToBoardNotation()
|
PieceFromCaptured = PieceFromHand
|
||||||
};
|
};
|
||||||
public ShogiApi.Move ToApiModel()
|
|
||||||
|
public Rules.Move ToRulesModel()
|
||||||
{
|
{
|
||||||
var pieceFromCaptured = PieceFromCaptured switch
|
return PieceFromHand != null
|
||||||
{
|
? new Rules.Move((Rules.WhichPiece)PieceFromHand, new Vector2(To.X, To.Y))
|
||||||
"B" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.Bishop),
|
: new Rules.Move(new Vector2(From!.X, From.Y), new Vector2(To.X, To.Y), IsPromotion);
|
||||||
"G" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.GoldenGeneral),
|
|
||||||
"K" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.King),
|
|
||||||
"k" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.Knight),
|
|
||||||
"L" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.Lance),
|
|
||||||
"P" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.Pawn),
|
|
||||||
"R" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.Rook),
|
|
||||||
"S" => new FSharpOption<ShogiApi.WhichPieceName>(ShogiApi.WhichPieceName.SilverGeneral),
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
var target = new ShogiApi.Move
|
|
||||||
{
|
|
||||||
Origin = new ShogiApi.BoardLocation { X = From.X, Y = From.Y },
|
|
||||||
Destination = new ShogiApi.BoardLocation { X = To.X, Y = To.Y },
|
|
||||||
IsPromotion = IsPromotion,
|
|
||||||
PieceFromCaptured = pieceFromCaptured
|
|
||||||
};
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
public BoardStateMove ToBoardModel()
|
|
||||||
{
|
|
||||||
return new BoardStateMove
|
|
||||||
{
|
|
||||||
From = new Vector2(From.X, From.Y),
|
|
||||||
IsPromotion = IsPromotion,
|
|
||||||
PieceFromCaptured = Enum.TryParse<WhichPiece>(PieceFromCaptured, out var whichPiece) ? whichPiece : null,
|
|
||||||
To = new Vector2(To.X, To.Y)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
using BoardStatePiece = Gameboard.ShogiUI.Rules.Pieces.Piece;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
public class Piece
|
public class Piece
|
||||||
{
|
{
|
||||||
public WhichPiece WhichPiece { get; set; }
|
public bool IsPromoted { get; }
|
||||||
|
public WhichPlayer Owner { get; }
|
||||||
|
public WhichPiece WhichPiece { get; }
|
||||||
|
|
||||||
public bool IsPromoted { get; set; }
|
public Piece(bool isPromoted, WhichPlayer owner, WhichPiece whichPiece)
|
||||||
|
{
|
||||||
public Piece(BoardStatePiece piece)
|
IsPromoted = isPromoted;
|
||||||
|
Owner = owner;
|
||||||
|
WhichPiece = whichPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece(Rules.Pieces.Piece piece)
|
||||||
{
|
{
|
||||||
WhichPiece = (WhichPiece)piece.WhichPiece;
|
|
||||||
IsPromoted = piece.IsPromoted;
|
IsPromoted = piece.IsPromoted;
|
||||||
|
Owner = (WhichPlayer)piece.Owner;
|
||||||
|
WhichPiece = (WhichPiece)piece.WhichPiece;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceModels.Socket.Types.Piece ToServiceModel()
|
public ServiceModels.Socket.Types.Piece ToServiceModel()
|
||||||
@@ -20,6 +27,7 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
return new ServiceModels.Socket.Types.Piece
|
return new ServiceModels.Socket.Types.Piece
|
||||||
{
|
{
|
||||||
IsPromoted = IsPromoted,
|
IsPromoted = IsPromoted,
|
||||||
|
Owner = Owner,
|
||||||
WhichPiece = WhichPiece
|
WhichPiece = WhichPiece
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.Extensions;
|
using Gameboard.ShogiUI.Sockets.Extensions;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
@@ -9,18 +10,20 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
{
|
{
|
||||||
public class Session
|
public class Session
|
||||||
{
|
{
|
||||||
|
[JsonIgnore] public ConcurrentDictionary<string, WebSocket> Subscriptions { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Player1 { get; }
|
public string Player1 { get; }
|
||||||
public string Player2 { get; }
|
public string? Player2 { get; }
|
||||||
|
public bool IsPrivate { get; }
|
||||||
|
|
||||||
public ConcurrentDictionary<string, WebSocket> Subscriptions { get; }
|
public Session(string name, bool isPrivate, string player1, string? player2 = null)
|
||||||
|
|
||||||
public Session(Shogi.Api.ServiceModels.Types.Session session)
|
|
||||||
{
|
{
|
||||||
Name = session.Name;
|
|
||||||
Player1 = session.Player1;
|
|
||||||
Player2 = session.Player2;
|
|
||||||
Subscriptions = new ConcurrentDictionary<string, WebSocket>();
|
Subscriptions = new ConcurrentDictionary<string, WebSocket>();
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
Player1 = player1;
|
||||||
|
Player2 = player2;
|
||||||
|
IsPrivate = isPrivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Subscribe(string playerName, WebSocket socket) => Subscriptions.TryAdd(playerName, socket);
|
public bool Subscribe(string playerName, WebSocket socket) => Subscriptions.TryAdd(playerName, socket);
|
||||||
@@ -47,7 +50,7 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
|
|
||||||
public Task SendToPlayer2(string message)
|
public Task SendToPlayer2(string message)
|
||||||
{
|
{
|
||||||
if (Subscriptions.TryGetValue(Player2, out var socket))
|
if (Player2 != null && Subscriptions.TryGetValue(Player2, out var socket))
|
||||||
{
|
{
|
||||||
return socket.SendTextAsync(message);
|
return socket.SendTextAsync(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class BoardState : CouchDocument
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Piece?[,] Board { get; set; }
|
||||||
|
public Piece[] Player1Hand { get; set; }
|
||||||
|
public Piece[] Player2Hand { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Move is null for first BoardState of a session - before anybody has made moves.
|
||||||
|
/// </summary>
|
||||||
|
public Move? Move { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor and setters are for deserialization.
|
||||||
|
/// </summary>
|
||||||
|
public BoardState() : base()
|
||||||
|
{
|
||||||
|
Name = string.Empty;
|
||||||
|
Board = new Piece[9, 9];
|
||||||
|
Player1Hand = Array.Empty<Piece>();
|
||||||
|
Player2Hand = Array.Empty<Piece>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoardState(string sessionName, Models.BoardState boardState) : base($"{sessionName}-{DateTime.Now:O}", nameof(BoardState))
|
||||||
|
{
|
||||||
|
Name = sessionName;
|
||||||
|
Board = new Piece[9, 9];
|
||||||
|
|
||||||
|
for (var x = 0; x < 9; x++)
|
||||||
|
for (var y = 0; y < 9; y++)
|
||||||
|
{
|
||||||
|
var piece = boardState.Board[x, y];
|
||||||
|
if (piece != null)
|
||||||
|
{
|
||||||
|
Board[x, y] = new Piece(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player1Hand = boardState.Player1Hand.Select(model => new Piece(model)).ToArray();
|
||||||
|
Player2Hand = boardState.Player2Hand.Select(model => new Piece(model)).ToArray();
|
||||||
|
if (boardState.Move != null)
|
||||||
|
{
|
||||||
|
Move = new Move(boardState.Move);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.BoardState ToDomainModel()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Board = new Piece[9, 9];
|
||||||
|
for (var x = 0; x < 9; x++)
|
||||||
|
for (var y = 0; y < 9; y++)
|
||||||
|
{
|
||||||
|
var piece = boardState.Board[x, y];
|
||||||
|
if (piece != null)
|
||||||
|
{
|
||||||
|
Board[x, y] = new Piece(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player1Hand = boardState.Player1Hand.Select(_ => new Piece(_)).ToList();
|
||||||
|
Player2Hand = boardState.Player2Hand.Select(_ => new Piece(_)).ToList();
|
||||||
|
if (boardState.Move != null)
|
||||||
|
{
|
||||||
|
Move = new Move(boardState.Move);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class CouchCreateResult
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public bool Ok { get; set; }
|
||||||
|
public string Rev { get; set; }
|
||||||
|
|
||||||
|
public CouchCreateResult()
|
||||||
|
{
|
||||||
|
Id = string.Empty;
|
||||||
|
Rev = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public abstract class CouchDocument
|
||||||
|
{
|
||||||
|
[JsonProperty("_id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public DateTimeOffset CreatedDate { get; set; }
|
||||||
|
|
||||||
|
public CouchDocument()
|
||||||
|
{
|
||||||
|
Id = string.Empty;
|
||||||
|
Type = string.Empty;
|
||||||
|
CreatedDate = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
|
public CouchDocument(string id, string type)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
internal class CouchFindResult<T>
|
||||||
|
{
|
||||||
|
public T[] docs;
|
||||||
|
|
||||||
|
public CouchFindResult()
|
||||||
|
{
|
||||||
|
docs = Array.Empty<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
Normal file
45
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Gameboard.ShogiUI.Sockets.Models;
|
||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class Move
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A board coordinate, like A3 or G6. When null, look for PieceFromHand to exist.
|
||||||
|
/// </summary>
|
||||||
|
public string? From { get; set; }
|
||||||
|
|
||||||
|
public bool IsPromotion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The piece placed from the player's hand.
|
||||||
|
/// </summary>
|
||||||
|
public WhichPiece? PieceFromHand { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A board coordinate, like A3 or G6.
|
||||||
|
/// </summary>
|
||||||
|
public string To { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor and setters are for deserialization.
|
||||||
|
/// </summary>
|
||||||
|
public Move()
|
||||||
|
{
|
||||||
|
To = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Move(Models.Move move)
|
||||||
|
{
|
||||||
|
From = move.From?.ToBoardNotation();
|
||||||
|
IsPromotion = move.IsPromotion;
|
||||||
|
To = move.To.ToBoardNotation();
|
||||||
|
PieceFromHand = move.PieceFromHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Move ToDomainModel() => PieceFromHand.HasValue
|
||||||
|
? new((ServiceModels.Socket.Types.WhichPiece)PieceFromHand, Coords.FromBoardNotation(To))
|
||||||
|
: new(Coords.FromBoardNotation(From!), Coords.FromBoardNotation(To), IsPromotion);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
Normal file
27
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class Piece
|
||||||
|
{
|
||||||
|
public bool IsPromoted { get; set; }
|
||||||
|
public WhichPlayer Owner { get; set; }
|
||||||
|
public WhichPiece WhichPiece { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor and setters are for deserialization.
|
||||||
|
/// </summary>
|
||||||
|
public Piece()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece(Models.Piece piece)
|
||||||
|
{
|
||||||
|
IsPromoted = piece.IsPromoted;
|
||||||
|
Owner = piece.Owner;
|
||||||
|
WhichPiece = piece.WhichPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Piece ToDomainModel() => new(IsPromoted, Owner, WhichPiece);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Couch Models
|
||||||
|
|
||||||
|
Couch models should accept domain models during construction and offer a ToDomainModel method which constructs a domain model.
|
||||||
|
In this way, domain models have the freedom to define their valid states.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class Session : CouchDocument
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Player1 { get; set; }
|
||||||
|
public string? Player2 { get; set; }
|
||||||
|
public bool IsPrivate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor and setters are for deserialization.
|
||||||
|
/// </summary>
|
||||||
|
public Session() : base()
|
||||||
|
{
|
||||||
|
Name = string.Empty;
|
||||||
|
Player1 = string.Empty;
|
||||||
|
Player2 = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Session(string id, Models.Session session) : base(id, nameof(Session))
|
||||||
|
{
|
||||||
|
Name = session.Name;
|
||||||
|
Player1 = session.Player1;
|
||||||
|
Player2 = session.Player2;
|
||||||
|
IsPrivate = session.IsPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Session ToDomainModel() => new(Name, IsPrivate, Player1, Player2);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/User.cs
Normal file
23
Gameboard.ShogiUI.Sockets/Repositories/CouchModels/User.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
|
{
|
||||||
|
public class User : CouchDocument
|
||||||
|
{
|
||||||
|
public static string GetDocumentId(string userName) => $"org.couchdb.user:{userName}";
|
||||||
|
|
||||||
|
public enum LoginPlatform
|
||||||
|
{
|
||||||
|
Microsoft,
|
||||||
|
Guest
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public LoginPlatform Platform { get; set; }
|
||||||
|
public User(string name, LoginPlatform platform) : base($"org.couchdb.user:{name}", nameof(User))
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Platform = platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Repositories.CouchModels;
|
||||||
using Gameboard.ShogiUI.Sockets.Models;
|
using Microsoft.Extensions.Logging;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories.Utility;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -13,141 +12,173 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
{
|
{
|
||||||
public interface IGameboardRepository
|
public interface IGameboardRepository
|
||||||
{
|
{
|
||||||
Task DeleteGame(string gameName);
|
Task<bool> CreateBoardState(string sessionName, Models.BoardState boardState, Models.Move? move);
|
||||||
Task<Session> GetGame(string gameName);
|
Task<bool> CreateGuestUser(string userName);
|
||||||
Task<GetSessionsResponse> GetGames();
|
Task<bool> CreateSession(Models.Session session);
|
||||||
Task<GetSessionsResponse> GetGames(string playerName);
|
Task<IList<Models.Session>> ReadSessions();
|
||||||
Task<List<Move>> GetMoves(string gameName);
|
Task<bool> IsGuestUser(string userName);
|
||||||
Task<string> PostSession(PostSession request);
|
|
||||||
Task<string> PostJoinPrivateSession(PostJoinPrivateSession request);
|
|
||||||
Task<bool> PutJoinPublicSession(PutJoinPublicSession request);
|
|
||||||
Task PostMove(string gameName, PostMove request);
|
|
||||||
Task<string> PostJoinCode(string gameName, string userName);
|
Task<string> PostJoinCode(string gameName, string userName);
|
||||||
Task<Player> GetPlayer(string userName);
|
Task<Models.Session?> ReadSession(string name);
|
||||||
Task<bool> PostPlayer(PostPlayer request);
|
Task<IList<Models.BoardState>> ReadBoardStates(string name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameboardRepository : IGameboardRepository
|
public class GameboardRepository : IGameboardRepository
|
||||||
{
|
{
|
||||||
private const string GetSessionsRoute = "Sessions";
|
private const string ApplicationJson = "application/json";
|
||||||
private const string PostSessionRoute = "Session";
|
private readonly HttpClient client;
|
||||||
private const string JoinSessionRoute = "Session/Join";
|
private readonly ILogger<GameboardRepository> logger;
|
||||||
private const string PlayerRoute = "Player";
|
|
||||||
private const string MediaType = "application/json";
|
public GameboardRepository(IHttpClientFactory clientFactory, ILogger<GameboardRepository> logger)
|
||||||
private readonly IAuthenticatedHttpClient client;
|
|
||||||
public GameboardRepository(IAuthenticatedHttpClient client)
|
|
||||||
{
|
{
|
||||||
this.client = client;
|
client = clientFactory.CreateClient("couchdb");
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GetSessionsResponse> GetGames()
|
public async Task<IList<Models.Session>> ReadSessions()
|
||||||
{
|
{
|
||||||
var response = await client.GetAsync(GetSessionsRoute);
|
var selector = $@"{{ ""{nameof(Session.Type)}"": ""{nameof(Session)}"" }}";
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var query = $@"{{ ""selector"": {selector} }}";
|
||||||
return JsonConvert.DeserializeObject<GetSessionsResponse>(json);
|
var content = new StringContent(query, Encoding.UTF8, ApplicationJson);
|
||||||
|
var response = await client.PostAsync("_find", content);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<CouchFindResult<Session>>(responseContent);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
logger.LogError("Unable to deserialize couchdb result during {0}.", nameof(this.ReadSessions));
|
||||||
|
return Array.Empty<Models.Session>();
|
||||||
|
}
|
||||||
|
return result.docs
|
||||||
|
.Select(_ => _.ToDomainModel())
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GetSessionsResponse> GetGames(string playerName)
|
public async Task<Models.Session?> ReadSession(string name)
|
||||||
{
|
{
|
||||||
var uri = $"Sessions/{playerName}";
|
var response = await client.GetAsync(name);
|
||||||
var response = await client.GetAsync(Uri.EscapeUriString(uri));
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var couchModel = JsonConvert.DeserializeObject<Session>(responseContent);
|
||||||
return JsonConvert.DeserializeObject<GetSessionsResponse>(json);
|
return couchModel.ToDomainModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Session> GetGame(string gameName)
|
public async Task<IList<Models.BoardState>> ReadBoardStates(string name)
|
||||||
{
|
{
|
||||||
var uri = $"Session/{gameName}";
|
var selector = $@"{{ ""{nameof(BoardState.Type)}"": ""{nameof(BoardState)}"", ""{nameof(BoardState.Name)}"": ""{name}"" }}";
|
||||||
var response = await client.GetAsync(Uri.EscapeUriString(uri));
|
var sort = $@"{{ ""{nameof(BoardState.CreatedDate)}"" : ""desc"" }}";
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var query = $@"{{ ""selector"": {selector}, ""sort"": {sort} }}";
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
var content = new StringContent(query, Encoding.UTF8, ApplicationJson);
|
||||||
|
var response = await client.PostAsync("_find", content);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<CouchFindResult<BoardState>>(responseContent);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
{
|
{
|
||||||
return null;
|
logger.LogError("Unable to deserialize couchdb result during {0}.", nameof(this.ReadSessions));
|
||||||
|
return Array.Empty<Models.BoardState>();
|
||||||
}
|
}
|
||||||
return new Session(JsonConvert.DeserializeObject<GetSessionResponse>(json).Session);
|
return result.docs
|
||||||
|
.Select(_ => new Models.BoardState(_))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteGame(string gameName)
|
//public async Task DeleteGame(string gameName)
|
||||||
|
//{
|
||||||
|
// //var uri = $"Session/{gameName}";
|
||||||
|
// //await client.DeleteAsync(Uri.EscapeUriString(uri));
|
||||||
|
//}
|
||||||
|
|
||||||
|
public async Task<bool> CreateSession(Models.Session session)
|
||||||
{
|
{
|
||||||
var uri = $"Session/{gameName}";
|
var couchModel = new Session(session.Name, session);
|
||||||
await client.DeleteAsync(Uri.EscapeUriString(uri));
|
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||||
|
var response = await client.PostAsync(string.Empty, content);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> PostSession(PostSession request)
|
public async Task<bool> CreateBoardState(string sessionName, Models.BoardState boardState, Models.Move? move)
|
||||||
{
|
{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
var couchModel = new BoardState(sessionName, boardState, move);
|
||||||
var response = await client.PostAsync(PostSessionRoute, content);
|
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var response = await client.PostAsync(string.Empty, content);
|
||||||
return JsonConvert.DeserializeObject<PostSessionResponse>(json).SessionName;
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PutJoinPublicSession(PutJoinPublicSession request)
|
//public async Task<bool> PutJoinPublicSession(PutJoinPublicSession request)
|
||||||
{
|
//{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
// var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
||||||
var response = await client.PutAsync(JoinSessionRoute, content);
|
// var response = await client.PutAsync(JoinSessionRoute, content);
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
// var json = await response.Content.ReadAsStringAsync();
|
||||||
return JsonConvert.DeserializeObject<PutJoinPublicSessionResponse>(json).JoinSucceeded;
|
// return JsonConvert.DeserializeObject<PutJoinPublicSessionResponse>(json).JoinSucceeded;
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<string> PostJoinPrivateSession(PostJoinPrivateSession request)
|
//public async Task<string> PostJoinPrivateSession(PostJoinPrivateSession request)
|
||||||
{
|
//{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
// var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
||||||
var response = await client.PostAsync(JoinSessionRoute, content);
|
// var response = await client.PostAsync(JoinSessionRoute, content);
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
// var json = await response.Content.ReadAsStringAsync();
|
||||||
var deserialized = JsonConvert.DeserializeObject<PostJoinPrivateSessionResponse>(json);
|
// var deserialized = JsonConvert.DeserializeObject<PostJoinPrivateSessionResponse>(json);
|
||||||
if (deserialized.JoinSucceeded)
|
// if (deserialized.JoinSucceeded)
|
||||||
{
|
// {
|
||||||
return deserialized.SessionName;
|
// return deserialized.SessionName;
|
||||||
}
|
// }
|
||||||
return null;
|
// return null;
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<List<Move>> GetMoves(string gameName)
|
//public async Task<List<Move>> GetMoves(string gameName)
|
||||||
{
|
//{
|
||||||
var uri = $"Session/{gameName}/Moves";
|
// var uri = $"Session/{gameName}/Moves";
|
||||||
var get = await client.GetAsync(Uri.EscapeUriString(uri));
|
// var get = await client.GetAsync(Uri.EscapeUriString(uri));
|
||||||
var json = await get.Content.ReadAsStringAsync();
|
// var json = await get.Content.ReadAsStringAsync();
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
// if (string.IsNullOrWhiteSpace(json))
|
||||||
{
|
// {
|
||||||
return new List<Move>();
|
// return new List<Move>();
|
||||||
}
|
// }
|
||||||
var response = JsonConvert.DeserializeObject<GetMovesResponse>(json);
|
// var response = JsonConvert.DeserializeObject<GetMovesResponse>(json);
|
||||||
return response.Moves.Select(m => new Move(m)).ToList();
|
// return response.Moves.Select(m => new Move(m)).ToList();
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task PostMove(string gameName, PostMove request)
|
//public async Task PostMove(string gameName, PostMove request)
|
||||||
{
|
//{
|
||||||
var uri = $"Session/{gameName}/Move";
|
// var uri = $"Session/{gameName}/Move";
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
// var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
||||||
await client.PostAsync(Uri.EscapeUriString(uri), content);
|
// await client.PostAsync(Uri.EscapeUriString(uri), content);
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<string> PostJoinCode(string gameName, string userName)
|
public async Task<string> PostJoinCode(string gameName, string userName)
|
||||||
{
|
{
|
||||||
var uri = $"JoinCode/{gameName}";
|
// var uri = $"JoinCode/{gameName}";
|
||||||
var serialized = JsonConvert.SerializeObject(new PostJoinCode { PlayerName = userName });
|
// var serialized = JsonConvert.SerializeObject(new PostJoinCode { PlayerName = userName });
|
||||||
var content = new StringContent(serialized, Encoding.UTF8, MediaType);
|
// var content = new StringContent(serialized, Encoding.UTF8, MediaType);
|
||||||
var json = await (await client.PostAsync(Uri.EscapeUriString(uri), content)).Content.ReadAsStringAsync();
|
// var json = await (await client.PostAsync(Uri.EscapeUriString(uri), content)).Content.ReadAsStringAsync();
|
||||||
return JsonConvert.DeserializeObject<PostJoinCodeResponse>(json).JoinCode;
|
// return JsonConvert.DeserializeObject<PostJoinCodeResponse>(json).JoinCode;
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Player> GetPlayer(string playerName)
|
//public async Task<Player> GetPlayer(string playerName)
|
||||||
|
//{
|
||||||
|
// var uri = $"Player/{playerName}";
|
||||||
|
// var get = await client.GetAsync(Uri.EscapeUriString(uri));
|
||||||
|
// var content = await get.Content.ReadAsStringAsync();
|
||||||
|
// if (!string.IsNullOrWhiteSpace(content))
|
||||||
|
// {
|
||||||
|
// var response = JsonConvert.DeserializeObject<GetPlayerResponse>(content);
|
||||||
|
// return new Player(response.Player.Name);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
//}
|
||||||
|
|
||||||
|
public async Task<bool> CreateGuestUser(string userName)
|
||||||
{
|
{
|
||||||
var uri = $"Player/{playerName}";
|
var couchModel = new User(userName, User.LoginPlatform.Guest);
|
||||||
var get = await client.GetAsync(Uri.EscapeUriString(uri));
|
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||||
var content = await get.Content.ReadAsStringAsync();
|
var response = await client.PostAsync(string.Empty, content);
|
||||||
if (!string.IsNullOrWhiteSpace(content))
|
return response.IsSuccessStatusCode;
|
||||||
{
|
|
||||||
var response = JsonConvert.DeserializeObject<GetPlayerResponse>(content);
|
|
||||||
return new Player(response.Player.Name);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PostPlayer(PostPlayer request)
|
public async Task<bool> IsGuestUser(string userName)
|
||||||
{
|
{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
|
var req = new HttpRequestMessage(HttpMethod.Head, new Uri($"{client.BaseAddress}/{User.GetDocumentId(userName)}"));
|
||||||
var response = await client.PostAsync(PlayerRoute, content);
|
var response = await client.SendAsync(req);
|
||||||
return response.IsSuccessStatusCode;
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Gameboard.Shogi.Api.ServiceModels.Messages;
|
using Gameboard.ShogiUI.Sockets.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
|
|||||||
Task<string> CreateGuestUser();
|
Task<string> CreateGuestUser();
|
||||||
Task<bool> IsPlayer1(string sessionName, string playerName);
|
Task<bool> IsPlayer1(string sessionName, string playerName);
|
||||||
bool IsGuest(string playerName);
|
bool IsGuest(string playerName);
|
||||||
Task<bool> PlayerExists(string playerName);
|
Task<bool> CreateSession(Session session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameboardRepositoryManager : IGameboardRepositoryManager
|
public class GameboardRepositoryManager : IGameboardRepositoryManager
|
||||||
@@ -30,11 +30,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
|
|||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
var clientId = $"Guest-{Guid.NewGuid()}";
|
var clientId = $"Guest-{Guid.NewGuid()}";
|
||||||
var request = new PostPlayer
|
var isCreated = await repository.CreateGuestUser(clientId);
|
||||||
{
|
|
||||||
PlayerName = clientId
|
|
||||||
};
|
|
||||||
var isCreated = await repository.PostPlayer(request);
|
|
||||||
if (isCreated)
|
if (isCreated)
|
||||||
{
|
{
|
||||||
return clientId;
|
return clientId;
|
||||||
@@ -45,22 +41,31 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
|
|||||||
|
|
||||||
public async Task<bool> IsPlayer1(string sessionName, string playerName)
|
public async Task<bool> IsPlayer1(string sessionName, string playerName)
|
||||||
{
|
{
|
||||||
var session = await repository.GetGame(sessionName);
|
//var session = await repository.GetGame(sessionName);
|
||||||
return session?.Player1 == playerName;
|
//return session?.Player1 == playerName;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateJoinCode(string sessionName, string playerName)
|
public async Task<string> CreateJoinCode(string sessionName, string playerName)
|
||||||
{
|
{
|
||||||
var session = await repository.GetGame(sessionName);
|
//var session = await repository.GetGame(sessionName);
|
||||||
if (playerName == session?.Player1)
|
//if (playerName == session?.Player1)
|
||||||
{
|
//{
|
||||||
return await repository.PostJoinCode(sessionName, playerName);
|
// return await repository.PostJoinCode(sessionName, playerName);
|
||||||
}
|
//}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix);
|
public async Task<bool> CreateSession(Session session)
|
||||||
|
{
|
||||||
|
var success = await repository.CreateSession(session);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return await repository.CreateBoardState(session.Name, new BoardState(), null);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> PlayerExists(string playerName) => await repository.GetPlayer(playerName) != null;
|
public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
using IdentityModel.Client;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Repositories.Utility
|
|
||||||
{
|
|
||||||
public interface IAuthenticatedHttpClient
|
|
||||||
{
|
|
||||||
Task<HttpResponseMessage> DeleteAsync(string requestUri);
|
|
||||||
Task<HttpResponseMessage> GetAsync(string requestUri);
|
|
||||||
Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
|
|
||||||
Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AuthenticatedHttpClient : HttpClient, IAuthenticatedHttpClient
|
|
||||||
{
|
|
||||||
private readonly ILogger<AuthenticatedHttpClient> logger;
|
|
||||||
private readonly string identityServerUrl;
|
|
||||||
private TokenResponse tokenResponse;
|
|
||||||
private readonly string clientId;
|
|
||||||
private readonly string clientSecret;
|
|
||||||
|
|
||||||
public AuthenticatedHttpClient(ILogger<AuthenticatedHttpClient> logger, IConfiguration configuration) : base()
|
|
||||||
{
|
|
||||||
this.logger = logger;
|
|
||||||
identityServerUrl = configuration["AppSettings:IdentityServer"];
|
|
||||||
clientId = configuration["AppSettings:ClientId"];
|
|
||||||
clientSecret = configuration["AppSettings:ClientSecret"];
|
|
||||||
BaseAddress = new Uri(configuration["AppSettings:GameboardShogiApi"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshBearerToken()
|
|
||||||
{
|
|
||||||
var disco = await this.GetDiscoveryDocumentAsync(identityServerUrl);
|
|
||||||
if (disco.IsError)
|
|
||||||
{
|
|
||||||
logger.LogError("{DiscoveryErrorType}", disco.ErrorType);
|
|
||||||
throw new Exception(disco.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new ClientCredentialsTokenRequest
|
|
||||||
{
|
|
||||||
Address = disco.TokenEndpoint,
|
|
||||||
ClientId = clientId,
|
|
||||||
ClientSecret = clientSecret
|
|
||||||
};
|
|
||||||
var response = await this.RequestClientCredentialsTokenAsync(request);
|
|
||||||
if (response.IsError)
|
|
||||||
{
|
|
||||||
throw new Exception(response.Error);
|
|
||||||
}
|
|
||||||
tokenResponse = response;
|
|
||||||
logger.LogInformation("Refreshing Bearer Token to {BaseAddress}", BaseAddress);
|
|
||||||
this.SetBearerToken(tokenResponse.AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
|
|
||||||
{
|
|
||||||
var response = await base.GetAsync(requestUri);
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
await RefreshBearerToken();
|
|
||||||
response = await base.GetAsync(requestUri);
|
|
||||||
}
|
|
||||||
logger.LogInformation(
|
|
||||||
"Repository GET to {BaseUrl}{RequestUrl} \nResponse: {Response}\n",
|
|
||||||
BaseAddress,
|
|
||||||
requestUri,
|
|
||||||
await response.Content.ReadAsStringAsync());
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
public new async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
|
|
||||||
{
|
|
||||||
var response = await base.PostAsync(requestUri, content);
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
await RefreshBearerToken();
|
|
||||||
response = await base.PostAsync(requestUri, content);
|
|
||||||
}
|
|
||||||
logger.LogInformation(
|
|
||||||
"Repository POST to {BaseUrl}{RequestUrl} \n\tRespCode: {RespCode} \n\tRequest: {Request}\n\tResponse: {Response}\n",
|
|
||||||
BaseAddress,
|
|
||||||
requestUri,
|
|
||||||
response.StatusCode,
|
|
||||||
await content.ReadAsStringAsync(),
|
|
||||||
await response.Content.ReadAsStringAsync());
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
public new async Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content)
|
|
||||||
{
|
|
||||||
var response = await base.PutAsync(requestUri, content);
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
await RefreshBearerToken();
|
|
||||||
response = await base.PutAsync(requestUri, content);
|
|
||||||
}
|
|
||||||
logger.LogInformation(
|
|
||||||
"Repository PUT to {BaseUrl}{RequestUrl} \n\tRespCode: {RespCode} \n\tRequest: {Request}\n\tResponse: {Response}\n",
|
|
||||||
BaseAddress,
|
|
||||||
requestUri,
|
|
||||||
response.StatusCode,
|
|
||||||
await content.ReadAsStringAsync(),
|
|
||||||
await response.Content.ReadAsStringAsync());
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
public new async Task<HttpResponseMessage> DeleteAsync(string requestUri)
|
|
||||||
{
|
|
||||||
var response = await base.DeleteAsync(requestUri);
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
await RefreshBearerToken();
|
|
||||||
response = await base.DeleteAsync(requestUri);
|
|
||||||
}
|
|
||||||
logger.LogInformation("Repository DELETE to {BaseUrl}{RequestUrl}", BaseAddress, requestUri);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,6 @@ using Gameboard.ShogiUI.Sockets.Managers;
|
|||||||
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
|
||||||
using Gameboard.ShogiUI.Sockets.Repositories.Utility;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@@ -15,8 +13,8 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets
|
namespace Gameboard.ShogiUI.Sockets
|
||||||
{
|
{
|
||||||
@@ -33,36 +31,33 @@ namespace Gameboard.ShogiUI.Sockets
|
|||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
// Socket ActionHandlers
|
// Socket ActionHandlers
|
||||||
services.AddSingleton<CreateGameHandler>();
|
services.AddSingleton<ICreateGameHandler, CreateGameHandler>();
|
||||||
services.AddSingleton<JoinByCodeHandler>();
|
services.AddSingleton<IJoinByCodeHandler, JoinByCodeHandler>();
|
||||||
services.AddSingleton<JoinGameHandler>();
|
services.AddSingleton<IJoinGameHandler, JoinGameHandler>();
|
||||||
services.AddSingleton<ListGamesHandler>();
|
services.AddSingleton<IListGamesHandler, ListGamesHandler>();
|
||||||
services.AddSingleton<LoadGameHandler>();
|
services.AddSingleton<ILoadGameHandler, LoadGameHandler>();
|
||||||
services.AddSingleton<MoveHandler>();
|
services.AddSingleton<IMoveHandler, MoveHandler>();
|
||||||
|
|
||||||
// Managers
|
// Managers
|
||||||
services.AddSingleton<ISocketCommunicationManager, SocketCommunicationManager>();
|
services.AddSingleton<ISocketCommunicationManager, SocketCommunicationManager>();
|
||||||
services.AddSingleton<ISocketTokenManager, SocketTokenManager>();
|
services.AddSingleton<ISocketTokenManager, SocketTokenManager>();
|
||||||
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
|
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
|
||||||
services.AddScoped<IGameboardRepositoryManager, GameboardRepositoryManager>();
|
services.AddSingleton<IGameboardRepositoryManager, GameboardRepositoryManager>();
|
||||||
services.AddSingleton<IBoardManager, BoardManager>();
|
services.AddSingleton<IBoardManager, BoardManager>();
|
||||||
services.AddSingleton<ActionHandlerResolver>(sp => action =>
|
|
||||||
{
|
|
||||||
return action switch
|
|
||||||
{
|
|
||||||
ClientAction.ListGames => sp.GetService<ListGamesHandler>(),
|
|
||||||
ClientAction.CreateGame => sp.GetService<CreateGameHandler>(),
|
|
||||||
ClientAction.JoinGame => sp.GetService<JoinGameHandler>(),
|
|
||||||
ClientAction.JoinByCode => sp.GetService<JoinByCodeHandler>(),
|
|
||||||
ClientAction.LoadGame => sp.GetService<LoadGameHandler>(),
|
|
||||||
ClientAction.Move => sp.GetService<MoveHandler>(),
|
|
||||||
_ => throw new KeyNotFoundException($"Unable to resolve {nameof(IActionHandler)} for {nameof(ClientAction)} {action}"),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
|
services.AddHttpClient("couchdb", c =>
|
||||||
|
{
|
||||||
|
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:admin"));
|
||||||
|
c.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
c.DefaultRequestHeaders.Add("Authorization", $"Basic {base64}");
|
||||||
|
|
||||||
|
var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/";
|
||||||
|
c.BaseAddress = new Uri(baseUrl);
|
||||||
|
});
|
||||||
services.AddTransient<IGameboardRepository, GameboardRepository>();
|
services.AddTransient<IGameboardRepository, GameboardRepository>();
|
||||||
services.AddSingleton<IAuthenticatedHttpClient, AuthenticatedHttpClient>();
|
//services.AddSingleton<IAuthenticatedHttpClient, AuthenticatedHttpClient>();
|
||||||
|
//services.AddSingleton<ICouchClient>(provider => new CouchClient(databaseName, couchUrl));
|
||||||
|
|
||||||
services.AddControllers();
|
services.AddControllers();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"AppSettings": {
|
"AppSettings": {
|
||||||
"IdentityServer": "https://identity.lucaserver.space/",
|
"CouchDB": {
|
||||||
"GameboardShogiApi": "https://dev.lucaserver.space/Gameboard.Shogi.Api/",
|
"Database": "shogi-dev",
|
||||||
"ClientId": "DevClientId",
|
"Url": "http://192.168.1.15:5984"
|
||||||
"ClientSecret": "DevSecret",
|
}
|
||||||
"Scope": "DevEnvironment"
|
|
||||||
},
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
var name = self.WhichPiece switch
|
var name = self.WhichPiece switch
|
||||||
{
|
{
|
||||||
WhichPiece.King => " K ",
|
WhichPiece.King => " K ",
|
||||||
WhichPiece.GoldenGeneral => " G ",
|
WhichPiece.GoldGeneral => " G ",
|
||||||
WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ",
|
WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ",
|
||||||
WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ",
|
WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ",
|
||||||
WhichPiece.Rook => self.IsPromoted ? "^R " : " R ",
|
WhichPiece.Rook => self.IsPromoted ? "^R " : " R ",
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
board[0, 0].WhichPiece.Should().Be(WhichPiece.Lance);
|
board[0, 0].WhichPiece.Should().Be(WhichPiece.Lance);
|
||||||
board[1, 0].WhichPiece.Should().Be(WhichPiece.Knight);
|
board[1, 0].WhichPiece.Should().Be(WhichPiece.Knight);
|
||||||
board[2, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
board[2, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
||||||
board[3, 0].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
|
board[3, 0].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
|
||||||
board[4, 0].WhichPiece.Should().Be(WhichPiece.King);
|
board[4, 0].WhichPiece.Should().Be(WhichPiece.King);
|
||||||
board[5, 0].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
|
board[5, 0].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
|
||||||
board[6, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
board[6, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
||||||
board[7, 0].WhichPiece.Should().Be(WhichPiece.Knight);
|
board[7, 0].WhichPiece.Should().Be(WhichPiece.Knight);
|
||||||
board[8, 0].WhichPiece.Should().Be(WhichPiece.Lance);
|
board[8, 0].WhichPiece.Should().Be(WhichPiece.Lance);
|
||||||
@@ -51,9 +51,9 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
board[0, 8].WhichPiece.Should().Be(WhichPiece.Lance);
|
board[0, 8].WhichPiece.Should().Be(WhichPiece.Lance);
|
||||||
board[1, 8].WhichPiece.Should().Be(WhichPiece.Knight);
|
board[1, 8].WhichPiece.Should().Be(WhichPiece.Knight);
|
||||||
board[2, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
board[2, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
||||||
board[3, 8].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
|
board[3, 8].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
|
||||||
board[4, 8].WhichPiece.Should().Be(WhichPiece.King);
|
board[4, 8].WhichPiece.Should().Be(WhichPiece.King);
|
||||||
board[5, 8].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
|
board[5, 8].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
|
||||||
board[6, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
board[6, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
|
||||||
board[7, 8].WhichPiece.Should().Be(WhichPiece.Knight);
|
board[7, 8].WhichPiece.Should().Be(WhichPiece.Knight);
|
||||||
board[8, 8].WhichPiece.Should().Be(WhichPiece.Lance);
|
board[8, 8].WhichPiece.Should().Be(WhichPiece.Lance);
|
||||||
@@ -256,23 +256,23 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
// 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, 0].Should().BeNull();
|
shogi.Board[7, 0].Should().BeNull();
|
||||||
var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 0) });
|
var dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Knight, To = new Vector2(7, 0) });
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
shogi.Board[7, 0].Should().BeNull();
|
shogi.Board[7, 0].Should().BeNull();
|
||||||
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Knight, To = new Vector2(7, 1) });
|
dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Knight, To = new Vector2(7, 1) });
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
shogi.Board[7, 1].Should().BeNull();
|
shogi.Board[7, 1].Should().BeNull();
|
||||||
|
|
||||||
/// try illegally placing Pawn from the hand
|
/// try illegally placing Pawn from the hand
|
||||||
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Pawn, To = new Vector2(7, 0) });
|
dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Pawn, To = new Vector2(7, 0) });
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
|
||||||
shogi.Board[7, 0].Should().BeNull();
|
shogi.Board[7, 0].Should().BeNull();
|
||||||
|
|
||||||
/// try illegally placing Lance from the hand
|
/// try illegally placing Lance from the hand
|
||||||
dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(7, 0) });
|
dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Lance, To = new Vector2(7, 0) });
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
shogi.Board[7, 0].Should().BeNull();
|
shogi.Board[7, 0].Should().BeNull();
|
||||||
@@ -312,7 +312,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
|
||||||
|
|
||||||
// Act - P1 tries to place a Lance while in check.
|
// Act - P1 tries to place a Lance while in check.
|
||||||
var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(4, 4) });
|
var dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Lance, To = new Vector2(4, 4) });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
@@ -347,7 +347,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
|
|||||||
shogi.Board[4, 0].Should().NotBeNull();
|
shogi.Board[4, 0].Should().NotBeNull();
|
||||||
|
|
||||||
// Act - P1 tries to place Bishop from hand to an already-occupied position
|
// 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) });
|
var dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Bishop, To = new Vector2(4, 0) });
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
dropSuccess.Should().BeFalse();
|
dropSuccess.Should().BeFalse();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace PathFinding
|
|||||||
{
|
{
|
||||||
public interface IPlanarCollection<T> : IEnumerable<T> where T : IPlanarElement
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
<AnalysisLevel>5</AnalysisLevel>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user