Skip to content

Commit

Permalink
Feature/refactor interaction (#66)
Browse files Browse the repository at this point in the history
* feat: 前端「確認角色身分」5 秒後能夠收到 SignalR 訊息,並列印「確認角色身分結束」

* feat: 女巫使用毒藥
  • Loading branch information
ricksu978 committed Aug 19, 2023
1 parent ed876f8 commit e0d5adb
Show file tree
Hide file tree
Showing 20 changed files with 675 additions and 326 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Wsa.Gaas.Werewolf.Application.Options;
using Wsa.Gaas.Werewolf.Domain.Events;
using Wsa.Gaas.Werewolf.Domain.Exceptions;
using Wsa.Gaas.Werewolf.Domain.Objects;

namespace Wsa.Gaas.Werewolf.Application.Policies;

Expand Down
40 changes: 40 additions & 0 deletions src/BackEnd/src/Application/UseCases/WitchUsePoisonUseCase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Wsa.Gaas.Werewolf.Application.Common;
using Wsa.Gaas.Werewolf.Domain.Events;
using Wsa.Gaas.Werewolf.Domain.Exceptions;

namespace Wsa.Gaas.Werewolf.Application.UseCases
{
public class WitchUsePoisonRequest
{
public ulong DiscordVoiceChannelId { get; set; }
public ulong PlayerId { get; set; }
public ulong TargetPlayerId { get; set; }
}

public class WitchUsePoisonUseCase : UseCase<WitchUsePoisonRequest, WitchUsePoisonEvent>
{
public WitchUsePoisonUseCase(IRepository repository, GameEventBus gameEventBus) : base(repository, gameEventBus)
{
}

public override async Task ExecuteAsync(WitchUsePoisonRequest request, IPresenter<WitchUsePoisonEvent> presenter, CancellationToken cancellationToken = default)
{
// 查
var game = Repository.FindByDiscordChannelId(request.DiscordVoiceChannelId);

if (game == null)
{
throw new GameNotFoundException(request.DiscordVoiceChannelId);
}

// 改
var events = game.WitchUsePoison(request.PlayerId, request.TargetPlayerId);

// 存
await Repository.SaveAsync(game);

// 推
await presenter.PresentAsync(events, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<None Remove="UseCases\NewFile1.txt" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
Expand Down
11 changes: 11 additions & 0 deletions src/BackEnd/src/Domain/Events/WitchUsePoisonEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Wsa.Gaas.Werewolf.Domain.Objects;

namespace Wsa.Gaas.Werewolf.Domain.Events
{
public class WitchUsePoisonEvent : GameEvent
{
public WitchUsePoisonEvent(Game data) : base(data)
{
}
}
}
42 changes: 42 additions & 0 deletions src/BackEnd/src/Domain/Objects/Game.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using Wsa.Gaas.Werewolf.Domain.Common;
using Wsa.Gaas.Werewolf.Domain.Events;
using Wsa.Gaas.Werewolf.Domain.Exceptions;
using Wsa.Gaas.Werewolf.Domain.Objects.Roles;
Expand Down Expand Up @@ -266,5 +267,46 @@ internal List<GameEvent> AnnounceNightResult()
return events;
}

public WitchUsePoisonEvent WitchUsePoison(ulong witchUserId, ulong targetPlayerId)
{
var witch = Players.FirstOrDefault(x => x.UserId == witchUserId);

if (witch is null)
{
throw new PlayerNotFoundException(DiscordVoiceChannelId, witchUserId);
}

if (witch.Role is not Witch)
{
throw new PlayerNotWitchException("Player not witch");
}

if (witch.IsPoisonUsed)
{
throw new GameException("Witch poison is used");
}

// 找出被狼殺的玩家
var targetPlayer = Players.FirstOrDefault(x =>
x.UserId == targetPlayerId
);

if (targetPlayer == null)
{
throw new GameException("Target player not found");
}

if (targetPlayer.IsDead)
{
throw new GameException("Target player is dead");
}

// 標記被女巫毒
targetPlayer.BuffStatus |= BuffStatus.KilledByWitch;

// 標記毒藥已使用
witch.IsPoisonUsed = true;

return new WitchUsePoisonEvent(this);
}
}
1 change: 1 addition & 0 deletions src/BackEnd/src/Domain/Objects/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class Player
public BuffStatus BuffStatus { get; internal set; }
public bool IsDead { get; internal set; }
public bool IsAntidoteUsed { get; internal set; }
public bool IsPoisonUsed { get; internal set; }

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private Player() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Wsa.Gaas.Werewolf.Application.UseCases;
using Wsa.Gaas.Werewolf.Domain.Events;
using Wsa.Gaas.Werewolf.WebApi.Common;

namespace Wsa.Gaas.Werewolf.WebApi.Endpoints
{

public class WitchUsePoisonResponse
{
public required string Message { get; set; }
}

public class WitchUsePoisonEndpoint : WebApiEndpoint<WitchUsePoisonRequest, WitchUsePoisonEvent, WitchUsePoisonResponse>
{
public override void Configure()
{
Post("/games/{DiscordVoiceChannelId}/players/{playerId}:usePoison");
AllowAnonymous();
}

public override async Task<WitchUsePoisonResponse> ExecuteAsync(WitchUsePoisonRequest req, CancellationToken ct)
{
await UseCase.ExecuteAsync(req, this, ct);

if (ViewModel == null)
{
throw new Exception("View Model is null");
}

return ViewModel; // <= 把 ViewModel 轉 JSON
}

public override Task PresentAsync(WitchUsePoisonEvent gameEvent, CancellationToken cancellationToken = default)
{
ViewModel = new WitchUsePoisonResponse
{
Message = "Ok",
};

return Task.CompletedTask;
}
}


}
3 changes: 3 additions & 0 deletions src/BackEnd/src/InterfaceAdapter/WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FastEndpoints;
using FastEndpoints.Swagger;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Text.Json.Serialization;
using Wsa.Gaas.Werewolf.Application.Common;
using Wsa.Gaas.Werewolf.Application.Options;
Expand All @@ -12,6 +13,8 @@
builder.Services.Configure<GameSettingOptions>(
opt => builder.Configuration.Bind(nameof(GameSettingOptions), opt)
);


builder.Services.SwaggerDocument(opt =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<None Remove="Endpoints\NewFile.txt" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FastEndpoints" Version="5.13.0" />
<PackageReference Include="FastEndpoints.Swagger" Version="5.13.0" />
Expand Down
70 changes: 70 additions & 0 deletions src/BackEnd/test/WebApiTests/ATDD/GameTests/WitchUsePoisonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using FastEndpoints;
using Wsa.Gaas.Werewolf.Application.Common;
using Wsa.Gaas.Werewolf.Application.UseCases;
using Wsa.Gaas.Werewolf.Domain.Objects.Roles;
using Wsa.Gaas.Werewolf.Domain.Objects;
using Wsa.Gaas.Werewolf.WebApi.Endpoints;
using Wsa.Gaas.Werewolf.WebApiTests.ATDD.Common;
using Moq;
using Microsoft.Extensions.Options;

namespace Wsa.Gaas.Werewolf.WebApiTests.ATDD.GameTests
{
public class WitchUsePoisonTests
{
readonly WebApiTestServer _server = new();

[OneTimeSetUp]
public async Task OneTimeSetup()
{
await _server.StartAsync();
}

[Test]
public async Task WitchUsePoison()
{
// Arrange
var game = _server.CreateGameBuilder()
.WithRandomDiscordVoiceChannel()
.WithGameStatus(GameStatus.WitchRoundStarted)
.WithRandomPlayers(9)
.Build();

var witch = game.Players.First(x => x.Role is Witch);

var targetPlayer = game.Players.First(x => x.IsDead == false);

var request = new WitchUsePoisonRequest
{
DiscordVoiceChannelId = game.DiscordVoiceChannelId,
PlayerId = witch.UserId,
TargetPlayerId = targetPlayer.UserId,
};

// Act
var response = await _server.Client.POSTAsync<
WitchUsePoisonEndpoint,
WitchUsePoisonRequest,
WitchUsePoisonResponse>(request);

// Assert 200
response.Response.EnsureSuccessStatusCode();

//
var repository = _server.GetRequiredService<IRepository>();

var actualGame = repository.FindByDiscordChannelId(game.DiscordVoiceChannelId);

var actualTargetPlayer = actualGame!.Players.First(x => x.UserId == targetPlayer.UserId);
var actualWitch = actualGame!.Players.First(x => x.Role is Witch);

// 新標記被女巫毒
((actualTargetPlayer.BuffStatus & BuffStatus.KilledByWitch) == BuffStatus.KilledByWitch)
.Should().BeTrue();

// 女巫毒藥已使用
actualWitch.IsPoisonUsed.Should().BeTrue();

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public BackendApi(IOptions<BackendApiEndpointOptions> options)
_apiOptions = options.Value;
_httpClient = new HttpClient
{
BaseAddress = new Uri(_apiOptions.Endpoint),
BaseAddress = new Uri(_apiOptions.Restful),
};

_jsonOptions = new JsonSerializerOptions
Expand Down
Loading

0 comments on commit e0d5adb

Please sign in to comment.