| @@ -4,13 +4,25 @@ | |||||
| ## Visual Studio使用说明 | ## Visual Studio使用说明 | ||||
| 选手开始编写代码之前,要先编译好服务器(logic)。具体方法是进入logic文件夹,分别编译`logic.sln`和client文件夹里的`client.sln`,然后就可以开始编写自己的代码。需要注意,选手需要分别在Debug和Release模式下编译,然后才可以分别运行`gameServer_dbg.cmd`和`gameServer_rls.cmd` | |||||
| 比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||||
| ## Python使用说明 | |||||
| ### 生成模式的设置 | |||||
| 选手编写Python代码之前需要安装必要的Python包,具体方法为:Windows下运行(需要在有Python环境的情况下运行)`generate_proto.cmd`,Linux下运行`generate_proto.sh`,然后可以开始进行代码编写。 | |||||
| 菜单栏下方一行 | |||||
| ## cmd脚本的参数修改 | |||||
|  | |||||
| 可以更改生成模式为`Debug`或`Release` | |||||
| ### 命令行参数的设置 | |||||
| 左上方菜单栏 `调试->调试属性` | |||||
|  | |||||
| 在命令参数一栏中加入命令行参数进行调试 | |||||
| ### cmd脚本的参数修改 | |||||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | ||||
| @@ -234,7 +246,7 @@ for (auto itr = begin(arr); itr != end(arr); ++itr) | |||||
| ## Python接口必看 | ## Python接口必看 | ||||
| 比赛**只保证!!**支持Python3.9,不保证支持其他版本 | |||||
| 比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | 比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | ||||
| @@ -695,7 +707,7 @@ int main() | |||||
| ##### 概览 | ##### 概览 | ||||
| `shared_ptr` 可以说是最常用的智能指针了。它的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。 | |||||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||||
| 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | ||||
| @@ -923,7 +935,7 @@ else | |||||
| #### `std::unique_ptr` | #### `std::unique_ptr` | ||||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有。它部分涉及到 `xvalue` 、右值引用与移动语义的问题,在此不做过多展开。 | |||||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开。 | |||||
| @@ -931,5 +943,4 @@ else | |||||
| + [cppreference_shared_ptr](https://zh.cppreference.com/w/cpp/memory/shared_ptr) | + [cppreference_shared_ptr](https://zh.cppreference.com/w/cpp/memory/shared_ptr) | ||||
| + [cppreference_weak_ptr](https://zh.cppreference.com/w/cpp/memory/weak_ptr) | + [cppreference_weak_ptr](https://zh.cppreference.com/w/cpp/memory/weak_ptr) | ||||
| + [cppreference_unique_ptr](https://zh.cppreference.com/w/cpp/memory/unique_ptr) | |||||
| + [cppreference_unique_ptr](https://zh.cppreference.com/w/cpp/memory/unique_ptr) | |||||
| @@ -0,0 +1,13 @@ | |||||
| @echo off | |||||
| python -m pip install -r .\CAPI\python\requirements.txt | |||||
| MKDIR .\CAPI\python\proto | |||||
| echo generating python grpc files... | |||||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto MessageType.proto | |||||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto Message2Clients.proto | |||||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto Message2Server.proto | |||||
| python -m grpc_tools.protoc -I.\CAPI\proto\ --python_out=.\CAPI\python\proto --pyi_out=.\CAPI\python\proto --grpc_python_out=.\CAPI\python\proto Services.proto | |||||
| pause | |||||
| @@ -0,0 +1,7 @@ | |||||
| @echo off | |||||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 0 -o -d | |||||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 1 -o -d | |||||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 2 -o -d | |||||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 3 -o -d | |||||
| start .\CAPI\cpp\x64\Debug\API.exe -I 127.0.0.1 -P 8888 -p 4 -o -d | |||||
| @@ -0,0 +1,6 @@ | |||||
| @echo off | |||||
| :: 添加 --cl 参数,程序运行时将自动识别命令行参数,并自动连接server | |||||
| start cmd /k win64\Client.exe --port 8888 --characterID 114514 --type 0 --occupation 1 --ip 127.0.0.1 --cl | |||||
| :: characterID > 2023时是观战Client,否则是正常Client | |||||
| @@ -0,0 +1,7 @@ | |||||
| @echo off | |||||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 0 -d -o | |||||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 1 -d -o | |||||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 2 -d -o | |||||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 3 -d -o | |||||
| start python .\CAPI\python\PyAPI\main.py -I 127.0.0.1 -P 8888 -p 4 -d -o | |||||
| @@ -0,0 +1,5 @@ | |||||
| @echo off | |||||
| .\win64\Server.exe --port 8888 --studentCount 1 --trickerCount 1 --gameTimeInSecond 600 --fileName video | |||||
| pause | |||||
| @@ -0,0 +1,5 @@ | |||||
| @echo off | |||||
| .\win64\Debug\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||||
| pause | |||||
| @@ -13,17 +13,21 @@ public: | |||||
| virtual void play(ITrickerAPI& api) = 0; | virtual void play(ITrickerAPI& api) = 0; | ||||
| }; | }; | ||||
| using CreateAIFunc = std::unique_ptr<IAI> (*)(); | |||||
| using CreateAIFunc = std::unique_ptr<IAI> (*)(int64_t playerID); | |||||
| class AI : public IAI | class AI : public IAI | ||||
| { | { | ||||
| public: | public: | ||||
| AI() : | |||||
| IAI() | |||||
| AI(int64_t pID) : | |||||
| IAI(), | |||||
| playerID(pID) | |||||
| { | { | ||||
| } | } | ||||
| void play(IStudentAPI& api) override; | void play(IStudentAPI& api) override; | ||||
| void play(ITrickerAPI& api) override; | void play(ITrickerAPI& api) override; | ||||
| private: | |||||
| int64_t playerID; | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -8,6 +8,7 @@ | |||||
| namespace Constants | namespace Constants | ||||
| { | { | ||||
| SCCI int frameDuration = 50; // 每帧毫秒数 | |||||
| // 地图相关 | // 地图相关 | ||||
| SCCI int numOfGridPerCell = 1000; // 单位坐标数 | SCCI int numOfGridPerCell = 1000; // 单位坐标数 | ||||
| SCCI int rows = 50; // 地图行数 | SCCI int rows = 50; // 地图行数 | ||||
| @@ -28,8 +29,8 @@ namespace Constants | |||||
| SCCI int basicEncourageSpeed = 100; | SCCI int basicEncourageSpeed = 100; | ||||
| SCCI int basicFixSpeed = 123; | SCCI int basicFixSpeed = 123; | ||||
| SCCI int basicSpeedOfOpeningOrLocking = 4000; | SCCI int basicSpeedOfOpeningOrLocking = 4000; | ||||
| SCCI int basicStudentSpeedOfClimbingThroughWindows = 611; | |||||
| SCCI int basicTrickerSpeedOfClimbingThroughWindows = 1270; | |||||
| SCCI int basicStudentSpeedOfClimbingThroughWindows = 1222; | |||||
| SCCI int basicTrickerSpeedOfClimbingThroughWindows = 2540; | |||||
| SCCI int basicSpeedOfOpenChest = 1000; | SCCI int basicSpeedOfOpenChest = 1000; | ||||
| SCCI int basicHp = 3000000; | SCCI int basicHp = 3000000; | ||||
| @@ -37,8 +38,8 @@ namespace Constants | |||||
| SCCI int basicEncouragementDegree = 1500000; | SCCI int basicEncouragementDegree = 1500000; | ||||
| SCCI int basicTimeOfRouse = 1000; | SCCI int basicTimeOfRouse = 1000; | ||||
| SCCI int basicStudentSpeed = 1270; | |||||
| SCCI int basicTrickerSpeed = 1504; | |||||
| SCCI int basicStudentSpeed = 3000; | |||||
| SCCI int basicTrickerSpeed = 3600; | |||||
| SCCI double basicConcealment = 1; | SCCI double basicConcealment = 1; | ||||
| SCCI int basicStudentAlertnessRadius = 15 * numOfGridPerCell; | SCCI int basicStudentAlertnessRadius = 15 * numOfGridPerCell; | ||||
| @@ -58,10 +59,10 @@ namespace Constants | |||||
| SCCI int basicRecoveryFromHit = 3700; // 基本命中攻击恢复时长 | SCCI int basicRecoveryFromHit = 3700; // 基本命中攻击恢复时长 | ||||
| SCCI int basicStunnedTimeOfStudent = 4300; | SCCI int basicStunnedTimeOfStudent = 4300; | ||||
| SCCI int basicBulletMoveSpeed = 3700; // 基本子弹移动速度 | |||||
| SCCI double basicRemoteAttackRange = 3000; // 基本远程攻击范围 | |||||
| SCCI double basicAttackShortRange = 1100; // 基本近程攻击范围 | |||||
| SCCI double basicBulletBombRange = 1000; // 基本子弹爆炸范围 | |||||
| SCCI int basicBulletMoveSpeed = 7400; // 基本子弹移动速度 | |||||
| SCCI double basicRemoteAttackRange = 6000; // 基本远程攻击范围 | |||||
| SCCI double basicAttackShortRange = 2200; // 基本近程攻击范围 | |||||
| SCCI double basicBulletBombRange = 2000; // 基本子弹爆炸范围 | |||||
| // 道具相关 | // 道具相关 | ||||
| @@ -269,7 +270,7 @@ namespace Constants | |||||
| struct ShowTime | struct ShowTime | ||||
| { | { | ||||
| SCCI int skillCD = commonSkillCD * 3; | |||||
| SCCI int skillCD = commonSkillCD * 8 / 3; | |||||
| SCCI int durationTime = commonSkillTime * 1; | SCCI int durationTime = commonSkillTime * 1; | ||||
| }; | }; | ||||
| @@ -137,7 +137,7 @@ namespace THUAI6 | |||||
| Swinging = 11, | Swinging = 11, | ||||
| Attacking = 12, | Attacking = 12, | ||||
| Locking = 13, | Locking = 13, | ||||
| Rummaging = 14, | |||||
| // Rummaging = 14, | |||||
| Climbing = 15, | Climbing = 15, | ||||
| OpeningAChest = 16, | OpeningAChest = 16, | ||||
| UsingSpecialSkill = 17, | UsingSpecialSkill = 17, | ||||
| @@ -314,7 +314,7 @@ namespace THUAI6 | |||||
| {PlayerState::Swinging, "Swinging"}, | {PlayerState::Swinging, "Swinging"}, | ||||
| {PlayerState::Attacking, "Attacking"}, | {PlayerState::Attacking, "Attacking"}, | ||||
| {PlayerState::Locking, "Locking"}, | {PlayerState::Locking, "Locking"}, | ||||
| {PlayerState::Rummaging, "Rummaging"}, | |||||
| // {PlayerState::Rummaging, "Rummaging"}, | |||||
| {PlayerState::Climbing, "Climbing"}, | {PlayerState::Climbing, "Climbing"}, | ||||
| {PlayerState::OpeningAChest, "OpeningAChest"}, | {PlayerState::OpeningAChest, "OpeningAChest"}, | ||||
| {PlayerState::UsingSpecialSkill, "UsingSpecialSkill"}, | {PlayerState::UsingSpecialSkill, "UsingSpecialSkill"}, | ||||
| @@ -162,7 +162,7 @@ namespace Proto2THUAI6 | |||||
| {protobuf::PlayerState::SWINGING, THUAI6::PlayerState::Swinging}, | {protobuf::PlayerState::SWINGING, THUAI6::PlayerState::Swinging}, | ||||
| {protobuf::PlayerState::ATTACKING, THUAI6::PlayerState::Attacking}, | {protobuf::PlayerState::ATTACKING, THUAI6::PlayerState::Attacking}, | ||||
| {protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking}, | {protobuf::PlayerState::LOCKING, THUAI6::PlayerState::Locking}, | ||||
| {protobuf::PlayerState::RUMMAGING, THUAI6::PlayerState::Rummaging}, | |||||
| // {protobuf::PlayerState::RUMMAGING, THUAI6::PlayerState::Rummaging}, | |||||
| {protobuf::PlayerState::CLIMBING, THUAI6::PlayerState::Climbing}, | {protobuf::PlayerState::CLIMBING, THUAI6::PlayerState::Climbing}, | ||||
| {protobuf::PlayerState::OPENING_A_CHEST, THUAI6::PlayerState::OpeningAChest}, | {protobuf::PlayerState::OPENING_A_CHEST, THUAI6::PlayerState::OpeningAChest}, | ||||
| {protobuf::PlayerState::USING_SPECIAL_SKILL, THUAI6::PlayerState::UsingSpecialSkill}, | {protobuf::PlayerState::USING_SPECIAL_SKILL, THUAI6::PlayerState::UsingSpecialSkill}, | ||||
| @@ -1,31 +1,49 @@ | |||||
| #include <vector> | #include <vector> | ||||
| #include <thread> | #include <thread> | ||||
| #include <array> | |||||
| #include "AI.h" | #include "AI.h" | ||||
| #include "constants.h" | #include "constants.h" | ||||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | ||||
| extern const bool asynchronous = false; | extern const bool asynchronous = false; | ||||
| // 选手必须定义该变量来选择自己的阵营 | |||||
| extern const THUAI6::PlayerType playerType = THUAI6::PlayerType::TrickerPlayer; | |||||
| // 选手需要依次将player0到player4的职业在这里定义 | |||||
| // 选手需要将两个都定义,本份代码中不选择的阵营任意定义即可 | |||||
| extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin; | |||||
| extern const std::array<THUAI6::StudentType, 4> studentType = { | |||||
| THUAI6::StudentType::Athlete, | |||||
| THUAI6::StudentType::Teacher, | |||||
| THUAI6::StudentType::StraightAStudent, | |||||
| THUAI6::StudentType::Sunshine}; | |||||
| extern const THUAI6::StudentType studentType = THUAI6::StudentType::Athlete; | |||||
| extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin; | |||||
| // 选手只需写一个即可,为了调试方便写了两个的话也不会有影响 | |||||
| //可以在AI.cpp内部声明变量与函数 | |||||
| void AI::play(IStudentAPI& api) | void AI::play(IStudentAPI& api) | ||||
| { | { | ||||
| api.PrintTricker(); | |||||
| // 公共操作 | |||||
| if (this->playerID == 0) | |||||
| { | |||||
| // 玩家0执行操作 | |||||
| } | |||||
| else if (this->playerID == 1) | |||||
| { | |||||
| // 玩家1执行操作 | |||||
| } | |||||
| else if (this->playerID == 2) | |||||
| { | |||||
| // 玩家2执行操作 | |||||
| } | |||||
| else if (this->playerID == 3) | |||||
| { | |||||
| // 玩家3执行操作 | |||||
| } | |||||
| //当然可以写成if (this->playerID == 2||this->playerID == 3)之类的操作 | |||||
| // 公共操作 | |||||
| } | } | ||||
| void AI::play(ITrickerAPI& api) | void AI::play(ITrickerAPI& api) | ||||
| { | { | ||||
| if (api.HaveMessage()) | |||||
| { | |||||
| auto msg = api.GetMessage(); | |||||
| api.Print("Message from " + std::to_string(msg.first) + ": " + msg.second); | |||||
| } | |||||
| auto self = api.GetSelfInfo(); | |||||
| api.PrintSelfInfo(); | |||||
| } | } | ||||
| @@ -863,7 +863,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f | |||||
| cvAI.wait(lock, [this]() | cvAI.wait(lock, [this]() | ||||
| { return AIStart; }); | { return AIStart; }); | ||||
| } | } | ||||
| auto ai = createAI(); | |||||
| auto ai = createAI(playerID); | |||||
| while (AILoop) | while (AILoop) | ||||
| { | { | ||||
| @@ -2,6 +2,7 @@ | |||||
| #include "logic.h" | #include "logic.h" | ||||
| #include "structures.h" | #include "structures.h" | ||||
| #include <tclap/CmdLine.h> | #include <tclap/CmdLine.h> | ||||
| #include <array> | |||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||
| #pragma warning(disable : 4996) | #pragma warning(disable : 4996) | ||||
| @@ -15,9 +16,8 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| bool file = false; | bool file = false; | ||||
| bool print = false; | bool print = false; | ||||
| bool warnOnly = false; | bool warnOnly = false; | ||||
| extern const THUAI6::PlayerType playerType; | |||||
| extern const THUAI6::TrickerType trickerType; | extern const THUAI6::TrickerType trickerType; | ||||
| extern const THUAI6::StudentType studentType; | |||||
| extern const std::array<THUAI6::StudentType, 4> studentType; | |||||
| // { | // { | ||||
| // file = true; | // file = true; | ||||
| // print = true; | // print = true; | ||||
| @@ -71,7 +71,16 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| } | } | ||||
| try | try | ||||
| { | { | ||||
| Logic logic(playerType, pID, trickerType, studentType); | |||||
| THUAI6::PlayerType playerType; | |||||
| THUAI6::StudentType stuType = THUAI6::StudentType::NullStudentType; | |||||
| if (pID == 4) | |||||
| playerType = THUAI6::PlayerType::TrickerPlayer; | |||||
| else | |||||
| { | |||||
| playerType = THUAI6::PlayerType::StudentPlayer; | |||||
| stuType = studentType[pID]; | |||||
| } | |||||
| Logic logic(playerType, pID, trickerType, stuType); | |||||
| logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | ||||
| } | } | ||||
| catch (const std::exception& e) | catch (const std::exception& e) | ||||
| @@ -81,9 +90,9 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| std::unique_ptr<IAI> CreateAI() | |||||
| std::unique_ptr<IAI> CreateAI(int64_t pID) | |||||
| { | { | ||||
| return std::make_unique<AI>(); | |||||
| return std::make_unique<AI>(pID); | |||||
| } | } | ||||
| int main(int argc, char* argv[]) | int main(int argc, char* argv[]) | ||||
| @@ -0,0 +1,25 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2016 Gabi Melman. | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| -- NOTE: Third party dependency used by this software -- | |||||
| This software depends on the fmt lib (MIT License), | |||||
| and users must comply to its license: https://github.com/fmtlib/fmt/blob/master/LICENSE.rst | |||||
| @@ -1,6 +1,6 @@ | |||||
| import PyAPI.structures as THUAI6 | import PyAPI.structures as THUAI6 | ||||
| from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI | from PyAPI.Interface import IStudentAPI, ITrickerAPI, IAI | ||||
| from typing import Union, Final, cast | |||||
| from typing import Union, Final, cast, List | |||||
| from PyAPI.constants import Constants | from PyAPI.constants import Constants | ||||
| import queue | import queue | ||||
| @@ -13,15 +13,10 @@ class Setting: | |||||
| def asynchronous() -> bool: | def asynchronous() -> bool: | ||||
| return True | return True | ||||
| # 选手必须修改该函数的返回值来选择自己的阵营 | |||||
| # 选手需要依次将player0到player4的职业都定义 | |||||
| @staticmethod | @staticmethod | ||||
| def playerType() -> THUAI6.PlayerType: | |||||
| return THUAI6.PlayerType.StudentPlayer | |||||
| # 选手需要将两个都定义,本份代码中不选择的阵营任意定义即可 | |||||
| @staticmethod | |||||
| def studentType() -> THUAI6.StudentType: | |||||
| return THUAI6.StudentType.Athlete | |||||
| def studentType() -> List[THUAI6.StudentType]: | |||||
| return [THUAI6.StudentType.Athlete, THUAI6.StudentType.Teacher, THUAI6.StudentType.StraightAStudent, THUAI6.StudentType.Sunshine] | |||||
| @staticmethod | @staticmethod | ||||
| def trickerType() -> THUAI6.TrickerType: | def trickerType() -> THUAI6.TrickerType: | ||||
| @@ -43,112 +38,29 @@ class AssistFunction: | |||||
| return grid // numOfGridPerCell | return grid // numOfGridPerCell | ||||
| path = [] | |||||
| cur = 0 | |||||
| fixedclass = [] | |||||
| class AI(IAI): | class AI(IAI): | ||||
| # 选手在这里实现自己的逻辑,要求和上面选择的阵营保持一致 | |||||
| def StudentPlay(self, api: IStudentAPI) -> None: | |||||
| # global fixedclass | |||||
| # selfInfo = api.GetSelfInfo() | |||||
| # available = [THUAI6.PlaceType.Land, | |||||
| # THUAI6.PlaceType.Grass, THUAI6.PlaceType.Door3, THUAI6.PlaceType.Door6, THUAI6.PlaceType.Door5, THUAI6.PlaceType.Gate] | |||||
| # def bfs(x, y): | |||||
| # if api.GetPlaceType(x, y) not in available: | |||||
| # return [] | |||||
| # def GetSuccessors(x, y): | |||||
| # successors = [] | |||||
| # if x > 0 and api.GetPlaceType(x - 1, y) in available: | |||||
| # successors.append((x - 1, y)) | |||||
| # if x < 49 and api.GetPlaceType(x + 1, y) in available: | |||||
| # successors.append((x + 1, y)) | |||||
| # if y > 0 and api.GetPlaceType(x, y - 1) in available: | |||||
| # successors.append((x, y - 1)) | |||||
| # if y < 49 and api.GetPlaceType(x, y + 1) in available: | |||||
| # successors.append((x, y + 1)) | |||||
| # return successors | |||||
| # selfX = AssistFunction.GridToCell(api.GetSelfInfo().x) | |||||
| # selfY = AssistFunction.GridToCell(api.GetSelfInfo().y) | |||||
| # frontier = queue.Queue() | |||||
| # frontier.put((selfX, selfY, [])) | |||||
| # visited = [] | |||||
| # while not frontier.empty(): | |||||
| # currentX, currentY, path = frontier.get() | |||||
| # if currentX == x and currentY == y: | |||||
| # return path | |||||
| # for nextX, nextY in GetSuccessors(currentX, currentY): | |||||
| # if (nextX, nextY) not in visited: | |||||
| # visited.append((nextX, nextY)) | |||||
| # frontier.put((nextX, nextY, path + [(nextX, nextY)])) | |||||
| # return [] | |||||
| def __init__(self, pID: int): | |||||
| self.__playerID = pID | |||||
| # def GoTo(x, y): | |||||
| # global path, cur | |||||
| # if path != [] and cur < len(path): | |||||
| # selfX = api.GetSelfInfo().x | |||||
| # selfY = api.GetSelfInfo().y | |||||
| # nextX, nextY = path[cur] | |||||
| # nextX = AssistFunction.CellToGrid(nextX) | |||||
| # nextY = AssistFunction.CellToGrid(nextY) | |||||
| # if selfX < nextX - 100: | |||||
| # api.MoveDown(10) | |||||
| # time.sleep(0.01) | |||||
| # return | |||||
| # if selfX > nextX + 100: | |||||
| # api.MoveUp(10) | |||||
| # time.sleep(0.01) | |||||
| # return | |||||
| # if selfY < nextY - 100: | |||||
| # api.MoveRight(10) | |||||
| # time.sleep(0.01) | |||||
| # return | |||||
| # if selfY > nextY + 100: | |||||
| # api.MoveLeft(10) | |||||
| # time.sleep(0.01) | |||||
| # return | |||||
| # cur += 1 | |||||
| # return | |||||
| # else: | |||||
| # path = bfs(x, y) | |||||
| # cur = 0 | |||||
| # return | |||||
| # if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) < 18000: | |||||
| # api.StartOpenGate() | |||||
| # return | |||||
| # if (AssistFunction.GridToCell(api.GetSelfInfo().x), AssistFunction.GridToCell(api.GetSelfInfo().y)) == (6, 6) and api.GetGateProgress(5, 6) >= 18000: | |||||
| # api.Graduate() | |||||
| # return | |||||
| # if len(fixedclass) == 7: | |||||
| # GoTo(6, 6) | |||||
| # return | |||||
| # if api.GetPlaceType(AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) == THUAI6.PlaceType.ClassRoom: | |||||
| # api.Print("Trying to fix!") | |||||
| # if api.GetClassroomProgress(AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) < 103000: | |||||
| # api.StartLearning() | |||||
| # return | |||||
| # else: | |||||
| # if (AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y)) not in fixedclass: | |||||
| # fixedclass.append( | |||||
| # (AssistFunction.GridToCell(api.GetSelfInfo().x) + 1, AssistFunction.GridToCell(api.GetSelfInfo().y))) | |||||
| # for i in range(50): | |||||
| # for j in range(50): | |||||
| # if api.GetPlaceType(i, j) == THUAI6.PlaceType.ClassRoom and (i, j) not in fixedclass: | |||||
| # if api.GetPlaceType(i - 1, j) in available: | |||||
| # GoTo(i - 1, j) | |||||
| # return | |||||
| api.PrintTricker() | |||||
| def StudentPlay(self, api: IStudentAPI) -> None: | |||||
| #公共操作 | |||||
| if self.__playerID == 0: | |||||
| # 玩家0执行操作 | |||||
| return | |||||
| elif self.__playerID == 1: | |||||
| # 玩家1执行操作 | |||||
| return | |||||
| elif self.__playerID == 2: | |||||
| # 玩家2执行操作 | |||||
| return | |||||
| elif self.__playerID == 3: | |||||
| # 玩家3执行操作 | |||||
| return | |||||
| #可以写成if self.__playerID<2之类的写法 | |||||
| #公共操作 | |||||
| return | |||||
| def TrickerPlay(self, api: ITrickerAPI) -> None: | def TrickerPlay(self, api: ITrickerAPI) -> None: | ||||
| api.UseSkill(0) | |||||
| api.UseSkill(1) | |||||
| selfInfo = api.GetSelfInfo() | |||||
| api.PrintSelfInfo() | api.PrintSelfInfo() | ||||
| return | return | ||||
| @@ -198,11 +198,15 @@ class Communication: | |||||
| self.__haveNewMessage = False | self.__haveNewMessage = False | ||||
| return self.__message2Client | return self.__message2Client | ||||
| def AddPlayer(self, playerID: int) -> None: | |||||
| def AddPlayer(self, playerID: int, playerType: THUAI6.PlayerType) -> None: | |||||
| def tMessage(): | def tMessage(): | ||||
| try: | try: | ||||
| if playerType == THUAI6.PlayerType.StudentPlayer: | |||||
| studentType = Setting.studentType()[playerID] | |||||
| else: | |||||
| studentType = THUAI6.StudentType.NullStudentType | |||||
| playerMsg = THUAI62Proto.THUAI62ProtobufPlayer( | playerMsg = THUAI62Proto.THUAI62ProtobufPlayer( | ||||
| playerID, Setting.playerType(), Setting.studentType(), Setting.trickerType()) | |||||
| playerID, playerType, studentType, Setting.trickerType()) | |||||
| for msg in self.__THUAI6Stub.AddPlayer(playerMsg): | for msg in self.__THUAI6Stub.AddPlayer(playerMsg): | ||||
| with self.__cvMessage: | with self.__cvMessage: | ||||
| self.__haveNewMessage = True | self.__haveNewMessage = True | ||||
| @@ -8,6 +8,7 @@ class NoInstance: | |||||
| class Constants(NoInstance): | class Constants(NoInstance): | ||||
| frameDuration = 50 # 每帧毫秒数 | |||||
| numOfGridPerCell = 1000 # 单位坐标数 | numOfGridPerCell = 1000 # 单位坐标数 | ||||
| rows = 50 # 地图行数 | rows = 50 # 地图行数 | ||||
| cols = 50 # 地图列数 | cols = 50 # 地图列数 | ||||
| @@ -27,8 +28,8 @@ class Constants(NoInstance): | |||||
| basicEncourageSpeed = 100 | basicEncourageSpeed = 100 | ||||
| basicLearnSpeed = 123 | basicLearnSpeed = 123 | ||||
| basicSpeedOfOpeningOrLocking = 4000 | basicSpeedOfOpeningOrLocking = 4000 | ||||
| basicStudentSpeedOfClimbingThroughWindows = 611 | |||||
| basicTrickerSpeedOfClimbingThroughWindows = 1270 | |||||
| basicStudentSpeedOfClimbingThroughWindows = 1222 | |||||
| basicTrickerSpeedOfClimbingThroughWindows = 2540 | |||||
| basicSpeedOfOpenChest = 1000 | basicSpeedOfOpenChest = 1000 | ||||
| basicHp = 3000000 | basicHp = 3000000 | ||||
| @@ -36,8 +37,8 @@ class Constants(NoInstance): | |||||
| basicEncouragementDegree = 1500000 | basicEncouragementDegree = 1500000 | ||||
| basicTimeOfRouse = 1000 | basicTimeOfRouse = 1000 | ||||
| basicStudentSpeed = 1270 | |||||
| basicTrickerSpeed = 1504 | |||||
| basicStudentSpeed = 3000 | |||||
| basicTrickerSpeed = 3600 | |||||
| basicConcealment = 1.0 | basicConcealment = 1.0 | ||||
| basicStudentAlertnessRadius = 15 * numOfGridPerCell | basicStudentAlertnessRadius = 15 * numOfGridPerCell | ||||
| @@ -57,10 +58,10 @@ class Constants(NoInstance): | |||||
| basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长 | ||||
| basicStunnedTimeOfStudent = 4300 | basicStunnedTimeOfStudent = 4300 | ||||
| basicBulletmoveSpeed = 3700 # 基本子弹移动速度 | |||||
| basicRemoteAttackRange = 3000 # 基本远程攻击范围 | |||||
| basicAttackShortRange = 1100 # 基本近程攻击范围 | |||||
| basicBulletBombRange = 1000 # 基本子弹爆炸范围 | |||||
| basicBulletmoveSpeed = 7400 # 基本子弹移动速度 | |||||
| basicRemoteAttackRange = 6000 # 基本远程攻击范围 | |||||
| basicAttackShortRange = 2200 # 基本近程攻击范围 | |||||
| basicBulletBombRange = 2000 # 基本子弹爆炸范围 | |||||
| # 道具相关 | # 道具相关 | ||||
| @@ -84,10 +85,10 @@ class Constants(NoInstance): | |||||
| addedTimeOfSpeedWhenInspire = 1.6 | addedTimeOfSpeedWhenInspire = 1.6 | ||||
| timeOfAddingSpeedWhenInspire = 6000 | timeOfAddingSpeedWhenInspire = 6000 | ||||
| addHpWhenEncourage = basicHp / 4; | |||||
| addHpWhenEncourage = basicHp / 4 | |||||
| checkIntervalWhenShowTime = 200; | |||||
| addAddictionPer100msWhenShowTime = 300; | |||||
| checkIntervalWhenShowTime = 200 | |||||
| addAddictionPer100msWhenShowTime = 300 | |||||
| class Assassin: | class Assassin: | ||||
| @@ -270,7 +271,7 @@ class Howl: | |||||
| class ShowTime: | class ShowTime: | ||||
| skillCD = (int)(3.0 * Constants.commonSkillCD) | |||||
| skillCD = (int)(8 * Constants.commonSkillCD / 3) | |||||
| durationTime = (int)(1.0 * Constants.commonSkillTime) | durationTime = (int)(1.0 * Constants.commonSkillTime) | ||||
| @@ -18,12 +18,14 @@ from PyAPI.Interface import ILogic, IGameTimer | |||||
| class Logic(ILogic): | class Logic(ILogic): | ||||
| def __init__(self, playerID: int) -> None: | |||||
| def __init__(self, playerID: int, playerType: THUAI6.PlayerType) -> None: | |||||
| # ID | # ID | ||||
| self.__playerID: int = playerID | self.__playerID: int = playerID | ||||
| self.__playerGUIDs: List[int] = [] | self.__playerGUIDs: List[int] = [] | ||||
| self.__playerType: THUAI6.PlayerType = playerType | |||||
| # 通信 | # 通信 | ||||
| self.__comm: Communication | self.__comm: Communication | ||||
| @@ -268,7 +270,7 @@ class Logic(ILogic): | |||||
| def __ProcessMessage(self) -> None: | def __ProcessMessage(self) -> None: | ||||
| def messageThread(): | def messageThread(): | ||||
| self.__logger.info("Message thread start!") | self.__logger.info("Message thread start!") | ||||
| self.__comm.AddPlayer(self.__playerID) | |||||
| self.__comm.AddPlayer(self.__playerID, self.__playerType) | |||||
| self.__logger.info("Join the player!") | self.__logger.info("Join the player!") | ||||
| while self.__gameState != THUAI6.GameState.GameEnd: | while self.__gameState != THUAI6.GameState.GameEnd: | ||||
| @@ -344,7 +346,7 @@ class Logic(ILogic): | |||||
| self.__logger.debug("Buffer cleared!") | self.__logger.debug("Buffer cleared!") | ||||
| self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( | self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( | ||||
| message.all_message) | message.all_message) | ||||
| if Setting.playerType() == THUAI6.PlayerType.StudentPlayer: | |||||
| if self.__playerType == THUAI6.PlayerType.StudentPlayer: | |||||
| for item in message.obj_message: | for item in message.obj_message: | ||||
| if item.WhichOneof("message_of_obj") == "student_message": | if item.WhichOneof("message_of_obj") == "student_message": | ||||
| if item.student_message.player_id == self.__playerID: | if item.student_message.player_id == self.__playerID: | ||||
| @@ -590,20 +592,20 @@ class Logic(ILogic): | |||||
| self.__logger.info("asynchronous: %s", Setting.asynchronous()) | self.__logger.info("asynchronous: %s", Setting.asynchronous()) | ||||
| self.__logger.info("server: %s:%s", IP, port) | self.__logger.info("server: %s:%s", IP, port) | ||||
| self.__logger.info("playerID: %s", self.__playerID) | self.__logger.info("playerID: %s", self.__playerID) | ||||
| self.__logger.info("player type: %s", Setting.playerType().name) | |||||
| self.__logger.info("player type: %s", self.__playerType.name) | |||||
| self.__logger.info("****************************") | self.__logger.info("****************************") | ||||
| # 建立通信组件 | # 建立通信组件 | ||||
| self.__comm = Communication(IP, port) | self.__comm = Communication(IP, port) | ||||
| # 构造timer | # 构造timer | ||||
| if Setting.playerType() == THUAI6.PlayerType.StudentPlayer: | |||||
| if self.__playerType == THUAI6.PlayerType.StudentPlayer: | |||||
| if not file and not screen: | if not file and not screen: | ||||
| self.__timer = StudentAPI(self) | self.__timer = StudentAPI(self) | ||||
| else: | else: | ||||
| self.__timer = StudentDebugAPI( | self.__timer = StudentDebugAPI( | ||||
| self, file, screen, warnOnly, self.__playerID) | self, file, screen, warnOnly, self.__playerID) | ||||
| elif Setting.playerType() == THUAI6.PlayerType.TrickerPlayer: | |||||
| elif self.__playerType == THUAI6.PlayerType.TrickerPlayer: | |||||
| if not file and not screen: | if not file and not screen: | ||||
| self.__timer = TrickerAPI(self) | self.__timer = TrickerAPI(self) | ||||
| else: | else: | ||||
| @@ -615,7 +617,7 @@ class Logic(ILogic): | |||||
| with self.__cvAI: | with self.__cvAI: | ||||
| self.__cvAI.wait_for(lambda: self.__AIStart) | self.__cvAI.wait_for(lambda: self.__AIStart) | ||||
| ai = createAI() | |||||
| ai = createAI(self.__playerID) | |||||
| while self.__AILoop: | while self.__AILoop: | ||||
| if Setting.asynchronous(): | if Setting.asynchronous(): | ||||
| self.__Wait() | self.__Wait() | ||||
| @@ -9,6 +9,7 @@ from PyAPI.AI import AI | |||||
| from PyAPI.logic import Logic | from PyAPI.logic import Logic | ||||
| from typing import List, Callable | from typing import List, Callable | ||||
| import argparse | import argparse | ||||
| import PyAPI.structures as THUAI6 | |||||
| def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | ||||
| @@ -39,12 +40,17 @@ def THUAI6Main(argv: List[str], AIBuilder: Callable) -> None: | |||||
| file = args.file | file = args.file | ||||
| screen = args.screen | screen = args.screen | ||||
| warnOnly = args.warnOnly | warnOnly = args.warnOnly | ||||
| logic = Logic(pID) | |||||
| playerType = THUAI6.PlayerType.NullPlayerType | |||||
| if pID == 4: | |||||
| playerType = THUAI6.PlayerType.TrickerPlayer | |||||
| else: | |||||
| playerType = THUAI6.PlayerType.StudentPlayer | |||||
| logic = Logic(pID, playerType) | |||||
| logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) | logic.Main(AIBuilder, sIP, sPort, file, screen, warnOnly) | ||||
| def CreateAI() -> IAI: | |||||
| return AI() | |||||
| def CreateAI(pID: int) -> IAI: | |||||
| return AI(pID) | |||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||
| @@ -113,7 +113,7 @@ class PlayerState(Enum): | |||||
| Swinging = 11 | Swinging = 11 | ||||
| Attacking = 12 | Attacking = 12 | ||||
| Locking = 13 | Locking = 13 | ||||
| Rummaging = 14 | |||||
| # Rummaging = 14 | |||||
| Climbing = 15 | Climbing = 15 | ||||
| OpeningAChest = 16 | OpeningAChest = 16 | ||||
| UsingSpecialSkill = 17 | UsingSpecialSkill = 17 | ||||
| @@ -145,7 +145,7 @@ class Proto2THUAI6(NoInstance): | |||||
| MessageType.SWINGING: THUAI6.PlayerState.Swinging, | MessageType.SWINGING: THUAI6.PlayerState.Swinging, | ||||
| MessageType.ATTACKING: THUAI6.PlayerState.Attacking, | MessageType.ATTACKING: THUAI6.PlayerState.Attacking, | ||||
| MessageType.LOCKING: THUAI6.PlayerState.Locking, | MessageType.LOCKING: THUAI6.PlayerState.Locking, | ||||
| MessageType.RUMMAGING: THUAI6.PlayerState.Rummaging, | |||||
| # MessageType.RUMMAGING: THUAI6.PlayerState.Rummaging, | |||||
| MessageType.CLIMBING: THUAI6.PlayerState.Climbing, | MessageType.CLIMBING: THUAI6.PlayerState.Climbing, | ||||
| MessageType.OPENING_A_CHEST: THUAI6.PlayerState.OpeningAChest, | MessageType.OPENING_A_CHEST: THUAI6.PlayerState.OpeningAChest, | ||||
| MessageType.USING_SPECIAL_SKILL: THUAI6.PlayerState.UsingSpecialSkill, | MessageType.USING_SPECIAL_SKILL: THUAI6.PlayerState.UsingSpecialSkill, | ||||
| @@ -1,6 +1,7 @@ | |||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o & | python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o & | ||||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -d & | |||||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2 -d & | |||||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3 -d & | |||||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -d & | |||||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2 -d & | |||||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3 -d & | |||||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d & | |||||
| @@ -0,0 +1,3 @@ | |||||
| #!/usr/bin/env bash | |||||
| ./CAPI/proto/cpp_output.sh | |||||
| @@ -0,0 +1,12 @@ | |||||
| #!/usr/bin/env bash | |||||
| python -m pip install -r ./CAPI/python/requirements.txt | |||||
| mkdir -p proto | |||||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto MessageType.proto | |||||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto Message2Clients.proto | |||||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto Message2Server.proto | |||||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto --grpc_python_out=./CAPI/python/proto Services.proto | |||||
| @@ -0,0 +1,6 @@ | |||||
| #!/usr/bin/env bash | |||||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 0 -d -o & | |||||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 1 -d -o & | |||||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 2 -d -o & | |||||
| ./CAPI/cpp/build/capi -I 127.0.0.1 -P 8888 -p 3 -d -o & | |||||
| @@ -0,0 +1,6 @@ | |||||
| #!/usr/bin/env bash | |||||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 0 -d -o & | |||||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 1 -d -o & | |||||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 2 -d -o & | |||||
| python ./CAPI/python/PyAPI/main.py -I 127.0.0.1 -P 8888 -p 3 -d -o & | |||||
| @@ -0,0 +1,3 @@ | |||||
| #!/usr/bin/env bash | |||||
| ./linux64/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||||
| @@ -0,0 +1,3 @@ | |||||
| #!/usr/bin/env bash | |||||
| ./linux64/Debug/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 | |||||
| @@ -0,0 +1,64 @@ | |||||
| # Q&A | |||||
| [TOC] | |||||
| ## 常见简单问题 | |||||
| Q: Windows找不到文件:\CAPI\cpp\x64\Debug\APl.exe? | |||||
| A: | |||||
| 应该是还没有编译,打开CAPI\cpp目录,在里面打开CAPI.sln,然后点击生成,对代码进行编译 | |||||
| Q: 怎么修改.cmd参数? | |||||
| A: | |||||
| 见选手包中的使用文档部分 | |||||
| Q: 怎么开始游戏? | |||||
| A: | |||||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||||
| Q: Mac怎么用? | |||||
| A: | |||||
| 安装Windows虚拟机 | |||||
| ## C++ | |||||
| Q:显示API项目已卸载 | |||||
|  | |||||
| A:可能是没有安装C++ | |||||
| Q:CAPI.sln编译不通过 | |||||
|  | |||||
| A: | |||||
| 出现_std_find_trivial_1的报错就是没更新到VS2022, | |||||
| 对于VS2022依旧报错_std_find_trivial_1的,先考虑是否版本过旧 | |||||
|  | |||||
| 确保上图项目属性中平台工具集在V143及以上,C++17标准 | |||||
| ## Python | |||||
| ### grpc版本更新失败 | |||||
| Q:运行GeneratePythonProto.cmd报错 | |||||
|  | |||||
| A: | |||||
| - 可能措施1. | |||||
| 首先保证Python版本在3.9及以上 | |||||
| - 可能措施2. 更换为国内镜像源 | |||||
| 在终端输入 | |||||
| `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||||
| - 可能措施3. 更新pip | |||||
| `python -m pip install --upgrade pip` (pip 版本最好为23.1) | |||||
| ## 比赛相关 | |||||
| Q:职业数值会修改吗? | |||||
| A:初赛结束会调数值及机制,增加新角色 | |||||
| @@ -8,31 +8,37 @@ RUN apt-get update && apt-get install --no-install-recommends -y gcc g++ make wg | |||||
| RUN git clone -b v1.46.3 --depth 1 --shallow-submodules https://gitee.com/mirrors/grpc.git | RUN git clone -b v1.46.3 --depth 1 --shallow-submodules https://gitee.com/mirrors/grpc.git | ||||
| RUN wget -P . https://cloud.tsinghua.edu.cn/f/1f2713efd9e44255abd6/?dl=1 | RUN wget -P . https://cloud.tsinghua.edu.cn/f/1f2713efd9e44255abd6/?dl=1 | ||||
| RUN mv 'index.html?dl=1' third_party.tar.gz | RUN mv 'index.html?dl=1' third_party.tar.gz | ||||
| RUN cd grpc | |||||
| WORKDIR /usr/local/grpc | |||||
| RUN rm -rf third_party | RUN rm -rf third_party | ||||
| RUN mv ../third_party.tar.gz . | RUN mv ../third_party.tar.gz . | ||||
| RUN tar -zxvf third_party.tar.gz | RUN tar -zxvf third_party.tar.gz | ||||
| RUN mkdir -p cmake/build | RUN mkdir -p cmake/build | ||||
| RUN pushd cmake/build | |||||
| WORKDIR /usr/local/grpc/cmake/build | |||||
| RUN cmake -DgRPC_INSTALL=ON \ | RUN cmake -DgRPC_INSTALL=ON \ | ||||
| -DgRPC_BUILD_TESTS=OFF \ | -DgRPC_BUILD_TESTS=OFF \ | ||||
| ../.. | ../.. | ||||
| RUN make -j$(nproc) | RUN make -j$(nproc) | ||||
| RUN make install | RUN make install | ||||
| RUN popd | |||||
| #安装protobuf | #安装protobuf | ||||
| RUN cd /usr/local | |||||
| WORKDIR /usr/local | |||||
| RUN git clone https://gitee.com/mirrors/protobuf_source.git ./protobuf | RUN git clone https://gitee.com/mirrors/protobuf_source.git ./protobuf | ||||
| RUN cd protobuf | |||||
| RUN git checkout 3.22.1 | |||||
| RUN ./autogen.sh | |||||
| WORKDIR /usr/local/protobuf | |||||
| RUN git checkout 3.20.x | |||||
| RUN ./autogen.sh | |||||
| RUN ./configure | RUN ./configure | ||||
| RUN make -j$(nproc) | RUN make -j$(nproc) | ||||
| RUN make install | RUN make install | ||||
| RUN ldconfig | RUN ldconfig | ||||
| #RUN git submodule update --init --recursive | |||||
| #RUN cmake . | |||||
| #RUN cmake --build . --parallel 10 | |||||
| #RUN make install | |||||
| COPY ./CAPI /usr/local/PlayerCode | |||||
| RUN cd /usr/local/PlayerCode/CAPI | |||||
| RUN cmake CMakeLists.txt | |||||
| COPY ./CAPI /usr/local/PlayerCode/CAPI | |||||
| COPY ./dependency /usr/local/PlayerCode/dependency | |||||
| WORKDIR /usr/local/PlayerCode/dependency/proto | |||||
| RUN ./cpp_output.sh | |||||
| WORKDIR /usr/local/PlayerCode/CAPI/cpp | |||||
| RUN cmake ./CMakeLists.txt | |||||
| RUN make | RUN make | ||||
| @@ -0,0 +1,8 @@ | |||||
| @ECHO OFF | |||||
| CD %~dp0 | |||||
| dotnet publish "../../Logic/Server/Server.csproj" -c Release -r linux-x64 --self-contained true | |||||
| dotnet publish "../../Logic/Server/Server.csproj" -c Release -r win-x64 --self-contained true | |||||
| dotnet publish "../../Logic/Server/Server.csproj" -c Debug -r win-x64 --self-contained true | |||||
| dotnet publish "../../Logic/Server/Server.csproj" -c Debug -r linux-x64 --self-contained true | |||||
| dotnet publish "../../Logic/Client/Client.csproj" -c Release -r win-x64 --self-contained true | |||||
| PAUSE | |||||
| @@ -0,0 +1,38 @@ | |||||
| #! /bin/bash | |||||
| workdir=/d/伤风/软件部/MyTHUAI6/THUAI6/logic | |||||
| targetdir=/d/伤风/软件部/THUAI6-毕业吧少女 | |||||
| mkdir -p ${targetdir} | |||||
| pushd ${targetdir} | |||||
| mkdir -p win/win64 | |||||
| mkdir -p linux/linux64/ | |||||
| mkdir -p win/win64/Debug/ | |||||
| mkdir -p linux/linux64/Debug/ | |||||
| popd | |||||
| pushd ${workdir} | |||||
| pushd Server/bin/Release/net6.0/linux-x64/publish | |||||
| rm *.pdb | |||||
| cp -rf * ${targetdir}/linux/linux64/ | |||||
| popd | |||||
| pushd Server/bin/Debug/net6.0/linux-x64/publish | |||||
| cp -rf * ${targetdir}/linux/linux64/Debug/ | |||||
| popd | |||||
| pushd Server/bin/Debug/net6.0/win-x64/publish | |||||
| cp -rf * ${targetdir}/win/win64/Debug/ | |||||
| popd | |||||
| pushd Server/bin/Release/net6.0/win-x64/publish | |||||
| rm *.pdb | |||||
| cp -rf * ${targetdir}/win/win64/ | |||||
| popd | |||||
| pushd Client/bin/Release/net6.0-windows/win-x64/publish | |||||
| rm *.pdb | |||||
| cp -rf * ${targetdir}/win/win64/ | |||||
| popd | |||||
| @@ -9,6 +9,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" /> | |||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | ||||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
| <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34" /> | <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34" /> | ||||
| @@ -38,17 +38,19 @@ | |||||
| <TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="5" Name="RouteTxtBox" Text="{Binding Route}" Visibility="{Binding RouteBoxVis}"/> | <TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="5" Name="RouteTxtBox" Text="{Binding Route}" Visibility="{Binding RouteBoxVis}"/> | ||||
| <Button Grid.Row="3" Grid.Column="6" Name="GetRouteBtn" Content="选择文件夹" Command="{Binding ClickBrowseCommand}" Visibility="{Binding RouteBoxVis}" /> | <Button Grid.Row="3" Grid.Column="6" Name="GetRouteBtn" Content="选择文件夹" Command="{Binding ClickBrowseCommand}" Visibility="{Binding RouteBoxVis}" /> | ||||
| <Button Grid.Row="3" Grid.Column="7" Name="SetBtm" Content="确认并安装" Command="{Binding ClickConfirmCommand}" Visibility="{Binding RouteBoxVis}"/> | |||||
| <Button Grid.Row="3" Grid.Column="7" Name="SetBtm" Content="{Binding ConfirmBtnCont}" Command="{Binding ClickConfirmCommand}" Visibility="{Binding RouteBoxVis}"/> | |||||
| <Button Grid.Row="4" Grid.Column="6" Grid.ColumnSpan="2" Name="ReadExsisted" Content="所选文件夹即为选手包" Command="{Binding ClickReadCommand}" Visibility="{Binding NewUserVis}"/> | |||||
| <Button Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Name="UpdateBtn" Content="{Binding UpdateBtnCont}" Command="{Binding ClickUpdateCommand}" Visibility="{Binding MenuVis}" /> | <Button Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" Name="UpdateBtn" Content="{Binding UpdateBtnCont}" Command="{Binding ClickUpdateCommand}" Visibility="{Binding MenuVis}" /> | ||||
| <TextBlock Grid.Row="4" Grid.Column="2" Text="{Binding UpdateInfo}" Visibility="{Binding MenuVis}" /> | |||||
| <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" FontSize="10" Text="{Binding UpdateInfo}" Visibility="{Binding UpdateInfoVis}" /> | |||||
| <Button Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="2" Name="MoveBtn" Content="移动文件" Command="{Binding ClickMoveCommand}" Visibility="{Binding MenuVis}" /> | <Button Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="2" Name="MoveBtn" Content="移动文件" Command="{Binding ClickMoveCommand}" Visibility="{Binding MenuVis}" /> | ||||
| <Button Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Name="UninstBtn" Content="卸载选手包" Command="{Binding ClickUninstCommand}" Visibility="{Binding MenuVis}" /> | <Button Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Name="UninstBtn" Content="卸载选手包" Command="{Binding ClickUninstCommand}" Visibility="{Binding MenuVis}" /> | ||||
| <Button Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="2" Name="MenuBackBtn" Content="回到登陆界面" Command="{Binding ClickBackCommand}" Visibility="{Binding MenuVis}" /> | <Button Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="2" Name="MenuBackBtn" Content="回到登陆界面" Command="{Binding ClickBackCommand}" Visibility="{Binding MenuVis}" /> | ||||
| <TextBlock Grid.Row="3" Grid.Column="4" Text="Processing" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||||
| <ProgressBar Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="6" Minimum="0" Maximum="100" Name="Progress" Visibility="{Binding ProgressVis}" IsIndeterminate="True"/> | |||||
| <TextBlock Grid.Row="3" Grid.Column="3" Text="正在下载……" Grid.ColumnSpan="2" Visibility="{Binding ProgressVis}"/> | |||||
| <ProgressBar Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="7" Minimum="0" Maximum="100" Name="Progress" Visibility="{Binding ProgressVis}" IsIndeterminate="True"/> | |||||
| <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="4" Text="操作完成!你可以继续操作或退出" Visibility="{Binding CompleteVis}"/> | <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="4" Text="操作完成!你可以继续操作或退出" Visibility="{Binding CompleteVis}"/> | ||||
| <Button Grid.Row="6" Grid.Column="1" Name="BackBtn" Content="返回" Command="{Binding ClickBackCommand}" Visibility="{Binding CompleteVis}" Click="BackBtn_Click"/> | <Button Grid.Row="6" Grid.Column="1" Name="BackBtn" Content="返回" Command="{Binding ClickBackCommand}" Visibility="{Binding CompleteVis}" Click="BackBtn_Click"/> | ||||
| @@ -72,20 +74,21 @@ | |||||
| <TextBlock Grid.Row="3" Grid.Column="0" Text="密码:" Visibility="{Binding LoginVis}" /> | <TextBlock Grid.Row="3" Grid.Column="0" Text="密码:" Visibility="{Binding LoginVis}" /> | ||||
| <TextBox Grid.Row="1" Grid.Column="1" Name="Username" Visibility="{Binding LoginVis}" Text="{Binding Username}" /> | <TextBox Grid.Row="1" Grid.Column="1" Name="Username" Visibility="{Binding LoginVis}" Text="{Binding Username}" /> | ||||
| <PasswordBox Grid.Row="3" Grid.Column="1" Name="Password" Visibility="{Binding LoginVis}" c:PasswordHelper.Attach="True" c:PasswordHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> | <PasswordBox Grid.Row="3" Grid.Column="1" Name="Password" Visibility="{Binding LoginVis}" c:PasswordHelper.Attach="True" c:PasswordHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> | ||||
| <!--<CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}">记住我</CheckBox>--> | |||||
| <CheckBox Grid.Row="5" Grid.Column="0" Visibility="{Binding LoginVis}" IsChecked="{Binding RememberMe}">记住我</CheckBox> | |||||
| <TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/> | <TextBlock Grid.Row="5" Grid.Column="1" Foreground="Red" Text=" 用户名或密码错误!" Visibility="{Binding LoginFailVis}"/> | ||||
| </Grid> | </Grid> | ||||
| </StackPanel> | </StackPanel> | ||||
| <Button Grid.Row="7" Grid.Column="1" Name="Login" Content="登录" Command="{Binding ClickLoginCommand}" Visibility="{Binding LoginVis}"/> | <Button Grid.Row="7" Grid.Column="1" Name="Login" Content="登录" Command="{Binding ClickLoginCommand}" Visibility="{Binding LoginVis}"/> | ||||
| <Button Grid.Row="7" Grid.Column="2" Name="Launch" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}"/> | |||||
| <Button Grid.Row="7" Grid.Column="2" Name="Launch" FontSize="11" Content="{Binding LaunchBtnCont}" Command="{Binding ClickLaunchCommand}" Visibility="{Binding LoginVis}"/> | |||||
| <Button Grid.Row="7" Grid.Column="3" Name="ShiftLanguage" FontSize="11" Content="更改语言" Command="{Binding ClickShiftLanguageCommand}" Visibility="{Binding LaunchVis}"/> | |||||
| <Button Grid.Row="7" Grid.Column="4" Grid.ColumnSpan="2" Name="Edit" Content="修改文件" Command="{Binding ClickEditCommand}" Visibility="{Binding LoginVis}"/> | <Button Grid.Row="7" Grid.Column="4" Grid.ColumnSpan="2" Name="Edit" Content="修改文件" Command="{Binding ClickEditCommand}" Visibility="{Binding LoginVis}"/> | ||||
| <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Text="你有已完成的比赛!" Visibility="{Binding MatchFinishedVis}"/> | <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Text="你有已完成的比赛!" Visibility="{Binding MatchFinishedVis}"/> | ||||
| <Button Grid.Row="3" Grid.Column="1" Name ="Upload" Content="{Binding UploadBtnCont}" Command="{Binding ClickUploadCommand}" Visibility="{Binding WebVis}"/> | <Button Grid.Row="3" Grid.Column="1" Name ="Upload" Content="{Binding UploadBtnCont}" Command="{Binding ClickUploadCommand}" Visibility="{Binding WebVis}"/> | ||||
| <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding CodeName}" Visibility="{Binding UploadReadyVis}" /> | <TextBlock Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding CodeName}" Visibility="{Binding UploadReadyVis}" /> | ||||
| <Button Grid.Row="3" Grid.Column="2" Name="ReUpload" Content="放弃上传" Command="{Binding ClickReselectCommand}" Visibility="{Binding UploadReadyVis}" /> | |||||
| <Button Grid.Row="3" Grid.Column="2" Name="ReUpload" Content="{Binding AbortOrSelLanguage}" Command="{Binding ClickAboutUploadCommand}" Visibility="{Binding WebVis}" /> | |||||
| <Button Grid.Row="6" Grid.Column="3" Grid.ColumnSpan="2" Content="退出登录" Command="{Binding ClickBackCommand}" Visibility="{Binding WebVis}" /> | <Button Grid.Row="6" Grid.Column="3" Grid.ColumnSpan="2" Content="退出登录" Command="{Binding ClickBackCommand}" Visibility="{Binding WebVis}" /> | ||||
| <StackPanel Grid.Row="5" Grid.Column="1" Grid.RowSpan="3" Grid.ColumnSpan="2"> | <StackPanel Grid.Row="5" Grid.Column="1" Grid.RowSpan="3" Grid.ColumnSpan="2"> | ||||
| <Grid> | <Grid> | ||||
| @@ -21,10 +21,18 @@ using System.Net.Http; | |||||
| using System.Windows; | using System.Windows; | ||||
| using System.Windows.Shapes; | using System.Windows.Shapes; | ||||
| //using System.Windows.Forms; | //using System.Windows.Forms; | ||||
| using System.Threading.Tasks; | |||||
| using System.Threading; | |||||
| using MessageBox = System.Windows.MessageBox; | using MessageBox = System.Windows.MessageBox; | ||||
| using Downloader; | using Downloader; | ||||
| using COSXML.Transfer; | using COSXML.Transfer; | ||||
| using WebConnect; | |||||
| using System.IO.Compression; | |||||
| using ICSharpCode.SharpZipLib.Tar; | |||||
| using ICSharpCode.SharpZipLib.GZip; | |||||
| using static System.Net.WebRequestMethods; | |||||
| using File = System.IO.File; | |||||
| namespace starter.viewmodel.settings | namespace starter.viewmodel.settings | ||||
| { | { | ||||
| @@ -52,6 +60,7 @@ namespace starter.viewmodel.settings | |||||
| PlayerNum = "nSelect"; | PlayerNum = "nSelect"; | ||||
| UploadReady = false; | UploadReady = false; | ||||
| LoginFailed = false; | LoginFailed = false; | ||||
| launchLanguage = LaunchLanguage.cpp; | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -118,7 +127,7 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| if (updateInfo.changedFileCount != 0 || updateInfo.newFileCount != 0) | if (updateInfo.changedFileCount != 0 || updateInfo.newFileCount != 0) | ||||
| { | { | ||||
| Updates = "发现新版本"; | |||||
| Updates = $"{updateInfo.newFileCount}个新文件,{updateInfo.changedFileCount}个文件变化"; | |||||
| } | } | ||||
| return Status.menu; | return Status.menu; | ||||
| } | } | ||||
| @@ -128,6 +137,38 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| return await web.LoginToEEsast(client, Username, Password); | return await web.LoginToEEsast(client, Username, Password); | ||||
| } | } | ||||
| public bool RememberUser() | |||||
| { | |||||
| int result = 0; | |||||
| result |= Web.WriteUserEmail(Username); | |||||
| result |= Web.WriteUserPassword(Password); | |||||
| return result == 0; | |||||
| } | |||||
| public bool RecallUser() | |||||
| { | |||||
| Username = Web.ReadUserEmail(); | |||||
| if (Username == null || Username.Equals("")) | |||||
| { | |||||
| Username = ""; | |||||
| return false; | |||||
| } | |||||
| Password = Web.ReadUserPassword(); | |||||
| if (Password == null || Username.Equals("")) | |||||
| { | |||||
| Password = ""; | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| public bool ForgetUser() | |||||
| { | |||||
| int result = 0; | |||||
| result |= Web.WriteUserEmail(""); | |||||
| result |= Web.WriteUserPassword(""); | |||||
| return result == 0; | |||||
| } | |||||
| public bool Update() | public bool Update() | ||||
| { | { | ||||
| return Tencent_cos_download.Update(); | return Tencent_cos_download.Update(); | ||||
| @@ -254,6 +295,15 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| get; set; | get; set; | ||||
| } | } | ||||
| public bool RememberMe | |||||
| { | |||||
| get; set; | |||||
| } | |||||
| public enum LaunchLanguage { cpp, python }; | |||||
| public LaunchLanguage launchLanguage | |||||
| { | |||||
| get; set; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| namespace Downloader | namespace Downloader | ||||
| @@ -292,8 +342,8 @@ namespace Downloader | |||||
| public static string dataPath = ""; // C盘的文档文件夹 | public static string dataPath = ""; // C盘的文档文件夹 | ||||
| public Data(string path) | public Data(string path) | ||||
| { | { | ||||
| // dataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); | |||||
| dataPath = new DirectoryInfo(".").FullName; | |||||
| dataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); | |||||
| //dataPath = new DirectoryInfo(".").FullName; | |||||
| Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | ||||
| if (File.Exists(Data.path)) | if (File.Exists(Data.path)) | ||||
| { | { | ||||
| @@ -397,8 +447,9 @@ namespace Downloader | |||||
| .Build(); // 创建 CosXmlConfig 对象 | .Build(); // 创建 CosXmlConfig 对象 | ||||
| // 永久密钥访问凭证 | // 永久密钥访问凭证 | ||||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||||
| long durationSecond = 1000; // 每次请求签名有效时长,单位为秒 | long durationSecond = 1000; // 每次请求签名有效时长,单位为秒 | ||||
| QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider( | QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider( | ||||
| @@ -418,7 +469,7 @@ namespace Downloader | |||||
| Dictionary<string, string> test = request.GetRequestHeaders(); | Dictionary<string, string> test = request.GetRequestHeaders(); | ||||
| request.SetCosProgressCallback(delegate (long completed, long total) | request.SetCosProgressCallback(delegate (long completed, long total) | ||||
| { | { | ||||
| Console.WriteLine(String.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||||
| //Console.WriteLine(String.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||||
| }); | }); | ||||
| // 执行请求 | // 执行请求 | ||||
| GetObjectResult result = cosXml.GetObject(request); | GetObjectResult result = cosXml.GetObject(request); | ||||
| @@ -462,6 +513,8 @@ namespace Downloader | |||||
| { | { | ||||
| if (fst != null) | if (fst != null) | ||||
| fst.Close(); | fst.Close(); | ||||
| if (File.Exists(strFileFullPath)) | |||||
| return "conflict"; | |||||
| return ""; | return ""; | ||||
| } | } | ||||
| finally | finally | ||||
| @@ -515,11 +568,16 @@ namespace Downloader | |||||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | ||||
| foreach (KeyValuePair<string, string> pair in jsonDict) | foreach (KeyValuePair<string, string> pair in jsonDict) | ||||
| { | { | ||||
| MD5 = GetFileMd5Hash(System.IO.Path.Combine(Data.FilePath, pair.Key)); | |||||
| if (MD5.Length == 0) // 文档不存在 | |||||
| newFileName.Add(pair.Key); | |||||
| else if (MD5 != pair.Value) // MD5不匹配 | |||||
| updateFileName.Add(pair.Key); | |||||
| if (System.IO.Path.GetFileName(pair.Key) != "AI.cpp" && System.IO.Path.GetFileName(pair.Key) != "AI.py") | |||||
| { | |||||
| MD5 = GetFileMd5Hash(System.IO.Path.Combine(Data.FilePath, pair.Key)); | |||||
| if (MD5.Length == 0) // 文档不存在 | |||||
| newFileName.Add(pair.Key); | |||||
| else if (MD5.Equals("conflict")) | |||||
| MessageBox.Show($"文件{pair.Key}已打开,无法检查是否为最新,若需要,请关闭文件稍后手动检查更新", "文件正在使用", MessageBoxButton.OK, MessageBoxImage.Warning); | |||||
| else if (MD5 != pair.Value) // MD5不匹配 | |||||
| updateFileName.Add(pair.Key); | |||||
| } | |||||
| } | } | ||||
| newFile = newFileName.Count; | newFile = newFileName.Count; | ||||
| @@ -577,7 +635,6 @@ namespace Downloader | |||||
| private static void Download() | private static void Download() | ||||
| { | { | ||||
| Tencent_cos_download Downloader = new Tencent_cos_download(); | Tencent_cos_download Downloader = new Tencent_cos_download(); | ||||
| int newFile = 0, updateFile = 0; | int newFile = 0, updateFile = 0; | ||||
| int totalnew = newFileName.Count, totalupdate = updateFileName.Count; | int totalnew = newFileName.Count, totalupdate = updateFileName.Count; | ||||
| filenum = totalnew + totalupdate; | filenum = totalnew + totalupdate; | ||||
| @@ -588,19 +645,20 @@ namespace Downloader | |||||
| { | { | ||||
| foreach (string filename in newFileName) | foreach (string filename in newFileName) | ||||
| { | { | ||||
| Console.WriteLine(newFile + 1 + "/" + totalnew + ":开始下载" + filename); | |||||
| //Console.WriteLine(newFile + 1 + "/" + totalnew + ":开始下载" + filename); | |||||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | ||||
| Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||||
| newFile++; | newFile++; | ||||
| } | } | ||||
| foreach (string filename in updateFileName) | foreach (string filename in updateFileName) | ||||
| { | { | ||||
| Console.WriteLine(updateFile + 1 + "/" + totalupdate + ":开始下载" + filename); | |||||
| //Console.WriteLine(updateFile + 1 + "/" + totalupdate + ":开始下载" + filename); | |||||
| File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | File.Delete(System.IO.Path.Combine(@Data.FilePath, filename)); | ||||
| Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | Downloader.download(System.IO.Path.Combine(@Data.FilePath, filename), filename); | ||||
| Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||||
| //Console.WriteLine(filename + "下载完毕!" + Environment.NewLine); | |||||
| updateFile++; | updateFile++; | ||||
| } | } | ||||
| UpdatePlanned = false; | |||||
| } | } | ||||
| catch (CosClientException clientEx) | catch (CosClientException clientEx) | ||||
| { | { | ||||
| @@ -696,12 +754,35 @@ namespace Downloader | |||||
| newFileName.Clear(); | newFileName.Clear(); | ||||
| updateFileName.Clear(); | updateFileName.Clear(); | ||||
| foreach (KeyValuePair<string, string> pair in jsonDict) | |||||
| newFileName.Add("THUAI6.tar.gz"); | |||||
| Download(); | |||||
| Stream inStream = null; | |||||
| Stream gzipStream = null; | |||||
| TarArchive tarArchive = null; | |||||
| try | |||||
| { | { | ||||
| newFileName.Add(pair.Key); | |||||
| using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) | |||||
| { | |||||
| using (gzipStream = new GZipInputStream(inStream)) | |||||
| { | |||||
| tarArchive = TarArchive.CreateInputTarArchive(gzipStream); | |||||
| tarArchive.ExtractContents(Data.FilePath); | |||||
| tarArchive.Close(); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| Download(); | |||||
| catch (Exception ex) | |||||
| { | |||||
| //出错 | |||||
| } | |||||
| finally | |||||
| { | |||||
| if (null != tarArchive) tarArchive.Close(); | |||||
| if (null != gzipStream) gzipStream.Close(); | |||||
| if (null != inStream) inStream.Close(); | |||||
| } | |||||
| FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); | |||||
| fileInfo.Delete(); | |||||
| string json2; | string json2; | ||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | Dictionary<string, string> dict = new Dictionary<string, string>(); | ||||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | ||||
| @@ -728,6 +809,18 @@ namespace Downloader | |||||
| using StreamWriter sw = new StreamWriter(fs2); | using StreamWriter sw = new StreamWriter(fs2); | ||||
| fs2.SetLength(0); | fs2.SetLength(0); | ||||
| sw.Write(JsonConvert.SerializeObject(dict)); | sw.Write(JsonConvert.SerializeObject(dict)); | ||||
| Check(); | |||||
| Download(); | |||||
| if (File.Exists(Data.FilePath + "/THUAI6/AI.cpp")) | |||||
| { | |||||
| FileInfo userCpp = new FileInfo((Data.FilePath + "/THUAI6/AI.cpp").Replace("/", "\\")); | |||||
| userCpp.MoveTo(Data.FilePath + "/THUAI6/win/CAPI/cpp/API/src/AI.cpp", true); | |||||
| } | |||||
| if (File.Exists(Data.FilePath + "/THUAI6/AI.py")) | |||||
| { | |||||
| FileInfo userCpp = new FileInfo((Data.FilePath + "/THUAI6/AI.py").Replace("/", "\\")); | |||||
| userCpp.MoveTo(Data.FilePath + "/THUAI6/win/CAPI/python/PyAPI/AI.cpp", true); | |||||
| } | |||||
| } | } | ||||
| public static void Change_all_hash(string topDir, Dictionary<string, string> jsonDict) // 更改HASH | public static void Change_all_hash(string topDir, Dictionary<string, string> jsonDict) // 更改HASH | ||||
| @@ -739,7 +832,7 @@ namespace Downloader | |||||
| foreach (FileInfo NextFile in theFolder.GetFiles()) | foreach (FileInfo NextFile in theFolder.GetFiles()) | ||||
| { | { | ||||
| string filepath = topDir + @"/" + NextFile.Name; // 文件路径 | string filepath = topDir + @"/" + NextFile.Name; // 文件路径 | ||||
| Console.WriteLine(filepath); | |||||
| //Console.WriteLine(filepath); | |||||
| foreach (KeyValuePair<string, string> pair in jsonDict) | foreach (KeyValuePair<string, string> pair in jsonDict) | ||||
| { | { | ||||
| if (System.IO.Path.Equals(filepath, System.IO.Path.Combine(Data.FilePath, pair.Key).Replace('\\', '/'))) | if (System.IO.Path.Equals(filepath, System.IO.Path.Combine(Data.FilePath, pair.Key).Replace('\\', '/'))) | ||||
| @@ -807,27 +900,31 @@ namespace Downloader | |||||
| public static int DeleteAll() | public static int DeleteAll() | ||||
| { | { | ||||
| DirectoryInfo di = new DirectoryInfo(Data.FilePath); | |||||
| DirectoryInfo player = new DirectoryInfo(System.IO.Path.GetFullPath(System.IO.Path.Combine(Data.FilePath, playerFolder))); | |||||
| DirectoryInfo di = new DirectoryInfo(Data.FilePath + "/THUAI6"); | |||||
| //DirectoryInfo player = new DirectoryInfo(System.IO.Path.GetFullPath(System.IO.Path.Combine(Data.FilePath, playerFolder))); | |||||
| FileInfo[] allfile = di.GetFiles(); | |||||
| try | try | ||||
| { | { | ||||
| foreach (FileInfo file in di.GetFiles()) | |||||
| foreach (FileInfo file in allfile) | |||||
| { | { | ||||
| //if(file.Name == "AI.cpp" || file.Name == "AI.py") | |||||
| //{ | |||||
| // string filename = System.IO.Path.GetFileName(file.FullName); | |||||
| // file.MoveTo(System.IO.Path.Combine(Data.FilePath, filename)); | |||||
| // continue; | |||||
| //} | |||||
| file.Delete(); | file.Delete(); | ||||
| } | } | ||||
| foreach (FileInfo file in player.GetFiles()) | |||||
| { | |||||
| if (file.Name == "README.md") | |||||
| { | |||||
| continue; | |||||
| } | |||||
| string filename = System.IO.Path.GetFileName(file.FullName); | |||||
| file.MoveTo(System.IO.Path.Combine(Data.FilePath, filename)); | |||||
| } | |||||
| FileInfo userFileCpp = new FileInfo(Data.FilePath + "/THUAI6/win/CAPI/cpp/API/src/AI.cpp"); | |||||
| FileInfo userFilePy = new FileInfo(Data.FilePath + "/THUAI6/win/CAPI/python/PyAPI/AI.py"); | |||||
| userFileCpp.MoveTo(System.IO.Path.Combine(Data.FilePath + "/THUAI6", System.IO.Path.GetFileName(userFileCpp.FullName))); | |||||
| userFilePy.MoveTo(System.IO.Path.Combine(Data.FilePath + "/THUAI6", System.IO.Path.GetFileName(userFilePy.FullName))); | |||||
| foreach (DirectoryInfo subdi in di.GetDirectories()) | foreach (DirectoryInfo subdi in di.GetDirectories()) | ||||
| { | { | ||||
| subdi.Delete(true); | subdi.Delete(true); | ||||
| } | } | ||||
| FileInfo hashFile = new FileInfo(Data.FilePath + "/hash.json"); | |||||
| hashFile.Delete(); | |||||
| } | } | ||||
| catch (UnauthorizedAccessException) | catch (UnauthorizedAccessException) | ||||
| { | { | ||||
| @@ -891,7 +988,6 @@ namespace Downloader | |||||
| Console.WriteLine("文件已经打开,请关闭后再删除"); | Console.WriteLine("文件已经打开,请关闭后再删除"); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| Console.WriteLine($"删除成功!player文件夹中的文件已经放在{ProgramName}的根目录下"); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -1071,7 +1167,7 @@ namespace WebConnect | |||||
| switch (response.StatusCode) | switch (response.StatusCode) | ||||
| { | { | ||||
| case System.Net.HttpStatusCode.OK: | case System.Net.HttpStatusCode.OK: | ||||
| Console.WriteLine("Success login"); | |||||
| //Console.WriteLine("Success login"); | |||||
| token = (System.Text.Json.JsonSerializer.Deserialize(await response.Content.ReadAsStreamAsync(), typeof(LoginResponse), new JsonSerializerOptions() | token = (System.Text.Json.JsonSerializer.Deserialize(await response.Content.ReadAsStreamAsync(), typeof(LoginResponse), new JsonSerializerOptions() | ||||
| { | { | ||||
| PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | ||||
| @@ -1087,7 +1183,7 @@ namespace WebConnect | |||||
| default: | default: | ||||
| int code = ((int)response.StatusCode); | int code = ((int)response.StatusCode); | ||||
| Console.WriteLine(code); | |||||
| //Console.WriteLine(code); | |||||
| if (code == 401) | if (code == 401) | ||||
| { | { | ||||
| //Console.WriteLine("邮箱或密码错误!"); | //Console.WriteLine("邮箱或密码错误!"); | ||||
| @@ -1120,7 +1216,7 @@ namespace WebConnect | |||||
| try | try | ||||
| { | { | ||||
| string content; | string content; | ||||
| client.DefaultRequestHeaders.Authorization = new("bearertoken", logintoken); | |||||
| client.DefaultRequestHeaders.Authorization = new("Bearer", logintoken); | |||||
| if (!File.Exists(tarfile)) | if (!File.Exists(tarfile)) | ||||
| { | { | ||||
| //Console.WriteLine("文件不存在!"); | //Console.WriteLine("文件不存在!"); | ||||
| @@ -1171,13 +1267,13 @@ namespace WebConnect | |||||
| uploadTask.progressCallback = delegate (long completed, long total) | uploadTask.progressCallback = delegate (long completed, long total) | ||||
| { | { | ||||
| Console.WriteLine(string.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||||
| //Console.WriteLine(string.Format("progress = {0:##.##}%", completed * 100.0 / total)); | |||||
| }; | }; | ||||
| try | try | ||||
| { | { | ||||
| COSXMLUploadTask.UploadTaskResult result = await transferManager.UploadAsync(uploadTask); | COSXMLUploadTask.UploadTaskResult result = await transferManager.UploadAsync(uploadTask); | ||||
| Console.WriteLine(result.GetResultInfo()); | |||||
| //Console.WriteLine(result.GetResultInfo()); | |||||
| string eTag = result.eTag; | string eTag = result.eTag; | ||||
| //到这里应该是成功了,但是因为我没有试过,也不知道具体情况,可能还要根据result的内容判断 | //到这里应该是成功了,但是因为我没有试过,也不知道具体情况,可能还要根据result的内容判断 | ||||
| } | } | ||||
| @@ -1290,6 +1386,125 @@ namespace WebConnect | |||||
| Console.WriteLine("写入token.dat发生冲突!请检查token.dat是否被其它程序占用!"); | Console.WriteLine("写入token.dat发生冲突!请检查token.dat是否被其它程序占用!"); | ||||
| } | } | ||||
| } | } | ||||
| public static int WriteUserEmail(string email) | |||||
| { | |||||
| try | |||||
| { | |||||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||||
| StreamReader sr = new StreamReader(fs); | |||||
| string json = sr.ReadToEnd(); | |||||
| if (json == null || json == "") | |||||
| { | |||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||||
| } | |||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| if (!dict.ContainsKey("email")) | |||||
| { | |||||
| dict.Add("email", email); | |||||
| } | |||||
| else | |||||
| { | |||||
| dict["email"] = email; | |||||
| } | |||||
| sr.Close(); | |||||
| fs.Close(); | |||||
| FileStream fs2 = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||||
| StreamWriter sw = new StreamWriter(fs2); | |||||
| sw.WriteLine(JsonConvert.SerializeObject(dict)); | |||||
| sw.Close(); | |||||
| fs2.Close(); | |||||
| return 0;//成功 | |||||
| } | |||||
| catch | |||||
| { | |||||
| return -1;//失败 | |||||
| } | |||||
| } | |||||
| public static int WriteUserPassword(string password) | |||||
| { | |||||
| try | |||||
| { | |||||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||||
| StreamReader sr = new StreamReader(fs); | |||||
| string json = sr.ReadToEnd(); | |||||
| if (json == null || json == "") | |||||
| { | |||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||||
| } | |||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| if (!dict.ContainsKey("password")) | |||||
| { | |||||
| dict.Add("password", password); | |||||
| } | |||||
| else | |||||
| { | |||||
| dict["password"] = password; | |||||
| } | |||||
| sr.Close(); | |||||
| fs.Close(); | |||||
| FileStream fs2 = new FileStream(savepath, FileMode.Open, FileAccess.ReadWrite); | |||||
| StreamWriter sw = new StreamWriter(fs2); | |||||
| sw.WriteLine(JsonConvert.SerializeObject(dict)); | |||||
| sw.Close(); | |||||
| fs2.Close(); | |||||
| return 0;//成功 | |||||
| } | |||||
| catch | |||||
| { | |||||
| return -1;//失败,THUAI6.json 文件不存在或者已被占用 | |||||
| } | |||||
| } | |||||
| public static string ReadUserPassword() | |||||
| { | |||||
| try | |||||
| { | |||||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.Read); | |||||
| StreamReader sr = new StreamReader(fs); | |||||
| string json = sr.ReadToEnd(); | |||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| if (json == null || json == "") | |||||
| { | |||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||||
| } | |||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| return dict["password"]; | |||||
| } | |||||
| catch | |||||
| { | |||||
| return null; //文件不存在或者已被占用 | |||||
| } | |||||
| } | |||||
| public static string ReadUserEmail() | |||||
| { | |||||
| try | |||||
| { | |||||
| string savepath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||||
| FileStream fs = new FileStream(savepath, FileMode.Open, FileAccess.Read); | |||||
| StreamReader sr = new StreamReader(fs); | |||||
| string json = sr.ReadToEnd(); | |||||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||||
| if (json == null || json == "") | |||||
| { | |||||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||||
| } | |||||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||||
| return dict["email"]; | |||||
| } | |||||
| catch | |||||
| { | |||||
| return null; | |||||
| } | |||||
| } | |||||
| public bool ReadToken() // 读取token | public bool ReadToken() // 读取token | ||||
| { | { | ||||
| try | try | ||||
| @@ -7,11 +7,20 @@ using Downloader; | |||||
| using MessageBox = System.Windows.MessageBox; | using MessageBox = System.Windows.MessageBox; | ||||
| using System.Configuration; | using System.Configuration; | ||||
| using System.Drawing.Design; | using System.Drawing.Design; | ||||
| using Application = System.Windows.Application; | |||||
| using System.ComponentModel; | |||||
| using Installer; | |||||
| using static System.Windows.Forms.VisualStyles.VisualStyleElement; | |||||
| using System.IO; | |||||
| using System.Windows.Automation.Provider; | |||||
| namespace starter.viewmodel.settings | namespace starter.viewmodel.settings | ||||
| { | { | ||||
| public class SettingsViewModel : NotificationObject | public class SettingsViewModel : NotificationObject | ||||
| { | { | ||||
| //定义BackgroundWorker | |||||
| BackgroundWorker asyncDownloader; | |||||
| BackgroundWorker asyncUpdater; | |||||
| /// <summary> | /// <summary> | ||||
| /// Model object | /// Model object | ||||
| /// </summary> | /// </summary> | ||||
| @@ -19,18 +28,43 @@ namespace starter.viewmodel.settings | |||||
| /// <summary> | /// <summary> | ||||
| /// initializer | /// initializer | ||||
| /// </summary> | /// </summary> | ||||
| public SettingsViewModel() | public SettingsViewModel() | ||||
| { | { | ||||
| //Program.Tencent_cos_download.UpdateHash(); | //Program.Tencent_cos_download.UpdateHash(); | ||||
| //实例化BackgroundWorker | |||||
| asyncDownloader = new BackgroundWorker(); | |||||
| asyncUpdater = new BackgroundWorker(); | |||||
| //指示BackgroundWorker是否可以报告进度更新 | |||||
| //当该属性值为True是,将可以成功调用ReportProgress方法,否则将引发InvalidOperationException异常。 | |||||
| asyncDownloader.WorkerReportsProgress = true; | |||||
| asyncUpdater.WorkerReportsProgress = true; | |||||
| //挂载方法: | |||||
| asyncDownloader.DoWork += AsyncDownloader_DoWork; | |||||
| asyncUpdater.DoWork += AsyncUpdater_DoWork; | |||||
| //完成通知器: | |||||
| asyncDownloader.RunWorkerCompleted += AsyncDownloader_RunWorkerCompleted; | |||||
| asyncUpdater.RunWorkerCompleted += AsyncUpdater_RunWorkerCompleted; | |||||
| UpdateInfoVis = Visibility.Collapsed; | |||||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | ||||
| { | { | ||||
| obj.checkUpdate(); | obj.checkUpdate(); | ||||
| Status = SettingsModel.Status.login; | Status = SettingsModel.Status.login; | ||||
| this.RaisePropertyChanged("WindowWidth"); | this.RaisePropertyChanged("WindowWidth"); | ||||
| //TODO:在启动时立刻检查更新,确保选手启动最新版选手包 | |||||
| //TODO:若有更新,将启动键改为更新键; | |||||
| //TODO:相应地,使用login界面启动; | |||||
| //TODO:结构:上方为登录框架,下方有“修改选手包”按钮 | |||||
| this.RaisePropertyChanged("LaunchVis"); | |||||
| if (obj.RecallUser()) | |||||
| RememberMe = true; | |||||
| else | |||||
| RememberMe = false; | |||||
| this.RaisePropertyChanged("RememberMe"); | |||||
| //在启动时立刻检查更新,确保选手启动最新版选手包 | |||||
| //若有更新,将启动键改为更新键; | |||||
| //相应地,使用login界面启动; | |||||
| //结构:上方为登录框架,下方有“修改选手包”按钮 | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -40,6 +74,85 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| } | } | ||||
| private void AsyncDownloader_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) | |||||
| { | |||||
| if (e.Result == null) | |||||
| { | |||||
| Status = SettingsModel.Status.error; | |||||
| } | |||||
| else if ((bool)e.Result) | |||||
| { | |||||
| Status = SettingsModel.Status.successful; | |||||
| } | |||||
| else | |||||
| { | |||||
| Status = SettingsModel.Status.newUser; | |||||
| } | |||||
| } | |||||
| private void AsyncUpdater_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) | |||||
| { | |||||
| if (e.Result == null) | |||||
| { | |||||
| Status = SettingsModel.Status.error; | |||||
| } | |||||
| else | |||||
| { | |||||
| this.RaisePropertyChanged("LaunchVis"); | |||||
| if ((int)e.Result == 1) | |||||
| { | |||||
| Status = SettingsModel.Status.successful; | |||||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||||
| this.RaisePropertyChanged("UpdateInfo"); | |||||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||||
| } | |||||
| else if ((int)e.Result == 2) | |||||
| { | |||||
| Status = SettingsModel.Status.login; | |||||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||||
| this.RaisePropertyChanged("UpdateInfo"); | |||||
| } | |||||
| } | |||||
| } | |||||
| private void AsyncUpdater_DoWork(object? sender, DoWorkEventArgs e) | |||||
| { | |||||
| if (asyncUpdater.CancellationPending) | |||||
| { | |||||
| e.Cancel = true; | |||||
| return; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (obj.Update()) | |||||
| if (e.Argument.ToString().Equals("Manual")) | |||||
| { | |||||
| e.Result = 1; | |||||
| } | |||||
| else | |||||
| e.Result = 2; | |||||
| else | |||||
| e.Result = -1; | |||||
| } | |||||
| } | |||||
| private void AsyncDownloader_DoWork(object? sender, DoWorkEventArgs e) | |||||
| { | |||||
| if (asyncDownloader.CancellationPending) | |||||
| { | |||||
| e.Cancel = true; | |||||
| return; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (obj.install()) | |||||
| e.Result = true; | |||||
| else | |||||
| e.Result = false; | |||||
| } | |||||
| } | |||||
| //TODO:参赛界面:包括上传参赛代码、申请对战 | //TODO:参赛界面:包括上传参赛代码、申请对战 | ||||
| //TODO:界面中应包含上次对战完成提示及下载回放按钮 | //TODO:界面中应包含上次对战完成提示及下载回放按钮 | ||||
| @@ -93,6 +206,10 @@ namespace starter.viewmodel.settings | |||||
| this.RaisePropertyChanged("CompleteVis"); | this.RaisePropertyChanged("CompleteVis"); | ||||
| this.RaisePropertyChanged("WindowWidth"); | this.RaisePropertyChanged("WindowWidth"); | ||||
| this.RaisePropertyChanged("WebVis"); | this.RaisePropertyChanged("WebVis"); | ||||
| this.RaisePropertyChanged("CoverVis"); | |||||
| this.RaisePropertyChanged("LaunchVis"); | |||||
| this.RaisePropertyChanged("NewUserVis"); | |||||
| this.RaisePropertyChanged("ConfirmBtnCont"); | |||||
| } | } | ||||
| } | } | ||||
| public string Intro | public string Intro | ||||
| @@ -126,14 +243,28 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| case SettingsModel.Status.newUser: | case SettingsModel.Status.newUser: | ||||
| return "将主体程序安装在:"; | |||||
| return "将选手包安装在(将创建THUAI6文件夹):"; | |||||
| case SettingsModel.Status.move: | case SettingsModel.Status.move: | ||||
| return "将主体程序移动到:"; | |||||
| return "将选手包移动到(THUAI6文件夹将会被整体移动):"; | |||||
| default: | default: | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public string AbortOrSelLanguage | |||||
| { | |||||
| get | |||||
| { | |||||
| string ans = ""; | |||||
| if (obj.UploadReady) | |||||
| ans = "放弃上传"; | |||||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||||
| ans = "语言:c++"; | |||||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.python) | |||||
| ans = "语言:python"; | |||||
| return ans; | |||||
| } | |||||
| } | |||||
| public int PlayerNum | public int PlayerNum | ||||
| { | { | ||||
| get | get | ||||
| @@ -177,7 +308,10 @@ namespace starter.viewmodel.settings | |||||
| public string Route | public string Route | ||||
| { | { | ||||
| get => obj.Route; | |||||
| get | |||||
| { | |||||
| return obj.Route; | |||||
| } | |||||
| set | set | ||||
| { | { | ||||
| obj.Route = value; | obj.Route = value; | ||||
| @@ -209,6 +343,27 @@ namespace starter.viewmodel.settings | |||||
| return obj.CodeRoute.Substring(obj.CodeRoute.LastIndexOf('/') == -1 ? obj.CodeRoute.LastIndexOf('\\') + 1 : obj.CodeRoute.LastIndexOf('/') + 1); | return obj.CodeRoute.Substring(obj.CodeRoute.LastIndexOf('/') == -1 ? obj.CodeRoute.LastIndexOf('\\') + 1 : obj.CodeRoute.LastIndexOf('/') + 1); | ||||
| } | } | ||||
| } | } | ||||
| public bool RememberMe | |||||
| { | |||||
| get | |||||
| { | |||||
| return obj.RememberMe; | |||||
| } | |||||
| set | |||||
| { | |||||
| obj.RememberMe = value; | |||||
| this.RaisePropertyChanged("RememberMe"); | |||||
| } | |||||
| } | |||||
| public Visibility NewUserVis | |||||
| { | |||||
| get | |||||
| { | |||||
| return Status == SettingsModel.Status.newUser ? Visibility.Visible : Visibility.Collapsed; | |||||
| } | |||||
| } | |||||
| public Visibility MenuVis | public Visibility MenuVis | ||||
| { | { | ||||
| get | get | ||||
| @@ -277,6 +432,19 @@ namespace starter.viewmodel.settings | |||||
| get { return obj.UploadReady ? Visibility.Visible : Visibility.Collapsed; } | get { return obj.UploadReady ? Visibility.Visible : Visibility.Collapsed; } | ||||
| } | } | ||||
| public Visibility UpdateInfoVis | |||||
| { | |||||
| get; set; | |||||
| } | |||||
| public Visibility LaunchVis | |||||
| { | |||||
| get | |||||
| { | |||||
| return obj.status == SettingsModel.Status.login && (!obj.UpdatePlanned) ? Visibility.Visible : Visibility.Collapsed; | |||||
| } | |||||
| } | |||||
| public string UpdateBtnCont | public string UpdateBtnCont | ||||
| { | { | ||||
| get | get | ||||
| @@ -291,14 +459,21 @@ namespace starter.viewmodel.settings | |||||
| if (obj.UpdatePlanned) | if (obj.UpdatePlanned) | ||||
| return obj.Updates; | return obj.Updates; | ||||
| else | else | ||||
| return ""; | |||||
| return "已是最新版本"; | |||||
| } | } | ||||
| } | } | ||||
| public string LaunchBtnCont | public string LaunchBtnCont | ||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| return obj.UpdatePlanned ? "更新" : "启动"; | |||||
| string ans; | |||||
| if (obj.UpdatePlanned) | |||||
| ans = "更新"; | |||||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||||
| ans = "启动c++包"; | |||||
| else | |||||
| ans = "启动python包"; | |||||
| return ans; | |||||
| } | } | ||||
| } | } | ||||
| public string UploadBtnCont | public string UploadBtnCont | ||||
| @@ -308,6 +483,28 @@ namespace starter.viewmodel.settings | |||||
| return obj.UploadReady ? "上传代码" : "选择代码上传"; | return obj.UploadReady ? "上传代码" : "选择代码上传"; | ||||
| } | } | ||||
| } | } | ||||
| public string ShiftLanguageBtnCont | |||||
| { | |||||
| get | |||||
| { | |||||
| return obj.launchLanguage == SettingsModel.LaunchLanguage.cpp ? "改为python" : "改为c++"; | |||||
| } | |||||
| } | |||||
| public string ConfirmBtnCont | |||||
| { | |||||
| get | |||||
| { | |||||
| switch (Status) | |||||
| { | |||||
| case SettingsModel.Status.newUser: | |||||
| return "确认并安装"; | |||||
| case SettingsModel.Status.move: | |||||
| return "确认并移动"; | |||||
| default: | |||||
| return ""; | |||||
| } | |||||
| } | |||||
| } | |||||
| public string RouteSelectWindow(string type) | public string RouteSelectWindow(string type) | ||||
| { | { | ||||
| @@ -322,10 +519,18 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| else if (type == "File") | else if (type == "File") | ||||
| { | { | ||||
| var openFileDialog = new Microsoft.Win32.OpenFileDialog() | |||||
| var openFileDialog = new Microsoft.Win32.OpenFileDialog(); | |||||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||||
| { | { | ||||
| Filter = "c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h|python Source File (.py)|*.py" | |||||
| }; | |||||
| openFileDialog.InitialDirectory = (Route + "/THUAI6/win/CAPI/cpp/API/src/").Replace("/", "\\"); | |||||
| openFileDialog.Filter = "c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h|python Source File (.py)|*.py"; | |||||
| } | |||||
| else if (obj.launchLanguage == SettingsModel.LaunchLanguage.python) | |||||
| { | |||||
| openFileDialog.InitialDirectory = (Route + "/THUAI6/win/CAPI/python/PyAPI/").Replace("/", "\\"); | |||||
| openFileDialog.Filter = "python Source File (.py)|*.py|c++ Source Files (.cpp)|*.cpp|c++ Header File (.h)|*.h"; | |||||
| } | |||||
| var result = openFileDialog.ShowDialog(); | var result = openFileDialog.ShowDialog(); | ||||
| if (result == true) | if (result == true) | ||||
| { | { | ||||
| @@ -363,16 +568,22 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| Status = SettingsModel.Status.working; | Status = SettingsModel.Status.working; | ||||
| this.RaisePropertyChanged("ProgressVis"); | this.RaisePropertyChanged("ProgressVis"); | ||||
| if (obj.install()) | |||||
| /*if (obj.install()) | |||||
| { | { | ||||
| Status = SettingsModel.Status.successful; | Status = SettingsModel.Status.successful; | ||||
| }*/ | |||||
| if (asyncDownloader.IsBusy) | |||||
| return; | |||||
| else | |||||
| { | |||||
| asyncDownloader.RunWorkerAsync(); | |||||
| } | } | ||||
| } | } | ||||
| else if (Status == SettingsModel.Status.move) | else if (Status == SettingsModel.Status.move) | ||||
| { | { | ||||
| Status = SettingsModel.Status.working; | |||||
| this.RaisePropertyChanged("ProgressVis"); | |||||
| //Status = SettingsModel.Status.working; | |||||
| //this.RaisePropertyChanged("ProgressVis"); | |||||
| switch (obj.move()) | switch (obj.move()) | ||||
| { | { | ||||
| case -1: | case -1: | ||||
| @@ -398,11 +609,14 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| clickUpdateCommand = new BaseCommand(new Action<object>(o => | clickUpdateCommand = new BaseCommand(new Action<object>(o => | ||||
| { | { | ||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| if (obj.UpdatePlanned) | if (obj.UpdatePlanned) | ||||
| { | { | ||||
| UpdateInfoVis = Visibility.Collapsed; | |||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| Status = SettingsModel.Status.working; | Status = SettingsModel.Status.working; | ||||
| this.RaisePropertyChanged("ProgressVis"); | this.RaisePropertyChanged("ProgressVis"); | ||||
| if (obj.Update()) | |||||
| /*if (obj.Update()) | |||||
| { | { | ||||
| Status = SettingsModel.Status.successful; | Status = SettingsModel.Status.successful; | ||||
| @@ -411,15 +625,22 @@ namespace starter.viewmodel.settings | |||||
| } | } | ||||
| else | else | ||||
| Status = SettingsModel.Status.error; | |||||
| Status = SettingsModel.Status.error;*/ | |||||
| if (asyncUpdater.IsBusy) | |||||
| return; | |||||
| else | |||||
| asyncUpdater.RunWorkerAsync("Manual"); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| UpdateInfoVis = Visibility.Visible; | |||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| Status = SettingsModel.Status.working; | Status = SettingsModel.Status.working; | ||||
| this.RaisePropertyChanged("ProgressVis"); | this.RaisePropertyChanged("ProgressVis"); | ||||
| Status = obj.checkUpdate(); | Status = obj.checkUpdate(); | ||||
| this.RaisePropertyChanged("UpdateBtnCont"); | this.RaisePropertyChanged("UpdateBtnCont"); | ||||
| this.RaisePropertyChanged("UpdateInfo"); | this.RaisePropertyChanged("UpdateInfo"); | ||||
| this.RaisePropertyChanged("LaunchVis"); | |||||
| } | } | ||||
| })); | })); | ||||
| } | } | ||||
| @@ -450,8 +671,8 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| clickUninstCommand = new BaseCommand(new Action<object>(o => | clickUninstCommand = new BaseCommand(new Action<object>(o => | ||||
| { | { | ||||
| Status = SettingsModel.Status.working; | |||||
| this.RaisePropertyChanged("ProgressVis"); | |||||
| UpdateInfoVis = Visibility.Collapsed; | |||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| switch (obj.Uninst()) | switch (obj.Uninst()) | ||||
| { | { | ||||
| case -1: | case -1: | ||||
| @@ -459,13 +680,15 @@ namespace starter.viewmodel.settings | |||||
| MessageBox.Show("文件已经打开,请关闭后再删除", "", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK); | MessageBox.Show("文件已经打开,请关闭后再删除", "", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK); | ||||
| break; | break; | ||||
| case 0: | case 0: | ||||
| Status = SettingsModel.Status.successful; | |||||
| Status = SettingsModel.Status.newUser; | |||||
| MessageBox.Show($"删除成功!player文件夹中的文件已经放在{Downloader.Program.Data.FilePath}/{Downloader.Program.ProgramName}的根目录下", "", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK); | |||||
| break; | break; | ||||
| default: | default: | ||||
| Status = SettingsModel.Status.error; | Status = SettingsModel.Status.error; | ||||
| break; | break; | ||||
| } | } | ||||
| })); | })); | ||||
| } | } | ||||
| return clickUninstCommand; | return clickUninstCommand; | ||||
| @@ -489,6 +712,22 @@ namespace starter.viewmodel.settings | |||||
| case 0: | case 0: | ||||
| obj.LoginFailed = false; | obj.LoginFailed = false; | ||||
| Status = SettingsModel.Status.web; | Status = SettingsModel.Status.web; | ||||
| if (obj.RememberMe) | |||||
| { | |||||
| obj.RememberUser(); | |||||
| RememberMe = true; | |||||
| this.RaisePropertyChanged("RememberMe"); | |||||
| } | |||||
| else | |||||
| { | |||||
| obj.ForgetUser(); | |||||
| RememberMe = false; | |||||
| this.RaisePropertyChanged("RememberMe"); | |||||
| Username = ""; | |||||
| Password = ""; | |||||
| this.RaisePropertyChanged("Username"); | |||||
| this.RaisePropertyChanged("Password"); | |||||
| } | |||||
| this.RaisePropertyChanged("CoverVis"); | this.RaisePropertyChanged("CoverVis"); | ||||
| break; | break; | ||||
| case -2: | case -2: | ||||
| @@ -515,15 +754,10 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| Status = SettingsModel.Status.working; | Status = SettingsModel.Status.working; | ||||
| this.RaisePropertyChanged("ProgressVis"); | this.RaisePropertyChanged("ProgressVis"); | ||||
| if (obj.Update()) | |||||
| { | |||||
| this.RaisePropertyChanged("UpdateBtnCont"); | |||||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||||
| Status = SettingsModel.Status.login; | |||||
| this.RaisePropertyChanged("UpdateInfo"); | |||||
| } | |||||
| if (asyncUpdater.IsBusy) | |||||
| return; | |||||
| else | else | ||||
| Status = SettingsModel.Status.error; | |||||
| asyncUpdater.RunWorkerAsync("Auto"); | |||||
| } | } | ||||
| else if (!obj.Launch()) | else if (!obj.Launch()) | ||||
| { | { | ||||
| @@ -544,6 +778,9 @@ namespace starter.viewmodel.settings | |||||
| clickEditCommand = new BaseCommand(new Action<object>(o => | clickEditCommand = new BaseCommand(new Action<object>(o => | ||||
| { | { | ||||
| Status = SettingsModel.Status.menu; | Status = SettingsModel.Status.menu; | ||||
| if (obj.UpdatePlanned) | |||||
| UpdateInfoVis = Visibility.Visible; | |||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| })); | })); | ||||
| } | } | ||||
| return clickEditCommand; | return clickEditCommand; | ||||
| @@ -558,7 +795,12 @@ namespace starter.viewmodel.settings | |||||
| { | { | ||||
| clickBackCommand = new BaseCommand(new Action<object>(o => | clickBackCommand = new BaseCommand(new Action<object>(o => | ||||
| { | { | ||||
| Status = SettingsModel.Status.login; | |||||
| UpdateInfoVis = Visibility.Collapsed; | |||||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||||
| if (Downloader.Program.Tencent_cos_download.CheckAlreadyDownload()) | |||||
| Status = SettingsModel.Status.login; | |||||
| else | |||||
| Status = SettingsModel.Status.newUser; | |||||
| })); | })); | ||||
| } | } | ||||
| return clickBackCommand; | return clickBackCommand; | ||||
| @@ -614,6 +856,7 @@ namespace starter.viewmodel.settings | |||||
| this.RaisePropertyChanged("UploadBtnCont"); | this.RaisePropertyChanged("UploadBtnCont"); | ||||
| this.RaisePropertyChanged("UploadReadyVis"); | this.RaisePropertyChanged("UploadReadyVis"); | ||||
| this.RaisePropertyChanged("CoverVis"); | this.RaisePropertyChanged("CoverVis"); | ||||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| @@ -626,6 +869,7 @@ namespace starter.viewmodel.settings | |||||
| this.RaisePropertyChanged("UploadReadyVis"); | this.RaisePropertyChanged("UploadReadyVis"); | ||||
| this.RaisePropertyChanged("CodeName"); | this.RaisePropertyChanged("CodeName"); | ||||
| this.RaisePropertyChanged("CoverVis"); | this.RaisePropertyChanged("CoverVis"); | ||||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -637,24 +881,93 @@ namespace starter.viewmodel.settings | |||||
| return clickUploadCommand; | return clickUploadCommand; | ||||
| } | } | ||||
| } | } | ||||
| private BaseCommand clickReselectCommand; | |||||
| public BaseCommand ClickReselectCommand | |||||
| private BaseCommand clickAboutUploadCommand; | |||||
| public BaseCommand ClickAboutUploadCommand | |||||
| { | |||||
| get | |||||
| { | |||||
| if (clickAboutUploadCommand == null) | |||||
| { | |||||
| clickAboutUploadCommand = new BaseCommand(new Action<object>(o => | |||||
| { | |||||
| if (obj.UploadReady) | |||||
| { | |||||
| obj.CodeRoute = ""; | |||||
| obj.UploadReady = false; | |||||
| this.RaisePropertyChanged("UploadBtnCont"); | |||||
| this.RaisePropertyChanged("UploadReadyVis"); | |||||
| this.RaisePropertyChanged("CodeName"); | |||||
| this.RaisePropertyChanged("CoverVis"); | |||||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | |||||
| else | |||||
| obj.launchLanguage = SettingsModel.LaunchLanguage.cpp; | |||||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||||
| this.RaisePropertyChanged("ShiftLanguageBtnCont"); | |||||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||||
| } | |||||
| })); | |||||
| } | |||||
| return clickAboutUploadCommand; | |||||
| } | |||||
| } | |||||
| private BaseCommand clickExitCommand; | |||||
| public BaseCommand ClickExitCommand | |||||
| { | |||||
| get | |||||
| { | |||||
| if (clickExitCommand == null) | |||||
| { | |||||
| clickExitCommand = new BaseCommand(new Action<object>(o => | |||||
| { | |||||
| Application.Current.Shutdown(); | |||||
| })); | |||||
| } | |||||
| return clickExitCommand; | |||||
| } | |||||
| } | |||||
| private BaseCommand clickShiftLanguageCommand; | |||||
| public BaseCommand ClickShiftLanguageCommand | |||||
| { | |||||
| get | |||||
| { | |||||
| if (clickShiftLanguageCommand == null) | |||||
| { | |||||
| clickShiftLanguageCommand = new BaseCommand(new Action<object>(o => | |||||
| { | |||||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | |||||
| else | |||||
| obj.launchLanguage = SettingsModel.LaunchLanguage.cpp; | |||||
| this.RaisePropertyChanged("ShiftLanguageBtnCont"); | |||||
| this.RaisePropertyChanged("LaunchBtnCont"); | |||||
| this.RaisePropertyChanged("AbortOrSelLanguage"); | |||||
| })); | |||||
| } | |||||
| return clickShiftLanguageCommand; | |||||
| } | |||||
| } | |||||
| private BaseCommand clickReadCommand; | |||||
| public BaseCommand ClickReadCommand | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| if (clickReselectCommand == null) | |||||
| if (clickReadCommand == null) | |||||
| { | { | ||||
| clickReselectCommand = new BaseCommand(new Action<object>(o => | |||||
| clickReadCommand = new BaseCommand(new Action<object>(o => | |||||
| { | { | ||||
| obj.CodeRoute = ""; | |||||
| obj.UploadReady = false; | |||||
| this.RaisePropertyChanged("UploadBtnCont"); | |||||
| this.RaisePropertyChanged("UploadReadyVis"); | |||||
| this.RaisePropertyChanged("CodeName"); | |||||
| this.RaisePropertyChanged("CoverVis"); | |||||
| if (!Directory.Exists(Route + "/THUAI6")) | |||||
| Route = Route.Substring(0, Route.Length - 7); | |||||
| Program.Data.ResetFilepath(Route); | |||||
| if (Program.Tencent_cos_download.CheckAlreadyDownload()) | |||||
| Status = SettingsModel.Status.login; | |||||
| })); | })); | ||||
| } | } | ||||
| return clickReselectCommand; | |||||
| return clickReadCommand; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -12,7 +12,7 @@ namespace Client | |||||
| public class ArgumentOptions | public class ArgumentOptions | ||||
| { | { | ||||
| [Option('u', "cl", Required = false, HelpText = "Whether to use command line")] | [Option('u', "cl", Required = false, HelpText = "Whether to use command line")] | ||||
| public bool cl { get; set; } = false; | |||||
| public bool cl { get; set; } = true; | |||||
| [Option('i', "ip", Required = false, HelpText = "Client connected ip")] | [Option('i', "ip", Required = false, HelpText = "Client connected ip")] | ||||
| public string Ip { get; set; } = "127.0.0.1"; | public string Ip { get; set; } = "127.0.0.1"; | ||||
| @@ -108,8 +108,8 @@ namespace Client | |||||
| return; | return; | ||||
| } | } | ||||
| _ = Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(o => | _ = Parser.Default.ParseArguments<ArgumentOptions>(args).WithParsed(o => | ||||
| { options = o; }); | |||||
| if ((args.Length == 3 || args.Length == 4) && options != null && Convert.ToInt64(options.PlayerID) > 2023) | |||||
| { options = o; }); | |||||
| if (options != null && Convert.ToInt64(options.PlayerID) > 2023) | |||||
| { | { | ||||
| isSpectatorMode = true; | isSpectatorMode = true; | ||||
| string[] comInfo = new string[3]; | string[] comInfo = new string[3]; | ||||
| @@ -158,6 +158,7 @@ namespace Client | |||||
| if ((map = pbClient.ReadDataFromFile(listOfProp, listOfHuman, listOfButcher, listOfBullet, listOfBombedBullet, listOfAll, listOfChest, listOfClassroom, listOfDoor, listOfHiddenGate, listOfGate, drawPicLock)) != null) | if ((map = pbClient.ReadDataFromFile(listOfProp, listOfHuman, listOfButcher, listOfBullet, listOfBombedBullet, listOfAll, listOfChest, listOfClassroom, listOfDoor, listOfHiddenGate, listOfGate, drawPicLock)) != null) | ||||
| { | { | ||||
| isClientStocked = false; | isClientStocked = false; | ||||
| PorC.Content = "⏸"; | |||||
| isPlaybackMode = true; | isPlaybackMode = true; | ||||
| defaultMap = map; | defaultMap = map; | ||||
| mapFlag = true; | mapFlag = true; | ||||
| @@ -166,6 +167,7 @@ namespace Client | |||||
| { | { | ||||
| MessageBox.Show("Failed to read the playback file!"); | MessageBox.Show("Failed to read the playback file!"); | ||||
| isClientStocked = true; | isClientStocked = true; | ||||
| PorC.Content = "▶"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -569,6 +571,8 @@ namespace Client | |||||
| { | { | ||||
| if (msg.Place == human.Place) | if (msg.Place == human.Place) | ||||
| return true; | return true; | ||||
| if (msg.PlayerId == playerID + Preparation.Utility.GameData.numOfPeople)//robot and its owner | |||||
| return true; | |||||
| } | } | ||||
| else if (!humanOrButcher && butcher != null) | else if (!humanOrButcher && butcher != null) | ||||
| { | { | ||||
| @@ -743,7 +747,7 @@ namespace Client | |||||
| foreach (var data in listOfAll) | foreach (var data in listOfAll) | ||||
| { | { | ||||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID); | |||||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | |||||
| } | } | ||||
| if (!hasDrawed && mapFlag) | if (!hasDrawed && mapFlag) | ||||
| DrawMap(); | DrawMap(); | ||||
| @@ -1028,6 +1032,7 @@ namespace Client | |||||
| BorderBrush = Brushes.Transparent, | BorderBrush = Brushes.Transparent, | ||||
| IsReadOnly = true | IsReadOnly = true | ||||
| }; | }; | ||||
| UpperLayerOfMap.Children.Add(icon); | |||||
| } | } | ||||
| } | } | ||||
| //} | //} | ||||
| @@ -1265,7 +1270,7 @@ namespace Client | |||||
| isClientStocked = true; | isClientStocked = true; | ||||
| PorC.Content = "▶"; | PorC.Content = "▶"; | ||||
| } | } | ||||
| else if (!isPlaybackMode) | |||||
| else | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| "profiles": { | "profiles": { | ||||
| "Client": { | "Client": { | ||||
| "commandName": "Project", | "commandName": "Project", | ||||
| "commandLineArgs": "--cl --playbackFile .\\video.thuaipb" | |||||
| "commandLineArgs": "--port 8888 --characterID 3 --type 1 --occupation 5" | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -41,7 +41,7 @@ namespace Client | |||||
| } | } | ||||
| } | } | ||||
| public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId) | |||||
| public void SetValue(MessageOfAll obj, bool gateOpened, bool hiddenGateRefreshed, bool hiddenGateOpened, long playerId, bool isPlaybackMode) | |||||
| { | { | ||||
| int min, sec; | int min, sec; | ||||
| sec = obj.GameTime / 1000; | sec = obj.GameTime / 1000; | ||||
| @@ -57,17 +57,22 @@ namespace Client | |||||
| { | { | ||||
| time.Text += Convert.ToString(sec); | time.Text += Convert.ToString(sec); | ||||
| } | } | ||||
| if (playerId == GameData.numOfStudent) | |||||
| { | |||||
| name.Text = "🚀 Tricker's"; | |||||
| } | |||||
| else if (playerId < GameData.numOfStudent) | |||||
| { | |||||
| name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s"; | |||||
| } | |||||
| if (isPlaybackMode) | |||||
| name.Text = "🚀 Playback"; | |||||
| else | else | ||||
| { | { | ||||
| name.Text = "🚀 Spectator's"; | |||||
| if (playerId == GameData.numOfStudent) | |||||
| { | |||||
| name.Text = "🚀 Tricker's"; | |||||
| } | |||||
| else if (playerId < GameData.numOfStudent) | |||||
| { | |||||
| name.Text = "🚀 Student" + Convert.ToString(playerId) + "'s"; | |||||
| } | |||||
| else | |||||
| { | |||||
| name.Text = "🚀 Spectator's"; | |||||
| } | |||||
| } | } | ||||
| if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair) | if (obj.SubjectFinished < Preparation.Utility.GameData.numOfGeneratorRequiredForRepair) | ||||
| { | { | ||||
| @@ -141,6 +141,12 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public override void AddScore(int add) | |||||
| { | |||||
| if (parent == null) | |||||
| base.AddScore(add); | |||||
| else parent.AddScore(add); | |||||
| } | |||||
| public Golem(XY initPos, int initRadius, Character? parent) : base(initPos, initRadius, CharacterType.Robot) | public Golem(XY initPos, int initRadius, Character? parent) : base(initPos, initRadius, CharacterType.Robot) | ||||
| { | { | ||||
| this.parent = parent; | this.parent = parent; | ||||
| @@ -349,7 +349,7 @@ namespace GameClass.GameObj | |||||
| /// 加分 | /// 加分 | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="add">增加量</param> | /// <param name="add">增加量</param> | ||||
| public void AddScore(int add) | |||||
| public virtual void AddScore(int add) | |||||
| { | { | ||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| { | { | ||||
| @@ -357,18 +357,6 @@ namespace GameClass.GameObj | |||||
| //Debugger.Output(this, " 's score has been added to: " + score.ToString()); | //Debugger.Output(this, " 's score has been added to: " + score.ToString()); | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// 减分 | |||||
| /// </summary> | |||||
| /// <param name="sub">减少量</param> | |||||
| public void SubScore(int sub) | |||||
| { | |||||
| lock (gameObjLock) | |||||
| { | |||||
| score -= sub; | |||||
| Debugger.Output(this, " 's score has been subed to: " + score.ToString()); | |||||
| } | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// 角色所属队伍ID | /// 角色所属队伍ID | ||||
| @@ -584,7 +572,7 @@ namespace GameClass.GameObj | |||||
| this.Vampire = this.OriVampire; | this.Vampire = this.OriVampire; | ||||
| } | } | ||||
| }*/ | }*/ | ||||
| public void Die(PlayerStateType playerStateType) | |||||
| public void RemoveFromGame(PlayerStateType playerStateType) | |||||
| { | { | ||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| { | { | ||||
| @@ -1,5 +1,5 @@ | |||||
| # 规则 | # 规则 | ||||
| V4.4 | |||||
| V5.0 | |||||
| - [规则](#规则) | - [规则](#规则) | ||||
| - [简则](#简则) | - [简则](#简则) | ||||
| - [地图](#地图) | - [地图](#地图) | ||||
| @@ -44,38 +44,48 @@ V4.4 | |||||
| - [箱子](#箱子-1) | - [箱子](#箱子-1) | ||||
| - [得分](#得分-1) | - [得分](#得分-1) | ||||
| - [信息相关](#信息相关-1) | - [信息相关](#信息相关-1) | ||||
| - [技能](#技能) | |||||
| - [职业](#职业) | |||||
| ## 简则 | ## 简则 | ||||
| - 每场比赛分为上下两个半场,上下半场双方换边 | |||||
| - 单局比赛分为学生阵营(4 人)和捣蛋鬼阵营(1 人) | |||||
| - 最终将两场比赛己方所得分数相加,高者获胜 | |||||
| ### 地图 | ### 地图 | ||||
| - 地图为矩形区域,游戏对象坐标为(x, y),x和y均为整数。 | - 地图为矩形区域,游戏对象坐标为(x, y),x和y均为整数。 | ||||
| - **x坐标轴正方向竖直向下,y坐标轴正方向水平向右**; | - **x坐标轴正方向竖直向下,y坐标轴正方向水平向右**; | ||||
| - **极坐标以x坐标轴为极轴,角度逆时针为正方向**。 | - **极坐标以x坐标轴为极轴,角度逆时针为正方向**。 | ||||
| - 地图由50 * 50个格子构成,其中每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到: | |||||
| - $$CellX=\frac{x}{1000},CellY=\frac{y}{1000}$$ | |||||
| - 地图由50 * 50个格子构成,每个格子代表1000 * 1000的正方形。每个格子的编号(CellX,CellY)可以计算得到: | |||||
| $$ | |||||
| CellX=\frac{x}{1000},CellY=\frac{y}{1000} | |||||
| $$ | |||||
| - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | ||||
| ### 人物 | ### 人物 | ||||
| - 人物半径为800 | |||||
| - 人物直径为800 | |||||
| - 人物共有17种不可叠加的状态: | - 人物共有17种不可叠加的状态: | ||||
| 1. (可)移动状态 | |||||
| 2. 学习 | |||||
| 3. 被勉励 | |||||
| 4. 在勉励 | |||||
| 5. 开或锁门 | |||||
| 6. 翻箱 | |||||
| 7. 使用技能 | |||||
| 8. 开启校门 | |||||
| 9. 唤醒他人中 | |||||
| - 之后八项为不可行动状态 | |||||
| 1. 被唤醒中 | |||||
| 2. 沉迷 | |||||
| 3. 退学 | |||||
| 4. 毕业 | |||||
| 5. 被眩晕 | |||||
| 6. 前摇 | |||||
| 7. 后摇 | |||||
| 8. 翻窗 | |||||
| 1. (可)移动状态 Idel | |||||
| 2. 学习 Learning | |||||
| 3. 被勉励 Encouraged | |||||
| 4. 在勉励 Encouraging | |||||
| 5. 开或锁门 Locking | |||||
| 6. 翻箱 Rummaging | |||||
| 7. 使用技能 UsingSpecialSkill | |||||
| 8. 开启校门 OpeningAGate | |||||
| 9. 唤醒他人中 Rousing | |||||
| - 之后八项为不可接受指令状态 | |||||
| 1. 被唤醒中 Roused | |||||
| 2. 沉迷 Addicted | |||||
| 3. 退学 Quit | |||||
| 4. 毕业 Graduated | |||||
| 5. 被眩晕 Stunned | |||||
| 6. 前摇 Attacking | |||||
| 7. 后摇 Swinging | |||||
| 8. 翻窗 Climbing | |||||
| ### 攻击 | ### 攻击 | ||||
| - 攻击类型CommonAttackOfGhost攻击未写完的作业,会造成对应攻击力的损坏 | - 攻击类型CommonAttackOfGhost攻击未写完的作业,会造成对应攻击力的损坏 | ||||
| @@ -83,10 +93,10 @@ V4.4 | |||||
| | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | | 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty | | ||||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | ||||
| | 子弹爆炸范围 | 0 | 0 | 1000 | 500 | | |||||
| | 子弹攻击距离 | 1100 | 39000 | 1100 | 2200 | | |||||
| | 攻击力 | 1500000 | 1200000 | 1,800,000 | 900000 | | |||||
| | 移动速度/s | 3700 | 9250 | 3000 | 4300 | | |||||
| | 子弹爆炸范围 | 0 | 0 | 2000 | 1000 | | |||||
| | 子弹攻击距离 | 2200 | 78000 | 2200 | 4400 | | |||||
| | 攻击力 | 1500000 | 1200000 | 1800000 | 900000 | | |||||
| | 移动速度/s | 7400 | 18500 | 6000 | 8600 | | |||||
| | 前摇(ms) | 297 | 400 | 366 | - | | | 前摇(ms) | 297 | 400 | 366 | - | | ||||
| |未攻击至目标时的后摇(ms)| 800 | 0 | 3700 | - | | |未攻击至目标时的后摇(ms)| 800 | 0 | 3700 | - | | ||||
| |攻击至目标时的后摇(ms)| 3700 | 0 | 3700 | - | | |攻击至目标时的后摇(ms)| 3700 | 0 | 3700 | - | | ||||
| @@ -99,18 +109,18 @@ V4.4 | |||||
| - 作业,门,箱子完成/开启进度达到10000000为完成 | - 作业,门,箱子完成/开启进度达到10000000为完成 | ||||
| #### 学习与毕业 | #### 学习与毕业 | ||||
| - 每张地图都有10间教室,学生需要完成其中的**7间**教室的作业,才可以开启任意校门。 | |||||
| - 开启校门所需时间为18秒,开启的进度不清空 | |||||
| - 当**3间**教室的作业完成时,隐藏校门在3-5个刷新点之一随机显现;当只剩1名学生时,隐藏校门自动打开。 | |||||
| - 从开启的校门或隐藏校门离开是学生终极目标 | |||||
| - 共有10间教室,学生需要完成**7间**教室的作业,才可以开启校门。 | |||||
| - 开启校门需要18秒,开启进度不清空 | |||||
| - **3间**教室的作业完成时,隐藏校门会在刷新点之一随机显现;当只剩1名学生时,隐藏校门自动打开。 | |||||
| - 从开启的校门或隐藏校门毕业是学生终极目标 | |||||
| #### 勉励 | #### 勉励 | ||||
| - 当被勉励程度达到当前损失的毅力值或1500000时,勉励完成,学生毅力增加对应被勉励程度。 | - 当被勉励程度达到当前损失的毅力值或1500000时,勉励完成,学生毅力增加对应被勉励程度。 | ||||
| - 勉励中断时,被勉励程度保留;遭到攻击时被勉励程度清空 | - 勉励中断时,被勉励程度保留;遭到攻击时被勉励程度清空 | ||||
| #### 沉迷与唤醒 | #### 沉迷与唤醒 | ||||
| - 学习毅力归零时,学生原地进入沉迷状态,每毫秒增加1沉迷度 | |||||
| - 唤醒需要时间1秒,之后学习毅力恢复至1/2。沉迷程度不清空。 | |||||
| - 学习毅力归零时,学生进入沉迷状态,每毫秒增加1沉迷度 | |||||
| - 唤醒需要1秒,之后学习毅力恢复至1/2。沉迷程度不清空。 | |||||
| - 进入沉迷状态时,如果学生原沉迷程度在(0,该玩家最大沉迷度/3)中,沉迷程度直接变为其最大沉迷度/3;原沉迷程度在[其最大沉迷度/3,其最大沉迷度x2/3)中,沉迷程度直接变为其最大沉迷度x2/3;原沉迷程度大于其最大沉迷度x2/3,从游戏中出局; | - 进入沉迷状态时,如果学生原沉迷程度在(0,该玩家最大沉迷度/3)中,沉迷程度直接变为其最大沉迷度/3;原沉迷程度在[其最大沉迷度/3,其最大沉迷度x2/3)中,沉迷程度直接变为其最大沉迷度x2/3;原沉迷程度大于其最大沉迷度x2/3,从游戏中出局; | ||||
| - 当学生沉迷程度达到其最大沉迷程度时,从游戏中出局 | - 当学生沉迷程度达到其最大沉迷程度时,从游戏中出局 | ||||
| @@ -120,7 +130,7 @@ V4.4 | |||||
| - 锁门过程中,门所在格子内有人会使锁门过程中断 | - 锁门过程中,门所在格子内有人会使锁门过程中断 | ||||
| #### 窗 | #### 窗 | ||||
| - 翻窗时玩家应当在窗前后左右一个格子内 | |||||
| - 翻窗时玩家应当在窗前后一个格子内 | |||||
| #### 箱子 | #### 箱子 | ||||
| - 开箱后将有2个随机道具掉落在玩家位置。 | - 开箱后将有2个随机道具掉落在玩家位置。 | ||||
| @@ -130,7 +140,7 @@ V4.4 | |||||
| 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(int)(警戒半径/二者距离) | 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(int)(警戒半径/二者距离) | ||||
| 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(int)(警戒半径/可被发觉的最近的学生距离) | 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(int)(警戒半径/可被发觉的最近的学生距离) | ||||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=(int)((警戒半径x学习进度百分比)/二者距离) | 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=(int)((警戒半径x学习进度百分比)/二者距离) | ||||
| - 可以向其他每一个队友发送不超过256字节的信息 | |||||
| - 可以向每一个队友发送不超过256字节的信息 | |||||
| ### 可视范围 | ### 可视范围 | ||||
| - 小于视野半径,且受限于墙和草地 | - 小于视野半径,且受限于墙和草地 | ||||
| @@ -176,12 +186,12 @@ V4.4 | |||||
| | 捣蛋鬼职业 | Assassin | Klee | 喧哗者ANoisyPerson | Idol | | | 捣蛋鬼职业 | Assassin | Klee | 喧哗者ANoisyPerson | Idol | | ||||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | ||||
| | 移动速度/s | 1980 | 1800 | 1926 | 1,800 | | |||||
| | 移动速度/s | 3960 | 3600 | 3852 | 3600 | | |||||
| | 隐蔽度 | 1.5 | 1 | 0.8 | 0.75| | | 隐蔽度 | 1.5 | 1 | 0.8 | 0.75| | ||||
| | 警戒范围 | 22,100 | 17000 | 15300 | 17000 | | 警戒范围 | 22,100 | 17000 | 15300 | 17000 | ||||
| | 视野范围 | 15600 | 13000 | 13000 | 14300| | | 视野范围 | 15600 | 13000 | 13000 | 14300| | ||||
| | 开锁门速度 | 4000 | 4000 | 4000 |4000 | | | 开锁门速度 | 4000 | 4000 | 4000 |4000 | | ||||
| | 翻窗速度 | 1270 | 1270 | 1,397 | 1270| | |||||
| | 翻窗速度 | 2540 | 2540 | 2794 | 2540| | |||||
| | 翻箱速度 | 1000 | 1100 | 1000 |1000| | | 翻箱速度 | 1000 | 1100 | 1000 |1000| | ||||
| #### Assassin | #### Assassin | ||||
| @@ -230,7 +240,7 @@ V4.4 | |||||
| | 学生职业 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine | | | 学生职业 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine | | ||||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | ||||
| | 移动速度 | 1350 | 1575 | 1440 | 1500 | | |||||
| | 移动速度 | 2700 | 3150 | 2880 | 3000 | | |||||
| | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | | | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | | ||||
| | 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 | | | 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 | | ||||
| | 学习一科速度 | 0 | 73 | 135 | 123 | | | 学习一科速度 | 0 | 73 | 135 | 123 | | ||||
| @@ -239,7 +249,7 @@ V4.4 | |||||
| | 警戒范围 | 7500 | 15000 | 13,500 | 15000 | | | 警戒范围 | 7500 | 15000 | 13,500 | 15000 | | ||||
| | 视野范围 | 9,000 | 11000 | 9,000 | 10000 | | | 视野范围 | 9,000 | 11000 | 9,000 | 10000 | | ||||
| | 开锁门速度 | 4000 | 4000 | 4000 | 2800 | | | 开锁门速度 | 4000 | 4000 | 4000 | 2800 | | ||||
| | 翻窗速度 | 635 | 1,524 | 1,058 | 1270 | | |||||
| | 翻窗速度 | 1270 | 3048 | 2116 | 2540 | | |||||
| | 翻箱速度 | 1000 | 1000 | 1000 | 900 | | | 翻箱速度 | 1000 | 1000 | 1000 | 900 | | ||||
| #### 运动员 | #### 运动员 | ||||
| @@ -262,13 +272,13 @@ V4.4 | |||||
| #### 学霸 | #### 学霸 | ||||
| - 特性 | - 特性 | ||||
| - 冥想 | - 冥想 | ||||
| - 当玩家处于可接受指令状态且不在修机时,会积累学习进度,速度为40/ms | |||||
| - 受到攻击(并非伤害)或学习或进入不可接受治疗状态(包括翻窗)学习进度清零 | |||||
| - 当处于可接受指令状态且不在学习时,会积累“冥想进度”,速度为40/ms | |||||
| - 受到攻击(并非伤害)、进入学习状态或进入不可接受指令状态(包括翻窗)冥想进度清零 | |||||
| - 主动技能5 | - 主动技能5 | ||||
| - 写答案 WriteAnswers | - 写答案 WriteAnswers | ||||
| - CD:30s | - CD:30s | ||||
| - 使用瞬间,对于可互动范围内的一台电机增加这个学习进度 | |||||
| - 通过修机获得对应得分 | |||||
| - 使用瞬间,对于可交互范围内的一间教室的作业增加冥想进度,冥想进度清零 | |||||
| - 通过学习获得对应得分 | |||||
| #### 开心果 | #### 开心果 | ||||
| - 主动技能 | - 主动技能 | ||||
| @@ -295,6 +305,7 @@ V4.4 | |||||
| ### 人物 | ### 人物 | ||||
| - EndAllAction()及Move指令调用数总和一帧内不超过10次 | - EndAllAction()及Move指令调用数总和一帧内不超过10次 | ||||
| - 眩晕状态中的玩家不能再次被眩晕 | |||||
| ### 初始状态 | ### 初始状态 | ||||
| - 玩家出生点固定且一定为空地 | - 玩家出生点固定且一定为空地 | ||||
| @@ -346,4 +357,10 @@ V4.4 | |||||
| - 眩晕或毅力值归零时无牵制得分 | - 眩晕或毅力值归零时无牵制得分 | ||||
| ### 信息相关 | ### 信息相关 | ||||
| - Bgm在没有符合条件的情况下,值为0。 | |||||
| - Bgm在没有符合条件的情况下,值为0。 | |||||
| ### 技能 | |||||
| - CD冷却计时是在开始使用技能的瞬间开始的 | |||||
| ### 职业 | |||||
| - 学生职业可以重复选择 | |||||
| @@ -43,6 +43,14 @@ namespace Gaming | |||||
| return true; | return true; | ||||
| } | } | ||||
| public bool MovePlayerWhenStunned(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||||
| { | |||||
| if (!playerToMove.Commandable() && playerToMove.PlayerState != PlayerStateType.Stunned) return false; | |||||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Stunned); | |||||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||||
| return true; | |||||
| } | |||||
| public bool Stop(Character player) | public bool Stop(Character player) | ||||
| { | { | ||||
| if (player.Commandable() || !TryToStop()) | if (player.Commandable() || !TryToStop()) | ||||
| @@ -129,7 +137,7 @@ namespace Gaming | |||||
| { | { | ||||
| player.AddScore(GameData.StudentScoreEscape); | player.AddScore(GameData.StudentScoreEscape); | ||||
| ++gameMap.NumOfEscapedStudent; | ++gameMap.NumOfEscapedStudent; | ||||
| player.Die(PlayerStateType.Escaped); | |||||
| player.RemoveFromGame(PlayerStateType.Escaped); | |||||
| return true; | return true; | ||||
| } | } | ||||
| else | else | ||||
| @@ -139,7 +147,7 @@ namespace Gaming | |||||
| { | { | ||||
| player.AddScore(GameData.StudentScoreEscape); | player.AddScore(GameData.StudentScoreEscape); | ||||
| ++gameMap.NumOfEscapedStudent; | ++gameMap.NumOfEscapedStudent; | ||||
| player.Die(PlayerStateType.Escaped); | |||||
| player.RemoveFromGame(PlayerStateType.Escaped); | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| @@ -155,7 +155,7 @@ namespace Gaming | |||||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | ||||
| else newPlayer.AddBgm(BgmType.GhostIsComing, 0); | else newPlayer.AddBgm(BgmType.GhostIsComing, 0); | ||||
| } | } | ||||
| if (newPlayer.CharacterType != CharacterType.Teacher && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.Distance(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.Distance(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||||
| { | { | ||||
| TimePinningDown += GameData.checkInterval; | TimePinningDown += GameData.checkInterval; | ||||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | ||||
| @@ -378,12 +378,12 @@ namespace Gaming | |||||
| return true; | return true; | ||||
| } | } | ||||
| private void Die(Character player) | |||||
| public void Die(Student player) | |||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| Debugger.Output(player, "die."); | Debugger.Output(player, "die."); | ||||
| #endif | #endif | ||||
| player.Die(PlayerStateType.Deceased); | |||||
| player.RemoveFromGame(PlayerStateType.Deceased); | |||||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | ||||
| { | { | ||||
| @@ -49,8 +49,25 @@ namespace Gaming | |||||
| { | { | ||||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | ||||
| { | { | ||||
| if (!person.IsGhost()) | |||||
| actionManager.MovePlayer(person, GameData.frameDuration, (player.Position - person.Position).Angle()); | |||||
| if (!person.IsGhost() && player.CharacterType != CharacterType.Robot && !person.NoHp()) | |||||
| { | |||||
| double dis = XY.Distance(person.Position, player.Position); | |||||
| if (dis >= player.AlertnessRadius) | |||||
| { | |||||
| person.AddMoveSpeed(GameData.checkIntervalWhenShowTime, dis / player.AlertnessRadius); | |||||
| actionManager.MovePlayerWhenStunned(person, GameData.checkIntervalWhenShowTime, (player.Position - person.Position).Angle()); | |||||
| } | |||||
| else if (dis >= player.ViewRange) | |||||
| { | |||||
| Student student = (Student)person; | |||||
| student.GamingAddiction += GameData.checkIntervalWhenShowTime; | |||||
| if (student.GamingAddiction == student.MaxGamingAddiction) | |||||
| { | |||||
| player.AddScore(GameData.TrickerScoreStudentDie); | |||||
| characterManager.Die(student); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| finally | finally | ||||
| @@ -58,7 +75,7 @@ namespace Gaming | |||||
| gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | gameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | ||||
| } | } | ||||
| }, | }, | ||||
| timeInterval: GameData.frameDuration, | |||||
| timeInterval: GameData.checkIntervalWhenShowTime, | |||||
| finallyReturn: () => 0 | finallyReturn: () => 0 | ||||
| ) | ) | ||||
| @@ -86,14 +103,15 @@ namespace Gaming | |||||
| { }); | { }); | ||||
| } | } | ||||
| public static bool UseRobot(Character player) | |||||
| public bool UseRobot(Character player) | |||||
| { | { | ||||
| IGolem? golem = (IGolem?)(((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned); | IGolem? golem = (IGolem?)(((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned); | ||||
| Debugger.Output(player, (golem != null).ToString()); | |||||
| if ((!player.Commandable()) || ((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned == null) return false; | if ((!player.Commandable()) || ((SummonGolem)player.FindIActiveSkill(ActiveSkillType.SummonGolem)).GolemSummoned == null) return false; | ||||
| Debugger.Output(player, player.PlayerID.ToString()); | |||||
| Debugger.Output(player, "use robot!"); | |||||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.UseRobot); | IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.UseRobot); | ||||
| activeSkill.IsBeingUsed = (activeSkill.IsBeingUsed) ? false : true; | activeSkill.IsBeingUsed = (activeSkill.IsBeingUsed) ? false : true; | ||||
| if (activeSkill.IsBeingUsed) characterManager.SetPlayerState(player, PlayerStateType.UsingSkill); | |||||
| else characterManager.SetPlayerState(player); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -167,7 +185,7 @@ namespace Gaming | |||||
| { | { | ||||
| foreach (Character character in gameMap.GameObjDict[GameObjType.Character]) | foreach (Character character in gameMap.GameObjDict[GameObjType.Character]) | ||||
| { | { | ||||
| if (!character.IsGhost() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||||
| if (!character.IsGhost() && !character.NoHp() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||||
| { | { | ||||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | ||||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | ||||
| @@ -123,7 +123,7 @@ namespace Preparation.Interface | |||||
| public class ShowTime : IActiveSkill | public class ShowTime : IActiveSkill | ||||
| { | { | ||||
| public int SkillCD => GameData.commonSkillCD * 3; | |||||
| public int SkillCD => GameData.commonSkillCD * 8 / 3; | |||||
| public int DurationTime => GameData.commonSkillTime; | public int DurationTime => GameData.commonSkillTime; | ||||
| private readonly object commonSkillLock = new(); | private readonly object commonSkillLock = new(); | ||||
| @@ -93,8 +93,8 @@ namespace Preparation.Utility | |||||
| public const int basicTreatSpeed = 100; | public const int basicTreatSpeed = 100; | ||||
| public const int basicFixSpeed = 123; | public const int basicFixSpeed = 123; | ||||
| public const int basicSpeedOfOpeningOrLocking = 4000; | public const int basicSpeedOfOpeningOrLocking = 4000; | ||||
| public const int basicStudentSpeedOfClimbingThroughWindows = 611; | |||||
| public const int basicGhostSpeedOfClimbingThroughWindows = 1270; | |||||
| public const int basicStudentSpeedOfClimbingThroughWindows = 1222; | |||||
| public const int basicGhostSpeedOfClimbingThroughWindows = 2540; | |||||
| public const int basicSpeedOfOpenChest = 1000; | public const int basicSpeedOfOpenChest = 1000; | ||||
| public const int basicHp = 3000000; // 初始血量 | public const int basicHp = 3000000; // 初始血量 | ||||
| @@ -105,9 +105,9 @@ namespace Preparation.Utility | |||||
| public const int basicTimeOfRescue = 1000; | public const int basicTimeOfRescue = 1000; | ||||
| #if DEBUG | #if DEBUG | ||||
| public const int basicStudentMoveSpeed = 9000;// 基本移动速度,单位:s-1 | |||||
| public const int basicStudentMoveSpeed = 3000;// 基本移动速度,单位:s-1 | |||||
| #else | #else | ||||
| public const int basicStudentMoveSpeed = 1500; | |||||
| public const int basicStudentMoveSpeed = 3000; | |||||
| #endif | #endif | ||||
| public const int basicGhostMoveSpeed = (int)(basicStudentMoveSpeed * 1.2); | public const int basicGhostMoveSpeed = (int)(basicStudentMoveSpeed * 1.2); | ||||
| @@ -200,10 +200,10 @@ namespace Preparation.Utility | |||||
| public const int basicRecoveryFromHit = 3700;//基本命中攻击恢复时长 | public const int basicRecoveryFromHit = 3700;//基本命中攻击恢复时长 | ||||
| public const int basicStunnedTimeOfStudent = 4300; | public const int basicStunnedTimeOfStudent = 4300; | ||||
| public const int basicBulletMoveSpeed = 3700; // 基本子弹移动速度,单位:s-1 | |||||
| public const double basicRemoteAttackRange = 3000; // 基本远程攻击范围 | |||||
| public const double basicAttackShortRange = 1100; // 基本近程攻击范围 | |||||
| public const double basicBulletBombRange = 1000; // 基本子弹爆炸范围 | |||||
| public const int basicBulletMoveSpeed = 7400; // 基本子弹移动速度,单位:s-1 | |||||
| public const double basicRemoteAttackRange = 6000; // 基本远程攻击范围 | |||||
| public const double basicAttackShortRange = 2200; // 基本近程攻击范围 | |||||
| public const double basicBulletBombRange = 2000; // 基本子弹爆炸范围 | |||||
| #endregion | #endregion | ||||
| #region 技能相关 | #region 技能相关 | ||||
| public const int maxNumOfSkill = 3; | public const int maxNumOfSkill = 3; | ||||
| @@ -2,7 +2,7 @@ | |||||
| "profiles": { | "profiles": { | ||||
| "Server": { | "Server": { | ||||
| "commandName": "Project", | "commandName": "Project", | ||||
| "commandLineArgs": "--ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 30 --fileName E:\\GSY\\软件部\\THUAI6-dev\\THUAI6\\logic\\cmd\\test.thuaipb --playback true" | |||||
| "commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030" | |||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -4,14 +4,14 @@ start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 -- | |||||
| ping -n 2 127.0.0.1 > NUL | ping -n 2 127.0.0.1 > NUL | ||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 4 --type 2 --occupation 4 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 4 --type 2 --occupation 1 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 0 --type 1 --occupation 5 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 0 --type 1 --occupation 1 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 1 --type 1 --occupation 5 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 1 --type 1 --occupation 2 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2 --type 1 --occupation 3 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 2 --type 1 --occupation 5 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 3 --type 1 --occupation 3 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 3 --type 1 --occupation 5 | |||||
| ::start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 | |||||
| ::start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 2030 | |||||
| @@ -1,5 +1,5 @@ | |||||
| @echo off | @echo off | ||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --playbackFile .\video.thuaipb --playbackSpeed 1 | |||||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --playbackFile .\test.thuaipb --playbackSpeed 2 | |||||
| ping -n 2 127.0.0.1 > NUL | ping -n 2 127.0.0.1 > NUL | ||||
| @@ -1,28 +0,0 @@ | |||||
| ## 键鼠控制 | |||||
| | 键位 | 效果 | | |||||
| | ------------ | ---------------------------------------------- | | |||||
| | W/NumPad8 | (Both)向上移动 | | |||||
| | S/NumPad2 | (Both)向下移动 | | |||||
| | D/NumPad6 | (Both)向右移动 | | |||||
| | A/NumPad4 | (Both)向左移动 | | |||||
| | J | (Tri)攻击,方向向上 | | |||||
| | 鼠标双击某点 | (Tri)攻击,方向与从Tricker指向该点的向量相同 | | |||||
| | K | (Stu)开始学习 | | |||||
| | R | (Stu)开始唤醒(陷入沉迷状态的同伴) | | |||||
| | T | (Stu)开始勉励(学习毅力下降的同伴) | | |||||
| | G | (Stu)发出毕业请求 | | |||||
| | H | (Stu)申请毕业(或称为开校门) | | |||||
| | O | (Both)开(教学楼)门 | | |||||
| | P | (Both)关(教学楼)门 | | |||||
| | U | (Both)翻窗 | | |||||
| | I | (Both)翻箱子 | | |||||
| | E | (Both)结束当前行动,回到Idle状态 | | |||||
| | F | (Both)随机捡起一个在周围的道具 | | |||||
| | C | (Both)随机扔下一个已经持有的道具 | | |||||
| | V | (Both)随机使用一个已经持有的道具 | | |||||
| | B | (Both)使用0号技能 | | |||||
| | N | (Both)使用1号技能 | | |||||
| | M | (Both)使用2号技能 | | |||||
| @@ -1,19 +1,32 @@ | |||||
| # 使用文档 | # 使用文档 | ||||
| 本文档仅供Windows选手参考,Linux选手仿照本文档即可 | |||||
| 本文档仅供Windows选手参考,Linux选手仿照本文档即可。 | |||||
| ## 路径 | |||||
| ## C++接口使用说明 | |||||
| Windows选手使用脚本位于Win文件夹下,Linux选手使用脚本位于Linux文件夹下 | |||||
| - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||||
| - Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。 | |||||
| - 然后在`CAPI\cpp\API\src\AI.cpp`中编写代码 | |||||
| - Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式 | |||||
| - Linux:用`cmake`,对`CAPI\cpp\CMakeLists.txt`进行编译 | |||||
| - Windows:最后使用`RunCpp.cmd`执行比赛代码 | |||||
| - Linux:最后使用`RunCpp.sh`执行比赛代码 | |||||
| ## 游戏启动方式(For Debug) | |||||
| ## Python接口使用说明 | |||||
| - 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件 | |||||
| - 然后在`CAPI\python\PyAPI\AI.py`中编写代码 | |||||
| - Windows:最后通过运行`RunPython.cmd`执行比赛代码 | |||||
| - Linux:通过运行`RunPython.sh`执行比赛代码 | |||||
| ## 游戏基本启动方式(For Debug) | |||||
| 1. 首先启动Server:`RunServerForDebug.cmd` | 1. 首先启动Server:`RunServerForDebug.cmd` | ||||
| 2. 再启动Client:Python使用`RunPython.cmd`,C++使用`RunCpp.cmd` | 2. 再启动Client:Python使用`RunPython.cmd`,C++使用`RunCpp.cmd` | ||||
| ## 实机体验游戏(ServerForPlay) | |||||
| ## 启动服务器(RunServer) | |||||
| 启动游戏:ServerForPlay.cmd,其内部参数可自主设定,设定方式:右键-编辑 | |||||
| 启动服务器:`RunServer.cmd`,其内部参数可自主设定,设定方式:右键-打开更多选项-编辑 | |||||
| Server脚本中参数格式一般如下: | Server脚本中参数格式一般如下: | ||||
| @@ -44,7 +57,9 @@ Server脚本中参数格式一般如下: | |||||
| `--fileName`为回放文件的保存名称 | `--fileName`为回放文件的保存名称 | ||||
| 此外,Server中还可以默认打开一个/多个WPF客户端,一般格式如下: | |||||
| ## 启动WPF客户端(RunClient) | |||||
| 通过`RunGUIClient.cmd`,可以打开WPF客户端,其内部参数可自主设定,设定方式:右键-打开更多选项-编辑 | |||||
| ```shell | ```shell | ||||
| --port 8888 --characterID 4 --type 2 --occupation 1 | --port 8888 --characterID 4 --type 2 --occupation 1 | ||||
| @@ -68,6 +83,16 @@ Server脚本中参数格式一般如下: | |||||
| `--occupation`为职业选择,参考下表 | `--occupation`为职业选择,参考下表 | ||||
| ### 观看回放文件 | |||||
| 通过WPF客户端,还可以观看比赛回放。在`RunGUIClient.cmd`中,将参数改为 | |||||
| ```shell | |||||
| start cmd /k win64\Client.exe --cl --playbackFile .\video.thuaipb --playbackSpeed 2 | |||||
| ``` | |||||
| 即可观看回放文件。其中`--playbackFile`后面的参数为回放文件的路径,`--playbackSpeed`后面的参数为回放速度,取值为1~10,1为正常速度,10为最快速度。 | |||||
| ### 职业对应表: | ### 职业对应表: | ||||
| * 学生 | * 学生 | ||||
| @@ -94,11 +119,7 @@ Server脚本中参数格式一般如下: | |||||
| ## Client | ## Client | ||||
| ### C++接口 | |||||
| 选手用Visual Studio打开CAPI.sln,编写AI.cpp,建议用Debug模式生成以方便自己调试,然后可以使用`RunCpp.cmd`启动。 | |||||
| `RunCpp.cmd`的脚本参数格式如下: | |||||
| `RunCpp.cmd`或`RunPython.cmd`的脚本参数格式如下: | |||||
| ```shell | ```shell | ||||
| -I 127.0.0.1 -P 8888 -p 0 -d -o -w | -I 127.0.0.1 -P 8888 -p 0 -d -o -w | ||||
| @@ -116,30 +137,40 @@ Server脚本中参数格式一般如下: | |||||
| `-w`只在开启`-o`的情况下生效,此时屏幕上只输出警告或报错(而日志文件依然完整保存)。 | `-w`只在开启`-o`的情况下生效,此时屏幕上只输出警告或报错(而日志文件依然完整保存)。 | ||||
| ## WPF观看回放 | |||||
| 使用`RunPlayback.cmd`,默认观看当前目录下的 video.thuaipb。 | |||||
| > 可以通过更改脚本中FileName参数,来观看不同的回放文件 | |||||
| ## cmd脚本的参数修改 | ## cmd脚本的参数修改 | ||||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | ||||
| ## WPF简易调试界面 | ## WPF简易调试界面 | ||||
|  | |||||
|  | |||||
| ### 界面介绍 | ### 界面介绍 | ||||
| * 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门 | |||||
| * 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门,浅橙红色代表隐藏校门 | |||||
| * 动态方块:蓝色方块代表大门,橙色方块代表箱子,粉色方块代表教室,上面的数字均代表进度 | * 动态方块:蓝色方块代表大门,橙色方块代表箱子,粉色方块代表教室,上面的数字均代表进度 | ||||
| * 物件:橙色圆代表捣蛋鬼,紫色圆代表学生,且上面的编号对应学生的ID | |||||
| * 物件:橙色圆代表捣蛋鬼,紫色圆代表学生,且上面的编号对应学生的ID,灰色表示Robot,它的编号表示所属的TechOtaku | |||||
| * 道具对应表如下 | |||||
| | 道具类型 | UI | | |||||
| | ---------------------- | --- | | |||||
| | Key3 | 🔑3 | | |||||
| | Key5 | 🔑5 | | |||||
| | Key6 | 🔑6 | | |||||
| | AddSpeed | ⛸ | | |||||
| | AddLifeOrClairaudience | 🏅 | | |||||
| | AddHpOrAp | ♥ | | |||||
| | ShieldOrSpear: | 🛡 | | |||||
| | RecoveryFromDizziness | 🕶 | | |||||
| * UI:左侧为UI,表示游戏内各类信息 | * UI:左侧为UI,表示游戏内各类信息 | ||||
| ### 实机体验之键鼠操作 | ### 实机体验之键鼠操作 | ||||
| 仅当启动ServerForPlay.cmd时可以使用,观看回放时不能使用 | |||||
| 仅当以玩家身份启动`RunGUIClient.cmd`时可以使用,观看回放时不能使用 | |||||
| | 键位 | 效果 | | | 键位 | 效果 | | ||||
| | ------------ | ---------------------------------------------- | | | ------------ | ---------------------------------------------- | | ||||
| @@ -164,4 +195,4 @@ Server脚本中参数格式一般如下: | |||||
| | V | (Both)随机使用一个已经持有的道具 | | | V | (Both)随机使用一个已经持有的道具 | | ||||
| | B | (Both)使用0号技能 | | | B | (Both)使用0号技能 | | ||||
| | N | (Both)使用1号技能 | | | N | (Both)使用1号技能 | | ||||
| | M | (Both)使用2号技能 | | |||||
| | M | (Both)使用2号技能 | | |||||