| @@ -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); | |||
| } | |||
| @@ -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") | |||
| @@ -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,2 +1,3 @@ | |||
| grpcio==1.52.0 | |||
| grpcio-tools==1.52.0 | |||
| numpy | |||
| @@ -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; | |||
| } | |||
| ``` | |||
| @@ -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 | |||
| @@ -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`:查询特定位置教室作业完成进度 | |||
| @@ -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`:查询特定位置教室作业完成进度 | |||
| @@ -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的小炸弹有碰撞体积 | |||
| - 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 | |||
| ### 职业 | |||
| - 学生职业可以重复选择 | |||
| @@ -22,6 +22,11 @@ Q:卡死在第一帧不动 | |||
| A:大概率是你的代码死循环了 | |||
| Q: | |||
|  | |||
| A:命令行参数的type设置有误 | |||
| ## C++ | |||
| Q:显示API项目已卸载 | |||
| @@ -56,7 +61,11 @@ A: | |||
| 2. 不要点重新生成,要点生成 | |||
| 3. 开启下图选项 | |||
|  | |||
| Q:这是什么错误啊 | |||
|  | |||
| 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()修改 等 | |||
| @@ -2,9 +2,9 @@ | |||
| [toc] | |||
| ## Visual Studio使用说明 | |||
| ## Visual Studio 使用说明 | |||
| 比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||
| 比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||
| ### 生成模式的设置 | |||
| @@ -12,7 +12,7 @@ | |||
|  | |||
| 可以更改生成模式为`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)...)); | |||
| } | |||
| } | |||
| ``` | |||
| @@ -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; | |||
| @@ -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" /> | |||
| @@ -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> | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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) | |||
| { | |||
| @@ -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"> | |||
| @@ -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"/> | |||
| @@ -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(); | |||
| @@ -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" /> | |||
| @@ -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> | |||
| @@ -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 }; | |||
| @@ -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"; | |||
| @@ -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) + "%"; | |||
| @@ -1,6 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -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) | |||
| { | |||
| @@ -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) | |||
| { | |||
| @@ -1,6 +1,5 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| @@ -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; | |||
| @@ -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: () => | |||
| @@ -6,7 +6,6 @@ using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| namespace Gaming | |||
| { | |||
| @@ -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); | |||
| @@ -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!"); | |||
| }, | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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(); | |||
| @@ -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 | |||
| { | |||
| @@ -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; | |||
| } | |||
| @@ -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" | |||
| } | |||
| } | |||
| } | |||
| @@ -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)) | |||
| { | |||
| @@ -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,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,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 | |||