Browse Source

Merge branch 'dev' of github.com:DragonAura/THUAI6 into dev

tags/0.1.0^2^2
DragonAura 2 years ago
parent
commit
944a7f3d6b
50 changed files with 778 additions and 444 deletions
  1. +26
    -0
      CAPI/cpp/API/src/main.cpp
  2. +2
    -2
      CAPI/python/PyAPI/logic.py
  3. +24
    -0
      CAPI/python/PyAPI/main.py
  4. +1
    -0
      CAPI/python/requirements.txt
  5. +125
    -0
      dependency/algorithm/README.md
  6. +1
    -1
      dependency/shell/compile.sh
  7. +2
    -2
      docs/CAPI接口(cpp).md
  8. +2
    -2
      docs/CAPI接口(python).md
  9. +4
    -2
      docs/GameRules.md
  10. +25
    -2
      docs/QandA.md
  11. +83
    -46
      docs/Tool_tutorial.md
  12. +16
    -16
      installer/Installer/Common.cs
  13. +13
    -0
      installer/Installer/Installer.csproj
  14. +3
    -1
      installer/Installer/MainWindow.xaml
  15. +94
    -76
      installer/Installer/Model.cs
  16. +35
    -34
      installer/Installer/ViewModel.cs
  17. BIN
      installer/Installer/eesast_software_trans_enlarged.ico
  18. +8
    -0
      installer/InstallerUpdater/InstallerUpdater.csproj
  19. +3
    -1
      installer/InstallerUpdater/MainWindow.xaml
  20. +14
    -10
      installer/InstallerUpdater/Program.cs
  21. +0
    -4
      logic/Client/Client.csproj
  22. BIN
      logic/Client/EESASTLogo.png
  23. BIN
      logic/Client/Logo.png
  24. +3
    -2
      logic/Client/MainWindow.xaml
  25. +60
    -79
      logic/Client/MainWindow.xaml.cs
  26. +4
    -4
      logic/Client/StatusBarOfHunter.xaml.cs
  27. +4
    -4
      logic/Client/StatusBarOfSurvivor.xaml.cs
  28. +0
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  29. +0
    -1
      logic/GameClass/GameObj/Map/Chest.cs
  30. +28
    -11
      logic/GameClass/GameObj/Map/Map.cs
  31. +0
    -1
      logic/GameEngine/CollisionChecker.cs
  32. +8
    -4
      logic/GameEngine/MoveEngine.cs
  33. +2
    -0
      logic/Gaming/ActionManager.cs
  34. +0
    -1
      logic/Gaming/AttackManager.cs
  35. +4
    -0
      logic/Gaming/CharacterManager .cs
  36. +1
    -0
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  37. +13
    -26
      logic/Gaming/SkillManager/SkillManager.cs
  38. +1
    -1
      logic/Server/CopyInfo.cs
  39. +8
    -13
      logic/Server/GameServer.cs
  40. +70
    -22
      logic/Server/PlaybackServer.cs
  41. +68
    -55
      logic/Server/Program.cs
  42. +1
    -1
      logic/Server/Properties/launchSettings.json
  43. +6
    -13
      logic/Server/RpcServices.cs
  44. +14
    -0
      logic/Server/ServerBase.cs
  45. +1
    -5
      logic/cmd/PlaybackServer.cmd
  46. +1
    -1
      logic/cmd/gameServerAndClient.cmd
  47. BIN
      resource/eesast_software_trans_enlarged.png
  48. BIN
      resource/eesast_software_trans_enlarged_256x256.png
  49. BIN
      resource/vector.png
  50. BIN
      resource/wrongType.png

+ 26
- 0
CAPI/cpp/API/src/main.cpp View File

@@ -3,6 +3,7 @@
#include "structures.h"
#include <tclap/CmdLine.h>
#include <array>
#include <string_view>

#undef GetMessage
#undef SendMessage
@@ -12,6 +13,26 @@
#pragma warning(disable : 4996)
#endif

using namespace std::literals::string_view_literals;

// Generated by http://www.network-science.de/ascii/ with font "standard"
static constexpr std::string_view welcomeString = R"welcome(

_____ _ _ _ _ _ ___ __
|_ _| | | | | | | / \ |_ _/ /_
| | | |_| | | | |/ _ \ | | '_ \
| | | _ | |_| / ___ \ | | (_) |
|_| |_| |_|\___/_/ \_\___\___/

____ _ _ ____ _ _ _
/ ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| |
| | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| |
| |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_|
\____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_)


)welcome"sv;

int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder)
{
int pID = 0;
@@ -84,6 +105,11 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder)
playerType = THUAI6::PlayerType::StudentPlayer;
stuType = studentType[pID];
}

#ifdef _MSC_VER
std::cout << welcomeString << std::endl;
#endif

Logic logic(playerType, pID, trickerType, stuType);
logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly);
}


+ 2
- 2
CAPI/python/PyAPI/logic.py View File

@@ -512,10 +512,10 @@ class Logic(ILogic):

if platform.system().lower() == "windows":
os.system(
f"mkdir {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}")
f"mkdir \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}\\logs\"")
else:
os.system(
f"mkdir -p {os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/logs'}")
f"mkdir -p \"{os.path.dirname(os.path.dirname(os.path.realpath(__file__)))}/logs\"")

fileHandler = logging.FileHandler(os.path.dirname(
os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8")


+ 24
- 0
CAPI/python/PyAPI/main.py View File

@@ -9,8 +9,28 @@ from PyAPI.AI import AI
from PyAPI.logic import Logic
from typing import List, Callable
import argparse
import platform
import PyAPI.structures as THUAI6

def PrintWelcomeString() -> None:
# Generated by http://www.network-science.de/ascii/ with font "standard"
welcomeString = """

_____ _ _ _ _ _ ___ __
|_ _| | | | | | | / \ |_ _/ /_
| | | |_| | | | |/ _ \ | | '_ \
| | | _ | |_| / ___ \ | | (_) |
|_| |_| |_|\___/_/ \_\___\___/

____ _ _ ____ _ _ _
/ ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| |
| | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| |
| |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_|
\____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_)


"""
print(welcomeString)

def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None:
pID: int = 0
@@ -45,6 +65,10 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None:
playerType = THUAI6.PlayerType.TrickerPlayer
else:
playerType = THUAI6.PlayerType.StudentPlayer

if platform.system().lower() == "windows":
PrintWelcomeString()

logic = Logic(pID, playerType)
logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly)



+ 1
- 0
CAPI/python/requirements.txt View File

@@ -1,2 +1,3 @@
grpcio==1.52.0
grpcio-tools==1.52.0
numpy

+ 125
- 0
dependency/algorithm/README.md View File

@@ -247,6 +247,8 @@ int main()

## THUAI6

### high-ladder

因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。

```c++
@@ -330,3 +332,126 @@ mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore)
}
```

### competition

与天梯得分算法要满足的“枫氏七条”类似,比赛得分算法也要满足“唐氏四律”,分别如下:

1. 两队经过某场比赛的得分变化,应只与该场比赛有关,而与历史积分无关。
2. 须赋予比赛获胜一方基础得分,哪怕获胜一方的优势非常小。也就是说,哪怕胜利一方仅以微弱优势获胜,也需要拉开胜者与败者的分差。
3. 胜利一方优势越大,得分理应越高。
4. 对于一场比赛,胜利一方的得分不能无限大,须控制在一个合理的数值以下。

- 在非平局的情况下,(胜者)天梯得分与双方比赛分差值成正相关,得分函数如下(以x表示得分差值,y表示(胜者)天梯得分,a、b为固定参数)

​ $$y=ax^2(1-0.375\cdot(\tanh(\frac{x}{b}-1)+1))$$

- 在平局情况下,(双方)天梯得分与比赛分成正相关,得分函数如下(以x表示比赛分,y表示(双方)天梯得分,c为固定参数)

​ $$y=cx^2$$

- 不管是哪种情况,都有得分下界,非平局为100,平局为25

```c++
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cassert>
using namespace std;

template <typename T>
using mypair = pair<T, T>;
double minScore = 100;

double TieScore(double gameScore)
{
const double get = 9e-5; // 天梯得分权值
double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore
double highScore = 6000.0; // 将highScore设定为较大值,使得correct较小
double correct = 1 - 0.375 * (tanh((highScore - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量
cout << "correct: " << correct << endl;
int score = round(gameScore * gameScore * get * correct / 4);
return score > minScore / 4 ? score : minScore / 4;
}

double WinScore(double delta, double winnerGameScore) // 根据游戏得分差值,与绝对分数,决定最后的加分
{
assert(delta > 0);
const double firstnerGet = 9e-5; // 胜利者天梯得分权值
double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore
double correct = 1 - 0.375 * (tanh((delta - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量
cout << "correct: " << correct << endl;
int score = round(delta * delta * firstnerGet * correct);
return score > minScore ? score : minScore;
}

// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分
mypair<double> cal(mypair<double> orgScore, mypair<double> competitionScore)
{
// 调整顺序,让第一个元素成为获胜者,便于计算

bool reverse = false; // 记录是否需要调整

if (competitionScore.first < competitionScore.second)
{
reverse = true;
}

if (reverse) // 如果需要换,换两者的顺序
{
swap(competitionScore.first, competitionScore.second);
swap(orgScore.first, orgScore.second);
}

double delta = competitionScore.first - competitionScore.second;
double addScore;
mypair<double> resScore;

// 先处理平局的情况
if (delta == 0)
{
addScore = TieScore(competitionScore.first);
resScore = mypair<double>(orgScore.first + addScore, orgScore.second + addScore);
}

// 再处理有胜负的情况
else
{
addScore = WinScore(delta, competitionScore.first);
resScore = mypair<double>(orgScore.first + addScore, orgScore.second);
}

// 如果换过,再换回来
if (reverse)
{
swap(resScore.first, resScore.second);
}

return resScore;
}

void Print(mypair<double> score)
{
std::cout << "team1: " << score.first << std::endl
<< "team2: " << score.second << std::endl;
}

int main()
{
double x, y, t, i = 0;
cin >> t;
while (i < t)
{
cout << "----------------------------------------\n";
std::cout << "origin score of team 1 and 2: " << std::endl;
std::cin >> x >> y;
auto ori = mypair<double>(x, y);
std::cout << "game score of team 1 and 2: " << std::endl;
std::cin >> x >> y;
auto sco = mypair<double>(x, y);
Print(cal(ori, sco));
++i;
}
return 0;
}
```


+ 1
- 1
dependency/shell/compile.sh View File

@@ -7,7 +7,7 @@ while (( $i <= 5 ))
do
if [ -f "${bind}/player${i}.cpp" ]; then
cp -f $bind/player$i.cpp ./API/src/AI.cpp
cmake ./CMakeLists.txt && make >compile_log$i.txt 2>&1
cmake ./CMakeLists.txt && make -j$(nproc) >compile_log$i.txt 2>&1
mv ./capi $bind/capi$i # executable file
if [ $? -ne 0 ]; then
flag=0


+ 2
- 2
docs/CAPI接口(cpp).md View File

@@ -43,7 +43,7 @@
### 信息获取

#### 队内信息
- `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。
- `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。
- `bool HaveMessage()`:是否有队友发来的尚未接收的信息。
- `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。

@@ -59,7 +59,7 @@
下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。

- `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。
- 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度
- `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度
- `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度


+ 2
- 2
docs/CAPI接口(python).md View File

@@ -55,7 +55,7 @@

#### 队内信息

- `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。
- `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。
- `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。
- `def GetMessage(self) -> Tuple[int, str]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。

@@ -72,7 +72,7 @@
下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。

- `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。
- 以下指令,若查询物品当前在视野内,则返回最新进度;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。
- `def GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度
- `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度
- `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度


+ 4
- 2
docs/GameRules.md View File

@@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000}
$$

- 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子
- 隐藏校门刷新点的区域类型始终为隐藏校门
- 任何格子的区域类型(PlaceType)始终不变,所有隐藏校门刷新点的区域类型均为隐藏校门

### 人物
- 人物直径为800
@@ -142,7 +142,7 @@ $$
- Bgm (在structures.h/.py中的student类或Tricker类中作为其属性)
1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(警戒半径/二者距离)
2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(警戒半径/可被发觉的最近的学生距离)
3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=((警戒半径x学习进度百分比)/二者距离)
3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=((警戒半径x学习进度百分比)/最近二者距离)
- 可以向每一个队友发送不超过256字节的信息

### 可视范围
@@ -360,10 +360,12 @@ $$

### 信息相关
- Bgm在没有符合条件的情况下,值为0。
- 不能给自己发信息

### 技能
- CD冷却计时是在开始使用技能的瞬间开始的
- Klee的小炸弹有碰撞体积
- 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能

### 职业
- 学生职业可以重复选择

+ 25
- 2
docs/QandA.md View File

@@ -22,6 +22,11 @@ Q:卡死在第一帧不动

A:大概率是你的代码死循环了

Q:
![wrongType](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/wrongType.png)

A:命令行参数的type设置有误

## C++

Q:显示API项目已卸载
@@ -56,7 +61,11 @@ A:
2. 不要点重新生成,要点生成
3. 开启下图选项
![CompileFaster](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/CompileFaster.png)

Q:这是什么错误啊
![vector](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/vector.png)

A:调用了容量为0的vector

## Python

@@ -73,6 +82,20 @@ A:
- 可能措施3. 更新pip
`python -m pip install --upgrade pip` (pip 版本最好为23.1)

## 游戏引擎/机制
Q:咱们这边play函数调用机制究竟是怎么样的?如果50ms内没有执行完当前程序,是在下一帧不会重新调用play吗
还是只有move这样的明确有时间为参量的才会有上面那个机制

A:
- 调用任何主动指令都不会占用你play函数多少时间,你可以把它想成一个信号,告诉游戏引擎你想做什么
- 50ms内没有执行完当前程序,是指你的play函数例如求最短路之类的操作会占用的时间
- 准确地说,50ms内没有执行完当前程序,在下一帧一般会重新调用play
- 比如说,你第一次调用花了70ms
那么下一次调用会在这次之后立刻开始
如果你三次都70ms,就会4帧3次play了
- 当然第一次调用花了110ms,第二帧自然不调用了


## 比赛相关
Q:职业数值会修改吗?

@@ -80,4 +103,4 @@ A:初赛结束会调数值及机制,增加新角色

Q:初赛后会修改什么呢?

A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件
A:可能的修改:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM;HaveView()修改 等

+ 83
- 46
docs/Tool_tutorial.md View File

@@ -2,9 +2,9 @@

[toc]

## Visual Studio使用说明
## Visual Studio 使用说明

比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。
比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。

### 生成模式的设置

@@ -12,7 +12,7 @@

![image-20230416010705076](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/image-20230416010705076.png)

可以更改生成模式为`Debug`或`Release`
可以更改生成模式为 `Debug` `Release`

### 命令行参数的设置

@@ -22,19 +22,19 @@

在命令参数一栏中加入命令行参数进行调试

### cmd脚本的参数修改
### cmd 脚本的参数修改

右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。
右键点击 `.cmd` `.bat` 文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上 `::`,可以注释掉该行。

## C++接口必看
## C++ 接口必看

**在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! **

除非特殊指明,以下代码均在 MSVC 19.28.29913 /std:c++17 与 g++ 10.2 for linux -std=c++17 两个平台下通过。
除非特殊指明,以下代码均在 MSVC 19.28.29913 x64 `/std:c++17` 与 GCC 10.2 x86_64-linux-gnu `-std=c++17` 两个平台下通过。



由于我们的比赛最终会运行在Linux平台上,因此程设课上学到的一些只适用于Windows的C++操作很可能并不能正确执行。此外,代码中使用了大量Modern C++中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用C++接口必须了解的知识。
由于我们的比赛最终会运行在 Linux 平台上,因此程设课上学到的一些只适用于 Windows C++ 操作很可能并不能正确执行。此外,代码中使用了大量 Modern C++ 中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用 C++ 接口必须了解的知识。



@@ -42,7 +42,7 @@



编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!! 应当使用 `std::chrono`
编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!!应当使用 `std::chrono`

头文件:`#include <chrono>`

@@ -127,7 +127,7 @@ int main()



### `auto`类型推导
### `auto` 类型推导



@@ -147,7 +147,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型



#### std::vector
#### `std::vector`

头文件:`#include <vector>`,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。

@@ -155,7 +155,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型

```cpp
std::vector<int> v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ...
v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是10
v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是 10
v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 }
```

@@ -245,7 +245,7 @@ for (auto itr = begin(arr); itr != end(arr); ++itr)



### fmt库的使用
### fmt 库的使用



@@ -264,11 +264,11 @@ std::string str_fmt = fmt::format("number: {}{}", 1, teststr); // 两种方法



## Python接口必看
## Python 接口必看

比赛**只保证!!**支持 Python 3.9,不保证支持其他版本

比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。
比赛中的 Python 接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。

```python
from concurrent.futures import Future, ThreadPoolExecutor
@@ -297,15 +297,15 @@ if __name__ == '__main__':



## C++相关小知识
## C++ 相关小知识



### lambda表达式
### lambda 表达式



#### lambda表达式概述
#### lambda 表达式概述



@@ -456,7 +456,7 @@ f(); // 输出 4,而非 9
> []()
> {
> int y = x; // OK, constant folding
> int z = Func1(x); // Compile error! odr-used! x is not captured!
> int z = Func1(x); // Compile error! odr-used! x is not captured!
> }();
> }
> ```
@@ -587,7 +587,7 @@ lambda 表达式还有很多有趣之处,例如泛型 lambda、返回 lambda



### std::thread
### `std::thread`

头文件:`#include <thread>`。用于开启新的线程。示例代码:

@@ -727,7 +727,7 @@ int main()

##### 概览

`shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。
`shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的 `std::shared_ptr` 数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。

像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如:

@@ -759,27 +759,27 @@ void Func()
// 上述此语句执行过后,只有一个智能指针 sp1 指向这个 int,引用计数为 1

{
auto sp2 = sp1; // 构造一个智能指针 sp2,指向 sp1 指向的内存,并将引用计数+1
auto sp2 = sp1; // 构造一个 std::shared_ptr sp2,指向 sp1 指向的对象,并将引用计数加一

// 故此处引用计数为2

std::cout << *sp2 << std::endl; // 输出 110

// 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数-1,因此此时引用计数为1
// 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数减一,因此此时引用计数为 1
}

// 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再-1,故引用计数降为0
// 也就是不再有智能指针指向它了,调用 delete 释放内存
// 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再减一,故引用计数降为 0
// 也就是不再有 std::shared_ptr 指向它了,调用 delete 释放
}
}
```

普通指针交给智能指针托管:
裸指针交给 `std::shared_ptr` 托管:

```cpp
int* p = new int(110);
int* q = new int(110);
std::shared_ptr sp(p); // 把 p 指向的内存交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放
std::shared_ptr sp(p); // 把 p 指向的对象交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放
std::shared_ptr sq; // sq 什么也不托管
sq.reset(q); // 让 sq 托管 q

@@ -792,22 +792,38 @@ sq.reset(q); // 让 sq 托管 q

之前说过 ,默认情况下是释放内存的函数是 `delete` 运算符,但有时我们并不希望这样。比如下面的几个情况:

+ 使用智能指针托管动态数组
+ 使用 `std::shared_ptr` 托管动态数组
+ C++11 起
```cpp
#include <memory>
void IntArrayDeleter(int* p) { delete[] p; }
int main()
{
std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数
sp.get()[0] = 0; // 访问第 0 个元素
// sp 析构时自动调用 IntArrayDeleter 释放该 int 数组
return 0;
}
// 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; });
```
+ C++17 起
```cpp
std::shared_ptr<int[]> sp(new int[10]);
sp[0] = 0; // 访问第 0 个元素
```

```cpp
#include <memory>
void IntArrayDeleter(int* p) { delete[] p; }
int main()
{
std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数
// sp 析构时自动调用 IntArrayDeleter 释放该 int 数组
return 0;
}
// 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; });
```
+ C++20 起
```cpp
auto sp = std::make_shared<int[]>(10);
sp[0] = 0; // 访问第 0 个元素
```


@@ -817,12 +833,12 @@ sq.reset(q); // 让 sq 托管 q

```c++
HDC hdc; // DC: Device context,一个指向 DC 的句柄(HANDLE)
hdc = GetDC(hWnd); // 获取设备上下文
hdc = GetDC(hWnd); // 获取设备上下文
/*执行绘图操作*/
ReleaseDC(hWnd, hdc); // 绘图完毕,将设备上下文资源释放,归还给 Windows 系统
```

使用智能指针对其进行托管,代码如下:
使用 `std::shared_ptr` 对其进行托管,代码如下:

```c++
// 使用 lambda 表达式写法(推荐)
@@ -866,7 +882,7 @@ void Func()
}
```

这是因为,只有复制构造函数里面才有使引用计数加1的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。
这是因为,只有复制构造函数里面才有使引用计数加的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了 2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。

另一个著名的错误用法,请继续阅读 `std::weak_ptr`。

@@ -955,7 +971,28 @@ else

#### `std::unique_ptr`

`std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开。
`std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开,仅提供一个例子作为参考:
```cpp
{
auto p = std::make_unique<int>(5); // 创建一个 int 对象并初始化为 5
std::cout << *p << std::endl; // 输出 5
// 该 int 对象随着 p 的析构而被 delete
}
```
需要注意的是,由于[标准委员会的疏忽~忘了~(partly an oversight)](https://herbsutter.com/gotw/_102/),C++14 中才引进`std::make_unique`,C++11 中无法使用。因此 C++11 若想使用则需自定义 `make_unique`:
```cpp
namespace
{
template<typename T, typename... Args>
std::unique_ptr<T> make_unique( Args&&... args )
{
return std::unique_ptr<T>(new T( std::forward<Args>(args)...));
}
}
```





+ 16
- 16
installer/Installer/Common.cs View File

@@ -10,7 +10,7 @@ namespace starter.viewmodel.common
{
public abstract class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
///< summary>
/// announce notification
/// </summary>
@@ -25,21 +25,21 @@ namespace starter.viewmodel.common
/// </summary>
public class BaseCommand : ICommand
{
private Func<object, bool> _canExecute;
private Action<object> _execute;
private Func<object?, bool>? _canExecute;
private Action<object?> _execute;

public BaseCommand(Func<object, bool> canExecute, Action<object> execute)
public BaseCommand(Func<object?, bool>? canExecute, Action<object?> execute)
{
_canExecute = canExecute;
_execute = execute;
}

public BaseCommand(Action<object> execute) :
public BaseCommand(Action<object?> execute) :
this(null, execute)
{
}

public event EventHandler CanExecuteChanged
public event EventHandler? CanExecuteChanged
{
add
{
@@ -57,12 +57,12 @@ namespace starter.viewmodel.common
}
}

public bool CanExecute(object parameter)
public bool CanExecute(object? parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}

public void Execute(object parameter)
public void Execute(object? parameter)
{
if (_execute != null && CanExecute(parameter))
{
@@ -79,15 +79,15 @@ namespace starter.viewmodel.common
{
return false;
}
string checkvalue = value.ToString();
string targetvalue = parameter.ToString();
string checkvalue = value.ToString() ?? "";
string targetvalue = parameter.ToString() ?? "";
bool r = checkvalue.Equals(targetvalue);
return r;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value == null || parameter == null)
if (value is null || parameter is null)
{
return null;
}
@@ -132,22 +132,22 @@ namespace starter.viewmodel.common
static bool _isUpdating = false;
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox pb = d as PasswordBox;
PasswordBox pb = (d as PasswordBox)!;
pb.PasswordChanged -= Pb_PasswordChanged;
if (!_isUpdating)
(d as PasswordBox).Password = e.NewValue.ToString();
(d as PasswordBox)!.Password = e.NewValue.ToString();
pb.PasswordChanged += Pb_PasswordChanged;
}

private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox pb = d as PasswordBox;
PasswordBox pb = (d as PasswordBox)!;
pb.PasswordChanged += Pb_PasswordChanged;
}

private static void Pb_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox pb = sender as PasswordBox;
PasswordBox pb = (sender as PasswordBox)!;
_isUpdating = true;
SetPassword(pb, pb.Password);
_isUpdating = false;


+ 13
- 0
installer/Installer/Installer.csproj View File

@@ -6,8 +6,21 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon>
<PackageIcon>eesast_software_trans.png</PackageIcon>
</PropertyGroup>

<ItemGroup>
<Content Include="eesast_software_trans_enlarged.ico" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\resource\eesast_software_trans.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />


+ 3
- 1
installer/Installer/MainWindow.xaml View File

@@ -5,7 +5,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Installer" xmlns:c="clr-namespace:starter.viewmodel.common"
mc:Ignorable="d"
Title="Installer" Window.SizeToContent="WidthAndHeight">
Title="Installer" Window.SizeToContent="WidthAndHeight"
ResizeMode="CanMinimize"
>
<Window.Resources>
<c:RadioConverter x:Key="RadioConverter"/>
</Window.Resources>


+ 94
- 76
installer/Installer/Model.cs View File

@@ -21,7 +21,6 @@ using System.Net.Http;
using System.Windows;
using System.Windows.Shapes;
//using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;

using MessageBox = System.Windows.MessageBox;
@@ -153,18 +152,22 @@ namespace starter.viewmodel.settings
}
public bool RecallUser()
{
Username = Web.ReadJson("email");
if (Username == null || Username.Equals(""))
var username = Web.ReadJson("email");
if (username == null || username.Equals(""))
{
Username = "";
return false;
}
Password = Web.ReadJson("password");
if (Password == null || Username.Equals(""))
Username = username;

var password = Web.ReadJson("password");
if (password == null || password.Equals(""))
{
Password = "";
return false;
}
Password = password;

return true;
}
public bool ForgetUser()
@@ -210,8 +213,6 @@ namespace starter.viewmodel.settings
switch (CodeRoute.Substring(CodeRoute.LastIndexOf('.') + 1))
{
case "cpp":
Language = "cpp";
break;
case "h":
Language = "cpp";
break;
@@ -244,15 +245,12 @@ namespace starter.viewmodel.settings
}
public UsingOS ReadUsingOS()
{
string OS = Web.ReadJson("OS");
if (OS == null)
return UsingOS.Win;
else if (OS.Equals("linux"))
return UsingOS.Linux;
else if (OS.Equals("osx"))
return UsingOS.OSX;
else
return UsingOS.Win;
return Web.ReadJson("OS") switch
{
"linux" => UsingOS.Linux,
"osx" => UsingOS.OSX,
_ => UsingOS.Win,
};
}
/// <summary>
/// Route of files
@@ -274,7 +272,7 @@ namespace starter.viewmodel.settings
{
get; set;
}
public string Language
public string? Language
{
get; set;
}
@@ -394,7 +392,7 @@ namespace Downloader
public class Data
{
public static string path = ""; // 标记路径记录文件THUAI6.json的路径
public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录
public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录
public static string dataPath = ""; // C盘的文档文件夹
public Data(string path)
{
@@ -403,7 +401,7 @@ namespace Downloader
Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json");
if (File.Exists(Data.path))
{
var dict = new Dictionary<string, string>();
Dictionary<string, string>? dict;
using (StreamReader r = new StreamReader(Data.path))
{
string json = r.ReadToEnd();
@@ -411,7 +409,7 @@ namespace Downloader
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json);
if (dict != null && dict.ContainsKey("installpath"))
{
FilePath = dict["installpath"].Replace('\\', '/');
@@ -425,11 +423,12 @@ namespace Downloader
}
else
{
FilePath = System.IO.Path.GetDirectoryName(@path);
FilePath = System.IO.Path.GetDirectoryName(@path)
?? throw new Exception("Failed to get the path of the file");

//将dat文件写入程序运行路径
string json;
Dictionary<string, string> dict = new Dictionary<string, string>();
Dictionary<string, string>? dict;
using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite);
using (StreamReader r = new StreamReader(fs))
{
@@ -438,7 +437,7 @@ namespace Downloader
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json);
dict?.Add("installpath", path);
}
using FileStream fs2 = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite);
@@ -451,7 +450,7 @@ namespace Downloader
public static void ResetFilepath(string newPath)
{
string json;
Dictionary<string, string> dict = new Dictionary<string, string>();
Dictionary<string, string>? dict;
FilePath = newPath.Replace('\\', '/');
path = System.IO.Path.Combine(dataPath, "THUAI6.json");
using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite);
@@ -462,14 +461,14 @@ namespace Downloader
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json);
if (dict != null && dict.ContainsKey("installpath"))
{
dict["installpath"] = newPath;
}
else
{
dict.Add("installpath", newPath);
dict?.Add("installpath", newPath);
}
if (dict == null || !dict.ContainsKey("download"))
{
@@ -517,9 +516,10 @@ namespace Downloader
// 创建存储桶
try
{
string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID
string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹
string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名
string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID
string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹
?? throw new Exception("本地文件夹路径获取失败");
string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名
GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName);

Dictionary<string, string> test = request.GetRequestHeaders();
@@ -553,7 +553,7 @@ namespace Downloader

public static string GetFileMd5Hash(string strFileFullPath)
{
FileStream fst = null;
FileStream? fst = null;
try
{
fst = new FileStream(strFileFullPath, FileMode.Open, FileAccess.Read);
@@ -634,7 +634,7 @@ namespace Downloader
using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName)))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty);
Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
string updatingFolder = "";
switch (OS)
{
@@ -815,7 +815,7 @@ namespace Downloader
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json);
if (dict == null || !dict.ContainsKey("download") || "false" == dict["download"])
{
return false;
@@ -865,15 +865,15 @@ namespace Downloader
using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName)))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty);
Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
// var jsonDict = Utils.DeserializeJson<Dictionary1<string, string>>(json);

newFileName.Clear();
updateFileName.Clear();
newFileName.Add("THUAI6.tar.gz");
Download();
Stream inStream = null;
Stream gzipStream = null;
TarArchive tarArchive = null;
Stream? inStream = null;
Stream? gzipStream = null;
TarArchive? tarArchive = null;
try
{
using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")))
@@ -886,7 +886,7 @@ namespace Downloader
}
}
}
catch (Exception ex)
catch
{
//出错
}
@@ -899,7 +899,7 @@ namespace Downloader
FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"));
fileInfo.Delete();
string json2;
Dictionary<string, string> dict = new Dictionary<string, string>();
Dictionary<string, string>? dict;
string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json");
using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite);
using (StreamReader r = new StreamReader(fs))
@@ -909,7 +909,7 @@ namespace Downloader
{
json2 += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2);
dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2);
if (dict == null || !dict.ContainsKey("download"))
{
dict?.Add("download", "true");
@@ -1000,7 +1000,7 @@ namespace Downloader
using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, "hash.json")))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace("/", @"\\");
Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
Dictionary<string, string> jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
Change_all_hash(Data.FilePath, jsonDict);
OverwriteHash(jsonDict);
break;
@@ -1008,7 +1008,7 @@ namespace Downloader
else
{
Console.WriteLine("读取路径失败!请重新输入文件路径:");
Data.ResetFilepath(Console.ReadLine());
Data.ResetFilepath(Console.ReadLine() ?? "");
}
}
}
@@ -1058,7 +1058,7 @@ namespace Downloader
}

string json2;
Dictionary<string, string> dict = new Dictionary<string, string>();
Dictionary<string, string>? dict;
string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json");
using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite);
using (StreamReader r = new StreamReader(fs))
@@ -1068,7 +1068,7 @@ namespace Downloader
{
json2 += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2);
dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2);
if (dict == null || !dict.ContainsKey("download"))
{
dict?.Add("download", "false");
@@ -1189,7 +1189,7 @@ namespace Downloader
while (true)
{
Console.WriteLine($"1. 更新hash.json 2. 检查更新 3.下载{ProgramName} 4.删除{ProgramName} 5.启动进程 6.移动{ProgramName}到其它路径");
string choose = Console.ReadLine();
string choose = Console.ReadLine() ?? "";
if (choose == "1")
{
if (!CheckAlreadyDownload())
@@ -1216,7 +1216,7 @@ namespace Downloader
else
{
Console.WriteLine("读取路径失败!请重新输入文件路径:");
Data.ResetFilepath(Console.ReadLine());
Data.ResetFilepath(Console.ReadLine() ?? "");
}
}
}
@@ -1230,7 +1230,7 @@ namespace Downloader
{
string newpath;
Console.WriteLine("请输入下载路径:");
newpath = Console.ReadLine();
newpath = Console.ReadLine() ?? "";
Data.ResetFilepath(newpath);
DownloadAll();
}
@@ -1253,15 +1253,15 @@ namespace Downloader
else if (choose == "6")
{
string newPath;
newPath = Console.ReadLine();
newPath = Console.ReadLine() ?? "";
MoveProgram(newPath);
}
else if (choose == "7")
{
Console.WriteLine("请输入email:");
string username = Console.ReadLine();
string username = Console.ReadLine() ?? "";
Console.WriteLine("请输入密码:");
string password = Console.ReadLine();
string password = Console.ReadLine() ?? "";

await web.LoginToEEsast(client, username, password);
}
@@ -1285,7 +1285,8 @@ namespace Downloader
string keyHead = "Installer/";
Tencent_cos_download downloader = new Tencent_cos_download();
string hashName = "installerHash.json";
string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)
?? throw new Exception("Failed to get current directory");
int result = 0;
try
{
@@ -1301,7 +1302,7 @@ namespace Downloader
using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, hashName)))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty);
Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var jsonDict = Utils.TryDeserializeJson<Dictionary<string, string>>(json);
string md5 = "";
List<string> awaitUpdate = new List<string>();
if (jsonDict != null)
@@ -1343,15 +1344,16 @@ namespace Downloader
static public bool SelfUpdateDismissed()
{
string json;
string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)
?? throw new Exception("Failed to get directory!");
if (!File.Exists(System.IO.Path.Combine(dir, "updateList.json")))
return false;
using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, "updateList.json")))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty);
List<string> jsonList;
List<string>? jsonList;
if (json != null)
jsonList = JsonConvert.DeserializeObject<List<string>>(json);
jsonList = Utils.TryDeserializeJson<List<string>>(json);
else
return false;
if (jsonList != null && jsonList.Contains("Dismiss"))
@@ -1403,7 +1405,7 @@ namespace WebConnect
throw new Exception("no token!");
logintoken = token;
SaveToken();
var info = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
var info = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
Downloader.UserInfo._id = info["_id"];
Downloader.UserInfo.email = info["email"];
break;
@@ -1459,19 +1461,19 @@ namespace WebConnect
{
case System.Net.HttpStatusCode.OK:

var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID)
string region = "ap-beijing"; // 设置一个默认的存储桶地域
var res = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID)
string region = "ap-beijing"; // 设置一个默认的存储桶地域
CosXmlConfig config = new CosXmlConfig.Builder()
.IsHttps(true) // 设置默认 HTTPS 请求
.SetAppid(appid) // 设置腾讯云账户的账户标识 APPID
.SetRegion(region) // 设置一个默认的存储桶地域
.SetDebugLog(true) // 显示日志
.Build(); // 创建 CosXmlConfig 对象
string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId";
string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey";
string tmpToken = res["SecurityToken"]; //"临时密钥 token";
long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]);//临时密钥有效截止时间,精确到秒
.IsHttps(true) // 设置默认 HTTPS 请求
.SetAppid(appid) // 设置腾讯云账户的账户标识 APPID
.SetRegion(region) // 设置一个默认的存储桶地域
.SetDebugLog(true) // 显示日志
.Build(); // 创建 CosXmlConfig 对象
string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId";
string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey";
string tmpToken = res["SecurityToken"]; //"临时密钥 token";
long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]); //临时密钥有效截止时间,精确到秒
QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider(
tmpSecretId, tmpSecretKey, tmpExpiredTime, tmpToken
);
@@ -1584,12 +1586,12 @@ namespace WebConnect
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
if (dict.ContainsKey("token"))
{
dict.Remove("token");
}
dict?.Add("token", logintoken);
dict.Add("token", logintoken);
}
using FileStream fs2 = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
using StreamWriter sw = new StreamWriter(fs2);
@@ -1627,7 +1629,7 @@ namespace WebConnect
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
Dictionary<string, string> dict = new Dictionary<string, string>();
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
if (!dict.ContainsKey(key))
{
dict.Add(key, data);
@@ -1651,7 +1653,7 @@ namespace WebConnect
}
}

public static string ReadJson(string key)
public static string? ReadJson(string key)
{
try
{
@@ -1664,7 +1666,7 @@ namespace WebConnect
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
fs.Close();
sr.Close();
return dict[key];
@@ -1691,7 +1693,7 @@ namespace WebConnect
{
json += @"{""THUAI6""" + ":" + @"""2023""}";
}
dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
dict = Utils.DeserializeJson1<Dictionary<string, string>>(json);
if (!dict.ContainsKey("token"))
{
return false;
@@ -1745,9 +1747,9 @@ namespace WebConnect
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var info = await response.Content.ReadAsStringAsync();
var s1 = JsonConvert.DeserializeObject<Dictionary<string, object>>(info)["data"];
var s2 = JsonConvert.DeserializeObject<Dictionary<string, List<object>>>(s1.ToString())["contest_team_member"];
var sres = JsonConvert.DeserializeObject<Dictionary<string, string>>(s2[0].ToString())["team_id"];
var s1 = Utils.DeserializeJson1<Dictionary<string, object>>(info)["data"];
var s2 = Utils.DeserializeJson1<Dictionary<string, List<object>>>(s1.ToString() ?? "")["contest_team_member"];
var sres = Utils.DeserializeJson1<Dictionary<string, string>>(s2[0].ToString() ?? "")["team_id"];
return sres;
}
async public Task<string> GetUserId(string learnNumber)
@@ -1773,4 +1775,20 @@ namespace WebConnect
public string Token { get; set; } = "";
}

internal static class Utils
{
public static T DeserializeJson1<T>(string json)
where T : notnull
{
return JsonConvert.DeserializeObject<T>(json)
?? throw new Exception("Failed to deserialize json.");
}

public static T? TryDeserializeJson<T>(string json)
where T : notnull
{
return JsonConvert.DeserializeObject<T>(json);
}
}

}

+ 35
- 34
installer/Installer/ViewModel.cs View File

@@ -36,14 +36,15 @@ namespace starter.viewmodel.settings
//Program.Tencent_cos_download.UpdateHash();

Status = SettingsModel.Status.working;
string CurrentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
string currentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)
?? throw new Exception("Fail to get current directory");
//MessageBox.Show("更新器工作正常");
if (!Program.Tencent_cos_download.SelfUpdateDismissed())
{
switch (Program.Tencent_cos_download.CheckSelfVersion())
{
case 1:
Process.Start(System.IO.Path.Combine(CurrentDirectory, "InstallerUpdater.exe"));
Process.Start(System.IO.Path.Combine(currentDirectory, "InstallerUpdater.exe"));
Environment.Exit(0);
break;
case -1:
@@ -156,7 +157,7 @@ namespace starter.viewmodel.settings
{
e.Cancel = true;
MessageBox.Show("下载取消");
if (e.Argument.ToString().Equals("Manual"))
if (e.Argument?.ToString()?.Equals("Manual") ?? false)
{
Status = SettingsModel.Status.menu;
}
@@ -167,7 +168,7 @@ namespace starter.viewmodel.settings
else
{
if (obj.Update())
if (e.Argument.ToString().Equals("Manual"))
if (e.Argument?.ToString()?.Equals("Manual") ?? false)
{
e.Result = 1;
}
@@ -598,14 +599,14 @@ namespace starter.viewmodel.settings
return "";
}

private BaseCommand clickBrowseCommand;
private BaseCommand? clickBrowseCommand;
public BaseCommand ClickBrowseCommand
{
get
{
if (clickBrowseCommand == null)
{
clickBrowseCommand = new BaseCommand(new Action<object>(o =>
clickBrowseCommand = new BaseCommand(new Action<object?>(o =>
{
Route = RouteSelectWindow("Folder");
}));
@@ -613,14 +614,14 @@ namespace starter.viewmodel.settings
return clickBrowseCommand;
}
}
private BaseCommand clickConfirmCommand;
private BaseCommand? clickConfirmCommand;
public BaseCommand ClickConfirmCommand
{
get
{
if (clickConfirmCommand == null)
{
clickConfirmCommand = new BaseCommand(new Action<object>(o =>
clickConfirmCommand = new BaseCommand(new Action<object?>(o =>
{
if (Status == SettingsModel.Status.newUser)
{
@@ -672,14 +673,14 @@ namespace starter.viewmodel.settings
return clickConfirmCommand;
}
}
private BaseCommand clickUpdateCommand;
private BaseCommand? clickUpdateCommand;
public BaseCommand ClickUpdateCommand
{
get
{
if (clickUpdateCommand == null)
{
clickUpdateCommand = new BaseCommand(new Action<object>(o =>
clickUpdateCommand = new BaseCommand(new Action<object?>(o =>
{
this.RaisePropertyChanged("UpdateInfoVis");
if (obj.UpdatePlanned)
@@ -719,14 +720,14 @@ namespace starter.viewmodel.settings
return clickUpdateCommand;
}
}
private BaseCommand clickMoveCommand;
private BaseCommand? clickMoveCommand;
public BaseCommand ClickMoveCommand
{
get
{
if (clickMoveCommand == null)
{
clickMoveCommand = new BaseCommand(new Action<object>(o =>
clickMoveCommand = new BaseCommand(new Action<object?>(o =>
{
Status = SettingsModel.Status.move;
}));
@@ -734,14 +735,14 @@ namespace starter.viewmodel.settings
return clickMoveCommand;
}
}
private BaseCommand clickUninstCommand;
private BaseCommand? clickUninstCommand;
public BaseCommand ClickUninstCommand
{
get
{
if (clickUninstCommand == null)
{
clickUninstCommand = new BaseCommand(new Action<object>(o =>
clickUninstCommand = new BaseCommand(new Action<object?>(o =>
{
UpdateInfoVis = Visibility.Collapsed;
this.RaisePropertyChanged("UpdateInfoVis");
@@ -767,14 +768,14 @@ namespace starter.viewmodel.settings
}
}

private BaseCommand clickLoginCommand;
private BaseCommand? clickLoginCommand;
public BaseCommand ClickLoginCommand
{
get
{
if (clickLoginCommand == null)
{
clickLoginCommand = new BaseCommand(new Action<object>(async o =>
clickLoginCommand = new BaseCommand(new Action<object?>(async o =>
{
switch (await obj.Login())
{
@@ -813,14 +814,14 @@ namespace starter.viewmodel.settings
}
}

private BaseCommand clickLaunchCommand;
private BaseCommand? clickLaunchCommand;
public BaseCommand ClickLaunchCommand
{
get
{
if (clickLaunchCommand == null)
{
clickLaunchCommand = new BaseCommand(new Action<object>(o =>
clickLaunchCommand = new BaseCommand(new Action<object?>(o =>
{
if (obj.UpdatePlanned)
{
@@ -840,14 +841,14 @@ namespace starter.viewmodel.settings
return clickLaunchCommand;
}
}
private BaseCommand clickEditCommand;
private BaseCommand? clickEditCommand;
public BaseCommand ClickEditCommand
{
get
{
if (clickEditCommand == null)
{
clickEditCommand = new BaseCommand(new Action<object>(o =>
clickEditCommand = new BaseCommand(new Action<object?>(o =>
{
Status = SettingsModel.Status.menu;
if (obj.UpdatePlanned)
@@ -858,14 +859,14 @@ namespace starter.viewmodel.settings
return clickEditCommand;
}
}
private BaseCommand clickBackCommand;
private BaseCommand? clickBackCommand;
public BaseCommand ClickBackCommand
{
get
{
if (clickBackCommand == null)
{
clickBackCommand = new BaseCommand(new Action<object>(o =>
clickBackCommand = new BaseCommand(new Action<object?>(o =>
{
UpdateInfoVis = Visibility.Collapsed;
this.RaisePropertyChanged("UpdateInfoVis");
@@ -878,14 +879,14 @@ namespace starter.viewmodel.settings
return clickBackCommand;
}
}
private BaseCommand clickUploadCommand;
private BaseCommand? clickUploadCommand;
public BaseCommand ClickUploadCommand
{
get
{
if (clickUploadCommand == null)
{
clickUploadCommand = new BaseCommand(new Action<object>(async o =>
clickUploadCommand = new BaseCommand(new Action<object?>(async o =>
{
if (obj.UploadReady)
{
@@ -953,14 +954,14 @@ namespace starter.viewmodel.settings
return clickUploadCommand;
}
}
private BaseCommand clickAboutUploadCommand;
private BaseCommand? clickAboutUploadCommand;
public BaseCommand ClickAboutUploadCommand
{
get
{
if (clickAboutUploadCommand == null)
{
clickAboutUploadCommand = new BaseCommand(new Action<object>(o =>
clickAboutUploadCommand = new BaseCommand(new Action<object?>(o =>
{
if (obj.UploadReady)
{
@@ -987,14 +988,14 @@ namespace starter.viewmodel.settings
return clickAboutUploadCommand;
}
}
private BaseCommand clickExitCommand;
private BaseCommand? clickExitCommand;
public BaseCommand ClickExitCommand
{
get
{
if (clickExitCommand == null)
{
clickExitCommand = new BaseCommand(new Action<object>(o =>
clickExitCommand = new BaseCommand(new Action<object?>(o =>
{
Application.Current.Shutdown();
}));
@@ -1002,14 +1003,14 @@ namespace starter.viewmodel.settings
return clickExitCommand;
}
}
private BaseCommand clickShiftLanguageCommand;
private BaseCommand? clickShiftLanguageCommand;
public BaseCommand ClickShiftLanguageCommand
{
get
{
if (clickShiftLanguageCommand == null)
{
clickShiftLanguageCommand = new BaseCommand(new Action<object>(o =>
clickShiftLanguageCommand = new BaseCommand(new Action<object?>(o =>
{
if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp)
obj.launchLanguage = SettingsModel.LaunchLanguage.python;
@@ -1023,14 +1024,14 @@ namespace starter.viewmodel.settings
return clickShiftLanguageCommand;
}
}
private BaseCommand clickReadCommand;
private BaseCommand? clickReadCommand;
public BaseCommand ClickReadCommand
{
get
{
if (clickReadCommand == null)
{
clickReadCommand = new BaseCommand(new Action<object>(o =>
clickReadCommand = new BaseCommand(new Action<object?>(o =>
{
if (!Directory.Exists(Route + "/THUAI6/win"))
{
@@ -1050,14 +1051,14 @@ namespace starter.viewmodel.settings
return clickReadCommand;
}
}
private BaseCommand clickSwitchOSCommand;
private BaseCommand? clickSwitchOSCommand;
public BaseCommand ClickSwitchOSCommand
{
get
{
if (clickSwitchOSCommand == null)
{
clickSwitchOSCommand = new BaseCommand(new Action<object>(o =>
clickSwitchOSCommand = new BaseCommand(new Action<object?>(o =>
{
switch (obj.usingOS)
{


BIN
installer/Installer/eesast_software_trans_enlarged.ico View File

Before After

+ 8
- 0
installer/InstallerUpdater/InstallerUpdater.csproj View File

@@ -5,8 +5,16 @@
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<PackageIcon>eesast_software_trans.png</PackageIcon>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\resource\eesast_software_trans.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34">


+ 3
- 1
installer/InstallerUpdater/MainWindow.xaml View File

@@ -5,7 +5,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InstallerUpdater"
mc:Ignorable="d"
Title="MainWindow" Height="180" Width="300">
Title="MainWindow" Height="180" Width="300"
ResizeMode="CanMinimize"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>


+ 14
- 10
installer/InstallerUpdater/Program.cs View File

@@ -18,7 +18,8 @@ namespace Program
{
class Updater
{
public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName)
?? throw new Exception("Cannot get current directory");
public static string InstallerName = "Installer.exe";
public static string jsonKey = "installerHash.json";
public static string KeyHead = "Installer/";
@@ -31,7 +32,8 @@ namespace Program
using (StreamReader r = new StreamReader(System.IO.Path.Combine(Dir, "updateList.json")))
json = r.ReadToEnd();
json = json.Replace("\r", string.Empty).Replace("\n", string.Empty);
List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json);
List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json)
?? throw new Exception("Failed to deserialize json!");
foreach (string todo in jsonList)
{
if (!todo.Equals("None"))
@@ -41,14 +43,14 @@ namespace Program
}
}
}
catch (IOException)
catch (IOException ex)
{
MessageBox.Show("下载器本体未能成功关闭");
MessageBox.Show($"下载器本体未能成功关闭:\n{ex}");
return false;
}
catch
catch (Exception ex)
{
MessageBox.Show("尝试下载时出现问题");
MessageBox.Show($"尝试下载时出现问题:\n{ex}\n{ex.StackTrace}");
return false;
}
return true;
@@ -67,7 +69,8 @@ namespace Program
json += @"{""None""}";
}
List<string> ls = new List<string>();
ls = JsonConvert.DeserializeObject<List<string>>(json);
ls = JsonConvert.DeserializeObject<List<string>>(json)
?? throw new Exception("Failed to deserialize json!");
if (!ls.Contains("Dismiss"))
{
ls.Add("Dismiss");
@@ -114,9 +117,10 @@ namespace Program
// 创建存储桶
try
{
string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID
string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹
string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名
string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID
string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹
?? throw new Exception("Failed to get directory name!");
string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名
GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName);

Dictionary<string, string> test = request.GetRequestHeaders();


+ 0
- 4
logic/Client/Client.csproj View File

@@ -12,10 +12,6 @@
<None Remove="Logo.png" />
</ItemGroup>

<ItemGroup>
<Content Include="eesast_software_trans_enlarged.ico" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="FrameRateTask" Version="1.2.0" />


BIN
logic/Client/EESASTLogo.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 95 kB

BIN
logic/Client/Logo.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 95 kB Width: 2048  |  Height: 2048  |  Size: 56 kB

+ 3
- 2
logic/Client/MainWindow.xaml View File

@@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client"
mc:Ignorable="d"
Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100">
Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100"
SizeChanged="ZoomMap">

<Window.Resources>
<ImageBrush x:Key="Logo" ImageSource="Logo.png"/>
@@ -50,7 +51,7 @@
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-0.377"/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Label.RenderTransform>


+ 60
- 79
logic/Client/MainWindow.xaml.cs View File

@@ -20,6 +20,7 @@ using Playback;
using CommandLine;
using Preparation.Utility;
using Preparation.Interface;
using System.Diagnostics.CodeAnalysis;

// 目前MainWindow还未复现的功能:
// private void ClickToSetMode(object sender, RoutedEventArgs e)
@@ -34,8 +35,6 @@ namespace Client
{
public MainWindow()
{
unitHeight = unitWidth = unit = 13;
bonusflag = true;
timer = new DispatcherTimer
{
Interval = new TimeSpan(50000) // 每50ms刷新一次
@@ -60,9 +59,14 @@ namespace Client
listOfGate = new List<MessageOfGate>();
listOfHiddenGate = new List<MessageOfHiddenGate>();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitFontsize = unit / 13;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
ReactToCommandline();
}

[MemberNotNull(nameof(StatusBarsOfSurvivor), nameof(StatusBarsOfHunter), nameof(StatusBarsOfCircumstance))]
private void SetStatusBar()
{
StatusBarsOfSurvivor = new StatusBarOfSurvivor[4];
@@ -194,6 +198,7 @@ namespace Client
0 => PlayerType.NullPlayerType,
1 => PlayerType.StudentPlayer,
2 => PlayerType.TrickerPlayer,
_ => PlayerType.NullPlayerType
};
playerMsg.PlayerType = playerType;
if (Convert.ToInt64(comInfo[3]) == 1)
@@ -268,9 +273,9 @@ namespace Client
{
TextBox icon = new()
{
FontSize = 10,
Width = 20,
Height = 20,
FontSize = 7 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = text,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
@@ -282,37 +287,23 @@ namespace Client
UpperLayerOfMap.Children.Add(icon);
}

private void ZoomMap()
{
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
{
if (mapPatches[i, j] != null && (mapPatches[i, j].Width != UpperLayerOfMap.ActualWidth / 50 || mapPatches[i, j].Height != UpperLayerOfMap.ActualHeight / 50))
{
mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50;
mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50;
mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left;
mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top;
mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0);
}
}
}
}

private void ZoomMapAtFirst()
private void ZoomMap(object sender, SizeChangedEventArgs e)
{
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitFontsize = unit / 13;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
{
if (mapPatches[i, j] != null)
{
mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50;
mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50;
mapPatches[i, j].Width = unitWidth;
mapPatches[i, j].Height = unitHeight;
mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left;
mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top;
mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0);
mapPatches[i, j].Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0);
}
}
}
@@ -330,7 +321,7 @@ namespace Client
Height = unitHeight,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(Width * (j), Height * (i), 0, 0)
Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0)//unitWidth cannot be replaced by Width
};
switch (defaultMap[i, j])
{
@@ -697,7 +688,7 @@ namespace Client
MaxButton.Content = "🗖";
foreach (var obj in listOfHuman)
{
if (obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot)
if (!isDataFixed[obj.PlayerId] && obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot)
{
IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType));
totalLife[obj.PlayerId] = occupation.MaxHp;
@@ -709,60 +700,44 @@ namespace Client
coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD;
++i;
}
isDataFixed[obj.PlayerId] = true;
}
}
foreach (var obj in listOfButcher)
{
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
if (!isDataFixed[obj.PlayerId])
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType));
int j = 0;
foreach (var skill in occupation1.ListOfIActiveSkill)
{
var iActiveSkill = SkillFactory.FindIActiveSkill(skill);
coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD;
++j;
}
isDataFixed[obj.PlayerId] = true;
}
}
if (StatusBarsOfSurvivor != null)

for (int i = 0; i < GameData.numOfStudent; i++)
{
for (int i = 0; i < GameData.numOfStudent; i++)
{
StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime);
}
StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime);
}
if (StatusBarsOfHunter != null)

StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);

for (int i = 0; i < GameData.numOfStudent; i++)
{
StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);
StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize);
}
if (StatusBarsOfCircumstance != null)
StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650);
// 完成窗口信息更新
StatusBarsOfHunter.SetFontSize(12 * unitFontsize);
StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize);
if (!isClientStocked)
{
unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50;
unitHeight = UpperLayerOfMap.ActualHeight / 50;
unitWidth = UpperLayerOfMap.ActualWidth / 50;
try
{
// if (log != null)
//{
// string temp = "";
// for (int i = 0; i < dataDict[GameObjType.Character].Count; i++)
// {
// temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n";
// }
// log.Content = temp;
// }
UpperLayerOfMap.Children.Clear();
// if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode)
//{
// UnderLayerOfMap.Children.Clear();
// throw new Exception("Client is unconnected.");
// }
// else
//{

foreach (var data in listOfAll)
{
StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode);
@@ -770,7 +745,6 @@ namespace Client
if (!hasDrawed && mapFlag)
{
DrawMap();
ZoomMapAtFirst();
}
foreach (var data in listOfHuman)
{
@@ -791,7 +765,7 @@ namespace Client
icon.Fill = Brushes.Gray;
TextBox num = new()
{
FontSize = 7 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 7 * unitFontsize,
Width = 2 * radiusTimes * unitWidth,
Height = 2 * radiusTimes * unitHeight,
Text = Convert.ToString(data.PlayerId),
@@ -943,7 +917,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator);
TextBox icon = new()
{
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = Convert.ToString(deg),
@@ -965,7 +939,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest);
TextBox icon = new()
{
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = Convert.ToString(deg),
@@ -987,7 +961,7 @@ namespace Client
int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway);
TextBox icon = new()
{
FontSize = 8 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 8 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = Convert.ToString(deg),
@@ -1009,7 +983,7 @@ namespace Client
{
TextBox icon = new()
{
FontSize = 9 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 9 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
HorizontalAlignment = HorizontalAlignment.Left,
@@ -1042,7 +1016,7 @@ namespace Client
isEmergencyOpened = true;
TextBox icon = new()
{
FontSize = 9 * UpperLayerOfMap.ActualHeight / 650,
FontSize = 9 * unitFontsize,
Width = unitWidth,
Height = unitHeight,
Text = Convert.ToString("🔓"),
@@ -1056,8 +1030,6 @@ namespace Client
UpperLayerOfMap.Children.Add(icon);
}
}
//}
ZoomMap();
}
catch (Exception exc)
{
@@ -1076,6 +1048,11 @@ namespace Client
{
if (!isPlaybackMode && !isSpectatorMode)
{
if (client is null)
{
return;
}

switch (e.Key)
{
case Key.W:
@@ -1257,6 +1234,10 @@ namespace Client
{
if (!isPlaybackMode && !isSpectatorMode)
{
if (client is null)
{
return;
}
if (humanOrButcher && human != null)
{
AttackMsg msgJ = new()
@@ -1407,7 +1388,7 @@ namespace Client
// 以下为Mainwindow自定义属性
private readonly DispatcherTimer timer; // 定时器
private long counter; // 预留的取时间变量
AvailableService.AvailableServiceClient client;
AvailableService.AvailableServiceClient? client;
AsyncServerStreamingCall<MessageToClient>? responseStream;
private StatusBarOfSurvivor[] StatusBarsOfSurvivor;
private StatusBarOfHunter StatusBarsOfHunter;
@@ -1440,7 +1421,6 @@ namespace Client
private MessageOfTricker? butcher = null;
private bool humanOrButcher;//true for human

private bool bonusflag;
private bool mapFlag = false;
private bool hasDrawed = false;
public int[,] defaultMap = new int[,] {
@@ -1502,7 +1482,8 @@ namespace Client
bool isSpectatorMode = false;
bool isEmergencyOpened = false;
bool isEmergencyDrawed = false;
bool isDataFixed = false;
bool[] isDataFixed = new bool[5] { false, false, false, false, false };
double unitFontsize = 10;
const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell;
private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 };


+ 4
- 4
logic/Client/StatusBarOfHunter.xaml.cs View File

@@ -82,19 +82,19 @@ namespace Client
state.Text = "Quit";
break;
case PlayerState.Treated:
state.Text = "Treated";
state.Text = "Encouraged";
break;
case PlayerState.Rescued:
state.Text = "Rescued";
state.Text = "Roused";
break;
case PlayerState.Stunned:
state.Text = "Stunned";
break;
case PlayerState.Treating:
state.Text = "Treating";
state.Text = "Encouraging";
break;
case PlayerState.Rescuing:
state.Text = "Rescuing";
state.Text = "Rousing";
break;
case PlayerState.Swinging:
state.Text = "Swinging";


+ 4
- 4
logic/Client/StatusBarOfSurvivor.xaml.cs View File

@@ -87,19 +87,19 @@ namespace Client
status.Text = "💀" + "\nQuit";
break;
case PlayerState.Treated:
status.Text = "♥: " + Convert.ToString(life) + "\nTreated, " + Convert.ToString(perLife) + "%";
status.Text = "♥: " + Convert.ToString(life) + "\nEncouraged, " + Convert.ToString(perLife) + "%";
break;
case PlayerState.Rescued:
status.Text = "💀: " + Convert.ToString(death) + "\nRescued, " + Convert.ToString(perDeath) + "%";
status.Text = "💀: " + Convert.ToString(death) + "\nRoused, " + Convert.ToString(perDeath) + "%";
break;
case PlayerState.Stunned:
status.Text = "♥: " + Convert.ToString(life) + "\nStunned, " + Convert.ToString(perLife) + "%";
break;
case PlayerState.Treating:
status.Text = "♥: " + Convert.ToString(life) + "\nTreating, " + Convert.ToString(perLife) + "%";
status.Text = "♥: " + Convert.ToString(life) + "\nEncouraging, " + Convert.ToString(perLife) + "%";
break;
case PlayerState.Rescuing:
status.Text = "♥: " + Convert.ToString(life) + "\nRescuing, " + Convert.ToString(perLife) + "%";
status.Text = "♥: " + Convert.ToString(life) + "\nRousing, " + Convert.ToString(perLife) + "%";
break;
case PlayerState.Swinging:
status.Text = "♥: " + Convert.ToString(life) + "\nSwinging, " + Convert.ToString(perLife) + "%";


+ 0
- 1
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -1,6 +1,5 @@
using Preparation.Interface;
using Preparation.Utility;
using System;

namespace GameClass.GameObj
{


+ 0
- 1
logic/GameClass/GameObj/Map/Chest.cs View File

@@ -23,7 +23,6 @@ namespace GameClass.GameObj
private int openStartTime = 0;
public int OpenStartTime => openStartTime;
private Character? whoOpen = null;

public Character? WhoOpen => whoOpen;
public void Open(int startTime, Character character)
{


+ 28
- 11
logic/GameClass/GameObj/Map/Map.cs View File

@@ -3,7 +3,6 @@ using System.Threading;
using Preparation.Interface;
using Preparation.Utility;
using System;
using GameClass.GameObj;

namespace GameClass.GameObj
{
@@ -13,29 +12,47 @@ namespace GameClass.GameObj
private readonly Dictionary<uint, XY> birthPointList; // 出生点列表
public Dictionary<uint, XY> BirthPointList => birthPointList;

private object lockForNum = new();
private readonly object lockForNum = new();
private void WhenStudentNumChange()
{
if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent)
{
Timer.IsGaming = false;
return;
}

if (GameData.numOfStudent - NumOfDeceasedStudent - NumOfEscapedStudent == 1)
if (GameData.numOfStudent - numOfDeceasedStudent - numOfEscapedStudent == 1)
{
GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock();
GameObjLockDict[GameObjType.Character].EnterReadLock();
try
{
foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit])
if (emergencyExit.CanOpen)
foreach (Character player in GameObjDict[GameObjType.Character])
if (player.PlayerState == PlayerStateType.Addicted)
{
emergencyExit.IsOpen = true;
Timer.IsGaming = false;
break;
}
}
finally
{
GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock();
GameObjLockDict[GameObjType.Character].ExitReadLock();
}
if (Timer.IsGaming)
{
GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock();
try
{
foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit])
if (emergencyExit.CanOpen)
{
emergencyExit.IsOpen = true;
break;
}
}
finally
{
GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock();
}
}
}
}
@@ -215,14 +232,14 @@ namespace GameClass.GameObj
break;
}
}
if (ToDel != null)
GameObjDict[gameObj.Type].Remove(ToDel);
}
finally
{
GameObjLockDict[gameObj.Type].ExitWriteLock();
}
if (ToDel == null) return false;
GameObjDict[gameObj.Type].Remove(ToDel);
return true;
return ToDel != null;
}
public bool RemoveJustFromMap(GameObj gameObj)
{


+ 0
- 1
logic/GameEngine/CollisionChecker.cs View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using Preparation.Interface;
using Preparation.Utility;


+ 8
- 4
logic/GameEngine/MoveEngine.cs View File

@@ -147,14 +147,16 @@ namespace GameEngine
isDestroyed = true;
return false;
case AfterCollision.MoveMax:
MoveMax(obj, res);
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
}
} while (flag);

deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res)));
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res)));

return true;
},
@@ -172,7 +174,8 @@ namespace GameEngine
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
obj.MovingSetPos(res, GetPlaceType(obj.Position + res));
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
obj.MovingSetPos(res, GetPlaceType(obj.Position + res));
}
else
{
@@ -186,7 +189,8 @@ namespace GameEngine
isDestroyed = true;
break;
case AfterCollision.MoveMax:
MoveMax(obj, res);
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;


+ 2
- 0
logic/Gaming/ActionManager.cs View File

@@ -37,6 +37,7 @@ namespace Gaming
{
if (moveTimeInMilliseconds < 5) return false;
if (!playerToMove.Commandable() || !TryToStop()) return false;
if (playerToMove.IsMoving) return false;
characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving);
moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection);
return true;
@@ -76,6 +77,7 @@ namespace Gaming
(
() =>
{
Thread.Sleep(GameData.frameDuration);
new FrameRateTaskExecutor<int>(
loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum,
loopToDo: () =>


+ 0
- 1
logic/Gaming/AttackManager.cs View File

@@ -6,7 +6,6 @@ using Preparation.Utility;
using GameEngine;
using Preparation.Interface;
using Timothy.FrameRateTask;
using System.Numerics;

namespace Gaming
{


+ 4
- 0
logic/Gaming/CharacterManager .cs View File

@@ -37,10 +37,14 @@ namespace Gaming
case PlayerStateType.Addicted:
if (value == PlayerStateType.Rescued)
player.ChangePlayerStateInOneThread(value, gameObj);
else
player.ChangePlayerState(value, gameObj);
break;
case PlayerStateType.Rescued:
if (value == PlayerStateType.Addicted)
player.ChangePlayerStateInOneThread(value, gameObj);
else
player.ChangePlayerState(value, gameObj);
break;
default:
player.ChangePlayerState(value, gameObj);


+ 1
- 0
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -96,6 +96,7 @@ namespace Gaming
IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible);
return ActiveSkillEffect(activeSkill, player, () =>
{
player.AddScore(GameData.ScoreBecomeInvisible);
player.AddInvisible(activeSkill.DurationTime);
Debugger.Output(player, "become invisible!");
},


+ 13
- 26
logic/Gaming/SkillManager/SkillManager.cs View File

@@ -17,44 +17,31 @@ namespace Gaming
switch (activeSkillType)
{
case ActiveSkillType.BecomeInvisible:
BecomeInvisible(character);
break;
return BecomeInvisible(character);
case ActiveSkillType.UseKnife:
UseKnife(character);
break;
return UseKnife(character);
case ActiveSkillType.Howl:
Howl(character);
break;
return Howl(character);
case ActiveSkillType.CanBeginToCharge:
CanBeginToCharge(character);
break;
return CanBeginToCharge(character);
case ActiveSkillType.Inspire:
Inspire(character);
break;
return Inspire(character);
case ActiveSkillType.Encourage:
Encourage(character);
break;
return Encourage(character);
case ActiveSkillType.Punish:
Punish(character);
break;
return Punish(character);
case ActiveSkillType.JumpyBomb:
JumpyBomb(character);
break;
return JumpyBomb(character);
case ActiveSkillType.WriteAnswers:
WriteAnswers(character);
break;
return WriteAnswers(character);
case ActiveSkillType.SummonGolem:
SummonGolem(character);
break;
return SummonGolem(character);
case ActiveSkillType.UseRobot:
UseRobot(character);
break;
return UseRobot(character);
case ActiveSkillType.Rouse:
Rouse(character);
break;
return Rouse(character);
case ActiveSkillType.ShowTime:
ShowTime(character);
break;
return ShowTime(character);
default:
return false;
}


+ 1
- 1
logic/Server/CopyInfo.cs View File

@@ -272,7 +272,7 @@ namespace Server
Y = chest.Position.y
}
};
int progress = (chest.OpenStartTime > 0) ? ((time - chest.OpenStartTime) * chest.WhoOpen!.SpeedOfOpenChest) : 0;
int progress = (chest.WhoOpen != null) ? ((time - chest.OpenStartTime) * chest.WhoOpen.SpeedOfOpenChest) : 0;
msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress;
return msg;
}


+ 8
- 13
logic/Server/GameServer.cs View File

@@ -1,22 +1,17 @@
using Grpc.Core;
using Protobuf;
using System.Threading;
using Timothy.FrameRateTask;
using System;
using System.Net.Http.Headers;
using GameClass.GameObj;
using Gaming;
using GameClass.GameObj;
using Preparation.Utility;
using Playback;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Preparation.Interface;
using Playback;
using Preparation.Utility;
using Protobuf;
using System.Collections.Concurrent;
using Timothy.FrameRateTask;


namespace Server
{
public partial class GameServer : AvailableService.AvailableServiceBase
partial class GameServer : ServerBase
{
private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
// private object semaDictLock = new();
@@ -86,7 +81,7 @@ namespace Server
}
}

public void WaitForEnd()
public override void WaitForEnd()
{
this.endGameSem.Wait();
mwr?.Dispose();
@@ -202,7 +197,7 @@ namespace Server
return false;
}

public int[] GetScore()
public override int[] GetScore()
{
int[] score = new int[2]; // 0代表Student,1代表Tricker
game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();


+ 70
- 22
logic/Server/PlaybackServer.cs View File

@@ -1,24 +1,40 @@
using Protobuf;
using Gaming;
using Grpc.Core;
using Playback;
using System;
using System.Threading;
using Protobuf;
using System.Collections.Concurrent;
using Timothy.FrameRateTask;
using Gaming;
using Grpc.Core;

namespace Server
{
class PlaybackServer : AvailableService.AvailableServiceBase
class PlaybackServer : ServerBase
{
protected readonly ArgumentOptions options;
private int[,] teamScore;
private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
private object semaDictLock = new();
private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
// private object semaDictLock = new();
private MessageToClient? currentGameInfo = new();
private MessageOfObj currentMapMsg = new();
private uint spectatorMinPlayerID = 2023;
private List<uint> spectatorList = new List<uint>();
// private List<uint> spectatorList = new List<uint>();
public int TeamCount => options.TeamCount;
private MessageWriter? mwr = null;
private object spetatorJoinLock = new();
protected object spectatorLock = new object();
protected bool isSpectatorJoin = false;
protected bool IsSpectatorJoin
{
get
{
lock (spectatorLock)
return isSpectatorJoin;
}

set
{
lock (spectatorLock)
isSpectatorJoin = value;
}
}
private bool IsGaming { get; set; }
private int[] finalScore;
public int[] FinalScore
@@ -28,6 +44,7 @@ namespace Server
return finalScore;
}
}
public override int[] GetScore() => FinalScore;
public PlaybackServer(ArgumentOptions options)
{
this.options = options;
@@ -38,18 +55,21 @@ namespace Server

public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context)
{
if (request.PlayerId >= spectatorMinPlayerID)
Console.WriteLine($"AddPlayer: {request.PlayerId}");
if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false)
{
// 观战模式
uint tp = (uint)request.PlayerId;
if (!spectatorList.Contains(tp))
lock (spetatorJoinLock) // 具体原因见另一个上锁的地方
{
spectatorList.Add(tp);
Console.WriteLine("A new spectator comes to watch this game.");
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
lock (semaDictLock)
if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1))))
{
semaDict.Add(request.PlayerId, temp);
Console.WriteLine("A new spectator comes to watch this game.");
IsSpectatorJoin = true;
}
else
{
Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}");
return;
}
}
do
@@ -63,15 +83,33 @@ namespace Server
//Console.WriteLine("Send!");
}
}
catch (InvalidOperationException)
{
if (semaDict.TryRemove(request.PlayerId, out var semas))
{
try
{
semas.Item1.Release();
semas.Item2.Release();
}
catch { }
Console.WriteLine($"The spectator {request.PlayerId} exited");
return;
}
}
catch (Exception)
{
//Console.WriteLine(ex);
// Console.WriteLine(ex);
}
finally
{
semaDict[request.PlayerId].Item2.Release();
try
{
semaDict[request.PlayerId].Item2.Release();
}
catch { }
}
} while (IsGaming == true);
} while (IsGaming);
return;
}
}
@@ -79,6 +117,16 @@ namespace Server
public void ReportGame(MessageToClient? msg)
{
currentGameInfo = msg;
if (currentGameInfo != null && currentGameInfo.GameState == GameState.GameStart)
{
currentMapMsg = currentGameInfo.ObjMessage[0];
}

if (currentGameInfo != null && IsSpectatorJoin)
{
currentGameInfo.ObjMessage.Add(currentMapMsg);
IsSpectatorJoin = false;
}

foreach (var kvp in semaDict)
{
@@ -91,7 +139,7 @@ namespace Server
}
}

public void WaitForGame()
public override void WaitForEnd()
{
try
{


+ 68
- 55
logic/Server/Program.cs View File

@@ -1,15 +1,55 @@
using Grpc.Core;
using CommandLine;
using Grpc.Core;
using Protobuf;
using System.Threading;
using Timothy.FrameRateTask;
using System;
using System.Net.Http.Headers;
using CommandLine;

namespace Server
{
public class Program
{
/// <summary>
/// Generated by http://www.network-science.de/ascii/ with font "standard"
/// </summary>
const string welcome =
@"

_____ _ _ _ _ _ ___ __
|_ _| | | | | | | / \ |_ _/ /_
| | | |_| | | | |/ _ \ | | '_ \
| | | _ | |_| / ___ \ | | (_) |
|_| |_| |_|\___/_/ \_\___\___/

____ _ _ ____ _ _ _
/ ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| |
| | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| |
| |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_|
\____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_)


";
// 特别说明:在 .NET 7 及以上,C# 支持新的多行字符串,允许多行前面缩进,因此可以不必再定格写字符串,
// 即升级 .NET 版本后可以改为如下的:
// const string welcome = """
//
// _____ _ _ _ _ _ ___ __
// |_ _| | | | | | | / \ |_ _/ /_
// | | | |_| | | | |/ _ \ | | '_ \
// | | | _ | |_| / ___ \ | | (_) |
// |_| |_| |_|\___/_/ \_\___\___/
//
// ____ _ _ ____ _ _ _
// / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| |
// | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| |
// | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_|
// \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_)
//
//
// """; // 将以此结束符为基准开始缩进(但 Python 没有这个 feature,差评 x)

static ServerBase CreateServer(ArgumentOptions options)
{
return options.Playback ? new PlaybackServer(options) : new GameServer(options);
}

static int Main(string[] args)
{
foreach (var arg in args)
@@ -26,63 +66,36 @@ namespace Server
return 1;
}

if (options.StartLockFile == DefaultArgumentOptions.FileName)
{
Console.WriteLine(welcome);
}
Console.WriteLine("Server begins to run: " + options.ServerPort.ToString());

if (options.Playback)
try
{
try
var server = CreateServer(options);
Grpc.Core.Server rpcServer = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
{
PlaybackServer? playbackServer = new(options);
Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
{
Services = { AvailableService.BindService(playbackServer) },
Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) }
};
server.Start();
Services = { AvailableService.BindService(server) },
Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) }
};
rpcServer.Start();

Console.WriteLine("Server begins to listen!");
playbackServer.WaitForGame();
Console.WriteLine("Server end!");
server.ShutdownAsync().Wait();
Console.WriteLine("Server begins to listen!");
server.WaitForEnd();
Console.WriteLine("Server end!");
rpcServer.ShutdownAsync().Wait();

Thread.Sleep(50);
Console.WriteLine("");
Console.WriteLine("=================== Final Score ====================");
Console.WriteLine($"Studnet: {playbackServer.FinalScore[0]}");
Console.WriteLine($"Tricker: {playbackServer.FinalScore[1]}");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Thread.Sleep(50);
Console.WriteLine("");
Console.WriteLine("=================== Final Score ====================");
Console.WriteLine($"Studnet: {server.GetScore()[0]}");
Console.WriteLine($"Tricker: {server.GetScore()[1]}");
}
else
catch (Exception ex)
{
try
{
GameServer? gameServer = new(options);
Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
{
Services = { AvailableService.BindService(gameServer) },
Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) }
};
server.Start();

Console.WriteLine("Server begins to listen!");
gameServer.WaitForEnd();
Console.WriteLine("Server end!");
server.ShutdownAsync().Wait();

Thread.Sleep(50);
Console.WriteLine("");
Console.WriteLine("=================== Final Score ====================");
Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}");
Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine(ex.ToString());
}
return 0;
}


+ 1
- 1
logic/Server/Properties/launchSettings.json View File

@@ -2,7 +2,7 @@
"profiles": {
"Server": {
"commandName": "Project",
"commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --resultFileName result --gameTimeInSecond 1 --fileName video"
"commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --resultFileName result --gameTimeInSecond 551 --fileName video"
}
}
}

+ 6
- 13
logic/Server/RpcServices.cs View File

@@ -1,20 +1,12 @@
using Grpc.Core;
using Protobuf;
using System.Threading;
using Timothy.FrameRateTask;
using System;
using System.Net.Http.Headers;
using GameClass.GameObj;
using Gaming;
using GameClass.GameObj;
using Grpc.Core;
using Preparation.Utility;
using Playback;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Preparation.Interface;
using Protobuf;

namespace Server
{
public partial class GameServer : AvailableService.AvailableServiceBase
partial class GameServer : ServerBase
{
private int playerCountNow = 0;
protected object spectatorLock = new object();
@@ -95,6 +87,7 @@ namespace Server
}
catch { }
Console.WriteLine($"The spectator {request.PlayerId} exited");
return;
}
}
catch (Exception)
@@ -136,7 +129,7 @@ namespace Server
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
bool start = false;
Console.WriteLine($"Id: {request.PlayerId} joins.");
// lock (semaDictLock)
lock (spetatorJoinLock) // 为了保证绝对安全,还是加上这个锁吧
{
if (semaDict.TryAdd(request.PlayerId, temp))
{


+ 14
- 0
logic/Server/ServerBase.cs View File

@@ -0,0 +1,14 @@
using Grpc.Core;
using Playback;
using Protobuf;
using System.Collections.Concurrent;
using Timothy.FrameRateTask;

namespace Server
{
abstract class ServerBase : AvailableService.AvailableServiceBase
{
public abstract void WaitForEnd();
public abstract int[] GetScore();
}
}

+ 1
- 5
logic/cmd/PlaybackServer.cmd View File

@@ -1,6 +1,2 @@
@echo off
::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true

ping -n 2 127.0.0.1 > NUL

start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030
start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0

+ 1
- 1
logic/cmd/gameServerAndClient.cmd View File

@@ -1,6 +1,6 @@
@echo off

start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test
start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 0 --trickerCount 1 --gameTimeInSecond 600 --fileName test

ping -n 2 127.0.0.1 > NUL


BIN
resource/eesast_software_trans_enlarged.png View File

Before After
Width: 2048  |  Height: 2048  |  Size: 115 kB

BIN
resource/eesast_software_trans_enlarged_256x256.png View File

Before After
Width: 256  |  Height: 256  |  Size: 14 kB

BIN
resource/vector.png View File

Before After
Width: 929  |  Height: 731  |  Size: 37 kB

BIN
resource/wrongType.png View File

Before After
Width: 856  |  Height: 420  |  Size: 28 kB

Loading…
Cancel
Save