yep
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
using Gameboard.ShogiUI.Sockets.Managers;
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -13,34 +15,36 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
[Route("[controller]")]
|
||||
public class GameController : ControllerBase
|
||||
{
|
||||
private readonly IGameboardManager manager;
|
||||
private static readonly string UsernameClaim = "preferred_username";
|
||||
private readonly IGameboardManager gameboardManager;
|
||||
private readonly IGameboardRepository gameboardRepository;
|
||||
private readonly ISocketConnectionManager communicationManager;
|
||||
private readonly IGameboardRepository repository;
|
||||
private string? JwtUserName => HttpContext.User.Claims.FirstOrDefault(c => c.Type == UsernameClaim)?.Value;
|
||||
|
||||
public GameController(
|
||||
IGameboardRepository repository,
|
||||
IGameboardManager manager,
|
||||
ISocketConnectionManager communicationManager)
|
||||
{
|
||||
this.manager = manager;
|
||||
gameboardManager = manager;
|
||||
gameboardRepository = repository;
|
||||
this.communicationManager = communicationManager;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
[HttpPost("JoinCode")]
|
||||
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
|
||||
{
|
||||
var userName = HttpContext.User.Claims.First(c => c.Type == "preferred_username").Value;
|
||||
var isPlayer1 = await manager.IsPlayer1(request.SessionName, userName);
|
||||
if (isPlayer1)
|
||||
{
|
||||
var code = await repository.PostJoinCode(request.SessionName, userName);
|
||||
return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UnauthorizedResult();
|
||||
}
|
||||
|
||||
//var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName);
|
||||
//if (isPlayer1)
|
||||
//{
|
||||
// var code = await gameboardRepository.PostJoinCode(request.SessionName, userName);
|
||||
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
return new UnauthorizedResult();
|
||||
//}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
@@ -48,19 +52,58 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
|
||||
{
|
||||
|
||||
var isGuest = manager.IsGuest(request.GuestId);
|
||||
var isPlayer1 = manager.IsPlayer1(request.SessionName, request.GuestId);
|
||||
if (isGuest && await isPlayer1)
|
||||
{
|
||||
var code = await repository.PostJoinCode(request.SessionName, request.GuestId);
|
||||
return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UnauthorizedResult();
|
||||
}
|
||||
//var isGuest = gameboardManager.IsGuest(request.GuestId);
|
||||
//var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId);
|
||||
//if (isGuest && await isPlayer1)
|
||||
//{
|
||||
// var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId);
|
||||
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
return new UnauthorizedResult();
|
||||
//}
|
||||
}
|
||||
|
||||
[HttpPost("{gameName}/Move")]
|
||||
public async Task<IActionResult> PostMove([FromRoute] string gameName, [FromBody] PostMove request)
|
||||
{
|
||||
Models.User? user = null;
|
||||
if (Request.Cookies.ContainsKey(SocketController.WebSessionKey))
|
||||
{
|
||||
var webSessionId = Guid.Parse(Request.Cookies[SocketController.WebSessionKey]!);
|
||||
user = await gameboardManager.ReadUser(webSessionId);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(JwtUserName))
|
||||
{
|
||||
user = await gameboardManager.ReadUser(JwtUserName);
|
||||
}
|
||||
|
||||
var session = await gameboardManager.ReadSession(gameName);
|
||||
|
||||
if (session == null || user == null || (session.Player1 != user.Name && session.Player2 != user.Name))
|
||||
{
|
||||
throw new UnauthorizedAccessException("You are not seated at this game.");
|
||||
}
|
||||
|
||||
var move = request.Move;
|
||||
var moveModel = move.PieceFromCaptured.HasValue
|
||||
? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion)
|
||||
: new Models.Move(move.From!, move.To, move.IsPromotion);
|
||||
var moveSuccess = session.Shogi.Move(moveModel);
|
||||
|
||||
if (moveSuccess)
|
||||
{
|
||||
await communicationManager.BroadcastToPlayers(new MoveResponse
|
||||
{
|
||||
GameName = session.Name,
|
||||
PlayerName = user.Name,
|
||||
Move = moveModel.ToServiceModel()
|
||||
}, session.Player1, session.Player2);
|
||||
return Ok();
|
||||
}
|
||||
throw new InvalidOperationException("Illegal move.");
|
||||
}
|
||||
// TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
|
||||
//[Route("")]
|
||||
//public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
||||
@@ -69,7 +112,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
// var success = await repository.CreateSession(model);
|
||||
// if (success)
|
||||
// {
|
||||
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Socket.Types.ClientAction.CreateGame)
|
||||
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame)
|
||||
// {
|
||||
// Game = model.ToServiceModel(),
|
||||
// PlayerName =
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Gameboard.ShogiUI.Sockets.Managers;
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -14,10 +16,13 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
[ApiController]
|
||||
public class SocketController : ControllerBase
|
||||
{
|
||||
public static readonly string WebSessionKey = "session-id";
|
||||
private readonly ILogger<SocketController> logger;
|
||||
private readonly ISocketTokenManager tokenManager;
|
||||
private readonly IGameboardManager gameboardManager;
|
||||
private readonly IGameboardRepository gameboardRepository;
|
||||
private readonly CookieOptions createSessionOptions;
|
||||
private readonly CookieOptions deleteSessionOptions;
|
||||
|
||||
public SocketController(
|
||||
ILogger<SocketController> logger,
|
||||
@@ -29,6 +34,23 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
this.tokenManager = tokenManager;
|
||||
this.gameboardManager = gameboardManager;
|
||||
this.gameboardRepository = gameboardRepository;
|
||||
createSessionOptions = new CookieOptions
|
||||
{
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.None,
|
||||
Expires = DateTimeOffset.Now.AddYears(5)
|
||||
};
|
||||
deleteSessionOptions = new CookieOptions();
|
||||
}
|
||||
|
||||
[HttpGet("Yep")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Yep()
|
||||
{
|
||||
deleteSessionOptions.Expires = DateTimeOffset.Now.AddDays(-1);
|
||||
Response.Cookies.Append(WebSessionKey, "", deleteSessionOptions);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("Token")]
|
||||
@@ -39,25 +61,36 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||
return new JsonResult(new GetTokenResponse(token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a token for guest users to send when requesting a socket connection.
|
||||
/// Sends a HttpOnly cookie to the client with which to identify guest users.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("GuestToken")]
|
||||
public async Task<IActionResult> GetGuestToken([FromQuery] GetGuestToken request)
|
||||
public async Task<IActionResult> GetGuestToken()
|
||||
{
|
||||
if (request.ClientId == null)
|
||||
var cookies = Request.Cookies;
|
||||
var webSessionId = cookies.ContainsKey(WebSessionKey)
|
||||
? Guid.Parse(cookies[WebSessionKey]!)
|
||||
: Guid.NewGuid();
|
||||
var webSessionIdAsString = webSessionId.ToString();
|
||||
|
||||
var user = await gameboardRepository.ReadGuestUser(webSessionId);
|
||||
if (user == null)
|
||||
{
|
||||
var clientId = await gameboardManager.CreateGuestUser();
|
||||
var token = tokenManager.GenerateToken(clientId);
|
||||
return new JsonResult(new GetGuestTokenResponse(clientId, token));
|
||||
var userName = await gameboardManager.CreateGuestUser(webSessionId);
|
||||
var token = tokenManager.GenerateToken(webSessionIdAsString);
|
||||
Response.Cookies.Append(WebSessionKey, webSessionIdAsString, createSessionOptions);
|
||||
return new JsonResult(new GetGuestTokenResponse(userName, token));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await gameboardRepository.IsGuestUser(request.ClientId))
|
||||
{
|
||||
var token = tokenManager.GenerateToken(request.ClientId);
|
||||
return new JsonResult(new GetGuestTokenResponse(request.ClientId, token));
|
||||
}
|
||||
var token = tokenManager.GenerateToken(webSessionIdAsString);
|
||||
Response.Cookies.Append(WebSessionKey, webSessionIdAsString, createSessionOptions);
|
||||
return new JsonResult(new GetGuestTokenResponse(user.Name, token));
|
||||
}
|
||||
return new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -26,12 +26,12 @@ namespace Gameboard.ShogiUI.Sockets.Extensions
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void PrintStateAsAscii(this Models.Shogi self)
|
||||
public static string PrintStateAsAscii(this Models.Shogi self)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(" Player 2(.)");
|
||||
builder.AppendLine();
|
||||
for (var y = 0; y < 9; y++)
|
||||
for (var y = 8; y >= 0; y--)
|
||||
{
|
||||
builder.Append("- ");
|
||||
for (var x = 0; x < 8; x++) builder.Append("- - ");
|
||||
@@ -40,7 +40,7 @@ namespace Gameboard.ShogiUI.Sockets.Extensions
|
||||
builder.Append('|');
|
||||
for (var x = 0; x < 9; x++)
|
||||
{
|
||||
var piece = self.Board[y, x];
|
||||
var piece = self.Board[x, y];
|
||||
if (piece == null)
|
||||
{
|
||||
builder.Append(" ");
|
||||
@@ -58,7 +58,7 @@ namespace Gameboard.ShogiUI.Sockets.Extensions
|
||||
builder.Append("- -");
|
||||
builder.AppendLine();
|
||||
builder.Append(" Player 1");
|
||||
Console.WriteLine(builder.ToString());
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using Gameboard.ShogiUI.Sockets.Models;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
{
|
||||
public interface IActiveSessionManager
|
||||
{
|
||||
void Add(Session session);
|
||||
Session? Get(string sessionName);
|
||||
}
|
||||
|
||||
// TODO: Consider moving this class' functionality into the ConnectionManager class.
|
||||
public class ActiveSessionManager : IActiveSessionManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Session> Sessions;
|
||||
|
||||
public ActiveSessionManager()
|
||||
{
|
||||
Sessions = new ConcurrentDictionary<string, Session>();
|
||||
}
|
||||
|
||||
public void Add(Session session) => Sessions.TryAdd(session.Name, session);
|
||||
|
||||
public Session? Get(string sessionName)
|
||||
{
|
||||
if (Sessions.TryGetValue(sessionName, out var session))
|
||||
{
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Gameboard.ShogiUI.Sockets.Models;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
@@ -31,14 +31,14 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
|
||||
if (!success)
|
||||
{
|
||||
var error = new CreateGameResponse(request.Action)
|
||||
var error = new CreateGameResponse()
|
||||
{
|
||||
Error = "Unable to create game with this name."
|
||||
};
|
||||
await connectionManager.BroadcastToPlayers(error, userName);
|
||||
}
|
||||
|
||||
var response = new CreateGameResponse(request.Action)
|
||||
var response = new CreateGameResponse()
|
||||
{
|
||||
PlayerName = userName,
|
||||
Game = model.ToServiceModel()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
@@ -24,7 +24,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
{
|
||||
var joinSucceeded = await gameboardManager.AssignPlayer2ToSession(request.GameName, userName);
|
||||
|
||||
var response = new JoinGameResponse(ClientAction.JoinGame)
|
||||
var response = new JoinGameResponse()
|
||||
{
|
||||
PlayerName = userName,
|
||||
GameName = request.GameName
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
var sessions = await repository.ReadSessionMetadatas();
|
||||
var games = sessions.Select(s => new Game(s.Name, s.Player1, s.Player2)).ToList();
|
||||
|
||||
var response = new ListGamesResponse(ClientAction.ListGames)
|
||||
var response = new ListGamesResponse()
|
||||
{
|
||||
Games = games
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Gameboard.ShogiUI.Sockets.Models;
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -21,18 +21,15 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
private readonly ILogger<LoadGameHandler> logger;
|
||||
private readonly IGameboardRepository gameboardRepository;
|
||||
private readonly ISocketConnectionManager communicationManager;
|
||||
private readonly IActiveSessionManager boardManager;
|
||||
|
||||
public LoadGameHandler(
|
||||
ILogger<LoadGameHandler> logger,
|
||||
ISocketConnectionManager communicationManager,
|
||||
IGameboardRepository gameboardRepository,
|
||||
IActiveSessionManager boardManager)
|
||||
IGameboardRepository gameboardRepository)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.gameboardRepository = gameboardRepository;
|
||||
this.communicationManager = communicationManager;
|
||||
this.boardManager = boardManager;
|
||||
}
|
||||
|
||||
public async Task Handle(LoadGameRequest request, string userName)
|
||||
@@ -41,15 +38,14 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
if (sessionModel == null)
|
||||
{
|
||||
logger.LogWarning("{action} - {user} was unable to load session named {session}.", ClientAction.LoadGame, userName, request.GameName);
|
||||
var error = new LoadGameResponse(ClientAction.LoadGame) { Error = "Game not found." };
|
||||
var error = new LoadGameResponse() { Error = "Game not found." };
|
||||
await communicationManager.BroadcastToPlayers(error, userName);
|
||||
return;
|
||||
}
|
||||
|
||||
communicationManager.SubscribeToGame(sessionModel, userName);
|
||||
boardManager.Add(sessionModel);
|
||||
|
||||
var response = new LoadGameResponse(ClientAction.LoadGame)
|
||||
var response = new LoadGameResponse()
|
||||
{
|
||||
Game = new SessionMetadata(sessionModel).ToServiceModel(),
|
||||
BoardState = sessionModel.Shogi.ToServiceModel(),
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using Gameboard.ShogiUI.Sockets.Models;
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
{
|
||||
public interface IMoveHandler
|
||||
@@ -12,66 +9,53 @@ namespace Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers
|
||||
}
|
||||
public class MoveHandler : IMoveHandler
|
||||
{
|
||||
private readonly IActiveSessionManager boardManager;
|
||||
private readonly IGameboardManager gameboardManager;
|
||||
private readonly ISocketConnectionManager communicationManager;
|
||||
private readonly ISocketConnectionManager connectionManager;
|
||||
public MoveHandler(
|
||||
IActiveSessionManager boardManager,
|
||||
ISocketConnectionManager communicationManager,
|
||||
ISocketConnectionManager connectionManager,
|
||||
IGameboardManager gameboardManager)
|
||||
{
|
||||
this.boardManager = boardManager;
|
||||
this.gameboardManager = gameboardManager;
|
||||
this.communicationManager = communicationManager;
|
||||
this.connectionManager = connectionManager;
|
||||
}
|
||||
|
||||
public async Task Handle(MoveRequest request, string userName)
|
||||
{
|
||||
Move moveModel;
|
||||
Models.Move moveModel;
|
||||
if (request.Move.PieceFromCaptured.HasValue)
|
||||
{
|
||||
moveModel = new Move(request.Move.PieceFromCaptured.Value, request.Move.To);
|
||||
moveModel = new Models.Move(request.Move.PieceFromCaptured.Value, request.Move.To);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveModel = new Move(request.Move.From!, request.Move.To, request.Move.IsPromotion);
|
||||
moveModel = new Models.Move(request.Move.From!, request.Move.To, request.Move.IsPromotion);
|
||||
}
|
||||
|
||||
var board = boardManager.Get(request.GameName);
|
||||
if (board == null)
|
||||
var session = await gameboardManager.ReadSession(request.GameName);
|
||||
if (session != null)
|
||||
{
|
||||
// TODO: Find a flow for this
|
||||
var response = new MoveResponse(ServiceModels.Socket.Types.ClientAction.Move)
|
||||
var shogi = session.Shogi;
|
||||
var moveSuccess = shogi.Move(moveModel);
|
||||
if (moveSuccess)
|
||||
{
|
||||
Error = $"Game isn't loaded. Send a message with the {ServiceModels.Socket.Types.ClientAction.LoadGame} action first."
|
||||
};
|
||||
await communicationManager.BroadcastToPlayers(response, userName);
|
||||
await gameboardManager.CreateBoardState(session.Name, shogi);
|
||||
var response = new MoveResponse()
|
||||
{
|
||||
GameName = request.GameName,
|
||||
PlayerName = userName,
|
||||
Move = moveModel.ToServiceModel()
|
||||
};
|
||||
await connectionManager.BroadcastToPlayers(response, session.Player1, session.Player2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = new MoveResponse()
|
||||
{
|
||||
Error = "Invalid move."
|
||||
};
|
||||
await connectionManager.BroadcastToPlayers(response, userName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//var moveSuccess = board.Move(boardMove);
|
||||
//if (moveSuccess)
|
||||
//{
|
||||
// await gameboardRepository.PostMove(request.GameName, new PostMove(moveModel.ToApiModel()));
|
||||
// var boardState = new BoardState(board);
|
||||
// var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
||||
// {
|
||||
// GameName = request.GameName,
|
||||
// PlayerName = userName,
|
||||
// BoardState = boardState.ToServiceModel()
|
||||
// };
|
||||
// await communicationManager.BroadcastToGame(request.GameName, response);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// var response = new Service.Messages.MoveResponse(Service.Types.ClientAction.Move)
|
||||
// {
|
||||
// Error = "Invalid move."
|
||||
// };
|
||||
// await communicationManager.BroadcastToPlayers(response, userName);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,20 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
{
|
||||
public interface IGameboardManager
|
||||
{
|
||||
Task<string> CreateGuestUser();
|
||||
Task<string> CreateGuestUser(Guid webSessionId);
|
||||
Task<bool> IsPlayer1(string sessionName, string playerName);
|
||||
bool IsGuest(string playerName);
|
||||
Task<bool> CreateSession(SessionMetadata session);
|
||||
Task<Session?> ReadSession(string gameName);
|
||||
Task<bool> UpdateSession(Session session);
|
||||
Task<bool> UpdateSession(SessionMetadata session);
|
||||
Task<bool> AssignPlayer2ToSession(string sessionName, string userName);
|
||||
Task<bool> CreateBoardState(string sessionName, Shogi shogi);
|
||||
Task<User?> ReadUser(string userName);
|
||||
Task<User?> ReadUser(Guid webSessionId);
|
||||
}
|
||||
|
||||
public class GameboardManager : IGameboardManager
|
||||
{
|
||||
private const int MaxTries = 3;
|
||||
private const string GuestPrefix = "Guest-";
|
||||
private readonly IGameboardRepository repository;
|
||||
|
||||
public GameboardManager(IGameboardRepository repository)
|
||||
@@ -27,22 +28,30 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public async Task<string> CreateGuestUser()
|
||||
public async Task<string> CreateGuestUser(Guid webSessionId)
|
||||
{
|
||||
var count = 0;
|
||||
while (count < MaxTries)
|
||||
{
|
||||
count++;
|
||||
var clientId = $"Guest-{Guid.NewGuid()}";
|
||||
var isCreated = await repository.CreateGuestUser(clientId);
|
||||
var isCreated = await repository.CreateGuestUser(clientId, webSessionId);
|
||||
if (isCreated)
|
||||
{
|
||||
return clientId;
|
||||
}
|
||||
}
|
||||
throw new OperationCanceledException($"Failed to create guest user after {MaxTries} tries.");
|
||||
throw new OperationCanceledException($"Failed to create guest user after {count} tries.");
|
||||
}
|
||||
|
||||
public Task<User?> ReadUser(Guid webSessionId)
|
||||
{
|
||||
return repository.ReadGuestUser(webSessionId);
|
||||
}
|
||||
public Task<User?> ReadUser(string userName)
|
||||
{
|
||||
return repository.ReadUser(userName);
|
||||
}
|
||||
public async Task<bool> IsPlayer1(string sessionName, string playerName)
|
||||
{
|
||||
//var session = await repository.GetGame(sessionName);
|
||||
@@ -65,8 +74,6 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
return repository.CreateSession(session);
|
||||
}
|
||||
|
||||
public bool IsGuest(string playerName) => playerName.StartsWith(GuestPrefix);
|
||||
|
||||
public Task<Session?> ReadSession(string sessionName)
|
||||
{
|
||||
return repository.ReadSession(sessionName);
|
||||
@@ -77,15 +84,20 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
/// </summary>
|
||||
/// <param name="session">The session to save.</param>
|
||||
/// <returns>True if the session was saved successfully.</returns>
|
||||
public Task<bool> UpdateSession(Session session)
|
||||
public Task<bool> UpdateSession(SessionMetadata session)
|
||||
{
|
||||
return repository.UpdateSession(session);
|
||||
}
|
||||
|
||||
public Task<bool> CreateBoardState(string sessionName, Shogi shogi)
|
||||
{
|
||||
return repository.CreateBoardState(sessionName, shogi);
|
||||
}
|
||||
|
||||
public async Task<bool> AssignPlayer2ToSession(string sessionName, string userName)
|
||||
{
|
||||
var isSuccess = false;
|
||||
var session = await repository.ReadSession(sessionName);
|
||||
var session = await repository.ReadSessionMetaData(sessionName);
|
||||
if (session != null && !session.IsPrivate && string.IsNullOrEmpty(session.Player2))
|
||||
{
|
||||
session.SetPlayer2(userName);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Gameboard.ShogiUI.Sockets.Extensions;
|
||||
using Gameboard.ShogiUI.Sockets.Models;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -19,7 +19,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
void SubscribeToBroadcast(WebSocket socket, string playerName);
|
||||
void UnsubscribeFromBroadcastAndGames(string playerName);
|
||||
void UnsubscribeFromGame(string gameName, string playerName);
|
||||
Task BroadcastToPlayers(IResponse response, params string[] playerNames);
|
||||
Task BroadcastToPlayers(IResponse response, params string?[] playerNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,17 +84,16 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
}
|
||||
}
|
||||
|
||||
public async Task BroadcastToPlayers(IResponse response, params string[] playerNames)
|
||||
public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames)
|
||||
{
|
||||
var tasks = new List<Task>(playerNames.Length);
|
||||
foreach (var name in playerNames)
|
||||
{
|
||||
if (connections.TryGetValue(name, out var socket))
|
||||
if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket))
|
||||
{
|
||||
var serialized = JsonConvert.SerializeObject(response);
|
||||
logger.LogInformation("Response to {0} \n{1}\n", name, serialized);
|
||||
tasks.Add(socket.SendTextAsync(serialized));
|
||||
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
||||
public class SocketTokenManager : ISocketTokenManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Key is userName
|
||||
/// Key is userName or webSessionId
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Guid> Tokens;
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.Utility
|
||||
{
|
||||
public class Request : IRequest
|
||||
{
|
||||
public ClientAction Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using System;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using Gameboard.ShogiUI.Sockets.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Models
|
||||
{
|
||||
[DebuggerDisplay("{From} - {To}")]
|
||||
public class Move
|
||||
{
|
||||
private static readonly string BoardNotationRegex = @"(?<file>[A-I])(?<rank>[1-9])";
|
||||
private static readonly char A = 'A';
|
||||
|
||||
public Vector2? From { get; }
|
||||
public Vector2? From { get; } // TODO: Use string notation
|
||||
public bool IsPromotion { get; }
|
||||
public WhichPiece? PieceFromHand { get; }
|
||||
public Vector2 To { get; }
|
||||
@@ -37,8 +33,8 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
/// <param name="isPromotion">If the moving piece should be promoted.</param>
|
||||
public Move(string fromNotation, string toNotation, bool isPromotion = false)
|
||||
{
|
||||
From = FromBoardNotation(fromNotation);
|
||||
To = FromBoardNotation(toNotation);
|
||||
From = NotationHelper.FromBoardNotation(fromNotation);
|
||||
To = NotationHelper.FromBoardNotation(toNotation);
|
||||
IsPromotion = isPromotion;
|
||||
}
|
||||
|
||||
@@ -52,35 +48,16 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
{
|
||||
From = null;
|
||||
PieceFromHand = pieceFromHand;
|
||||
To = FromBoardNotation(toNotation);
|
||||
To = NotationHelper.FromBoardNotation(toNotation);
|
||||
IsPromotion = isPromotion;
|
||||
}
|
||||
|
||||
public ServiceModels.Socket.Types.Move ToServiceModel() => new()
|
||||
public ServiceModels.Types.Move ToServiceModel() => new()
|
||||
{
|
||||
From = From.HasValue ? ToBoardNotation(From.Value) : null,
|
||||
From = From.HasValue ? NotationHelper.ToBoardNotation(From.Value) : null,
|
||||
IsPromotion = IsPromotion,
|
||||
PieceFromCaptured = PieceFromHand.HasValue ? PieceFromHand : null,
|
||||
To = ToBoardNotation(To)
|
||||
To = NotationHelper.ToBoardNotation(To)
|
||||
};
|
||||
|
||||
private static string ToBoardNotation(Vector2 vector)
|
||||
{
|
||||
var file = (char)(vector.X + A);
|
||||
var rank = vector.Y + 1;
|
||||
return $"{file}{rank}";
|
||||
}
|
||||
|
||||
private static Vector2 FromBoardNotation(string notation)
|
||||
{
|
||||
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);
|
||||
}
|
||||
throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using PathFinding;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
Owner = owner;
|
||||
IsPromoted = isPromoted;
|
||||
}
|
||||
public Piece(Piece piece) : this(piece.WhichPiece, piece.Owner, piece.IsPromoted)
|
||||
{
|
||||
}
|
||||
|
||||
public bool CanPromote => !IsPromoted
|
||||
&& WhichPiece != WhichPiece.King
|
||||
@@ -54,9 +57,9 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
_ => throw new System.NotImplementedException()
|
||||
};
|
||||
|
||||
public ServiceModels.Socket.Types.Piece ToServiceModel()
|
||||
public ServiceModels.Types.Piece ToServiceModel()
|
||||
{
|
||||
return new ServiceModels.Socket.Types.Piece
|
||||
return new ServiceModels.Types.Piece
|
||||
{
|
||||
IsPromoted = IsPromoted,
|
||||
Owner = Owner,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Player1 { get; }
|
||||
public string? Player2 { get; }
|
||||
public string? Player2 { get; private set; }
|
||||
public bool IsPrivate { get; }
|
||||
|
||||
public SessionMetadata(string name, bool isPrivate, string player1, string? player2)
|
||||
@@ -25,6 +25,11 @@
|
||||
Player2 = sessionModel.Player2;
|
||||
}
|
||||
|
||||
public ServiceModels.Socket.Types.Game ToServiceModel() => new(Name, Player1, Player2);
|
||||
public void SetPlayer2(string playerName)
|
||||
{
|
||||
Player2 = playerName;
|
||||
}
|
||||
|
||||
public ServiceModels.Types.Game ToServiceModel() => new(Name, Player1, Player2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using Gameboard.ShogiUI.Sockets.Utilities;
|
||||
using PathFinding;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -19,8 +20,10 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
private Shogi? validationBoard;
|
||||
private Vector2 player1King;
|
||||
private Vector2 player2King;
|
||||
public IReadOnlyDictionary<WhichPlayer, List<Piece>> Hands { get; }
|
||||
public PlanarCollection<Piece> Board { get; } //TODO: Hide this being a getter method
|
||||
private List<Piece> Hand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand;
|
||||
public List<Piece> Player1Hand { get; }
|
||||
public List<Piece> Player2Hand { get; }
|
||||
public CoordsToNotationCollection Board { get; } //TODO: Hide this being a getter method
|
||||
public List<Move> MoveHistory { get; }
|
||||
public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2;
|
||||
public WhichPlayer? InCheck { get; private set; }
|
||||
@@ -30,15 +33,13 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
|
||||
public Shogi()
|
||||
{
|
||||
Board = new PlanarCollection<Piece>(9, 9);
|
||||
Board = new CoordsToNotationCollection();
|
||||
MoveHistory = new List<Move>(20);
|
||||
Hands = new Dictionary<WhichPlayer, List<Piece>> {
|
||||
{ WhichPlayer.Player1, new List<Piece>()},
|
||||
{ WhichPlayer.Player2, new List<Piece>()},
|
||||
};
|
||||
pathFinder = new PathFinder2D<Piece>(Board);
|
||||
player1King = new Vector2(4, 8);
|
||||
player2King = new Vector2(4, 0);
|
||||
Player1Hand = new List<Piece>();
|
||||
Player2Hand = new List<Piece>();
|
||||
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
||||
player1King = new Vector2(4, 0);
|
||||
player2King = new Vector2(4, 8);
|
||||
Error = string.Empty;
|
||||
|
||||
InitializeBoardState();
|
||||
@@ -58,24 +59,16 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
|
||||
private Shogi(Shogi toCopy)
|
||||
{
|
||||
Board = new PlanarCollection<Piece>(9, 9);
|
||||
for (var x = 0; x < 9; x++)
|
||||
for (var y = 0; y < 9; y++)
|
||||
{
|
||||
var piece = toCopy.Board[y, x];
|
||||
if (piece != null)
|
||||
{
|
||||
Board[y, x] = new Piece(piece.WhichPiece, piece.Owner, piece.IsPromoted);
|
||||
}
|
||||
}
|
||||
|
||||
pathFinder = new PathFinder2D<Piece>(Board);
|
||||
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
||||
Hands = new Dictionary<WhichPlayer, List<Piece>>
|
||||
Board = new CoordsToNotationCollection();
|
||||
foreach (var kvp in toCopy.Board)
|
||||
{
|
||||
{ WhichPlayer.Player1, new List<Piece>(toCopy.Hands[WhichPlayer.Player1]) },
|
||||
{ WhichPlayer.Player2, new List<Piece>(toCopy.Hands[WhichPlayer.Player2]) }
|
||||
};
|
||||
Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value);
|
||||
}
|
||||
|
||||
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
||||
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
||||
Player1Hand = new List<Piece>(toCopy.Player1Hand);
|
||||
Player2Hand = new List<Piece>(toCopy.Player2Hand);
|
||||
player1King = toCopy.player1King;
|
||||
player2King = toCopy.player2King;
|
||||
Error = toCopy.Error;
|
||||
@@ -115,6 +108,8 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
: validationBoard.PlaceFromBoard(move);
|
||||
if (!isValid)
|
||||
{
|
||||
// Surface the error description.
|
||||
Error = validationBoard.Error;
|
||||
// Invalidate the "throw away" board.
|
||||
validationBoard = null;
|
||||
return false;
|
||||
@@ -137,37 +132,55 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
/// <returns>True if the move was successful.</returns>
|
||||
private bool PlaceFromHand(Move move)
|
||||
{
|
||||
if (move.PieceFromHand.HasValue == false) return false; //Invalid move
|
||||
var index = Hands[WhoseTurn].FindIndex(p => p.WhichPiece == move.PieceFromHand);
|
||||
if (index < 0) return false; // Invalid move
|
||||
if (Board[move.To.Y, move.To.X] != null) return false; // Invalid move; cannot capture while playing from the hand.
|
||||
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;
|
||||
}
|
||||
|
||||
var minimumY = 0;
|
||||
switch (move.PieceFromHand.Value)
|
||||
switch (move.PieceFromHand!.Value)
|
||||
{
|
||||
case WhichPiece.Knight:
|
||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||
minimumY = WhoseTurn == WhichPlayer.Player1 ? 6 : 2;
|
||||
break;
|
||||
{
|
||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||
if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y > 6)
|
||||
|| (WhoseTurn == WhichPlayer.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.
|
||||
minimumY = WhoseTurn == WhichPlayer.Player1 ? 7 : 1;
|
||||
break;
|
||||
{
|
||||
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
||||
if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y == 8)
|
||||
|| (WhoseTurn == WhichPlayer.Player2 && move.To.Y == 0))
|
||||
{
|
||||
Error = $"{move.PieceFromHand} has no valid moves after placed.";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (WhoseTurn == WhichPlayer.Player1 && move.To.Y < minimumY) return false;
|
||||
if (WhoseTurn == WhichPlayer.Player2 && move.To.Y > minimumY) return false;
|
||||
|
||||
// Mutate the board.
|
||||
Board[move.To.Y, move.To.X] = Hands[WhoseTurn][index];
|
||||
Hands[WhoseTurn].RemoveAt(index);
|
||||
Board[move.To] = Hand[index];
|
||||
Hand.RemoveAt(index);
|
||||
|
||||
return true;
|
||||
}
|
||||
/// <returns>True if the move was successful.</returns>
|
||||
private bool PlaceFromBoard(Move move)
|
||||
{
|
||||
var fromPiece = Board[move.From.Value.Y, move.From.Value.X];
|
||||
var fromPiece = Board[move.From!.Value];
|
||||
if (fromPiece == null)
|
||||
{
|
||||
Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
|
||||
@@ -184,28 +197,28 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
return false; // Invalid move; move not part of move-set.
|
||||
}
|
||||
|
||||
var captured = Board[move.To.Y, move.To.X];
|
||||
var captured = Board[move.To];
|
||||
if (captured != null)
|
||||
{
|
||||
if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece.
|
||||
captured.Capture();
|
||||
Hands[captured.Owner].Add(captured);
|
||||
Hand.Add(captured);
|
||||
}
|
||||
|
||||
//Mutate the board.
|
||||
if (move.IsPromotion)
|
||||
{
|
||||
if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y < 3 || move.From.Value.Y < 3))
|
||||
if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5))
|
||||
{
|
||||
fromPiece.Promote();
|
||||
}
|
||||
else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y > 5 || move.From.Value.Y > 5))
|
||||
else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3))
|
||||
{
|
||||
fromPiece.Promote();
|
||||
}
|
||||
}
|
||||
Board[move.To.Y, move.To.X] = fromPiece;
|
||||
Board[move.From.Value.Y, move.From.Value.X] = null;
|
||||
Board[move.To] = fromPiece;
|
||||
Board[move.From!.Value] = null;
|
||||
if (fromPiece.WhichPiece == WhichPiece.King)
|
||||
{
|
||||
if (fromPiece.Owner == WhichPlayer.Player1)
|
||||
@@ -225,7 +238,7 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
|
||||
private bool IsPathable(Vector2 from, Vector2 to)
|
||||
{
|
||||
var piece = Board[from.Y, from.X];
|
||||
var piece = Board[from];
|
||||
if (piece == null) return false;
|
||||
|
||||
var isObstructed = false;
|
||||
@@ -239,56 +252,66 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
#region Rules Validation
|
||||
private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer)
|
||||
{
|
||||
if (whichPlayer == InCheck) return true; // If we already know the player is in check, don't bother.
|
||||
|
||||
var isCheck = false;
|
||||
var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King;
|
||||
|
||||
// Check if the move put the king in check.
|
||||
if (pathFinder.PathTo(move.To, kingPosition)) return true;
|
||||
|
||||
// Get line equation from king through the now-unoccupied location.
|
||||
var direction = Vector2.Subtract(kingPosition, move.From.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 (move.From.HasValue)
|
||||
{
|
||||
// if slope of the move is also infinity...can skip this?
|
||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||
// 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 (piece.Owner != whichPlayer)
|
||||
// if slope of the move is also infinity...can skip this?
|
||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||
{
|
||||
switch (piece.WhichPiece)
|
||||
if (piece.Owner != whichPlayer)
|
||||
{
|
||||
case WhichPiece.Rook:
|
||||
isCheck = true;
|
||||
break;
|
||||
case WhichPiece.Lance:
|
||||
if (!piece.IsPromoted) isCheck = true;
|
||||
break;
|
||||
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) =>
|
||||
});
|
||||
}
|
||||
else if (slope == 1)
|
||||
{
|
||||
if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Bishop)
|
||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||
{
|
||||
isCheck = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (slope == 0)
|
||||
{
|
||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||
if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Bishop)
|
||||
{
|
||||
isCheck = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (slope == 0)
|
||||
{
|
||||
if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Rook)
|
||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||
{
|
||||
isCheck = true;
|
||||
}
|
||||
});
|
||||
if (piece.Owner != whichPlayer && 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;
|
||||
@@ -299,12 +322,11 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
|
||||
// Assume true and try to disprove.
|
||||
var isCheckmate = true;
|
||||
Board.ForEachNotNull((piece, x, y) => // For each piece...
|
||||
Board.ForEachNotNull((piece, from) => // For each piece...
|
||||
{
|
||||
// Short circuit
|
||||
if (!isCheckmate) return;
|
||||
|
||||
var from = new Vector2(x, y);
|
||||
if (piece.Owner == InCheck) // ...owned by the player in check...
|
||||
{
|
||||
// ...evaluate if any move gets the player out of check.
|
||||
@@ -328,82 +350,108 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialize
|
||||
private void ResetEmptyRows()
|
||||
{
|
||||
for (int y = 3; y < 6; y++)
|
||||
for (int x = 0; x < 9; x++)
|
||||
Board[y, x] = null;
|
||||
}
|
||||
private void ResetFrontRow(WhichPlayer player)
|
||||
{
|
||||
int y = player == WhichPlayer.Player1 ? 6 : 2;
|
||||
for (int x = 0; x < 9; x++) Board[y, x] = new Piece(WhichPiece.Pawn, player);
|
||||
}
|
||||
private void ResetMiddleRow(WhichPlayer player)
|
||||
{
|
||||
int y = player == WhichPlayer.Player1 ? 7 : 1;
|
||||
|
||||
Board[y, 0] = null;
|
||||
for (int x = 2; x < 7; x++) Board[y, x] = null;
|
||||
Board[y, 8] = null;
|
||||
if (player == WhichPlayer.Player1)
|
||||
{
|
||||
Board[y, 1] = new Piece(WhichPiece.Bishop, player);
|
||||
Board[y, 7] = new Piece(WhichPiece.Rook, player);
|
||||
}
|
||||
else
|
||||
{
|
||||
Board[y, 1] = new Piece(WhichPiece.Rook, player);
|
||||
Board[y, 7] = new Piece(WhichPiece.Bishop, player);
|
||||
}
|
||||
}
|
||||
private void ResetRearRow(WhichPlayer player)
|
||||
{
|
||||
int y = player == WhichPlayer.Player1 ? 8 : 0;
|
||||
|
||||
Board[y, 0] = new Piece(WhichPiece.Lance, player);
|
||||
Board[y, 1] = new Piece(WhichPiece.Knight, player);
|
||||
Board[y, 2] = new Piece(WhichPiece.SilverGeneral, player);
|
||||
Board[y, 3] = new Piece(WhichPiece.GoldGeneral, player);
|
||||
Board[y, 4] = new Piece(WhichPiece.King, player);
|
||||
Board[y, 5] = new Piece(WhichPiece.GoldGeneral, player);
|
||||
Board[y, 6] = new Piece(WhichPiece.SilverGeneral, player);
|
||||
Board[y, 7] = new Piece(WhichPiece.Knight, player);
|
||||
Board[y, 8] = new Piece(WhichPiece.Lance, player);
|
||||
}
|
||||
private void InitializeBoardState()
|
||||
{
|
||||
ResetRearRow(WhichPlayer.Player2);
|
||||
ResetMiddleRow(WhichPlayer.Player2);
|
||||
ResetFrontRow(WhichPlayer.Player2);
|
||||
ResetEmptyRows();
|
||||
ResetFrontRow(WhichPlayer.Player1);
|
||||
ResetMiddleRow(WhichPlayer.Player1);
|
||||
ResetRearRow(WhichPlayer.Player1);
|
||||
Board["A1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1);
|
||||
Board["B1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1);
|
||||
Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1);
|
||||
Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1);
|
||||
Board["E1"] = new Piece(WhichPiece.King, WhichPlayer.Player1);
|
||||
Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1);
|
||||
Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1);
|
||||
Board["H1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1);
|
||||
Board["I1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1);
|
||||
|
||||
Board["A2"] = null;
|
||||
Board["B2"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player1);
|
||||
Board["C2"] = null;
|
||||
Board["D2"] = null;
|
||||
Board["E2"] = null;
|
||||
Board["F2"] = null;
|
||||
Board["G2"] = null;
|
||||
Board["H2"] = new Piece(WhichPiece.Rook, WhichPlayer.Player1);
|
||||
Board["I2"] = null;
|
||||
|
||||
Board["A3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["B3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["C3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["D3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["E3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["F3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["G3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["H3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
||||
Board["I3"] = new Piece(WhichPiece.Pawn, WhichPlayer.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, WhichPlayer.Player2);
|
||||
Board["B7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["C7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["D7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["E7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["F7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["G7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["H7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
Board["I7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
||||
|
||||
Board["A8"] = null;
|
||||
Board["B8"] = new Piece(WhichPiece.Rook, WhichPlayer.Player2);
|
||||
Board["C8"] = null;
|
||||
Board["D8"] = null;
|
||||
Board["E8"] = null;
|
||||
Board["F8"] = null;
|
||||
Board["G8"] = null;
|
||||
Board["H8"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player2);
|
||||
Board["I8"] = null;
|
||||
|
||||
Board["A9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2);
|
||||
Board["B9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2);
|
||||
Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2);
|
||||
Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2);
|
||||
Board["E9"] = new Piece(WhichPiece.King, WhichPlayer.Player2);
|
||||
Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2);
|
||||
Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2);
|
||||
Board["H9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2);
|
||||
Board["I9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public BoardState ToServiceModel()
|
||||
{
|
||||
var board = new ServiceModels.Socket.Types.Piece[9, 9];
|
||||
for (var x = 0; x < 9; x++)
|
||||
for (var y = 0; y < 9; y++)
|
||||
{
|
||||
var piece = Board[y, x];
|
||||
if (piece != null)
|
||||
{
|
||||
board[y, x] = piece.ToServiceModel();
|
||||
}
|
||||
}
|
||||
|
||||
return new BoardState
|
||||
{
|
||||
Board = board,
|
||||
Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()),
|
||||
PlayerInCheck = InCheck,
|
||||
WhoseTurn = WhoseTurn,
|
||||
Player1Hand = Hands[WhichPlayer.Player1].Select(_ => _.ToServiceModel()).ToList(),
|
||||
Player2Hand = Hands[WhichPlayer.Player2].Select(_ => _.ToServiceModel()).ToList()
|
||||
Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(),
|
||||
Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
18
Gameboard.ShogiUI.Sockets/Models/User.cs
Normal file
18
Gameboard.ShogiUI.Sockets/Models/User.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public static readonly string GuestPrefix = "Guest-";
|
||||
public string Name { get; }
|
||||
public Guid? WebSessionId { get; }
|
||||
public bool IsGuest => Name.StartsWith(GuestPrefix) && WebSessionId.HasValue;
|
||||
|
||||
public User(string name, Guid? webSessionId = null)
|
||||
{
|
||||
Name = name;
|
||||
WebSessionId = webSessionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using Gameboard.ShogiUI.Sockets.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
{
|
||||
@@ -8,7 +11,10 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public Piece?[,] Board { get; set; }
|
||||
/// <summary>
|
||||
/// A dictionary where the key is a board-notation position, like D3.
|
||||
/// </summary>
|
||||
public Dictionary<string, Piece?> Board { get; set; }
|
||||
|
||||
public Piece[] Player1Hand { get; set; }
|
||||
|
||||
@@ -25,7 +31,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
public BoardStateDocument() : base(WhichDocumentType.BoardState)
|
||||
{
|
||||
Name = string.Empty;
|
||||
Board = new Piece[9, 9];
|
||||
Board = new Dictionary<string, Piece?>(81, StringComparer.OrdinalIgnoreCase);
|
||||
Player1Hand = Array.Empty<Piece>();
|
||||
Player2Hand = Array.Empty<Piece>();
|
||||
}
|
||||
@@ -34,20 +40,23 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
: base($"{sessionName}-{DateTime.Now:O}", WhichDocumentType.BoardState)
|
||||
{
|
||||
Name = sessionName;
|
||||
Board = new Piece[9, 9];
|
||||
Board = new Dictionary<string, Piece?>(81, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
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];
|
||||
|
||||
if (piece != null)
|
||||
{
|
||||
Board[y, x] = new Piece(piece);
|
||||
var positionNotation = NotationHelper.ToBoardNotation(position);
|
||||
Board[positionNotation] = new Piece(piece);
|
||||
}
|
||||
}
|
||||
|
||||
Player1Hand = shogi.Hands[WhichPlayer.Player1].Select(model => new Piece(model)).ToArray();
|
||||
Player2Hand = shogi.Hands[WhichPlayer.Player2].Select(model => new Piece(model)).ToArray();
|
||||
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]);
|
||||
|
||||
@@ -5,10 +5,12 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
internal class CouchFindResult<T>
|
||||
{
|
||||
public T[] docs;
|
||||
public string warning;
|
||||
|
||||
public CouchFindResult()
|
||||
{
|
||||
docs = Array.Empty<T>();
|
||||
warning = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
using System;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||
{
|
||||
public class UserDocument : CouchDocument
|
||||
{
|
||||
public static string GetDocumentId(string userName) => $"org.couchdb.user:{userName}";
|
||||
|
||||
public enum LoginPlatform
|
||||
{
|
||||
Microsoft,
|
||||
@@ -12,10 +12,27 @@
|
||||
|
||||
public string Name { get; set; }
|
||||
public LoginPlatform Platform { get; set; }
|
||||
public UserDocument(string name, LoginPlatform platform) : base($"org.couchdb.user:{name}", WhichDocumentType.User)
|
||||
/// <summary>
|
||||
/// The browser session ID saved via Set-Cookie headers.
|
||||
/// Only used with guest accounts.
|
||||
/// </summary>
|
||||
public Guid? WebSessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for JSON deserializing.
|
||||
/// </summary>
|
||||
public UserDocument() : base(WhichDocumentType.User)
|
||||
{
|
||||
Name = string.Empty;
|
||||
}
|
||||
|
||||
public UserDocument(string name, Guid? webSessionId = null) : base($"org.couchdb.user:{name}", WhichDocumentType.User)
|
||||
{
|
||||
Name = name;
|
||||
Platform = platform;
|
||||
WebSessionId = webSessionId;
|
||||
Platform = WebSessionId.HasValue
|
||||
? LoginPlatform.Guest
|
||||
: LoginPlatform.Microsoft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,16 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
{
|
||||
public interface IGameboardRepository
|
||||
{
|
||||
Task<bool> CreateGuestUser(string userName);
|
||||
Task<bool> CreateBoardState(string sessionName, Models.Shogi shogi);
|
||||
Task<bool> CreateSession(Models.SessionMetadata session);
|
||||
Task<bool> CreateUser(Models.User user);
|
||||
Task<IList<Models.SessionMetadata>> ReadSessionMetadatas();
|
||||
Task<bool> IsGuestUser(string userName);
|
||||
Task<string> PostJoinCode(string gameName, string userName);
|
||||
Task<Models.User?> ReadGuestUser(Guid webSessionId);
|
||||
Task<Models.Session?> ReadSession(string name);
|
||||
Task<Models.Shogi?> ReadShogi(string name);
|
||||
Task<bool> UpdateSession(Models.Session session);
|
||||
Task<bool> UpdateSession(Models.SessionMetadata session);
|
||||
Task<Models.SessionMetadata?> ReadSessionMetaData(string name);
|
||||
Task<Models.User?> ReadUser(string userName);
|
||||
}
|
||||
|
||||
public class GameboardRepository : IGameboardRepository
|
||||
@@ -65,6 +67,14 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
return couchModel.ToDomainModel(shogi);
|
||||
}
|
||||
|
||||
public async Task<Models.SessionMetadata?> ReadSessionMetaData(string name)
|
||||
{
|
||||
var response = await client.GetAsync(name);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var couchModel = JsonConvert.DeserializeObject<SessionDocument>(responseContent);
|
||||
return couchModel.ToDomainModel();
|
||||
}
|
||||
|
||||
public async Task<Models.Shogi?> ReadShogi(string name)
|
||||
{
|
||||
var selector = new Dictionary<string, object>(2)
|
||||
@@ -74,7 +84,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
};
|
||||
var sort = new Dictionary<string, object>(1)
|
||||
{
|
||||
[nameof(BoardStateDocument.CreatedDate)] = "desc"
|
||||
[nameof(BoardStateDocument.CreatedDate)] = "asc"
|
||||
};
|
||||
var query = JsonConvert.SerializeObject(new { selector, sort = new[] { sort } });
|
||||
|
||||
@@ -103,6 +113,14 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
return new Models.Shogi(moves);
|
||||
}
|
||||
|
||||
public async Task<bool> CreateBoardState(string sessionName, Models.Shogi shogi)
|
||||
{
|
||||
var boardStateDocument = new BoardStateDocument(sessionName, shogi);
|
||||
var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
|
||||
var response = await client.PostAsync(string.Empty, content);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public async Task<bool> CreateSession(Models.SessionMetadata session)
|
||||
{
|
||||
var sessionDocument = new SessionDocument(session);
|
||||
@@ -120,7 +138,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateSession(Models.Session session)
|
||||
public async Task<bool> UpdateSession(Models.SessionMetadata session)
|
||||
{
|
||||
var couchModel = new SessionDocument(session);
|
||||
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||
@@ -178,32 +196,53 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//public async Task<Player> GetPlayer(string playerName)
|
||||
//{
|
||||
// var uri = $"Player/{playerName}";
|
||||
// var get = await client.GetAsync(Uri.EscapeUriString(uri));
|
||||
// var content = await get.Content.ReadAsStringAsync();
|
||||
// if (!string.IsNullOrWhiteSpace(content))
|
||||
// {
|
||||
// var response = JsonConvert.DeserializeObject<GetPlayerResponse>(content);
|
||||
// return new Player(response.Player.Name);
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
public async Task<bool> CreateGuestUser(string userName)
|
||||
public async Task<Models.User?> ReadUser(string userName)
|
||||
{
|
||||
var couchModel = new UserDocument(userName, UserDocument.LoginPlatform.Guest);
|
||||
var document = new UserDocument(userName);
|
||||
var response = await client.GetAsync(document.Id);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var user = JsonConvert.DeserializeObject<UserDocument>(responseContent);
|
||||
|
||||
return new Models.User(user.Name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> CreateUser(Models.User user)
|
||||
{
|
||||
var couchModel = new UserDocument(user.Name, user.WebSessionId);
|
||||
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||
var response = await client.PostAsync(string.Empty, content);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public async Task<bool> IsGuestUser(string userName)
|
||||
public async Task<Models.User?> ReadGuestUser(Guid webSessionId)
|
||||
{
|
||||
var req = new HttpRequestMessage(HttpMethod.Head, new Uri($"{client.BaseAddress}/{UserDocument.GetDocumentId(userName)}"));
|
||||
var response = await client.SendAsync(req);
|
||||
return response.IsSuccessStatusCode;
|
||||
var selector = new Dictionary<string, object>(2)
|
||||
{
|
||||
[nameof(UserDocument.DocumentType)] = WhichDocumentType.User,
|
||||
[nameof(UserDocument.WebSessionId)] = webSessionId.ToString()
|
||||
};
|
||||
var query = JsonConvert.SerializeObject(new { selector, limit = 1 });
|
||||
var content = new StringContent(query, Encoding.UTF8, ApplicationJson);
|
||||
var response = await client.PostAsync("_find", content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
logger.LogError("Couch error during _find in {func}: {error}.\n\nQuery: {query}", nameof(ReadGuestUser), responseContent, query);
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = JsonConvert.DeserializeObject<CouchFindResult<UserDocument>>(responseContent);
|
||||
if (!string.IsNullOrWhiteSpace(result.warning))
|
||||
{
|
||||
logger.LogError(new InvalidOperationException(result.warning), result.warning);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Models.User(result.docs.Single().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.RequestValidators
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using FluentValidation;
|
||||
using Gameboard.ShogiUI.Sockets.Controllers;
|
||||
using Gameboard.ShogiUI.Sockets.Extensions;
|
||||
using Gameboard.ShogiUI.Sockets.Managers;
|
||||
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
||||
using Gameboard.ShogiUI.Sockets.Managers.Utility;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Types;
|
||||
using Gameboard.ShogiUI.Sockets.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;
|
||||
@@ -29,6 +30,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
|
||||
{
|
||||
private readonly ILogger<SocketService> logger;
|
||||
private readonly ISocketConnectionManager communicationManager;
|
||||
private readonly IGameboardRepository gameboardRepository;
|
||||
private readonly ISocketTokenManager tokenManager;
|
||||
private readonly ICreateGameHandler createGameHandler;
|
||||
private readonly IJoinByCodeHandler joinByCodeHandler;
|
||||
@@ -46,6 +48,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
|
||||
public SocketService(
|
||||
ILogger<SocketService> logger,
|
||||
ISocketConnectionManager communicationManager,
|
||||
IGameboardRepository gameboardRepository,
|
||||
ISocketTokenManager tokenManager,
|
||||
ICreateGameHandler createGameHandler,
|
||||
IJoinByCodeHandler joinByCodeHandler,
|
||||
@@ -63,6 +66,7 @@ namespace Gameboard.ShogiUI.Sockets.Services
|
||||
{
|
||||
this.logger = logger;
|
||||
this.communicationManager = communicationManager;
|
||||
this.gameboardRepository = gameboardRepository;
|
||||
this.tokenManager = tokenManager;
|
||||
this.createGameHandler = createGameHandler;
|
||||
this.joinByCodeHandler = joinByCodeHandler;
|
||||
@@ -80,105 +84,115 @@ namespace Gameboard.ShogiUI.Sockets.Services
|
||||
|
||||
public async Task HandleSocketRequest(HttpContext context)
|
||||
{
|
||||
var hasToken = context.Request.Query.Keys.Contains("token");
|
||||
if (hasToken)
|
||||
string? userName = null;
|
||||
if (context.Request.Cookies.ContainsKey(SocketController.WebSessionKey))
|
||||
{
|
||||
var oneTimeToken = context.Request.Query["token"][0];
|
||||
var tokenAsGuid = Guid.Parse(oneTimeToken);
|
||||
var userName = tokenManager.GetUsername(tokenAsGuid);
|
||||
if (userName != null)
|
||||
{
|
||||
var socket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
// Guest account
|
||||
var webSessionId = Guid.Parse(context.Request.Cookies[SocketController.WebSessionKey]!);
|
||||
userName = (await gameboardRepository.ReadGuestUser(webSessionId))?.Name;
|
||||
}
|
||||
else if (context.Request.Query.Keys.Contains("token"))
|
||||
{
|
||||
// Microsoft account
|
||||
var token = Guid.Parse(context.Request.Query["token"][0]);
|
||||
userName = tokenManager.GetUsername(token);
|
||||
}
|
||||
|
||||
communicationManager.SubscribeToBroadcast(socket, userName);
|
||||
while (socket.State == WebSocketState.Open)
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var socket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
communicationManager.SubscribeToBroadcast(socket, userName);
|
||||
while (socket.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
var message = await socket.ReceiveTextAsync();
|
||||
if (string.IsNullOrWhiteSpace(message)) continue;
|
||||
logger.LogInformation("Request \n{0}\n", message);
|
||||
var request = JsonConvert.DeserializeObject<Request>(message);
|
||||
if (!Enum.IsDefined(typeof(ClientAction), request.Action))
|
||||
{
|
||||
var message = await socket.ReceiveTextAsync();
|
||||
if (string.IsNullOrWhiteSpace(message)) continue;
|
||||
logger.LogInformation("Request \n{0}\n", message);
|
||||
var request = JsonConvert.DeserializeObject<Request>(message);
|
||||
if (!Enum.IsDefined(typeof(ClientAction), request.Action))
|
||||
{
|
||||
await socket.SendTextAsync("Error: Action not recognized.");
|
||||
continue;
|
||||
}
|
||||
switch (request.Action)
|
||||
{
|
||||
case ClientAction.ListGames:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<ListGamesRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, listGamesRequestValidator, req))
|
||||
{
|
||||
await listGamesHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.CreateGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<CreateGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, createGameRequestValidator, req))
|
||||
{
|
||||
await createGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.JoinGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<JoinGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, joinGameRequestValidator, req))
|
||||
{
|
||||
await joinGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.JoinByCode:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<JoinByCodeRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, joinByCodeRequestValidator, req))
|
||||
{
|
||||
await joinByCodeHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.LoadGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<LoadGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, loadGameRequestValidator, req))
|
||||
{
|
||||
await loadGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.Move:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<MoveRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, moveRequestValidator, req))
|
||||
{
|
||||
await moveHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
await socket.SendTextAsync("Error: Action not recognized.");
|
||||
continue;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
switch (request.Action)
|
||||
{
|
||||
logger.LogError(ex.Message);
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketConnectionManager)}.");
|
||||
logger.LogInformation("Probably tried writing to a closed socket.");
|
||||
logger.LogError(ex.Message);
|
||||
case ClientAction.ListGames:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<ListGamesRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, listGamesRequestValidator, req))
|
||||
{
|
||||
await listGamesHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.CreateGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<CreateGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, createGameRequestValidator, req))
|
||||
{
|
||||
await createGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.JoinGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<JoinGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, joinGameRequestValidator, req))
|
||||
{
|
||||
await joinGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.JoinByCode:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<JoinByCodeRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, joinByCodeRequestValidator, req))
|
||||
{
|
||||
await joinByCodeHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.LoadGame:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<LoadGameRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, loadGameRequestValidator, req))
|
||||
{
|
||||
await loadGameHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientAction.Move:
|
||||
{
|
||||
var req = JsonConvert.DeserializeObject<MoveRequest>(message);
|
||||
if (await ValidateRequestAndReplyIfInvalid(socket, moveRequestValidator, req))
|
||||
{
|
||||
await moveHandler.Handle(req, userName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
communicationManager.UnsubscribeFromBroadcastAndGames(userName);
|
||||
return;
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogError(ex.Message);
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketConnectionManager)}.");
|
||||
logger.LogInformation("Probably tried writing to a closed socket.");
|
||||
logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
communicationManager.UnsubscribeFromBroadcastAndGames(userName);
|
||||
return;
|
||||
}
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateRequestAndReplyIfInvalid<TRequest>(WebSocket socket, IValidator<TRequest> validator, TRequest request)
|
||||
@@ -186,7 +200,12 @@ namespace Gameboard.ShogiUI.Sockets.Services
|
||||
var results = validator.Validate(request);
|
||||
if (!results.IsValid)
|
||||
{
|
||||
await socket.SendTextAsync(string.Join('\n', results.Errors.Select(_ => _.ErrorMessage).ToString()));
|
||||
var errors = string.Join('\n', results.Errors.Select(_ => _.ErrorMessage));
|
||||
var message = JsonConvert.SerializeObject(new Response
|
||||
{
|
||||
Error = errors
|
||||
});
|
||||
await socket.SendTextAsync(message);
|
||||
}
|
||||
return results.IsValid;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Interfaces;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Managers.Utility
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.Utility
|
||||
{
|
||||
public class JsonRequest
|
||||
{
|
||||
10
Gameboard.ShogiUI.Sockets/Services/Utility/Request.cs
Normal file
10
Gameboard.ShogiUI.Sockets/Services/Utility/Request.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
10
Gameboard.ShogiUI.Sockets/Services/Utility/Response.cs
Normal file
10
Gameboard.ShogiUI.Sockets/Services/Utility/Response.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Services.Utility
|
||||
{
|
||||
public class Response : IResponse
|
||||
{
|
||||
public string Action { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Gameboard.ShogiUI.Sockets.Extensions;
|
||||
using Gameboard.ShogiUI.Sockets.Managers;
|
||||
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
|
||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket.Messages;
|
||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
|
||||
using Gameboard.ShogiUI.Sockets.Services;
|
||||
using Gameboard.ShogiUI.Sockets.Services.RequestValidators;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
@@ -18,6 +18,7 @@ using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets
|
||||
{
|
||||
@@ -45,7 +46,6 @@ namespace Gameboard.ShogiUI.Sockets
|
||||
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
|
||||
services.AddSingleton<ISocketTokenManager, SocketTokenManager>();
|
||||
services.AddSingleton<IGameboardManager, GameboardManager>();
|
||||
services.AddSingleton<IActiveSessionManager, ActiveSessionManager>();
|
||||
|
||||
// Services
|
||||
services.AddSingleton<IValidator<CreateGameRequest>, CreateGameRequestValidator>();
|
||||
@@ -84,6 +84,18 @@ namespace Gameboard.ShogiUI.Sockets
|
||||
options.Audience = "935df672-efa6-45fa-b2e8-b76dfd65a122";
|
||||
options.TokenValidationParameters.ValidateIssuer = true;
|
||||
options.TokenValidationParameters.ValidateAudience = true;
|
||||
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = (context) =>
|
||||
{
|
||||
if (context.HttpContext.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
Console.WriteLine("Yep");
|
||||
}
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,6 +126,7 @@ namespace Gameboard.ShogiUI.Sockets
|
||||
.WithOrigins(origins)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.WithExposedHeaders("Set-Cookie")
|
||||
.AllowCredentials()
|
||||
)
|
||||
.UseRouting()
|
||||
@@ -126,12 +139,7 @@ namespace Gameboard.ShogiUI.Sockets
|
||||
})
|
||||
.Use(async (context, next) =>
|
||||
{
|
||||
var isUpgradeHeader = context
|
||||
.Request
|
||||
.Headers
|
||||
.Any(h => h.Key.Contains("upgrade", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& h.Value.ToString().Contains("websocket", StringComparison.InvariantCultureIgnoreCase));
|
||||
if (isUpgradeHeader)
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
await socketConnectionManager.HandleSocketRequest(context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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<string, Piece?>, IPlanarCollection<Piece>
|
||||
{
|
||||
public delegate void ForEachDelegate(Piece element, Vector2 position);
|
||||
|
||||
public CoordsToNotationCollection() : base(81, StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public CoordsToNotationCollection(Dictionary<string, Piece?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Gameboard.ShogiUI.Sockets/Utilities/NotationHelper.cs
Normal file
37
Gameboard.ShogiUI.Sockets/Utilities/NotationHelper.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Gameboard.ShogiUI.Sockets.Utilities
|
||||
{
|
||||
public static class NotationHelper
|
||||
{
|
||||
private static readonly string BoardNotationRegex = @"(?<file>[a-iA-I])(?<rank>[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;
|
||||
Console.WriteLine($"({x},{y}) - {file}{rank}");
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user