diff --git a/Benchmarking/Benchmarking.csproj b/Benchmarking/Benchmarking.csproj
index ec8dd8e..d84d541 100644
--- a/Benchmarking/Benchmarking.csproj
+++ b/Benchmarking/Benchmarking.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Benchmarking/Benchmarks.cs b/Benchmarking/Benchmarks.cs
index b5785ed..31f6599 100644
--- a/Benchmarking/Benchmarks.cs
+++ b/Benchmarking/Benchmarks.cs
@@ -16,33 +16,33 @@ namespace Benchmarking
public Benchmarks()
{
- moves = new[]
- {
- // P1 Rook
- new Move { From = new Vector2(7, 1), To = new Vector2(4, 1) },
- // P2 Gold
- new Move { From = new Vector2(3, 8), To = new Vector2(2, 7) },
- // P1 Pawn
- new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) },
- // P2 other Gold
- new Move { From = new Vector2(5, 8), To = new Vector2(6, 7) },
- // P1 same Pawn
- new Move { From = new Vector2(4, 3), To = new Vector2(4, 4) },
- // P2 Pawn
- new Move { From = new Vector2(4, 6), To = new Vector2(4, 5) },
- // P1 Pawn takes P2 Pawn
- new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) },
- // P2 King
- new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) },
- // P1 Pawn promotes
- new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true },
- // P2 King retreat
- new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) },
- };
- var rand = new Random();
+ //moves = new[]
+ //{
+ // // P1 Rook
+ // new Move { From = new Vector2(7, 1), To = new Vector2(4, 1) },
+ // // P2 Gold
+ // new Move { From = new Vector2(3, 8), To = new Vector2(2, 7) },
+ // // P1 Pawn
+ // new Move { From = new Vector2(4, 2), To = new Vector2(4, 3) },
+ // // P2 other Gold
+ // new Move { From = new Vector2(5, 8), To = new Vector2(6, 7) },
+ // // P1 same Pawn
+ // new Move { From = new Vector2(4, 3), To = new Vector2(4, 4) },
+ // // P2 Pawn
+ // new Move { From = new Vector2(4, 6), To = new Vector2(4, 5) },
+ // // P1 Pawn takes P2 Pawn
+ // new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) },
+ // // P2 King
+ // new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) },
+ // // P1 Pawn promotes
+ // new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true },
+ // // P2 King retreat
+ // new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) },
+ //};
+ //var rand = new Random();
- directions = new Vector2[10];
- for (var n = 0; n < 10; n++) directions[n] = new Vector2(rand.Next(-2, 2), rand.Next(-2, 2));
+ //directions = new Vector2[10];
+ //for (var n = 0; n < 10; n++) directions[n] = new Vector2(rand.Next(-2, 2), rand.Next(-2, 2));
}
//[Benchmark]
diff --git a/Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj b/CouchDB/CouchDB.csproj
similarity index 67%
rename from Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj
rename to CouchDB/CouchDB.csproj
index f208d30..83f1b25 100644
--- a/Gameboard.ShogiUI.Domain/Gameboard.ShogiUI.Domain.csproj
+++ b/CouchDB/CouchDB.csproj
@@ -4,4 +4,8 @@
net5.0
+
+
+
+
diff --git a/CouchDB/CouchDocument.cs b/CouchDB/CouchDocument.cs
new file mode 100644
index 0000000..8f29f6c
--- /dev/null
+++ b/CouchDB/CouchDocument.cs
@@ -0,0 +1,16 @@
+namespace CouchDB
+{
+ public class CouchDocument
+ {
+ 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);
+ }
+ }
+}
diff --git a/CouchDB/Selectors/CouchQuery.cs b/CouchDB/Selectors/CouchQuery.cs
new file mode 100644
index 0000000..7bd7b29
--- /dev/null
+++ b/CouchDB/Selectors/CouchQuery.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace CouchDB.Selectors
+{
+ public class CouchQuery
+ {
+ public static CouchQuery Select => new();
+
+ private readonly List equals;
+ protected CouchQuery()
+ {
+ equals = new List();
+ }
+
+ 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}";
+ }
+ }
+}
diff --git a/CouchDB/Selectors/Equals.cs b/CouchDB/Selectors/Equals.cs
new file mode 100644
index 0000000..418f547
--- /dev/null
+++ b/CouchDB/Selectors/Equals.cs
@@ -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}}} }}";
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.BoardState/Move.cs b/Gameboard.ShogiUI.BoardState/Move.cs
deleted file mode 100644
index 693c954..0000000
--- a/Gameboard.ShogiUI.BoardState/Move.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/Gameboard.ShogiUI.Domain/Entities/Board.cs b/Gameboard.ShogiUI.Domain/Entities/Board.cs
deleted file mode 100644
index 9533d8d..0000000
--- a/Gameboard.ShogiUI.Domain/Entities/Board.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Gameboard.ShogiUI.Domain
-{
- public class Board
- {
- }
-}
diff --git a/Gameboard.ShogiUI.Domain/Entities/Match.cs b/Gameboard.ShogiUI.Domain/Entities/Match.cs
deleted file mode 100644
index 2d54945..0000000
--- a/Gameboard.ShogiUI.Domain/Entities/Match.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace Gameboard.ShogiUI.Domain
-{
- public class Match
- {
- public string Name { get; }
- public string Player1 { get; }
- public string Player2 { get; }
-
- ///
- /// Initialize pre-existing Match.
- ///
- public Match(MatchMeta meta, Board board)
- {
- Name = meta.Name;
- Player1 = meta.Player1;
- Player2 = meta.Player2;
- }
-
- ///
- /// Create a new Match.
- ///
- public Match(string name, string player1)
- {
- Name = name;
- Player1 = player1;
- }
-
-
- }
-}
diff --git a/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs b/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs
deleted file mode 100644
index 6939def..0000000
--- a/Gameboard.ShogiUI.Domain/ValueObjects/Class1.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.Rules.csproj b/Gameboard.ShogiUI.Rules/Gameboard.ShogiUI.Rules.csproj
similarity index 90%
rename from Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.Rules.csproj
rename to Gameboard.ShogiUI.Rules/Gameboard.ShogiUI.Rules.csproj
index 1a2ca2c..5befd78 100644
--- a/Gameboard.ShogiUI.BoardState/Gameboard.ShogiUI.Rules.csproj
+++ b/Gameboard.ShogiUI.Rules/Gameboard.ShogiUI.Rules.csproj
@@ -4,6 +4,7 @@
net5.0
true
5
+ enable
diff --git a/Gameboard.ShogiUI.Rules/Move.cs b/Gameboard.ShogiUI.Rules/Move.cs
new file mode 100644
index 0000000..81286fe
--- /dev/null
+++ b/Gameboard.ShogiUI.Rules/Move.cs
@@ -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;
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs b/Gameboard.ShogiUI.Rules/Pieces/Bishop.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/Bishop.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Bishop.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs b/Gameboard.ShogiUI.Rules/Pieces/GoldGeneral.cs
similarity index 97%
rename from Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs
rename to Gameboard.ShogiUI.Rules/Pieces/GoldGeneral.cs
index 055b779..fa984ab 100644
--- a/Gameboard.ShogiUI.BoardState/Pieces/GoldGeneral.cs
+++ b/Gameboard.ShogiUI.Rules/Pieces/GoldGeneral.cs
@@ -14,7 +14,7 @@ namespace Gameboard.ShogiUI.Rules.Pieces
new PathFinding.Move(Direction.Right),
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);
promotedMoveSet = new MoveSet(this, Moves);
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/King.cs b/Gameboard.ShogiUI.Rules/Pieces/King.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/King.cs
rename to Gameboard.ShogiUI.Rules/Pieces/King.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Knight.cs b/Gameboard.ShogiUI.Rules/Pieces/Knight.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/Knight.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Knight.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Lance.cs b/Gameboard.ShogiUI.Rules/Pieces/Lance.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/Lance.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Lance.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs b/Gameboard.ShogiUI.Rules/Pieces/Pawn.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/Pawn.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Pawn.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs b/Gameboard.ShogiUI.Rules/Pieces/Piece.cs
similarity index 95%
rename from Gameboard.ShogiUI.BoardState/Pieces/Piece.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Piece.cs
index 1f019b2..b64584b 100644
--- a/Gameboard.ShogiUI.BoardState/Pieces/Piece.cs
+++ b/Gameboard.ShogiUI.Rules/Pieces/Piece.cs
@@ -25,7 +25,7 @@ namespace Gameboard.ShogiUI.Rules.Pieces
public bool CanPromote => !IsPromoted
&& WhichPiece != WhichPiece.King
- && WhichPiece != WhichPiece.GoldenGeneral;
+ && WhichPiece != WhichPiece.GoldGeneral;
public void ToggleOwnership()
{
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/Rook.cs b/Gameboard.ShogiUI.Rules/Pieces/Rook.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/Rook.cs
rename to Gameboard.ShogiUI.Rules/Pieces/Rook.cs
diff --git a/Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs b/Gameboard.ShogiUI.Rules/Pieces/SilverGeneral.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/Pieces/SilverGeneral.cs
rename to Gameboard.ShogiUI.Rules/Pieces/SilverGeneral.cs
diff --git a/Gameboard.ShogiUI.BoardState/PlanarCollection.cs b/Gameboard.ShogiUI.Rules/PlanarCollection.cs
similarity index 92%
rename from Gameboard.ShogiUI.BoardState/PlanarCollection.cs
rename to Gameboard.ShogiUI.Rules/PlanarCollection.cs
index 4baf867..d606134 100644
--- a/Gameboard.ShogiUI.BoardState/PlanarCollection.cs
+++ b/Gameboard.ShogiUI.Rules/PlanarCollection.cs
@@ -8,7 +8,7 @@ namespace Gameboard.ShogiUI.Rules
public class PlanarCollection : IPlanarCollection, IEnumerable where T : IPlanarElement
{
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 height;
@@ -19,12 +19,12 @@ namespace Gameboard.ShogiUI.Rules
array = new T[width * height];
}
- public T this[int x, int y]
+ public T? this[int x, int y]
{
get => array[y * width + x];
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];
set => array[(int)y * width + (int)x] = value;
diff --git a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs b/Gameboard.ShogiUI.Rules/ShogiBoard.cs
similarity index 93%
rename from Gameboard.ShogiUI.BoardState/ShogiBoard.cs
rename to Gameboard.ShogiUI.Rules/ShogiBoard.cs
index 0449668..e5f104e 100644
--- a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs
+++ b/Gameboard.ShogiUI.Rules/ShogiBoard.cs
@@ -14,9 +14,8 @@ namespace Gameboard.ShogiUI.Rules
public class ShogiBoard
{
private delegate void MoveSetCallback(Piece piece, Vector2 position);
- private readonly bool isValidationBoard;
private readonly PathFinder2D pathFinder;
- private ShogiBoard validationBoard;
+ private ShogiBoard? validationBoard;
private Vector2 player1King;
private Vector2 player2King;
public IReadOnlyDictionary> Hands { get; }
@@ -25,7 +24,6 @@ namespace Gameboard.ShogiUI.Rules
public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2;
public WhichPlayer? InCheck { get; private set; }
public bool IsCheckmate { get; private set; }
-
public string Error { get; private set; }
@@ -41,6 +39,7 @@ namespace Gameboard.ShogiUI.Rules
InitializeBoardState();
player1King = new Vector2(4, 8);
player2King = new Vector2(4, 0);
+ Error = string.Empty;
}
public ShogiBoard(IList moves) : this()
@@ -57,11 +56,16 @@ namespace Gameboard.ShogiUI.Rules
private ShogiBoard(ShogiBoard toCopy)
{
- isValidationBoard = true;
Board = new PlanarCollection(9, 9);
for (var x = 0; x < 9; x++)
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(Board);
MoveHistory = new List(toCopy.MoveHistory);
@@ -72,6 +76,7 @@ namespace Gameboard.ShogiUI.Rules
};
player1King = toCopy.player1King;
player2King = toCopy.player2King;
+ Error = toCopy.Error;
}
public bool Move(Move move)
@@ -103,7 +108,7 @@ namespace Gameboard.ShogiUI.Rules
validationBoard = new ShogiBoard(this);
}
- var isValid = move.PieceFromCaptured.HasValue
+ var isValid = move.PieceFromHand.HasValue
? validationBoard.PlaceFromHand(move)
: validationBoard.PlaceFromBoard(move);
if (!isValid)
@@ -123,20 +128,20 @@ namespace Gameboard.ShogiUI.Rules
}
// The move is valid and legal; update board state.
- if (move.PieceFromCaptured.HasValue) PlaceFromHand(move);
+ if (move.PieceFromHand.HasValue) PlaceFromHand(move);
else PlaceFromBoard(move);
return true;
}
/// True if the move was successful.
private bool PlaceFromHand(Move move)
{
- if (move.PieceFromCaptured.HasValue == false) return false; //Invalid move
- var index = Hands[WhoseTurn].FindIndex(p => p.WhichPiece == move.PieceFromCaptured);
+ if (move.PieceFromHand.HasValue == false) return false; //Invalid move
+ var index = Hands[WhoseTurn].FindIndex(p => p.WhichPiece == move.PieceFromHand);
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.
var minimumY = 0;
- switch (move.PieceFromCaptured.Value)
+ switch (move.PieceFromHand.Value)
{
case WhichPiece.Knight:
// Knight cannot be placed onto the farthest two ranks from the hand.
@@ -160,7 +165,7 @@ namespace Gameboard.ShogiUI.Rules
/// True if the move was successful.
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)
{
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";
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.";
return false; // Invalid move; move not part of move-set.
@@ -188,17 +193,17 @@ namespace Gameboard.ShogiUI.Rules
//Mutate the board.
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();
}
- 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();
}
}
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.Owner == WhichPlayer.Player1)
@@ -239,7 +244,7 @@ namespace Gameboard.ShogiUI.Rules
if (pathFinder.PathTo(move.To, kingPosition)) return true;
// 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);
// 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.
@@ -304,7 +309,7 @@ namespace Gameboard.ShogiUI.Rules
pathFinder.PathEvery(from, (other, position) =>
{
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);
if (moveSuccess)
{
diff --git a/Gameboard.ShogiUI.BoardState/WhichPiece.cs b/Gameboard.ShogiUI.Rules/WhichPiece.cs
similarity index 89%
rename from Gameboard.ShogiUI.BoardState/WhichPiece.cs
rename to Gameboard.ShogiUI.Rules/WhichPiece.cs
index ec4fd4d..5e6356c 100644
--- a/Gameboard.ShogiUI.BoardState/WhichPiece.cs
+++ b/Gameboard.ShogiUI.Rules/WhichPiece.cs
@@ -3,7 +3,7 @@
public enum WhichPiece
{
King,
- GoldenGeneral,
+ GoldGeneral,
SilverGeneral,
Bishop,
Rook,
diff --git a/Gameboard.ShogiUI.BoardState/WhichPlayer.cs b/Gameboard.ShogiUI.Rules/WhichPlayer.cs
similarity index 100%
rename from Gameboard.ShogiUI.BoardState/WhichPlayer.cs
rename to Gameboard.ShogiUI.Rules/WhichPlayer.cs
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/GetGuestToken.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/GetGuestToken.cs
index 6f6a751..6049f8e 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/GetGuestToken.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/GetGuestToken.cs
@@ -4,7 +4,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages
{
public class GetGuestToken
{
- public string ClientId { get; set; }
+ public string? ClientId { get; set; }
}
public class GetGuestTokenResponse
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/PostSession.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/PostSession.cs
new file mode 100644
index 0000000..8b7061a
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/Messages/PostSession.cs
@@ -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; }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj b/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj
index ee29921..4466c3d 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Gameboard.ShogiUI.Sockets.ServiceModels.csproj
@@ -4,6 +4,7 @@
net5.0
true
5
+ enable
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Interfaces/IRequest.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Interfaces/IRequest.cs
index 79ec262..79f20e5 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Interfaces/IRequest.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Interfaces/IRequest.cs
@@ -4,6 +4,6 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces
{
public interface IRequest
{
- ClientAction Action { get; set; }
+ ClientAction Action { get; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/CreateGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/CreateGame.cs
index 09d7455..9fa701a 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/CreateGame.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/CreateGame.cs
@@ -6,13 +6,13 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public class CreateGameRequest : IRequest
{
public ClientAction Action { get; set; }
- public string GameName { get; set; }
+ public string GameName { get; set; } = string.Empty;
public bool IsPrivate { get; set; }
}
public class CreateGameResponse : IResponse
{
- public string Action { get; private set; }
+ public string Action { get; }
public string Error { get; set; }
public Game Game { get; set; }
public string PlayerName { get; set; }
@@ -20,6 +20,9 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public CreateGameResponse(ClientAction action)
{
Action = action.ToString();
+ Error = string.Empty;
+ Game = new Game();
+ PlayerName = string.Empty;
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ErrorResponse.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ErrorResponse.cs
deleted file mode 100644
index 376ffc8..0000000
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ErrorResponse.cs
+++ /dev/null
@@ -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();
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinByCode.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinByCode.cs
deleted file mode 100644
index 51f2f8d..0000000
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinByCode.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinGame.cs
index ef8842d..b662cb1 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinGame.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/JoinGame.cs
@@ -3,6 +3,12 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
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 ClientAction Action { get; set; }
@@ -11,7 +17,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public class JoinGameResponse : IResponse
{
- public string Action { get; private set; }
+ public string Action { get; }
public string Error { get; set; }
public string GameName { get; set; }
public string PlayerName { get; set; }
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ListGames.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ListGames.cs
index c45d51c..ab9d67f 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ListGames.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/ListGames.cs
@@ -11,7 +11,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public class ListGamesResponse : IResponse
{
- public string Action { get; private set; }
+ public string Action { get; }
public string Error { get; set; }
public ICollection Games { get; set; }
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs
index 981ebf6..a879953 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/LoadGame.cs
@@ -1,6 +1,5 @@
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
-using System.Collections.Generic;
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
{
@@ -12,7 +11,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public class LoadGameResponse : IResponse
{
- public string Action { get; private set; }
+ public string Action { get; }
public Game Game { get; set; }
public BoardState BoardState { get; set; }
public string Error { get; set; }
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs
index e46f9a0..3ce4c6e 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Messages/Move.cs
@@ -6,8 +6,8 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public class MoveRequest : IRequest
{
public ClientAction Action { get; set; }
- public string GameName { get; set; }
- public Move Move { get; set; }
+ public string GameName { get; set; } = string.Empty;
+ public Move Move { get; set; } = new Move();
}
public class MoveResponse : IResponse
@@ -21,6 +21,10 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages
public MoveResponse(ClientAction action)
{
Action = action.ToString();
+ Error = string.Empty;
+ GameName = string.Empty;
+ BoardState = new BoardState();
+ PlayerName = string.Empty;
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/BoardState.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/BoardState.cs
index b42c398..6ee3051 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/BoardState.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/BoardState.cs
@@ -1,11 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
{
public class BoardState
{
- public Piece[,] Board { get; set; }
- public IReadOnlyCollection Player1Hand { get; set; }
- public IReadOnlyCollection Player2Hand { get; set; }
+ public Piece[,] Board { get; set; } = new Piece[0, 0];
+ public IReadOnlyCollection Player1Hand { get; set; } = Array.Empty();
+ public IReadOnlyCollection Player2Hand { get; set; } = Array.Empty();
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/ClientActionEnum.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/ClientActionEnum.cs
index 7b91157..5215171 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/ClientActionEnum.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/ClientActionEnum.cs
@@ -7,7 +7,6 @@
JoinGame,
JoinByCode,
LoadGame,
- Move,
- KeepAlive
+ Move
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Game.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Game.cs
index 3f5ddc6..8ff5fb1 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Game.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Game.cs
@@ -1,13 +1,14 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
{
public class Game
{
- public string GameName { get; set; }
+ public string GameName { get; set; } = string.Empty;
///
/// Players[0] is the session owner, Players[1] is the other guy
///
- public IReadOnlyList Players { get; set; }
+ public IReadOnlyList Players { get; set; } = Array.Empty();
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs
index 3ad3719..ef9723a 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Move.cs
@@ -2,11 +2,11 @@
{
public class Move
{
- public string PieceFromCaptured { get; set; }
+ public WhichPiece? PieceFromCaptured { get; set; }
/// Board position notation, like A3 or G1
- public string From { get; set; }
+ public string? From { get; set; }
/// Board position notation, like A3 or G1
- public string To { get; set; }
+ public string To { get; set; } = string.Empty;
public bool IsPromotion { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs
index 8f9fd23..71d9dbc 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/Piece.cs
@@ -2,8 +2,8 @@
{
public class Piece
{
- public WhichPiece WhichPiece { get; set; }
-
public bool IsPromoted { get; set; }
+ public WhichPiece WhichPiece { get; set; }
+ public WhichPlayer Owner { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/WhichPlayer.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/WhichPlayer.cs
new file mode 100644
index 0000000..835b8e6
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/Types/WhichPlayer.cs
@@ -0,0 +1,8 @@
+namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types
+{
+ public enum WhichPlayer
+ {
+ Player1,
+ Player2
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln
index 76d0058..a1576c2 100644
--- a/Gameboard.ShogiUI.Sockets.sln
+++ b/Gameboard.ShogiUI.Sockets.sln
@@ -9,15 +9,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets.S
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}"
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}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PathFinding", "PathFinding\PathFinding.csproj", "{A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}"
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
Global
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}.Release|Any CPU.ActiveCfg = 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.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = 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
- {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2CB188B7-3EE8-44FB-9548-8C0CFBF7E40B}.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}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EDFED1DF-253D-463B-842A-0B66F95214A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EDFED1DF-253D-463B-842A-0B66F95214A7}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
index 72a1fa8..7900ed2 100644
--- a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
+++ b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
@@ -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.ServiceModels.Api.Messages;
using Microsoft.AspNetCore.Authorization;
@@ -14,17 +15,20 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
public class GameController : ControllerBase
{
private readonly IGameboardRepositoryManager manager;
+ private readonly ISocketCommunicationManager communicationManager;
private readonly IGameboardRepository repository;
public GameController(
IGameboardRepository repository,
- IGameboardRepositoryManager manager)
+ IGameboardRepositoryManager manager,
+ ISocketCommunicationManager communicationManager)
{
this.manager = manager;
+ this.communicationManager = communicationManager;
this.repository = repository;
}
- [Route("JoinCode")]
+ [HttpPost("JoinCode")]
public async Task PostGameInvitation([FromBody] PostGameInvitation request)
{
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
@@ -41,7 +45,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
}
[AllowAnonymous]
- [Route("GuestJoinCode")]
+ [HttpPost("GuestJoinCode")]
public async Task PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
{
@@ -57,5 +61,26 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
return new UnauthorizedResult();
}
}
+
+ // TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
+ //[Route("")]
+ //public async Task 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();
+ //}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
index 4891963..f00b5bf 100644
--- a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
+++ b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
@@ -1,8 +1,10 @@
using Gameboard.ShogiUI.Sockets.Managers;
+using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
@@ -13,18 +15,24 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
[ApiController]
public class SocketController : ControllerBase
{
+ private readonly ILogger logger;
private readonly ISocketTokenManager tokenManager;
private readonly IGameboardRepositoryManager gameboardManager;
+ private readonly IGameboardRepository gameboardRepository;
public SocketController(
- ISocketTokenManager tokenManager,
- IGameboardRepositoryManager gameboardManager)
+ ILogger logger,
+ ISocketTokenManager tokenManager,
+ IGameboardRepositoryManager gameboardManager,
+ IGameboardRepository gameboardRepository)
{
+ this.logger = logger;
this.tokenManager = tokenManager;
this.gameboardManager = gameboardManager;
+ this.gameboardRepository = gameboardRepository;
}
- [Route("Token")]
+ [HttpGet("Token")]
public IActionResult GetToken()
{
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
@@ -33,7 +41,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
}
[AllowAnonymous]
- [Route("GuestToken")]
+ [HttpGet("GuestToken")]
public async Task GetGuestToken([FromQuery] GetGuestToken request)
{
if (request.ClientId == null)
@@ -44,7 +52,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
}
else
{
- if (await gameboardManager.PlayerExists(request.ClientId))
+ if (await gameboardRepository.IsGuestUser(request.ClientId))
{
var token = tokenManager.GenerateToken(request.ClientId);
return new JsonResult(new GetGuestTokenResponse(request.ClientId, token));
diff --git a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
index dd477c5..c558037 100644
--- a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
+++ b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
@@ -4,10 +4,18 @@
net5.0
true
5
+ enable
-
+
+
+
+
+
+
+
+
@@ -17,7 +25,8 @@
-
+
+
diff --git a/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs
index 813887e..f153527 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/BoardManager.cs
@@ -6,7 +6,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
public interface IBoardManager
{
void Add(string sessionName, ShogiBoard board);
- ShogiBoard Get(string sessionName);
+ ShogiBoard? Get(string sessionName);
}
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 ShogiBoard Get(string sessionName)
+ public ShogiBoard? Get(string sessionName)
{
if (Boards.TryGetValue(sessionName, out var board))
+ {
return board;
+ }
return null;
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs
index bd93ab6..8ff9dd1 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/CreateGameHandler.cs
@@ -1,61 +1,55 @@
-using Gameboard.Shogi.Api.ServiceModels.Messages;
-using Gameboard.ShogiUI.Sockets.Repositories;
+using Gameboard.ShogiUI.Sockets.Models;
+using Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers;
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;
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.
// 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;
public CreateGameHandler(
ISocketCommunicationManager communicationManager,
- IGameboardRepository repository)
+ IGameboardRepositoryManager manager)
{
- this.repository = repository;
+ this.manager = manager;
this.communicationManager = communicationManager;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(CreateGameRequest request, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
- var sessionName = await repository.PostSession(new PostSession
+ var model = new Session(request.GameName, request.IsPrivate, userName);
+ var success = await manager.CreateSession(model);
+
+ if (!success)
{
- SessionName = request.GameName,
- PlayerName = userName,
- IsPrivate = request.IsPrivate
- });
+ var error = new CreateGameResponse(request.Action)
+ {
+ Error = "Unable to create game with this name."
+ };
+ await communicationManager.BroadcastToPlayers(error, userName);
+ }
var response = new CreateGameResponse(request.Action)
{
PlayerName = userName,
- Game = new Game
- {
- GameName = sessionName,
- Players = new[] { userName }
- }
+ Game = model.ToServiceModel()
};
- if (string.IsNullOrWhiteSpace(sessionName))
- {
- response.Error = "Game already exists.";
- }
+ var task = request.IsPrivate
+ ? communicationManager.BroadcastToPlayers(response, userName)
+ : communicationManager.BroadcastToAll(response);
- if (request.IsPrivate)
- {
- await communicationManager.BroadcastToPlayers(response, userName);
- }
- else
- {
- await communicationManager.BroadcastToAll(response);
- }
+ await task;
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/IActionHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/IActionHandler.cs
deleted file mode 100644
index 24cf9d2..0000000
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/IActionHandler.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
-using System.Threading.Tasks;
-
-namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
-{
- public interface IActionHandler
- {
- ///
- /// Responsible for parsing json and handling the request.
- ///
- Task Handle(string json, string userName);
- }
-
- public delegate IActionHandler ActionHandlerResolver(ClientAction action);
-}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs
index 6f22279..9f3ae26 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinByCodeHandler.cs
@@ -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.Types;
-using Newtonsoft.Json;
using System.Threading.Tasks;
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 ISocketCommunicationManager communicationManager;
@@ -20,44 +21,44 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
this.communicationManager = communicationManager;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(JoinByCodeRequest request, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
- var sessionName = await repository.PostJoinPrivateSession(new PostJoinPrivateSession
- {
- PlayerName = userName,
- JoinCode = request.JoinCode
- });
+ //var request = JsonConvert.DeserializeObject(json);
+ //var sessionName = await repository.PostJoinPrivateSession(new PostJoinPrivateSession
+ //{
+ // PlayerName = userName,
+ // JoinCode = request.JoinCode
+ //});
- if (sessionName == null)
- {
- var response = new JoinGameResponse(ClientAction.JoinByCode)
- {
- PlayerName = userName,
- GameName = sessionName,
- Error = "Error joining game."
- };
- await communicationManager.BroadcastToPlayers(response, userName);
- }
- else
- {
- // Other members of the game see a regular JoinGame occur.
- var response = new JoinGameResponse(ClientAction.JoinGame)
- {
- PlayerName = userName,
- GameName = sessionName
- };
- // At this time, userName hasn't subscribed and won't receive this message.
- await communicationManager.BroadcastToGame(sessionName, response);
+ //if (sessionName == null)
+ //{
+ // var response = new JoinGameResponse(ClientAction.JoinByCode)
+ // {
+ // PlayerName = userName,
+ // GameName = sessionName,
+ // Error = "Error joining game."
+ // };
+ // await communicationManager.BroadcastToPlayers(response, userName);
+ //}
+ //else
+ //{
+ // // Other members of the game see a regular JoinGame occur.
+ // var response = new JoinGameResponse(ClientAction.JoinGame)
+ // {
+ // PlayerName = userName,
+ // GameName = sessionName
+ // };
+ // // At this time, userName hasn't subscribed and won't receive this message.
+ // await communicationManager.BroadcastToGame(sessionName, response);
- // The player joining sees the JoinByCode occur.
- response = new JoinGameResponse(ClientAction.JoinByCode)
- {
- PlayerName = userName,
- GameName = sessionName
- };
- await communicationManager.BroadcastToPlayers(response, userName);
- }
+ // // The player joining sees the JoinByCode occur.
+ // response = new JoinGameResponse(ClientAction.JoinByCode)
+ // {
+ // PlayerName = userName,
+ // GameName = sessionName
+ // };
+ // await communicationManager.BroadcastToPlayers(response, userName);
+ //}
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs
index 150a137..a37cdfc 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/JoinGameHandler.cs
@@ -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.Types;
-using Newtonsoft.Json;
using System.Threading.Tasks;
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 ISocketCommunicationManager communicationManager;
@@ -19,30 +20,30 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
this.communicationManager = communicationManager;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(JoinGameRequest request, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
+ //var request = JsonConvert.DeserializeObject(json);
- var joinSucceeded = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession
- {
- PlayerName = userName,
- SessionName = request.GameName
- });
+ //var joinSucceeded = await gameboardRepository.PutJoinPublicSession(new PutJoinPublicSession
+ //{
+ // PlayerName = userName,
+ // SessionName = request.GameName
+ //});
- var response = new JoinGameResponse(ClientAction.JoinGame)
- {
- PlayerName = userName,
- GameName = request.GameName
- };
- if (joinSucceeded)
- {
- await communicationManager.BroadcastToAll(response);
- }
- else
- {
- response.Error = "Game is full.";
- await communicationManager.BroadcastToPlayers(response, userName);
- }
+ //var response = new JoinGameResponse(ClientAction.JoinGame)
+ //{
+ // PlayerName = userName,
+ // GameName = request.GameName
+ //};
+ //if (joinSucceeded)
+ //{
+ // await communicationManager.BroadcastToAll(response);
+ //}
+ //else
+ //{
+ // response.Error = "Game is full.";
+ // await communicationManager.BroadcastToPlayers(response, userName);
+ //}
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/ListGamesHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/ListGamesHandler.cs
index d4379e3..41b5d5e 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/ListGamesHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/ListGamesHandler.cs
@@ -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.Types;
-using Newtonsoft.Json;
using System.Linq;
using System.Threading.Tasks;
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.
// It can be an HTTP route.
- public class ListGamesHandler : IActionHandler
+ public class ListGamesHandler : IListGamesHandler
{
private readonly ISocketCommunicationManager communicationManager;
private readonly IGameboardRepository repository;
@@ -23,16 +26,10 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
this.repository = repository;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(ListGamesRequest _, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
- var getGamesResponse = string.IsNullOrWhiteSpace(userName)
- ? 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 sessions = await repository.ReadSessions();
+ var games = sessions.Select(s => s.ToServiceModel()); // yuck
var response = new ListGamesResponse(ClientAction.ListGames)
{
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs
index 9efc43b..9fcb72f 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/LoadGameHandler.cs
@@ -3,16 +3,20 @@ using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
using System.Linq;
using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
{
+ public interface ILoadGameHandler
+ {
+ Task Handle(LoadGameRequest request, string userName);
+ }
+
///
/// Subscribes a user to messages for a session and loads that session into the BoardManager for playing.
///
- public class LoadGameHandler : IActionHandler
+ public class LoadGameHandler : ILoadGameHandler
{
private readonly ILogger logger;
private readonly IGameboardRepository gameboardRepository;
@@ -31,35 +35,35 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
this.boardManager = boardManager;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(LoadGameRequest request, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
- var gameTask = gameboardRepository.GetGame(request.GameName);
- var moveTask = gameboardRepository.GetMoves(request.GameName);
+ var readSession = gameboardRepository.ReadSession(request.GameName);
+ var readStates = gameboardRepository.ReadBoardStates(request.GameName);
- var sessionModel = await gameTask;
+ var sessionModel = await readSession;
if (sessionModel == null)
{
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." };
- await communicationManager.BroadcastToPlayers(response, userName);
+ var error = new LoadGameResponse(ClientAction.LoadGame) { Error = "Game not found." };
+ await communicationManager.BroadcastToPlayers(error, userName);
+ return;
}
- else
+
+ communicationManager.SubscribeToGame(sessionModel, userName);
+ var boardStates = await readStates;
+ var moveModels = boardStates
+ .Where(_ => _.Move != null)
+ .Select(_ => _.Move!.ToRulesModel())
+ .ToList();
+ var shogiBoard = new ShogiBoard(moveModels);
+ boardManager.Add(sessionModel.Name, shogiBoard);
+
+ var response = new LoadGameResponse(ClientAction.LoadGame)
{
- var moveModels = await moveTask;
-
- communicationManager.SubscribeToGame(sessionModel, userName);
- var boardMoves = moveModels.Select(_ => _.ToBoardModel()).ToList();
- var shogiBoard = new ShogiBoard(boardMoves);
- boardManager.Add(sessionModel.Name, shogiBoard);
-
- var response = new LoadGameResponse(ClientAction.LoadGame)
- {
- Game = sessionModel.ToServiceModel(),
- BoardState = new Models.BoardState(shogiBoard).ToServiceModel()
- };
- await communicationManager.BroadcastToPlayers(response, userName);
- }
+ Game = sessionModel.ToServiceModel(),
+ BoardState = new Models.BoardState(shogiBoard).ToServiceModel()
+ };
+ await communicationManager.BroadcastToPlayers(response, userName);
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs
index 3fa5bb5..74141d0 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/ClientActionHandlers/MoveHandler.cs
@@ -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.ServiceModels.Socket.Messages;
using Newtonsoft.Json;
using System.Threading.Tasks;
-using Service = Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
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 IGameboardRepository gameboardRepository;
@@ -23,43 +26,43 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
this.communicationManager = communicationManager;
}
- public async Task Handle(string json, string userName)
+ public async Task Handle(MoveRequest request, string userName)
{
- var request = JsonConvert.DeserializeObject(json);
- var moveModel = new Move(request.Move);
- var board = boardManager.Get(request.GameName);
- if (board == null)
- {
- // TODO: Find a flow for this
- 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."
- };
- await communicationManager.BroadcastToPlayers(response, userName);
+ //var request = JsonConvert.DeserializeObject(json);
+ //var moveModel = new Move(request.Move);
+ //var board = boardManager.Get(request.GameName);
+ //if (board == null)
+ //{
+ // // TODO: Find a flow for this
+ // 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."
+ // };
+ // await communicationManager.BroadcastToPlayers(response, userName);
- }
- var boardMove = moveModel.ToBoardModel();
- var moveSuccess = board.Move(boardMove);
- if (moveSuccess)
- {
- await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel()));
- var boardState = new BoardState(board);
- var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
- {
- GameName = request.GameName,
- PlayerName = userName,
- BoardState = boardState.ToServiceModel()
- };
- await communicationManager.BroadcastToGame(request.GameName, response);
- }
- else
- {
- var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
- {
- Error = "Invalid move."
- };
- await communicationManager.BroadcastToPlayers(response, userName);
- }
+ //}
+ //var boardMove = moveModel.ToBoardModel();
+ //var moveSuccess = board.Move(boardMove);
+ //if (moveSuccess)
+ //{
+ // await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel()));
+ // var boardState = new BoardState(board);
+ // var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
+ // {
+ // GameName = request.GameName,
+ // PlayerName = userName,
+ // BoardState = boardState.ToServiceModel()
+ // };
+ // await communicationManager.BroadcastToGame(request.GameName, response);
+ //}
+ //else
+ //{
+ // var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
+ // {
+ // Error = "Invalid move."
+ // };
+ // await communicationManager.BroadcastToPlayers(response, userName);
+ //}
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketCommunicationManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketCommunicationManager.cs
index 33ec4d2..c3dfc6a 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/SocketCommunicationManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/SocketCommunicationManager.cs
@@ -3,6 +3,7 @@ using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
using Gameboard.ShogiUI.Sockets.Managers.Utility;
using Gameboard.ShogiUI.Sockets.Models;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
+using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -16,10 +17,9 @@ namespace Gameboard.ShogiUI.Sockets.Managers
{
public interface ISocketCommunicationManager
{
- Task CommunicateWith(WebSocket w, string s);
Task BroadcastToAll(IResponse response);
- Task BroadcastToGame(string gameName, IResponse response);
- Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
+ //Task BroadcastToGame(string gameName, IResponse response);
+ //Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
void SubscribeToGame(Session session, string playerName);
void SubscribeToBroadcast(WebSocket socket, string playerName);
void UnsubscribeFromBroadcastAndGames(string playerName);
@@ -34,54 +34,14 @@ namespace Gameboard.ShogiUI.Sockets.Managers
/// Dictionary key is game name.
private readonly ConcurrentDictionary sessions;
private readonly ILogger logger;
- private readonly ActionHandlerResolver handlerResolver;
- public SocketCommunicationManager(
- ILogger logger,
- ActionHandlerResolver handlerResolver)
+ public SocketCommunicationManager(ILogger logger)
{
this.logger = logger;
- this.handlerResolver = handlerResolver;
connections = new ConcurrentDictionary();
sessions = new ConcurrentDictionary();
}
- 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(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)
{
connections.TryAdd(playerName, socket);
@@ -154,27 +114,27 @@ namespace Gameboard.ShogiUI.Sockets.Managers
return Task.WhenAll(tasks);
}
- public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
- {
- if (sessions.TryGetValue(gameName, out var session))
- {
- var serialized1 = JsonConvert.SerializeObject(forPlayer1);
- var serialized2 = JsonConvert.SerializeObject(forPlayer2);
- return Task.WhenAll(
- session.SendToPlayer1(serialized1),
- session.SendToPlayer2(serialized2));
- }
- return Task.CompletedTask;
- }
+ //public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
+ //{
+ // if (sessions.TryGetValue(gameName, out var session))
+ // {
+ // var serialized1 = JsonConvert.SerializeObject(forPlayer1);
+ // var serialized2 = JsonConvert.SerializeObject(forPlayer2);
+ // return Task.WhenAll(
+ // session.SendToPlayer1(serialized1),
+ // session.SendToPlayer2(serialized2));
+ // }
+ // return Task.CompletedTask;
+ //}
- public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
- {
- if (sessions.TryGetValue(gameName, out var session))
- {
- var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
- return session.Broadcast(serialized);
- }
- return Task.CompletedTask;
- }
+ //public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
+ //{
+ // if (sessions.TryGetValue(gameName, out var session))
+ // {
+ // var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
+ // return session.Broadcast(serialized);
+ // }
+ // return Task.CompletedTask;
+ //}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
index 05374e3..5ae3ff0 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
@@ -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.Net;
+using System.Net.WebSockets;
using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets.Managers
@@ -12,14 +20,36 @@ namespace Gameboard.ShogiUI.Sockets.Managers
public class SocketConnectionManager : ISocketConnectionManager
{
+ private readonly ILogger logger;
private readonly ISocketCommunicationManager communicationManager;
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 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.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)
@@ -33,7 +63,74 @@ namespace Gameboard.ShogiUI.Sockets.Managers
if (userName != null)
{
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(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(message);
+ await listGamesHandler.Handle(req, userName);
+ break;
+ }
+ case ClientAction.CreateGame:
+ {
+ var req = JsonConvert.DeserializeObject(message);
+ await createGameHandler.Handle(req, userName);
+ break;
+ }
+ case ClientAction.JoinGame:
+ {
+ var req = JsonConvert.DeserializeObject(message);
+ await joinGameHandler.Handle(req, userName);
+ break;
+ }
+ case ClientAction.JoinByCode:
+ {
+ var req = JsonConvert.DeserializeObject(message);
+ await joinByCodeHandler.Handle(req, userName);
+ break;
+ }
+ case ClientAction.LoadGame:
+ {
+ var req = JsonConvert.DeserializeObject(message);
+ await loadGameHandler.Handle(req, userName);
+ break;
+ }
+ case ClientAction.Move:
+ {
+ var req = JsonConvert.DeserializeObject(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;
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketTokenManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketTokenManager.cs
index 971e438..b2e2f73 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/SocketTokenManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/SocketTokenManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -16,27 +17,24 @@ namespace Gameboard.ShogiUI.Sockets.Managers
///
/// Key is userName
///
- private readonly Dictionary Tokens;
+ private readonly ConcurrentDictionary Tokens;
public SocketTokenManager()
{
- Tokens = new Dictionary();
+ Tokens = new ConcurrentDictionary();
}
public Guid GenerateToken(string userName)
{
- var guid = Guid.NewGuid();
+ Tokens.Remove(userName, out _);
- if (Tokens.ContainsKey(userName))
- {
- Tokens.Remove(userName);
- }
- Tokens.Add(userName, guid);
+ var guid = Guid.NewGuid();
+ Tokens.TryAdd(userName, guid);
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMinutes(1));
- Tokens.Remove(userName);
+ Tokens.Remove(userName, out _);
});
return guid;
@@ -45,13 +43,12 @@ namespace Gameboard.ShogiUI.Sockets.Managers
/// User name associated to the guid or null.
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);
- return username;
+ Tokens.Remove(userName, out _);
}
- return null;
+ return userName;
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Managers/Utility/Request.cs b/Gameboard.ShogiUI.Sockets/Managers/Utility/Request.cs
index df3f245..9d8903f 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/Utility/Request.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/Utility/Request.cs
@@ -6,6 +6,5 @@ namespace Gameboard.ShogiUI.Sockets.Managers.Utility
public class Request : IRequest
{
public ClientAction Action { get; set; }
- public string PlayerName { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Models/BoardState.cs b/Gameboard.ShogiUI.Sockets/Models/BoardState.cs
index 1d50469..df3d1f0 100644
--- a/Gameboard.ShogiUI.Sockets/Models/BoardState.cs
+++ b/Gameboard.ShogiUI.Sockets/Models/BoardState.cs
@@ -1,5 +1,6 @@
using Gameboard.ShogiUI.Rules;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using ServiceTypes = Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
@@ -7,28 +8,53 @@ namespace Gameboard.ShogiUI.Sockets.Models
{
public class BoardState
{
- public Piece[,] Board { get; set; }
- public IReadOnlyCollection Player1Hand { get; set; }
- public IReadOnlyCollection Player2Hand { get; set; }
+ // TODO: Create a custom 2D array implementation which removes the (x,y) or (y,x) ambiguity.
+ public Piece?[,] Board { get; }
+ public IReadOnlyCollection Player1Hand { get; }
+ public IReadOnlyCollection Player2Hand { get; }
+ ///
+ /// Move is null in the first BoardState of a Session, before any moves have been made.
+ ///
+ public Move? Move { get; }
+
+ public BoardState() : this(new ShogiBoard()) { }
+
+ public BoardState(Piece?[,] board, IList player1Hand, ICollection player2Hand, Move move)
+ {
+ Board = board;
+ Player1Hand = new ReadOnlyCollection(player1Hand);
+ }
public BoardState(ShogiBoard shogi)
{
Board = new Piece[9, 9];
for (var x = 0; x < 9; x++)
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();
Player2Hand = shogi.Hands[WhichPlayer.Player2].Select(_ => new Piece(_)).ToList();
+ Move = new Move(shogi.MoveHistory[^1]);
}
public ServiceTypes.BoardState ToServiceModel()
{
var board = new ServiceTypes.Piece[9, 9];
- Board = new Piece[9, 9];
for (var x = 0; x < 9; x++)
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
{
Board = board,
diff --git a/Gameboard.ShogiUI.Sockets/Models/Move.cs b/Gameboard.ShogiUI.Sockets/Models/Move.cs
index a597493..3617d88 100644
--- a/Gameboard.ShogiUI.Sockets/Models/Move.cs
+++ b/Gameboard.ShogiUI.Sockets/Models/Move.cs
@@ -1,88 +1,41 @@
-using Gameboard.ShogiUI.Rules;
-using Microsoft.FSharp.Core;
-using System;
+using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
using System.Numerics;
-using BoardStateMove = Gameboard.ShogiUI.Rules.Move;
-using ShogiApi = Gameboard.Shogi.Api.ServiceModels.Types;
namespace Gameboard.ShogiUI.Sockets.Models
{
public class Move
{
- public string PieceFromCaptured { get; set; }
- public Coords From { get; set; }
- public Coords To { get; set; }
+ public Coords? From { 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);
- To = Coords.FromBoardNotation(move.To);
- PieceFromCaptured = move.PieceFromCaptured;
- IsPromotion = move.IsPromotion;
+ From = from;
+ To = to;
+ IsPromotion = isPromotion;
}
- public Move(ShogiApi.Move move)
+
+ public Move(WhichPiece pieceFromHand, Coords to)
{
- string pieceFromCaptured = null;
- if (move.PieceFromCaptured != null)
- {
- 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;
+ PieceFromHand = pieceFromHand;
+ To = to;
}
+
public ServiceModels.Socket.Types.Move ToServiceModel() => new()
{
- From = From.ToBoardNotation(),
+ From = From?.ToBoardNotation(),
IsPromotion = IsPromotion,
- PieceFromCaptured = PieceFromCaptured,
- To = To.ToBoardNotation()
+ To = To.ToBoardNotation(),
+ PieceFromCaptured = PieceFromHand
};
- public ShogiApi.Move ToApiModel()
+
+ public Rules.Move ToRulesModel()
{
- var pieceFromCaptured = PieceFromCaptured switch
- {
- "B" => new FSharpOption(ShogiApi.WhichPieceName.Bishop),
- "G" => new FSharpOption(ShogiApi.WhichPieceName.GoldenGeneral),
- "K" => new FSharpOption(ShogiApi.WhichPieceName.King),
- "k" => new FSharpOption(ShogiApi.WhichPieceName.Knight),
- "L" => new FSharpOption(ShogiApi.WhichPieceName.Lance),
- "P" => new FSharpOption(ShogiApi.WhichPieceName.Pawn),
- "R" => new FSharpOption(ShogiApi.WhichPieceName.Rook),
- "S" => new FSharpOption(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(PieceFromCaptured, out var whichPiece) ? whichPiece : null,
- To = new Vector2(To.X, To.Y)
- };
+ return PieceFromHand != null
+ ? new Rules.Move((Rules.WhichPiece)PieceFromHand, new Vector2(To.X, To.Y))
+ : new Rules.Move(new Vector2(From!.X, From.Y), new Vector2(To.X, To.Y), IsPromotion);
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Models/Piece.cs b/Gameboard.ShogiUI.Sockets/Models/Piece.cs
index 8ce5124..d8bc5b9 100644
--- a/Gameboard.ShogiUI.Sockets/Models/Piece.cs
+++ b/Gameboard.ShogiUI.Sockets/Models/Piece.cs
@@ -1,18 +1,25 @@
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
-using BoardStatePiece = Gameboard.ShogiUI.Rules.Pieces.Piece;
namespace Gameboard.ShogiUI.Sockets.Models
{
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(BoardStatePiece piece)
+ public Piece(bool isPromoted, WhichPlayer owner, WhichPiece whichPiece)
+ {
+ IsPromoted = isPromoted;
+ Owner = owner;
+ WhichPiece = whichPiece;
+ }
+
+ public Piece(Rules.Pieces.Piece piece)
{
- WhichPiece = (WhichPiece)piece.WhichPiece;
IsPromoted = piece.IsPromoted;
+ Owner = (WhichPlayer)piece.Owner;
+ WhichPiece = (WhichPiece)piece.WhichPiece;
}
public ServiceModels.Socket.Types.Piece ToServiceModel()
@@ -20,6 +27,7 @@ namespace Gameboard.ShogiUI.Sockets.Models
return new ServiceModels.Socket.Types.Piece
{
IsPromoted = IsPromoted,
+ Owner = Owner,
WhichPiece = WhichPiece
};
}
diff --git a/Gameboard.ShogiUI.Sockets/Models/Session.cs b/Gameboard.ShogiUI.Sockets/Models/Session.cs
index 1e824f7..bf2af49 100644
--- a/Gameboard.ShogiUI.Sockets/Models/Session.cs
+++ b/Gameboard.ShogiUI.Sockets/Models/Session.cs
@@ -1,5 +1,6 @@
using Gameboard.ShogiUI.Sockets.Extensions;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
+using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.WebSockets;
@@ -9,18 +10,20 @@ namespace Gameboard.ShogiUI.Sockets.Models
{
public class Session
{
+ [JsonIgnore] public ConcurrentDictionary Subscriptions { get; }
public string Name { get; }
public string Player1 { get; }
- public string Player2 { get; }
+ public string? Player2 { get; }
+ public bool IsPrivate { get; }
- public ConcurrentDictionary Subscriptions { get; }
-
- public Session(Shogi.Api.ServiceModels.Types.Session session)
+ public Session(string name, bool isPrivate, string player1, string? player2 = null)
{
- Name = session.Name;
- Player1 = session.Player1;
- Player2 = session.Player2;
Subscriptions = new ConcurrentDictionary();
+
+ Name = name;
+ Player1 = player1;
+ Player2 = player2;
+ IsPrivate = isPrivate;
}
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)
{
- if (Subscriptions.TryGetValue(Player2, out var socket))
+ if (Player2 != null && Subscriptions.TryGetValue(Player2, out var socket))
{
return socket.SendTextAsync(message);
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardState.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardState.cs
new file mode 100644
index 0000000..2658248
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardState.cs
@@ -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; }
+ ///
+ /// Move is null for first BoardState of a session - before anybody has made moves.
+ ///
+ public Move? Move { get; set; }
+
+ ///
+ /// Default constructor and setters are for deserialization.
+ ///
+ public BoardState() : base()
+ {
+ Name = string.Empty;
+ Board = new Piece[9, 9];
+ Player1Hand = Array.Empty();
+ Player2Hand = Array.Empty();
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchCreatedResult.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchCreatedResult.cs
new file mode 100644
index 0000000..3435e7b
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchCreatedResult.cs
@@ -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;
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchDocument.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchDocument.cs
new file mode 100644
index 0000000..bd1a793
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchDocument.cs
@@ -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;
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchFindResult.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchFindResult.cs
new file mode 100644
index 0000000..fd02af6
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/CouchFindResult.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
+{
+ internal class CouchFindResult
+ {
+ public T[] docs;
+
+ public CouchFindResult()
+ {
+ docs = Array.Empty();
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
new file mode 100644
index 0000000..a362f7d
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
@@ -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
+ {
+ ///
+ /// A board coordinate, like A3 or G6. When null, look for PieceFromHand to exist.
+ ///
+ public string? From { get; set; }
+
+ public bool IsPromotion { get; set; }
+
+ ///
+ /// The piece placed from the player's hand.
+ ///
+ public WhichPiece? PieceFromHand { get; set; }
+
+ ///
+ /// A board coordinate, like A3 or G6.
+ ///
+ public string To { get; set; }
+
+ ///
+ /// Default constructor and setters are for deserialization.
+ ///
+ 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);
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
new file mode 100644
index 0000000..68642f9
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
@@ -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; }
+
+ ///
+ /// Default constructor and setters are for deserialization.
+ ///
+ public Piece()
+ {
+ }
+
+ public Piece(Models.Piece piece)
+ {
+ IsPromoted = piece.IsPromoted;
+ Owner = piece.Owner;
+ WhichPiece = piece.WhichPiece;
+ }
+
+ public Models.Piece ToDomainModel() => new(IsPromoted, Owner, WhichPiece);
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Readme.md b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Readme.md
new file mode 100644
index 0000000..50c4380
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Readme.md
@@ -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.
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Session.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Session.cs
new file mode 100644
index 0000000..a3dd34e
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Session.cs
@@ -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; }
+
+ ///
+ /// Default constructor and setters are for deserialization.
+ ///
+ 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);
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/User.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/User.cs
new file mode 100644
index 0000000..22d30c7
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/User.cs
@@ -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;
+ }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
index bd483eb..162e2df 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
@@ -1,6 +1,5 @@
-using Gameboard.Shogi.Api.ServiceModels.Messages;
-using Gameboard.ShogiUI.Sockets.Models;
-using Gameboard.ShogiUI.Sockets.Repositories.Utility;
+using Gameboard.ShogiUI.Sockets.Repositories.CouchModels;
+using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -13,141 +12,173 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
{
public interface IGameboardRepository
{
- Task DeleteGame(string gameName);
- Task GetGame(string gameName);
- Task GetGames();
- Task GetGames(string playerName);
- Task> GetMoves(string gameName);
- Task PostSession(PostSession request);
- Task PostJoinPrivateSession(PostJoinPrivateSession request);
- Task PutJoinPublicSession(PutJoinPublicSession request);
- Task PostMove(string gameName, PostMove request);
+ Task CreateBoardState(string sessionName, Models.BoardState boardState, Models.Move? move);
+ Task CreateGuestUser(string userName);
+ Task CreateSession(Models.Session session);
+ Task> ReadSessions();
+ Task IsGuestUser(string userName);
Task PostJoinCode(string gameName, string userName);
- Task GetPlayer(string userName);
- Task PostPlayer(PostPlayer request);
+ Task ReadSession(string name);
+ Task> ReadBoardStates(string name);
}
public class GameboardRepository : IGameboardRepository
{
- private const string GetSessionsRoute = "Sessions";
- private const string PostSessionRoute = "Session";
- private const string JoinSessionRoute = "Session/Join";
- private const string PlayerRoute = "Player";
- private const string MediaType = "application/json";
- private readonly IAuthenticatedHttpClient client;
- public GameboardRepository(IAuthenticatedHttpClient client)
+ private const string ApplicationJson = "application/json";
+ private readonly HttpClient client;
+ private readonly ILogger logger;
+
+ public GameboardRepository(IHttpClientFactory clientFactory, ILogger logger)
{
- this.client = client;
+ client = clientFactory.CreateClient("couchdb");
+ this.logger = logger;
}
- public async Task GetGames()
+ public async Task> ReadSessions()
{
- var response = await client.GetAsync(GetSessionsRoute);
- var json = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(json);
- }
+ var selector = $@"{{ ""{nameof(Session.Type)}"": ""{nameof(Session)}"" }}";
+ var query = $@"{{ ""selector"": {selector} }}";
+ 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>(responseContent);
- public async Task GetGames(string playerName)
- {
- var uri = $"Sessions/{playerName}";
- var response = await client.GetAsync(Uri.EscapeUriString(uri));
- var json = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(json);
- }
-
- public async Task GetGame(string gameName)
- {
- var uri = $"Session/{gameName}";
- var response = await client.GetAsync(Uri.EscapeUriString(uri));
- var json = await response.Content.ReadAsStringAsync();
- if (string.IsNullOrWhiteSpace(json))
+ if (result == null)
{
- return null;
+ logger.LogError("Unable to deserialize couchdb result during {0}.", nameof(this.ReadSessions));
+ return Array.Empty();
}
- return new Session(JsonConvert.DeserializeObject(json).Session);
+ return result.docs
+ .Select(_ => _.ToDomainModel())
+ .ToList();
}
- public async Task DeleteGame(string gameName)
+ public async Task ReadSession(string name)
{
- var uri = $"Session/{gameName}";
- await client.DeleteAsync(Uri.EscapeUriString(uri));
+ var response = await client.GetAsync(name);
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var couchModel = JsonConvert.DeserializeObject(responseContent);
+ return couchModel.ToDomainModel();
}
- public async Task PostSession(PostSession request)
+ public async Task> ReadBoardStates(string name)
{
- var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
- var response = await client.PostAsync(PostSessionRoute, content);
- var json = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(json).SessionName;
- }
+ var selector = $@"{{ ""{nameof(BoardState.Type)}"": ""{nameof(BoardState)}"", ""{nameof(BoardState.Name)}"": ""{name}"" }}";
+ var sort = $@"{{ ""{nameof(BoardState.CreatedDate)}"" : ""desc"" }}";
+ var query = $@"{{ ""selector"": {selector}, ""sort"": {sort} }}";
+ 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>(responseContent);
- public async Task PutJoinPublicSession(PutJoinPublicSession request)
- {
- var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
- var response = await client.PutAsync(JoinSessionRoute, content);
- var json = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(json).JoinSucceeded;
- }
-
- public async Task PostJoinPrivateSession(PostJoinPrivateSession request)
- {
- var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
- var response = await client.PostAsync(JoinSessionRoute, content);
- var json = await response.Content.ReadAsStringAsync();
- var deserialized = JsonConvert.DeserializeObject(json);
- if (deserialized.JoinSucceeded)
+ if (result == null)
{
- return deserialized.SessionName;
+ logger.LogError("Unable to deserialize couchdb result during {0}.", nameof(this.ReadSessions));
+ return Array.Empty();
}
- return null;
+ return result.docs
+ .Select(_ => new Models.BoardState(_))
+ .ToList();
}
- public async Task> GetMoves(string gameName)
+ //public async Task DeleteGame(string gameName)
+ //{
+ // //var uri = $"Session/{gameName}";
+ // //await client.DeleteAsync(Uri.EscapeUriString(uri));
+ //}
+
+ public async Task CreateSession(Models.Session session)
{
- var uri = $"Session/{gameName}/Moves";
- var get = await client.GetAsync(Uri.EscapeUriString(uri));
- var json = await get.Content.ReadAsStringAsync();
- if (string.IsNullOrWhiteSpace(json))
- {
- return new List();
- }
- var response = JsonConvert.DeserializeObject(json);
- return response.Moves.Select(m => new Move(m)).ToList();
+ var couchModel = new Session(session.Name, session);
+ var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
+ var response = await client.PostAsync(string.Empty, content);
+ return response.IsSuccessStatusCode;
}
- public async Task PostMove(string gameName, PostMove request)
+ public async Task CreateBoardState(string sessionName, Models.BoardState boardState, Models.Move? move)
{
- var uri = $"Session/{gameName}/Move";
- var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
- await client.PostAsync(Uri.EscapeUriString(uri), content);
+ var couchModel = new BoardState(sessionName, boardState, move);
+ var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
+ var response = await client.PostAsync(string.Empty, content);
+ return response.IsSuccessStatusCode;
}
+ //public async Task PutJoinPublicSession(PutJoinPublicSession request)
+ //{
+ // var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
+ // var response = await client.PutAsync(JoinSessionRoute, content);
+ // var json = await response.Content.ReadAsStringAsync();
+ // return JsonConvert.DeserializeObject(json).JoinSucceeded;
+ //}
+
+ //public async Task PostJoinPrivateSession(PostJoinPrivateSession request)
+ //{
+ // var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
+ // var response = await client.PostAsync(JoinSessionRoute, content);
+ // var json = await response.Content.ReadAsStringAsync();
+ // var deserialized = JsonConvert.DeserializeObject(json);
+ // if (deserialized.JoinSucceeded)
+ // {
+ // return deserialized.SessionName;
+ // }
+ // return null;
+ //}
+
+ //public async Task> GetMoves(string gameName)
+ //{
+ // var uri = $"Session/{gameName}/Moves";
+ // var get = await client.GetAsync(Uri.EscapeUriString(uri));
+ // var json = await get.Content.ReadAsStringAsync();
+ // if (string.IsNullOrWhiteSpace(json))
+ // {
+ // return new List();
+ // }
+ // var response = JsonConvert.DeserializeObject(json);
+ // return response.Moves.Select(m => new Move(m)).ToList();
+ //}
+
+ //public async Task PostMove(string gameName, PostMove request)
+ //{
+ // var uri = $"Session/{gameName}/Move";
+ // var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
+ // await client.PostAsync(Uri.EscapeUriString(uri), content);
+ //}
+
public async Task PostJoinCode(string gameName, string userName)
{
- var uri = $"JoinCode/{gameName}";
- var serialized = JsonConvert.SerializeObject(new PostJoinCode { PlayerName = userName });
- var content = new StringContent(serialized, Encoding.UTF8, MediaType);
- var json = await (await client.PostAsync(Uri.EscapeUriString(uri), content)).Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(json).JoinCode;
+ // var uri = $"JoinCode/{gameName}";
+ // var serialized = JsonConvert.SerializeObject(new PostJoinCode { PlayerName = userName });
+ // var content = new StringContent(serialized, Encoding.UTF8, MediaType);
+ // var json = await (await client.PostAsync(Uri.EscapeUriString(uri), content)).Content.ReadAsStringAsync();
+ // return JsonConvert.DeserializeObject(json).JoinCode;
+ return string.Empty;
}
- public async Task GetPlayer(string playerName)
+ //public async Task 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(content);
+ // return new Player(response.Player.Name);
+ // }
+ // return null;
+ //}
+
+ public async Task CreateGuestUser(string userName)
{
- 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(content);
- return new Player(response.Player.Name);
- }
- return null;
+ var couchModel = new User(userName, User.LoginPlatform.Guest);
+ var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
+ var response = await client.PostAsync(string.Empty, content);
+ return response.IsSuccessStatusCode;
}
- public async Task PostPlayer(PostPlayer request)
+ public async Task IsGuestUser(string userName)
{
- var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, MediaType);
- var response = await client.PostAsync(PlayerRoute, content);
+ var req = new HttpRequestMessage(HttpMethod.Head, new Uri($"{client.BaseAddress}/{User.GetDocumentId(userName)}"));
+ var response = await client.SendAsync(req);
return response.IsSuccessStatusCode;
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs b/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs
index 30fed61..d219516 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/RepositoryManagers/GameboardRepositoryManager.cs
@@ -1,4 +1,4 @@
-using Gameboard.Shogi.Api.ServiceModels.Messages;
+using Gameboard.ShogiUI.Sockets.Models;
using System;
using System.Threading.Tasks;
@@ -9,7 +9,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
Task CreateGuestUser();
Task IsPlayer1(string sessionName, string playerName);
bool IsGuest(string playerName);
- Task PlayerExists(string playerName);
+ Task CreateSession(Session session);
}
public class GameboardRepositoryManager : IGameboardRepositoryManager
@@ -30,11 +30,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
{
count++;
var clientId = $"Guest-{Guid.NewGuid()}";
- var request = new PostPlayer
- {
- PlayerName = clientId
- };
- var isCreated = await repository.PostPlayer(request);
+ var isCreated = await repository.CreateGuestUser(clientId);
if (isCreated)
{
return clientId;
@@ -45,22 +41,31 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.RepositoryManagers
public async Task IsPlayer1(string sessionName, string playerName)
{
- var session = await repository.GetGame(sessionName);
- return session?.Player1 == playerName;
+ //var session = await repository.GetGame(sessionName);
+ //return session?.Player1 == playerName;
+ return true;
}
public async Task CreateJoinCode(string sessionName, string playerName)
{
- var session = await repository.GetGame(sessionName);
- if (playerName == session?.Player1)
- {
- return await repository.PostJoinCode(sessionName, playerName);
- }
+ //var session = await repository.GetGame(sessionName);
+ //if (playerName == session?.Player1)
+ //{
+ // return await repository.PostJoinCode(sessionName, playerName);
+ //}
return null;
}
- public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix);
+ public async Task 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 PlayerExists(string playerName) => await repository.GetPlayer(playerName) != null;
+ public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix);
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/Utility/AuthenticatedHttpClient.cs b/Gameboard.ShogiUI.Sockets/Repositories/Utility/AuthenticatedHttpClient.cs
deleted file mode 100644
index 3b4067d..0000000
--- a/Gameboard.ShogiUI.Sockets/Repositories/Utility/AuthenticatedHttpClient.cs
+++ /dev/null
@@ -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 DeleteAsync(string requestUri);
- Task GetAsync(string requestUri);
- Task PostAsync(string requestUri, HttpContent content);
- Task PutAsync(string requestUri, HttpContent content);
- }
-
- public class AuthenticatedHttpClient : HttpClient, IAuthenticatedHttpClient
- {
- private readonly ILogger logger;
- private readonly string identityServerUrl;
- private TokenResponse tokenResponse;
- private readonly string clientId;
- private readonly string clientSecret;
-
- public AuthenticatedHttpClient(ILogger 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 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 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 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 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;
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Startup.cs b/Gameboard.ShogiUI.Sockets/Startup.cs
index 8431c68..cda318d 100644
--- a/Gameboard.ShogiUI.Sockets/Startup.cs
+++ b/Gameboard.ShogiUI.Sockets/Startup.cs
@@ -3,8 +3,6 @@ using Gameboard.ShogiUI.Sockets.Managers;
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
using Gameboard.ShogiUI.Sockets.Repositories;
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.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -15,8 +13,8 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
-using System.Collections.Generic;
using System.Linq;
+using System.Text;
namespace Gameboard.ShogiUI.Sockets
{
@@ -33,36 +31,33 @@ namespace Gameboard.ShogiUI.Sockets
public void ConfigureServices(IServiceCollection services)
{
// Socket ActionHandlers
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
// Managers
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
- services.AddScoped();
+ services.AddSingleton();
services.AddSingleton();
- services.AddSingleton(sp => action =>
- {
- return action switch
- {
- ClientAction.ListGames => sp.GetService(),
- ClientAction.CreateGame => sp.GetService(),
- ClientAction.JoinGame => sp.GetService(),
- ClientAction.JoinByCode => sp.GetService(),
- ClientAction.LoadGame => sp.GetService(),
- ClientAction.Move => sp.GetService(),
- _ => throw new KeyNotFoundException($"Unable to resolve {nameof(IActionHandler)} for {nameof(ClientAction)} {action}"),
- };
- });
// 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();
- services.AddSingleton();
+ //services.AddSingleton();
+ //services.AddSingleton(provider => new CouchClient(databaseName, couchUrl));
services.AddControllers();
diff --git a/Gameboard.ShogiUI.Sockets/appsettings.json b/Gameboard.ShogiUI.Sockets/appsettings.json
index 1b50ee6..9d9f40f 100644
--- a/Gameboard.ShogiUI.Sockets/appsettings.json
+++ b/Gameboard.ShogiUI.Sockets/appsettings.json
@@ -1,10 +1,9 @@
{
"AppSettings": {
- "IdentityServer": "https://identity.lucaserver.space/",
- "GameboardShogiApi": "https://dev.lucaserver.space/Gameboard.Shogi.Api/",
- "ClientId": "DevClientId",
- "ClientSecret": "DevSecret",
- "Scope": "DevEnvironment"
+ "CouchDB": {
+ "Database": "shogi-dev",
+ "Url": "http://192.168.1.15:5984"
+ }
},
"Logging": {
"LogLevel": {
diff --git a/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs b/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs
index 7145e5e..8cf4734 100644
--- a/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs
+++ b/Gameboard.ShogiUI.UnitTests/Rules/BoardStateExtensions.cs
@@ -13,7 +13,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
var name = self.WhichPiece switch
{
WhichPiece.King => " K ",
- WhichPiece.GoldenGeneral => " G ",
+ WhichPiece.GoldGeneral => " G ",
WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ",
WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ",
WhichPiece.Rook => self.IsPromoted ? "^R " : " R ",
diff --git a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
index fbe26b3..c558571 100644
--- a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
+++ b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
@@ -26,9 +26,9 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
board[0, 0].WhichPiece.Should().Be(WhichPiece.Lance);
board[1, 0].WhichPiece.Should().Be(WhichPiece.Knight);
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[5, 0].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
+ board[5, 0].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board[6, 0].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board[7, 0].WhichPiece.Should().Be(WhichPiece.Knight);
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[1, 8].WhichPiece.Should().Be(WhichPiece.Knight);
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[5, 8].WhichPiece.Should().Be(WhichPiece.GoldenGeneral);
+ board[5, 8].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
board[6, 8].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
board[7, 8].WhichPiece.Should().Be(WhichPiece.Knight);
board[8, 8].WhichPiece.Should().Be(WhichPiece.Lance);
@@ -256,23 +256,23 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
// Act | Assert - It is P1 turn
/// try illegally placing Knight from the hand.
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();
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
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();
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
shogi.Board[7, 1].Should().BeNull();
/// 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();
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
shogi.Board[7, 0].Should().BeNull();
/// 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();
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
shogi.Board[7, 0].Should().BeNull();
@@ -312,7 +312,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
shogi.Hands[WhichPlayer.Player1].Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
// Act - P1 tries to place a Lance while in check.
- var dropSuccess = shogi.Move(new Move { PieceFromCaptured = WhichPiece.Lance, To = new Vector2(4, 4) });
+ var dropSuccess = shogi.Move(new Move { PieceFromHand = WhichPiece.Lance, To = new Vector2(4, 4) });
// Assert
dropSuccess.Should().BeFalse();
@@ -347,7 +347,7 @@ namespace Gameboard.ShogiUI.UnitTests.Rules
shogi.Board[4, 0].Should().NotBeNull();
// 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
dropSuccess.Should().BeFalse();
diff --git a/PathFinding/IPlanarCollection.cs b/PathFinding/IPlanarCollection.cs
index d276b12..48b20e7 100644
--- a/PathFinding/IPlanarCollection.cs
+++ b/PathFinding/IPlanarCollection.cs
@@ -4,7 +4,7 @@ namespace PathFinding
{
public interface IPlanarCollection : IEnumerable where T : IPlanarElement
{
- T this[float x, float y] { get; set; }
+ T? this[float x, float y] { get; set; }
int GetLength(int dimension);
}
}
diff --git a/PathFinding/PathFinding.csproj b/PathFinding/PathFinding.csproj
index ee29921..4466c3d 100644
--- a/PathFinding/PathFinding.csproj
+++ b/PathFinding/PathFinding.csproj
@@ -4,6 +4,7 @@
net5.0
true
5
+ enable