convert to blazor server side render
This commit is contained in:
@@ -1,67 +0,0 @@
|
|||||||
using Shogi.Contracts.Types;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Api.Extensions;
|
|
||||||
|
|
||||||
public static class ContractsExtensions
|
|
||||||
{
|
|
||||||
public static WhichPlayer ToContract(this Domain.ValueObjects.WhichPlayer player)
|
|
||||||
{
|
|
||||||
return player switch
|
|
||||||
{
|
|
||||||
Domain.ValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1,
|
|
||||||
Domain.ValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WhichPiece ToContract(this Domain.ValueObjects.WhichPiece piece)
|
|
||||||
{
|
|
||||||
return piece switch
|
|
||||||
{
|
|
||||||
Domain.ValueObjects.WhichPiece.King => WhichPiece.King,
|
|
||||||
Domain.ValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
|
|
||||||
Domain.ValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
|
|
||||||
Domain.ValueObjects.WhichPiece.Bishop => WhichPiece.Bishop,
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Piece ToContract(this Domain.ValueObjects.Piece piece) => new()
|
|
||||||
{
|
|
||||||
IsPromoted = piece.IsPromoted,
|
|
||||||
Owner = piece.Owner.ToContract(),
|
|
||||||
WhichPiece = piece.WhichPiece.ToContract()
|
|
||||||
};
|
|
||||||
|
|
||||||
public static IReadOnlyList<Piece> ToContract(this List<Domain.ValueObjects.Piece> pieces)
|
|
||||||
{
|
|
||||||
return pieces
|
|
||||||
.Select(ToContract)
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<string, Piece?> ToContract(this ReadOnlyDictionary<string, Domain.ValueObjects.Piece?> boardState) =>
|
|
||||||
boardState.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToContract());
|
|
||||||
|
|
||||||
public static Domain.ValueObjects.WhichPiece ToDomain(this WhichPiece piece)
|
|
||||||
{
|
|
||||||
return piece switch
|
|
||||||
{
|
|
||||||
WhichPiece.King => Domain.ValueObjects.WhichPiece.King,
|
|
||||||
WhichPiece.GoldGeneral => Domain.ValueObjects.WhichPiece.GoldGeneral,
|
|
||||||
WhichPiece.SilverGeneral => Domain.ValueObjects.WhichPiece.SilverGeneral,
|
|
||||||
WhichPiece.Bishop => Domain.ValueObjects.WhichPiece.Bishop,
|
|
||||||
WhichPiece.Rook => Domain.ValueObjects.WhichPiece.Rook,
|
|
||||||
WhichPiece.Knight => Domain.ValueObjects.WhichPiece.Knight,
|
|
||||||
WhichPiece.Lance => Domain.ValueObjects.WhichPiece.Lance,
|
|
||||||
WhichPiece.Pawn => Domain.ValueObjects.WhichPiece.Pawn,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.ResponseCompression;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Shogi.Api;
|
|
||||||
using Shogi.Api.Application;
|
|
||||||
using Shogi.Api.Controllers;
|
|
||||||
using Shogi.Api.Identity;
|
|
||||||
using Shogi.Api.Repositories;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
var allowedOrigins = builder
|
|
||||||
.Configuration
|
|
||||||
.GetSection("Cors:AllowedOrigins")
|
|
||||||
.Get<string[]>() ?? throw new InvalidOperationException("Configuration for allowed origins is missing.");
|
|
||||||
|
|
||||||
builder.Services
|
|
||||||
.AddControllers()
|
|
||||||
.AddJsonOptions(options =>
|
|
||||||
{
|
|
||||||
options.JsonSerializerOptions.WriteIndented = true;
|
|
||||||
});
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
builder.Services.AddTransient<SessionRepository>();
|
|
||||||
builder.Services.AddTransient<QueryRepository>();
|
|
||||||
builder.Services.AddTransient<ShogiApplication>();
|
|
||||||
builder.Services.AddTransient<GameHubContext>();
|
|
||||||
builder.Services.AddHttpClient<IEmailSender, EmailSender>();
|
|
||||||
builder.Services.Configure<ApiKeys>(builder.Configuration.GetSection("ApiKeys"));
|
|
||||||
|
|
||||||
AddIdentity(builder, builder.Configuration);
|
|
||||||
builder.Services.AddSignalR();
|
|
||||||
builder.Services.AddResponseCompression(opts =>
|
|
||||||
{
|
|
||||||
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]);
|
|
||||||
});
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
app.MyMapIdentityApi<ShogiUser>(builder.Environment);
|
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseHttpsRedirection(); // Apache handles HTTPS in production.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
app.UseResponseCompression();
|
|
||||||
}
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI(options => options.DocumentTitle = "Shogi.Api");
|
|
||||||
app.UseAuthorization();
|
|
||||||
app.Map("/", () => "OK");
|
|
||||||
app.MapControllers();
|
|
||||||
app.UseCors(policy =>
|
|
||||||
{
|
|
||||||
policy.WithOrigins(allowedOrigins).AllowAnyHeader().AllowAnyMethod().AllowCredentials();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.MapHub<GameHub>("/gamehub");
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
|
|
||||||
static void AddIdentity(WebApplicationBuilder builder, ConfigurationManager configuration)
|
|
||||||
{
|
|
||||||
builder.Services
|
|
||||||
.AddAuthorizationBuilder()
|
|
||||||
.AddPolicy("Admin", policy =>
|
|
||||||
{
|
|
||||||
policy.RequireAuthenticatedUser();
|
|
||||||
policy.RequireAssertion(context => context.User?.Identity?.Name switch
|
|
||||||
{
|
|
||||||
"Hauth@live.com" => true,
|
|
||||||
"aat-account" => true,
|
|
||||||
_ => false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services
|
|
||||||
.AddDbContext<ApplicationDbContext>(options =>
|
|
||||||
{
|
|
||||||
var cs = configuration.GetConnectionString("ShogiDatabase") ?? throw new InvalidOperationException("Database not configured.");
|
|
||||||
options.UseSqlServer(cs);
|
|
||||||
|
|
||||||
// This is helpful to debug account issues without affecting the database.
|
|
||||||
//options.UseInMemoryDatabase("AppDb");
|
|
||||||
})
|
|
||||||
.AddIdentityApiEndpoints<ShogiUser>(options =>
|
|
||||||
{
|
|
||||||
options.SignIn.RequireConfirmedEmail = true;
|
|
||||||
options.User.RequireUniqueEmail = true;
|
|
||||||
})
|
|
||||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
|
||||||
|
|
||||||
builder.Services.ConfigureApplicationCookie(options =>
|
|
||||||
{
|
|
||||||
options.SlidingExpiration = true;
|
|
||||||
options.ExpireTimeSpan = TimeSpan.FromDays(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"Kestrel": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"VaultUri": "https://gameboardshogiuisocketsv.vault.azure.net/",
|
|
||||||
"AZURE_USERNAME": "Hauth@live.com"
|
|
||||||
},
|
|
||||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:50728/",
|
|
||||||
"sslPort": 44315
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<GenerateDocumentationFile>False</GenerateDocumentationFile>
|
|
||||||
<SignAssembly>False</SignAssembly>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<UserSecretsId>973a1f5f-ef25-4f1c-a24d-b0fc7d016ab8</UserSecretsId>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Repositories\CouchModels\**" />
|
|
||||||
<Content Remove="Repositories\CouchModels\**" />
|
|
||||||
<EmbeddedResource Remove="Repositories\CouchModels\**" />
|
|
||||||
<None Remove="Repositories\CouchModels\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Repositories\GameboardRepository.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
|
||||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
|
||||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Shogi.Contracts\Shogi.Contracts.csproj" />
|
|
||||||
<ProjectReference Include="..\Shogi.Domain\Shogi.Domain.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
|
||||||
<AnalysisLevel>5</AnalysisLevel>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
|
||||||
<Title>Shogi Service Models</Title>
|
|
||||||
<Description>Contains DTOs use for http requests to Shogi backend services.</Description>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Api\Queries\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Shogi.Contracts;
|
|
||||||
|
|
||||||
public class ShogiApiJsonSerializerSettings
|
|
||||||
{
|
|
||||||
public readonly static JsonSerializerOptions SystemTextJsonSerializerOptions = new(JsonSerializerDefaults.Web)
|
|
||||||
{
|
|
||||||
WriteIndented = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Shogi.Contracts.Types
|
|
||||||
{
|
|
||||||
public class Piece
|
|
||||||
{
|
|
||||||
public bool IsPromoted { get; set; }
|
|
||||||
public WhichPiece WhichPiece { get; set; }
|
|
||||||
public WhichPlayer Owner { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
-- Create a user named Shogi.Api
|
-- Create a user named Shogi
|
||||||
|
|
||||||
-- Create a role and grant execute permission to that role
|
-- Create a role and grant execute permission to that role
|
||||||
--CREATE ROLE db_executor
|
--CREATE ROLE db_executor
|
||||||
--GRANT EXECUTE To db_executor
|
--GRANT EXECUTE To db_executor
|
||||||
|
|
||||||
-- Give Shogi.Api user permission to db_executor, db_datareader, db_datawriter
|
-- Give Shogi user permission to db_executor, db_datareader, db_datawriter
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,5 +13,5 @@
|
|||||||
*
|
*
|
||||||
* 2. Setup Entity Framework because that's what the login system uses.
|
* 2. Setup Entity Framework because that's what the login system uses.
|
||||||
* 2.a. Install the Entity Framework dotnet tools, via power shell run this command: dotnet tool install --global dotnet-ef
|
* 2.a. Install the Entity Framework dotnet tools, via power shell run this command: dotnet tool install --global dotnet-ef
|
||||||
* 2.b. To setup the Entity Framework users database, run this powershell command using Shogi.Api as the target project: dotnet ef database update
|
* 2.b. To setup the Entity Framework users database, run this powershell command using Shogi as the target project: dotnet ef database update
|
||||||
*/
|
*/
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Using Include="System" />
|
|
||||||
<Using Include="System.Collections.Generic" />
|
|
||||||
<Using Include="System.Linq" />
|
|
||||||
<Using Include="System.Numerics" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Entities\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
namespace Shogi.Domain.ValueObjects;
|
|
||||||
|
|
||||||
public static class Direction
|
|
||||||
{
|
|
||||||
public static readonly Vector2 Forward = new(0, 1);
|
|
||||||
public static readonly Vector2 Backward = new(0, -1);
|
|
||||||
public static readonly Vector2 Left = new(-1, 0);
|
|
||||||
public static readonly Vector2 Right = new(1, 0);
|
|
||||||
public static readonly Vector2 ForwardLeft = new(-1, 1);
|
|
||||||
public static readonly Vector2 ForwardRight = new(1, 1);
|
|
||||||
public static readonly Vector2 BackwardLeft = new(-1, -1);
|
|
||||||
public static readonly Vector2 BackwardRight = new(1, -1);
|
|
||||||
public static readonly Vector2 KnightLeft = new(-1, 2);
|
|
||||||
public static readonly Vector2 KnightRight = new(1, 2);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
public record MoveResult(bool IsSuccess, string Reason = "")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal record class Bishop : Piece
|
|
||||||
{
|
|
||||||
private static readonly ReadOnlyCollection<Path> BishopPaths = new(new List<Path>(4)
|
|
||||||
{
|
|
||||||
new Path(Direction.ForwardLeft, Distance.MultiStep),
|
|
||||||
new Path(Direction.ForwardRight, Distance.MultiStep),
|
|
||||||
new Path(Direction.BackwardLeft, Distance.MultiStep),
|
|
||||||
new Path(Direction.BackwardRight, Distance.MultiStep)
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> PromotedBishopPaths = new(new List<Path>(8)
|
|
||||||
{
|
|
||||||
new Path(Direction.Forward),
|
|
||||||
new Path(Direction.Left),
|
|
||||||
new Path(Direction.Right),
|
|
||||||
new Path(Direction.Backward),
|
|
||||||
new Path(Direction.ForwardLeft, Distance.MultiStep),
|
|
||||||
new Path(Direction.ForwardRight, Distance.MultiStep),
|
|
||||||
new Path(Direction.BackwardLeft, Distance.MultiStep),
|
|
||||||
new Path(Direction.BackwardRight, Distance.MultiStep)
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
|
||||||
BishopPaths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2PromotedPaths =
|
|
||||||
PromotedBishopPaths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public Bishop(WhichPlayer owner, bool isPromoted = false)
|
|
||||||
: base(WhichPiece.Bishop, owner, isPromoted)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override IEnumerable<Path> MoveSet => IsPromoted ? PromotedBishopPaths : BishopPaths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal record class Knight : Piece
|
|
||||||
{
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(2)
|
|
||||||
{
|
|
||||||
new Path(Direction.KnightLeft),
|
|
||||||
new Path(Direction.KnightRight)
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
|
||||||
Player1Paths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public Knight(WhichPlayer owner, bool isPromoted = false)
|
|
||||||
: base(WhichPiece.Knight, owner, isPromoted)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
|
||||||
{
|
|
||||||
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
|
||||||
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal record class Lance : Piece
|
|
||||||
{
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(1)
|
|
||||||
{
|
|
||||||
new Path(Direction.Forward, Distance.MultiStep),
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
|
||||||
Player1Paths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public Lance(WhichPlayer owner, bool isPromoted = false)
|
|
||||||
: base(WhichPiece.Lance, owner, isPromoted)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
|
||||||
{
|
|
||||||
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
|
||||||
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal record class Pawn : Piece
|
|
||||||
{
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(1)
|
|
||||||
{
|
|
||||||
new Path(Direction.Forward)
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
|
||||||
Player1Paths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public Pawn(WhichPlayer owner, bool isPromoted = false)
|
|
||||||
: base(WhichPiece.Pawn, owner, isPromoted)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
|
||||||
{
|
|
||||||
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
|
||||||
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("{WhichPiece} {Owner}")]
|
|
||||||
public abstract record class Piece
|
|
||||||
{
|
|
||||||
public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
|
||||||
{
|
|
||||||
return piece switch
|
|
||||||
{
|
|
||||||
WhichPiece.King => new King(owner, isPromoted),
|
|
||||||
WhichPiece.GoldGeneral => new GoldGeneral(owner, isPromoted),
|
|
||||||
WhichPiece.SilverGeneral => new SilverGeneral(owner, isPromoted),
|
|
||||||
WhichPiece.Bishop => new Bishop(owner, isPromoted),
|
|
||||||
WhichPiece.Rook => new Rook(owner, isPromoted),
|
|
||||||
WhichPiece.Knight => new Knight(owner, isPromoted),
|
|
||||||
WhichPiece.Lance => new Lance(owner, isPromoted),
|
|
||||||
WhichPiece.Pawn => new Pawn(owner, isPromoted),
|
|
||||||
_ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
public abstract IEnumerable<Path> MoveSet { get; }
|
|
||||||
public WhichPiece WhichPiece { get; }
|
|
||||||
public WhichPlayer Owner { get; private set; }
|
|
||||||
public bool IsPromoted { get; private set; }
|
|
||||||
protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
|
||||||
{
|
|
||||||
WhichPiece = piece;
|
|
||||||
Owner = owner;
|
|
||||||
IsPromoted = isPromoted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanPromote => !IsPromoted
|
|
||||||
&& WhichPiece != WhichPiece.King
|
|
||||||
&& WhichPiece != WhichPiece.GoldGeneral;
|
|
||||||
|
|
||||||
public void Promote() => IsPromoted = CanPromote;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prep the piece for capture by changing ownership and demoting.
|
|
||||||
/// </summary>
|
|
||||||
public void Capture(WhichPlayer newOwner)
|
|
||||||
{
|
|
||||||
Owner = newOwner;
|
|
||||||
IsPromoted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Respecting the move-set of the Piece, collect all positions along the shortest path from start to end.
|
|
||||||
/// Useful if you need to iterate a move-set.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="start"></param>
|
|
||||||
/// <param name="end"></param>
|
|
||||||
/// <returns>An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions.</returns>
|
|
||||||
public IEnumerable<Vector2> GetPathFromStartToEnd(Vector2 start, Vector2 end)
|
|
||||||
{
|
|
||||||
var steps = new List<Vector2>(10);
|
|
||||||
|
|
||||||
var path = GetNearestPath(MoveSet, start, end);
|
|
||||||
var position = start;
|
|
||||||
while (Vector2.Distance(start, position) < Vector2.Distance(start, end))
|
|
||||||
{
|
|
||||||
position += path.Step;
|
|
||||||
steps.Add(position);
|
|
||||||
|
|
||||||
if (path.Distance == Distance.OneStep) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == end)
|
|
||||||
{
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path GetNearestPath(IEnumerable<Path> paths, Vector2 start, Vector2 end)
|
|
||||||
{
|
|
||||||
if (!paths.DefaultIfEmpty().Any())
|
|
||||||
{
|
|
||||||
throw new ArgumentException("No paths to get nearest path from.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var shortestPath = paths.First();
|
|
||||||
foreach (var path in paths.Skip(1))
|
|
||||||
{
|
|
||||||
var distance = Vector2.Distance(start + path.Step, end);
|
|
||||||
var shortestDistance = Vector2.Distance(start + shortestPath.Step, end);
|
|
||||||
if (distance < shortestDistance)
|
|
||||||
{
|
|
||||||
shortestPath = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shortestPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal record class SilverGeneral : Piece
|
|
||||||
{
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(4)
|
|
||||||
{
|
|
||||||
new Path(Direction.Forward),
|
|
||||||
new Path(Direction.ForwardLeft),
|
|
||||||
new Path(Direction.ForwardRight),
|
|
||||||
new Path(Direction.BackwardLeft),
|
|
||||||
new Path(Direction.BackwardRight)
|
|
||||||
});
|
|
||||||
|
|
||||||
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
|
||||||
Player1Paths
|
|
||||||
.Select(p => p.Invert())
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public SilverGeneral(WhichPlayer owner, bool isPromoted = false)
|
|
||||||
: base(WhichPiece.SilverGeneral, owner, isPromoted)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
|
||||||
{
|
|
||||||
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
|
||||||
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
|
||||||
_ => throw new NotImplementedException(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
|
||||||
using BoardTile = System.Collections.Generic.KeyValuePair<System.Numerics.Vector2, Shogi.Domain.ValueObjects.Piece>;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects
|
|
||||||
{
|
|
||||||
internal class StandardRules
|
|
||||||
{
|
|
||||||
private readonly BoardState boardState;
|
|
||||||
|
|
||||||
internal StandardRules(BoardState board)
|
|
||||||
{
|
|
||||||
boardState = board;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the last move put the player who moved in check.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This strategy recognizes that a "discover check" could only occur from a subset of pieces: Rook, Bishop, Lance.
|
|
||||||
/// In this way, only those pieces need to be considered when evaluating if a move placed the moving player in check.
|
|
||||||
/// </remarks>
|
|
||||||
internal bool DidPlayerPutThemselfInCheck()
|
|
||||||
{
|
|
||||||
if (boardState.PreviousMove.From == null)
|
|
||||||
{
|
|
||||||
// You can't place yourself in check by placing a piece from your hand.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var previousMovedPiece = boardState[boardState.PreviousMove.To];
|
|
||||||
if (previousMovedPiece == null) throw new ArgumentNullException(nameof(previousMovedPiece), $"No piece exists at position {boardState.PreviousMove.To}.");
|
|
||||||
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition;
|
|
||||||
|
|
||||||
|
|
||||||
var isDiscoverCheck = false;
|
|
||||||
// Get line equation from king through the now-unoccupied location.
|
|
||||||
var direction = Vector2.Subtract(kingPosition, boardState.PreviousMove.From.Value);
|
|
||||||
var slope = Math.Abs(direction.Y / direction.X);
|
|
||||||
var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMove.From.Value, Vector2.Normalize(direction));
|
|
||||||
var threat = boardState.QueryFirstPieceInPath(path);
|
|
||||||
if (threat == null || threat.Owner == previousMovedPiece.Owner) return false;
|
|
||||||
// If absolute slope is 45°, look for a bishop along the line.
|
|
||||||
// If absolute slope is 0° or 90°, look for a rook along the line.
|
|
||||||
// if absolute slope is 0°, look for lance along the line.
|
|
||||||
if (float.IsInfinity(slope))
|
|
||||||
{
|
|
||||||
isDiscoverCheck = threat.WhichPiece switch
|
|
||||||
{
|
|
||||||
WhichPiece.Lance => !threat.IsPromoted,
|
|
||||||
WhichPiece.Rook => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (slope == 1)
|
|
||||||
{
|
|
||||||
isDiscoverCheck = threat.WhichPiece switch
|
|
||||||
{
|
|
||||||
WhichPiece.Bishop => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (slope == 0)
|
|
||||||
{
|
|
||||||
isDiscoverCheck = threat.WhichPiece switch
|
|
||||||
{
|
|
||||||
WhichPiece.Rook => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return isDiscoverCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsOpponentInCheckAfterMove() => IsOpposingKingThreatenedByPosition(boardState.PreviousMove.To);
|
|
||||||
|
|
||||||
internal bool IsOpposingKingThreatenedByPosition(Vector2 position)
|
|
||||||
{
|
|
||||||
var previousMovedPiece = boardState[position];
|
|
||||||
if (previousMovedPiece == null) return false;
|
|
||||||
|
|
||||||
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition;
|
|
||||||
var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition);
|
|
||||||
var threatenedPiece = boardState.QueryFirstPieceInPath(path);
|
|
||||||
if (!path.Any() || threatenedPiece == null) return false;
|
|
||||||
|
|
||||||
return threatenedPiece.WhichPiece == WhichPiece.King;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsOpponentInCheckMate()
|
|
||||||
{
|
|
||||||
// Assume checkmate, then try to disprove.
|
|
||||||
if (!boardState.InCheck.HasValue) return false;
|
|
||||||
// Get all pieces from opponent who threaten the king in question.
|
|
||||||
var opponent = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
|
||||||
var tilesOccupiedByOpponent = boardState.GetTilesOccupiedBy(opponent);
|
|
||||||
var kingPosition = boardState.WhoseTurn == WhichPlayer.Player1
|
|
||||||
? boardState.Player1KingPosition
|
|
||||||
: boardState.Player2KingPosition;
|
|
||||||
var threats = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList();
|
|
||||||
if (threats.Count == 1)
|
|
||||||
{
|
|
||||||
/* If there is exactly one threat it is possible to block the check.
|
|
||||||
* Foreach piece owned by whichPlayer
|
|
||||||
* if piece can intercept check, return false;
|
|
||||||
*/
|
|
||||||
var threat = threats.Single();
|
|
||||||
var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition);
|
|
||||||
var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn);
|
|
||||||
foreach (var threatBlockingPosition in pathFromThreatToKing)
|
|
||||||
{
|
|
||||||
var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat
|
|
||||||
.Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (tilesThatDoBlockThreat.Any())
|
|
||||||
{
|
|
||||||
return false; // Cannot be check-mate if a piece can intercept the threat.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* If no ability to block the check, maybe the king can evade check by moving.
|
|
||||||
*/
|
|
||||||
|
|
||||||
foreach (var maybeSafePosition in GetPossiblePositionsForKing(boardState.WhoseTurn))
|
|
||||||
{
|
|
||||||
threats = tilesOccupiedByOpponent
|
|
||||||
.Where(tile => PieceHasLineOfSight(tile, maybeSafePosition))
|
|
||||||
.ToList();
|
|
||||||
if (!threats.Any())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Vector2> GetPossiblePositionsForKing(WhichPlayer whichPlayer)
|
|
||||||
{
|
|
||||||
var kingPosition = whichPlayer == WhichPlayer.Player1
|
|
||||||
? boardState.Player1KingPosition
|
|
||||||
: boardState.Player2KingPosition;
|
|
||||||
|
|
||||||
var paths = boardState[kingPosition]!.MoveSet;
|
|
||||||
return paths
|
|
||||||
.Select(path => path.Step + kingPosition)
|
|
||||||
// Because the king could be on the edge of the board, where some of its paths do not make sense.
|
|
||||||
.Where(newPosition => newPosition.IsInsideBoardBoundary())
|
|
||||||
// Where tile at position is empty, meaning the king could move there.
|
|
||||||
.Where(newPosition => boardState[newPosition] == null)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget)
|
|
||||||
{
|
|
||||||
var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget);
|
|
||||||
return path
|
|
||||||
.SkipLast(1)
|
|
||||||
.All(position => boardState[Notation.ToBoardNotation(position)] == null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Shogi.Domain.ValueObjects;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.YetToBeAssimilatedIntoDDD
|
|
||||||
{
|
|
||||||
internal static class DomainExtensions
|
|
||||||
{
|
|
||||||
public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King;
|
|
||||||
|
|
||||||
public static bool IsBetween(this float self, float min, float max)
|
|
||||||
{
|
|
||||||
return self >= min && self <= max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsInsideBoardBoundary(this Vector2 self)
|
|
||||||
{
|
|
||||||
return self.X.IsBetween(0, 8) && self.Y.IsBetween(0, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Shogi.Domain.YetToBeAssimilatedIntoDDD
|
|
||||||
{
|
|
||||||
public static class Notation
|
|
||||||
{
|
|
||||||
private static readonly string BoardNotationRegex = @"(?<file>[A-I])(?<rank>[1-9])";
|
|
||||||
private static readonly char A = 'A';
|
|
||||||
|
|
||||||
public static string ToBoardNotation(Vector2 vector)
|
|
||||||
{
|
|
||||||
return ToBoardNotation((int)vector.X, (int)vector.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToBoardNotation(int x, int y)
|
|
||||||
{
|
|
||||||
var file = (char)(x + A);
|
|
||||||
var rank = y + 1;
|
|
||||||
return $"{file}{rank}";
|
|
||||||
}
|
|
||||||
public static Vector2 FromBoardNotation(string notation)
|
|
||||||
{
|
|
||||||
if (Regex.IsMatch(notation, BoardNotationRegex))
|
|
||||||
{
|
|
||||||
var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase);
|
|
||||||
char file = match.Groups["file"].Value[0];
|
|
||||||
int rank = int.Parse(match.Groups["rank"].Value);
|
|
||||||
return new Vector2(file - A, rank - 1);
|
|
||||||
}
|
|
||||||
throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Shogi.Domain
|
|
||||||
|
|
||||||
### TODO:
|
|
||||||
* There are enough non-DDD classes around navigating the standard 9x9 Shogi board that probably a new value object or entity is merited. See classes within Pathing folder as well as Notation.cs.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {
|
|
||||||
"microsoft.dotnet-msidentity": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"commands": [
|
|
||||||
"dotnet-msidentity"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
|
||||||
|
|
||||||
namespace Shogi.UI.Identity;
|
|
||||||
|
|
||||||
public class CookieCredentialsMessageHandler : DelegatingHandler
|
|
||||||
{
|
|
||||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
|
|
||||||
request.Headers.Add("X-Requested-With", ["XMLHttpRequest"]);
|
|
||||||
|
|
||||||
return base.SendAsync(request, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@using Shogi.UI.Pages.Home.VisualAids
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@using Contracts.Types;
|
|
||||||
|
|
||||||
<GameBoardPresentation Perspective="WhichPlayer.Player1" />
|
|
||||||
|
|
||||||
@code {
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
using Microsoft.AspNetCore.ResponseCompression;
|
|
||||||
using Shogi.UI;
|
|
||||||
using Shogi.UI.Identity;
|
|
||||||
using Shogi.UI.Shared;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
|
||||||
builder.RootComponents.Add<App>("#app");
|
|
||||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
|
||||||
builder.Logging.AddConfiguration(
|
|
||||||
builder.Configuration.GetSection("Logging"));
|
|
||||||
ConfigureDependencies(builder.Services, builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddResponseCompression(options =>
|
|
||||||
{
|
|
||||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
|
||||||
|
|
||||||
static void ConfigureDependencies(IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Why two HTTP clients?
|
|
||||||
* See https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?source=recommendations&view=aspnetcore-6.0#unauthenticated-or-unauthorized-web-api-requests-in-an-app-with-a-secure-default-client
|
|
||||||
*/
|
|
||||||
var baseUrl = configuration["ShogiApiUrl"];
|
|
||||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ShogiApiUrl configuration is missing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var shogiApiUrl = new Uri(baseUrl, UriKind.Absolute);
|
|
||||||
|
|
||||||
services
|
|
||||||
.AddTransient<CookieCredentialsMessageHandler>()
|
|
||||||
.AddTransient<ILocalStorage, LocalStorage>();
|
|
||||||
|
|
||||||
// Identity
|
|
||||||
services
|
|
||||||
.AddAuthorizationCore(options => options.AddPolicy("Admin", policy => policy.RequireUserName("Hauth@live.com")))
|
|
||||||
.AddScoped<AuthenticationStateProvider, CookieAuthenticationStateProvider>()
|
|
||||||
.AddScoped(sp => (IAccountManagement)sp.GetRequiredService<AuthenticationStateProvider>())
|
|
||||||
.AddHttpClient("Auth", client => client.BaseAddress = shogiApiUrl) // "Auth" is the name expected by the auth library.
|
|
||||||
.AddHttpMessageHandler<CookieCredentialsMessageHandler>();
|
|
||||||
|
|
||||||
// Network clients
|
|
||||||
services
|
|
||||||
.AddHttpClient<ShogiApi>(client => client.BaseAddress = shogiApiUrl)
|
|
||||||
.AddHttpMessageHandler<CookieCredentialsMessageHandler>();
|
|
||||||
services
|
|
||||||
.AddSingleton<GameHubNode>();
|
|
||||||
|
|
||||||
|
|
||||||
var serializerOptions = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
};
|
|
||||||
services.AddSingleton((sp) => serializerOptions);
|
|
||||||
}
|
|
||||||
63
Shogi.UI/Properties/Resources.Designer.cs
generated
63
Shogi.UI/Properties/Resources.Designer.cs
generated
@@ -1,63 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
// <auto-generated>
|
|
||||||
// This code was generated by a tool.
|
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
|
||||||
// the code is regenerated.
|
|
||||||
// </auto-generated>
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace Shogi.UI.Properties {
|
|
||||||
using System;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
|
||||||
/// </summary>
|
|
||||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
|
||||||
// class via a tool like ResGen or Visual Studio.
|
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
|
||||||
// with the /str option, or rebuild your VS project.
|
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
|
||||||
internal class Resources {
|
|
||||||
|
|
||||||
private static global::System.Resources.ResourceManager resourceMan;
|
|
||||||
|
|
||||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
|
||||||
|
|
||||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
|
||||||
internal Resources() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the cached ResourceManager instance used by this class.
|
|
||||||
/// </summary>
|
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
|
||||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
|
||||||
get {
|
|
||||||
if (object.ReferenceEquals(resourceMan, null)) {
|
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shogi.UI.Properties.Resources", typeof(Resources).Assembly);
|
|
||||||
resourceMan = temp;
|
|
||||||
}
|
|
||||||
return resourceMan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overrides the current thread's CurrentUICulture property for all
|
|
||||||
/// resource lookups using this strongly typed resource class.
|
|
||||||
/// </summary>
|
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
|
||||||
internal static global::System.Globalization.CultureInfo Culture {
|
|
||||||
get {
|
|
||||||
return resourceCulture;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
resourceCulture = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<!--
|
|
||||||
Microsoft ResX Schema
|
|
||||||
|
|
||||||
Version 1.3
|
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
|
||||||
that is mostly human readable. The generation and parsing of the
|
|
||||||
various data types are done through the TypeConverter classes
|
|
||||||
associated with the data types.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
|
||||||
<resheader name="version">1.3</resheader>
|
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
|
||||||
<data name="Name1">this is my long string</data>
|
|
||||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
|
||||||
[base64 mime encoded serialized .NET Framework object]
|
|
||||||
</data>
|
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
|
||||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
|
||||||
</data>
|
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
|
||||||
name/value pairs.
|
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
|
||||||
text/value conversion through the TypeConverter architecture.
|
|
||||||
Classes that don't support this are serialized and stored with the
|
|
||||||
mimetype set.
|
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
|
||||||
read any of the formats listed below.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
|
||||||
value : The object must be serialized into a byte array
|
|
||||||
: using a System.ComponentModel.TypeConverter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>1.3</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
</root>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"Shogi.UI": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
},
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
|
||||||
"applicationUrl": "https://localhost:3000",
|
|
||||||
"nativeDebugging": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"identityapp1": {
|
|
||||||
"type": "identityapp",
|
|
||||||
"dynamicId": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"identityapp1": {
|
|
||||||
"type": "identityapp.default",
|
|
||||||
"dynamicId": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
using Shogi.Contracts.Api.Commands;
|
|
||||||
using Shogi.Contracts.Types;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Reflection.Metadata.Ecma335;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Shogi.UI.Shared;
|
|
||||||
|
|
||||||
public class ShogiApi(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
private readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
||||||
|
|
||||||
public async Task Register(string email, string password)
|
|
||||||
{
|
|
||||||
var response = await httpClient.PostAsJsonAsync(Relative("register"), new { email, password });
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoginEventArgs(string email, string password)
|
|
||||||
{
|
|
||||||
var response = await httpClient.PostAsJsonAsync("login?useCookies=true", new { email, password });
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Logout()
|
|
||||||
{
|
|
||||||
var response = await httpClient.PutAsync(Relative("User/GuestLogout"), null);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Session?> GetSession(string name)
|
|
||||||
{
|
|
||||||
var response = await httpClient.GetAsync(Relative($"Sessions/{name}"));
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return (await response.Content.ReadFromJsonAsync<Session>(this.serializerOptions));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SessionMetadata[]> GetAllSessionsMetadata()
|
|
||||||
{
|
|
||||||
var response = await httpClient.GetAsync(Relative("Sessions"));
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return (await response.Content.ReadFromJsonAsync<SessionMetadata[]>(this.serializerOptions))!;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns false if the move was not accepted by the server.
|
|
||||||
/// </summary>
|
|
||||||
public async Task<bool> Move(Guid sessionName, MovePieceCommand command)
|
|
||||||
{
|
|
||||||
var response = await httpClient.PatchAsync(Relative($"Sessions/{sessionName}/Move"), JsonContent.Create(command));
|
|
||||||
return response.IsSuccessStatusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> PostSession()
|
|
||||||
{
|
|
||||||
var response = await httpClient.PostAsync(Relative("Sessions"), null);
|
|
||||||
var sessionId = response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : null;
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> PatchJoinGame(string name)
|
|
||||||
{
|
|
||||||
return httpClient.PatchAsync(Relative($"Sessions/{name}/Join"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> DeleteSession(Guid sessionId)
|
|
||||||
{
|
|
||||||
return httpClient.DeleteAsync(Relative($"Sessions/{sessionId}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Uri Relative(string path) => new(path, UriKind.Relative);
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="zzzNotSure\**" />
|
|
||||||
<Content Remove="zzzNotSure\**" />
|
|
||||||
<EmbeddedResource Remove="zzzNotSure\**" />
|
|
||||||
<None Remove="zzzNotSure\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="Pages\Home\VisualAids\PromotedPieceVisualAid.razor.css" />
|
|
||||||
<None Remove="Pages\Play\GameBoard\OpponentName.razor.css" />
|
|
||||||
<None Remove="Pages\Play\GameBoard\PlayerName.razor.css" />
|
|
||||||
<None Remove="Pages\Play\GameBrowserEntry.razor.css" />
|
|
||||||
<None Remove="Pages\SearchPage.razor.css" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="Pages\Home\VisualAids\PromotedPieceVisualAid.razor.css" />
|
|
||||||
<Content Include="Pages\Play\GameBoard\OpponentName.razor.css" />
|
|
||||||
<Content Include="Pages\Play\GameBoard\PlayerName.razor.css" />
|
|
||||||
<Content Include="Pages\Play\GameBrowserEntry.razor.css" />
|
|
||||||
<Content Include="Pages\SearchPage.razor.css" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client.Core" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Shogi.Contracts\Shogi.Contracts.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Warning",
|
|
||||||
"Microsoft": "Warning",
|
|
||||||
"Microsoft.Hosting.Lifetime": "Error",
|
|
||||||
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
|
|
||||||
"System.Net.Http.HttpClient": "Error",
|
|
||||||
"Microsoft.AspNetCore.SignalR": "Debug",
|
|
||||||
"Microsoft.AspNetCore.Http.Connections": "Debug"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ShogiApiUrl": "https://localhost:5001",
|
|
||||||
"ShogiApiUrl2": "https://api.lucaserver.space/Shogi.Api/"
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
||||||
<title>Shogi.UI</title>
|
|
||||||
<script type="text/javascript">
|
|
||||||
const base = document.createElement('base');
|
|
||||||
base.href = window.location.href.includes("localhost")
|
|
||||||
? "/"
|
|
||||||
: "/shogi/";
|
|
||||||
document.head.appendChild(base);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link href="css/app.css" rel="stylesheet" />
|
|
||||||
<link href="css/themes.css" rel="stylesheet" />
|
|
||||||
<link href="Shogi.UI.styles.css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
|
|
||||||
<div id="blazor-error-ui">
|
|
||||||
An unhandled error has occurred.
|
|
||||||
<a href="" class="reload">Reload</a>
|
|
||||||
<a class="dismiss">🗙</a>
|
|
||||||
</div>
|
|
||||||
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
|
|
||||||
<script src="_framework/blazor.webassembly.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
71
Shogi.sln
71
Shogi.sln
@@ -1,18 +1,8 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 18.3.11312.210
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Domain", "Shogi.Domain\Shogi.Domain.csproj", "{0211B1E4-20F0-4058-AAC4-3845D19910AF}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A968C8E6-47B7-4F72-A27A-AC9B643FD320}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.AcceptanceTests", "Tests\AcceptanceTests\Shogi.AcceptanceTests.csproj", "{30F4E3DB-027F-4885-BE06-884167C1C6CF}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Contracts", "Shogi.Contracts\Shogi.Contracts.csproj", "{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E69DE334-29A7-46AE-9647-54DC0187CD8D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E69DE334-29A7-46AE-9647-54DC0187CD8D}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
@@ -22,65 +12,52 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.UI", "Shogi.UI\Shogi.UI.csproj", "{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}"
|
|
||||||
EndProject
|
|
||||||
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "Shogi.Database", "Shogi.Database\Shogi.Database.sqlproj", "{9B115B71-088F-41EF-858F-C7B155271A9F}"
|
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "Shogi.Database", "Shogi.Database\Shogi.Database.sqlproj", "{9B115B71-088F-41EF-858F-C7B155271A9F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Api", "Shogi.Api\Shogi.Api.csproj", "{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "Tests\E2ETests\E2ETests.csproj", "{401120C3-45D6-4A23-8D87-C2BED29F4950}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoardRules", "BoardRules\BoardRules.csproj", "{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoardRules", "BoardRules\BoardRules.csproj", "{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{20DA20BB-85F1-4DBE-9B22-3C4FAF89647B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi.AcceptanceTests", "Tests\AcceptanceTests\Shogi.AcceptanceTests.csproj", "{768F37ED-FB62-A57F-BCFA-91F26B4F794F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{9D1DD2CD-7B04-4472-4377-027563F356CA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shogi", "Shogi\Shogi.csproj", "{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0211B1E4-20F0-4058-AAC4-3845D19910AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{30F4E3DB-027F-4885-BE06-884167C1C6CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{30F4E3DB-027F-4885-BE06-884167C1C6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{30F4E3DB-027F-4885-BE06-884167C1C6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{1B9445A9-9C4D-46E3-8027-926C7FE0B55B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{12B90F81-AFE6-4CD5-8517-218C0D70A1B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
{9B115B71-088F-41EF-858F-C7B155271A9F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
{9B115B71-088F-41EF-858F-C7B155271A9F}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{62604006-6E18-45DA-8D5A-6ADD1C6D3CE2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{401120C3-45D6-4A23-8D87-C2BED29F4950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{401120C3-45D6-4A23-8D87-C2BED29F4950}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{768F37ED-FB62-A57F-BCFA-91F26B4F794F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9D1DD2CD-7B04-4472-4377-027563F356CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9D1DD2CD-7B04-4472-4377-027563F356CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9D1DD2CD-7B04-4472-4377-027563F356CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9D1DD2CD-7B04-4472-4377-027563F356CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E6BEF2A0-4372-D199-EF2D-F92A890DBC3A}.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
|
||||||
{4F93F735-DCCE-4A5D-ADDC-E0986DE4C48D} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320}
|
{768F37ED-FB62-A57F-BCFA-91F26B4F794F} = {20DA20BB-85F1-4DBE-9B22-3C4FAF89647B}
|
||||||
{30F4E3DB-027F-4885-BE06-884167C1C6CF} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320}
|
{9D1DD2CD-7B04-4472-4377-027563F356CA} = {20DA20BB-85F1-4DBE-9B22-3C4FAF89647B}
|
||||||
{401120C3-45D6-4A23-8D87-C2BED29F4950} = {A968C8E6-47B7-4F72-A27A-AC9B643FD320}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB}
|
SolutionGuid = {1D0B04F2-0DA1-4CB4-A82A-5A1C3B52ACEB}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Api;
|
namespace Shogi;
|
||||||
|
|
||||||
public class ApiKeys
|
public class ApiKeys
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace Shogi.Api.Application;
|
namespace Shogi.BackEnd.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to receive signals from connected clients.
|
/// Used to receive signals from connected clients.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace Shogi.Api.Application;
|
namespace Shogi.BackEnd.Application;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to send signals to connected clients.
|
/// Used to send signals to connected clients.
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Shogi.Api.Controllers;
|
using Shogi.BackEnd.Controllers;
|
||||||
using Shogi.Api.Extensions;
|
using Shogi.BackEnd.Extensions;
|
||||||
using Shogi.Api.Identity;
|
using Shogi.BackEnd.Identity;
|
||||||
using Shogi.Api.Repositories;
|
using Shogi.BackEnd.Repositories;
|
||||||
using Shogi.Api.Repositories.Dto;
|
using Shogi.BackEnd.Repositories.Dto;
|
||||||
using Shogi.Contracts.Api.Commands;
|
using Shogi.BackEnd.Domains.Aggregates;
|
||||||
using Shogi.Domain.Aggregates;
|
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
namespace Shogi.Api.Application;
|
namespace Shogi.BackEnd.Application;
|
||||||
|
|
||||||
public class ShogiApplication(
|
public class ShogiApplication(
|
||||||
QueryRepository queryRepository,
|
QueryRepository queryRepository,
|
||||||
@@ -72,10 +71,10 @@ public class ShogiApplication(
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> MovePiece(string playerId, string sessionId, MovePieceCommand command)
|
public async Task<IActionResult> MovePiece(string playerId, string sessionId, Types.MovePieceCommand command)
|
||||||
{
|
{
|
||||||
var session = await this.ReadSession(sessionId);
|
var session = await this.ReadSession(sessionId);
|
||||||
if (session == null)
|
if (session is null)
|
||||||
{
|
{
|
||||||
return new NotFoundResult();
|
return new NotFoundResult();
|
||||||
}
|
}
|
||||||
@@ -105,7 +104,7 @@ public class ShogiApplication(
|
|||||||
public async Task<IActionResult> JoinSession(string sessionId, string player2Id)
|
public async Task<IActionResult> JoinSession(string sessionId, string player2Id)
|
||||||
{
|
{
|
||||||
var session = await this.ReadSession(sessionId);
|
var session = await this.ReadSession(sessionId);
|
||||||
if (session == null) return new NotFoundResult();
|
if (session is null) return new NotFoundResult();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.Player2))
|
if (string.IsNullOrEmpty(session.Player2))
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Shogi.Api.Identity;
|
using Shogi.BackEnd.Identity;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace Shogi.Api.Controllers;
|
namespace Shogi.BackEnd.Controllers;
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace Shogi.Api.Controllers;
|
namespace Shogi.BackEnd.Controllers;
|
||||||
|
|
||||||
public static class Extentions
|
public static class Extentions
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
namespace Shogi.Api.Controllers;
|
namespace Shogi.BackEnd.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add identity endpoints.
|
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add identity endpoints.
|
||||||
@@ -407,7 +407,7 @@ public static class MyIdentityApiEndpointRouteBuilderExtensions
|
|||||||
routeValues.Add("changedEmail", email);
|
routeValues.Add("changedEmail", email);
|
||||||
}
|
}
|
||||||
|
|
||||||
HostString? host = environment.IsDevelopment() ? null : new HostString("api.lucaserver.space/Shogi.Api");
|
HostString? host = environment.IsDevelopment() ? null : new HostString("api.lucaserver.space/Shogi");
|
||||||
var confirmEmailUrl = linkGenerator.GetUriByName(context, confirmEmailEndpointName, routeValues, host: host)
|
var confirmEmailUrl = linkGenerator.GetUriByName(context, confirmEmailEndpointName, routeValues, host: host)
|
||||||
?? throw new NotSupportedException($"Could not find endpoint named '{confirmEmailEndpointName}'.");
|
?? throw new NotSupportedException($"Could not find endpoint named '{confirmEmailEndpointName}'.");
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Shogi.Api.Application;
|
using Shogi.BackEnd.Application;
|
||||||
using Shogi.Api.Extensions;
|
using Shogi.BackEnd.Extensions;
|
||||||
using Shogi.Api.Repositories;
|
using Shogi.BackEnd.Repositories;
|
||||||
using Shogi.Contracts.Api.Commands;
|
using Shogi.BackEnd.Types;
|
||||||
using Shogi.Contracts.Types;
|
|
||||||
|
|
||||||
namespace Shogi.Api.Controllers;
|
namespace Shogi.BackEnd.Controllers;
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
@@ -57,25 +56,25 @@ public class SessionsController(
|
|||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult<Session>> GetSession(Guid sessionId)
|
public async Task<ActionResult<Session>> GetSession(Guid sessionId)
|
||||||
{
|
{
|
||||||
var session = await application.ReadSession(sessionId.ToString());
|
var domainSession = await application.ReadSession(sessionId.ToString());
|
||||||
if (session == null) return this.NotFound();
|
if (domainSession is null) return this.NotFound();
|
||||||
|
|
||||||
return new Session
|
return new Session
|
||||||
{
|
{
|
||||||
BoardState = new BoardState
|
BoardState = new BoardState
|
||||||
{
|
{
|
||||||
Board = session.Board.BoardState.State.ToContract(),
|
Board = domainSession.Board.BoardState.State.ToContract(),
|
||||||
Player1Hand = session.Board.BoardState.Player1Hand.ToContract(),
|
Player1Hand = domainSession.Board.BoardState.Player1Hand.ToContract(),
|
||||||
Player2Hand = session.Board.BoardState.Player2Hand.ToContract(),
|
Player2Hand = domainSession.Board.BoardState.Player2Hand.ToContract(),
|
||||||
PlayerInCheck = session.Board.BoardState.InCheck?.ToContract(),
|
PlayerInCheck = domainSession.Board.BoardState.InCheck?.ToContract(),
|
||||||
WhoseTurn = session.Board.BoardState.WhoseTurn.ToContract(),
|
WhoseTurn = domainSession.Board.BoardState.WhoseTurn.ToContract(),
|
||||||
Victor = session.Board.BoardState.IsCheckmate
|
Victor = domainSession.Board.BoardState.IsCheckmate
|
||||||
? session.Board.BoardState.InCheck == Domain.ValueObjects.WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1
|
? domainSession.Board.BoardState.InCheck == Domains.ValueObjects.WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1
|
||||||
: null
|
: null
|
||||||
},
|
},
|
||||||
Player1 = application.GetUsername(session.Player1),
|
Player1 = application.GetUsername(domainSession.Player1),
|
||||||
Player2 = application.GetUsername(session.Player2),
|
Player2 = application.GetUsername(domainSession.Player2),
|
||||||
SessionId = session.Id
|
SessionId = domainSession.Id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Shogi.Domain.ValueObjects;
|
using Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Rules;
|
||||||
|
|
||||||
namespace Shogi.Domain.Aggregates;
|
namespace Shogi.BackEnd.Domains.Aggregates;
|
||||||
|
|
||||||
public class Session(Guid id, string player1Name)
|
public class Session(Guid id, string player1Name)
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
using System.Numerics;
|
||||||
using BoardTile = System.Collections.Generic.KeyValuePair<System.Numerics.Vector2, Shogi.Domain.ValueObjects.Piece>;
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD;
|
||||||
|
using BoardTile = System.Collections.Generic.KeyValuePair<System.Numerics.Vector2, Shogi.BackEnd.Domains.ValueObjects.Pieces.Piece>;
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
|
||||||
public class BoardState
|
public class BoardState
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
internal enum InCheckResult
|
internal enum InCheckResult
|
||||||
17
Shogi/BackEnd/Domains/ValueObjects/Movement/Direction.cs
Normal file
17
Shogi/BackEnd/Domains/ValueObjects/Movement/Direction.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
|
||||||
|
public static class Direction
|
||||||
|
{
|
||||||
|
public static readonly Vector2 Forward = new(0, 1);
|
||||||
|
public static readonly Vector2 Backward = new(0, -1);
|
||||||
|
public static readonly Vector2 Left = new(-1, 0);
|
||||||
|
public static readonly Vector2 Right = new(1, 0);
|
||||||
|
public static readonly Vector2 ForwardLeft = new(-1, 1);
|
||||||
|
public static readonly Vector2 ForwardRight = new(1, 1);
|
||||||
|
public static readonly Vector2 BackwardLeft = new(-1, -1);
|
||||||
|
public static readonly Vector2 BackwardRight = new(1, -1);
|
||||||
|
public static readonly Vector2 KnightLeft = new(-1, 2);
|
||||||
|
public static readonly Vector2 KnightRight = new(1, 2);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
|
||||||
public enum Distance
|
public enum Distance
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Shogi.Domain.ValueObjects;
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single piece being moved by a player from <paramref name="From"/> to <paramref name="To"/>.
|
/// Represents a single piece being moved by a player from <paramref name="From"/> to <paramref name="To"/>.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
|
||||||
|
public record MoveResult(bool IsSuccess, string Reason = "")
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects.Movement;
|
namespace Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
|
||||||
[DebuggerDisplay("{Step} - {Distance}")]
|
[DebuggerDisplay("{Step} - {Distance}")]
|
||||||
public record Path
|
public record Path
|
||||||
47
Shogi/BackEnd/Domains/ValueObjects/Pieces/Bishop.cs
Normal file
47
Shogi/BackEnd/Domains/ValueObjects/Pieces/Bishop.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
internal record class Bishop : Piece
|
||||||
|
{
|
||||||
|
private static readonly ReadOnlyCollection<Path> BishopPaths = new(new List<Path>(4)
|
||||||
|
{
|
||||||
|
new Path(Direction.ForwardLeft, Distance.MultiStep),
|
||||||
|
new Path(Direction.ForwardRight, Distance.MultiStep),
|
||||||
|
new Path(Direction.BackwardLeft, Distance.MultiStep),
|
||||||
|
new Path(Direction.BackwardRight, Distance.MultiStep)
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> PromotedBishopPaths = new(new List<Path>(8)
|
||||||
|
{
|
||||||
|
new Path(Direction.Forward),
|
||||||
|
new Path(Direction.Left),
|
||||||
|
new Path(Direction.Right),
|
||||||
|
new Path(Direction.Backward),
|
||||||
|
new Path(Direction.ForwardLeft, Distance.MultiStep),
|
||||||
|
new Path(Direction.ForwardRight, Distance.MultiStep),
|
||||||
|
new Path(Direction.BackwardLeft, Distance.MultiStep),
|
||||||
|
new Path(Direction.BackwardRight, Distance.MultiStep)
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
||||||
|
BishopPaths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2PromotedPaths =
|
||||||
|
PromotedBishopPaths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public Bishop(WhichPlayer owner, bool isPromoted = false)
|
||||||
|
: base(WhichPiece.Bishop, owner, isPromoted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Path> MoveSet => IsPromoted ? PromotedBishopPaths : BishopPaths;
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
internal record class GoldGeneral : Piece
|
internal record class GoldGeneral : Piece
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
internal record class King : Piece
|
internal record class King : Piece
|
||||||
{
|
{
|
||||||
32
Shogi/BackEnd/Domains/ValueObjects/Pieces/Knight.cs
Normal file
32
Shogi/BackEnd/Domains/ValueObjects/Pieces/Knight.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
internal record class Knight : Piece
|
||||||
|
{
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(2)
|
||||||
|
{
|
||||||
|
new Path(Direction.KnightLeft),
|
||||||
|
new Path(Direction.KnightRight)
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
||||||
|
Player1Paths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public Knight(WhichPlayer owner, bool isPromoted = false)
|
||||||
|
: base(WhichPiece.Knight, owner, isPromoted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
||||||
|
{
|
||||||
|
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
||||||
|
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
31
Shogi/BackEnd/Domains/ValueObjects/Pieces/Lance.cs
Normal file
31
Shogi/BackEnd/Domains/ValueObjects/Pieces/Lance.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
internal record class Lance : Piece
|
||||||
|
{
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(1)
|
||||||
|
{
|
||||||
|
new Path(Direction.Forward, Distance.MultiStep),
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
||||||
|
Player1Paths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public Lance(WhichPlayer owner, bool isPromoted = false)
|
||||||
|
: base(WhichPiece.Lance, owner, isPromoted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
||||||
|
{
|
||||||
|
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
||||||
|
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
31
Shogi/BackEnd/Domains/ValueObjects/Pieces/Pawn.cs
Normal file
31
Shogi/BackEnd/Domains/ValueObjects/Pieces/Pawn.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
internal record class Pawn : Piece
|
||||||
|
{
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(1)
|
||||||
|
{
|
||||||
|
new Path(Direction.Forward)
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
||||||
|
Player1Paths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public Pawn(WhichPlayer owner, bool isPromoted = false)
|
||||||
|
: base(WhichPiece.Pawn, owner, isPromoted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
||||||
|
{
|
||||||
|
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
||||||
|
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
100
Shogi/BackEnd/Domains/ValueObjects/Pieces/Piece.cs
Normal file
100
Shogi/BackEnd/Domains/ValueObjects/Pieces/Piece.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
[DebuggerDisplay("{WhichPiece} {Owner}")]
|
||||||
|
public abstract record class Piece
|
||||||
|
{
|
||||||
|
public static Piece Create(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
||||||
|
{
|
||||||
|
return piece switch
|
||||||
|
{
|
||||||
|
WhichPiece.King => new King(owner, isPromoted),
|
||||||
|
WhichPiece.GoldGeneral => new GoldGeneral(owner, isPromoted),
|
||||||
|
WhichPiece.SilverGeneral => new SilverGeneral(owner, isPromoted),
|
||||||
|
WhichPiece.Bishop => new Bishop(owner, isPromoted),
|
||||||
|
WhichPiece.Rook => new Rook(owner, isPromoted),
|
||||||
|
WhichPiece.Knight => new Knight(owner, isPromoted),
|
||||||
|
WhichPiece.Lance => new Lance(owner, isPromoted),
|
||||||
|
WhichPiece.Pawn => new Pawn(owner, isPromoted),
|
||||||
|
_ => throw new ArgumentException($"Unknown {nameof(WhichPiece)} when cloning a {nameof(Piece)}.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public abstract IEnumerable<Path> MoveSet { get; }
|
||||||
|
public WhichPiece WhichPiece { get; }
|
||||||
|
public WhichPlayer Owner { get; private set; }
|
||||||
|
public bool IsPromoted { get; private set; }
|
||||||
|
protected Piece(WhichPiece piece, WhichPlayer owner, bool isPromoted = false)
|
||||||
|
{
|
||||||
|
WhichPiece = piece;
|
||||||
|
Owner = owner;
|
||||||
|
IsPromoted = isPromoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanPromote => !IsPromoted
|
||||||
|
&& WhichPiece != WhichPiece.King
|
||||||
|
&& WhichPiece != WhichPiece.GoldGeneral;
|
||||||
|
|
||||||
|
public void Promote() => IsPromoted = CanPromote;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prep the piece for capture by changing ownership and demoting.
|
||||||
|
/// </summary>
|
||||||
|
public void Capture(WhichPlayer newOwner)
|
||||||
|
{
|
||||||
|
Owner = newOwner;
|
||||||
|
IsPromoted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Respecting the move-set of the Piece, collect all positions along the shortest path from start to end.
|
||||||
|
/// Useful if you need to iterate a move-set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start"></param>
|
||||||
|
/// <param name="end"></param>
|
||||||
|
/// <returns>An empty list if the piece cannot legally traverse from start to end. Otherwise, a list of positions.</returns>
|
||||||
|
public IEnumerable<Vector2> GetPathFromStartToEnd(Vector2 start, Vector2 end)
|
||||||
|
{
|
||||||
|
var steps = new List<Vector2>(10);
|
||||||
|
|
||||||
|
var path = GetNearestPath(MoveSet, start, end);
|
||||||
|
var position = start;
|
||||||
|
while (Vector2.Distance(start, position) < Vector2.Distance(start, end))
|
||||||
|
{
|
||||||
|
position += path.Step;
|
||||||
|
steps.Add(position);
|
||||||
|
|
||||||
|
if (path.Distance == Distance.OneStep) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position == end)
|
||||||
|
{
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path GetNearestPath(IEnumerable<Path> paths, Vector2 start, Vector2 end)
|
||||||
|
{
|
||||||
|
if (!paths.DefaultIfEmpty().Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("No paths to get nearest path from.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortestPath = paths.First();
|
||||||
|
foreach (var path in paths.Skip(1))
|
||||||
|
{
|
||||||
|
var distance = Vector2.Distance(start + path.Step, end);
|
||||||
|
var shortestDistance = Vector2.Distance(start + shortestPath.Step, end);
|
||||||
|
if (distance < shortestDistance)
|
||||||
|
{
|
||||||
|
shortestPath = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shortestPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
namespace Shogi.Domain.ValueObjects;
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
public record class Rook : Piece
|
public record class Rook : Piece
|
||||||
{
|
{
|
||||||
35
Shogi/BackEnd/Domains/ValueObjects/Pieces/SilverGeneral.cs
Normal file
35
Shogi/BackEnd/Domains/ValueObjects/Pieces/SilverGeneral.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
internal record class SilverGeneral : Piece
|
||||||
|
{
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player1Paths = new(new List<Path>(4)
|
||||||
|
{
|
||||||
|
new Path(Direction.Forward),
|
||||||
|
new Path(Direction.ForwardLeft),
|
||||||
|
new Path(Direction.ForwardRight),
|
||||||
|
new Path(Direction.BackwardLeft),
|
||||||
|
new Path(Direction.BackwardRight)
|
||||||
|
});
|
||||||
|
|
||||||
|
public static readonly ReadOnlyCollection<Path> Player2Paths =
|
||||||
|
Player1Paths
|
||||||
|
.Select(p => p.Invert())
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
|
||||||
|
public SilverGeneral(WhichPlayer owner, bool isPromoted = false)
|
||||||
|
: base(WhichPiece.SilverGeneral, owner, isPromoted)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ReadOnlyCollection<Path> MoveSet => Owner switch
|
||||||
|
{
|
||||||
|
WhichPlayer.Player1 => IsPromoted ? GoldGeneral.Player1Paths : Player1Paths,
|
||||||
|
WhichPlayer.Player2 => IsPromoted ? GoldGeneral.Player2Paths : Player2Paths,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
using Shogi.Domain.ValueObjects.Movement;
|
using System.Numerics;
|
||||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
using Shogi.BackEnd.Domains.ValueObjects.Movement;
|
||||||
namespace Shogi.Domain.ValueObjects;
|
using Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD;
|
||||||
|
using Path = Shogi.BackEnd.Domains.ValueObjects.Movement.Path;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Rules;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
|
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
|
||||||
166
Shogi/BackEnd/Domains/ValueObjects/Rules/StandardRules.cs
Normal file
166
Shogi/BackEnd/Domains/ValueObjects/Rules/StandardRules.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
using Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD;
|
||||||
|
using System.Numerics;
|
||||||
|
using BoardTile = System.Collections.Generic.KeyValuePair<System.Numerics.Vector2, Shogi.BackEnd.Domains.ValueObjects.Pieces.Piece>;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.ValueObjects.Rules;
|
||||||
|
|
||||||
|
internal class StandardRules
|
||||||
|
{
|
||||||
|
private readonly BoardState boardState;
|
||||||
|
|
||||||
|
internal StandardRules(BoardState board)
|
||||||
|
{
|
||||||
|
boardState = board;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the last move put the player who moved in check.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This strategy recognizes that a "discover check" could only occur from a subset of pieces: Rook, Bishop, Lance.
|
||||||
|
/// In this way, only those pieces need to be considered when evaluating if a move placed the moving player in check.
|
||||||
|
/// </remarks>
|
||||||
|
internal bool DidPlayerPutThemselfInCheck()
|
||||||
|
{
|
||||||
|
if (boardState.PreviousMove.From == null)
|
||||||
|
{
|
||||||
|
// You can't place yourself in check by placing a piece from your hand.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousMovedPiece = boardState[boardState.PreviousMove.To];
|
||||||
|
if (previousMovedPiece == null) throw new ArgumentNullException(nameof(previousMovedPiece), $"No piece exists at position {boardState.PreviousMove.To}.");
|
||||||
|
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition;
|
||||||
|
|
||||||
|
|
||||||
|
var isDiscoverCheck = false;
|
||||||
|
// Get line equation from king through the now-unoccupied location.
|
||||||
|
var direction = Vector2.Subtract(kingPosition, boardState.PreviousMove.From.Value);
|
||||||
|
var slope = Math.Abs(direction.Y / direction.X);
|
||||||
|
var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMove.From.Value, Vector2.Normalize(direction));
|
||||||
|
var threat = boardState.QueryFirstPieceInPath(path);
|
||||||
|
if (threat == null || threat.Owner == previousMovedPiece.Owner) return false;
|
||||||
|
// If absolute slope is 45°, look for a bishop along the line.
|
||||||
|
// If absolute slope is 0° or 90°, look for a rook along the line.
|
||||||
|
// if absolute slope is 0°, look for lance along the line.
|
||||||
|
if (float.IsInfinity(slope))
|
||||||
|
{
|
||||||
|
isDiscoverCheck = threat.WhichPiece switch
|
||||||
|
{
|
||||||
|
WhichPiece.Lance => !threat.IsPromoted,
|
||||||
|
WhichPiece.Rook => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (slope == 1)
|
||||||
|
{
|
||||||
|
isDiscoverCheck = threat.WhichPiece switch
|
||||||
|
{
|
||||||
|
WhichPiece.Bishop => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (slope == 0)
|
||||||
|
{
|
||||||
|
isDiscoverCheck = threat.WhichPiece switch
|
||||||
|
{
|
||||||
|
WhichPiece.Rook => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return isDiscoverCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsOpponentInCheckAfterMove() => IsOpposingKingThreatenedByPosition(boardState.PreviousMove.To);
|
||||||
|
|
||||||
|
internal bool IsOpposingKingThreatenedByPosition(Vector2 position)
|
||||||
|
{
|
||||||
|
var previousMovedPiece = boardState[position];
|
||||||
|
if (previousMovedPiece == null) return false;
|
||||||
|
|
||||||
|
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition;
|
||||||
|
var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition);
|
||||||
|
var threatenedPiece = boardState.QueryFirstPieceInPath(path);
|
||||||
|
if (!path.Any() || threatenedPiece == null) return false;
|
||||||
|
|
||||||
|
return threatenedPiece.WhichPiece == WhichPiece.King;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsOpponentInCheckMate()
|
||||||
|
{
|
||||||
|
// Assume checkmate, then try to disprove.
|
||||||
|
if (!boardState.InCheck.HasValue) return false;
|
||||||
|
// Get all pieces from opponent who threaten the king in question.
|
||||||
|
var opponent = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
||||||
|
var tilesOccupiedByOpponent = boardState.GetTilesOccupiedBy(opponent);
|
||||||
|
var kingPosition = boardState.WhoseTurn == WhichPlayer.Player1
|
||||||
|
? boardState.Player1KingPosition
|
||||||
|
: boardState.Player2KingPosition;
|
||||||
|
var threats = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList();
|
||||||
|
if (threats.Count == 1)
|
||||||
|
{
|
||||||
|
/* If there is exactly one threat it is possible to block the check.
|
||||||
|
* Foreach piece owned by whichPlayer
|
||||||
|
* if piece can intercept check, return false;
|
||||||
|
*/
|
||||||
|
var threat = threats.Single();
|
||||||
|
var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition);
|
||||||
|
var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn);
|
||||||
|
foreach (var threatBlockingPosition in pathFromThreatToKing)
|
||||||
|
{
|
||||||
|
var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat
|
||||||
|
.Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (tilesThatDoBlockThreat.Any())
|
||||||
|
{
|
||||||
|
return false; // Cannot be check-mate if a piece can intercept the threat.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If no ability to block the check, maybe the king can evade check by moving.
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach (var maybeSafePosition in GetPossiblePositionsForKing(boardState.WhoseTurn))
|
||||||
|
{
|
||||||
|
threats = tilesOccupiedByOpponent
|
||||||
|
.Where(tile => PieceHasLineOfSight(tile, maybeSafePosition))
|
||||||
|
.ToList();
|
||||||
|
if (!threats.Any())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Vector2> GetPossiblePositionsForKing(WhichPlayer whichPlayer)
|
||||||
|
{
|
||||||
|
var kingPosition = whichPlayer == WhichPlayer.Player1
|
||||||
|
? boardState.Player1KingPosition
|
||||||
|
: boardState.Player2KingPosition;
|
||||||
|
|
||||||
|
var paths = boardState[kingPosition]!.MoveSet;
|
||||||
|
return paths
|
||||||
|
.Select(path => path.Step + kingPosition)
|
||||||
|
// Because the king could be on the edge of the board, where some of its paths do not make sense.
|
||||||
|
.Where(newPosition => newPosition.IsInsideBoardBoundary())
|
||||||
|
// Where tile at position is empty, meaning the king could move there.
|
||||||
|
.Where(newPosition => boardState[newPosition] == null)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget)
|
||||||
|
{
|
||||||
|
var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget);
|
||||||
|
return path
|
||||||
|
.SkipLast(1)
|
||||||
|
.All(position => boardState[Notation.ToBoardNotation(position)] == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
using Shogi.BackEnd.Domains.ValueObjects.Pieces;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD;
|
||||||
|
|
||||||
|
internal static class DomainExtensions
|
||||||
|
{
|
||||||
|
public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King;
|
||||||
|
|
||||||
|
public static bool IsBetween(this float self, float min, float max)
|
||||||
|
{
|
||||||
|
return self >= min && self <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsInsideBoardBoundary(this System.Numerics.Vector2 self)
|
||||||
|
{
|
||||||
|
return self.X.IsBetween(0, 8) && self.Y.IsBetween(0, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/Notation.cs
Normal file
33
Shogi/BackEnd/Domains/YetToBeAssimilatedIntoDDD/Notation.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Domains.YetToBeAssimilatedIntoDDD;
|
||||||
|
|
||||||
|
public static class Notation
|
||||||
|
{
|
||||||
|
private static readonly string BoardNotationRegex = @"(?<file>[A-I])(?<rank>[1-9])";
|
||||||
|
private static readonly char A = 'A';
|
||||||
|
|
||||||
|
public static string ToBoardNotation(Vector2 vector)
|
||||||
|
{
|
||||||
|
return ToBoardNotation((int)vector.X, (int)vector.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToBoardNotation(int x, int y)
|
||||||
|
{
|
||||||
|
var file = (char)(x + A);
|
||||||
|
var rank = y + 1;
|
||||||
|
return $"{file}{rank}";
|
||||||
|
}
|
||||||
|
public static Vector2 FromBoardNotation(string notation)
|
||||||
|
{
|
||||||
|
if (Regex.IsMatch(notation, BoardNotationRegex))
|
||||||
|
{
|
||||||
|
var match = Regex.Match(notation, BoardNotationRegex, RegexOptions.IgnoreCase);
|
||||||
|
char file = match.Groups["file"].Value[0];
|
||||||
|
int rank = int.Parse(match.Groups["rank"].Value);
|
||||||
|
return new Vector2(file - A, rank - 1);
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Board notation not recognized. Notation given: {notation}");
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Shogi/BackEnd/Extensions/ContractsExtensions.cs
Normal file
68
Shogi/BackEnd/Extensions/ContractsExtensions.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Shogi.BackEnd.Types;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using DomainValueObjects = Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
|
||||||
|
namespace Shogi.BackEnd.Extensions;
|
||||||
|
|
||||||
|
public static class ContractsExtensions
|
||||||
|
{
|
||||||
|
public static WhichPlayer ToContract(this DomainValueObjects.WhichPlayer player)
|
||||||
|
{
|
||||||
|
return player switch
|
||||||
|
{
|
||||||
|
DomainValueObjects.WhichPlayer.Player1 => WhichPlayer.Player1,
|
||||||
|
DomainValueObjects.WhichPlayer.Player2 => WhichPlayer.Player2,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WhichPiece ToContract(this DomainValueObjects.WhichPiece piece)
|
||||||
|
{
|
||||||
|
return piece switch
|
||||||
|
{
|
||||||
|
DomainValueObjects.WhichPiece.King => WhichPiece.King,
|
||||||
|
DomainValueObjects.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
|
||||||
|
DomainValueObjects.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
|
||||||
|
DomainValueObjects.WhichPiece.Bishop => WhichPiece.Bishop,
|
||||||
|
DomainValueObjects.WhichPiece.Rook => WhichPiece.Rook,
|
||||||
|
DomainValueObjects.WhichPiece.Knight => WhichPiece.Knight,
|
||||||
|
DomainValueObjects.WhichPiece.Lance => WhichPiece.Lance,
|
||||||
|
DomainValueObjects.WhichPiece.Pawn => WhichPiece.Pawn,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Piece ToContract(this DomainValueObjects.Pieces.Piece piece) => new()
|
||||||
|
{
|
||||||
|
IsPromoted = piece.IsPromoted,
|
||||||
|
Owner = piece.Owner.ToContract(),
|
||||||
|
WhichPiece = piece.WhichPiece.ToContract()
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyList<Piece> ToContract(this List<DomainValueObjects.Pieces.Piece> pieces)
|
||||||
|
{
|
||||||
|
return pieces
|
||||||
|
.Select(ToContract)
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, Piece?> ToContract(this ReadOnlyDictionary<string, DomainValueObjects.Pieces.Piece?> boardState) =>
|
||||||
|
boardState.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToContract());
|
||||||
|
|
||||||
|
public static DomainValueObjects.WhichPiece ToDomain(this WhichPiece piece)
|
||||||
|
{
|
||||||
|
return piece switch
|
||||||
|
{
|
||||||
|
WhichPiece.King => DomainValueObjects.WhichPiece.King,
|
||||||
|
WhichPiece.GoldGeneral => DomainValueObjects.WhichPiece.GoldGeneral,
|
||||||
|
WhichPiece.SilverGeneral => DomainValueObjects.WhichPiece.SilverGeneral,
|
||||||
|
WhichPiece.Bishop => DomainValueObjects.WhichPiece.Bishop,
|
||||||
|
WhichPiece.Rook => DomainValueObjects.WhichPiece.Rook,
|
||||||
|
WhichPiece.Knight => DomainValueObjects.WhichPiece.Knight,
|
||||||
|
WhichPiece.Lance => DomainValueObjects.WhichPiece.Lance,
|
||||||
|
WhichPiece.Pawn => DomainValueObjects.WhichPiece.Pawn,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Shogi.Api.Identity;
|
namespace Shogi.BackEnd.Identity;
|
||||||
|
|
||||||
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ShogiUser>(options)
|
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ShogiUser>(options)
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace Shogi.Api.Identity;
|
namespace Shogi.BackEnd.Identity;
|
||||||
|
|
||||||
public class ShogiUser : IdentityUser
|
public class ShogiUser : IdentityUser
|
||||||
{
|
{
|
||||||
@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
|||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Shogi.Api.Identity;
|
using Shogi.BackEnd.Identity;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace Shogi.Api.Migrations
|
namespace Shogi.BackEnd.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20240816002834_InitialCreate")]
|
[Migration("20240816002834_InitialCreate")]
|
||||||
@@ -158,7 +158,7 @@ namespace Shogi.Api.Migrations
|
|||||||
b.ToTable("AspNetUserTokens", (string)null);
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Shogi.Api.Models.User", b =>
|
modelBuilder.Entity("Shogi.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("nvarchar(450)");
|
.HasColumnType("nvarchar(450)");
|
||||||
@@ -234,7 +234,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -243,7 +243,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -258,7 +258,7 @@ namespace Shogi.Api.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -267,7 +267,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace Shogi.Api.Migrations
|
namespace Shogi.BackEnd.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class InitialCreate : Migration
|
public partial class InitialCreate : Migration
|
||||||
@@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Shogi.Api.Identity;
|
using Shogi.BackEnd.Identity;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace Shogi.Api.Migrations
|
namespace Shogi.BackEnd.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||||
@@ -155,7 +155,7 @@ namespace Shogi.Api.Migrations
|
|||||||
b.ToTable("AspNetUserTokens", (string)null);
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Shogi.Api.Models.User", b =>
|
modelBuilder.Entity("Shogi.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("nvarchar(450)");
|
.HasColumnType("nvarchar(450)");
|
||||||
@@ -231,7 +231,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -240,7 +240,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -255,7 +255,7 @@ namespace Shogi.Api.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -264,7 +264,7 @@ namespace Shogi.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Shogi.Api.Models.User", null)
|
b.HasOne("Shogi.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Shogi.Domain.ValueObjects;
|
using Shogi.BackEnd.Domains.ValueObjects;
|
||||||
|
|
||||||
namespace Shogi.Api.Repositories.Dto;
|
namespace Shogi.BackEnd.Repositories.Dto;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Useful with Dapper to read from database.
|
/// Useful with Dapper to read from database.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Api.Repositories.Dto;
|
namespace Shogi.BackEnd.Repositories.Dto;
|
||||||
|
|
||||||
public readonly record struct SessionDto(string Id, string Player1Id, string Player2Id)
|
public readonly record struct SessionDto(string Id, string Player1Id, string Player2Id)
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Options;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Shogi.Api.Repositories;
|
namespace Shogi.BackEnd.Repositories;
|
||||||
|
|
||||||
// https://app-smtp.brevo.com/real-time
|
// https://app-smtp.brevo.com/real-time
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Shogi.Api.Repositories.Dto;
|
using Shogi.BackEnd.Repositories.Dto;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
namespace Shogi.Api.Repositories;
|
namespace Shogi.BackEnd.Repositories;
|
||||||
|
|
||||||
public class QueryRepository(IConfiguration configuration)
|
public class QueryRepository(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Shogi.Api.Repositories.Dto;
|
using Shogi.BackEnd.Repositories.Dto;
|
||||||
using Shogi.Contracts.Api.Commands;
|
using Shogi.BackEnd.Domains.Aggregates;
|
||||||
using Shogi.Domain.Aggregates;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
namespace Shogi.Api.Repositories;
|
namespace Shogi.BackEnd.Repositories;
|
||||||
|
|
||||||
public class SessionRepository(IConfiguration configuration)
|
public class SessionRepository(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
@@ -53,7 +52,7 @@ public class SessionRepository(IConfiguration configuration)
|
|||||||
return new(sessionDtos.First(), moveDtos);
|
return new(sessionDtos.First(), moveDtos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateMove(string sessionId, MovePieceCommand command)
|
public async Task CreateMove(string sessionId, Types.MovePieceCommand command)
|
||||||
{
|
{
|
||||||
using var connection = new SqlConnection(this.connectionString);
|
using var connection = new SqlConnection(this.connectionString);
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
namespace Shogi.BackEnd.Types;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Shogi.Contracts.Types;
|
|
||||||
|
|
||||||
public class BoardState
|
public class BoardState
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using Shogi.Contracts.Types;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Shogi.Contracts.Api.Commands;
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
public partial class MovePieceCommand : IValidatableObject
|
public partial class MovePieceCommand : IValidatableObject
|
||||||
{
|
{
|
||||||
8
Shogi/BackEnd/Types/Piece.cs
Normal file
8
Shogi/BackEnd/Types/Piece.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
|
public class Piece
|
||||||
|
{
|
||||||
|
public bool IsPromoted { get; set; }
|
||||||
|
public WhichPiece WhichPiece { get; set; }
|
||||||
|
public WhichPlayer Owner { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
namespace Shogi.Contracts.Types;
|
|
||||||
|
|
||||||
public class Session
|
public class Session
|
||||||
{
|
{
|
||||||
@@ -16,5 +14,5 @@ public class Session
|
|||||||
|
|
||||||
public Guid SessionId { get; set; }
|
public Guid SessionId { get; set; }
|
||||||
|
|
||||||
public BoardState BoardState { get; set; }
|
public BoardState BoardState { get; set; } = new();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
namespace Shogi.Contracts.Types;
|
|
||||||
|
|
||||||
public class SessionMetadata
|
public class SessionMetadata
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Contracts.Types;
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
public enum WhichPiece
|
public enum WhichPiece
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.Contracts.Types;
|
namespace Shogi.BackEnd.Types;
|
||||||
|
|
||||||
public enum WhichPlayer
|
public enum WhichPlayer
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.UI.Identity;
|
namespace Shogi.FrontEnd.Client;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.UI.Identity;
|
namespace Shogi.FrontEnd.Client;
|
||||||
|
|
||||||
public class FormResult
|
public class FormResult
|
||||||
{
|
{
|
||||||
@@ -1,28 +1,19 @@
|
|||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Shogi.UI.Identity;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
|
||||||
namespace Shogi.UI.Shared;
|
namespace Shogi.FrontEnd.Client;
|
||||||
|
|
||||||
public class GameHubNode : IAsyncDisposable
|
public class GameHubNode : IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly HubConnection hubConnection;
|
private readonly HubConnection hubConnection;
|
||||||
|
|
||||||
public GameHubNode(IConfiguration configuration)
|
public GameHubNode(NavigationManager navigationManager)
|
||||||
{
|
{
|
||||||
var baseUrl = configuration["ShogiApiUrl"];
|
var hubUrl = navigationManager.ToAbsoluteUri("/gamehub");
|
||||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ShogiApiUrl configuration is missing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hubConnection = new HubConnectionBuilder()
|
this.hubConnection = new HubConnectionBuilder()
|
||||||
.WithUrl(new Uri(new Uri(baseUrl, UriKind.Absolute), "gamehub"), options =>
|
.WithUrl(hubUrl)
|
||||||
{
|
.Build();
|
||||||
options.HttpMessageHandlerFactory = handler => new CookieCredentialsMessageHandler { InnerHandler = handler };
|
|
||||||
options.SkipNegotiation = true;
|
|
||||||
options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets;
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.hubConnection.Closed += this.HubConnection_Closed;
|
this.hubConnection.Closed += this.HubConnection_Closed;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Shogi.UI.Identity;
|
namespace Shogi.FrontEnd.Client;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Account management services.
|
/// Account management services.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user