diff --git a/.github/workflows/upload_COS.yml b/.github/workflows/upload_COS.yml index e67fe86..a1be1f8 100644 --- a/.github/workflows/upload_COS.yml +++ b/.github/workflows/upload_COS.yml @@ -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 diff --git a/CAPI/cpp/API/include/AI.h b/CAPI/cpp/API/include/AI.h index cb6b373..d99063f 100644 --- a/CAPI/cpp/API/include/AI.h +++ b/CAPI/cpp/API/include/AI.h @@ -4,6 +4,10 @@ #include "API.h" +#undef GetMessage +#undef SendMessage +#undef PeekMessage + class IAI { public: diff --git a/CAPI/cpp/API/include/API.h b/CAPI/cpp/API/include/API.h index 75824ae..81d73ed 100644 --- a/CAPI/cpp/API/include/API.h +++ b/CAPI/cpp/API/include/API.h @@ -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 GetMessage() = 0; @@ -83,6 +87,8 @@ public: virtual bool Attack(double angle) = 0; virtual std::vector 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 EndAllAction() = 0; // 发送信息、接受信息,注意收消息时无消息则返回nullopt - virtual std::future SendMessage(int64_t, std::string) = 0; + virtual std::future SendTextMessage(int64_t, std::string) = 0; + virtual std::future SendBinaryMessage(int64_t, std::string) = 0; [[nodiscard]] virtual bool HaveMessage() = 0; [[nodiscard]] virtual std::pair 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 StartOpenChest() override; std::future EndAllAction() override; - std::future SendMessage(int64_t, std::string) override; + std::future SendTextMessage(int64_t, std::string) override; + std::future SendBinaryMessage(int64_t, std::string) override; [[nodiscard]] bool HaveMessage() override; [[nodiscard]] std::pair GetMessage() override; @@ -271,6 +281,8 @@ public: std::future Graduate() override; [[nodiscard]] std::shared_ptr 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 StartOpenChest() override; std::future EndAllAction() override; - std::future SendMessage(int64_t, std::string) override; + std::future SendTextMessage(int64_t, std::string) override; + std::future SendBinaryMessage(int64_t, std::string) override; [[nodiscard]] bool HaveMessage() override; [[nodiscard]] std::pair GetMessage() override; @@ -356,6 +369,8 @@ public: std::future Attack(double angleInRadian) override; [[nodiscard]] std::shared_ptr 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 StartOpenChest() override; std::future EndAllAction() override; - std::future SendMessage(int64_t, std::string) override; + std::future SendTextMessage(int64_t, std::string) override; + std::future SendBinaryMessage(int64_t, std::string) override; [[nodiscard]] bool HaveMessage() override; [[nodiscard]] std::pair GetMessage() override; @@ -439,6 +455,8 @@ public: std::future Graduate() override; [[nodiscard]] virtual std::shared_ptr 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 StartOpenChest() override; std::future EndAllAction() override; - std::future SendMessage(int64_t, std::string) override; + std::future SendTextMessage(int64_t, std::string) override; + std::future SendBinaryMessage(int64_t, std::string) override; [[nodiscard]] bool HaveMessage() override; [[nodiscard]] std::pair GetMessage() override; @@ -509,6 +528,8 @@ public: std::future Attack(double angleInRadian) override; [[nodiscard]] std::shared_ptr 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; diff --git a/CAPI/cpp/API/include/Communication.h b/CAPI/cpp/API/include/Communication.h index 2f75e43..c4b98c7 100644 --- a/CAPI/cpp/API/include/Communication.h +++ b/CAPI/cpp/API/include/Communication.h @@ -14,6 +14,10 @@ #include #include +#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); diff --git a/CAPI/cpp/API/include/ConcurrentQueue.hpp b/CAPI/cpp/API/include/ConcurrentQueue.hpp index 19fdf04..08e4a7c 100644 --- a/CAPI/cpp/API/include/ConcurrentQueue.hpp +++ b/CAPI/cpp/API/include/ConcurrentQueue.hpp @@ -8,6 +8,10 @@ #include #include +#undef GetMessage +#undef SendMessage +#undef PeekMessage + template class ConcurrentQueue { diff --git a/CAPI/cpp/API/include/constants.h b/CAPI/cpp/API/include/constants.h index 1bf0cd6..51d2034 100644 --- a/CAPI/cpp/API/include/constants.h +++ b/CAPI/cpp/API/include/constants.h @@ -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 diff --git a/CAPI/cpp/API/include/logic.h b/CAPI/cpp/API/include/logic.h index 7bd8d1a..686b3dc 100644 --- a/CAPI/cpp/API/include/logic.h +++ b/CAPI/cpp/API/include/logic.h @@ -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 playerGUIDs; - std::unique_ptr 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 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); diff --git a/CAPI/cpp/API/include/state.h b/CAPI/cpp/API/include/state.h index 02f5543..c27c479 100644 --- a/CAPI/cpp/API/include/state.h +++ b/CAPI/cpp/API/include/state.h @@ -9,6 +9,10 @@ #include "structures.h" +#undef GetMessage +#undef SendMessage +#undef PeekMessage + // 存储场上的状态 struct State { diff --git a/CAPI/cpp/API/include/structures.h b/CAPI/cpp/API/include/structures.h index ccda444..4831b94 100644 --- a/CAPI/cpp/API/include/structures.h +++ b/CAPI/cpp/API/include/structures.h @@ -8,6 +8,10 @@ #include #include +#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 { diff --git a/CAPI/cpp/API/include/utils.hpp b/CAPI/cpp/API/include/utils.hpp index 1044b74..07e52c5 100644 --- a/CAPI/cpp/API/include/utils.hpp +++ b/CAPI/cpp/API/include/utils.hpp @@ -13,6 +13,10 @@ #include "structures.h" +#undef GetMessage +#undef SendMessage +#undef PeekMessage + namespace AssistFunction { @@ -202,6 +206,12 @@ namespace Proto2THUAI6 }; + inline std::map 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 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; diff --git a/CAPI/cpp/API/src/AI.cpp b/CAPI/cpp/API/src/AI.cpp index c37b914..6152fd0 100644 --- a/CAPI/cpp/API/src/AI.cpp +++ b/CAPI/cpp/API/src/AI.cpp @@ -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) diff --git a/CAPI/cpp/API/src/API.cpp b/CAPI/cpp/API/src/API.cpp index 9b4693a..49138a6 100644 --- a/CAPI/cpp/API/src/API.cpp +++ b/CAPI/cpp/API/src/API.cpp @@ -1,6 +1,11 @@ #include #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 TrickerAPI::EndAllAction() { return logic.EndAllAction(); }); } -std::future StudentAPI::SendMessage(int64_t toID, std::string message) +std::future StudentAPI::SendTextMessage(int64_t toID, std::string message) +{ + return std::async(std::launch::async, [=]() + { return logic.SendMessage(toID, message, false); }); +} + +std::future TrickerAPI::SendTextMessage(int64_t toID, std::string message) +{ + return std::async(std::launch::async, [=]() + { return logic.SendMessage(toID, message, false); }); +} + +std::future 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 TrickerAPI::SendMessage(int64_t toID, std::string message) +std::future 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 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); diff --git a/CAPI/cpp/API/src/Communication.cpp b/CAPI/cpp/API/src/Communication.cpp index 6bcf8d8..09156d2 100644 --- a/CAPI/cpp/API/src/Communication.cpp +++ b/CAPI/cpp/API/src/Communication.cpp @@ -5,6 +5,10 @@ #include #include +#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 lock(mtxMessage); + message2Client = std::move(buffer2Client); haveNewMessage = true; } cvMessage.notify_one(); diff --git a/CAPI/cpp/API/src/DebugAPI.cpp b/CAPI/cpp/API/src/DebugAPI.cpp index 8aa3165..b56cd4c 100644 --- a/CAPI/cpp/API/src/DebugAPI.cpp +++ b/CAPI/cpp/API/src/DebugAPI.cpp @@ -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 TrickerDebugAPI::EndAllAction() return result; }); } -std::future StudentDebugAPI::SendMessage(int64_t toID, std::string message) +std::future 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 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 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 TrickerDebugAPI::SendMessage(int64_t toID, std::string message) +std::future 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 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); diff --git a/CAPI/cpp/API/src/logic.cpp b/CAPI/cpp/API/src/logic.cpp index 8baaee1..62167c4 100644 --- a/CAPI/cpp/API/src/logic.cpp +++ b/CAPI/cpp/API/src/logic.cpp @@ -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 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 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 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) diff --git a/CAPI/cpp/API/src/main.cpp b/CAPI/cpp/API/src/main.cpp index ee0ecb6..02276ee 100644 --- a/CAPI/cpp/API/src/main.cpp +++ b/CAPI/cpp/API/src/main.cpp @@ -3,11 +3,36 @@ #include "structures.h" #include #include +#include + +#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); } diff --git a/CAPI/cpp/proto/Message2Clients.pb.cc b/CAPI/cpp/proto/Message2Clients.pb.cc index 3de566f..147fd09 100644 --- a/CAPI/cpp/proto/Message2Clients.pb.cc +++ b/CAPI/cpp/proto/Message2Clients.pb.cc @@ -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(reinterpret_cast(&_impl_.to_id_) - reinterpret_cast(&_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(reinterpret_cast(&_impl_.to_id_) - reinterpret_cast(&_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(reinterpret_cast(&_impl_.to_id_) - reinterpret_cast(&_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(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(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(this->_internal_news().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.MessageOfNews.news" + this->_internal_text_message().data(), static_cast(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(&_impl_.from_id_), reinterpret_cast(&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 diff --git a/CAPI/cpp/proto/Message2Clients.pb.h b/CAPI/cpp/proto/Message2Clients.pb.h index c1050a1..8bbb6e2 100644 --- a/CAPI/cpp/proto/Message2Clients.pb.h +++ b/CAPI/cpp/proto/Message2Clients.pb.h @@ -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( @@ -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 - 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 + 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 + 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 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 - 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(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(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 + 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(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 diff --git a/CAPI/cpp/proto/Message2Server.pb.cc b/CAPI/cpp/proto/Message2Server.pb.cc index e54b2cd..88f5014 100644 --- a/CAPI/cpp/proto/Message2Server.pb.cc +++ b/CAPI/cpp/proto/Message2Server.pb.cc @@ -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(reinterpret_cast(&_impl_.to_player_id_) - reinterpret_cast(&_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(reinterpret_cast(&_impl_.to_player_id_) - reinterpret_cast(&_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(reinterpret_cast(&_impl_.to_player_id_) - reinterpret_cast(&_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(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(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(this->_internal_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.SendMsg.message" + this->_internal_text_message().data(), static_cast(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(&_impl_.player_id_), reinterpret_cast(&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 diff --git a/CAPI/cpp/proto/Message2Server.pb.h b/CAPI/cpp/proto/Message2Server.pb.h index 1322b48..7b09cdd 100644 --- a/CAPI/cpp/proto/Message2Server.pb.h +++ b/CAPI/cpp/proto/Message2Server.pb.h @@ -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( @@ -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 - 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 + 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 + 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 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 - 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(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(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 + 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(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 diff --git a/CAPI/python/PyAPI/AI.py b/CAPI/python/PyAPI/AI.py index 010a3dc..740f7cb 100644 --- a/CAPI/python/PyAPI/AI.py +++ b/CAPI/python/PyAPI/AI.py @@ -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: diff --git a/CAPI/python/PyAPI/API.py b/CAPI/python/PyAPI/API.py index 2ce4506..cbade53 100644 --- a/CAPI/python/PyAPI/API.py +++ b/CAPI/python/PyAPI/API.py @@ -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: diff --git a/CAPI/python/PyAPI/Communication.py b/CAPI/python/PyAPI/Communication.py index e3b5960..19dfb04 100644 --- a/CAPI/python/PyAPI/Communication.py +++ b/CAPI/python/PyAPI/Communication.py @@ -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)) diff --git a/CAPI/python/PyAPI/DebugAPI.py b/CAPI/python/PyAPI/DebugAPI.py index a784026..deeb2c2 100644 --- a/CAPI/python/PyAPI/DebugAPI.py +++ b/CAPI/python/PyAPI/DebugAPI.py @@ -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: diff --git a/CAPI/python/PyAPI/Interface.py b/CAPI/python/PyAPI/Interface.py index 4d38060..c408c61 100644 --- a/CAPI/python/PyAPI/Interface.py +++ b/CAPI/python/PyAPI/Interface.py @@ -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 diff --git a/CAPI/python/PyAPI/State.py b/CAPI/python/PyAPI/State.py index f2eb339..d493617 100644 --- a/CAPI/python/PyAPI/State.py +++ b/CAPI/python/PyAPI/State.py @@ -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] diff --git a/CAPI/python/PyAPI/constants.py b/CAPI/python/PyAPI/constants.py index 9426e73..de7d171 100644 --- a/CAPI/python/PyAPI/constants.py +++ b/CAPI/python/PyAPI/constants.py @@ -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 diff --git a/CAPI/python/PyAPI/logic.py b/CAPI/python/PyAPI/logic.py index 1907451..5f034ab 100644 --- a/CAPI/python/PyAPI/logic.py +++ b/CAPI/python/PyAPI/logic.py @@ -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") diff --git a/CAPI/python/PyAPI/main.py b/CAPI/python/PyAPI/main.py index d915634..621dc55 100644 --- a/CAPI/python/PyAPI/main.py +++ b/CAPI/python/PyAPI/main.py @@ -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) diff --git a/CAPI/python/PyAPI/structures.py b/CAPI/python/PyAPI/structures.py index 939f452..72e61b4 100644 --- a/CAPI/python/PyAPI/structures.py +++ b/CAPI/python/PyAPI/structures.py @@ -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 diff --git a/CAPI/python/PyAPI/utils.py b/CAPI/python/PyAPI/utils.py index e286212..42c85a4 100644 --- a/CAPI/python/PyAPI/utils.py +++ b/CAPI/python/PyAPI/utils.py @@ -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: diff --git a/CAPI/python/requirements.txt b/CAPI/python/requirements.txt index 1e31ecf..3f4ef6c 100644 --- a/CAPI/python/requirements.txt +++ b/CAPI/python/requirements.txt @@ -1,2 +1,3 @@ grpcio==1.52.0 grpcio-tools==1.52.0 +numpy diff --git a/CAPI/python/run.sh b/CAPI/python/run.sh index f41c458..6d74095 100755 --- a/CAPI/python/run.sh +++ b/CAPI/python/run.sh @@ -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 & \ No newline at end of file +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& \ No newline at end of file diff --git a/dependency/Dockerfile/Dockerfile_run b/dependency/Dockerfile/Dockerfile_run index 4efce6b..001d93a 100644 --- a/dependency/Dockerfile/Dockerfile_run +++ b/dependency/Dockerfile/Dockerfile_run @@ -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" ] diff --git a/dependency/algorithm/README.md b/dependency/algorithm/README.md new file mode 100644 index 0000000..1ea7788 --- /dev/null +++ b/dependency/algorithm/README.md @@ -0,0 +1,457 @@ +# Algorithm + +--- + +天梯分数计算算法 + +原始记录在: + +内容如下: + +## THUAI4 + +关于根据队式每场比赛的分数映射到天梯分数的问题: +队式比赛为两队对战,每队得分的区间均为 [0, 2500]。 +以 tanh 函数为基础进行设计。 +设计原则如下: + +1. 输的扣少量天梯分,赢的得大量天梯分 +2. 本就有极高天梯分数的虐本就天梯分数低的,这种降维打击现象,天梯分数涨幅极小甚至不涨天梯分 +3. 如果在某场比赛中,两者表现差不多,即赢的比输的得分高得不多的话,那么天梯分数涨幅也不是很高 +4. 如果本来天梯分数很低的,赢了天梯分数很高的,那么他得到的天梯分会较高,而另一个人,天梯分数降分稍多一些 +5. 如果天梯分数低的赢了天梯分数高的,但是这场比赛赢得不多的话,会把两人的分数向中间靠拢 +6. 总体上,赢的队伍不会降天梯分;输的队伍不会加天梯分 +7. 其他条件相同的情况下,在本场游戏中得分越多,加的天梯分数也越高 + +上述原则可以保证以下两个目的的达成: + +1. 总体来看,进行的游戏场次越多,所有队伍的平均天梯分数就越高 +2. 经过足够多次的游戏场次,实力有一定差距的队伍的天体分数差距逐渐拉开,实力相近的队伍的天梯分数不会差别过大,各支队伍的排名趋近于收敛 + +用 cpp 代码编写算法代码如下(`cal` 函数): + +```cpp +#include +#include +#include +using namespace std; + +template +using mypair = pair; + +// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 + +mypair cal(mypair orgScore, mypair 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 orgScoreLf; + mypair competitionScoreLf; + orgScoreLf.first = orgScore.first; + orgScoreLf.second = orgScore.second; + competitionScoreLf.first = competitionScore.first; + competitionScoreLf.second = competitionScore.second; + mypair 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 +#include +#include +using namespace std; + +template +using mypair = pair; + +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 cal(mypair orgScore, mypair 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 orgScoreLf; + mypair competitionScoreLf; + orgScoreLf.first = orgScore.first; + orgScoreLf.second = orgScore.second; + competitionScoreLf.first = competitionScore.first; + competitionScoreLf.second = competitionScore.second; + mypair 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 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(x, y); + std::cout << "game score of team 1 and 2: " << std::endl; + std::cin >> x >> y; + auto sco = mypair(x, y); + Print(cal(ori, sco)); +} +``` + +`1000 - score`(x +`ReLU(1000 - score)`(√ +防止真的超过了 1000) + +## THUAI6 + +### high-ladder + +因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 + +```c++ +#include +#include +#include +using namespace std; + +template +using mypair = pair; + +// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 + +mypair cal(mypair orgScore, mypair 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 orgScoreLf; + mypair competitionScoreLf; + orgScoreLf.first = orgScore.first; + orgScoreLf.second = orgScore.second; + competitionScoreLf.first = competitionScore.first; + competitionScoreLf.second = competitionScore.second; + mypair 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 +#include +#include +#include +using namespace std; + +template +using mypair = pair; +double minScore = 100; + +double TieScore(double gameScore) +{ + const double get = 9e-5; // 天梯得分权值 + double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore + double highScore = 6000.0; // 将highScore设定为较大值,使得correct较小 + double correct = 1 - 0.375 * (tanh((highScore - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 + cout << "correct: " << correct << endl; + int score = round(gameScore * gameScore * get * correct / 4); + return score > minScore / 4 ? score : minScore / 4; +} + +double WinScore(double delta, double winnerGameScore) // 根据游戏得分差值,与绝对分数,决定最后的加分 +{ + assert(delta > 0); + const double firstnerGet = 9e-5; // 胜利者天梯得分权值 + double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore + double correct = 1 - 0.375 * (tanh((delta - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 + cout << "correct: " << correct << endl; + int score = round(delta * delta * firstnerGet * correct); + return score > minScore ? score : minScore; +} + +// orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 +mypair cal(mypair orgScore, mypair competitionScore) +{ + // 调整顺序,让第一个元素成为获胜者,便于计算 + + bool reverse = false; // 记录是否需要调整 + + if (competitionScore.first < competitionScore.second) + { + reverse = true; + } + + if (reverse) // 如果需要换,换两者的顺序 + { + swap(competitionScore.first, competitionScore.second); + swap(orgScore.first, orgScore.second); + } + + double delta = competitionScore.first - competitionScore.second; + double addScore; + mypair resScore; + + // 先处理平局的情况 + if (delta == 0) + { + addScore = TieScore(competitionScore.first); + resScore = mypair(orgScore.first + addScore, orgScore.second + addScore); + } + + // 再处理有胜负的情况 + else + { + addScore = WinScore(delta, competitionScore.first); + resScore = mypair(orgScore.first + addScore, orgScore.second); + } + + // 如果换过,再换回来 + if (reverse) + { + swap(resScore.first, resScore.second); + } + + return resScore; +} + +void Print(mypair score) +{ + std::cout << "team1: " << score.first << std::endl + << "team2: " << score.second << std::endl; +} + +int main() +{ + double x, y, t, i = 0; + cin >> t; + while (i < t) + { + cout << "----------------------------------------\n"; + std::cout << "origin score of team 1 and 2: " << std::endl; + std::cin >> x >> y; + auto ori = mypair(x, y); + std::cout << "game score of team 1 and 2: " << std::endl; + std::cin >> x >> y; + auto sco = mypair(x, y); + Print(cal(ori, sco)); + ++i; + } + return 0; +} +``` + diff --git a/dependency/proto/Message2Clients.proto b/dependency/proto/Message2Clients.proto index fe5b97b..c1711e3 100755 --- a/dependency/proto/Message2Clients.proto +++ b/dependency/proto/Message2Clients.proto @@ -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; } diff --git a/dependency/proto/Message2Server.proto b/dependency/proto/Message2Server.proto index d14b3cd..d764f5e 100755 --- a/dependency/proto/Message2Server.proto +++ b/dependency/proto/Message2Server.proto @@ -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 // 相当于攻击 diff --git a/dependency/proto/Protos.csproj b/dependency/proto/Protos.csproj index 98000c5..87fe468 100755 --- a/dependency/proto/Protos.csproj +++ b/dependency/proto/Protos.csproj @@ -14,11 +14,11 @@ --> - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/dependency/shell/README.md b/dependency/shell/README.md index 03496ee..b25b71a 100644 --- a/dependency/shell/README.md +++ b/dependency/shell/README.md @@ -2,3 +2,12 @@ 本目录用于存放程序所需的shell脚本 +## run.sh + +注意: + +1. server 和 client 程序要在后台进行 + +2. 忙等待到 server 结束 + +3. 结束后生成标志结束的文件 diff --git a/dependency/shell/compile.sh b/dependency/shell/compile.sh index db2cac2..382cd85 100644 --- a/dependency/shell/compile.sh +++ b/dependency/shell/compile.sh @@ -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. diff --git a/dependency/shell/run.sh b/dependency/shell/run.sh index 9c586b8..6a30e94 100644 --- a/dependency/shell/run.sh +++ b/dependency/shell/run.sh @@ -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}'}' diff --git a/docs/CAPI接口(cpp).md b/docs/CAPI接口(cpp).md index 01fc608..4f31cca 100644 --- a/docs/CAPI接口(cpp).md +++ b/docs/CAPI接口(cpp).md @@ -13,6 +13,9 @@ #### 人物 - `std::future EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动 +- 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 + - 实际上唤醒或勉励不同的人是有效的 +- EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 #### 攻击 - `std::future Attack(double angleInRadian)`:`angleInRadian`为攻击方向 @@ -40,7 +43,7 @@ ### 信息获取 #### 队内信息 - - `std::future SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 + - `std::future SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 - `std::pair GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 @@ -49,19 +52,20 @@ - `std::vector> GetTrickers() const` :返回所有可视捣蛋鬼的信息。 - `std::vector> GetProps() const` :返回所有可视道具的信息。 - `std::vector> 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 GetGameInfo() const`:查询当前游戏状态 @@ -71,9 +75,9 @@ - `std::vector> 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 props;//大小固定为3,空的位置为NullPropType + } +~~~ + ## 接口一览 ~~~c++ // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 @@ -123,7 +135,7 @@ // 获取视野内可见的道具信息 [[nodiscard]] virtual std::vector> GetProps() const = 0; - // 获取地图信息,视野外的地图统一为Land + // 获取地图信息 [[nodiscard]] virtual std::vector> 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模式的情况下可以使用 diff --git a/docs/CAPI接口(python).md b/docs/CAPI接口(python).md index c821959..851e055 100644 --- a/docs/CAPI接口(python).md +++ b/docs/CAPI接口(python).md @@ -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模式下有效 diff --git a/docs/GameRules.md b/docs/GameRules.md index ffc4fd4..ba03c8b 100644 --- a/docs/GameRules.md +++ b/docs/GameRules.md @@ -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的小炸弹有碰撞体积 +- 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 ### 职业 - 学生职业可以重复选择 \ No newline at end of file diff --git a/docs/QandA.md b/docs/QandA.md index 6e32bd1..ad8ac0e 100644 --- a/docs/QandA.md +++ b/docs/QandA.md @@ -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:初赛结束会调数值及机制,增加新角色 \ No newline at end of file +A:初赛结束会调数值及机制,增加新角色 + +Q:初赛后会修改什么呢? + +A:可能的修改:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM;HaveView()修改 等 \ No newline at end of file diff --git a/docs/Tool_tutorial.md b/docs/Tool_tutorial.md index 65b71b2..995689a 100644 --- a/docs/Tool_tutorial.md +++ b/docs/Tool_tutorial.md @@ -2,9 +2,9 @@ [toc] -## Visual Studio使用说明 +## Visual Studio 使用说明 -比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 +比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 ### 生成模式的设置 @@ -12,7 +12,7 @@ ![image-20230416010705076](https://raw.githubusercontent.com/shangfengh/THUAI6/new/resource/image-20230416010705076.png) -可以更改生成模式为`Debug`或`Release` +可以更改生成模式为 `Debug` 或 `Release` ### 命令行参数的设置 @@ -22,19 +22,19 @@ 在命令参数一栏中加入命令行参数进行调试 -### cmd脚本的参数修改 +### cmd 脚本的参数修改 -右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 +右键点击 `.cmd` 或 `.bat` 文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上 `::`,可以注释掉该行。 -## C++接口必看 +## C++ 接口必看 **在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! ** -除非特殊指明,以下代码均在 MSVC 19.28.29913 /std:c++17 与 g++ 10.2 for linux -std=c++17 两个平台下通过。 +除非特殊指明,以下代码均在 MSVC 19.28.29913 x64 `/std:c++17` 与 GCC 10.2 x86_64-linux-gnu `-std=c++17` 两个平台下通过。 -由于我们的比赛最终会运行在Linux平台上,因此程设课上学到的一些只适用于Windows的C++操作很可能并不能正确执行。此外,代码中使用了大量Modern C++中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用C++接口必须了解的知识。 +由于我们的比赛最终会运行在 Linux 平台上,因此程设课上学到的一些只适用于 Windows 的 C++ 操作很可能并不能正确执行。此外,代码中使用了大量 Modern C++ 中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用 C++ 接口必须了解的知识。 @@ -42,7 +42,7 @@ -编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!! 应当使用 `std::chrono` +编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!!应当使用 `std::chrono` 头文件:`#include ` @@ -65,9 +65,10 @@ int main() ### 线程睡眠 - 由于移动过程中会阻塞人物角色,因此玩家可能要在移动后让线程休眠一段时间,直到移动结束。C++ 标准库中使线程休眠需要包含头文件:`#include `。示例用法: +我们推荐小步移动,不太建议玩家使用线程睡眠超过一帧 + ```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 `,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。 @@ -154,7 +155,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 ```cpp std::vector v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ... -v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是10 +v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是 10 v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 } ``` @@ -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 `。用于开启新的线程。示例代码: @@ -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 + + void IntArrayDeleter(int* p) { delete[] p; } + + int main() + { + std::shared_ptr sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 + sp.get()[0] = 0; // 访问第 0 个元素 + // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 + return 0; + } + + // 或者利用 lambda 表达式:std::shared_ptr sp(new int[10], [](int* p) { delete[] p; }); + ``` + + + C++17 起 + + ```cpp + std::shared_ptr sp(new int[10]); + sp[0] = 0; // 访问第 0 个元素 + ``` - ```cpp - #include - - void IntArrayDeleter(int* p) { delete[] p; } - - int main() - { - std::shared_ptr sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 - // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 - return 0; - } - - // 或者利用 lambda 表达式:std::shared_ptr sp(new int[10], [](int* p) { delete[] p; }); - ``` + + C++20 起 + + ```cpp + auto sp = std::make_shared(10); + sp[0] = 0; // 访问第 0 个元素 + ``` @@ -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 sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 +这是因为,只有复制构造函数里面才有使引用计数加一的操作。即当我们写 `std::shared_ptr sq = sp` 的时候,确实引用计数变成了 2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 另一个著名的错误用法,请继续阅读 `std::weak_ptr`。 @@ -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(5); // 创建一个 int 对象并初始化为 5 + std::cout << *p << std::endl; // 输出 5 + // 该 int 对象随着 p 的析构而被 delete +} +``` + +需要注意的是,由于[标准委员会的疏忽~忘了~(partly an oversight)](https://herbsutter.com/gotw/_102/),C++14 中才引进`std::make_unique`,C++11 中无法使用。因此 C++11 若想使用则需自定义 `make_unique`: + +```cpp +namespace +{ + template + std::unique_ptr make_unique( Args&&... args ) + { + return std::unique_ptr(new T( std::forward(args)...)); + } +} +``` diff --git a/docs/使用文档.md b/docs/使用文档.md index 3782837..b9f3d46 100644 --- a/docs/使用文档.md +++ b/docs/使用文档.md @@ -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`执行比赛代码 diff --git a/installer/Installer/Common.cs b/installer/Installer/Common.cs index 0f3697d..d38059f 100644 --- a/installer/Installer/Common.cs +++ b/installer/Installer/Common.cs @@ -10,7 +10,7 @@ namespace starter.viewmodel.common { public abstract class NotificationObject : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; ///< summary> /// announce notification /// @@ -25,21 +25,21 @@ namespace starter.viewmodel.common /// public class BaseCommand : ICommand { - private Func _canExecute; - private Action _execute; + private Func? _canExecute; + private Action _execute; - public BaseCommand(Func canExecute, Action execute) + public BaseCommand(Func? canExecute, Action execute) { _canExecute = canExecute; _execute = execute; } - public BaseCommand(Action execute) : + public BaseCommand(Action execute) : this(null, execute) { } - public event EventHandler CanExecuteChanged + public event EventHandler? CanExecuteChanged { add { @@ -57,12 +57,12 @@ namespace starter.viewmodel.common } } - public bool CanExecute(object parameter) + public bool CanExecute(object? parameter) { return _canExecute == null ? true : _canExecute(parameter); } - public void Execute(object parameter) + public void Execute(object? parameter) { if (_execute != null && CanExecute(parameter)) { @@ -79,15 +79,15 @@ namespace starter.viewmodel.common { return false; } - string checkvalue = value.ToString(); - string targetvalue = parameter.ToString(); + string checkvalue = value.ToString() ?? ""; + string targetvalue = parameter.ToString() ?? ""; bool r = checkvalue.Equals(targetvalue); return r; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value == null || parameter == null) + if (value is null || parameter is null) { return null; } @@ -132,22 +132,22 @@ namespace starter.viewmodel.common static bool _isUpdating = false; private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - PasswordBox pb = d as PasswordBox; + PasswordBox pb = (d as PasswordBox)!; pb.PasswordChanged -= Pb_PasswordChanged; if (!_isUpdating) - (d as PasswordBox).Password = e.NewValue.ToString(); + (d as PasswordBox)!.Password = e.NewValue.ToString(); pb.PasswordChanged += Pb_PasswordChanged; } private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - PasswordBox pb = d as PasswordBox; + PasswordBox pb = (d as PasswordBox)!; pb.PasswordChanged += Pb_PasswordChanged; } private static void Pb_PasswordChanged(object sender, RoutedEventArgs e) { - PasswordBox pb = sender as PasswordBox; + PasswordBox pb = (sender as PasswordBox)!; _isUpdating = true; SetPassword(pb, pb.Password); _isUpdating = false; diff --git a/installer/Installer/Installer.csproj b/installer/Installer/Installer.csproj index 53d5639..0d16d13 100644 --- a/installer/Installer/Installer.csproj +++ b/installer/Installer/Installer.csproj @@ -6,8 +6,21 @@ enable true true + eesast_software_trans_enlarged.ico + eesast_software_trans.png + + + + + + + True + \ + + + diff --git a/installer/Installer/MainWindow.xaml b/installer/Installer/MainWindow.xaml index c29b75d..85f52ab 100644 --- a/installer/Installer/MainWindow.xaml +++ b/installer/Installer/MainWindow.xaml @@ -5,7 +5,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Installer" xmlns:c="clr-namespace:starter.viewmodel.common" mc:Ignorable="d" - Title="Installer" Window.SizeToContent="WidthAndHeight"> + Title="Installer" Window.SizeToContent="WidthAndHeight" + ResizeMode="CanMinimize" + > diff --git a/installer/Installer/Model.cs b/installer/Installer/Model.cs index f9bea79..a95f17b 100644 --- a/installer/Installer/Model.cs +++ b/installer/Installer/Model.cs @@ -21,7 +21,6 @@ using System.Net.Http; using System.Windows; using System.Windows.Shapes; //using System.Windows.Forms; -using System.Threading.Tasks; using System.Threading; using MessageBox = System.Windows.MessageBox; @@ -153,18 +152,22 @@ namespace starter.viewmodel.settings } public bool RecallUser() { - Username = Web.ReadJson("email"); - if (Username == null || Username.Equals("")) + var username = Web.ReadJson("email"); + if (username == null || username.Equals("")) { Username = ""; return false; } - Password = Web.ReadJson("password"); - if (Password == null || Username.Equals("")) + Username = username; + + var password = Web.ReadJson("password"); + if (password == null || password.Equals("")) { Password = ""; return false; } + Password = password; + return true; } public bool ForgetUser() @@ -210,8 +213,6 @@ namespace starter.viewmodel.settings switch (CodeRoute.Substring(CodeRoute.LastIndexOf('.') + 1)) { case "cpp": - Language = "cpp"; - break; case "h": Language = "cpp"; break; @@ -244,15 +245,12 @@ namespace starter.viewmodel.settings } public UsingOS ReadUsingOS() { - string OS = Web.ReadJson("OS"); - if (OS == null) - return UsingOS.Win; - else if (OS.Equals("linux")) - return UsingOS.Linux; - else if (OS.Equals("osx")) - return UsingOS.OSX; - else - return UsingOS.Win; + return Web.ReadJson("OS") switch + { + "linux" => UsingOS.Linux, + "osx" => UsingOS.OSX, + _ => UsingOS.Win, + }; } /// /// Route of files @@ -274,7 +272,7 @@ namespace starter.viewmodel.settings { get; set; } - public string Language + public string? Language { get; set; } @@ -394,7 +392,7 @@ namespace Downloader public class Data { public static string path = ""; // 标记路径记录文件THUAI6.json的路径 - public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 + public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 public static string dataPath = ""; // C盘的文档文件夹 public Data(string path) { @@ -403,7 +401,7 @@ namespace Downloader Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); if (File.Exists(Data.path)) { - var dict = new Dictionary(); + Dictionary? dict; using (StreamReader r = new StreamReader(Data.path)) { string json = r.ReadToEnd(); @@ -411,7 +409,7 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.TryDeserializeJson>(json); if (dict != null && dict.ContainsKey("installpath")) { FilePath = dict["installpath"].Replace('\\', '/'); @@ -425,11 +423,12 @@ namespace Downloader } else { - FilePath = System.IO.Path.GetDirectoryName(@path); + FilePath = System.IO.Path.GetDirectoryName(@path) + ?? throw new Exception("Failed to get the path of the file"); //将dat文件写入程序运行路径 string json; - Dictionary dict = new Dictionary(); + Dictionary? dict; using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); using (StreamReader r = new StreamReader(fs)) { @@ -438,7 +437,7 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.TryDeserializeJson>(json); dict?.Add("installpath", path); } using FileStream fs2 = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); @@ -451,7 +450,7 @@ namespace Downloader public static void ResetFilepath(string newPath) { string json; - Dictionary dict = new Dictionary(); + Dictionary? dict; FilePath = newPath.Replace('\\', '/'); path = System.IO.Path.Combine(dataPath, "THUAI6.json"); using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); @@ -462,14 +461,14 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.TryDeserializeJson>(json); if (dict != null && dict.ContainsKey("installpath")) { dict["installpath"] = newPath; } else { - dict.Add("installpath", newPath); + dict?.Add("installpath", newPath); } if (dict == null || !dict.ContainsKey("download")) { @@ -517,9 +516,10 @@ namespace Downloader // 创建存储桶 try { - string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID - string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 - string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 + string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID + string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 + ?? throw new Exception("本地文件夹路径获取失败"); + string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); Dictionary test = request.GetRequestHeaders(); @@ -553,7 +553,7 @@ namespace Downloader public static string GetFileMd5Hash(string strFileFullPath) { - FileStream fst = null; + FileStream? fst = null; try { fst = new FileStream(strFileFullPath, FileMode.Open, FileAccess.Read); @@ -634,7 +634,7 @@ namespace Downloader using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); - Dictionary jsonDict = JsonConvert.DeserializeObject>(json); + var jsonDict = Utils.DeserializeJson1>(json); string updatingFolder = ""; switch (OS) { @@ -815,7 +815,7 @@ namespace Downloader { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - var dict = JsonConvert.DeserializeObject>(json); + var dict = Utils.TryDeserializeJson>(json); if (dict == null || !dict.ContainsKey("download") || "false" == dict["download"]) { return false; @@ -865,15 +865,15 @@ namespace Downloader using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); - Dictionary jsonDict = JsonConvert.DeserializeObject>(json); + // var jsonDict = Utils.DeserializeJson>(json); newFileName.Clear(); updateFileName.Clear(); newFileName.Add("THUAI6.tar.gz"); Download(); - Stream inStream = null; - Stream gzipStream = null; - TarArchive tarArchive = null; + Stream? inStream = null; + Stream? gzipStream = null; + TarArchive? tarArchive = null; try { using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) @@ -886,7 +886,7 @@ namespace Downloader } } } - catch (Exception ex) + catch { //出错 } @@ -899,7 +899,7 @@ namespace Downloader FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); fileInfo.Delete(); string json2; - Dictionary dict = new Dictionary(); + Dictionary? dict; string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); using (StreamReader r = new StreamReader(fs)) @@ -909,7 +909,7 @@ namespace Downloader { json2 += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json2); + dict = Utils.TryDeserializeJson>(json2); if (dict == null || !dict.ContainsKey("download")) { dict?.Add("download", "true"); @@ -1000,7 +1000,7 @@ namespace Downloader using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, "hash.json"))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace("/", @"\\"); - Dictionary jsonDict = JsonConvert.DeserializeObject>(json); + Dictionary jsonDict = Utils.DeserializeJson1>(json); Change_all_hash(Data.FilePath, jsonDict); OverwriteHash(jsonDict); break; @@ -1008,7 +1008,7 @@ namespace Downloader else { Console.WriteLine("读取路径失败!请重新输入文件路径:"); - Data.ResetFilepath(Console.ReadLine()); + Data.ResetFilepath(Console.ReadLine() ?? ""); } } } @@ -1058,7 +1058,7 @@ namespace Downloader } string json2; - Dictionary dict = new Dictionary(); + Dictionary? dict; string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); using (StreamReader r = new StreamReader(fs)) @@ -1068,7 +1068,7 @@ namespace Downloader { json2 += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json2); + dict = Utils.TryDeserializeJson>(json2); if (dict == null || !dict.ContainsKey("download")) { dict?.Add("download", "false"); @@ -1189,7 +1189,7 @@ namespace Downloader while (true) { Console.WriteLine($"1. 更新hash.json 2. 检查更新 3.下载{ProgramName} 4.删除{ProgramName} 5.启动进程 6.移动{ProgramName}到其它路径"); - string choose = Console.ReadLine(); + string choose = Console.ReadLine() ?? ""; if (choose == "1") { if (!CheckAlreadyDownload()) @@ -1216,7 +1216,7 @@ namespace Downloader else { Console.WriteLine("读取路径失败!请重新输入文件路径:"); - Data.ResetFilepath(Console.ReadLine()); + Data.ResetFilepath(Console.ReadLine() ?? ""); } } } @@ -1230,7 +1230,7 @@ namespace Downloader { string newpath; Console.WriteLine("请输入下载路径:"); - newpath = Console.ReadLine(); + newpath = Console.ReadLine() ?? ""; Data.ResetFilepath(newpath); DownloadAll(); } @@ -1253,15 +1253,15 @@ namespace Downloader else if (choose == "6") { string newPath; - newPath = Console.ReadLine(); + newPath = Console.ReadLine() ?? ""; MoveProgram(newPath); } else if (choose == "7") { Console.WriteLine("请输入email:"); - string username = Console.ReadLine(); + string username = Console.ReadLine() ?? ""; Console.WriteLine("请输入密码:"); - string password = Console.ReadLine(); + string password = Console.ReadLine() ?? ""; await web.LoginToEEsast(client, username, password); } @@ -1285,7 +1285,8 @@ namespace Downloader string keyHead = "Installer/"; Tencent_cos_download downloader = new Tencent_cos_download(); string hashName = "installerHash.json"; - string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); + string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) + ?? throw new Exception("Failed to get current directory"); int result = 0; try { @@ -1301,7 +1302,7 @@ namespace Downloader using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, hashName))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); - Dictionary jsonDict = JsonConvert.DeserializeObject>(json); + var jsonDict = Utils.TryDeserializeJson>(json); string md5 = ""; List awaitUpdate = new List(); if (jsonDict != null) @@ -1343,15 +1344,16 @@ namespace Downloader static public bool SelfUpdateDismissed() { string json; - string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); + string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) + ?? throw new Exception("Failed to get directory!"); if (!File.Exists(System.IO.Path.Combine(dir, "updateList.json"))) return false; using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, "updateList.json"))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); - List jsonList; + List? jsonList; if (json != null) - jsonList = JsonConvert.DeserializeObject>(json); + jsonList = Utils.TryDeserializeJson>(json); else return false; if (jsonList != null && jsonList.Contains("Dismiss")) @@ -1403,7 +1405,7 @@ namespace WebConnect throw new Exception("no token!"); logintoken = token; SaveToken(); - var info = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + var info = Utils.DeserializeJson1>(await response.Content.ReadAsStringAsync()); Downloader.UserInfo._id = info["_id"]; Downloader.UserInfo.email = info["email"]; break; @@ -1459,19 +1461,19 @@ namespace WebConnect { case System.Net.HttpStatusCode.OK: - var res = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) - string region = "ap-beijing"; // 设置一个默认的存储桶地域 + var res = Utils.DeserializeJson1>(await response.Content.ReadAsStringAsync()); + string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) + string region = "ap-beijing"; // 设置一个默认的存储桶地域 CosXmlConfig config = new CosXmlConfig.Builder() - .IsHttps(true) // 设置默认 HTTPS 请求 - .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID - .SetRegion(region) // 设置一个默认的存储桶地域 - .SetDebugLog(true) // 显示日志 - .Build(); // 创建 CosXmlConfig 对象 - string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; - string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; - string tmpToken = res["SecurityToken"]; //"临时密钥 token"; - long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]);//临时密钥有效截止时间,精确到秒 + .IsHttps(true) // 设置默认 HTTPS 请求 + .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID + .SetRegion(region) // 设置一个默认的存储桶地域 + .SetDebugLog(true) // 显示日志 + .Build(); // 创建 CosXmlConfig 对象 + string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; + string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; + string tmpToken = res["SecurityToken"]; //"临时密钥 token"; + long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]); //临时密钥有效截止时间,精确到秒 QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider( tmpSecretId, tmpSecretKey, tmpExpiredTime, tmpToken ); @@ -1584,12 +1586,12 @@ namespace WebConnect { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(json); if (dict.ContainsKey("token")) { dict.Remove("token"); } - dict?.Add("token", logintoken); + dict.Add("token", logintoken); } using FileStream fs2 = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.ReadWrite); using StreamWriter sw = new StreamWriter(fs2); @@ -1627,7 +1629,7 @@ namespace WebConnect json += @"{""THUAI6""" + ":" + @"""2023""}"; } Dictionary dict = new Dictionary(); - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(json); if (!dict.ContainsKey(key)) { dict.Add(key, data); @@ -1651,7 +1653,7 @@ namespace WebConnect } } - public static string ReadJson(string key) + public static string? ReadJson(string key) { try { @@ -1664,7 +1666,7 @@ namespace WebConnect { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(json); fs.Close(); sr.Close(); return dict[key]; @@ -1691,7 +1693,7 @@ namespace WebConnect { json += @"{""THUAI6""" + ":" + @"""2023""}"; } - dict = JsonConvert.DeserializeObject>(json); + dict = Utils.DeserializeJson1>(json); if (!dict.ContainsKey("token")) { return false; @@ -1745,9 +1747,9 @@ namespace WebConnect var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); var info = await response.Content.ReadAsStringAsync(); - var s1 = JsonConvert.DeserializeObject>(info)["data"]; - var s2 = JsonConvert.DeserializeObject>>(s1.ToString())["contest_team_member"]; - var sres = JsonConvert.DeserializeObject>(s2[0].ToString())["team_id"]; + var s1 = Utils.DeserializeJson1>(info)["data"]; + var s2 = Utils.DeserializeJson1>>(s1.ToString() ?? "")["contest_team_member"]; + var sres = Utils.DeserializeJson1>(s2[0].ToString() ?? "")["team_id"]; return sres; } async public Task GetUserId(string learnNumber) @@ -1773,4 +1775,20 @@ namespace WebConnect public string Token { get; set; } = ""; } + internal static class Utils + { + public static T DeserializeJson1(string json) + where T : notnull + { + return JsonConvert.DeserializeObject(json) + ?? throw new Exception("Failed to deserialize json."); + } + + public static T? TryDeserializeJson(string json) + where T : notnull + { + return JsonConvert.DeserializeObject(json); + } + } + } diff --git a/installer/Installer/ViewModel.cs b/installer/Installer/ViewModel.cs index ca84add..742fbb1 100644 --- a/installer/Installer/ViewModel.cs +++ b/installer/Installer/ViewModel.cs @@ -36,14 +36,15 @@ namespace starter.viewmodel.settings //Program.Tencent_cos_download.UpdateHash(); Status = SettingsModel.Status.working; - string CurrentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); + string currentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) + ?? throw new Exception("Fail to get current directory"); //MessageBox.Show("更新器工作正常"); if (!Program.Tencent_cos_download.SelfUpdateDismissed()) { switch (Program.Tencent_cos_download.CheckSelfVersion()) { case 1: - Process.Start(System.IO.Path.Combine(CurrentDirectory, "InstallerUpdater.exe")); + Process.Start(System.IO.Path.Combine(currentDirectory, "InstallerUpdater.exe")); Environment.Exit(0); break; case -1: @@ -156,7 +157,7 @@ namespace starter.viewmodel.settings { e.Cancel = true; MessageBox.Show("下载取消"); - if (e.Argument.ToString().Equals("Manual")) + if (e.Argument?.ToString()?.Equals("Manual") ?? false) { Status = SettingsModel.Status.menu; } @@ -167,7 +168,7 @@ namespace starter.viewmodel.settings else { if (obj.Update()) - if (e.Argument.ToString().Equals("Manual")) + if (e.Argument?.ToString()?.Equals("Manual") ?? false) { e.Result = 1; } @@ -598,14 +599,14 @@ namespace starter.viewmodel.settings return ""; } - private BaseCommand clickBrowseCommand; + private BaseCommand? clickBrowseCommand; public BaseCommand ClickBrowseCommand { get { if (clickBrowseCommand == null) { - clickBrowseCommand = new BaseCommand(new Action(o => + clickBrowseCommand = new BaseCommand(new Action(o => { Route = RouteSelectWindow("Folder"); })); @@ -613,14 +614,14 @@ namespace starter.viewmodel.settings return clickBrowseCommand; } } - private BaseCommand clickConfirmCommand; + private BaseCommand? clickConfirmCommand; public BaseCommand ClickConfirmCommand { get { if (clickConfirmCommand == null) { - clickConfirmCommand = new BaseCommand(new Action(o => + clickConfirmCommand = new BaseCommand(new Action(o => { if (Status == SettingsModel.Status.newUser) { @@ -672,14 +673,14 @@ namespace starter.viewmodel.settings return clickConfirmCommand; } } - private BaseCommand clickUpdateCommand; + private BaseCommand? clickUpdateCommand; public BaseCommand ClickUpdateCommand { get { if (clickUpdateCommand == null) { - clickUpdateCommand = new BaseCommand(new Action(o => + clickUpdateCommand = new BaseCommand(new Action(o => { this.RaisePropertyChanged("UpdateInfoVis"); if (obj.UpdatePlanned) @@ -719,14 +720,14 @@ namespace starter.viewmodel.settings return clickUpdateCommand; } } - private BaseCommand clickMoveCommand; + private BaseCommand? clickMoveCommand; public BaseCommand ClickMoveCommand { get { if (clickMoveCommand == null) { - clickMoveCommand = new BaseCommand(new Action(o => + clickMoveCommand = new BaseCommand(new Action(o => { Status = SettingsModel.Status.move; })); @@ -734,14 +735,14 @@ namespace starter.viewmodel.settings return clickMoveCommand; } } - private BaseCommand clickUninstCommand; + private BaseCommand? clickUninstCommand; public BaseCommand ClickUninstCommand { get { if (clickUninstCommand == null) { - clickUninstCommand = new BaseCommand(new Action(o => + clickUninstCommand = new BaseCommand(new Action(o => { UpdateInfoVis = Visibility.Collapsed; this.RaisePropertyChanged("UpdateInfoVis"); @@ -767,14 +768,14 @@ namespace starter.viewmodel.settings } } - private BaseCommand clickLoginCommand; + private BaseCommand? clickLoginCommand; public BaseCommand ClickLoginCommand { get { if (clickLoginCommand == null) { - clickLoginCommand = new BaseCommand(new Action(async o => + clickLoginCommand = new BaseCommand(new Action(async o => { switch (await obj.Login()) { @@ -813,14 +814,14 @@ namespace starter.viewmodel.settings } } - private BaseCommand clickLaunchCommand; + private BaseCommand? clickLaunchCommand; public BaseCommand ClickLaunchCommand { get { if (clickLaunchCommand == null) { - clickLaunchCommand = new BaseCommand(new Action(o => + clickLaunchCommand = new BaseCommand(new Action(o => { if (obj.UpdatePlanned) { @@ -840,14 +841,14 @@ namespace starter.viewmodel.settings return clickLaunchCommand; } } - private BaseCommand clickEditCommand; + private BaseCommand? clickEditCommand; public BaseCommand ClickEditCommand { get { if (clickEditCommand == null) { - clickEditCommand = new BaseCommand(new Action(o => + clickEditCommand = new BaseCommand(new Action(o => { Status = SettingsModel.Status.menu; if (obj.UpdatePlanned) @@ -858,14 +859,14 @@ namespace starter.viewmodel.settings return clickEditCommand; } } - private BaseCommand clickBackCommand; + private BaseCommand? clickBackCommand; public BaseCommand ClickBackCommand { get { if (clickBackCommand == null) { - clickBackCommand = new BaseCommand(new Action(o => + clickBackCommand = new BaseCommand(new Action(o => { UpdateInfoVis = Visibility.Collapsed; this.RaisePropertyChanged("UpdateInfoVis"); @@ -878,14 +879,14 @@ namespace starter.viewmodel.settings return clickBackCommand; } } - private BaseCommand clickUploadCommand; + private BaseCommand? clickUploadCommand; public BaseCommand ClickUploadCommand { get { if (clickUploadCommand == null) { - clickUploadCommand = new BaseCommand(new Action(async o => + clickUploadCommand = new BaseCommand(new Action(async o => { if (obj.UploadReady) { @@ -953,14 +954,14 @@ namespace starter.viewmodel.settings return clickUploadCommand; } } - private BaseCommand clickAboutUploadCommand; + private BaseCommand? clickAboutUploadCommand; public BaseCommand ClickAboutUploadCommand { get { if (clickAboutUploadCommand == null) { - clickAboutUploadCommand = new BaseCommand(new Action(o => + clickAboutUploadCommand = new BaseCommand(new Action(o => { if (obj.UploadReady) { @@ -987,14 +988,14 @@ namespace starter.viewmodel.settings return clickAboutUploadCommand; } } - private BaseCommand clickExitCommand; + private BaseCommand? clickExitCommand; public BaseCommand ClickExitCommand { get { if (clickExitCommand == null) { - clickExitCommand = new BaseCommand(new Action(o => + clickExitCommand = new BaseCommand(new Action(o => { Application.Current.Shutdown(); })); @@ -1002,14 +1003,14 @@ namespace starter.viewmodel.settings return clickExitCommand; } } - private BaseCommand clickShiftLanguageCommand; + private BaseCommand? clickShiftLanguageCommand; public BaseCommand ClickShiftLanguageCommand { get { if (clickShiftLanguageCommand == null) { - clickShiftLanguageCommand = new BaseCommand(new Action(o => + clickShiftLanguageCommand = new BaseCommand(new Action(o => { if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) obj.launchLanguage = SettingsModel.LaunchLanguage.python; @@ -1023,14 +1024,14 @@ namespace starter.viewmodel.settings return clickShiftLanguageCommand; } } - private BaseCommand clickReadCommand; + private BaseCommand? clickReadCommand; public BaseCommand ClickReadCommand { get { if (clickReadCommand == null) { - clickReadCommand = new BaseCommand(new Action(o => + clickReadCommand = new BaseCommand(new Action(o => { if (!Directory.Exists(Route + "/THUAI6/win")) { @@ -1050,14 +1051,14 @@ namespace starter.viewmodel.settings return clickReadCommand; } } - private BaseCommand clickSwitchOSCommand; + private BaseCommand? clickSwitchOSCommand; public BaseCommand ClickSwitchOSCommand { get { if (clickSwitchOSCommand == null) { - clickSwitchOSCommand = new BaseCommand(new Action(o => + clickSwitchOSCommand = new BaseCommand(new Action(o => { switch (obj.usingOS) { diff --git a/installer/Installer/eesast_software_trans_enlarged.ico b/installer/Installer/eesast_software_trans_enlarged.ico new file mode 100644 index 0000000..cbafc97 Binary files /dev/null and b/installer/Installer/eesast_software_trans_enlarged.ico differ diff --git a/installer/InstallerUpdater/InstallerUpdater.csproj b/installer/InstallerUpdater/InstallerUpdater.csproj index 0dd4fb5..3be5681 100644 --- a/installer/InstallerUpdater/InstallerUpdater.csproj +++ b/installer/InstallerUpdater/InstallerUpdater.csproj @@ -5,8 +5,16 @@ net6.0-windows enable true + eesast_software_trans.png + + + True + \ + + + diff --git a/installer/InstallerUpdater/MainWindow.xaml b/installer/InstallerUpdater/MainWindow.xaml index 271161a..5a3fdf1 100644 --- a/installer/InstallerUpdater/MainWindow.xaml +++ b/installer/InstallerUpdater/MainWindow.xaml @@ -5,7 +5,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:InstallerUpdater" mc:Ignorable="d" - Title="MainWindow" Height="180" Width="300"> + Title="MainWindow" Height="180" Width="300" + ResizeMode="CanMinimize" + > diff --git a/installer/InstallerUpdater/Program.cs b/installer/InstallerUpdater/Program.cs index 9a9fced..17e620e 100644 --- a/installer/InstallerUpdater/Program.cs +++ b/installer/InstallerUpdater/Program.cs @@ -18,7 +18,8 @@ namespace Program { class Updater { - public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); + public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) + ?? throw new Exception("Cannot get current directory"); public static string InstallerName = "Installer.exe"; public static string jsonKey = "installerHash.json"; public static string KeyHead = "Installer/"; @@ -31,7 +32,8 @@ namespace Program using (StreamReader r = new StreamReader(System.IO.Path.Combine(Dir, "updateList.json"))) json = r.ReadToEnd(); json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); - List jsonList = JsonConvert.DeserializeObject>(json); + List jsonList = JsonConvert.DeserializeObject>(json) + ?? throw new Exception("Failed to deserialize json!"); foreach (string todo in jsonList) { if (!todo.Equals("None")) @@ -41,14 +43,14 @@ namespace Program } } } - catch (IOException) + catch (IOException ex) { - MessageBox.Show("下载器本体未能成功关闭"); + MessageBox.Show($"下载器本体未能成功关闭:\n{ex}"); return false; } - catch + catch (Exception ex) { - MessageBox.Show("尝试下载时出现问题"); + MessageBox.Show($"尝试下载时出现问题:\n{ex}\n{ex.StackTrace}"); return false; } return true; @@ -67,7 +69,8 @@ namespace Program json += @"{""None""}"; } List ls = new List(); - ls = JsonConvert.DeserializeObject>(json); + ls = JsonConvert.DeserializeObject>(json) + ?? throw new Exception("Failed to deserialize json!"); if (!ls.Contains("Dismiss")) { ls.Add("Dismiss"); @@ -114,9 +117,10 @@ namespace Program // 创建存储桶 try { - string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID - string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 - string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 + string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID + string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 + ?? throw new Exception("Failed to get directory name!"); + string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); Dictionary test = request.GetRequestHeaders(); diff --git a/logic/Client/Client.csproj b/logic/Client/Client.csproj index db66157..ffa5629 100644 --- a/logic/Client/Client.csproj +++ b/logic/Client/Client.csproj @@ -5,21 +5,17 @@ net6.0-windows enable true - EESAST.ico + eesast_software_trans_enlarged.ico - - - - - + diff --git a/logic/Client/EESASTLogo.png b/logic/Client/EESASTLogo.png new file mode 100644 index 0000000..efba5b5 Binary files /dev/null and b/logic/Client/EESASTLogo.png differ diff --git a/logic/Client/Logo.png b/logic/Client/Logo.png index efba5b5..6d7d511 100644 Binary files a/logic/Client/Logo.png and b/logic/Client/Logo.png differ diff --git a/logic/Client/MainWindow.xaml b/logic/Client/MainWindow.xaml index beacab5..7046f39 100644 --- a/logic/Client/MainWindow.xaml +++ b/logic/Client/MainWindow.xaml @@ -5,7 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Client" mc:Ignorable="d" - Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100"> + Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100" + SizeChanged="ZoomMap"> @@ -50,7 +51,7 @@ - + diff --git a/logic/Client/MainWindow.xaml.cs b/logic/Client/MainWindow.xaml.cs index 9001d75..d8dd6b5 100644 --- a/logic/Client/MainWindow.xaml.cs +++ b/logic/Client/MainWindow.xaml.cs @@ -20,6 +20,7 @@ using Playback; using CommandLine; using Preparation.Utility; using Preparation.Interface; +using System.Diagnostics.CodeAnalysis; // 目前MainWindow还未复现的功能: // private void ClickToSetMode(object sender, RoutedEventArgs e) @@ -34,8 +35,6 @@ namespace Client { public MainWindow() { - unitHeight = unitWidth = unit = 13; - bonusflag = true; timer = new DispatcherTimer { Interval = new TimeSpan(50000) // 每50ms刷新一次 @@ -60,9 +59,14 @@ namespace Client listOfGate = new List(); listOfHiddenGate = new List(); WindowStartupLocation = WindowStartupLocation.CenterScreen; + unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; + unitFontsize = unit / 13; + unitHeight = UpperLayerOfMap.ActualHeight / 50; + unitWidth = UpperLayerOfMap.ActualWidth / 50; ReactToCommandline(); } + [MemberNotNull(nameof(StatusBarsOfSurvivor), nameof(StatusBarsOfHunter), nameof(StatusBarsOfCircumstance))] private void SetStatusBar() { StatusBarsOfSurvivor = new StatusBarOfSurvivor[4]; @@ -194,6 +198,7 @@ namespace Client 0 => PlayerType.NullPlayerType, 1 => PlayerType.StudentPlayer, 2 => PlayerType.TrickerPlayer, + _ => PlayerType.NullPlayerType }; playerMsg.PlayerType = playerType; if (Convert.ToInt64(comInfo[3]) == 1) @@ -268,9 +273,9 @@ namespace Client { TextBox icon = new() { - FontSize = 10, - Width = 20, - Height = 20, + FontSize = 7 * unitFontsize, + Width = unitWidth, + Height = unitHeight, Text = text, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, @@ -282,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? 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 }; diff --git a/logic/Client/Properties/launchSettings.json b/logic/Client/Properties/launchSettings.json index 5cdc367..59aa8f1 100644 --- a/logic/Client/Properties/launchSettings.json +++ b/logic/Client/Properties/launchSettings.json @@ -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" } } -} +} \ No newline at end of file diff --git a/logic/Client/StatusBarOfHunter.xaml.cs b/logic/Client/StatusBarOfHunter.xaml.cs index 358db47..d8ab43e 100644 --- a/logic/Client/StatusBarOfHunter.xaml.cs +++ b/logic/Client/StatusBarOfHunter.xaml.cs @@ -82,19 +82,19 @@ namespace Client state.Text = "Quit"; break; case PlayerState.Treated: - state.Text = "Treated"; + state.Text = "Encouraged"; break; case PlayerState.Rescued: - state.Text = "Rescued"; + state.Text = "Roused"; break; case PlayerState.Stunned: state.Text = "Stunned"; break; case PlayerState.Treating: - state.Text = "Treating"; + state.Text = "Encouraging"; break; case PlayerState.Rescuing: - state.Text = "Rescuing"; + state.Text = "Rousing"; break; case PlayerState.Swinging: state.Text = "Swinging"; diff --git a/logic/Client/StatusBarOfSurvivor.xaml.cs b/logic/Client/StatusBarOfSurvivor.xaml.cs index 75011d8..1830c7e 100644 --- a/logic/Client/StatusBarOfSurvivor.xaml.cs +++ b/logic/Client/StatusBarOfSurvivor.xaml.cs @@ -87,19 +87,19 @@ namespace Client status.Text = "💀" + "\nQuit"; break; case PlayerState.Treated: - status.Text = "♥: " + Convert.ToString(life) + "\nTreated, " + Convert.ToString(perLife) + "%"; + status.Text = "♥: " + Convert.ToString(life) + "\nEncouraged, " + Convert.ToString(perLife) + "%"; break; case PlayerState.Rescued: - status.Text = "💀: " + Convert.ToString(death) + "\nRescued, " + Convert.ToString(perDeath) + "%"; + status.Text = "💀: " + Convert.ToString(death) + "\nRoused, " + Convert.ToString(perDeath) + "%"; break; case PlayerState.Stunned: status.Text = "♥: " + Convert.ToString(life) + "\nStunned, " + Convert.ToString(perLife) + "%"; break; case PlayerState.Treating: - status.Text = "♥: " + Convert.ToString(life) + "\nTreating, " + Convert.ToString(perLife) + "%"; + status.Text = "♥: " + Convert.ToString(life) + "\nEncouraging, " + Convert.ToString(perLife) + "%"; break; case PlayerState.Rescuing: - status.Text = "♥: " + Convert.ToString(life) + "\nRescuing, " + Convert.ToString(perLife) + "%"; + status.Text = "♥: " + Convert.ToString(life) + "\nRousing, " + Convert.ToString(perLife) + "%"; break; case PlayerState.Swinging: status.Text = "♥: " + Convert.ToString(life) + "\nSwinging, " + Convert.ToString(perLife) + "%"; diff --git a/logic/Client/eesast_software_trans.ico b/logic/Client/eesast_software_trans.ico new file mode 100644 index 0000000..2544246 Binary files /dev/null and b/logic/Client/eesast_software_trans.ico differ diff --git a/logic/Client/eesast_software_trans_enlarged.ico b/logic/Client/eesast_software_trans_enlarged.ico new file mode 100644 index 0000000..cbafc97 Binary files /dev/null and b/logic/Client/eesast_software_trans_enlarged.ico differ diff --git a/logic/ClientTest/ClientTest.csproj b/logic/ClientTest/ClientTest.csproj index 2e8f16d..b0894ed 100644 --- a/logic/ClientTest/ClientTest.csproj +++ b/logic/ClientTest/ClientTest.csproj @@ -8,7 +8,7 @@ - + diff --git a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs index 672e40f..2b3df5c 100644 --- a/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs +++ b/logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs @@ -1,6 +1,5 @@ using Preparation.Interface; using Preparation.Utility; -using System; namespace GameClass.GameObj { diff --git a/logic/GameClass/GameObj/Bullet/Bullet.cs b/logic/GameClass/GameObj/Bullet/Bullet.cs index 1a389b2..29f2d78 100644 --- a/logic/GameClass/GameObj/Bullet/Bullet.cs +++ b/logic/GameClass/GameObj/Bullet/Bullet.cs @@ -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; diff --git a/logic/GameClass/GameObj/Character/Character.cs b/logic/GameClass/GameObj/Character/Character.cs index b0c3d3b..d8dea18 100644 --- a/logic/GameClass/GameObj/Character/Character.cs +++ b/logic/GameClass/GameObj/Character/Character.cs @@ -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; } diff --git a/logic/GameClass/GameObj/Map/Chest.cs b/logic/GameClass/GameObj/Map/Chest.cs index 2d71764..23d9571 100644 --- a/logic/GameClass/GameObj/Map/Chest.cs +++ b/logic/GameClass/GameObj/Map/Chest.cs @@ -23,7 +23,6 @@ namespace GameClass.GameObj private int openStartTime = 0; public int OpenStartTime => openStartTime; private Character? whoOpen = null; - public Character? WhoOpen => whoOpen; public void Open(int startTime, Character character) { diff --git a/logic/GameClass/GameObj/Map/Generator.cs b/logic/GameClass/GameObj/Map/Generator.cs index 4718f65..d982161 100644 --- a/logic/GameClass/GameObj/Map/Generator.cs +++ b/logic/GameClass/GameObj/Map/Generator.cs @@ -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) diff --git a/logic/GameClass/GameObj/Map/Map.cs b/logic/GameClass/GameObj/Map/Map.cs index 1ca3ec9..3cfbf49 100644 --- a/logic/GameClass/GameObj/Map/Map.cs +++ b/logic/GameClass/GameObj/Map/Map.cs @@ -3,7 +3,6 @@ using System.Threading; using Preparation.Interface; using Preparation.Utility; using System; -using GameClass.GameObj; namespace GameClass.GameObj { @@ -13,29 +12,47 @@ namespace GameClass.GameObj private readonly Dictionary birthPointList; // 出生点列表 public Dictionary BirthPointList => birthPointList; - private object lockForNum = new(); + private readonly object lockForNum = new(); private void WhenStudentNumChange() { if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) { Timer.IsGaming = false; + return; } - if (GameData.numOfStudent - NumOfDeceasedStudent - NumOfEscapedStudent == 1) + if (GameData.numOfStudent - numOfDeceasedStudent - numOfEscapedStudent == 1) { - GameObjLockDict[GameObjType.EmergencyExit].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) { diff --git a/logic/GameEngine/CollisionChecker.cs b/logic/GameEngine/CollisionChecker.cs index 046d5bc..1a7a2e3 100644 --- a/logic/GameEngine/CollisionChecker.cs +++ b/logic/GameEngine/CollisionChecker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Numerics; using System.Threading; using Preparation.Interface; using Preparation.Utility; diff --git a/logic/GameEngine/MoveEngine.cs b/logic/GameEngine/MoveEngine.cs index ac6bdbe..aa13c68 100644 --- a/logic/GameEngine/MoveEngine.cs +++ b/logic/GameEngine/MoveEngine.cs @@ -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( - () => 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( + () => 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(); } diff --git a/logic/Gaming/ActionManager.cs b/logic/Gaming/ActionManager.cs index 7ad0e10..842e625 100644 --- a/logic/Gaming/ActionManager.cs +++ b/logic/Gaming/ActionManager.cs @@ -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( - 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( - 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( - 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( - 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( - 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( - 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); diff --git a/logic/Gaming/AttackManager.cs b/logic/Gaming/AttackManager.cs index f17ef0d..0db4748 100644 --- a/logic/Gaming/AttackManager.cs +++ b/logic/Gaming/AttackManager.cs @@ -6,7 +6,6 @@ using Preparation.Utility; using GameEngine; using Preparation.Interface; using Timothy.FrameRateTask; -using System.Numerics; namespace Gaming { @@ -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( - 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); } diff --git a/logic/Gaming/CharacterManager .cs b/logic/Gaming/CharacterManager .cs index f3cbd19..c7aefaf 100644 --- a/logic/Gaming/CharacterManager .cs +++ b/logic/Gaming/CharacterManager .cs @@ -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( - () => (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++) diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs index 4236430..8378eba 100644 --- a/logic/Gaming/Game.cs +++ b/logic/Gaming/Game.cs @@ -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 { diff --git a/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs b/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs index 2587cfe..a6d0a86 100644 --- a/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs +++ b/logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs @@ -96,6 +96,7 @@ namespace Gaming IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible); return ActiveSkillEffect(activeSkill, player, () => { + player.AddScore(GameData.ScoreBecomeInvisible); player.AddInvisible(activeSkill.DurationTime); Debugger.Output(player, "become invisible!"); }, diff --git a/logic/Gaming/SkillManager/SkillManager.cs b/logic/Gaming/SkillManager/SkillManager.cs index 9cf6f09..59fc7e3 100644 --- a/logic/Gaming/SkillManager/SkillManager.cs +++ b/logic/Gaming/SkillManager/SkillManager.cs @@ -17,44 +17,31 @@ namespace Gaming switch (activeSkillType) { case ActiveSkillType.BecomeInvisible: - BecomeInvisible(character); - break; + return BecomeInvisible(character); case ActiveSkillType.UseKnife: - UseKnife(character); - break; + return UseKnife(character); case ActiveSkillType.Howl: - Howl(character); - break; + return Howl(character); case ActiveSkillType.CanBeginToCharge: - CanBeginToCharge(character); - break; + return CanBeginToCharge(character); case ActiveSkillType.Inspire: - Inspire(character); - break; + return Inspire(character); case ActiveSkillType.Encourage: - Encourage(character); - break; + return Encourage(character); case ActiveSkillType.Punish: - Punish(character); - break; + return Punish(character); case ActiveSkillType.JumpyBomb: - JumpyBomb(character); - break; + return JumpyBomb(character); case ActiveSkillType.WriteAnswers: - WriteAnswers(character); - break; + return WriteAnswers(character); case ActiveSkillType.SummonGolem: - SummonGolem(character); - break; + return SummonGolem(character); case ActiveSkillType.UseRobot: - UseRobot(character); - break; + return UseRobot(character); case ActiveSkillType.Rouse: - Rouse(character); - break; + return Rouse(character); case ActiveSkillType.ShowTime: - ShowTime(character); - break; + return ShowTime(character); default: return false; } diff --git a/logic/Preparation/Interface/ICharacter.cs b/logic/Preparation/Interface/ICharacter.cs index ce2965c..29d087f 100644 --- a/logic/Preparation/Interface/ICharacter.cs +++ b/logic/Preparation/Interface/ICharacter.cs @@ -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(); } diff --git a/logic/Preparation/Preparation.csproj b/logic/Preparation/Preparation.csproj index 73cc535..4a5e031 100644 --- a/logic/Preparation/Preparation.csproj +++ b/logic/Preparation/Preparation.csproj @@ -9,7 +9,7 @@ - + diff --git a/logic/Preparation/Utility/GameData.cs b/logic/Preparation/Utility/GameData.cs index 17aca10..b788f41 100644 --- a/logic/Preparation/Utility/GameData.cs +++ b/logic/Preparation/Utility/GameData.cs @@ -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; // 检查位置标志、补充子弹的帧时长 diff --git a/logic/README.md b/logic/README.md index 16d928e..ae46d6a 100644 --- a/logic/README.md +++ b/logic/README.md @@ -47,4 +47,6 @@ ## 开发人员 -- ......(自己加) \ No newline at end of file +- 黄淞:底层逻辑 +- 游笑权:Client +- 高思研:Server \ No newline at end of file diff --git a/logic/Server/ArgumentOption.cs b/logic/Server/ArgumentOption.cs index 2dddf7d..641e7d3 100644 --- a/logic/Server/ArgumentOption.cs +++ b/logic/Server/ArgumentOption.cs @@ -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; } } \ No newline at end of file diff --git a/logic/Server/CopyInfo.cs b/logic/Server/CopyInfo.cs index 77dd8af..d6a1423 100644 --- a/logic/Server/CopyInfo.cs +++ b/logic/Server/CopyInfo.cs @@ -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; } diff --git a/logic/Server/GameServer.cs b/logic/Server/GameServer.cs index b81bfe7..6573e7b 100644 --- a/logic/Server/GameServer.cs +++ b/logic/Server/GameServer.cs @@ -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 semaDict = new(); - private object semaDictLock = new(); + private ConcurrentDictionary 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 spectatorList = new List(); 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; } diff --git a/logic/Server/PlaybackServer.cs b/logic/Server/PlaybackServer.cs index 1506b97..dc347ce 100644 --- a/logic/Server/PlaybackServer.cs +++ b/logic/Server/PlaybackServer.cs @@ -1,24 +1,40 @@ -using Protobuf; +using Gaming; +using Grpc.Core; using Playback; -using System; -using System.Threading; +using Protobuf; +using System.Collections.Concurrent; using Timothy.FrameRateTask; -using Gaming; -using Grpc.Core; namespace Server { - class PlaybackServer : AvailableService.AvailableServiceBase + class PlaybackServer : ServerBase { protected readonly ArgumentOptions options; private int[,] teamScore; - private Dictionary semaDict = new(); - private object semaDictLock = new(); + private ConcurrentDictionary semaDict = new(); + // private object semaDictLock = new(); private MessageToClient? currentGameInfo = new(); + private MessageOfObj currentMapMsg = new(); private uint spectatorMinPlayerID = 2023; - private List spectatorList = new List(); + // private List spectatorList = new List(); public int TeamCount => options.TeamCount; - private MessageWriter? mwr = null; + private object spetatorJoinLock = new(); + protected object spectatorLock = new object(); + protected bool isSpectatorJoin = false; + protected bool IsSpectatorJoin + { + get + { + lock (spectatorLock) + return isSpectatorJoin; + } + + set + { + lock (spectatorLock) + isSpectatorJoin = value; + } + } private bool IsGaming { get; set; } private int[] finalScore; public int[] FinalScore @@ -28,6 +44,7 @@ namespace Server return finalScore; } } + public override int[] GetScore() => FinalScore; public PlaybackServer(ArgumentOptions options) { this.options = options; @@ -38,18 +55,21 @@ namespace Server public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter responseStream, ServerCallContext context) { - if (request.PlayerId >= spectatorMinPlayerID) + Console.WriteLine($"AddPlayer: {request.PlayerId}"); + if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false) { // 观战模式 - uint tp = (uint)request.PlayerId; - if (!spectatorList.Contains(tp)) + lock (spetatorJoinLock) // 具体原因见另一个上锁的地方 { - spectatorList.Add(tp); - Console.WriteLine("A new spectator comes to watch this game."); - var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); - lock (semaDictLock) + if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)))) { - semaDict.Add(request.PlayerId, temp); + Console.WriteLine("A new spectator comes to watch this game."); + IsSpectatorJoin = true; + } + else + { + Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}"); + return; } } do @@ -63,15 +83,33 @@ namespace Server //Console.WriteLine("Send!"); } } + catch (InvalidOperationException) + { + if (semaDict.TryRemove(request.PlayerId, out var semas)) + { + try + { + semas.Item1.Release(); + semas.Item2.Release(); + } + catch { } + Console.WriteLine($"The spectator {request.PlayerId} exited"); + return; + } + } catch (Exception) { - //Console.WriteLine(ex); + // Console.WriteLine(ex); } finally { - semaDict[request.PlayerId].Item2.Release(); + try + { + semaDict[request.PlayerId].Item2.Release(); + } + catch { } } - } while (IsGaming == true); + } while (IsGaming); return; } } @@ -79,6 +117,16 @@ namespace Server public void ReportGame(MessageToClient? msg) { currentGameInfo = msg; + if (currentGameInfo != null && currentGameInfo.GameState == GameState.GameStart) + { + currentMapMsg = currentGameInfo.ObjMessage[0]; + } + + if (currentGameInfo != null && IsSpectatorJoin) + { + currentGameInfo.ObjMessage.Add(currentMapMsg); + IsSpectatorJoin = false; + } foreach (var kvp in semaDict) { @@ -91,7 +139,7 @@ namespace Server } } - public void WaitForGame() + public override void WaitForEnd() { try { diff --git a/logic/Server/Program.cs b/logic/Server/Program.cs index c934cd1..85bfe48 100644 --- a/logic/Server/Program.cs +++ b/logic/Server/Program.cs @@ -1,15 +1,55 @@ -using Grpc.Core; +using CommandLine; +using Grpc.Core; using Protobuf; -using System.Threading; -using Timothy.FrameRateTask; -using System; -using System.Net.Http.Headers; -using CommandLine; namespace Server { public class Program { + /// + /// Generated by http://www.network-science.de/ascii/ with font "standard" + /// + const string welcome = +@" + + _____ _ _ _ _ _ ___ __ + |_ _| | | | | | | / \ |_ _/ /_ + | | | |_| | | | |/ _ \ | | '_ \ + | | | _ | |_| / ___ \ | | (_) | + |_| |_| |_|\___/_/ \_\___\___/ + + ____ _ _ ____ _ _ _ + / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | + | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | + | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| + \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) + + +"; + // 特别说明:在 .NET 7 及以上,C# 支持新的多行字符串,允许多行前面缩进,因此可以不必再定格写字符串, + // 即升级 .NET 版本后可以改为如下的: + // const string welcome = """ + // + // _____ _ _ _ _ _ ___ __ + // |_ _| | | | | | | / \ |_ _/ /_ + // | | | |_| | | | |/ _ \ | | '_ \ + // | | | _ | |_| / ___ \ | | (_) | + // |_| |_| |_|\___/_/ \_\___\___/ + // + // ____ _ _ ____ _ _ _ + // / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | + // | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | + // | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| + // \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) + // + // + // """; // 将以此结束符为基准开始缩进(但 Python 没有这个 feature,差评 x) + + static ServerBase CreateServer(ArgumentOptions options) + { + return options.Playback ? new PlaybackServer(options) : new GameServer(options); + } + static int Main(string[] args) { foreach (var arg in args) @@ -26,63 +66,36 @@ namespace Server return 1; } + if (options.StartLockFile == DefaultArgumentOptions.FileName) + { + Console.WriteLine(welcome); + } Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); - if (options.Playback) + try { - try + var server = CreateServer(options); + Grpc.Core.Server rpcServer = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) { - PlaybackServer? playbackServer = new(options); - Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) - { - Services = { AvailableService.BindService(playbackServer) }, - Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } - }; - server.Start(); + Services = { AvailableService.BindService(server) }, + Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } + }; + rpcServer.Start(); - Console.WriteLine("Server begins to listen!"); - playbackServer.WaitForGame(); - Console.WriteLine("Server end!"); - server.ShutdownAsync().Wait(); + Console.WriteLine("Server begins to listen!"); + server.WaitForEnd(); + Console.WriteLine("Server end!"); + rpcServer.ShutdownAsync().Wait(); - Thread.Sleep(50); - Console.WriteLine(""); - Console.WriteLine("=================== Final Score ===================="); - Console.WriteLine($"Studnet: {playbackServer.FinalScore[0]}"); - Console.WriteLine($"Tricker: {playbackServer.FinalScore[1]}"); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } + Thread.Sleep(50); + Console.WriteLine(""); + Console.WriteLine("=================== Final Score ===================="); + Console.WriteLine($"Studnet: {server.GetScore()[0]}"); + Console.WriteLine($"Tricker: {server.GetScore()[1]}"); } - else + catch (Exception ex) { - try - { - GameServer? gameServer = new(options); - Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) - { - Services = { AvailableService.BindService(gameServer) }, - Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } - }; - server.Start(); - - Console.WriteLine("Server begins to listen!"); - gameServer.WaitForEnd(); - Console.WriteLine("Server end!"); - server.ShutdownAsync().Wait(); - - Thread.Sleep(50); - Console.WriteLine(""); - Console.WriteLine("=================== Final Score ===================="); - Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}"); - Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}"); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } + Console.WriteLine(ex.ToString()); } return 0; } diff --git a/logic/Server/Properties/launchSettings.json b/logic/Server/Properties/launchSettings.json index d126864..308724a 100644 --- a/logic/Server/Properties/launchSettings.json +++ b/logic/Server/Properties/launchSettings.json @@ -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" } } -} +} \ No newline at end of file diff --git a/logic/Server/RpcServices.cs b/logic/Server/RpcServices.cs index 0e95b63..30563d5 100644 --- a/logic/Server/RpcServices.cs +++ b/logic/Server/RpcServices.cs @@ -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 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 PickProp(PropMsg request, ServerCallContext context) { diff --git a/logic/Server/Server.csproj b/logic/Server/Server.csproj index 50052fe..25d1f5c 100644 --- a/logic/Server/Server.csproj +++ b/logic/Server/Server.csproj @@ -10,10 +10,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/logic/Server/ServerBase.cs b/logic/Server/ServerBase.cs new file mode 100644 index 0000000..dbb578c --- /dev/null +++ b/logic/Server/ServerBase.cs @@ -0,0 +1,14 @@ +using Grpc.Core; +using Playback; +using Protobuf; +using System.Collections.Concurrent; +using Timothy.FrameRateTask; + +namespace Server +{ + abstract class ServerBase : AvailableService.AvailableServiceBase + { + public abstract void WaitForEnd(); + public abstract int[] GetScore(); + } +} diff --git a/logic/cmd/PlaybackServer.cmd b/logic/cmd/PlaybackServer.cmd index c3dd0bb..916c9ea 100644 --- a/logic/cmd/PlaybackServer.cmd +++ b/logic/cmd/PlaybackServer.cmd @@ -1,6 +1,2 @@ @echo off -::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true - -ping -n 2 127.0.0.1 > NUL - -start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 \ No newline at end of file +start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0 diff --git a/logic/cmd/gameServerAndClient.cmd b/logic/cmd/gameServerAndClient.cmd new file mode 100644 index 0000000..125b391 --- /dev/null +++ b/logic/cmd/gameServerAndClient.cmd @@ -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 \ No newline at end of file diff --git a/playback/Playback/Playback.csproj b/playback/Playback/Playback.csproj index 74fc21b..ada2c68 100644 --- a/playback/Playback/Playback.csproj +++ b/playback/Playback/Playback.csproj @@ -7,7 +7,7 @@ - + diff --git a/resource/AIcpp.png b/resource/AIcpp.png new file mode 100644 index 0000000..93f6b5d Binary files /dev/null and b/resource/AIcpp.png differ diff --git a/resource/AIpy.png b/resource/AIpy.png new file mode 100644 index 0000000..1b0eb58 Binary files /dev/null and b/resource/AIpy.png differ diff --git a/resource/CompileFaster.png b/resource/CompileFaster.png new file mode 100644 index 0000000..f27537d Binary files /dev/null and b/resource/CompileFaster.png differ diff --git a/resource/eesast_software.png b/resource/eesast_software.png new file mode 100644 index 0000000..5d980f2 Binary files /dev/null and b/resource/eesast_software.png differ diff --git a/resource/eesast_software_trans.ico b/resource/eesast_software_trans.ico new file mode 100644 index 0000000..2544246 Binary files /dev/null and b/resource/eesast_software_trans.ico differ diff --git a/resource/eesast_software_trans.png b/resource/eesast_software_trans.png new file mode 100644 index 0000000..6d7d511 Binary files /dev/null and b/resource/eesast_software_trans.png differ diff --git a/resource/eesast_software_trans_enlarged.ico b/resource/eesast_software_trans_enlarged.ico new file mode 100644 index 0000000..cbafc97 Binary files /dev/null and b/resource/eesast_software_trans_enlarged.ico differ diff --git a/resource/eesast_software_trans_enlarged.png b/resource/eesast_software_trans_enlarged.png new file mode 100644 index 0000000..088700c Binary files /dev/null and b/resource/eesast_software_trans_enlarged.png differ diff --git a/resource/eesast_software_trans_enlarged_256x256.png b/resource/eesast_software_trans_enlarged_256x256.png new file mode 100644 index 0000000..17f18f4 Binary files /dev/null and b/resource/eesast_software_trans_enlarged_256x256.png differ diff --git a/resource/vector.png b/resource/vector.png new file mode 100644 index 0000000..72d1987 Binary files /dev/null and b/resource/vector.png differ diff --git a/resource/wrongType.png b/resource/wrongType.png new file mode 100644 index 0000000..37e2b3e Binary files /dev/null and b/resource/wrongType.png differ