Browse Source

finish merge

tags/v0.1.0
sendssf 2 years ago
parent
commit
85cb29bacf
100 changed files with 2954 additions and 1119 deletions
  1. +3
    -2
      .github/workflows/upload_COS.yml
  2. +4
    -0
      CAPI/cpp/API/include/AI.h
  3. +27
    -6
      CAPI/cpp/API/include/API.h
  4. +5
    -1
      CAPI/cpp/API/include/Communication.h
  5. +4
    -0
      CAPI/cpp/API/include/ConcurrentQueue.hpp
  6. +58
    -0
      CAPI/cpp/API/include/constants.h
  7. +7
    -4
      CAPI/cpp/API/include/logic.h
  8. +4
    -0
      CAPI/cpp/API/include/state.h
  9. +11
    -0
      CAPI/cpp/API/include/structures.h
  10. +15
    -2
      CAPI/cpp/API/include/utils.hpp
  11. +2
    -2
      CAPI/cpp/API/src/AI.cpp
  12. +33
    -4
      CAPI/cpp/API/src/API.cpp
  13. +10
    -3
      CAPI/cpp/API/src/Communication.cpp
  14. +45
    -8
      CAPI/cpp/API/src/DebugAPI.cpp
  15. +37
    -29
      CAPI/cpp/API/src/logic.cpp
  16. +30
    -0
      CAPI/cpp/API/src/main.cpp
  17. +160
    -79
      CAPI/cpp/proto/Message2Clients.pb.cc
  18. +251
    -46
      CAPI/cpp/proto/Message2Clients.pb.h
  19. +136
    -54
      CAPI/cpp/proto/Message2Server.pb.cc
  20. +251
    -46
      CAPI/cpp/proto/Message2Server.pb.h
  21. +3
    -3
      CAPI/python/PyAPI/AI.py
  22. +11
    -5
      CAPI/python/PyAPI/API.py
  23. +4
    -8
      CAPI/python/PyAPI/Communication.py
  24. +11
    -5
      CAPI/python/PyAPI/DebugAPI.py
  25. +10
    -2
      CAPI/python/PyAPI/Interface.py
  26. +21
    -9
      CAPI/python/PyAPI/State.py
  27. +44
    -1
      CAPI/python/PyAPI/constants.py
  28. +42
    -29
      CAPI/python/PyAPI/logic.py
  29. +26
    -0
      CAPI/python/PyAPI/main.py
  30. +71
    -61
      CAPI/python/PyAPI/structures.py
  31. +6
    -9
      CAPI/python/PyAPI/utils.py
  32. +1
    -0
      CAPI/python/requirements.txt
  33. +5
    -5
      CAPI/python/run.sh
  34. +7
    -3
      dependency/Dockerfile/Dockerfile_run
  35. +457
    -0
      dependency/algorithm/README.md
  36. +5
    -1
      dependency/proto/Message2Clients.proto
  37. +6
    -1
      dependency/proto/Message2Server.proto
  38. +3
    -3
      dependency/proto/Protos.csproj
  39. +9
    -0
      dependency/shell/README.md
  40. +10
    -6
      dependency/shell/compile.sh
  41. +44
    -9
      dependency/shell/run.sh
  42. +21
    -7
      docs/CAPI接口(cpp).md
  43. +26
    -11
      docs/CAPI接口(python).md
  44. +25
    -26
      docs/GameRules.md
  45. +40
    -13
      docs/QandA.md
  46. +85
    -47
      docs/Tool_tutorial.md
  47. +7
    -2
      docs/使用文档.md
  48. +16
    -16
      installer/Installer/Common.cs
  49. +13
    -0
      installer/Installer/Installer.csproj
  50. +3
    -1
      installer/Installer/MainWindow.xaml
  51. +94
    -76
      installer/Installer/Model.cs
  52. +35
    -34
      installer/Installer/ViewModel.cs
  53. BIN
      installer/Installer/eesast_software_trans_enlarged.ico
  54. +8
    -0
      installer/InstallerUpdater/InstallerUpdater.csproj
  55. +3
    -1
      installer/InstallerUpdater/MainWindow.xaml
  56. +14
    -10
      installer/InstallerUpdater/Program.cs
  57. +2
    -6
      logic/Client/Client.csproj
  58. BIN
      logic/Client/EESASTLogo.png
  59. BIN
      logic/Client/Logo.png
  60. +3
    -2
      logic/Client/MainWindow.xaml
  61. +63
    -61
      logic/Client/MainWindow.xaml.cs
  62. +2
    -2
      logic/Client/Properties/launchSettings.json
  63. +4
    -4
      logic/Client/StatusBarOfHunter.xaml.cs
  64. +4
    -4
      logic/Client/StatusBarOfSurvivor.xaml.cs
  65. BIN
      logic/Client/eesast_software_trans.ico
  66. BIN
      logic/Client/eesast_software_trans_enlarged.ico
  67. +1
    -1
      logic/ClientTest/ClientTest.csproj
  68. +0
    -1
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  69. +1
    -0
      logic/GameClass/GameObj/Bullet/Bullet.cs
  70. +33
    -13
      logic/GameClass/GameObj/Character/Character.cs
  71. +0
    -1
      logic/GameClass/GameObj/Map/Chest.cs
  72. +1
    -1
      logic/GameClass/GameObj/Map/Generator.cs
  73. +30
    -12
      logic/GameClass/GameObj/Map/Map.cs
  74. +0
    -1
      logic/GameEngine/CollisionChecker.cs
  75. +117
    -82
      logic/GameEngine/MoveEngine.cs
  76. +23
    -15
      logic/Gaming/ActionManager.cs
  77. +9
    -6
      logic/Gaming/AttackManager.cs
  78. +36
    -19
      logic/Gaming/CharacterManager .cs
  79. +1
    -6
      logic/Gaming/Game.cs
  80. +1
    -0
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  81. +13
    -26
      logic/Gaming/SkillManager/SkillManager.cs
  82. +1
    -0
      logic/Preparation/Interface/ICharacter.cs
  83. +1
    -1
      logic/Preparation/Preparation.csproj
  84. +2
    -1
      logic/Preparation/Utility/GameData.cs
  85. +3
    -1
      logic/README.md
  86. +5
    -2
      logic/Server/ArgumentOption.cs
  87. +1
    -3
      logic/Server/CopyInfo.cs
  88. +51
    -25
      logic/Server/GameServer.cs
  89. +70
    -22
      logic/Server/PlaybackServer.cs
  90. +68
    -55
      logic/Server/Program.cs
  91. +2
    -2
      logic/Server/Properties/launchSettings.json
  92. +102
    -46
      logic/Server/RpcServices.cs
  93. +2
    -2
      logic/Server/Server.csproj
  94. +14
    -0
      logic/Server/ServerBase.cs
  95. +1
    -5
      logic/cmd/PlaybackServer.cmd
  96. +7
    -0
      logic/cmd/gameServerAndClient.cmd
  97. +1
    -1
      playback/Playback/Playback.csproj
  98. BIN
      resource/AIcpp.png
  99. BIN
      resource/AIpy.png
  100. BIN
      resource/CompileFaster.png

+ 3
- 2
.github/workflows/upload_COS.yml View File

@@ -113,6 +113,9 @@ jobs:
name: my-artifact
path: ./THUAI6

- name: Remove ReadMe.md
run: rm ./docs/README.md

- name: Markdown to PDF and HTML
uses: BaileyJM02/markdown-to-pdf@v1.2.0
with:
@@ -145,7 +148,6 @@ jobs:
rm ./THUAI6/win/win64/WindowsBase.dll
rm ./THUAI6/win/win64/Debug/grpc_csharp_ext.x64.dll
rm ./THUAI6/win/win64/grpc_csharp_ext.x64.dll

rm -r ./THUAI6/win/CAPI/cpp/grpc
rm -r ./THUAI6/win/CAPI/cpp/spdlog
rm -r ./THUAI6/win/CAPI/cpp/tclap
@@ -162,7 +164,6 @@ jobs:
rm ./THUAI6/osx/osx64/Debug/System.*.dll
rm ./THUAI6/win/win64/System.*.dll
rm ./THUAI6/win/win64/Debug/System.*.dll

rm ./THUAI6/linux/linux64/*.so
rm ./THUAI6/linux/linux64/Debug/*.so


+ 4
- 0
CAPI/cpp/API/include/AI.h View File

@@ -4,6 +4,10 @@

#include "API.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

class IAI
{
public:


+ 27
- 6
CAPI/cpp/API/include/API.h View File

@@ -22,6 +22,10 @@

#include "structures.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

const constexpr int numOfGridPerCell = 1000;

class IAI;
@@ -57,7 +61,7 @@ public:
virtual bool UseProp(THUAI6::PropType prop) = 0;
virtual bool ThrowProp(THUAI6::PropType prop) = 0;
virtual bool UseSkill(int32_t skillID) = 0;
virtual bool SendMessage(int64_t toID, std::string message) = 0;
virtual bool SendMessage(int64_t toID, std::string message, bool binary) = 0;
virtual bool HaveMessage() = 0;
virtual std::pair<int64_t, std::string> GetMessage() = 0;

@@ -83,6 +87,8 @@ public:
virtual bool Attack(double angle) = 0;

virtual std::vector<int64_t> GetPlayerGUIDs() const = 0;

[[nodiscard]] virtual bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const = 0;
};

class IAPI
@@ -113,7 +119,8 @@ public:
virtual std::future<bool> EndAllAction() = 0;

// 发送信息、接受信息,注意收消息时无消息则返回nullopt
virtual std::future<bool> SendMessage(int64_t, std::string) = 0;
virtual std::future<bool> SendTextMessage(int64_t, std::string) = 0;
virtual std::future<bool> SendBinaryMessage(int64_t, std::string) = 0;
[[nodiscard]] virtual bool HaveMessage() = 0;
[[nodiscard]] virtual std::pair<int64_t, std::string> GetMessage() = 0;

@@ -162,6 +169,8 @@ public:
return grid / numOfGridPerCell;
}

[[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0;

// 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用

virtual void Print(std::string str) const = 0;
@@ -238,7 +247,8 @@ public:
std::future<bool> StartOpenChest() override;
std::future<bool> EndAllAction() override;

std::future<bool> SendMessage(int64_t, std::string) override;
std::future<bool> SendTextMessage(int64_t, std::string) override;
std::future<bool> SendBinaryMessage(int64_t, std::string) override;
[[nodiscard]] bool HaveMessage() override;
[[nodiscard]] std::pair<int64_t, std::string> GetMessage() override;

@@ -271,6 +281,8 @@ public:
std::future<bool> Graduate() override;
[[nodiscard]] std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override;

[[nodiscard]] bool HaveView(int gridX, int gridY) const override;

void Print(std::string str) const override
{
}
@@ -326,7 +338,8 @@ public:
std::future<bool> StartOpenChest() override;
std::future<bool> EndAllAction() override;

std::future<bool> SendMessage(int64_t, std::string) override;
std::future<bool> SendTextMessage(int64_t, std::string) override;
std::future<bool> SendBinaryMessage(int64_t, std::string) override;
[[nodiscard]] bool HaveMessage() override;
[[nodiscard]] std::pair<int64_t, std::string> GetMessage() override;

@@ -356,6 +369,8 @@ public:
std::future<bool> Attack(double angleInRadian) override;
[[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override;

[[nodiscard]] bool HaveView(int gridX, int gridY) const override;

void Print(std::string str) const override
{
}
@@ -406,7 +421,8 @@ public:
std::future<bool> StartOpenChest() override;
std::future<bool> EndAllAction() override;

std::future<bool> SendMessage(int64_t, std::string) override;
std::future<bool> SendTextMessage(int64_t, std::string) override;
std::future<bool> SendBinaryMessage(int64_t, std::string) override;
[[nodiscard]] bool HaveMessage() override;
[[nodiscard]] std::pair<int64_t, std::string> GetMessage() override;

@@ -439,6 +455,8 @@ public:
std::future<bool> Graduate() override;
[[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override;

[[nodiscard]] bool HaveView(int gridX, int gridY) const override;

void Print(std::string str) const override;
void PrintStudent() const override;
void PrintTricker() const override;
@@ -479,7 +497,8 @@ public:
std::future<bool> StartOpenChest() override;
std::future<bool> EndAllAction() override;

std::future<bool> SendMessage(int64_t, std::string) override;
std::future<bool> SendTextMessage(int64_t, std::string) override;
std::future<bool> SendBinaryMessage(int64_t, std::string) override;
[[nodiscard]] bool HaveMessage() override;
[[nodiscard]] std::pair<int64_t, std::string> GetMessage() override;

@@ -509,6 +528,8 @@ public:
std::future<bool> Attack(double angleInRadian) override;
[[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override;

[[nodiscard]] bool HaveView(int gridX, int gridY) const override;

void Print(std::string str) const override;
void PrintStudent() const override;
void PrintTricker() const override;


+ 5
- 1
CAPI/cpp/API/include/Communication.h View File

@@ -14,6 +14,10 @@
#include <queue>
#include <atomic>

#undef GetMessage
#undef SendMessage
#undef PeekMessage

class Logic;

class Communication
@@ -28,7 +32,7 @@ public:
bool UseProp(THUAI6::PropType prop, int64_t playerID);
bool ThrowProp(THUAI6::PropType prop, int64_t playerID);
bool UseSkill(int32_t skillID, int64_t playerID);
bool SendMessage(int64_t toID, std::string message, int64_t playerID);
bool SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID);
bool OpenDoor(int64_t playerID);
bool CloseDoor(int64_t playerID);
bool SkipWindow(int64_t playerID);


+ 4
- 0
CAPI/cpp/API/include/ConcurrentQueue.hpp View File

@@ -8,6 +8,10 @@
#include <utility>
#include <optional>

#undef GetMessage
#undef SendMessage
#undef PeekMessage

template<typename Elem>
class ConcurrentQueue
{


+ 58
- 0
CAPI/cpp/API/include/constants.h View File

@@ -6,6 +6,10 @@
#define SCCI static const constexpr inline
#endif

#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace Constants
{
SCCI int frameDuration = 50; // 每帧毫秒数
@@ -303,5 +307,59 @@ namespace Constants
SCCI int skillCD = commonSkillCD * 1;
SCCI int durationTime = commonSkillTime * 0;
};

struct CommonAttackOfTricker
{
SCCI double BulletBombRange = 0;
SCCI double BulletAttackRange = basicAttackShortRange;
SCCI int ap = basicApOfTricker;
SCCI int Speed = basicBulletMoveSpeed;
SCCI bool IsRemoteAttack = false;

SCCI int CastTime = (int)BulletAttackRange * 1000 / Speed;
SCCI int Backswing = basicBackswing;
SCCI int RecoveryFromHit = basicRecoveryFromHit;
SCCI int cd = basicBackswing;
SCCI int maxBulletNum = 1;
};

struct FlyingKnife
{
SCCI double BulletBombRange = 0;
SCCI double BulletAttackRange = basicRemoteAttackRange * 13;
SCCI int ap = basicApOfTricker * 4 / 5;
SCCI int Speed = basicBulletMoveSpeed * 25 / 10;
SCCI bool IsRemoteAttack = true;

SCCI int CastTime = basicCastTime * 4 / 5;
SCCI int Backswing = 0;
SCCI int RecoveryFromHit = 0;
SCCI int cd = basicBackswing / 2;
SCCI int maxBulletNum = 1;
};

struct BombBomb
{
SCCI double BulletBombRange = basicBulletBombRange;
SCCI double BulletAttackRange = basicAttackShortRange;
SCCI int ap = basicApOfTricker * 6 / 5;
SCCI int Speed = basicBulletMoveSpeed * 30 / 37;
SCCI bool IsRemoteAttack = false;

SCCI int CastTime = (int)BulletAttackRange * 1000 / Speed;
SCCI int Backswing = basicRecoveryFromHit;
SCCI int RecoveryFromHit = basicRecoveryFromHit;
SCCI int cd = basicCD;
SCCI int maxBulletNum = 1;
};

struct JumpyDumpty
{
SCCI double BulletBombRange = basicBulletBombRange / 2;
SCCI double BulletAttackRange = basicRemoteAttackRange * 2;
SCCI int ap = (int)(basicApOfTricker * 0.6);
SCCI int Speed = basicBulletMoveSpeed * 43 / 37;
SCCI bool IsRemoteAttack = false;
};
} // namespace Constants
#endif

+ 7
- 4
CAPI/cpp/API/include/logic.h View File

@@ -31,6 +31,10 @@
#include "Communication.h"
#include "ConcurrentQueue.hpp"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

// 封装了通信组件和对AI对象进行操作
class Logic : public ILogic
{
@@ -49,9 +53,6 @@ private:
THUAI6::TrickerType trickerType;
THUAI6::StudentType studentType;

// GUID信息
std::vector<int64_t> playerGUIDs;

std::unique_ptr<IGameTimer> timer;

std::thread tAI; // 用于运行AI的线程
@@ -117,7 +118,7 @@ private:
bool ThrowProp(THUAI6::PropType prop) override;
bool UseSkill(int32_t skillID) override;

bool SendMessage(int64_t toID, std::string message) override;
bool SendMessage(int64_t toID, std::string message, bool binary) override;
bool HaveMessage() override;
std::pair<int64_t, std::string> GetMessage() override;

@@ -161,6 +162,8 @@ private:
// 等待
void Wait() noexcept;

[[nodiscard]] bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const override;

public:
// 构造函数还需要传更多参数,有待补充
Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student);


+ 4
- 0
CAPI/cpp/API/include/state.h View File

@@ -9,6 +9,10 @@

#include "structures.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

// 存储场上的状态
struct State
{


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

@@ -8,6 +8,10 @@
#include <vector>
#include <string>

#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace THUAI6
{

@@ -168,6 +172,13 @@ namespace THUAI6
Opened = 2,
};

enum class NewsType : unsigned char
{
NullNewsType = 0,
TextMessage = 1,
BinaryMessage = 2,
};

// 玩家类
struct Player
{


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

@@ -13,6 +13,10 @@

#include "structures.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

namespace AssistFunction
{

@@ -202,6 +206,12 @@ namespace Proto2THUAI6

};

inline std::map<protobuf::MessageOfNews::NewsCase, THUAI6::NewsType> newsTypeDict{
{protobuf::MessageOfNews::NewsCase::NEWS_NOT_SET, THUAI6::NewsType::NullNewsType},
{protobuf::MessageOfNews::NewsCase::kTextMessage, THUAI6::NewsType::TextMessage},
{protobuf::MessageOfNews::NewsCase::kBinaryMessage, THUAI6::NewsType::BinaryMessage},
};

// 用于将Protobuf中的类转换为THUAI6的类
inline std::shared_ptr<THUAI6::Tricker> Protobuf2THUAI6Tricker(const protobuf::MessageOfTricker& trickerMsg)
{
@@ -456,10 +466,13 @@ namespace THUAI62Proto
return pickMsg;
}

inline protobuf::SendMsg THUAI62ProtobufSend(std::string msg, int64_t toID, int64_t id)
inline protobuf::SendMsg THUAI62ProtobufSend(std::string msg, int64_t toID, bool binary, int64_t id)
{
protobuf::SendMsg sendMsg;
sendMsg.set_message(msg);
if (binary)
sendMsg.set_binary_message(msg);
else
sendMsg.set_text_message(msg);
sendMsg.set_to_player_id(toID);
sendMsg.set_player_id(id);
return sendMsg;


+ 2
- 2
CAPI/cpp/API/src/AI.cpp View File

@@ -5,7 +5,7 @@
#include "constants.h"
// 注意不要使用conio.h,Windows.h等非标准库

// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
// 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次
extern const bool asynchronous = false;

// 选手需要依次将player0到player4的职业在这里定义
@@ -20,7 +20,7 @@ extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin;

// 可以在AI.cpp内部声明变量与函数

void AI::play(IStudentAPI& api)
void AI::play(IStudentAPI& api) // 每帧执行一次AI::play(IStudentAPI& api)或AI::play(ITrickerAPI& api)(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
{
// 公共操作
if (this->playerID == 0)


+ 33
- 4
CAPI/cpp/API/src/API.cpp View File

@@ -1,6 +1,11 @@
#include <optional>
#include "AI.h"
#include "API.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

#define PI 3.14159265358979323846

int StudentAPI::GetFrameCount() const
@@ -185,16 +190,28 @@ std::future<bool> TrickerAPI::EndAllAction()
{ return logic.EndAllAction(); });
}

std::future<bool> StudentAPI::SendMessage(int64_t toID, std::string message)
std::future<bool> StudentAPI::SendTextMessage(int64_t toID, std::string message)
{
return std::async(std::launch::async, [=]()
{ return logic.SendMessage(toID, message, false); });
}

std::future<bool> TrickerAPI::SendTextMessage(int64_t toID, std::string message)
{
return std::async(std::launch::async, [=]()
{ return logic.SendMessage(toID, message, false); });
}

std::future<bool> StudentAPI::SendBinaryMessage(int64_t toID, std::string message)
{
return std::async(std::launch::async, [=]()
{ return logic.SendMessage(toID, message); });
{ return logic.SendMessage(toID, message, false); });
}

std::future<bool> TrickerAPI::SendMessage(int64_t toID, std::string message)
std::future<bool> TrickerAPI::SendBinaryMessage(int64_t toID, std::string message)
{
return std::async(std::launch::async, [=]()
{ return logic.SendMessage(toID, message); });
{ return logic.SendMessage(toID, message, false); });
}

bool StudentAPI::HaveMessage()
@@ -419,6 +436,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerAPI::GetSelfInfo() const
return logic.TrickerGetSelfInfo();
}

bool StudentAPI::HaveView(int gridX, int gridY) const
{
auto selfInfo = GetSelfInfo();
return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange);
}

bool TrickerAPI::HaveView(int gridX, int gridY) const
{
auto selfInfo = GetSelfInfo();
return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange);
}

void StudentAPI::Play(IAI& ai)
{
ai.play(*this);


+ 10
- 3
CAPI/cpp/API/src/Communication.cpp View File

@@ -5,6 +5,10 @@
#include <mutex>
#include <condition_variable>

#undef GetMessage
#undef SendMessage
#undef PeekMessage

using grpc::ClientContext;

Communication::Communication(std::string sIP, std::string sPort)
@@ -74,11 +78,11 @@ bool Communication::UseSkill(int32_t skillID, int64_t playerID)
return false;
}

bool Communication::SendMessage(int64_t toID, std::string message, int64_t playerID)
bool Communication::SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID)
{
protobuf::BoolRes sendMessageResult;
ClientContext context;
auto request = THUAI62Proto::THUAI62ProtobufSend(message, toID, playerID);
auto request = THUAI62Proto::THUAI62ProtobufSend(message, toID, binary, playerID);
auto status = THUAI6Stub->SendMessage(&context, request, &sendMessageResult);
if (status.ok())
return sendMessageResult.act_success();
@@ -249,10 +253,13 @@ void Communication::AddPlayer(int64_t playerID, THUAI6::PlayerType playerType, T
grpc::ClientContext context;
auto MessageReader = THUAI6Stub->AddPlayer(&context, playerMsg);

while (MessageReader->Read(&message2Client))
protobuf::MessageToClient buffer2Client;

while (MessageReader->Read(&buffer2Client))
{
{
std::lock_guard<std::mutex> lock(mtxMessage);
message2Client = std::move(buffer2Client);
haveNewMessage = true;
}
cvMessage.notify_one();


+ 45
- 8
CAPI/cpp/API/src/DebugAPI.cpp View File

@@ -4,6 +4,11 @@
#include "API.h"
#include "utils.hpp"
#include "structures.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

#define PI 3.14159265358979323846

StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) :
@@ -347,23 +352,43 @@ std::future<bool> TrickerDebugAPI::EndAllAction()
return result; });
}

std::future<bool> StudentDebugAPI::SendMessage(int64_t toID, std::string message)
std::future<bool> StudentDebugAPI::SendTextMessage(int64_t toID, std::string message)
{
logger->info("SendTextMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.SendMessage(toID, message, false);
if (!result)
logger->warn("SendTextMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

std::future<bool> TrickerDebugAPI::SendTextMessage(int64_t toID, std::string message)
{
logger->info("SendTextMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.SendMessage(toID, message, false);
if (!result)
logger->warn("SendTextMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

std::future<bool> StudentDebugAPI::SendBinaryMessage(int64_t toID, std::string message)
{
logger->info("SendMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
logger->info("SendBinaryMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.SendMessage(toID, message);
{ auto result = logic.SendMessage(toID, message, true);
if (!result)
logger->warn("SendMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("SendBinaryMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

std::future<bool> TrickerDebugAPI::SendMessage(int64_t toID, std::string message)
std::future<bool> TrickerDebugAPI::SendBinaryMessage(int64_t toID, std::string message)
{
logger->info("SendMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
logger->info("SendBinaryMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint));
return std::async(std::launch::async, [=]()
{ auto result = logic.SendMessage(toID, message);
{ auto result = logic.SendMessage(toID, message, true);
if (!result)
logger->warn("SendMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
logger->warn("SendBinaryMessage: failed at {}ms", Time::TimeSinceStart(startPoint));
return result; });
}

@@ -631,6 +656,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerDebugAPI::GetSelfInfo() const
return logic.TrickerGetSelfInfo();
}

bool StudentDebugAPI::HaveView(int gridX, int gridY) const
{
auto selfInfo = GetSelfInfo();
return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange);
}

bool TrickerDebugAPI::HaveView(int gridX, int gridY) const
{
auto selfInfo = GetSelfInfo();
return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange);
}

void StudentDebugAPI::Print(std::string str) const
{
logger->info(str);


+ 37
- 29
CAPI/cpp/API/src/logic.cpp View File

@@ -8,6 +8,10 @@
#include "utils.hpp"
#include "Communication.h"

#undef GetMessage
#undef SendMessage
#undef PeekMessage

extern const bool asynchronous;

Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student) :
@@ -210,10 +214,10 @@ bool Logic::UseSkill(int32_t skill)
return pComm->UseSkill(skill, playerID);
}

bool Logic::SendMessage(int64_t toID, std::string message)
bool Logic::SendMessage(int64_t toID, std::string message, bool binary)
{
logger->debug("Called SendMessage");
return pComm->SendMessage(toID, message, playerID);
return pComm->SendMessage(toID, message, binary, playerID);
}

bool Logic::HaveMessage()
@@ -323,17 +327,6 @@ void Logic::ProcessMessage()
case THUAI6::GameState::GameStart:
logger->info("Game Start!");

// 重新读取玩家的guid,保证人类在前屠夫在后
playerGUIDs.clear();
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
playerGUIDs.push_back(obj.student_message().guid());
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
playerGUIDs.push_back(obj.tricker_message().guid());
currentState->guids = playerGUIDs;
bufferState->guids = playerGUIDs;

// 读取地图
for (const auto& item : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage)
@@ -368,16 +361,6 @@ void Logic::ProcessMessage()

break;
case THUAI6::GameState::GameRunning:
// 重新读取玩家的guid,guid确保人类在前屠夫在后
playerGUIDs.clear();
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
playerGUIDs.push_back(obj.student_message().guid());
for (const auto& obj : clientMsg.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
playerGUIDs.push_back(obj.tricker_message().guid());
currentState->guids = playerGUIDs;
bufferState->guids = playerGUIDs;

LoadBuffer(clientMsg);
break;
@@ -386,7 +369,6 @@ void Logic::ProcessMessage()
break;
}
}
AILoop = false;
{
std::lock_guard<std::mutex> lock(mtxBuffer);
bufferUpdated = true;
@@ -394,6 +376,7 @@ void Logic::ProcessMessage()
}
cvBuffer.notify_one();
logger->info("Game End!");
AILoop = false;
};
std::thread(messageThread).detach();
}
@@ -564,7 +547,7 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item)
}
case THUAI6::MessageOfObj::GateMessage:
{
if (!AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap))
if (AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap))
{
auto pos = std::make_pair(AssistFunction::GridToCell(item.gate_message().x()), AssistFunction::GridToCell(item.gate_message().y()));
if (bufferState->mapInfo->gateState.count(pos) == 0)
@@ -584,7 +567,20 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item)
{
auto news = item.news_message();
if (news.to_id() == playerID)
messageQueue.emplace(std::make_pair(news.from_id(), news.news()));
{
if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::TextMessage)
{
messageQueue.emplace(std::make_pair(news.to_id(), news.text_message()));
logger->debug("Add News!");
}
else if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::BinaryMessage)
{
messageQueue.emplace(std::make_pair(news.to_id(), news.binary_message()));
logger->debug("Add News!");
}
else
logger->error("Unknown NewsType!");
}
break;
}
case THUAI6::MessageOfObj::NullMessageOfObj:
@@ -605,9 +601,16 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message)
bufferState->props.clear();
bufferState->bullets.clear();
bufferState->bombedBullets.clear();
bufferState->guids.clear();

logger->debug("Buffer cleared!");
// 读取新的信息
for (const auto& obj : message.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage)
bufferState->guids.push_back(obj.student_message().guid());
for (const auto& obj : message.obj_message())
if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage)
bufferState->guids.push_back(obj.tricker_message().guid());
bufferState->gameInfo = Proto2THUAI6::Protobuf2THUAI6GameInfo(message.all_message());
LoadBufferSelf(message);
for (const auto& item : message.obj_message())
@@ -618,6 +621,7 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message)
{
std::lock_guard<std::mutex> lock(mtxState);
std::swap(currentState, bufferState);
counterState = counterBuffer;
logger->info("Update State!");
}
freshed = true;
@@ -686,6 +690,12 @@ bool Logic::TryConnection()
return pComm->TryConnection(playerID);
}

bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const
{
std::unique_lock<std::mutex> lock(mtxState);
return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap);
}

void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool file, bool print, bool warnOnly)
{
// 建立日志组件
@@ -695,9 +705,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f
fileLogger->set_pattern(pattern);
printLogger->set_pattern(pattern);
if (file)
{
fileLogger->set_level(spdlog::level::trace);
}
fileLogger->set_level(spdlog::level::debug);
else
fileLogger->set_level(spdlog::level::off);
if (print)


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

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

#undef GetMessage
#undef SendMessage
#undef PeekMessage

#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif

using namespace std::literals::string_view_literals;

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

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

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


)welcome"sv;

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

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

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


+ 160
- 79
CAPI/cpp/proto/Message2Clients.pb.cc View File

@@ -312,7 +312,7 @@ namespace protobuf
::_pbi::ConstantInitialized
) :
_impl_{
/*decltype(_impl_.news_)*/ {&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}, /*decltype(_impl_.from_id_)*/ int64_t{0}, /*decltype(_impl_.to_id_)*/ int64_t{0}, /*decltype(_impl_._cached_size_)*/ {}}
/*decltype(_impl_.
from_id_)*/ int64_t{0}, /*decltype(_impl_.to_id_)*/ int64_t{0}, /*decltype(_impl_.news_)*/ {}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}
{
}
struct MessageOfNewsDefaultTypeInternal
@@ -612,12 +612,14 @@ const uint32_t TableStruct_Message2Clients_2eproto::offsets[] PROTOBUF_SECTION_V
~0u, // no _has_bits_
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _internal_metadata_),
~0u, // no _extensions_
~0u, // no _oneof_case_
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_._oneof_case_[0]),
~0u, // no _weak_field_map_
~0u, // no _inlined_string_donated_
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.news_),
::_pbi::kInvalidFieldOffsetTag,
::_pbi::kInvalidFieldOffsetTag,
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.from_id_),
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.to_id_),
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.news_),
~0u, // no _has_bits_
PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfObj, _internal_metadata_),
~0u, // no _extensions_
@@ -690,11 +692,11 @@ static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protode
{149, -1, -1, sizeof(::protobuf::MessageOfMap_Row)},
{156, -1, -1, sizeof(::protobuf::MessageOfMap)},
{163, -1, -1, sizeof(::protobuf::MessageOfNews)},
{172, -1, -1, sizeof(::protobuf::MessageOfObj)},
{191, -1, -1, sizeof(::protobuf::MessageOfAll)},
{203, -1, -1, sizeof(::protobuf::MessageToClient)},
{212, -1, -1, sizeof(::protobuf::MoveRes)},
{221, -1, -1, sizeof(::protobuf::BoolRes)},
{174, -1, -1, sizeof(::protobuf::MessageOfObj)},
{193, -1, -1, sizeof(::protobuf::MessageOfAll)},
{205, -1, -1, sizeof(::protobuf::MessageToClient)},
{214, -1, -1, sizeof(::protobuf::MoveRes)},
{223, -1, -1, sizeof(::protobuf::BoolRes)},
};

static const ::_pb::Message* const file_default_instances[] = {
@@ -775,36 +777,37 @@ const char descriptor_table_protodef_Message2Clients_2eproto[] PROTOBUF_SECTION_
"\001(\005\022\t\n\001y\030\002 \001(\005\022\020\n\010progress\030\003 \001(\005\"`\n\014Mess"
"ageOfMap\022\'\n\003row\030\002 \003(\0132\032.protobuf.Message"
"OfMap.Row\032\'\n\003Row\022 \n\003col\030\001 \003(\0162\023.protobuf"
".PlaceType\"=\n\rMessageOfNews\022\014\n\004news\030\001 \001("
"\t\022\017\n\007from_id\030\002 \001(\003\022\r\n\005to_id\030\003 \001(\003\"\244\005\n\014Me"
"ssageOfObj\0225\n\017student_message\030\001 \001(\0132\032.pr"
"otobuf.MessageOfStudentH\000\0225\n\017tricker_mes"
"sage\030\002 \001(\0132\032.protobuf.MessageOfTrickerH\000"
"\022/\n\014prop_message\030\003 \001(\0132\027.protobuf.Messag"
"eOfPropH\000\0223\n\016bullet_message\030\004 \001(\0132\031.prot"
"obuf.MessageOfBulletH\000\022@\n\025bombed_bullet_"
"message\030\005 \001(\0132\037.protobuf.MessageOfBombed"
"BulletH\000\0229\n\021classroom_message\030\006 \001(\0132\034.pr"
"otobuf.MessageOfClassroomH\000\022/\n\014door_mess"
"age\030\007 \001(\0132\027.protobuf.MessageOfDoorH\000\022/\n\014"
"gate_message\030\010 \001(\0132\027.protobuf.MessageOfG"
"ateH\000\0221\n\rchest_message\030\t \001(\0132\030.protobuf."
"MessageOfChestH\000\022<\n\023hidden_gate_message\030"
"\n \001(\0132\035.protobuf.MessageOfHiddenGateH\000\022/"
"\n\014news_message\030\013 \001(\0132\027.protobuf.MessageO"
"fNewsH\000\022-\n\013map_message\030\014 \001(\0132\026.protobuf."
"MessageOfMapH\000B\020\n\016message_of_obj\"\234\001\n\014Mes"
"sageOfAll\022\021\n\tgame_time\030\001 \001(\005\022\030\n\020subject_"
"finished\030\002 \001(\005\022\031\n\021student_graduated\030\003 \001("
"\005\022\026\n\016student_quited\030\004 \001(\005\022\025\n\rstudent_sco"
"re\030\005 \001(\005\022\025\n\rtricker_score\030\006 \001(\005\"\224\001\n\017Mess"
"ageToClient\022+\n\013obj_message\030\001 \003(\0132\026.proto"
"buf.MessageOfObj\022\'\n\ngame_state\030\002 \001(\0162\023.p"
"rotobuf.GameState\022+\n\013all_message\030\003 \001(\0132\026"
".protobuf.MessageOfAll\"J\n\007MoveRes\022\024\n\014act"
"ual_speed\030\001 \001(\003\022\024\n\014actual_angle\030\002 \001(\001\022\023\n"
"\013act_success\030\003 \001(\010\"\036\n\007BoolRes\022\023\n\013act_suc"
"cess\030\001 \001(\010b\006proto3";
".PlaceType\"i\n\rMessageOfNews\022\026\n\014text_mess"
"age\030\001 \001(\tH\000\022\030\n\016binary_message\030\004 \001(\014H\000\022\017\n"
"\007from_id\030\002 \001(\003\022\r\n\005to_id\030\003 \001(\003B\006\n\004news\"\244\005"
"\n\014MessageOfObj\0225\n\017student_message\030\001 \001(\0132"
"\032.protobuf.MessageOfStudentH\000\0225\n\017tricker"
"_message\030\002 \001(\0132\032.protobuf.MessageOfTrick"
"erH\000\022/\n\014prop_message\030\003 \001(\0132\027.protobuf.Me"
"ssageOfPropH\000\0223\n\016bullet_message\030\004 \001(\0132\031."
"protobuf.MessageOfBulletH\000\022@\n\025bombed_bul"
"let_message\030\005 \001(\0132\037.protobuf.MessageOfBo"
"mbedBulletH\000\0229\n\021classroom_message\030\006 \001(\0132"
"\034.protobuf.MessageOfClassroomH\000\022/\n\014door_"
"message\030\007 \001(\0132\027.protobuf.MessageOfDoorH\000"
"\022/\n\014gate_message\030\010 \001(\0132\027.protobuf.Messag"
"eOfGateH\000\0221\n\rchest_message\030\t \001(\0132\030.proto"
"buf.MessageOfChestH\000\022<\n\023hidden_gate_mess"
"age\030\n \001(\0132\035.protobuf.MessageOfHiddenGate"
"H\000\022/\n\014news_message\030\013 \001(\0132\027.protobuf.Mess"
"ageOfNewsH\000\022-\n\013map_message\030\014 \001(\0132\026.proto"
"buf.MessageOfMapH\000B\020\n\016message_of_obj\"\234\001\n"
"\014MessageOfAll\022\021\n\tgame_time\030\001 \001(\005\022\030\n\020subj"
"ect_finished\030\002 \001(\005\022\031\n\021student_graduated\030"
"\003 \001(\005\022\026\n\016student_quited\030\004 \001(\005\022\025\n\rstudent"
"_score\030\005 \001(\005\022\025\n\rtricker_score\030\006 \001(\005\"\224\001\n\017"
"MessageToClient\022+\n\013obj_message\030\001 \003(\0132\026.p"
"rotobuf.MessageOfObj\022\'\n\ngame_state\030\002 \001(\016"
"2\023.protobuf.GameState\022+\n\013all_message\030\003 \001"
"(\0132\026.protobuf.MessageOfAll\"J\n\007MoveRes\022\024\n"
"\014actual_speed\030\001 \001(\003\022\024\n\014actual_angle\030\002 \001("
"\001\022\023\n\013act_success\030\003 \001(\010\"\036\n\007BoolRes\022\023\n\013act"
"_success\030\001 \001(\010b\006proto3";
static const ::_pbi::DescriptorTable* const descriptor_table_Message2Clients_2eproto_deps[1] = {
&::descriptor_table_MessageType_2eproto,
};
@@ -812,7 +815,7 @@ static ::_pbi::once_flag descriptor_table_Message2Clients_2eproto_once;
const ::_pbi::DescriptorTable descriptor_table_Message2Clients_2eproto = {
false,
false,
3378,
3422,
descriptor_table_protodef_Message2Clients_2eproto,
"Message2Clients.proto",
&descriptor_table_Message2Clients_2eproto_once,
@@ -5927,18 +5930,28 @@ namespace protobuf
MessageOfNews* const _this = this;
(void)_this;
new (&_impl_) Impl_{
decltype(_impl_.news_){}, decltype(_impl_.from_id_){}, decltype(_impl_.to_id_){}, /*decltype(_impl_._cached_size_)*/ {}};
decltype(_impl_.from_id_){}, decltype(_impl_.to_id_){}, decltype(_impl_.news_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}};

_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_);
_impl_.news_.InitDefault();
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
_impl_.news_.Set("", GetArenaForAllocation());
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
if (!from._internal_news().empty())
::memcpy(&_impl_.from_id_, &from._impl_.from_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_));
clear_has_news();
switch (from.news_case())
{
_this->_impl_.news_.Set(from._internal_news(), _this->GetArenaForAllocation());
case kTextMessage:
{
_this->_internal_set_text_message(from._internal_text_message());
break;
}
case kBinaryMessage:
{
_this->_internal_set_binary_message(from._internal_binary_message());
break;
}
case NEWS_NOT_SET:
{
break;
}
}
::memcpy(&_impl_.from_id_, &from._impl_.from_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_));
// @@protoc_insertion_point(copy_constructor:protobuf.MessageOfNews)
}

@@ -5949,11 +5962,8 @@ namespace protobuf
(void)arena;
(void)is_message_owned;
new (&_impl_) Impl_{
decltype(_impl_.news_){}, decltype(_impl_.from_id_){int64_t{0}}, decltype(_impl_.to_id_){int64_t{0}}, /*decltype(_impl_._cached_size_)*/ {}};
_impl_.news_.InitDefault();
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
_impl_.news_.Set("", GetArenaForAllocation());
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
decltype(_impl_.from_id_){int64_t{0}}, decltype(_impl_.to_id_){int64_t{0}}, decltype(_impl_.news_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}};
clear_has_news();
}

MessageOfNews::~MessageOfNews()
@@ -5970,7 +5980,10 @@ namespace protobuf
inline void MessageOfNews::SharedDtor()
{
GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
_impl_.news_.Destroy();
if (has_news())
{
clear_news();
}
}

void MessageOfNews::SetCachedSize(int size) const
@@ -5978,6 +5991,29 @@ namespace protobuf
_impl_._cached_size_.Set(size);
}

void MessageOfNews::clear_news()
{
// @@protoc_insertion_point(one_of_clear_start:protobuf.MessageOfNews)
switch (news_case())
{
case kTextMessage:
{
_impl_.news_.text_message_.Destroy();
break;
}
case kBinaryMessage:
{
_impl_.news_.binary_message_.Destroy();
break;
}
case NEWS_NOT_SET:
{
break;
}
}
_impl_._oneof_case_[0] = NEWS_NOT_SET;
}

void MessageOfNews::Clear()
{
// @@protoc_insertion_point(message_clear_start:protobuf.MessageOfNews)
@@ -5985,8 +6021,8 @@ namespace protobuf
// Prevent compiler warnings about cached_has_bits being unused
(void)cached_has_bits;

_impl_.news_.ClearToEmpty();
::memset(&_impl_.from_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_));
clear_news();
_internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>();
}

@@ -6001,14 +6037,14 @@ namespace protobuf
ptr = ::_pbi::ReadTag(ptr, &tag);
switch (tag >> 3)
{
// string news = 1;
// string text_message = 1;
case 1:
if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10))
{
auto str = _internal_mutable_news();
auto str = _internal_mutable_text_message();
ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
CHK_(ptr);
CHK_(::_pbi::VerifyUTF8(str, "protobuf.MessageOfNews.news"));
CHK_(::_pbi::VerifyUTF8(str, "protobuf.MessageOfNews.text_message"));
}
else
goto handle_unusual;
@@ -6033,6 +6069,17 @@ namespace protobuf
else
goto handle_unusual;
continue;
// bytes binary_message = 4;
case 4:
if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34))
{
auto str = _internal_mutable_binary_message();
ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
CHK_(ptr);
}
else
goto handle_unusual;
continue;
default:
goto handle_unusual;
} // switch
@@ -6067,14 +6114,14 @@ namespace protobuf
uint32_t cached_has_bits = 0;
(void)cached_has_bits;

// string news = 1;
if (!this->_internal_news().empty())
// string text_message = 1;
if (_internal_has_text_message())
{
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String(
this->_internal_news().data(), static_cast<int>(this->_internal_news().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.MessageOfNews.news"
this->_internal_text_message().data(), static_cast<int>(this->_internal_text_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.MessageOfNews.text_message"
);
target = stream->WriteStringMaybeAliased(
1, this->_internal_news(), target
1, this->_internal_text_message(), target
);
}

@@ -6092,6 +6139,14 @@ namespace protobuf
target = ::_pbi::WireFormatLite::WriteInt64ToArray(3, this->_internal_to_id(), target);
}

// bytes binary_message = 4;
if (_internal_has_binary_message())
{
target = stream->WriteBytesMaybeAliased(
4, this->_internal_binary_message(), target
);
}

if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields()))
{
target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray(
@@ -6111,15 +6166,6 @@ namespace protobuf
// Prevent compiler warnings about cached_has_bits being unused
(void)cached_has_bits;

// string news = 1;
if (!this->_internal_news().empty())
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize(
this->_internal_news()
);
}

// int64 from_id = 2;
if (this->_internal_from_id() != 0)
{
@@ -6132,6 +6178,31 @@ namespace protobuf
total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_to_id());
}

switch (news_case())
{
// string text_message = 1;
case kTextMessage:
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize(
this->_internal_text_message()
);
break;
}
// bytes binary_message = 4;
case kBinaryMessage:
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
this->_internal_binary_message()
);
break;
}
case NEWS_NOT_SET:
{
break;
}
}
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}

@@ -6152,10 +6223,6 @@ namespace protobuf
uint32_t cached_has_bits = 0;
(void)cached_has_bits;

if (!from._internal_news().empty())
{
_this->_internal_set_news(from._internal_news());
}
if (from._internal_from_id() != 0)
{
_this->_internal_set_from_id(from._internal_from_id());
@@ -6164,6 +6231,23 @@ namespace protobuf
{
_this->_internal_set_to_id(from._internal_to_id());
}
switch (from.news_case())
{
case kTextMessage:
{
_this->_internal_set_text_message(from._internal_text_message());
break;
}
case kBinaryMessage:
{
_this->_internal_set_binary_message(from._internal_binary_message());
break;
}
case NEWS_NOT_SET:
{
break;
}
}
_this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_);
}

@@ -6184,17 +6268,14 @@ namespace protobuf
void MessageOfNews::InternalSwap(MessageOfNews* other)
{
using std::swap;
auto* lhs_arena = GetArenaForAllocation();
auto* rhs_arena = other->GetArenaForAllocation();
_internal_metadata_.InternalSwap(&other->_internal_metadata_);
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap(
&_impl_.news_, lhs_arena, &other->_impl_.news_, rhs_arena
);
::PROTOBUF_NAMESPACE_ID::internal::memswap<
PROTOBUF_FIELD_OFFSET(MessageOfNews, _impl_.to_id_) + sizeof(MessageOfNews::_impl_.to_id_) - PROTOBUF_FIELD_OFFSET(MessageOfNews, _impl_.from_id_)>(
reinterpret_cast<char*>(&_impl_.from_id_),
reinterpret_cast<char*>(&other->_impl_.from_id_)
);
swap(_impl_.news_, other->_impl_.news_);
swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
}

::PROTOBUF_NAMESPACE_ID::Metadata MessageOfNews::GetMetadata() const


+ 251
- 46
CAPI/cpp/proto/Message2Clients.pb.h View File

@@ -3677,6 +3677,13 @@ namespace protobuf
{
return *internal_default_instance();
}
enum NewsCase
{
kTextMessage = 1,
kBinaryMessage = 4,
NEWS_NOT_SET = 0,
};

static inline const MessageOfNews* internal_default_instance()
{
return reinterpret_cast<const MessageOfNews*>(
@@ -3776,25 +3783,11 @@ namespace protobuf

enum : int
{
kNewsFieldNumber = 1,
kFromIdFieldNumber = 2,
kToIdFieldNumber = 3,
kTextMessageFieldNumber = 1,
kBinaryMessageFieldNumber = 4,
};
// string news = 1;
void clear_news();
const std::string& news() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_news(ArgT0&& arg0, ArgT... args);
std::string* mutable_news();
PROTOBUF_NODISCARD std::string* release_news();
void set_allocated_news(std::string* news);

private:
const std::string& _internal_news() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_news(const std::string& value);
std::string* _internal_mutable_news();

public:
// int64 from_id = 2;
void clear_from_id();
int64_t from_id() const;
@@ -3815,10 +3808,59 @@ namespace protobuf
void _internal_set_to_id(int64_t value);

public:
// string text_message = 1;
bool has_text_message() const;

private:
bool _internal_has_text_message() const;

public:
void clear_text_message();
const std::string& text_message() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_text_message(ArgT0&& arg0, ArgT... args);
std::string* mutable_text_message();
PROTOBUF_NODISCARD std::string* release_text_message();
void set_allocated_text_message(std::string* text_message);

private:
const std::string& _internal_text_message() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_text_message(const std::string& value);
std::string* _internal_mutable_text_message();

public:
// bytes binary_message = 4;
bool has_binary_message() const;

private:
bool _internal_has_binary_message() const;

public:
void clear_binary_message();
const std::string& binary_message() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_binary_message(ArgT0&& arg0, ArgT... args);
std::string* mutable_binary_message();
PROTOBUF_NODISCARD std::string* release_binary_message();
void set_allocated_binary_message(std::string* binary_message);

private:
const std::string& _internal_binary_message() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_binary_message(const std::string& value);
std::string* _internal_mutable_binary_message();

public:
void clear_news();
NewsCase news_case() const;
// @@protoc_insertion_point(class_scope:protobuf.MessageOfNews)

private:
class _Internal;
void set_has_text_message();
void set_has_binary_message();

inline bool has_news() const;
inline void clear_has_news();

template<typename T>
friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
@@ -3826,10 +3868,20 @@ namespace protobuf
typedef void DestructorSkippable_;
struct Impl_
{
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr news_;
int64_t from_id_;
int64_t to_id_;
union NewsUnion
{
constexpr NewsUnion() :
_constinit_{}
{
}
::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_message_;
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr binary_message_;
} news_;
mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
uint32_t _oneof_case_[1];
};
union
{
@@ -7609,61 +7661,202 @@ namespace protobuf

// MessageOfNews

// string news = 1;
inline void MessageOfNews::clear_news()
// string text_message = 1;
inline bool MessageOfNews::_internal_has_text_message() const
{
return news_case() == kTextMessage;
}
inline bool MessageOfNews::has_text_message() const
{
_impl_.news_.ClearToEmpty();
return _internal_has_text_message();
}
inline const std::string& MessageOfNews::news() const
inline void MessageOfNews::set_has_text_message()
{
// @@protoc_insertion_point(field_get:protobuf.MessageOfNews.news)
return _internal_news();
_impl_._oneof_case_[0] = kTextMessage;
}
inline void MessageOfNews::clear_text_message()
{
if (_internal_has_text_message())
{
_impl_.news_.text_message_.Destroy();
clear_has_news();
}
}
inline const std::string& MessageOfNews::text_message() const
{
// @@protoc_insertion_point(field_get:protobuf.MessageOfNews.text_message)
return _internal_text_message();
}
template<typename ArgT0, typename... ArgT>
inline PROTOBUF_ALWAYS_INLINE void MessageOfNews::set_news(ArgT0&& arg0, ArgT... args)
inline void MessageOfNews::set_text_message(ArgT0&& arg0, ArgT... args)
{
_impl_.news_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.MessageOfNews.news)
if (!_internal_has_text_message())
{
clear_news();
set_has_text_message();
_impl_.news_.text_message_.InitDefault();
}
_impl_.news_.text_message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.MessageOfNews.text_message)
}
inline std::string* MessageOfNews::mutable_news()
inline std::string* MessageOfNews::mutable_text_message()
{
std::string* _s = _internal_mutable_news();
// @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.news)
std::string* _s = _internal_mutable_text_message();
// @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.text_message)
return _s;
}
inline const std::string& MessageOfNews::_internal_news() const
inline const std::string& MessageOfNews::_internal_text_message() const
{
return _impl_.news_.Get();
if (_internal_has_text_message())
{
return _impl_.news_.text_message_.Get();
}
return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
}
inline void MessageOfNews::_internal_set_news(const std::string& value)
inline void MessageOfNews::_internal_set_text_message(const std::string& value)
{
_impl_.news_.Set(value, GetArenaForAllocation());
if (!_internal_has_text_message())
{
clear_news();
set_has_text_message();
_impl_.news_.text_message_.InitDefault();
}
_impl_.news_.text_message_.Set(value, GetArenaForAllocation());
}
inline std::string* MessageOfNews::_internal_mutable_news()
inline std::string* MessageOfNews::_internal_mutable_text_message()
{
return _impl_.news_.Mutable(GetArenaForAllocation());
if (!_internal_has_text_message())
{
clear_news();
set_has_text_message();
_impl_.news_.text_message_.InitDefault();
}
return _impl_.news_.text_message_.Mutable(GetArenaForAllocation());
}
inline std::string* MessageOfNews::release_news()
inline std::string* MessageOfNews::release_text_message()
{
// @@protoc_insertion_point(field_release:protobuf.MessageOfNews.news)
return _impl_.news_.Release();
// @@protoc_insertion_point(field_release:protobuf.MessageOfNews.text_message)
if (_internal_has_text_message())
{
clear_has_news();
return _impl_.news_.text_message_.Release();
}
else
{
return nullptr;
}
}
inline void MessageOfNews::set_allocated_news(std::string* news)
inline void MessageOfNews::set_allocated_text_message(std::string* text_message)
{
if (news != nullptr)
if (has_news())
{
clear_news();
}
if (text_message != nullptr)
{
set_has_text_message();
_impl_.news_.text_message_.InitAllocated(text_message, GetArenaForAllocation());
}
// @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.text_message)
}

// bytes binary_message = 4;
inline bool MessageOfNews::_internal_has_binary_message() const
{
return news_case() == kBinaryMessage;
}
inline bool MessageOfNews::has_binary_message() const
{
return _internal_has_binary_message();
}
inline void MessageOfNews::set_has_binary_message()
{
_impl_._oneof_case_[0] = kBinaryMessage;
}
inline void MessageOfNews::clear_binary_message()
{
if (_internal_has_binary_message())
{
_impl_.news_.binary_message_.Destroy();
clear_has_news();
}
}
inline const std::string& MessageOfNews::binary_message() const
{
// @@protoc_insertion_point(field_get:protobuf.MessageOfNews.binary_message)
return _internal_binary_message();
}
template<typename ArgT0, typename... ArgT>
inline void MessageOfNews::set_binary_message(ArgT0&& arg0, ArgT... args)
{
if (!_internal_has_binary_message())
{
clear_news();
set_has_binary_message();
_impl_.news_.binary_message_.InitDefault();
}
_impl_.news_.binary_message_.SetBytes(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.MessageOfNews.binary_message)
}
inline std::string* MessageOfNews::mutable_binary_message()
{
std::string* _s = _internal_mutable_binary_message();
// @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.binary_message)
return _s;
}
inline const std::string& MessageOfNews::_internal_binary_message() const
{
if (_internal_has_binary_message())
{
return _impl_.news_.binary_message_.Get();
}
return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
}
inline void MessageOfNews::_internal_set_binary_message(const std::string& value)
{
if (!_internal_has_binary_message())
{
clear_news();
set_has_binary_message();
_impl_.news_.binary_message_.InitDefault();
}
_impl_.news_.binary_message_.Set(value, GetArenaForAllocation());
}
inline std::string* MessageOfNews::_internal_mutable_binary_message()
{
if (!_internal_has_binary_message())
{
clear_news();
set_has_binary_message();
_impl_.news_.binary_message_.InitDefault();
}
return _impl_.news_.binary_message_.Mutable(GetArenaForAllocation());
}
inline std::string* MessageOfNews::release_binary_message()
{
// @@protoc_insertion_point(field_release:protobuf.MessageOfNews.binary_message)
if (_internal_has_binary_message())
{
clear_has_news();
return _impl_.news_.binary_message_.Release();
}
else
{
return nullptr;
}
}
inline void MessageOfNews::set_allocated_binary_message(std::string* binary_message)
{
if (has_news())
{
clear_news();
}
_impl_.news_.SetAllocated(news, GetArenaForAllocation());
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
if (_impl_.news_.IsDefault())
if (binary_message != nullptr)
{
_impl_.news_.Set("", GetArenaForAllocation());
set_has_binary_message();
_impl_.news_.binary_message_.InitAllocated(binary_message, GetArenaForAllocation());
}
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
// @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.news)
// @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.binary_message)
}

// int64 from_id = 2;
@@ -7714,6 +7907,18 @@ namespace protobuf
// @@protoc_insertion_point(field_set:protobuf.MessageOfNews.to_id)
}

inline bool MessageOfNews::has_news() const
{
return news_case() != NEWS_NOT_SET;
}
inline void MessageOfNews::clear_has_news()
{
_impl_._oneof_case_[0] = NEWS_NOT_SET;
}
inline MessageOfNews::NewsCase MessageOfNews::news_case() const
{
return MessageOfNews::NewsCase(_impl_._oneof_case_[0]);
}
// -------------------------------------------------------------------

// MessageOfObj


+ 136
- 54
CAPI/cpp/proto/Message2Server.pb.cc View File

@@ -92,7 +92,7 @@ namespace protobuf
::_pbi::ConstantInitialized
) :
_impl_{
/*decltype(_impl_.message_)*/ {&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}, /*decltype(_impl_.player_id_)*/ int64_t{0}, /*decltype(_impl_.to_player_id_)*/ int64_t{0}, /*decltype(_impl_._cached_size_)*/ {}}
/*decltype(_impl_.
player_id_)*/ int64_t{0}, /*decltype(_impl_.to_player_id_)*/ int64_t{0}, /*decltype(_impl_.message_)*/ {}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}
{
}
struct SendMsgDefaultTypeInternal
@@ -235,11 +235,13 @@ const uint32_t TableStruct_Message2Server_2eproto::offsets[] PROTOBUF_SECTION_VA
~0u, // no _has_bits_
PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _internal_metadata_),
~0u, // no _extensions_
~0u, // no _oneof_case_
PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_._oneof_case_[0]),
~0u, // no _weak_field_map_
~0u, // no _inlined_string_donated_
PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.player_id_),
PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.to_player_id_),
::_pbi::kInvalidFieldOffsetTag,
::_pbi::kInvalidFieldOffsetTag,
PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.message_),
~0u, // no _has_bits_
PROTOBUF_FIELD_OFFSET(::protobuf::AttackMsg, _internal_metadata_),
@@ -278,10 +280,10 @@ static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protode
{11, -1, -1, sizeof(::protobuf::MoveMsg)},
{20, -1, -1, sizeof(::protobuf::PropMsg)},
{28, -1, -1, sizeof(::protobuf::SendMsg)},
{37, -1, -1, sizeof(::protobuf::AttackMsg)},
{45, -1, -1, sizeof(::protobuf::IDMsg)},
{52, -1, -1, sizeof(::protobuf::TreatAndRescueMsg)},
{60, -1, -1, sizeof(::protobuf::SkillMsg)},
{39, -1, -1, sizeof(::protobuf::AttackMsg)},
{47, -1, -1, sizeof(::protobuf::IDMsg)},
{54, -1, -1, sizeof(::protobuf::TreatAndRescueMsg)},
{62, -1, -1, sizeof(::protobuf::SkillMsg)},
};

static const ::_pb::Message* const file_default_instances[] = {
@@ -305,13 +307,15 @@ const char descriptor_table_protodef_Message2Server_2eproto[] PROTOBUF_SECTION_V
"eMsg\022\021\n\tplayer_id\030\001 \001(\003\022\r\n\005angle\030\002 \001(\001\022\034"
"\n\024time_in_milliseconds\030\003 \001(\003\"C\n\007PropMsg\022"
"\021\n\tplayer_id\030\001 \001(\003\022%\n\tprop_type\030\002 \001(\0162\022."
"protobuf.PropType\"C\n\007SendMsg\022\021\n\tplayer_i"
"d\030\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\022\017\n\007message"
"\030\003 \001(\t\"-\n\tAttackMsg\022\021\n\tplayer_id\030\001 \001(\003\022\r"
"\n\005angle\030\002 \001(\001\"\032\n\005IDMsg\022\021\n\tplayer_id\030\001 \001("
"\003\"<\n\021TreatAndRescueMsg\022\021\n\tplayer_id\030\001 \001("
"\003\022\024\n\014to_player_id\030\002 \001(\003\"/\n\010SkillMsg\022\021\n\tp"
"layer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005b\006proto3";
"protobuf.PropType\"o\n\007SendMsg\022\021\n\tplayer_i"
"d\030\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\022\026\n\014text_me"
"ssage\030\003 \001(\tH\000\022\030\n\016binary_message\030\004 \001(\014H\000B"
"\t\n\007message\"-\n\tAttackMsg\022\021\n\tplayer_id\030\001 \001"
"(\003\022\r\n\005angle\030\002 \001(\001\"\032\n\005IDMsg\022\021\n\tplayer_id\030"
"\001 \001(\003\"<\n\021TreatAndRescueMsg\022\021\n\tplayer_id\030"
"\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\"/\n\010SkillMsg\022"
"\021\n\tplayer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005b\006pr"
"oto3";
static const ::_pbi::DescriptorTable* const descriptor_table_Message2Server_2eproto_deps[1] = {
&::descriptor_table_MessageType_2eproto,
};
@@ -319,7 +323,7 @@ static ::_pbi::once_flag descriptor_table_Message2Server_2eproto_once;
const ::_pbi::DescriptorTable descriptor_table_Message2Server_2eproto = {
false,
false,
640,
684,
descriptor_table_protodef_Message2Server_2eproto,
"Message2Server.proto",
&descriptor_table_Message2Server_2eproto_once,
@@ -1259,18 +1263,28 @@ namespace protobuf
SendMsg* const _this = this;
(void)_this;
new (&_impl_) Impl_{
decltype(_impl_.message_){}, decltype(_impl_.player_id_){}, decltype(_impl_.to_player_id_){}, /*decltype(_impl_._cached_size_)*/ {}};
decltype(_impl_.player_id_){}, decltype(_impl_.to_player_id_){}, decltype(_impl_.message_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}};

_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_);
_impl_.message_.InitDefault();
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
_impl_.message_.Set("", GetArenaForAllocation());
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
if (!from._internal_message().empty())
::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_));
clear_has_message();
switch (from.message_case())
{
_this->_impl_.message_.Set(from._internal_message(), _this->GetArenaForAllocation());
case kTextMessage:
{
_this->_internal_set_text_message(from._internal_text_message());
break;
}
case kBinaryMessage:
{
_this->_internal_set_binary_message(from._internal_binary_message());
break;
}
case MESSAGE_NOT_SET:
{
break;
}
}
::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_));
// @@protoc_insertion_point(copy_constructor:protobuf.SendMsg)
}

@@ -1281,11 +1295,8 @@ namespace protobuf
(void)arena;
(void)is_message_owned;
new (&_impl_) Impl_{
decltype(_impl_.message_){}, decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.to_player_id_){int64_t{0}}, /*decltype(_impl_._cached_size_)*/ {}};
_impl_.message_.InitDefault();
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
_impl_.message_.Set("", GetArenaForAllocation());
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.to_player_id_){int64_t{0}}, decltype(_impl_.message_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}};
clear_has_message();
}

SendMsg::~SendMsg()
@@ -1302,7 +1313,10 @@ namespace protobuf
inline void SendMsg::SharedDtor()
{
GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
_impl_.message_.Destroy();
if (has_message())
{
clear_message();
}
}

void SendMsg::SetCachedSize(int size) const
@@ -1310,6 +1324,29 @@ namespace protobuf
_impl_._cached_size_.Set(size);
}

void SendMsg::clear_message()
{
// @@protoc_insertion_point(one_of_clear_start:protobuf.SendMsg)
switch (message_case())
{
case kTextMessage:
{
_impl_.message_.text_message_.Destroy();
break;
}
case kBinaryMessage:
{
_impl_.message_.binary_message_.Destroy();
break;
}
case MESSAGE_NOT_SET:
{
break;
}
}
_impl_._oneof_case_[0] = MESSAGE_NOT_SET;
}

void SendMsg::Clear()
{
// @@protoc_insertion_point(message_clear_start:protobuf.SendMsg)
@@ -1317,8 +1354,8 @@ namespace protobuf
// Prevent compiler warnings about cached_has_bits being unused
(void)cached_has_bits;

_impl_.message_.ClearToEmpty();
::memset(&_impl_.player_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_));
clear_message();
_internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>();
}

@@ -1353,14 +1390,25 @@ namespace protobuf
else
goto handle_unusual;
continue;
// string message = 3;
// string text_message = 3;
case 3:
if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26))
{
auto str = _internal_mutable_message();
auto str = _internal_mutable_text_message();
ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
CHK_(ptr);
CHK_(::_pbi::VerifyUTF8(str, "protobuf.SendMsg.text_message"));
}
else
goto handle_unusual;
continue;
// bytes binary_message = 4;
case 4:
if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34))
{
auto str = _internal_mutable_binary_message();
ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
CHK_(ptr);
CHK_(::_pbi::VerifyUTF8(str, "protobuf.SendMsg.message"));
}
else
goto handle_unusual;
@@ -1413,14 +1461,22 @@ namespace protobuf
target = ::_pbi::WireFormatLite::WriteInt64ToArray(2, this->_internal_to_player_id(), target);
}

// string message = 3;
if (!this->_internal_message().empty())
// string text_message = 3;
if (_internal_has_text_message())
{
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String(
this->_internal_message().data(), static_cast<int>(this->_internal_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.SendMsg.message"
this->_internal_text_message().data(), static_cast<int>(this->_internal_text_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.SendMsg.text_message"
);
target = stream->WriteStringMaybeAliased(
3, this->_internal_message(), target
3, this->_internal_text_message(), target
);
}

// bytes binary_message = 4;
if (_internal_has_binary_message())
{
target = stream->WriteBytesMaybeAliased(
4, this->_internal_binary_message(), target
);
}

@@ -1443,15 +1499,6 @@ namespace protobuf
// Prevent compiler warnings about cached_has_bits being unused
(void)cached_has_bits;

// string message = 3;
if (!this->_internal_message().empty())
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize(
this->_internal_message()
);
}

// int64 player_id = 1;
if (this->_internal_player_id() != 0)
{
@@ -1464,6 +1511,31 @@ namespace protobuf
total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_to_player_id());
}

switch (message_case())
{
// string text_message = 3;
case kTextMessage:
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize(
this->_internal_text_message()
);
break;
}
// bytes binary_message = 4;
case kBinaryMessage:
{
total_size += 1 +
::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
this->_internal_binary_message()
);
break;
}
case MESSAGE_NOT_SET:
{
break;
}
}
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}

@@ -1484,10 +1556,6 @@ namespace protobuf
uint32_t cached_has_bits = 0;
(void)cached_has_bits;

if (!from._internal_message().empty())
{
_this->_internal_set_message(from._internal_message());
}
if (from._internal_player_id() != 0)
{
_this->_internal_set_player_id(from._internal_player_id());
@@ -1496,6 +1564,23 @@ namespace protobuf
{
_this->_internal_set_to_player_id(from._internal_to_player_id());
}
switch (from.message_case())
{
case kTextMessage:
{
_this->_internal_set_text_message(from._internal_text_message());
break;
}
case kBinaryMessage:
{
_this->_internal_set_binary_message(from._internal_binary_message());
break;
}
case MESSAGE_NOT_SET:
{
break;
}
}
_this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_);
}

@@ -1516,17 +1601,14 @@ namespace protobuf
void SendMsg::InternalSwap(SendMsg* other)
{
using std::swap;
auto* lhs_arena = GetArenaForAllocation();
auto* rhs_arena = other->GetArenaForAllocation();
_internal_metadata_.InternalSwap(&other->_internal_metadata_);
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap(
&_impl_.message_, lhs_arena, &other->_impl_.message_, rhs_arena
);
::PROTOBUF_NAMESPACE_ID::internal::memswap<
PROTOBUF_FIELD_OFFSET(SendMsg, _impl_.to_player_id_) + sizeof(SendMsg::_impl_.to_player_id_) - PROTOBUF_FIELD_OFFSET(SendMsg, _impl_.player_id_)>(
reinterpret_cast<char*>(&_impl_.player_id_),
reinterpret_cast<char*>(&other->_impl_.player_id_)
);
swap(_impl_.message_, other->_impl_.message_);
swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
}

::PROTOBUF_NAMESPACE_ID::Metadata SendMsg::GetMetadata() const


+ 251
- 46
CAPI/cpp/proto/Message2Server.pb.h View File

@@ -839,6 +839,13 @@ namespace protobuf
{
return *internal_default_instance();
}
enum MessageCase
{
kTextMessage = 3,
kBinaryMessage = 4,
MESSAGE_NOT_SET = 0,
};

static inline const SendMsg* internal_default_instance()
{
return reinterpret_cast<const SendMsg*>(
@@ -938,25 +945,11 @@ namespace protobuf

enum : int
{
kMessageFieldNumber = 3,
kPlayerIdFieldNumber = 1,
kToPlayerIdFieldNumber = 2,
kTextMessageFieldNumber = 3,
kBinaryMessageFieldNumber = 4,
};
// string message = 3;
void clear_message();
const std::string& message() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_message(ArgT0&& arg0, ArgT... args);
std::string* mutable_message();
PROTOBUF_NODISCARD std::string* release_message();
void set_allocated_message(std::string* message);

private:
const std::string& _internal_message() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_message(const std::string& value);
std::string* _internal_mutable_message();

public:
// int64 player_id = 1;
void clear_player_id();
int64_t player_id() const;
@@ -977,10 +970,59 @@ namespace protobuf
void _internal_set_to_player_id(int64_t value);

public:
// string text_message = 3;
bool has_text_message() const;

private:
bool _internal_has_text_message() const;

public:
void clear_text_message();
const std::string& text_message() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_text_message(ArgT0&& arg0, ArgT... args);
std::string* mutable_text_message();
PROTOBUF_NODISCARD std::string* release_text_message();
void set_allocated_text_message(std::string* text_message);

private:
const std::string& _internal_text_message() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_text_message(const std::string& value);
std::string* _internal_mutable_text_message();

public:
// bytes binary_message = 4;
bool has_binary_message() const;

private:
bool _internal_has_binary_message() const;

public:
void clear_binary_message();
const std::string& binary_message() const;
template<typename ArgT0 = const std::string&, typename... ArgT>
void set_binary_message(ArgT0&& arg0, ArgT... args);
std::string* mutable_binary_message();
PROTOBUF_NODISCARD std::string* release_binary_message();
void set_allocated_binary_message(std::string* binary_message);

private:
const std::string& _internal_binary_message() const;
inline PROTOBUF_ALWAYS_INLINE void _internal_set_binary_message(const std::string& value);
std::string* _internal_mutable_binary_message();

public:
void clear_message();
MessageCase message_case() const;
// @@protoc_insertion_point(class_scope:protobuf.SendMsg)

private:
class _Internal;
void set_has_text_message();
void set_has_binary_message();

inline bool has_message() const;
inline void clear_has_message();

template<typename T>
friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
@@ -988,10 +1030,20 @@ namespace protobuf
typedef void DestructorSkippable_;
struct Impl_
{
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr message_;
int64_t player_id_;
int64_t to_player_id_;
union MessageUnion
{
constexpr MessageUnion() :
_constinit_{}
{
}
::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_message_;
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr binary_message_;
} message_;
mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
uint32_t _oneof_case_[1];
};
union
{
@@ -2151,63 +2203,216 @@ namespace protobuf
// @@protoc_insertion_point(field_set:protobuf.SendMsg.to_player_id)
}

// string message = 3;
inline void SendMsg::clear_message()
// string text_message = 3;
inline bool SendMsg::_internal_has_text_message() const
{
return message_case() == kTextMessage;
}
inline bool SendMsg::has_text_message() const
{
return _internal_has_text_message();
}
inline void SendMsg::set_has_text_message()
{
_impl_._oneof_case_[0] = kTextMessage;
}
inline void SendMsg::clear_text_message()
{
_impl_.message_.ClearToEmpty();
if (_internal_has_text_message())
{
_impl_.message_.text_message_.Destroy();
clear_has_message();
}
}
inline const std::string& SendMsg::message() const
inline const std::string& SendMsg::text_message() const
{
// @@protoc_insertion_point(field_get:protobuf.SendMsg.message)
return _internal_message();
// @@protoc_insertion_point(field_get:protobuf.SendMsg.text_message)
return _internal_text_message();
}
template<typename ArgT0, typename... ArgT>
inline PROTOBUF_ALWAYS_INLINE void SendMsg::set_message(ArgT0&& arg0, ArgT... args)
inline void SendMsg::set_text_message(ArgT0&& arg0, ArgT... args)
{
_impl_.message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.SendMsg.message)
if (!_internal_has_text_message())
{
clear_message();
set_has_text_message();
_impl_.message_.text_message_.InitDefault();
}
_impl_.message_.text_message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.SendMsg.text_message)
}
inline std::string* SendMsg::mutable_message()
inline std::string* SendMsg::mutable_text_message()
{
std::string* _s = _internal_mutable_message();
// @@protoc_insertion_point(field_mutable:protobuf.SendMsg.message)
std::string* _s = _internal_mutable_text_message();
// @@protoc_insertion_point(field_mutable:protobuf.SendMsg.text_message)
return _s;
}
inline const std::string& SendMsg::_internal_message() const
inline const std::string& SendMsg::_internal_text_message() const
{
return _impl_.message_.Get();
if (_internal_has_text_message())
{
return _impl_.message_.text_message_.Get();
}
return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
}
inline void SendMsg::_internal_set_message(const std::string& value)
inline void SendMsg::_internal_set_text_message(const std::string& value)
{
_impl_.message_.Set(value, GetArenaForAllocation());
if (!_internal_has_text_message())
{
clear_message();
set_has_text_message();
_impl_.message_.text_message_.InitDefault();
}
_impl_.message_.text_message_.Set(value, GetArenaForAllocation());
}
inline std::string* SendMsg::_internal_mutable_message()
inline std::string* SendMsg::_internal_mutable_text_message()
{
return _impl_.message_.Mutable(GetArenaForAllocation());
if (!_internal_has_text_message())
{
clear_message();
set_has_text_message();
_impl_.message_.text_message_.InitDefault();
}
return _impl_.message_.text_message_.Mutable(GetArenaForAllocation());
}
inline std::string* SendMsg::release_message()
inline std::string* SendMsg::release_text_message()
{
// @@protoc_insertion_point(field_release:protobuf.SendMsg.message)
return _impl_.message_.Release();
// @@protoc_insertion_point(field_release:protobuf.SendMsg.text_message)
if (_internal_has_text_message())
{
clear_has_message();
return _impl_.message_.text_message_.Release();
}
else
{
return nullptr;
}
}
inline void SendMsg::set_allocated_text_message(std::string* text_message)
{
if (has_message())
{
clear_message();
}
if (text_message != nullptr)
{
set_has_text_message();
_impl_.message_.text_message_.InitAllocated(text_message, GetArenaForAllocation());
}
// @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.text_message)
}

// bytes binary_message = 4;
inline bool SendMsg::_internal_has_binary_message() const
{
return message_case() == kBinaryMessage;
}
inline bool SendMsg::has_binary_message() const
{
return _internal_has_binary_message();
}
inline void SendMsg::set_has_binary_message()
{
_impl_._oneof_case_[0] = kBinaryMessage;
}
inline void SendMsg::clear_binary_message()
{
if (_internal_has_binary_message())
{
_impl_.message_.binary_message_.Destroy();
clear_has_message();
}
}
inline const std::string& SendMsg::binary_message() const
{
// @@protoc_insertion_point(field_get:protobuf.SendMsg.binary_message)
return _internal_binary_message();
}
template<typename ArgT0, typename... ArgT>
inline void SendMsg::set_binary_message(ArgT0&& arg0, ArgT... args)
{
if (!_internal_has_binary_message())
{
clear_message();
set_has_binary_message();
_impl_.message_.binary_message_.InitDefault();
}
_impl_.message_.binary_message_.SetBytes(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation());
// @@protoc_insertion_point(field_set:protobuf.SendMsg.binary_message)
}
inline std::string* SendMsg::mutable_binary_message()
{
std::string* _s = _internal_mutable_binary_message();
// @@protoc_insertion_point(field_mutable:protobuf.SendMsg.binary_message)
return _s;
}
inline const std::string& SendMsg::_internal_binary_message() const
{
if (_internal_has_binary_message())
{
return _impl_.message_.binary_message_.Get();
}
return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
}
inline void SendMsg::_internal_set_binary_message(const std::string& value)
{
if (!_internal_has_binary_message())
{
clear_message();
set_has_binary_message();
_impl_.message_.binary_message_.InitDefault();
}
_impl_.message_.binary_message_.Set(value, GetArenaForAllocation());
}
inline std::string* SendMsg::_internal_mutable_binary_message()
{
if (!_internal_has_binary_message())
{
clear_message();
set_has_binary_message();
_impl_.message_.binary_message_.InitDefault();
}
return _impl_.message_.binary_message_.Mutable(GetArenaForAllocation());
}
inline void SendMsg::set_allocated_message(std::string* message)
inline std::string* SendMsg::release_binary_message()
{
if (message != nullptr)
// @@protoc_insertion_point(field_release:protobuf.SendMsg.binary_message)
if (_internal_has_binary_message())
{
clear_has_message();
return _impl_.message_.binary_message_.Release();
}
else
{
return nullptr;
}
}
inline void SendMsg::set_allocated_binary_message(std::string* binary_message)
{
if (has_message())
{
clear_message();
}
_impl_.message_.SetAllocated(message, GetArenaForAllocation());
#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING
if (_impl_.message_.IsDefault())
if (binary_message != nullptr)
{
_impl_.message_.Set("", GetArenaForAllocation());
set_has_binary_message();
_impl_.message_.binary_message_.InitAllocated(binary_message, GetArenaForAllocation());
}
#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING
// @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.message)
// @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.binary_message)
}

inline bool SendMsg::has_message() const
{
return message_case() != MESSAGE_NOT_SET;
}
inline void SendMsg::clear_has_message()
{
_impl_._oneof_case_[0] = MESSAGE_NOT_SET;
}
inline SendMsg::MessageCase SendMsg::message_case() const
{
return SendMsg::MessageCase(_impl_._oneof_case_[0]);
}
// -------------------------------------------------------------------

// AttackMsg


+ 3
- 3
CAPI/python/PyAPI/AI.py View File

@@ -8,10 +8,10 @@ import time


class Setting:
# 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新
# 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次
@staticmethod
def asynchronous() -> bool:
return True
return False

# 选手需要依次将player0到player4的职业都定义
@staticmethod
@@ -41,7 +41,7 @@ class AssistFunction:
class AI(IAI):
def __init__(self, pID: int):
self.__playerID = pID
# 每帧执行一次StudentPlay或TrickerPlay(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
def StudentPlay(self, api: IStudentAPI) -> None:
# 公共操作
if self.__playerID == 0:


+ 11
- 5
CAPI/python/PyAPI/API.py View File

@@ -2,7 +2,7 @@ import PyAPI.structures as THUAI6
from PyAPI.Interface import ILogic, IStudentAPI, ITrickerAPI, IGameTimer, IAI
from math import pi
from concurrent.futures import ThreadPoolExecutor, Future
from typing import List, cast, Tuple
from typing import List, cast, Tuple, Union


class StudentAPI(IStudentAPI, IGameTimer):
@@ -68,13 +68,13 @@ class StudentAPI(IStudentAPI, IGameTimer):

# 消息相关,接收消息时无消息则返回(-1, '')

def SendMessage(self, toID: int, message: str) -> Future[bool]:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]:
return self.__pool.submit(self.__logic.SendMessage, toID, message)

def HaveMessage(self) -> bool:
return self.__logic.HaveMessage()

def GetMessage(self) -> Tuple[int, str]:
def GetMessage(self) -> Tuple[int, Union[str, bytes]]:
return self.__logic.GetMessage()

# 等待下一帧
@@ -132,6 +132,9 @@ class StudentAPI(IStudentAPI, IGameTimer):
def GetGameInfo(self) -> THUAI6.GameInfo:
return self.__logic.GetGameInfo()

def HaveView(self, gridX: int, gridY: int) -> bool:
return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange)

# 用于DEBUG的输出函数,仅在DEBUG模式下有效

def Print(self, cont: str) -> None:
@@ -241,13 +244,13 @@ class TrickerAPI(ITrickerAPI, IGameTimer):

# 消息相关,接收消息时无消息则返回(-1, '')

def SendMessage(self, toID: int, message: str) -> Future[bool]:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]:
return self.__pool.submit(self.__logic.SendMessage, toID, message)

def HaveMessage(self) -> bool:
return self.__logic.HaveMessage()

def GetMessage(self) -> Tuple[int, str]:
def GetMessage(self) -> Tuple[int, Union[str, bytes]]:
return self.__logic.GetMessage()

# 等待下一帧
@@ -305,6 +308,9 @@ class TrickerAPI(ITrickerAPI, IGameTimer):
def GetGameInfo(self) -> THUAI6.GameInfo:
return self.__logic.GetGameInfo()

def HaveView(self, gridX: int, gridY: int) -> bool:
return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange)

# 用于DEBUG的输出函数,仅在DEBUG模式下有效

def Print(self, cont: str) -> None:


+ 4
- 8
CAPI/python/PyAPI/Communication.py View File

@@ -7,6 +7,8 @@ import proto.Message2Clients_pb2 as Message2Clients
import threading
import grpc

from typing import Union


# 使用gRPC的异步来减少通信对于选手而言损失的时间,而gRPC的return值有result()方法,故若连接错误时也应当返回一个具有result()方法的对象,使用此处的ErrorHandler类来实现
class BoolErrorHandler(IErrorHandler):
@@ -16,19 +18,13 @@ class BoolErrorHandler(IErrorHandler):


class Communication:

__THUAI6Stub: Services.AvailableServiceStub
__haveNewMessage: bool
__message2Client: Message2Clients.MessageToClient
__mtxMessage: threading.Lock
__cvMessage: threading.Condition

def __init__(self, sIP: str, sPort: str):
aim = sIP + ':' + sPort
channel = grpc.insecure_channel(aim)
self.__THUAI6Stub = Services.AvailableServiceStub(channel)
self.__haveNewMessage = False
self.__cvMessage = threading.Condition()
self.__message2Client: Message2Clients.MessageToClient

def Move(self, time: int, angle: float, playerID: int) -> bool:
try:
@@ -75,7 +71,7 @@ class Communication:
else:
return useResult.act_success

def SendMessage(self, toID: int, message: str, playerID: int) -> bool:
def SendMessage(self, toID: int, message: Union[str, bytes], playerID: int) -> bool:
try:
sendResult = self.__THUAI6Stub.SendMessage(
THUAI62Proto.THUAI62ProtobufSend(message, toID, playerID))


+ 11
- 5
CAPI/python/PyAPI/DebugAPI.py View File

@@ -1,6 +1,6 @@
from math import pi
from concurrent.futures import ThreadPoolExecutor, Future
from typing import List, cast, Tuple
from typing import List, cast, Tuple, Union
import logging
import os
import datetime
@@ -216,7 +216,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):

# 消息相关,接收消息时无消息则返回(-1, '')

def SendMessage(self, toID: int, message: str) -> Future[bool]:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]:
self.__logger.info(
f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms")

@@ -238,7 +238,7 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
f"HaveMessage: failed at {self.__GetTime()}ms")
return result

def GetMessage(self) -> Tuple[int, str]:
def GetMessage(self) -> Tuple[int, Union[str, bytes]]:
self.__logger.info(
f"GetMessage: called at {self.__GetTime()}ms")
result = self.__logic.GetMessage()
@@ -304,6 +304,9 @@ class StudentDebugAPI(IStudentAPI, IGameTimer):
def GetGameInfo(self) -> THUAI6.GameInfo:
return self.__logic.GetGameInfo()

def HaveView(self, gridX: int, gridY: int) -> bool:
return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange)

# 用于DEBUG的输出函数,仅在DEBUG模式下有效

def Print(self, cont: str) -> None:
@@ -668,7 +671,7 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):

# 消息相关,接收消息时无消息则返回(-1, '')

def SendMessage(self, toID: int, message: str) -> Future[bool]:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]:
self.__logger.info(
f"SendMessage: toID = {toID}, message = {message}, called at {self.__GetTime()}ms")

@@ -690,7 +693,7 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):
f"HaveMessage: failed at {self.__GetTime()}ms")
return result

def GetMessage(self) -> Tuple[int, str]:
def GetMessage(self) -> Tuple[int, Union[str, bytes]]:
self.__logger.info(
f"GetMessage: called at {self.__GetTime()}ms")
result = self.__logic.GetMessage()
@@ -756,6 +759,9 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer):
def GetGameInfo(self) -> THUAI6.GameInfo:
return self.__logic.GetGameInfo()

def HaveView(self, gridX: int, gridY: int) -> bool:
return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange)

# 用于DEBUG的输出函数,仅在DEBUG模式下有效

def Print(self, cont: str) -> None:


+ 10
- 2
CAPI/python/PyAPI/Interface.py View File

@@ -85,7 +85,7 @@ class ILogic(metaclass=ABCMeta):
pass

@abstractmethod
def SendMessage(self, toID: int, message: str) -> bool:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> bool:
pass

@abstractmethod
@@ -154,6 +154,10 @@ class ILogic(metaclass=ABCMeta):
def StartRouseMate(self, mateID: int) -> bool:
pass

@abstractmethod
def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool:
pass


class IAPI(metaclass=ABCMeta):

@@ -231,7 +235,7 @@ class IAPI(metaclass=ABCMeta):
# 消息相关,接收消息时无消息则返回(-1, '')

@abstractmethod
def SendMessage(self, toID: int, message: str) -> Future[bool]:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> Future[bool]:
pass

@abstractmethod
@@ -314,6 +318,10 @@ class IAPI(metaclass=ABCMeta):
def GetGameInfo(self) -> THUAI6.GameInfo:
pass

@abstractmethod
def HaveView(self, gridX: int, gridY: int) -> bool:
pass

# 用于DEBUG的输出函数,仅在DEBUG模式下有效

@abstractmethod


+ 21
- 9
CAPI/python/PyAPI/State.py View File

@@ -3,21 +3,33 @@ import PyAPI.structures as THUAI6


class State:
def __init__(self, **kwargs) -> None:
self.teamScore = 0
self.self = THUAI6.Student()
self.students = []
self.trickers = []
self.props = []
self.gameMap = []
self.bullets = []
self.bombedBullets = []
self.mapInfo = THUAI6.GameMap()
self.gameInfo = THUAI6.GameInfo()
self.guids = []
teamScore: int
self: Union[THUAI6.Student, THUAI6.Tricker]

students: List[THUAI6.Student] = []
trickers: List[THUAI6.Tricker] = []
students: List[THUAI6.Student]
trickers: List[THUAI6.Tricker]

props: List[THUAI6.Prop] = []
props: List[THUAI6.Prop]

gameMap: List[List[THUAI6.PlaceType]] = []
gameMap: List[List[THUAI6.PlaceType]]

bullets: List[THUAI6.Bullet] = []
bombedBullets: List[THUAI6.BombedBullet] = []
bullets: List[THUAI6.Bullet]
bombedBullets: List[THUAI6.BombedBullet]

mapInfo: THUAI6.GameMap = THUAI6.GameMap()
mapInfo: THUAI6.GameMap

gameInfo: THUAI6.GameInfo = THUAI6.GameInfo()
gameInfo: THUAI6.GameInfo

guids: List[int] = []
guids: List[int]

+ 44
- 1
CAPI/python/PyAPI/constants.py View File

@@ -58,7 +58,7 @@ class Constants(NoInstance):
basicRecoveryFromHit = 3700 # 基本命中攻击恢复时长
basicStunnedTimeOfStudent = 4300

basicBulletmoveSpeed = 7400 # 基本子弹移动速度
basicBulletMoveSpeed = 7400 # 基本子弹移动速度
basicRemoteAttackRange = 6000 # 基本远程攻击范围
basicAttackShortRange = 2200 # 基本近程攻击范围
basicBulletBombRange = 2000 # 基本子弹爆炸范围
@@ -298,3 +298,46 @@ class WriteAnswers:
class SummonGolem:
skillCD = (int)(1.0 * Constants.commonSkillCD)
durationTime = (int)(0.0 * Constants.commonSkillTime)

class CommonAttackOfTricker:
BulletBombRange = 0
BulletAttackRange = Constants.basicAttackShortRange
ap = Constants.basicApOfTricker
Speed = Constants.basicBulletMoveSpeed
IsRemoteAttack = False
CastTime = BulletAttackRange * 1000 / Speed
Backswing = Constants.basicBackswing
RecoveryFromHit = Constants.basicRecoveryFromHit
cd = Constants.basicBackswing
maxBulletNum = 1
class FlyingKnife:
BulletBombRange = 0
BulletAttackRange = Constants.basicRemoteAttackRange * 13
ap = Constants.basicApOfTricker* 4 / 5
Speed = Constants.basicBulletMoveSpeed* 25 / 10
IsRemoteAttack = True
CastTime = Constants.basicCastTime * 4 / 5
Backswing =0
RecoveryFromHit =0
cd = Constants.basicBackswing / 2
maxBulletNum = 1
class BombBomb:
BulletBombRange = Constants.basicBulletBombRange
BulletAttackRange = Constants.basicAttackShortRange
ap = Constants.basicApOfTricker * 6 / 5
Speed = Constants.basicBulletMoveSpeed* 30 / 37
IsRemoteAttack = False
CastTime = BulletAttackRange * 1000 / Speed
Backswing =Constants.basicRecoveryFromHit
RecoveryFromHit =Constants.basicRecoveryFromHit
cd = Constants.basicCD
maxBulletNum = 1
class JumpyDumpty:
BulletBombRange = Constants.basicBulletBombRange / 2
BulletAttackRange = Constants.basicRemoteAttackRange * 2
ap = (int)(Constants.basicApOfTricker* 0.6)
Speed = Constants.basicBulletMoveSpeed* 43 / 37
IsRemoteAttack = False

+ 42
- 29
CAPI/python/PyAPI/logic.py View File

@@ -3,6 +3,7 @@ from typing import List, Union, Callable, Tuple
import threading
import logging
import copy
import platform
import proto.MessageType_pb2 as MessageType
import proto.Message2Server_pb2 as Message2Server
import proto.Message2Clients_pb2 as Message2Clients
@@ -22,7 +23,6 @@ class Logic(ILogic):

# ID
self.__playerID: int = playerID
self.__playerGUIDs: List[int] = []

self.__playerType: THUAI6.PlayerType = playerType

@@ -188,7 +188,7 @@ class Logic(ILogic):
self.__logger.debug("Called UseSkill")
return self.__comm.UseSkill(skillID, self.__playerID)

def SendMessage(self, toID: int, message: str) -> bool:
def SendMessage(self, toID: int, message: Union[str, bytes]) -> bool:
self.__logger.debug("Called SendMessage")
return self.__comm.SendMessage(toID, message, self.__playerID)

@@ -196,7 +196,7 @@ class Logic(ILogic):
self.__logger.debug("Called HaveMessage")
return not self.__messageQueue.empty()

def GetMessage(self) -> Tuple[int, str]:
def GetMessage(self) -> Tuple[int, Union[str, bytes]]:
self.__logger.debug("Called GetMessage")
if self.__messageQueue.empty():
self.__logger.warning("Message queue is empty!")
@@ -214,7 +214,7 @@ class Logic(ILogic):

def GetPlayerGUIDs(self) -> List[int]:
with self.__mtxState:
return copy.deepcopy(self.__playerGUIDs)
return copy.deepcopy(self.__currentState.guids)

# IStudentAPI使用的接口

@@ -262,6 +262,10 @@ class Logic(ILogic):
self.__logger.debug("Called EndAllAction")
return self.__comm.EndAllAction(self.__playerID)

def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool:
with self.__mtxState:
return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap)

# Logic内部逻辑
def __TryConnection(self) -> bool:
self.__logger.info("Try to connect to server...")
@@ -283,15 +287,6 @@ class Logic(ILogic):
if self.__gameState == THUAI6.GameState.GameStart:
# 读取玩家的GUID
self.__logger.info("Game start!")
self.__playerGUIDs.clear()
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__playerGUIDs.append(obj.student_message.guid)
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__playerGUIDs.append(obj.tricker_message.guid)
self.__currentState.guids = self.__playerGUIDs
self.__bufferState.guids = self.__playerGUIDs

for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "map_message":
@@ -315,26 +310,17 @@ class Logic(ILogic):

elif self.__gameState == THUAI6.GameState.GameRunning:
# 读取玩家的GUID
self.__playerGUIDs.clear()
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__playerGUIDs.append(obj.student_message.guid)
for obj in clientMsg.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__playerGUIDs.append(obj.tricker_message.guid)
self.__currentState.guids = self.__playerGUIDs
self.__bufferState.guids = self.__playerGUIDs
self.__LoadBuffer(clientMsg)
else:
self.__logger.error("Unknown GameState!")
continue
self.__AILoop = False
with self.__cvBuffer:
self.__bufferUpdated = True
self.__counterBuffer = -1
self.__cvBuffer.notify()
self.__logger.info("Game End!")
self.__logger.info("Message thread end!")
self.__AILoop = False

threading.Thread(target=messageThread).start()

@@ -452,9 +438,16 @@ class Logic(ILogic):
self.__logger.debug("Update Gate!")
elif item.WhichOneof("message_of_obj") == "news_message":
if item.news_message.to_id == self.__playerID:
self.__messageQueue.put(
(item.news_message.from_id, item.news_message.news))
self.__logger.debug("Add News!")
if item.news_message.WhichOneof("news") == "text_message":
self.__messageQueue.put(
(item.news_message.from_id, item.news_message.text_message))
self.__logger.debug("Add News!")
elif item.news_message.WhichOneof("news") == "binary_message":
self.__messageQueue.put(
(item.news_message.from_id, item.news_message.binary_message))
self.__logger.debug("Add News!")
else:
self.__logger.error("Unknown News!")
else:
self.__logger.debug(
"Unknown Message!")
@@ -464,15 +457,28 @@ class Logic(ILogic):
self.__bufferState.students.clear()
self.__bufferState.trickers.clear()
self.__bufferState.props.clear()
self.__bufferState.bullets.clear()
self.__bufferState.bombedBullets.clear()
self.__bufferState.guids.clear()
self.__logger.debug("Buffer cleared!")

for obj in message.obj_message:
if obj.WhichOneof("message_of_obj") == "student_message":
self.__bufferState.guids.append(obj.student_message.guid)
for obj in message.obj_message:
if obj.WhichOneof("message_of_obj") == "tricker_message":
self.__bufferState.guids.append(obj.tricker_message.guid)

self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo(
message.all_message)

self.__LoadBufferSelf(message)
for item in message.obj_message:
self.__LoadBufferCase(item)
if Setting.asynchronous():
with self.__mtxState:
self.__currentState, self.__bufferState = self.__bufferState, self.__currentState
self.__counterState = self.__counterBuffer
self.__logger.info("Update state!")
self.__freshed = True
else:
@@ -507,9 +513,16 @@ class Logic(ILogic):
formatter = logging.Formatter(
"[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S')
# 确保文件存在
if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"):
os.makedirs(os.path.dirname(os.path.dirname(
os.path.realpath(__file__))) + "/logs")
# if not os.path.exists(os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/logs"):
# os.makedirs(os.path.dirname(os.path.dirname(
# os.path.realpath(__file__))) + "/logs")

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

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


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

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


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

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

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


"""
print(welcomeString)


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

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

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



+ 71
- 61
CAPI/python/PyAPI/structures.py View File

@@ -143,85 +143,95 @@ class HiddenGateState(Enum):


class Player:
x: int
y: int
speed: int
viewRange: int
playerID: int
guid: int
radius: int
score: int
facingDirection: float
timeUntilSkillAvailable: List[float] = []
playerType: PlayerType
prop: List[PropType] = []
place: PlaceType
bulletType: BulletType
playerState: PlayerState
def __init__(self, **kwargs) -> None:
self.x: int = 0
self.y: int = 0
self.speed: int = 0
self.viewRange: int = 0
self.playerID: int = 0
self.guid: int = 0
self.radius: int = 0
self.score: int = 0
self.facingDirection: float = 0.0
self.timeUntilSkillAvailable: List[float] = []
self.playerType: PlayerType = PlayerType.NullPlayerType
self.prop: List[PropType] = []
self.place: PlaceType = PlaceType.NullPlaceType
self.bulletType: BulletType = BulletType.NullBulletType
self.playerState: PlayerState = PlayerState.NullState


class Student(Player):
studentType: StudentType
determination: int
addiction: int
encourageProgress: int
rouseProgress: int
learningSpeed: int
encourageSpeed: int
dangerAlert: float
buff: List[StudentBuffType] = []
def __init__(self, **kwargs) -> None:
super().__init__()
self.studentType: StudentType = StudentType.NullStudentType
self.determination: int = 0
self.addiction: int = 0
self.encourageProgress: int = 0
self.rouseProgress: int = 0
self.learningSpeed: int = 0
self.encourageSpeed: int = 0
self.dangerAlert: float = 0.0
self.buff: List[StudentBuffType] = []


class Tricker(Player):
trickerType: TrickerType
trickDesire: float
classVolume: float
buff: List[TrickerBuffType] = []
def __init__(self, **kwargs) -> None:
super().__init__()
self.trickerType: TrickerType = TrickerType.NullTrickerType
self.trickDesire: float = 0.0
self.classVolume: float = 0.0
self.buff: List[TrickerBuffType] = []


class Prop:
x: int
y: int
guid: int
type: PropType
place: PlaceType
facingDirection: float
def __init__(self, **kwargs) -> None:
self.x: int = 0
self.y: int = 0
self.guid: int = 0
self.type: PropType = PropType.NullPropType
self.place: PlaceType = PlaceType.NullPlaceType
self.facingDirection: float = 0.0


class Bullet:
bulletType: BulletType
x: int
y: int
facingDirection: float
guid: int
team: PlayerType
place: PlaceType
bombRange: float
speed: int
def __init__(self, **kwargs) -> None:
self.bulletType: BulletType = BulletType.NullBulletType
self.x: int = 0
self.y: int = 0
self.facingDirection: float = 0.0
self.guid: int = 0
self.team: PlayerType = PlayerType.NullPlayerType
self.place: PlaceType = PlaceType.NullPlaceType
self.bombRange: float = 0.0
self.speed: int = 0


class BombedBullet:
bulletType: BulletType
x: int
y: int
facingDirection: float
mappingID: int
bombRange: float
def __init__(self, **kwargs) -> None:
self.bulletType: BulletType = BulletType.NullBulletType
self.x: int = 0
self.y: int = 0
self.facingDirection: float = 0.0
self.mappingID: int = 0
self.bombRange: float = 0.0


class GameMap:
classroomState: Dict[Tuple[int, int], int] = {}
gateState: Dict[Tuple[int, int], int] = {}
chestState: Dict[Tuple[int, int], int] = {}
doorState: Dict[Tuple[int, int], bool] = {}
doorProgress: Dict[Tuple[int, int], int] = {}
hiddenGateState: Dict[Tuple[int, int], HiddenGateState] = {}
def __init__(self, **kwargs) -> None:
self.classroomState: Dict[Tuple[int, int], int] = {}
self.gateState: Dict[Tuple[int, int], int] = {}
self.chestState: Dict[Tuple[int, int], int] = {}
self.doorState: Dict[Tuple[int, int], bool] = {}
self.doorProgress: Dict[Tuple[int, int], int] = {}
self.hiddenGateState: Dict[Tuple[int, int], HiddenGateState] = {}


class GameInfo:
gameTime: int
subjectFinished: int
studentGraduated: int
studentQuited: int
studentScore: int
trickerScore: int
def __init__(self, **kwargs) -> None:
self.gameTime: int = 0
self.subjectFinished: int = 0
self.studentGraduated: int = 0
self.studentQuited: int = 0
self.studentScore: int = 0
self.trickerScore: int = 0

+ 6
- 9
CAPI/python/PyAPI/utils.py View File

@@ -2,7 +2,7 @@ import proto.MessageType_pb2 as MessageType
import proto.Message2Server_pb2 as Message2Server
import proto.Message2Clients_pb2 as Message2Clients
import PyAPI.structures as THUAI6
from typing import Final, List
from typing import Final, List, Union

numOfGridPerCell: Final[int] = 1000

@@ -177,12 +177,10 @@ class Proto2THUAI6(NoInstance):
tricker.trickDesire = trickerMsg.trick_desire
tricker.classVolume = trickerMsg.class_volume
tricker.bulletType = Proto2THUAI6.bulletTypeDict[trickerMsg.bullet_type]
tricker.timeUntilSkillAvailable.clear()
for time in trickerMsg.time_until_skill_available:
tricker.timeUntilSkillAvailable.append(time)
tricker.place = Proto2THUAI6.placeTypeDict[trickerMsg.place]
tricker.playerState = Proto2THUAI6.playerStateDict[trickerMsg.player_state]
tricker.prop.clear()
for item in trickerMsg.prop:
tricker.prop.append(Proto2THUAI6.propTypeDict[item])
tricker.trickerType = Proto2THUAI6.trickerTypeDict[trickerMsg.tricker_type]
@@ -190,7 +188,6 @@ class Proto2THUAI6(NoInstance):
tricker.playerID = trickerMsg.player_id
tricker.viewRange = trickerMsg.view_range
tricker.radius = trickerMsg.radius
tricker.buff.clear()
for buff in trickerMsg.buff:
tricker.buff.append(Proto2THUAI6.trickerBuffTypeDict[buff])
tricker.playerType = THUAI6.PlayerType.TrickerPlayer
@@ -212,11 +209,9 @@ class Proto2THUAI6(NoInstance):
student.encourageProgress = studentMsg.treat_progress
student.rouseProgress = studentMsg.rescue_progress
student.dangerAlert = studentMsg.danger_alert
student.timeUntilSkillAvailable.clear()
for time in studentMsg.time_until_skill_available:
student.timeUntilSkillAvailable.append(time)
student.place = Proto2THUAI6.placeTypeDict[studentMsg.place]
student.prop.clear()
for item in studentMsg.prop:
student.prop.append(Proto2THUAI6.propTypeDict[item])
student.studentType = Proto2THUAI6.studentTypeDict[studentMsg.student_type]
@@ -225,7 +220,6 @@ class Proto2THUAI6(NoInstance):
student.playerID = studentMsg.player_id
student.viewRange = studentMsg.view_range
student.radius = studentMsg.radius
student.buff.clear()
for buff in studentMsg.buff:
student.buff.append(Proto2THUAI6.studentBuffTypeDict[buff])
student.playerType = THUAI6.PlayerType.StudentPlayer
@@ -356,8 +350,11 @@ class THUAI62Proto(NoInstance):
return Message2Server.PropMsg(player_id=id, prop_type=THUAI62Proto.propTypeDict[prop])

@ staticmethod
def THUAI62ProtobufSend(msg: str, toID: int, id: int) -> Message2Server.SendMsg:
return Message2Server.SendMsg(player_id=id, to_player_id=toID, message=msg)
def THUAI62ProtobufSend(msg: Union[str, bytes], toID: int, id: int) -> Message2Server.SendMsg:
if isinstance(msg, str):
return Message2Server.SendMsg(player_id=id, to_player_id=toID, text_message=msg)
elif isinstance(msg, bytes):
return Message2Server.SendMsg(player_id=id, to_player_id=toID, binary_message=msg)

@ staticmethod
def THUAI62ProtobufAttack(angle: float, id: int) -> Message2Server.AttackMsg:


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

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

+ 5
- 5
CAPI/python/run.sh View File

@@ -1,7 +1,7 @@
#!/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 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 &
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 -o&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3&
# python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4&

+ 7
- 3
dependency/Dockerfile/Dockerfile_run View File

@@ -1,6 +1,13 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0.408-jammy-amd64 AS build
MAINTAINER eesast
WORKDIR /usr/local
COPY . .
RUN dotnet publish logic/Server/Server.csproj -c Release -o /usr/local/Server --self-contained true -r linux-x64

FROM eesast/thuai6_base:base
MAINTAINER eesast
WORKDIR /usr/local
COPY --from=build /usr/local/Server .
RUN mkdir /usr/local/team1 && mkdir /usr/local/team2 && mkdir /usr/local/playback
COPY ./dependency/shell/run.sh .
COPY ./CAPI/python /usr/local/PlayerCode/CAPI/python
@@ -10,7 +17,4 @@ WORKDIR /usr/local/PlayerCode/CAPI/python
RUN bash ../../dependency/shell/generate_proto.sh

WORKDIR /usr/local
RUN wget -P . https://cloud.tsinghua.edu.cn/f/e48940314a6d4cdb8bd0/?dl=1
RUN mv 'index.html?dl=1' Server.tar.gz
RUN tar -zxvf Server.tar.gz
ENTRYPOINT [ "bash","./run.sh" ]

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

@@ -0,0 +1,457 @@
# Algorithm

---

天梯分数计算算法

原始记录在:<https://github.com/eesast/THUAI5/discussions/86>

内容如下:

## THUAI4

关于根据队式每场比赛的分数映射到天梯分数的问题:
队式比赛为两队对战,每队得分的区间均为 [0, 2500]。
以 tanh 函数为基础进行设计。
设计原则如下:

1. 输的扣少量天梯分,赢的得大量天梯分
2. 本就有极高天梯分数的虐本就天梯分数低的,这种降维打击现象,天梯分数涨幅极小甚至不涨天梯分
3. 如果在某场比赛中,两者表现差不多,即赢的比输的得分高得不多的话,那么天梯分数涨幅也不是很高
4. 如果本来天梯分数很低的,赢了天梯分数很高的,那么他得到的天梯分会较高,而另一个人,天梯分数降分稍多一些
5. 如果天梯分数低的赢了天梯分数高的,但是这场比赛赢得不多的话,会把两人的分数向中间靠拢
6. 总体上,赢的队伍不会降天梯分;输的队伍不会加天梯分
7. 其他条件相同的情况下,在本场游戏中得分越多,加的天梯分数也越高

上述原则可以保证以下两个目的的达成:

1. 总体来看,进行的游戏场次越多,所有队伍的平均天梯分数就越高
2. 经过足够多次的游戏场次,实力有一定差距的队伍的天体分数差距逐渐拉开,实力相近的队伍的天梯分数不会差别过大,各支队伍的排名趋近于收敛

用 cpp 代码编写算法代码如下(`cal` 函数):

```cpp
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

template <typename T>
using mypair = pair<T, T>;

// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分

mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore)
{

// 调整顺序,让第一个元素成为获胜者,便于计算

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

if (competitionScore.first < competitionScore.second)
{
reverse = true;
}
else if (competitionScore.first == competitionScore.second)
{
if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数
{
return orgScore;
}

if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者
{
reverse = true;
}
else
{
reverse = false;
}
}
if (reverse) // 如果需要换,换两者的顺序
{
swap(competitionScore.first, competitionScore.second);
swap(orgScore.first, orgScore.second);
}


// 转成浮点数
mypair<double> orgScoreLf;
mypair<double> competitionScoreLf;
orgScoreLf.first = orgScore.first;
orgScoreLf.second = orgScore.second;
competitionScoreLf.first = competitionScore.first;
competitionScoreLf.second = competitionScore.second;
mypair<int> resScore;

const double deltaWeight = 80.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化

double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight;
cout << "Tanh delta: " << tanh(delta) << endl;
{

const double firstnerGet = 8e-5; // 胜利者天梯得分权值
const double secondrGet = 5e-6; // 失败者天梯得分权值

double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大
double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大
double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量

resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分
resScore.second = orgScore.second - round((2500.0 - competitionScoreLf.second) * (2500.0 - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分,2500 为得分的最大值(THUAI4 每场得分介于 0~2500 之间)
}

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

return resScore;
}
```

**特别注意**:此算法是在 THUAI4 的比赛直接得分封顶为 2500 分、最低不低于 0 分的前提下设计的,因此并不一定适用于 THUAI5 的情形。

## THUAI5

今年把得分上限这个东西去掉了。理论上今年可以得很高很高分,但是我估计大部分比赛得分在400-600左右,最高估计1000左右。算法 借 鉴 了THUAI4,算法,换了个激活函数(正态CDF),感觉分数变化相对更好了一些?
代码如下:

```cpp
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

template <typename T>
using mypair = pair<T, T>;

double PHI(double x) // THUAI3: Sigmoid; THUAI4: Tanh; THUAI5: Normal Distribution CDF
{
//double a1 = 0.2548292592;
//double a2 = -0.284496736;
//double a3 = 1.421413741;
//double a4 = -1.453152027;
//double a5 = 1.061405429;
//double p = 0.3275911;
//int sign = 1;
//if (x < 0)
// sign = -1;
//x = fabs(x) / sqrt(2.0);
//double t = 1.0 / (1.0 + p * x);
//double y = 1.0 - ((((((a5 * t + a4) * t + a3) * t) + a2) * t) + a1) * t * exp(-x * x);
//double cdf = 0.5 * (1.0 + sign * y);
//return (cdf - 0.5) * 2.0; // 化到[-1,1]之间

return erf(x / sqrt(2));
}

// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分
mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore)
{

// 调整顺序,让第一个元素成为获胜者,便于计算

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

if (competitionScore.first < competitionScore.second)
{
reverse = true;
}
else if (competitionScore.first == competitionScore.second)
{
if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数
{
return orgScore;
}

if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者
{
reverse = true;
}
else
{
reverse = false;
}
}

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


// 转成浮点数
mypair<double> orgScoreLf;
mypair<double> competitionScoreLf;
orgScoreLf.first = orgScore.first;
orgScoreLf.second = orgScore.second;
competitionScoreLf.first = competitionScore.first;
competitionScoreLf.second = competitionScore.second;
mypair<int> resScore;

const double deltaWeight = 90.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化

double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight;
cout << "Normal CDF delta: " << PHI(delta) << endl;
{

const double firstnerGet = 3e-4; // 胜利者天梯得分权值
const double secondrGet = 1e-4; // 失败者天梯得分权值

double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大
double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大
double correct = 0.5 * (PHI((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量

resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - PHI(delta)) * correct); // 胜者所加天梯分
if (competitionScoreLf.second < 1000)
resScore.second = orgScore.second - round((1000.0 - competitionScoreLf.second) * (1000.0 - competitionScoreLf.second) * secondrGet * (1 - PHI(delta)) * correct); // 败者所扣天梯分
else
resScore.second = orgScore.second; // 败者拿1000分,已经很强了,不扣分
}

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

return resScore;
}

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

int main()
{
int x, y;
std::cout << "origin score of team 1 and 2: " << std::endl;
std::cin >> x >> y;
auto ori = mypair<int>(x, y);
std::cout << "game score of team 1 and 2: " << std::endl;
std::cin >> x >> y;
auto sco = mypair<int>(x, y);
Print(cal(ori, sco));
}
```

`1000 - score`(x
`ReLU(1000 - score)`(√
防止真的超过了 1000)

## THUAI6

### high-ladder

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

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

template <typename T>
using mypair = pair<T, T>;

// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分

mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore)
{

// 调整顺序,让第一个元素成为获胜者,便于计算

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

if (competitionScore.first < competitionScore.second)
{
reverse = true;
}
else if (competitionScore.first == competitionScore.second)
{
if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数
{
return orgScore;
}

if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者
{
reverse = true;
}
else
{
reverse = false;
}
}

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

// 转成浮点数
mypair<double> orgScoreLf;
mypair<double> competitionScoreLf;
orgScoreLf.first = orgScore.first;
orgScoreLf.second = orgScore.second;
competitionScoreLf.first = competitionScore.first;
competitionScoreLf.second = competitionScore.second;
mypair<int> resScore;

const double deltaWeight = 1000.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化

double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight;
cout << "Tanh delta: " << tanh(delta) << endl;
{

const double firstnerGet = 9e-6; // 胜利者天梯得分权值
const double secondrGet = 5e-6; // 失败者天梯得分权值

double deltaScore = 2100.0; // 两队竞争分差超过多少时就认为非常大
double correctRate = (orgScoreLf.first - orgScoreLf.second) / (deltaWeight * 1.2); // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大
double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量
cout << "correct: " << correct << endl;
resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分
resScore.second = orgScore.second - round((competitionScoreLf.first - competitionScoreLf.second) * (competitionScoreLf.first - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分
}

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

return resScore;
}
```

### competition

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

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

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

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

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

​ $$y=cx^2$$

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

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

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

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

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

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

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

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

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

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

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

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

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

return resScore;
}

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

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


+ 5
- 1
dependency/proto/Message2Clients.proto View File

@@ -143,7 +143,11 @@ message MessageOfMap

message MessageOfNews
{
string news = 1;
oneof news // 一条新闻
{
string text_message = 1;
bytes binary_message = 4;
}
int64 from_id = 2;
int64 to_id = 3;
}


+ 6
- 1
dependency/proto/Message2Server.proto View File

@@ -32,7 +32,12 @@ message SendMsg
{
int64 player_id = 1;
int64 to_player_id = 2;
string message = 3;
oneof message
{
string text_message = 3;
bytes binary_message = 4;
}
}

message AttackMsg // 相当于攻击


+ 3
- 3
dependency/proto/Protos.csproj View File

@@ -14,11 +14,11 @@
</ItemGroup>-->

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.22.3" />
<PackageReference Include="Grpc" Version="2.46.6" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
<PackageReference Include="Grpc.Tools" Version="2.53.0">
<PackageReference Include="Grpc.Tools" Version="2.54.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>


+ 9
- 0
dependency/shell/README.md View File

@@ -2,3 +2,12 @@

本目录用于存放程序所需的shell脚本

## run.sh

注意:

1. server 和 client 程序要在后台进行

2. 忙等待到 server 结束

3. 结束后生成标志结束的文件

+ 10
- 6
dependency/shell/compile.sh View File

@@ -3,15 +3,19 @@
i=1
flag=1
bind=/usr/local/mnt
while (( $i <= 4 ))
while (( $i <= 5 ))
do
mv -f $bind/player$i.cpp ./API/src/AI.cpp
cmake ./CMakeLists.txt && make >compile_log$i.txt 2>&1
mv ./capi $bind/capi$i # executable file
if [ $? -ne 0 ]; then
if [ -f "${bind}/player${i}.cpp" ]; then
cp -f $bind/player$i.cpp ./API/src/AI.cpp
cmake ./CMakeLists.txt && make -j$(nproc) >compile_log$i.txt 2>&1
mv ./capi $bind/capi$i # executable file
if [ $? -ne 0 ]; then
flag=0
fi
mv ./compile_log$i.txt $bind/compile_log$i.txt
elif [ ! -f "${bind}/player${i}.py" ]; then
flag=0
fi
mv ./compile_log$i.txt $bind/compile_log$i.txt
let "i++"
done
# HTML request to update status.


+ 44
- 9
dependency/shell/run.sh View File

@@ -1,6 +1,15 @@
#!/usr/local

nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --url $URL --token $TOKEN
python_dir=/usr/local/PlayerCode/CAPI/python/PyAPI
playback_dir=/usr/local/playback

if [ $EXPOSED -eq 1 ]; then
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log 2>&1 &
server_pid=$!
else
nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --resultFileName $playback_dir/result --gameTimeInSecond $TIME --notAllowSpectator --url $URL --token $TOKEN --fileName $playback_dir/video --startLockFile $playback_dir/start.lock > $playback_dir/server.log 2>&1 &
server_pid=$!
fi
sleep 5
for k in {1..2}
do
@@ -9,10 +18,11 @@ do
for i in {1..4}
do
j=$((i - 1))
if [ -f "./python/player$i.py"]; then
nice -0 python3 ./python/player$i.py -I 127.0.0.1 -P 8888 -p $j
elif [ -f "./capi$i"]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j
if [ -f "./player$i.py" ]; then
cp -f ./player$i.py $python_dir/AI.py
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
elif [ -f "./capi$i" ]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
else
echo "ERROR. $i is not found."
fi
@@ -21,10 +31,11 @@ do
for i in {5..5}
do
j=$((i - 1))
if [ -f "./python/player$i.py"]; then
nice -0 python3 ./python/player$i.py -I 127.0.0.1 -P 8888 -p $j
elif [ -f "./capi$i"]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j
if [ -f "./player$i.py" ]; then
cp -f ./player$i.py $python_dir/AI.py
nice -0 python3 $python_dir/main.py -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
elif [ -f "./capi$i" ]; then
nice -0 ./capi$i -I 127.0.0.1 -P 8888 -p $j > $playback_dir/team$k-player$j.log 2>&1 &
else
echo "ERROR. $i is not found."
fi
@@ -32,3 +43,27 @@ do
fi
popd
done

sleep 10

if [ -f $playback_dir/start.lock ]; then
ps -p $server_pid
while [ $? -eq 0 ]
do
sleep 1
ps -p $server_pid
done
touch $playback_dir/finish.lock
echo "Finish"
else
echo "Failed to start game."
touch $playback_dir/finish.lock
touch temp.lock
mv -f temp.lock $playback_dir/video.thuaipb
kill -9 $server_pid
fi

result=$(cat /usr/local/playback/result.json)
score0=$(echo "$result" | grep -oP '(?<="Student":)\d+')
score1=$(echo "$result" | grep -oP '(?<="Tricker":)\d+')
curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"result":[{"team_id":0, "score":'${score0}'}, {"team_id":1, "score":'${score1}'}], "mode":'${MODE}'}'

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

@@ -13,6 +13,9 @@

#### 人物
- `std::future<bool> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动
- 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令
- 实际上唤醒或勉励不同的人是有效的
- EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次

#### 攻击
- `std::future<bool> Attack(double angleInRadian)`:`angleInRadian`为攻击方向
@@ -40,7 +43,7 @@
### 信息获取

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

@@ -49,19 +52,20 @@
- `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const` :返回所有可视捣蛋鬼的信息。
- `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const` :返回所有可视道具的信息。
- `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const` :返回所有可视子弹(攻击)的信息。
- `bool HaveView(int gridX, int gridY) const`:判断坐标是否可见

#### 查询特定位置物体的信息

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

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

#### 其他
- `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态
@@ -71,9 +75,9 @@
- `std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const`:返回整张地图的地形信息。可以写成类似`api.GetFullMap()[x][y]`,其中x为地图自上到下第几行,y为自左向右第几列,注意从0开始

### 辅助函数
`static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标grid。
`static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标 grid。

`static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数cell。
`static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数 cell。

下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用
~~~c++
@@ -84,6 +88,14 @@
void PrintSelfInfo() const;
~~~

### 部分属性解释 stuctures.h
~~~c++
struct Player
{
std::vector<PropType> props;//大小固定为3,空的位置为NullPropType
}
~~~

## 接口一览
~~~c++
// 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴
@@ -123,7 +135,7 @@
// 获取视野内可见的道具信息
[[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0;

// 获取地图信息,视野外的地图统一为Land
// 获取地图信息
[[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0;
[[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0;

@@ -155,6 +167,8 @@
{
return grid / numOfGridPerCell;
}
[[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0;

// 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用



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

@@ -8,7 +8,8 @@

#### 移动

- `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为x轴,水平向右方向为y轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。
- `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为 x 轴,水平向右方向为 y 轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。
- 5ms以内的移动指令会被禁止,你不应当使用过小的移动指令
- `def MoveRight(self, timeInMilliseconds: int) -> Future[bool]`即向右移动,`MoveLeft`、`MoveDown`、`MoveUp`同理

#### 使用技能
@@ -18,6 +19,9 @@
#### 人物

- `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动
- 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令
- 实际上唤醒或勉励不同的人是有效的
- EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次

#### 攻击

@@ -31,8 +35,8 @@

#### 勉励与唤醒

- `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家ID的学生。
- `def StartRouseMate(self, mateID: int) -> Future[bool]`:唤醒对应玩家ID的沉迷的学生。
- `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家 ID 的学生。
- `def StartRouseMate(self, mateID: int) -> Future[bool]`:唤醒对应玩家 ID 的沉迷的学生。

#### 地图互动

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

#### 队内信息

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

@@ -61,20 +65,20 @@
- `def GetTrickers(self) -> List[THUAI6.Tricker]` :返回所有可视捣蛋鬼的信息。
- `def GetProps(self) -> List[THUAI6.Prop]` :返回所有可视道具的信息。
- `def GetBullets(self) -> List[THUAI6.Bullet]` :返回所有可视子弹(攻击)的信息。
- `def HaveView(self, gridX: int, gridY: int) -> bool`:判断坐标是否可见

#### 查询特定位置物体的信息

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

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

#### 其他

@@ -86,11 +90,11 @@

### 辅助函数

`def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标grid。
`def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标 grid。

`def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数cell。
`def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数 cell。

下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用
下面为用于DEBUG的输出函数,选手仅在开启 Debug 模式的情况下可以使用

~~~python
def Print(self, cont: str) -> None:
@@ -100,6 +104,13 @@
def PrintSelfInfo(self) -> None:
~~~

### 部分属性解释 stuctures.h
~~~python
class Player:
def __init__(self, **kwargs) -> None:
self.prop: List[PropType] = []//大小固定为3,空的位置为NullPropType
~~~

## 接口一览

~~~python
@@ -261,6 +272,10 @@ class IAPI(metaclass=ABCMeta):
@abstractmethod
def GetGameInfo(self) -> THUAI6.GameInfo:
pass
@abstractmethod
def HaveView(self, gridX: int, gridY: int) -> bool
pass

# 用于DEBUG的输出函数,仅在DEBUG模式下有效



+ 25
- 26
docs/GameRules.md View File

@@ -1,5 +1,5 @@
# 规则
V5.1
V5.5
- [规则](#规则)
- [简则](#简则)
- [地图](#地图)
@@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000}
$$

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

### 人物
- 人物直径为800
@@ -89,10 +89,10 @@ $$
8. 翻窗 Climbing

### 攻击
- 攻击类型CommonAttackOfGhost攻击未写完的作业,会造成对应攻击力的损坏
- 攻击类型CommonAttackOfTricker攻击未写完的作业,会造成对应攻击力的损坏
- 捣蛋鬼攻击交互状态或前后摇的学生,将使学生眩晕4.3s

| 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfGhost| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty |
| 攻击(子弹)类型 |搞蛋鬼的一般攻击CommonAttackOfTricker| 飞刀FlyingKnife | 蹦蹦炸弹BombBomb | 小炸弹JumpyDumpty |
| :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- |
| 子弹爆炸范围 | 0 | 0 | 2000 | 1000 |
| 子弹攻击距离 | 2200 | 78000 | 2200 | 4400 |
@@ -140,14 +140,14 @@ $$

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

### 可视范围
- 小于视野半径
- 对于在草地中的物体,物体中心与玩家中心连线上均为草地方可见
- 对于中心在草地中的物体,物体中心与玩家中心连线上均为草地方可见
- 不在草地的物体,物体中心与玩家中心连线上无墙即可见

### 道具
@@ -199,21 +199,21 @@ $$
| 翻箱速度/ms | 1000 | 1100 | 1000 |1000|

#### Assassin
- 普通攻击为 CommonAttackOfGhost
- 普通攻击为 CommonAttackOfTricker
- 主动技能
- 隐身 BecomeInvisible
0. 隐身 BecomeInvisible
- CD:40s 持续时间:10s
- 在持续时间内玩家隐身
- 使用瞬间得分15
- 使用飞刀
1. 使用飞刀
- CD:30s 持续时间:1s
- 在持续时间内,攻击类型变为飞刀
- 不直接得分

#### Klee
- 普通攻击为 CommonAttackOfGhost
- 普通攻击为 CommonAttackOfTricker
- 主动技能
- 蹦蹦炸弹 JumpyBomb
0. 蹦蹦炸弹 JumpyBomb
- CD:15s 持续时间:3s
- 在持续时间内,攻击类型变为蹦蹦炸弹
- 当蹦蹦炸弹因为碰撞而爆炸,向子弹方向上加上90°,270° 发出2个小炸弹
@@ -221,9 +221,9 @@ $$
- 不直接得分,通过眩晕等获得对应得分

#### 喧哗者ANoisyPerson
- 普通攻击为 CommonAttackOfGhost
- 普通攻击为 CommonAttackOfTricker
- 主动技能
- 嚎叫 Howl
0. 嚎叫 Howl
- CD:25s
- 使用瞬间,在视野半径范围内(不是可视区域)的学生被眩晕5500ms,自己进入800ms的后摇
- 通过眩晕获得对应得分
@@ -231,9 +231,9 @@ $$
- 在场所有学生Bgm系统被设为无用的值

#### Idol
- 普通攻击为 CommonAttackOfGhost
- 普通攻击为 CommonAttackOfTricker
- 主动技能
- ShowTime
0. ShowTime
- CD: 80s 持续时间:10s
- 持续时间内
- 使警戒范围外的学生眩晕并每200ms发送向自己移动200ms的指令(速度为学生本应速度*二者距离/警戒范围)
@@ -258,14 +258,14 @@ $$

#### 运动员
- 主动技能
- 冲撞 CanBeginToCharge
0. 冲撞 CanBeginToCharge
- CD:60s 持续时间:3s
- 在持续时间内,速度变为三倍,期间撞到捣蛋鬼,会导致捣蛋鬼眩晕7.22s,学生眩晕2.09s
- 通过眩晕获得对应得分

#### 教师
- 主动技能
- 惩罚 Punish
0. 惩罚 Punish
- CD:30s
- 使用瞬间,在可视范围内的翻窗、开锁门、攻击前后摇的捣蛋鬼会被眩晕(3070+300*已受伤害/基本伤害(1500000))ms,
- 通过眩晕获得对应得分
@@ -279,22 +279,22 @@ $$
- 当处于可接受指令状态且不在学习时,会积累“冥想进度”,速度为40/ms
- 受到攻击(并非伤害)、进入学习状态或进入不可接受指令状态(包括翻窗)冥想进度清零
- 主动技能5
- 写答案 WriteAnswers
0. 写答案 WriteAnswers
- CD:30s
- 使用瞬间,对于可交互范围内的一间教室的作业增加冥想进度,冥想进度清零
- 通过学习获得对应得分

#### 开心果
- 主动技能
- 唤醒 Rouse
0. 唤醒 Rouse
- CD:120s
- 使用瞬间,唤醒可视范围内一个沉迷中的人
- 通过唤醒获得对应得分
- 勉励 Encourage
1. 勉励 Encourage
- CD:120s
- 使用瞬间,为可视范围内随机一个毅力不足的人试图补充750000的毅力值
- 获得勉励750000的毅力值对应得分
- 鼓舞 Inspire
2. 鼓舞 Inspire
- CD:120s
- 使用瞬间,可视范围内学生(包括自己)获得持续6秒的1.6倍速Buff
- 每鼓舞一个学生得分10
@@ -308,7 +308,6 @@ $$
- 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。

### 人物
- EndAllAction()及Move指令调用数总和一帧内不超过10次
- 眩晕状态中的玩家不能再次被眩晕

### 初始状态
@@ -318,8 +317,6 @@ $$
- 使用钥匙相当于销毁

### 交互
- 在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令
- 实际上唤醒或勉励不同的人是有效的
- 被唤醒或被勉励不属于交互状态,翻窗属于交互状态

### 学习与毕业
@@ -363,10 +360,12 @@ $$

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

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

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

+ 40
- 13
docs/QandA.md View File

@@ -18,18 +18,15 @@ Q: 怎么修改.cmd参数?
A:
见选手包中的使用文档部分

Q:卡死在第一帧不动

Q: 怎么开始游戏?

A:
需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。
A:大概率是你的代码死循环了

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

Q: Mac怎么用?
A:命令行参数的type设置有误

A:
安装Windows虚拟机
try
## C++

Q:显示API项目已卸载
@@ -37,7 +34,7 @@ Q:显示API项目已卸载

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

Q:CAPI.sln编译不通过
Q:CAPI.sln编译不通过(第一种)
情况1:
![std_find_trivial](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/std_find_trivial.jpg)
情况2:
@@ -52,11 +49,24 @@ A:
![项目属性](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/项目属性.png)
确保上图项目属性中平台工具集在 v143,C++17 标准

Q:CAPI编译不通过
Q:CAPI编译不通过(第二种)
![lib](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/lib.png)

A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴

Q:编译好慢啊

A:
1. 尽量不要改其他文件,甚至连点下保存都别点
2. 不要点重新生成,要点生成
3. 开启下图选项
![CompileFaster](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/CompileFaster.png)

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

A:调用了容量为0的vector

## Python

### grpc版本更新失败
@@ -68,12 +78,29 @@ A:
- 可能措施1.
首先保证Python版本在3.9及以上
- 可能措施2. 更换为国内镜像源
在终端输入
`pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple`
在终端输入 `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:咱们这边play函数调用机制究竟是怎么样的?如果50ms内没有执行完当前程序,是在下一帧不会重新调用play吗
还是只有move这样的明确有时间为参量的才会有上面那个机制

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


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

A:初赛结束会调数值及机制,增加新角色
A:初赛结束会调数值及机制,增加新角色

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

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

+ 85
- 47
docs/Tool_tutorial.md View File

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

[toc]

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

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

### 生成模式的设置

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

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

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

### 命令行参数的设置

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

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

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

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

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

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

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



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



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



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

头文件:`#include <chrono>`

@@ -65,9 +65,10 @@ int main()
### 线程睡眠



由于移动过程中会阻塞人物角色,因此玩家可能要在移动后让线程休眠一段时间,直到移动结束。C++ 标准库中使线程休眠需要包含头文件:`#include <thread>`。示例用法:

我们推荐小步移动,不太建议玩家使用线程睡眠超过一帧

```cpp
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 休眠 20 毫秒
std::this_thread::sleep_for(std::chrono::seconds(2)); // 休眠 2 秒
@@ -126,7 +127,7 @@ int main()



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



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



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

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

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

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

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



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



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



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

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

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

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



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



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



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



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



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

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

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

##### 概览

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

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

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

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

// 故此处引用计数为2

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

#### `std::unique_ptr`

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





+ 7
- 2
docs/使用文档.md View File

@@ -6,12 +6,16 @@
## 不应当使用Windows特性的库,不建议使用.h结尾的库,建议使用标准库
- 不要使用conio.h,Windows.h

## 请注意下载器不更新AI.py,AI.cpp和脚本
- [最新版AI.cpp云盘](https://cloud.tsinghua.edu.cn/lib/54b4eb7b-956e-474c-b932-7b1ac29a9267/file/AI.cpp) 和[AI.py云盘](https://cloud.tsinghua.edu.cn/lib/54b4eb7b-956e-474c-b932-7b1ac29a9267/file/AI.py)

## C++接口使用说明

- 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`中编写代码
- 选手不应当修改`AI.cpp`中原来有的代码,除了`void AI::play(IStudentAPI& api)`和`void AI::play(ITrickerAPI& api)`
- 选手不应当修改`AI.cpp`中原来有的代码,除了`void AI::play(IStudentAPI& api)`和`void AI::play(ITrickerAPI& api)`,及修改asynchronous的返回值
- 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
- 选手可以在`AI.cpp`内`void AI::play`外新增函数和变量
- Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式
- Linux:用`cmake`,对`CAPI\cpp\CMakeLists.txt`进行编译
@@ -22,7 +26,8 @@

- 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件
- 然后在`CAPI\python\PyAPI\AI.py`中编写代码
- 选手不应当修改`AI.py`中原来有的代码,除了`StudentPlay(self, api: IStudentAPI)`和`TrickerPlay(self, api: ITrickerAPI)`
- 选手不应当修改`AI.py`中原来有的代码,除了`StudentPlay(self, api: IStudentAPI)`和`TrickerPlay(self, api: ITrickerAPI)`,及修改asynchronous的返回值
- 每帧执行一次`AI::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态
- 选手可以在`AI.py`内新增函数和变量
- Windows:最后通过运行`RunPython.cmd`执行比赛代码
- Linux:通过运行`RunPython.sh`执行比赛代码


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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


BIN
installer/Installer/eesast_software_trans_enlarged.ico View File

Before After

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

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

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

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


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

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


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

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

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


+ 2
- 6
logic/Client/Client.csproj View File

@@ -5,21 +5,17 @@
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ApplicationIcon>EESAST.ico</ApplicationIcon>
<ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<None Remove="Logo.png" />
</ItemGroup>

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

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="FrameRateTask" Version="1.2.0" />
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
<PackageReference Include="Grpc" Version="2.46.6" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
</ItemGroup>


BIN
logic/Client/EESASTLogo.png View File

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

BIN
logic/Client/Logo.png View File

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

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

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

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


+ 63
- 61
logic/Client/MainWindow.xaml.cs View File

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

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

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

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

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

StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime);

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

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

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

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


+ 2
- 2
logic/Client/Properties/launchSettings.json View File

@@ -2,7 +2,7 @@
"profiles": {
"Client": {
"commandName": "Project",
"commandLineArgs": "--port 8888 --characterID 3 --type 1 --occupation 5"
"commandLineArgs": "--port 8892 --characterID 8880 --type 1 --occupation 1 --ip thuai6.eesast.com --cl"
}
}
}
}

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

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


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

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


BIN
logic/Client/eesast_software_trans.ico View File

Before After

BIN
logic/Client/eesast_software_trans_enlarged.ico View File

Before After

+ 1
- 1
logic/ClientTest/ClientTest.csproj View File

@@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
<PackageReference Include="Grpc" Version="2.46.6" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
</ItemGroup>


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

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

namespace GameClass.GameObj
{


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

@@ -36,6 +36,7 @@ namespace GameClass.GameObj

public override bool IgnoreCollideExecutor(IGameObj targetObj)
{
if (targetObj == Parent && CanMove) return true;
if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet)
return true;
return false;


+ 33
- 13
logic/GameClass/GameObj/Character/Character.cs View File

@@ -291,7 +291,7 @@ namespace GameClass.GameObj
}
}
#endregion
#region 状态相关的基本属性与方法
private PlayerStateType playerState = PlayerStateType.Null;
public PlayerStateType PlayerState
{
@@ -318,7 +318,23 @@ namespace GameClass.GameObj
private GameObj? whatInteractingWith = null;
public GameObj? WhatInteractingWith => whatInteractingWith;

private long threadNum = 0;
public long ThreadNum => threadNum;

public void ChangePlayerState(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null)
{
lock (gameObjLock)
{
++threadNum;
whatInteractingWith = gameObj;
if (value != PlayerStateType.Moving)
IsMoving = false;
playerState = (value == PlayerStateType.Moving) ? PlayerStateType.Null : value;
//Debugger.Output(this,playerState.ToString()+" "+IsMoving.ToString());
}
}

public void ChangePlayerStateInOneThread(PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null)
{
lock (gameObjLock)
{
@@ -334,11 +350,26 @@ namespace GameClass.GameObj
{
lock (gameObjLock)
{
++threadNum;
whatInteractingWith = null;
IsMoving = false;
playerState = PlayerStateType.Null;
}
}

public void RemoveFromGame(PlayerStateType playerStateType)
{
lock (gameObjLock)
{
playerState = playerStateType;
CanMove = false;
IsResetting = true;
Position = GameData.PosWhoDie;
place = PlaceType.Grass;
}
}
#endregion

private int score = 0;
public int Score
{
@@ -572,17 +603,6 @@ namespace GameClass.GameObj
this.Vampire = this.OriVampire;
}
}*/
public void RemoveFromGame(PlayerStateType playerStateType)
{
lock (gameObjLock)
{
playerState = playerStateType;
CanMove = false;
IsResetting = true;
Position = GameData.PosWhoDie;
place = PlaceType.Grass;
}
}

public override bool IsRigid => true;
public override ShapeType Shape => ShapeType.Circle;
@@ -594,7 +614,7 @@ namespace GameClass.GameObj
{
return true;
}
if (targetObj.Type == GameObjType.Character && XY.DistanceFloor3(targetObj.Position, this.Position) < this.Radius + targetObj.Radius)
if (targetObj.Type == GameObjType.Character && XY.DistanceCeil3(targetObj.Position, this.Position) < this.Radius + targetObj.Radius - GameData.adjustLength)
return true;
return false;
}


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

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

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


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

@@ -46,7 +46,7 @@ namespace GameClass.GameObj

public bool Repair(int addDegree, Character character)
{
if (DegreeOfRepair == GameData.degreeOfFixedGenerator) return true;
if (DegreeOfRepair == GameData.degreeOfFixedGenerator) return false;
int orgDegreeOfRepair = degreeOfRepair;
DegreeOfRepair += addDegree;
if (DegreeOfRepair > orgDegreeOfRepair)


+ 30
- 12
logic/GameClass/GameObj/Map/Map.cs View File

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

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

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

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


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

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


+ 117
- 82
logic/GameEngine/MoveEngine.cs View File

@@ -79,6 +79,8 @@ namespace GameEngine
return;
if (!obj.IsAvailable || !gameTimer.IsGaming)
return;

long threadNum = (obj.Type == GameObjType.Character) ? ((ICharacter)obj).ThreadNum : 0;//对人特殊处理
new Thread
(
() =>
@@ -87,110 +89,143 @@ namespace GameEngine
obj.IsMoving = true;

double moveVecLength = 0.0;
XY res = new XY(direction, moveVecLength);
XY res = new(direction, moveVecLength);
double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); // 转向,并用deltaLen存储行走的误差
IGameObj? collisionObj = null;
bool isDestroyed = false;
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving,
() =>

bool flag; // 循环标志
do
{
flag = false;
collisionObj = collisionChecker.CheckCollision(obj, obj.Position);
if (collisionObj == null)
break;

switch (OnCollision(obj, collisionObj, res))
{
moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
res = new XY(direction, moveVecLength);
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
break;
case AfterCollision.MoveMax:
break;
}
} while (flag);

// 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志
do
if (!isDestroyed)
{
new FrameRateTaskExecutor<int>(
() => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving,
() =>
{
flag = false;
collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;
moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond;
res = new XY(direction, moveVecLength);

switch (OnCollision(obj, collisionObj, res))
//对人特殊处理
if (threadNum > 0 && ((ICharacter)obj).ThreadNum != threadNum) return false;

// 越界情况处理:如果越界,则与越界方块碰撞
bool flag; // 循环标志
do
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
return false;
case AfterCollision.MoveMax:
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
flag = false;
collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res);
if (collisionObj == null)
break;
}
} while (flag);

deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res)));
switch (OnCollision(obj, collisionObj, res))
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
return false;
case AfterCollision.MoveMax:
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
}
} while (flag);

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

return true;
},
GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
() =>
{
int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
bool flag;
do
return true;
},
GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond,
() =>
{
flag = false;
if (!isDestroyed)
int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond);
bool flag;
do
{
moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
obj.MovingSetPos(res, GetPlaceType(obj.Position + res));
}
else
flag = false;
if (!isDestroyed)
{
switch (OnCollision(obj, collisionObj, res))
moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell;
res = new XY(direction, moveVecLength);
if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null)
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
break;
case AfterCollision.MoveMax:
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
obj.MovingSetPos(res, GetPlaceType(obj.Position + res));
}
else
{
switch (OnCollision(obj, collisionObj, res))
{
case AfterCollision.ContinueCheck:
flag = true;
break;
case AfterCollision.Destroyed:
Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game.");
isDestroyed = true;
break;
case AfterCollision.MoveMax:
if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum)
MoveMax(obj, res);
moveVecLength = 0;
res = new XY(direction, moveVecLength);
break;
}
}
}
} while (flag);
if (leftTime > 0 && obj.IsMoving)
{
Thread.Sleep(leftTime); // 多移动的在这里补回来
}
} while (flag);
if (leftTime > 0 && obj.IsMoving)
{
Thread.Sleep(leftTime); // 多移动的在这里补回来
}
lock (obj.MoveLock)
obj.IsMoving = false; // 结束移动
EndMove(obj);
return 0;
},
maxTotalDuration: moveTime
)
{
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
TimeExceedAction = b =>
lock (obj.MoveLock)
obj.IsMoving = false; // 结束移动
EndMove(obj);
return 0;
},
maxTotalDuration: moveTime
)
{
if (b)
Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");
AllowTimeExceed = true,
MaxTolerantTimeExceedCount = ulong.MaxValue,
TimeExceedAction = b =>
{
if (b)
Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!");

#if DEBUG
else
{
Console.WriteLine("Debug info: Object moving time exceed for once.");
}
else
{
Console.WriteLine("Debug info: Object moving time exceed for once.");
}
#endif
}
}.Start();
}
}.Start();
}
}
).Start();
}


+ 23
- 15
logic/Gaming/ActionManager.cs View File

@@ -35,7 +35,9 @@ namespace Gaming
}
public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection)
{
if (moveTimeInMilliseconds < 5) return false;
if (!playerToMove.Commandable() || !TryToStop()) return false;
if (playerToMove.IsMoving) return false;
characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving);
moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection);
return true;
@@ -70,29 +72,30 @@ namespace Gaming

++generatorForFix.NumOfFixing;
characterManager.SetPlayerState(player, PlayerStateType.Fixing);
long threadNum = player.ThreadNum;
new Thread
(
() =>
{
Thread.Sleep(GameData.frameDuration);
new FrameRateTaskExecutor<int>(
loopCondition: () => gameMap.Timer.IsGaming && player.PlayerState == PlayerStateType.Fixing,
loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum,
loopToDo: () =>
{
if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player))
{
characterManager.SetPlayerState(player);
gameMap.NumOfRepairedGenerators++;
}
if (generatorForFix.DegreeOfRepair == GameData.degreeOfFixedGenerator)
characterManager.SetPlayerState(player);
},
timeInterval: GameData.frameDuration,
finallyReturn: () => 0
)
.Start();
--generatorForFix.NumOfFixing;
}

)
{ IsBackground = true }.Start();
--generatorForFix.NumOfFixing;

return true;
}
@@ -170,8 +173,10 @@ namespace Gaming
{
characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated);
characterManager.SetPlayerState(player, PlayerStateType.Treating);
long threadNum = player.ThreadNum;

new FrameRateTaskExecutor<int>(
loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && player.PlayerState == PlayerStateType.Treating && gameMap.Timer.IsGaming,
loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.ThreadNum && gameMap.Timer.IsGaming,
loopToDo: () =>
{
if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player))
@@ -182,7 +187,7 @@ namespace Gaming
)
.Start();

if (player.PlayerState == PlayerStateType.Treating) characterManager.SetPlayerState(player);
if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player);
else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated);
}
)
@@ -200,13 +205,14 @@ namespace Gaming
return false;
characterManager.SetPlayerState(player, PlayerStateType.Rescuing);
characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued);
long threadNum = player.ThreadNum;

new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && player.PlayerState == PlayerStateType.Rescuing && gameMap.Timer.IsGaming,
loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.ThreadNum && gameMap.Timer.IsGaming,
loopToDo: () =>
{
playerRescued.TimeOfRescue += GameData.frameDuration;
@@ -228,7 +234,7 @@ namespace Gaming
else
characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted);
}
if (player.PlayerState == PlayerStateType.Rescuing) characterManager.SetPlayerState(player);
if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player);
playerRescued.TimeOfRescue = 0;
}
)
@@ -297,13 +303,14 @@ namespace Gaming
// gameMap.Add(addWall);

characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows);
long threadNum = player.ThreadNum;
windowForClimb.WhoIsClimbing = player;
new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.ClimbingThroughWindows && gameMap.Timer.IsGaming,
loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming,
loopToDo: () => { },
timeInterval: GameData.frameDuration,
finallyReturn: () => 0,
@@ -322,7 +329,7 @@ namespace Gaming
moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle());

new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.ClimbingThroughWindows && gameMap.Timer.IsGaming,
loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming,
loopToDo: () =>
{
},
@@ -336,7 +343,7 @@ namespace Gaming
player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed);
windowForClimb.WhoIsClimbing = null;
// gameMap.Remove(addWall);
if (player.PlayerState == PlayerStateType.ClimbingThroughWindows)
if (threadNum == player.ThreadNum)
{
characterManager.SetPlayerState(player);
}
@@ -379,12 +386,13 @@ namespace Gaming
if (!flag) return false;

characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor);
long threadNum = player.ThreadNum;
new Thread
(
() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => flag && player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor,
loopCondition: () => flag && threadNum == player.ThreadNum && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor,
loopToDo: () =>
{
flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null);
@@ -399,7 +407,7 @@ namespace Gaming
{
doorToLock.IsOpen = (!doorToLock.IsOpen);
}
if (player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor)
if (threadNum == player.ThreadNum)
characterManager.SetPlayerState(player);
doorToLock.OpenOrLockDegree = 0;
}
@@ -472,7 +480,7 @@ namespace Gaming
OnCollision: (obj, collisionObj, moveVec) =>
{
SkillWhenColliding((Character)obj, collisionObj);
Preparation.Utility.Debugger.Output(obj, " end move with " + collisionObj.ToString());
//Preparation.Utility.Debugger.Output(obj, " end move with " + collisionObj.ToString());
//if (collisionObj is Mine)
//{
// ActivateMine((Character)obj, (Mine)collisionObj);


+ 9
- 6
logic/Gaming/AttackManager.cs View File

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

namespace Gaming
{
@@ -34,6 +33,7 @@ namespace Gaming
Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64);
if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty)
BulletBomb((Bullet)obj, null);
obj.CanMove = false;
}
);
this.characterManager = characterManager;
@@ -171,17 +171,19 @@ namespace Gaming
return false;
Debugger.Output(player, player.CharacterType.ToString() + "Attack in " + player.BulletOfPlayer.ToString());

Debugger.Output(player, player.Position.ToString() + " " + player.Radius.ToString() + " " + BulletFactory.BulletRadius(player.BulletOfPlayer).ToString());
XY res = player.Position + new XY // 子弹紧贴人物生成。
(
(int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle)),
(int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))
(int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle))) * ((Math.Cos(angle) > 0) ? 1 : -1),
(int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))) * ((Math.Sin(angle) > 0) ? 1 : -1)
);

Bullet? bullet = player.Attack(res, gameMap.GetPlaceType(res));

if (bullet != null)
{
Debugger.Output(player, "Attack in " + bullet.ToString());
player.FacingDirection = new(angle, bullet.BulletAttackRange);
Debugger.Output(bullet, "Attack in " + bullet.Position.ToString());
bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0;
bullet.CanMove = true;
gameMap.Add(bullet);
@@ -189,12 +191,13 @@ namespace Gaming
if (bullet.CastTime > 0)
{
characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack);
long threadNum = player.ThreadNum;

new Thread
(() =>
{
new FrameRateTaskExecutor<int>(
loopCondition: () => player.PlayerState == PlayerStateType.TryingToAttack && gameMap.Timer.IsGaming,
loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming,
loopToDo: () =>
{
},
@@ -206,7 +209,7 @@ namespace Gaming

if (gameMap.Timer.IsGaming)
{
if (player.PlayerState == PlayerStateType.TryingToAttack)
if (threadNum == player.ThreadNum)
{
characterManager.SetPlayerState(player);
}


+ 36
- 19
logic/Gaming/CharacterManager .cs View File

@@ -1,13 +1,9 @@
using System;
using System.Threading;
using System.Collections.Generic;
using GameClass.GameObj;
using Preparation.Utility;
using GameEngine;
using Preparation.Interface;
using Timothy.FrameRateTask;
using System.Numerics;
using System.Timers;

namespace Gaming
{
@@ -24,20 +20,37 @@ namespace Gaming

public void SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null)
{
switch (player.PlayerState)
lock (player.MoveLock)
{
case PlayerStateType.OpeningTheChest:
((Chest)player.WhatInteractingWith).StopOpen();
break;
case PlayerStateType.OpeningTheDoorway:
Doorway doorway = (Doorway)player.WhatInteractingWith;
doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime;
doorway.OpenStartTime = 0;
break;
default:
break;
switch (player.PlayerState)
{
case PlayerStateType.OpeningTheChest:
((Chest)player.WhatInteractingWith).StopOpen();
player.ChangePlayerState(value, gameObj);
break;
case PlayerStateType.OpeningTheDoorway:
Doorway doorway = (Doorway)player.WhatInteractingWith;
doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime;
doorway.OpenStartTime = 0;
player.ChangePlayerState(value, gameObj);
break;
case PlayerStateType.Addicted:
if (value == PlayerStateType.Rescued)
player.ChangePlayerStateInOneThread(value, gameObj);
else
player.ChangePlayerState(value, gameObj);
break;
case PlayerStateType.Rescued:
if (value == PlayerStateType.Addicted)
player.ChangePlayerStateInOneThread(value, gameObj);
else
player.ChangePlayerState(value, gameObj);
break;
default:
player.ChangePlayerState(value, gameObj);
break;
}
}
player.ChangePlayerState(value, gameObj);
}

public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null)
@@ -237,6 +250,7 @@ namespace Gaming
}
}
SetPlayerState(player, PlayerStateType.Addicted);
long threadNum = player.ThreadNum;
new Thread
(() =>
{
@@ -244,7 +258,7 @@ namespace Gaming
Debugger.Output(player, " is addicted ");
#endif
new FrameRateTaskExecutor<int>(
() => (player.PlayerState == PlayerStateType.Addicted || player.PlayerState == PlayerStateType.Rescued) && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming,
() => threadNum == player.ThreadNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming,
() =>
{
player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0;
@@ -273,8 +287,9 @@ namespace Gaming
(() =>
{
SetPlayerState(player, PlayerStateType.Stunned);
long threadNum = player.ThreadNum;
Thread.Sleep(time);
if (player.PlayerState == PlayerStateType.Stunned)
if (threadNum == player.ThreadNum)
SetPlayerState(player);
}
)
@@ -362,13 +377,14 @@ namespace Gaming
if (time <= 0) return false;
if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false;
SetPlayerState(player, PlayerStateType.Swinging);
long threadNum = player.ThreadNum;

new Thread
(() =>
{
Thread.Sleep(time);

if (player.PlayerState == PlayerStateType.Swinging)
if (threadNum == player.ThreadNum)
{
SetPlayerState(player);
}
@@ -383,6 +399,7 @@ namespace Gaming
#if DEBUG
Debugger.Output(player, "die.");
#endif
if (player.PlayerState == PlayerStateType.Deceased) return;
player.RemoveFromGame(PlayerStateType.Deceased);

for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++)


+ 1
- 6
logic/Gaming/Game.cs View File

@@ -80,12 +80,7 @@ namespace Gaming
Character? player = gameMap.FindPlayerToAction(playerID);
if (player != null)
{
bool res = actionManager.MovePlayer(player, moveTimeInMilliseconds, angle);
#if DEBUG
Console.WriteLine($"PlayerID:{playerID} move to ({player.Position.x},{player.Position.y})!");
#endif
return res;

return actionManager.MovePlayer(player, moveTimeInMilliseconds, angle);
}
else
{


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

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


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

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


+ 1
- 0
logic/Preparation/Interface/ICharacter.cs View File

@@ -14,6 +14,7 @@ namespace Preparation.Interface
public BulletType BulletOfPlayer { get; set; }
public CharacterType CharacterType { get; }
public int BulletNum { get; }
public long ThreadNum { get; }

public bool IsGhost();
}


+ 1
- 1
logic/Preparation/Preparation.csproj View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
</ItemGroup>

<ItemGroup>


+ 2
- 1
logic/Preparation/Utility/GameData.cs View File

@@ -9,7 +9,8 @@ namespace Preparation.Utility
#region 基本常数
public const int numOfStepPerSecond = 20; // 每秒行走的步数

public const int tolerancesLength = 10;
public const int tolerancesLength = 3;
public const int adjustLength = 3;

public const int frameDuration = 50; // 每帧时长
public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长


+ 3
- 1
logic/README.md View File

@@ -47,4 +47,6 @@

## 开发人员

- ......(自己加)
- 黄淞:底层逻辑
- 游笑权:Client
- 高思研:Server

+ 5
- 2
logic/Server/ArgumentOption.cs View File

@@ -39,8 +39,8 @@ namespace Server
[Option('f', "fileName", Required = false, HelpText = "The file to store playback file or to read file.")]
public string FileName { get; set; } = DefaultArgumentOptions.FileName;

[Option('s', "allowSpectator", Required = false, HelpText = "Whether to allow a spectator to watch the game.")]
public bool AllowSpectator { get; set; } = true;
[Option("notAllowSpectator", Required = false, HelpText = "Whether to allow a spectator to watch the game.")]
public bool NotAllowSpectator { get; set; } = false;

[Option('b', "playback", Required = false, HelpText = "Whether open the server in a playback mode.")]
public bool Playback { get; set; } = false;
@@ -71,5 +71,8 @@ namespace Server

[Option("resultFileName", Required = false, HelpText = "Result file name, saved as .json")]
public string ResultFileName { get; set; } = DefaultArgumentOptions.FileName;

[Option("startLockFile", Required = false, HelpText = "Whether to create a file that identifies whether the game has started")]
public string StartLockFile { get; set; } = DefaultArgumentOptions.FileName;
}
}

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

@@ -1,7 +1,5 @@
using Protobuf;
using System.Collections.Generic;
using GameClass.GameObj;
using System.Numerics;
using Preparation.Utility;
using Gaming;

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


+ 51
- 25
logic/Server/GameServer.cs View File

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


namespace Server
{
public partial class GameServer : AvailableService.AvailableServiceBase
partial class GameServer : ServerBase
{
private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
private object semaDictLock = new();
private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new();
// private object semaDictLock = new();
protected readonly ArgumentOptions options;
private HttpSender? httpSender;
private object gameLock = new();
@@ -29,13 +25,13 @@ namespace Server
private SemaphoreSlim endGameSem = new(0);
protected readonly Game game;
private uint spectatorMinPlayerID = 2023;
private List<uint> spectatorList = new List<uint>();
public int playerNum;
public int TeamCount => options.TeamCount;
protected long[] communicationToGameID; // 通信用的ID映射到游戏内的ID,通信中0-3为Student,4为Tricker
private readonly object messageToAllClientsLock = new();
public static readonly long SendMessageToClientIntervalInMilliseconds = 50;
private MessageWriter? mwr = null;
private object spetatorJoinLock = new();

public void StartGame()
{
@@ -45,6 +41,7 @@ namespace Server
if (id == GameObj.invalidID) return; //如果有未初始化的玩家,不开始游戏
}
Console.WriteLine("Game starts!");
CreateStartFile();
game.StartGame((int)options.GameTimeInSecond * 1000);
Thread.Sleep(1);
new Thread(() =>
@@ -74,7 +71,17 @@ namespace Server
})
{ IsBackground = true }.Start();
}
public void WaitForEnd()

public void CreateStartFile()
{
if (options.StartLockFile != DefaultArgumentOptions.FileName)
{
using var _ = File.Create(options.StartLockFile);
Console.WriteLine("Successfully Created StartLockFile!");
}
}

public override void WaitForEnd()
{
this.endGameSem.Wait();
mwr?.Dispose();
@@ -115,7 +122,7 @@ namespace Server
game.ClearAllLists();
mwr?.Flush();
if (options.ResultFileName != DefaultArgumentOptions.FileName)
SaveGameResult(options.ResultFileName + ".json");
SaveGameResult(options.ResultFileName.EndsWith(".json") ? options.ResultFileName : options.ResultFileName + ".json");
SendGameResult();
this.endGameSem.Release();
}
@@ -158,17 +165,39 @@ namespace Server
break;
}
}
foreach (var kvp in semaDict)
lock (spetatorJoinLock)
{
kvp.Value.Item1.Release();
}
foreach (var kvp in semaDict)
{
kvp.Value.Item1.Release();
}

foreach (var kvp in semaDict)
// 若此时观战者加入,则死锁,所以需要 spetatorJoinLock

foreach (var kvp in semaDict)
{
kvp.Value.Item2.Wait();
}
}
}
private bool playerDeceased(int playerID)
{
game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
try
{
kvp.Value.Item2.Wait();
foreach (Character character in game.GameMap.GameObjDict[GameObjType.Character])
{
if (character.PlayerID == playerID && character.PlayerState == PlayerStateType.Deceased) return true;
}
}
finally
{
game.GameMap.GameObjLockDict[GameObjType.Character].ExitReadLock();
}
return false;
}
public int[] GetScore()

public override int[] GetScore()
{
int[] score = new int[2]; // 0代表Student,1代表Tricker
game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock();
@@ -222,9 +251,6 @@ namespace Server
int[] score = GetScore();
msg.StudentScore = score[0];
msg.TrickerScore = score[1];
//msg.GateOpened
//msg.HiddenGateRefreshed
//msg.HiddenGateOpened
return msg;
}



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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

+ 102
- 46
logic/Server/RpcServices.cs View File

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

namespace Server
{
public partial class GameServer : AvailableService.AvailableServiceBase
partial class GameServer : ServerBase
{
private int playerCountNow = 0;
protected object spectatorLock = new object();
protected bool isSpectatorJoin = false;
protected bool IsSpectatorJoin
@@ -56,20 +49,21 @@ namespace Server
{

Console.WriteLine($"AddPlayer: {request.PlayerId}");
if (request.PlayerId >= spectatorMinPlayerID && options.AllowSpectator == true)
if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false)
{
// 观战模式
uint tp = (uint)request.PlayerId;
if (!spectatorList.Contains(tp))
lock (spetatorJoinLock) // 具体原因见另一个上锁的地方
{
spectatorList.Add(tp);
Console.WriteLine("A new spectator comes to watch this game.");
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
lock (semaDictLock)
if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1))))
{
Console.WriteLine("A new spectator comes to watch this game.");
IsSpectatorJoin = true;
}
else
{
semaDict.Add(request.PlayerId, temp);
Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}");
return;
}
IsSpectatorJoin = true;
}
do
{
@@ -82,13 +76,31 @@ namespace Server
//Console.WriteLine("Send!");
}
}
catch (InvalidOperationException)
{
if (semaDict.TryRemove(request.PlayerId, out var semas))
{
try
{
semas.Item1.Release();
semas.Item2.Release();
}
catch { }
Console.WriteLine($"The spectator {request.PlayerId} exited");
return;
}
}
catch (Exception)
{
//Console.WriteLine(ex);
// Console.WriteLine(ex);
}
finally
{
semaDict[request.PlayerId].Item2.Release();
try
{
semaDict[request.PlayerId].Item2.Release();
}
catch { }
}
} while (game.GameMap.Timer.IsGaming);
return;
@@ -117,14 +129,17 @@ namespace Server
var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1));
bool start = false;
Console.WriteLine($"Id: {request.PlayerId} joins.");
lock (semaDictLock)
lock (spetatorJoinLock) // 为了保证绝对安全,还是加上这个锁吧
{
semaDict.Add(request.PlayerId, temp);
start = (semaDict.Count - spectatorList.Count) == playerNum;
if (semaDict.TryAdd(request.PlayerId, temp))
{
start = Interlocked.Increment(ref playerCountNow) == playerNum;
}
}
if (start) StartGame();
}

bool exitFlag = false;
do
{
semaDict[request.PlayerId].Item1.Wait();
@@ -138,7 +153,11 @@ namespace Server
}
catch (Exception)
{
//Console.WriteLine(ex);
if (!exitFlag)
{
Console.WriteLine($"The client {request.PlayerId} exited");
exitFlag = true;
}
}
finally
{
@@ -194,7 +213,7 @@ namespace Server
public override Task<BoolRes> SendMessage(SendMsg request, ServerCallContext context)
{
var boolRes = new BoolRes();
if (request.PlayerId >= spectatorMinPlayerID)
if (request.PlayerId >= spectatorMinPlayerID || playerDeceased((int)request.PlayerId))
{
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
@@ -205,30 +224,67 @@ namespace Server
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
}
if (request.Message.Length > 256)

switch (request.MessageCase)
{
case SendMsg.MessageOneofCase.TextMessage:
{
if (request.TextMessage.Length > 256)
{
#if DEBUG
Console.WriteLine("Message string is too long!");
Console.WriteLine("Text message string is too long!");
#endif
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
}
else
{
MessageOfNews news = new();
news.News = request.Message;
news.FromId = request.PlayerId;
news.ToId = request.ToPlayerId;
lock (newsLock)
{
currentNews.Add(news);
}
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
}
MessageOfNews news = new();
news.TextMessage = request.TextMessage;
news.FromId = request.PlayerId;
news.ToId = request.ToPlayerId;
lock (newsLock)
{
currentNews.Add(news);
}
#if DEBUG
Console.WriteLine(news.TextMessage);
#endif
boolRes.ActSuccess = true;
return Task.FromResult(boolRes);
}
case SendMsg.MessageOneofCase.BinaryMessage:
{

if (request.BinaryMessage.Length > 256)
{
#if DEBUG
Console.WriteLine(news.News);
Console.WriteLine("Binary message string is too long!");
#endif
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
}
MessageOfNews news = new();
news.BinaryMessage = request.BinaryMessage;
news.FromId = request.PlayerId;
news.ToId = request.ToPlayerId;
lock (newsLock)
{
currentNews.Add(news);
}
#if DEBUG
Console.Write("BinaryMessageLength: ");
Console.WriteLine(news.BinaryMessage.Length);
#endif
boolRes.ActSuccess = true;
return Task.FromResult(boolRes);
}
default:
{
boolRes.ActSuccess = false;
return Task.FromResult(boolRes);
}
}
boolRes.ActSuccess = true;
return Task.FromResult(boolRes);
}
public override Task<BoolRes> PickProp(PropMsg request, ServerCallContext context)
{


+ 2
- 2
logic/Server/Server.csproj View File

@@ -10,10 +10,10 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="FrameRateTask" Version="1.2.0" />
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
<PackageReference Include="Grpc" Version="2.46.6" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
<PackageReference Include="Grpc.Tools" Version="2.53.0">
<PackageReference Include="Grpc.Tools" Version="2.54.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>


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

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

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

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

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

ping -n 2 127.0.0.1 > NUL

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

+ 7
- 0
logic/cmd/gameServerAndClient.cmd View File

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

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

ping -n 2 127.0.0.1 > NUL

start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --port 8888 --characterID 114514 --type 0 --occupation 1

+ 1
- 1
playback/Playback/Playback.csproj View File

@@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Google.Protobuf" Version="3.22.3" />
</ItemGroup>

<ItemGroup>


BIN
resource/AIcpp.png View File

Before After
Width: 1413  |  Height: 1253  |  Size: 57 kB

BIN
resource/AIpy.png View File

Before After
Width: 1453  |  Height: 1241  |  Size: 57 kB

BIN
resource/CompileFaster.png View File

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save