Gitea/workflows #1

Closed
jheaps wants to merge 47 commits from gitea/workflows into AddProject
4 changed files with 74 additions and 37 deletions
Showing only changes of commit e8a3bfe432 - Show all commits
+11 -29
View File
@@ -176,33 +176,7 @@ public class ChessController(
if (!_games.TryGetValue(gameId, out var gameState)) if (!_games.TryGetValue(gameId, out var gameState))
return NotFound("Game not found"); return NotFound("Game not found");
var response = new return Ok(gameState.ToDto());
{
gameState.GameId,
CurrentPlayer = gameState.CurrentPlayer.ToString(),
gameState.IsCheck,
gameState.IsCheckmate,
gameState.IsStalemate,
gameState.IsThreefoldRepetition,
EnPassantTarget = gameState.EnPassantTarget?.ToString() ?? null,
gameState.WhiteCanCastleKingside,
gameState.WhiteCanCastleQueenside,
gameState.BlackCanCastleKingside,
gameState.BlackCanCastleQueenside,
Pieces = gameState.Pieces
.Where(p => p.Position.Row >= 0)
.Select(p => new {
p.Id,
p.Type,
p.Color,
p.Position.Row,
p.Position.Col,
p.HasMoved
}),
gameState.MoveHistory
};
return Ok(response);
} }
/// <summary> /// <summary>
@@ -210,7 +184,7 @@ public class ChessController(
/// The test passes a JSON body with a MoveDto. /// The test passes a JSON body with a MoveDto.
/// </summary> /// </summary>
[HttpPost("move")] [HttpPost("move")]
public ActionResult MakeMove([FromBody] MoveDto moveDto) public async Task<ActionResult> MakeMove([FromBody] MoveDto moveDto)
{ {
if (!_games.TryGetValue(moveDto.GameId, out var gameState)) if (!_games.TryGetValue(moveDto.GameId, out var gameState))
return NotFound("Game not found"); return NotFound("Game not found");
@@ -245,10 +219,18 @@ public class ChessController(
else else
ScheduleRemoveGame(gameState.GameId, _multiplayerGameTimeout); ScheduleRemoveGame(gameState.GameId, _multiplayerGameTimeout);
var state = gameState.ToDto();
// Broadcast the move (with the full resulting state) to everyone watching this
// game. The mover also receives this echo but drops it via the version guard,
// since it already rendered the same state from this response.
await chessHub.Clients.Group(gameState.GameId.ToString())
.SendAsync("ReceiveMoveUpdate", gameState.GameId.ToString(), moveDto, result, state);
if (!isGameOver && gameState.IsVsComputer && gameState.Computer is not null) if (!isGameOver && gameState.IsVsComputer && gameState.Computer is not null)
queue.Queue(() => orchestrator.PlayAsync(gameState, gameState.Computer!)); queue.Queue(() => orchestrator.PlayAsync(gameState, gameState.Computer!));
return Ok(result); return Ok(new { result, state });
} }
/// <summary> /// <summary>
+1 -7
View File
@@ -1,5 +1,4 @@
using JoshHeaps.Net.Models; using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR;
namespace JoshHeaps.Net.Hubs; namespace JoshHeaps.Net.Hubs;
@@ -11,11 +10,6 @@ public class ChessHub : Hub
await Groups.AddToGroupAsync(Context.ConnectionId, gameId); await Groups.AddToGroupAsync(Context.ConnectionId, gameId);
} }
public async Task MoveMade(string gameId, MoveDto moveDto, MoveResultDto moveResult)
{
await Clients.OthersInGroup(gameId).SendAsync("ReceiveMoveUpdate", gameId, moveDto, moveResult);
}
public async Task LeaveWebsocketGroup(string gameId) public async Task LeaveWebsocketGroup(string gameId)
{ {
Console.WriteLine($"❌ Leaving group {gameId}"); Console.WriteLine($"❌ Leaving group {gameId}");
+61
View File
@@ -0,0 +1,61 @@
using System.Linq;
namespace JoshHeaps.Net.Models;
/// <summary>
/// A single piece as sent to the client. Captured pieces keep their original
/// type and color; their position is meaningless and omitted.
/// </summary>
public record ChessPieceDto(string Id, PieceType Type, PieceColor Color, int Row, int Col, bool HasMoved);
/// <summary>
/// The full board state pushed to clients. The same shape is returned by the
/// state endpoint, the move endpoint, and every SignalR move broadcast, so the
/// client always renders from one authoritative payload instead of re-fetching.
/// </summary>
public record GameStateDto(
Guid GameId,
string CurrentPlayer,
bool IsCheck,
bool IsCheckmate,
bool IsStalemate,
bool IsThreefoldRepetition,
string? EnPassantTarget,
bool WhiteCanCastleKingside,
bool WhiteCanCastleQueenside,
bool BlackCanCastleKingside,
bool BlackCanCastleQueenside,
IReadOnlyList<ChessPieceDto> Pieces,
IReadOnlyList<ChessPieceDto> CapturedPieces,
IReadOnlyList<string> MoveHistory,
// Moves in standard algebraic notation, for the move-list panel.
IReadOnlyList<string> SanHistory,
// Monotonic ply counter the client uses to drop stale or echoed updates.
int Version);
public static class GameStateMapper
{
public static GameStateDto ToDto(this GameState gameState) => new(
gameState.GameId,
gameState.CurrentPlayer.ToString(),
gameState.IsCheck,
gameState.IsCheckmate,
gameState.IsStalemate,
gameState.IsThreefoldRepetition,
gameState.EnPassantTarget?.ToString(),
gameState.WhiteCanCastleKingside,
gameState.WhiteCanCastleQueenside,
gameState.BlackCanCastleKingside,
gameState.BlackCanCastleQueenside,
gameState.Pieces
.Where(p => p.Position.Row >= 0)
.Select(p => new ChessPieceDto(p.Id, p.Type, p.Color, p.Position.Row, p.Position.Col, p.HasMoved))
.ToList(),
gameState.Pieces
.Where(p => p.Position.Row < 0)
.Select(p => new ChessPieceDto(p.Id, p.Type, p.Color, p.Position.Row, p.Position.Col, p.HasMoved))
.ToList(),
gameState.MoveHistory,
gameState.SanHistory,
gameState.MoveHistory.Count);
}
@@ -26,7 +26,7 @@ public sealed class ComputerMoveOrchestrator(
var result = chessService.MakeMove(state, move); var result = chessService.MakeMove(state, move);
await chessHub.Clients.Group(state.GameId.ToString()) await chessHub.Clients.Group(state.GameId.ToString())
.SendAsync("ReceiveMoveUpdate", state.GameId.ToString(), move, result); .SendAsync("ReceiveMoveUpdate", state.GameId.ToString(), move, result, state.ToDto());
return (move, result); return (move, result);
} }