Browse Source

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

tags/0.1.0
gsy1519 2 years ago
parent
commit
c061c3045b
65 changed files with 1202 additions and 434 deletions
  1. +20
    -9
      CAPI/Tool_tutorial.md
  2. +13
    -0
      CAPI/cmd/GeneratePythonProto.cmd
  3. +7
    -0
      CAPI/cmd/RunCpp.cmd
  4. +6
    -0
      CAPI/cmd/RunGUIClient.cmd
  5. +7
    -0
      CAPI/cmd/RunPython.cmd
  6. +5
    -0
      CAPI/cmd/RunServer.cmd
  7. +5
    -0
      CAPI/cmd/RunServerForDebug.cmd
  8. +7
    -3
      CAPI/cpp/API/include/AI.h
  9. +10
    -9
      CAPI/cpp/API/include/constants.h
  10. +2
    -2
      CAPI/cpp/API/include/structures.h
  11. +1
    -1
      CAPI/cpp/API/include/utils.hpp
  12. +30
    -12
      CAPI/cpp/API/src/AI.cpp
  13. +1
    -1
      CAPI/cpp/API/src/logic.cpp
  14. +14
    -5
      CAPI/cpp/API/src/main.cpp
  15. +25
    -0
      CAPI/cpp/spdlog/LICENSE
  16. +24
    -112
      CAPI/python/PyAPI/AI.py
  17. +6
    -2
      CAPI/python/PyAPI/Communication.py
  18. +13
    -12
      CAPI/python/PyAPI/constants.py
  19. +9
    -7
      CAPI/python/PyAPI/logic.py
  20. +9
    -3
      CAPI/python/PyAPI/main.py
  21. +1
    -1
      CAPI/python/PyAPI/structures.py
  22. +1
    -1
      CAPI/python/PyAPI/utils.py
  23. +4
    -3
      CAPI/python/run.sh
  24. +3
    -0
      CAPI/shell/GenerateCppProto.sh
  25. +12
    -0
      CAPI/shell/GeneratePythonProto.sh
  26. +6
    -0
      CAPI/shell/RunCpp.sh
  27. +6
    -0
      CAPI/shell/RunPython.sh
  28. +3
    -0
      CAPI/shell/RunServer.sh
  29. +3
    -0
      CAPI/shell/RunServerForDebug.sh
  30. BIN
      CAPI/src/image-20230416010705076.png
  31. BIN
      CAPI/src/image-20230416010816392.png
  32. +64
    -0
      Q&A.md
  33. +16
    -10
      dependency/Dockerfile/Dockerfile_cpp
  34. +8
    -0
      dependency/shell/publish.cmd
  35. +38
    -0
      dependency/shell/publish.sh
  36. +1
    -0
      installer/Installer/Installer.csproj
  37. +10
    -7
      installer/Installer/MainWindow.xaml
  38. +254
    -39
      installer/Installer/Model.cs
  39. +353
    -40
      installer/Installer/ViewModel.cs
  40. +1
    -1
      logic/Client/CommandLineArgs.cs
  41. +9
    -4
      logic/Client/MainWindow.xaml.cs
  42. +2
    -2
      logic/Client/Properties/launchSettings.json
  43. +15
    -10
      logic/Client/StatusBarOfCircumstance.xaml.cs
  44. +6
    -0
      logic/GameClass/GameObj/Character/Character.Student.cs
  45. +2
    -14
      logic/GameClass/GameObj/Character/Character.cs
  46. +60
    -43
      logic/GameRules.md
  47. +10
    -2
      logic/Gaming/ActionManager.cs
  48. +3
    -3
      logic/Gaming/CharacterManager .cs
  49. +25
    -7
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  50. +1
    -1
      logic/Preparation/Interface/ISkill.cs
  51. +8
    -8
      logic/Preparation/Utility/GameData.cs
  52. +2
    -2
      logic/Server/Properties/launchSettings.json
  53. +6
    -6
      logic/cmd/gameServer.cmd
  54. +1
    -1
      logic/cmd/playback.cmd
  55. +0
    -28
      logic/使用文档.md
  56. BIN
      resource/Nocplus.png
  57. BIN
      resource/Q&A_grpc.png
  58. BIN
      resource/RunCppCmd.png
  59. BIN
      resource/RunPython.png
  60. BIN
      resource/client.png
  61. BIN
      resource/client2.png
  62. BIN
      resource/howtouseif.png
  63. BIN
      resource/std_find_trivial.jpg
  64. BIN
      resource/项目属性.png
  65. +54
    -23
      使用文档.md

+ 20
- 9
CAPI/Tool_tutorial.md View File

@@ -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脚本的参数修改
![image-20230416010705076](src/image-20230416010705076.png)

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

### 命令行参数的设置

左上方菜单栏 `调试->调试属性`

![image-20230416010816392](src/image-20230416010816392.png)

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

### 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)

+ 13
- 0
CAPI/cmd/GeneratePythonProto.cmd View File

@@ -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

+ 7
- 0
CAPI/cmd/RunCpp.cmd View File

@@ -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

+ 6
- 0
CAPI/cmd/RunGUIClient.cmd View File

@@ -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

+ 7
- 0
CAPI/cmd/RunPython.cmd View File

@@ -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

+ 5
- 0
CAPI/cmd/RunServer.cmd View File

@@ -0,0 +1,5 @@
@echo off

.\win64\Server.exe --port 8888 --studentCount 1 --trickerCount 1 --gameTimeInSecond 600 --fileName video

pause

+ 5
- 0
CAPI/cmd/RunServerForDebug.cmd View File

@@ -0,0 +1,5 @@
@echo off

.\win64\Debug\Server.exe --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600

pause

+ 7
- 3
CAPI/cpp/API/include/AI.h View File

@@ -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

+ 10
- 9
CAPI/cpp/API/include/constants.h View File

@@ -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;
}; };




+ 2
- 2
CAPI/cpp/API/include/structures.h View File

@@ -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"},


+ 1
- 1
CAPI/cpp/API/include/utils.hpp View File

@@ -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},


+ 30
- 12
CAPI/cpp/API/src/AI.cpp View File

@@ -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();
} }

+ 1
- 1
CAPI/cpp/API/src/logic.cpp View File

@@ -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)
{ {


+ 14
- 5
CAPI/cpp/API/src/main.cpp View File

@@ -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[])


+ 25
- 0
CAPI/cpp/spdlog/LICENSE View File

@@ -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

+ 24
- 112
CAPI/python/PyAPI/AI.py View File

@@ -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

+ 6
- 2
CAPI/python/PyAPI/Communication.py View File

@@ -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


+ 13
- 12
CAPI/python/PyAPI/constants.py View File

@@ -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)






+ 9
- 7
CAPI/python/PyAPI/logic.py View File

@@ -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
- 3
CAPI/python/PyAPI/main.py View File

@@ -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__':


+ 1
- 1
CAPI/python/PyAPI/structures.py View File

@@ -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


+ 1
- 1
CAPI/python/PyAPI/utils.py View File

@@ -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,


+ 4
- 3
CAPI/python/run.sh View File

@@ -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 &

+ 3
- 0
CAPI/shell/GenerateCppProto.sh View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash

./CAPI/proto/cpp_output.sh

+ 12
- 0
CAPI/shell/GeneratePythonProto.sh View File

@@ -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



+ 6
- 0
CAPI/shell/RunCpp.sh View File

@@ -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 &

+ 6
- 0
CAPI/shell/RunPython.sh View File

@@ -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 &

+ 3
- 0
CAPI/shell/RunServer.sh View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash

./linux64/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600

+ 3
- 0
CAPI/shell/RunServerForDebug.sh View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash

./linux64/Debug/Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600

BIN
CAPI/src/image-20230416010705076.png View File

Before After
Width: 397  |  Height: 95  |  Size: 5.3 kB

BIN
CAPI/src/image-20230416010816392.png View File

Before After
Width: 1182  |  Height: 819  |  Size: 52 kB

+ 64
- 0
Q&A.md View File

@@ -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项目已卸载
![Nocplus](.\resource\Nocplus.png)

A:可能是没有安装C++

Q:CAPI.sln编译不通过
![std_find_trivial](.\resource\std_find_trivial.jpg)

A:
出现_std_find_trivial_1的报错就是没更新到VS2022,
对于VS2022依旧报错_std_find_trivial_1的,先考虑是否版本过旧
![项目属性](.\resource\项目属性.png)
确保上图项目属性中平台工具集在V143及以上,C++17标准

## Python

### grpc版本更新失败

Q:运行GeneratePythonProto.cmd报错
![grpcUpdate](.\resource\Q&A_grpc.png)

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:初赛结束会调数值及机制,增加新角色

dependency/Dockerfile/Dockerfile → dependency/Dockerfile/Dockerfile_cpp View File

@@ -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

+ 8
- 0
dependency/shell/publish.cmd View File

@@ -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

+ 38
- 0
dependency/shell/publish.sh View File

@@ -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

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

@@ -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" />


+ 10
- 7
installer/Installer/MainWindow.xaml View File

@@ -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>


+ 254
- 39
installer/Installer/Model.cs View File

@@ -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


+ 353
- 40
installer/Installer/ViewModel.cs View File

@@ -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;
} }
} }
} }


+ 1
- 1
logic/Client/CommandLineArgs.cs View File

@@ -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";


+ 9
- 4
logic/Client/MainWindow.xaml.cs View File

@@ -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
- 2
logic/Client/Properties/launchSettings.json View File

@@ -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"
} }
} }
}
}

+ 15
- 10
logic/Client/StatusBarOfCircumstance.xaml.cs View File

@@ -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)
{ {


+ 6
- 0
logic/GameClass/GameObj/Character/Character.Student.cs View File

@@ -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;


+ 2
- 14
logic/GameClass/GameObj/Character/Character.cs View File

@@ -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)
{ {


+ 60
- 43
logic/GameRules.md View File

@@ -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冷却计时是在开始使用技能的瞬间开始的

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

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

@@ -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;


+ 3
- 3
logic/Gaming/CharacterManager .cs View File

@@ -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++)
{ {


+ 25
- 7
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -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));


+ 1
- 1
logic/Preparation/Interface/ISkill.cs View File

@@ -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();


+ 8
- 8
logic/Preparation/Utility/GameData.cs View File

@@ -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
- 2
logic/Server/Properties/launchSettings.json View File

@@ -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"
} }
} }
}
}

+ 6
- 6
logic/cmd/gameServer.cmd View File

@@ -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
- 1
logic/cmd/playback.cmd View File

@@ -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

+ 0
- 28
logic/使用文档.md View File

@@ -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号技能 |

BIN
resource/Nocplus.png View File

Before After
Width: 734  |  Height: 635  |  Size: 27 kB

BIN
resource/Q&A_grpc.png View File

Before After
Width: 1503  |  Height: 461  |  Size: 59 kB

BIN
resource/RunCppCmd.png View File

Before After
Width: 1695  |  Height: 339  |  Size: 45 kB

BIN
resource/RunPython.png View File

Before After
Width: 1585  |  Height: 483  |  Size: 53 kB

BIN
resource/client.png View File

Before After
Width: 1622  |  Height: 1092  |  Size: 135 kB Width: 10848  |  Height: 6780  |  Size: 8.8 MB

BIN
resource/client2.png View File

Before After
Width: 1650  |  Height: 1107  |  Size: 149 kB

BIN
resource/howtouseif.png View File

Before After
Width: 1863  |  Height: 1117  |  Size: 180 kB

BIN
resource/std_find_trivial.jpg View File

Before After
Width: 1920  |  Height: 1080  |  Size: 669 kB

BIN
resource/项目属性.png View File

Before After
Width: 1170  |  Height: 1084  |  Size: 81 kB

+ 54
- 23
使用文档.md View File

@@ -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简易调试界面


![client](D:\2_autumn\thuai6\THUAI6\resource\client.png)
![client](.\resource\client.png)


### 界面介绍 ### 界面介绍


* 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门
* 固定方块:红色方块代表墙,绿色方块代表草,灰色方块代表窗户,黄色带锁标志的方块代表门,浅橙红色代表隐藏校门

* 动态方块:蓝色方块代表大门,橙色方块代表箱子,粉色方块代表教室,上面的数字均代表进度 * 动态方块:蓝色方块代表大门,橙色方块代表箱子,粉色方块代表教室,上面的数字均代表进度
* 物件:橙色圆代表捣蛋鬼,紫色圆代表学生,且上面的编号对应学生的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号技能 |

Loading…
Cancel
Save