| @@ -113,6 +113,9 @@ jobs: | |||
| name: my-artifact | |||
| path: ./THUAI6 | |||
| - name: Remove ReadMe.md | |||
| run: rm ./docs/README.md | |||
| - name: Markdown to PDF and HTML | |||
| uses: BaileyJM02/markdown-to-pdf@v1.2.0 | |||
| with: | |||
| @@ -145,7 +148,6 @@ jobs: | |||
| rm ./THUAI6/win/win64/WindowsBase.dll | |||
| rm ./THUAI6/win/win64/Debug/grpc_csharp_ext.x64.dll | |||
| rm ./THUAI6/win/win64/grpc_csharp_ext.x64.dll | |||
| rm -r ./THUAI6/win/CAPI/cpp/grpc | |||
| rm -r ./THUAI6/win/CAPI/cpp/spdlog | |||
| rm -r ./THUAI6/win/CAPI/cpp/tclap | |||
| @@ -162,7 +164,6 @@ jobs: | |||
| rm ./THUAI6/osx/osx64/Debug/System.*.dll | |||
| rm ./THUAI6/win/win64/System.*.dll | |||
| rm ./THUAI6/win/win64/Debug/System.*.dll | |||
| rm ./THUAI6/linux/linux64/*.so | |||
| rm ./THUAI6/linux/linux64/Debug/*.so | |||
| @@ -4,6 +4,10 @@ | |||
| #include "API.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| class IAI | |||
| { | |||
| public: | |||
| @@ -22,6 +22,10 @@ | |||
| #include "structures.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| const constexpr int numOfGridPerCell = 1000; | |||
| class IAI; | |||
| @@ -57,7 +61,7 @@ public: | |||
| virtual bool UseProp(THUAI6::PropType prop) = 0; | |||
| virtual bool ThrowProp(THUAI6::PropType prop) = 0; | |||
| virtual bool UseSkill(int32_t skillID) = 0; | |||
| virtual bool SendMessage(int64_t toID, std::string message) = 0; | |||
| virtual bool SendMessage(int64_t toID, std::string message, bool binary) = 0; | |||
| virtual bool HaveMessage() = 0; | |||
| virtual std::pair<int64_t, std::string> GetMessage() = 0; | |||
| @@ -83,6 +87,8 @@ public: | |||
| virtual bool Attack(double angle) = 0; | |||
| virtual std::vector<int64_t> GetPlayerGUIDs() const = 0; | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const = 0; | |||
| }; | |||
| class IAPI | |||
| @@ -113,7 +119,8 @@ public: | |||
| virtual std::future<bool> EndAllAction() = 0; | |||
| // 发送信息、接受信息,注意收消息时无消息则返回nullopt | |||
| virtual std::future<bool> SendMessage(int64_t, std::string) = 0; | |||
| virtual std::future<bool> SendTextMessage(int64_t, std::string) = 0; | |||
| virtual std::future<bool> SendBinaryMessage(int64_t, std::string) = 0; | |||
| [[nodiscard]] virtual bool HaveMessage() = 0; | |||
| [[nodiscard]] virtual std::pair<int64_t, std::string> GetMessage() = 0; | |||
| @@ -162,6 +169,8 @@ public: | |||
| return grid / numOfGridPerCell; | |||
| } | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0; | |||
| // 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| virtual void Print(std::string str) const = 0; | |||
| @@ -238,7 +247,8 @@ public: | |||
| std::future<bool> StartOpenChest() override; | |||
| std::future<bool> EndAllAction() override; | |||
| std::future<bool> SendMessage(int64_t, std::string) override; | |||
| std::future<bool> SendTextMessage(int64_t, std::string) override; | |||
| std::future<bool> SendBinaryMessage(int64_t, std::string) override; | |||
| [[nodiscard]] bool HaveMessage() override; | |||
| [[nodiscard]] std::pair<int64_t, std::string> GetMessage() override; | |||
| @@ -271,6 +281,8 @@ public: | |||
| std::future<bool> Graduate() override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override | |||
| { | |||
| } | |||
| @@ -326,7 +338,8 @@ public: | |||
| std::future<bool> StartOpenChest() override; | |||
| std::future<bool> EndAllAction() override; | |||
| std::future<bool> SendMessage(int64_t, std::string) override; | |||
| std::future<bool> SendTextMessage(int64_t, std::string) override; | |||
| std::future<bool> SendBinaryMessage(int64_t, std::string) override; | |||
| [[nodiscard]] bool HaveMessage() override; | |||
| [[nodiscard]] std::pair<int64_t, std::string> GetMessage() override; | |||
| @@ -356,6 +369,8 @@ public: | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override | |||
| { | |||
| } | |||
| @@ -406,7 +421,8 @@ public: | |||
| std::future<bool> StartOpenChest() override; | |||
| std::future<bool> EndAllAction() override; | |||
| std::future<bool> SendMessage(int64_t, std::string) override; | |||
| std::future<bool> SendTextMessage(int64_t, std::string) override; | |||
| std::future<bool> SendBinaryMessage(int64_t, std::string) override; | |||
| [[nodiscard]] bool HaveMessage() override; | |||
| [[nodiscard]] std::pair<int64_t, std::string> GetMessage() override; | |||
| @@ -439,6 +455,8 @@ public: | |||
| std::future<bool> Graduate() override; | |||
| [[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override; | |||
| void PrintStudent() const override; | |||
| void PrintTricker() const override; | |||
| @@ -479,7 +497,8 @@ public: | |||
| std::future<bool> StartOpenChest() override; | |||
| std::future<bool> EndAllAction() override; | |||
| std::future<bool> SendMessage(int64_t, std::string) override; | |||
| std::future<bool> SendTextMessage(int64_t, std::string) override; | |||
| std::future<bool> SendBinaryMessage(int64_t, std::string) override; | |||
| [[nodiscard]] bool HaveMessage() override; | |||
| [[nodiscard]] std::pair<int64_t, std::string> GetMessage() override; | |||
| @@ -509,6 +528,8 @@ public: | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override; | |||
| void PrintStudent() const override; | |||
| void PrintTricker() const override; | |||
| @@ -14,6 +14,10 @@ | |||
| #include <queue> | |||
| #include <atomic> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| class Logic; | |||
| class Communication | |||
| @@ -28,7 +32,7 @@ public: | |||
| bool UseProp(THUAI6::PropType prop, int64_t playerID); | |||
| bool ThrowProp(THUAI6::PropType prop, int64_t playerID); | |||
| bool UseSkill(int32_t skillID, int64_t playerID); | |||
| bool SendMessage(int64_t toID, std::string message, int64_t playerID); | |||
| bool SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID); | |||
| bool OpenDoor(int64_t playerID); | |||
| bool CloseDoor(int64_t playerID); | |||
| bool SkipWindow(int64_t playerID); | |||
| @@ -8,6 +8,10 @@ | |||
| #include <utility> | |||
| #include <optional> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| template<typename Elem> | |||
| class ConcurrentQueue | |||
| { | |||
| @@ -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 | |||
| @@ -31,6 +31,10 @@ | |||
| #include "Communication.h" | |||
| #include "ConcurrentQueue.hpp" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| // 封装了通信组件和对AI对象进行操作 | |||
| class Logic : public ILogic | |||
| { | |||
| @@ -49,9 +53,6 @@ private: | |||
| THUAI6::TrickerType trickerType; | |||
| THUAI6::StudentType studentType; | |||
| // GUID信息 | |||
| std::vector<int64_t> playerGUIDs; | |||
| std::unique_ptr<IGameTimer> timer; | |||
| std::thread tAI; // 用于运行AI的线程 | |||
| @@ -117,7 +118,7 @@ private: | |||
| bool ThrowProp(THUAI6::PropType prop) override; | |||
| bool UseSkill(int32_t skillID) override; | |||
| bool SendMessage(int64_t toID, std::string message) override; | |||
| bool SendMessage(int64_t toID, std::string message, bool binary) override; | |||
| bool HaveMessage() override; | |||
| std::pair<int64_t, std::string> GetMessage() override; | |||
| @@ -161,6 +162,8 @@ private: | |||
| // 等待 | |||
| void Wait() noexcept; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const override; | |||
| public: | |||
| // 构造函数还需要传更多参数,有待补充 | |||
| Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student); | |||
| @@ -9,6 +9,10 @@ | |||
| #include "structures.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| // 存储场上的状态 | |||
| struct State | |||
| { | |||
| @@ -8,6 +8,10 @@ | |||
| #include <vector> | |||
| #include <string> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| namespace THUAI6 | |||
| { | |||
| @@ -168,6 +172,13 @@ namespace THUAI6 | |||
| Opened = 2, | |||
| }; | |||
| enum class NewsType : unsigned char | |||
| { | |||
| NullNewsType = 0, | |||
| TextMessage = 1, | |||
| BinaryMessage = 2, | |||
| }; | |||
| // 玩家类 | |||
| struct Player | |||
| { | |||
| @@ -13,6 +13,10 @@ | |||
| #include "structures.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| namespace AssistFunction | |||
| { | |||
| @@ -202,6 +206,12 @@ namespace Proto2THUAI6 | |||
| }; | |||
| inline std::map<protobuf::MessageOfNews::NewsCase, THUAI6::NewsType> newsTypeDict{ | |||
| {protobuf::MessageOfNews::NewsCase::NEWS_NOT_SET, THUAI6::NewsType::NullNewsType}, | |||
| {protobuf::MessageOfNews::NewsCase::kTextMessage, THUAI6::NewsType::TextMessage}, | |||
| {protobuf::MessageOfNews::NewsCase::kBinaryMessage, THUAI6::NewsType::BinaryMessage}, | |||
| }; | |||
| // 用于将Protobuf中的类转换为THUAI6的类 | |||
| inline std::shared_ptr<THUAI6::Tricker> Protobuf2THUAI6Tricker(const protobuf::MessageOfTricker& trickerMsg) | |||
| { | |||
| @@ -456,10 +466,13 @@ namespace THUAI62Proto | |||
| return pickMsg; | |||
| } | |||
| inline protobuf::SendMsg THUAI62ProtobufSend(std::string msg, int64_t toID, int64_t id) | |||
| inline protobuf::SendMsg THUAI62ProtobufSend(std::string msg, int64_t toID, bool binary, int64_t id) | |||
| { | |||
| protobuf::SendMsg sendMsg; | |||
| sendMsg.set_message(msg); | |||
| if (binary) | |||
| sendMsg.set_binary_message(msg); | |||
| else | |||
| sendMsg.set_text_message(msg); | |||
| sendMsg.set_to_player_id(toID); | |||
| sendMsg.set_player_id(id); | |||
| return sendMsg; | |||
| @@ -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) | |||
| @@ -1,6 +1,11 @@ | |||
| #include <optional> | |||
| #include "AI.h" | |||
| #include "API.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| #define PI 3.14159265358979323846 | |||
| int StudentAPI::GetFrameCount() const | |||
| @@ -185,16 +190,28 @@ std::future<bool> TrickerAPI::EndAllAction() | |||
| { return logic.EndAllAction(); }); | |||
| } | |||
| std::future<bool> StudentAPI::SendMessage(int64_t toID, std::string message) | |||
| std::future<bool> StudentAPI::SendTextMessage(int64_t toID, std::string message) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.SendMessage(toID, message, false); }); | |||
| } | |||
| std::future<bool> TrickerAPI::SendTextMessage(int64_t toID, std::string message) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.SendMessage(toID, message, false); }); | |||
| } | |||
| std::future<bool> StudentAPI::SendBinaryMessage(int64_t toID, std::string message) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.SendMessage(toID, message); }); | |||
| { return logic.SendMessage(toID, message, false); }); | |||
| } | |||
| std::future<bool> TrickerAPI::SendMessage(int64_t toID, std::string message) | |||
| std::future<bool> TrickerAPI::SendBinaryMessage(int64_t toID, std::string message) | |||
| { | |||
| return std::async(std::launch::async, [=]() | |||
| { return logic.SendMessage(toID, message); }); | |||
| { return logic.SendMessage(toID, message, false); }); | |||
| } | |||
| bool StudentAPI::HaveMessage() | |||
| @@ -419,6 +436,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerAPI::GetSelfInfo() const | |||
| return logic.TrickerGetSelfInfo(); | |||
| } | |||
| bool StudentAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| bool TrickerAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| void StudentAPI::Play(IAI& ai) | |||
| { | |||
| ai.play(*this); | |||
| @@ -5,6 +5,10 @@ | |||
| #include <mutex> | |||
| #include <condition_variable> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| using grpc::ClientContext; | |||
| Communication::Communication(std::string sIP, std::string sPort) | |||
| @@ -74,11 +78,11 @@ bool Communication::UseSkill(int32_t skillID, int64_t playerID) | |||
| return false; | |||
| } | |||
| bool Communication::SendMessage(int64_t toID, std::string message, int64_t playerID) | |||
| bool Communication::SendMessage(int64_t toID, std::string message, bool binary, int64_t playerID) | |||
| { | |||
| protobuf::BoolRes sendMessageResult; | |||
| ClientContext context; | |||
| auto request = THUAI62Proto::THUAI62ProtobufSend(message, toID, playerID); | |||
| auto request = THUAI62Proto::THUAI62ProtobufSend(message, toID, binary, playerID); | |||
| auto status = THUAI6Stub->SendMessage(&context, request, &sendMessageResult); | |||
| if (status.ok()) | |||
| return sendMessageResult.act_success(); | |||
| @@ -249,10 +253,13 @@ void Communication::AddPlayer(int64_t playerID, THUAI6::PlayerType playerType, T | |||
| grpc::ClientContext context; | |||
| auto MessageReader = THUAI6Stub->AddPlayer(&context, playerMsg); | |||
| while (MessageReader->Read(&message2Client)) | |||
| protobuf::MessageToClient buffer2Client; | |||
| while (MessageReader->Read(&buffer2Client)) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxMessage); | |||
| message2Client = std::move(buffer2Client); | |||
| haveNewMessage = true; | |||
| } | |||
| cvMessage.notify_one(); | |||
| @@ -4,6 +4,11 @@ | |||
| #include "API.h" | |||
| #include "utils.hpp" | |||
| #include "structures.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| #define PI 3.14159265358979323846 | |||
| StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) : | |||
| @@ -347,23 +352,43 @@ std::future<bool> TrickerDebugAPI::EndAllAction() | |||
| return result; }); | |||
| } | |||
| std::future<bool> StudentDebugAPI::SendMessage(int64_t toID, std::string message) | |||
| std::future<bool> StudentDebugAPI::SendTextMessage(int64_t toID, std::string message) | |||
| { | |||
| logger->info("SendTextMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.SendMessage(toID, message, false); | |||
| if (!result) | |||
| logger->warn("SendTextMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| } | |||
| std::future<bool> TrickerDebugAPI::SendTextMessage(int64_t toID, std::string message) | |||
| { | |||
| logger->info("SendTextMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.SendMessage(toID, message, false); | |||
| if (!result) | |||
| logger->warn("SendTextMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| } | |||
| std::future<bool> StudentDebugAPI::SendBinaryMessage(int64_t toID, std::string message) | |||
| { | |||
| logger->info("SendMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| logger->info("SendBinaryMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.SendMessage(toID, message); | |||
| { auto result = logic.SendMessage(toID, message, true); | |||
| if (!result) | |||
| logger->warn("SendMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| logger->warn("SendBinaryMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| } | |||
| std::future<bool> TrickerDebugAPI::SendMessage(int64_t toID, std::string message) | |||
| std::future<bool> TrickerDebugAPI::SendBinaryMessage(int64_t toID, std::string message) | |||
| { | |||
| logger->info("SendMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| logger->info("SendBinaryMessage: toID = {}, message = {}, called at {}ms", toID, message, Time::TimeSinceStart(startPoint)); | |||
| return std::async(std::launch::async, [=]() | |||
| { auto result = logic.SendMessage(toID, message); | |||
| { auto result = logic.SendMessage(toID, message, true); | |||
| if (!result) | |||
| logger->warn("SendMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| logger->warn("SendBinaryMessage: failed at {}ms", Time::TimeSinceStart(startPoint)); | |||
| return result; }); | |||
| } | |||
| @@ -631,6 +656,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerDebugAPI::GetSelfInfo() const | |||
| return logic.TrickerGetSelfInfo(); | |||
| } | |||
| bool StudentDebugAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| bool TrickerDebugAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| void StudentDebugAPI::Print(std::string str) const | |||
| { | |||
| logger->info(str); | |||
| @@ -8,6 +8,10 @@ | |||
| #include "utils.hpp" | |||
| #include "Communication.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| extern const bool asynchronous; | |||
| Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student) : | |||
| @@ -210,10 +214,10 @@ bool Logic::UseSkill(int32_t skill) | |||
| return pComm->UseSkill(skill, playerID); | |||
| } | |||
| bool Logic::SendMessage(int64_t toID, std::string message) | |||
| bool Logic::SendMessage(int64_t toID, std::string message, bool binary) | |||
| { | |||
| logger->debug("Called SendMessage"); | |||
| return pComm->SendMessage(toID, message, playerID); | |||
| return pComm->SendMessage(toID, message, binary, playerID); | |||
| } | |||
| bool Logic::HaveMessage() | |||
| @@ -323,17 +327,6 @@ void Logic::ProcessMessage() | |||
| case THUAI6::GameState::GameStart: | |||
| logger->info("Game Start!"); | |||
| // 重新读取玩家的guid,保证人类在前屠夫在后 | |||
| playerGUIDs.clear(); | |||
| for (const auto& obj : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage) | |||
| playerGUIDs.push_back(obj.student_message().guid()); | |||
| for (const auto& obj : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage) | |||
| playerGUIDs.push_back(obj.tricker_message().guid()); | |||
| currentState->guids = playerGUIDs; | |||
| bufferState->guids = playerGUIDs; | |||
| // 读取地图 | |||
| for (const auto& item : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) | |||
| @@ -368,16 +361,6 @@ void Logic::ProcessMessage() | |||
| break; | |||
| case THUAI6::GameState::GameRunning: | |||
| // 重新读取玩家的guid,guid确保人类在前屠夫在后 | |||
| playerGUIDs.clear(); | |||
| for (const auto& obj : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage) | |||
| playerGUIDs.push_back(obj.student_message().guid()); | |||
| for (const auto& obj : clientMsg.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage) | |||
| playerGUIDs.push_back(obj.tricker_message().guid()); | |||
| currentState->guids = playerGUIDs; | |||
| bufferState->guids = playerGUIDs; | |||
| LoadBuffer(clientMsg); | |||
| break; | |||
| @@ -386,7 +369,6 @@ void Logic::ProcessMessage() | |||
| break; | |||
| } | |||
| } | |||
| AILoop = false; | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxBuffer); | |||
| bufferUpdated = true; | |||
| @@ -394,6 +376,7 @@ void Logic::ProcessMessage() | |||
| } | |||
| cvBuffer.notify_one(); | |||
| logger->info("Game End!"); | |||
| AILoop = false; | |||
| }; | |||
| std::thread(messageThread).detach(); | |||
| } | |||
| @@ -564,7 +547,7 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item) | |||
| } | |||
| case THUAI6::MessageOfObj::GateMessage: | |||
| { | |||
| if (!AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap)) | |||
| if (AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap)) | |||
| { | |||
| auto pos = std::make_pair(AssistFunction::GridToCell(item.gate_message().x()), AssistFunction::GridToCell(item.gate_message().y())); | |||
| if (bufferState->mapInfo->gateState.count(pos) == 0) | |||
| @@ -584,7 +567,20 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item) | |||
| { | |||
| auto news = item.news_message(); | |||
| if (news.to_id() == playerID) | |||
| messageQueue.emplace(std::make_pair(news.from_id(), news.news())); | |||
| { | |||
| if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::TextMessage) | |||
| { | |||
| messageQueue.emplace(std::make_pair(news.to_id(), news.text_message())); | |||
| logger->debug("Add News!"); | |||
| } | |||
| else if (Proto2THUAI6::newsTypeDict[news.news_case()] == THUAI6::NewsType::BinaryMessage) | |||
| { | |||
| messageQueue.emplace(std::make_pair(news.to_id(), news.binary_message())); | |||
| logger->debug("Add News!"); | |||
| } | |||
| else | |||
| logger->error("Unknown NewsType!"); | |||
| } | |||
| break; | |||
| } | |||
| case THUAI6::MessageOfObj::NullMessageOfObj: | |||
| @@ -605,9 +601,16 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message) | |||
| bufferState->props.clear(); | |||
| bufferState->bullets.clear(); | |||
| bufferState->bombedBullets.clear(); | |||
| bufferState->guids.clear(); | |||
| logger->debug("Buffer cleared!"); | |||
| // 读取新的信息 | |||
| for (const auto& obj : message.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::StudentMessage) | |||
| bufferState->guids.push_back(obj.student_message().guid()); | |||
| for (const auto& obj : message.obj_message()) | |||
| if (Proto2THUAI6::messageOfObjDict[obj.message_of_obj_case()] == THUAI6::MessageOfObj::TrickerMessage) | |||
| bufferState->guids.push_back(obj.tricker_message().guid()); | |||
| bufferState->gameInfo = Proto2THUAI6::Protobuf2THUAI6GameInfo(message.all_message()); | |||
| LoadBufferSelf(message); | |||
| for (const auto& item : message.obj_message()) | |||
| @@ -618,6 +621,7 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxState); | |||
| std::swap(currentState, bufferState); | |||
| counterState = counterBuffer; | |||
| logger->info("Update State!"); | |||
| } | |||
| freshed = true; | |||
| @@ -686,6 +690,12 @@ bool Logic::TryConnection() | |||
| return pComm->TryConnection(playerID); | |||
| } | |||
| bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const | |||
| { | |||
| std::unique_lock<std::mutex> lock(mtxState); | |||
| return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap); | |||
| } | |||
| void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool file, bool print, bool warnOnly) | |||
| { | |||
| // 建立日志组件 | |||
| @@ -695,9 +705,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f | |||
| fileLogger->set_pattern(pattern); | |||
| printLogger->set_pattern(pattern); | |||
| if (file) | |||
| { | |||
| fileLogger->set_level(spdlog::level::trace); | |||
| } | |||
| fileLogger->set_level(spdlog::level::debug); | |||
| else | |||
| fileLogger->set_level(spdlog::level::off); | |||
| if (print) | |||
| @@ -3,11 +3,36 @@ | |||
| #include "structures.h" | |||
| #include <tclap/CmdLine.h> | |||
| #include <array> | |||
| #include <string_view> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| #ifdef _MSC_VER | |||
| #pragma warning(disable : 4996) | |||
| #endif | |||
| using namespace std::literals::string_view_literals; | |||
| // Generated by http://www.network-science.de/ascii/ with font "standard" | |||
| static constexpr std::string_view welcomeString = R"welcome( | |||
| _____ _ _ _ _ _ ___ __ | |||
| |_ _| | | | | | | / \ |_ _/ /_ | |||
| | | | |_| | | | |/ _ \ | | '_ \ | |||
| | | | _ | |_| / ___ \ | | (_) | | |||
| |_| |_| |_|\___/_/ \_\___\___/ | |||
| ____ _ _ ____ _ _ _ | |||
| / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||
| | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||
| | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||
| \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||
| )welcome"sv; | |||
| int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| { | |||
| int pID = 0; | |||
| @@ -80,6 +105,11 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||
| playerType = THUAI6::PlayerType::StudentPlayer; | |||
| stuType = studentType[pID]; | |||
| } | |||
| #ifdef _MSC_VER | |||
| std::cout << welcomeString << std::endl; | |||
| #endif | |||
| Logic logic(playerType, pID, trickerType, stuType); | |||
| logic.Main(AIBuilder, sIP, sPort, file, print, warnOnly); | |||
| } | |||
| @@ -312,7 +312,7 @@ namespace protobuf | |||
| ::_pbi::ConstantInitialized | |||
| ) : | |||
| _impl_{ | |||
| /*decltype(_impl_.news_)*/ {&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}, /*decltype(_impl_.from_id_)*/ int64_t{0}, /*decltype(_impl_.to_id_)*/ int64_t{0}, /*decltype(_impl_._cached_size_)*/ {}} | |||
| /*decltype(_impl_. from_id_)*/ int64_t{0}, /*decltype(_impl_.to_id_)*/ int64_t{0}, /*decltype(_impl_.news_)*/ {}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}} |
|||
| { | |||
| } | |||
| struct MessageOfNewsDefaultTypeInternal | |||
| @@ -612,12 +612,14 @@ const uint32_t TableStruct_Message2Clients_2eproto::offsets[] PROTOBUF_SECTION_V | |||
| ~0u, // no _has_bits_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _internal_metadata_), | |||
| ~0u, // no _extensions_ | |||
| ~0u, // no _oneof_case_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_._oneof_case_[0]), | |||
| ~0u, // no _weak_field_map_ | |||
| ~0u, // no _inlined_string_donated_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.news_), | |||
| ::_pbi::kInvalidFieldOffsetTag, | |||
| ::_pbi::kInvalidFieldOffsetTag, | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.from_id_), | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.to_id_), | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfNews, _impl_.news_), | |||
| ~0u, // no _has_bits_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::MessageOfObj, _internal_metadata_), | |||
| ~0u, // no _extensions_ | |||
| @@ -690,11 +692,11 @@ static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protode | |||
| {149, -1, -1, sizeof(::protobuf::MessageOfMap_Row)}, | |||
| {156, -1, -1, sizeof(::protobuf::MessageOfMap)}, | |||
| {163, -1, -1, sizeof(::protobuf::MessageOfNews)}, | |||
| {172, -1, -1, sizeof(::protobuf::MessageOfObj)}, | |||
| {191, -1, -1, sizeof(::protobuf::MessageOfAll)}, | |||
| {203, -1, -1, sizeof(::protobuf::MessageToClient)}, | |||
| {212, -1, -1, sizeof(::protobuf::MoveRes)}, | |||
| {221, -1, -1, sizeof(::protobuf::BoolRes)}, | |||
| {174, -1, -1, sizeof(::protobuf::MessageOfObj)}, | |||
| {193, -1, -1, sizeof(::protobuf::MessageOfAll)}, | |||
| {205, -1, -1, sizeof(::protobuf::MessageToClient)}, | |||
| {214, -1, -1, sizeof(::protobuf::MoveRes)}, | |||
| {223, -1, -1, sizeof(::protobuf::BoolRes)}, | |||
| }; | |||
| static const ::_pb::Message* const file_default_instances[] = { | |||
| @@ -775,36 +777,37 @@ const char descriptor_table_protodef_Message2Clients_2eproto[] PROTOBUF_SECTION_ | |||
| "\001(\005\022\t\n\001y\030\002 \001(\005\022\020\n\010progress\030\003 \001(\005\"`\n\014Mess" | |||
| "ageOfMap\022\'\n\003row\030\002 \003(\0132\032.protobuf.Message" | |||
| "OfMap.Row\032\'\n\003Row\022 \n\003col\030\001 \003(\0162\023.protobuf" | |||
| ".PlaceType\"=\n\rMessageOfNews\022\014\n\004news\030\001 \001(" | |||
| "\t\022\017\n\007from_id\030\002 \001(\003\022\r\n\005to_id\030\003 \001(\003\"\244\005\n\014Me" | |||
| "ssageOfObj\0225\n\017student_message\030\001 \001(\0132\032.pr" | |||
| "otobuf.MessageOfStudentH\000\0225\n\017tricker_mes" | |||
| "sage\030\002 \001(\0132\032.protobuf.MessageOfTrickerH\000" | |||
| "\022/\n\014prop_message\030\003 \001(\0132\027.protobuf.Messag" | |||
| "eOfPropH\000\0223\n\016bullet_message\030\004 \001(\0132\031.prot" | |||
| "obuf.MessageOfBulletH\000\022@\n\025bombed_bullet_" | |||
| "message\030\005 \001(\0132\037.protobuf.MessageOfBombed" | |||
| "BulletH\000\0229\n\021classroom_message\030\006 \001(\0132\034.pr" | |||
| "otobuf.MessageOfClassroomH\000\022/\n\014door_mess" | |||
| "age\030\007 \001(\0132\027.protobuf.MessageOfDoorH\000\022/\n\014" | |||
| "gate_message\030\010 \001(\0132\027.protobuf.MessageOfG" | |||
| "ateH\000\0221\n\rchest_message\030\t \001(\0132\030.protobuf." | |||
| "MessageOfChestH\000\022<\n\023hidden_gate_message\030" | |||
| "\n \001(\0132\035.protobuf.MessageOfHiddenGateH\000\022/" | |||
| "\n\014news_message\030\013 \001(\0132\027.protobuf.MessageO" | |||
| "fNewsH\000\022-\n\013map_message\030\014 \001(\0132\026.protobuf." | |||
| "MessageOfMapH\000B\020\n\016message_of_obj\"\234\001\n\014Mes" | |||
| "sageOfAll\022\021\n\tgame_time\030\001 \001(\005\022\030\n\020subject_" | |||
| "finished\030\002 \001(\005\022\031\n\021student_graduated\030\003 \001(" | |||
| "\005\022\026\n\016student_quited\030\004 \001(\005\022\025\n\rstudent_sco" | |||
| "re\030\005 \001(\005\022\025\n\rtricker_score\030\006 \001(\005\"\224\001\n\017Mess" | |||
| "ageToClient\022+\n\013obj_message\030\001 \003(\0132\026.proto" | |||
| "buf.MessageOfObj\022\'\n\ngame_state\030\002 \001(\0162\023.p" | |||
| "rotobuf.GameState\022+\n\013all_message\030\003 \001(\0132\026" | |||
| ".protobuf.MessageOfAll\"J\n\007MoveRes\022\024\n\014act" | |||
| "ual_speed\030\001 \001(\003\022\024\n\014actual_angle\030\002 \001(\001\022\023\n" | |||
| "\013act_success\030\003 \001(\010\"\036\n\007BoolRes\022\023\n\013act_suc" | |||
| "cess\030\001 \001(\010b\006proto3"; | |||
| ".PlaceType\"i\n\rMessageOfNews\022\026\n\014text_mess" | |||
| "age\030\001 \001(\tH\000\022\030\n\016binary_message\030\004 \001(\014H\000\022\017\n" | |||
| "\007from_id\030\002 \001(\003\022\r\n\005to_id\030\003 \001(\003B\006\n\004news\"\244\005" | |||
| "\n\014MessageOfObj\0225\n\017student_message\030\001 \001(\0132" | |||
| "\032.protobuf.MessageOfStudentH\000\0225\n\017tricker" | |||
| "_message\030\002 \001(\0132\032.protobuf.MessageOfTrick" | |||
| "erH\000\022/\n\014prop_message\030\003 \001(\0132\027.protobuf.Me" | |||
| "ssageOfPropH\000\0223\n\016bullet_message\030\004 \001(\0132\031." | |||
| "protobuf.MessageOfBulletH\000\022@\n\025bombed_bul" | |||
| "let_message\030\005 \001(\0132\037.protobuf.MessageOfBo" | |||
| "mbedBulletH\000\0229\n\021classroom_message\030\006 \001(\0132" | |||
| "\034.protobuf.MessageOfClassroomH\000\022/\n\014door_" | |||
| "message\030\007 \001(\0132\027.protobuf.MessageOfDoorH\000" | |||
| "\022/\n\014gate_message\030\010 \001(\0132\027.protobuf.Messag" | |||
| "eOfGateH\000\0221\n\rchest_message\030\t \001(\0132\030.proto" | |||
| "buf.MessageOfChestH\000\022<\n\023hidden_gate_mess" | |||
| "age\030\n \001(\0132\035.protobuf.MessageOfHiddenGate" | |||
| "H\000\022/\n\014news_message\030\013 \001(\0132\027.protobuf.Mess" | |||
| "ageOfNewsH\000\022-\n\013map_message\030\014 \001(\0132\026.proto" | |||
| "buf.MessageOfMapH\000B\020\n\016message_of_obj\"\234\001\n" | |||
| "\014MessageOfAll\022\021\n\tgame_time\030\001 \001(\005\022\030\n\020subj" | |||
| "ect_finished\030\002 \001(\005\022\031\n\021student_graduated\030" | |||
| "\003 \001(\005\022\026\n\016student_quited\030\004 \001(\005\022\025\n\rstudent" | |||
| "_score\030\005 \001(\005\022\025\n\rtricker_score\030\006 \001(\005\"\224\001\n\017" | |||
| "MessageToClient\022+\n\013obj_message\030\001 \003(\0132\026.p" | |||
| "rotobuf.MessageOfObj\022\'\n\ngame_state\030\002 \001(\016" | |||
| "2\023.protobuf.GameState\022+\n\013all_message\030\003 \001" | |||
| "(\0132\026.protobuf.MessageOfAll\"J\n\007MoveRes\022\024\n" | |||
| "\014actual_speed\030\001 \001(\003\022\024\n\014actual_angle\030\002 \001(" | |||
| "\001\022\023\n\013act_success\030\003 \001(\010\"\036\n\007BoolRes\022\023\n\013act" | |||
| "_success\030\001 \001(\010b\006proto3"; | |||
| static const ::_pbi::DescriptorTable* const descriptor_table_Message2Clients_2eproto_deps[1] = { | |||
| &::descriptor_table_MessageType_2eproto, | |||
| }; | |||
| @@ -812,7 +815,7 @@ static ::_pbi::once_flag descriptor_table_Message2Clients_2eproto_once; | |||
| const ::_pbi::DescriptorTable descriptor_table_Message2Clients_2eproto = { | |||
| false, | |||
| false, | |||
| 3378, | |||
| 3422, | |||
| descriptor_table_protodef_Message2Clients_2eproto, | |||
| "Message2Clients.proto", | |||
| &descriptor_table_Message2Clients_2eproto_once, | |||
| @@ -5927,18 +5930,28 @@ namespace protobuf | |||
| MessageOfNews* const _this = this; | |||
| (void)_this; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.news_){}, decltype(_impl_.from_id_){}, decltype(_impl_.to_id_){}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| decltype(_impl_.from_id_){}, decltype(_impl_.to_id_){}, decltype(_impl_.news_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}; | |||
| _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| _impl_.news_.InitDefault(); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| _impl_.news_.Set("", GetArenaForAllocation()); | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| if (!from._internal_news().empty()) | |||
| ::memcpy(&_impl_.from_id_, &from._impl_.from_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_)); | |||
| clear_has_news(); | |||
| switch (from.news_case()) | |||
| { | |||
| _this->_impl_.news_.Set(from._internal_news(), _this->GetArenaForAllocation()); | |||
| case kTextMessage: | |||
| { | |||
| _this->_internal_set_text_message(from._internal_text_message()); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _this->_internal_set_binary_message(from._internal_binary_message()); | |||
| break; | |||
| } | |||
| case NEWS_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| ::memcpy(&_impl_.from_id_, &from._impl_.from_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_)); | |||
| // @@protoc_insertion_point(copy_constructor:protobuf.MessageOfNews) | |||
| } | |||
| @@ -5949,11 +5962,8 @@ namespace protobuf | |||
| (void)arena; | |||
| (void)is_message_owned; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.news_){}, decltype(_impl_.from_id_){int64_t{0}}, decltype(_impl_.to_id_){int64_t{0}}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| _impl_.news_.InitDefault(); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| _impl_.news_.Set("", GetArenaForAllocation()); | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| decltype(_impl_.from_id_){int64_t{0}}, decltype(_impl_.to_id_){int64_t{0}}, decltype(_impl_.news_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}; | |||
| clear_has_news(); | |||
| } | |||
| MessageOfNews::~MessageOfNews() | |||
| @@ -5970,7 +5980,10 @@ namespace protobuf | |||
| inline void MessageOfNews::SharedDtor() | |||
| { | |||
| GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); | |||
| _impl_.news_.Destroy(); | |||
| if (has_news()) | |||
| { | |||
| clear_news(); | |||
| } | |||
| } | |||
| void MessageOfNews::SetCachedSize(int size) const | |||
| @@ -5978,6 +5991,29 @@ namespace protobuf | |||
| _impl_._cached_size_.Set(size); | |||
| } | |||
| void MessageOfNews::clear_news() | |||
| { | |||
| // @@protoc_insertion_point(one_of_clear_start:protobuf.MessageOfNews) | |||
| switch (news_case()) | |||
| { | |||
| case kTextMessage: | |||
| { | |||
| _impl_.news_.text_message_.Destroy(); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _impl_.news_.binary_message_.Destroy(); | |||
| break; | |||
| } | |||
| case NEWS_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| _impl_._oneof_case_[0] = NEWS_NOT_SET; | |||
| } | |||
| void MessageOfNews::Clear() | |||
| { | |||
| // @@protoc_insertion_point(message_clear_start:protobuf.MessageOfNews) | |||
| @@ -5985,8 +6021,8 @@ namespace protobuf | |||
| // Prevent compiler warnings about cached_has_bits being unused | |||
| (void)cached_has_bits; | |||
| _impl_.news_.ClearToEmpty(); | |||
| ::memset(&_impl_.from_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_id_) - reinterpret_cast<char*>(&_impl_.from_id_)) + sizeof(_impl_.to_id_)); | |||
| clear_news(); | |||
| _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); | |||
| } | |||
| @@ -6001,14 +6037,14 @@ namespace protobuf | |||
| ptr = ::_pbi::ReadTag(ptr, &tag); | |||
| switch (tag >> 3) | |||
| { | |||
| // string news = 1; | |||
| // string text_message = 1; | |||
| case 1: | |||
| if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) | |||
| { | |||
| auto str = _internal_mutable_news(); | |||
| auto str = _internal_mutable_text_message(); | |||
| ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); | |||
| CHK_(ptr); | |||
| CHK_(::_pbi::VerifyUTF8(str, "protobuf.MessageOfNews.news")); | |||
| CHK_(::_pbi::VerifyUTF8(str, "protobuf.MessageOfNews.text_message")); | |||
| } | |||
| else | |||
| goto handle_unusual; | |||
| @@ -6033,6 +6069,17 @@ namespace protobuf | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| // bytes binary_message = 4; | |||
| case 4: | |||
| if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) | |||
| { | |||
| auto str = _internal_mutable_binary_message(); | |||
| ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); | |||
| CHK_(ptr); | |||
| } | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| default: | |||
| goto handle_unusual; | |||
| } // switch | |||
| @@ -6067,14 +6114,14 @@ namespace protobuf | |||
| uint32_t cached_has_bits = 0; | |||
| (void)cached_has_bits; | |||
| // string news = 1; | |||
| if (!this->_internal_news().empty()) | |||
| // string text_message = 1; | |||
| if (_internal_has_text_message()) | |||
| { | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( | |||
| this->_internal_news().data(), static_cast<int>(this->_internal_news().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.MessageOfNews.news" | |||
| this->_internal_text_message().data(), static_cast<int>(this->_internal_text_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.MessageOfNews.text_message" | |||
| ); | |||
| target = stream->WriteStringMaybeAliased( | |||
| 1, this->_internal_news(), target | |||
| 1, this->_internal_text_message(), target | |||
| ); | |||
| } | |||
| @@ -6092,6 +6139,14 @@ namespace protobuf | |||
| target = ::_pbi::WireFormatLite::WriteInt64ToArray(3, this->_internal_to_id(), target); | |||
| } | |||
| // bytes binary_message = 4; | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| target = stream->WriteBytesMaybeAliased( | |||
| 4, this->_internal_binary_message(), target | |||
| ); | |||
| } | |||
| if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) | |||
| { | |||
| target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( | |||
| @@ -6111,15 +6166,6 @@ namespace protobuf | |||
| // Prevent compiler warnings about cached_has_bits being unused | |||
| (void)cached_has_bits; | |||
| // string news = 1; | |||
| if (!this->_internal_news().empty()) | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( | |||
| this->_internal_news() | |||
| ); | |||
| } | |||
| // int64 from_id = 2; | |||
| if (this->_internal_from_id() != 0) | |||
| { | |||
| @@ -6132,6 +6178,31 @@ namespace protobuf | |||
| total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_to_id()); | |||
| } | |||
| switch (news_case()) | |||
| { | |||
| // string text_message = 1; | |||
| case kTextMessage: | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( | |||
| this->_internal_text_message() | |||
| ); | |||
| break; | |||
| } | |||
| // bytes binary_message = 4; | |||
| case kBinaryMessage: | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( | |||
| this->_internal_binary_message() | |||
| ); | |||
| break; | |||
| } | |||
| case NEWS_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); | |||
| } | |||
| @@ -6152,10 +6223,6 @@ namespace protobuf | |||
| uint32_t cached_has_bits = 0; | |||
| (void)cached_has_bits; | |||
| if (!from._internal_news().empty()) | |||
| { | |||
| _this->_internal_set_news(from._internal_news()); | |||
| } | |||
| if (from._internal_from_id() != 0) | |||
| { | |||
| _this->_internal_set_from_id(from._internal_from_id()); | |||
| @@ -6164,6 +6231,23 @@ namespace protobuf | |||
| { | |||
| _this->_internal_set_to_id(from._internal_to_id()); | |||
| } | |||
| switch (from.news_case()) | |||
| { | |||
| case kTextMessage: | |||
| { | |||
| _this->_internal_set_text_message(from._internal_text_message()); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _this->_internal_set_binary_message(from._internal_binary_message()); | |||
| break; | |||
| } | |||
| case NEWS_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| } | |||
| @@ -6184,17 +6268,14 @@ namespace protobuf | |||
| void MessageOfNews::InternalSwap(MessageOfNews* other) | |||
| { | |||
| using std::swap; | |||
| auto* lhs_arena = GetArenaForAllocation(); | |||
| auto* rhs_arena = other->GetArenaForAllocation(); | |||
| _internal_metadata_.InternalSwap(&other->_internal_metadata_); | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( | |||
| &_impl_.news_, lhs_arena, &other->_impl_.news_, rhs_arena | |||
| ); | |||
| ::PROTOBUF_NAMESPACE_ID::internal::memswap< | |||
| PROTOBUF_FIELD_OFFSET(MessageOfNews, _impl_.to_id_) + sizeof(MessageOfNews::_impl_.to_id_) - PROTOBUF_FIELD_OFFSET(MessageOfNews, _impl_.from_id_)>( | |||
| reinterpret_cast<char*>(&_impl_.from_id_), | |||
| reinterpret_cast<char*>(&other->_impl_.from_id_) | |||
| ); | |||
| swap(_impl_.news_, other->_impl_.news_); | |||
| swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]); | |||
| } | |||
| ::PROTOBUF_NAMESPACE_ID::Metadata MessageOfNews::GetMetadata() const | |||
| @@ -3677,6 +3677,13 @@ namespace protobuf | |||
| { | |||
| return *internal_default_instance(); | |||
| } | |||
| enum NewsCase | |||
| { | |||
| kTextMessage = 1, | |||
| kBinaryMessage = 4, | |||
| NEWS_NOT_SET = 0, | |||
| }; | |||
| static inline const MessageOfNews* internal_default_instance() | |||
| { | |||
| return reinterpret_cast<const MessageOfNews*>( | |||
| @@ -3776,25 +3783,11 @@ namespace protobuf | |||
| enum : int | |||
| { | |||
| kNewsFieldNumber = 1, | |||
| kFromIdFieldNumber = 2, | |||
| kToIdFieldNumber = 3, | |||
| kTextMessageFieldNumber = 1, | |||
| kBinaryMessageFieldNumber = 4, | |||
| }; | |||
| // string news = 1; | |||
| void clear_news(); | |||
| const std::string& news() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_news(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_news(); | |||
| PROTOBUF_NODISCARD std::string* release_news(); | |||
| void set_allocated_news(std::string* news); | |||
| private: | |||
| const std::string& _internal_news() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_news(const std::string& value); | |||
| std::string* _internal_mutable_news(); | |||
| public: | |||
| // int64 from_id = 2; | |||
| void clear_from_id(); | |||
| int64_t from_id() const; | |||
| @@ -3815,10 +3808,59 @@ namespace protobuf | |||
| void _internal_set_to_id(int64_t value); | |||
| public: | |||
| // string text_message = 1; | |||
| bool has_text_message() const; | |||
| private: | |||
| bool _internal_has_text_message() const; | |||
| public: | |||
| void clear_text_message(); | |||
| const std::string& text_message() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_text_message(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_text_message(); | |||
| PROTOBUF_NODISCARD std::string* release_text_message(); | |||
| void set_allocated_text_message(std::string* text_message); | |||
| private: | |||
| const std::string& _internal_text_message() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_text_message(const std::string& value); | |||
| std::string* _internal_mutable_text_message(); | |||
| public: | |||
| // bytes binary_message = 4; | |||
| bool has_binary_message() const; | |||
| private: | |||
| bool _internal_has_binary_message() const; | |||
| public: | |||
| void clear_binary_message(); | |||
| const std::string& binary_message() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_binary_message(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_binary_message(); | |||
| PROTOBUF_NODISCARD std::string* release_binary_message(); | |||
| void set_allocated_binary_message(std::string* binary_message); | |||
| private: | |||
| const std::string& _internal_binary_message() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_binary_message(const std::string& value); | |||
| std::string* _internal_mutable_binary_message(); | |||
| public: | |||
| void clear_news(); | |||
| NewsCase news_case() const; | |||
| // @@protoc_insertion_point(class_scope:protobuf.MessageOfNews) | |||
| private: | |||
| class _Internal; | |||
| void set_has_text_message(); | |||
| void set_has_binary_message(); | |||
| inline bool has_news() const; | |||
| inline void clear_has_news(); | |||
| template<typename T> | |||
| friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; | |||
| @@ -3826,10 +3868,20 @@ namespace protobuf | |||
| typedef void DestructorSkippable_; | |||
| struct Impl_ | |||
| { | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr news_; | |||
| int64_t from_id_; | |||
| int64_t to_id_; | |||
| union NewsUnion | |||
| { | |||
| constexpr NewsUnion() : | |||
| _constinit_{} | |||
| { | |||
| } | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_message_; | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr binary_message_; | |||
| } news_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| uint32_t _oneof_case_[1]; | |||
| }; | |||
| union | |||
| { | |||
| @@ -7609,61 +7661,202 @@ namespace protobuf | |||
| // MessageOfNews | |||
| // string news = 1; | |||
| inline void MessageOfNews::clear_news() | |||
| // string text_message = 1; | |||
| inline bool MessageOfNews::_internal_has_text_message() const | |||
| { | |||
| return news_case() == kTextMessage; | |||
| } | |||
| inline bool MessageOfNews::has_text_message() const | |||
| { | |||
| _impl_.news_.ClearToEmpty(); | |||
| return _internal_has_text_message(); | |||
| } | |||
| inline const std::string& MessageOfNews::news() const | |||
| inline void MessageOfNews::set_has_text_message() | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfNews.news) | |||
| return _internal_news(); | |||
| _impl_._oneof_case_[0] = kTextMessage; | |||
| } | |||
| inline void MessageOfNews::clear_text_message() | |||
| { | |||
| if (_internal_has_text_message()) | |||
| { | |||
| _impl_.news_.text_message_.Destroy(); | |||
| clear_has_news(); | |||
| } | |||
| } | |||
| inline const std::string& MessageOfNews::text_message() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfNews.text_message) | |||
| return _internal_text_message(); | |||
| } | |||
| template<typename ArgT0, typename... ArgT> | |||
| inline PROTOBUF_ALWAYS_INLINE void MessageOfNews::set_news(ArgT0&& arg0, ArgT... args) | |||
| inline void MessageOfNews::set_text_message(ArgT0&& arg0, ArgT... args) | |||
| { | |||
| _impl_.news_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfNews.news) | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_text_message(); | |||
| _impl_.news_.text_message_.InitDefault(); | |||
| } | |||
| _impl_.news_.text_message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfNews.text_message) | |||
| } | |||
| inline std::string* MessageOfNews::mutable_news() | |||
| inline std::string* MessageOfNews::mutable_text_message() | |||
| { | |||
| std::string* _s = _internal_mutable_news(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.news) | |||
| std::string* _s = _internal_mutable_text_message(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.text_message) | |||
| return _s; | |||
| } | |||
| inline const std::string& MessageOfNews::_internal_news() const | |||
| inline const std::string& MessageOfNews::_internal_text_message() const | |||
| { | |||
| return _impl_.news_.Get(); | |||
| if (_internal_has_text_message()) | |||
| { | |||
| return _impl_.news_.text_message_.Get(); | |||
| } | |||
| return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(); | |||
| } | |||
| inline void MessageOfNews::_internal_set_news(const std::string& value) | |||
| inline void MessageOfNews::_internal_set_text_message(const std::string& value) | |||
| { | |||
| _impl_.news_.Set(value, GetArenaForAllocation()); | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_text_message(); | |||
| _impl_.news_.text_message_.InitDefault(); | |||
| } | |||
| _impl_.news_.text_message_.Set(value, GetArenaForAllocation()); | |||
| } | |||
| inline std::string* MessageOfNews::_internal_mutable_news() | |||
| inline std::string* MessageOfNews::_internal_mutable_text_message() | |||
| { | |||
| return _impl_.news_.Mutable(GetArenaForAllocation()); | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_text_message(); | |||
| _impl_.news_.text_message_.InitDefault(); | |||
| } | |||
| return _impl_.news_.text_message_.Mutable(GetArenaForAllocation()); | |||
| } | |||
| inline std::string* MessageOfNews::release_news() | |||
| inline std::string* MessageOfNews::release_text_message() | |||
| { | |||
| // @@protoc_insertion_point(field_release:protobuf.MessageOfNews.news) | |||
| return _impl_.news_.Release(); | |||
| // @@protoc_insertion_point(field_release:protobuf.MessageOfNews.text_message) | |||
| if (_internal_has_text_message()) | |||
| { | |||
| clear_has_news(); | |||
| return _impl_.news_.text_message_.Release(); | |||
| } | |||
| else | |||
| { | |||
| return nullptr; | |||
| } | |||
| } | |||
| inline void MessageOfNews::set_allocated_news(std::string* news) | |||
| inline void MessageOfNews::set_allocated_text_message(std::string* text_message) | |||
| { | |||
| if (news != nullptr) | |||
| if (has_news()) | |||
| { | |||
| clear_news(); | |||
| } | |||
| if (text_message != nullptr) | |||
| { | |||
| set_has_text_message(); | |||
| _impl_.news_.text_message_.InitAllocated(text_message, GetArenaForAllocation()); | |||
| } | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.text_message) | |||
| } | |||
| // bytes binary_message = 4; | |||
| inline bool MessageOfNews::_internal_has_binary_message() const | |||
| { | |||
| return news_case() == kBinaryMessage; | |||
| } | |||
| inline bool MessageOfNews::has_binary_message() const | |||
| { | |||
| return _internal_has_binary_message(); | |||
| } | |||
| inline void MessageOfNews::set_has_binary_message() | |||
| { | |||
| _impl_._oneof_case_[0] = kBinaryMessage; | |||
| } | |||
| inline void MessageOfNews::clear_binary_message() | |||
| { | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| _impl_.news_.binary_message_.Destroy(); | |||
| clear_has_news(); | |||
| } | |||
| } | |||
| inline const std::string& MessageOfNews::binary_message() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.MessageOfNews.binary_message) | |||
| return _internal_binary_message(); | |||
| } | |||
| template<typename ArgT0, typename... ArgT> | |||
| inline void MessageOfNews::set_binary_message(ArgT0&& arg0, ArgT... args) | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_binary_message(); | |||
| _impl_.news_.binary_message_.InitDefault(); | |||
| } | |||
| _impl_.news_.binary_message_.SetBytes(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfNews.binary_message) | |||
| } | |||
| inline std::string* MessageOfNews::mutable_binary_message() | |||
| { | |||
| std::string* _s = _internal_mutable_binary_message(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.MessageOfNews.binary_message) | |||
| return _s; | |||
| } | |||
| inline const std::string& MessageOfNews::_internal_binary_message() const | |||
| { | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| return _impl_.news_.binary_message_.Get(); | |||
| } | |||
| return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(); | |||
| } | |||
| inline void MessageOfNews::_internal_set_binary_message(const std::string& value) | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_binary_message(); | |||
| _impl_.news_.binary_message_.InitDefault(); | |||
| } | |||
| _impl_.news_.binary_message_.Set(value, GetArenaForAllocation()); | |||
| } | |||
| inline std::string* MessageOfNews::_internal_mutable_binary_message() | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_news(); | |||
| set_has_binary_message(); | |||
| _impl_.news_.binary_message_.InitDefault(); | |||
| } | |||
| return _impl_.news_.binary_message_.Mutable(GetArenaForAllocation()); | |||
| } | |||
| inline std::string* MessageOfNews::release_binary_message() | |||
| { | |||
| // @@protoc_insertion_point(field_release:protobuf.MessageOfNews.binary_message) | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| clear_has_news(); | |||
| return _impl_.news_.binary_message_.Release(); | |||
| } | |||
| else | |||
| { | |||
| return nullptr; | |||
| } | |||
| } | |||
| inline void MessageOfNews::set_allocated_binary_message(std::string* binary_message) | |||
| { | |||
| if (has_news()) | |||
| { | |||
| clear_news(); | |||
| } | |||
| _impl_.news_.SetAllocated(news, GetArenaForAllocation()); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| if (_impl_.news_.IsDefault()) | |||
| if (binary_message != nullptr) | |||
| { | |||
| _impl_.news_.Set("", GetArenaForAllocation()); | |||
| set_has_binary_message(); | |||
| _impl_.news_.binary_message_.InitAllocated(binary_message, GetArenaForAllocation()); | |||
| } | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.news) | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.MessageOfNews.binary_message) | |||
| } | |||
| // int64 from_id = 2; | |||
| @@ -7714,6 +7907,18 @@ namespace protobuf | |||
| // @@protoc_insertion_point(field_set:protobuf.MessageOfNews.to_id) | |||
| } | |||
| inline bool MessageOfNews::has_news() const | |||
| { | |||
| return news_case() != NEWS_NOT_SET; | |||
| } | |||
| inline void MessageOfNews::clear_has_news() | |||
| { | |||
| _impl_._oneof_case_[0] = NEWS_NOT_SET; | |||
| } | |||
| inline MessageOfNews::NewsCase MessageOfNews::news_case() const | |||
| { | |||
| return MessageOfNews::NewsCase(_impl_._oneof_case_[0]); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // MessageOfObj | |||
| @@ -92,7 +92,7 @@ namespace protobuf | |||
| ::_pbi::ConstantInitialized | |||
| ) : | |||
| _impl_{ | |||
| /*decltype(_impl_.message_)*/ {&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}, /*decltype(_impl_.player_id_)*/ int64_t{0}, /*decltype(_impl_.to_player_id_)*/ int64_t{0}, /*decltype(_impl_._cached_size_)*/ {}} | |||
| /*decltype(_impl_. player_id_)*/ int64_t{0}, /*decltype(_impl_.to_player_id_)*/ int64_t{0}, /*decltype(_impl_.message_)*/ {}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}} |
|||
| { | |||
| } | |||
| struct SendMsgDefaultTypeInternal | |||
| @@ -235,11 +235,13 @@ const uint32_t TableStruct_Message2Server_2eproto::offsets[] PROTOBUF_SECTION_VA | |||
| ~0u, // no _has_bits_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _internal_metadata_), | |||
| ~0u, // no _extensions_ | |||
| ~0u, // no _oneof_case_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_._oneof_case_[0]), | |||
| ~0u, // no _weak_field_map_ | |||
| ~0u, // no _inlined_string_donated_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.player_id_), | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.to_player_id_), | |||
| ::_pbi::kInvalidFieldOffsetTag, | |||
| ::_pbi::kInvalidFieldOffsetTag, | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::SendMsg, _impl_.message_), | |||
| ~0u, // no _has_bits_ | |||
| PROTOBUF_FIELD_OFFSET(::protobuf::AttackMsg, _internal_metadata_), | |||
| @@ -278,10 +280,10 @@ static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protode | |||
| {11, -1, -1, sizeof(::protobuf::MoveMsg)}, | |||
| {20, -1, -1, sizeof(::protobuf::PropMsg)}, | |||
| {28, -1, -1, sizeof(::protobuf::SendMsg)}, | |||
| {37, -1, -1, sizeof(::protobuf::AttackMsg)}, | |||
| {45, -1, -1, sizeof(::protobuf::IDMsg)}, | |||
| {52, -1, -1, sizeof(::protobuf::TreatAndRescueMsg)}, | |||
| {60, -1, -1, sizeof(::protobuf::SkillMsg)}, | |||
| {39, -1, -1, sizeof(::protobuf::AttackMsg)}, | |||
| {47, -1, -1, sizeof(::protobuf::IDMsg)}, | |||
| {54, -1, -1, sizeof(::protobuf::TreatAndRescueMsg)}, | |||
| {62, -1, -1, sizeof(::protobuf::SkillMsg)}, | |||
| }; | |||
| static const ::_pb::Message* const file_default_instances[] = { | |||
| @@ -305,13 +307,15 @@ const char descriptor_table_protodef_Message2Server_2eproto[] PROTOBUF_SECTION_V | |||
| "eMsg\022\021\n\tplayer_id\030\001 \001(\003\022\r\n\005angle\030\002 \001(\001\022\034" | |||
| "\n\024time_in_milliseconds\030\003 \001(\003\"C\n\007PropMsg\022" | |||
| "\021\n\tplayer_id\030\001 \001(\003\022%\n\tprop_type\030\002 \001(\0162\022." | |||
| "protobuf.PropType\"C\n\007SendMsg\022\021\n\tplayer_i" | |||
| "d\030\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\022\017\n\007message" | |||
| "\030\003 \001(\t\"-\n\tAttackMsg\022\021\n\tplayer_id\030\001 \001(\003\022\r" | |||
| "\n\005angle\030\002 \001(\001\"\032\n\005IDMsg\022\021\n\tplayer_id\030\001 \001(" | |||
| "\003\"<\n\021TreatAndRescueMsg\022\021\n\tplayer_id\030\001 \001(" | |||
| "\003\022\024\n\014to_player_id\030\002 \001(\003\"/\n\010SkillMsg\022\021\n\tp" | |||
| "layer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005b\006proto3"; | |||
| "protobuf.PropType\"o\n\007SendMsg\022\021\n\tplayer_i" | |||
| "d\030\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\022\026\n\014text_me" | |||
| "ssage\030\003 \001(\tH\000\022\030\n\016binary_message\030\004 \001(\014H\000B" | |||
| "\t\n\007message\"-\n\tAttackMsg\022\021\n\tplayer_id\030\001 \001" | |||
| "(\003\022\r\n\005angle\030\002 \001(\001\"\032\n\005IDMsg\022\021\n\tplayer_id\030" | |||
| "\001 \001(\003\"<\n\021TreatAndRescueMsg\022\021\n\tplayer_id\030" | |||
| "\001 \001(\003\022\024\n\014to_player_id\030\002 \001(\003\"/\n\010SkillMsg\022" | |||
| "\021\n\tplayer_id\030\001 \001(\003\022\020\n\010skill_id\030\002 \001(\005b\006pr" | |||
| "oto3"; | |||
| static const ::_pbi::DescriptorTable* const descriptor_table_Message2Server_2eproto_deps[1] = { | |||
| &::descriptor_table_MessageType_2eproto, | |||
| }; | |||
| @@ -319,7 +323,7 @@ static ::_pbi::once_flag descriptor_table_Message2Server_2eproto_once; | |||
| const ::_pbi::DescriptorTable descriptor_table_Message2Server_2eproto = { | |||
| false, | |||
| false, | |||
| 640, | |||
| 684, | |||
| descriptor_table_protodef_Message2Server_2eproto, | |||
| "Message2Server.proto", | |||
| &descriptor_table_Message2Server_2eproto_once, | |||
| @@ -1259,18 +1263,28 @@ namespace protobuf | |||
| SendMsg* const _this = this; | |||
| (void)_this; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.message_){}, decltype(_impl_.player_id_){}, decltype(_impl_.to_player_id_){}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| decltype(_impl_.player_id_){}, decltype(_impl_.to_player_id_){}, decltype(_impl_.message_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}; | |||
| _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| _impl_.message_.InitDefault(); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| _impl_.message_.Set("", GetArenaForAllocation()); | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| if (!from._internal_message().empty()) | |||
| ::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_)); | |||
| clear_has_message(); | |||
| switch (from.message_case()) | |||
| { | |||
| _this->_impl_.message_.Set(from._internal_message(), _this->GetArenaForAllocation()); | |||
| case kTextMessage: | |||
| { | |||
| _this->_internal_set_text_message(from._internal_text_message()); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _this->_internal_set_binary_message(from._internal_binary_message()); | |||
| break; | |||
| } | |||
| case MESSAGE_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| ::memcpy(&_impl_.player_id_, &from._impl_.player_id_, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_)); | |||
| // @@protoc_insertion_point(copy_constructor:protobuf.SendMsg) | |||
| } | |||
| @@ -1281,11 +1295,8 @@ namespace protobuf | |||
| (void)arena; | |||
| (void)is_message_owned; | |||
| new (&_impl_) Impl_{ | |||
| decltype(_impl_.message_){}, decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.to_player_id_){int64_t{0}}, /*decltype(_impl_._cached_size_)*/ {}}; | |||
| _impl_.message_.InitDefault(); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| _impl_.message_.Set("", GetArenaForAllocation()); | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| decltype(_impl_.player_id_){int64_t{0}}, decltype(_impl_.to_player_id_){int64_t{0}}, decltype(_impl_.message_){}, /*decltype(_impl_._cached_size_)*/ {}, /*decltype(_impl_._oneof_case_)*/ {}}; | |||
| clear_has_message(); | |||
| } | |||
| SendMsg::~SendMsg() | |||
| @@ -1302,7 +1313,10 @@ namespace protobuf | |||
| inline void SendMsg::SharedDtor() | |||
| { | |||
| GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); | |||
| _impl_.message_.Destroy(); | |||
| if (has_message()) | |||
| { | |||
| clear_message(); | |||
| } | |||
| } | |||
| void SendMsg::SetCachedSize(int size) const | |||
| @@ -1310,6 +1324,29 @@ namespace protobuf | |||
| _impl_._cached_size_.Set(size); | |||
| } | |||
| void SendMsg::clear_message() | |||
| { | |||
| // @@protoc_insertion_point(one_of_clear_start:protobuf.SendMsg) | |||
| switch (message_case()) | |||
| { | |||
| case kTextMessage: | |||
| { | |||
| _impl_.message_.text_message_.Destroy(); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _impl_.message_.binary_message_.Destroy(); | |||
| break; | |||
| } | |||
| case MESSAGE_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| _impl_._oneof_case_[0] = MESSAGE_NOT_SET; | |||
| } | |||
| void SendMsg::Clear() | |||
| { | |||
| // @@protoc_insertion_point(message_clear_start:protobuf.SendMsg) | |||
| @@ -1317,8 +1354,8 @@ namespace protobuf | |||
| // Prevent compiler warnings about cached_has_bits being unused | |||
| (void)cached_has_bits; | |||
| _impl_.message_.ClearToEmpty(); | |||
| ::memset(&_impl_.player_id_, 0, static_cast<size_t>(reinterpret_cast<char*>(&_impl_.to_player_id_) - reinterpret_cast<char*>(&_impl_.player_id_)) + sizeof(_impl_.to_player_id_)); | |||
| clear_message(); | |||
| _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); | |||
| } | |||
| @@ -1353,14 +1390,25 @@ namespace protobuf | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| // string message = 3; | |||
| // string text_message = 3; | |||
| case 3: | |||
| if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 26)) | |||
| { | |||
| auto str = _internal_mutable_message(); | |||
| auto str = _internal_mutable_text_message(); | |||
| ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); | |||
| CHK_(ptr); | |||
| CHK_(::_pbi::VerifyUTF8(str, "protobuf.SendMsg.text_message")); | |||
| } | |||
| else | |||
| goto handle_unusual; | |||
| continue; | |||
| // bytes binary_message = 4; | |||
| case 4: | |||
| if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 34)) | |||
| { | |||
| auto str = _internal_mutable_binary_message(); | |||
| ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); | |||
| CHK_(ptr); | |||
| CHK_(::_pbi::VerifyUTF8(str, "protobuf.SendMsg.message")); | |||
| } | |||
| else | |||
| goto handle_unusual; | |||
| @@ -1413,14 +1461,22 @@ namespace protobuf | |||
| target = ::_pbi::WireFormatLite::WriteInt64ToArray(2, this->_internal_to_player_id(), target); | |||
| } | |||
| // string message = 3; | |||
| if (!this->_internal_message().empty()) | |||
| // string text_message = 3; | |||
| if (_internal_has_text_message()) | |||
| { | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( | |||
| this->_internal_message().data(), static_cast<int>(this->_internal_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.SendMsg.message" | |||
| this->_internal_text_message().data(), static_cast<int>(this->_internal_text_message().length()), ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, "protobuf.SendMsg.text_message" | |||
| ); | |||
| target = stream->WriteStringMaybeAliased( | |||
| 3, this->_internal_message(), target | |||
| 3, this->_internal_text_message(), target | |||
| ); | |||
| } | |||
| // bytes binary_message = 4; | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| target = stream->WriteBytesMaybeAliased( | |||
| 4, this->_internal_binary_message(), target | |||
| ); | |||
| } | |||
| @@ -1443,15 +1499,6 @@ namespace protobuf | |||
| // Prevent compiler warnings about cached_has_bits being unused | |||
| (void)cached_has_bits; | |||
| // string message = 3; | |||
| if (!this->_internal_message().empty()) | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( | |||
| this->_internal_message() | |||
| ); | |||
| } | |||
| // int64 player_id = 1; | |||
| if (this->_internal_player_id() != 0) | |||
| { | |||
| @@ -1464,6 +1511,31 @@ namespace protobuf | |||
| total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_to_player_id()); | |||
| } | |||
| switch (message_case()) | |||
| { | |||
| // string text_message = 3; | |||
| case kTextMessage: | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( | |||
| this->_internal_text_message() | |||
| ); | |||
| break; | |||
| } | |||
| // bytes binary_message = 4; | |||
| case kBinaryMessage: | |||
| { | |||
| total_size += 1 + | |||
| ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( | |||
| this->_internal_binary_message() | |||
| ); | |||
| break; | |||
| } | |||
| case MESSAGE_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); | |||
| } | |||
| @@ -1484,10 +1556,6 @@ namespace protobuf | |||
| uint32_t cached_has_bits = 0; | |||
| (void)cached_has_bits; | |||
| if (!from._internal_message().empty()) | |||
| { | |||
| _this->_internal_set_message(from._internal_message()); | |||
| } | |||
| if (from._internal_player_id() != 0) | |||
| { | |||
| _this->_internal_set_player_id(from._internal_player_id()); | |||
| @@ -1496,6 +1564,23 @@ namespace protobuf | |||
| { | |||
| _this->_internal_set_to_player_id(from._internal_to_player_id()); | |||
| } | |||
| switch (from.message_case()) | |||
| { | |||
| case kTextMessage: | |||
| { | |||
| _this->_internal_set_text_message(from._internal_text_message()); | |||
| break; | |||
| } | |||
| case kBinaryMessage: | |||
| { | |||
| _this->_internal_set_binary_message(from._internal_binary_message()); | |||
| break; | |||
| } | |||
| case MESSAGE_NOT_SET: | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); | |||
| } | |||
| @@ -1516,17 +1601,14 @@ namespace protobuf | |||
| void SendMsg::InternalSwap(SendMsg* other) | |||
| { | |||
| using std::swap; | |||
| auto* lhs_arena = GetArenaForAllocation(); | |||
| auto* rhs_arena = other->GetArenaForAllocation(); | |||
| _internal_metadata_.InternalSwap(&other->_internal_metadata_); | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( | |||
| &_impl_.message_, lhs_arena, &other->_impl_.message_, rhs_arena | |||
| ); | |||
| ::PROTOBUF_NAMESPACE_ID::internal::memswap< | |||
| PROTOBUF_FIELD_OFFSET(SendMsg, _impl_.to_player_id_) + sizeof(SendMsg::_impl_.to_player_id_) - PROTOBUF_FIELD_OFFSET(SendMsg, _impl_.player_id_)>( | |||
| reinterpret_cast<char*>(&_impl_.player_id_), | |||
| reinterpret_cast<char*>(&other->_impl_.player_id_) | |||
| ); | |||
| swap(_impl_.message_, other->_impl_.message_); | |||
| swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]); | |||
| } | |||
| ::PROTOBUF_NAMESPACE_ID::Metadata SendMsg::GetMetadata() const | |||
| @@ -839,6 +839,13 @@ namespace protobuf | |||
| { | |||
| return *internal_default_instance(); | |||
| } | |||
| enum MessageCase | |||
| { | |||
| kTextMessage = 3, | |||
| kBinaryMessage = 4, | |||
| MESSAGE_NOT_SET = 0, | |||
| }; | |||
| static inline const SendMsg* internal_default_instance() | |||
| { | |||
| return reinterpret_cast<const SendMsg*>( | |||
| @@ -938,25 +945,11 @@ namespace protobuf | |||
| enum : int | |||
| { | |||
| kMessageFieldNumber = 3, | |||
| kPlayerIdFieldNumber = 1, | |||
| kToPlayerIdFieldNumber = 2, | |||
| kTextMessageFieldNumber = 3, | |||
| kBinaryMessageFieldNumber = 4, | |||
| }; | |||
| // string message = 3; | |||
| void clear_message(); | |||
| const std::string& message() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_message(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_message(); | |||
| PROTOBUF_NODISCARD std::string* release_message(); | |||
| void set_allocated_message(std::string* message); | |||
| private: | |||
| const std::string& _internal_message() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_message(const std::string& value); | |||
| std::string* _internal_mutable_message(); | |||
| public: | |||
| // int64 player_id = 1; | |||
| void clear_player_id(); | |||
| int64_t player_id() const; | |||
| @@ -977,10 +970,59 @@ namespace protobuf | |||
| void _internal_set_to_player_id(int64_t value); | |||
| public: | |||
| // string text_message = 3; | |||
| bool has_text_message() const; | |||
| private: | |||
| bool _internal_has_text_message() const; | |||
| public: | |||
| void clear_text_message(); | |||
| const std::string& text_message() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_text_message(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_text_message(); | |||
| PROTOBUF_NODISCARD std::string* release_text_message(); | |||
| void set_allocated_text_message(std::string* text_message); | |||
| private: | |||
| const std::string& _internal_text_message() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_text_message(const std::string& value); | |||
| std::string* _internal_mutable_text_message(); | |||
| public: | |||
| // bytes binary_message = 4; | |||
| bool has_binary_message() const; | |||
| private: | |||
| bool _internal_has_binary_message() const; | |||
| public: | |||
| void clear_binary_message(); | |||
| const std::string& binary_message() const; | |||
| template<typename ArgT0 = const std::string&, typename... ArgT> | |||
| void set_binary_message(ArgT0&& arg0, ArgT... args); | |||
| std::string* mutable_binary_message(); | |||
| PROTOBUF_NODISCARD std::string* release_binary_message(); | |||
| void set_allocated_binary_message(std::string* binary_message); | |||
| private: | |||
| const std::string& _internal_binary_message() const; | |||
| inline PROTOBUF_ALWAYS_INLINE void _internal_set_binary_message(const std::string& value); | |||
| std::string* _internal_mutable_binary_message(); | |||
| public: | |||
| void clear_message(); | |||
| MessageCase message_case() const; | |||
| // @@protoc_insertion_point(class_scope:protobuf.SendMsg) | |||
| private: | |||
| class _Internal; | |||
| void set_has_text_message(); | |||
| void set_has_binary_message(); | |||
| inline bool has_message() const; | |||
| inline void clear_has_message(); | |||
| template<typename T> | |||
| friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; | |||
| @@ -988,10 +1030,20 @@ namespace protobuf | |||
| typedef void DestructorSkippable_; | |||
| struct Impl_ | |||
| { | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr message_; | |||
| int64_t player_id_; | |||
| int64_t to_player_id_; | |||
| union MessageUnion | |||
| { | |||
| constexpr MessageUnion() : | |||
| _constinit_{} | |||
| { | |||
| } | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_; | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_message_; | |||
| ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr binary_message_; | |||
| } message_; | |||
| mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; | |||
| uint32_t _oneof_case_[1]; | |||
| }; | |||
| union | |||
| { | |||
| @@ -2151,63 +2203,216 @@ namespace protobuf | |||
| // @@protoc_insertion_point(field_set:protobuf.SendMsg.to_player_id) | |||
| } | |||
| // string message = 3; | |||
| inline void SendMsg::clear_message() | |||
| // string text_message = 3; | |||
| inline bool SendMsg::_internal_has_text_message() const | |||
| { | |||
| return message_case() == kTextMessage; | |||
| } | |||
| inline bool SendMsg::has_text_message() const | |||
| { | |||
| return _internal_has_text_message(); | |||
| } | |||
| inline void SendMsg::set_has_text_message() | |||
| { | |||
| _impl_._oneof_case_[0] = kTextMessage; | |||
| } | |||
| inline void SendMsg::clear_text_message() | |||
| { | |||
| _impl_.message_.ClearToEmpty(); | |||
| if (_internal_has_text_message()) | |||
| { | |||
| _impl_.message_.text_message_.Destroy(); | |||
| clear_has_message(); | |||
| } | |||
| } | |||
| inline const std::string& SendMsg::message() const | |||
| inline const std::string& SendMsg::text_message() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.SendMsg.message) | |||
| return _internal_message(); | |||
| // @@protoc_insertion_point(field_get:protobuf.SendMsg.text_message) | |||
| return _internal_text_message(); | |||
| } | |||
| template<typename ArgT0, typename... ArgT> | |||
| inline PROTOBUF_ALWAYS_INLINE void SendMsg::set_message(ArgT0&& arg0, ArgT... args) | |||
| inline void SendMsg::set_text_message(ArgT0&& arg0, ArgT... args) | |||
| { | |||
| _impl_.message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.SendMsg.message) | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_text_message(); | |||
| _impl_.message_.text_message_.InitDefault(); | |||
| } | |||
| _impl_.message_.text_message_.Set(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.SendMsg.text_message) | |||
| } | |||
| inline std::string* SendMsg::mutable_message() | |||
| inline std::string* SendMsg::mutable_text_message() | |||
| { | |||
| std::string* _s = _internal_mutable_message(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.SendMsg.message) | |||
| std::string* _s = _internal_mutable_text_message(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.SendMsg.text_message) | |||
| return _s; | |||
| } | |||
| inline const std::string& SendMsg::_internal_message() const | |||
| inline const std::string& SendMsg::_internal_text_message() const | |||
| { | |||
| return _impl_.message_.Get(); | |||
| if (_internal_has_text_message()) | |||
| { | |||
| return _impl_.message_.text_message_.Get(); | |||
| } | |||
| return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(); | |||
| } | |||
| inline void SendMsg::_internal_set_message(const std::string& value) | |||
| inline void SendMsg::_internal_set_text_message(const std::string& value) | |||
| { | |||
| _impl_.message_.Set(value, GetArenaForAllocation()); | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_text_message(); | |||
| _impl_.message_.text_message_.InitDefault(); | |||
| } | |||
| _impl_.message_.text_message_.Set(value, GetArenaForAllocation()); | |||
| } | |||
| inline std::string* SendMsg::_internal_mutable_message() | |||
| inline std::string* SendMsg::_internal_mutable_text_message() | |||
| { | |||
| return _impl_.message_.Mutable(GetArenaForAllocation()); | |||
| if (!_internal_has_text_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_text_message(); | |||
| _impl_.message_.text_message_.InitDefault(); | |||
| } | |||
| return _impl_.message_.text_message_.Mutable(GetArenaForAllocation()); | |||
| } | |||
| inline std::string* SendMsg::release_message() | |||
| inline std::string* SendMsg::release_text_message() | |||
| { | |||
| // @@protoc_insertion_point(field_release:protobuf.SendMsg.message) | |||
| return _impl_.message_.Release(); | |||
| // @@protoc_insertion_point(field_release:protobuf.SendMsg.text_message) | |||
| if (_internal_has_text_message()) | |||
| { | |||
| clear_has_message(); | |||
| return _impl_.message_.text_message_.Release(); | |||
| } | |||
| else | |||
| { | |||
| return nullptr; | |||
| } | |||
| } | |||
| inline void SendMsg::set_allocated_text_message(std::string* text_message) | |||
| { | |||
| if (has_message()) | |||
| { | |||
| clear_message(); | |||
| } | |||
| if (text_message != nullptr) | |||
| { | |||
| set_has_text_message(); | |||
| _impl_.message_.text_message_.InitAllocated(text_message, GetArenaForAllocation()); | |||
| } | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.text_message) | |||
| } | |||
| // bytes binary_message = 4; | |||
| inline bool SendMsg::_internal_has_binary_message() const | |||
| { | |||
| return message_case() == kBinaryMessage; | |||
| } | |||
| inline bool SendMsg::has_binary_message() const | |||
| { | |||
| return _internal_has_binary_message(); | |||
| } | |||
| inline void SendMsg::set_has_binary_message() | |||
| { | |||
| _impl_._oneof_case_[0] = kBinaryMessage; | |||
| } | |||
| inline void SendMsg::clear_binary_message() | |||
| { | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| _impl_.message_.binary_message_.Destroy(); | |||
| clear_has_message(); | |||
| } | |||
| } | |||
| inline const std::string& SendMsg::binary_message() const | |||
| { | |||
| // @@protoc_insertion_point(field_get:protobuf.SendMsg.binary_message) | |||
| return _internal_binary_message(); | |||
| } | |||
| template<typename ArgT0, typename... ArgT> | |||
| inline void SendMsg::set_binary_message(ArgT0&& arg0, ArgT... args) | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_binary_message(); | |||
| _impl_.message_.binary_message_.InitDefault(); | |||
| } | |||
| _impl_.message_.binary_message_.SetBytes(static_cast<ArgT0&&>(arg0), args..., GetArenaForAllocation()); | |||
| // @@protoc_insertion_point(field_set:protobuf.SendMsg.binary_message) | |||
| } | |||
| inline std::string* SendMsg::mutable_binary_message() | |||
| { | |||
| std::string* _s = _internal_mutable_binary_message(); | |||
| // @@protoc_insertion_point(field_mutable:protobuf.SendMsg.binary_message) | |||
| return _s; | |||
| } | |||
| inline const std::string& SendMsg::_internal_binary_message() const | |||
| { | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| return _impl_.message_.binary_message_.Get(); | |||
| } | |||
| return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(); | |||
| } | |||
| inline void SendMsg::_internal_set_binary_message(const std::string& value) | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_binary_message(); | |||
| _impl_.message_.binary_message_.InitDefault(); | |||
| } | |||
| _impl_.message_.binary_message_.Set(value, GetArenaForAllocation()); | |||
| } | |||
| inline std::string* SendMsg::_internal_mutable_binary_message() | |||
| { | |||
| if (!_internal_has_binary_message()) | |||
| { | |||
| clear_message(); | |||
| set_has_binary_message(); | |||
| _impl_.message_.binary_message_.InitDefault(); | |||
| } | |||
| return _impl_.message_.binary_message_.Mutable(GetArenaForAllocation()); | |||
| } | |||
| inline void SendMsg::set_allocated_message(std::string* message) | |||
| inline std::string* SendMsg::release_binary_message() | |||
| { | |||
| if (message != nullptr) | |||
| // @@protoc_insertion_point(field_release:protobuf.SendMsg.binary_message) | |||
| if (_internal_has_binary_message()) | |||
| { | |||
| clear_has_message(); | |||
| return _impl_.message_.binary_message_.Release(); | |||
| } | |||
| else | |||
| { | |||
| return nullptr; | |||
| } | |||
| } | |||
| inline void SendMsg::set_allocated_binary_message(std::string* binary_message) | |||
| { | |||
| if (has_message()) | |||
| { | |||
| clear_message(); | |||
| } | |||
| _impl_.message_.SetAllocated(message, GetArenaForAllocation()); | |||
| #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| if (_impl_.message_.IsDefault()) | |||
| if (binary_message != nullptr) | |||
| { | |||
| _impl_.message_.Set("", GetArenaForAllocation()); | |||
| set_has_binary_message(); | |||
| _impl_.message_.binary_message_.InitAllocated(binary_message, GetArenaForAllocation()); | |||
| } | |||
| #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.message) | |||
| // @@protoc_insertion_point(field_set_allocated:protobuf.SendMsg.binary_message) | |||
| } | |||
| inline bool SendMsg::has_message() const | |||
| { | |||
| return message_case() != MESSAGE_NOT_SET; | |||
| } | |||
| inline void SendMsg::clear_has_message() | |||
| { | |||
| _impl_._oneof_case_[0] = MESSAGE_NOT_SET; | |||
| } | |||
| inline SendMsg::MessageCase SendMsg::message_case() const | |||
| { | |||
| return SendMsg::MessageCase(_impl_._oneof_case_[0]); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // AttackMsg | |||
| @@ -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: | |||
| @@ -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: | |||
| @@ -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)) | |||
| @@ -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: | |||
| @@ -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 | |||
| @@ -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] | |||
| @@ -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 | |||
| @@ -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") | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -2,7 +2,7 @@ import proto.MessageType_pb2 as MessageType | |||
| import proto.Message2Server_pb2 as Message2Server | |||
| import proto.Message2Clients_pb2 as Message2Clients | |||
| import PyAPI.structures as THUAI6 | |||
| from typing import Final, List | |||
| from typing import Final, List, Union | |||
| numOfGridPerCell: Final[int] = 1000 | |||
| @@ -177,12 +177,10 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.trickDesire = trickerMsg.trick_desire | |||
| tricker.classVolume = trickerMsg.class_volume | |||
| tricker.bulletType = Proto2THUAI6.bulletTypeDict[trickerMsg.bullet_type] | |||
| tricker.timeUntilSkillAvailable.clear() | |||
| for time in trickerMsg.time_until_skill_available: | |||
| tricker.timeUntilSkillAvailable.append(time) | |||
| tricker.place = Proto2THUAI6.placeTypeDict[trickerMsg.place] | |||
| tricker.playerState = Proto2THUAI6.playerStateDict[trickerMsg.player_state] | |||
| tricker.prop.clear() | |||
| for item in trickerMsg.prop: | |||
| tricker.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| tricker.trickerType = Proto2THUAI6.trickerTypeDict[trickerMsg.tricker_type] | |||
| @@ -190,7 +188,6 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.playerID = trickerMsg.player_id | |||
| tricker.viewRange = trickerMsg.view_range | |||
| tricker.radius = trickerMsg.radius | |||
| tricker.buff.clear() | |||
| for buff in trickerMsg.buff: | |||
| tricker.buff.append(Proto2THUAI6.trickerBuffTypeDict[buff]) | |||
| tricker.playerType = THUAI6.PlayerType.TrickerPlayer | |||
| @@ -212,11 +209,9 @@ class Proto2THUAI6(NoInstance): | |||
| student.encourageProgress = studentMsg.treat_progress | |||
| student.rouseProgress = studentMsg.rescue_progress | |||
| student.dangerAlert = studentMsg.danger_alert | |||
| student.timeUntilSkillAvailable.clear() | |||
| for time in studentMsg.time_until_skill_available: | |||
| student.timeUntilSkillAvailable.append(time) | |||
| student.place = Proto2THUAI6.placeTypeDict[studentMsg.place] | |||
| student.prop.clear() | |||
| for item in studentMsg.prop: | |||
| student.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| student.studentType = Proto2THUAI6.studentTypeDict[studentMsg.student_type] | |||
| @@ -225,7 +220,6 @@ class Proto2THUAI6(NoInstance): | |||
| student.playerID = studentMsg.player_id | |||
| student.viewRange = studentMsg.view_range | |||
| student.radius = studentMsg.radius | |||
| student.buff.clear() | |||
| for buff in studentMsg.buff: | |||
| student.buff.append(Proto2THUAI6.studentBuffTypeDict[buff]) | |||
| student.playerType = THUAI6.PlayerType.StudentPlayer | |||
| @@ -356,8 +350,11 @@ class THUAI62Proto(NoInstance): | |||
| return Message2Server.PropMsg(player_id=id, prop_type=THUAI62Proto.propTypeDict[prop]) | |||
| @ staticmethod | |||
| def THUAI62ProtobufSend(msg: str, toID: int, id: int) -> Message2Server.SendMsg: | |||
| return Message2Server.SendMsg(player_id=id, to_player_id=toID, message=msg) | |||
| def THUAI62ProtobufSend(msg: Union[str, bytes], toID: int, id: int) -> Message2Server.SendMsg: | |||
| if isinstance(msg, str): | |||
| return Message2Server.SendMsg(player_id=id, to_player_id=toID, text_message=msg) | |||
| elif isinstance(msg, bytes): | |||
| return Message2Server.SendMsg(player_id=id, to_player_id=toID, binary_message=msg) | |||
| @ staticmethod | |||
| def THUAI62ProtobufAttack(angle: float, id: int) -> Message2Server.AttackMsg: | |||
| @@ -1,2 +1,3 @@ | |||
| grpcio==1.52.0 | |||
| grpcio-tools==1.52.0 | |||
| numpy | |||
| @@ -1,7 +1,7 @@ | |||
| #!/usr/bin/env bash | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3 -d & | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -o& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4& | |||
| @@ -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" ] | |||
| @@ -0,0 +1,457 @@ | |||
| # Algorithm | |||
| --- | |||
| 天梯分数计算算法 | |||
| 原始记录在:<https://github.com/eesast/THUAI5/discussions/86> | |||
| 内容如下: | |||
| ## THUAI4 | |||
| 关于根据队式每场比赛的分数映射到天梯分数的问题: | |||
| 队式比赛为两队对战,每队得分的区间均为 [0, 2500]。 | |||
| 以 tanh 函数为基础进行设计。 | |||
| 设计原则如下: | |||
| 1. 输的扣少量天梯分,赢的得大量天梯分 | |||
| 2. 本就有极高天梯分数的虐本就天梯分数低的,这种降维打击现象,天梯分数涨幅极小甚至不涨天梯分 | |||
| 3. 如果在某场比赛中,两者表现差不多,即赢的比输的得分高得不多的话,那么天梯分数涨幅也不是很高 | |||
| 4. 如果本来天梯分数很低的,赢了天梯分数很高的,那么他得到的天梯分会较高,而另一个人,天梯分数降分稍多一些 | |||
| 5. 如果天梯分数低的赢了天梯分数高的,但是这场比赛赢得不多的话,会把两人的分数向中间靠拢 | |||
| 6. 总体上,赢的队伍不会降天梯分;输的队伍不会加天梯分 | |||
| 7. 其他条件相同的情况下,在本场游戏中得分越多,加的天梯分数也越高 | |||
| 上述原则可以保证以下两个目的的达成: | |||
| 1. 总体来看,进行的游戏场次越多,所有队伍的平均天梯分数就越高 | |||
| 2. 经过足够多次的游戏场次,实力有一定差距的队伍的天体分数差距逐渐拉开,实力相近的队伍的天梯分数不会差别过大,各支队伍的排名趋近于收敛 | |||
| 用 cpp 代码编写算法代码如下(`cal` 函数): | |||
| ```cpp | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 80.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Tanh delta: " << tanh(delta) << endl; | |||
| { | |||
| const double firstnerGet = 8e-5; // 胜利者天梯得分权值 | |||
| const double secondrGet = 5e-6; // 失败者天梯得分权值 | |||
| double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分 | |||
| resScore.second = orgScore.second - round((2500.0 - competitionScoreLf.second) * (2500.0 - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分,2500 为得分的最大值(THUAI4 每场得分介于 0~2500 之间) | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| ``` | |||
| **特别注意**:此算法是在 THUAI4 的比赛直接得分封顶为 2500 分、最低不低于 0 分的前提下设计的,因此并不一定适用于 THUAI5 的情形。 | |||
| ## THUAI5 | |||
| 今年把得分上限这个东西去掉了。理论上今年可以得很高很高分,但是我估计大部分比赛得分在400-600左右,最高估计1000左右。算法 借 鉴 了THUAI4,算法,换了个激活函数(正态CDF),感觉分数变化相对更好了一些? | |||
| 代码如下: | |||
| ```cpp | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| double PHI(double x) // THUAI3: Sigmoid; THUAI4: Tanh; THUAI5: Normal Distribution CDF | |||
| { | |||
| //double a1 = 0.2548292592; | |||
| //double a2 = -0.284496736; | |||
| //double a3 = 1.421413741; | |||
| //double a4 = -1.453152027; | |||
| //double a5 = 1.061405429; | |||
| //double p = 0.3275911; | |||
| //int sign = 1; | |||
| //if (x < 0) | |||
| // sign = -1; | |||
| //x = fabs(x) / sqrt(2.0); | |||
| //double t = 1.0 / (1.0 + p * x); | |||
| //double y = 1.0 - ((((((a5 * t + a4) * t + a3) * t) + a2) * t) + a1) * t * exp(-x * x); | |||
| //double cdf = 0.5 * (1.0 + sign * y); | |||
| //return (cdf - 0.5) * 2.0; // 化到[-1,1]之间 | |||
| return erf(x / sqrt(2)); | |||
| } | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 90.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Normal CDF delta: " << PHI(delta) << endl; | |||
| { | |||
| const double firstnerGet = 3e-4; // 胜利者天梯得分权值 | |||
| const double secondrGet = 1e-4; // 失败者天梯得分权值 | |||
| double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (PHI((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - PHI(delta)) * correct); // 胜者所加天梯分 | |||
| if (competitionScoreLf.second < 1000) | |||
| resScore.second = orgScore.second - round((1000.0 - competitionScoreLf.second) * (1000.0 - competitionScoreLf.second) * secondrGet * (1 - PHI(delta)) * correct); // 败者所扣天梯分 | |||
| else | |||
| resScore.second = orgScore.second; // 败者拿1000分,已经很强了,不扣分 | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| void Print(mypair<int> score) | |||
| { | |||
| std::cout << " team1: " << score.first << std::endl | |||
| << "team2: " << score.second << std::endl; | |||
| } | |||
| int main() | |||
| { | |||
| int x, y; | |||
| std::cout << "origin score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto ori = mypair<int>(x, y); | |||
| std::cout << "game score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto sco = mypair<int>(x, y); | |||
| Print(cal(ori, sco)); | |||
| } | |||
| ``` | |||
| `1000 - score`(x | |||
| `ReLU(1000 - score)`(√ | |||
| 防止真的超过了 1000) | |||
| ## THUAI6 | |||
| ### high-ladder | |||
| 因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 | |||
| ```c++ | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 1000.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Tanh delta: " << tanh(delta) << endl; | |||
| { | |||
| const double firstnerGet = 9e-6; // 胜利者天梯得分权值 | |||
| const double secondrGet = 5e-6; // 失败者天梯得分权值 | |||
| double deltaScore = 2100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / (deltaWeight * 1.2); // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| cout << "correct: " << correct << endl; | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分 | |||
| resScore.second = orgScore.second - round((competitionScoreLf.first - competitionScoreLf.second) * (competitionScoreLf.first - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分 | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| ``` | |||
| ### competition | |||
| 与天梯得分算法要满足的“枫氏七条”类似,比赛得分算法也要满足“唐氏四律”,分别如下: | |||
| 1. 两队经过某场比赛的得分变化,应只与该场比赛有关,而与历史积分无关。 | |||
| 2. 须赋予比赛获胜一方基础得分,哪怕获胜一方的优势非常小。也就是说,哪怕胜利一方仅以微弱优势获胜,也需要拉开胜者与败者的分差。 | |||
| 3. 胜利一方优势越大,得分理应越高。 | |||
| 4. 对于一场比赛,胜利一方的得分不能无限大,须控制在一个合理的数值以下。 | |||
| - 在非平局的情况下,(胜者)天梯得分与双方比赛分差值成正相关,得分函数如下(以x表示得分差值,y表示(胜者)天梯得分,a、b为固定参数) | |||
| $$y=ax^2(1-0.375\cdot(\tanh(\frac{x}{b}-1)+1))$$ | |||
| - 在平局情况下,(双方)天梯得分与比赛分成正相关,得分函数如下(以x表示比赛分,y表示(双方)天梯得分,c为固定参数) | |||
| $$y=cx^2$$ | |||
| - 不管是哪种情况,都有得分下界,非平局为100,平局为25 | |||
| ```c++ | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| #include <cassert> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| double minScore = 100; | |||
| double TieScore(double gameScore) | |||
| { | |||
| const double get = 9e-5; // 天梯得分权值 | |||
| double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore | |||
| double highScore = 6000.0; // 将highScore设定为较大值,使得correct较小 | |||
| double correct = 1 - 0.375 * (tanh((highScore - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 | |||
| cout << "correct: " << correct << endl; | |||
| int score = round(gameScore * gameScore * get * correct / 4); | |||
| return score > minScore / 4 ? score : minScore / 4; | |||
| } | |||
| double WinScore(double delta, double winnerGameScore) // 根据游戏得分差值,与绝对分数,决定最后的加分 | |||
| { | |||
| assert(delta > 0); | |||
| const double firstnerGet = 9e-5; // 胜利者天梯得分权值 | |||
| double deltaScore = 2000.0; // 订正的幅度,该值越小,则在双方分差较大时,天梯分数改变量越小,整个函数的变化范围大约为0~2*deltaScore | |||
| double correct = 1 - 0.375 * (tanh((delta - deltaScore) / deltaScore) + 1.0); // 一场比赛中,在双方分差较大时,减小天梯分数的改变量 | |||
| cout << "correct: " << correct << endl; | |||
| int score = round(delta * delta * firstnerGet * correct); | |||
| return score > minScore ? score : minScore; | |||
| } | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<double> cal(mypair<double> orgScore, mypair<double> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| double delta = competitionScore.first - competitionScore.second; | |||
| double addScore; | |||
| mypair<double> resScore; | |||
| // 先处理平局的情况 | |||
| if (delta == 0) | |||
| { | |||
| addScore = TieScore(competitionScore.first); | |||
| resScore = mypair<double>(orgScore.first + addScore, orgScore.second + addScore); | |||
| } | |||
| // 再处理有胜负的情况 | |||
| else | |||
| { | |||
| addScore = WinScore(delta, competitionScore.first); | |||
| resScore = mypair<double>(orgScore.first + addScore, orgScore.second); | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| void Print(mypair<double> score) | |||
| { | |||
| std::cout << "team1: " << score.first << std::endl | |||
| << "team2: " << score.second << std::endl; | |||
| } | |||
| int main() | |||
| { | |||
| double x, y, t, i = 0; | |||
| cin >> t; | |||
| while (i < t) | |||
| { | |||
| cout << "----------------------------------------\n"; | |||
| std::cout << "origin score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto ori = mypair<double>(x, y); | |||
| std::cout << "game score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto sco = mypair<double>(x, y); | |||
| Print(cal(ori, sco)); | |||
| ++i; | |||
| } | |||
| return 0; | |||
| } | |||
| ``` | |||
| @@ -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; | |||
| } | |||
| @@ -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 // 相当于攻击 | |||
| @@ -14,11 +14,11 @@ | |||
| </ItemGroup>--> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.53.0"> | |||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | |||
| <PrivateAssets>all</PrivateAssets> | |||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
| </PackageReference> | |||
| @@ -2,3 +2,12 @@ | |||
| 本目录用于存放程序所需的shell脚本 | |||
| ## run.sh | |||
| 注意: | |||
| 1. server 和 client 程序要在后台进行 | |||
| 2. 忙等待到 server 结束 | |||
| 3. 结束后生成标志结束的文件 | |||
| @@ -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. | |||
| @@ -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}'}' | |||
| @@ -13,6 +13,9 @@ | |||
| #### 人物 | |||
| - `std::future<bool> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 攻击 | |||
| - `std::future<bool> Attack(double angleInRadian)`:`angleInRadian`为攻击方向 | |||
| @@ -40,7 +43,7 @@ | |||
| ### 信息获取 | |||
| #### 队内信息 | |||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息,队友在下一帧收到。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | |||
| - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | |||
| @@ -49,19 +52,20 @@ | |||
| - `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const` :返回所有可视捣蛋鬼的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const` :返回所有可视道具的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const` :返回所有可视子弹(攻击)的信息。 | |||
| - `bool HaveView(int gridX, int gridY) const`:判断坐标是否可见 | |||
| #### 查询特定位置物体的信息 | |||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | |||
| - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | |||
| - `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门也返回false | |||
| - 以下指令特定位置没有对应物品返回-1 | |||
| - 以下指令,若查询物品当前在视野内,则返回最新进度/状态;若物品当前不在视野内、但曾经出现在视野内,则返回最后一次看到时的进度/状态;若物品从未出现在视野内,或查询位置没有对应的物品,则返回 -1。 | |||
| - `int32_t GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | |||
| - `int32_t GetGateProgress(int32_t cellX, int32_t cellY) const`:查询特定位置校门开启进度 | |||
| - `int32_t GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | |||
| - `int32_t GetDoorProgress(int32_t cellX, int32_t cellY) const`:查询特定位置门开启状态 | |||
| - `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态,没有隐藏校门返回THUAI6::HiddenGateState::Null | |||
| - `bool IsDoorOpen(int32_t cellX, int32_t cellY) const`:查询特定位置门是否开启,没有门/不在视野内也返回false | |||
| - `THUAI6::HiddenGateState GetHiddenGateState(int32_t cellX, int32_t cellY) const`::查询特定位置隐藏校门状态,没有隐藏校门/不在视野内返回THUAI6::HiddenGateState::Null | |||
| #### 其他 | |||
| - `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态 | |||
| @@ -71,9 +75,9 @@ | |||
| - `std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const`:返回整张地图的地形信息。可以写成类似`api.GetFullMap()[x][y]`,其中x为地图自上到下第几行,y为自左向右第几列,注意从0开始 | |||
| ### 辅助函数 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标grid。 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数cell。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| ~~~c++ | |||
| @@ -84,6 +88,14 @@ | |||
| void PrintSelfInfo() const; | |||
| ~~~ | |||
| ### 部分属性解释 stuctures.h | |||
| ~~~c++ | |||
| struct Player | |||
| { | |||
| std::vector<PropType> props;//大小固定为3,空的位置为NullPropType | |||
| } | |||
| ~~~ | |||
| ## 接口一览 | |||
| ~~~c++ | |||
| // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 | |||
| @@ -123,7 +135,7 @@ | |||
| // 获取视野内可见的道具信息 | |||
| [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0; | |||
| // 获取地图信息,视野外的地图统一为Land | |||
| // 获取地图信息 | |||
| [[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0; | |||
| [[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0; | |||
| @@ -155,6 +167,8 @@ | |||
| { | |||
| return grid / numOfGridPerCell; | |||
| } | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0; | |||
| // 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| @@ -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模式下有效 | |||
| @@ -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的小炸弹有碰撞体积 | |||
| - 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 | |||
| ### 职业 | |||
| - 学生职业可以重复选择 | |||
| @@ -18,18 +18,15 @@ Q: 怎么修改.cmd参数? | |||
| A: | |||
| 见选手包中的使用文档部分 | |||
| Q:卡死在第一帧不动 | |||
| Q: 怎么开始游戏? | |||
| A: | |||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||
| A:大概率是你的代码死循环了 | |||
| Q: | |||
|  | |||
| 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: | |||
|  | |||
| 情况2: | |||
| @@ -52,11 +49,24 @@ A: | |||
|  | |||
| 确保上图项目属性中平台工具集在 v143,C++17 标准 | |||
| Q:CAPI编译不通过 | |||
| Q:CAPI编译不通过(第二种) | |||
|  | |||
| A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||
| Q:编译好慢啊 | |||
| A: | |||
| 1. 尽量不要改其他文件,甚至连点下保存都别点 | |||
| 2. 不要点重新生成,要点生成 | |||
| 3. 开启下图选项 | |||
|  | |||
| Q:这是什么错误啊 | |||
|  | |||
| A:调用了容量为0的vector | |||
| ## Python | |||
| ### grpc版本更新失败 | |||
| @@ -68,12 +78,29 @@ A: | |||
| - 可能措施1. | |||
| 首先保证Python版本在3.9及以上 | |||
| - 可能措施2. 更换为国内镜像源 | |||
| 在终端输入 | |||
| `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||
| 在终端输入 `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||
| - 可能措施3. 更新pip | |||
| `python -m pip install --upgrade pip` (pip 版本最好为23.1) | |||
| ## 游戏引擎/机制 | |||
| Q:咱们这边play函数调用机制究竟是怎么样的?如果50ms内没有执行完当前程序,是在下一帧不会重新调用play吗 | |||
| 还是只有move这样的明确有时间为参量的才会有上面那个机制 | |||
| A: | |||
| - 调用任何主动指令都不会占用你play函数多少时间,你可以把它想成一个信号,告诉游戏引擎你想做什么 | |||
| - 50ms内没有执行完当前程序,是指你的play函数例如求最短路之类的操作会占用的时间 | |||
| - 准确地说,50ms内没有执行完当前程序,在下一帧一般会重新调用play | |||
| - 比如说,你第一次调用花了70ms | |||
| 那么下一次调用会在这次之后立刻开始 | |||
| 如果你三次都70ms,就会4帧3次play了 | |||
| - 当然第一次调用花了110ms,第二帧自然不调用了 | |||
| ## 比赛相关 | |||
| Q:职业数值会修改吗? | |||
| A:初赛结束会调数值及机制,增加新角色 | |||
| A:初赛结束会调数值及机制,增加新角色 | |||
| Q:初赛后会修改什么呢? | |||
| A:可能的修改:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM;HaveView()修改 等 | |||
| @@ -2,9 +2,9 @@ | |||
| [toc] | |||
| ## Visual Studio使用说明 | |||
| ## Visual Studio 使用说明 | |||
| 比赛**只保证!!!支持**VS2022最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||
| 比赛**只保证!!!支持** VS 2022 最新版本,选手使用其他版本后果自负(实际上应该不能编译)。 | |||
| ### 生成模式的设置 | |||
| @@ -12,7 +12,7 @@ | |||
|  | |||
| 可以更改生成模式为`Debug`或`Release` | |||
| 可以更改生成模式为 `Debug` 或 `Release` | |||
| ### 命令行参数的设置 | |||
| @@ -22,19 +22,19 @@ | |||
| 在命令参数一栏中加入命令行参数进行调试 | |||
| ### cmd脚本的参数修改 | |||
| ### cmd 脚本的参数修改 | |||
| 右键点击`.cmd`或`.bat`文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上`::`,可以注释掉该行。 | |||
| 右键点击 `.cmd` 或 `.bat` 文件之后,选择编辑就可以开始修改文件。通过在一行的开头加上 `::`,可以注释掉该行。 | |||
| ## C++接口必看 | |||
| ## C++ 接口必看 | |||
| **在此鸣谢\xfgg/\xfgg/\xfgg/,看到这里的选手可以到选手群膜一膜!!! ** | |||
| 除非特殊指明,以下代码均在 MSVC 19.28.29913 /std:c++17 与 g++ 10.2 for linux -std=c++17 两个平台下通过。 | |||
| 除非特殊指明,以下代码均在 MSVC 19.28.29913 x64 `/std:c++17` 与 GCC 10.2 x86_64-linux-gnu `-std=c++17` 两个平台下通过。 | |||
| 由于我们的比赛最终会运行在Linux平台上,因此程设课上学到的一些只适用于Windows的C++操作很可能并不能正确执行。此外,代码中使用了大量Modern C++中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用C++接口必须了解的知识。 | |||
| 由于我们的比赛最终会运行在 Linux 平台上,因此程设课上学到的一些只适用于 Windows 的 C++ 操作很可能并不能正确执行。此外,代码中使用了大量 Modern C++ 中的新特性,可能会使选手在编程过程中遇到较大困难。因此,此处介绍一些比赛中使用 C++ 接口必须了解的知识。 | |||
| @@ -42,7 +42,7 @@ | |||
| 编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!! 应当使用 `std::chrono` | |||
| 编写代码过程中,我们可能需要获取系统时间等一系列操作,C++ 标准库提供了这样的行为。尤其注意**不要**使用 Windows 平台上的 `GetTickCount` 或者 `GetTickCount64` !!!应当使用 `std::chrono` | |||
| 头文件:`#include <chrono>` | |||
| @@ -65,9 +65,10 @@ int main() | |||
| ### 线程睡眠 | |||
| 由于移动过程中会阻塞人物角色,因此玩家可能要在移动后让线程休眠一段时间,直到移动结束。C++ 标准库中使线程休眠需要包含头文件:`#include <thread>`。示例用法: | |||
| 我们推荐小步移动,不太建议玩家使用线程睡眠超过一帧 | |||
| ```cpp | |||
| std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 休眠 20 毫秒 | |||
| std::this_thread::sleep_for(std::chrono::seconds(2)); // 休眠 2 秒 | |||
| @@ -126,7 +127,7 @@ int main() | |||
| ### `auto`类型推导 | |||
| ### `auto` 类型推导 | |||
| @@ -146,7 +147,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 | |||
| #### std::vector | |||
| #### `std::vector` | |||
| 头文件:`#include <vector>`,类似于可变长的数组,支持下标运算符 `[]` 访问其元素,此时与 C 风格数组用法相似。支持 `size` 成员函数获取其中的元素数量。 | |||
| @@ -154,7 +155,7 @@ auto&& w = 4; // auto 被推导为 int,w 是 int&& 类型 | |||
| ```cpp | |||
| std::vector<int> v { 9, 1, 2, 3, 4 }; // 初始化 vector 有五个元素,v[0] = 9, ... | |||
| v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是10 | |||
| v.emplace_back(10); // 向 v 尾部添加一个元素,该元素饿构造函数的参数为 10(对于 int,只有一个语法意义上的构造函数,无真正的构造函数),即现在 v 有六个元素,v[5] 的值是 10 | |||
| v.pop_back(); // 把最后一个元素删除,现在 v 还是 { 9, 1, 2, 3, 4 } | |||
| ``` | |||
| @@ -244,7 +245,7 @@ for (auto itr = begin(arr); itr != end(arr); ++itr) | |||
| ### fmt库的使用 | |||
| ### fmt 库的使用 | |||
| @@ -263,11 +264,11 @@ std::string str_fmt = fmt::format("number: {}{}", 1, teststr); // 两种方法 | |||
| ## Python接口必看 | |||
| ## Python 接口必看 | |||
| 比赛**只保证!!**支持 Python 3.9,不保证支持其他版本 | |||
| 比赛中的Python接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | |||
| 比赛中的 Python 接口大多使用异步接口,即返回一个类似于 `Future[bool]` 的值。为了获取实际的值,需要调用 `result()` 方法。 | |||
| ```python | |||
| from concurrent.futures import Future, ThreadPoolExecutor | |||
| @@ -296,15 +297,15 @@ if __name__ == '__main__': | |||
| ## C++相关小知识 | |||
| ## C++ 相关小知识 | |||
| ### lambda表达式 | |||
| ### lambda 表达式 | |||
| #### lambda表达式概述 | |||
| #### lambda 表达式概述 | |||
| @@ -455,7 +456,7 @@ f(); // 输出 4,而非 9 | |||
| > []() | |||
| > { | |||
| > int y = x; // OK, constant folding | |||
| > int z = Func1(x); // Compile error! odr-used! x is not captured! | |||
| > int z = Func1(x); // Compile error! odr-used! x is not captured! | |||
| > }(); | |||
| > } | |||
| > ``` | |||
| @@ -586,7 +587,7 @@ lambda 表达式还有很多有趣之处,例如泛型 lambda、返回 lambda | |||
| ### std::thread | |||
| ### `std::thread` | |||
| 头文件:`#include <thread>`。用于开启新的线程。示例代码: | |||
| @@ -726,7 +727,7 @@ int main() | |||
| ##### 概览 | |||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的智能指针数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||
| `shared_ptr` 的用法最为灵活,内部实现方式是**引用计数**。即,它会记录有多少个 `shared_ptr` 正在指向某个资源,并当指向该资源的 `std::shared_ptr` 数为零时,调用相应的释放函数(默认为 `delete` 操作符)释放该资源。不过也需要注意,使用 `std::shared_ptr` 会比传统的指针带来额外的引用计数的开销,因此只有当后面将会介绍的 `std::unique_ptr` 无法满足要求时方可考虑 `std::shared_ptr`。 | |||
| 像 `new` 会在自由存储区动态获取一块内存并返回其一样,如果要动态分配一块内存并得到其智能指针,可以使用 `std::make_shared` 模板,例如: | |||
| @@ -758,27 +759,27 @@ void Func() | |||
| // 上述此语句执行过后,只有一个智能指针 sp1 指向这个 int,引用计数为 1 | |||
| { | |||
| auto sp2 = sp1; // 构造一个智能指针 sp2,指向 sp1 指向的内存,并将引用计数+1 | |||
| auto sp2 = sp1; // 构造一个 std::shared_ptr sp2,指向 sp1 指向的对象,并将引用计数加一 | |||
| // 故此处引用计数为2 | |||
| std::cout << *sp2 << std::endl; // 输出 110 | |||
| // 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数-1,因此此时引用计数为1 | |||
| // 此处 sp2 生存期已到,调用 sp2 的析构函数,使引用计数减一,因此此时引用计数为 1 | |||
| } | |||
| // 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再-1,故引用计数降为0 | |||
| // 也就是不再有智能指针指向它了,调用 delete 释放内存 | |||
| // 此处 sp1 生命期也已经到了,调用 sp1 析构函数,引用计数再减一,故引用计数降为 0 | |||
| // 也就是不再有 std::shared_ptr 指向它了,调用 delete 释放 | |||
| } | |||
| } | |||
| ``` | |||
| 将普通指针交给智能指针托管: | |||
| 将裸指针交给 `std::shared_ptr` 托管: | |||
| ```cpp | |||
| int* p = new int(110); | |||
| int* q = new int(110); | |||
| std::shared_ptr sp(p); // 把 p 指向的内存交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放 | |||
| std::shared_ptr sp(p); // 把 p 指向的对象交给 sp 托管,此后 p 便不需要 delete,sp 析构时会自动释放 | |||
| std::shared_ptr sq; // sq 什么也不托管 | |||
| sq.reset(q); // 让 sq 托管 q | |||
| @@ -791,22 +792,38 @@ sq.reset(q); // 让 sq 托管 q | |||
| 之前说过 ,默认情况下是释放内存的函数是 `delete` 运算符,但有时我们并不希望这样。比如下面的几个情况: | |||
| + 使用智能指针托管动态数组 | |||
| + 使用 `std::shared_ptr` 托管动态数组 | |||
| + C++11 起 | |||
| ```cpp | |||
| #include <memory> | |||
| void IntArrayDeleter(int* p) { delete[] p; } | |||
| int main() | |||
| { | |||
| std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 | |||
| sp.get()[0] = 0; // 访问第 0 个元素 | |||
| // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 | |||
| return 0; | |||
| } | |||
| // 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; }); | |||
| ``` | |||
| + C++17 起 | |||
| ```cpp | |||
| std::shared_ptr<int[]> sp(new int[10]); | |||
| sp[0] = 0; // 访问第 0 个元素 | |||
| ``` | |||
| ```cpp | |||
| #include <memory> | |||
| void IntArrayDeleter(int* p) { delete[] p; } | |||
| int main() | |||
| { | |||
| std::shared_ptr<int> sp(new int[10], IntArrayDeleter); // 让 IntArrayDeleter 作为释放资源的函数 | |||
| // sp 析构时自动调用 IntArrayDeleter 释放该 int 数组 | |||
| return 0; | |||
| } | |||
| // 或者利用 lambda 表达式:std::shared_ptr<int> sp(new int[10], [](int* p) { delete[] p; }); | |||
| ``` | |||
| + C++20 起 | |||
| ```cpp | |||
| auto sp = std::make_shared<int[]>(10); | |||
| sp[0] = 0; // 访问第 0 个元素 | |||
| ``` | |||
| @@ -816,12 +833,12 @@ sq.reset(q); // 让 sq 托管 q | |||
| ```c++ | |||
| HDC hdc; // DC: Device context,一个指向 DC 的句柄(HANDLE) | |||
| hdc = GetDC(hWnd); // 获取设备上下文 | |||
| hdc = GetDC(hWnd); // 获取设备上下文 | |||
| /*执行绘图操作*/ | |||
| ReleaseDC(hWnd, hdc); // 绘图完毕,将设备上下文资源释放,归还给 Windows 系统 | |||
| ``` | |||
| 使用智能指针对其进行托管,代码如下: | |||
| 使用 `std::shared_ptr` 对其进行托管,代码如下: | |||
| ```c++ | |||
| // 使用 lambda 表达式写法(推荐) | |||
| @@ -865,7 +882,7 @@ void Func() | |||
| } | |||
| ``` | |||
| 这是因为,只有复制构造函数里面才有使引用计数加1的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 | |||
| 这是因为,只有复制构造函数里面才有使引用计数加一的操作。即当我们写 `std::shared_ptr<int> sq = sp` 的时候,确实引用计数变成了 2,但是我们都用一个外部的裸指针 `p` 去初始化 `sp` 和 `sq`,智能指针并不能感知到它们托管的内存相同。所以 `sp` 和 `sq` 所托管的内存被看做是独立的。这样,当它们析构的时候,均会释放它们所指的内存,因此同一块内存被释放了两次,导致程序出错。所以个人还是推荐使用 `make_shared` ,而不是用裸指针去获取内存。 | |||
| 另一个著名的错误用法,请继续阅读 `std::weak_ptr`。 | |||
| @@ -954,7 +971,28 @@ else | |||
| #### `std::unique_ptr` | |||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开。 | |||
| `std::unique_ptr` 顾名思义,独有的指针,即资源只能同时为一个 `unique_ptr` 所占有,是基于 RAII 的思想设计的智能指针,并且相比于原始指针并不会带来任何额外开销,是智能指针的首选。它部分涉及到对象的生命期、右值引用与移动语义的问题,在此不做过多展开,仅提供一个例子作为参考: | |||
| ```cpp | |||
| { | |||
| auto p = std::make_unique<int>(5); // 创建一个 int 对象并初始化为 5 | |||
| std::cout << *p << std::endl; // 输出 5 | |||
| // 该 int 对象随着 p 的析构而被 delete | |||
| } | |||
| ``` | |||
| 需要注意的是,由于[标准委员会的疏忽~忘了~(partly an oversight)](https://herbsutter.com/gotw/_102/),C++14 中才引进`std::make_unique`,C++11 中无法使用。因此 C++11 若想使用则需自定义 `make_unique`: | |||
| ```cpp | |||
| namespace | |||
| { | |||
| template<typename T, typename... Args> | |||
| std::unique_ptr<T> make_unique( Args&&... args ) | |||
| { | |||
| return std::unique_ptr<T>(new T( std::forward<Args>(args)...)); | |||
| } | |||
| } | |||
| ``` | |||
| @@ -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`执行比赛代码 | |||
| @@ -10,7 +10,7 @@ namespace starter.viewmodel.common | |||
| { | |||
| public abstract class NotificationObject : INotifyPropertyChanged | |||
| { | |||
| public event PropertyChangedEventHandler PropertyChanged; | |||
| public event PropertyChangedEventHandler? PropertyChanged; | |||
| ///< summary> | |||
| /// announce notification | |||
| /// </summary> | |||
| @@ -25,21 +25,21 @@ namespace starter.viewmodel.common | |||
| /// </summary> | |||
| public class BaseCommand : ICommand | |||
| { | |||
| private Func<object, bool> _canExecute; | |||
| private Action<object> _execute; | |||
| private Func<object?, bool>? _canExecute; | |||
| private Action<object?> _execute; | |||
| public BaseCommand(Func<object, bool> canExecute, Action<object> execute) | |||
| public BaseCommand(Func<object?, bool>? canExecute, Action<object?> execute) | |||
| { | |||
| _canExecute = canExecute; | |||
| _execute = execute; | |||
| } | |||
| public BaseCommand(Action<object> execute) : | |||
| public BaseCommand(Action<object?> execute) : | |||
| this(null, execute) | |||
| { | |||
| } | |||
| public event EventHandler CanExecuteChanged | |||
| public event EventHandler? CanExecuteChanged | |||
| { | |||
| add | |||
| { | |||
| @@ -57,12 +57,12 @@ namespace starter.viewmodel.common | |||
| } | |||
| } | |||
| public bool CanExecute(object parameter) | |||
| public bool CanExecute(object? parameter) | |||
| { | |||
| return _canExecute == null ? true : _canExecute(parameter); | |||
| } | |||
| public void Execute(object parameter) | |||
| public void Execute(object? parameter) | |||
| { | |||
| if (_execute != null && CanExecute(parameter)) | |||
| { | |||
| @@ -79,15 +79,15 @@ namespace starter.viewmodel.common | |||
| { | |||
| return false; | |||
| } | |||
| string checkvalue = value.ToString(); | |||
| string targetvalue = parameter.ToString(); | |||
| string checkvalue = value.ToString() ?? ""; | |||
| string targetvalue = parameter.ToString() ?? ""; | |||
| bool r = checkvalue.Equals(targetvalue); | |||
| return r; | |||
| } | |||
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |||
| public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) | |||
| { | |||
| if (value == null || parameter == null) | |||
| if (value is null || parameter is null) | |||
| { | |||
| return null; | |||
| } | |||
| @@ -132,22 +132,22 @@ namespace starter.viewmodel.common | |||
| static bool _isUpdating = false; | |||
| private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |||
| { | |||
| PasswordBox pb = d as PasswordBox; | |||
| PasswordBox pb = (d as PasswordBox)!; | |||
| pb.PasswordChanged -= Pb_PasswordChanged; | |||
| if (!_isUpdating) | |||
| (d as PasswordBox).Password = e.NewValue.ToString(); | |||
| (d as PasswordBox)!.Password = e.NewValue.ToString(); | |||
| pb.PasswordChanged += Pb_PasswordChanged; | |||
| } | |||
| private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |||
| { | |||
| PasswordBox pb = d as PasswordBox; | |||
| PasswordBox pb = (d as PasswordBox)!; | |||
| pb.PasswordChanged += Pb_PasswordChanged; | |||
| } | |||
| private static void Pb_PasswordChanged(object sender, RoutedEventArgs e) | |||
| { | |||
| PasswordBox pb = sender as PasswordBox; | |||
| PasswordBox pb = (sender as PasswordBox)!; | |||
| _isUpdating = true; | |||
| SetPassword(pb, pb.Password); | |||
| _isUpdating = false; | |||
| @@ -6,8 +6,21 @@ | |||
| <Nullable>enable</Nullable> | |||
| <UseWPF>true</UseWPF> | |||
| <UseWindowsForms>true</UseWindowsForms> | |||
| <ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon> | |||
| <PackageIcon>eesast_software_trans.png</PackageIcon> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <Content Include="eesast_software_trans_enlarged.ico" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <None Include="..\..\resource\eesast_software_trans.png"> | |||
| <Pack>True</Pack> | |||
| <PackagePath>\</PackagePath> | |||
| </None> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="ICSharpCode.SharpZipLib.dll" Version="0.85.4.369" /> | |||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | |||
| @@ -5,7 +5,9 @@ | |||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |||
| xmlns:local="clr-namespace:Installer" xmlns:c="clr-namespace:starter.viewmodel.common" | |||
| mc:Ignorable="d" | |||
| Title="Installer" Window.SizeToContent="WidthAndHeight"> | |||
| Title="Installer" Window.SizeToContent="WidthAndHeight" | |||
| ResizeMode="CanMinimize" | |||
| > | |||
| <Window.Resources> | |||
| <c:RadioConverter x:Key="RadioConverter"/> | |||
| </Window.Resources> | |||
| @@ -21,7 +21,6 @@ using System.Net.Http; | |||
| using System.Windows; | |||
| using System.Windows.Shapes; | |||
| //using System.Windows.Forms; | |||
| using System.Threading.Tasks; | |||
| using System.Threading; | |||
| using MessageBox = System.Windows.MessageBox; | |||
| @@ -153,18 +152,22 @@ namespace starter.viewmodel.settings | |||
| } | |||
| public bool RecallUser() | |||
| { | |||
| Username = Web.ReadJson("email"); | |||
| if (Username == null || Username.Equals("")) | |||
| var username = Web.ReadJson("email"); | |||
| if (username == null || username.Equals("")) | |||
| { | |||
| Username = ""; | |||
| return false; | |||
| } | |||
| Password = Web.ReadJson("password"); | |||
| if (Password == null || Username.Equals("")) | |||
| Username = username; | |||
| var password = Web.ReadJson("password"); | |||
| if (password == null || password.Equals("")) | |||
| { | |||
| Password = ""; | |||
| return false; | |||
| } | |||
| Password = password; | |||
| return true; | |||
| } | |||
| public bool ForgetUser() | |||
| @@ -210,8 +213,6 @@ namespace starter.viewmodel.settings | |||
| switch (CodeRoute.Substring(CodeRoute.LastIndexOf('.') + 1)) | |||
| { | |||
| case "cpp": | |||
| Language = "cpp"; | |||
| break; | |||
| case "h": | |||
| Language = "cpp"; | |||
| break; | |||
| @@ -244,15 +245,12 @@ namespace starter.viewmodel.settings | |||
| } | |||
| public UsingOS ReadUsingOS() | |||
| { | |||
| string OS = Web.ReadJson("OS"); | |||
| if (OS == null) | |||
| return UsingOS.Win; | |||
| else if (OS.Equals("linux")) | |||
| return UsingOS.Linux; | |||
| else if (OS.Equals("osx")) | |||
| return UsingOS.OSX; | |||
| else | |||
| return UsingOS.Win; | |||
| return Web.ReadJson("OS") switch | |||
| { | |||
| "linux" => UsingOS.Linux, | |||
| "osx" => UsingOS.OSX, | |||
| _ => UsingOS.Win, | |||
| }; | |||
| } | |||
| /// <summary> | |||
| /// Route of files | |||
| @@ -274,7 +272,7 @@ namespace starter.viewmodel.settings | |||
| { | |||
| get; set; | |||
| } | |||
| public string Language | |||
| public string? Language | |||
| { | |||
| get; set; | |||
| } | |||
| @@ -394,7 +392,7 @@ namespace Downloader | |||
| public class Data | |||
| { | |||
| public static string path = ""; // 标记路径记录文件THUAI6.json的路径 | |||
| public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 | |||
| public static string FilePath = ""; // 最后一级为THUAI6文件夹所在目录 | |||
| public static string dataPath = ""; // C盘的文档文件夹 | |||
| public Data(string path) | |||
| { | |||
| @@ -403,7 +401,7 @@ namespace Downloader | |||
| Data.path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | |||
| if (File.Exists(Data.path)) | |||
| { | |||
| var dict = new Dictionary<string, string>(); | |||
| Dictionary<string, string>? dict; | |||
| using (StreamReader r = new StreamReader(Data.path)) | |||
| { | |||
| string json = r.ReadToEnd(); | |||
| @@ -411,7 +409,7 @@ namespace Downloader | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||
| if (dict != null && dict.ContainsKey("installpath")) | |||
| { | |||
| FilePath = dict["installpath"].Replace('\\', '/'); | |||
| @@ -425,11 +423,12 @@ namespace Downloader | |||
| } | |||
| else | |||
| { | |||
| FilePath = System.IO.Path.GetDirectoryName(@path); | |||
| FilePath = System.IO.Path.GetDirectoryName(@path) | |||
| ?? throw new Exception("Failed to get the path of the file"); | |||
| //将dat文件写入程序运行路径 | |||
| string json; | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| Dictionary<string, string>? dict; | |||
| using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | |||
| using (StreamReader r = new StreamReader(fs)) | |||
| { | |||
| @@ -438,7 +437,7 @@ namespace Downloader | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||
| dict?.Add("installpath", path); | |||
| } | |||
| using FileStream fs2 = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | |||
| @@ -451,7 +450,7 @@ namespace Downloader | |||
| public static void ResetFilepath(string newPath) | |||
| { | |||
| string json; | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| Dictionary<string, string>? dict; | |||
| FilePath = newPath.Replace('\\', '/'); | |||
| path = System.IO.Path.Combine(dataPath, "THUAI6.json"); | |||
| using FileStream fs = new FileStream(Data.path, FileMode.Create, FileAccess.ReadWrite); | |||
| @@ -462,14 +461,14 @@ namespace Downloader | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||
| if (dict != null && dict.ContainsKey("installpath")) | |||
| { | |||
| dict["installpath"] = newPath; | |||
| } | |||
| else | |||
| { | |||
| dict.Add("installpath", newPath); | |||
| dict?.Add("installpath", newPath); | |||
| } | |||
| if (dict == null || !dict.ContainsKey("download")) | |||
| { | |||
| @@ -517,9 +516,10 @@ namespace Downloader | |||
| // 创建存储桶 | |||
| try | |||
| { | |||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||
| string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 | |||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||
| string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 | |||
| ?? throw new Exception("本地文件夹路径获取失败"); | |||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||
| GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | |||
| Dictionary<string, string> test = request.GetRequestHeaders(); | |||
| @@ -553,7 +553,7 @@ namespace Downloader | |||
| public static string GetFileMd5Hash(string strFileFullPath) | |||
| { | |||
| FileStream fst = null; | |||
| FileStream? fst = null; | |||
| try | |||
| { | |||
| fst = new FileStream(strFileFullPath, FileMode.Open, FileAccess.Read); | |||
| @@ -634,7 +634,7 @@ namespace Downloader | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | |||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| var jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| string updatingFolder = ""; | |||
| switch (OS) | |||
| { | |||
| @@ -815,7 +815,7 @@ namespace Downloader | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| var dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||
| if (dict == null || !dict.ContainsKey("download") || "false" == dict["download"]) | |||
| { | |||
| return false; | |||
| @@ -865,15 +865,15 @@ namespace Downloader | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, jsonName))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | |||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| // var jsonDict = Utils.DeserializeJson<Dictionary1<string, string>>(json); | |||
| newFileName.Clear(); | |||
| updateFileName.Clear(); | |||
| newFileName.Add("THUAI6.tar.gz"); | |||
| Download(); | |||
| Stream inStream = null; | |||
| Stream gzipStream = null; | |||
| TarArchive tarArchive = null; | |||
| Stream? inStream = null; | |||
| Stream? gzipStream = null; | |||
| TarArchive? tarArchive = null; | |||
| try | |||
| { | |||
| using (inStream = File.OpenRead(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz"))) | |||
| @@ -886,7 +886,7 @@ namespace Downloader | |||
| } | |||
| } | |||
| } | |||
| catch (Exception ex) | |||
| catch | |||
| { | |||
| //出错 | |||
| } | |||
| @@ -899,7 +899,7 @@ namespace Downloader | |||
| FileInfo fileInfo = new FileInfo(System.IO.Path.Combine(Data.FilePath, "THUAI6.tar.gz")); | |||
| fileInfo.Delete(); | |||
| string json2; | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| Dictionary<string, string>? dict; | |||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | |||
| using (StreamReader r = new StreamReader(fs)) | |||
| @@ -909,7 +909,7 @@ namespace Downloader | |||
| { | |||
| json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2); | |||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2); | |||
| if (dict == null || !dict.ContainsKey("download")) | |||
| { | |||
| dict?.Add("download", "true"); | |||
| @@ -1000,7 +1000,7 @@ namespace Downloader | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Data.FilePath, "hash.json"))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty).Replace("/", @"\\"); | |||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| Dictionary<string, string> jsonDict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| Change_all_hash(Data.FilePath, jsonDict); | |||
| OverwriteHash(jsonDict); | |||
| break; | |||
| @@ -1008,7 +1008,7 @@ namespace Downloader | |||
| else | |||
| { | |||
| Console.WriteLine("读取路径失败!请重新输入文件路径:"); | |||
| Data.ResetFilepath(Console.ReadLine()); | |||
| Data.ResetFilepath(Console.ReadLine() ?? ""); | |||
| } | |||
| } | |||
| } | |||
| @@ -1058,7 +1058,7 @@ namespace Downloader | |||
| } | |||
| string json2; | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| Dictionary<string, string>? dict; | |||
| string existpath = System.IO.Path.Combine(Data.dataPath, "THUAI6.json"); | |||
| using FileStream fs = new FileStream(existpath, FileMode.Open, FileAccess.ReadWrite); | |||
| using (StreamReader r = new StreamReader(fs)) | |||
| @@ -1068,7 +1068,7 @@ namespace Downloader | |||
| { | |||
| json2 += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json2); | |||
| dict = Utils.TryDeserializeJson<Dictionary<string, string>>(json2); | |||
| if (dict == null || !dict.ContainsKey("download")) | |||
| { | |||
| dict?.Add("download", "false"); | |||
| @@ -1189,7 +1189,7 @@ namespace Downloader | |||
| while (true) | |||
| { | |||
| Console.WriteLine($"1. 更新hash.json 2. 检查更新 3.下载{ProgramName} 4.删除{ProgramName} 5.启动进程 6.移动{ProgramName}到其它路径"); | |||
| string choose = Console.ReadLine(); | |||
| string choose = Console.ReadLine() ?? ""; | |||
| if (choose == "1") | |||
| { | |||
| if (!CheckAlreadyDownload()) | |||
| @@ -1216,7 +1216,7 @@ namespace Downloader | |||
| else | |||
| { | |||
| Console.WriteLine("读取路径失败!请重新输入文件路径:"); | |||
| Data.ResetFilepath(Console.ReadLine()); | |||
| Data.ResetFilepath(Console.ReadLine() ?? ""); | |||
| } | |||
| } | |||
| } | |||
| @@ -1230,7 +1230,7 @@ namespace Downloader | |||
| { | |||
| string newpath; | |||
| Console.WriteLine("请输入下载路径:"); | |||
| newpath = Console.ReadLine(); | |||
| newpath = Console.ReadLine() ?? ""; | |||
| Data.ResetFilepath(newpath); | |||
| DownloadAll(); | |||
| } | |||
| @@ -1253,15 +1253,15 @@ namespace Downloader | |||
| else if (choose == "6") | |||
| { | |||
| string newPath; | |||
| newPath = Console.ReadLine(); | |||
| newPath = Console.ReadLine() ?? ""; | |||
| MoveProgram(newPath); | |||
| } | |||
| else if (choose == "7") | |||
| { | |||
| Console.WriteLine("请输入email:"); | |||
| string username = Console.ReadLine(); | |||
| string username = Console.ReadLine() ?? ""; | |||
| Console.WriteLine("请输入密码:"); | |||
| string password = Console.ReadLine(); | |||
| string password = Console.ReadLine() ?? ""; | |||
| await web.LoginToEEsast(client, username, password); | |||
| } | |||
| @@ -1285,7 +1285,8 @@ namespace Downloader | |||
| string keyHead = "Installer/"; | |||
| Tencent_cos_download downloader = new Tencent_cos_download(); | |||
| string hashName = "installerHash.json"; | |||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||
| ?? throw new Exception("Failed to get current directory"); | |||
| int result = 0; | |||
| try | |||
| { | |||
| @@ -1301,7 +1302,7 @@ namespace Downloader | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, hashName))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | |||
| Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| var jsonDict = Utils.TryDeserializeJson<Dictionary<string, string>>(json); | |||
| string md5 = ""; | |||
| List<string> awaitUpdate = new List<string>(); | |||
| if (jsonDict != null) | |||
| @@ -1343,15 +1344,16 @@ namespace Downloader | |||
| static public bool SelfUpdateDismissed() | |||
| { | |||
| string json; | |||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||
| string dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||
| ?? throw new Exception("Failed to get directory!"); | |||
| if (!File.Exists(System.IO.Path.Combine(dir, "updateList.json"))) | |||
| return false; | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(dir, "updateList.json"))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | |||
| List<string> jsonList; | |||
| List<string>? jsonList; | |||
| if (json != null) | |||
| jsonList = JsonConvert.DeserializeObject<List<string>>(json); | |||
| jsonList = Utils.TryDeserializeJson<List<string>>(json); | |||
| else | |||
| return false; | |||
| if (jsonList != null && jsonList.Contains("Dismiss")) | |||
| @@ -1403,7 +1405,7 @@ namespace WebConnect | |||
| throw new Exception("no token!"); | |||
| logintoken = token; | |||
| SaveToken(); | |||
| var info = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||
| var info = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||
| Downloader.UserInfo._id = info["_id"]; | |||
| Downloader.UserInfo.email = info["email"]; | |||
| break; | |||
| @@ -1459,19 +1461,19 @@ namespace WebConnect | |||
| { | |||
| case System.Net.HttpStatusCode.OK: | |||
| var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||
| string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) | |||
| string region = "ap-beijing"; // 设置一个默认的存储桶地域 | |||
| var res = Utils.DeserializeJson1<Dictionary<string, string>>(await response.Content.ReadAsStringAsync()); | |||
| string appid = "1255334966"; // 设置腾讯云账户的账户标识(APPID) | |||
| string region = "ap-beijing"; // 设置一个默认的存储桶地域 | |||
| CosXmlConfig config = new CosXmlConfig.Builder() | |||
| .IsHttps(true) // 设置默认 HTTPS 请求 | |||
| .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID | |||
| .SetRegion(region) // 设置一个默认的存储桶地域 | |||
| .SetDebugLog(true) // 显示日志 | |||
| .Build(); // 创建 CosXmlConfig 对象 | |||
| string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; | |||
| string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; | |||
| string tmpToken = res["SecurityToken"]; //"临时密钥 token"; | |||
| long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]);//临时密钥有效截止时间,精确到秒 | |||
| .IsHttps(true) // 设置默认 HTTPS 请求 | |||
| .SetAppid(appid) // 设置腾讯云账户的账户标识 APPID | |||
| .SetRegion(region) // 设置一个默认的存储桶地域 | |||
| .SetDebugLog(true) // 显示日志 | |||
| .Build(); // 创建 CosXmlConfig 对象 | |||
| string tmpSecretId = res["TmpSecretId"]; //"临时密钥 SecretId"; | |||
| string tmpSecretKey = res["TmpSecretKey"]; //"临时密钥 SecretKey"; | |||
| string tmpToken = res["SecurityToken"]; //"临时密钥 token"; | |||
| long tmpExpiredTime = Convert.ToInt64(res["ExpiredTime"]); //临时密钥有效截止时间,精确到秒 | |||
| QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider( | |||
| tmpSecretId, tmpSecretKey, tmpExpiredTime, tmpToken | |||
| ); | |||
| @@ -1584,12 +1586,12 @@ namespace WebConnect | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| if (dict.ContainsKey("token")) | |||
| { | |||
| dict.Remove("token"); | |||
| } | |||
| dict?.Add("token", logintoken); | |||
| dict.Add("token", logintoken); | |||
| } | |||
| using FileStream fs2 = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.ReadWrite); | |||
| using StreamWriter sw = new StreamWriter(fs2); | |||
| @@ -1627,7 +1629,7 @@ namespace WebConnect | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| Dictionary<string, string> dict = new Dictionary<string, string>(); | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| if (!dict.ContainsKey(key)) | |||
| { | |||
| dict.Add(key, data); | |||
| @@ -1651,7 +1653,7 @@ namespace WebConnect | |||
| } | |||
| } | |||
| public static string ReadJson(string key) | |||
| public static string? ReadJson(string key) | |||
| { | |||
| try | |||
| { | |||
| @@ -1664,7 +1666,7 @@ namespace WebConnect | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| fs.Close(); | |||
| sr.Close(); | |||
| return dict[key]; | |||
| @@ -1691,7 +1693,7 @@ namespace WebConnect | |||
| { | |||
| json += @"{""THUAI6""" + ":" + @"""2023""}"; | |||
| } | |||
| dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); | |||
| dict = Utils.DeserializeJson1<Dictionary<string, string>>(json); | |||
| if (!dict.ContainsKey("token")) | |||
| { | |||
| return false; | |||
| @@ -1745,9 +1747,9 @@ namespace WebConnect | |||
| var response = await client.SendAsync(request); | |||
| response.EnsureSuccessStatusCode(); | |||
| var info = await response.Content.ReadAsStringAsync(); | |||
| var s1 = JsonConvert.DeserializeObject<Dictionary<string, object>>(info)["data"]; | |||
| var s2 = JsonConvert.DeserializeObject<Dictionary<string, List<object>>>(s1.ToString())["contest_team_member"]; | |||
| var sres = JsonConvert.DeserializeObject<Dictionary<string, string>>(s2[0].ToString())["team_id"]; | |||
| var s1 = Utils.DeserializeJson1<Dictionary<string, object>>(info)["data"]; | |||
| var s2 = Utils.DeserializeJson1<Dictionary<string, List<object>>>(s1.ToString() ?? "")["contest_team_member"]; | |||
| var sres = Utils.DeserializeJson1<Dictionary<string, string>>(s2[0].ToString() ?? "")["team_id"]; | |||
| return sres; | |||
| } | |||
| async public Task<string> GetUserId(string learnNumber) | |||
| @@ -1773,4 +1775,20 @@ namespace WebConnect | |||
| public string Token { get; set; } = ""; | |||
| } | |||
| internal static class Utils | |||
| { | |||
| public static T DeserializeJson1<T>(string json) | |||
| where T : notnull | |||
| { | |||
| return JsonConvert.DeserializeObject<T>(json) | |||
| ?? throw new Exception("Failed to deserialize json."); | |||
| } | |||
| public static T? TryDeserializeJson<T>(string json) | |||
| where T : notnull | |||
| { | |||
| return JsonConvert.DeserializeObject<T>(json); | |||
| } | |||
| } | |||
| } | |||
| @@ -36,14 +36,15 @@ namespace starter.viewmodel.settings | |||
| //Program.Tencent_cos_download.UpdateHash(); | |||
| Status = SettingsModel.Status.working; | |||
| string CurrentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||
| string currentDirectory = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||
| ?? throw new Exception("Fail to get current directory"); | |||
| //MessageBox.Show("更新器工作正常"); | |||
| if (!Program.Tencent_cos_download.SelfUpdateDismissed()) | |||
| { | |||
| switch (Program.Tencent_cos_download.CheckSelfVersion()) | |||
| { | |||
| case 1: | |||
| Process.Start(System.IO.Path.Combine(CurrentDirectory, "InstallerUpdater.exe")); | |||
| Process.Start(System.IO.Path.Combine(currentDirectory, "InstallerUpdater.exe")); | |||
| Environment.Exit(0); | |||
| break; | |||
| case -1: | |||
| @@ -156,7 +157,7 @@ namespace starter.viewmodel.settings | |||
| { | |||
| e.Cancel = true; | |||
| MessageBox.Show("下载取消"); | |||
| if (e.Argument.ToString().Equals("Manual")) | |||
| if (e.Argument?.ToString()?.Equals("Manual") ?? false) | |||
| { | |||
| Status = SettingsModel.Status.menu; | |||
| } | |||
| @@ -167,7 +168,7 @@ namespace starter.viewmodel.settings | |||
| else | |||
| { | |||
| if (obj.Update()) | |||
| if (e.Argument.ToString().Equals("Manual")) | |||
| if (e.Argument?.ToString()?.Equals("Manual") ?? false) | |||
| { | |||
| e.Result = 1; | |||
| } | |||
| @@ -598,14 +599,14 @@ namespace starter.viewmodel.settings | |||
| return ""; | |||
| } | |||
| private BaseCommand clickBrowseCommand; | |||
| private BaseCommand? clickBrowseCommand; | |||
| public BaseCommand ClickBrowseCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickBrowseCommand == null) | |||
| { | |||
| clickBrowseCommand = new BaseCommand(new Action<object>(o => | |||
| clickBrowseCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| Route = RouteSelectWindow("Folder"); | |||
| })); | |||
| @@ -613,14 +614,14 @@ namespace starter.viewmodel.settings | |||
| return clickBrowseCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickConfirmCommand; | |||
| private BaseCommand? clickConfirmCommand; | |||
| public BaseCommand ClickConfirmCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickConfirmCommand == null) | |||
| { | |||
| clickConfirmCommand = new BaseCommand(new Action<object>(o => | |||
| clickConfirmCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| if (Status == SettingsModel.Status.newUser) | |||
| { | |||
| @@ -672,14 +673,14 @@ namespace starter.viewmodel.settings | |||
| return clickConfirmCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickUpdateCommand; | |||
| private BaseCommand? clickUpdateCommand; | |||
| public BaseCommand ClickUpdateCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickUpdateCommand == null) | |||
| { | |||
| clickUpdateCommand = new BaseCommand(new Action<object>(o => | |||
| clickUpdateCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| if (obj.UpdatePlanned) | |||
| @@ -719,14 +720,14 @@ namespace starter.viewmodel.settings | |||
| return clickUpdateCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickMoveCommand; | |||
| private BaseCommand? clickMoveCommand; | |||
| public BaseCommand ClickMoveCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickMoveCommand == null) | |||
| { | |||
| clickMoveCommand = new BaseCommand(new Action<object>(o => | |||
| clickMoveCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| Status = SettingsModel.Status.move; | |||
| })); | |||
| @@ -734,14 +735,14 @@ namespace starter.viewmodel.settings | |||
| return clickMoveCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickUninstCommand; | |||
| private BaseCommand? clickUninstCommand; | |||
| public BaseCommand ClickUninstCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickUninstCommand == null) | |||
| { | |||
| clickUninstCommand = new BaseCommand(new Action<object>(o => | |||
| clickUninstCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| @@ -767,14 +768,14 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| private BaseCommand clickLoginCommand; | |||
| private BaseCommand? clickLoginCommand; | |||
| public BaseCommand ClickLoginCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickLoginCommand == null) | |||
| { | |||
| clickLoginCommand = new BaseCommand(new Action<object>(async o => | |||
| clickLoginCommand = new BaseCommand(new Action<object?>(async o => | |||
| { | |||
| switch (await obj.Login()) | |||
| { | |||
| @@ -813,14 +814,14 @@ namespace starter.viewmodel.settings | |||
| } | |||
| } | |||
| private BaseCommand clickLaunchCommand; | |||
| private BaseCommand? clickLaunchCommand; | |||
| public BaseCommand ClickLaunchCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickLaunchCommand == null) | |||
| { | |||
| clickLaunchCommand = new BaseCommand(new Action<object>(o => | |||
| clickLaunchCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| if (obj.UpdatePlanned) | |||
| { | |||
| @@ -840,14 +841,14 @@ namespace starter.viewmodel.settings | |||
| return clickLaunchCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickEditCommand; | |||
| private BaseCommand? clickEditCommand; | |||
| public BaseCommand ClickEditCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickEditCommand == null) | |||
| { | |||
| clickEditCommand = new BaseCommand(new Action<object>(o => | |||
| clickEditCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| Status = SettingsModel.Status.menu; | |||
| if (obj.UpdatePlanned) | |||
| @@ -858,14 +859,14 @@ namespace starter.viewmodel.settings | |||
| return clickEditCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickBackCommand; | |||
| private BaseCommand? clickBackCommand; | |||
| public BaseCommand ClickBackCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickBackCommand == null) | |||
| { | |||
| clickBackCommand = new BaseCommand(new Action<object>(o => | |||
| clickBackCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| UpdateInfoVis = Visibility.Collapsed; | |||
| this.RaisePropertyChanged("UpdateInfoVis"); | |||
| @@ -878,14 +879,14 @@ namespace starter.viewmodel.settings | |||
| return clickBackCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickUploadCommand; | |||
| private BaseCommand? clickUploadCommand; | |||
| public BaseCommand ClickUploadCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickUploadCommand == null) | |||
| { | |||
| clickUploadCommand = new BaseCommand(new Action<object>(async o => | |||
| clickUploadCommand = new BaseCommand(new Action<object?>(async o => | |||
| { | |||
| if (obj.UploadReady) | |||
| { | |||
| @@ -953,14 +954,14 @@ namespace starter.viewmodel.settings | |||
| return clickUploadCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickAboutUploadCommand; | |||
| private BaseCommand? clickAboutUploadCommand; | |||
| public BaseCommand ClickAboutUploadCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickAboutUploadCommand == null) | |||
| { | |||
| clickAboutUploadCommand = new BaseCommand(new Action<object>(o => | |||
| clickAboutUploadCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| if (obj.UploadReady) | |||
| { | |||
| @@ -987,14 +988,14 @@ namespace starter.viewmodel.settings | |||
| return clickAboutUploadCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickExitCommand; | |||
| private BaseCommand? clickExitCommand; | |||
| public BaseCommand ClickExitCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickExitCommand == null) | |||
| { | |||
| clickExitCommand = new BaseCommand(new Action<object>(o => | |||
| clickExitCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| Application.Current.Shutdown(); | |||
| })); | |||
| @@ -1002,14 +1003,14 @@ namespace starter.viewmodel.settings | |||
| return clickExitCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickShiftLanguageCommand; | |||
| private BaseCommand? clickShiftLanguageCommand; | |||
| public BaseCommand ClickShiftLanguageCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickShiftLanguageCommand == null) | |||
| { | |||
| clickShiftLanguageCommand = new BaseCommand(new Action<object>(o => | |||
| clickShiftLanguageCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| if (obj.launchLanguage == SettingsModel.LaunchLanguage.cpp) | |||
| obj.launchLanguage = SettingsModel.LaunchLanguage.python; | |||
| @@ -1023,14 +1024,14 @@ namespace starter.viewmodel.settings | |||
| return clickShiftLanguageCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickReadCommand; | |||
| private BaseCommand? clickReadCommand; | |||
| public BaseCommand ClickReadCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickReadCommand == null) | |||
| { | |||
| clickReadCommand = new BaseCommand(new Action<object>(o => | |||
| clickReadCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| if (!Directory.Exists(Route + "/THUAI6/win")) | |||
| { | |||
| @@ -1050,14 +1051,14 @@ namespace starter.viewmodel.settings | |||
| return clickReadCommand; | |||
| } | |||
| } | |||
| private BaseCommand clickSwitchOSCommand; | |||
| private BaseCommand? clickSwitchOSCommand; | |||
| public BaseCommand ClickSwitchOSCommand | |||
| { | |||
| get | |||
| { | |||
| if (clickSwitchOSCommand == null) | |||
| { | |||
| clickSwitchOSCommand = new BaseCommand(new Action<object>(o => | |||
| clickSwitchOSCommand = new BaseCommand(new Action<object?>(o => | |||
| { | |||
| switch (obj.usingOS) | |||
| { | |||
| @@ -5,8 +5,16 @@ | |||
| <TargetFramework>net6.0-windows</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <UseWPF>true</UseWPF> | |||
| <PackageIcon>eesast_software_trans.png</PackageIcon> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <None Include="..\..\resource\eesast_software_trans.png"> | |||
| <Pack>True</Pack> | |||
| <PackagePath>\</PackagePath> | |||
| </None> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||
| <PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.34"> | |||
| @@ -5,7 +5,9 @@ | |||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |||
| xmlns:local="clr-namespace:InstallerUpdater" | |||
| mc:Ignorable="d" | |||
| Title="MainWindow" Height="180" Width="300"> | |||
| Title="MainWindow" Height="180" Width="300" | |||
| ResizeMode="CanMinimize" | |||
| > | |||
| <Grid> | |||
| <Grid.RowDefinitions> | |||
| <RowDefinition Height="40"/> | |||
| @@ -18,7 +18,8 @@ namespace Program | |||
| { | |||
| class Updater | |||
| { | |||
| public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); | |||
| public static string Dir = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName) | |||
| ?? throw new Exception("Cannot get current directory"); | |||
| public static string InstallerName = "Installer.exe"; | |||
| public static string jsonKey = "installerHash.json"; | |||
| public static string KeyHead = "Installer/"; | |||
| @@ -31,7 +32,8 @@ namespace Program | |||
| using (StreamReader r = new StreamReader(System.IO.Path.Combine(Dir, "updateList.json"))) | |||
| json = r.ReadToEnd(); | |||
| json = json.Replace("\r", string.Empty).Replace("\n", string.Empty); | |||
| List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json); | |||
| List<string> jsonList = JsonConvert.DeserializeObject<List<string>>(json) | |||
| ?? throw new Exception("Failed to deserialize json!"); | |||
| foreach (string todo in jsonList) | |||
| { | |||
| if (!todo.Equals("None")) | |||
| @@ -41,14 +43,14 @@ namespace Program | |||
| } | |||
| } | |||
| } | |||
| catch (IOException) | |||
| catch (IOException ex) | |||
| { | |||
| MessageBox.Show("下载器本体未能成功关闭"); | |||
| MessageBox.Show($"下载器本体未能成功关闭:\n{ex}"); | |||
| return false; | |||
| } | |||
| catch | |||
| catch (Exception ex) | |||
| { | |||
| MessageBox.Show("尝试下载时出现问题"); | |||
| MessageBox.Show($"尝试下载时出现问题:\n{ex}\n{ex.StackTrace}"); | |||
| return false; | |||
| } | |||
| return true; | |||
| @@ -67,7 +69,8 @@ namespace Program | |||
| json += @"{""None""}"; | |||
| } | |||
| List<string> ls = new List<string>(); | |||
| ls = JsonConvert.DeserializeObject<List<string>>(json); | |||
| ls = JsonConvert.DeserializeObject<List<string>>(json) | |||
| ?? throw new Exception("Failed to deserialize json!"); | |||
| if (!ls.Contains("Dismiss")) | |||
| { | |||
| ls.Add("Dismiss"); | |||
| @@ -114,9 +117,10 @@ namespace Program | |||
| // 创建存储桶 | |||
| try | |||
| { | |||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||
| string localDir = System.IO.Path.GetDirectoryName(download_dir); // 本地文件夹 | |||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||
| string bucket = "thuai6-1314234950"; // 格式:BucketName-APPID | |||
| string localDir = System.IO.Path.GetDirectoryName(download_dir) // 本地文件夹 | |||
| ?? throw new Exception("Failed to get directory name!"); | |||
| string localFileName = System.IO.Path.GetFileName(download_dir); // 指定本地保存的文件名 | |||
| GetObjectRequest request = new GetObjectRequest(bucket, key, localDir, localFileName); | |||
| Dictionary<string, string> test = request.GetRequestHeaders(); | |||
| @@ -5,21 +5,17 @@ | |||
| <TargetFramework>net6.0-windows</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <UseWPF>true</UseWPF> | |||
| <ApplicationIcon>EESAST.ico</ApplicationIcon> | |||
| <ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <None Remove="Logo.png" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Content Include="EESAST.ico" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -5,7 +5,8 @@ | |||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |||
| xmlns:local="clr-namespace:Client" | |||
| mc:Ignorable="d" | |||
| Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100"> | |||
| Title="ClientVI" Height="738" Width="850" KeyDown="KeyBoardControl" Background="White" ResizeMode="CanResizeWithGrip" WindowStyle="None" MouseLeftButtonDown="DragWindow" MouseDoubleClick="Attack" MinHeight="738" MinWidth="1100" | |||
| SizeChanged="ZoomMap"> | |||
| <Window.Resources> | |||
| <ImageBrush x:Key="Logo" ImageSource="Logo.png"/> | |||
| @@ -50,7 +51,7 @@ | |||
| <TransformGroup> | |||
| <ScaleTransform/> | |||
| <SkewTransform/> | |||
| <RotateTransform Angle="-0.377"/> | |||
| <RotateTransform Angle="0"/> | |||
| <TranslateTransform/> | |||
| </TransformGroup> | |||
| </Label.RenderTransform> | |||
| @@ -20,6 +20,7 @@ using Playback; | |||
| using CommandLine; | |||
| using Preparation.Utility; | |||
| using Preparation.Interface; | |||
| using System.Diagnostics.CodeAnalysis; | |||
| // 目前MainWindow还未复现的功能: | |||
| // private void ClickToSetMode(object sender, RoutedEventArgs e) | |||
| @@ -34,8 +35,6 @@ namespace Client | |||
| { | |||
| public MainWindow() | |||
| { | |||
| unitHeight = unitWidth = unit = 13; | |||
| bonusflag = true; | |||
| timer = new DispatcherTimer | |||
| { | |||
| Interval = new TimeSpan(50000) // 每50ms刷新一次 | |||
| @@ -60,9 +59,14 @@ namespace Client | |||
| listOfGate = new List<MessageOfGate>(); | |||
| listOfHiddenGate = new List<MessageOfHiddenGate>(); | |||
| WindowStartupLocation = WindowStartupLocation.CenterScreen; | |||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||
| unitFontsize = unit / 13; | |||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||
| ReactToCommandline(); | |||
| } | |||
| [MemberNotNull(nameof(StatusBarsOfSurvivor), nameof(StatusBarsOfHunter), nameof(StatusBarsOfCircumstance))] | |||
| private void SetStatusBar() | |||
| { | |||
| StatusBarsOfSurvivor = new StatusBarOfSurvivor[4]; | |||
| @@ -194,6 +198,7 @@ namespace Client | |||
| 0 => PlayerType.NullPlayerType, | |||
| 1 => PlayerType.StudentPlayer, | |||
| 2 => PlayerType.TrickerPlayer, | |||
| _ => PlayerType.NullPlayerType | |||
| }; | |||
| playerMsg.PlayerType = playerType; | |||
| if (Convert.ToInt64(comInfo[3]) == 1) | |||
| @@ -268,9 +273,9 @@ namespace Client | |||
| { | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 10, | |||
| Width = 20, | |||
| Height = 20, | |||
| FontSize = 7 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = text, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| @@ -282,19 +287,23 @@ namespace Client | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| } | |||
| private void ZoomMap() | |||
| private void ZoomMap(object sender, SizeChangedEventArgs e) | |||
| { | |||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||
| unitFontsize = unit / 13; | |||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||
| for (int i = 0; i < 50; i++) | |||
| { | |||
| for (int j = 0; j < 50; j++) | |||
| { | |||
| if (mapPatches[i, j] != null && (mapPatches[i, j].Width != UpperLayerOfMap.ActualWidth / 50 || mapPatches[i, j].Height != UpperLayerOfMap.ActualHeight / 50)) | |||
| if (mapPatches[i, j] != null) | |||
| { | |||
| mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50; | |||
| mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50; | |||
| mapPatches[i, j].Width = unitWidth; | |||
| mapPatches[i, j].Height = unitHeight; | |||
| mapPatches[i, j].HorizontalAlignment = HorizontalAlignment.Left; | |||
| mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; | |||
| mapPatches[i, j].Margin = new Thickness(UpperLayerOfMap.ActualWidth / 50 * j, UpperLayerOfMap.ActualHeight / 50 * i, 0, 0); | |||
| mapPatches[i, j].Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0); | |||
| } | |||
| } | |||
| } | |||
| @@ -312,7 +321,7 @@ namespace Client | |||
| Height = unitHeight, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| VerticalAlignment = VerticalAlignment.Top, | |||
| Margin = new Thickness(Width * (j), Height * (i), 0, 0) | |||
| Margin = new Thickness(unitWidth * j, unitHeight * i, 0, 0)//unitWidth cannot be replaced by Width | |||
| }; | |||
| switch (defaultMap[i, j]) | |||
| { | |||
| @@ -679,7 +688,7 @@ namespace Client | |||
| MaxButton.Content = "🗖"; | |||
| foreach (var obj in listOfHuman) | |||
| { | |||
| if (obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot) | |||
| if (!isDataFixed[obj.PlayerId] && obj.PlayerId < GameData.numOfStudent && obj.StudentType != StudentType.Robot) | |||
| { | |||
| IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | |||
| totalLife[obj.PlayerId] = occupation.MaxHp; | |||
| @@ -691,66 +700,52 @@ namespace Client | |||
| coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | |||
| ++i; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| } | |||
| foreach (var obj in listOfButcher) | |||
| { | |||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||
| int j = 0; | |||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||
| if (!isDataFixed[obj.PlayerId]) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||
| ++j; | |||
| IGhostType occupation1 = (IGhostType)OccupationFactory.FindIOccupation(Transformation.ToTrickerType(obj.TrickerType)); | |||
| int j = 0; | |||
| foreach (var skill in occupation1.ListOfIActiveSkill) | |||
| { | |||
| var iActiveSkill = SkillFactory.FindIActiveSkill(skill); | |||
| coolTime[j, GameData.numOfStudent] = iActiveSkill.SkillCD; | |||
| ++j; | |||
| } | |||
| isDataFixed[obj.PlayerId] = true; | |||
| } | |||
| } | |||
| if (StatusBarsOfSurvivor != null) | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| if (StatusBarsOfHunter != null) | |||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize); | |||
| } | |||
| if (StatusBarsOfCircumstance != null) | |||
| StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| // 完成窗口信息更新 | |||
| StatusBarsOfHunter.SetFontSize(12 * unitFontsize); | |||
| StatusBarsOfCircumstance.SetFontSize(12 * unitFontsize); | |||
| if (!isClientStocked) | |||
| { | |||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||
| try | |||
| { | |||
| // if (log != null) | |||
| //{ | |||
| // string temp = ""; | |||
| // for (int i = 0; i < dataDict[GameObjType.Character].Count; i++) | |||
| // { | |||
| // temp += Convert.ToString(dataDict[GameObjType.Character][i].MessageOfCharacter.TeamID) + "\n"; | |||
| // } | |||
| // log.Content = temp; | |||
| // } | |||
| UpperLayerOfMap.Children.Clear(); | |||
| // if ((communicator == null || !communicator.Client.IsConnected) && !isPlaybackMode) | |||
| //{ | |||
| // UnderLayerOfMap.Children.Clear(); | |||
| // throw new Exception("Client is unconnected."); | |||
| // } | |||
| // else | |||
| //{ | |||
| foreach (var data in listOfAll) | |||
| { | |||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | |||
| } | |||
| if (!hasDrawed && mapFlag) | |||
| { | |||
| DrawMap(); | |||
| } | |||
| foreach (var data in listOfHuman) | |||
| { | |||
| if (data.StudentType != StudentType.Robot) | |||
| @@ -770,7 +765,7 @@ namespace Client | |||
| icon.Fill = Brushes.Gray; | |||
| TextBox num = new() | |||
| { | |||
| FontSize = 7 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 7 * unitFontsize, | |||
| Width = 2 * radiusTimes * unitWidth, | |||
| Height = 2 * radiusTimes * unitHeight, | |||
| Text = Convert.ToString(data.PlayerId), | |||
| @@ -922,7 +917,7 @@ namespace Client | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| @@ -944,7 +939,7 @@ namespace Client | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| @@ -966,7 +961,7 @@ namespace Client | |||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 8 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString(deg), | |||
| @@ -988,7 +983,7 @@ namespace Client | |||
| { | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| @@ -1021,7 +1016,7 @@ namespace Client | |||
| isEmergencyOpened = true; | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString("🔓"), | |||
| @@ -1035,8 +1030,6 @@ namespace Client | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| } | |||
| } | |||
| //} | |||
| ZoomMap(); | |||
| } | |||
| catch (Exception exc) | |||
| { | |||
| @@ -1055,6 +1048,11 @@ namespace Client | |||
| { | |||
| if (!isPlaybackMode && !isSpectatorMode) | |||
| { | |||
| if (client is null) | |||
| { | |||
| return; | |||
| } | |||
| switch (e.Key) | |||
| { | |||
| case Key.W: | |||
| @@ -1236,6 +1234,10 @@ namespace Client | |||
| { | |||
| if (!isPlaybackMode && !isSpectatorMode) | |||
| { | |||
| if (client is null) | |||
| { | |||
| return; | |||
| } | |||
| if (humanOrButcher && human != null) | |||
| { | |||
| AttackMsg msgJ = new() | |||
| @@ -1386,7 +1388,7 @@ namespace Client | |||
| // 以下为Mainwindow自定义属性 | |||
| private readonly DispatcherTimer timer; // 定时器 | |||
| private long counter; // 预留的取时间变量 | |||
| AvailableService.AvailableServiceClient client; | |||
| AvailableService.AvailableServiceClient? client; | |||
| AsyncServerStreamingCall<MessageToClient>? responseStream; | |||
| private StatusBarOfSurvivor[] StatusBarsOfSurvivor; | |||
| private StatusBarOfHunter StatusBarsOfHunter; | |||
| @@ -1419,7 +1421,6 @@ namespace Client | |||
| private MessageOfTricker? butcher = null; | |||
| private bool humanOrButcher;//true for human | |||
| private bool bonusflag; | |||
| private bool mapFlag = false; | |||
| private bool hasDrawed = false; | |||
| public int[,] defaultMap = new int[,] { | |||
| @@ -1481,7 +1482,8 @@ namespace Client | |||
| bool isSpectatorMode = false; | |||
| bool isEmergencyOpened = false; | |||
| bool isEmergencyDrawed = false; | |||
| bool isDataFixed = false; | |||
| bool[] isDataFixed = new bool[5] { false, false, false, false, false }; | |||
| double unitFontsize = 10; | |||
| const double radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | |||
| const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | |||
| private int[] totalLife = new int[4] { 100, 100, 100, 100 }, totalDeath = new int[4] { 100, 100, 100, 100 }; | |||
| @@ -2,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" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -82,19 +82,19 @@ namespace Client | |||
| state.Text = "Quit"; | |||
| break; | |||
| case PlayerState.Treated: | |||
| state.Text = "Treated"; | |||
| state.Text = "Encouraged"; | |||
| break; | |||
| case PlayerState.Rescued: | |||
| state.Text = "Rescued"; | |||
| state.Text = "Roused"; | |||
| break; | |||
| case PlayerState.Stunned: | |||
| state.Text = "Stunned"; | |||
| break; | |||
| case PlayerState.Treating: | |||
| state.Text = "Treating"; | |||
| state.Text = "Encouraging"; | |||
| break; | |||
| case PlayerState.Rescuing: | |||
| state.Text = "Rescuing"; | |||
| state.Text = "Rousing"; | |||
| break; | |||
| case PlayerState.Swinging: | |||
| state.Text = "Swinging"; | |||
| @@ -87,19 +87,19 @@ namespace Client | |||
| status.Text = "💀" + "\nQuit"; | |||
| break; | |||
| case PlayerState.Treated: | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nTreated, " + Convert.ToString(perLife) + "%"; | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nEncouraged, " + Convert.ToString(perLife) + "%"; | |||
| break; | |||
| case PlayerState.Rescued: | |||
| status.Text = "💀: " + Convert.ToString(death) + "\nRescued, " + Convert.ToString(perDeath) + "%"; | |||
| status.Text = "💀: " + Convert.ToString(death) + "\nRoused, " + Convert.ToString(perDeath) + "%"; | |||
| break; | |||
| case PlayerState.Stunned: | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nStunned, " + Convert.ToString(perLife) + "%"; | |||
| break; | |||
| case PlayerState.Treating: | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nTreating, " + Convert.ToString(perLife) + "%"; | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nEncouraging, " + Convert.ToString(perLife) + "%"; | |||
| break; | |||
| case PlayerState.Rescuing: | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nRescuing, " + Convert.ToString(perLife) + "%"; | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nRousing, " + Convert.ToString(perLife) + "%"; | |||
| break; | |||
| case PlayerState.Swinging: | |||
| status.Text = "♥: " + Convert.ToString(life) + "\nSwinging, " + Convert.ToString(perLife) + "%"; | |||
| @@ -8,7 +8,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -1,6 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -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) | |||
| { | |||
| @@ -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) | |||
| @@ -3,7 +3,6 @@ using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| using GameClass.GameObj; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -13,29 +12,47 @@ namespace GameClass.GameObj | |||
| private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | |||
| public Dictionary<uint, XY> BirthPointList => birthPointList; | |||
| private object lockForNum = new(); | |||
| private readonly object lockForNum = new(); | |||
| private void WhenStudentNumChange() | |||
| { | |||
| if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) | |||
| { | |||
| Timer.IsGaming = false; | |||
| return; | |||
| } | |||
| if (GameData.numOfStudent - NumOfDeceasedStudent - NumOfEscapedStudent == 1) | |||
| if (GameData.numOfStudent - numOfDeceasedStudent - numOfEscapedStudent == 1) | |||
| { | |||
| GameObjLockDict[GameObjType.EmergencyExit].EnterReadLock(); | |||
| GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit]) | |||
| if (emergencyExit.CanOpen) | |||
| foreach (Character player in GameObjDict[GameObjType.Character]) | |||
| if (player.PlayerState == PlayerStateType.Addicted) | |||
| { | |||
| emergencyExit.IsOpen = true; | |||
| Timer.IsGaming = false; | |||
| break; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| GameObjLockDict[GameObjType.EmergencyExit].ExitReadLock(); | |||
| GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| if (Timer.IsGaming) | |||
| { | |||
| GameObjLockDict[GameObjType.EmergencyExit].EnterWriteLock(); | |||
| try | |||
| { | |||
| foreach (EmergencyExit emergencyExit in GameObjDict[GameObjType.EmergencyExit]) | |||
| if (emergencyExit.CanOpen) | |||
| { | |||
| emergencyExit.IsOpen = true; | |||
| break; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| GameObjLockDict[GameObjType.EmergencyExit].ExitWriteLock(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -203,7 +220,7 @@ namespace GameClass.GameObj | |||
| } | |||
| public bool Remove(GameObj gameObj) | |||
| { | |||
| bool flag = false; | |||
| GameObj? ToDel = null; | |||
| GameObjLockDict[gameObj.Type].EnterWriteLock(); | |||
| try | |||
| { | |||
| @@ -211,17 +228,18 @@ namespace GameClass.GameObj | |||
| { | |||
| if (gameObj.ID == obj.ID) | |||
| { | |||
| GameObjDict[gameObj.Type].Remove(obj); | |||
| flag = true; | |||
| ToDel = obj; | |||
| break; | |||
| } | |||
| } | |||
| if (ToDel != null) | |||
| GameObjDict[gameObj.Type].Remove(ToDel); | |||
| } | |||
| finally | |||
| { | |||
| GameObjLockDict[gameObj.Type].ExitWriteLock(); | |||
| } | |||
| return flag; | |||
| return ToDel != null; | |||
| } | |||
| public bool RemoveJustFromMap(GameObj gameObj) | |||
| { | |||
| @@ -1,6 +1,5 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| @@ -79,6 +79,8 @@ namespace GameEngine | |||
| return; | |||
| if (!obj.IsAvailable || !gameTimer.IsGaming) | |||
| return; | |||
| long threadNum = (obj.Type == GameObjType.Character) ? ((ICharacter)obj).ThreadNum : 0;//对人特殊处理 | |||
| new Thread | |||
| ( | |||
| () => | |||
| @@ -87,110 +89,143 @@ namespace GameEngine | |||
| obj.IsMoving = true; | |||
| double moveVecLength = 0.0; | |||
| XY res = new XY(direction, moveVecLength); | |||
| XY res = new(direction, moveVecLength); | |||
| double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); // 转向,并用deltaLen存储行走的误差 | |||
| IGameObj? collisionObj = null; | |||
| bool isDestroyed = false; | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||
| () => | |||
| bool flag; // 循环标志 | |||
| do | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollision(obj, obj.Position); | |||
| if (collisionObj == null) | |||
| break; | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| res = new XY(direction, moveVecLength); | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| break; | |||
| } | |||
| } while (flag); | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| if (!isDestroyed) | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||
| () => | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| res = new XY(direction, moveVecLength); | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| //对人特殊处理 | |||
| if (threadNum > 0 && ((ICharacter)obj).ThreadNum != threadNum) return false; | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| return false; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| } | |||
| } while (flag); | |||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| return false; | |||
| case AfterCollision.MoveMax: | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } while (flag); | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||
| return true; | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| bool flag; | |||
| do | |||
| return true; | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| flag = false; | |||
| if (!isDestroyed) | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| bool flag; | |||
| do | |||
| { | |||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| } | |||
| else | |||
| flag = false; | |||
| if (!isDestroyed) | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| } | |||
| else | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| if (threadNum == 0 || ((ICharacter)obj).ThreadNum == threadNum) | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } while (flag); | |||
| if (leftTime > 0 && obj.IsMoving) | |||
| { | |||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | |||
| } | |||
| } while (flag); | |||
| if (leftTime > 0 && obj.IsMoving) | |||
| { | |||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | |||
| } | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = false; // 结束移动 | |||
| EndMove(obj); | |||
| return 0; | |||
| }, | |||
| maxTotalDuration: moveTime | |||
| ) | |||
| { | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = false; // 结束移动 | |||
| EndMove(obj); | |||
| return 0; | |||
| }, | |||
| maxTotalDuration: moveTime | |||
| ) | |||
| { | |||
| if (b) | |||
| Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| { | |||
| if (b) | |||
| Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); | |||
| #if DEBUG | |||
| else | |||
| { | |||
| Console.WriteLine("Debug info: Object moving time exceed for once."); | |||
| } | |||
| else | |||
| { | |||
| Console.WriteLine("Debug info: Object moving time exceed for once."); | |||
| } | |||
| #endif | |||
| } | |||
| }.Start(); | |||
| } | |||
| }.Start(); | |||
| } | |||
| } | |||
| ).Start(); | |||
| } | |||
| @@ -35,7 +35,9 @@ namespace Gaming | |||
| } | |||
| public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| { | |||
| if (moveTimeInMilliseconds < 5) return false; | |||
| if (!playerToMove.Commandable() || !TryToStop()) return false; | |||
| if (playerToMove.IsMoving) return false; | |||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||
| return true; | |||
| @@ -70,29 +72,30 @@ namespace Gaming | |||
| ++generatorForFix.NumOfFixing; | |||
| characterManager.SetPlayerState(player, PlayerStateType.Fixing); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| Thread.Sleep(GameData.frameDuration); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => gameMap.Timer.IsGaming && player.PlayerState == PlayerStateType.Fixing, | |||
| loopCondition: () => gameMap.Timer.IsGaming && threadNum == player.ThreadNum, | |||
| loopToDo: () => | |||
| { | |||
| if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| gameMap.NumOfRepairedGenerators++; | |||
| } | |||
| if (generatorForFix.DegreeOfRepair == GameData.degreeOfFixedGenerator) | |||
| characterManager.SetPlayerState(player); | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| .Start(); | |||
| --generatorForFix.NumOfFixing; | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| --generatorForFix.NumOfFixing; | |||
| return true; | |||
| } | |||
| @@ -170,8 +173,10 @@ namespace Gaming | |||
| { | |||
| characterManager.SetPlayerState(playerTreated, PlayerStateType.Treated); | |||
| characterManager.SetPlayerState(player, PlayerStateType.Treating); | |||
| long threadNum = player.ThreadNum; | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && player.PlayerState == PlayerStateType.Treating && gameMap.Timer.IsGaming, | |||
| loopCondition: () => playerTreated.PlayerState == PlayerStateType.Treated && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| if (playerTreated.AddDegreeOfTreatment(GameData.frameDuration * player.TreatSpeed, player)) | |||
| @@ -182,7 +187,7 @@ namespace Gaming | |||
| ) | |||
| .Start(); | |||
| if (player.PlayerState == PlayerStateType.Treating) characterManager.SetPlayerState(player); | |||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||
| else if (playerTreated.PlayerState == PlayerStateType.Treated) characterManager.SetPlayerState(playerTreated); | |||
| } | |||
| ) | |||
| @@ -200,13 +205,14 @@ namespace Gaming | |||
| return false; | |||
| characterManager.SetPlayerState(player, PlayerStateType.Rescuing); | |||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Rescued); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && player.PlayerState == PlayerStateType.Rescuing && gameMap.Timer.IsGaming, | |||
| loopCondition: () => playerRescued.PlayerState == PlayerStateType.Rescued && threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| playerRescued.TimeOfRescue += GameData.frameDuration; | |||
| @@ -228,7 +234,7 @@ namespace Gaming | |||
| else | |||
| characterManager.SetPlayerState(playerRescued, PlayerStateType.Addicted); | |||
| } | |||
| if (player.PlayerState == PlayerStateType.Rescuing) characterManager.SetPlayerState(player); | |||
| if (threadNum == player.ThreadNum) characterManager.SetPlayerState(player); | |||
| playerRescued.TimeOfRescue = 0; | |||
| } | |||
| ) | |||
| @@ -297,13 +303,14 @@ namespace Gaming | |||
| // gameMap.Add(addWall); | |||
| characterManager.SetPlayerState(player, PlayerStateType.ClimbingThroughWindows); | |||
| long threadNum = player.ThreadNum; | |||
| windowForClimb.WhoIsClimbing = player; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => player.PlayerState == PlayerStateType.ClimbingThroughWindows && gameMap.Timer.IsGaming, | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => { }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0, | |||
| @@ -322,7 +329,7 @@ namespace Gaming | |||
| moveEngine.MoveObj(player, (int)(windowToPlayer.Length() * 3.0 * 1000 / player.MoveSpeed), (-1 * windowToPlayer).Angle()); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => player.PlayerState == PlayerStateType.ClimbingThroughWindows && gameMap.Timer.IsGaming, | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| }, | |||
| @@ -336,7 +343,7 @@ namespace Gaming | |||
| player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | |||
| windowForClimb.WhoIsClimbing = null; | |||
| // gameMap.Remove(addWall); | |||
| if (player.PlayerState == PlayerStateType.ClimbingThroughWindows) | |||
| if (threadNum == player.ThreadNum) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| } | |||
| @@ -379,12 +386,13 @@ namespace Gaming | |||
| if (!flag) return false; | |||
| characterManager.SetPlayerState(player, PlayerStateType.LockingOrOpeningTheDoor); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => flag && player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||
| loopCondition: () => flag && threadNum == player.ThreadNum && gameMap.Timer.IsGaming && doorToLock.OpenOrLockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||
| loopToDo: () => | |||
| { | |||
| flag = ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) == null); | |||
| @@ -399,7 +407,7 @@ namespace Gaming | |||
| { | |||
| doorToLock.IsOpen = (!doorToLock.IsOpen); | |||
| } | |||
| if (player.PlayerState == PlayerStateType.LockingOrOpeningTheDoor) | |||
| if (threadNum == player.ThreadNum) | |||
| characterManager.SetPlayerState(player); | |||
| doorToLock.OpenOrLockDegree = 0; | |||
| } | |||
| @@ -472,7 +480,7 @@ namespace Gaming | |||
| OnCollision: (obj, collisionObj, moveVec) => | |||
| { | |||
| SkillWhenColliding((Character)obj, collisionObj); | |||
| Preparation.Utility.Debugger.Output(obj, " end move with " + collisionObj.ToString()); | |||
| //Preparation.Utility.Debugger.Output(obj, " end move with " + collisionObj.ToString()); | |||
| //if (collisionObj is Mine) | |||
| //{ | |||
| // ActivateMine((Character)obj, (Mine)collisionObj); | |||
| @@ -6,7 +6,6 @@ using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| namespace Gaming | |||
| { | |||
| @@ -34,6 +33,7 @@ namespace Gaming | |||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | |||
| BulletBomb((Bullet)obj, null); | |||
| obj.CanMove = false; | |||
| } | |||
| ); | |||
| this.characterManager = characterManager; | |||
| @@ -171,17 +171,19 @@ namespace Gaming | |||
| return false; | |||
| Debugger.Output(player, player.CharacterType.ToString() + "Attack in " + player.BulletOfPlayer.ToString()); | |||
| Debugger.Output(player, player.Position.ToString() + " " + player.Radius.ToString() + " " + BulletFactory.BulletRadius(player.BulletOfPlayer).ToString()); | |||
| XY res = player.Position + new XY // 子弹紧贴人物生成。 | |||
| ( | |||
| (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle)), | |||
| (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle)) | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle))) * ((Math.Cos(angle) > 0) ? 1 : -1), | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))) * ((Math.Sin(angle) > 0) ? 1 : -1) | |||
| ); | |||
| Bullet? bullet = player.Attack(res, gameMap.GetPlaceType(res)); | |||
| if (bullet != null) | |||
| { | |||
| Debugger.Output(player, "Attack in " + bullet.ToString()); | |||
| player.FacingDirection = new(angle, bullet.BulletAttackRange); | |||
| Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); | |||
| bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0; | |||
| bullet.CanMove = true; | |||
| gameMap.Add(bullet); | |||
| @@ -189,12 +191,13 @@ namespace Gaming | |||
| if (bullet.CastTime > 0) | |||
| { | |||
| characterManager.SetPlayerState(player, PlayerStateType.TryingToAttack); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| (() => | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => player.PlayerState == PlayerStateType.TryingToAttack && gameMap.Timer.IsGaming, | |||
| loopCondition: () => threadNum == player.ThreadNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| }, | |||
| @@ -206,7 +209,7 @@ namespace Gaming | |||
| if (gameMap.Timer.IsGaming) | |||
| { | |||
| if (player.PlayerState == PlayerStateType.TryingToAttack) | |||
| if (threadNum == player.ThreadNum) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| } | |||
| @@ -1,13 +1,9 @@ | |||
| using System; | |||
| using System.Threading; | |||
| using System.Collections.Generic; | |||
| using GameClass.GameObj; | |||
| using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| using System.Timers; | |||
| namespace Gaming | |||
| { | |||
| @@ -24,20 +20,37 @@ namespace Gaming | |||
| public void SetPlayerState(Character player, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | |||
| { | |||
| switch (player.PlayerState) | |||
| lock (player.MoveLock) | |||
| { | |||
| case PlayerStateType.OpeningTheChest: | |||
| ((Chest)player.WhatInteractingWith).StopOpen(); | |||
| break; | |||
| case PlayerStateType.OpeningTheDoorway: | |||
| Doorway doorway = (Doorway)player.WhatInteractingWith; | |||
| doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | |||
| doorway.OpenStartTime = 0; | |||
| break; | |||
| default: | |||
| break; | |||
| switch (player.PlayerState) | |||
| { | |||
| case PlayerStateType.OpeningTheChest: | |||
| ((Chest)player.WhatInteractingWith).StopOpen(); | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.OpeningTheDoorway: | |||
| Doorway doorway = (Doorway)player.WhatInteractingWith; | |||
| doorway.OpenDegree += gameMap.Timer.nowTime() - doorway.OpenStartTime; | |||
| doorway.OpenStartTime = 0; | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.Addicted: | |||
| if (value == PlayerStateType.Rescued) | |||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||
| else | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| case PlayerStateType.Rescued: | |||
| if (value == PlayerStateType.Addicted) | |||
| player.ChangePlayerStateInOneThread(value, gameObj); | |||
| else | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| default: | |||
| player.ChangePlayerState(value, gameObj); | |||
| break; | |||
| } | |||
| } | |||
| player.ChangePlayerState(value, gameObj); | |||
| } | |||
| public Character? AddPlayer(XY pos, int teamID, int playerID, CharacterType characterType, Character? parent = null) | |||
| @@ -237,6 +250,7 @@ namespace Gaming | |||
| } | |||
| } | |||
| SetPlayerState(player, PlayerStateType.Addicted); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| (() => | |||
| { | |||
| @@ -244,7 +258,7 @@ namespace Gaming | |||
| Debugger.Output(player, " is addicted "); | |||
| #endif | |||
| new FrameRateTaskExecutor<int>( | |||
| () => (player.PlayerState == PlayerStateType.Addicted || player.PlayerState == PlayerStateType.Rescued) && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => threadNum == player.ThreadNum && player.GamingAddiction < player.MaxGamingAddiction && gameMap.Timer.IsGaming, | |||
| () => | |||
| { | |||
| player.GamingAddiction += (player.PlayerState == PlayerStateType.Addicted) ? GameData.frameDuration : 0; | |||
| @@ -273,8 +287,9 @@ namespace Gaming | |||
| (() => | |||
| { | |||
| SetPlayerState(player, PlayerStateType.Stunned); | |||
| long threadNum = player.ThreadNum; | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Stunned) | |||
| if (threadNum == player.ThreadNum) | |||
| SetPlayerState(player); | |||
| } | |||
| ) | |||
| @@ -362,13 +377,14 @@ namespace Gaming | |||
| if (time <= 0) return false; | |||
| if (player.PlayerState == PlayerStateType.Swinging || (!player.Commandable() && player.PlayerState != PlayerStateType.TryingToAttack)) return false; | |||
| SetPlayerState(player, PlayerStateType.Swinging); | |||
| long threadNum = player.ThreadNum; | |||
| new Thread | |||
| (() => | |||
| { | |||
| Thread.Sleep(time); | |||
| if (player.PlayerState == PlayerStateType.Swinging) | |||
| if (threadNum == player.ThreadNum) | |||
| { | |||
| SetPlayerState(player); | |||
| } | |||
| @@ -383,6 +399,7 @@ namespace Gaming | |||
| #if DEBUG | |||
| Debugger.Output(player, "die."); | |||
| #endif | |||
| if (player.PlayerState == PlayerStateType.Deceased) return; | |||
| player.RemoveFromGame(PlayerStateType.Deceased); | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| @@ -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 | |||
| { | |||
| @@ -96,6 +96,7 @@ namespace Gaming | |||
| IActiveSkill activeSkill = player.FindIActiveSkill(ActiveSkillType.BecomeInvisible); | |||
| return ActiveSkillEffect(activeSkill, player, () => | |||
| { | |||
| player.AddScore(GameData.ScoreBecomeInvisible); | |||
| player.AddInvisible(activeSkill.DurationTime); | |||
| Debugger.Output(player, "become invisible!"); | |||
| }, | |||
| @@ -17,44 +17,31 @@ namespace Gaming | |||
| switch (activeSkillType) | |||
| { | |||
| case ActiveSkillType.BecomeInvisible: | |||
| BecomeInvisible(character); | |||
| break; | |||
| return BecomeInvisible(character); | |||
| case ActiveSkillType.UseKnife: | |||
| UseKnife(character); | |||
| break; | |||
| return UseKnife(character); | |||
| case ActiveSkillType.Howl: | |||
| Howl(character); | |||
| break; | |||
| return Howl(character); | |||
| case ActiveSkillType.CanBeginToCharge: | |||
| CanBeginToCharge(character); | |||
| break; | |||
| return CanBeginToCharge(character); | |||
| case ActiveSkillType.Inspire: | |||
| Inspire(character); | |||
| break; | |||
| return Inspire(character); | |||
| case ActiveSkillType.Encourage: | |||
| Encourage(character); | |||
| break; | |||
| return Encourage(character); | |||
| case ActiveSkillType.Punish: | |||
| Punish(character); | |||
| break; | |||
| return Punish(character); | |||
| case ActiveSkillType.JumpyBomb: | |||
| JumpyBomb(character); | |||
| break; | |||
| return JumpyBomb(character); | |||
| case ActiveSkillType.WriteAnswers: | |||
| WriteAnswers(character); | |||
| break; | |||
| return WriteAnswers(character); | |||
| case ActiveSkillType.SummonGolem: | |||
| SummonGolem(character); | |||
| break; | |||
| return SummonGolem(character); | |||
| case ActiveSkillType.UseRobot: | |||
| UseRobot(character); | |||
| break; | |||
| return UseRobot(character); | |||
| case ActiveSkillType.Rouse: | |||
| Rouse(character); | |||
| break; | |||
| return Rouse(character); | |||
| case ActiveSkillType.ShowTime: | |||
| ShowTime(character); | |||
| break; | |||
| return ShowTime(character); | |||
| default: | |||
| return false; | |||
| } | |||
| @@ -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(); | |||
| } | |||
| @@ -9,7 +9,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| @@ -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; // 检查位置标志、补充子弹的帧时长 | |||
| @@ -47,4 +47,6 @@ | |||
| ## 开发人员 | |||
| - ......(自己加) | |||
| - 黄淞:底层逻辑 | |||
| - 游笑权:Client | |||
| - 高思研:Server | |||
| @@ -39,8 +39,8 @@ namespace Server | |||
| [Option('f', "fileName", Required = false, HelpText = "The file to store playback file or to read file.")] | |||
| public string FileName { get; set; } = DefaultArgumentOptions.FileName; | |||
| [Option('s', "allowSpectator", Required = false, HelpText = "Whether to allow a spectator to watch the game.")] | |||
| public bool AllowSpectator { get; set; } = true; | |||
| [Option("notAllowSpectator", Required = false, HelpText = "Whether to allow a spectator to watch the game.")] | |||
| public bool NotAllowSpectator { get; set; } = false; | |||
| [Option('b', "playback", Required = false, HelpText = "Whether open the server in a playback mode.")] | |||
| public bool Playback { get; set; } = false; | |||
| @@ -71,5 +71,8 @@ namespace Server | |||
| [Option("resultFileName", Required = false, HelpText = "Result file name, saved as .json")] | |||
| public string ResultFileName { get; set; } = DefaultArgumentOptions.FileName; | |||
| [Option("startLockFile", Required = false, HelpText = "Whether to create a file that identifies whether the game has started")] | |||
| public string StartLockFile { get; set; } = DefaultArgumentOptions.FileName; | |||
| } | |||
| } | |||
| @@ -1,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; | |||
| } | |||
| @@ -1,24 +1,20 @@ | |||
| using Grpc.Core; | |||
| using Protobuf; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using GameClass.GameObj; | |||
| using Gaming; | |||
| using GameClass.GameObj; | |||
| using Preparation.Utility; | |||
| using Playback; | |||
| using Newtonsoft.Json; | |||
| using Newtonsoft.Json.Linq; | |||
| using Preparation.Interface; | |||
| using Playback; | |||
| using Preparation.Utility; | |||
| using Protobuf; | |||
| using System.Collections.Concurrent; | |||
| using Timothy.FrameRateTask; | |||
| namespace Server | |||
| { | |||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||
| partial class GameServer : ServerBase | |||
| { | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object semaDictLock = new(); | |||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| // private object semaDictLock = new(); | |||
| protected readonly ArgumentOptions options; | |||
| private HttpSender? httpSender; | |||
| private object gameLock = new(); | |||
| @@ -29,13 +25,13 @@ namespace Server | |||
| private SemaphoreSlim endGameSem = new(0); | |||
| protected readonly Game game; | |||
| private uint spectatorMinPlayerID = 2023; | |||
| private List<uint> spectatorList = new List<uint>(); | |||
| public int playerNum; | |||
| public int TeamCount => options.TeamCount; | |||
| protected long[] communicationToGameID; // 通信用的ID映射到游戏内的ID,通信中0-3为Student,4为Tricker | |||
| private readonly object messageToAllClientsLock = new(); | |||
| public static readonly long SendMessageToClientIntervalInMilliseconds = 50; | |||
| private MessageWriter? mwr = null; | |||
| private object spetatorJoinLock = new(); | |||
| public void StartGame() | |||
| { | |||
| @@ -45,6 +41,7 @@ namespace Server | |||
| if (id == GameObj.invalidID) return; //如果有未初始化的玩家,不开始游戏 | |||
| } | |||
| Console.WriteLine("Game starts!"); | |||
| CreateStartFile(); | |||
| game.StartGame((int)options.GameTimeInSecond * 1000); | |||
| Thread.Sleep(1); | |||
| new Thread(() => | |||
| @@ -74,7 +71,17 @@ namespace Server | |||
| }) | |||
| { IsBackground = true }.Start(); | |||
| } | |||
| public void WaitForEnd() | |||
| public void CreateStartFile() | |||
| { | |||
| if (options.StartLockFile != DefaultArgumentOptions.FileName) | |||
| { | |||
| using var _ = File.Create(options.StartLockFile); | |||
| Console.WriteLine("Successfully Created StartLockFile!"); | |||
| } | |||
| } | |||
| public override void WaitForEnd() | |||
| { | |||
| this.endGameSem.Wait(); | |||
| mwr?.Dispose(); | |||
| @@ -115,7 +122,7 @@ namespace Server | |||
| game.ClearAllLists(); | |||
| mwr?.Flush(); | |||
| if (options.ResultFileName != DefaultArgumentOptions.FileName) | |||
| SaveGameResult(options.ResultFileName + ".json"); | |||
| SaveGameResult(options.ResultFileName.EndsWith(".json") ? options.ResultFileName : options.ResultFileName + ".json"); | |||
| SendGameResult(); | |||
| this.endGameSem.Release(); | |||
| } | |||
| @@ -158,17 +165,39 @@ namespace Server | |||
| break; | |||
| } | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| lock (spetatorJoinLock) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| // 若此时观战者加入,则死锁,所以需要 spetatorJoinLock | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| } | |||
| private bool playerDeceased(int playerID) | |||
| { | |||
| game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| try | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| foreach (Character character in game.GameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (character.PlayerID == playerID && character.PlayerState == PlayerStateType.Deceased) return true; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| game.GameMap.GameObjLockDict[GameObjType.Character].ExitReadLock(); | |||
| } | |||
| return false; | |||
| } | |||
| public int[] GetScore() | |||
| public override int[] GetScore() | |||
| { | |||
| int[] score = new int[2]; // 0代表Student,1代表Tricker | |||
| game.GameMap.GameObjLockDict[GameObjType.Character].EnterReadLock(); | |||
| @@ -222,9 +251,6 @@ namespace Server | |||
| int[] score = GetScore(); | |||
| msg.StudentScore = score[0]; | |||
| msg.TrickerScore = score[1]; | |||
| //msg.GateOpened | |||
| //msg.HiddenGateRefreshed | |||
| //msg.HiddenGateOpened | |||
| return msg; | |||
| } | |||
| @@ -1,24 +1,40 @@ | |||
| using Protobuf; | |||
| using Gaming; | |||
| using Grpc.Core; | |||
| using Playback; | |||
| using System; | |||
| using System.Threading; | |||
| using Protobuf; | |||
| using System.Collections.Concurrent; | |||
| using Timothy.FrameRateTask; | |||
| using Gaming; | |||
| using Grpc.Core; | |||
| namespace Server | |||
| { | |||
| class PlaybackServer : AvailableService.AvailableServiceBase | |||
| class PlaybackServer : ServerBase | |||
| { | |||
| protected readonly ArgumentOptions options; | |||
| private int[,] teamScore; | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object semaDictLock = new(); | |||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| // private object semaDictLock = new(); | |||
| private MessageToClient? currentGameInfo = new(); | |||
| private MessageOfObj currentMapMsg = new(); | |||
| private uint spectatorMinPlayerID = 2023; | |||
| private List<uint> spectatorList = new List<uint>(); | |||
| // private List<uint> spectatorList = new List<uint>(); | |||
| public int TeamCount => options.TeamCount; | |||
| private MessageWriter? mwr = null; | |||
| private object spetatorJoinLock = new(); | |||
| protected object spectatorLock = new object(); | |||
| protected bool isSpectatorJoin = false; | |||
| protected bool IsSpectatorJoin | |||
| { | |||
| get | |||
| { | |||
| lock (spectatorLock) | |||
| return isSpectatorJoin; | |||
| } | |||
| set | |||
| { | |||
| lock (spectatorLock) | |||
| isSpectatorJoin = value; | |||
| } | |||
| } | |||
| private bool IsGaming { get; set; } | |||
| private int[] finalScore; | |||
| public int[] FinalScore | |||
| @@ -28,6 +44,7 @@ namespace Server | |||
| return finalScore; | |||
| } | |||
| } | |||
| public override int[] GetScore() => FinalScore; | |||
| public PlaybackServer(ArgumentOptions options) | |||
| { | |||
| this.options = options; | |||
| @@ -38,18 +55,21 @@ namespace Server | |||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | |||
| { | |||
| if (request.PlayerId >= spectatorMinPlayerID) | |||
| Console.WriteLine($"AddPlayer: {request.PlayerId}"); | |||
| if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false) | |||
| { | |||
| // 观战模式 | |||
| uint tp = (uint)request.PlayerId; | |||
| if (!spectatorList.Contains(tp)) | |||
| lock (spetatorJoinLock) // 具体原因见另一个上锁的地方 | |||
| { | |||
| spectatorList.Add(tp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| lock (semaDictLock) | |||
| if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)))) | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| IsSpectatorJoin = true; | |||
| } | |||
| else | |||
| { | |||
| Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}"); | |||
| return; | |||
| } | |||
| } | |||
| do | |||
| @@ -63,15 +83,33 @@ namespace Server | |||
| //Console.WriteLine("Send!"); | |||
| } | |||
| } | |||
| catch (InvalidOperationException) | |||
| { | |||
| if (semaDict.TryRemove(request.PlayerId, out var semas)) | |||
| { | |||
| try | |||
| { | |||
| semas.Item1.Release(); | |||
| semas.Item2.Release(); | |||
| } | |||
| catch { } | |||
| Console.WriteLine($"The spectator {request.PlayerId} exited"); | |||
| return; | |||
| } | |||
| } | |||
| catch (Exception) | |||
| { | |||
| //Console.WriteLine(ex); | |||
| // Console.WriteLine(ex); | |||
| } | |||
| finally | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| try | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| } | |||
| catch { } | |||
| } | |||
| } while (IsGaming == true); | |||
| } while (IsGaming); | |||
| return; | |||
| } | |||
| } | |||
| @@ -79,6 +117,16 @@ namespace Server | |||
| public void ReportGame(MessageToClient? msg) | |||
| { | |||
| currentGameInfo = msg; | |||
| if (currentGameInfo != null && currentGameInfo.GameState == GameState.GameStart) | |||
| { | |||
| currentMapMsg = currentGameInfo.ObjMessage[0]; | |||
| } | |||
| if (currentGameInfo != null && IsSpectatorJoin) | |||
| { | |||
| currentGameInfo.ObjMessage.Add(currentMapMsg); | |||
| IsSpectatorJoin = false; | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| @@ -91,7 +139,7 @@ namespace Server | |||
| } | |||
| } | |||
| public void WaitForGame() | |||
| public override void WaitForEnd() | |||
| { | |||
| try | |||
| { | |||
| @@ -1,15 +1,55 @@ | |||
| using Grpc.Core; | |||
| using CommandLine; | |||
| using Grpc.Core; | |||
| using Protobuf; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using CommandLine; | |||
| namespace Server | |||
| { | |||
| public class Program | |||
| { | |||
| /// <summary> | |||
| /// Generated by http://www.network-science.de/ascii/ with font "standard" | |||
| /// </summary> | |||
| const string welcome = | |||
| @" | |||
| _____ _ _ _ _ _ ___ __ | |||
| |_ _| | | | | | | / \ |_ _/ /_ | |||
| | | | |_| | | | |/ _ \ | | '_ \ | |||
| | | | _ | |_| / ___ \ | | (_) | | |||
| |_| |_| |_|\___/_/ \_\___\___/ | |||
| ____ _ _ ____ _ _ _ | |||
| / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||
| | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||
| | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||
| \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||
| "; | |||
| // 特别说明:在 .NET 7 及以上,C# 支持新的多行字符串,允许多行前面缩进,因此可以不必再定格写字符串, | |||
| // 即升级 .NET 版本后可以改为如下的: | |||
| // const string welcome = """ | |||
| // | |||
| // _____ _ _ _ _ _ ___ __ | |||
| // |_ _| | | | | | | / \ |_ _/ /_ | |||
| // | | | |_| | | | |/ _ \ | | '_ \ | |||
| // | | | _ | |_| / ___ \ | | (_) | | |||
| // |_| |_| |_|\___/_/ \_\___\___/ | |||
| // | |||
| // ____ _ _ ____ _ _ _ | |||
| // / ___|_ __ __ _ __| |_ _ __ _| |_ ___ / ___(_)_ __| |___| | | |||
| // | | _| '__/ _` |/ _` | | | |/ _` | __/ _ \ | | _| | '__| / __| | | |||
| // | |_| | | | (_| | (_| | |_| | (_| | || __/_ | |_| | | | | \__ \_| | |||
| // \____|_| \__,_|\__,_|\__,_|\__,_|\__\___( ) \____|_|_| |_|___(_) | |||
| // | |||
| // | |||
| // """; // 将以此结束符为基准开始缩进(但 Python 没有这个 feature,差评 x) | |||
| static ServerBase CreateServer(ArgumentOptions options) | |||
| { | |||
| return options.Playback ? new PlaybackServer(options) : new GameServer(options); | |||
| } | |||
| static int Main(string[] args) | |||
| { | |||
| foreach (var arg in args) | |||
| @@ -26,63 +66,36 @@ namespace Server | |||
| return 1; | |||
| } | |||
| if (options.StartLockFile == DefaultArgumentOptions.FileName) | |||
| { | |||
| Console.WriteLine(welcome); | |||
| } | |||
| Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); | |||
| if (options.Playback) | |||
| try | |||
| { | |||
| try | |||
| var server = CreateServer(options); | |||
| Grpc.Core.Server rpcServer = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| PlaybackServer? playbackServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| Services = { AvailableService.BindService(playbackServer) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| Services = { AvailableService.BindService(server) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| rpcServer.Start(); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| playbackServer.WaitForGame(); | |||
| Console.WriteLine("Server end!"); | |||
| server.ShutdownAsync().Wait(); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| server.WaitForEnd(); | |||
| Console.WriteLine("Server end!"); | |||
| rpcServer.ShutdownAsync().Wait(); | |||
| Thread.Sleep(50); | |||
| Console.WriteLine(""); | |||
| Console.WriteLine("=================== Final Score ===================="); | |||
| Console.WriteLine($"Studnet: {playbackServer.FinalScore[0]}"); | |||
| Console.WriteLine($"Tricker: {playbackServer.FinalScore[1]}"); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| Thread.Sleep(50); | |||
| Console.WriteLine(""); | |||
| Console.WriteLine("=================== Final Score ===================="); | |||
| Console.WriteLine($"Studnet: {server.GetScore()[0]}"); | |||
| Console.WriteLine($"Tricker: {server.GetScore()[1]}"); | |||
| } | |||
| else | |||
| catch (Exception ex) | |||
| { | |||
| try | |||
| { | |||
| GameServer? gameServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| Services = { AvailableService.BindService(gameServer) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| gameServer.WaitForEnd(); | |||
| Console.WriteLine("Server end!"); | |||
| server.ShutdownAsync().Wait(); | |||
| Thread.Sleep(50); | |||
| Console.WriteLine(""); | |||
| Console.WriteLine("=================== Final Score ===================="); | |||
| Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}"); | |||
| Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}"); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Server": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030" | |||
| "commandLineArgs": "--port 8888 --studentCount 4 --trickerCount 1 --resultFileName result --gameTimeInSecond 551 --fileName video" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,21 +1,14 @@ | |||
| using Grpc.Core; | |||
| using Protobuf; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using GameClass.GameObj; | |||
| using Gaming; | |||
| using GameClass.GameObj; | |||
| using Grpc.Core; | |||
| using Preparation.Utility; | |||
| using Playback; | |||
| using Newtonsoft.Json; | |||
| using Newtonsoft.Json.Linq; | |||
| using Preparation.Interface; | |||
| using Protobuf; | |||
| namespace Server | |||
| { | |||
| public partial class GameServer : AvailableService.AvailableServiceBase | |||
| partial class GameServer : ServerBase | |||
| { | |||
| private int playerCountNow = 0; | |||
| protected object spectatorLock = new object(); | |||
| protected bool isSpectatorJoin = false; | |||
| protected bool IsSpectatorJoin | |||
| @@ -56,20 +49,21 @@ namespace Server | |||
| { | |||
| Console.WriteLine($"AddPlayer: {request.PlayerId}"); | |||
| if (request.PlayerId >= spectatorMinPlayerID && options.AllowSpectator == true) | |||
| if (request.PlayerId >= spectatorMinPlayerID && options.NotAllowSpectator == false) | |||
| { | |||
| // 观战模式 | |||
| uint tp = (uint)request.PlayerId; | |||
| if (!spectatorList.Contains(tp)) | |||
| lock (spetatorJoinLock) // 具体原因见另一个上锁的地方 | |||
| { | |||
| spectatorList.Add(tp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| lock (semaDictLock) | |||
| if (semaDict.TryAdd(request.PlayerId, (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)))) | |||
| { | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| IsSpectatorJoin = true; | |||
| } | |||
| else | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| Console.WriteLine($"Duplicated Spectator ID {request.PlayerId}"); | |||
| return; | |||
| } | |||
| IsSpectatorJoin = true; | |||
| } | |||
| do | |||
| { | |||
| @@ -82,13 +76,31 @@ namespace Server | |||
| //Console.WriteLine("Send!"); | |||
| } | |||
| } | |||
| catch (InvalidOperationException) | |||
| { | |||
| if (semaDict.TryRemove(request.PlayerId, out var semas)) | |||
| { | |||
| try | |||
| { | |||
| semas.Item1.Release(); | |||
| semas.Item2.Release(); | |||
| } | |||
| catch { } | |||
| Console.WriteLine($"The spectator {request.PlayerId} exited"); | |||
| return; | |||
| } | |||
| } | |||
| catch (Exception) | |||
| { | |||
| //Console.WriteLine(ex); | |||
| // Console.WriteLine(ex); | |||
| } | |||
| finally | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| try | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| } | |||
| catch { } | |||
| } | |||
| } while (game.GameMap.Timer.IsGaming); | |||
| return; | |||
| @@ -117,14 +129,17 @@ namespace Server | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| bool start = false; | |||
| Console.WriteLine($"Id: {request.PlayerId} joins."); | |||
| lock (semaDictLock) | |||
| lock (spetatorJoinLock) // 为了保证绝对安全,还是加上这个锁吧 | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| start = (semaDict.Count - spectatorList.Count) == playerNum; | |||
| if (semaDict.TryAdd(request.PlayerId, temp)) | |||
| { | |||
| start = Interlocked.Increment(ref playerCountNow) == playerNum; | |||
| } | |||
| } | |||
| if (start) StartGame(); | |||
| } | |||
| bool exitFlag = false; | |||
| do | |||
| { | |||
| semaDict[request.PlayerId].Item1.Wait(); | |||
| @@ -138,7 +153,11 @@ namespace Server | |||
| } | |||
| catch (Exception) | |||
| { | |||
| //Console.WriteLine(ex); | |||
| if (!exitFlag) | |||
| { | |||
| Console.WriteLine($"The client {request.PlayerId} exited"); | |||
| exitFlag = true; | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| @@ -194,7 +213,7 @@ namespace Server | |||
| public override Task<BoolRes> SendMessage(SendMsg request, ServerCallContext context) | |||
| { | |||
| var boolRes = new BoolRes(); | |||
| if (request.PlayerId >= spectatorMinPlayerID) | |||
| if (request.PlayerId >= spectatorMinPlayerID || playerDeceased((int)request.PlayerId)) | |||
| { | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| @@ -205,30 +224,67 @@ namespace Server | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| if (request.Message.Length > 256) | |||
| switch (request.MessageCase) | |||
| { | |||
| case SendMsg.MessageOneofCase.TextMessage: | |||
| { | |||
| if (request.TextMessage.Length > 256) | |||
| { | |||
| #if DEBUG | |||
| Console.WriteLine("Message string is too long!"); | |||
| Console.WriteLine("Text message string is too long!"); | |||
| #endif | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| else | |||
| { | |||
| MessageOfNews news = new(); | |||
| news.News = request.Message; | |||
| news.FromId = request.PlayerId; | |||
| news.ToId = request.ToPlayerId; | |||
| lock (newsLock) | |||
| { | |||
| currentNews.Add(news); | |||
| } | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| MessageOfNews news = new(); | |||
| news.TextMessage = request.TextMessage; | |||
| news.FromId = request.PlayerId; | |||
| news.ToId = request.ToPlayerId; | |||
| lock (newsLock) | |||
| { | |||
| currentNews.Add(news); | |||
| } | |||
| #if DEBUG | |||
| Console.WriteLine(news.TextMessage); | |||
| #endif | |||
| boolRes.ActSuccess = true; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| case SendMsg.MessageOneofCase.BinaryMessage: | |||
| { | |||
| if (request.BinaryMessage.Length > 256) | |||
| { | |||
| #if DEBUG | |||
| Console.WriteLine(news.News); | |||
| Console.WriteLine("Binary message string is too long!"); | |||
| #endif | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| MessageOfNews news = new(); | |||
| news.BinaryMessage = request.BinaryMessage; | |||
| news.FromId = request.PlayerId; | |||
| news.ToId = request.ToPlayerId; | |||
| lock (newsLock) | |||
| { | |||
| currentNews.Add(news); | |||
| } | |||
| #if DEBUG | |||
| Console.Write("BinaryMessageLength: "); | |||
| Console.WriteLine(news.BinaryMessage.Length); | |||
| #endif | |||
| boolRes.ActSuccess = true; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| default: | |||
| { | |||
| boolRes.ActSuccess = false; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| } | |||
| boolRes.ActSuccess = true; | |||
| return Task.FromResult(boolRes); | |||
| } | |||
| public override Task<BoolRes> PickProp(PropMsg request, ServerCallContext context) | |||
| { | |||
| @@ -10,10 +10,10 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.53.0"> | |||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | |||
| <PrivateAssets>all</PrivateAssets> | |||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
| </PackageReference> | |||
| @@ -0,0 +1,14 @@ | |||
| using Grpc.Core; | |||
| using Playback; | |||
| using Protobuf; | |||
| using System.Collections.Concurrent; | |||
| using Timothy.FrameRateTask; | |||
| namespace Server | |||
| { | |||
| abstract class ServerBase : AvailableService.AvailableServiceBase | |||
| { | |||
| public abstract void WaitForEnd(); | |||
| public abstract int[] GetScore(); | |||
| } | |||
| } | |||
| @@ -1,6 +1,2 @@ | |||
| @echo off | |||
| ::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true | |||
| ping -n 2 127.0.0.1 > NUL | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 | |||
| start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --port 8888 --fileName .\ladder1.thuaipb --playback --playbackSpeed 4.0 | |||
| @@ -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 | |||
| @@ -7,7 +7,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||