Saving snapshots

This commit is contained in:
2024-11-09 13:35:39 -06:00
parent 0a62eb7582
commit 13e79eb490
14 changed files with 176 additions and 16 deletions

View File

@@ -92,6 +92,7 @@ public class ShogiApplication(
if (moveResult.IsSuccess) if (moveResult.IsSuccess)
{ {
await sessionRepository.CreateMove(sessionId, command); await sessionRepository.CreateMove(sessionId, command);
await sessionRepository.CreateState(session);
await gameHubContext.Emit_PieceMoved(sessionId); await gameHubContext.Emit_PieceMoved(sessionId);
return new NoContentResult(); return new NoContentResult();
} }

View File

@@ -0,0 +1,34 @@
namespace Shogi.Api.Repositories.Dto.SessionState;
public class Piece
{
public bool IsPromoted { get; set; }
public WhichPiece WhichPiece { get; set; }
public WhichPlayer Owner { get; set; }
public Piece() { }
public Piece(Domain.ValueObjects.Piece piece)
{
IsPromoted = piece.IsPromoted;
WhichPiece = piece.WhichPiece switch
{
Domain.ValueObjects.WhichPiece.Bishop => WhichPiece.Bishop,
Domain.ValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
Domain.ValueObjects.WhichPiece.King => WhichPiece.King,
Domain.ValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
Domain.ValueObjects.WhichPiece.Rook => WhichPiece.Rook,
Domain.ValueObjects.WhichPiece.Knight => WhichPiece.Knight,
Domain.ValueObjects.WhichPiece.Lance => WhichPiece.Lance,
Domain.ValueObjects.WhichPiece.Pawn => WhichPiece.Pawn,
_ => throw new NotImplementedException()
};
Owner = piece.Owner switch
{
Domain.ValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1,
Domain.ValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2,
_ => throw new NotImplementedException()
};
}
}

View File

@@ -0,0 +1,69 @@
namespace Shogi.Api.Repositories.Dto.SessionState;
public class SessionStateDocument
{
public long Id { get; set; }
public Dictionary<string, Piece?> Board { get; set; }
public WhichPiece[] Player1Hand { get; set; }
public WhichPiece[] Player2Hand { get; set; }
public WhichPlayer? PlayerInCheck { get; set; }
public WhichPlayer WhoseTurn { get; set; }
public bool IsGameOver { get; set; }
public string DocumentVersion { get; set; } = "1";
public SessionStateDocument() { }
public SessionStateDocument(Domain.ValueObjects.BoardState boardState)
{
this.Board = boardState.State.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value == null ? null : new Piece(kvp.Value));
this.Player1Hand = boardState.Player1Hand
.Select(piece => Map(piece.WhichPiece))
.ToArray();
this.Player2Hand = boardState.Player2Hand
.Select(piece => Map(piece.WhichPiece))
.ToArray();
this.PlayerInCheck = boardState.InCheck.HasValue
? Map(boardState.InCheck.Value)
: null;
this.IsGameOver = boardState.IsCheckmate;
}
static WhichPiece Map(Domain.ValueObjects.WhichPiece whichPiece)
{
return whichPiece switch
{
Domain.ValueObjects.WhichPiece.Bishop => WhichPiece.Bishop,
Domain.ValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
Domain.ValueObjects.WhichPiece.King => WhichPiece.King,
Domain.ValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
Domain.ValueObjects.WhichPiece.Rook => WhichPiece.Rook,
Domain.ValueObjects.WhichPiece.Knight => WhichPiece.Knight,
Domain.ValueObjects.WhichPiece.Lance => WhichPiece.Lance,
Domain.ValueObjects.WhichPiece.Pawn => WhichPiece.Pawn,
_ => throw new NotImplementedException()
};
}
static WhichPlayer Map(Domain.ValueObjects.WhichPlayer whichPlayer)
{
return whichPlayer switch
{
Domain.ValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1,
Domain.ValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2,
_ => throw new NotImplementedException()
};
}
}

View File

@@ -0,0 +1,13 @@
namespace Shogi.Api.Repositories.Dto.SessionState;
public enum WhichPiece
{
King,
GoldGeneral,
SilverGeneral,
Bishop,
Rook,
Knight,
Lance,
Pawn
}

View File

@@ -0,0 +1,7 @@
namespace Shogi.Api.Repositories.Dto.SessionState;
public enum WhichPlayer
{
Player1,
Player2
}

View File

@@ -1,9 +1,11 @@
using Dapper; using Dapper;
using Shogi.Api.Repositories.Dto; using Shogi.Api.Repositories.Dto;
using Shogi.Api.Repositories.Dto.SessionState;
using Shogi.Contracts.Api.Commands; using Shogi.Contracts.Api.Commands;
using Shogi.Domain.Aggregates; using Shogi.Domain.Aggregates;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Text.Json;
namespace Shogi.Api.Repositories; namespace Shogi.Api.Repositories;
@@ -81,4 +83,19 @@ public class SessionRepository(IConfiguration configuration)
}, },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
public async Task CreateState(Session session)
{
var document = new SessionStateDocument(session.Board.BoardState);
using var connection = new SqlConnection(this.connectionString);
await connection.ExecuteAsync(
"session.CreateState",
new
{
SessionId = session.Id.ToString(),
Document = JsonSerializer.Serialize(document)
},
commandType: CommandType.StoredProcedure);
}
} }

View File

@@ -1,9 +1,8 @@
namespace Shogi.Contracts.Types namespace Shogi.Contracts.Types;
{
public class Piece public class Piece
{ {
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 WhichPlayer Owner { get; set; }
} }
}

View File

@@ -0,0 +1,6 @@
CREATE PROCEDURE [session].[CreateState]
@SessionId [session].[SessionSurrogateKey],
@Document NVARCHAR(MAX)
AS
INSERT INTO [session].[State] (SessionId, Document) VALUES (@SessionId, @Document);

View File

@@ -0,0 +1,7 @@
CREATE PROCEDURE [session].[ReadStatesBySession]
@SessionId [session].[SessionSurrogateKey]
AS
SELECT Id, SessionId, Document
FROM [session].[State]
WHERE Id = @SessionId;

View File

@@ -0,0 +1,9 @@
CREATE TABLE [session].[State]
(
[Id] BIGINT NOT NULL PRIMARY KEY IDENTITY,
[SessionId] [session].[SessionSurrogateKey] NOT NULL,
[Document] NVARCHAR(MAX) NOT NULL,
CONSTRAINT [FK_State_ToSession] FOREIGN KEY (SessionId) REFERENCES [session].[Session](Id),
CONSTRAINT [StateDocument must be JSON] CHECK(ISJSON(Document)=1)
)

View File

@@ -1,2 +1,3 @@
CREATE TYPE [session].[SessionSurrogateKey] -- C# Guid
CREATE TYPE [session].[SessionSurrogateKey]
FROM CHAR(36) NOT NULL FROM CHAR(36) NOT NULL

View File

@@ -79,6 +79,9 @@
<Build Include="Session\Stored Procedures\ReadSessionsMetadata.sql" /> <Build Include="Session\Stored Procedures\ReadSessionsMetadata.sql" />
<Build Include="AspNetUsersId.sql" /> <Build Include="AspNetUsersId.sql" />
<Build Include="Session\Functions\MaxNewSessionsPerUser.sql" /> <Build Include="Session\Functions\MaxNewSessionsPerUser.sql" />
<Build Include="Session\Tables\State.sql" />
<Build Include="Session\Stored Procedures\CreateState.sql" />
<Build Include="Session\Stored Procedures\ReadStatesBySession.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PostDeploy Include="Post Deployment\Script.PostDeployment.sql" /> <PostDeploy Include="Post Deployment\Script.PostDeployment.sql" />

View File

@@ -1,8 +1,7 @@
namespace Shogi.Domain.ValueObjects namespace Shogi.Domain.ValueObjects;
{
public enum WhichPlayer public enum WhichPlayer
{ {
Player1, Player1,
Player2 Player2
} }
}

View File

@@ -4,13 +4,8 @@ using System.Linq;
namespace UnitTests namespace UnitTests
{ {
public class ShogiShould public class ShogiShould(ITestOutputHelper console)
{ {
private readonly ITestOutputHelper console;
public ShogiShould(ITestOutputHelper console)
{
this.console = console;
}
[Fact] [Fact]
public void MoveAPieceToAnEmptyPosition() public void MoveAPieceToAnEmptyPosition()