Compare commits

..

10 Commits

Author SHA1 Message Date
ZZY
13450ea09a dev: 更新代码,并准备LuaMod 2025-06-14 14:38:28 +08:00
ZZY
3fa39fc71e update: 更新到godot4.4.1,并大量重构代码 2025-06-09 18:17:06 +08:00
ZZY
b27abb55a2 build(project): 更新项目配置和代码结构
- 升级 Godot.NET.Sdk 版本至 4.4.0
- 更新场景文件中的资源引用方式
- 调整部分代码逻辑和数据结构
- 更新项目配置文件
2025-04-21 21:49:03 +08:00
ZZY
e16f76e11f refactor(重构): 重构了事件驱动的代码体系,使用全新命名和版本,以及测试套件的初试
- 移除了.csproj文件
- 更新了.gitignore,添加了.editorconfig
- 重构了IBoard和IPiece接口,引入了新的事件处理机制
- 优化了CCBoard、CCPiece等类的实现,使用新的事件驱动模型
- 删除了冗余代码,提高了代码的可读性和可维护性
2024-11-24 15:42:30 +08:00
ZZY
327c2df94d refactor(chess): 重构象棋程序基础结构,使用Nullable重构核心代码
- 更新了多个文件的代码结构和类型定义,提高了代码的健壮性和可维护性
- 优化了事件处理、棋子管理和移动逻辑,为后续功能扩展打下坚实基础
- 修复了一些潜在的空指针异常问题,提高了程序的稳定性
- 修复部分bug
2024-11-22 23:51:32 +08:00
ZZY
9c619784af feat(重构): 重构棋盘和棋子的逻辑
- 重构了 ChessBoard 和 ChessPiece 类的逻辑
- 添加了新版本的 ChineseChess 库
- 删除了旧版本的 VirtualBoard 和 VirtualPiece 类
- 更新了相关的场景和脚本
2024-11-22 20:01:40 +08:00
ZZY
8ee9732a6f feat(chinese-chess): 实现中国象棋核心逻辑和基本功能
- 抽象出 ChessCore 类,包含游戏初始化、行棋逻辑、悔棋等功能
- 重构 Player 类,优化行棋和记录逻辑
- 更新 ChessBoard 和 ChessPiece 类,适应新逻辑
- 移除冗余代码,提高代码可读性和可维护性
2024-11-07 20:48:08 +08:00
ZZY
6daf09b300 feat(chess): 初始化棋盘和玩家颜色,优化代码结构,重构多人游戏代码
- 在 ChessGame 中初始化棋盘,根据玩家颜色设置棋子
- 更新 UI 显示玩家颜色
- 调整棋盘位置和 UI 布局
- 重构部分代码以提高可读性和维护性
2024-11-07 15:28:03 +08:00
ZZY
d6cbb5e11d feat: 重构多人游戏界面和代码,使用文件存储游戏配置,重构底层代码 2024-11-06 22:13:03 +08:00
ZZY
d323a0bee7 bugfix 重新提交暂存区 2024-11-03 21:15:33 +08:00
86 changed files with 6806 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

41
.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
# Godot 4+ specific ignores
.godot/
# Godot-specific
*.import
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
mono_crash.*.json
# MyProject specific ignores
.*/
.vscode/
.vs/
android/
Bin/
bin/
*.aseprite
*.csproj.old*
*.sln
*.error
*.key*
.editorconfig
# cs project files
!*.csproj
# Test files
Test/bin
Test/obj
# temp ignores
Lang/*
*.zip

38
Asserts/Chessboard.tres Normal file
View File

@ -0,0 +1,38 @@
[gd_resource type="TileSet" load_steps=7 format=3 uid="uid://bibjj3ay65ral"]
[ext_resource type="Texture2D" uid="uid://p060studh7ph" path="res://Asserts/pic/chessboard.png" id="2_5i53i"]
[ext_resource type="Texture2D" uid="uid://dwjo6wa85r4il" path="res://Asserts/pic/cross_set_plus.png" id="3_ouwnc"]
[sub_resource type="TileMapPattern" id="TileMapPattern_s62cf"]
tile_data = PackedInt32Array(0, 36, 1610612737, 131072, 37, 1073741824, 262144, 196644, 1073741824, 393216, 196644, 1073741824, 524288, 36, 805306369, 2, 196644, 0, 131074, 131108, 0, 262146, 196644, 1, 393218, 131108, 0, 524290, 196644, 536870912, 4, 196644, 0, 131076, 196644, 1, 262148, 131108, 0, 393220, 131108, 0, 524292, 196644, 536870912, 6, 196644, 0, 131078, 131108, 0, 262150, 65572, 1, 393222, 65572, 2, 524294, 131108, 536870913, 8, 196644, 0, 131080, 196644, 1, 262152, 65572, 1342177282, 393224, 262180, 1, 524296, 262180, 536870912, 10, 196644, 0, 131082, 131108, 0, 262154, 65572, 1342177281, 393226, 65572, 268435458, 524298, 131108, 805306369, 12, 196644, 0, 131084, 196644, 1, 262156, 131108, 0, 393228, 131108, 0, 524300, 196644, 536870912, 14, 196644, 0, 131086, 131108, 0, 262158, 196644, 1, 393230, 131108, 0, 524302, 196644, 536870912, 16, 36, 1879048193, 131088, 37, 1342177280, 262160, 196644, 1342177280, 393232, 196644, 1342177280, 524304, 36, 536870913)
[sub_resource type="TileMapPattern" id="TileMapPattern_3yn03"]
tile_data = PackedInt32Array(0, 36, 268435457, 131072, 196644, 1610612736, 262144, 196644, 1610612736, 393216, 37, 1610612736, 524288, 36, 1073741825, 655360, 36, 1610612737, 786432, 37, 1073741824, 917504, 196644, 1073741824, 1048576, 196644, 1073741824, 1179648, 36, 805306369, 2, 196644, 0, 131074, 131108, 536870912, 262146, 196644, 536870913, 393218, 131108, 536870912, 524290, 196644, 536870912, 655362, 196644, 0, 786434, 131108, 0, 917506, 196644, 1, 1048578, 131108, 0, 1179650, 196644, 536870912, 4, 196644, 0, 131076, 131108, 536870912, 262148, 131108, 536870912, 393220, 196644, 536870913, 524292, 196644, 536870912, 655364, 196644, 0, 786436, 196644, 1, 917508, 131108, 0, 1048580, 131108, 0, 1179652, 196644, 536870912, 6, 131108, 1, 131078, 65572, 536870914, 262150, 65572, 536870913, 393222, 131108, 536870912, 524294, 196644, 536870912, 655366, 196644, 0, 786438, 131108, 0, 917510, 65572, 1, 1048582, 65572, 2, 1179654, 131108, 536870913, 8, 262180, 0, 131080, 262180, 536870913, 262152, 65572, 1879048194, 393224, 196644, 536870913, 524296, 196644, 536870912, 655368, 196644, 0, 786440, 196644, 1, 917512, 65572, 1342177282, 1048584, 262180, 1, 1179656, 262180, 536870912, 10, 131108, 268435457, 131082, 65572, 805306370, 262154, 65572, 1879048193, 393226, 131108, 536870912, 524298, 196644, 536870912, 655370, 196644, 0, 786442, 131108, 0, 917514, 65572, 1342177281, 1048586, 65572, 268435458, 1179658, 131108, 805306369, 12, 196644, 0, 131084, 131108, 536870912, 262156, 131108, 536870912, 393228, 196644, 536870913, 524300, 196644, 536870912, 655372, 196644, 0, 786444, 196644, 1, 917516, 131108, 0, 1048588, 131108, 0, 1179660, 196644, 536870912, 14, 196644, 0, 131086, 131108, 536870912, 262158, 196644, 536870913, 393230, 131108, 536870912, 524302, 196644, 536870912, 655374, 196644, 0, 786446, 131108, 0, 917518, 196644, 1, 1048590, 131108, 0, 1179662, 196644, 536870912, 16, 36, 1, 131088, 196644, 1879048192, 262160, 196644, 1879048192, 393232, 37, 1879048192, 524304, 36, 1342177281, 655376, 36, 1879048193, 786448, 37, 1342177280, 917520, 196644, 1342177280, 1048592, 196644, 1342177280, 1179664, 36, 536870913)
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7ihb1"]
texture = ExtResource("2_5i53i")
texture_region_size = Vector2i(32, 32)
0:0/0 = 0
1:0/0 = 0
2:0/0 = 0
3:0/0 = 0
4:0/0 = 0
0:1/0 = 0
1:1/0 = 0
2:1/0 = 0
3:1/0 = 0
4:1/0 = 0
0:2/0 = 0
1:2/0 = 0
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_je1ok"]
texture = ExtResource("3_ouwnc")
texture_region_size = Vector2i(32, 32)
0:0/0 = 0
[resource]
sources/36 = SubResource("TileSetAtlasSource_7ihb1")
sources/37 = SubResource("TileSetAtlasSource_je1ok")
tile_proxies/coords_level = [[0, Vector2i(0, 0)], [10, Vector2i(0, 0)], [0, Vector2i(0, 1)], [10, Vector2i(0, 1)], [0, Vector2i(1, 0)], [10, Vector2i(1, 0)], [0, Vector2i(1, 1)], [10, Vector2i(1, 1)], [1, Vector2i(0, 0)], [10, Vector2i(2, 0)], [1, Vector2i(0, 1)], [10, Vector2i(2, 1)], [1, Vector2i(1, 0)], [10, Vector2i(3, 0)], [1, Vector2i(1, 1)], [10, Vector2i(3, 1)], [2, Vector2i(0, 0)], [10, Vector2i(4, 0)], [2, Vector2i(0, 1)], [10, Vector2i(4, 1)], [2, Vector2i(1, 0)], [10, Vector2i(5, 0)], [2, Vector2i(1, 1)], [10, Vector2i(5, 1)], [3, Vector2i(0, 0)], [10, Vector2i(6, 0)], [3, Vector2i(0, 1)], [10, Vector2i(6, 1)], [3, Vector2i(1, 0)], [10, Vector2i(7, 0)], [3, Vector2i(1, 1)], [10, Vector2i(7, 1)], [4, Vector2i(0, 0)], [10, Vector2i(8, 0)], [4, Vector2i(0, 1)], [10, Vector2i(8, 1)], [4, Vector2i(1, 0)], [10, Vector2i(9, 0)], [4, Vector2i(1, 1)], [10, Vector2i(9, 1)], [5, Vector2i(0, 0)], [10, Vector2i(0, 2)], [5, Vector2i(0, 1)], [10, Vector2i(0, 3)], [5, Vector2i(1, 0)], [10, Vector2i(1, 2)], [5, Vector2i(1, 1)], [10, Vector2i(1, 3)], [6, Vector2i(0, 0)], [10, Vector2i(2, 2)], [6, Vector2i(0, 1)], [10, Vector2i(2, 3)], [6, Vector2i(1, 0)], [10, Vector2i(3, 2)], [6, Vector2i(1, 1)], [10, Vector2i(3, 3)], [8, Vector2i(0, 0)], [10, Vector2i(4, 2)], [8, Vector2i(0, 1)], [10, Vector2i(4, 3)], [8, Vector2i(1, 0)], [10, Vector2i(5, 2)], [8, Vector2i(1, 1)], [10, Vector2i(5, 3)], [9, Vector2i(0, 0)], [10, Vector2i(6, 2)], [9, Vector2i(0, 1)], [10, Vector2i(6, 3)], [9, Vector2i(1, 0)], [10, Vector2i(7, 2)], [9, Vector2i(1, 1)], [10, Vector2i(7, 3)], [11, Vector2i(0, 0)], [22, Vector2i(0, 0)], [11, Vector2i(0, 1)], [22, Vector2i(0, 1)], [11, Vector2i(1, 0)], [22, Vector2i(1, 0)], [11, Vector2i(1, 1)], [22, Vector2i(1, 1)], [12, Vector2i(0, 0)], [22, Vector2i(2, 0)], [12, Vector2i(0, 1)], [22, Vector2i(2, 1)], [12, Vector2i(1, 0)], [22, Vector2i(3, 0)], [12, Vector2i(1, 1)], [22, Vector2i(3, 1)], [13, Vector2i(0, 0)], [22, Vector2i(4, 0)], [13, Vector2i(0, 1)], [22, Vector2i(4, 1)], [13, Vector2i(1, 0)], [22, Vector2i(5, 0)], [13, Vector2i(1, 1)], [22, Vector2i(5, 1)], [14, Vector2i(0, 0)], [22, Vector2i(6, 0)], [14, Vector2i(0, 1)], [22, Vector2i(6, 1)], [14, Vector2i(1, 0)], [22, Vector2i(7, 0)], [14, Vector2i(1, 1)], [22, Vector2i(7, 1)], [15, Vector2i(0, 0)], [22, Vector2i(8, 0)], [15, Vector2i(0, 1)], [22, Vector2i(8, 1)], [15, Vector2i(1, 0)], [22, Vector2i(9, 0)], [15, Vector2i(1, 1)], [22, Vector2i(9, 1)], [16, Vector2i(0, 0)], [22, Vector2i(0, 2)], [16, Vector2i(0, 1)], [22, Vector2i(0, 3)], [16, Vector2i(1, 0)], [22, Vector2i(1, 2)], [16, Vector2i(1, 1)], [22, Vector2i(1, 3)], [17, Vector2i(0, 0)], [22, Vector2i(2, 2)], [17, Vector2i(0, 1)], [22, Vector2i(2, 3)], [17, Vector2i(1, 0)], [22, Vector2i(3, 2)], [17, Vector2i(1, 1)], [22, Vector2i(3, 3)], [18, Vector2i(0, 0)], [22, Vector2i(4, 2)], [18, Vector2i(0, 1)], [22, Vector2i(4, 3)], [18, Vector2i(1, 0)], [22, Vector2i(5, 2)], [18, Vector2i(1, 1)], [22, Vector2i(5, 3)], [19, Vector2i(0, 0)], [22, Vector2i(6, 2)], [19, Vector2i(0, 1)], [22, Vector2i(6, 3)], [19, Vector2i(1, 0)], [22, Vector2i(7, 2)], [19, Vector2i(1, 1)], [22, Vector2i(7, 3)], [20, Vector2i(0, 0)], [22, Vector2i(8, 2)], [20, Vector2i(0, 1)], [22, Vector2i(8, 3)], [20, Vector2i(1, 0)], [22, Vector2i(9, 2)], [20, Vector2i(1, 1)], [22, Vector2i(9, 3)], [21, Vector2i(0, 0)], [22, Vector2i(0, 4)], [21, Vector2i(0, 1)], [22, Vector2i(0, 5)], [21, Vector2i(1, 0)], [22, Vector2i(1, 4)], [21, Vector2i(1, 1)], [22, Vector2i(1, 5)]]
pattern_0 = SubResource("TileMapPattern_s62cf")
pattern_1 = SubResource("TileMapPattern_3yn03")

View File

@ -0,0 +1,4 @@
[gd_resource type="CompressedTexture2D" format=3 uid="uid://bthav6cae4fni"]
[resource]
load_path = "res://.godot/imported/base.png-b2215abe8c7e720db36c2fb00d361b79.ctex"

2603
Asserts/GameTheme.tres Normal file

File diff suppressed because one or more lines are too long

1
Asserts/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" fill="#478cbf"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>

After

Width:  |  Height:  |  Size: 949 B

BIN
Asserts/pic/base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

BIN
Asserts/pic/chessboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
Asserts/pic/col.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

1027
Asserts/pic/col.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 58 KiB

BIN
Asserts/pic/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
Asserts/pic/cross_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

BIN
Asserts/pic/cross_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

BIN
Asserts/pic/cross_1_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

BIN
Asserts/pic/cross_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

BIN
Asserts/pic/cross_plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
Asserts/pic/cross_set.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

BIN
Asserts/pic/eight_cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

BIN
Asserts/pic/row.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

24
ChessGame.csproj Normal file
View File

@ -0,0 +1,24 @@
<Project Sdk="Godot.NET.Sdk/4.4.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors> -->
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test\**\*" />
<Compile Remove="android\**\*" />
<Compile Remove="Protos\**\*" />
<!-- <Protobuf Include="Protos\chess.proto" GrpcServices="Client" /> -->
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
<PackageReference Include="Grpc.Net.Client" Version="2.70.0" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="KeraLua" Version="1.4.5" />
<PackageReference Include="NLua" Version="1.7.5" />
</ItemGroup>
</Project>

32
Main.cs Normal file
View File

@ -0,0 +1,32 @@
using Godot;
public partial class Main : Node2D {
// Called when the node enters the scene tree for the first time.
GlobalManager global = null!;
public override void _Ready() {
// GetTree().Connect("screen_resized", ResizeChessboardToFitScreen);
global = GlobalManager.Instance;
global.GlobalTheme = GetChild<Control>(GetChildCount() - 1).Theme;
global.GlobalThemeConfigFlush();
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void GoToSignlePlayer() {
global?.GotoScene("res://Scenes/ChessGame.tscn");
}
private void GoToMultiPlayer() {
global?.GotoScene("res://Scenes/GameLobby.tscn");
}
private void GoToSetting() {
global?.GotoScene("res://Scenes/Setting.tscn");
}
private void GotoMods() {
global?.GotoScene("res://Scenes/Mods.tscn");
}
}

1
Main.cs.uid Normal file
View File

@ -0,0 +1 @@
uid://kdxc7ummin4b

59
Main.tscn Normal file
View File

@ -0,0 +1,59 @@
[gd_scene load_steps=3 format=3 uid="uid://boa4od72355o4"]
[ext_resource type="Script" uid="uid://kdxc7ummin4b" path="res://Main.cs" id="1_h4cv2"]
[ext_resource type="Theme" uid="uid://intlbeu8h82r" path="res://Asserts/GameTheme.tres" id="2_afihr"]
[node name="Main" type="Node2D"]
script = ExtResource("1_h4cv2")
[node name="CenterContainer" type="CenterContainer" parent="."]
offset_right = 720.0
offset_bottom = 1280.0
theme = ExtResource("2_afihr")
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
custom_minimum_size = Vector2(720, 720)
layout_mode = 2
theme_override_constants/separation = 30
[node name="Label" type="Label" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 6
theme_override_font_sizes/font_size = 48
text = "KEY_ChessGame"
horizontal_alignment = 1
vertical_alignment = 1
[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 30
theme_override_constants/margin_right = 30
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="SignlePlayer" type="Button" parent="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "KEY_SinglePlayer"
[node name="MultiPlayer" type="Button" parent="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "KEY_MultiPlayer"
[node name="Setting" type="Button" parent="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "KEY_Setting"
[node name="Mod" type="Button" parent="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "KEY_Mode"
[connection signal="pressed" from="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer/SignlePlayer" to="." method="GoToSignlePlayer"]
[connection signal="pressed" from="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer/MultiPlayer" to="." method="GoToMultiPlayer"]
[connection signal="pressed" from="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/Setting" to="." method="GoToSetting"]
[connection signal="pressed" from="CenterContainer/VBoxContainer/MarginContainer/VBoxContainer/Mod" to="." method="GotoMods"]

110
Scenes/ChessGame.tscn Normal file
View File

@ -0,0 +1,110 @@
[gd_scene load_steps=4 format=3 uid="uid://g40y10iaf7qb"]
[ext_resource type="Script" uid="uid://cqgctouhh8qok" path="res://Scripts/Controllers/ChessGame.cs" id="1_3x8ac"]
[ext_resource type="PackedScene" uid="uid://b1tx7v3230wab" path="res://Scenes/Entities/Chessboard.tscn" id="1_yheur"]
[ext_resource type="Theme" uid="uid://intlbeu8h82r" path="res://Asserts/GameTheme.tres" id="3_rcfhx"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("3_rcfhx")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 133.0
grow_horizontal = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/MarginContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Home" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "KEY_BackHome"
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Undo" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 4
text = "KEY_GameUndo"
[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ReInit" type="Button" parent="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3"]
unique_name_in_owner = true
layout_mode = 2
text = "KEY_GameReInit"
[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HFlowContainer" type="HFlowContainer" parent="VBoxContainer/MarginContainer3"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/MarginContainer3/HFlowContainer"]
layout_mode = 2
text = "KEY_Content_YouAre"
[node name="LineEdit_YouAre" type="LineEdit" parent="VBoxContainer/MarginContainer3/HFlowContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
editable = false
[node name="Label2" type="Label" parent="VBoxContainer/MarginContainer3/HFlowContainer"]
layout_mode = 2
text = "KEY_Content_TurnOn"
[node name="LineEdit_TurnSide" type="LineEdit" parent="VBoxContainer/MarginContainer3/HFlowContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
editable = false
[node name="MarginContainer2" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -114.0
grow_horizontal = 2
grow_vertical = 0
[node name="Over" type="Button" parent="MarginContainer2"]
unique_name_in_owner = true
layout_mode = 2
text = "KEY_OverThisTurn"
[node name="ChessGame" type="Node2D" parent="."]
unique_name_in_owner = true
script = ExtResource("1_3x8ac")
[node name="Chessboard" parent="ChessGame" instance=ExtResource("1_yheur")]
unique_name_in_owner = true
position = Vector2(360, 650)
scale = Vector2(2.5, 2.5)
[connection signal="pressed" from="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer/Home" to="ChessGame" method="GoHome"]
[connection signal="pressed" from="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2/Undo" to="ChessGame" method="Undo"]
[connection signal="pressed" from="VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3/ReInit" to="ChessGame" method="ReInit"]
[connection signal="pressed" from="MarginContainer2/Over" to="ChessGame" method="BtnOver"]

View File

@ -0,0 +1,22 @@
[gd_scene load_steps=4 format=4 uid="uid://b1tx7v3230wab"]
[ext_resource type="Script" uid="uid://dmc012tt32dkl" path="res://Scripts/Entities/ChessBoard.cs" id="1_pjakt"]
[ext_resource type="TileSet" uid="uid://bibjj3ay65ral" path="res://Asserts/Chessboard.tres" id="1_ws3cq"]
[ext_resource type="Script" uid="uid://c8eafskfpuesm" path="res://Scripts/Entities/ChessPiece.cs" id="3_26g24"]
[node name="Chessboard" type="Node2D"]
script = ExtResource("1_pjakt")
[node name="Sprite2D" type="Sprite2D" parent="."]
visible = false
script = ExtResource("3_26g24")
[node name="Layer0" type="TileMapLayer" parent="."]
show_behind_parent = true
position = Vector2(-8, 184)
tile_map_data = PackedByteArray("AAAAAPv/JAAEAAEAAAD+//v/JAABAAIAAAACAPv/JAABAAIAABAAAPn/JAABAAIAAFD+//n/JAABAAEAAAACAPn/JAABAAEAAFAAAP3/JAAEAAAAACD+//3/JAACAAEAACACAP3/JAACAAEAADAEAP3/JAADAAAAACAGAP3/JAADAAAAACAIAP3/JAAAAAEAACD8//3/JAADAAAAACD6//3/JAADAAAAACD4//3/JAAAAAEAADAEAPv/JAACAAAAAAAGAPv/JAACAAAAAAAEAPn/JAACAAAAAAD8//v/JAACAAAAAAD8//n/JAACAAAAAAD6//v/JAACAAAAAAD6//f/JAACAAAAAAD+//f/JAACAAAAAAACAPf/JAACAAAAAAAGAPf/JAACAAAAAAAAAPX/JAADAAAAAAACAPX/JAADAAAAAAAEAPX/JAADAAAAAAAGAPX/JAADAAAAAAD+//X/JAADAAAAAAD8//X/JAADAAAAAAD6//X/JAADAAAAAAAIAPn/JAADAAAAAFD4//v/JAADAAAAAED4//n/JAADAAAAAED4//X/JAAAAAEAAGAIAPX/JAAAAAEAAHD6//n/JAADAAEAAAAGAPn/JAADAAEAAAAIAPv/JAADAAAAAFD8//f/JAADAAEAAAAAAPf/JAADAAEAAAAEAPf/JAADAAEAAAAIAPf/JQAAAAAAAFD4//f/JQAAAAAAAED4//P/JAAAAAEAAED6//P/JAADAAAAACD8//P/JAADAAAAACD+//P/JAADAAAAACAAAPP/JAADAAAAACACAPP/JAADAAAAACAEAPP/JAADAAAAACAGAPP/JAADAAAAACAIAPP/JAAAAAEAAFD4//H/JQAAAAAAAGD6//H/JAACAAAAACD8//H/JAADAAEAACD+//H/JAACAAAAACAAAPH/JAADAAEAACACAPH/JAACAAAAACAEAPH/JAADAAEAACAGAPH/JAACAAAAACAIAPH/JQAAAAAAAHD4/+//JAADAAAAAGD6/+//JAADAAEAACD8/+//JAACAAAAACD+/+//JAABAAEAACAAAO//JAABAAIAAHACAO//JAABAAEAAHAEAO//JAACAAAAACAGAO//JAADAAEAACAIAO//JAADAAAAAHD4/+3/JAADAAAAAGD6/+3/JAACAAAAACD8/+3/JAACAAAAACD+/+3/JAABAAIAACAAAO3/JAAEAAEAACACAO3/JAABAAIAADAEAO3/JAACAAAAACAGAO3/JAACAAAAACAIAO3/JAADAAAAAHD4/+v/JAAAAAEAABD6/+v/JAADAAAAAAD8/+v/JAADAAAAAAD+/+v/JAACAAEAAAAAAOv/JAAEAAAAAAACAOv/JAACAAEAABAEAOv/JAADAAAAAAAGAOv/JAADAAAAAAAIAOv/JAAAAAEAAAA=")
tile_set = ExtResource("1_ws3cq")
collision_enabled = false
collision_visibility_mode = 2
navigation_enabled = false
navigation_visibility_mode = 2

View File

@ -0,0 +1,10 @@
[gd_scene load_steps=3 format=3 uid="uid://gkbtavjf2273"]
[ext_resource type="Texture2D" uid="uid://bthav6cae4fni" path="res://Asserts/ChesspieceBase.tres" id="1_8v1j6"]
[ext_resource type="Script" uid="uid://c8eafskfpuesm" path="res://Scripts/Entities/ChessPiece.cs" id="2_y54gx"]
[node name="Chesspiece" type="Node2D"]
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_8v1j6")
script = ExtResource("2_y54gx")

View File

@ -0,0 +1,23 @@
[gd_scene format=3 uid="uid://pc83bstfltn"]
[node name="ServerStatus" type="HBoxContainer"]
anchors_preset = -1
anchor_right = 0.176389
anchor_bottom = 0.0179687
offset_right = -126.0
metadata/_edit_use_anchors_ = true
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 0
text = "KEY_ServerConnectStatus"
[node name="ColorRect" type="ColorRect" parent="."]
custom_minimum_size = Vector2(15, 15)
layout_mode = 2
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 2
[node name="TextureProgressBar" type="TextureProgressBar" parent="."]
layout_mode = 2

78
Scenes/GameLobby.tscn Normal file
View File

@ -0,0 +1,78 @@
[gd_scene load_steps=3 format=3 uid="uid://00jxjgnmfbn7"]
[ext_resource type="Theme" uid="uid://intlbeu8h82r" path="res://Asserts/GameTheme.tres" id="1_ge0tq"]
[ext_resource type="Script" uid="uid://cn273e4u256r" path="res://Scripts/Controllers/GameLobby.cs" id="2_blx46"]
[node name="GameLobby" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_ge0tq")
script = ExtResource("2_blx46")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
text = "KEY_BackHome"
[node name="MarginContainer2" type="HFlowContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/MarginContainer2"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/MarginContainer2/MarginContainer"]
layout_mode = 2
text = "KEY_ServerConnectStatus"
[node name="MarginContainer2" type="MarginContainer" parent="MarginContainer/VBoxContainer/MarginContainer2"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect" type="ColorRect" parent="MarginContainer/VBoxContainer/MarginContainer2/MarginContainer2"]
layout_mode = 2
size_flags_horizontal = 3
[node name="MarginContainer3" type="MarginContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ItemList_Users" type="ItemList" parent="MarginContainer/VBoxContainer/MarginContainer3"]
unique_name_in_owner = true
layout_mode = 2
[node name="Dialogs" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
[node name="AcceptDialog" type="AcceptDialog" parent="Dialogs"]
auto_translate_mode = 1
initial_position = 2
size = Vector2i(100, 118)
[node name="PopupMenu" type="PopupMenu" parent="Dialogs"]
auto_translate_mode = 1
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="Dialogs"]
auto_translate_mode = 1
initial_position = 2
size = Vector2i(200, 118)
[connection signal="pressed" from="MarginContainer/VBoxContainer/MarginContainer/Button" to="." method="OnBack"]
[connection signal="item_activated" from="MarginContainer/VBoxContainer/MarginContainer3/ItemList_Users" to="." method="OnItemSelected"]

60
Scenes/Mods.tscn Normal file
View File

@ -0,0 +1,60 @@
[gd_scene load_steps=3 format=3 uid="uid://c2ov83uk7r6rq"]
[ext_resource type="Theme" uid="uid://intlbeu8h82r" path="res://Asserts/GameTheme.tres" id="1_mhtia"]
[ext_resource type="Script" uid="uid://c8t8d8e7inajy" path="res://Scripts/Controllers/Mods.cs" id="1_mnyng"]
[node name="MarginContainer" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_mhtia")
theme_override_constants/margin_left = 30
theme_override_constants/margin_top = 30
theme_override_constants/margin_right = 30
theme_override_constants/margin_bottom = 30
script = ExtResource("1_mnyng")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="Button" type="Button" parent="VBoxContainer"]
layout_mode = 2
text = "KEY_BackHome"
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="CheckButton" type="CheckButton" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "KEY_EnableMod"
[node name="Button2" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "KEY_ReloadMod"
[node name="Button2" type="Button" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "KEY_OpenModDir"
[node name="VScrollBar" type="VScrollBar" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 1
size_flags_vertical = 3
[node name="ItemList" type="ItemList" parent="VBoxContainer/VScrollBar"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[connection signal="pressed" from="VBoxContainer/Button" to="." method="OnBack"]
[connection signal="toggled" from="VBoxContainer/HBoxContainer/CheckButton" to="." method="OnEnableMods"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Button2" to="." method="OnReSearchMods"]
[connection signal="pressed" from="VBoxContainer/Button2" to="." method="OnOpenModFileDir"]

211
Scenes/Setting.tscn Normal file
View File

@ -0,0 +1,211 @@
[gd_scene load_steps=5 format=3 uid="uid://c6gxufppw1fu3"]
[ext_resource type="Theme" uid="uid://intlbeu8h82r" path="res://Asserts/GameTheme.tres" id="1_6yfoi"]
[ext_resource type="Script" uid="uid://71ril3nh84rw" path="res://Scripts/Controllers/Setting.cs" id="1_xbvb3"]
[ext_resource type="PackedScene" uid="uid://pc83bstfltn" path="res://Scenes/Entities/ServerStatus.tscn" id="3_e50hu"]
[sub_resource type="ButtonGroup" id="ButtonGroup_efio3"]
[node name="Setting" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_6yfoi")
script = ExtResource("1_xbvb3")
[node name="BoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Back" type="Button" parent="BoxContainer/MarginContainer"]
layout_mode = 2
text = "KEY_BackHome"
[node name="MarginContainer7" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Button" type="Button" parent="BoxContainer/MarginContainer7"]
layout_mode = 2
text = "KEY_SaveConfigFile"
[node name="MarginContainer9" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Button" type="Button" parent="BoxContainer/MarginContainer9"]
layout_mode = 2
text = "KEY_GetConfigDir"
[node name="MarginContainer2" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Server" type="HFlowContainer" parent="BoxContainer/MarginContainer2"]
layout_mode = 2
[node name="Label" type="Label" parent="BoxContainer/MarginContainer2/Server"]
layout_mode = 2
text = "KEY_ServerIP"
[node name="LineEdit_ServerIP" type="LineEdit" parent="BoxContainer/MarginContainer2/Server"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="MarginContainer3" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Name" type="HFlowContainer" parent="BoxContainer/MarginContainer3"]
layout_mode = 2
[node name="Label" type="Label" parent="BoxContainer/MarginContainer3/Name"]
layout_mode = 2
text = "KEY_UserName"
[node name="LineEdit_UserName" type="LineEdit" parent="BoxContainer/MarginContainer3/Name"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Button" type="Button" parent="BoxContainer/MarginContainer3/Name"]
visible = false
layout_mode = 2
text = "OK"
[node name="MarginContainer4" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="HFlowContainer" type="HFlowContainer" parent="BoxContainer/MarginContainer4"]
layout_mode = 2
[node name="ServerStatus" parent="BoxContainer/MarginContainer4/HFlowContainer" instance=ExtResource("3_e50hu")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CheckButton" type="CheckButton" parent="BoxContainer/MarginContainer4/HFlowContainer"]
visible = false
layout_mode = 2
text = "Server Start"
[node name="MarginContainer5" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HFlowContainer" parent="BoxContainer/MarginContainer5"]
layout_mode = 2
[node name="Label" type="Label" parent="BoxContainer/MarginContainer5/HBoxContainer"]
layout_mode = 2
text = "KEY_GlobalFontSize"
[node name="FontSize" type="LineEdit" parent="BoxContainer/MarginContainer5/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
editable = false
[node name="MarginContainer6" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="FontSizeBar" type="HSlider" parent="BoxContainer/MarginContainer6"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
min_value = 10.0
max_value = 40.0
value = 10.0
[node name="HBoxContainer2" type="HFlowContainer" parent="BoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="BoxContainer/HBoxContainer2"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Button" type="Button" parent="BoxContainer/HBoxContainer2/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
toggle_mode = true
button_group = SubResource("ButtonGroup_efio3")
text = "简体中文"
[node name="MarginContainer2" type="MarginContainer" parent="BoxContainer/HBoxContainer2"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Button2" type="Button" parent="BoxContainer/HBoxContainer2/MarginContainer2"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_efio3")
text = "English"
[node name="HBoxContainer" type="HFlowContainer" parent="BoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="BoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Button" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "Clear Config"
[node name="MarginContainer2" type="MarginContainer" parent="BoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Button2" type="Button" parent="BoxContainer/HBoxContainer/MarginContainer2"]
layout_mode = 2
disabled = true
text = "Clear User Data"
[node name="HFlowContainer" type="HFlowContainer" parent="BoxContainer"]
layout_mode = 2
[node name="Button" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "GetCacheDir"
[node name="Button2" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "GetConfigDir"
[node name="Button3" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "GetDataDir"
[node name="Button4" type="Button" parent="BoxContainer/HFlowContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "GetUserDataDir"
[node name="MarginContainer8" type="MarginContainer" parent="BoxContainer"]
layout_mode = 2
[node name="ColorPickerButton" type="ColorPickerButton" parent="BoxContainer/MarginContainer8"]
layout_mode = 2
text = "Trace Color"
[connection signal="pressed" from="BoxContainer/MarginContainer/Back" to="." method="OnBack"]
[connection signal="pressed" from="BoxContainer/MarginContainer7/Button" to="." method="OnSave"]
[connection signal="pressed" from="BoxContainer/MarginContainer9/Button" to="." method="OnGetSettingsFile"]
[connection signal="text_changed" from="BoxContainer/MarginContainer2/Server/LineEdit_ServerIP" to="." method="OnServerUrlChanged"]
[connection signal="text_changed" from="BoxContainer/MarginContainer3/Name/LineEdit_UserName" to="." method="OnNameChanged"]
[connection signal="value_changed" from="BoxContainer/MarginContainer6/FontSizeBar" to="." method="OnFontSizeChanged"]
[connection signal="toggled" from="BoxContainer/HBoxContainer2/MarginContainer/Button" to="." method="ChangeLangZHCN"]
[connection signal="toggled" from="BoxContainer/HBoxContainer2/MarginContainer2/Button2" to="." method="ChangeLangEN"]
[connection signal="pressed" from="BoxContainer/HBoxContainer/MarginContainer2/Button2" to="." method="OnClearUserData"]
[connection signal="pressed" from="BoxContainer/HFlowContainer/Button" to="." method="OnGetCacheDir"]
[connection signal="pressed" from="BoxContainer/HFlowContainer/Button2" to="." method="OnGetConfigDir"]
[connection signal="pressed" from="BoxContainer/HFlowContainer/Button3" to="." method="OnGetDataDir"]
[connection signal="pressed" from="BoxContainer/HFlowContainer/Button4" to="." method="OnGetUserDataDir"]

View File

@ -0,0 +1,74 @@
using Godot;
using ChineseChess;
public partial class ChessGame : Node2D {
GlobalManager global = GlobalManager.Instance;
readonly Logging.Logger logger = Logging.GetLogger("ChessGame");
ChessBoard board = null!;
ConfirmationDialog dialog = null!;
ChessCore Game = null!;
private bool isSession = false;
ChessCore.TurnsSideType sideSelf;
ChessCore.TurnsSideType sideOpposite;
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
board = GetNode<ChessBoard>("%Chessboard");
GetNode<LineEdit>("%LineEdit_YouAre").Text = global.GlobalData["player_color"].AsString();
LineEdit turnSideEdit = GetNode<LineEdit>("%LineEdit_TurnSide");
turnSideEdit.Text = "red";
dialog = new ConfirmationDialog {
DialogAutowrap = true,
MinSize = new Vector2I(400, 200),
Position = new Vector2I(200, 400),
};
AddChild(dialog);
if (global.GlobalData["player_color"].AsString() == "red") {
sideSelf = ChessCore.TurnsSideType.Red;
sideOpposite = ChessCore.TurnsSideType.Black;
} else {
sideSelf = ChessCore.TurnsSideType.Black;
sideOpposite = ChessCore.TurnsSideType.Red;
}
Game = new(isSession ? ChessCore.Mode.MultiMode : ChessCore.Mode.SingleMode, sideSelf, board);
board.OnStepsChanged += (sender, e) => {
turnSideEdit.Text = Game.GetTurnsType() == ChessCore.TurnsSideType.Red ? "red" : "black";
};
board.OnPosClicked += (sender, pos) => {
int posX = (int)pos.X;
int posY = (int)pos.Y;
Vector.Vector2I vector = new(posX, posY);
Game.OnPosClicked(vector);
};
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void BtnOver() {
logger.Info($"BtnOver");
}
public void GoHome() {
// TODO ClearServer();
global.GotoScene("res://Main.tscn");
}
public void Undo() {
logger.Info($"Undo");
if (Game.board.Steps == 0) board.Clear();
Game.Undo();
}
public void ReInit() {
// TODO using dialog to confirm
logger.Info($"ReInit");
Game.ReInit();
}
}

View File

@ -0,0 +1 @@
uid://cqgctouhh8qok

View File

@ -0,0 +1,60 @@
using Godot;
using Godot.Collections;
public partial class GameLobby : Control {
GlobalManager global = GlobalManager.Instance;
readonly Logging.Logger logger = Logging.GetLogger("GameLobby");
Timer timer = null!;
ItemList lists = null!;
ConfirmationDialog dialog = null!;
string URL = null!;
string userName = null!;
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
lists = GetNode<ItemList>("%ItemList_Users");
// TODO using %
dialog = GetNode<ConfirmationDialog>("Dialogs/ConfirmationDialog");
URL = global.ConfigManager.GetValue<string>("server_addr");
userName = global.ConfigManager.GetValue<string>("user_name");
dialog.MinSize = new Vector2I(400, 200);
dialog.DialogAutowrap = true;
dialog.Canceled += () => {
// TODO Cancel to Accept Session Create
};
dialog.Confirmed += () => {
if (dialog.Title == "Session Created") {
// TODO Create Session
}
};
// TODO using new State Shower And Using Heartbeat to calucate the delay time
ColorRect colorRect = GetNode<ColorRect>("MarginContainer/VBoxContainer/MarginContainer2/MarginContainer2/ColorRect");
Connect();
}
public void Connect() {
logger.Info("Connect To Server");
// TODO connet to server
global.SetProcess(true);
}
private void OnItemSelected(int index) {
Dictionary item = lists.GetItemMetadata(index).AsGodotDictionary();
logger.Debug($"Item {index} selected, {item}");
string[] _ = [item["id"].ToString()];
// TODO new a Session
}
private void FlushUserData() {
// TODO Flush user data or heartbeat flush
}
private void OnBack() {
// TODO ExitServer();
global.GotoScene("res://Main.tscn");
}
}

View File

@ -0,0 +1 @@
uid://cn273e4u256r

View File

@ -0,0 +1,43 @@
using Godot;
using System;
public partial class Mods : Control {
GlobalManager global = GlobalManager.Instance;
ItemList modsItemList = null!;
public override void _Ready() {
// TODO
modsItemList = GetNode<ItemList>("VBoxContainer/VScrollBar/ItemList");
FlushModState();
}
private void FlushModState() {
modsItemList.Clear();
foreach (var mod in global.Mods.ModList) {
var item = modsItemList.AddItem(mod.Key);
}
}
private void OnEnableMods(bool enable) {
if (enable) {
global.Mods.LoadAllMods();
}
else {
global.Mods.ClearAllMods();
}
}
private void OnReSearchMods() {
global.Mods.SearchAllMods(true);
}
public void OnOpenModFileDir() {
string path = global.ConfigManager.GetValue<string>("mods_fpath") ?? "user://mods";
OS.ShellOpen(ProjectSettings.GlobalizePath(path));
}
public void OnBack() {
ArgumentNullException.ThrowIfNull(global);
global.GotoScene("res://Main.tscn");
}
}

View File

@ -0,0 +1 @@
uid://c8t8d8e7inajy

View File

@ -0,0 +1,91 @@
using Godot;
using System;
using System.IO;
public partial class Setting : Control {
private GlobalManager global = GlobalManager.Instance;
private readonly IConfigManager config = GlobalManager.Instance.ConfigManager;
private int font_size;
private LineEdit fontOut = null!;
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
fontOut = GetNode<LineEdit>("%FontSize");
font_size = config.GetValue<int>("font_size");
GetNode<HSlider>("%FontSizeBar")
.Value = font_size;
fontOut.Text = font_size.ToString();
GetNode<LineEdit>("%LineEdit_ServerIP")
.Text = config.GetValue<string>("server_addr");
GetNode<LineEdit>("%LineEdit_UserName")
.Text = config.GetValue<string>("user_name");
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
}
private void OnBack() {
global.GotoScene("res://Main.tscn", null);
}
private void OnSave() {
global.SaveConfig();
}
void OnNameChanged(string Value) {
config.SetValue("user_name", Value);
}
private void OnServerUrlChanged(string Value) {
config.SetValue("server_url", Value);
}
private void OnFontSizeChanged(float Value) {
font_size = (int)Value;
config.SetValue("font_size", font_size);
global.GlobalThemeConfigFlush();
fontOut.Text = font_size.ToString();
}
private static void OnClearData() {
// ProjectSettings.GlobalizePath("user://");
// DirAccess.CopyAbsolute("res://userdata", "user://");
}
private static void OnClearUserData() {
string path = ProjectSettings.GlobalizePath("user://");
// DirAccess dirAccess = DirAccess.Open(path);
// dirAccess.Remove(path);
if (DirAccess.RemoveAbsolute(path) != Error.Ok) {
FormattableString msg = $"Failed to delete user data at {path}";
OS.Alert(msg.ToString(), "Error");
}
}
private static void OnGetSettingsFile() {
string path = ProjectSettings.GlobalizePath(GlobalManager.ConfigPath);
OS.ShellOpen(Path.GetDirectoryName(path));
}
private static void OnGetCacheDir() {
OS.Alert(OS.GetCacheDir(), "Cache Dir");
}
private static void OnGetConfigDir() {
OS.Alert(OS.GetConfigDir(), "Config Dir");
}
private static void OnGetDataDir() {
OS.Alert(OS.GetDataDir(), "Data Dir");
}
private static void OnGetUserDataDir() {
OS.Alert(OS.GetUserDataDir(), "User Data Dir");
}
private static void ChangeLangZHCN(bool _) => TranslationServer.SetLocale("zh_CN");
private static void ChangeLangEN(bool _) => TranslationServer.SetLocale("en");
}

View File

@ -0,0 +1 @@
uid://71ril3nh84rw

View File

@ -0,0 +1,87 @@
// Chessboard.cs
using System;
using Godot;
using ChineseChess;
using Godot.Collections;
using static IBoard;
public partial class ChessBoard : Node2D, ICCBoardOn {
public event EventHandler<Vector2>? OnMouseClicked;
public event EventHandler<Vector2>? OnPosClicked;
public event EventHandler<int>? OnStepsChanged;
public Vector2 from = Vector2.Inf, to = Vector2.Inf;
public Array<Vector2> canMovePos = [];
public override void _Ready() {
}
public void Clear() {
from = to = Vector2.Inf;
QueueRedraw();
}
void IBoardOn.OnInsert(object self, IPiece piece) {
ChessPiece? node = piece.On as ChessPiece;
// throw new InvalidOperationException();
node ??= new ChessPiece((CCPiece)piece);
node.ShowBehindParent = true;
AddChild(node);
// ChessPiece chessPiece = null;
// if (piece.Data.TryGetValue("Godot", out object node)) {
// chessPiece = node as ChessPiece;
// } else {
// chessPiece = new((CCPiece)piece);
// }
// chessPiece.ShowBehindParent = true;
// AddChild(chessPiece);
}
void IBoardOn.OnRemove(object self, IPiece piece) {
if (piece.On is not ChessPiece node) {
throw new InvalidOperationException();
}
RemoveChild(node);
}
void IBoardOn.OnMove(object self, IBoardOn.MoveEventArgs vals) {
from = PosTrans.transArrToPix * new Vector2(vals.From.X, vals.From.Y);
to = PosTrans.transArrToPix * new Vector2(vals.To.X, vals.To.Y);
QueueRedraw();
}
void ICCBoardOn.OnSteps(object _self, int oldSteps) {
if (_self is not CCBoard self) return;
OnStepsChanged?.Invoke(self, self.Steps);
}
public override void _Draw() {
Color color = new(0.7f, 0.7f, 0.7f, 0.5f);
DrawLine(from, to, color, PosTrans.pixGripSize / 5);
DrawCircle(from, PosTrans.pixGripSize / 2.2f, color);
foreach (Vector2 pos in canMovePos) {
DrawCircle(pos, PosTrans.pixGripSize / 2.2f,
color.Inverted());
}
}
public override void _Input(InputEvent @event) {
if (@event is InputEventMouseButton mouseEvent &&
mouseEvent.Pressed &&
mouseEvent.ButtonIndex == MouseButton.Left) {
OnMouseClicked?.Invoke(this, GetLocalMousePosition());
OnPosClicked?.Invoke(this, (PosTrans.transArrToPix.AffineInverse() *
GetLocalMousePosition()).Round());
}
}
public static class PosTrans {
public static readonly int pixGripSize = 32;
public static readonly Transform2D transArrToPix = new(
new Vector2(pixGripSize, 0),
new Vector2(0, pixGripSize),
new Vector2(-4, -4.5f) * pixGripSize
);
}
}

View File

@ -0,0 +1 @@
uid://dmc012tt32dkl

View File

@ -0,0 +1,78 @@
// Chesspiece.cs
using Godot;
using ChineseChess;
using static IPiece;
public partial class ChessPiece : Sprite2D, IPieceOn {
[Export]
public string? PieceLabel { get; set; } = null;
// Text Color (Can Export for Editor Adjust)
[Export]
public Color LabelColor { get; set; } = new Color("white");
private Vector2 textureSize;
private Label? labelOfChessName;
void IPieceOn.OnPos(object _self, Vector.Vector2I oldPos) {
if (_self is not CCPiece self) return;
Position = ChessBoard.PosTrans.transArrToPix * new Vector2(self.Pos.X, self.Pos.Y);
}
void IPieceOn.OnSelected(object _self, bool oldIsSelected) {
if (GetParent() is not ChessBoard chessBoard || _self is not CCPiece self) return;
if (self.IsSelected) {
GD.Print($"{self.Pos} is selected");
Transform *= transToSeleted;
foreach (var item in self.CanMoveAllPos()) {
chessBoard.canMovePos.Add(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y));
}
} else {
GD.Print($"{self.Pos} is deselected");
Transform *= transToSeleted.AffineInverse();
foreach (var item in self.CanMoveAllPos()) {
chessBoard.canMovePos.Remove(ChessBoard.PosTrans.transArrToPix * new Vector2(item.X, item.Y));
}
}
chessBoard.QueueRedraw();
}
Transform2D transToSeleted = new(
new Vector2(1.2f, 0),
new Vector2(0, 1.2f),
new Vector2(0, 0)
);
public ChessPiece() : this(new CCPiece()) {
}
public ChessPiece(CCPiece piece) {
PieceLabel = piece.CNName;
LabelColor = piece.TurnsSide == ChessCore.TurnsSideType.Red ? new Color("red") : new Color("black");
piece.On = this;
// Must Be Call for init Pos
piece.On.OnPos(piece, piece.Pos);
piece.Data.TryAdd("Godot", this);
}
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
InitLabel();
}
private void InitLabel() {
Texture ??= (Texture2D)ResourceLoader.Load("res://Asserts/ChesspieceBase.tres");
textureSize = Texture.GetSize();
Vector2 labalPosition = new(
-textureSize.X / 2,
-textureSize.Y / 2);
labelOfChessName = new Label {
Text = PieceLabel,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Modulate = LabelColor,
Position = labalPosition,
Size = textureSize,
};
// labelOfChessName.SetAnchorsPreset(Control.LayoutPreset.FullRect);
AddChild(labelOfChessName);
}
}

View File

@ -0,0 +1 @@
uid://c8eafskfpuesm

133
Scripts/GlobalManager.cs Normal file
View File

@ -0,0 +1,133 @@
using System.Collections.Generic;
using Godot;
public partial class GlobalManager : Node {
private static readonly Logging.Logger logger = Logging.GetLogger("GlobalManager");
public static GlobalManager Instance { get; private set; } = null!;
public static string ConfigPath { get; private set; } = "user://config.json";
private GlobalManager() {
ConfigManager.SetFilePath(ProjectSettings.GlobalizePath(ConfigPath));
ConfigManager.SetDefault(_default_config);
ConfigManager.LoadFromFile();
Mods = new GlobalModManager(ConfigManager.GetValue<string>("mods_fpath"));
Mods.SearchAllMods();
}
public readonly GlobalModManager Mods = null!;
public readonly IConfigManager ConfigManager = new JsonConfigManager();
private static readonly Dictionary<string, object> _default_config = new() {
{"font_size", 20},
{"server_addr", "wss://game.zzyxyz.com/"},
{"user_name", "undefined"},
{"mods_fpath", "user://mods"},
{"mods_allowed", false},
};
public string sessionId = "";
public Node CurrentScene { get; set; } = null!;
public Theme GlobalTheme = null!;
// readonly GodotConfigManager GlobalConfig = new("user://config.cfg");
// TODO will remove this
public Dictionary<string, Variant> GlobalData = new() {
{"player_color", "red"},
};
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
if (Instance == null) {
Instance = this;
}
else {
logger.Error("GlobalManager already exists");
QueueFree();
}
if (OS.GetName() == "Android") {
bool ret = OS.RequestPermissions();
logger.Warning($"RequestPermissions ret is {ret}");
}
Viewport root = GetTree().Root;
CurrentScene = root.GetChild(root.GetChildCount() - 1);
SetProcess(false);
}
public void GlobalThemeConfigFlush() {
int font_size = ConfigManager.GetValue<int>("font_size");
GlobalTheme.DefaultFontSize = font_size;
// GlobalTheme?.SetFontSize("font_size", "Label", font_size);
// GlobalTheme?.SetFontSize("font_size", "Button", font_size);
// GlobalTheme?.SetFontSize("font_size", "TextEdit", font_size);
// GlobalTheme?.SetFontSize("font_size", "LineEdit", font_size);
// CurrentScene.GetWindow().AddThemeFontSizeOverride("Control", (int)GlobalConfigDict["font_size"]);
}
private void OnGotoScene() {
GlobalThemeConfigFlush();
}
public void SaveConfig() {
ConfigManager.SaveToFile();
// GlobalConfig.SaveConfig("Global", _conf_dict);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {
// RPClient.PollEx(delta);
}
public override void _Notification(int what) {
if (what == NotificationWMCloseRequest) {
// SaveConfig();
// RPClient.Close();
GetTree().Quit(); // default behavior
}
}
public delegate void ChangeSceneCallback(Node newSence);
private static ChangeSceneCallback? changeSceneCallback = null;
public void GotoScene(string path, ChangeSceneCallback? callback = null) {
// This function will usually be called from a signal callback,
// or some other function from the current scene.
// Deleting the current scene at this point is
// a bad idea, because it may still be executing code.
// This will result in a crash or unexpected behavior.
// The solution is to defer the load to a later time, when
// we can be sure that no code from the current scene is running:
if (callback != null) {
changeSceneCallback = callback;
}
Callable callbackWrapper = new(null, nameof(changeSceneCallback));
CallDeferred(MethodName.DeferredGotoScene, path, callbackWrapper);
changeSceneCallback = null;
}
public void DeferredGotoScene(string path, Callable onLoaded) {
// It is now safe to remove the current scene.
CurrentScene.Free();
// Load a new scene.
var nextScene = GD.Load<PackedScene>(path);
// Instance the new scene.
CurrentScene = nextScene.Instantiate();
if (changeSceneCallback != null) {
onLoaded.Call(CurrentScene);
}
// Add it to the active scene, as child of root.
GetTree().Root.AddChild(CurrentScene);
// Optionally, to make it compatible with the SceneTree.change_scene_to_file() API.
GetTree().CurrentScene = CurrentScene;
OnGotoScene();
}
}

View File

@ -0,0 +1 @@
uid://gfwgyu7p8hoi

175
Scripts/Logger.cs Normal file
View File

@ -0,0 +1,175 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
/// <summary>
/// 仿照 Python logging 的 日志系统
/// </summary>
public static class Logging {
// ========== 日志级别 ==========
public enum Level {
NOSET = 0,
DEBUG = 10,
INFO = 20,
WARNING = 30,
ERROR = 40,
CRITICAL = 50
}
/// <summary>
/// 日志记录器
/// </summary>
public class Logger(string name) {
public string Name { get; } = name;
public Level LogLevel { get; set; } = Level.INFO;
private readonly List<Action<Level, string, string, string>> _handlers = [];
public void AddHandler(Action<Level, string, string, string> handler) =>
_handlers.Add(handler);
// ===== 日志方法 =====
public void Debug(string fmt, params object[] args) =>
Log(Level.DEBUG, fmt, false, args);
public void Info(string fmt, params object[] args) =>
Log(Level.INFO, fmt, false, args);
public void Warning(string fmt, params object[] args) =>
Log(Level.WARNING, fmt, false, args);
public void Error(string fmt, params object[] args) =>
Log(Level.ERROR, fmt, false, args);
public void Critical(string fmt, params object[] args) =>
Log(Level.CRITICAL, fmt, true, args);
public void Exception(string fmt, Exception e, params object[] args) {
// TODO for godot not support /r/n yet
string stackTrace = e.StackTrace?.Replace("\r\n", "\n") ?? "";
string exceptionMessage = $"{fmt}\n{e.Message}\n{stackTrace}";
Log(Level.ERROR, exceptionMessage, false, args);
}
// ===== 核心Log方法 =====
private void Log(Level level, string fmt, bool isExec = false, params object[] args) {
if (level < LogLevel) return;
// 格式化消息
string message = args.Length > 0 ?
string.Format(fmt, args) : fmt;
// 添加执行堆栈信息
string stackInfo = isExec ?
$"\nExecution Stack:\n{System.Environment.StackTrace}" : "";
// 构建前缀
string prefix = $"[{DateTime.Now:HH:mm:ss}] [{level}] [{Name}]";
// 调用所有处理器
foreach (var handler in _handlers) {
handler(level, prefix, message, stackInfo);
}
}
[Conditional("DEBUG")]
public void Assert(bool condition, string fmt, params object[] args) {
if (condition) return;
string message = args.Length > 0 ?
string.Format(fmt, args) : fmt;
Error("Assertion Failed: " + message, true);
if (Debugger.IsAttached)
Debugger.Break();
System.Diagnostics.Debug.Assert(condition, message);
}
}
// ========== 全局管理器 ==========
private static readonly Dictionary<string, Logger> _loggers = [];
private static readonly Logger _root = GetLogger("root");
/// <summary>
/// 获取或创建日志记录器
/// </summary>
public static Logger GetLogger(string name = "root") {
if (!_loggers.TryGetValue(name, out var logger)) {
logger = new Logger(name);
logger.AddHandler(DefaultHandler);
_loggers[name] = logger;
}
return logger;
}
// ========== 优化的默认处理器 ==========
private static void DefaultHandler(Level level, string prefix, string message, string stackInfo) {
// 构建完整日志消息
string fullMessage = $"{prefix} {message}{stackInfo}";
switch (level) {
case Level.DEBUG:
// DEBUG级别使用GD.Print + OS条件
if (OS.IsStdOutVerbose())
GD.Print(fullMessage);
break;
case Level.INFO:
// INFO级别使用GD.Print
GD.Print(fullMessage);
break;
case Level.WARNING:
// WARNING级别使用PrintRich + 黄色
GD.PrintRich($"[color=yellow]{fullMessage}[/color]");
break;
case Level.ERROR:
// ERROR级别使用GD.PrintErr
GD.PrintRich($"[color=red][b]{fullMessage}[/b][/color]");
// GD.PrintErr(fullMessage);
break;
case Level.CRITICAL:
// CRITICAL级别使用GD.PushError
GD.PushError(fullMessage);
break;
}
}
// ========== 快捷操作方式 ==========
public static void Debug(string fmt, params object[] args) =>
_root.Debug(fmt, args);
public static void Info(string fmt, params object[] args) =>
_root.Info(fmt, args);
public static void Warning(string fmt, params object[] args) =>
_root.Warning(fmt, args);
public static void Error(string fmt, params object[] args) =>
_root.Error(fmt, args);
public static void Critical(string fmt, params object[] args) =>
_root.Critical(fmt, args);
// ===== C语言风格的宏断言快捷方式 =====
[Conditional("DEBUG")]
public static void Assert(bool condition, string fmt, params object[] args) =>
_root.Assert(condition, fmt, args);
[Conditional("DEBUG")]
public static void TestPrint() {
GD.Print("GameSystem Singleton Initialized");
GD.PrintErr("This is a print error");
GD.PushWarning("This is a warning");
GD.PushError("This is a error");
Debug("This is a debug");
Info("This is a info");
Warning("This is a warn");
Error("This is a error");
Critical("This is a critical");
Assert(true, "This is a assert");
}
}

1
Scripts/Logger.cs.uid Normal file
View File

@ -0,0 +1 @@
uid://bud682vprk1w6

View File

@ -0,0 +1,72 @@
using Godot;
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class GlobalModManager(string modPath = "user://mods") {
public static GlobalModManager Instance { get; } = new();
private readonly Dictionary<string, LuaModManager> _mods = [];
public ReadOnlyDictionary<string, LuaModManager> Mods => new(_mods);
private readonly Dictionary<string, string> _modList = [];
public ReadOnlyDictionary<string, string> ModList => new(_modList);
private readonly string modPath = modPath;
private static readonly Logging.Logger logger = Logging.GetLogger("GlobalMod");
public void SearchAllMods(bool isFlush = false) {
if (isFlush) {
_modList.Clear();
}
if (!DirAccess.DirExistsAbsolute(modPath))
return;
using var dir = DirAccess.Open(modPath);
if (dir == null) return;
dir.ListDirBegin();
string fileName = dir.GetNext();
while (!string.IsNullOrEmpty(fileName)) {
string fullPath = $"{modPath}/{fileName}";
const string entryFileName = "main.lua";
if (dir.CurrentIsDir() && !fileName.StartsWith('.') &&
FileAccess.FileExists($"{fullPath}/{entryFileName}")) {
_modList.Add($"{fullPath}/{entryFileName}", fullPath);
}
fileName = dir.GetNext();
}
}
public void LoadAllMods(bool isFlush = false) {
if (isFlush) {
_mods.Clear();
}
foreach (var mod in _modList) {
try {
LoadMod(mod.Key, mod.Value);
}
catch (System.Exception e) {
logger.Exception("Failed to load mod {0}", e, mod.Key);
}
}
}
public void ClearAllMods() {
_mods.Clear();
}
private void LoadMod(string filePath, string modPath) {
string _filePath = ProjectSettings.GlobalizePath(filePath);
string _modPath = ProjectSettings.GlobalizePath(modPath);
_mods.Add(filePath, new LuaModManager(_filePath, _modPath));
}
private static void SaveFile(string path, string content) {
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
file.StoreString(content);
}
private static string LoadFile(string path) {
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
string content = file.GetAsText();
return content;
}
}

View File

@ -0,0 +1 @@
uid://c45a7ldunixnh

View File

@ -0,0 +1,44 @@
using System;
using System.IO;
using System.Linq;
using NLua;
public class LuaModManager : IDisposable {
private readonly Lua lua = new();
private static readonly Logging.Logger logger = Logging.GetLogger("LuaMod");
public LuaModManager(string path, string? package_path = null) {
lua.LoadCLRPackage();
lua.State.Encoding = System.Text.Encoding.UTF8;
lua["package.path"] = package_path ?? Path.GetDirectoryName(path) + "/?.lua;";
lua.RegisterFunction("print", new Action<object[]>(Godot.GD.Print).Method);
logger.Info("Loading Lua Mod: " + path);
lua.DoFile(path);
}
public void BindFunc(ModManager mod) {
foreach (var api in mod.GetAPIs()) {
lua.RegisterFunction(api.Key, api.Value.Target, api.Value.Method);
}
foreach (var hook in mod.GetHooks()) {
if (lua[hook.Key] is LuaFunction func) {
mod.AddHook(hook.Key, (args) => {
var rets = func.Call(args).FirstOrDefault();
if (rets is bool b) return b;
return false;
});
}
}
}
public void Dispose() {
lua?.Dispose();
GC.SuppressFinalize(this);
}
public static void WriteComment(Delegate @delegate) {
// TODO
}
}

View File

@ -0,0 +1 @@
uid://dtqaboju24vmn

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
public class ModManager {
private readonly Dictionary<string, Delegate> _api = [];
private readonly Dictionary<string, Delegate> _hooks = [];
public delegate bool OnCallback(params object[] args);
private readonly Dictionary<string, List<OnCallback>> _callbackList = [];
public ReadOnlyDictionary<string, Delegate> GetAPIs() => new(_api);
public ReadOnlyDictionary<string, Delegate> GetHooks() => new(_hooks);
public ModManager RegistryFunc<TDelegate>(string name, TDelegate handler, bool isHook = false)
where TDelegate : Delegate {
MethodInfo _ = handler.Method;
Dictionary<string, Delegate> dict = isHook ? _hooks : _api;
dict.Add(name, handler);
return this;
}
public bool AddHook(string name, OnCallback handler) {
if (_hooks.ContainsKey(name)) {
if (!_callbackList.TryGetValue(name, out var list)) {
list = [];
_callbackList[name] = list;
}
list.Add(handler);
return true;
}
return false;
}
public bool InvokeFunc<TDelegate>(string name, params object[] args)
where TDelegate : Delegate {
List<object> res = [];
if (_callbackList.TryGetValue(name, out var callback)) {
foreach (var func in callback) {
func?.Invoke(args);
}
}
return true;
}
}

View File

@ -0,0 +1 @@
uid://lmuddan86bi8

View File

@ -0,0 +1,117 @@
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
using static IBoard;
public abstract class AbstractBoard(int rows, int cols, int maxRecords = int.MaxValue,
IBoardOn? on = null) : IBoard {
private readonly int rows = rows;
private readonly int cols = cols;
protected readonly IPiece?[,] pieces = new IPiece[rows, cols];
protected readonly List<MoveRecord> moveRecords = [];
protected readonly int MAX_RECORDS = maxRecords;
public int Rows => rows;
public int Cols => cols;
protected IBoardOn? on = on;
public virtual IBoardOn? On { get => on; set => on = value; }
public virtual bool IsPosOutOfRange(Vector2I arrayPos) {
return arrayPos.X < 0 || arrayPos.X >= Rows || arrayPos.Y < 0 || arrayPos.Y >= Cols;
}
public virtual IPiece? GetPiece(Vector2I arrayPos) {
if (IsPosOutOfRange(arrayPos)) return null;
return pieces[arrayPos.X, arrayPos.Y];
}
public virtual IPiece? SetPiece(IPiece? piece, Vector2I pos) {
if (IsPosOutOfRange(pos)) return null;
IPiece? oldPiece = pieces[pos.X, pos.Y];
pieces[pos.X, pos.Y] = piece;
if (piece is not null) piece.Pos = pos;
// if (oldPiece != null) oldPiece.Pos = Vector2I.Zero;
on?.OnSetPieceInteral(this, new IBoardOn.SetPieceEventArgs
(oldPiece, piece, pos));
return oldPiece;
}
public virtual bool InsertPiece(IPiece piece, Vector2I pos) {
if (GetPiece(pos) is not null && piece == null) {
return false;
}
on?.OnInsert(this, piece);
SetPiece(piece, pos);
return true;
}
public virtual bool MovePiece(Vector2I from, Vector2I to) {
if (IsPosOutOfRange(to) || IsPosOutOfRange(from)) {
return false;
}
IPiece? piece = GetPiece(from);
if (GetPiece(to) is not null || piece is null) {
return false;
}
on?.OnMove(this, new IBoardOn.MoveEventArgs(piece, from, to));
SetPiece(null, from);
SetPiece(piece, to);
return true;
}
public virtual IPiece? RemovePiece(Vector2I pos) {
IPiece? piece = GetPiece(pos);
if (piece is null) return null;
on?.OnRemove(this, piece);
return SetPiece(null, pos);
}
public virtual void Clear() { Clear(true); }
public virtual void Clear(bool clearRecords) {
for (int i = 0; i < Rows; i++) {
for (int j = 0; j < Cols; j++) {
RemovePiece(new Vector2I(i, j));
}
}
if (clearRecords) ClearRecords();
}
public virtual void ClearRecords() {
moveRecords.Clear();
}
public virtual void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
if (moveRecords.Count >= MAX_RECORDS) {
moveRecords.RemoveAt(0);
}
MoveRecord record = new(From, To, FromPos, ToPos);
on?.OnAddRecord(this, record);
moveRecords.Add(record);
}
public virtual void UndoRecord() {
if (moveRecords.Count == 0) return;
MoveRecord record = moveRecords[^1];
moveRecords.RemoveAt(moveRecords.Count - 1);
// 恢复新位置的棋子order is very inmportant
if (record.From is not null) {
MovePiece(record.ToPos, record.FromPos);
}
// 恢复旧位置的棋子
if (record.To is not null) {
InsertPiece(record.To, record.ToPos);
}
on?.OnUndoRecord(this, record);
}
}

View File

@ -0,0 +1 @@
uid://d1xim7v5bes7j

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
using static IPiece;
public abstract class AbstractPiece(string name = "", Vector2I? pos = null,
IPieceOn? on = null) : IPiece {
private Vector2I pos = pos ?? new(); // 注意这个坐标的非像素坐标而是棋盘坐标
private bool isSelected = false;
protected string name = name;
private Dictionary<string, object> data = [];
protected IPieceOn? on = on;
public virtual IPieceOn? On { get => on; set => on = value; }
public virtual Vector2I Pos {
get => pos;
set {
if (pos != value) {
var oldPos = pos;
pos = value;
on?.OnPos(this, oldPos);
}
}
}
public virtual bool IsSelected {
get => isSelected;
set {
if (isSelected != value) {
var oldIsSelected = isSelected;
isSelected = value;
on?.OnSelected(this, oldIsSelected);
}
}
}
public virtual string Name {
get => name;
set {
if (name != value) {
var oldName = name;
name = value;
on?.OnName(this, oldName);
}
}
}
public virtual Dictionary<string, object> Data {
get => data;
set => data = value;
}
public virtual bool Move(Vector2I pos) {
if (!CanMove(pos)) {
return false;
}
Pos = pos;
on?.OnMove(this, pos);
return true;
}
public abstract bool CanMove(Vector2I to);
public override string ToString() {
return $"{Name} at {pos}";
}
}

View File

@ -0,0 +1 @@
uid://cxlgd4kjfae7i

View File

@ -0,0 +1,89 @@
using System.Collections;
using static ChineseChess.ChessCore;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public interface ICCBoardOn : IBoard.IBoardOn {
void OnSteps(object self, int oldSteps) {}
}
public class CCBoard(ICCBoardOn? on = null) : AbstractBoard(9, 10, on: on) {
private int steps = 0;
protected new ICCBoardOn? on = on;
// public virtual IPieceOn? On { get => on; set => on = value; }
public new ICCBoardOn? On { get => on; set {
on = value; base.on = value;} }
public int Steps {
get => steps;
protected set {
if (steps != value) {
var oldSteps = steps;
steps = value;
on?.OnSteps(this, oldSteps);
}
}
}
public override void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos) {
base.AddRecord(From, To, FromPos, ToPos);
Steps += 1;
}
public override void UndoRecord() {
base.UndoRecord();
if (Steps > 0) Steps -= 1;
}
public (ArrayList, ArrayList) InitGame() {
Steps = 0;
ArrayList blackPart = InitOnePartPieces(TurnsSideType.Black, [
new CCChariot (this, TurnsSideType.Black, new Vector2I( 4, 0)),
new CCHorse (this, TurnsSideType.Black, new Vector2I( 3, 0)),
new CCElephant(this, TurnsSideType.Black, new Vector2I( 2, 0)),
new CCAdvisor (this, TurnsSideType.Black, new Vector2I( 1, 0)),
new CCGeneral (this, TurnsSideType.Black, new Vector2I( 0, 0)),
new CCAdvisor (this, TurnsSideType.Black, new Vector2I(-1, 0)),
new CCElephant(this, TurnsSideType.Black, new Vector2I(-2, 0)),
new CCHorse (this, TurnsSideType.Black, new Vector2I(-3, 0)),
new CCChariot (this, TurnsSideType.Black, new Vector2I(-4, 0)),
new CCCannon (this, TurnsSideType.Black, new Vector2I( 3, 2)),
new CCCannon (this, TurnsSideType.Black, new Vector2I(-3, 2)),
new CCPawn (this, TurnsSideType.Black, new Vector2I(-4, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I(-2, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 0, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 2, 3)),
new CCPawn (this, TurnsSideType.Black, new Vector2I( 4, 3)),
]);
ArrayList redPart = InitOnePartPieces(TurnsSideType.Red, [
new CCChariot (this, TurnsSideType.Red, new Vector2I( 4, 0)),
new CCHorse (this, TurnsSideType.Red, new Vector2I( 3, 0)),
new CCElephant(this, TurnsSideType.Red, new Vector2I( 2, 0)),
new CCAdvisor (this, TurnsSideType.Red, new Vector2I( 1, 0)),
new CCGeneral (this, TurnsSideType.Red, new Vector2I( 0, 0)),
new CCAdvisor (this, TurnsSideType.Red, new Vector2I(-1, 0)),
new CCElephant(this, TurnsSideType.Red, new Vector2I(-2, 0)),
new CCHorse (this, TurnsSideType.Red, new Vector2I(-3, 0)),
new CCChariot (this, TurnsSideType.Red, new Vector2I(-4, 0)),
new CCCannon (this, TurnsSideType.Red, new Vector2I( 3, 2)),
new CCCannon (this, TurnsSideType.Red, new Vector2I(-3, 2)),
new CCPawn (this, TurnsSideType.Red, new Vector2I(-4, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I(-2, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 0, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 2, 3)),
new CCPawn (this, TurnsSideType.Red, new Vector2I( 4, 3)),
]);
return (blackPart, redPart);
}
private ArrayList InitOnePartPieces(TurnsSideType side, CCPiece[] pieces) {
ArrayList list = [];
foreach (var piece in pieces) {
list.Add(piece);
InsertPiece(piece, piece.Pos);
}
return list;
}
}

View File

@ -0,0 +1 @@
uid://dpcteo12eebjn

View File

@ -0,0 +1,119 @@
using Vector2 = Vector.Vector2I;
using System;
using System.Collections;
using static IBoard.IBoardOn;
namespace ChineseChess;
public class ChessCore {
public enum Mode {
SingleMode,
MultiMode,
DebugMode,
};
public enum TurnsSideType {
Red,
Black,
};
public enum PlayerSideType {
Self,
Opponent,
Any,
};
private TurnsSideType sideType = TurnsSideType.Red;
private readonly TurnsSideType selfSide;
public readonly CCBoard board;
private readonly Player playerSelf;
private readonly Player playerOpponent;
// public EventHandler<IBoard.MoveEventArgs> OnMove;
public ChessCore(Mode mode, TurnsSideType selfSide, ICCBoardOn? boardOn = null) {
this.selfSide = selfSide;
board = new(boardOn);
playerSelf = new(board, Player.PlayerType.Human);
playerOpponent = new(board, Player.PlayerType.Human);
void func(object? _self, MoveEventArgs record) {
// 防止 Undo 时 Selected Clear 出现 Null Pointer Exception
playerSelf.SelectedClear();
playerOpponent.SelectedClear();
// TODO FIXME it can be simple
if (record.From is null || record.To is null) {
return;
}
IPiece? from = record.From is not null ? board.GetPiece(record.From) : null;
IPiece? to = record.To is not null ? board.GetPiece(record.To) : null;
board.AddRecord(from, to, record.From ?? new(), record.To ?? new());
}
playerSelf.OnMove += func;
playerOpponent.OnMove += func;
switch (mode) {
case Mode.SingleMode:
break;
case Mode.MultiMode:
break;
default:
case Mode.DebugMode:
throw new NotImplementedException();
}
Init();
}
public void Init() {
(ArrayList blackPart, ArrayList redPart) = board.InitGame();
if (selfSide == TurnsSideType.Red) {
playerSelf.SetAllowedPieces(redPart);
playerOpponent.SetAllowedPieces(blackPart);
} else {
playerSelf.SetAllowedPieces(blackPart);
playerOpponent.SetAllowedPieces(redPart);
}
}
public void OnPosClicked(Vector2 pos, PlayerSideType clickedSide = PlayerSideType.Any) {
if (GetTurnsType() == selfSide) {
playerSelf.CanMove = true;
playerOpponent.CanMove = false;
} else {
playerSelf.CanMove = false;
playerOpponent.CanMove = true;
}
switch (clickedSide) {
case PlayerSideType.Any:
playerSelf.HandleBoardPosClick(pos);
playerOpponent.HandleBoardPosClick(pos);
break;
case PlayerSideType.Self:
playerSelf.HandleBoardPosClick(pos);
break;
case PlayerSideType.Opponent:
playerOpponent.HandleBoardPosClick(pos);
break;
}
}
public TurnsSideType GetTurnsType() {
sideType = board.Steps % 2 == 0 ? TurnsSideType.Red : TurnsSideType.Black;
return sideType;
}
public void Undo() {
playerSelf.SelectedClear();
playerOpponent.SelectedClear();
board.UndoRecord();
}
public void ReInit() {
playerSelf.SelectedClear();
playerOpponent.SelectedClear();
board.Clear(true);
Init();
}
}

View File

@ -0,0 +1 @@
uid://sgx62hxlp6fr

View File

@ -0,0 +1,85 @@
namespace ChineseChess;
using System.Collections.Generic;
using System.Linq;
using Vector;
using static ChineseChess.ChessCore;
public class CCPiece : AbstractPiece {
public string CNName { get; protected set; } = "unknown";
public TurnsSideType TurnsSide { get; }
protected CCBoard board;
protected Vector2I localPos = new();
static readonly Trans2DI transRedGlobal2Local =
new(new Vector2I(1, 0), new Vector2I(0, -1), new Vector2I(-4, 9));
static readonly Trans2DI transBlackGlobal2Local =
new(new Vector2I(-1, 0), new Vector2I(0, 1), new Vector2I(4, 0));
public CCPiece(CCBoard? board = null, TurnsSideType turnsSide = TurnsSideType.Red,
string name = "", Vector2I? pos = null) : base(name) {
this.board = board ?? new();
TurnsSide = turnsSide;
Pos = pos is not null ? Local2Global(pos) : Vector2I.Zero;
}
public override Vector2I Pos {
get => base.Pos; set {
base.Pos = value;
localPos = Global2Local(Pos);
}
}
public override bool CanMove(Vector2I to) {
return CanMoveAllPos().Any(item => item == to);
}
protected Vector2I Local2Global(in Vector2I pos) {
return TurnsSide == TurnsSideType.Red ? (pos * transRedGlobal2Local) : (pos * transBlackGlobal2Local);
}
protected Vector2I Global2Local(in Vector2I pos) {
return TurnsSide == TurnsSideType.Red ? (transRedGlobal2Local * pos) : (transBlackGlobal2Local * pos);
}
protected CCPiece? GetCCPieceLocal(in Vector2I pos) {
return (CCPiece?)board.GetPiece(Local2Global(pos));
}
public virtual List<Vector2I> CanMoveAllPosSelf() {
return [];
}
public IEnumerable<Vector2I> CanMoveAllPosLocal() {
var self = CanMoveAllPosSelf().Select(item => item + localPos);
var ret = self.Where(item => {
CCPiece? piece = GetCCPieceLocal(item);
bool ret = piece is null || piece.TurnsSide != TurnsSide;
// Console.WriteLine($"{item} can move: {ret}");
return ret;
});
// Console.WriteLine("localPos: {0}", localPos);
// Console.WriteLine("CanMoveAllPosSelf: {0}", string.Join(",", self));
// Console.WriteLine("CanMoveAllPosLocal: {0}", string.Join(",", ret));
return ret;
}
public IEnumerable<Vector2I> CanMoveAllPos() {
var ret = CanMoveAllPosLocal()
.Select(item => Local2Global(item));
// Console.WriteLine("CanMoveAllPos: {0}", string.Join(",", ret));
return ret;
}
protected bool IsPosOutOfRangeLocal(Vector2I pos) {
return board.IsPosOutOfRange(Local2Global(pos));
}
protected (CCPiece?, Vector2I) GetRecursivePieceLocal(Vector2I origin, Vector2I pos) {
Vector2I with = origin + pos;
while (!IsPosOutOfRangeLocal(with) && GetCCPieceLocal(with) == null) {
with += pos;
}
return (GetCCPieceLocal(with), with);
}
}

View File

@ -0,0 +1 @@
uid://b6ajoxcxs66n1

View File

@ -0,0 +1,122 @@
using System;
using System.Collections;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
using static IBoard.IBoardOn;
public class Player {
private readonly CCBoard board;
private readonly SelectedPiece selectedNode;
public EventHandler<MoveEventArgs>? OnMove;
public bool CanMove { get; set; } = true;
public enum PlayerType {
Human,
AI
}
public Player(CCBoard board, PlayerType type = PlayerType.Human) {
this.board = board;
this.selectedNode = new SelectedPiece(board);
}
public void HandleBoardPosClick(Vector2I clickPos) {
if (board.IsPosOutOfRange(clickPos)) return;
// Console.WriteLine($"VirtualBoard {clickPos} clicked");
IPiece? clickChess = board.GetPiece(clickPos);
if (!selectedNode.HasSelected()) {
// Select piece
if (clickChess == null) {
// selectedNode.Clear();
return;
}
selectedNode.SetPos(clickPos);
} else if (clickChess == selectedNode.GetPiece()) {
// Unselect piece
selectedNode.Clear();
} else {
// Move piece
// GD.Print("default MoveFunc Move: ", selectedNode.GetPos(), "->", clickPos);
if (CanMove) MoveAndRecord(clickPos, selectedNode.GetPos());
}
}
public void MoveAndRecord(Vector2I toPos, Vector2I fromPos) {
// GD.Print($"{fromPos} move to {toPos}");
IPiece? toChess = board.GetPiece(toPos);
IPiece? fromChess = board.GetPiece(fromPos);
if (fromChess != null) fromChess.IsSelected = false;
else return;
selectedNode.Clear();
if (!fromChess.CanMove(toPos)) {
return;
}
// MUST BE THERE !!! 防止删除节点后在启动回调导致错误
OnMove?.Invoke(this, new MoveEventArgs
(fromChess, fromPos, toPos));
if (toChess != null) {
board.RemovePiece(toPos);
}
board.MovePiece(fromPos, toPos);
selectedNode.Clear();
}
public void SelectedClear() {
selectedNode.Clear();
}
public void SetAllowedPieces(ArrayList allowedPieces) {
selectedNode.allowedPieces = allowedPieces;
}
private class SelectedPiece {
// Called when the node enters the scene tree for the first time.
private Vector2I selectedNodePos = Vector2I.MaxValue;
private IPiece? piece = null;
private readonly CCBoard board;
public ArrayList? allowedPieces = null;
public SelectedPiece(CCBoard board) {
this.board = board;
}
public void Clear() {
if (selectedNodePos != Vector2I.MaxValue) {
selectedNodePos = Vector2I.MaxValue;
if (piece != null)
piece.IsSelected = false;
}
// Console.WriteLine("SelectedPiece.Clear {0}", piece);
}
public void SetPos(Vector2I pos) {
piece = board.GetPiece(pos);
if (allowedPieces != null && allowedPieces.Contains(piece) == false) {
return;
}
selectedNodePos = pos;
if (piece == null)
return;
piece.IsSelected = true;
Console.WriteLine("SelectedPiece.SetPos {0}", piece);
}
public IPiece? GetPiece() {
return piece;
}
public Vector2I GetPos() {
return selectedNodePos;
}
public bool HasSelected() {
return selectedNodePos != Vector2I.MaxValue;
}
}
}

View File

@ -0,0 +1 @@
uid://blpqjd37p3r38

View File

@ -0,0 +1,250 @@
using System.Collections.Generic;
using static ChineseChess.ChessCore;
using Vector2I = Vector.Vector2I;
namespace ChineseChess;
public class CCGeneral : CCPiece {
public CCGeneral(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "General", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "帅";
} else {
CNName = "将";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [
new(1, 0),
new(-1, 0),
new(0, 1),
new(0, -1),
];
// 移除不符合条件的元素
list.RemoveAll(item =>
localPos.X + item.X > 1 || localPos.X + item.X < -1 ||
localPos.Y + item.Y > 2 || localPos.Y + item.Y < 0);
(var piece, Vector2I pos) = GetRecursivePieceLocal(localPos, new(0, 1));
if (piece is CCGeneral) {
list.Add(pos);
}
return list;
}
}
public class CCAdvisor : CCPiece {
public CCAdvisor(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Advisor", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "仕";
} else {
CNName = "士";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [
new(1, 1),
new(-1, 1),
new(1, -1),
new(-1, -1),
];
// 移除不符合条件的元素
list.RemoveAll(item =>
localPos.X + item.X > 1 || localPos.X + item.X < -1 ||
localPos.Y + item.Y > 2 || localPos.Y + item.Y < 0);
return list;
}
}
public class CCElephant : CCPiece {
public CCElephant(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Bishop", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "相";
} else {
CNName = "象";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [
new(2, 2),
new(-2, 2),
new(2, -2),
new(-2, -2),
];
list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item));
// 移除不符合条件的元素
list.RemoveAll(item => localPos.Y + item.Y < 0 || localPos.Y + item.Y > 4 ||
GetCCPieceLocal(new(localPos.X + item.X / 2, localPos.Y + item.Y / 2)) is not null);
return list;
}
}
public class CCHorse : CCPiece {
public CCHorse(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Horse", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "馬";
} else {
CNName = "马";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [
new Vector2I(1, 2),
new Vector2I(1, -2),
new Vector2I(2, 1),
new Vector2I(2, -1),
new Vector2I(-1, -2),
new Vector2I(-1, 2),
new Vector2I(-2, -1),
new Vector2I(-2, 1),
];
list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item));
list.RemoveAll(item => {
Vector2I pos = new(localPos);
if (item.X == 2) {
pos.X += 1;
} else if (item.X == -2) {
pos.X -= 1;
} else if (item.Y == 2) {
pos.Y += 1;
} else if (item.Y == -2) {
pos.Y -= 1;
}
return GetCCPieceLocal(pos) is not null;
});
return list;
}
}
public class CCChariot : CCPiece {
public CCChariot(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Chariot", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "車";
} else {
CNName = "车";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [];
void func(Vector2I added) {
Vector2I ptr = new(localPos);
while (true) {
ptr += added;
if (IsPosOutOfRangeLocal(ptr)) {
break;
}
if (GetCCPieceLocal(ptr) != null) {
list.Add(ptr - localPos);
break;
}
list.Add(ptr - localPos);
}
}
func(Vector2I.Up);
func(Vector2I.Down);
func(Vector2I.Left);
func(Vector2I.Right);
return list;
}
}
public class CCCannon : CCPiece {
public CCCannon(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Cannon", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "砲";
} else {
CNName = "炮";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [];
void func(Vector2I added) {
Vector2I ptr = new(localPos);
bool flag = true;
while (true) {
ptr += added;
if (IsPosOutOfRangeLocal(ptr)) {
break;
}
if (GetCCPieceLocal(ptr) != null) {
if (flag) {
flag = false;
} else {
list.Add(ptr - localPos);
break;
}
}
if (flag) list.Add(ptr - localPos);
}
}
func(Vector2I.Up);
func(Vector2I.Down);
func(Vector2I.Left);
func(Vector2I.Right);
return list;
}
}
public class CCPawn : CCPiece {
public CCPawn(CCBoard board, TurnsSideType turnsSide = TurnsSideType.Red, Vector2I? pos = null)
: base(board, turnsSide, name : "Pawn", pos) {
if (turnsSide == TurnsSideType.Red) {
CNName = "兵";
} else {
CNName = "卒";
}
}
public override List<Vector2I> CanMoveAllPosSelf() {
List<Vector2I> list = [
new(0, 1),
new(1, 0),
new(-1, 0),
];
list.RemoveAll(item => IsPosOutOfRangeLocal(localPos + item));
list.RemoveAll(item => localPos.Y <= 4 && item != new Vector2I(0, 1));
return list;
}
}
// 帅/将 (General) - 代表双方的最高统帅。
// 子类名ChessGeneral
// 仕/士 (Advisor) - 保护帅/将的近身侍卫。
// 子类名ChessAdvisor
// 相/象 (Elephant) - 行动受限,走田字格,不能过河。
// 子类名ChessElephant
// 車/车 (Chariot) - 横竖移动,威力巨大。
// 子类名ChessChariot
// 馬/马 (Horse) - 走日字形,跳跃式移动。
// 子类名ChessHorse
// 砲/炮 (Cannon) - 需要隔子才能吃子,直线移动。
// 子类名ChessCannon
// 兵/卒 (Pawn) - 最基础的棋子,过河后可横移。
// 子类名ChessPawn

View File

@ -0,0 +1 @@
uid://mv1sbsok7q3k

33
Scripts/Src/IBoard.cs Normal file
View File

@ -0,0 +1,33 @@
using System;
using Godot;
using Vector2I = Vector.Vector2I;
public interface IBoard {
public interface IBoardOn {
void OnInsert(object self, IPiece piece) {}
void OnRemove(object self, IPiece piece) {}
void OnMove(object self, MoveEventArgs args) {}
void OnAddRecord(object self, MoveRecord record) {}
void OnUndoRecord(object self, MoveRecord record) {}
void OnSetPieceInteral(object self, SetPieceEventArgs args) {}
record SetPieceEventArgs(IPiece? OldPiece, IPiece? NewPiece, Vector2I Pos);
record MoveEventArgs(IPiece? Piece, Vector2I From, Vector2I To);
}
public IBoardOn? On { get; protected set; }
public int Rows { get; }
public int Cols { get; }
bool IsPosOutOfRange(Vector2I pos);
IPiece? GetPiece(Vector2I pos);
IPiece? SetPiece(IPiece? piece, Vector2I pos);
bool InsertPiece(IPiece piece, Vector2I pos);
bool MovePiece(Vector2I from, Vector2I to);
IPiece? RemovePiece(Vector2I pos);
void Clear(bool clearRecords);
void AddRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos);
void UndoRecord();
record MoveRecord(IPiece? From, IPiece? To, Vector2I FromPos, Vector2I ToPos);
}

View File

@ -0,0 +1 @@
uid://cgbntdvka6y2f

22
Scripts/Src/IPiece.cs Normal file
View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using Vector2I = Vector.Vector2I;
public interface IPiece {
public interface IPieceOn {
void OnPos(object self, Vector2I oldPos) {}
void OnMove(object self, Vector2I oldPos) {}
void OnSelected(object self, bool oldIsSelected) {}
void OnName(object self, string oldName) {}
}
IPieceOn? On { get; protected set; }
Vector2I Pos { get; set; }
string Name { get; set; }
bool IsSelected { get; set; }
Dictionary<string, object> Data { get; set; }
bool CanMove(Vector2I to);
bool Move(Vector2I pos);
}

View File

@ -0,0 +1 @@
uid://dr1tx5gmpvy2h

133
Scripts/Src/Vector.cs Normal file
View File

@ -0,0 +1,133 @@
using System.Diagnostics;
namespace Vector;
public class Vector2I {
public Vector2I() {
X = 0;
Y = 0;
}
public Vector2I(int x, int y) {
X = x;
Y = y;
}
public Vector2I(Vector2I other) {
X = other.X;
Y = other.Y;
}
public int X { get; set; }
public int Y { get; set; }
public static bool operator ==(Vector2I a, Vector2I b) {
return a?.X == b?.X && a?.Y == b?.Y;
}
public static bool operator !=(Vector2I a, Vector2I b) {
return !(a == b);
}
public static Vector2I operator +(Vector2I left, Vector2I right) {
return new (
left.X + right.X,
left.Y + right.Y);
}
public static Vector2I operator -(Vector2I left, Vector2I right) {
return new (
left.X - right.X,
left.Y - right.Y);
}
public static Vector2I operator *(Vector2I left, Vector2I right) {
return new (
left.X * right.X,
left.Y * right.Y);
}
public int Dot(Vector2I with) {
return X * with.X + Y * with.Y;
}
public override bool Equals(object? obj) {
if (obj is Vector2I other) {
return this == other;
}
return false;
}
public override int GetHashCode() {
return System.HashCode.Combine(X, Y);
}
public static readonly Vector2I MaxValue = new(int.MaxValue, int.MinValue);
public static readonly Vector2I MinValue = new (int.MinValue, int.MaxValue);
public static readonly Vector2I Zero = new(0, 0);
public static readonly Vector2I Up = new(0, 1);
public static readonly Vector2I Down = new(0, -1);
public static readonly Vector2I Left = new(-1, 0);
public static readonly Vector2I Right = new(1, 0);
public override string ToString() {
return $"({X},{Y})";
}
}
public class Trans2DI {
public Vector2I XAxis { get; set; } // x axis , first column
public Vector2I YAxis { get; set; } // y axis , second column
public Vector2I Origin { get; set; } // origin point
public Trans2DI() {
XAxis = new Vector2I(1, 0);
YAxis = new Vector2I(0, 1);
Origin = new Vector2I(0, 0);
}
public Trans2DI(Vector2I xAxis, Vector2I yAxis, Vector2I origin) {
XAxis = xAxis;
YAxis = yAxis;
Origin = origin;
}
public Trans2DI(Trans2DI other) {
XAxis = other.XAxis;
YAxis = other.YAxis;
Origin = other.Origin;
}
public Trans2DI(int xx, int xy, int yx, int yy, int ox, int oy) {
XAxis = new Vector2I(xx, xy);
YAxis = new Vector2I(yx, yy);
Origin = new Vector2I(ox, oy);
}
public static Trans2DI Identity => new();
/**
* X.x Y.x * v.x + O.x = X.x * v.x + Y.x * v.y + O.x
* X.y Y.y v.y O.y X.y * v.x + Y.y * v.y + O.y
*/
public static Vector2I operator *(in Trans2DI trans, in Vector2I vec) {
return new Vector2I(
trans.XAxis.X * vec.X + trans.XAxis.Y * vec.Y,
trans.YAxis.X * vec.X + trans.YAxis.Y * vec.Y) + trans.Origin;
}
public static Vector2I operator *(in Vector2I vec, in Trans2DI trans) {
Vector2I with = vec - trans.Origin;
return new Vector2I(
trans.XAxis.X * with.X + trans.XAxis.Y * with.Y,
trans.YAxis.X * with.X + trans.YAxis.Y * with.Y);
}
public static Trans2DI operator *(Trans2DI a, Trans2DI b) {
return new Trans2DI();
}
public override string ToString() {
return $"[{XAxis}, {YAxis}, {Origin}]";
}
}

View File

@ -0,0 +1 @@
uid://btjgh7imdrha1

View File

@ -0,0 +1,58 @@
using Godot;
using System.Collections.Generic;
class GodotConfigManager {
private readonly string ConfigFilePath;
private readonly ConfigFile config = new();
private readonly bool successful;
public GodotConfigManager(string ConfigFilePath) {
this.ConfigFilePath = ConfigFilePath;
Error err = config.Load(ConfigFilePath);
if (err == Error.Ok) {
successful = true;
} else {
successful = false;
GD.PrintErr("config.Load", err);
// OS.Alert("配置文件加载失败!", "错误");
return;
}
}
public void LoadConfig(string SessionName,
Dictionary<string, Variant> data) {
// 如果文件没有加载,忽略它。
if (!successful) {
return;
}
// 迭代所有小节。
foreach (string session in config.GetSections()) {
if (session == SessionName) {
foreach (var key in config.GetSectionKeys(session)) {
if (data.ContainsKey(key)) {
data[key] = config.GetValue(session, key);
}
}
}
}
}
public void SaveConfig(string SessionName, Dictionary<string, Variant> data)
{
// 将 GlobalConfig 中的键值对写入配置文件
foreach (var key in data.Keys)
{
GD.Print(data[key]);
config.SetValue(SessionName, key, data[key]);
}
// 保存配置文件
if (config.Save(ConfigFilePath) != Error.Ok) {
OS.Alert("配置文件保存失败!", "错误");
}
}
}

View File

@ -0,0 +1 @@
uid://qfml5c031hb0

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
public interface IConfigManager {
void SetFilePath(string filePath);
void SetDefault(Dictionary<string, object> defalutConfig);
T GetValue<T>(string key);
void SetValue<T>(string key, [NotNull]T value) {
ArgumentNullException.ThrowIfNull(key);
SetValue(key, (object?)value);
}
void SetValue(string key, [NotNull]object? value);
bool HasKey(string key);
void SaveToFile();
void LoadFromFile();
}

View File

@ -0,0 +1 @@
uid://b8jvplvi4a7iv

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json;
public class JsonConfigManager : IConfigManager {
private string? filePath;
private Dictionary<string, object> config = [];
private Dictionary<string, object>? defaultConfig;
public T GetValue<T>(string key) {
if (config.TryGetValue(key, out object? val)) {
if (val is T t) {
return t;
} else if (val is JsonElement element) {
return element.Deserialize<T>() ??
throw new InvalidCastException($"无法将键'{key}'的值转换为类型{typeof(T).Name}");
}
}
if (
defaultConfig != null &&
defaultConfig.TryGetValue(key, out object? dval) &&
dval is T ret) {
return ret;
}
throw new KeyNotFoundException($"Key '{key}' not found in config.");
}
public bool HasKey(string key) => config.ContainsKey(key);
public void SaveToFile() {
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
File.WriteAllText(filePath, JsonSerializer.Serialize(config));
}
public void LoadFromFile() {
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
if (!File.Exists(filePath)) {
return;
}
config = JsonSerializer.Deserialize
<Dictionary<string, object>>(File.ReadAllText(filePath)) ?? [];
}
public void SetDefault(Dictionary<string, object> defaultConfig) => this.defaultConfig = defaultConfig;
public void SetFilePath(string filePath) => this.filePath = filePath;
public void SetValue(string key, [NotNull]object? value) {
ArgumentNullException.ThrowIfNull(value);
config[key] = value;
}
}

View File

@ -0,0 +1 @@
uid://ddvrwflje2x87

0
Test/.gdignore Normal file
View File

31
Test/Test.csproj Normal file
View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\*.csproj" />
</ItemGroup>
<!-- <ItemGroup>
<Compile Include="..\Scripts\Src\*.cs" />
</ItemGroup> -->
</Project>

16
Test/UnitTest1.cs Normal file
View File

@ -0,0 +1,16 @@
using Vector;
namespace Test;
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}

277
Test/src/TestVector.cs Normal file
View File

@ -0,0 +1,277 @@
using Vector;
namespace Test.Vector;
public class TestsVector2I {
[SetUp]
public void Setup() {
}
[Test]
public void TestVector2I_Constructors() {
// Arrange & Act
var v1 = new Vector2I();
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(v2);
// Assert
Assert.Multiple(() => {
Assert.That(v1.X, Is.EqualTo(0));
Assert.That(v1.Y, Is.EqualTo(0));
Assert.That(v2.X, Is.EqualTo(1));
Assert.That(v2.Y, Is.EqualTo(2));
Assert.That(v3.X, Is.EqualTo(1));
Assert.That(v3.Y, Is.EqualTo(2));
});
}
[Test]
public void TestVector2I_Operators() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var v3 = v1 + v2;
var v4 = v1 - v2;
var v5 = v1 * v2;
// Assert
Assert.Multiple(() => {
Assert.That(v3, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v4, Is.EqualTo(new Vector2I(-2, -2)));
Assert.That(v5, Is.EqualTo(new Vector2I(3, 8)));
});
}
[Test]
public void TestVector2I_DotProduct() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var dotProduct = v1.Dot(v2);
// Assert
Assert.That(dotProduct, Is.EqualTo(11));
}
[Test]
public void TestVector2I_ParameterModification() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var v3 = v1 + v2;
var v4 = v1 - v2;
var v5 = v1 * v2;
// Assert
Assert.Multiple(() => {
Assert.That(v1, Is.EqualTo(new Vector2I(1, 2)), "v1 should not be modified");
Assert.That(v2, Is.EqualTo(new Vector2I(3, 4)), "v2 should not be modified");
});
}
[Test]
public void TestVector2I_Equality() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(3, 4);
// Act & Assert
Assert.Multiple(() => {
Assert.That(v1, Is.EqualTo(v2));
Assert.That(v1, Is.Not.EqualTo(v3));
});
}
[Test]
public void TestVector2I_HashCode() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(3, 4);
// Act & Assert
Assert.Multiple(() => {
Assert.That(v1.GetHashCode(), Is.EqualTo(v2.GetHashCode()));
Assert.That(v1.GetHashCode(), Is.Not.EqualTo(v3.GetHashCode()));
});
}
[Test]
public void TestVector2I_BoundaryValues() {
// Arrange
var v1 = new Vector2I(0, 0);
var v2 = new Vector2I(-1, -2);
var v3 = new Vector2I(int.MaxValue, int.MaxValue);
var v4 = new Vector2I(int.MinValue, int.MinValue);
// Act
var v5 = v1 + v2;
var v6 = v1 - v2;
var v7 = v1 * v2;
var v8 = v3 + v4;
var v9 = v3 - v4;
var v10 = v3 * v4;
// Assert
Assert.Multiple(() => {
Assert.That(v5, Is.EqualTo(new Vector2I(-1, -2)));
Assert.That(v6, Is.EqualTo(new Vector2I(1, 2)));
Assert.That(v7, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(v8, Is.EqualTo(new Vector2I(-1, -1)));
// Assert.That(v9, Is.EqualTo(new Vector2I(2 * int.MaxValue - 1, 2 * int.MaxValue - 1)));
// Assert.That(v10, Is.EqualTo(new Vector2I(int.MaxValue * int.MinValue, int.MaxValue * int.MinValue)));
});
}
}
public class TestsTrans2DI {
[SetUp]
public void Setup() {
}
[Test]
public void Test1() {
Assert.Pass();
}
[Test]
public void TestVector2I_Constructors() {
// Arrange & Act
var v1 = new Vector2I();
var v2 = new Vector2I(1, 2);
var v3 = new Vector2I(v2);
Assert.Multiple(() => {
// Assert
Assert.That(v1.X, Is.EqualTo(0));
Assert.That(v1.Y, Is.EqualTo(0));
Assert.That(v2.X, Is.EqualTo(1));
Assert.That(v2.Y, Is.EqualTo(2));
Assert.That(v3.X, Is.EqualTo(1));
Assert.That(v3.Y, Is.EqualTo(2));
});
}
[Test]
public void TestVector2I_Operators() {
// Arrange
var v1 = new Vector2I(1, 2);
var v2 = new Vector2I(3, 4);
// Act
var v3 = v1 + v2;
var v4 = v1 - v2;
var v5 = v1 * v2;
Assert.Multiple(() => {
// Assert
Assert.That(v3, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v4, Is.EqualTo(new Vector2I(-2, -2)));
Assert.That(v5, Is.EqualTo(new Vector2I(3, 8)));
});
}
[Test]
public void TestTrans2DI_Constructors() {
// Arrange & Act
var t1 = new Trans2DI();
var t2 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(0, 0));
var t3 = new Trans2DI(t2);
var t4 = new Trans2DI(1, 0, 0, 1, 0, 0);
Assert.Multiple(() => {
// Assert
Assert.That(t1.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t1.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t1.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t2.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t2.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t2.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t3.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t3.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t3.Origin, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(t4.XAxis, Is.EqualTo(new Vector2I(1, 0)));
Assert.That(t4.YAxis, Is.EqualTo(new Vector2I(0, 1)));
Assert.That(t4.Origin, Is.EqualTo(new Vector2I(0, 0)));
});
}
[Test]
public void TestTrans2DI_Operators() {
// Arrange
var t1 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(1, 2));
var v1 = new Vector2I(3, 4);
// Act
var v2 = t1 * v1;
var v3 = v1 * t1;
Assert.Multiple(() => {
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(4, 6)));
Assert.That(v3, Is.EqualTo(new Vector2I(2, 2)));
});
}
[Test]
public void TestTrans2DI_BoundaryValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(int.MaxValue, 0), new Vector2I(0, int.MaxValue), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(int.MinValue, 0), new Vector2I(0, int.MinValue), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
Assert.Multiple(() => {
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(int.MaxValue, int.MaxValue)));
Assert.That(v3, Is.EqualTo(new Vector2I(int.MinValue, int.MinValue)));
});
}
[Test]
public void TestTrans2DI_SpecialValues() {
// Arrange
var t1 = new Trans2DI(new Vector2I(0, 0), new Vector2I(0, 0), new Vector2I(0, 0));
var t2 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(0, 0));
var v1 = new Vector2I(1, 1);
// Act
var v2 = t1 * v1;
var v3 = t2 * v1;
Assert.Multiple(() => {
// Assert
Assert.That(v2, Is.EqualTo(new Vector2I(0, 0)));
Assert.That(v3, Is.EqualTo(new Vector2I(1, 1)));
});
}
[Test]
public void TestTrans2DI_Multiplication() {
// Arrange
var t1 = new Trans2DI(new Vector2I(1, 0), new Vector2I(0, 1), new Vector2I(1, 2));
var t2 = new Trans2DI(new Vector2I(2, 0), new Vector2I(0, 2), new Vector2I(3, 4));
// Act
var t3 = t1 * t2;
Assert.Multiple(() => {
// Assert
Assert.That(t3.XAxis, Is.EqualTo(new Vector2I(2, 0)));
Assert.That(t3.YAxis, Is.EqualTo(new Vector2I(0, 2)));
Assert.That(t3.Origin, Is.EqualTo(new Vector2I(5, 8)));
});
}
}

1
icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

44
project.godot Normal file
View File

@ -0,0 +1,44 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="ChessGame"
config/version="0.0.6"
run/main_scene="res://Main.tscn"
config/features=PackedStringArray("4.4", "C#", "Mobile")
[autoload]
GlobalManager="*res://Scripts/GlobalManager.cs"
[display]
window/size/viewport_width=720
window/size/viewport_height=1280
window/stretch/mode="viewport"
window/handheld/orientation=1
[dotnet]
project/assembly_name="ChessGame"
[internationalization]
locale/translations=PackedStringArray("res://Lang/godot_chessgame/content/zh_Hans.po", "res://Lang/godot_chessgame/content/en_devel.po")
[navigation]
2d/name_localized={}
2d/version="0.0."
[rendering]
textures/vram_compression/import_etc2_astc=true