diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs
deleted file mode 100644
index 8fdbfe4..0000000
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSession.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using System.Collections.Generic;
-
-namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api
-{
- public class GetSessionResponse
- {
- public Game Game { get; set; }
- ///
- /// The perspective on the game of the requesting user.
- ///
- public WhichPerspective PlayerPerspective { get; set; }
- public BoardState BoardState { get; set; }
- public IList MoveHistory { get; set; }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs
index cc972bc..5fa50d1 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Api/GetSessions.cs
@@ -5,7 +5,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api
{
public class GetSessionsResponse
{
- public Collection PlayerHasJoinedSessions { get; set; }
- public Collection AllOtherSessions { get; set; }
+ public Collection PlayerHasJoinedSessions { get; set; }
+ public Collection AllOtherSessions { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs
index e1b59bb..71fdeca 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Socket/CreateGame.cs
@@ -6,7 +6,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
public class CreateGameResponse : IResponse
{
public string Action { get; }
- public Game Game { get; set; }
+ public Session Game { get; set; }
///
/// The player who created the game.
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs
index b6d0a98..398ba4a 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/BoardState.cs
@@ -8,7 +8,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
public Dictionary Board { get; set; } = new Dictionary();
public IReadOnlyCollection Player1Hand { get; set; } = Array.Empty();
public IReadOnlyCollection Player2Hand { get; set; } = Array.Empty();
- public WhichPerspective? PlayerInCheck { get; set; }
- public WhichPerspective WhoseTurn { get; set; }
+ public WhichPlayer? PlayerInCheck { get; set; }
+ public WhichPlayer WhoseTurn { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs
deleted file mode 100644
index 5b70ad8..0000000
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Game.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Collections.Generic;
-
-namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
-{
- public class Game
- {
- public string Player1 { get; set; }
- public string? Player2 { get; set; }
- public string GameName { get; set; } = string.Empty;
-
- ///
- /// Players[0] is the session owner, Players[1] is the other person.
- ///
- public IReadOnlyList Players
- {
- get
- {
- var list = new List(2) { Player1 };
- if (!string.IsNullOrEmpty(Player2)) list.Add(Player2);
- return list;
- }
- }
-
- ///
- /// Constructor for serialization.
- ///
- public Game()
- {
- }
-
- public Game(string gameName, string player1, string? player2 = null)
- {
- GameName = gameName;
- Player1 = player1;
- Player2 = player2;
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs
index 8e28d04..1c0ee78 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Piece.cs
@@ -4,6 +4,6 @@
{
public bool IsPromoted { get; set; }
public WhichPiece WhichPiece { get; set; }
- public WhichPerspective Owner { get; set; }
+ public WhichPlayer Owner { get; set; }
}
}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Session.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Session.cs
new file mode 100644
index 0000000..a2426c8
--- /dev/null
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/Session.cs
@@ -0,0 +1,10 @@
+namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
+{
+ public class Session
+ {
+ public string Player1 { get; set; }
+ public string? Player2 { get; set; }
+ public string GameName { get; set; }
+ public BoardState BoardState { get; set; }
+ }
+}
diff --git a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs
index cf8a4c4..3fb839b 100644
--- a/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs
+++ b/Gameboard.ShogiUI.Sockets.ServiceModels/Types/WhichPerspective.cs
@@ -1,9 +1,8 @@
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
{
- public enum WhichPerspective
+ public enum WhichPlayer
{
Player1,
- Player2,
- Spectator
+ Player2
}
}
diff --git a/Gameboard.ShogiUI.Sockets.sln b/Gameboard.ShogiUI.Sockets.sln
index 3b54d4e..82bd209 100644
--- a/Gameboard.ShogiUI.Sockets.sln
+++ b/Gameboard.ShogiUI.Sockets.sln
@@ -13,13 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.xUnitTests", "Gameboard.ShogiUI.xUnitTests\Gameboard.ShogiUI.xUnitTests.csproj", "{12530716-C11E-40CE-9F71-CCCC243F03E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi.Domain.UnitTests", "Shogi.Domain.UnitTests\Shogi.Domain.UnitTests.csproj", "{F256989E-B6AF-4731-9DB4-88991C40B2CE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain.UnitTests", "Shogi.Domain.UnitTests\Shogi.Domain.UnitTests.csproj", "{F256989E-B6AF-4731-9DB4-88991C40B2CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -43,10 +41,6 @@ Global
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU
- {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A0AC8C5A-6ADA-45C6-BD1E-EB1061213E47}.Release|Any CPU.Build.0 = Release|Any CPU
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
index 0f76e3b..15fe9cf 100644
--- a/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
+++ b/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
@@ -1,5 +1,4 @@
-using Gameboard.ShogiUI.Sockets.Extensions;
-using Gameboard.ShogiUI.Sockets.Managers;
+using Gameboard.ShogiUI.Sockets.Managers;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
@@ -7,10 +6,8 @@ using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using System.Security.Claims;
using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets.Controllers
@@ -169,25 +166,48 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
return NotFound();
}
- var playerPerspective = WhichPerspective.Spectator;
- if (session.Player1.Id == user.Id)
- {
- playerPerspective = WhichPerspective.Player1;
- }
- else if (session.Player2?.Id == user.Id)
- {
- playerPerspective = WhichPerspective.Player2;
- }
+ var playerPerspective = session.Player2Name == user.Id
+ ? WhichPlayer.Player2
+ : WhichPlayer.Player1;
- communicationManager.SubscribeToGame(session, user!.Id);
- var response = new GetSessionResponse()
+ var response = new Session
{
- Game = new Models.SessionMetadata(session).ToServiceModel(),
- BoardState = session.Shogi.ToServiceModel(),
- MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(),
- PlayerPerspective = playerPerspective
+ BoardState = new BoardState
+ {
+ Board = null,
+ Player1Hand = session.Player1Hand.Select(MapPiece).ToList(),
+ Player2Hand = session.Player2Hand.Select(MapPiece).ToList(),
+ PlayerInCheck = session.InCheck.HasValue ? Map(session.InCheck.Value) : null
+ },
+ GameName = session.Name,
+ Player1 = session.Player1Name,
+ Player2 = session.Player2Name
};
- return new JsonResult(response);
+ return this.Ok(response);
+
+ static WhichPlayer Map(Shogi.Domain.WhichPlayer whichPlayer)
+ {
+ return whichPlayer == Shogi.Domain.WhichPlayer.Player1
+ ? WhichPlayer.Player1
+ : WhichPlayer.Player2;
+ }
+ static Piece MapPiece(Shogi.Domain.Pieces.Piece piece)
+ {
+ var owner = Map(piece.Owner);
+ var whichPiece = piece.WhichPiece switch
+ {
+ Shogi.Domain.WhichPiece.King => WhichPiece.King,
+ Shogi.Domain.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
+ Shogi.Domain.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
+ Shogi.Domain.WhichPiece.Bishop => WhichPiece.Bishop,
+ Shogi.Domain.WhichPiece.Rook => WhichPiece.Rook,
+ Shogi.Domain.WhichPiece.Knight => WhichPiece.Knight,
+ Shogi.Domain.WhichPiece.Lance => WhichPiece.Lance,
+ Shogi.Domain.WhichPiece.Pawn => WhichPiece.Pawn,
+ _ => throw new ArgumentException($"Unknown value for {nameof(WhichPiece)}")
+ };
+ return new Piece { IsPromoted = piece.IsPromoted, Owner = owner, WhichPiece = whichPiece };
+ }
}
[HttpGet]
@@ -207,8 +227,8 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
return new GetSessionsResponse
{
- PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser),
- AllOtherSessions = new Collection(sessionsNotJoinedByUser)
+ PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser),
+ AllOtherSessions = new Collection(sessionsNotJoinedByUser)
};
}
diff --git a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
index 6f442bd..4824284 100644
--- a/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
+++ b/Gameboard.ShogiUI.Sockets/Controllers/SocketController.cs
@@ -1,6 +1,5 @@
using Gameboard.ShogiUI.Sockets.Extensions;
using Gameboard.ShogiUI.Sockets.Managers;
-using Gameboard.ShogiUI.Sockets.Models;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
using Microsoft.AspNetCore.Authentication;
@@ -56,7 +55,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
var userId = User?.UserId();
if (!string.IsNullOrEmpty(userId))
{
- connectionManager.UnsubscribeFromBroadcastAndGames(userId);
+ connectionManager.Unsubscribe(userId);
}
await signoutTask;
diff --git a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs b/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs
deleted file mode 100644
index f9ce7f9..0000000
--- a/Gameboard.ShogiUI.Sockets/Extensions/ModelExtensions.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace Gameboard.ShogiUI.Sockets.Extensions
-{
- public static class ModelExtensions
- {
- public static string GetShortName(this Models.Piece self)
- {
- var name = self.WhichPiece switch
- {
- WhichPiece.King => " K ",
- WhichPiece.GoldGeneral => " G ",
- WhichPiece.SilverGeneral => self.IsPromoted ? "^S " : " S ",
- WhichPiece.Bishop => self.IsPromoted ? "^B " : " B ",
- WhichPiece.Rook => self.IsPromoted ? "^R " : " R ",
- WhichPiece.Knight => self.IsPromoted ? "^k " : " k ",
- WhichPiece.Lance => self.IsPromoted ? "^L " : " L ",
- WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ",
- _ => " ? ",
- };
- if (self.Owner == WhichPerspective.Player2)
- name = Regex.Replace(name, @"([^\s]+)\s", "$1.");
- return name;
- }
-
- public static string PrintStateAsAscii(this Models.Shogi self)
- {
- var builder = new StringBuilder();
- builder.Append(" Player 2(.)");
- builder.AppendLine();
- for (var y = 8; y >= 0; y--)
- {
- builder.Append("- ");
- for (var x = 0; x < 8; x++) builder.Append("- - ");
- builder.Append("- -");
- builder.AppendLine();
- builder.Append('|');
- for (var x = 0; x < 9; x++)
- {
- var piece = self.Board[x, y];
- if (piece == null)
- {
- builder.Append(" ");
- }
- else
- {
- builder.AppendFormat("{0}", piece.GetShortName());
- }
- builder.Append('|');
- }
- builder.AppendLine();
- }
- builder.Append("- ");
- for (var x = 0; x < 8; x++) builder.Append("- - ");
- builder.Append("- -");
- builder.AppendLine();
- builder.Append(" Player 1");
- return builder.ToString();
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
index 4ba5c59..e9540eb 100644
--- a/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
+++ b/Gameboard.ShogiUI.Sockets/Gameboard.ShogiUI.Sockets.csproj
@@ -21,9 +21,12 @@
-
+
+
+
+
diff --git a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
index 0b2a5b8..ecace67 100644
--- a/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
+++ b/Gameboard.ShogiUI.Sockets/Managers/SocketConnectionManager.cs
@@ -1,5 +1,4 @@
using Gameboard.ShogiUI.Sockets.Extensions;
-using Gameboard.ShogiUI.Sockets.Models;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -14,12 +13,8 @@ namespace Gameboard.ShogiUI.Sockets.Managers
public interface ISocketConnectionManager
{
Task BroadcastToAll(IResponse response);
- //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);
- void UnsubscribeFromGame(string gameName, string playerName);
+ void Subscribe(WebSocket socket, string playerName);
+ void Unsubscribe(string playerName);
Task BroadcastToPlayers(IResponse response, params string?[] playerNames);
}
@@ -31,59 +26,23 @@ namespace Gameboard.ShogiUI.Sockets.Managers
/// Dictionary key is player name.
private readonly ConcurrentDictionary connections;
/// Dictionary key is game name.
- private readonly ConcurrentDictionary sessions;
private readonly ILogger logger;
public SocketConnectionManager(ILogger logger)
{
this.logger = logger;
connections = new ConcurrentDictionary();
- sessions = new ConcurrentDictionary();
}
- public void SubscribeToBroadcast(WebSocket socket, string playerName)
+ public void Subscribe(WebSocket socket, string playerName)
{
connections.TryRemove(playerName, out var _);
connections.TryAdd(playerName, socket);
}
- public void UnsubscribeFromBroadcastAndGames(string playerName)
+ public void Unsubscribe(string playerName)
{
connections.TryRemove(playerName, out _);
- foreach (var kvp in sessions)
- {
- var sessionName = kvp.Key;
- UnsubscribeFromGame(sessionName, playerName);
- }
- }
-
- ///
- /// Unsubscribes the player from their current game, then subscribes to the new game.
- ///
- public void SubscribeToGame(Session session, string playerName)
- {
- // Unsubscribe from any other games
- foreach (var kvp in sessions)
- {
- var gameNameKey = kvp.Key;
- UnsubscribeFromGame(gameNameKey, playerName);
- }
-
- // Subscribe
- if (connections.TryGetValue(playerName, out var socket))
- {
- var s = sessions.GetOrAdd(session.Name, session);
- s.Subscriptions.TryAdd(playerName, socket);
- }
- }
-
- public void UnsubscribeFromGame(string gameName, string playerName)
- {
- if (sessions.TryGetValue(gameName, out var s))
- {
- s.Subscriptions.TryRemove(playerName, out _);
- if (s.Subscriptions.IsEmpty) sessions.TryRemove(gameName, out _);
- }
}
public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames)
@@ -113,15 +72,15 @@ namespace Gameboard.ShogiUI.Sockets.Managers
tasks.Add(socket.SendTextAsync(message));
}
- catch (WebSocketException webSocketException)
+ catch (WebSocketException)
{
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
- UnsubscribeFromBroadcastAndGames(kvp.Key);
+ Unsubscribe(kvp.Key);
}
- catch (Exception exception)
+ catch
{
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
- UnsubscribeFromBroadcastAndGames(kvp.Key);
+ Unsubscribe(kvp.Key);
}
}
try
@@ -129,34 +88,11 @@ namespace Gameboard.ShogiUI.Sockets.Managers
var task = Task.WhenAll(tasks);
return task;
}
- catch (Exception e)
+ catch
{
Console.WriteLine("Yo");
}
return Task.FromResult(0);
}
-
- //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;
- //}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Models/MoveSets.cs b/Gameboard.ShogiUI.Sockets/Models/MoveSets.cs
deleted file mode 100644
index 5f4ee8e..0000000
--- a/Gameboard.ShogiUI.Sockets/Models/MoveSets.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using PathFinding;
-using System.Collections.Generic;
-
-namespace Gameboard.ShogiUI.Sockets.Models
-{
- public static class MoveSets
- {
- public static readonly List King = new(8)
- {
- new PathFinding.Move(Direction.Up),
- new PathFinding.Move(Direction.Left),
- new PathFinding.Move(Direction.Right),
- new PathFinding.Move(Direction.Down),
- new PathFinding.Move(Direction.UpLeft),
- new PathFinding.Move(Direction.UpRight),
- new PathFinding.Move(Direction.DownLeft),
- new PathFinding.Move(Direction.DownRight)
- };
-
- public static readonly List Bishop = new(4)
- {
- new PathFinding.Move(Direction.UpLeft, Distance.MultiStep),
- new PathFinding.Move(Direction.UpRight, Distance.MultiStep),
- new PathFinding.Move(Direction.DownLeft, Distance.MultiStep),
- new PathFinding.Move(Direction.DownRight, Distance.MultiStep)
- };
-
- public static readonly List PromotedBishop = new(8)
- {
- new PathFinding.Move(Direction.Up),
- new PathFinding.Move(Direction.Left),
- new PathFinding.Move(Direction.Right),
- new PathFinding.Move(Direction.Down),
- new PathFinding.Move(Direction.UpLeft, Distance.MultiStep),
- new PathFinding.Move(Direction.UpRight, Distance.MultiStep),
- new PathFinding.Move(Direction.DownLeft, Distance.MultiStep),
- new PathFinding.Move(Direction.DownRight, Distance.MultiStep)
- };
-
- public static readonly List GoldGeneral = new(6)
- {
- new PathFinding.Move(Direction.Up),
- new PathFinding.Move(Direction.UpLeft),
- new PathFinding.Move(Direction.UpRight),
- new PathFinding.Move(Direction.Left),
- new PathFinding.Move(Direction.Right),
- new PathFinding.Move(Direction.Down)
- };
-
- public static readonly List Knight = new(2)
- {
- new PathFinding.Move(Direction.KnightLeft),
- new PathFinding.Move(Direction.KnightRight)
- };
-
- public static readonly List Lance = new(1)
- {
- new PathFinding.Move(Direction.Up, Distance.MultiStep),
- };
-
- public static readonly List Pawn = new(1)
- {
- new PathFinding.Move(Direction.Up)
- };
-
- public static readonly List Rook = new(4)
- {
- new PathFinding.Move(Direction.Up, Distance.MultiStep),
- new PathFinding.Move(Direction.Left, Distance.MultiStep),
- new PathFinding.Move(Direction.Right, Distance.MultiStep),
- new PathFinding.Move(Direction.Down, Distance.MultiStep)
- };
-
- public static readonly List PromotedRook = new(8)
- {
- new PathFinding.Move(Direction.Up, Distance.MultiStep),
- new PathFinding.Move(Direction.Left, Distance.MultiStep),
- new PathFinding.Move(Direction.Right, Distance.MultiStep),
- new PathFinding.Move(Direction.Down, Distance.MultiStep),
- new PathFinding.Move(Direction.UpLeft),
- new PathFinding.Move(Direction.UpRight),
- new PathFinding.Move(Direction.DownLeft),
- new PathFinding.Move(Direction.DownRight)
- };
-
- public static readonly List SilverGeneral = new(4)
- {
- new PathFinding.Move(Direction.Up),
- new PathFinding.Move(Direction.UpLeft),
- new PathFinding.Move(Direction.UpRight),
- new PathFinding.Move(Direction.DownLeft),
- new PathFinding.Move(Direction.DownRight)
- };
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Models/Piece.cs b/Gameboard.ShogiUI.Sockets/Models/Piece.cs
deleted file mode 100644
index 7b86808..0000000
--- a/Gameboard.ShogiUI.Sockets/Models/Piece.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using PathFinding;
-using System.Diagnostics;
-
-namespace Gameboard.ShogiUI.Sockets.Models
-{
- [DebuggerDisplay("{WhichPiece} {Owner}")]
- public class Piece : IPlanarElement
- {
- public WhichPiece WhichPiece { get; }
- public WhichPerspective Owner { get; private set; }
- public bool IsPromoted { get; private set; }
- public bool IsUpsideDown => Owner == WhichPerspective.Player2;
-
- public Piece(WhichPiece piece, WhichPerspective owner, bool isPromoted = false)
- {
- WhichPiece = piece;
- Owner = owner;
- IsPromoted = isPromoted;
- }
- public Piece(Piece piece) : this(piece.WhichPiece, piece.Owner, piece.IsPromoted)
- {
- }
-
- public bool CanPromote => !IsPromoted
- && WhichPiece != WhichPiece.King
- && WhichPiece != WhichPiece.GoldGeneral;
-
- public void ToggleOwnership()
- {
- Owner = Owner == WhichPerspective.Player1
- ? WhichPerspective.Player2
- : WhichPerspective.Player1;
- }
-
- public void Promote() => IsPromoted = CanPromote;
-
- public void Demote() => IsPromoted = false;
-
- public void Capture()
- {
- ToggleOwnership();
- Demote();
- }
-
- // TODO: There is no reason to make "new" MoveSets every time this property is accessed.
- public MoveSet MoveSet => WhichPiece switch
- {
- WhichPiece.King => new MoveSet(this, MoveSets.King),
- WhichPiece.GoldGeneral => new MoveSet(this, MoveSets.GoldGeneral),
- WhichPiece.SilverGeneral => new MoveSet(this, IsPromoted ? MoveSets.GoldGeneral : MoveSets.SilverGeneral),
- WhichPiece.Bishop => new MoveSet(this, IsPromoted ? MoveSets.PromotedBishop : MoveSets.Bishop),
- WhichPiece.Rook => new MoveSet(this, IsPromoted ? MoveSets.PromotedRook : MoveSets.Rook),
- WhichPiece.Knight => new MoveSet(this, IsPromoted ? MoveSets.GoldGeneral : MoveSets.Knight),
- WhichPiece.Lance => new MoveSet(this, IsPromoted ? MoveSets.GoldGeneral : MoveSets.Lance),
- WhichPiece.Pawn => new MoveSet(this, IsPromoted ? MoveSets.GoldGeneral : MoveSets.Pawn),
- _ => throw new System.NotImplementedException()
- };
-
- public ServiceModels.Types.Piece ToServiceModel()
- {
- return new ServiceModels.Types.Piece
- {
- IsPromoted = IsPromoted,
- Owner = Owner,
- WhichPiece = WhichPiece
- };
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Models/Session.cs b/Gameboard.ShogiUI.Sockets/Models/Session.cs
deleted file mode 100644
index fd73b40..0000000
--- a/Gameboard.ShogiUI.Sockets/Models/Session.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using Newtonsoft.Json;
-using System.Collections.Concurrent;
-using System.Net.WebSockets;
-
-namespace Gameboard.ShogiUI.Sockets.Models
-{
- public class Session
- {
- // TODO: Separate subscriptions to the Session from the Session.
- [JsonIgnore] public ConcurrentDictionary Subscriptions { get; }
- public string Name { get; }
- public User Player1 { get; }
- public User? Player2 { get; private set; }
- public bool IsPrivate { get; }
-
- // TODO: Don't retain the entire rules system within the Session model. It just needs the board state after rules are applied.
- public Shogi Shogi { get; }
-
- public Session(string name, bool isPrivate, Shogi shogi, User player1, User? player2 = null)
- {
- Subscriptions = new ConcurrentDictionary();
-
- Name = name;
- Player1 = player1;
- Player2 = player2;
- IsPrivate = isPrivate;
- Shogi = shogi;
- }
-
- public void SetPlayer2(User user)
- {
- Player2 = user;
- }
-
- public Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs b/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs
index 350b273..9747608 100644
--- a/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs
+++ b/Gameboard.ShogiUI.Sockets/Models/SessionMetadata.cs
@@ -32,6 +32,6 @@
public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id;
- public ServiceModels.Types.Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
+ public ServiceModels.Types.Session ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Models/Shogi.cs b/Gameboard.ShogiUI.Sockets/Models/Shogi.cs
deleted file mode 100644
index ec944a2..0000000
--- a/Gameboard.ShogiUI.Sockets/Models/Shogi.cs
+++ /dev/null
@@ -1,463 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using Gameboard.ShogiUI.Sockets.Utilities;
-using PathFinding;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-
-namespace Gameboard.ShogiUI.Sockets.Models
-{
- ///
- /// Facilitates Shogi board state transitions, cognisant of Shogi rules.
- /// The board is always from Player1's perspective.
- /// [0,0] is the lower-left position, [8,8] is the higher-right position
- ///
- public class Shogi
- {
- private delegate void MoveSetCallback(Piece piece, Vector2 position);
- private readonly PathFinder2D pathFinder;
- private Shogi? validationBoard;
- private Vector2 player1King;
- private Vector2 player2King;
- private List Hand => WhoseTurn == WhichPerspective.Player1 ? Player1Hand : Player2Hand;
- public List Player1Hand { get; }
- public List Player2Hand { get; }
- public CoordsToNotationCollection Board { get; } //TODO: Hide this being a getter method
- public List MoveHistory { get; }
- public WhichPerspective WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPerspective.Player1 : WhichPerspective.Player2;
- public WhichPerspective? InCheck { get; private set; }
- public bool IsCheckmate { get; private set; }
-
- public string Error { get; private set; }
-
- public Shogi()
- {
- Board = new CoordsToNotationCollection();
- MoveHistory = new List(20);
- Player1Hand = new List();
- Player2Hand = new List();
- pathFinder = new PathFinder2D(Board, 9, 9);
- player1King = new Vector2(4, 0);
- player2King = new Vector2(4, 8);
- Error = string.Empty;
-
- InitializeBoardState();
- }
-
- public Shogi(IList moves) : this()
- {
- for (var i = 0; i < moves.Count; i++)
- {
- if (!Move(moves[i]))
- {
- // Todo: Add some smarts to know why a move was invalid. In check? Piece not found? etc.
- throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}. {Error}");
- }
- }
- }
-
- private Shogi(Shogi toCopy)
- {
- Board = new CoordsToNotationCollection();
- foreach (var kvp in toCopy.Board)
- {
- Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value);
- }
-
- pathFinder = new PathFinder2D(Board, 9, 9);
- MoveHistory = new List(toCopy.MoveHistory);
- Player1Hand = new List(toCopy.Player1Hand);
- Player2Hand = new List(toCopy.Player2Hand);
- player1King = toCopy.player1King;
- player2King = toCopy.player2King;
- Error = toCopy.Error;
- }
-
- public bool Move(Move move)
- {
- var otherPlayer = WhoseTurn == WhichPerspective.Player1 ? WhichPerspective.Player2 : WhichPerspective.Player1;
- var moveSuccess = TryMove(move);
-
- if (!moveSuccess)
- {
- return false;
- }
-
- // Evaluate check
- if (EvaluateCheckAfterMove(move, otherPlayer))
- {
- InCheck = otherPlayer;
- IsCheckmate = EvaluateCheckmate();
- }
- else
- {
- InCheck = null;
- }
- return true;
- }
- ///
- /// Attempts a given move. Returns false if the move is illegal.
- ///
- private bool TryMove(Move move)
- {
- // Try making the move in a "throw away" board.
- if (validationBoard == null)
- {
- validationBoard = new Shogi(this);
- }
-
- var isValid = move.PieceFromHand.HasValue
- ? validationBoard.PlaceFromHand(move)
- : validationBoard.PlaceFromBoard(move);
- if (!isValid)
- {
- // Surface the error description.
- Error = validationBoard.Error;
- // Invalidate the "throw away" board.
- validationBoard = null;
- return false;
- }
- // If already in check, assert the move that resulted in check no longer results in check.
- if (InCheck == WhoseTurn)
- {
- if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn))
- {
- // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn;
- return false;
- }
- }
-
- // The move is valid and legal; update board state.
- if (move.PieceFromHand.HasValue) PlaceFromHand(move);
- else PlaceFromBoard(move);
- return true;
- }
- /// True if the move was successful.
- private bool PlaceFromHand(Move move)
- {
- var index = Hand.FindIndex(p => p.WhichPiece == move.PieceFromHand);
- if (index < 0)
- {
- Error = $"{move.PieceFromHand} does not exist in the hand.";
- return false;
- }
- if (Board[move.To] != null)
- {
- Error = $"Illegal move - attempting to capture while playing a piece from the hand.";
- return false;
- }
-
- switch (move.PieceFromHand!.Value)
- {
- case WhichPiece.Knight:
- {
- // Knight cannot be placed onto the farthest two ranks from the hand.
- if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y > 6)
- || (WhoseTurn == WhichPerspective.Player2 && move.To.Y < 2))
- {
- Error = $"Knight has no valid moves after placed.";
- return false;
- }
- break;
- }
- case WhichPiece.Lance:
- case WhichPiece.Pawn:
- {
- // Lance and Pawn cannot be placed onto the farthest rank from the hand.
- if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y == 8)
- || (WhoseTurn == WhichPerspective.Player2 && move.To.Y == 0))
- {
- Error = $"{move.PieceFromHand} has no valid moves after placed.";
- return false;
- }
- break;
- }
- }
-
- // Mutate the board.
- Board[move.To] = Hand[index];
- Hand.RemoveAt(index);
- MoveHistory.Add(move);
-
- return true;
- }
- /// True if the move was successful.
- private bool PlaceFromBoard(Move move)
- {
- var fromPiece = Board[move.From!.Value];
- if (fromPiece == null)
- {
- Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
- return false; // Invalid move
- }
- if (fromPiece.Owner != WhoseTurn)
- {
- Error = "Not allowed to move the opponents piece";
- return false; // Invalid move; cannot move other players pieces.
- }
- 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.
- }
-
- var captured = Board[move.To];
- if (captured != null)
- {
- if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece.
- captured.Capture();
- Hand.Add(captured);
- }
-
- //Mutate the board.
- if (move.IsPromotion)
- {
- if (WhoseTurn == WhichPerspective.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5))
- {
- fromPiece.Promote();
- }
- else if (WhoseTurn == WhichPerspective.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3))
- {
- fromPiece.Promote();
- }
- }
- Board[move.To] = fromPiece;
- Board[move.From!.Value] = null;
- if (fromPiece.WhichPiece == WhichPiece.King)
- {
- if (fromPiece.Owner == WhichPerspective.Player1)
- {
- player1King.X = move.To.X;
- player1King.Y = move.To.Y;
- }
- else if (fromPiece.Owner == WhichPerspective.Player2)
- {
- player2King.X = move.To.X;
- player2King.Y = move.To.Y;
- }
- }
- MoveHistory.Add(move);
- return true;
- }
-
- private bool IsPathable(Vector2 from, Vector2 to)
- {
- var piece = Board[from];
- if (piece == null) return false;
-
- var isObstructed = false;
- var isPathable = pathFinder.PathTo(from, to, (other, position) =>
- {
- if (other.Owner == piece.Owner) isObstructed = true;
- });
- return !isObstructed && isPathable;
- }
-
- #region Rules Validation
- private bool EvaluateCheckAfterMove(Move move, WhichPerspective WhichPerspective)
- {
- if (WhichPerspective == InCheck) return true; // If we already know the player is in check, don't bother.
-
- var isCheck = false;
- var kingPosition = WhichPerspective == WhichPerspective.Player1 ? player1King : player2King;
-
- // Check if the move put the king in check.
- if (pathFinder.PathTo(move.To, kingPosition)) return true;
-
- if (move.From.HasValue)
- {
- // Get line equation from king through the now-unoccupied location.
- 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.
- // if absolute slope is 0°, look for lance along the line.
- if (float.IsInfinity(slope))
- {
- // if slope of the move is also infinity...can skip this?
- pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
- {
- if (piece.Owner != WhichPerspective)
- {
- switch (piece.WhichPiece)
- {
- case WhichPiece.Rook:
- isCheck = true;
- break;
- case WhichPiece.Lance:
- if (!piece.IsPromoted) isCheck = true;
- break;
- }
- }
- });
- }
- else if (slope == 1)
- {
- pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
- {
- if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Bishop)
- {
- isCheck = true;
- }
- });
- }
- else if (slope == 0)
- {
- pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
- {
- if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Rook)
- {
- isCheck = true;
- }
- });
- }
- }
- else
- {
- // TODO: Check for illegal move from hand. It is illegal to place from the hand such that you check-mate your opponent.
- // Go read the shogi rules to be sure this is true.
- }
-
- return isCheck;
- }
- private bool EvaluateCheckmate()
- {
- if (!InCheck.HasValue) return false;
-
- // Assume true and try to disprove.
- var isCheckmate = true;
- Board.ForEachNotNull((piece, from) => // For each piece...
- {
- // Short circuit
- if (!isCheckmate) return;
-
- if (piece.Owner == InCheck) // ...owned by the player in check...
- {
- // ...evaluate if any move gets the player out of check.
- pathFinder.PathEvery(from, (other, position) =>
- {
- if (validationBoard == null) validationBoard = new Shogi(this);
- var moveToTry = new Move(from, position);
- var moveSuccess = validationBoard.TryMove(moveToTry);
- if (moveSuccess)
- {
- validationBoard = null;
- if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value))
- {
- isCheckmate = false;
- }
- }
- });
- }
- });
- return isCheckmate;
- }
- #endregion
-
- private void InitializeBoardState()
- {
- Board["A1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1);
- Board["B1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1);
- Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1);
- Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1);
- Board["E1"] = new Piece(WhichPiece.King, WhichPerspective.Player1);
- Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1);
- Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1);
- Board["H1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1);
- Board["I1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1);
-
- Board["A2"] = null;
- Board["B2"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player1);
- Board["C2"] = null;
- Board["D2"] = null;
- Board["E2"] = null;
- Board["F2"] = null;
- Board["G2"] = null;
- Board["H2"] = new Piece(WhichPiece.Rook, WhichPerspective.Player1);
- Board["I2"] = null;
-
- Board["A3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["B3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["C3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["D3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["E3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["F3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["G3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["H3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
- Board["I3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
-
- Board["A4"] = null;
- Board["B4"] = null;
- Board["C4"] = null;
- Board["D4"] = null;
- Board["E4"] = null;
- Board["F4"] = null;
- Board["G4"] = null;
- Board["H4"] = null;
- Board["I4"] = null;
-
- Board["A5"] = null;
- Board["B5"] = null;
- Board["C5"] = null;
- Board["D5"] = null;
- Board["E5"] = null;
- Board["F5"] = null;
- Board["G5"] = null;
- Board["H5"] = null;
- Board["I5"] = null;
-
- Board["A6"] = null;
- Board["B6"] = null;
- Board["C6"] = null;
- Board["D6"] = null;
- Board["E6"] = null;
- Board["F6"] = null;
- Board["G6"] = null;
- Board["H6"] = null;
- Board["I6"] = null;
-
- Board["A7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["B7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["C7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["D7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["E7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["F7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["G7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["H7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
- Board["I7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
-
- Board["A8"] = null;
- Board["B8"] = new Piece(WhichPiece.Rook, WhichPerspective.Player2);
- Board["C8"] = null;
- Board["D8"] = null;
- Board["E8"] = null;
- Board["F8"] = null;
- Board["G8"] = null;
- Board["H8"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player2);
- Board["I8"] = null;
-
- Board["A9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2);
- Board["B9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2);
- Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2);
- Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2);
- Board["E9"] = new Piece(WhichPiece.King, WhichPerspective.Player2);
- Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2);
- Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2);
- Board["H9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2);
- Board["I9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2);
- }
-
- public BoardState ToServiceModel()
- {
- return new BoardState
- {
- Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()),
- PlayerInCheck = InCheck,
- WhoseTurn = WhoseTurn,
- Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(),
- Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList()
- };
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardStateDocument.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardStateDocument.cs
index ecd0450..ba76c72 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardStateDocument.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/BoardStateDocument.cs
@@ -1,65 +1,52 @@
-using Gameboard.ShogiUI.Sockets.Utilities;
+using Shogi.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Numerics;
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
{
- public class BoardStateDocument : CouchDocument
- {
- public string Name { get; set; }
+ public class BoardStateDocument : CouchDocument
+ {
+ public string Name { get; set; }
- ///
- /// A dictionary where the key is a board-notation position, like D3.
- ///
- public Dictionary Board { get; set; }
+ ///
+ /// A dictionary where the key is a board-notation position, like D3.
+ ///
+ public Dictionary Board { get; set; }
- public Piece[] Player1Hand { get; set; }
+ public Piece[] Player1Hand { get; set; }
- public Piece[] Player2Hand { 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; }
+ ///
+ /// 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 BoardStateDocument() : base(WhichDocumentType.BoardState)
- {
- Name = string.Empty;
- Board = new Dictionary(81, StringComparer.OrdinalIgnoreCase);
- Player1Hand = Array.Empty();
- Player2Hand = Array.Empty();
- }
+ ///
+ /// Default constructor and setters are for deserialization.
+ ///
+ public BoardStateDocument() : base(WhichDocumentType.BoardState)
+ {
+ Name = string.Empty;
+ Board = new Dictionary(81, StringComparer.OrdinalIgnoreCase);
+ Player1Hand = Array.Empty();
+ Player2Hand = Array.Empty();
+ }
- public BoardStateDocument(string sessionName, Models.Shogi shogi)
- : base($"{sessionName}-{DateTime.Now:O}", WhichDocumentType.BoardState)
- {
- Name = sessionName;
- Board = new Dictionary(81, StringComparer.OrdinalIgnoreCase);
+ public BoardStateDocument(string sessionName, Session shogi)
+ : base($"{sessionName}-{DateTime.Now:O}", WhichDocumentType.BoardState)
+ {
+ static Piece MapPiece(Shogi.Domain.Pieces.Piece piece)
+ {
+ return new Piece { IsPromoted = piece.IsPromoted, Owner = piece.Owner, WhichPiece = piece.WhichPiece };
+ }
- for (var x = 0; x < 9; x++)
- for (var y = 0; y < 9; y++)
- {
- var position = new Vector2(x, y);
- var piece = shogi.Board[y, x];
+ Name = sessionName;
+ Board = shogi.BoardState.ToDictionary(kvp => kvp.Key, kvp => kvp.Value == null ? null : MapPiece(kvp.Value));
- if (piece != null)
- {
- var positionNotation = NotationHelper.ToBoardNotation(position);
- Board[positionNotation] = new Piece(piece);
- }
- }
-
- Player1Hand = shogi.Player1Hand.Select(model => new Piece(model)).ToArray();
- Player2Hand = shogi.Player2Hand.Select(model => new Piece(model)).ToArray();
- if (shogi.MoveHistory.Count > 0)
- {
- Move = new Move(shogi.MoveHistory[^1]);
- }
- }
- }
+ Player1Hand = shogi.Player1Hand.Select(piece => MapPiece(piece)).ToArray();
+ Player2Hand = shogi.Player2Hand.Select(piece => MapPiece(piece)).ToArray();
+ }
+ }
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
index 8b29998..917fdf7 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Move.cs
@@ -1,5 +1,4 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using System.Numerics;
+using Shogi.Domain;
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
{
@@ -29,28 +28,5 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
{
To = string.Empty;
}
-
- public Move(Models.Move move)
- {
- if (move.From.HasValue)
- {
- From = ToBoardNotation(move.From.Value);
- }
- IsPromotion = move.IsPromotion;
- To = ToBoardNotation(move.To);
- PieceFromHand = move.PieceFromHand;
- }
-
- private static readonly char A = 'A';
- private static string ToBoardNotation(Vector2 vector)
- {
- var file = (char)(vector.X + A);
- var rank = vector.Y + 1;
- return $"{file}{rank}";
- }
-
- public Models.Move ToDomainModel() => PieceFromHand.HasValue
- ? new(PieceFromHand.Value, To)
- : new(From!, To, IsPromotion);
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
index 7f0be6f..e5df4fc 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/Piece.cs
@@ -1,27 +1,11 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
+using Shogi.Domain;
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
{
- public class Piece
- {
- public bool IsPromoted { get; set; }
- public WhichPerspective 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(WhichPiece, Owner, IsPromoted);
- }
+ public class Piece
+ {
+ public bool IsPromoted { get; set; }
+ public WhichPlayer Owner { get; set; }
+ public WhichPiece WhichPiece { get; set; }
+ }
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs
index 6db129e..75249e1 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/CouchModels/SessionDocument.cs
@@ -1,14 +1,11 @@
-using System.Collections.Generic;
-
-namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
+namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
{
- public class SessionDocument : CouchDocument
+ public class SessionDocument : CouchDocument
{
public string Name { get; set; }
public string Player1Id { get; set; }
public string? Player2Id { get; set; }
public bool IsPrivate { get; set; }
- public IList History { get; set; }
///
/// Default constructor and setters are for deserialization.
@@ -18,17 +15,6 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
Name = string.Empty;
Player1Id = string.Empty;
Player2Id = string.Empty;
- History = new List(0);
- }
-
- public SessionDocument(Models.Session session)
- : base(session.Name, WhichDocumentType.Session)
- {
- Name = session.Name;
- Player1Id = session.Player1.Id;
- Player2Id = session.Player2?.Id;
- IsPrivate = session.IsPrivate;
- History = new List(0);
}
public SessionDocument(Models.SessionMetadata sessionMetaData)
@@ -38,7 +24,6 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
Player1Id = sessionMetaData.Player1.Id;
Player2Id = sessionMetaData.Player2?.Id;
IsPrivate = sessionMetaData.IsPrivate;
- History = new List(0);
}
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
index 3489693..075e388 100644
--- a/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
+++ b/Gameboard.ShogiUI.Sockets/Repositories/GameboardRepository.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Shogi.Domain;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -11,282 +12,302 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
-using System.Web;
namespace Gameboard.ShogiUI.Sockets.Repositories
{
- public interface IGameboardRepository
- {
- Task CreateBoardState(Models.Session session);
- Task CreateSession(Models.SessionMetadata session);
- Task CreateUser(Models.User user);
- Task> ReadSessionMetadatas();
- Task ReadSession(string name);
- Task UpdateSession(Models.SessionMetadata session);
- Task ReadSessionMetaData(string name);
- Task ReadUser(string userName);
- }
+ public interface IGameboardRepository
+ {
+ Task CreateBoardState(Session session);
+ Task CreateSession(Models.SessionMetadata session);
+ Task CreateUser(Models.User user);
+ Task> ReadSessionMetadatas();
+ Task ReadSession(string name);
+ Task UpdateSession(Models.SessionMetadata session);
+ Task ReadSessionMetaData(string name);
+ Task ReadUser(string userName);
+ }
- public class GameboardRepository : IGameboardRepository
- {
- ///
- /// Returns session, board state, and user documents, grouped by session.
- ///
- private static readonly string View_SessionWithBoardState = "_design/session/_view/session-with-boardstate";
- ///
- /// Returns session and user documents, grouped by session.
- ///
- private static readonly string View_SessionMetadata = "_design/session/_view/session-metadata";
- private static readonly string View_User = "_design/user/_view/user";
- private const string ApplicationJson = "application/json";
- private readonly HttpClient client;
- private readonly ILogger logger;
+ public class GameboardRepository : IGameboardRepository
+ {
+ ///
+ /// Returns session, board state, and user documents, grouped by session.
+ ///
+ private static readonly string View_SessionWithBoardState = "_design/session/_view/session-with-boardstate";
+ ///
+ /// Returns session and user documents, grouped by session.
+ ///
+ private static readonly string View_SessionMetadata = "_design/session/_view/session-metadata";
+ private static readonly string View_User = "_design/user/_view/user";
+ private const string ApplicationJson = "application/json";
+ private readonly HttpClient client;
+ private readonly ILogger logger;
- public GameboardRepository(IHttpClientFactory clientFactory, ILogger logger)
- {
- client = clientFactory.CreateClient("couchdb");
- this.logger = logger;
- }
+ public GameboardRepository(IHttpClientFactory clientFactory, ILogger logger)
+ {
+ client = clientFactory.CreateClient("couchdb");
+ this.logger = logger;
+ }
- public async Task> ReadSessionMetadatas()
- {
- var queryParams = new QueryBuilder { { "include_docs", "true" } }.ToQueryString();
- var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
- var responseContent = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject>(responseContent);
- if (result != null)
- {
- var groupedBySession = result.rows.GroupBy(row => row.id);
- var sessions = new List(result.total_rows / 3);
- foreach (var group in groupedBySession)
- {
- /**
+ public async Task> ReadSessionMetadatas()
+ {
+ var queryParams = new QueryBuilder { { "include_docs", "true" } }.ToQueryString();
+ var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ if (result != null)
+ {
+ var groupedBySession = result.rows.GroupBy(row => row.id);
+ var sessions = new List(result.total_rows / 3);
+ foreach (var group in groupedBySession)
+ {
+ /**
* A group contains 3 elements.
* 1) The session metadata.
* 2) User document of Player1.
* 3) User document of Player2.
*/
- var session = group.FirstOrDefault()?.doc.ToObject();
- var player1Doc = group.Skip(1).FirstOrDefault()?.doc.ToObject();
- var player2Doc = group.Skip(2).FirstOrDefault()?.doc.ToObject();
- if (session != null && player1Doc != null)
- {
- var player2 = player2Doc == null ? null : new Models.User(player2Doc);
- sessions.Add(new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2));
- }
- }
- return new Collection(sessions);
- }
- return new Collection(Array.Empty());
- }
+ var session = group.FirstOrDefault()?.doc.ToObject();
+ var player1Doc = group.Skip(1).FirstOrDefault()?.doc.ToObject();
+ var player2Doc = group.Skip(2).FirstOrDefault()?.doc.ToObject();
+ if (session != null && player1Doc != null)
+ {
+ var player2 = player2Doc == null ? null : new Models.User(player2Doc);
+ sessions.Add(new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2));
+ }
+ }
+ return new Collection(sessions);
+ }
+ return new Collection(Array.Empty());
+ }
- public async Task ReadSession(string name)
- {
- var queryParams = new QueryBuilder
- {
- { "include_docs", "true" },
- { "startkey", JsonConvert.SerializeObject(new [] {name}) },
- { "endkey", JsonConvert.SerializeObject(new object [] {name, int.MaxValue}) }
- }.ToQueryString();
- var query = $"{View_SessionWithBoardState}{queryParams}";
- logger.LogInformation("ReadSession() query: {query}", query);
- var response = await client.GetAsync(query);
- var responseContent = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject>(responseContent);
- if (result != null && result.rows.Length > 2)
- {
- var group = result.rows;
- /**
- * A group contains 3 type of elements.
- * 1) The session metadata.
- * 2) User documents of Player1 and Player2.
+ public async Task ReadSession(string name)
+ {
+ static Shogi.Domain.Pieces.Piece? MapPiece(Piece? piece)
+ {
+ return piece == null
+ ? null
+ : Shogi.Domain.Pieces.Piece.Create(piece.WhichPiece, piece.Owner, piece.IsPromoted);
+ }
+
+ var queryParams = new QueryBuilder
+ {
+ { "include_docs", "true" },
+ { "startkey", JsonConvert.SerializeObject(new [] {name}) },
+ { "endkey", JsonConvert.SerializeObject(new object [] {name, int.MaxValue}) }
+ }.ToQueryString();
+ var query = $"{View_SessionWithBoardState}{queryParams}";
+ logger.LogInformation("ReadSession() query: {query}", query);
+ var response = await client.GetAsync(query);
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ if (result != null && result.rows.Length > 2)
+ {
+ var group = result.rows;
+ /**
+ * A group contains multiple elements.
+ * 0) The session metadata.
+ * 1) User documents of Player1.
+ * 2) User documents of Player1.
* 2.a) If the Player2 document doesn't exist, CouchDB will return the SessionDocument instead :(
- * 3) BoardState
+ * Everything Else) Snapshots of the boardstate after every player move.
*/
- var session = group[0].doc.ToObject();
- var player1Doc = group[1].doc.ToObject();
- var group2DocumentType = group[2].doc.Property(nameof(UserDocument.DocumentType).ToCamelCase())?.Value.Value();
- var player2Doc = group2DocumentType == WhichDocumentType.User.ToString()
- ? group[2].doc.ToObject()
- : null;
- var moves = group
- .Skip(4) // Skip 4 because group[3] will not have a .Move property since it's the first/initial BoardState of the session.
- // TODO: Deserialize just the Move property.
- .Select(row => row.doc.ToObject())
- .Select(boardState =>
- {
- var move = boardState!.Move!;
- return move.PieceFromHand.HasValue
- ? new Models.Move(move.PieceFromHand.Value, move.To)
- : new Models.Move(move.From!, move.To, move.IsPromotion);
- })
- .ToList();
+ var session = group[0].doc.ToObject();
+ var player1Doc = group[1].doc.ToObject();
+ var group2DocumentType = group[2].doc.Property(nameof(UserDocument.DocumentType).ToCamelCase())?.Value.Value();
+ var player2Doc = group2DocumentType == WhichDocumentType.User.ToString()
+ ? group[2].doc.ToObject()
+ : null;
+ var boardState = group.Last().doc.ToObject();
- var shogi = new Models.Shogi(moves);
- if (session != null && player1Doc != null)
- {
- var player2 = player2Doc == null ? null : new Models.User(player2Doc);
- return new Models.Session(session.Name, session.IsPrivate, shogi, new(player1Doc), player2);
- }
- }
- return null;
- }
+ if (session != null && player1Doc != null && boardState != null)
+ {
+ var player2 = player2Doc == null ? null : new Models.User(player2Doc);
+ var metaData = new SessionMetadata(session.Name, session.IsPrivate, player1Doc.DisplayName, player2Doc?.DisplayName);
+ var shogiBoardState = new BoardState(boardState.Board.ToDictionary(kvp => kvp.Key, kvp => MapPiece(kvp.Value)));
+ return new Session(shogiBoardState, metaData);
+ }
+ }
+ return null;
+ }
- public async Task ReadSessionMetaData(string name)
- {
- var queryParams = new QueryBuilder
- {
- { "include_docs", "true" },
- { "startkey", JsonConvert.SerializeObject(new [] {name}) },
- { "endkey", JsonConvert.SerializeObject(new object [] {name, int.MaxValue}) }
- }.ToQueryString();
- var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
- var responseContent = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject>(responseContent);
- if (result != null && result.rows.Length > 2)
- {
- var group = result.rows;
- /**
+ public async Task ReadSessionMetaData(string name)
+ {
+ var queryParams = new QueryBuilder
+ {
+ { "include_docs", "true" },
+ { "startkey", JsonConvert.SerializeObject(new [] {name}) },
+ { "endkey", JsonConvert.SerializeObject(new object [] {name, int.MaxValue}) }
+ }.ToQueryString();
+ var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ if (result != null && result.rows.Length > 2)
+ {
+ var group = result.rows;
+ /**
* A group contains 3 elements.
* 1) The session metadata.
* 2) User document of Player1.
* 3) User document of Player2.
*/
- var session = group[0].doc.ToObject();
- var player1Doc = group[1].doc.ToObject();
- var group2DocumentType = group[2].doc.Property(nameof(UserDocument.DocumentType).ToCamelCase())?.Value.Value();
- var player2Doc = group2DocumentType == WhichDocumentType.User.ToString()
- ? group[2].doc.ToObject()
- : null;
- if (session != null && player1Doc != null)
- {
- var player2 = player2Doc == null ? null : new Models.User(player2Doc);
- return new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2);
- }
- }
- return null;
- }
+ var session = group[0].doc.ToObject();
+ var player1Doc = group[1].doc.ToObject();
+ var group2DocumentType = group[2].doc.Property(nameof(UserDocument.DocumentType).ToCamelCase())?.Value.Value();
+ var player2Doc = group2DocumentType == WhichDocumentType.User.ToString()
+ ? group[2].doc.ToObject()
+ : null;
+ if (session != null && player1Doc != null)
+ {
+ var player2 = player2Doc == null ? null : new Models.User(player2Doc);
+ return new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2);
+ }
+ }
+ return null;
+ }
- ///
- /// Saves a snapshot of board state and the most recent move.
- ///
- public async Task CreateBoardState(Models.Session session)
- {
- var boardStateDocument = new BoardStateDocument(session.Name, session.Shogi);
- var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
- var response = await client.PostAsync(string.Empty, content);
- return response.IsSuccessStatusCode;
- }
+ ///
+ /// Saves a snapshot of board state and the most recent move.
+ ///
+ public async Task CreateBoardState(Session session)
+ {
+ Piece? MapPiece(Shogi.Domain.Pieces.Piece? piece)
+ {
+ return piece == null
+ ? null
+ : new Piece { IsPromoted = piece.IsPromoted, Owner = piece.Owner, WhichPiece = piece.WhichPiece };
+ }
- public async Task CreateSession(Models.SessionMetadata session)
- {
- var sessionDocument = new SessionDocument(session);
- var sessionContent = new StringContent(JsonConvert.SerializeObject(sessionDocument), Encoding.UTF8, ApplicationJson);
- var postSessionDocumentTask = client.PostAsync(string.Empty, sessionContent);
+ var boardStateDocument = new BoardStateDocument(session.Name, session);
+ var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
+ var response = await client.PostAsync(string.Empty, content);
+ return response.IsSuccessStatusCode;
+ }
- var boardStateDocument = new BoardStateDocument(session.Name, new Models.Shogi());
- var boardStateContent = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
+ public async Task CreateSession(Models.SessionMetadata session)
+ {
+ var sessionDocument = new SessionDocument(session);
+ var sessionContent = new StringContent(JsonConvert.SerializeObject(sessionDocument), Encoding.UTF8, ApplicationJson);
+ var postSessionDocumentTask = client.PostAsync(string.Empty, sessionContent);
- if ((await postSessionDocumentTask).IsSuccessStatusCode)
- {
- var response = await client.PostAsync(string.Empty, boardStateContent);
- return response.IsSuccessStatusCode;
- }
- return false;
- }
+ var boardStateDocument = new BoardStateDocument(session.Name, new Session());
+ var boardStateContent = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
- public async Task UpdateSession(Models.SessionMetadata session)
- {
- // GET existing session to get revisionId.
- var readResponse = await client.GetAsync(session.Name);
- if (!readResponse.IsSuccessStatusCode) return false;
- var sessionDocument = JsonConvert.DeserializeObject(await readResponse.Content.ReadAsStringAsync());
+ if ((await postSessionDocumentTask).IsSuccessStatusCode)
+ {
+ var response = await client.PostAsync(string.Empty, boardStateContent);
+ return response.IsSuccessStatusCode;
+ }
+ return false;
+ }
- // PUT the document with the revisionId.
- var couchModel = new SessionDocument(session)
- {
- RevisionId = sessionDocument?.RevisionId
- };
- var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
- var response = await client.PutAsync(couchModel.Id, 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 UpdateSession(Models.SessionMetadata session)
+ {
+ // GET existing session to get revisionId.
+ var readResponse = await client.GetAsync(session.Name);
+ if (!readResponse.IsSuccessStatusCode) return false;
+ var sessionDocument = JsonConvert.DeserializeObject(await readResponse.Content.ReadAsStringAsync());
- //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;
- //}
+ // PUT the document with the revisionId.
+ var couchModel = new SessionDocument(session)
+ {
+ RevisionId = sessionDocument?.RevisionId
+ };
+ var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
+ var response = await client.PutAsync(couchModel.Id, 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> 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 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 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> 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 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;
- return string.Empty;
- }
+ //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 ReadUser(string id)
- {
- var queryParams = new QueryBuilder
- {
- { "include_docs", "true" },
- { "key", JsonConvert.SerializeObject(id) },
- }.ToQueryString();
- var response = await client.GetAsync($"{View_User}{queryParams}");
- var responseContent = await response.Content.ReadAsStringAsync();
- var result = JsonConvert.DeserializeObject>(responseContent);
- if (result != null && result.rows.Length > 0)
- {
- return new Models.User(result.rows[0].doc);
- }
+ 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;
+ return string.Empty;
+ }
- return null;
- }
+ public async Task ReadUser(string id)
+ {
+ var queryParams = new QueryBuilder
+ {
+ { "include_docs", "true" },
+ { "key", JsonConvert.SerializeObject(id) },
+ }.ToQueryString();
+ var response = await client.GetAsync($"{View_User}{queryParams}");
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ if (result != null && result.rows.Length > 0)
+ {
+ return new Models.User(result.rows[0].doc);
+ }
- public async Task CreateUser(Models.User user)
- {
- var couchModel = new UserDocument(user.Id, user.DisplayName, user.LoginPlatform);
- var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
- var response = await client.PostAsync(string.Empty, content);
- return response.IsSuccessStatusCode;
- }
+ return null;
+ }
- }
+ public async Task CreateUser(Models.User user)
+ {
+ var couchModel = new UserDocument(user.Id, user.DisplayName, user.LoginPlatform);
+ var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
+ var response = await client.PostAsync(string.Empty, content);
+ return response.IsSuccessStatusCode;
+ }
+
+ public void ReadMoveHistory()
+ {
+ //TODO: Separate move history into a separate request.
+ //var moves = group
+ // .Skip(4) // Skip 4 because group[3] will not have a .Move property since it's the first/initial BoardState of the session.
+ // // TODO: Deserialize just the Move property.
+ // .Select(row => row.doc.ToObject())
+ // .Select(boardState =>
+ // {
+ // var move = boardState!.Move!;
+ // return move.PieceFromHand.HasValue
+ // ? new Models.Move(move.PieceFromHand.Value, move.To)
+ // : new Models.Move(move.From!, move.To, move.IsPromotion);
+ // })
+ // .ToList();
+ }
+ }
}
diff --git a/Gameboard.ShogiUI.Sockets/Services/SocketService.cs b/Gameboard.ShogiUI.Sockets/Services/SocketService.cs
index 0418950..fefbd52 100644
--- a/Gameboard.ShogiUI.Sockets/Services/SocketService.cs
+++ b/Gameboard.ShogiUI.Sockets/Services/SocketService.cs
@@ -5,7 +5,6 @@ using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-using Gameboard.ShogiUI.Sockets.Services.Utility;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -17,7 +16,7 @@ using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets.Services
{
- public interface ISocketService
+ public interface ISocketService
{
Task HandleSocketRequest(HttpContext context);
}
@@ -74,7 +73,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
}
var socket = await context.WebSockets.AcceptWebSocketAsync();
- communicationManager.SubscribeToBroadcast(socket, userName);
+ communicationManager.Subscribe(socket, userName);
while (socket.State == WebSocketState.Open)
{
try
@@ -82,7 +81,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
var message = await socket.ReceiveTextAsync();
if (string.IsNullOrWhiteSpace(message)) continue;
logger.LogInformation("Request \n{0}\n", message);
- var request = JsonConvert.DeserializeObject(message);
+ var request = JsonConvert.DeserializeObject(message);
if (request == null || !Enum.IsDefined(typeof(ClientAction), request.Action))
{
await socket.SendTextAsync("Error: Action not recognized.");
@@ -114,7 +113,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
logger.LogInformation("Probably tried writing to a closed socket.");
logger.LogError(ex.Message);
}
- communicationManager.UnsubscribeFromBroadcastAndGames(userName);
+ communicationManager.Unsubscribe(userName);
}
}
diff --git a/Gameboard.ShogiUI.Sockets/Services/Utility/JsonRequest.cs b/Gameboard.ShogiUI.Sockets/Services/Utility/JsonRequest.cs
deleted file mode 100644
index c4fde65..0000000
--- a/Gameboard.ShogiUI.Sockets/Services/Utility/JsonRequest.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
-
-namespace Gameboard.ShogiUI.Sockets.Services.Utility
-{
- public class JsonRequest
- {
- public IRequest Request { get; private set; }
- public string Json { get; private set; }
- public JsonRequest(IRequest request, string json)
- {
- Request = request;
- Json = json;
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Services/Utility/Request.cs b/Gameboard.ShogiUI.Sockets/Services/Utility/Request.cs
deleted file mode 100644
index 1928408..0000000
--- a/Gameboard.ShogiUI.Sockets/Services/Utility/Request.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
-using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
-
-namespace Gameboard.ShogiUI.Sockets.Services.Utility
-{
- public class Request : IRequest
- {
- public ClientAction Action { get; set; }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Services/Utility/Response.cs b/Gameboard.ShogiUI.Sockets/Services/Utility/Response.cs
deleted file mode 100644
index 067d374..0000000
--- a/Gameboard.ShogiUI.Sockets/Services/Utility/Response.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
-
-namespace Gameboard.ShogiUI.Sockets.Services.Utility
-{
- public class Response : IResponse
- {
- public string Action { get; set; }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Utilities/CoordsToNotationCollection.cs b/Gameboard.ShogiUI.Sockets/Utilities/CoordsToNotationCollection.cs
deleted file mode 100644
index c302164..0000000
--- a/Gameboard.ShogiUI.Sockets/Utilities/CoordsToNotationCollection.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Gameboard.ShogiUI.Sockets.Models;
-using PathFinding;
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-
-namespace Gameboard.ShogiUI.Sockets.Utilities
-{
- public class CoordsToNotationCollection : Dictionary, IPlanarCollection
- {
- public delegate void ForEachDelegate(Piece element, Vector2 position);
-
- public CoordsToNotationCollection() : base(81, StringComparer.OrdinalIgnoreCase)
- {
- }
-
- public CoordsToNotationCollection(Dictionary board) : base(board, StringComparer.OrdinalIgnoreCase)
- {
- }
-
- public Piece? this[Vector2 vector]
- {
- get => this[NotationHelper.ToBoardNotation(vector)];
- set => this[NotationHelper.ToBoardNotation(vector)] = value;
- }
-
- public Piece? this[int x, int y]
- {
- get => this[NotationHelper.ToBoardNotation(x, y)];
- set => this[NotationHelper.ToBoardNotation(x, y)] = value;
- }
-
-
- public void ForEachNotNull(ForEachDelegate callback)
- {
- for (var x = 0; x < 9; x++)
- {
- for (var y = 0; y < 9; y++)
- {
- var position = new Vector2(x, y);
- var elem = this[position];
- if (elem != null)
- callback(elem, position);
- }
- }
- }
- }
-}
diff --git a/Gameboard.ShogiUI.Sockets/Utilities/NotationHelper.cs b/Gameboard.ShogiUI.Sockets/Utilities/NotationHelper.cs
deleted file mode 100644
index c6831db..0000000
--- a/Gameboard.ShogiUI.Sockets/Utilities/NotationHelper.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Numerics;
-using System.Text.RegularExpressions;
-
-namespace Gameboard.ShogiUI.Sockets.Utilities
-{
- public static class NotationHelper
- {
- private static readonly string BoardNotationRegex = @"(?[a-iA-I])(?[1-9])";
- private static readonly char A = 'A';
-
- public static string ToBoardNotation(Vector2 vector)
- {
- return ToBoardNotation((int)vector.X, (int)vector.Y);
- }
- public static string ToBoardNotation(int x, int y)
- {
- var file = (char)(x + A);
- var rank = y + 1;
- return $"{file}{rank}";
- }
-
- public static Vector2 FromBoardNotation(string notation)
- {
- notation = notation.ToUpper();
- if (Regex.IsMatch(notation, BoardNotationRegex))
- {
- var match = Regex.Match(notation, BoardNotationRegex);
- char file = match.Groups["file"].Value[0];
- int rank = int.Parse(match.Groups["rank"].Value);
- return new Vector2(file - A, rank - 1);
- }
- throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
- }
- }
-}
diff --git a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
index 1476dbb..e13dea3 100644
--- a/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
+++ b/Gameboard.ShogiUI.UnitTests/Rules/ShogiBoardShould.cs
@@ -3,7 +3,7 @@ using Gameboard.ShogiUI.Sockets.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Numerics;
-using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPerspective;
+using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer;
using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece;
namespace Gameboard.ShogiUI.UnitTests.Rules
{
diff --git a/Gameboard.ShogiUI.xUnitTests/CoordsToNotationCollectionShould.cs b/Gameboard.ShogiUI.xUnitTests/CoordsToNotationCollectionShould.cs
deleted file mode 100644
index d7c0123..0000000
--- a/Gameboard.ShogiUI.xUnitTests/CoordsToNotationCollectionShould.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using AutoFixture;
-using FluentAssertions;
-using Gameboard.ShogiUI.Sockets.Models;
-using Gameboard.ShogiUI.Sockets.Utilities;
-using Xunit;
-
-namespace Gameboard.ShogiUI.xUnitTests
-{
- public class CoordsToNotationCollectionShould
- {
- private readonly Fixture fixture;
- private readonly CoordsToNotationCollection collection;
- public CoordsToNotationCollectionShould()
- {
- fixture = new Fixture();
- collection = new CoordsToNotationCollection();
- }
-
- [Fact]
- public void TranslateCoordinatesToNotation()
- {
- // Arrange
- collection[0, 0] = fixture.Create();
- collection[4, 4] = fixture.Create();
- collection[8, 8] = fixture.Create();
- collection[2, 2] = fixture.Create();
-
- // Assert
- collection["A1"].Should().BeSameAs(collection[0, 0]);
- collection["E5"].Should().BeSameAs(collection[4, 4]);
- collection["I9"].Should().BeSameAs(collection[8, 8]);
- collection["C3"].Should().BeSameAs(collection[2, 2]);
- }
-
- [Fact]
- public void Yep()
- {
-
- }
- }
-}
diff --git a/Gameboard.ShogiUI.xUnitTests/GameShould.cs b/Gameboard.ShogiUI.xUnitTests/GameShould.cs
index 7d9354e..451d4f3 100644
--- a/Gameboard.ShogiUI.xUnitTests/GameShould.cs
+++ b/Gameboard.ShogiUI.xUnitTests/GameShould.cs
@@ -9,7 +9,7 @@ namespace Gameboard.ShogiUI.xUnitTests
[Fact]
public void DiscardNullPLayers()
{
- var game = new Game("Test", "P1", null);
+ var game = new Session("Test", "P1", null);
game.Players.Count.Should().Be(1);
}
diff --git a/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs b/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs
deleted file mode 100644
index 8b130c7..0000000
--- a/Gameboard.ShogiUI.xUnitTests/ShogiShould.cs
+++ /dev/null
@@ -1,621 +0,0 @@
-using FluentAssertions;
-using FluentAssertions.Execution;
-using Gameboard.ShogiUI.Sockets.Extensions;
-using Gameboard.ShogiUI.Sockets.Models;
-using System.Linq;
-using Xunit;
-using Xunit.Abstractions;
-using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece;
-using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPerspective;
-using ShogiModel = Gameboard.ShogiUI.Sockets.Models.Shogi;
-
-namespace Gameboard.ShogiUI.xUnitTests
-{
- public class ShogiShould
- {
- private readonly ITestOutputHelper output;
- public ShogiShould(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void InitializeBoardState()
- {
- // Act
- var board = new ShogiModel().Board;
-
- // Assert
- board["A1"].WhichPiece.Should().Be(WhichPiece.Lance);
- board["A1"].Owner.Should().Be(WhichPerspective.Player1);
- board["A1"].IsPromoted.Should().Be(false);
- board["B1"].WhichPiece.Should().Be(WhichPiece.Knight);
- board["B1"].Owner.Should().Be(WhichPerspective.Player1);
- board["B1"].IsPromoted.Should().Be(false);
- board["C1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
- board["C1"].Owner.Should().Be(WhichPerspective.Player1);
- board["C1"].IsPromoted.Should().Be(false);
- board["D1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
- board["D1"].Owner.Should().Be(WhichPerspective.Player1);
- board["D1"].IsPromoted.Should().Be(false);
- board["E1"].WhichPiece.Should().Be(WhichPiece.King);
- board["E1"].Owner.Should().Be(WhichPerspective.Player1);
- board["E1"].IsPromoted.Should().Be(false);
- board["F1"].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
- board["F1"].Owner.Should().Be(WhichPerspective.Player1);
- board["F1"].IsPromoted.Should().Be(false);
- board["G1"].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
- board["G1"].Owner.Should().Be(WhichPerspective.Player1);
- board["G1"].IsPromoted.Should().Be(false);
- board["H1"].WhichPiece.Should().Be(WhichPiece.Knight);
- board["H1"].Owner.Should().Be(WhichPerspective.Player1);
- board["H1"].IsPromoted.Should().Be(false);
- board["I1"].WhichPiece.Should().Be(WhichPiece.Lance);
- board["I1"].Owner.Should().Be(WhichPerspective.Player1);
- board["I1"].IsPromoted.Should().Be(false);
-
- board["A2"].Should().BeNull();
- board["B2"].WhichPiece.Should().Be(WhichPiece.Bishop);
- board["B2"].Owner.Should().Be(WhichPerspective.Player1);
- board["B2"].IsPromoted.Should().Be(false);
- board["C2"].Should().BeNull();
- board["D2"].Should().BeNull();
- board["E2"].Should().BeNull();
- board["F2"].Should().BeNull();
- board["G2"].Should().BeNull();
- board["H2"].WhichPiece.Should().Be(WhichPiece.Rook);
- board["H2"].Owner.Should().Be(WhichPerspective.Player1);
- board["H2"].IsPromoted.Should().Be(false);
- board["I2"].Should().BeNull();
-
- board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["A3"].Owner.Should().Be(WhichPerspective.Player1);
- board["A3"].IsPromoted.Should().Be(false);
- board["B3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["B3"].Owner.Should().Be(WhichPerspective.Player1);
- board["B3"].IsPromoted.Should().Be(false);
- board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["C3"].Owner.Should().Be(WhichPerspective.Player1);
- board["C3"].IsPromoted.Should().Be(false);
- board["D3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["D3"].Owner.Should().Be(WhichPerspective.Player1);
- board["D3"].IsPromoted.Should().Be(false);
- board["E3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["E3"].Owner.Should().Be(WhichPerspective.Player1);
- board["E3"].IsPromoted.Should().Be(false);
- board["F3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["F3"].Owner.Should().Be(WhichPerspective.Player1);
- board["F3"].IsPromoted.Should().Be(false);
- board["G3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["G3"].Owner.Should().Be(WhichPerspective.Player1);
- board["G3"].IsPromoted.Should().Be(false);
- board["H3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["H3"].Owner.Should().Be(WhichPerspective.Player1);
- board["H3"].IsPromoted.Should().Be(false);
- board["I3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["I3"].Owner.Should().Be(WhichPerspective.Player1);
- board["I3"].IsPromoted.Should().Be(false);
-
- board["A4"].Should().BeNull();
- board["B4"].Should().BeNull();
- board["C4"].Should().BeNull();
- board["D4"].Should().BeNull();
- board["E4"].Should().BeNull();
- board["F4"].Should().BeNull();
- board["G4"].Should().BeNull();
- board["H4"].Should().BeNull();
- board["I4"].Should().BeNull();
-
- board["A5"].Should().BeNull();
- board["B5"].Should().BeNull();
- board["C5"].Should().BeNull();
- board["D5"].Should().BeNull();
- board["E5"].Should().BeNull();
- board["F5"].Should().BeNull();
- board["G5"].Should().BeNull();
- board["H5"].Should().BeNull();
- board["I5"].Should().BeNull();
-
- board["A6"].Should().BeNull();
- board["B6"].Should().BeNull();
- board["C6"].Should().BeNull();
- board["D6"].Should().BeNull();
- board["E6"].Should().BeNull();
- board["F6"].Should().BeNull();
- board["G6"].Should().BeNull();
- board["H6"].Should().BeNull();
- board["I6"].Should().BeNull();
-
- board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["A7"].Owner.Should().Be(WhichPerspective.Player2);
- board["A7"].IsPromoted.Should().Be(false);
- board["B7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["B7"].Owner.Should().Be(WhichPerspective.Player2);
- board["B7"].IsPromoted.Should().Be(false);
- board["C7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["C7"].Owner.Should().Be(WhichPerspective.Player2);
- board["C7"].IsPromoted.Should().Be(false);
- board["D7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["D7"].Owner.Should().Be(WhichPerspective.Player2);
- board["D7"].IsPromoted.Should().Be(false);
- board["E7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["E7"].Owner.Should().Be(WhichPerspective.Player2);
- board["E7"].IsPromoted.Should().Be(false);
- board["F7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["F7"].Owner.Should().Be(WhichPerspective.Player2);
- board["F7"].IsPromoted.Should().Be(false);
- board["G7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["G7"].Owner.Should().Be(WhichPerspective.Player2);
- board["G7"].IsPromoted.Should().Be(false);
- board["H7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["H7"].Owner.Should().Be(WhichPerspective.Player2);
- board["H7"].IsPromoted.Should().Be(false);
- board["I7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- board["I7"].Owner.Should().Be(WhichPerspective.Player2);
- board["I7"].IsPromoted.Should().Be(false);
-
- board["A8"].Should().BeNull();
- board["B8"].WhichPiece.Should().Be(WhichPiece.Rook);
- board["B8"].Owner.Should().Be(WhichPerspective.Player2);
- board["B8"].IsPromoted.Should().Be(false);
- board["C8"].Should().BeNull();
- board["D8"].Should().BeNull();
- board["E8"].Should().BeNull();
- board["F8"].Should().BeNull();
- board["G8"].Should().BeNull();
- board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop);
- board["H8"].Owner.Should().Be(WhichPerspective.Player2);
- board["H8"].IsPromoted.Should().Be(false);
- board["I8"].Should().BeNull();
-
- board["A9"].WhichPiece.Should().Be(WhichPiece.Lance);
- board["A9"].Owner.Should().Be(WhichPerspective.Player2);
- board["A9"].IsPromoted.Should().Be(false);
- board["B9"].WhichPiece.Should().Be(WhichPiece.Knight);
- board["B9"].Owner.Should().Be(WhichPerspective.Player2);
- board["B9"].IsPromoted.Should().Be(false);
- board["C9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
- board["C9"].Owner.Should().Be(WhichPerspective.Player2);
- board["C9"].IsPromoted.Should().Be(false);
- board["D9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
- board["D9"].Owner.Should().Be(WhichPerspective.Player2);
- board["D9"].IsPromoted.Should().Be(false);
- board["E9"].WhichPiece.Should().Be(WhichPiece.King);
- board["E9"].Owner.Should().Be(WhichPerspective.Player2);
- board["E9"].IsPromoted.Should().Be(false);
- board["F9"].WhichPiece.Should().Be(WhichPiece.GoldGeneral);
- board["F9"].Owner.Should().Be(WhichPerspective.Player2);
- board["F9"].IsPromoted.Should().Be(false);
- board["G9"].WhichPiece.Should().Be(WhichPiece.SilverGeneral);
- board["G9"].Owner.Should().Be(WhichPerspective.Player2);
- board["G9"].IsPromoted.Should().Be(false);
- board["H9"].WhichPiece.Should().Be(WhichPiece.Knight);
- board["H9"].Owner.Should().Be(WhichPerspective.Player2);
- board["H9"].IsPromoted.Should().Be(false);
- board["I9"].WhichPiece.Should().Be(WhichPiece.Lance);
- board["I9"].Owner.Should().Be(WhichPerspective.Player2);
- board["I9"].IsPromoted.Should().Be(false);
- }
-
- [Fact]
- public void InitializeBoardStateWithMoves()
- {
- var moves = new[]
- {
- // P1 Pawn
- new Move("A3", "A4")
- };
- var shogi = new ShogiModel(moves);
- shogi.Board["A3"].Should().BeNull();
- shogi.Board["A4"].WhichPiece.Should().Be(WhichPiece.Pawn);
- }
-
- [Fact]
- public void AllowValidMoves_AfterCheck()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("G7", "G6"),
- // P1 Bishop puts P2 in check
- new Move("B2", "G7"),
- };
- var shogi = new ShogiModel(moves);
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
-
- // Act - P2 is able to un-check theirself.
- /// P2 King moves out of check
- var moveSuccess = shogi.Move(new Move("E9", "E8"));
-
- // Assert
- using var _ = new AssertionScope();
- moveSuccess.Should().BeTrue();
- shogi.InCheck.Should().BeNull();
- }
-
- [Fact]
- public void PreventInvalidMoves_MoveFromEmptyPosition()
- {
- // Arrange
- var shogi = new ShogiModel();
- shogi.Board["D5"].Should().BeNull();
-
- // Act
- var moveSuccess = shogi.Move(new Move("D5", "D6"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.Board["D5"].Should().BeNull();
- shogi.Board["D6"].Should().BeNull();
- }
-
- [Fact]
- public void PreventInvalidMoves_MoveToCurrentPosition()
- {
- // Arrange
- var shogi = new ShogiModel();
-
- // Act - P1 "moves" pawn to the position it already exists at.
- var moveSuccess = shogi.Move(new Move("A3", "A3"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- shogi.Player1Hand.Should().BeEmpty();
- shogi.Player2Hand.Should().BeEmpty();
- }
-
- [Fact]
- public void PreventInvalidMoves_MoveSet()
- {
- // Arrange
- var shogi = new ShogiModel();
-
- // Act - Move Lance illegally
- var moveSuccess = shogi.Move(new Move("A1", "D5"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance);
- shogi.Board["A5"].Should().BeNull();
- shogi.Player1Hand.Should().BeEmpty();
- shogi.Player2Hand.Should().BeEmpty();
- }
-
- [Fact]
- public void PreventInvalidMoves_Ownership()
- {
- // Arrange
- var shogi = new ShogiModel();
- shogi.WhoseTurn.Should().Be(WhichPerspective.Player1);
- shogi.Board["A7"].Owner.Should().Be(WhichPerspective.Player2);
-
- // Act - Move Player2 Pawn when it is Player1 turn.
- var moveSuccess = shogi.Move(new Move("A7", "A6"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.Board["A7"].WhichPiece.Should().Be(WhichPiece.Pawn);
- shogi.Board["A6"].Should().BeNull();
- }
-
- [Fact]
- public void PreventInvalidMoves_MoveThroughAllies()
- {
- // Arrange
- var shogi = new ShogiModel();
-
- // Act - Move P1 Lance through P1 Pawn.
- var moveSuccess = shogi.Move(new Move("A1", "A5"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.Board["A1"].WhichPiece.Should().Be(WhichPiece.Lance);
- shogi.Board["A3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- shogi.Board["A5"].Should().BeNull();
- }
-
- [Fact]
- public void PreventInvalidMoves_CaptureAlly()
- {
- // Arrange
- var shogi = new ShogiModel();
-
- // Act - P1 Knight tries to capture P1 Pawn.
- var moveSuccess = shogi.Move(new Move("B1", "C3"));
-
- // Arrange
- moveSuccess.Should().BeFalse();
- shogi.Board["B1"].WhichPiece.Should().Be(WhichPiece.Knight);
- shogi.Board["C3"].WhichPiece.Should().Be(WhichPiece.Pawn);
- shogi.Player1Hand.Should().BeEmpty();
- shogi.Player2Hand.Should().BeEmpty();
- }
-
- [Fact]
- public void PreventInvalidMoves_Check()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("G7", "G6"),
- // P1 Bishop puts P2 in check
- new Move("B2", "G7")
- };
- var shogi = new ShogiModel(moves);
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
-
- // Act - P2 moves Lance while in check.
- var moveSuccess = shogi.Move(new Move("I9", "I8"));
-
- // Assert
- moveSuccess.Should().BeFalse();
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
- shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance);
- shogi.Board["I8"].Should().BeNull();
- }
-
- [Fact]
- public void PreventInvalidDrops_MoveSet()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("I7", "I6"),
- // P1 Bishop takes P2 Pawn.
- new Move("B2", "G7"),
- // P2 Gold, block check from P1 Bishop.
- new Move("F9", "F8"),
- // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance
- new Move("G7", "H8", true),
- // P2 Pawn again
- new Move("I6", "I5"),
- // P1 Bishop takes P2 Knight
- new Move("H8", "H9"),
- // P2 Pawn again
- new Move("I5", "I4"),
- // P1 Bishop takes P2 Lance
- new Move("H9", "I9"),
- // P2 Pawn captures P1 Pawn
- new Move("I4", "I3")
- };
- var shogi = new ShogiModel(moves);
- shogi.Player1Hand.Count.Should().Be(4);
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight);
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
- shogi.WhoseTurn.Should().Be(WhichPerspective.Player1);
-
- // Act | Assert - Illegally placing Knight from the hand in farthest row.
- shogi.Board["H9"].Should().BeNull();
- var dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9"));
- dropSuccess.Should().BeFalse();
- shogi.Board["H9"].Should().BeNull();
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight);
-
- // Act | Assert - Illegally placing Knight from the hand in second farthest row.
- shogi.Board["H8"].Should().BeNull();
- dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H8"));
- dropSuccess.Should().BeFalse();
- shogi.Board["H8"].Should().BeNull();
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight);
-
- // Act | Assert - Illegally place Lance from the hand.
- shogi.Board["H9"].Should().BeNull();
- dropSuccess = shogi.Move(new Move(WhichPiece.Knight, "H9"));
- dropSuccess.Should().BeFalse();
- shogi.Board["H9"].Should().BeNull();
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance);
-
- // Act | Assert - Illegally place Pawn from the hand.
- shogi.Board["H9"].Should().BeNull();
- dropSuccess = shogi.Move(new Move(WhichPiece.Pawn, "H9"));
- dropSuccess.Should().BeFalse();
- shogi.Board["H9"].Should().BeNull();
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn);
-
- // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn.
- // TODO
- }
-
- [Fact]
- public void PreventInvalidDrop_Check()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("G7", "G6"),
- // P1 Pawn, arbitrary move.
- new Move("A3", "A4"),
- // P2 Bishop takes P1 Bishop
- new Move("H8", "B2"),
- // P1 Silver takes P2 Bishop
- new Move("C1", "B2"),
- // P2 Pawn, arbtrary move
- new Move("A7", "A6"),
- // P1 drop Bishop, place P2 in check
- new Move(WhichPiece.Bishop, "G7")
- };
- var shogi = new ShogiModel(moves);
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
- shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
- shogi.Board["E5"].Should().BeNull();
-
- // Act - P2 places a Bishop while in check.
- var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "E5"));
-
- // Assert
- dropSuccess.Should().BeFalse();
- shogi.Board["E5"].Should().BeNull();
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
- shogi.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
- }
-
- [Fact]
- public void PreventInvalidDrop_Capture()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("G7", "G6"),
- // P1 Bishop capture P2 Bishop
- new Move("B2", "H8"),
- // P2 Pawn
- new Move("G6", "G5")
- };
- var shogi = new ShogiModel(moves);
- using (new AssertionScope())
- {
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
- shogi.Board["I9"].Should().NotBeNull();
- shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance);
- shogi.Board["I9"].Owner.Should().Be(WhichPerspective.Player2);
- }
-
- // Act - P1 tries to place a piece where an opponent's piece resides.
- var dropSuccess = shogi.Move(new Move(WhichPiece.Bishop, "I9"));
-
- // Assert
- using (new AssertionScope())
- {
- dropSuccess.Should().BeFalse();
- shogi.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop);
- shogi.Board["I9"].Should().NotBeNull();
- shogi.Board["I9"].WhichPiece.Should().Be(WhichPiece.Lance);
- shogi.Board["I9"].Owner.Should().Be(WhichPerspective.Player2);
- }
- }
-
- [Fact]
- public void Check()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4"),
- // P2 Pawn
- new Move("G7", "G6"),
- };
- var shogi = new ShogiModel(moves);
-
- // Act - P1 Bishop, check
- shogi.Move(new Move("B2", "G7"));
-
- // Assert
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
- }
-
- [Fact]
- public void Promote()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Pawn
- new Move("C3", "C4" ),
- // P2 Pawn
- new Move("G7", "G6" )
- };
- var shogi = new ShogiModel(moves);
-
- // Act - P1 moves across promote threshold.
- var moveSuccess = shogi.Move(new Move("B2", "G7", true));
-
- // Assert
- using (new AssertionScope())
- {
- moveSuccess.Should().BeTrue();
- shogi.Board["B2"].Should().BeNull();
- shogi.Board["G7"].Should().NotBeNull();
- shogi.Board["G7"].WhichPiece.Should().Be(WhichPiece.Bishop);
- shogi.Board["G7"].Owner.Should().Be(WhichPerspective.Player1);
- shogi.Board["G7"].IsPromoted.Should().BeTrue();
- }
- }
-
- [Fact]
- public void CheckMate()
- {
- // Arrange
- var moves = new[]
- {
- // P1 Rook
- new Move("H2", "E2"),
- // P2 Gold
- new Move("F9", "G8"),
- // P1 Pawn
- new Move("E3", "E4"),
- // P2 other Gold
- new Move("D9", "C8"),
- // P1 same Pawn
- new Move("E4", "E5"),
- // P2 Pawn
- new Move("E7", "E6"),
- // P1 Pawn takes P2 Pawn
- new Move("E5", "E6"),
- // P2 King
- new Move("E9", "E8"),
- // P1 Pawn promotes, threatens P2 King
- new Move("E6", "E7", true),
- // P2 King retreat
- new Move("E8", "E9"),
- };
- var shogi = new ShogiModel(moves);
- output.WriteLine(shogi.PrintStateAsAscii());
-
- // Act - P1 Pawn wins by checkmate.
- var moveSuccess = shogi.Move(new Move("E7", "E8"));
- output.WriteLine(shogi.PrintStateAsAscii());
-
- // Assert - checkmate
- moveSuccess.Should().BeTrue();
- shogi.IsCheckmate.Should().BeTrue();
- shogi.InCheck.Should().Be(WhichPerspective.Player2);
- }
-
- [Fact]
- public void Capture()
- {
- // Arrange
- var moves = new[]
- {
- new Move("C3", "C4"),
- new Move("G7", "G6")
- };
- var shogi = new ShogiModel(moves);
-
- // Act - P1 Bishop captures P2 Bishop
- var moveSuccess = shogi.Move(new Move("B2", "H8"));
-
- // Assert
- moveSuccess.Should().BeTrue();
- shogi.Board["B2"].Should().BeNull();
- shogi.Board["H8"].WhichPiece.Should().Be(WhichPiece.Bishop);
- shogi.Board["H8"].Owner.Should().Be(WhichPerspective.Player1);
- shogi.Board.Values
- .Where(p => p != null)
- .Should().ContainSingle(piece => piece.WhichPiece == WhichPiece.Bishop);
-
- shogi.Player1Hand
- .Should()
- .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPerspective.Player1);
- }
- }
-}
diff --git a/Shogi.Domain.UnitTests/NotationShould.cs b/Shogi.Domain.UnitTests/NotationShould.cs
new file mode 100644
index 0000000..4374276
--- /dev/null
+++ b/Shogi.Domain.UnitTests/NotationShould.cs
@@ -0,0 +1,27 @@
+using FluentAssertions;
+using System.Numerics;
+using Xunit;
+
+namespace Shogi.Domain.UnitTests
+{
+ public class NotationShould
+ {
+ [Fact]
+ public void ConvertFromNotationToVector()
+ {
+ Notation.FromBoardNotation("A1").Should().Be(new Vector2(0, 0));
+ Notation.FromBoardNotation("E5").Should().Be(new Vector2(4, 4));
+ Notation.FromBoardNotation("I9").Should().Be(new Vector2(8, 8));
+ Notation.FromBoardNotation("C3").Should().Be(new Vector2(2, 2));
+ }
+
+ [Fact]
+ public void ConvertFromVectorToNotation()
+ {
+ Notation.ToBoardNotation(new Vector2(0, 0)).Should().Be("A1");
+ Notation.ToBoardNotation(new Vector2(4, 4)).Should().Be("E5");
+ Notation.ToBoardNotation(new Vector2(8, 8)).Should().Be("I9");
+ Notation.ToBoardNotation(new Vector2(2, 2)).Should().Be("C3");
+ }
+ }
+}
diff --git a/Shogi.Domain.UnitTests/ShogiBoardStateShould.cs b/Shogi.Domain.UnitTests/ShogiBoardStateShould.cs
index 470a3cb..a2592f7 100644
--- a/Shogi.Domain.UnitTests/ShogiBoardStateShould.cs
+++ b/Shogi.Domain.UnitTests/ShogiBoardStateShould.cs
@@ -9,7 +9,7 @@ namespace Shogi.Domain.UnitTests
public void InitializeBoardState()
{
// Act
- var board = new ShogiBoardState();
+ var board = new BoardState();
// Assert
board["A1"]?.WhichPiece.Should().Be(WhichPiece.Lance);
diff --git a/Shogi.Domain.UnitTests/ShogiShould.cs b/Shogi.Domain.UnitTests/ShogiShould.cs
index c73d0ee..e36b046 100644
--- a/Shogi.Domain.UnitTests/ShogiShould.cs
+++ b/Shogi.Domain.UnitTests/ShogiShould.cs
@@ -18,8 +18,8 @@ namespace Shogi.Domain.UnitTests
public void MoveAPieceToAnEmptyPosition()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
board["A4"].Should().BeNull();
var expectedPiece = board["A3"];
expectedPiece.Should().NotBeNull();
@@ -36,8 +36,8 @@ namespace Shogi.Domain.UnitTests
public void AllowValidMoves_AfterCheck()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
// P1 Pawn
shogi.Move("C3", "C4", false);
// P2 Pawn
@@ -61,8 +61,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_MoveFromEmptyPosition()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
board["D5"].Should().BeNull();
// Act
@@ -80,8 +80,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_MoveToCurrentPosition()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
var expectedPiece = board["A3"];
// Act - P1 "moves" pawn to the position it already exists at.
@@ -101,8 +101,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_MoveSet()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
var expectedPiece = board["A1"];
expectedPiece!.WhichPiece.Should().Be(WhichPiece.Lance);
@@ -124,8 +124,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_Ownership()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
var expectedPiece = board["A7"];
expectedPiece!.Owner.Should().Be(WhichPlayer.Player2);
board.WhoseTurn.Should().Be(WhichPlayer.Player1);
@@ -146,8 +146,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_MoveThroughAllies()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
var lance = board["A1"];
var pawn = board["A3"];
lance!.Owner.Should().Be(pawn!.Owner);
@@ -169,8 +169,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_CaptureAlly()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
var knight = board["B1"];
var pawn = board["C3"];
knight!.Owner.Should().Be(pawn!.Owner);
@@ -193,8 +193,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidMoves_Check()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
// P1 Pawn
shogi.Move("C3", "C4", false);
// P2 Pawn
@@ -222,8 +222,8 @@ namespace Shogi.Domain.UnitTests
public void PreventInvalidDrops_MoveSet()
{
// Arrange
- var board = new ShogiBoardState();
- var shogi = new Shogi(board);
+ var board = new BoardState();
+ var shogi = new Session(board);
// P1 Pawn
shogi.Move("C3", "C4", false);
// P2 Pawn
@@ -361,8 +361,8 @@ namespace Shogi.Domain.UnitTests
public void Check()
{
// Arrange
- var boardState = new ShogiBoardState();
- var shogi = new Shogi(boardState);
+ var boardState = new BoardState();
+ var shogi = new Session(boardState);
// P1 Pawn
shogi.Move("C3", "C4", false);
// P2 Pawn
@@ -379,8 +379,8 @@ namespace Shogi.Domain.UnitTests
public void Promote()
{
// Arrange
- var boardState = new ShogiBoardState();
- var shogi = new Shogi(boardState);
+ var boardState = new BoardState();
+ var shogi = new Session(boardState);
// P1 Pawn
shogi.Move("C3", "C4", false);
// P2 Pawn
@@ -404,8 +404,8 @@ namespace Shogi.Domain.UnitTests
public void Capture()
{
// Arrange
- var boardState = new ShogiBoardState();
- var shogi = new Shogi(boardState);
+ var boardState = new BoardState();
+ var shogi = new Session(boardState);
var p1Bishop = boardState["B2"];
p1Bishop!.WhichPiece.Should().Be(WhichPiece.Bishop);
shogi.Move("C3", "C4", false);
@@ -428,8 +428,8 @@ namespace Shogi.Domain.UnitTests
public void CheckMate()
{
// Arrange
- var boardState = new ShogiBoardState();
- var shogi = new Shogi(boardState);
+ var boardState = new BoardState();
+ var shogi = new Session(boardState);
// P1 Rook
shogi.Move("H2", "E2", false);
// P2 Gold
diff --git a/Shogi.Domain/ShogiBoardState.cs b/Shogi.Domain/BoardState.cs
similarity index 79%
rename from Shogi.Domain/ShogiBoardState.cs
rename to Shogi.Domain/BoardState.cs
index 69c1645..9802010 100644
--- a/Shogi.Domain/ShogiBoardState.cs
+++ b/Shogi.Domain/BoardState.cs
@@ -1,22 +1,26 @@
using Shogi.Domain.Pieces;
-using System.Text.RegularExpressions;
using BoardTile = System.Collections.Generic.KeyValuePair;
namespace Shogi.Domain
{
- // TODO: Avoid extending dictionary. Use composition instead.
- // Then validation can occur when assigning a piece to a position.
- public class ShogiBoardState
+ public class BoardState
{
- private static readonly string BoardNotationRegex = @"(?[A-I])(?[1-9])";
- private static readonly char A = 'A';
public delegate void ForEachDelegate(Piece element, Vector2 position);
///
/// Key is position notation, such as "E4".
///
private readonly Dictionary board;
- public ShogiBoardState()
+
+ public BoardState(Dictionary state)
+ {
+ board = state;
+ Player1Hand = new List();
+ Player2Hand = new List();
+ PreviousMoveTo = Vector2.Zero;
+ }
+
+ public BoardState()
{
board = new Dictionary(81, StringComparer.OrdinalIgnoreCase);
InitializeBoardState();
@@ -25,13 +29,14 @@ namespace Shogi.Domain
PreviousMoveTo = Vector2.Zero;
}
+ public Dictionary State => board;
public List ActivePlayerHand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand;
- public Vector2 Player1KingPosition => FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
+ public Vector2 Player1KingPosition => Notation.FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
{
var piece = kvp.Value;
return piece!.IsKing() && piece!.Owner == WhichPlayer.Player1;
}).Key);
- public Vector2 Player2KingPosition => FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
+ public Vector2 Player2KingPosition => Notation.FromBoardNotation(this.board.Where(kvp => kvp.Value != null).Single(kvp =>
{
var piece = kvp.Value;
return piece!.IsKing() && piece!.Owner == WhichPlayer.Player2;
@@ -47,12 +52,12 @@ namespace Shogi.Domain
///
/// Copy constructor.
///
- public ShogiBoardState(ShogiBoardState other) : this()
+ public BoardState(BoardState other) : this()
{
foreach (var kvp in other.board)
{
// Replace copy constructor with static factory method in Piece.cs
- board[kvp.Key] = kvp.Value == null ? null : Piece.Create(kvp.Value);
+ board[kvp.Key] = kvp.Value == null ? null : Piece.CreateCopy(kvp.Value);
}
WhoseTurn = other.WhoseTurn;
InCheck = other.InCheck;
@@ -71,14 +76,14 @@ namespace Shogi.Domain
public Piece? this[Vector2 vector]
{
- get => this[ToBoardNotation(vector)];
- set => this[ToBoardNotation(vector)] = value;
+ get => this[Notation.ToBoardNotation(vector)];
+ set => this[Notation.ToBoardNotation(vector)] = value;
}
public Piece? this[int x, int y]
{
- get => this[ToBoardNotation(x, y)];
- set => this[ToBoardNotation(x, y)] = value;
+ get => this[Notation.ToBoardNotation(x, y)];
+ set => this[Notation.ToBoardNotation(x, y)] = value;
}
internal void RememberAsMostRecentMove(Vector2 from, Vector2 to)
@@ -111,7 +116,7 @@ namespace Shogi.Domain
internal List GetTilesOccupiedBy(WhichPlayer whichPlayer) => board
.Where(kvp => kvp.Value?.Owner == whichPlayer)
- .Select(kvp => new BoardTile(FromBoardNotation(kvp.Key), kvp.Value!))
+ .Select(kvp => new BoardTile(Notation.FromBoardNotation(kvp.Key), kvp.Value!))
.ToList();
internal void Capture(Vector2 to)
@@ -144,27 +149,6 @@ namespace Shogi.Domain
}
return null;
}
- public static string ToBoardNotation(Vector2 vector)
- {
- return ToBoardNotation((int)vector.X, (int)vector.Y);
- }
- private static string ToBoardNotation(int x, int y)
- {
- var file = (char)(x + A);
- var rank = y + 1;
- return $"{file}{rank}";
- }
- public static Vector2 FromBoardNotation(string notation)
- {
- if (Regex.IsMatch(notation, BoardNotationRegex))
- {
- var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase);
- char file = match.Groups["file"].Value[0];
- int rank = int.Parse(match.Groups["rank"].Value);
- return new Vector2(file - A, rank - 1);
- }
- throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
- }
private void InitializeBoardState()
{
diff --git a/Shogi.Domain/Notation.cs b/Shogi.Domain/Notation.cs
new file mode 100644
index 0000000..a6fb831
--- /dev/null
+++ b/Shogi.Domain/Notation.cs
@@ -0,0 +1,33 @@
+using System.Text.RegularExpressions;
+
+namespace Shogi.Domain
+{
+ public static class Notation
+ {
+ private static readonly string BoardNotationRegex = @"(?[A-I])(?[1-9])";
+ private static readonly char A = 'A';
+
+ public static string ToBoardNotation(Vector2 vector)
+ {
+ return ToBoardNotation((int)vector.X, (int)vector.Y);
+ }
+
+ public static string ToBoardNotation(int x, int y)
+ {
+ var file = (char)(x + A);
+ var rank = y + 1;
+ return $"{file}{rank}";
+ }
+ public static Vector2 FromBoardNotation(string notation)
+ {
+ if (Regex.IsMatch(notation, BoardNotationRegex))
+ {
+ var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase);
+ char file = match.Groups["file"].Value[0];
+ int rank = int.Parse(match.Groups["rank"].Value);
+ return new Vector2(file - A, rank - 1);
+ }
+ throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
+ }
+ }
+}
diff --git a/Shogi.Domain/Pieces/Piece.cs b/Shogi.Domain/Pieces/Piece.cs
index 7135edd..eb57909 100644
--- a/Shogi.Domain/Pieces/Piece.cs
+++ b/Shogi.Domain/Pieces/Piece.cs
@@ -9,7 +9,7 @@ namespace Shogi.Domain.Pieces
///
/// Creates a clone of an existing piece.
///
- public static Piece Create(Piece piece) => Create(piece.WhichPiece, piece.Owner, piece.IsPromoted);
+ public static Piece CreateCopy(Piece piece) => Create(piece.WhichPiece, piece.Owner, piece.IsPromoted);
public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
{
return piece switch
@@ -25,8 +25,6 @@ namespace Shogi.Domain.Pieces
_ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.")
};
}
-
- // TODO: MoveSet doesn't account for Player2's pieces which are upside-down.
public abstract IEnumerable MoveSet { get; }
public WhichPiece WhichPiece { get; }
public WhichPlayer Owner { get; private set; }
diff --git a/Shogi.Domain/Shogi.cs b/Shogi.Domain/Session.cs
similarity index 85%
rename from Shogi.Domain/Shogi.cs
rename to Shogi.Domain/Session.cs
index 028bb3b..0584722 100644
--- a/Shogi.Domain/Shogi.cs
+++ b/Shogi.Domain/Session.cs
@@ -8,21 +8,38 @@ namespace Shogi.Domain
/// The board is always from Player1's perspective.
/// [0,0] is the lower-left position, [8,8] is the higher-right position
///
- public sealed class Shogi
+ public sealed class Session
{
- private readonly ShogiBoardState boardState;
+ private readonly Tuple Players;
+ private readonly BoardState boardState;
private readonly StandardRules rules;
+ private readonly SessionMetadata metadata;
- public Shogi() : this(new ShogiBoardState())
+ public Session() : this(new BoardState())
{
}
- public Shogi(ShogiBoardState board)
+ public Session(BoardState state) : this(state, new SessionMetadata(string.Empty, false, string.Empty, string.Empty))
{
- this.rules = new StandardRules(board);
- this.boardState = board;
}
+ public Session(BoardState state, SessionMetadata metadata)
+ {
+ rules = new StandardRules(state);
+ boardState = state;
+ this.metadata = metadata;
+ Players = new(string.Empty, string.Empty);
+ }
+
+ public string Name => metadata.Name;
+ public string Player1Name => metadata.Player1;
+ public string? Player2Name => metadata.Player2;
+ public IDictionary BoardState => boardState.State;
+ public IList Player1Hand => boardState.Player1Hand;
+ public IList Player2Hand => boardState.Player2Hand;
+ public WhichPlayer? InCheck => boardState.InCheck;
+ public bool IsCheckMate => boardState.IsCheckmate;
+
///
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
///
@@ -33,7 +50,7 @@ namespace Shogi.Domain
///
public void Move(string from, string to, bool isPromotion)
{
- var simulationState = new ShogiBoardState(boardState);
+ var simulationState = new BoardState(boardState);
var simulation = new StandardRules(simulationState);
var moveResult = simulation.Move(from, to, isPromotion);
if (!moveResult.Success)
@@ -83,7 +100,7 @@ namespace Shogi.Domain
throw new InvalidOperationException("Illegal placement of piece from the hand. Destination is not empty.");
}
- var toVector = ShogiBoardState.FromBoardNotation(to);
+ var toVector = Notation.FromBoardNotation(to);
switch (pieceInHand)
{
case WhichPiece.Knight:
@@ -109,7 +126,7 @@ namespace Shogi.Domain
}
}
- var tempBoard = new ShogiBoardState(boardState);
+ var tempBoard = new BoardState(boardState);
var simulation = new StandardRules(tempBoard);
var moveResult = simulation.Move(pieceInHand, to);
if (!moveResult.Success)
diff --git a/Shogi.Domain/SessionMetadata.cs b/Shogi.Domain/SessionMetadata.cs
new file mode 100644
index 0000000..972823c
--- /dev/null
+++ b/Shogi.Domain/SessionMetadata.cs
@@ -0,0 +1,26 @@
+namespace Shogi.Domain
+{
+ ///
+ /// A representation of a Session without the board and game-rules.
+ ///
+ public class SessionMetadata
+ {
+ public string Name { get; }
+ public string Player1 { get; }
+ public string? Player2 { get; private set; }
+ public bool IsPrivate { get; }
+
+ public SessionMetadata(string name, bool isPrivate, string player1, string? player2 = null)
+ {
+ Name = name;
+ IsPrivate = isPrivate;
+ Player1 = player1;
+ Player2 = player2;
+ }
+
+ public void SetPlayer2(string user)
+ {
+ Player2 = user;
+ }
+ }
+}
diff --git a/Shogi.Domain/StandardRules.cs b/Shogi.Domain/StandardRules.cs
index 2699d16..9d9b588 100644
--- a/Shogi.Domain/StandardRules.cs
+++ b/Shogi.Domain/StandardRules.cs
@@ -6,9 +6,9 @@ namespace Shogi.Domain
{
internal class StandardRules
{
- private readonly ShogiBoardState boardState;
+ private readonly BoardState boardState;
- internal StandardRules(ShogiBoardState board)
+ internal StandardRules(BoardState board)
{
boardState = board;
}
@@ -22,8 +22,8 @@ namespace Shogi.Domain
/// A describing the success or failure of the move.
internal MoveResult Move(string fromNotation, string toNotation, bool isPromotionRequested = false)
{
- var from = ShogiBoardState.FromBoardNotation(fromNotation);
- var to = ShogiBoardState.FromBoardNotation(toNotation);
+ var from = Notation.FromBoardNotation(fromNotation);
+ var to = Notation.FromBoardNotation(toNotation);
var fromPiece = boardState[from];
if (fromPiece == null)
{
@@ -69,7 +69,7 @@ namespace Shogi.Domain
/// A describing the success or failure of the simulation.
internal MoveResult Move(WhichPiece pieceInHand, string toNotation)
{
- var to = ShogiBoardState.FromBoardNotation(toNotation);
+ var to = Notation.FromBoardNotation(toNotation);
var index = boardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand);
if (index == -1)
{
@@ -130,7 +130,7 @@ namespace Shogi.Domain
// Get line equation from king through the now-unoccupied location.
var direction = Vector2.Subtract(kingPosition, boardState.PreviousMoveFrom);
var slope = Math.Abs(direction.Y / direction.X);
- var path = ShogiBoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMoveFrom, Vector2.Normalize(direction));
+ var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMoveFrom, Vector2.Normalize(direction));
var threat = boardState.QueryFirstPieceInPath(path);
if (threat == null || threat.Owner == previousMovedPiece.Owner) return false;
// If absolute slope is 45°, look for a bishop along the line.
@@ -253,7 +253,7 @@ namespace Shogi.Domain
var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget);
return path
.SkipLast(1)
- .All(position => boardState[ShogiBoardState.ToBoardNotation(position)] == null);
+ .All(position => boardState[Notation.ToBoardNotation(position)] == null);
}
}
}