mapper class and delete old stuff
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Api
|
||||||
{
|
{
|
||||||
public class GetSessionsResponse
|
public class GetSessionsResponse
|
||||||
{
|
{
|
||||||
public Collection<Session> PlayerHasJoinedSessions { get; set; }
|
public IList<SessionMetadata> PlayerHasJoinedSessions { get; set; }
|
||||||
public Collection<Session> AllOtherSessions { get; set; }
|
public IList<SessionMetadata> AllOtherSessions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
||||||
{
|
{
|
||||||
public class CreateGameResponse : IResponse
|
public class CreateGameResponse : ISocketResponse
|
||||||
{
|
{
|
||||||
public string Action { get; }
|
public string Action { get; }
|
||||||
public Session Game { get; set; }
|
public SessionMetadata Game { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The player who created the game.
|
/// The player who created the game.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
||||||
{
|
{
|
||||||
public interface IResponse
|
public interface ISocketResponse
|
||||||
{
|
{
|
||||||
string Action { get; }
|
string Action { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
|||||||
public string GameName { get; set; } = "";
|
public string GameName { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class JoinGameResponse : IResponse
|
public class JoinGameResponse : ISocketResponse
|
||||||
{
|
{
|
||||||
public string Action { get; protected set; }
|
public string Action { get; protected set; }
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; }
|
||||||
@@ -31,7 +31,7 @@ namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class JoinByCodeResponse : JoinGameResponse, IResponse
|
public class JoinByCodeResponse : JoinGameResponse, ISocketResponse
|
||||||
{
|
{
|
||||||
public JoinByCodeResponse()
|
public JoinByCodeResponse()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Socket
|
||||||
{
|
{
|
||||||
public class MoveResponse : IResponse
|
public class MoveResponse : ISocketResponse
|
||||||
{
|
{
|
||||||
public string Action { get; }
|
public string Action { get; }
|
||||||
public string GameName { get; set; }
|
public string GameName { get; set; }
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Gameboard.ShogiUI.Sockets.ServiceModels.Types
|
||||||
|
{
|
||||||
|
public class SessionMetadata
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Player1 { get; set; }
|
||||||
|
public string? Player2 { get; set; }
|
||||||
|
public bool IsPrivate { get; set; }
|
||||||
|
|
||||||
|
public string[] Players => string.IsNullOrEmpty(Player2) ? new[] { Player1 } : new[] { Player1, Player2 };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,16 +9,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.Sockets.S
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.UnitTests", "Gameboard.ShogiUI.UnitTests\Gameboard.ShogiUI.UnitTests.csproj", "{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarking", "Benchmarking\Benchmarking.csproj", "{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gameboard.ShogiUI.xUnitTests", "Gameboard.ShogiUI.xUnitTests\Gameboard.ShogiUI.xUnitTests.csproj", "{12530716-C11E-40CE-9F71-CCCC243F03E1}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain.UnitTests", "Shogi.Domain.UnitTests\Shogi.Domain.UnitTests.csproj", "{F256989E-B6AF-4731-9DB4-88991C40B2CE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain.UnitTests", "Shogi.Domain.UnitTests\Shogi.Domain.UnitTests.csproj", "{F256989E-B6AF-4731-9DB4-88991C40B2CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi.AcceptanceTests", "Shogi.AcceptanceTests\Shogi.AcceptanceTests.csproj", "{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -33,18 +31,10 @@ Global
|
|||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FE775DE4-50F0-4C5D-AD2B-01320B1E7086}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DADFF5D6-581F-4D69-845D-53ABD6ABF62F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{12530716-C11E-40CE-9F71-CCCC243F03E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -53,14 +43,17 @@ Global
|
|||||||
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F256989E-B6AF-4731-9DB4-88991C40B2CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{DC8A933A-DBCB-46B9-AA0B-7B3DC9E763F3} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}
|
|
||||||
{12530716-C11E-40CE-9F71-CCCC243F03E1} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}
|
|
||||||
{F256989E-B6AF-4731-9DB4-88991C40B2CE} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}
|
{F256989E-B6AF-4731-9DB4-88991C40B2CE} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}
|
||||||
|
{F4AB1C7C-CDE5-465D-81A1-DAF1D97225BA} = {F35A56FB-B8D8-4CB7-ABF6-D40049C9B42E}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB}
|
SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB}
|
||||||
|
|||||||
@@ -21,15 +21,18 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
private readonly IGameboardManager gameboardManager;
|
private readonly IGameboardManager gameboardManager;
|
||||||
private readonly IGameboardRepository gameboardRepository;
|
private readonly IGameboardRepository gameboardRepository;
|
||||||
private readonly ISocketConnectionManager communicationManager;
|
private readonly ISocketConnectionManager communicationManager;
|
||||||
|
private readonly IModelMapper mapper;
|
||||||
|
|
||||||
public GameController(
|
public GameController(
|
||||||
IGameboardRepository repository,
|
IGameboardRepository repository,
|
||||||
IGameboardManager manager,
|
IGameboardManager manager,
|
||||||
ISocketConnectionManager communicationManager)
|
ISocketConnectionManager communicationManager,
|
||||||
|
IModelMapper mapper)
|
||||||
{
|
{
|
||||||
gameboardManager = manager;
|
gameboardManager = manager;
|
||||||
gameboardRepository = repository;
|
gameboardRepository = repository;
|
||||||
this.communicationManager = communicationManager;
|
this.communicationManager = communicationManager;
|
||||||
|
this.mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("JoinCode")]
|
[HttpPost("JoinCode")]
|
||||||
@@ -75,32 +78,35 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id))
|
if (user == null || (session.Player1Name != user.Id && session.Player2Name != user.Id))
|
||||||
{
|
{
|
||||||
return Forbid("User is not seated at this game.");
|
return Forbid("User is not seated at this game.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var move = request.Move;
|
try
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var createSuccess = await gameboardRepository.CreateBoardState(session);
|
var move = request.Move;
|
||||||
if (!createSuccess)
|
if (move.PieceFromCaptured.HasValue)
|
||||||
{
|
session.Move(mapper.Map(move.PieceFromCaptured.Value), move.To);
|
||||||
throw new ApplicationException("Unable to persist board state.");
|
else if (!string.IsNullOrWhiteSpace(move.From))
|
||||||
}
|
session.Move(move.From, move.To, move.IsPromotion);
|
||||||
await communicationManager.BroadcastToPlayers(new MoveResponse
|
|
||||||
{
|
await gameboardRepository.CreateBoardState(session);
|
||||||
GameName = session.Name,
|
await communicationManager.BroadcastToPlayers(
|
||||||
PlayerName = user.Id
|
new MoveResponse
|
||||||
}, session.Player1.Id, session.Player2?.Id);
|
{
|
||||||
|
GameName = session.Name,
|
||||||
|
PlayerName = user.Id
|
||||||
|
},
|
||||||
|
session.Player1Name,
|
||||||
|
session.Player2Name);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
return Conflict("Illegal move.");
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return Conflict(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -128,28 +134,15 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
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 Shogi.Domain.SessionMetadata(request.Name, request.IsPrivate, user.Id);
|
||||||
var success = await gameboardRepository.CreateSession(session);
|
await gameboardRepository.CreateSession(session);
|
||||||
|
await communicationManager.BroadcastToAll(new CreateGameResponse
|
||||||
if (success)
|
|
||||||
{
|
{
|
||||||
try
|
Game = mapper.Map(session),
|
||||||
{
|
PlayerName = user.Id
|
||||||
|
});
|
||||||
|
|
||||||
await communicationManager.BroadcastToAll(new CreateGameResponse
|
return Ok();
|
||||||
{
|
|
||||||
Game = session.ToServiceModel(),
|
|
||||||
PlayerName = user.Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine("Error broadcasting during PostSession");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
return Conflict();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,40 +167,16 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
{
|
{
|
||||||
BoardState = new BoardState
|
BoardState = new BoardState
|
||||||
{
|
{
|
||||||
Board = null,
|
Board = mapper.Map(session.BoardState),
|
||||||
Player1Hand = session.Player1Hand.Select(MapPiece).ToList(),
|
Player1Hand = session.Player1Hand.Select(mapper.Map).ToList(),
|
||||||
Player2Hand = session.Player2Hand.Select(MapPiece).ToList(),
|
Player2Hand = session.Player2Hand.Select(mapper.Map).ToList(),
|
||||||
PlayerInCheck = session.InCheck.HasValue ? Map(session.InCheck.Value) : null
|
PlayerInCheck = mapper.Map(session.InCheck)
|
||||||
},
|
},
|
||||||
GameName = session.Name,
|
GameName = session.Name,
|
||||||
Player1 = session.Player1Name,
|
Player1 = session.Player1Name,
|
||||||
Player2 = session.Player2Name
|
Player2 = session.Player2Name
|
||||||
};
|
};
|
||||||
return this.Ok(response);
|
return Ok(response);
|
||||||
|
|
||||||
static WhichPlayer Map(Shogi.Domain.WhichPlayer whichPlayer)
|
|
||||||
{
|
|
||||||
return whichPlayer == Shogi.Domain.WhichPlayer.Player1
|
|
||||||
? WhichPlayer.Player1
|
|
||||||
: WhichPlayer.Player2;
|
|
||||||
}
|
|
||||||
static Piece MapPiece(Shogi.Domain.Pieces.Piece piece)
|
|
||||||
{
|
|
||||||
var owner = Map(piece.Owner);
|
|
||||||
var whichPiece = piece.WhichPiece switch
|
|
||||||
{
|
|
||||||
Shogi.Domain.WhichPiece.King => WhichPiece.King,
|
|
||||||
Shogi.Domain.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
|
|
||||||
Shogi.Domain.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
|
|
||||||
Shogi.Domain.WhichPiece.Bishop => WhichPiece.Bishop,
|
|
||||||
Shogi.Domain.WhichPiece.Rook => WhichPiece.Rook,
|
|
||||||
Shogi.Domain.WhichPiece.Knight => WhichPiece.Knight,
|
|
||||||
Shogi.Domain.WhichPiece.Lance => WhichPiece.Lance,
|
|
||||||
Shogi.Domain.WhichPiece.Pawn => WhichPiece.Pawn,
|
|
||||||
_ => throw new ArgumentException($"Unknown value for {nameof(WhichPiece)}")
|
|
||||||
};
|
|
||||||
return new Piece { IsPromoted = piece.IsPromoted, Owner = owner, WhichPiece = whichPiece };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -217,18 +186,18 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
var sessions = await gameboardRepository.ReadSessionMetadatas();
|
var sessions = await gameboardRepository.ReadSessionMetadatas();
|
||||||
|
|
||||||
var sessionsJoinedByUser = sessions
|
var sessionsJoinedByUser = sessions
|
||||||
.Where(s => s.IsSeated(user))
|
.Where(s => s.IsSeated(user.Id))
|
||||||
.Select(s => s.ToServiceModel())
|
.Select(s => mapper.Map(s))
|
||||||
.ToList();
|
.ToList();
|
||||||
var sessionsNotJoinedByUser = sessions
|
var sessionsNotJoinedByUser = sessions
|
||||||
.Where(s => !s.IsSeated(user))
|
.Where(s => !s.IsSeated(user.Id))
|
||||||
.Select(s => s.ToServiceModel())
|
.Select(s => mapper.Map(s))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return new GetSessionsResponse
|
return new GetSessionsResponse
|
||||||
{
|
{
|
||||||
PlayerHasJoinedSessions = new Collection<Session>(sessionsJoinedByUser),
|
PlayerHasJoinedSessions = sessionsJoinedByUser,
|
||||||
AllOtherSessions = new Collection<Session>(sessionsNotJoinedByUser)
|
AllOtherSessions = sessionsNotJoinedByUser
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,13 +215,12 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
return this.Conflict("This session already has two seated players and is full.");
|
return this.Conflict("This session already has two seated players and is full.");
|
||||||
}
|
}
|
||||||
|
|
||||||
session.SetPlayer2(user);
|
session.SetPlayer2(user.Id);
|
||||||
var success = await gameboardRepository.UpdateSession(session);
|
await gameboardRepository.UpdateSession(session);
|
||||||
if (!success) return this.Problem(detail: "Unable to update session.");
|
|
||||||
|
|
||||||
var opponentName = user.Id == session.Player1.Id
|
var opponentName = user.Id == session.Player1
|
||||||
? session.Player2!.Id
|
? session.Player2!
|
||||||
: session.Player1.Id;
|
: session.Player1;
|
||||||
await communicationManager.BroadcastToPlayers(new JoinGameResponse
|
await communicationManager.BroadcastToPlayers(new JoinGameResponse
|
||||||
{
|
{
|
||||||
GameName = session.Name,
|
GameName = session.Name,
|
||||||
|
|||||||
@@ -68,10 +68,8 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
var user = await gameboardManager.ReadUser(User);
|
var user = await gameboardManager.ReadUser(User);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
if (await gameboardManager.CreateUser(User))
|
await gameboardManager.CreateUser(User);
|
||||||
{
|
user = await gameboardManager.ReadUser(User);
|
||||||
user = await gameboardManager.ReadUser(User);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@@ -92,11 +90,7 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
|
|||||||
{
|
{
|
||||||
// Create a guest user.
|
// Create a guest user.
|
||||||
var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString());
|
var newUser = Models.User.CreateGuestUser(Guid.NewGuid().ToString());
|
||||||
var success = await gameboardRepository.CreateUser(newUser);
|
await gameboardRepository.CreateUser(newUser);
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
return Conflict();
|
|
||||||
}
|
|
||||||
|
|
||||||
var identity = newUser.CreateClaimsIdentity();
|
var identity = newUser.CreateClaimsIdentity();
|
||||||
await HttpContext.SignInAsync(
|
await HttpContext.SignInAsync(
|
||||||
|
|||||||
@@ -7,68 +7,68 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Managers
|
namespace Gameboard.ShogiUI.Sockets.Managers
|
||||||
{
|
{
|
||||||
public interface IGameboardManager
|
public interface IGameboardManager
|
||||||
{
|
{
|
||||||
Task<bool> AssignPlayer2ToSession(string sessionName, User user);
|
Task AssignPlayer2ToSession(string sessionName, User user);
|
||||||
Task<User?> ReadUser(ClaimsPrincipal user);
|
Task<User?> ReadUser(ClaimsPrincipal user);
|
||||||
Task<bool> CreateUser(ClaimsPrincipal user);
|
Task<User?> CreateUser(ClaimsPrincipal user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameboardManager : IGameboardManager
|
public class GameboardManager : IGameboardManager
|
||||||
{
|
{
|
||||||
private readonly IGameboardRepository repository;
|
private readonly IGameboardRepository repository;
|
||||||
|
|
||||||
public GameboardManager(IGameboardRepository repository)
|
public GameboardManager(IGameboardRepository repository)
|
||||||
{
|
{
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> CreateUser(ClaimsPrincipal principal)
|
public async Task<User> CreateUser(ClaimsPrincipal principal)
|
||||||
{
|
{
|
||||||
var id = principal.UserId();
|
var id = principal.UserId();
|
||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
return Task.FromResult(false);
|
throw new InvalidOperationException("Cannot create user from given claims.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = principal.IsGuest()
|
var user = principal.IsGuest()
|
||||||
? User.CreateGuestUser(id)
|
? User.CreateGuestUser(id)
|
||||||
: User.CreateMsalUser(id);
|
: User.CreateMsalUser(id);
|
||||||
|
|
||||||
return repository.CreateUser(user);
|
await repository.CreateUser(user);
|
||||||
}
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<User?> ReadUser(ClaimsPrincipal principal)
|
public Task<User?> ReadUser(ClaimsPrincipal principal)
|
||||||
{
|
{
|
||||||
var userId = principal.UserId();
|
var userId = principal.UserId();
|
||||||
if (!string.IsNullOrEmpty(userId))
|
if (!string.IsNullOrEmpty(userId))
|
||||||
{
|
{
|
||||||
return repository.ReadUser(userId);
|
return repository.ReadUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult<User?>(null);
|
return Task.FromResult<User?>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<string> CreateJoinCode(string sessionName, string playerName)
|
public async Task<string> CreateJoinCode(string sessionName, string playerName)
|
||||||
{
|
{
|
||||||
//var session = await repository.GetGame(sessionName);
|
//var session = await repository.GetGame(sessionName);
|
||||||
//if (playerName == session?.Player1)
|
//if (playerName == session?.Player1)
|
||||||
//{
|
//{
|
||||||
// return await repository.PostJoinCode(sessionName, playerName);
|
// return await repository.PostJoinCode(sessionName, playerName);
|
||||||
//}
|
//}
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> AssignPlayer2ToSession(string sessionName, User user)
|
public async Task AssignPlayer2ToSession(string sessionName, User user)
|
||||||
{
|
{
|
||||||
var session = await repository.ReadSessionMetaData(sessionName);
|
var session = await repository.ReadSessionMetaData(sessionName);
|
||||||
if (session != null && !session.IsPrivate && session.Player2 == null)
|
if (session != null && !session.IsPrivate && session.Player2 == null)
|
||||||
{
|
{
|
||||||
session.SetPlayer2(user);
|
session.SetPlayer2(user.Id);
|
||||||
return await repository.UpdateSession(session);
|
await repository.UpdateSession(session);
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
100
Gameboard.ShogiUI.Sockets/Managers/ModelMapper.cs
Normal file
100
Gameboard.ShogiUI.Sockets/Managers/ModelMapper.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using DomainWhichPiece = Shogi.Domain.WhichPiece;
|
||||||
|
using DomainWhichPlayer = Shogi.Domain.WhichPlayer;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Managers
|
||||||
|
{
|
||||||
|
public class ModelMapper : IModelMapper
|
||||||
|
{
|
||||||
|
public WhichPlayer Map(DomainWhichPlayer whichPlayer)
|
||||||
|
{
|
||||||
|
return whichPlayer switch
|
||||||
|
{
|
||||||
|
DomainWhichPlayer.Player1 => WhichPlayer.Player1,
|
||||||
|
DomainWhichPlayer.Player2 => WhichPlayer.Player2,
|
||||||
|
_ => throw new ArgumentException("Unrecognized value for WhichPlayer", nameof(whichPlayer))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhichPlayer? Map(DomainWhichPlayer? whichPlayer)
|
||||||
|
{
|
||||||
|
return whichPlayer.HasValue
|
||||||
|
? Map(whichPlayer.Value)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhichPiece Map(DomainWhichPiece whichPiece)
|
||||||
|
{
|
||||||
|
return whichPiece switch
|
||||||
|
{
|
||||||
|
DomainWhichPiece.King => WhichPiece.King,
|
||||||
|
DomainWhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
|
||||||
|
DomainWhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
|
||||||
|
DomainWhichPiece.Bishop => WhichPiece.Bishop,
|
||||||
|
DomainWhichPiece.Rook => WhichPiece.Rook,
|
||||||
|
DomainWhichPiece.Knight => WhichPiece.Knight,
|
||||||
|
DomainWhichPiece.Lance => WhichPiece.Lance,
|
||||||
|
DomainWhichPiece.Pawn => WhichPiece.Pawn,
|
||||||
|
_ => throw new ArgumentException("Unrecognized value", nameof(whichPiece)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public DomainWhichPiece Map(WhichPiece whichPiece)
|
||||||
|
{
|
||||||
|
return whichPiece switch
|
||||||
|
{
|
||||||
|
WhichPiece.King => DomainWhichPiece.King,
|
||||||
|
WhichPiece.GoldGeneral => DomainWhichPiece.GoldGeneral,
|
||||||
|
WhichPiece.SilverGeneral => DomainWhichPiece.SilverGeneral,
|
||||||
|
WhichPiece.Bishop => DomainWhichPiece.Bishop,
|
||||||
|
WhichPiece.Rook => DomainWhichPiece.Rook,
|
||||||
|
WhichPiece.Knight => DomainWhichPiece.Knight,
|
||||||
|
WhichPiece.Lance => DomainWhichPiece.Lance,
|
||||||
|
WhichPiece.Pawn => DomainWhichPiece.Pawn,
|
||||||
|
_ => throw new ArgumentException("Unrecognized value", nameof(whichPiece)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionMetadata Map(Shogi.Domain.SessionMetadata session)
|
||||||
|
{
|
||||||
|
return new SessionMetadata
|
||||||
|
{
|
||||||
|
Name = session.Name,
|
||||||
|
Player1 = session.Player1,
|
||||||
|
Player2 = session.Player2,
|
||||||
|
IsPrivate = session.IsPrivate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece Map(Shogi.Domain.Pieces.Piece piece)
|
||||||
|
{
|
||||||
|
return new Piece { IsPromoted = piece.IsPromoted, Owner = Map(piece.Owner), WhichPiece = Map(piece.WhichPiece) };
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Piece?> Map(IDictionary<string, Shogi.Domain.Pieces.Piece?> boardState)
|
||||||
|
{
|
||||||
|
return boardState.ToDictionary(kvp => kvp.Key, kvp => MapNullable(kvp.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece? MapNullable(Shogi.Domain.Pieces.Piece? piece)
|
||||||
|
{
|
||||||
|
if (piece == null) return null;
|
||||||
|
return Map(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModelMapper
|
||||||
|
{
|
||||||
|
WhichPlayer Map(DomainWhichPlayer whichPlayer);
|
||||||
|
WhichPlayer? Map(DomainWhichPlayer? whichPlayer);
|
||||||
|
WhichPiece Map(DomainWhichPiece whichPiece);
|
||||||
|
DomainWhichPiece Map(WhichPiece value);
|
||||||
|
SessionMetadata Map(Shogi.Domain.SessionMetadata session);
|
||||||
|
Piece Map(Shogi.Domain.Pieces.Piece p);
|
||||||
|
Piece? MapNullable(Shogi.Domain.Pieces.Piece? p);
|
||||||
|
Dictionary<string, Piece?> Map(IDictionary<string, Shogi.Domain.Pieces.Piece?> boardState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,10 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
{
|
{
|
||||||
public interface ISocketConnectionManager
|
public interface ISocketConnectionManager
|
||||||
{
|
{
|
||||||
Task BroadcastToAll(IResponse response);
|
Task BroadcastToAll(ISocketResponse response);
|
||||||
void Subscribe(WebSocket socket, string playerName);
|
void Subscribe(WebSocket socket, string playerName);
|
||||||
void Unsubscribe(string playerName);
|
void Unsubscribe(string playerName);
|
||||||
Task BroadcastToPlayers(IResponse response, params string?[] playerNames);
|
Task BroadcastToPlayers(ISocketResponse response, params string?[] playerNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,7 +45,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
connections.TryRemove(playerName, out _);
|
connections.TryRemove(playerName, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BroadcastToPlayers(IResponse response, params string?[] playerNames)
|
public async Task BroadcastToPlayers(ISocketResponse 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)
|
||||||
@@ -59,7 +59,7 @@ namespace Gameboard.ShogiUI.Sockets.Managers
|
|||||||
}
|
}
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
public Task BroadcastToAll(IResponse response)
|
public Task BroadcastToAll(ISocketResponse response)
|
||||||
{
|
{
|
||||||
var message = JsonConvert.SerializeObject(response);
|
var message = JsonConvert.SerializeObject(response);
|
||||||
logger.LogInformation($"Broadcasting\n{0}", message);
|
logger.LogInformation($"Broadcasting\n{0}", message);
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
|
||||||
using Gameboard.ShogiUI.Sockets.Utilities;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("{From} - {To}")]
|
|
||||||
public class Move
|
|
||||||
{
|
|
||||||
public Vector2? From { get; } // TODO: Use string notation
|
|
||||||
public bool IsPromotion { get; }
|
|
||||||
public WhichPiece? PieceFromHand { get; }
|
|
||||||
public Vector2 To { get; }
|
|
||||||
|
|
||||||
public Move(Vector2 from, Vector2 to, bool isPromotion = false)
|
|
||||||
{
|
|
||||||
From = from;
|
|
||||||
To = to;
|
|
||||||
IsPromotion = isPromotion;
|
|
||||||
}
|
|
||||||
public Move(WhichPiece pieceFromHand, Vector2 to)
|
|
||||||
{
|
|
||||||
PieceFromHand = pieceFromHand;
|
|
||||||
To = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor to represent moving a piece on the Board to another position on the Board.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fromNotation">Position the piece is being moved from.</param>
|
|
||||||
/// <param name="toNotation">Position the piece is being moved to.</param>
|
|
||||||
/// <param name="isPromotion">If the moving piece should be promoted.</param>
|
|
||||||
public Move(string fromNotation, string toNotation, bool isPromotion = false)
|
|
||||||
{
|
|
||||||
From = NotationHelper.FromBoardNotation(fromNotation);
|
|
||||||
To = NotationHelper.FromBoardNotation(toNotation);
|
|
||||||
IsPromotion = isPromotion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor to represent moving a piece from the Hand to the Board.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pieceFromHand">The piece being moved from the Hand to the Board.</param>
|
|
||||||
/// <param name="toNotation">Position the piece is being moved to.</param>
|
|
||||||
/// <param name="isPromotion">If the moving piece should be promoted.</param>
|
|
||||||
public Move(WhichPiece pieceFromHand, string toNotation, bool isPromotion = false)
|
|
||||||
{
|
|
||||||
From = null;
|
|
||||||
PieceFromHand = pieceFromHand;
|
|
||||||
To = NotationHelper.FromBoardNotation(toNotation);
|
|
||||||
IsPromotion = isPromotion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceModels.Types.Move ToServiceModel() => new()
|
|
||||||
{
|
|
||||||
From = From.HasValue ? NotationHelper.ToBoardNotation(From.Value) : null,
|
|
||||||
IsPromotion = IsPromotion,
|
|
||||||
PieceFromCaptured = PieceFromHand.HasValue ? PieceFromHand : null,
|
|
||||||
To = NotationHelper.ToBoardNotation(To)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
namespace Gameboard.ShogiUI.Sockets.Models
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A representation of a Session without the board and game-rules.
|
|
||||||
/// </summary>
|
|
||||||
public class SessionMetadata
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public User Player1 { get; }
|
|
||||||
public User? Player2 { get; private set; }
|
|
||||||
public bool IsPrivate { get; }
|
|
||||||
|
|
||||||
public SessionMetadata(string name, bool isPrivate, User player1, User? player2 = null)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
IsPrivate = isPrivate;
|
|
||||||
Player1 = player1;
|
|
||||||
Player2 = player2;
|
|
||||||
}
|
|
||||||
public SessionMetadata(Session sessionModel)
|
|
||||||
{
|
|
||||||
Name = sessionModel.Name;
|
|
||||||
IsPrivate = sessionModel.IsPrivate;
|
|
||||||
Player1 = sessionModel.Player1;
|
|
||||||
Player2 = sessionModel.Player2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetPlayer2(User user)
|
|
||||||
{
|
|
||||||
Player2 = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSeated(User user) => user.Id == Player1.Id || user.Id == Player2?.Id;
|
|
||||||
|
|
||||||
public ServiceModels.Types.Session ToServiceModel() => new(Name, Player1.DisplayName, Player2?.DisplayName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Kestrel": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "/swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||||
|
}
|
||||||
|
},
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
"iisSettings": {
|
"iisSettings": {
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
@@ -7,23 +18,5 @@
|
|||||||
"applicationUrl": "http://localhost:50728/",
|
"applicationUrl": "http://localhost:50728/",
|
||||||
"sslPort": 44315
|
"sslPort": 44315
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchUrl": "weatherforecast",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Kestrel": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "/swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
},
|
|
||||||
"applicationUrl": "http://localhost:5100"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
using Shogi.Domain;
|
||||||
|
|
||||||
|
namespace Gameboard.ShogiUI.Sockets.Repositories.CouchModels
|
||||||
{
|
{
|
||||||
public class SessionDocument : CouchDocument
|
public class SessionDocument : CouchDocument
|
||||||
{
|
{
|
||||||
@@ -17,12 +19,12 @@
|
|||||||
Player2Id = string.Empty;
|
Player2Id = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionDocument(Models.SessionMetadata sessionMetaData)
|
public SessionDocument(SessionMetadata sessionMetaData)
|
||||||
: base(sessionMetaData.Name, WhichDocumentType.Session)
|
: base(sessionMetaData.Name, WhichDocumentType.Session)
|
||||||
{
|
{
|
||||||
Name = sessionMetaData.Name;
|
Name = sessionMetaData.Name;
|
||||||
Player1Id = sessionMetaData.Player1.Id;
|
Player1Id = sessionMetaData.Player1;
|
||||||
Player2Id = sessionMetaData.Player2?.Id;
|
Player2Id = sessionMetaData.Player2;
|
||||||
IsPrivate = sessionMetaData.IsPrivate;
|
IsPrivate = sessionMetaData.IsPrivate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
{
|
{
|
||||||
public interface IGameboardRepository
|
public interface IGameboardRepository
|
||||||
{
|
{
|
||||||
Task<bool> CreateBoardState(Session session);
|
Task CreateBoardState(Session session);
|
||||||
Task<bool> CreateSession(Models.SessionMetadata session);
|
Task CreateSession(SessionMetadata session);
|
||||||
Task<bool> CreateUser(Models.User user);
|
Task CreateUser(Models.User user);
|
||||||
Task<Collection<Models.SessionMetadata>> ReadSessionMetadatas();
|
Task<Collection<SessionMetadata>> ReadSessionMetadatas();
|
||||||
Task<Session?> ReadSession(string name);
|
Task<Session?> ReadSession(string name);
|
||||||
Task<bool> UpdateSession(Models.SessionMetadata session);
|
Task UpdateSession(SessionMetadata session);
|
||||||
Task<Models.SessionMetadata?> ReadSessionMetaData(string name);
|
Task<SessionMetadata?> ReadSessionMetaData(string name);
|
||||||
Task<Models.User?> ReadUser(string userName);
|
Task<Models.User?> ReadUser(string userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Collection<Models.SessionMetadata>> ReadSessionMetadatas()
|
public async Task<Collection<SessionMetadata>> ReadSessionMetadatas()
|
||||||
{
|
{
|
||||||
var queryParams = new QueryBuilder { { "include_docs", "true" } }.ToQueryString();
|
var queryParams = new QueryBuilder { { "include_docs", "true" } }.ToQueryString();
|
||||||
var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
|
var response = await client.GetAsync($"{View_SessionMetadata}{queryParams}");
|
||||||
@@ -57,7 +57,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
var groupedBySession = result.rows.GroupBy(row => row.id);
|
var groupedBySession = result.rows.GroupBy(row => row.id);
|
||||||
var sessions = new List<Models.SessionMetadata>(result.total_rows / 3);
|
var sessions = new List<SessionMetadata>(result.total_rows / 3);
|
||||||
foreach (var group in groupedBySession)
|
foreach (var group in groupedBySession)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -72,12 +72,12 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
if (session != null && player1Doc != null)
|
if (session != null && player1Doc != null)
|
||||||
{
|
{
|
||||||
var player2 = player2Doc == null ? null : new Models.User(player2Doc);
|
var player2 = player2Doc == null ? null : new Models.User(player2Doc);
|
||||||
sessions.Add(new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2));
|
sessions.Add(new SessionMetadata(session.Name, session.IsPrivate, player1Doc.Id, player2?.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Collection<Models.SessionMetadata>(sessions);
|
return new Collection<SessionMetadata>(sessions);
|
||||||
}
|
}
|
||||||
return new Collection<Models.SessionMetadata>(Array.Empty<Models.SessionMetadata>());
|
return new Collection<SessionMetadata>(Array.Empty<SessionMetadata>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Session?> ReadSession(string name)
|
public async Task<Session?> ReadSession(string name)
|
||||||
@@ -130,7 +130,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Models.SessionMetadata?> ReadSessionMetaData(string name)
|
public async Task<SessionMetadata?> ReadSessionMetaData(string name)
|
||||||
{
|
{
|
||||||
var queryParams = new QueryBuilder
|
var queryParams = new QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -159,7 +159,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
if (session != null && player1Doc != null)
|
if (session != null && player1Doc != null)
|
||||||
{
|
{
|
||||||
var player2 = player2Doc == null ? null : new Models.User(player2Doc);
|
var player2 = player2Doc == null ? null : new Models.User(player2Doc);
|
||||||
return new Models.SessionMetadata(session.Name, session.IsPrivate, new(player1Doc), player2);
|
return new SessionMetadata(session.Name, session.IsPrivate, player1Doc.Id, player2?.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -168,22 +168,15 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves a snapshot of board state and the most recent move.
|
/// Saves a snapshot of board state and the most recent move.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> CreateBoardState(Session session)
|
public async Task CreateBoardState(Session session)
|
||||||
{
|
{
|
||||||
Piece? MapPiece(Shogi.Domain.Pieces.Piece? piece)
|
|
||||||
{
|
|
||||||
return piece == null
|
|
||||||
? null
|
|
||||||
: new Piece { IsPromoted = piece.IsPromoted, Owner = piece.Owner, WhichPiece = piece.WhichPiece };
|
|
||||||
}
|
|
||||||
|
|
||||||
var boardStateDocument = new BoardStateDocument(session.Name, session);
|
var boardStateDocument = new BoardStateDocument(session.Name, session);
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
|
var content = new StringContent(JsonConvert.SerializeObject(boardStateDocument), Encoding.UTF8, ApplicationJson);
|
||||||
var response = await client.PostAsync(string.Empty, content);
|
var response = await client.PostAsync(string.Empty, content);
|
||||||
return response.IsSuccessStatusCode;
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CreateSession(Models.SessionMetadata session)
|
public async Task CreateSession(SessionMetadata session)
|
||||||
{
|
{
|
||||||
var sessionDocument = new SessionDocument(session);
|
var sessionDocument = new SessionDocument(session);
|
||||||
var sessionContent = new StringContent(JsonConvert.SerializeObject(sessionDocument), Encoding.UTF8, ApplicationJson);
|
var sessionContent = new StringContent(JsonConvert.SerializeObject(sessionDocument), Encoding.UTF8, ApplicationJson);
|
||||||
@@ -195,16 +188,15 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
if ((await postSessionDocumentTask).IsSuccessStatusCode)
|
if ((await postSessionDocumentTask).IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var response = await client.PostAsync(string.Empty, boardStateContent);
|
var response = await client.PostAsync(string.Empty, boardStateContent);
|
||||||
return response.IsSuccessStatusCode;
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateSession(Models.SessionMetadata session)
|
public async Task UpdateSession(SessionMetadata session)
|
||||||
{
|
{
|
||||||
// GET existing session to get revisionId.
|
// GET existing session to get revisionId.
|
||||||
var readResponse = await client.GetAsync(session.Name);
|
var readResponse = await client.GetAsync(session.Name);
|
||||||
if (!readResponse.IsSuccessStatusCode) return false;
|
readResponse.EnsureSuccessStatusCode();
|
||||||
var sessionDocument = JsonConvert.DeserializeObject<SessionDocument>(await readResponse.Content.ReadAsStringAsync());
|
var sessionDocument = JsonConvert.DeserializeObject<SessionDocument>(await readResponse.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
// PUT the document with the revisionId.
|
// PUT the document with the revisionId.
|
||||||
@@ -214,7 +206,7 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
};
|
};
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||||
var response = await client.PutAsync(couchModel.Id, content);
|
var response = await client.PutAsync(couchModel.Id, content);
|
||||||
return response.IsSuccessStatusCode;
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
//public async Task<bool> PutJoinPublicSession(PutJoinPublicSession request)
|
//public async Task<bool> PutJoinPublicSession(PutJoinPublicSession request)
|
||||||
//{
|
//{
|
||||||
@@ -285,12 +277,12 @@ namespace Gameboard.ShogiUI.Sockets.Repositories
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CreateUser(Models.User user)
|
public async Task CreateUser(Models.User user)
|
||||||
{
|
{
|
||||||
var couchModel = new UserDocument(user.Id, user.DisplayName, user.LoginPlatform);
|
var couchModel = new UserDocument(user.Id, user.DisplayName, user.LoginPlatform);
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
var content = new StringContent(JsonConvert.SerializeObject(couchModel), Encoding.UTF8, ApplicationJson);
|
||||||
var response = await client.PostAsync(string.Empty, content);
|
var response = await client.PostAsync(string.Empty, content);
|
||||||
return response.IsSuccessStatusCode;
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReadMoveHistory()
|
public void ReadMoveHistory()
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ namespace Gameboard.ShogiUI.Sockets
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
var newUser = Models.User.CreateMsalUser(nameClaim.Value);
|
var newUser = Models.User.CreateMsalUser(nameClaim.Value);
|
||||||
var success = await gameboardRepository.CreateUser(newUser);
|
await gameboardRepository.CreateUser(newUser);
|
||||||
if (success) user = newUser;
|
user = newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ namespace Gameboard.ShogiUI.Sockets
|
|||||||
var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/";
|
var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/";
|
||||||
c.BaseAddress = new Uri(baseUrl);
|
c.BaseAddress = new Uri(baseUrl);
|
||||||
});
|
});
|
||||||
|
services.AddTransient<IModelMapper, ModelMapper>();
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddControllers()
|
.AddControllers()
|
||||||
@@ -128,13 +129,17 @@ namespace Gameboard.ShogiUI.Sockets
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove default HttpClient logging.
|
services.AddHttpLogging(options =>
|
||||||
services.RemoveAll<IHttpMessageHandlerBuilderFilter>();
|
{
|
||||||
|
options.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.Request;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISocketService socketConnectionManager)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISocketService socketConnectionManager)
|
||||||
{
|
{
|
||||||
|
app.UseHttpLogging();
|
||||||
|
|
||||||
var origins = new[] {
|
var origins = new[] {
|
||||||
"http://localhost:3000", "https://localhost:3000",
|
"http://localhost:3000", "https://localhost:3000",
|
||||||
"http://127.0.0.1:3000", "https://127.0.0.1:3000",
|
"http://127.0.0.1:3000", "https://127.0.0.1:3000",
|
||||||
@@ -147,25 +152,25 @@ namespace Gameboard.ShogiUI.Sockets
|
|||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
var client = PublicClientApplicationBuilder
|
//var client = PublicClientApplicationBuilder
|
||||||
.Create(Configuration["AzureAd:ClientId"])
|
// .Create(Configuration["AzureAd:ClientId"])
|
||||||
.WithLogging(
|
// .WithLogging(
|
||||||
(level, message, pii) =>
|
// (level, message, pii) =>
|
||||||
{
|
// {
|
||||||
Console.WriteLine(message);
|
// Console.WriteLine(message);
|
||||||
},
|
// },
|
||||||
LogLevel.Verbose,
|
// LogLevel.Verbose,
|
||||||
true,
|
// true,
|
||||||
true
|
// true
|
||||||
)
|
// )
|
||||||
.Build();
|
// .Build();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
app
|
app
|
||||||
.UseRequestResponseLogging()
|
//.UseRequestResponseLogging()
|
||||||
.UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials())
|
.UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials())
|
||||||
.UseRouting()
|
.UseRouting()
|
||||||
.UseAuthentication()
|
.UseAuthentication()
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
|
||||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0-preview-20211130-02" />
|
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.2-pre.12" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets.ServiceModels\Gameboard.ShogiUI.Sockets.ServiceModels.csproj" />
|
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets\Gameboard.ShogiUI.Sockets.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using PathFinding;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class PathFinder2DShould
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void Maths()
|
|
||||||
{
|
|
||||||
var result = PathFinder2D<IPlanarElement>.IsPathable(
|
|
||||||
new Vector2(2, 2),
|
|
||||||
new Vector2(7, 7),
|
|
||||||
new Vector2(1, 1)
|
|
||||||
);
|
|
||||||
result.Should().BeTrue();
|
|
||||||
|
|
||||||
result = PathFinder2D<IPlanarElement>.IsPathable(
|
|
||||||
new Vector2(2, 2),
|
|
||||||
new Vector2(7, 7),
|
|
||||||
new Vector2(0, 0)
|
|
||||||
);
|
|
||||||
result.Should().BeFalse();
|
|
||||||
|
|
||||||
result = PathFinder2D<IPlanarElement>.IsPathable(
|
|
||||||
new Vector2(2, 2),
|
|
||||||
new Vector2(7, 7),
|
|
||||||
new Vector2(-1, 1)
|
|
||||||
);
|
|
||||||
result.Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using AutoFixture;
|
|
||||||
using FluentAssertions;
|
|
||||||
using FluentAssertions.Execution;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class PlanarCollectionShould
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public void Index()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var collection = new TestPlanarCollection();
|
|
||||||
var expected1 = new SimpleElement(1);
|
|
||||||
var expected2 = new SimpleElement(2);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
collection[0, 0] = expected1;
|
|
||||||
collection[2, 1] = expected2;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
collection[0, 0].Should().Be(expected1);
|
|
||||||
collection[2, 1].Should().Be(expected2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Iterate()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var planarCollection = new TestPlanarCollection();
|
|
||||||
planarCollection[0, 0] = new SimpleElement(1);
|
|
||||||
planarCollection[0, 1] = new SimpleElement(2);
|
|
||||||
planarCollection[0, 2] = new SimpleElement(3);
|
|
||||||
planarCollection[1, 0] = new SimpleElement(4);
|
|
||||||
planarCollection[1, 1] = new SimpleElement(5);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = new List<SimpleElement>();
|
|
||||||
foreach (var elem in planarCollection)
|
|
||||||
actual.Add(elem);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
using (new AssertionScope())
|
|
||||||
{
|
|
||||||
actual[0].Number.Should().Be(1);
|
|
||||||
actual[1].Number.Should().Be(2);
|
|
||||||
actual[2].Number.Should().Be(3);
|
|
||||||
actual[3].Number.Should().Be(4);
|
|
||||||
actual[4].Number.Should().Be(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using PathFinding;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.UnitTests.PathFinding
|
|
||||||
{
|
|
||||||
public class SimpleElement : IPlanarElement
|
|
||||||
{
|
|
||||||
public int Number { get; }
|
|
||||||
public MoveSet MoveSet => null;
|
|
||||||
public bool IsUpsideDown => false;
|
|
||||||
|
|
||||||
public SimpleElement(int number)
|
|
||||||
{
|
|
||||||
Number = number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestPlanarCollection : IPlanarCollection<SimpleElement>
|
|
||||||
{
|
|
||||||
private readonly SimpleElement[,] array;
|
|
||||||
public TestPlanarCollection()
|
|
||||||
{
|
|
||||||
array = new SimpleElement[3, 3];
|
|
||||||
}
|
|
||||||
public SimpleElement this[int x, int y]
|
|
||||||
{
|
|
||||||
get => array[x, y];
|
|
||||||
set => array[x, y] = value;
|
|
||||||
}
|
|
||||||
public SimpleElement this[Vector2 vector]
|
|
||||||
{
|
|
||||||
get => this[(int)vector.X, (int)vector.Y];
|
|
||||||
set => this[(int)vector.X, (int)vector.Y] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<SimpleElement> GetEnumerator()
|
|
||||||
{
|
|
||||||
foreach (var e in array)
|
|
||||||
yield return e;
|
|
||||||
}
|
|
||||||
//IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
//{
|
|
||||||
// return array.GetEnumerator();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Gameboard.ShogiUI.Sockets.Models;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using WhichPerspective = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPlayer;
|
|
||||||
using WhichPiece = Gameboard.ShogiUI.Sockets.ServiceModels.Types.WhichPiece;
|
|
||||||
namespace Gameboard.ShogiUI.UnitTests.Rules
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class ShogiBoardShould
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.xUnitTests
|
|
||||||
{
|
|
||||||
public class GameShould
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void DiscardNullPLayers()
|
|
||||||
{
|
|
||||||
var game = new Session("Test", "P1", null);
|
|
||||||
|
|
||||||
game.Players.Count.Should().Be(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Gameboard.ShogiUI.Sockets.Utilities;
|
|
||||||
using System.Numerics;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Gameboard.ShogiUI.xUnitTests
|
|
||||||
{
|
|
||||||
public class NotationHelperShould
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void TranslateVectorsToNotation()
|
|
||||||
{
|
|
||||||
NotationHelper.ToBoardNotation(2, 2).Should().Be("C3");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TranslateNotationToVectors()
|
|
||||||
{
|
|
||||||
NotationHelper.FromBoardNotation("C3").Should().Be(new Vector2(2, 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"methodDisplay": "method"
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public static class Direction
|
|
||||||
{
|
|
||||||
public static readonly Vector2 Up = new(0, 1);
|
|
||||||
public static readonly Vector2 Down = new(0, -1);
|
|
||||||
public static readonly Vector2 Left = new(-1, 0);
|
|
||||||
public static readonly Vector2 Right = new(1, 0);
|
|
||||||
public static readonly Vector2 UpLeft = new(-1, 1);
|
|
||||||
public static readonly Vector2 UpRight = new(1, 1);
|
|
||||||
public static readonly Vector2 DownLeft = new(-1, -1);
|
|
||||||
public static readonly Vector2 DownRight = new(1, -1);
|
|
||||||
public static readonly Vector2 KnightLeft = new(-1, 2);
|
|
||||||
public static readonly Vector2 KnightRight = new(1, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public enum Distance
|
|
||||||
{
|
|
||||||
OneStep,
|
|
||||||
MultiStep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public interface IPlanarCollection<T> where T : IPlanarElement
|
|
||||||
{
|
|
||||||
T? this[Vector2 vector] { get; set; }
|
|
||||||
T? this[int x, int y] { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public interface IPlanarElement
|
|
||||||
{
|
|
||||||
MoveSet MoveSet { get; }
|
|
||||||
bool IsUpsideDown { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("{Direction} - {Distance}")]
|
|
||||||
public class Move
|
|
||||||
{
|
|
||||||
public Vector2 Direction { get; }
|
|
||||||
public Distance Distance { get; }
|
|
||||||
public Move(Vector2 direction, Distance distance = Distance.OneStep)
|
|
||||||
{
|
|
||||||
Direction = direction;
|
|
||||||
Distance = distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public class MoveSet
|
|
||||||
{
|
|
||||||
private readonly IPlanarElement element;
|
|
||||||
private readonly ICollection<Move> moves;
|
|
||||||
private readonly ICollection<Move> upsidedownMoves;
|
|
||||||
|
|
||||||
public MoveSet(IPlanarElement element, ICollection<Move> moves)
|
|
||||||
{
|
|
||||||
this.element = element;
|
|
||||||
this.moves = moves;
|
|
||||||
upsidedownMoves = moves.Select(_ => new Move(Vector2.Negate(_.Direction), _.Distance)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<Move> GetMoves() => element.IsUpsideDown ? upsidedownMoves : moves;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PathFinding
|
|
||||||
{
|
|
||||||
public class PathFinder2D<T> where T : IPlanarElement
|
|
||||||
{
|
|
||||||
/// <param name="element">Guaranteed to be non-null.</param>
|
|
||||||
/// <param name="position"></param>
|
|
||||||
public delegate void Callback(T collider, Vector2 position);
|
|
||||||
|
|
||||||
private readonly IPlanarCollection<T> collection;
|
|
||||||
private readonly int width;
|
|
||||||
private readonly int height;
|
|
||||||
|
|
||||||
/// <param name="width">Horizontal size, in steps, of the pathable plane.</param>
|
|
||||||
/// <param name="height">Vertical size, in steps, of the pathable plane.</param>
|
|
||||||
public PathFinder2D(IPlanarCollection<T> collection, int width, int height)
|
|
||||||
{
|
|
||||||
this.collection = collection;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigate the collection such that each "step" is always towards the destination, respecting the Paths available to the element at origin.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="element">The pathing element.</param>
|
|
||||||
/// <param name="origin">The starting location.</param>
|
|
||||||
/// <param name="destination">The destination.</param>
|
|
||||||
/// <param name="callback">Do cool stuff here.</param>
|
|
||||||
/// <returns>True if the element reached the destination.</returns>
|
|
||||||
public bool PathTo(Vector2 origin, Vector2 destination, Callback? callback = null)
|
|
||||||
{
|
|
||||||
if (destination.X > width - 1 || destination.Y > height - 1 || destination.X < 0 || destination.Y < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var element = collection[origin];
|
|
||||||
if (element == null) return false;
|
|
||||||
|
|
||||||
var path = FindDirectionTowardsDestination(element.MoveSet.GetMoves(), origin, destination);
|
|
||||||
if (!IsPathable(origin, destination, path.Direction))
|
|
||||||
{
|
|
||||||
// Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shouldPath = true;
|
|
||||||
var next = origin;
|
|
||||||
while (shouldPath && next != destination)
|
|
||||||
{
|
|
||||||
next = Vector2.Add(next, path.Direction);
|
|
||||||
var collider = collection[next];
|
|
||||||
if (collider != null)
|
|
||||||
{
|
|
||||||
callback?.Invoke(collider, next);
|
|
||||||
shouldPath = false;
|
|
||||||
}
|
|
||||||
else if (path.Distance == Distance.OneStep)
|
|
||||||
{
|
|
||||||
shouldPath = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next == destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PathEvery(Vector2 from, Callback callback)
|
|
||||||
{
|
|
||||||
var element = collection[from];
|
|
||||||
if (element == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (var path in element.MoveSet.GetMoves())
|
|
||||||
{
|
|
||||||
var shouldPath = true;
|
|
||||||
var next = Vector2.Add(from, path.Direction); ;
|
|
||||||
while (shouldPath && next.X < width && next.Y < height && next.X >= 0 && next.Y >= 0)
|
|
||||||
{
|
|
||||||
var collider = collection[(int)next.Y, (int)next.X];
|
|
||||||
if (collider != null)
|
|
||||||
{
|
|
||||||
callback(collider, next);
|
|
||||||
shouldPath = false;
|
|
||||||
}
|
|
||||||
if (path.Distance == Distance.OneStep)
|
|
||||||
{
|
|
||||||
shouldPath = false;
|
|
||||||
}
|
|
||||||
next = Vector2.Add(next, path.Direction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Path the line from origin to destination, ignoring any Paths defined by the element at origin.
|
|
||||||
/// </summary>
|
|
||||||
public void LinePathTo(Vector2 origin, Vector2 direction, Callback callback)
|
|
||||||
{
|
|
||||||
direction = Vector2.Normalize(direction);
|
|
||||||
|
|
||||||
var next = Vector2.Add(origin, direction);
|
|
||||||
while (next.X >= 0 && next.X < width && next.Y >= 0 && next.Y < height)
|
|
||||||
{
|
|
||||||
var element = collection[next];
|
|
||||||
if (element != null) callback(element, next);
|
|
||||||
next = Vector2.Add(next, direction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Move FindDirectionTowardsDestination(ICollection<Move> paths, Vector2 origin, Vector2 destination) =>
|
|
||||||
paths.Aggregate((a, b) =>
|
|
||||||
{
|
|
||||||
var distanceA = Vector2.Distance(destination, Vector2.Add(origin, a.Direction));
|
|
||||||
var distanceB = Vector2.Distance(destination, Vector2.Add(origin, b.Direction));
|
|
||||||
return distanceA < distanceB ? a : b;
|
|
||||||
});
|
|
||||||
|
|
||||||
public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction)
|
|
||||||
{
|
|
||||||
var next = Vector2.Add(origin, direction);
|
|
||||||
if (Vector2.Distance(next, destination) >= Vector2.Distance(origin, destination)) return false;
|
|
||||||
|
|
||||||
var slope = (destination.Y - origin.Y) / (destination.X - origin.X);
|
|
||||||
if (float.IsInfinity(slope))
|
|
||||||
{
|
|
||||||
return next.X == destination.X;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// b = -mx + y
|
|
||||||
var yIntercept = -slope * origin.X + origin.Y;
|
|
||||||
// y = mx + b
|
|
||||||
return next.Y == slope * next.X + yIntercept;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets.ServiceModels\Gameboard.ShogiUI.Sockets.ServiceModels.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
16
Shogi.AcceptanceTests/AcceptanceTests.cs
Normal file
16
Shogi.AcceptanceTests/AcceptanceTests.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Shogi.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class AcceptanceTests
|
||||||
|
{
|
||||||
|
public AcceptanceTests()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateAndReadSession()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,33 +2,23 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0-preview-20211130-02" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.2-pre.12" />
|
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Gameboard.ShogiUI.Sockets\Gameboard.ShogiUI.Sockets.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="xunit.runner.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
1
Shogi.AcceptanceTests/Usings.cs
Normal file
1
Shogi.AcceptanceTests/Usings.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using Xunit;
|
||||||
@@ -22,5 +22,7 @@
|
|||||||
{
|
{
|
||||||
Player2 = user;
|
Player2 = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSeated(string playerName) => playerName == Player1 || playerName == Player2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user