diff --git a/CAPI/cpp/API/src/main.cpp b/CAPI/cpp/API/src/main.cpp index 611e347..459679d 100644 --- a/CAPI/cpp/API/src/main.cpp +++ b/CAPI/cpp/API/src/main.cpp @@ -3,6 +3,7 @@ #include "structures.h" #include #include +#include #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); } diff --git a/CAPI/python/PyAPI/logic.py b/CAPI/python/PyAPI/logic.py index dd8c4b3..030dd4f 100644 --- a/CAPI/python/PyAPI/logic.py +++ b/CAPI/python/PyAPI/logic.py @@ -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") diff --git a/CAPI/python/PyAPI/main.py b/CAPI/python/PyAPI/main.py index d915634..553e7e0 100644 --- a/CAPI/python/PyAPI/main.py +++ b/CAPI/python/PyAPI/main.py @@ -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) diff --git a/CAPI/python/requirements.txt b/CAPI/python/requirements.txt index 1e31ecf..3f4ef6c 100644 --- a/CAPI/python/requirements.txt +++ b/CAPI/python/requirements.txt @@ -1,2 +1,3 @@ grpcio==1.52.0 grpcio-tools==1.52.0 +numpy diff --git a/dependency/algorithm/README.md b/dependency/algorithm/README.md index fe9624e..1ea7788 100644 --- a/dependency/algorithm/README.md +++ b/dependency/algorithm/README.md @@ -247,6 +247,8 @@ int main() ## THUAI6 +### high-ladder + 因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 ```c++ @@ -330,3 +332,126 @@ mypair cal(mypair orgScore, mypair 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 +#include +#include +#include +using namespace std; + +template +using mypair = pair; +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 cal(mypair orgScore, mypair 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 resScore; + + // 先处理平局的情况 + if (delta == 0) + { + addScore = TieScore(competitionScore.first); + resScore = mypair(orgScore.first + addScore, orgScore.second + addScore); + } + + // 再处理有胜负的情况 + else + { + addScore = WinScore(delta, competitionScore.first); + resScore = mypair(orgScore.first + addScore, orgScore.second); + } + + // 如果换过,再换回来 + if (reverse) + { + swap(resScore.first, resScore.second); + } + + return resScore; +} + +void Print(mypair 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(x, y); + std::cout << "game score of team 1 and 2: " << std::endl; + std::cin >> x >> y; + auto sco = mypair(x, y); + Print(cal(ori, sco)); + ++i; + } + return 0; +} +``` + diff --git a/dependency/shell/compile.sh b/dependency/shell/compile.sh index 4e7b6f3..382cd85 100644 --- a/dependency/shell/compile.sh +++ b/dependency/shell/compile.sh @@ -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 diff --git a/docs/CAPI接口(cpp).md b/docs/CAPI接口(cpp).md index b427644..4f31cca 100644 --- a/docs/CAPI接口(cpp).md +++ b/docs/CAPI接口(cpp).md @@ -43,7 +43,7 @@ ### 信息获取 #### 队内信息 - - `std::future SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 + - `std::future SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 - `std::pair 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`:查询特定位置教室作业完成进度 diff --git a/docs/CAPI接口(python).md b/docs/CAPI接口(python).md index 500da59..851e055 100644 --- a/docs/CAPI接口(python).md +++ b/docs/CAPI接口(python).md @@ -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`:查询特定位置教室作业完成进度 diff --git a/docs/GameRules.md b/docs/GameRules.md index c88870f..ba03c8b 100644 --- a/docs/GameRules.md +++ b/docs/GameRules.md @@ -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的小炸弹有碰撞体积 +- 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 ### 职业 - 学生职业可以重复选择 \ No newline at end of file diff --git a/docs/QandA.md b/docs/QandA.md index 6995cec..ad8ac0e 100644 --- a/docs/QandA.md +++ b/docs/QandA.md @@ -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信息;提供不同格式的信息传递;增加职业;优化游戏结束条件 \ No newline at end of file +A:可能的修改:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM;HaveView()修改 等 \ No newline at end of file diff --git a/docs/Tool_tutorial.md b/docs/Tool_tutorial.md index b90e7d6..995689a 100644 --- a/docs/Tool_tutorial.md +++ b/docs/Tool_tutorial.md @@ -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 ` @@ -127,7 +127,7 @@ int main() -### `auto`类型推导 +### `auto` 类型推导 @@ -147,7 +147,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 -#### std::vector +#### `std::vector` 头文件:`#include `,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。 @@ -155,7 +155,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 ```cpp std::vector 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 `。用于开启新的线程。示例代码: @@ -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 + + void IntArrayDeleter(int* p) { delete[] p; } + + int main() + { + std::shared_ptr sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 + sp.get()[0] = 0; // 访问第 0 个元素 + // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 + return 0; + } + + // 或者利用 lambda 表达式:std::shared_ptr sp(new int[10], [](int* p) { delete[] p; }); + ``` + + + C++17 起 + + ```cpp + std::shared_ptr sp(new int[10]); + sp[0] = 0; // 访问第 0 个元素 + ``` - ```cpp - #include - - void IntArrayDeleter(int* p) { delete[] p; } - - int main() - { - std::shared_ptr sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 - // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 - return 0; - } - - // 或者利用 lambda 表达式:std::shared_ptr sp(new int[10], [](int* p) { delete[] p; }); - ``` + + C++20 起 + + ```cpp + auto sp = std::make_shared(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 sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 +这是因为,只有复制构造函数里面才有使引用计数加一的操作。即当我们写 `std::shared_ptr 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(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 + std::unique_ptr make_unique( Args&&... args ) + { + return std::unique_ptr(new T( std::forward(args)...)); + } +} +``` diff --git a/installer/Installer/Common.cs b/installer/Installer/Common.cs index 0f3697d..d38059f 100644 --- a/installer/Installer/Common.cs +++ b/installer/Installer/Common.cs @@ -10,7 +10,7 @@ namespace starter.viewmodel.common { public abstract class NotificationObject : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; ///< summary> /// announce notification /// @@ -25,21 +25,21 @@ namespace starter.viewmodel.common /// public class BaseCommand : ICommand { - private Func _canExecute; - private Action _execute; + private Func? _canExecute; + private Action _execute; - public BaseCommand(Func canExecute, Action execute) + public BaseCommand(Func? canExecute, Action execute) { _canExecute = canExecute; _execute = execute; } - public BaseCommand(Action execute) : + public BaseCommand(Action 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; diff --git a/installer/Installer/Installer.csproj b/installer/Installer/Installer.csproj index 53d5639..0d16d13 100644 --- a/installer/Installer/Installer.csproj +++ b/installer/Installer/Installer.csproj @@ -6,8 +6,21 @@ enable true true + eesast_software_trans_enlarged.ico + eesast_software_trans.png + + + + + + + True + \ + + + diff --git a/installer/Installer/MainWindow.xaml b/installer/Installer/MainWindow.xaml index c29b75d..85f52ab 100644 --- a/installer/Installer/MainWindow.xaml +++ b/installer/Installer/MainWindow.xaml @@ -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" + > diff --git a/installer/Installer/Model.cs b/installer/Installer/Model.cs index f9bea79..a95f17b 100644 --- a/installer/Installer/Model.cs +++ b/installer/Installer/Model.cs @@ -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, + }; } /// /// 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(); + Dictionary? dict; using (StreamReader r = new StreamReader(Data.path)) { string json = r.ReadToEnd(); @@ -411,7 +409,7 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.TryDeserializeJson>(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 dict = new Dictionary(); + Dictionary? 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>(json); + dict = Utils.TryDeserializeJson>(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 dict = new Dictionary(); + Dictionary? 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>(json); + dict = Utils.TryDeserializeJson>(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 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 jsonDict = JsonConvert.DeserializeObject>(json); + var jsonDict = Utils.DeserializeJson1>(json); string updatingFolder = ""; switch (OS) { @@ -815,7 +815,7 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - var dict = JsonConvert.DeserializeObject>(json); + var dict = Utils.TryDeserializeJson>(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 jsonDict = JsonConvert.DeserializeObject>(json); + // var jsonDict = Utils.DeserializeJson>(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 dict = new Dictionary(); + Dictionary? 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>(json2); + dict = Utils.TryDeserializeJson>(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 jsonDict = JsonConvert.DeserializeObject>(json); + Dictionary jsonDict = Utils.DeserializeJson1>(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 dict = new Dictionary(); + Dictionary? 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>(json2); + dict = Utils.TryDeserializeJson>(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 jsonDict = JsonConvert.DeserializeObject>(json); + var jsonDict = Utils.TryDeserializeJson>(json); string md5 = ""; List awaitUpdate = new List(); 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 jsonList; + List? jsonList; if (json != null) - jsonList = JsonConvert.DeserializeObject>(json); + jsonList = Utils.TryDeserializeJson>(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>(await response.Content.ReadAsStringAsync()); + var info = Utils.DeserializeJson1>(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>(await response.Content.ReadAsStringAsync()); - string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) - string region = "ap-beijing"; // 设置一个默认的存储桶地域 + var res = Utils.DeserializeJson1>(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>(json); + dict = Utils.DeserializeJson1>(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 dict = new Dictionary(); - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(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>(json); + dict = Utils.DeserializeJson1>(json); fs.Close(); sr.Close(); return dict[key]; @@ -1691,7 +1693,7 @@ namespace WebConnect { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(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>(info)["data"]; - var s2 = JsonConvert.DeserializeObject>>(s1.ToString())["contest_team_member"]; - var sres = JsonConvert.DeserializeObject>(s2[0].ToString())["team_id"]; + var s1 = Utils.DeserializeJson1>(info)["data"]; + var s2 = Utils.DeserializeJson1>>(s1.ToString() ?? "")["contest_team_member"]; + var sres = Utils.DeserializeJson1>(s2[0].ToString() ?? "")["team_id"]; return sres; } async public Task GetUserId(string learnNumber) @@ -1773,4 +1775,20 @@ namespace WebConnect public string Token { get; set; } = ""; } + internal static class Utils + { + public static T DeserializeJson1(string json) + where T : notnull + { + return JsonConvert.DeserializeObject(json) + ?? throw new Exception("Failed to deserialize json."); + } + + public static T? TryDeserializeJson(string json) + where T : notnull + { + return JsonConvert.DeserializeObject(json); + } + } + } diff --git a/installer/Installer/ViewModel.cs b/installer/Installer/ViewModel.cs index ca84add..742fbb1 100644 --- a/installer/Installer/ViewModel.cs +++ b/installer/Installer/ViewModel.cs @@ -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(o => + clickBrowseCommand = new BaseCommand(new Action(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(o => + clickConfirmCommand = new BaseCommand(new Action(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(o => + clickUpdateCommand = new BaseCommand(new Action(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(o => + clickMoveCommand = new BaseCommand(new Action(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(o => + clickUninstCommand = new BaseCommand(new Action(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(async o => + clickLoginCommand = new BaseCommand(new Action(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(o => + clickLaunchCommand = new BaseCommand(new Action(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(o => + clickEditCommand = new BaseCommand(new Action(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(o => + clickBackCommand = new BaseCommand(new Action(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(async o => + clickUploadCommand = new BaseCommand(new Action(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(o => + clickAboutUploadCommand = new BaseCommand(new Action(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(o => + clickExitCommand = new BaseCommand(new Action(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(o => + clickShiftLanguageCommand = new BaseCommand(new Action(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(o => + clickReadCommand = new BaseCommand(new Action(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(o => + clickSwitchOSCommand = new BaseCommand(new Action(o => { switch (obj.usingOS) { diff --git a/installer/Installer/eesast_software_trans_enlarged.ico b/installer/Installer/eesast_software_trans_enlarged.ico new file mode 100644 index 0000000..cbafc97 Binary files /dev/null and b/installer/Installer/eesast_software_trans_enlarged.ico differ diff --git a/installer/InstallerUpdater/InstallerUpdater.csproj b/installer/InstallerUpdater/InstallerUpdater.csproj index 0dd4fb5..3be5681 100644 --- a/installer/InstallerUpdater/InstallerUpdater.csproj +++ b/installer/InstallerUpdater/InstallerUpdater.csproj @@ -5,8 +5,16 @@ net6.0-windows enable true + eesast_software_trans.png + + + True + \ + + + diff --git a/installer/InstallerUpdater/MainWindow.xaml b/installer/InstallerUpdater/MainWindow.xaml index 271161a..5a3fdf1 100644 --- a/installer/InstallerUpdater/MainWindow.xaml +++ b/installer/InstallerUpdater/MainWindow.xaml @@ -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" + > diff --git a/installer/InstallerUpdater/Program.cs b/installer/InstallerUpdater/Program.cs index 9a9fced..17e620e 100644 --- a/installer/InstallerUpdater/Program.cs +++ b/installer/InstallerUpdater/Program.cs @@ -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 jsonList = JsonConvert.DeserializeObject>(json); + List jsonList = JsonConvert.DeserializeObject>(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 ls = new List(); - ls = JsonConvert.DeserializeObject>(json); + ls = JsonConvert.DeserializeObject>(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 test = request.GetRequestHeaders(); diff --git a/logic/Client/Client.csproj b/logic/Client/Client.csproj index abcb9a6..ffa5629 100644 --- a/logic/Client/Client.csproj +++ b/logic/Client/Client.csproj @@ -12,10 +12,6 @@ - - - - diff --git a/logic/Client/EESASTLogo.png b/logic/Client/EESASTLogo.png new file mode 100644 index 0000000..efba5b5 Binary files /dev/null and b/logic/Client/EESASTLogo.png differ diff --git a/logic/Client/Logo.png b/logic/Client/Logo.png index efba5b5..6d7d511 100644 Binary files a/logic/Client/Logo.png and b/logic/Client/Logo.png differ diff --git a/logic/Client/MainWindow.xaml b/logic/Client/MainWindow.xaml index beacab5..7046f39 100644 --- a/logic/Client/MainWindow.xaml +++ b/logic/Client/MainWindow.xaml @@ -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"> @@ -50,7 +51,7 @@ - + diff --git a/logic/Client/MainWindow.xaml.cs b/logic/Client/MainWindow.xaml.cs index 4ab6665..d8dd6b5 100644 --- a/logic/Client/MainWindow.xaml.cs +++ b/logic/Client/MainWindow.xaml.cs @@ -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(); listOfHiddenGate = new List(); 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? 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 }; diff --git a/logic/Client/StatusBarOfHunter.xaml.cs b/logic/Client/StatusBarOfHunter.xaml.cs index 358db47..d8ab43e 100644 --- a/logic/Client/StatusBarOfHunter.xaml.cs +++ b/logic/Client/StatusBarOfHunter.xaml.cs @@ -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"; diff --git a/logic/Client/StatusBarOfSurvivor.xaml.cs b/logic/Client/StatusBarOfSurvivor.xaml.cs index 75011d8..1830c7e 100644 --- a/logic/Client/StatusBarOfSurvivor.xaml.cs +++ b/logic/Client/StatusBarOfSurvivor.xaml.cs @@ -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) + "%"; diff --git a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs index 672e40f..2b3df5c 100644 --- a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs +++ b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs @@ -1,6 +1,5 @@ using Preparation.Interface; using Preparation.Utility; -using System; namespace GameClass.GameObj { diff --git a/logic/GameClass/GameObj/Map/Chest.cs b/logic/GameClass/GameObj/Map/Chest.cs index 2d71764..23d9571 100644 --- a/logic/GameClass/GameObj/Map/Chest.cs +++ b/logic/GameClass/GameObj/Map/Chest.cs @@ -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) { diff --git a/logic/GameClass/GameObj/Map/Map.cs b/logic/GameClass/GameObj/Map/Map.cs index 427c76d..3cfbf49 100644 --- a/logic/GameClass/GameObj/Map/Map.cs +++ b/logic/GameClass/GameObj/Map/Map.cs @@ -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 birthPointList; // 出生点列表 public Dictionary 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) { diff --git a/logic/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs index 046d5bc..1a7a2e3 100644 --- a/logic/GameEngine/CollisionChecker.cs +++ b/logic/GameEngine/CollisionChecker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Numerics; using System.Threading; using Preparation.Interface; using Preparation.Utility; diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs index 978f6c5..aa13c68 100644 --- a/logic/GameEngine/MoveEngine.cs +++ b/logic/GameEngine/MoveEngine.cs @@ -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; diff --git a/logic/Gaming/ActionManager.cs b/logic/Gaming/ActionManager.cs index f39cfcd..842e625 100644 --- a/logic/Gaming/ActionManager.cs +++ b/logic/Gaming/ActionManager.cs @@ -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( loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, loopToDo: () => diff --git a/logic/Gaming/AttackManager.cs b/logic/Gaming/AttackManager.cs index e86e429..0db4748 100644 --- a/logic/Gaming/AttackManager.cs +++ b/logic/Gaming/AttackManager.cs @@ -6,7 +6,6 @@ using Preparation.Utility; using GameEngine; using Preparation.Interface; using Timothy.FrameRateTask; -using System.Numerics; namespace Gaming { diff --git a/logic/Gaming/CharacterManager .cs b/logic/Gaming/CharacterManager .cs index b0e5c7b..c7aefaf 100644 --- a/logic/Gaming/CharacterManager .cs +++ b/logic/Gaming/CharacterManager .cs @@ -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); diff --git a/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs b/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs index 2587cfe..a6d0a86 100644 --- a/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs +++ b/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs @@ -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!"); }, diff --git a/logic/Gaming/SkillManager/SkillManager.cs b/logic/Gaming/SkillManager/SkillManager.cs index 9cf6f09..59fc7e3 100644 --- a/logic/Gaming/SkillManager/SkillManager.cs +++ b/logic/Gaming/SkillManager/SkillManager.cs @@ -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; } diff --git a/logic/Server/CopyInfo.cs b/logic/Server/CopyInfo.cs index 42a6b1b..d6a1423 100644 --- a/logic/Server/CopyInfo.cs +++ b/logic/Server/CopyInfo.cs @@ -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; } diff --git a/logic/Server/GameServer.cs b/logic/Server/GameServer.cs index 5d2724c..6573e7b 100644 --- a/logic/Server/GameServer.cs +++ b/logic/Server/GameServer.cs @@ -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 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(); diff --git a/logic/Server/PlaybackServer.cs b/logic/Server/PlaybackServer.cs index 1506b97..dc347ce 100644 --- a/logic/Server/PlaybackServer.cs +++ b/logic/Server/PlaybackServer.cs @@ -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 semaDict = new(); - private object semaDictLock = new(); + private ConcurrentDictionary semaDict = new(); + // private object semaDictLock = new(); private MessageToClient? currentGameInfo = new(); + private MessageOfObj currentMapMsg = new(); private uint spectatorMinPlayerID = 2023; - private List spectatorList = new List(); + // private List spectatorList = new List(); 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 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 { diff --git a/logic/Server/Program.cs b/logic/Server/Program.cs index c934cd1..85bfe48 100644 --- a/logic/Server/Program.cs +++ b/logic/Server/Program.cs @@ -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 { + /// + /// Generated by http://www.network-science.de/ascii/ with font "standard" + /// + 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; } diff --git a/logic/Server/Properties/launchSettings.json b/logic/Server/Properties/launchSettings.json index 6d2873f..308724a 100644 --- a/logic/Server/Properties/launchSettings.json +++ b/logic/Server/Properties/launchSettings.json @@ -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" } } } \ No newline at end of file diff --git a/logic/Server/RpcServices.cs b/logic/Server/RpcServices.cs index cac7984..2db93b2 100644 --- a/logic/Server/RpcServices.cs +++ b/logic/Server/RpcServices.cs @@ -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)) { diff --git a/logic/Server/ServerBase.cs b/logic/Server/ServerBase.cs new file mode 100644 index 0000000..dbb578c --- /dev/null +++ b/logic/Server/ServerBase.cs @@ -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(); + } +} diff --git a/logic/cmd/PlaybackServer.cmd b/logic/cmd/PlaybackServer.cmd index c3dd0bb..916c9ea 100644 --- a/logic/cmd/PlaybackServer.cmd +++ b/logic/cmd/PlaybackServer.cmd @@ -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 \ No newline at end of file +start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0 diff --git a/logic/cmd/gameServerAndClient.cmd b/logic/cmd/gameServerAndClient.cmd index 71f90c7..125b391 100644 --- a/logic/cmd/gameServerAndClient.cmd +++ b/logic/cmd/gameServerAndClient.cmd @@ -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 diff --git a/resource/eesast_software_trans_enlarged.png b/resource/eesast_software_trans_enlarged.png new file mode 100644 index 0000000..088700c Binary files /dev/null and b/resource/eesast_software_trans_enlarged.png differ diff --git a/resource/eesast_software_trans_enlarged_256x256.png b/resource/eesast_software_trans_enlarged_256x256.png new file mode 100644 index 0000000..17f18f4 Binary files /dev/null and b/resource/eesast_software_trans_enlarged_256x256.png differ diff --git a/resource/vector.png b/resource/vector.png new file mode 100644 index 0000000..72d1987 Binary files /dev/null and b/resource/vector.png differ diff --git a/resource/wrongType.png b/resource/wrongType.png new file mode 100644 index 0000000..37e2b3e Binary files /dev/null and b/resource/wrongType.png differ