From 3dcd652850256ca47110eddeb84cbc1b44e62efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=97=8B?= <2574356905@qq.com> Date: Fri, 15 Aug 2025 08:53:53 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=EF=BC=8C=E5=90=84=E4=B8=AA=E5=B1=82=E6=AC=A1=E7=9A=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 81 ++++++++----------- .../Commands/CreatePlayerCommand.cs | 3 + .../CreatePlayerCommandHandler.cs | 29 +++++++ .../QueryHandlers/GetPlayerQueryHandler.cs | 21 +++++ .../Queries/GetPlayerQuery.cs | 5 ++ .../src/TerritoryGame.Domain/Entities/Game.cs | 10 +++ .../TerritoryGame.Domain/Entities/GameRoom.cs | 35 ++++++++ .../Entities/GameSession.cs | 54 +++++++++++++ .../Entities/PaintAction.cs | 54 +++++++++++++ .../TerritoryGame.Domain/Entities/Player.cs | 50 ++++++++++++ .../Repositories/IGameRepository.cs | 10 +++ .../Repositories/IPlayerRepository.cs | 11 +++ .../Repositories/GameRepository.cs | 33 ++++++++ .../Repositories/PlayerRepository.cs | 39 +++++++++ .../Persistence/TerritoryGameDbContext.cs | 19 +++++ 15 files changed, 405 insertions(+), 49 deletions(-) create mode 100644 backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs create mode 100644 backend/src/TerritoryGame.Domain/Entities/Game.cs create mode 100644 backend/src/TerritoryGame.Domain/Entities/GameRoom.cs create mode 100644 backend/src/TerritoryGame.Domain/Entities/GameSession.cs create mode 100644 backend/src/TerritoryGame.Domain/Entities/PaintAction.cs create mode 100644 backend/src/TerritoryGame.Domain/Entities/Player.cs create mode 100644 backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs create mode 100644 backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs create mode 100644 backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs create mode 100644 backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs create mode 100644 backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs diff --git a/README.md b/README.md index 979f6c9..461c7cc 100644 --- a/README.md +++ b/README.md @@ -66,72 +66,55 @@ territory-paint-game/ │ └── vite.config.js ├── backend/ # .NET 8后端项目 (DDD架构) │ ├── src/ -│ │ ├── TerritoryGame.API/ # 接口层 -│ │ │ ├── Controllers/ # RESTful API控制器 -│ │ │ │ ├── CommandController.cs # 命令处理 (CQRS) -│ │ │ │ └── QueryController.cs # 查询处理 (CQRS) -│ │ │ ├── Hubs/ # SignalR实时通信 +│ │ ├── TerritoryGame.API/ # 接口层 (ASP.NET Core Web API) +│ │ │ ├── Controllers/ # API控制器 +│ │ │ │ ├── CommandController.cs +│ │ │ │ └── QueryController.cs +│ │ │ ├── Hubs/ # SignalR Hubs │ │ │ │ └── GameHub.cs -│ │ │ ├── Program.cs # 启动配置 +│ │ │ ├── Program.cs │ │ │ └── appsettings.json -│ │ ├── TerritoryGame.Domain/ # 领域层 (核心业务逻辑) -│ │ │ ├── Entities/ # 实体 -│ │ │ │ ├── Game.cs # 游戏聚合根 -│ │ │ │ ├── Player.cs # 玩家实体 -│ │ │ │ └── GameRoom.cs # 房间实体 -│ │ │ ├── ValueObjects/ # 值对象 -│ │ │ │ ├── Position.cs # 位置坐标 -│ │ │ │ ├── Color.cs # 玩家颜色 -│ │ │ │ └── GameScore.cs # 游戏分数 -│ │ │ ├── Events/ # 领域事件 -│ │ │ │ ├── GameStartedEvent.cs -│ │ │ │ ├── AreaCapturedEvent.cs -│ │ │ │ └── GameEndedEvent.cs -│ │ │ ├── Services/ # 领域服务 -│ │ │ │ ├── IGameLogicService.cs -│ │ │ │ └── GameLogicService.cs # 游戏规则引擎 -│ │ │ └── Repositories/ # 仓储接口 -│ │ │ ├── IGameRepository.cs -│ │ │ └── IPlayerRepository.cs -│ │ ├── TerritoryGame.Application/ # 应用层 (用例编排) -│ │ │ ├── Commands/ # 命令模式 +│ │ ├── TerritoryGame.Application/ # 应用层 +│ │ │ ├── Commands/ # 命令定义 │ │ │ │ ├── CreateRoomCommand.cs │ │ │ │ ├── JoinRoomCommand.cs │ │ │ │ └── PaintActionCommand.cs -│ │ │ ├── Queries/ # 查询模式 +│ │ │ ├── Queries/ # 查询定义 │ │ │ │ ├── GetRoomQuery.cs │ │ │ │ └── GetLeaderboardQuery.cs -│ │ │ ├── Handlers/ # CQRS处理器 +│ │ │ ├── Handlers/ # 命令和查询处理器 │ │ │ │ ├── CommandHandlers/ │ │ │ │ └── QueryHandlers/ │ │ │ └── Services/ # 应用服务 -│ │ │ └── GameOrchestrator.cs # 游戏编排服务 +│ │ │ └── GameOrchestrator.cs +│ │ ├── TerritoryGame.Domain/ # 领域层 +│ │ │ ├── Entities/ # 聚合根和实体 +│ │ │ │ ├── Game.cs +│ │ │ │ ├── Player.cs +│ │ │ │ └── GameRoom.cs +│ │ │ ├── Events/ # 领域事件 +│ │ │ │ ├── GameStartedEvent.cs +│ │ │ │ ├── AreaCapturedEvent.cs +│ │ │ │ └── GameEndedEvent.cs +│ │ │ ├── Repositories/ # 仓储接口 +│ │ │ │ ├── IGameRepository.cs +│ │ │ │ └── IPlayerRepository.cs +│ │ │ ├── Services/ # 领域服务 +│ │ │ │ └── IGameLogicService.cs +│ │ │ └── ValueObjects/ # 值对象 +│ │ │ ├── Position.cs +│ │ │ ├── Color.cs +│ │ │ └── GameScore.cs │ │ ├── TerritoryGame.Infrastructure/ # 基础设施层 -│ │ │ ├── Persistence/ # 数据持久化 +│ │ │ ├── Persistence/ # 数据持久化 (EF Core) │ │ │ │ ├── GameContext.cs # EF Core上下文 │ │ │ │ └── Repositories/ # 仓储实现 │ │ │ │ ├── GameRepository.cs │ │ │ │ └── PlayerRepository.cs -│ │ │ ├── Redis/ # 缓存服务 +│ │ │ ├── Caching/ # 缓存 (Redis) │ │ │ │ └── GameCacheService.cs -│ │ │ └── Messaging/ # 事件总线 +│ │ │ └── Messaging/ # 消息/事件总线 │ │ │ └── EventBus.cs -│ │ │ ├── Entities/ # 实体类 -│ │ │ │ ├── GameRoom.cs -│ │ │ │ ├── Player.cs -│ │ │ │ ├── GameSession.cs -│ │ │ │ └── PaintAction.cs -│ │ │ └── Interfaces/ -│ │ ├── TerritoryGame.Infrastructure/ -│ │ │ ├── Data/ -│ │ │ │ └── GameDbContext.cs -│ │ │ └── Repositories/ -│ │ └── TerritoryGame.Application/ -│ │ ├── DTOs/ -│ │ ├── Services/ -│ │ │ ├── GameService.cs -│ │ │ └── AreaCalculationService.cs -│ │ └── Hubs/ │ └── TerritoryGame.sln ├── paint-demo.html # 游戏演示页面 ├── docker-compose.yml # Docker编排文件 diff --git a/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs new file mode 100644 index 0000000..8725739 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs @@ -0,0 +1,3 @@ +namespace TerritoryGame.Application.Commands; + +public record CreatePlayerCommand(string Name, string Color); \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs new file mode 100644 index 0000000..b083a40 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs @@ -0,0 +1,29 @@ +using MediatR; +using TerritoryGame.Application.Commands; +using TerritoryGame.Domain.Entities; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.CommandHandlers; + +public class CreatePlayerCommandHandler : IRequestHandler +{ + private readonly IPlayerRepository _playerRepository; + + public CreatePlayerCommandHandler(IPlayerRepository playerRepository) + { + _playerRepository = playerRepository; + } + + public async Task Handle(CreatePlayerCommand request, CancellationToken cancellationToken) + { + var player = new Player + { + Name = request.Name, + Color = request.Color + }; + + await _playerRepository.AddAsync(player); + + return Unit.Value; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs new file mode 100644 index 0000000..6f1b478 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs @@ -0,0 +1,21 @@ +using MediatR; +using TerritoryGame.Application.Queries; +using TerritoryGame.Domain.Entities; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.QueryHandlers; + +public class GetPlayerQueryHandler : IRequestHandler +{ + private readonly IPlayerRepository _playerRepository; + + public GetPlayerQueryHandler(IPlayerRepository playerRepository) + { + _playerRepository = playerRepository; + } + + public async Task Handle(GetPlayerQuery request, CancellationToken cancellationToken) + { + return await _playerRepository.GetByIdAsync(request.Id); + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs new file mode 100644 index 0000000..d348c64 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs @@ -0,0 +1,5 @@ +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Application.Queries; + +public record GetPlayerQuery(Guid Id); \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/Game.cs b/backend/src/TerritoryGame.Domain/Entities/Game.cs new file mode 100644 index 0000000..11c7d00 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Entities/Game.cs @@ -0,0 +1,10 @@ +namespace TerritoryGame.Domain.Entities; + +public class Game +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public List Players { get; set; } = new(); + public DateTime StartTime { get; set; } + public DateTime? EndTime { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs new file mode 100644 index 0000000..a67cbf4 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace TerritoryGame.Domain.Entities; + +/// +/// 表示一个游戏房间,玩家可以在其中加入和玩游戏。 +/// +public class GameRoom +{ + /// + /// 游戏房间的唯一标识符。 + /// + public Guid Id { get; set; } + + /// + /// 游戏房间的名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 创建游戏房间时的时间戳。 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 此房间中的游戏会话集合。 + /// + public ICollection GameSessions { get; set; } = new List(); + + /// + /// 在此房间中执行的涂色动作集合。 + /// + public ICollection PaintActions { get; set; } = new List(); +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/GameSession.cs b/backend/src/TerritoryGame.Domain/Entities/GameSession.cs new file mode 100644 index 0000000..e751a68 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Entities/GameSession.cs @@ -0,0 +1,54 @@ +using System; + +namespace TerritoryGame.Domain.Entities; + +/// +/// 表示玩家的游戏会话。 +/// +public class GameSession +{ + /// + /// 游戏会话的唯一标识符。 + /// + public Guid Id { get; set; } + + /// + /// 游戏房间的ID。 + /// + public Guid RoomId { get; set; } + + /// + /// 玩家的ID。 + /// + public Guid PlayerId { get; set; } + + /// + /// 玩家在游戏中的颜色。 + /// + public string PlayerColor { get; set; } = string.Empty; + + /// + /// 玩家最终占领的区域面积。 + /// + public int FinalArea { get; set; } + + /// + /// 玩家的排名。 + /// + public int? Rank { get; set; } + + /// + /// 玩家加入游戏的时间戳。 + /// + public DateTime JoinedAt { get; set; } + + /// + /// 导航属性,指向游戏房间。 + /// + public GameRoom? GameRoom { get; set; } + + /// + /// 导航属性,指向玩家。 + /// + public Player? Player { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs b/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs new file mode 100644 index 0000000..31f4841 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs @@ -0,0 +1,54 @@ +using System; + +namespace TerritoryGame.Domain.Entities; + +/// +/// 表示玩家的涂色动作。 +/// +public class PaintAction +{ + /// + /// 涂色动作的唯一标识符。 + /// + public Guid Id { get; set; } + + /// + /// 游戏房间的ID。 + /// + public Guid RoomId { get; set; } + + /// + /// 玩家的ID。 + /// + public Guid PlayerId { get; set; } + + /// + /// 涂色点的X坐标。 + /// + public int X { get; set; } + + /// + /// 涂色点的Y坐标。 + /// + public int Y { get; set; } + + /// + /// 笔刷的大小。 + /// + public int BrushSize { get; set; } + + /// + /// 动作发生时的时间戳。 + /// + public DateTime Timestamp { get; set; } + + /// + /// 导航属性,指向游戏房间。 + /// + public GameRoom? GameRoom { get; set; } + + /// + /// 导航属性,指向玩家。 + /// + public Player? Player { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/Player.cs b/backend/src/TerritoryGame.Domain/Entities/Player.cs new file mode 100644 index 0000000..b6d7dce --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Entities/Player.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace TerritoryGame.Domain.Entities; + +/// +/// 表示游戏中的玩家。 +/// +public class Player +{ + /// + /// 玩家的唯一标识符。 + /// + public Guid Id { get; set; } + + /// + /// 玩家的昵称。 + /// + public string NickName { get; set; } = string.Empty; + + /// + /// 玩家头像的URL。 + /// + public string? Avatar { get; set; } + + /// + /// 玩家进行的游戏总数。 + /// + public int TotalGames { get; set; } + + /// + /// 玩家获胜的游戏数。 + /// + public int WinCount { get; set; } + + /// + /// 创建玩家时的时间戳。 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 玩家参与的游戏会话集合。 + /// + public ICollection GameSessions { get; set; } = new List(); + + /// + /// 玩家执行的涂色动作集合。 + /// + public ICollection PaintActions { get; set; } = new List(); +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs b/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs new file mode 100644 index 0000000..19527aa --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs @@ -0,0 +1,10 @@ +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Domain.Repositories; + +public interface IGameRepository +{ + Task GetByIdAsync(Guid id); + Task AddAsync(Game game); + Task UpdateAsync(Game game); +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs b/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs new file mode 100644 index 0000000..d6e47a5 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs @@ -0,0 +1,11 @@ +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Domain.Repositories; + +public interface IPlayerRepository +{ + Task GetByIdAsync(Guid id); + Task AddAsync(Player player); + Task UpdateAsync(Player player); + Task DeleteAsync(Player player); +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs new file mode 100644 index 0000000..da5ab06 --- /dev/null +++ b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using TerritoryGame.Domain.Entities; +using TerritoryGame.Domain.Repositories; +using TerritoryGame.Infrastructure.Persistence; + +namespace TerritoryGame.Infrastructure.Persistence.Repositories; + +public class GameRepository : IGameRepository +{ + private readonly TerritoryGameDbContext _context; + + public GameRepository(TerritoryGameDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(Guid id) + { + return await _context.Games.Include(g => g.Players).FirstOrDefaultAsync(g => g.Id == id); + } + + public async Task AddAsync(Game game) + { + await _context.Games.AddAsync(game); + await _context.SaveChangesAsync(); + } + + public async Task UpdateAsync(Game game) + { + _context.Games.Update(game); + await _context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs new file mode 100644 index 0000000..d61bbd1 --- /dev/null +++ b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using TerritoryGame.Domain.Entities; +using TerritoryGame.Domain.Repositories; +using TerritoryGame.Infrastructure.Persistence; + +namespace TerritoryGame.Infrastructure.Persistence.Repositories; + +public class PlayerRepository : IPlayerRepository +{ + private readonly TerritoryGameDbContext _context; + + public PlayerRepository(TerritoryGameDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(Guid id) + { + return await _context.Players.FindAsync(id); + } + + public async Task AddAsync(Player player) + { + await _context.Players.AddAsync(player); + await _context.SaveChangesAsync(); + } + + public async Task UpdateAsync(Player player) + { + _context.Players.Update(player); + await _context.SaveChangesAsync(); + } + + public async Task DeleteAsync(Player player) + { + _context.Players.Remove(player); + await _context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs new file mode 100644 index 0000000..7e9af67 --- /dev/null +++ b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Infrastructure.Persistence; + +public class TerritoryGameDbContext : DbContext +{ + public TerritoryGameDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Players { get; set; } + public DbSet Games { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file -- Gitee From 35ad8e40d4cec57ab67a47e042e3490f80f2e825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=97=8B?= <2574356905@qq.com> Date: Fri, 15 Aug 2025 09:12:04 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=AE=8C=E5=96=84dto=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E6=88=BF=E9=97=B4=E5=92=8C=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E6=88=BF=E9=97=B4=EF=BC=8C=E6=9F=A5=E8=AF=A2=E6=88=BF=E9=97=B4?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Commands/CreateRoomCommand.cs | 14 ++ .../Commands/JoinRoomCommand.cs | 19 ++ .../Commands/PaintActionCommand.cs | 34 +++ .../DTOs/GameRoomDto.cs | 27 +++ .../DTOs/LeaderboardDto.cs | 12 + .../DTOs/LeaderboardItemDto.cs | 22 ++ .../DTOs/PlayerDto.cs | 17 ++ .../Queries/GetLeaderboardQuery.cs | 11 + .../Queries/GetRoomQuery.cs | 15 ++ .../src/TerritoryGame.Domain/Entities/Game.cs | 10 - .../Repositories/IGameRepository.cs | 10 - .../Repositories/GameRepository.cs | 33 --- .../Persistence/TerritoryGameDbContext.cs | 24 +- docs/BACKEND_DEVELOPMENT_PROCESS.md | 207 ++++++++++++++++++ 14 files changed, 401 insertions(+), 54 deletions(-) create mode 100644 backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs create mode 100644 backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs create mode 100644 backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs create mode 100644 backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs create mode 100644 backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs create mode 100644 backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs create mode 100644 backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs create mode 100644 backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs create mode 100644 backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs delete mode 100644 backend/src/TerritoryGame.Domain/Entities/Game.cs delete mode 100644 backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs delete mode 100644 backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs create mode 100644 docs/BACKEND_DEVELOPMENT_PROCESS.md diff --git a/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs new file mode 100644 index 0000000..9f036c9 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace TerritoryGame.Application.Commands; + +/// +/// 创建游戏房间的命令 +/// +public class CreateRoomCommand : IRequest +{ + /// + /// 房间名称 + /// + public string Name { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs new file mode 100644 index 0000000..213b387 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace TerritoryGame.Application.Commands; + +/// +/// 加入游戏房间的命令 +/// +public class JoinRoomCommand : IRequest +{ + /// + /// 房间ID + /// + public Guid RoomId { get; set; } + + /// + /// 玩家ID + /// + public Guid PlayerId { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs new file mode 100644 index 0000000..1c52439 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs @@ -0,0 +1,34 @@ +using MediatR; + +namespace TerritoryGame.Application.Commands; + +/// +/// 玩家涂色动作的命令 +/// +public class PaintActionCommand : IRequest +{ + /// + /// 房间ID + /// + public Guid RoomId { get; set; } + + /// + /// 玩家ID + /// + public Guid PlayerId { get; set; } + + /// + /// 涂色点的X坐标 + /// + public int X { get; set; } + + /// + /// 涂色点的Y坐标 + /// + public int Y { get; set; } + + /// + /// 笔刷大小 + /// + public int BrushSize { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs b/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs new file mode 100644 index 0000000..631029c --- /dev/null +++ b/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs @@ -0,0 +1,27 @@ +namespace TerritoryGame.Application.DTOs; + +/// +/// 游戏房间信息的数据传输对象 +/// +public class GameRoomDto +{ + /// + /// 房间ID + /// + public Guid Id { get; set; } + + /// + /// 房间名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 房间内的玩家列表 + /// + public List Players { get; set; } = new(); + + /// + /// 游戏状态 + /// + public string Status { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs b/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs new file mode 100644 index 0000000..86df480 --- /dev/null +++ b/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs @@ -0,0 +1,12 @@ +namespace TerritoryGame.Application.DTOs; + +/// +/// 排行榜信息的数据传输对象 +/// +public class LeaderboardDto +{ + /// + /// 排行榜条目列表 + /// + public List Items { get; set; } = new(); +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs new file mode 100644 index 0000000..fd5abf7 --- /dev/null +++ b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs @@ -0,0 +1,22 @@ +namespace TerritoryGame.Application.DTOs; + +/// +/// 排行榜条目的数据传输对象 +/// +public class LeaderboardItemDto +{ + /// + /// 玩家昵称 + /// + public string PlayerName { get; set; } = string.Empty; + + /// + /// 获胜次数 + /// + public int WinCount { get; set; } + + /// + /// 总游戏次数 + /// + public int TotalGames { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs new file mode 100644 index 0000000..412537c --- /dev/null +++ b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs @@ -0,0 +1,17 @@ +namespace TerritoryGame.Application.DTOs; + +/// +/// 玩家信息的数据传输对象 +/// +public class PlayerDto +{ + /// + /// 玩家ID + /// + public Guid Id { get; set; } + + /// + /// 玩家昵称 + /// + public string NickName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs new file mode 100644 index 0000000..dda69c1 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs @@ -0,0 +1,11 @@ +using MediatR; +using TerritoryGame.Application.DTOs; + +namespace TerritoryGame.Application.Queries; + +/// +/// 获取排行榜信息的查询 +/// +public class GetLeaderboardQuery : IRequest +{ +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs new file mode 100644 index 0000000..6a2fa32 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs @@ -0,0 +1,15 @@ +using MediatR; +using TerritoryGame.Application.DTOs; + +namespace TerritoryGame.Application.Queries; + +/// +/// 获取游戏房间信息的查询 +/// +public class GetRoomQuery : IRequest +{ + /// + /// 房间ID + /// + public Guid RoomId { get; set; } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/Game.cs b/backend/src/TerritoryGame.Domain/Entities/Game.cs deleted file mode 100644 index 11c7d00..0000000 --- a/backend/src/TerritoryGame.Domain/Entities/Game.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace TerritoryGame.Domain.Entities; - -public class Game -{ - public Guid Id { get; set; } - public string Name { get; set; } = string.Empty; - public List Players { get; set; } = new(); - public DateTime StartTime { get; set; } - public DateTime? EndTime { get; set; } -} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs b/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs deleted file mode 100644 index 19527aa..0000000 --- a/backend/src/TerritoryGame.Domain/Repositories/IGameRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using TerritoryGame.Domain.Entities; - -namespace TerritoryGame.Domain.Repositories; - -public interface IGameRepository -{ - Task GetByIdAsync(Guid id); - Task AddAsync(Game game); - Task UpdateAsync(Game game); -} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs deleted file mode 100644 index da5ab06..0000000 --- a/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/GameRepository.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using TerritoryGame.Domain.Entities; -using TerritoryGame.Domain.Repositories; -using TerritoryGame.Infrastructure.Persistence; - -namespace TerritoryGame.Infrastructure.Persistence.Repositories; - -public class GameRepository : IGameRepository -{ - private readonly TerritoryGameDbContext _context; - - public GameRepository(TerritoryGameDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(Guid id) - { - return await _context.Games.Include(g => g.Players).FirstOrDefaultAsync(g => g.Id == id); - } - - public async Task AddAsync(Game game) - { - await _context.Games.AddAsync(game); - await _context.SaveChangesAsync(); - } - - public async Task UpdateAsync(Game game) - { - _context.Games.Update(game); - await _context.SaveChangesAsync(); - } -} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs index 7e9af67..bf3981e 100644 --- a/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs +++ b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs @@ -10,10 +10,32 @@ public class TerritoryGameDbContext : DbContext } public DbSet Players { get; set; } - public DbSet Games { get; set; } + public DbSet GameRooms { get; set; } + public DbSet GameSessions { get; set; } + public DbSet PaintActions { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasOne(gs => gs.GameRoom) + .WithMany(gr => gr.GameSessions) + .HasForeignKey(gs => gs.RoomId); + + modelBuilder.Entity() + .HasOne(gs => gs.Player) + .WithMany(p => p.GameSessions) + .HasForeignKey(gs => gs.PlayerId); + + modelBuilder.Entity() + .HasOne(pa => pa.GameRoom) + .WithMany(gr => gr.PaintActions) + .HasForeignKey(pa => pa.RoomId); + + modelBuilder.Entity() + .HasOne(pa => pa.Player) + .WithMany(p => p.PaintActions) + .HasForeignKey(pa => pa.PlayerId); } } \ No newline at end of file diff --git a/docs/BACKEND_DEVELOPMENT_PROCESS.md b/docs/BACKEND_DEVELOPMENT_PROCESS.md new file mode 100644 index 0000000..8ccbe7e --- /dev/null +++ b/docs/BACKEND_DEVELOPMENT_PROCESS.md @@ -0,0 +1,207 @@ +# 后端开发全流程指南 + +本文档旨在为后端开发团队提供一个标准化的、端到端的开发流程,涵盖从需求分析到最终部署和长期维护的每一个环节。遵循此流程有助于提高开发效率、保证代码质量、降低沟通成本,并确保项目成功交付。 + +## 目录 +1. [阶段一:需求分析 (Requirements Analysis)](#阶段一需求分析-requirements-analysis) +2. [阶段二:系统设计 (System Design)](#阶段二系统设计-system-design) +3. [阶段三:开发实现 (Development)](#阶段三开发实现-development) +4. [阶段四:测试验证 (Testing)](#阶段四测试验证-testing) +5. [阶段五:部署发布 (Deployment)](#阶段五部署发布-deployment) +6. [阶段六:运维与监控 (Operations & Monitoring)](#阶段六运维与监控-operations--monitoring) +7. [阶段七:维护与迭代 (Maintenance & Iteration)](#阶段七维护与迭代-maintenance--iteration) + +--- + +## 阶段一:需求分析 (Requirements Analysis) + +此阶段的目标是清晰、完整地理解要做什么,并确保所有项目相关方对需求有统一的认识。 + +- **关键任务**: + - 与产品经理(PM)、项目经理和客户进行深入沟通,理解业务目标和用户场景。 + - 明确功能性需求(如:用户注册、商品下单、数据查询等)。 + - 明确非功能性需求(如:性能指标、安全标准、可扩展性、数据一致性要求等)。 + - 参与需求评审会议,从技术角度评估需求的可行性、复杂度和潜在风险。 + +- **参与人员**: + - `主要`:后端开发负责人、产品经理 + - `次要`:架构师、测试工程师、项目经理 + +- **交付物**: + - **需求规格说明书 (SRS)**:由产品经理主导,技术团队评审确认。 + - **用户故事 (User Stories)** 和 **用例 (Use Cases)**:清晰描述每个功能点。 + - **技术可行性分析报告**:针对复杂或不明确的需求。 + +- **最佳实践**: + - **刨根问底**:不要只满足于表面需求,要理解其背后的商业逻辑。 + - **量化指标**:尽可能量化非功能性需求,例如“接口响应时间应在200ms以内”、“系统需支持1000 QPS的并发量”。 + - **早期介入**:开发人员应尽早参与需求讨论,以便提前识别技术挑战。 + - **文档化**:所有需求和变更都应记录在案,避免口头约定。 + +--- + +## 阶段二:系统设计 (System Design) + +此阶段将需求转化为详细的技术方案和蓝图,是后续开发工作的指导方针。 + +- **关键任务**: + - **架构设计**:根据需求选择合适的架构模式(如:微服务、单体、Serverless),划分服务模块。 + - **技术选型**:选择合适的编程语言、框架、中间件(如:数据库、缓存、消息队列)。 + - **数据库设计**:设计数据库表结构(ER图)、索引和数据字典。 + - **API 设计**:设计清晰、一致的 API 接口(如:RESTful、GraphQL),并撰写接口文档。 + - **安全设计**:规划认证授权方案(如:OAuth2, JWT)、数据加密、防范常见攻击(如:SQL注入, XSS)。 + +- **参与人员**: + - `主要`:架构师、高级后端工程师 + - `次要`:后端开发团队、DBA、安全专家 + +- **交付物**: + - **架构设计文档**:包含系统架构图、模块划分、核心流程图。 + - **数据库设计文档**:包含 ER 图、表结构详情、数据字典。 + - **API 接口文档**:使用 OpenAPI/Swagger 等工具生成,包含详细的请求/响应格式、参数说明和错误码。 + - **技术选型报告**:说明选型原因和优缺点对比。 + +- **最佳实践**: + - **KISS, DRY, SOLID**:遵循高内聚、低耦合等基本设计原则。 + - **面向未来设计**:充分考虑系统的可扩展性、可维护性和弹性。 + - **评审机制**:组织设计评审会议,让团队成员充分讨论,凝聚共识。 + - **文档先行**:在编码前完成关键设计文档的编写。 + +--- + +## 阶段三:开发实现 (Development) + +此阶段是根据设计文档将技术方案转化为实际可运行的代码。 + +- **关键任务**: + - 搭建和配置本地开发环境。 + - 根据任务分配,实现业务逻辑、数据访问层和 API 接口。 + - 编写单元测试,确保代码模块的正确性。 + - 代码审查(Code Review),保证代码质量和风格统一。 + - 与前端、测试等团队保持密切沟通,及时解决联调和集成问题。 + - 配置并实践持续集成(CI),自动化构建和测试流程。 + +- **参与人员**: + - `主要`:后端开发工程师 + +- **交付物**: + - **源代码**:托管于版本控制系统(如 Git)。 + - **单元测试用例**及测试报告。 + - **Code Review 记录**。 + - **构建脚本**(如:Dockerfile, Maven/Gradle 配置)。 + +- **最佳实践**: + - **编码规范**:遵循团队统一的编码规范和代码风格。 + - **版本控制**:使用 Git Flow 或类似的分支管理策略,保持提交信息清晰明了。 + - **TDD/BDD**:鼓励测试驱动开发或行为驱动开发。 + - **小步快跑**:频繁提交、小批量集成,尽早发现和解决冲突。 + - **注释与文档**:对复杂逻辑或公共模块编写必要的注释和开发者文档。 + +--- + +## 阶段四:测试验证 (Testing) + +此阶段通过系统化的测试来保证软件质量,确保其符合需求并能稳定运行。 + +- **关键任务**: + - **集成测试**:测试不同模块或服务协同工作的正确性。 + - **API 功能测试**:使用 Postman、Insomnia 等工具对所有 API 接口进行黑盒测试。 + - **性能测试**:对核心接口进行压力测试和负载测试,评估系统性能瓶颈。 + - **安全测试**:进行漏洞扫描和渗透测试。 + - **回归测试**:在新版本发布前,确保原有功能未受影响。 + - 缺陷管理:记录、跟踪和验证 Bug 的修复。 + +- **参与人员**: + - `主要`:测试工程师(QA) + - `次要`:后端开发工程师、产品经理 + +- **交付物**: + - **测试计划**和**测试用例**。 + - **测试报告**:包含功能、性能、安全等维度的测试结果。 + - **缺陷报告列表**(Bug List)。 + +- **最佳实践**: + - **测试自动化**:将回归测试和核心功能的 API 测试自动化,提高效率。 + - **分层测试**:建立单元测试、集成测试、端到端测试的金字塔模型。 + - **隔离的测试环境**:搭建与生产环境尽可能一致的独立测试环境。 + - **有效的缺陷管理**:使用 Jira 等工具进行系统化的缺陷跟踪。 + +--- + +## 阶段五:部署发布 (Deployment) + +此阶段将经过测试验证的应用程序安全、平稳地发布到生产环境。 + +- **关键任务**: + - 准备生产环境,包括服务器、网络、数据库等基础设施。 + - 编写和测试部署脚本(如:Ansible, Terraform)。 + - 构建最终的部署产物(如:Docker 镜像)。 + - 执行部署流程,并进行部署后验证(Smoke Test)。 + - 制定回滚计划,以应对部署失败的情况。 + +- **参与人员**: + - `主要`:运维工程师(DevOps/Ops) + - `次要`:后端开发工程师 + +- **交付物**: + - **持续部署(CD)流水线**。 + - **部署操作手册**和**回滚方案**。 + - **版本发布说明(Release Notes)**。 + +- **最佳实践**: + - **IaC(基础设施即代码)**:使用代码来管理和配置基础设施,实现自动化和可重复性。 + - **蓝绿部署/金丝雀发布**:采用先进的发布策略,实现零停机或灰度发布,降低发布风险。 + - **自动化**:尽可能自动化整个部署流程,减少人为错误。 + - **配置中心**:将应用配置与代码分离,实现动态配置管理。 + +--- + +## 阶段六:运维与监控 (Operations & Monitoring) + +应用上线后,需要持续监控其运行状态,保障服务的稳定性和可用性。 + +- **关键任务**: + - **日志管理**:集中收集、存储和分析应用日志(ELK/EFK Stack)。 + - **性能监控**:监控系统关键指标(CPU、内存、磁盘、网络、JVM/CLR 等)。 + - **业务监控**:监控核心业务指标(如:订单量、用户注册数)。 + - **告警系统**:设置合理的告警阈值,在问题发生时通过短信、电话、邮件等方式及时通知相关人员。 + - **备份与恢复**:制定并定期演练数据备份和灾难恢复计划。 + +- **参与人员**: + - `主要`:运维工程师(DevOps/SRE) + - `次要`:后端开发工程师 + +- **交付物**: + - **监控仪表盘(Dashboard)**:如 Grafana, Prometheus。 + - **告警规则和通知策略**。 + - **应急预案(On-Call Playbook)**和**灾备演练报告**。 + +- **最佳实践**: + - **可观测性(Observability)**:建立涵盖日志(Logging)、指标(Metrics)和追踪(Tracing)的全面监控体系。 + - **定义SLO/SLA**:明确服务水平目标和服务水平协议,作为衡量服务质量的标准。 + - **自动化运维**:利用脚本和工具实现自动化故障发现和自愈。 + - **定期演练**:定期进行故障注入和灾备演练,确保预案的有效性。 + +--- + +## 阶段七:维护与迭代 (Maintenance & Iteration) + +此阶段是应用的长期生命周期管理,包括修复问题、优化系统和响应新的业务需求。 + +- **关键任务**: + - **问题修复**:快速响应和修复线上发现的 Bug。 + - **技术优化**:进行代码重构、性能优化、升级老旧依赖,偿还技术债务。 + - **需求迭代**:根据用户反馈和新的业务规划,进入新的开发循环(重复阶段一至六)。 + - **文档更新**:同步更新架构、设计、API 等文档,确保其与代码一致。 + +- **参与人员**: + - 所有项目相关方 + +- **交付物**: + - **软件补丁**和**新版本**。 + - **更新后的各类文档**。 + +- **最佳实践**: + - **敏捷迭代**:采用 Scrum 或 Kanban 等敏捷方法,快速响应变化。 + - **建立知识库**:沉淀问题解决方案和最佳实践,供团队共享。 + - **定期复盘**:定期召开项目复盘会议,总结经验教训,持续改进流程。 \ No newline at end of file -- Gitee From dcf0e229d7484fac30315b3024ab950808c485ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=97=8B?= <2574356905@qq.com> Date: Fri, 15 Aug 2025 09:27:25 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E6=A0=B8=E5=BF=83=E5=8A=9F=E8=83=BD=E4=B8=8E=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E9=A2=86=E5=9F=9F=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Commands/CreatePlayerCommand.cs | 7 +- .../Commands/CreateRoomCommand.cs | 12 +-- .../Commands/JoinRoomCommand.cs | 18 ++-- .../Commands/PaintActionCommand.cs | 35 ++------ .../CreatePlayerCommandHandler.cs | 24 ++++-- .../CreateRoomCommandHandler.cs | 32 +++++++ .../CommandHandlers/JoinRoomCommandHandler.cs | 47 ++++++++++ .../PaintActionCommandHandler.cs | 44 ++++++++++ .../GetLeaderboardQueryHandler.cs | 38 +++++++++ .../QueryHandlers/GetPlayerQueryHandler.cs | 22 ++++- .../QueryHandlers/GetRoomQueryHandler.cs | 34 ++++++++ .../Queries/GetLeaderboardQuery.cs | 4 +- .../Queries/GetPlayerQuery.cs | 9 +- .../Queries/GetRoomQuery.cs | 11 +-- .../TerritoryGame.Domain/Entities/GameRoom.cs | 85 ++++++++++++++++--- .../TerritoryGame.Domain/Entities/Player.cs | 77 ++++++++++++----- .../TerritoryGame.Domain/Enums/GameStatus.cs | 22 +++++ .../Services/GameLogicService.cs | 46 ++++++++++ .../Services/IGameLogicService.cs | 19 +++++ 19 files changed, 479 insertions(+), 107 deletions(-) create mode 100644 backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs create mode 100644 backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs create mode 100644 backend/src/TerritoryGame.Domain/Enums/GameStatus.cs create mode 100644 backend/src/TerritoryGame.Domain/Services/GameLogicService.cs create mode 100644 backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs diff --git a/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs index 8725739..e6d1801 100644 --- a/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs +++ b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs @@ -1,3 +1,8 @@ namespace TerritoryGame.Application.Commands; -public record CreatePlayerCommand(string Name, string Color); \ No newline at end of file +/// +/// 创建新玩家的命令 +/// +/// 玩家昵称 +/// 玩家头像的URL(可选) +public record CreatePlayerCommand(string NickName, string? Avatar) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs index 9f036c9..34d22aa 100644 --- a/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs +++ b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs @@ -1,14 +1,10 @@ using MediatR; +using TerritoryGame.Application.DTOs; namespace TerritoryGame.Application.Commands; /// -/// 创建游戏房间的命令 +/// 创建新游戏房间的命令 /// -public class CreateRoomCommand : IRequest -{ - /// - /// 房间名称 - /// - public string Name { get; set; } = string.Empty; -} \ No newline at end of file +/// 房间名称 +public record CreateRoomCommand(string Name) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs index 213b387..a462352 100644 --- a/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs +++ b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs @@ -1,19 +1,11 @@ using MediatR; +using TerritoryGame.Application.DTOs; namespace TerritoryGame.Application.Commands; /// -/// 加入游戏房间的命令 +/// 玩家加入游戏房间的命令 /// -public class JoinRoomCommand : IRequest -{ - /// - /// 房间ID - /// - public Guid RoomId { get; set; } - - /// - /// 玩家ID - /// - public Guid PlayerId { get; set; } -} \ No newline at end of file +/// 房间ID +/// 玩家ID +public record JoinRoomCommand(Guid RoomId, Guid PlayerId) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs index 1c52439..9991424 100644 --- a/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs +++ b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs @@ -3,32 +3,11 @@ using MediatR; namespace TerritoryGame.Application.Commands; /// -/// 玩家涂色动作的命令 +/// 玩家执行涂色动作的命令 /// -public class PaintActionCommand : IRequest -{ - /// - /// 房间ID - /// - public Guid RoomId { get; set; } - - /// - /// 玩家ID - /// - public Guid PlayerId { get; set; } - - /// - /// 涂色点的X坐标 - /// - public int X { get; set; } - - /// - /// 涂色点的Y坐标 - /// - public int Y { get; set; } - - /// - /// 笔刷大小 - /// - public int BrushSize { get; set; } -} \ No newline at end of file +/// 房间ID +/// 玩家ID +/// X坐标 +/// Y坐标 +/// 笔刷大小 +public record PaintActionCommand(Guid RoomId, Guid PlayerId, int X, int Y, int BrushSize) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs index b083a40..ed02b99 100644 --- a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs +++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs @@ -1,11 +1,12 @@ using MediatR; using TerritoryGame.Application.Commands; +using TerritoryGame.Application.DTOs; using TerritoryGame.Domain.Entities; using TerritoryGame.Domain.Repositories; namespace TerritoryGame.Application.Handlers.CommandHandlers; -public class CreatePlayerCommandHandler : IRequestHandler +public class CreatePlayerCommandHandler : IRequestHandler { private readonly IPlayerRepository _playerRepository; @@ -14,16 +15,21 @@ public class CreatePlayerCommandHandler : IRequestHandler _playerRepository = playerRepository; } - public async Task Handle(CreatePlayerCommand request, CancellationToken cancellationToken) + public async Task Handle(CreatePlayerCommand request, CancellationToken cancellationToken) { - var player = new Player - { - Name = request.Name, - Color = request.Color - }; + // 使用充血模型的构造函数创建实体 + var player = new Player(request.NickName, request.Avatar); - await _playerRepository.AddAsync(player); + await _playerRepository.AddAsync(player, cancellationToken); - return Unit.Value; + // 返回一个DTO,而不是领域实体 + return new PlayerDto + { + Id = player.Id, + NickName = player.NickName, + Avatar = player.Avatar, + TotalGames = player.TotalGames, + WinCount = player.WinCount + }; } } \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs new file mode 100644 index 0000000..ce9bab1 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs @@ -0,0 +1,32 @@ +using MediatR; +using TerritoryGame.Application.Commands; +using TerritoryGame.Application.DTOs; +using TerritoryGame.Domain.Entities; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.CommandHandlers; + +public class CreateRoomCommandHandler : IRequestHandler +{ + private readonly IGameRoomRepository _gameRoomRepository; + + public CreateRoomCommandHandler(IGameRoomRepository gameRoomRepository) + { + _gameRoomRepository = gameRoomRepository; + } + + public async Task Handle(CreateRoomCommand request, CancellationToken cancellationToken) + { + var gameRoom = new GameRoom(request.Name); + + await _gameRoomRepository.AddAsync(gameRoom, cancellationToken); + + return new GameRoomDto + { + Id = gameRoom.Id, + Name = gameRoom.Name, + Status = gameRoom.Status.ToString(), + Players = new List() // Initially empty + }; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs new file mode 100644 index 0000000..a3ee0ae --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs @@ -0,0 +1,47 @@ +using MediatR; +using TerritoryGame.Application.Commands; +using TerritoryGame.Application.DTOs; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.CommandHandlers; + +public class JoinRoomCommandHandler : IRequestHandler +{ + private readonly IGameRoomRepository _gameRoomRepository; + private readonly IPlayerRepository _playerRepository; + + public JoinRoomCommandHandler(IGameRoomRepository gameRoomRepository, IPlayerRepository playerRepository) + { + _gameRoomRepository = gameRoomRepository; + _playerRepository = playerRepository; + } + + public async Task Handle(JoinRoomCommand request, CancellationToken cancellationToken) + { + var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken); + if (gameRoom == null) + { + // Or throw a custom exception + throw new Exception("Game room not found."); + } + + var player = await _playerRepository.GetByIdAsync(request.PlayerId, cancellationToken); + if (player == null) + { + // Or throw a custom exception + throw new Exception("Player not found."); + } + + gameRoom.AddPlayer(player); + + await _gameRoomRepository.UpdateAsync(gameRoom, cancellationToken); + + return new GameRoomDto + { + Id = gameRoom.Id, + Name = gameRoom.Name, + Status = gameRoom.Status.ToString(), + Players = gameRoom.Players.Select(p => new PlayerDto { Id = p.Id, NickName = p.NickName, Avatar = p.Avatar }).ToList() + }; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs new file mode 100644 index 0000000..1d2da52 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs @@ -0,0 +1,44 @@ +using MediatR; +using TerritoryGame.Application.Commands; +using TerritoryGame.Domain.Repositories; +using TerritoryGame.Domain.Services; + +namespace TerritoryGame.Application.Handlers.CommandHandlers; + +public class PaintActionCommandHandler : IRequestHandler +{ + private readonly IGameRoomRepository _gameRoomRepository; + private readonly IPlayerRepository _playerRepository; + private readonly IGameLogicService _gameLogicService; + + public PaintActionCommandHandler( + IGameRoomRepository gameRoomRepository, + IPlayerRepository playerRepository, + IGameLogicService gameLogicService) + { + _gameRoomRepository = gameRoomRepository; + _playerRepository = playerRepository; + _gameLogicService = gameLogicService; + } + + public async Task Handle(PaintActionCommand request, CancellationToken cancellationToken) + { + var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken); + if (gameRoom == null) + { + throw new Exception("Game room not found."); + } + + var player = await _playerRepository.GetByIdAsync(request.PlayerId, cancellationToken); + if (player == null) + { + throw new Exception("Player not found."); + } + + _gameLogicService.HandlePaintAction(gameRoom, player, request.X, request.Y, request.BrushSize); + + await _gameRoomRepository.UpdateAsync(gameRoom, cancellationToken); + + return Unit.Value; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs new file mode 100644 index 0000000..7aac7c7 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs @@ -0,0 +1,38 @@ +using MediatR; +using TerritoryGame.Application.DTOs; +using TerritoryGame.Application.Queries; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.QueryHandlers; + +public class GetLeaderboardQueryHandler : IRequestHandler +{ + private readonly IPlayerRepository _playerRepository; + + public GetLeaderboardQueryHandler(IPlayerRepository playerRepository) + { + _playerRepository = playerRepository; + } + + public async Task Handle(GetLeaderboardQuery request, CancellationToken cancellationToken) + { + // This is a simplified implementation. A real-world leaderboard might involve more complex logic + // and potentially a separate, optimized data store for leaderboard data. + var players = await _playerRepository.GetAllAsync(cancellationToken); + + var leaderboardItems = players + .OrderByDescending(p => p.WinCount) // Or any other metric + .Select(p => new LeaderboardItemDto + { + PlayerId = p.Id, + PlayerName = p.NickName, + Score = p.WinCount // Or a calculated score + }) + .ToList(); + + return new LeaderboardDto + { + Items = leaderboardItems + }; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs index 6f1b478..611b68d 100644 --- a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs +++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs @@ -1,11 +1,11 @@ using MediatR; +using TerritoryGame.Application.DTOs; using TerritoryGame.Application.Queries; -using TerritoryGame.Domain.Entities; using TerritoryGame.Domain.Repositories; namespace TerritoryGame.Application.Handlers.QueryHandlers; -public class GetPlayerQueryHandler : IRequestHandler +public class GetPlayerQueryHandler : IRequestHandler { private readonly IPlayerRepository _playerRepository; @@ -14,8 +14,22 @@ public class GetPlayerQueryHandler : IRequestHandler _playerRepository = playerRepository; } - public async Task Handle(GetPlayerQuery request, CancellationToken cancellationToken) + public async Task Handle(GetPlayerQuery request, CancellationToken cancellationToken) { - return await _playerRepository.GetByIdAsync(request.Id); + var player = await _playerRepository.GetByIdAsync(request.Id, cancellationToken); + + if (player == null) + { + return null; + } + + return new PlayerDto + { + Id = player.Id, + NickName = player.NickName, + Avatar = player.Avatar, + TotalGames = player.TotalGames, + WinCount = player.WinCount + }; } } \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs new file mode 100644 index 0000000..8d33a42 --- /dev/null +++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs @@ -0,0 +1,34 @@ +using MediatR; +using TerritoryGame.Application.DTOs; +using TerritoryGame.Application.Queries; +using TerritoryGame.Domain.Repositories; + +namespace TerritoryGame.Application.Handlers.QueryHandlers; + +public class GetRoomQueryHandler : IRequestHandler +{ + private readonly IGameRoomRepository _gameRoomRepository; + + public GetRoomQueryHandler(IGameRoomRepository gameRoomRepository) + { + _gameRoomRepository = gameRoomRepository; + } + + public async Task Handle(GetRoomQuery request, CancellationToken cancellationToken) + { + var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken); + + if (gameRoom == null) + { + return null; + } + + return new GameRoomDto + { + Id = gameRoom.Id, + Name = gameRoom.Name, + Status = gameRoom.Status.ToString(), + Players = gameRoom.Players.Select(p => new PlayerDto { Id = p.Id, NickName = p.NickName, Avatar = p.Avatar }).ToList() + }; + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs index dda69c1..d13edc8 100644 --- a/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs +++ b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs @@ -6,6 +6,4 @@ namespace TerritoryGame.Application.Queries; /// /// 获取排行榜信息的查询 /// -public class GetLeaderboardQuery : IRequest -{ -} \ No newline at end of file +public record GetLeaderboardQuery : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs index d348c64..e9b3609 100644 --- a/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs +++ b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs @@ -1,5 +1,10 @@ -using TerritoryGame.Domain.Entities; +using MediatR; +using TerritoryGame.Application.DTOs; namespace TerritoryGame.Application.Queries; -public record GetPlayerQuery(Guid Id); \ No newline at end of file +/// +/// 获取单个玩家信息的查询 +/// +/// 玩家ID +public record GetPlayerQuery(Guid Id) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs index 6a2fa32..3dd0e42 100644 --- a/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs +++ b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs @@ -4,12 +4,7 @@ using TerritoryGame.Application.DTOs; namespace TerritoryGame.Application.Queries; /// -/// 获取游戏房间信息的查询 +/// 获取单个游戏房间信息的查询 /// -public class GetRoomQuery : IRequest -{ - /// - /// 房间ID - /// - public Guid RoomId { get; set; } -} \ No newline at end of file +/// 房间ID +public record GetRoomQuery(Guid RoomId) : IRequest; \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs index a67cbf4..9cb2c38 100644 --- a/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs +++ b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs @@ -1,35 +1,98 @@ using System; using System.Collections.Generic; +using System.Linq; +using TerritoryGame.Domain.Enums; namespace TerritoryGame.Domain.Entities; /// -/// 表示一个游戏房间,玩家可以在其中加入和玩游戏。 +/// 游戏房间 /// public class GameRoom { /// - /// 游戏房间的唯一标识符。 + /// 房间ID /// - public Guid Id { get; set; } + public Guid Id { get; private set; } /// - /// 游戏房间的名称。 + /// 房间名称 /// - public string Name { get; set; } = string.Empty; + public string Name { get; private set; } /// - /// 创建游戏房间时的时间戳。 + /// 游戏状态 /// - public DateTime CreatedAt { get; set; } + public GameStatus Status { get; private set; } /// - /// 此房间中的游戏会话集合。 + /// 房间内的玩家列表 /// - public ICollection GameSessions { get; set; } = new List(); + private readonly List _players = new(); + public IReadOnlyCollection Players => _players.AsReadOnly(); /// - /// 在此房间中执行的涂色动作集合。 + /// 游戏会话列表 /// - public ICollection PaintActions { get; set; } = new List(); + public ICollection GameSessions { get; private set; } = new List(); + + /// + /// 涂色动作列表 + /// + public ICollection PaintActions { get; private set; } = new List(); + + // EF Core 构造函数 + private GameRoom() { } + + public GameRoom(string name) + { + Id = Guid.NewGuid(); + Name = name; + Status = GameStatus.Waiting; + } + + /// + /// 添加玩家到房间 + /// + /// 要添加的玩家 + public void AddPlayer(Player player) + { + if (Status != GameStatus.Waiting) + throw new InvalidOperationException("游戏已经开始或结束,不能加入玩家。"); + + if (_players.Count >= 6) // 假设最大玩家数为6 + throw new InvalidOperationException("房间已满。"); + + if (_players.Any(p => p.Id == player.Id)) + throw new InvalidOperationException("玩家已在房间内。"); + + _players.Add(player); + } + + /// + /// 开始游戏 + /// + public void StartGame() + { + if (Status != GameStatus.Waiting) + throw new InvalidOperationException("游戏已经开始或结束。"); + + if (_players.Count < 2) // 假设最少需要2个玩家才能开始 + throw new InvalidOperationException("玩家人数不足,无法开始游戏。"); + + Status = GameStatus.InProgress; + // 在这里可以发布一个领域事件,例如 GameStartedEvent + } + + /// + /// 结束游戏 + /// + public void EndGame() + { + if (Status != GameStatus.InProgress) + throw new InvalidOperationException("游戏尚未开始,不能结束。"); + + Status = GameStatus.Finished; + // 在这里可以发布一个领域事件,例如 GameEndedEvent + } } \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Entities/Player.cs b/backend/src/TerritoryGame.Domain/Entities/Player.cs index b6d7dce..1e12d2f 100644 --- a/backend/src/TerritoryGame.Domain/Entities/Player.cs +++ b/backend/src/TerritoryGame.Domain/Entities/Player.cs @@ -1,50 +1,87 @@ -using System; -using System.Collections.Generic; - namespace TerritoryGame.Domain.Entities; /// -/// 表示游戏中的玩家。 +/// 玩家实体 /// public class Player { /// - /// 玩家的唯一标识符。 + /// 玩家ID + /// + public Guid Id { get; private set; } + + /// + /// 玩家昵称 + /// + public string NickName { get; private set; } + + /// + /// 玩家头像的URL /// - public Guid Id { get; set; } + public string? Avatar { get; private set; } /// - /// 玩家的昵称。 + /// 玩家进行的游戏总数 /// - public string NickName { get; set; } = string.Empty; + public int TotalGames { get; private set; } /// - /// 玩家头像的URL。 + /// 玩家获胜的游戏数 /// - public string? Avatar { get; set; } + public int WinCount { get; private set; } /// - /// 玩家进行的游戏总数。 + /// 创建时间 /// - public int TotalGames { get; set; } + public DateTime CreatedAt { get; private set; } /// - /// 玩家获胜的游戏数。 + /// 游戏会话列表 /// - public int WinCount { get; set; } + public ICollection GameSessions { get; private set; } = new List(); /// - /// 创建玩家时的时间戳。 + /// 涂色动作列表 /// - public DateTime CreatedAt { get; set; } + public ICollection PaintActions { get; private set; } = new List(); + + // EF Core 构造函数 + private Player() { } + + public Player(string nickName, string? avatar = null) + { + Id = Guid.NewGuid(); + NickName = nickName; + Avatar = avatar; + TotalGames = 0; + WinCount = 0; + CreatedAt = DateTime.UtcNow; + } /// - /// 玩家参与的游戏会话集合。 + /// 更新玩家资料 /// - public ICollection GameSessions { get; set; } = new List(); + /// 新昵称 + /// 新头像 + public void UpdateProfile(string newNickName, string? newAvatar) + { + if (string.IsNullOrWhiteSpace(newNickName)) + throw new ArgumentException("昵称不能为空。", nameof(newNickName)); + + NickName = newNickName; + Avatar = newAvatar; + } /// - /// 玩家执行的涂色动作集合。 + /// 记录一次游戏结果 /// - public ICollection PaintActions { get; set; } = new List(); + /// 是否获胜 + public void RecordGamePlayed(bool hasWon) + { + TotalGames++; + if (hasWon) + { + WinCount++; + } + } } \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs b/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs new file mode 100644 index 0000000..9d759f3 --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs @@ -0,0 +1,22 @@ +namespace TerritoryGame.Domain.Enums; + +/// +/// 游戏状态 +/// +public enum GameStatus +{ + /// + /// 等待中 + /// + Waiting, + + /// + /// 进行中 + /// + InProgress, + + /// + /// 已结束 + /// + Finished +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs b/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs new file mode 100644 index 0000000..71c305a --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs @@ -0,0 +1,46 @@ +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Domain.Services; + +/// +/// 游戏核心逻辑的服务 +/// +public class GameLogicService : IGameLogicService +{ + /// + /// 处理玩家的涂色动作 + /// + /// 当前游戏房间 + /// 执行动作的玩家 + /// X坐标 + /// Y坐标 + /// 笔刷大小 + public void HandlePaintAction(GameRoom gameRoom, Player player, int x, int y, int brushSize) + { + // 1. 验证游戏是否正在进行中 + if (gameRoom.Status != Enums.GameStatus.InProgress) + { + // 可以抛出异常或直接返回 + return; + } + + // 2. 验证玩家是否在房间内 + if (!gameRoom.Players.Any(p => p.Id == player.Id)) + { + // 玩家不在房间内,非法操作 + return; + } + + // 3. 创建并记录涂色动作(此处只是示例,具体实现可能需要更复杂的逻辑) + var paintAction = new PaintAction(player.Id, gameRoom.Id, x, y, brushSize, DateTime.UtcNow); + gameRoom.PaintActions.Add(paintAction); + + // 4. TODO: 在此实现更复杂的逻辑,例如: + // - 检查涂色区域是否合法(例如,是否在画布边界内) + // - 判断是否覆盖了其他玩家的颜色 + // - 计算新占领的面积 + // - 发布领域事件(如 AreaPaintedEvent),通知其他部分(如应用层)更新状态或通知客户端 + + Console.WriteLine($"Player {player.NickName} painted at ({x}, {y}) in room {gameRoom.Name}"); + } +} \ No newline at end of file diff --git a/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs b/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs new file mode 100644 index 0000000..7cac3ad --- /dev/null +++ b/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs @@ -0,0 +1,19 @@ +using TerritoryGame.Domain.Entities; + +namespace TerritoryGame.Domain.Services; + +/// +/// 定义游戏核心逻辑的服务接口 +/// +public interface IGameLogicService +{ + /// + /// 处理玩家的涂色动作 + /// + /// 当前游戏房间 + /// 执行动作的玩家 + /// X坐标 + /// Y坐标 + /// 笔刷大小 + void HandlePaintAction(GameRoom gameRoom, Player player, int x, int y, int brushSize); +} \ No newline at end of file -- Gitee From 502b127af17a4c81545a016698f8ae0a434277d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=97=8B?= <2574356905@qq.com> Date: Fri, 15 Aug 2025 10:20:56 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat(DTOs):=20=E6=B7=BB=E5=8A=A0=E7=8E=A9?= =?UTF-8?q?=E5=AE=B6=E9=A2=9C=E8=89=B2=E5=92=8C=E5=A4=B4=E5=83=8F=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=B9=B6=E4=BF=AE=E6=94=B9=E6=8E=92=E8=A1=8C=E6=A6=9C?= =?UTF-8?q?=E9=A1=B9=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在PlayerDto中添加颜色和头像字段以支持个性化设置 --- .../DTOs/LeaderboardItemDto.cs | 12 ++++++------ .../src/TerritoryGame.Application/DTOs/PlayerDto.cs | 10 ++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs index fd5abf7..e3b98ff 100644 --- a/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs +++ b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs @@ -6,17 +6,17 @@ namespace TerritoryGame.Application.DTOs; public class LeaderboardItemDto { /// - /// 玩家昵称 + /// 玩家ID /// - public string PlayerName { get; set; } = string.Empty; + public Guid PlayerId { get; set; } /// - /// 获胜次数 + /// 玩家昵称 /// - public int WinCount { get; set; } + public string PlayerName { get; set; } = string.Empty; /// - /// 总游戏次数 + /// 分数 /// - public int TotalGames { get; set; } + public int Score { get; set; } } \ No newline at end of file diff --git a/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs index 412537c..59a0114 100644 --- a/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs +++ b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs @@ -14,4 +14,14 @@ public class PlayerDto /// 玩家昵称 /// public string NickName { get; set; } = string.Empty; + + /// + /// 玩家颜色 + /// + public string Color { get; set; } = string.Empty; + + /// + /// 玩家头像 + /// + public string Avatar { get; set; } = string.Empty; } \ No newline at end of file -- Gitee