checkpoint
This commit is contained in:
@@ -6,7 +6,10 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api
|
|||||||
public class GetSessionResponse
|
public class GetSessionResponse
|
||||||
{
|
{
|
||||||
public Game Game { get; set; }
|
public Game Game { get; set; }
|
||||||
public WhichPlayer PlayerPerspective { get; set; }
|
/// <summary>
|
||||||
|
/// The perspective on the game of the requesting user.
|
||||||
|
/// </summary>
|
||||||
|
public WhichPerspective PlayerPerspective { get; set; }
|
||||||
public BoardState BoardState { get; set; }
|
public BoardState BoardState { get; set; }
|
||||||
public IList<Move> MoveHistory { get; set; }
|
public IList<Move> MoveHistory { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
|||||||
public Dictionary<string, Piece?> Board { get; set; } = new Dictionary<string, Piece?>();
|
public Dictionary<string, Piece?> Board { get; set; } = new Dictionary<string, Piece?>();
|
||||||
public IReadOnlyCollection<Piece> Player1Hand { get; set; } = Array.Empty<Piece>();
|
public IReadOnlyCollection<Piece> Player1Hand { get; set; } = Array.Empty<Piece>();
|
||||||
public IReadOnlyCollection<Piece> Player2Hand { get; set; } = Array.Empty<Piece>();
|
public IReadOnlyCollection<Piece> Player2Hand { get; set; } = Array.Empty<Piece>();
|
||||||
public WhichPlayer? PlayerInCheck { get; set; }
|
public WhichPerspective? PlayerInCheck { get; set; }
|
||||||
public WhichPlayer WhoseTurn { get; set; }
|
public WhichPerspective WhoseTurn { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,37 @@
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
||||||
{
|
{
|
||||||
public class Game
|
public class Game
|
||||||
{
|
{
|
||||||
public string Player1 { get; set; } = string.Empty;
|
public string Player1 { get; set; }
|
||||||
public string? Player2 { get; set; } = string.Empty;
|
public string? Player2 { get; set; }
|
||||||
public string GameName { get; set; } = string.Empty;
|
public string GameName { get; set; } = string.Empty;
|
||||||
/// <summary>
|
|
||||||
/// Players[0] is the session owner, Players[1] is the other person.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<string> Players
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var list = new List<string>(2) { Player1 };
|
|
||||||
if (!string.IsNullOrEmpty(Player2)) list.Add(Player2);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Game()
|
/// <summary>
|
||||||
{
|
/// Players[0] is the session owner, Players[1] is the other person.
|
||||||
}
|
/// </summary>
|
||||||
public Game(string gameName, string player1, string? player2 = null)
|
public IReadOnlyList<string> Players
|
||||||
{
|
{
|
||||||
GameName = gameName;
|
get
|
||||||
Player1 = player1;
|
{
|
||||||
Player2 = player2;
|
var list = new List<string>(2) { Player1 };
|
||||||
}
|
if (!string.IsNullOrEmpty(Player2)) list.Add(Player2);
|
||||||
}
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for serialization.
|
||||||
|
/// </summary>
|
||||||
|
public Game()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Game(string gameName, string player1, string? player2 = null)
|
||||||
|
{
|
||||||
|
GameName = gameName;
|
||||||
|
Player1 = player1;
|
||||||
|
Player2 = player2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
{
|
{
|
||||||
public bool IsPromoted { get; set; }
|
public bool IsPromoted { get; set; }
|
||||||
public WhichPiece WhichPiece { get; set; }
|
public WhichPiece WhichPiece { get; set; }
|
||||||
public WhichPlayer Owner { get; set; }
|
public WhichPerspective Owner { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs
Normal file
9
Gameboard.ShogiUI.Sockets.ServiceModels/Types/User.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
||||||
|
{
|
||||||
|
public enum WhichPerspective
|
||||||
|
{
|
||||||
|
Player1,
|
||||||
|
Player2,
|
||||||
|
Spectator
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
|
||||||
{
|
|
||||||
public enum WhichPlayer
|
|
||||||
{
|
|
||||||
Player1,
|
|
||||||
Player2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,226 +16,239 @@ using System.Threading.Tasks;
|
|||||||
namespace Gameboard.ShogiUI.Sockets.Controllers
|
namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||||
{
|
{
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
[Authorize(Roles = "Shogi")]
|
[Authorize(Roles = "Shogi")]
|
||||||
public class GameController : ControllerBase
|
public class GameController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IGameboardManager gameboardManager;
|
private readonly IGameboardManager gameboardManager;
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
private readonly ISocketConnectionManager communicationManager;
|
private readonly ISocketConnectionManager communicationManager;
|
||||||
|
|
||||||
public GameController(
|
public GameController(
|
||||||
IGameboardRepository repository,
|
IGameboardRepository repository,
|
||||||
IGameboardManager manager,
|
IGameboardManager manager,
|
||||||
ISocketConnectionManager communicationManager)
|
ISocketConnectionManager communicationManager)
|
||||||
{
|
{
|
||||||
gameboardManager = manager;
|
gameboardManager = manager;
|
||||||
gameboardRepository = repository;
|
gameboardRepository = repository;
|
||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("JoinCode")]
|
[HttpPost("JoinCode")]
|
||||||
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
|
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
|
||||||
{
|
{
|
||||||
|
|
||||||
//var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName);
|
//var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName);
|
||||||
//if (isPlayer1)
|
//if (isPlayer1)
|
||||||
//{
|
//{
|
||||||
// var code = await gameboardRepository.PostJoinCode(request.SessionName, userName);
|
// var code = await gameboardRepository.PostJoinCode(request.SessionName, userName);
|
||||||
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||||
//}
|
//}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
return new UnauthorizedResult();
|
return new UnauthorizedResult();
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("GuestJoinCode")]
|
[HttpPost("GuestJoinCode")]
|
||||||
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
|
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
|
||||||
{
|
{
|
||||||
|
|
||||||
//var isGuest = gameboardManager.IsGuest(request.GuestId);
|
//var isGuest = gameboardManager.IsGuest(request.GuestId);
|
||||||
//var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId);
|
//var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId);
|
||||||
//if (isGuest && await isPlayer1)
|
//if (isGuest && await isPlayer1)
|
||||||
//{
|
//{
|
||||||
// var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId);
|
// var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId);
|
||||||
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
// return new CreatedResult("", new PostGameInvitationResponse(code));
|
||||||
//}
|
//}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
return new UnauthorizedResult();
|
return new UnauthorizedResult();
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{gameName}/Move")]
|
[HttpPost("{gameName}/Move")]
|
||||||
public async Task<IActionResult> PostMove([FromRoute] string gameName, [FromBody] PostMove request)
|
public async Task<IActionResult> PostMove([FromRoute] string gameName, [FromBody] PostMove request)
|
||||||
{
|
{
|
||||||
var user = await gameboardManager.ReadUser(User);
|
var user = await gameboardManager.ReadUser(User);
|
||||||
var session = await gameboardRepository.ReadSession(gameName);
|
var session = await gameboardRepository.ReadSession(gameName);
|
||||||
if (session == null)
|
if (session == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id))
|
if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id))
|
||||||
{
|
{
|
||||||
return Forbid("User is not seated at this game.");
|
return Forbid("User is not seated at this game.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var move = request.Move;
|
var move = request.Move;
|
||||||
var moveModel = move.PieceFromCaptured.HasValue
|
var moveModel = move.PieceFromCaptured.HasValue
|
||||||
? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion)
|
? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion)
|
||||||
: new Models.Move(move.From!, move.To, move.IsPromotion);
|
: new Models.Move(move.From!, move.To, move.IsPromotion);
|
||||||
var moveSuccess = session.Shogi.Move(moveModel);
|
var moveSuccess = session.Shogi.Move(moveModel);
|
||||||
|
|
||||||
if (moveSuccess)
|
if (moveSuccess)
|
||||||
{
|
{
|
||||||
var createSuccess = await gameboardRepository.CreateBoardState(session);
|
var createSuccess = await gameboardRepository.CreateBoardState(session);
|
||||||
if (!createSuccess)
|
if (!createSuccess)
|
||||||
{
|
{
|
||||||
throw new ApplicationException("Unable to persist board state.");
|
throw new ApplicationException("Unable to persist board state.");
|
||||||
}
|
}
|
||||||
await communicationManager.BroadcastToPlayers(new MoveResponse
|
await communicationManager.BroadcastToPlayers(new MoveResponse
|
||||||
{
|
{
|
||||||
GameName = session.Name,
|
GameName = session.Name,
|
||||||
PlayerName = user.Id
|
PlayerName = user.Id
|
||||||
}, session.Player1.Id, session.Player2?.Id);
|
}, session.Player1.Id, session.Player2?.Id);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
return Conflict("Illegal move.");
|
return Conflict("Illegal move.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
|
// TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
|
||||||
//[Route("")]
|
//[Route("")]
|
||||||
//public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
//public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
||||||
//{
|
//{
|
||||||
// var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2);
|
// var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2);
|
||||||
// var success = await repository.CreateSession(model);
|
// var success = await repository.CreateSession(model);
|
||||||
// if (success)
|
// if (success)
|
||||||
// {
|
// {
|
||||||
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame)
|
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame)
|
||||||
// {
|
// {
|
||||||
// Game = model.ToServiceModel(),
|
// Game = model.ToServiceModel(),
|
||||||
// PlayerName =
|
// PlayerName =
|
||||||
// }
|
// }
|
||||||
// var task = request.IsPrivate
|
// var task = request.IsPrivate
|
||||||
// ? communicationManager.BroadcastToPlayers(response, userName)
|
// ? communicationManager.BroadcastToPlayers(response, userName)
|
||||||
// : communicationManager.BroadcastToAll(response);
|
// : communicationManager.BroadcastToAll(response);
|
||||||
// return new CreatedResult("", null);
|
// return new CreatedResult("", null);
|
||||||
// }
|
// }
|
||||||
// return new ConflictResult();
|
// return new ConflictResult();
|
||||||
//}
|
//}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
public async Task<IActionResult> PostSession([FromBody] PostSession request)
|
||||||
{
|
{
|
||||||
var user = await ReadUserOrThrow();
|
var user = await ReadUserOrThrow();
|
||||||
var session = new Models.SessionMetadata(request.Name, request.IsPrivate, user!);
|
var session = new Models.SessionMetadata(request.Name, request.IsPrivate, user!);
|
||||||
var success = await gameboardRepository.CreateSession(session);
|
var success = await gameboardRepository.CreateSession(session);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
await communicationManager.BroadcastToAll(new CreateGameResponse
|
try
|
||||||
{
|
{
|
||||||
Game = session.ToServiceModel(),
|
|
||||||
PlayerName = user.Id
|
|
||||||
}).ContinueWith(cont =>
|
|
||||||
{
|
|
||||||
if (cont.Exception != null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine("Yep");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
return Conflict();
|
|
||||||
|
|
||||||
}
|
await communicationManager.BroadcastToAll(new CreateGameResponse
|
||||||
|
{
|
||||||
|
Game = session.ToServiceModel(),
|
||||||
|
PlayerName = user.Id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Error broadcasting during PostSession");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
return Ok();
|
||||||
/// Reads the board session and subscribes the caller to socket events for that session.
|
}
|
||||||
/// </summary>
|
return Conflict();
|
||||||
[HttpGet("{gameName}")]
|
|
||||||
public async Task<IActionResult> GetSession([FromRoute] string gameName)
|
|
||||||
{
|
|
||||||
var user = await ReadUserOrThrow();
|
|
||||||
var session = await gameboardRepository.ReadSession(gameName);
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
communicationManager.SubscribeToGame(session, user!.Id);
|
}
|
||||||
var response = new GetSessionResponse()
|
|
||||||
{
|
|
||||||
Game = new Models.SessionMetadata(session).ToServiceModel(user),
|
|
||||||
BoardState = session.Shogi.ToServiceModel(),
|
|
||||||
MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(),
|
|
||||||
PlayerPerspective = user.Id == session.Player1.Id ? WhichPlayer.Player1 : WhichPlayer.Player2
|
|
||||||
};
|
|
||||||
return new JsonResult(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
/// <summary>
|
||||||
public async Task<GetSessionsResponse> GetSessions()
|
/// Reads the board session and subscribes the caller to socket events for that session.
|
||||||
{
|
/// </summary>
|
||||||
var user = await ReadUserOrThrow();
|
[HttpGet("{gameName}")]
|
||||||
var sessions = await gameboardRepository.ReadSessionMetadatas();
|
public async Task<IActionResult> GetSession([FromRoute] string gameName)
|
||||||
|
{
|
||||||
|
var user = await ReadUserOrThrow();
|
||||||
|
var session = await gameboardRepository.ReadSession(gameName);
|
||||||
|
if (session == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
var sessionsJoinedByUser = sessions
|
var playerPerspective = WhichPerspective.Spectator;
|
||||||
.Where(s => s.IsSeated(user))
|
if (session.Player1.Id == user.Id)
|
||||||
.Select(s => s.ToServiceModel())
|
{
|
||||||
.ToList();
|
playerPerspective = WhichPerspective.Player1;
|
||||||
var sessionsNotJoinedByUser = sessions
|
}
|
||||||
.Where(s => !s.IsSeated(user))
|
else if (session.Player2?.Id == user.Id)
|
||||||
.Select(s => s.ToServiceModel())
|
{
|
||||||
.ToList();
|
playerPerspective = WhichPerspective.Player2;
|
||||||
|
}
|
||||||
|
|
||||||
return new GetSessionsResponse
|
communicationManager.SubscribeToGame(session, user!.Id);
|
||||||
{
|
var response = new GetSessionResponse()
|
||||||
PlayerHasJoinedSessions = new Collection<Game>(sessionsJoinedByUser),
|
{
|
||||||
AllOtherSessions = new Collection<Game>(sessionsNotJoinedByUser)
|
Game = new Models.SessionMetadata(session).ToServiceModel(),
|
||||||
};
|
BoardState = session.Shogi.ToServiceModel(),
|
||||||
}
|
MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(),
|
||||||
|
PlayerPerspective = playerPerspective
|
||||||
|
};
|
||||||
|
return new JsonResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPut("{gameName}")]
|
[HttpGet]
|
||||||
public async Task<IActionResult> PutJoinSession([FromRoute] string gameName)
|
public async Task<GetSessionsResponse> GetSessions()
|
||||||
{
|
{
|
||||||
var user = await ReadUserOrThrow();
|
var user = await ReadUserOrThrow();
|
||||||
var session = await gameboardRepository.ReadSessionMetaData(gameName);
|
var sessions = await gameboardRepository.ReadSessionMetadatas();
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
if (session.Player2 != null)
|
|
||||||
{
|
|
||||||
return this.Conflict("This session already has two seated players and is full.");
|
|
||||||
}
|
|
||||||
|
|
||||||
session.SetPlayer2(user);
|
var sessionsJoinedByUser = sessions
|
||||||
var success = await gameboardRepository.UpdateSession(session);
|
.Where(s => s.IsSeated(user))
|
||||||
if (!success) return this.Problem(detail: "Unable to update session.");
|
.Select(s => s.ToServiceModel())
|
||||||
|
.ToList();
|
||||||
|
var sessionsNotJoinedByUser = sessions
|
||||||
|
.Where(s => !s.IsSeated(user))
|
||||||
|
.Select(s => s.ToServiceModel())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
var opponentName = user.Id == session.Player1.Id
|
return new GetSessionsResponse
|
||||||
? session.Player2!.Id
|
{
|
||||||
: session.Player1.Id;
|
PlayerHasJoinedSessions = new Collection<Game>(sessionsJoinedByUser),
|
||||||
await communicationManager.BroadcastToPlayers(new JoinGameResponse
|
AllOtherSessions = new Collection<Game>(sessionsNotJoinedByUser)
|
||||||
{
|
};
|
||||||
GameName = session.Name,
|
}
|
||||||
PlayerName = user.Id
|
|
||||||
}, opponentName);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Models.User> ReadUserOrThrow()
|
[HttpPut("{gameName}")]
|
||||||
{
|
public async Task<IActionResult> PutJoinSession([FromRoute] string gameName)
|
||||||
var user = await gameboardManager.ReadUser(User);
|
{
|
||||||
if (user == null)
|
var user = await ReadUserOrThrow();
|
||||||
{
|
var session = await gameboardRepository.ReadSessionMetaData(gameName);
|
||||||
throw new UnauthorizedAccessException("Unknown user claims.");
|
if (session == null)
|
||||||
}
|
{
|
||||||
return user;
|
return NotFound();
|
||||||
}
|
}
|
||||||
}
|
if (session.Player2 != null)
|
||||||
|
{
|
||||||
|
return this.Conflict("This session already has two seated players and is full.");
|
||||||
|
}
|
||||||
|
|
||||||
|
session.SetPlayer2(user);
|
||||||
|
var success = await gameboardRepository.UpdateSession(session);
|
||||||
|
if (!success) return this.Problem(detail: "Unable to update session.");
|
||||||
|
|
||||||
|
var opponentName = user.Id == session.Player1.Id
|
||||||
|
? session.Player2!.Id
|
||||||
|
: session.Player1.Id;
|
||||||
|
await communicationManager.BroadcastToPlayers(new JoinGameResponse
|
||||||
|
{
|
||||||
|
GameName = session.Name,
|
||||||
|
PlayerName = user.Id
|
||||||
|
}, opponentName);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Models.User> ReadUserOrThrow()
|
||||||
|
{
|
||||||
|
var user = await gameboardManager.ReadUser(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Unknown user claims.");
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,89 +15,101 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Controllers
|
namespace Gameboard.ShogiUI.Sockets.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
[Authorize(Roles = "Shogi")]
|
[Authorize(Roles = "Shogi")]
|
||||||
public class SocketController : ControllerBase
|
public class SocketController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<SocketController> logger;
|
private readonly ILogger<SocketController> logger;
|
||||||
private readonly ISocketTokenCache tokenCache;
|
private readonly ISocketTokenCache tokenCache;
|
||||||
private readonly IGameboardManager gameboardManager;
|
private readonly IGameboardManager gameboardManager;
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
private readonly AuthenticationProperties authenticationProps;
|
private readonly ISocketConnectionManager connectionManager;
|
||||||
|
private readonly AuthenticationProperties authenticationProps;
|
||||||
|
|
||||||
public SocketController(
|
public SocketController(
|
||||||
ILogger<SocketController> logger,
|
ILogger<SocketController> logger,
|
||||||
ISocketTokenCache tokenCache,
|
ISocketTokenCache tokenCache,
|
||||||
IGameboardManager gameboardManager,
|
IGameboardManager gameboardManager,
|
||||||
IGameboardRepository gameboardRepository)
|
IGameboardRepository gameboardRepository,
|
||||||
{
|
ISocketConnectionManager connectionManager)
|
||||||
this.logger = logger;
|
{
|
||||||
this.tokenCache = tokenCache;
|
this.logger = logger;
|
||||||
this.gameboardManager = gameboardManager;
|
this.tokenCache = tokenCache;
|
||||||
this.gameboardRepository = gameboardRepository;
|
this.gameboardManager = gameboardManager;
|
||||||
authenticationProps = new AuthenticationProperties
|
this.gameboardRepository = gameboardRepository;
|
||||||
{
|
this.connectionManager = connectionManager;
|
||||||
AllowRefresh = true,
|
|
||||||
IsPersistent = true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("GuestLogout")]
|
authenticationProps = new AuthenticationProperties
|
||||||
[AllowAnonymous]
|
{
|
||||||
public async Task<IActionResult> GuestLogout()
|
AllowRefresh = true,
|
||||||
{
|
IsPersistent = true
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
};
|
||||||
return Ok();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("Token")]
|
[HttpGet("GuestLogout")]
|
||||||
public async Task<IActionResult> GetToken()
|
[AllowAnonymous]
|
||||||
{
|
public async Task<IActionResult> GuestLogout()
|
||||||
var user = await gameboardManager.ReadUser(User);
|
{
|
||||||
if (user == null)
|
var signoutTask = HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
{
|
|
||||||
if (await gameboardManager.CreateUser(User))
|
|
||||||
{
|
|
||||||
user = await gameboardManager.ReadUser(User);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user == null)
|
var userId = User?.UserId();
|
||||||
{
|
if (!string.IsNullOrEmpty(userId))
|
||||||
return Unauthorized();
|
{
|
||||||
}
|
connectionManager.UnsubscribeFromBroadcastAndGames(userId);
|
||||||
|
}
|
||||||
|
|
||||||
var token = tokenCache.GenerateToken(user.Id);
|
await signoutTask;
|
||||||
return new JsonResult(new GetTokenResponse(token));
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GuestToken")]
|
[HttpGet("Token")]
|
||||||
[AllowAnonymous]
|
public async Task<IActionResult> GetToken()
|
||||||
public async Task<IActionResult> GetGuestToken()
|
{
|
||||||
{
|
var user = await gameboardManager.ReadUser(User);
|
||||||
var user = await gameboardManager.ReadUser(User);
|
if (user == null)
|
||||||
if (user == null)
|
{
|
||||||
{
|
if (await gameboardManager.CreateUser(User))
|
||||||
// Create a guest user.
|
{
|
||||||
var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString());
|
user = await gameboardManager.ReadUser(User);
|
||||||
var success = await gameboardRepository.CreateUser(newUser);
|
}
|
||||||
if (!success)
|
}
|
||||||
{
|
|
||||||
return Conflict();
|
|
||||||
}
|
|
||||||
|
|
||||||
var identity = newUser.CreateClaimsIdentity();
|
if (user == null)
|
||||||
await HttpContext.SignInAsync(
|
{
|
||||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
return Unauthorized();
|
||||||
new ClaimsPrincipal(identity),
|
}
|
||||||
authenticationProps
|
|
||||||
);
|
|
||||||
user = newUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = tokenCache.GenerateToken(user.Id.ToString());
|
var token = tokenCache.GenerateToken(user.Id);
|
||||||
return this.Ok(new GetGuestTokenResponse(user.Id, user.DisplayName, token));
|
return new JsonResult(new GetTokenResponse(token));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
[HttpGet("GuestToken")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> GetGuestToken()
|
||||||
|
{
|
||||||
|
var user = await gameboardManager.ReadUser(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
// Create a guest user.
|
||||||
|
var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString());
|
||||||
|
var success = await gameboardRepository.CreateUser(newUser);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
return Conflict();
|
||||||
|
}
|
||||||
|
|
||||||
|
var identity = newUser.CreateClaimsIdentity();
|
||||||
|
await HttpContext.SignInAsync(
|
||||||
|
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
|
new ClaimsPrincipal(identity),
|
||||||
|
authenticationProps
|
||||||
|
);
|
||||||
|
user = newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = tokenCache.GenerateToken(user.Id.ToString());
|
||||||
|
return this.Ok(new GetGuestTokenResponse(user.Id, user.DisplayName, token));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,50 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Extensions
|
namespace Gameboard.ShogiUI.Sockets.Extensions
|
||||||
{
|
{
|
||||||
public class LogMiddleware
|
public class LogMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate next;
|
private readonly RequestDelegate next;
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
|
|
||||||
public LogMiddleware(RequestDelegate next, ILoggerFactory factory)
|
|
||||||
{
|
|
||||||
this.next = next;
|
|
||||||
logger = factory.CreateLogger<LogMiddleware>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public LogMiddleware(RequestDelegate next, ILoggerFactory factory)
|
||||||
{
|
{
|
||||||
try
|
this.next = next;
|
||||||
{
|
logger = factory.CreateLogger<LogMiddleware>();
|
||||||
await next(context);
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
logger.LogInformation("Request {method} {url} => {statusCode}",
|
|
||||||
context.Request?.Method,
|
|
||||||
context.Request?.Path.Value,
|
|
||||||
context.Response?.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class IApplicationBuilderExtensions
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
|
try
|
||||||
{
|
{
|
||||||
builder.UseMiddleware<LogMiddleware>();
|
await next(context);
|
||||||
return builder;
|
}
|
||||||
}
|
finally
|
||||||
}
|
{
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
context.Request?.Body.CopyToAsync(stream);
|
||||||
|
|
||||||
|
logger.LogInformation("Request {method} {url} => {statusCode} \n Body: {body}",
|
||||||
|
context.Request?.Method,
|
||||||
|
context.Request?.Path.Value,
|
||||||
|
context.Response?.StatusCode,
|
||||||
|
Encoding.UTF8.GetString(stream.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IApplicationBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.UseMiddleware<LogMiddleware>();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||||
using System;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ namespace Gameboard.ShogiUI.Sockets.Extensions
|
|||||||
WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ",
|
WhichPiece.Pawn => self.IsPromoted ? "^P " : " P ",
|
||||||
_ => " ? ",
|
_ => " ? ",
|
||||||
};
|
};
|
||||||
if (self.Owner == WhichPlayer.Player2)
|
if (self.Owner == WhichPerspective.Player2)
|
||||||
name = Regex.Replace(name, @"([^\s]+)\s", "$1.");
|
name = Regex.Replace(name, @"([^\s]+)\s", "$1.");
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,151 +11,152 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers
|
namespace Gameboard.ShogiUI.Sockets.Managers
|
||||||
{
|
{
|
||||||
public interface ISocketConnectionManager
|
public interface ISocketConnectionManager
|
||||||
{
|
{
|
||||||
Task BroadcastToAll(IResponse response);
|
Task BroadcastToAll(IResponse response);
|
||||||
//Task BroadcastToGame(string gameName, IResponse response);
|
//Task BroadcastToGame(string gameName, IResponse response);
|
||||||
//Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
|
//Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2);
|
||||||
void SubscribeToGame(Session session, string playerName);
|
void SubscribeToGame(Session session, string playerName);
|
||||||
void SubscribeToBroadcast(WebSocket socket, string playerName);
|
void SubscribeToBroadcast(WebSocket socket, string playerName);
|
||||||
void UnsubscribeFromBroadcastAndGames(string playerName);
|
void UnsubscribeFromBroadcastAndGames(string playerName);
|
||||||
void UnsubscribeFromGame(string gameName, string playerName);
|
void UnsubscribeFromGame(string gameName, string playerName);
|
||||||
Task BroadcastToPlayers(IResponse response, params string?[] playerNames);
|
Task BroadcastToPlayers(IResponse response, params string?[] playerNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retains all active socket connections and provides convenient methods for sending messages to clients.
|
/// Retains all active socket connections and provides convenient methods for sending messages to clients.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SocketConnectionManager : ISocketConnectionManager
|
public class SocketConnectionManager : ISocketConnectionManager
|
||||||
{
|
{
|
||||||
/// <summary>Dictionary key is player name.</summary>
|
/// <summary>Dictionary key is player name.</summary>
|
||||||
private readonly ConcurrentDictionary<string, WebSocket> connections;
|
private readonly ConcurrentDictionary<string, WebSocket> connections;
|
||||||
/// <summary>Dictionary key is game name.</summary>
|
/// <summary>Dictionary key is game name.</summary>
|
||||||
private readonly ConcurrentDictionary<string, Session> sessions;
|
private readonly ConcurrentDictionary<string, Session> sessions;
|
||||||
private readonly ILogger<SocketConnectionManager> logger;
|
private readonly ILogger<SocketConnectionManager> logger;
|
||||||
|
|
||||||
public SocketConnectionManager(ILogger<SocketConnectionManager> logger)
|
public SocketConnectionManager(ILogger<SocketConnectionManager> logger)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
connections = new ConcurrentDictionary<string, WebSocket>();
|
connections = new ConcurrentDictionary<string, WebSocket>();
|
||||||
sessions = new ConcurrentDictionary<string, Session>();
|
sessions = new ConcurrentDictionary<string, Session>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubscribeToBroadcast(WebSocket socket, string playerName)
|
public void SubscribeToBroadcast(WebSocket socket, string playerName)
|
||||||
{
|
{
|
||||||
connections.TryAdd(playerName, socket);
|
connections.TryRemove(playerName, out var _);
|
||||||
}
|
connections.TryAdd(playerName, socket);
|
||||||
|
}
|
||||||
|
|
||||||
public void UnsubscribeFromBroadcastAndGames(string playerName)
|
public void UnsubscribeFromBroadcastAndGames(string playerName)
|
||||||
{
|
{
|
||||||
connections.TryRemove(playerName, out _);
|
connections.TryRemove(playerName, out _);
|
||||||
foreach (var kvp in sessions)
|
foreach (var kvp in sessions)
|
||||||
{
|
{
|
||||||
var sessionName = kvp.Key;
|
var sessionName = kvp.Key;
|
||||||
UnsubscribeFromGame(sessionName, playerName);
|
UnsubscribeFromGame(sessionName, playerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsubscribes the player from their current game, then subscribes to the new game.
|
/// Unsubscribes the player from their current game, then subscribes to the new game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SubscribeToGame(Session session, string playerName)
|
public void SubscribeToGame(Session session, string playerName)
|
||||||
{
|
{
|
||||||
// Unsubscribe from any other games
|
// Unsubscribe from any other games
|
||||||
foreach (var kvp in sessions)
|
foreach (var kvp in sessions)
|
||||||
{
|
{
|
||||||
var gameNameKey = kvp.Key;
|
var gameNameKey = kvp.Key;
|
||||||
UnsubscribeFromGame(gameNameKey, playerName);
|
UnsubscribeFromGame(gameNameKey, playerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe
|
// Subscribe
|
||||||
if (connections.TryGetValue(playerName, out var socket))
|
if (connections.TryGetValue(playerName, out var socket))
|
||||||
{
|
{
|
||||||
var s = sessions.GetOrAdd(session.Name, session);
|
var s = sessions.GetOrAdd(session.Name, session);
|
||||||
s.Subscriptions.TryAdd(playerName, socket);
|
s.Subscriptions.TryAdd(playerName, socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnsubscribeFromGame(string gameName, string playerName)
|
public void UnsubscribeFromGame(string gameName, string playerName)
|
||||||
{
|
{
|
||||||
if (sessions.TryGetValue(gameName, out var s))
|
if (sessions.TryGetValue(gameName, out var s))
|
||||||
{
|
{
|
||||||
s.Subscriptions.TryRemove(playerName, out _);
|
s.Subscriptions.TryRemove(playerName, out _);
|
||||||
if (s.Subscriptions.IsEmpty) sessions.TryRemove(gameName, out _);
|
if (s.Subscriptions.IsEmpty) sessions.TryRemove(gameName, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
var tasks = new List<Task>(playerNames.Length);
|
||||||
foreach (var name in playerNames)
|
foreach (var name in playerNames)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket))
|
if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket))
|
||||||
{
|
{
|
||||||
var serialized = JsonConvert.SerializeObject(response);
|
var serialized = JsonConvert.SerializeObject(response);
|
||||||
logger.LogInformation("Response to {0} \n{1}\n", name, serialized);
|
logger.LogInformation("Response to {0} \n{1}\n", name, serialized);
|
||||||
tasks.Add(socket.SendTextAsync(serialized));
|
tasks.Add(socket.SendTextAsync(serialized));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
public Task BroadcastToAll(IResponse response)
|
public Task BroadcastToAll(IResponse response)
|
||||||
{
|
{
|
||||||
var message = JsonConvert.SerializeObject(response);
|
var message = JsonConvert.SerializeObject(response);
|
||||||
logger.LogInformation($"Broadcasting\n{0}", message);
|
logger.LogInformation($"Broadcasting\n{0}", message);
|
||||||
var tasks = new List<Task>(connections.Count);
|
var tasks = new List<Task>(connections.Count);
|
||||||
foreach (var kvp in connections)
|
foreach (var kvp in connections)
|
||||||
{
|
{
|
||||||
var socket = kvp.Value;
|
var socket = kvp.Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
tasks.Add(socket.SendTextAsync(message));
|
tasks.Add(socket.SendTextAsync(message));
|
||||||
}
|
}
|
||||||
catch (WebSocketException webSocketException)
|
catch (WebSocketException webSocketException)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
|
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
|
||||||
UnsubscribeFromBroadcastAndGames(kvp.Key);
|
UnsubscribeFromBroadcastAndGames(kvp.Key);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
|
logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key);
|
||||||
UnsubscribeFromBroadcastAndGames(kvp.Key);
|
UnsubscribeFromBroadcastAndGames(kvp.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var task = Task.WhenAll(tasks);
|
var task = Task.WhenAll(tasks);
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Yo");
|
Console.WriteLine("Yo");
|
||||||
}
|
}
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
|
//public Task BroadcastToGame(string gameName, IResponse forPlayer1, IResponse forPlayer2)
|
||||||
//{
|
//{
|
||||||
// if (sessions.TryGetValue(gameName, out var session))
|
// if (sessions.TryGetValue(gameName, out var session))
|
||||||
// {
|
// {
|
||||||
// var serialized1 = JsonConvert.SerializeObject(forPlayer1);
|
// var serialized1 = JsonConvert.SerializeObject(forPlayer1);
|
||||||
// var serialized2 = JsonConvert.SerializeObject(forPlayer2);
|
// var serialized2 = JsonConvert.SerializeObject(forPlayer2);
|
||||||
// return Task.WhenAll(
|
// return Task.WhenAll(
|
||||||
// session.SendToPlayer1(serialized1),
|
// session.SendToPlayer1(serialized1),
|
||||||
// session.SendToPlayer2(serialized2));
|
// session.SendToPlayer2(serialized2));
|
||||||
// }
|
// }
|
||||||
// return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
|
//public Task BroadcastToGame(string gameName, IResponse messageForAllPlayers)
|
||||||
//{
|
//{
|
||||||
// if (sessions.TryGetValue(gameName, out var session))
|
// if (sessions.TryGetValue(gameName, out var session))
|
||||||
// {
|
// {
|
||||||
// var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
|
// var serialized = JsonConvert.SerializeObject(messageForAllPlayers);
|
||||||
// return session.Broadcast(serialized);
|
// return session.Broadcast(serialized);
|
||||||
// }
|
// }
|
||||||
// return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
public class Piece : IPlanarElement
|
public class Piece : IPlanarElement
|
||||||
{
|
{
|
||||||
public WhichPiece WhichPiece { get; }
|
public WhichPiece WhichPiece { get; }
|
||||||
public WhichPlayer Owner { get; private set; }
|
public WhichPerspective Owner { get; private set; }
|
||||||
public bool IsPromoted { get; private set; }
|
public bool IsPromoted { get; private set; }
|
||||||
public bool IsUpsideDown => Owner == WhichPlayer.Player2;
|
public bool IsUpsideDown => Owner == WhichPerspective.Player2;
|
||||||
|
|
||||||
public Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
public Piece(WhichPiece piece, WhichPerspective owner, bool isPromoted = false)
|
||||||
{
|
{
|
||||||
WhichPiece = piece;
|
WhichPiece = piece;
|
||||||
Owner = owner;
|
Owner = owner;
|
||||||
@@ -28,9 +28,9 @@ namespace Gameboard.ShogiUI.Sockets.Models
|
|||||||
|
|
||||||
public void ToggleOwnership()
|
public void ToggleOwnership()
|
||||||
{
|
{
|
||||||
Owner = Owner == WhichPlayer.Player1
|
Owner = Owner == WhichPerspective.Player1
|
||||||
? WhichPlayer.Player2
|
? WhichPerspective.Player2
|
||||||
: WhichPlayer.Player1;
|
: WhichPerspective.Player1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Promote() => IsPromoted = CanPromote;
|
public void Promote() => IsPromoted = CanPromote;
|
||||||
|
|||||||
@@ -5,34 +5,34 @@ using System.Net.WebSockets;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
public class Session
|
public class Session
|
||||||
{
|
{
|
||||||
// TODO: Separate subscriptions to the Session from the Session.
|
// TODO: Separate subscriptions to the Session from the Session.
|
||||||
[JsonIgnore] public ConcurrentDictionary<string, WebSocket> Subscriptions { get; }
|
[JsonIgnore] public ConcurrentDictionary<string, WebSocket> Subscriptions { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public User Player1 { get; }
|
public User Player1 { get; }
|
||||||
public User? Player2 { get; private set; }
|
public User? Player2 { get; private set; }
|
||||||
public bool IsPrivate { get; }
|
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.
|
// 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 Shogi Shogi { get; }
|
||||||
|
|
||||||
public Session(string name, bool isPrivate, Shogi shogi, User player1, User? player2 = null)
|
public Session(string name, bool isPrivate, Shogi shogi, User player1, User? player2 = null)
|
||||||
{
|
{
|
||||||
Subscriptions = new ConcurrentDictionary<string, WebSocket>();
|
Subscriptions = new ConcurrentDictionary<string, WebSocket>();
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
Player1 = player1;
|
Player1 = player1;
|
||||||
Player2 = player2;
|
Player2 = player2;
|
||||||
IsPrivate = isPrivate;
|
IsPrivate = isPrivate;
|
||||||
Shogi = shogi;
|
Shogi = shogi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPlayer2(User user)
|
public void SetPlayer2(User user)
|
||||||
{
|
{
|
||||||
Player2 = user;
|
Player2 = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Game ToServiceModel() => new() { GameName = Name, Player1 = Player1.DisplayName, Player2 = Player2?.DisplayName };
|
public Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,37 @@
|
|||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A representation of a Session without the board and game-rules.
|
/// A representation of a Session without the board and game-rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionMetadata
|
public class SessionMetadata
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public User Player1 { get; }
|
public User Player1 { get; }
|
||||||
public User? Player2 { get; private set; }
|
public User? Player2 { get; private set; }
|
||||||
public bool IsPrivate { get; }
|
public bool IsPrivate { get; }
|
||||||
|
|
||||||
public SessionMetadata(string name, bool isPrivate, User player1, User? player2 = null)
|
public SessionMetadata(string name, bool isPrivate, User player1, User? player2 = null)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
IsPrivate = isPrivate;
|
IsPrivate = isPrivate;
|
||||||
Player1 = player1;
|
Player1 = player1;
|
||||||
Player2 = player2;
|
Player2 = player2;
|
||||||
}
|
}
|
||||||
public SessionMetadata(Session sessionModel)
|
public SessionMetadata(Session sessionModel)
|
||||||
{
|
{
|
||||||
Name = sessionModel.Name;
|
Name = sessionModel.Name;
|
||||||
IsPrivate = sessionModel.IsPrivate;
|
IsPrivate = sessionModel.IsPrivate;
|
||||||
Player1 = sessionModel.Player1;
|
Player1 = sessionModel.Player1;
|
||||||
Player2 = sessionModel.Player2;
|
Player2 = sessionModel.Player2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPlayer2(User user)
|
public void SetPlayer2(User user)
|
||||||
{
|
{
|
||||||
Player2 = user;
|
Player2 = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id;
|
public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id;
|
||||||
|
|
||||||
public ServiceModels.Types.Game ToServiceModel(User? user = null)
|
public ServiceModels.Types.Game ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
|
||||||
{
|
}
|
||||||
// TODO: Find a better way for the UI to know whether or not they are seated at a given game than client-side ID matching.
|
|
||||||
var player1 = Player1.DisplayName;
|
|
||||||
var player2 = Player2?.DisplayName;
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
if (user.Id == Player1.Id) player1 = Player1.Id;
|
|
||||||
if (Player2 != null && user.Id == Player2.Id)
|
|
||||||
{
|
|
||||||
player2 = Player2.DisplayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new(Name, player1, player2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,451 +8,456 @@ using System.Numerics;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
|
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
|
||||||
/// The board is always from Player1's perspective.
|
/// The board is always from Player1's perspective.
|
||||||
/// [0,0] is the lower-left position, [8,8] is the higher-right position
|
/// [0,0] is the lower-left position, [8,8] is the higher-right position
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Shogi
|
public class Shogi
|
||||||
{
|
{
|
||||||
private delegate void MoveSetCallback(Piece piece, Vector2 position);
|
private delegate void MoveSetCallback(Piece piece, Vector2 position);
|
||||||
private readonly PathFinder2D<Piece> pathFinder;
|
private readonly PathFinder2D<Piece> pathFinder;
|
||||||
private Shogi? validationBoard;
|
private Shogi? validationBoard;
|
||||||
private Vector2 player1King;
|
private Vector2 player1King;
|
||||||
private Vector2 player2King;
|
private Vector2 player2King;
|
||||||
private List<Piece> Hand => WhoseTurn == WhichPlayer.Player1 ? Player1Hand : Player2Hand;
|
private List<Piece> Hand => WhoseTurn == WhichPerspective.Player1 ? Player1Hand : Player2Hand;
|
||||||
public List<Piece> Player1Hand { get; }
|
public List<Piece> Player1Hand { get; }
|
||||||
public List<Piece> Player2Hand { get; }
|
public List<Piece> Player2Hand { get; }
|
||||||
public CoordsToNotationCollection Board { get; } //TODO: Hide this being a getter method
|
public CoordsToNotationCollection Board { get; } //TODO: Hide this being a getter method
|
||||||
public List<Move> MoveHistory { get; }
|
public List<Move> MoveHistory { get; }
|
||||||
public WhichPlayer WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPlayer.Player1 : WhichPlayer.Player2;
|
public WhichPerspective WhoseTurn => MoveHistory.Count % 2 == 0 ? WhichPerspective.Player1 : WhichPerspective.Player2;
|
||||||
public WhichPlayer? InCheck { get; private set; }
|
public WhichPerspective? InCheck { get; private set; }
|
||||||
public bool IsCheckmate { get; private set; }
|
public bool IsCheckmate { get; private set; }
|
||||||
|
|
||||||
public string Error { get; private set; }
|
public string Error { get; private set; }
|
||||||
|
|
||||||
public Shogi()
|
public Shogi()
|
||||||
{
|
{
|
||||||
Board = new CoordsToNotationCollection();
|
Board = new CoordsToNotationCollection();
|
||||||
MoveHistory = new List<Move>(20);
|
MoveHistory = new List<Move>(20);
|
||||||
Player1Hand = new List<Piece>();
|
Player1Hand = new List<Piece>();
|
||||||
Player2Hand = new List<Piece>();
|
Player2Hand = new List<Piece>();
|
||||||
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
||||||
player1King = new Vector2(4, 0);
|
player1King = new Vector2(4, 0);
|
||||||
player2King = new Vector2(4, 8);
|
player2King = new Vector2(4, 8);
|
||||||
Error = string.Empty;
|
Error = string.Empty;
|
||||||
|
|
||||||
InitializeBoardState();
|
InitializeBoardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shogi(IList<Move> moves) : this()
|
public Shogi(IList<Move> moves) : this()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < moves.Count; i++)
|
for (var i = 0; i < moves.Count; i++)
|
||||||
{
|
{
|
||||||
if (!Move(moves[i]))
|
if (!Move(moves[i]))
|
||||||
{
|
{
|
||||||
// Todo: Add some smarts to know why a move was invalid. In check? Piece not found? etc.
|
// 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}");
|
throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}. {Error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Shogi(Shogi toCopy)
|
private Shogi(Shogi toCopy)
|
||||||
{
|
{
|
||||||
Board = new CoordsToNotationCollection();
|
Board = new CoordsToNotationCollection();
|
||||||
foreach (var kvp in toCopy.Board)
|
foreach (var kvp in toCopy.Board)
|
||||||
{
|
{
|
||||||
Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value);
|
Board[kvp.Key] = kvp.Value == null ? null : new Piece(kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
pathFinder = new PathFinder2D<Piece>(Board, 9, 9);
|
||||||
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
MoveHistory = new List<Move>(toCopy.MoveHistory);
|
||||||
Player1Hand = new List<Piece>(toCopy.Player1Hand);
|
Player1Hand = new List<Piece>(toCopy.Player1Hand);
|
||||||
Player2Hand = new List<Piece>(toCopy.Player2Hand);
|
Player2Hand = new List<Piece>(toCopy.Player2Hand);
|
||||||
player1King = toCopy.player1King;
|
player1King = toCopy.player1King;
|
||||||
player2King = toCopy.player2King;
|
player2King = toCopy.player2King;
|
||||||
Error = toCopy.Error;
|
Error = toCopy.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Move(Move move)
|
public bool Move(Move move)
|
||||||
{
|
{
|
||||||
var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
var otherPlayer = WhoseTurn == WhichPerspective.Player1 ? WhichPerspective.Player2 : WhichPerspective.Player1;
|
||||||
var moveSuccess = TryMove(move);
|
var moveSuccess = TryMove(move);
|
||||||
|
|
||||||
if (!moveSuccess)
|
if (!moveSuccess)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate check
|
// Evaluate check
|
||||||
if (EvaluateCheckAfterMove(move, otherPlayer))
|
if (EvaluateCheckAfterMove(move, otherPlayer))
|
||||||
{
|
{
|
||||||
InCheck = otherPlayer;
|
InCheck = otherPlayer;
|
||||||
IsCheckmate = EvaluateCheckmate();
|
IsCheckmate = EvaluateCheckmate();
|
||||||
}
|
}
|
||||||
return true;
|
else
|
||||||
}
|
{
|
||||||
/// <summary>
|
InCheck = null;
|
||||||
/// Attempts a given move. Returns false if the move is illegal.
|
}
|
||||||
/// </summary>
|
return true;
|
||||||
private bool TryMove(Move move)
|
}
|
||||||
{
|
/// <summary>
|
||||||
// Try making the move in a "throw away" board.
|
/// Attempts a given move. Returns false if the move is illegal.
|
||||||
if (validationBoard == null)
|
/// </summary>
|
||||||
{
|
private bool TryMove(Move move)
|
||||||
validationBoard = new Shogi(this);
|
{
|
||||||
}
|
// Try making the move in a "throw away" board.
|
||||||
|
if (validationBoard == null)
|
||||||
|
{
|
||||||
|
validationBoard = new Shogi(this);
|
||||||
|
}
|
||||||
|
|
||||||
var isValid = move.PieceFromHand.HasValue
|
var isValid = move.PieceFromHand.HasValue
|
||||||
? validationBoard.PlaceFromHand(move)
|
? validationBoard.PlaceFromHand(move)
|
||||||
: validationBoard.PlaceFromBoard(move);
|
: validationBoard.PlaceFromBoard(move);
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
{
|
{
|
||||||
// Surface the error description.
|
// Surface the error description.
|
||||||
Error = validationBoard.Error;
|
Error = validationBoard.Error;
|
||||||
// Invalidate the "throw away" board.
|
// Invalidate the "throw away" board.
|
||||||
validationBoard = null;
|
validationBoard = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// If already in check, assert the move that resulted in check no longer results in check.
|
// If already in check, assert the move that resulted in check no longer results in check.
|
||||||
if (InCheck == WhoseTurn)
|
if (InCheck == WhoseTurn)
|
||||||
{
|
{
|
||||||
if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn))
|
if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn))
|
||||||
{
|
{
|
||||||
// Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn;
|
// Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The move is valid and legal; update board state.
|
// The move is valid and legal; update board state.
|
||||||
if (move.PieceFromHand.HasValue) PlaceFromHand(move);
|
if (move.PieceFromHand.HasValue) PlaceFromHand(move);
|
||||||
else PlaceFromBoard(move);
|
else PlaceFromBoard(move);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/// <returns>True if the move was successful.</returns>
|
/// <returns>True if the move was successful.</returns>
|
||||||
private bool PlaceFromHand(Move move)
|
private bool PlaceFromHand(Move move)
|
||||||
{
|
{
|
||||||
var index = Hand.FindIndex(p => p.WhichPiece == move.PieceFromHand);
|
var index = Hand.FindIndex(p => p.WhichPiece == move.PieceFromHand);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
Error = $"{move.PieceFromHand} does not exist in the hand.";
|
Error = $"{move.PieceFromHand} does not exist in the hand.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (Board[move.To] != null)
|
if (Board[move.To] != null)
|
||||||
{
|
{
|
||||||
Error = $"Illegal move - attempting to capture while playing a piece from the hand.";
|
Error = $"Illegal move - attempting to capture while playing a piece from the hand.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (move.PieceFromHand!.Value)
|
switch (move.PieceFromHand!.Value)
|
||||||
{
|
{
|
||||||
case WhichPiece.Knight:
|
case WhichPiece.Knight:
|
||||||
{
|
{
|
||||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||||
if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y > 6)
|
if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y > 6)
|
||||||
|| (WhoseTurn == WhichPlayer.Player2 && move.To.Y < 2))
|
|| (WhoseTurn == WhichPerspective.Player2 && move.To.Y < 2))
|
||||||
{
|
{
|
||||||
Error = $"Knight has no valid moves after placed.";
|
Error = $"Knight has no valid moves after placed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WhichPiece.Lance:
|
case WhichPiece.Lance:
|
||||||
case WhichPiece.Pawn:
|
case WhichPiece.Pawn:
|
||||||
{
|
{
|
||||||
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
||||||
if ((WhoseTurn == WhichPlayer.Player1 && move.To.Y == 8)
|
if ((WhoseTurn == WhichPerspective.Player1 && move.To.Y == 8)
|
||||||
|| (WhoseTurn == WhichPlayer.Player2 && move.To.Y == 0))
|
|| (WhoseTurn == WhichPerspective.Player2 && move.To.Y == 0))
|
||||||
{
|
{
|
||||||
Error = $"{move.PieceFromHand} has no valid moves after placed.";
|
Error = $"{move.PieceFromHand} has no valid moves after placed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutate the board.
|
// Mutate the board.
|
||||||
Board[move.To] = Hand[index];
|
Board[move.To] = Hand[index];
|
||||||
Hand.RemoveAt(index);
|
Hand.RemoveAt(index);
|
||||||
|
MoveHistory.Add(move);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/// <returns>True if the move was successful.</returns>
|
/// <returns>True if the move was successful.</returns>
|
||||||
private bool PlaceFromBoard(Move move)
|
private bool PlaceFromBoard(Move move)
|
||||||
{
|
{
|
||||||
var fromPiece = Board[move.From!.Value];
|
var fromPiece = Board[move.From!.Value];
|
||||||
if (fromPiece == null)
|
if (fromPiece == null)
|
||||||
{
|
{
|
||||||
Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
|
Error = $"No piece exists at {nameof(move)}.{nameof(move.From)}.";
|
||||||
return false; // Invalid move
|
return false; // Invalid move
|
||||||
}
|
}
|
||||||
if (fromPiece.Owner != WhoseTurn)
|
if (fromPiece.Owner != WhoseTurn)
|
||||||
{
|
{
|
||||||
Error = "Not allowed to move the opponents piece";
|
Error = "Not allowed to move the opponents piece";
|
||||||
return false; // Invalid move; cannot move other players pieces.
|
return false; // Invalid move; cannot move other players pieces.
|
||||||
}
|
}
|
||||||
if (IsPathable(move.From.Value, move.To) == false)
|
if (IsPathable(move.From.Value, move.To) == false)
|
||||||
{
|
{
|
||||||
Error = $"Illegal move for {fromPiece.WhichPiece}. {nameof(move)}.{nameof(move.To)} is not part of the move-set.";
|
Error = $"Illegal move for {fromPiece.WhichPiece}. {nameof(move)}.{nameof(move.To)} is not part of the move-set.";
|
||||||
return false; // Invalid move; move not part of move-set.
|
return false; // Invalid move; move not part of move-set.
|
||||||
}
|
}
|
||||||
|
|
||||||
var captured = Board[move.To];
|
var captured = Board[move.To];
|
||||||
if (captured != null)
|
if (captured != null)
|
||||||
{
|
{
|
||||||
if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece.
|
if (captured.Owner == WhoseTurn) return false; // Invalid move; cannot capture your own piece.
|
||||||
captured.Capture();
|
captured.Capture();
|
||||||
Hand.Add(captured);
|
Hand.Add(captured);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Mutate the board.
|
//Mutate the board.
|
||||||
if (move.IsPromotion)
|
if (move.IsPromotion)
|
||||||
{
|
{
|
||||||
if (WhoseTurn == WhichPlayer.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5))
|
if (WhoseTurn == WhichPerspective.Player1 && (move.To.Y > 5 || move.From.Value.Y > 5))
|
||||||
{
|
{
|
||||||
fromPiece.Promote();
|
fromPiece.Promote();
|
||||||
}
|
}
|
||||||
else if (WhoseTurn == WhichPlayer.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3))
|
else if (WhoseTurn == WhichPerspective.Player2 && (move.To.Y < 3 || move.From.Value.Y < 3))
|
||||||
{
|
{
|
||||||
fromPiece.Promote();
|
fromPiece.Promote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Board[move.To] = fromPiece;
|
Board[move.To] = fromPiece;
|
||||||
Board[move.From!.Value] = null;
|
Board[move.From!.Value] = null;
|
||||||
if (fromPiece.WhichPiece == WhichPiece.King)
|
if (fromPiece.WhichPiece == WhichPiece.King)
|
||||||
{
|
{
|
||||||
if (fromPiece.Owner == WhichPlayer.Player1)
|
if (fromPiece.Owner == WhichPerspective.Player1)
|
||||||
{
|
{
|
||||||
player1King.X = move.To.X;
|
player1King.X = move.To.X;
|
||||||
player1King.Y = move.To.Y;
|
player1King.Y = move.To.Y;
|
||||||
}
|
}
|
||||||
else if (fromPiece.Owner == WhichPlayer.Player2)
|
else if (fromPiece.Owner == WhichPerspective.Player2)
|
||||||
{
|
{
|
||||||
player2King.X = move.To.X;
|
player2King.X = move.To.X;
|
||||||
player2King.Y = move.To.Y;
|
player2King.Y = move.To.Y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MoveHistory.Add(move);
|
MoveHistory.Add(move);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPathable(Vector2 from, Vector2 to)
|
private bool IsPathable(Vector2 from, Vector2 to)
|
||||||
{
|
{
|
||||||
var piece = Board[from];
|
var piece = Board[from];
|
||||||
if (piece == null) return false;
|
if (piece == null) return false;
|
||||||
|
|
||||||
var isObstructed = false;
|
var isObstructed = false;
|
||||||
var isPathable = pathFinder.PathTo(from, to, (other, position) =>
|
var isPathable = pathFinder.PathTo(from, to, (other, position) =>
|
||||||
{
|
{
|
||||||
if (other.Owner == piece.Owner) isObstructed = true;
|
if (other.Owner == piece.Owner) isObstructed = true;
|
||||||
});
|
});
|
||||||
return !isObstructed && isPathable;
|
return !isObstructed && isPathable;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Rules Validation
|
#region Rules Validation
|
||||||
private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer)
|
private bool EvaluateCheckAfterMove(Move move, WhichPerspective WhichPerspective)
|
||||||
{
|
{
|
||||||
if (whichPlayer == InCheck) return true; // If we already know the player is in check, don't bother.
|
if (WhichPerspective == InCheck) return true; // If we already know the player is in check, don't bother.
|
||||||
|
|
||||||
var isCheck = false;
|
var isCheck = false;
|
||||||
var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King;
|
var kingPosition = WhichPerspective == WhichPerspective.Player1 ? player1King : player2King;
|
||||||
|
|
||||||
// Check if the move put the king in check.
|
// Check if the move put the king in check.
|
||||||
if (pathFinder.PathTo(move.To, kingPosition)) return true;
|
if (pathFinder.PathTo(move.To, kingPosition)) return true;
|
||||||
|
|
||||||
if (move.From.HasValue)
|
if (move.From.HasValue)
|
||||||
{
|
{
|
||||||
// Get line equation from king through the now-unoccupied location.
|
// Get line equation from king through the now-unoccupied location.
|
||||||
var direction = Vector2.Subtract(kingPosition, move.From!.Value);
|
var direction = Vector2.Subtract(kingPosition, move.From!.Value);
|
||||||
var slope = Math.Abs(direction.Y / direction.X);
|
var slope = Math.Abs(direction.Y / direction.X);
|
||||||
// If absolute slope is 45°, look for a bishop along the line.
|
// If absolute slope is 45°, look for a bishop along the line.
|
||||||
// If absolute slope is 0° or 90°, look for a rook along the line.
|
// If absolute slope is 0° or 90°, look for a rook along the line.
|
||||||
// if absolute slope is 0°, look for lance along the line.
|
// if absolute slope is 0°, look for lance along the line.
|
||||||
if (float.IsInfinity(slope))
|
if (float.IsInfinity(slope))
|
||||||
{
|
{
|
||||||
// if slope of the move is also infinity...can skip this?
|
// if slope of the move is also infinity...can skip this?
|
||||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||||
{
|
{
|
||||||
if (piece.Owner != whichPlayer)
|
if (piece.Owner != WhichPerspective)
|
||||||
{
|
{
|
||||||
switch (piece.WhichPiece)
|
switch (piece.WhichPiece)
|
||||||
{
|
{
|
||||||
case WhichPiece.Rook:
|
case WhichPiece.Rook:
|
||||||
isCheck = true;
|
isCheck = true;
|
||||||
break;
|
break;
|
||||||
case WhichPiece.Lance:
|
case WhichPiece.Lance:
|
||||||
if (!piece.IsPromoted) isCheck = true;
|
if (!piece.IsPromoted) isCheck = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (slope == 1)
|
else if (slope == 1)
|
||||||
{
|
{
|
||||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||||
{
|
{
|
||||||
if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Bishop)
|
if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Bishop)
|
||||||
{
|
{
|
||||||
isCheck = true;
|
isCheck = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (slope == 0)
|
else if (slope == 0)
|
||||||
{
|
{
|
||||||
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
pathFinder.LinePathTo(kingPosition, direction, (piece, position) =>
|
||||||
{
|
{
|
||||||
if (piece.Owner != whichPlayer && piece.WhichPiece == WhichPiece.Rook)
|
if (piece.Owner != WhichPerspective && piece.WhichPiece == WhichPiece.Rook)
|
||||||
{
|
{
|
||||||
isCheck = true;
|
isCheck = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Check for illegal move from hand. It is illegal to place from the hand such that you check-mate your opponent.
|
// 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.
|
// Go read the shogi rules to be sure this is true.
|
||||||
}
|
}
|
||||||
|
|
||||||
return isCheck;
|
return isCheck;
|
||||||
}
|
}
|
||||||
private bool EvaluateCheckmate()
|
private bool EvaluateCheckmate()
|
||||||
{
|
{
|
||||||
if (!InCheck.HasValue) return false;
|
if (!InCheck.HasValue) return false;
|
||||||
|
|
||||||
// Assume true and try to disprove.
|
// Assume true and try to disprove.
|
||||||
var isCheckmate = true;
|
var isCheckmate = true;
|
||||||
Board.ForEachNotNull((piece, from) => // For each piece...
|
Board.ForEachNotNull((piece, from) => // For each piece...
|
||||||
{
|
{
|
||||||
// Short circuit
|
// Short circuit
|
||||||
if (!isCheckmate) return;
|
if (!isCheckmate) return;
|
||||||
|
|
||||||
if (piece.Owner == InCheck) // ...owned by the player in check...
|
if (piece.Owner == InCheck) // ...owned by the player in check...
|
||||||
{
|
{
|
||||||
// ...evaluate if any move gets the player out of check.
|
// ...evaluate if any move gets the player out of check.
|
||||||
pathFinder.PathEvery(from, (other, position) =>
|
pathFinder.PathEvery(from, (other, position) =>
|
||||||
{
|
{
|
||||||
if (validationBoard == null) validationBoard = new Shogi(this);
|
if (validationBoard == null) validationBoard = new Shogi(this);
|
||||||
var moveToTry = new Move(from, position);
|
var moveToTry = new Move(from, position);
|
||||||
var moveSuccess = validationBoard.TryMove(moveToTry);
|
var moveSuccess = validationBoard.TryMove(moveToTry);
|
||||||
if (moveSuccess)
|
if (moveSuccess)
|
||||||
{
|
{
|
||||||
validationBoard = null;
|
validationBoard = null;
|
||||||
if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value))
|
if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value))
|
||||||
{
|
{
|
||||||
isCheckmate = false;
|
isCheckmate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return isCheckmate;
|
return isCheckmate;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void InitializeBoardState()
|
private void InitializeBoardState()
|
||||||
{
|
{
|
||||||
Board["A1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1);
|
Board["A1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1);
|
||||||
Board["B1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1);
|
Board["B1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1);
|
||||||
Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1);
|
Board["C1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1);
|
||||||
Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1);
|
Board["D1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1);
|
||||||
Board["E1"] = new Piece(WhichPiece.King, WhichPlayer.Player1);
|
Board["E1"] = new Piece(WhichPiece.King, WhichPerspective.Player1);
|
||||||
Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player1);
|
Board["F1"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player1);
|
||||||
Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player1);
|
Board["G1"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player1);
|
||||||
Board["H1"] = new Piece(WhichPiece.Knight, WhichPlayer.Player1);
|
Board["H1"] = new Piece(WhichPiece.Knight, WhichPerspective.Player1);
|
||||||
Board["I1"] = new Piece(WhichPiece.Lance, WhichPlayer.Player1);
|
Board["I1"] = new Piece(WhichPiece.Lance, WhichPerspective.Player1);
|
||||||
|
|
||||||
Board["A2"] = null;
|
Board["A2"] = null;
|
||||||
Board["B2"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player1);
|
Board["B2"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player1);
|
||||||
Board["C2"] = null;
|
Board["C2"] = null;
|
||||||
Board["D2"] = null;
|
Board["D2"] = null;
|
||||||
Board["E2"] = null;
|
Board["E2"] = null;
|
||||||
Board["F2"] = null;
|
Board["F2"] = null;
|
||||||
Board["G2"] = null;
|
Board["G2"] = null;
|
||||||
Board["H2"] = new Piece(WhichPiece.Rook, WhichPlayer.Player1);
|
Board["H2"] = new Piece(WhichPiece.Rook, WhichPerspective.Player1);
|
||||||
Board["I2"] = null;
|
Board["I2"] = null;
|
||||||
|
|
||||||
Board["A3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["A3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["B3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["B3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["C3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["C3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["D3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["D3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["E3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["E3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["F3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["F3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["G3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["G3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["H3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["H3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
Board["I3"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player1);
|
Board["I3"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player1);
|
||||||
|
|
||||||
Board["A4"] = null;
|
Board["A4"] = null;
|
||||||
Board["B4"] = null;
|
Board["B4"] = null;
|
||||||
Board["C4"] = null;
|
Board["C4"] = null;
|
||||||
Board["D4"] = null;
|
Board["D4"] = null;
|
||||||
Board["E4"] = null;
|
Board["E4"] = null;
|
||||||
Board["F4"] = null;
|
Board["F4"] = null;
|
||||||
Board["G4"] = null;
|
Board["G4"] = null;
|
||||||
Board["H4"] = null;
|
Board["H4"] = null;
|
||||||
Board["I4"] = null;
|
Board["I4"] = null;
|
||||||
|
|
||||||
Board["A5"] = null;
|
Board["A5"] = null;
|
||||||
Board["B5"] = null;
|
Board["B5"] = null;
|
||||||
Board["C5"] = null;
|
Board["C5"] = null;
|
||||||
Board["D5"] = null;
|
Board["D5"] = null;
|
||||||
Board["E5"] = null;
|
Board["E5"] = null;
|
||||||
Board["F5"] = null;
|
Board["F5"] = null;
|
||||||
Board["G5"] = null;
|
Board["G5"] = null;
|
||||||
Board["H5"] = null;
|
Board["H5"] = null;
|
||||||
Board["I5"] = null;
|
Board["I5"] = null;
|
||||||
|
|
||||||
Board["A6"] = null;
|
Board["A6"] = null;
|
||||||
Board["B6"] = null;
|
Board["B6"] = null;
|
||||||
Board["C6"] = null;
|
Board["C6"] = null;
|
||||||
Board["D6"] = null;
|
Board["D6"] = null;
|
||||||
Board["E6"] = null;
|
Board["E6"] = null;
|
||||||
Board["F6"] = null;
|
Board["F6"] = null;
|
||||||
Board["G6"] = null;
|
Board["G6"] = null;
|
||||||
Board["H6"] = null;
|
Board["H6"] = null;
|
||||||
Board["I6"] = null;
|
Board["I6"] = null;
|
||||||
|
|
||||||
Board["A7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["A7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["B7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["B7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["C7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["C7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["D7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["D7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["E7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["E7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["F7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["F7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["G7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["G7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["H7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["H7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
Board["I7"] = new Piece(WhichPiece.Pawn, WhichPlayer.Player2);
|
Board["I7"] = new Piece(WhichPiece.Pawn, WhichPerspective.Player2);
|
||||||
|
|
||||||
Board["A8"] = null;
|
Board["A8"] = null;
|
||||||
Board["B8"] = new Piece(WhichPiece.Rook, WhichPlayer.Player2);
|
Board["B8"] = new Piece(WhichPiece.Rook, WhichPerspective.Player2);
|
||||||
Board["C8"] = null;
|
Board["C8"] = null;
|
||||||
Board["D8"] = null;
|
Board["D8"] = null;
|
||||||
Board["E8"] = null;
|
Board["E8"] = null;
|
||||||
Board["F8"] = null;
|
Board["F8"] = null;
|
||||||
Board["G8"] = null;
|
Board["G8"] = null;
|
||||||
Board["H8"] = new Piece(WhichPiece.Bishop, WhichPlayer.Player2);
|
Board["H8"] = new Piece(WhichPiece.Bishop, WhichPerspective.Player2);
|
||||||
Board["I8"] = null;
|
Board["I8"] = null;
|
||||||
|
|
||||||
Board["A9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2);
|
Board["A9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2);
|
||||||
Board["B9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2);
|
Board["B9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2);
|
||||||
Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2);
|
Board["C9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2);
|
||||||
Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2);
|
Board["D9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2);
|
||||||
Board["E9"] = new Piece(WhichPiece.King, WhichPlayer.Player2);
|
Board["E9"] = new Piece(WhichPiece.King, WhichPerspective.Player2);
|
||||||
Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPlayer.Player2);
|
Board["F9"] = new Piece(WhichPiece.GoldGeneral, WhichPerspective.Player2);
|
||||||
Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPlayer.Player2);
|
Board["G9"] = new Piece(WhichPiece.SilverGeneral, WhichPerspective.Player2);
|
||||||
Board["H9"] = new Piece(WhichPiece.Knight, WhichPlayer.Player2);
|
Board["H9"] = new Piece(WhichPiece.Knight, WhichPerspective.Player2);
|
||||||
Board["I9"] = new Piece(WhichPiece.Lance, WhichPlayer.Player2);
|
Board["I9"] = new Piece(WhichPiece.Lance, WhichPerspective.Player2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoardState ToServiceModel()
|
public BoardState ToServiceModel()
|
||||||
{
|
{
|
||||||
return new BoardState
|
return new BoardState
|
||||||
{
|
{
|
||||||
Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()),
|
Board = Board.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToServiceModel()),
|
||||||
PlayerInCheck = InCheck,
|
PlayerInCheck = InCheck,
|
||||||
WhoseTurn = WhoseTurn,
|
WhoseTurn = WhoseTurn,
|
||||||
Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(),
|
Player1Hand = Player1Hand.Select(_ => _.ToServiceModel()).ToList(),
|
||||||
Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList()
|
Player2Hand = Player2Hand.Select(_ => _.ToServiceModel()).ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,72 +8,78 @@ using System.Security.Claims;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
namespace Gameboard.ShogiUI.Sockets.Models
|
||||||
{
|
{
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
public static readonly ReadOnlyCollection<string> Adjectives = new(new[] {
|
public static readonly ReadOnlyCollection<string> Adjectives = new(new[] {
|
||||||
"Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly"
|
"Fortuitous", "Retractable", "Happy", "Habbitable", "Creative", "Fluffy", "Impervious", "Kingly"
|
||||||
});
|
});
|
||||||
public static readonly ReadOnlyCollection<string> Subjects = new(new[] {
|
public static readonly ReadOnlyCollection<string> Subjects = new(new[] {
|
||||||
"Hippo", "Basil", "Mouse", "Walnut", "Prince", "Lima Bean", "Coala", "Potato"
|
"Hippo", "Basil", "Mouse", "Walnut", "Prince", "Lima Bean", "Coala", "Potato", "Penguin"
|
||||||
});
|
});
|
||||||
public static User CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft);
|
public static User CreateMsalUser(string id) => new(id, id, WhichLoginPlatform.Microsoft);
|
||||||
public static User CreateGuestUser(string id)
|
public static User CreateGuestUser(string id)
|
||||||
{
|
{
|
||||||
var random = new Random();
|
var random = new Random();
|
||||||
// Adjective
|
// Adjective
|
||||||
var index = (int)Math.Floor(random.NextDouble() * Adjectives.Count);
|
var index = (int)Math.Floor(random.NextDouble() * Adjectives.Count);
|
||||||
var adj = Adjectives[index];
|
var adj = Adjectives[index];
|
||||||
// Subject
|
// Subject
|
||||||
index = (int)Math.Floor(random.NextDouble() * Subjects.Count);
|
index = (int)Math.Floor(random.NextDouble() * Subjects.Count);
|
||||||
var subj = Subjects[index];
|
var subj = Subjects[index];
|
||||||
|
|
||||||
return new User(id, $"{adj} {subj}", WhichLoginPlatform.Guest);
|
return new User(id, $"{adj} {subj}", WhichLoginPlatform.Guest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
public string DisplayName { get; }
|
public string DisplayName { get; }
|
||||||
|
|
||||||
public WhichLoginPlatform LoginPlatform { get; }
|
public WhichLoginPlatform LoginPlatform { get; }
|
||||||
|
|
||||||
public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest;
|
public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest;
|
||||||
|
|
||||||
public User(string id, string displayName, WhichLoginPlatform platform)
|
public User(string id, string displayName, WhichLoginPlatform platform)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
DisplayName = displayName;
|
DisplayName = displayName;
|
||||||
LoginPlatform = platform;
|
LoginPlatform = platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User(UserDocument document)
|
public User(UserDocument document)
|
||||||
{
|
{
|
||||||
Id = document.Id;
|
Id = document.Id;
|
||||||
DisplayName = document.DisplayName;
|
DisplayName = document.DisplayName;
|
||||||
LoginPlatform = document.Platform;
|
LoginPlatform = document.Platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClaimsIdentity CreateClaimsIdentity()
|
public ClaimsIdentity CreateClaimsIdentity()
|
||||||
{
|
{
|
||||||
if (LoginPlatform == WhichLoginPlatform.Guest)
|
if (LoginPlatform == WhichLoginPlatform.Guest)
|
||||||
{
|
{
|
||||||
var claims = new List<Claim>(4)
|
var claims = new List<Claim>(4)
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.NameIdentifier, Id),
|
new Claim(ClaimTypes.NameIdentifier, Id),
|
||||||
new Claim(ClaimTypes.Name, DisplayName),
|
new Claim(ClaimTypes.Name, DisplayName),
|
||||||
new Claim(ClaimTypes.Role, "Guest"),
|
new Claim(ClaimTypes.Role, "Guest"),
|
||||||
new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers.
|
new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers.
|
||||||
};
|
};
|
||||||
return new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
return new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var claims = new List<Claim>(3)
|
var claims = new List<Claim>(3)
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.NameIdentifier, Id),
|
new Claim(ClaimTypes.NameIdentifier, Id),
|
||||||
new Claim(ClaimTypes.Name, DisplayName),
|
new Claim(ClaimTypes.Name, DisplayName),
|
||||||
new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers.
|
new Claim(ClaimTypes.Role, "Shogi") // The Shogi role grants access to api controllers.
|
||||||
};
|
};
|
||||||
return new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme);
|
return new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public ServiceModels.Types.User ToServiceModel() => new()
|
||||||
|
{
|
||||||
|
Id = Id,
|
||||||
|
Name = DisplayName
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
|||||||
public class Piece
|
public class Piece
|
||||||
{
|
{
|
||||||
public bool IsPromoted { get; set; }
|
public bool IsPromoted { get; set; }
|
||||||
public WhichPlayer Owner { get; set; }
|
public WhichPerspective Owner { get; set; }
|
||||||
public WhichPiece WhichPiece { get; set; }
|
public WhichPiece WhichPiece { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.Repositories;
|
using Gameboard.ShogiUI.Sockets.Repositories;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Gameboard.ShogiUI.Sockets.Models;
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using WhichPlayer = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer;
|
using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPerspective;
|
||||||
using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece;
|
using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece;
|
||||||
namespace Gameboard.ShogiUI.UnitTests.Rules
|
namespace Gameboard.ShogiUI.UnitTests.Rules
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user