Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1ff2b84bc | |||
| ed70c58800 | |||
| 93211af255 | |||
| 767073c4dc | |||
| 496b34cdcc | |||
| fd8665e6ad | |||
| fd8e983830 | |||
| abd23b9035 | |||
| 7b740027fb | |||
| cd7c80d3e1 | |||
| a9faa5227f | |||
| 985a58c58a | |||
| 6e26f87ba7 | |||
| 253d19ae81 | |||
| 71ed23acec | |||
| d037012c01 | |||
| d19f5e67a3 | |||
| fbfe1efcb2 | |||
| 1f3a96d8c8 | |||
| fa1267aaf1 | |||
| d4f4743d64 | |||
| 58f8e2b519 | |||
| 5da1f879f2 | |||
| ea5f2d4134 | |||
| 4665be0640 | |||
| 34b58a94a5 | |||
| c5cff05e9c | |||
| 9689db40a5 | |||
| fe3b04415c | |||
| fad74fd7fb | |||
| 01dd076655 | |||
| 5063390b6d | |||
| 69138a0080 | |||
| 6a0bb29422 | |||
| f39e6542d0 | |||
| 46d3ad4802 | |||
| 74a70fda59 | |||
| 27f4c38239 | |||
| 8dd1bd331c | |||
| 5e21ada905 | |||
| 786fdfe0b1 | |||
| 3ede22cac6 | |||
| baf659be35 | |||
| 87b1d9b7b6 | |||
| fa92a7aa2f | |||
| 588c4d69df | |||
| 95d8cb09e0 | |||
| c8e3d7ca6e | |||
| 0f9f933171 | |||
| cc119912a9 | |||
| 880c122db4 | |||
| b7f3082f68 | |||
| b61e1a836b | |||
| df2be89ae6 | |||
| 0438f85b7f | |||
| 2832388996 | |||
| 709702ca5e | |||
| 5775a5345c | |||
| 02c9c08717 | |||
| 4633b16069 | |||
| a8ce3f606c | |||
| b8182baf12 | |||
| f3090cd855 | |||
| ad977ed74f | |||
| 4405b0da39 | |||
| 916792bbb7 | |||
| 09c703af7e | |||
| dc2680d124 | |||
| 603ee417db | |||
| 88d35442f4 | |||
| d8d0a44a79 | |||
| 378da184e0 | |||
| 61a2275a7d | |||
| 48128e9e08 | |||
| 3ab22373ff | |||
| 7d32de3d12 | |||
| 4d6033f73f | |||
| e780e7af7e | |||
| 489c532128 | |||
| 554e5918e8 | |||
| 2bb7a1e4be | |||
| 3dee08b268 | |||
| bbdb775ee0 | |||
| ef2d7b6c10 | |||
| 9d6da46f4e |
@@ -1,8 +1,6 @@
|
||||
.vs/
|
||||
.idea/
|
||||
lib
|
||||
*.lock.json
|
||||
package-lock.json
|
||||
*.user
|
||||
*.min.css
|
||||
*.min.js
|
||||
@@ -12,7 +10,6 @@ dist/
|
||||
node_modules/
|
||||
Properties/
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
bundle.js
|
||||
appsettings.Production.json
|
||||
appsettings.Development.json
|
||||
|
||||
+5
-1
@@ -13,6 +13,9 @@ before_script:
|
||||
- 'echo "Hostname: $(hostname)"'
|
||||
- 'dotnet --info'
|
||||
|
||||
variables:
|
||||
GIT_CLONE_PATH: '$CI_BUILDS_DIR/$CI_PROJECT_NAME/$CI_PIPELINE_ID'
|
||||
|
||||
restore:
|
||||
stage: build
|
||||
script:
|
||||
@@ -30,7 +33,8 @@ lint:
|
||||
needs:
|
||||
- build
|
||||
script:
|
||||
- jb inspectcode ./*.sln --output=analyze_output.xml --build -f=xml
|
||||
# 3 times retry because sometimes the first time will fail
|
||||
- jb inspectcode ./*.sln --output=analyze_output.xml --build -f=xml || jb inspectcode ./*.sln --output=analyze_output.xml --build -f=xml || jb inspectcode ./*.sln --output=analyze_output.xml --build -f=xml
|
||||
# Remove the warning of UnusedAutoPropertyAccessor InconsistentNaming
|
||||
- sed -i '/InconsistentNaming/d' analyze_output.xml
|
||||
- sed -i '/AssignNullToNotNullAttribute/d' analyze_output.xml # This is because jetbrains is not smart enough to understand the nullability of C# 8.0
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Aiursoft Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
We are committed to creating a positive and inclusive environment where all participants feel respected and valued.
|
||||
|
||||
## Our Principles
|
||||
|
||||
To ensure that the software we develop aligns with the Aiursoft Principles and serves all users equally well, we uphold the following:
|
||||
|
||||
1. **Offline Functionality**
|
||||
Any compiled artifacts of the software must be capable of performing their full functions without requiring an internet connection.
|
||||
|
||||
2. **Local Compilation in Aiursoft**
|
||||
The source code of the software must be capable of being compiled locally within Aiursoft's infrastructure without requiring an internet connection.
|
||||
|
||||
3. **Third-Party Local Compilation**
|
||||
The source code of the software must be capable of being compiled locally by third-party developers with an internet connection.
|
||||
|
||||
These principles ensure the software’s reliability, accessibility, and functionality across varied environments and user conditions.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others’ private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [anduin@aiursoft.com]. All complaints will be reviewed and investigated promptly and fairly. Maintainers are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
By participating in this project, you agree to uphold this Code of Conduct and to adhere to the principles and standards we strive to maintain for a respectful, inclusive, and productive community.
|
||||
+4
-4
@@ -3,17 +3,17 @@ ARG PROJ_NAME="Aiursoft.ChessServer"
|
||||
|
||||
# ============================
|
||||
# Prepare NPM Environment
|
||||
FROM hub.aiursoft.cn/node:21-alpine as npm-env
|
||||
FROM hub.aiursoft.cn/node:21-alpine AS npm-env
|
||||
ARG CSPROJ_PATH
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
# NPM Build at PGK_JSON_PATH
|
||||
RUN npm install --prefix "${CSPROJ_PATH}wwwroot"
|
||||
RUN npm install --prefix "${CSPROJ_PATH}wwwroot" --loglevel verbose
|
||||
|
||||
# ============================
|
||||
# Prepare Building Environment
|
||||
FROM hub.aiursoft.cn/mcr.microsoft.com/dotnet/sdk:8.0 as build-env
|
||||
FROM hub.aiursoft.cn/mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
|
||||
ARG CSPROJ_PATH
|
||||
ARG PROJ_NAME
|
||||
WORKDIR /src
|
||||
@@ -25,7 +25,7 @@ RUN cp -r ${CSPROJ_PATH}/wwwroot/* /app/wwwroot
|
||||
|
||||
# ============================
|
||||
# Prepare Runtime Environment
|
||||
FROM hub.aiursoft.cn/mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
FROM hub.aiursoft.cn/mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
ARG PROJ_NAME
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app .
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Aiursoft
|
||||
Copyright (c) 2025 Aiursoft
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -19,3 +19,43 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
# Developer Certificate of Origin
|
||||
|
||||
In addition to the permissions and conditions stated above, the maintenance and development of the Software shall adhere to the following Aiursoft Principles:
|
||||
|
||||
1. Any compiled artifacts of the Software must be capable of performing their full functions without requiring an internet connection.
|
||||
2. The source code of the Software must be capable of being compiled locally within Aiursoft's infrastructure without requiring an internet connection.
|
||||
3. The source code of the Software must be capable of being compiled locally by third-party developers with an internet connection.
|
||||
|
||||
# Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
# Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others’ private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
# Our Responsibilities
|
||||
|
||||
Maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
# Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
@@ -37,7 +37,7 @@ It will install the app as a systemd service, and start it automatically. Binary
|
||||
|
||||
Requirements about how to run
|
||||
|
||||
1. Install [.NET 8 SDK](http://dot.net/) and [Node.js](https://nodejs.org/).
|
||||
1. Install [.NET 9 SDK](http://dot.net/) and [Node.js](https://nodejs.org/).
|
||||
2. Execute `npm install` at `wwwroot` folder to install the dependencies.
|
||||
3. Execute `dotnet run` to run the app.
|
||||
4. Use your browser to view [http://localhost:5000](http://localhost:5000).
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ install()
|
||||
wwwrootPath=$(dirname "/tmp/repo/$proj_path")/wwwroot
|
||||
if [ -d "$wwwrootPath" ]; then
|
||||
echo "Found wwwroot folder $wwwrootPath, will install node modules."
|
||||
sudo npm install --prefix "$wwwrootPath" -force
|
||||
sudo npm install --prefix "$wwwrootPath" -force --loglevel verbose
|
||||
fi
|
||||
|
||||
# Publish the app
|
||||
|
||||
@@ -8,6 +8,8 @@ files:
|
||||
contentUri: https://gitlab.aiursoft.cn/aiursoft/tracer/-/raw/master/.gitlab-ci.yml
|
||||
- name: LICENSE
|
||||
contentUri: https://gitlab.aiursoft.cn/aiursoft/tracer/-/raw/master/LICENSE
|
||||
- name: CODE_OF_CONDUCT.md
|
||||
contentUri: https://gitlab.aiursoft.cn/aiursoft/tracer/-/raw/master/CODE_OF_CONDUCT.md
|
||||
- name: ninja.yaml
|
||||
contentUri: https://gitlab.aiursoft.cn/aiursoft/tracer/-/raw/master/ninja.yaml
|
||||
- name: nuget.config
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<AssemblyName>Aiursoft.ChessServer</AssemblyName>
|
||||
<RootNamespace>Aiursoft.ChessServer</RootNamespace>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
@@ -10,11 +10,12 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aiursoft.AiurObserver.Extensions" Version="8.0.2" />
|
||||
<PackageReference Include="Aiursoft.AiurObserver.WebSocket.Server" Version="8.0.2" />
|
||||
<PackageReference Include="Aiursoft.Scanner" Version="8.0.1" />
|
||||
<PackageReference Include="Aiursoft.WebTools" Version="8.0.21" />
|
||||
<PackageReference Include="Gera.Chess" Version="1.1.0" />
|
||||
<PackageReference Include="LynxChess.Lynx.Internal" Version="1.5.1" />
|
||||
<PackageReference Include="Aiursoft.AiurObserver.Extensions" Version="9.0.0" />
|
||||
<PackageReference Include="Aiursoft.AiurObserver.WebSocket.Server" Version="9.0.0" />
|
||||
<PackageReference Include="Aiursoft.InMemoryKvDb" Version="1.0.7" />
|
||||
<PackageReference Include="Aiursoft.Scanner" Version="9.0.1" />
|
||||
<PackageReference Include="Aiursoft.WebTools" Version="9.0.0" />
|
||||
<PackageReference Include="Gera.Chess" Version="1.1.1" />
|
||||
<PackageReference Include="LynxChess.Lynx" Version="1.8.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Aiursoft.AiurObserver.Extensions;
|
||||
using Aiursoft.AiurObserver;
|
||||
using Aiursoft.AiurObserver.WebSocket.Server;
|
||||
using Aiursoft.ChessServer.Attributes;
|
||||
using Aiursoft.ChessServer.Data;
|
||||
@@ -7,6 +7,7 @@ using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.ChessServer.Models.ViewModels;
|
||||
using Aiursoft.CSTools.Services;
|
||||
using Aiursoft.WebTools.Attributes;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aiursoft.ChessServer.Controllers;
|
||||
@@ -157,10 +158,17 @@ public class ChallengesController (
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
catch (ConnectionAbortedException)
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
finally
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
outSub.Unsubscribe();
|
||||
if (pusher.Connected)
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using Aiursoft.AiurObserver;
|
||||
using Aiursoft.AiurObserver.Extensions;
|
||||
using Aiursoft.AiurObserver.WebSocket.Server;
|
||||
using Aiursoft.ChessServer.Attributes;
|
||||
using Aiursoft.ChessServer.Data;
|
||||
using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.WebTools.Attributes;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aiursoft.ChessServer.Controllers;
|
||||
@@ -50,11 +50,18 @@ public class ChatController(InMemoryDatabase database) : ControllerBase
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
catch (ConnectionAbortedException)
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
finally
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
outSub.Unsubscribe();
|
||||
inSub.Unsubscribe();
|
||||
if (pusher.Connected)
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using Aiursoft.AiurObserver.WebSocket.Server;
|
||||
using Aiursoft.ChessServer.Data;
|
||||
using Aiursoft.ChessServer.Models.ViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Aiursoft.AiurObserver.Extensions;
|
||||
using Aiursoft.ChessServer.Attributes;
|
||||
using Aiursoft.WebTools.Attributes;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace Aiursoft.ChessServer.Controllers;
|
||||
|
||||
@@ -88,11 +88,18 @@ public class GamesController(InMemoryDatabase database) : Controller
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
catch (ConnectionAbortedException)
|
||||
{
|
||||
// Ignore. This happens when the client closes the connection.
|
||||
}
|
||||
finally
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
outSub.Unsubscribe();
|
||||
inSub.Unsubscribe();
|
||||
if (pusher.Connected)
|
||||
{
|
||||
await pusher.Close(HttpContext.RequestAborted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using Aiursoft.ChessServer.Data;
|
||||
using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.CSTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Aiursoft.AiurObserver.Extensions;
|
||||
using Aiursoft.ChessServer.Services;
|
||||
using Chess;
|
||||
|
||||
@@ -52,6 +51,10 @@ public class PveController(
|
||||
var webSocketSchema = HttpContext.Request.Scheme == "https" ? "wss" : "ws";
|
||||
var webSocketEndpoint =
|
||||
$"{webSocketSchema}://{HttpContext.Request.Host}/games/{challengeId}.ws?playerId={computerId}";
|
||||
|
||||
// TODO: Refactor required. Currently, PVE, the computer is also a WebSocket client connecting to localhost.
|
||||
// This is not a good practice. The computer should be a WebSocket server that listens to the game's WebSocket.
|
||||
// Because currently, if the user dropped, the computer will not know and will keep calculating the best move.
|
||||
var client = await webSocketEndpoint.ConnectAsWebSocketServer();
|
||||
subscription = client.Subscribe(async fen =>
|
||||
{
|
||||
|
||||
@@ -1,73 +1,55 @@
|
||||
using Aiursoft.Scanner.Abstractions;
|
||||
using System.Collections.Concurrent;
|
||||
using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.InMemoryKvDb.AutoCreate;
|
||||
using Aiursoft.InMemoryKvDb.ManualCreate;
|
||||
|
||||
namespace Aiursoft.ChessServer.Data;
|
||||
|
||||
public class InMemoryDatabase : ISingletonDependency
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public class InMemoryDatabase(
|
||||
LruMemoryStore<Player, Guid> playersDb,
|
||||
LruMemoryStoreManualCreated<Challenge, int> challenges) : ISingletonDependency
|
||||
{
|
||||
private ConcurrentDictionary<Guid, Player> Players { get; } = new();
|
||||
|
||||
private ConcurrentDictionary<int, Challenge> Challenges { get; } = new();
|
||||
|
||||
private IEnumerable<KeyValuePair<int, Challenge>> OpenChallenges =>
|
||||
Challenges.Where(t => t.Value is not AcceptedChallenge);
|
||||
challenges.GetAllWithKeys().Where(t => t.Value is not AcceptedChallenge);
|
||||
|
||||
private IEnumerable<KeyValuePair<int, Challenge>> OnGoingChallenges =>
|
||||
Challenges.Where(t => t.Value is AcceptedChallenge);
|
||||
challenges.GetAllWithKeys().Where(t => t.Value is AcceptedChallenge);
|
||||
|
||||
public Player GetOrAddPlayer(Guid id)
|
||||
{
|
||||
lock (Players)
|
||||
{
|
||||
return Players.GetOrAdd(id, _ => new Player(id)
|
||||
{
|
||||
NickName = "Anonymous " + new Random().Next(1000, 9999)
|
||||
});
|
||||
}
|
||||
return playersDb.GetOrAdd(id);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<KeyValuePair<int, Challenge>> GetPublicOpenChallenges()
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
return OpenChallenges
|
||||
.Where(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.ToArray();
|
||||
}
|
||||
return OpenChallenges
|
||||
.Where(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<KeyValuePair<int, Challenge>> GetOnGoingOpenChallenges()
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
return OnGoingChallenges
|
||||
.Where(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.ToArray();
|
||||
}
|
||||
return OnGoingChallenges
|
||||
.Where(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public int? GetMyOpenChallenge(Guid playerId)
|
||||
{
|
||||
lock (Challenges)
|
||||
if (OpenChallenges.All(t => t.Value.Creator.Id != playerId))
|
||||
{
|
||||
if (OpenChallenges.All(t => t.Value.Creator.Id != playerId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenChallenges
|
||||
.FirstOrDefault(t => t.Value.Creator.Id == playerId)
|
||||
.Key;
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenChallenges
|
||||
.FirstOrDefault(t => t.Value.Creator.Id == playerId)
|
||||
.Key;
|
||||
}
|
||||
|
||||
public Challenge? GetChallenge(int id)
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
return Challenges.GetValueOrDefault(id);
|
||||
}
|
||||
return challenges.Get(id);
|
||||
}
|
||||
|
||||
public AcceptedChallenge? GetAcceptedChallenge(int id)
|
||||
@@ -78,68 +60,52 @@ public class InMemoryDatabase : ISingletonDependency
|
||||
|
||||
public async Task PatchChallengeAsAcceptedAsync(int id, Guid accepter)
|
||||
{
|
||||
lock (Challenges)
|
||||
var challenge = challenges.Get(id);
|
||||
if (challenge == null)
|
||||
{
|
||||
var challenge = Challenges.GetValueOrDefault(id);
|
||||
if (challenge == null)
|
||||
{
|
||||
throw new InvalidOperationException("Challenge not found!");
|
||||
}
|
||||
if (challenge.Creator.Id == accepter)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot accept your own challenge!");
|
||||
}
|
||||
if (challenge is AcceptedChallenge)
|
||||
{
|
||||
throw new InvalidOperationException("Challenge already accepted!");
|
||||
}
|
||||
|
||||
var newChallenge = new AcceptedChallenge(
|
||||
creator: challenge.Creator,
|
||||
accepter: GetOrAddPlayer(accepter),
|
||||
message: challenge.Message,
|
||||
roleRule: challenge.RoleRule,
|
||||
timeLimit: challenge.TimeLimit,
|
||||
permission: challenge.Permission,
|
||||
challengeChangedChannel: challenge.ChallengeChangedChannel);
|
||||
|
||||
Challenges[id] = newChallenge;
|
||||
throw new InvalidOperationException("Challenge not found!");
|
||||
}
|
||||
await Challenges.GetValueOrDefault(id)!.ChallengeChangedChannel.BroadcastAsync("game-started");
|
||||
if (challenge.Creator.Id == accepter)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot accept your own challenge!");
|
||||
}
|
||||
if (challenge is AcceptedChallenge)
|
||||
{
|
||||
throw new InvalidOperationException("Challenge already accepted!");
|
||||
}
|
||||
|
||||
var newChallenge = new AcceptedChallenge(
|
||||
creator: challenge.Creator,
|
||||
accepter: GetOrAddPlayer(accepter),
|
||||
message: challenge.Message,
|
||||
roleRule: challenge.RoleRule,
|
||||
timeLimit: challenge.TimeLimit,
|
||||
permission: challenge.Permission,
|
||||
challengeChangedChannel: challenge.ChallengeChangedChannel);
|
||||
|
||||
challenges.AddToCache(id, newChallenge);
|
||||
await challenges.Get(id)!.ChallengeChangedChannel.BroadcastAsync("game-started");
|
||||
}
|
||||
|
||||
public int? GetFirstPublicChallengeKey()
|
||||
{
|
||||
lock (Challenges)
|
||||
if (OpenChallenges.All(t => t.Value.Permission != ChallengePermission.Public))
|
||||
{
|
||||
if (OpenChallenges.All(t => t.Value.Permission != ChallengePermission.Public))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenChallenges
|
||||
.FirstOrDefault(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.Key;
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenChallenges
|
||||
.FirstOrDefault(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.Key;
|
||||
}
|
||||
|
||||
public void DeleteChallenge(int id)
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
Challenges.TryRemove(id, out _);
|
||||
}
|
||||
challenges.Remove(id);
|
||||
}
|
||||
|
||||
public void CreateChallenge(int id, Challenge challenge)
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
var result = Challenges.TryAdd(id, challenge);
|
||||
if (!result)
|
||||
{
|
||||
throw new InvalidOperationException("Challenge already exists!");
|
||||
}
|
||||
}
|
||||
challenges.AddToCache(id, challenge);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class ChessEngine
|
||||
|
||||
public ChessEngine()
|
||||
{
|
||||
var channel = Channel.CreateUnbounded<string>(new UnboundedChannelOptions
|
||||
var channel = Channel.CreateUnbounded<object>(new UnboundedChannelOptions
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = true
|
||||
@@ -39,19 +39,8 @@ public class ChessEngine
|
||||
_engine.AdjustPosition($"position fen {fen}");
|
||||
var positionClone = new Position(_engine.Game.CurrentPosition);
|
||||
|
||||
var depth = difficulty - 1;
|
||||
var result = _engine.IDDFS(depth, 10);
|
||||
_engine.Game.ResetCurrentPositionToBeforeSearchState();
|
||||
|
||||
if (difficulty > 4)
|
||||
{
|
||||
// If difficulty is higher than 5, we should use the best move
|
||||
return result.BestMove.ToEPDString(positionClone);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Randomly choose one of the moves
|
||||
return result.Moves[new Random().Next(result.Moves.Count)].ToEPDString(positionClone);
|
||||
}
|
||||
return _engine.BestMove(new($"go depth {difficulty}"))
|
||||
.BestMove
|
||||
.ToEPDString(positionClone);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Reflection;
|
||||
using Aiursoft.ChessServer.Middlewares;
|
||||
using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.ChessServer.Services;
|
||||
using Aiursoft.InMemoryKvDb;
|
||||
using Aiursoft.Scanner;
|
||||
using Aiursoft.WebTools.Abstractions.Models;
|
||||
|
||||
@@ -12,6 +14,13 @@ public class Startup : IWebStartup
|
||||
{
|
||||
services.AddLibraryDependencies();
|
||||
|
||||
services.AddLruMemoryStore<Player, Guid>(
|
||||
id => new Player(id) { NickName = "Anonymous " + new Random().Next(1000, 9999) },
|
||||
maxCachedItemsCount: 1024);
|
||||
|
||||
services.AddLruMemoryStoreManualCreate<Challenge, int>(
|
||||
maxCachedItemsCount: 256);
|
||||
|
||||
services.AddTransient<ChessEngine>();
|
||||
|
||||
services
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div id="board" style="width: 100%" class="w-100"></div>
|
||||
<p id="status" class="text-center text-wrap text-primary mt-3"></p>
|
||||
<p id="role" class="text-center text-wrap text-secondary mt-1"></p>
|
||||
<audio src="/sounds/chess_piece_move.mp3" id="pieceMoveSound" loop></audio>
|
||||
|
||||
<script type="module">
|
||||
import ChessBuilder from "/scripts/chessboard/chessboard.js";
|
||||
@@ -13,7 +14,8 @@
|
||||
var playerColor = await getPlayerColor(@Model.GameId);
|
||||
var statusControl = document.getElementById("status");
|
||||
var roleControl = document.getElementById("role");
|
||||
let anduinChessBoard = new ChessBuilder(playerColor, statusControl, roleControl).default();
|
||||
const soundControl = document.getElementById('pieceMoveSound');
|
||||
let anduinChessBoard = new ChessBuilder(playerColor, statusControl, roleControl, soundControl).default();
|
||||
anduinChessBoard.run(playerId, @Model.GameId);
|
||||
|
||||
document.body.addEventListener('pointerdown', (e) => {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
registry=https://npm.aiursoft.cn
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"name": "wwwroot",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wwwroot",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aiursoft/autodark.js": "^1.2.0",
|
||||
"@chrisoakman/chessboardjs": "^1.0.0",
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"chess.js": "^1.0.0-beta.6",
|
||||
"clipboard": "^2.0.11",
|
||||
"jquery-validation": "^1.19.5",
|
||||
"jquery-validation-unobtrusive": "^3.2.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@aiursoft/autodark.js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://npm.aiursoft.cn/@aiursoft/autodark.js/-/autodark.js-1.2.0.tgz",
|
||||
"integrity": "sha512-3EZh7AaT2ns2DGq24HMcNxAjSVSST49t/tZhR2MV0pxrwYgb+1SCk6zj9qv4TJB1bAixM0K/5nsH0YazZzfvng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@chrisoakman/chessboardjs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://npm.aiursoft.cn/@chrisoakman/chessboardjs/-/chessboardjs-1.0.0.tgz",
|
||||
"integrity": "sha512-JHXHoQwwc86xW3F0YIdFcEWLnPldee5mHkqwJbOTeDh5gvNmYXyBj6AkeecDkj2WtORF959yaWYlpyZHUl3LCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jquery": ">=3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://npm.aiursoft.cn/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz",
|
||||
"integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==",
|
||||
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://npm.aiursoft.cn/bootstrap/-/bootstrap-4.6.2.tgz",
|
||||
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"jquery": "1.9.1 - 3",
|
||||
"popper.js": "^1.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chess.js": {
|
||||
"version": "1.0.0-beta.8",
|
||||
"resolved": "https://npm.aiursoft.cn/chess.js/-/chess.js-1.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-UngzUMXmexcQaQA/UEJuJj5vatEy34awYMD5YMOp/FW3HM7lqspp7ymYs5JAmquDq0WROtURRfSffoa/vrCCyw==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/clipboard": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://npm.aiursoft.cn/clipboard/-/clipboard-2.0.11.tgz",
|
||||
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://npm.aiursoft.cn/delegate/-/delegate-3.2.0.tgz",
|
||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://npm.aiursoft.cn/good-listener/-/good-listener-1.2.2.tgz",
|
||||
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delegate": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://npm.aiursoft.cn/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jquery-validation": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://npm.aiursoft.cn/jquery-validation/-/jquery-validation-1.21.0.tgz",
|
||||
"integrity": "sha512-xNot0rlUIgu7duMcQ5qb6MGkGL/Z1PQaRJQoZAURW9+a/2PGOUxY36o/WyNeP2T9R6jvWB8Z9lUVvvQWI/Zs5w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"jquery": "^1.7 || ^2.0 || ^3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery-validation-unobtrusive": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://npm.aiursoft.cn/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.12.tgz",
|
||||
"integrity": "sha512-kPixGhVcuat7vZXngGFfSIksy4VlzZcHyRgnBIZdsfVneCU+D5sITC8T8dD/9c9K/Q+qkMlgp7ufJHz93nKSuQ==",
|
||||
"license": "https://aka.ms/jqueryunobtrusivelicense",
|
||||
"dependencies": {
|
||||
"jquery": "^3.5.1",
|
||||
"jquery-validation": ">=1.16"
|
||||
}
|
||||
},
|
||||
"node_modules/popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://npm.aiursoft.cn/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://npm.aiursoft.cn/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://npm.aiursoft.cn/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
import { Chess } from "../../node_modules/chess.js/dist/esm/chess.js";
|
||||
|
||||
import {
|
||||
initSoundForMobile,
|
||||
initSoundForPC,
|
||||
playSoundForPC,
|
||||
playSoundForMobile,
|
||||
} from "./sound.js";
|
||||
import {
|
||||
buildOnDragStart,
|
||||
buildOnDrop,
|
||||
buildOnSnapEnd,
|
||||
buildOnChange,
|
||||
WHITE_ABBREVIATION,
|
||||
BLACK_ABBREVIATION,
|
||||
WHITE,
|
||||
BLACK,
|
||||
findMove,
|
||||
} from "./defaultConfig.js";
|
||||
|
||||
@@ -20,7 +28,7 @@ import {
|
||||
* @param {HTMLElement} statusControl status control element
|
||||
* @param {HTMLElement} roleControl role control element
|
||||
*/
|
||||
function ChessBuilder(color, statusControl, roleControl) {
|
||||
function ChessBuilder(color, statusControl, roleControl, soundControl) {
|
||||
this.onDragStart = undefined;
|
||||
this.onDrop = undefined;
|
||||
this.onSnapEnd = undefined;
|
||||
@@ -34,8 +42,10 @@ function ChessBuilder(color, statusControl, roleControl) {
|
||||
anduinChessBoard.config.onDragStart = buildOnDragStart(anduinChessBoard);
|
||||
anduinChessBoard.config.onDrop = buildOnDrop(anduinChessBoard);
|
||||
anduinChessBoard.config.onSnapEnd = buildOnSnapEnd(anduinChessBoard);
|
||||
anduinChessBoard.config.onChange = buildOnChange(anduinChessBoard);
|
||||
anduinChessBoard.statusControl = statusControl;
|
||||
anduinChessBoard.roleControl = roleControl;
|
||||
anduinChessBoard.soundControl = soundControl;
|
||||
|
||||
return anduinChessBoard;
|
||||
};
|
||||
@@ -48,7 +58,11 @@ function AnduinChessBoard(color) {
|
||||
this.socket = null;
|
||||
this.statusControl = null;
|
||||
this.roleControl = null;
|
||||
this.soundControl = null;
|
||||
this.lastMovePair = [null, null];
|
||||
this.isWhiteCheck = false;
|
||||
this.isBlackCheck = false;
|
||||
this.statusText = "";
|
||||
|
||||
this.config = {
|
||||
orientation: this.color === BLACK_ABBREVIATION ? "black" : "white",
|
||||
@@ -60,7 +74,28 @@ function AnduinChessBoard(color) {
|
||||
onSnapEnd: null,
|
||||
};
|
||||
|
||||
this.renderTrack = () => {
|
||||
this._initSound = () => {
|
||||
if (/Mobile/.test(navigator.userAgent)) {
|
||||
initSoundForMobile.bind(this)(this);
|
||||
this._playSound = playSoundForMobile.bind(this);
|
||||
} else {
|
||||
initSoundForPC.bind(this)(this);
|
||||
this._playSound = playSoundForPC.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
this._playSound = () => {};
|
||||
|
||||
/**
|
||||
* render some styles, like highlight, red light
|
||||
*/
|
||||
this.render = () => {
|
||||
this._renderTrack();
|
||||
this._renderCheck();
|
||||
this._renderStatusText();
|
||||
};
|
||||
|
||||
this._renderTrack = () => {
|
||||
if (this.lastMovePair[0] !== null && this.lastMovePair[1] !== null) {
|
||||
let allSquares = Array.from(
|
||||
document.querySelectorAll(`#board [data-square]`)
|
||||
@@ -84,6 +119,37 @@ function AnduinChessBoard(color) {
|
||||
}
|
||||
};
|
||||
|
||||
this._renderCheck = () => {
|
||||
let checkedPosition = null;
|
||||
if (this.isWhiteCheck) {
|
||||
checkedPosition = this._getKingPosition(WHITE_ABBREVIATION);
|
||||
} else if (this.isBlackCheck) {
|
||||
checkedPosition = this._getKingPosition(BLACK_ABBREVIATION);
|
||||
}
|
||||
|
||||
document.querySelectorAll("#board [data-square]").forEach((p) => {
|
||||
p.style.backgroundImage = "";
|
||||
});
|
||||
if (checkedPosition !== null) {
|
||||
document.querySelector(
|
||||
`#board [data-square=${checkedPosition}]`
|
||||
).style.backgroundImage = "radial-gradient(circle, red 5%, transparent)";
|
||||
}
|
||||
};
|
||||
|
||||
this._renderStatusText = () => {
|
||||
this.statusControl.innerHTML = this.statusText;
|
||||
};
|
||||
|
||||
this._getKingPosition = (bOrW) => {
|
||||
let pieces = []
|
||||
.concat(...this.game.board())
|
||||
.filter((p) => p !== null && p.type === "k" && p.color === bOrW)
|
||||
.map((p) => p.square);
|
||||
|
||||
return pieces.length > 0 ? pieces[0] : null;
|
||||
};
|
||||
|
||||
this.run = (player, gameId) => {
|
||||
this.roleControl.innerHTML = `You are ${
|
||||
this.color === WHITE_ABBREVIATION
|
||||
@@ -93,6 +159,8 @@ function AnduinChessBoard(color) {
|
||||
: "Spectator"
|
||||
} player.`;
|
||||
|
||||
this._initSound();
|
||||
|
||||
const completeInit = (fen) => {
|
||||
this.config.position = fen;
|
||||
|
||||
@@ -105,7 +173,7 @@ function AnduinChessBoard(color) {
|
||||
this.board = ChessBoard("board", this.config);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
this.refresh(event.data);
|
||||
this._refresh(event.data);
|
||||
};
|
||||
this.socket.onclose = () => {
|
||||
setTimeout(() => {
|
||||
@@ -113,7 +181,7 @@ function AnduinChessBoard(color) {
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
this.refresh(fen);
|
||||
this._refresh(fen);
|
||||
};
|
||||
|
||||
fetch(`/games/${gameId}.fen`)
|
||||
@@ -121,39 +189,48 @@ function AnduinChessBoard(color) {
|
||||
.then(completeInit);
|
||||
};
|
||||
|
||||
this.refresh = (newFEN) => {
|
||||
this._refresh = (newFEN) => {
|
||||
if (this.game !== null) {
|
||||
let [position1, position2] = findMove(this.game.fen(), newFEN);
|
||||
this.lastMovePair = [position1, position2];
|
||||
this.renderTrack();
|
||||
}
|
||||
|
||||
this.game = new Chess(newFEN);
|
||||
this.board.position(newFEN);
|
||||
console.log(`Got fen ${newFEN}. refreshing board...`);
|
||||
|
||||
this.updateStatusText();
|
||||
this._updateStatus();
|
||||
this.render();
|
||||
};
|
||||
|
||||
this.updateStatusText = () => {
|
||||
let status;
|
||||
let moveColor = "White";
|
||||
this._updateStatus = () => {
|
||||
let moveColor = WHITE;
|
||||
if (this.game.turn() === BLACK_ABBREVIATION) {
|
||||
moveColor = "Black";
|
||||
moveColor = BLACK;
|
||||
}
|
||||
|
||||
this.isWhiteCheck = null;
|
||||
this.isBlackCheck = null;
|
||||
|
||||
this.statusText = `${moveColor} to move`;
|
||||
|
||||
if (this.game.isCheck()) {
|
||||
this.isWhiteCheck = moveColor === WHITE;
|
||||
this.isBlackCheck = moveColor === BLACK;
|
||||
this.statusText += `, ${moveColor} is in check`;
|
||||
}
|
||||
|
||||
if (this.game.isCheckmate()) {
|
||||
status = `Game over, ${moveColor} is in checkmate, and winner is ${
|
||||
this.isWhiteCheck = moveColor === WHITE;
|
||||
this.isBlackCheck = moveColor === BLACK;
|
||||
this.statusText = `Game over, ${moveColor} is in checkmate, and winner is ${
|
||||
this.game.turn() === WHITE_ABBREVIATION ? "Black" : "White"
|
||||
}`;
|
||||
} else if (this.game.isDraw()) {
|
||||
status = "Game over, drawn position";
|
||||
} else {
|
||||
status = `${moveColor} to move`;
|
||||
if (this.game.isCheck()) {
|
||||
status += `, ${moveColor} is in check`;
|
||||
}
|
||||
}
|
||||
this.statusControl.innerHTML = status;
|
||||
|
||||
if (this.game.isDraw()) {
|
||||
this.statusText = "Game over, drawn position";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const WHITE_ABBREVIATION = "w";
|
||||
const BLACK_ABBREVIATION = "b";
|
||||
const WHITE = "White";
|
||||
const BLACK = "Black";
|
||||
|
||||
const WHITE_SQUARE_GREY = "#a9a9a9";
|
||||
const BLACK_SQUARE_GREY = "#696969";
|
||||
@@ -65,7 +67,7 @@ function buildOnDragStart(globalParams) {
|
||||
*
|
||||
* # example:
|
||||
* ```js
|
||||
* let onDrop = onDrop({game, socket, lastMovePair, renderTrack});
|
||||
* let onDrop = onDrop({game, socket, lastMovePair, render});
|
||||
* ```
|
||||
*
|
||||
* @param {{game, socket}} globalParams
|
||||
@@ -75,7 +77,7 @@ function buildOnDrop(globalParams) {
|
||||
const realOnDrop = (source, target) => {
|
||||
let game = globalParams.game;
|
||||
let socket = globalParams.socket;
|
||||
let renderTrack = globalParams.renderTrack;
|
||||
let render = globalParams.render;
|
||||
|
||||
document.querySelectorAll("#board [data-square]").forEach((square) => {
|
||||
square.style.backgroundColor = "";
|
||||
@@ -93,7 +95,7 @@ function buildOnDrop(globalParams) {
|
||||
|
||||
if (source !== target) {
|
||||
globalParams.lastMovePair = [source, target];
|
||||
renderTrack();
|
||||
render();
|
||||
}
|
||||
|
||||
const lastMove = game.history({ verbose: true }).pop().san;
|
||||
@@ -128,7 +130,13 @@ function buildOnSnapEnd(globalParams) {
|
||||
return realOnSnapEnd;
|
||||
}
|
||||
|
||||
function trackFEN() {}
|
||||
function buildOnChange(globalParams) {
|
||||
const onChange = (oldPos, newPos) => {
|
||||
globalParams._playSound();
|
||||
};
|
||||
|
||||
return onChange;
|
||||
}
|
||||
|
||||
function parseFEN(fen) {
|
||||
const parts = fen.split(" ")[0].split("/");
|
||||
@@ -194,9 +202,12 @@ export {
|
||||
buildOnDragStart,
|
||||
buildOnDrop,
|
||||
buildOnSnapEnd,
|
||||
buildOnChange,
|
||||
WHITE_ABBREVIATION,
|
||||
BLACK_ABBREVIATION,
|
||||
WHITE_SQUARE_GREY,
|
||||
BLACK_SQUARE_GREY,
|
||||
WHITE,
|
||||
BLACK,
|
||||
findMove,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
const MINIMAL_BUT_NOT_MUTE = 0.0001;
|
||||
|
||||
function initSoundForPC() {
|
||||
if (this.soundControl === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.soundControl.volume = MINIMAL_BUT_NOT_MUTE;
|
||||
|
||||
const activeSound = () => {
|
||||
if (this.soundControl.played.length === 0) {
|
||||
this.soundControl.play();
|
||||
} else {
|
||||
document.body.removeEventListener("click", activeSound);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener("click", activeSound);
|
||||
}
|
||||
|
||||
function initSoundForMobile() {
|
||||
if (this.soundControl === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.soundControl.muted = true;
|
||||
|
||||
const activeSound = () => {
|
||||
if (this.soundControl.played.length === 0) {
|
||||
this.soundControl.play();
|
||||
} else {
|
||||
document.body.removeEventListener("click", activeSound);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener("click", activeSound);
|
||||
}
|
||||
|
||||
function playSoundForPC() {
|
||||
const START = 0;
|
||||
// 'this' should be points anduinChessBoard
|
||||
if (this.soundControl !== null) {
|
||||
this.soundControl.currentTime = START;
|
||||
this.soundControl.volume = 1;
|
||||
setTimeout(() => {
|
||||
this.soundControl.volume = MINIMAL_BUT_NOT_MUTE;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function playSoundForMobile() {
|
||||
// 'this' should be points anduinChessBoard
|
||||
if (this.soundControl !== null) {
|
||||
this.soundControl.currentTime = 0;
|
||||
this.soundControl.muted = false;
|
||||
setTimeout(() => {
|
||||
this.soundControl.muted = true;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
initSoundForPC,
|
||||
initSoundForMobile,
|
||||
playSoundForPC,
|
||||
playSoundForMobile,
|
||||
};
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Aiursoft.ChessServer.Tests</RootNamespace>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
@@ -9,15 +9,15 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="3.1.12" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
|
||||
<PackageReference Include="AngleSharp" Version="1.1.2" />
|
||||
<PackageReference Include="JunitXml.TestLogger" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.7.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.7.0" />
|
||||
<PackageReference Include="AngleSharp" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Aiursoft.ChessServer\Aiursoft.ChessServer.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user