| @@ -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; | |||
| @@ -14,6 +14,10 @@ | |||
| #include <queue> | |||
| #include <atomic> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| class Logic; | |||
| class Communication | |||
| @@ -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; // 每帧毫秒数 | |||
| @@ -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的线程 | |||
| @@ -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 | |||
| { | |||
| @@ -13,6 +13,10 @@ | |||
| #include "structures.h" | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| namespace AssistFunction | |||
| { | |||
| @@ -5,7 +5,7 @@ | |||
| #include "constants.h" | |||
| // 注意不要使用conio.h,Windows.h等非标准库 | |||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 | |||
| extern const bool asynchronous = false; | |||
| // 选手需要依次将player0到player4的职业在这里定义 | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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) : | |||
| @@ -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) : | |||
| @@ -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; | |||
| @@ -605,9 +588,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()) | |||
| @@ -689,6 +679,7 @@ bool Logic::TryConnection() | |||
| 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); | |||
| } | |||
| @@ -4,6 +4,10 @@ | |||
| #include <tclap/CmdLine.h> | |||
| #include <array> | |||
| #undef GetMessage | |||
| #undef SendMessage | |||
| #undef PeekMessage | |||
| #ifdef _MSC_VER | |||
| #pragma warning(disable : 4996) | |||
| #endif | |||
| @@ -25,6 +25,8 @@ | |||
| #include <grpcpp/impl/codegen/stub_options.h> | |||
| #include <grpcpp/impl/codegen/sync_stream.h> | |||
| #undef SendMessage | |||
| namespace protobuf | |||
| { | |||
| @@ -8,7 +8,7 @@ import time | |||
| class Setting: | |||
| # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 | |||
| @staticmethod | |||
| def asynchronous() -> bool: | |||
| return False | |||
| @@ -301,13 +301,13 @@ class SummonGolem: | |||
| class CommonAttackOfTricker: | |||
| BulletBombRange = 0 | |||
| BulletAttackRange = Constants.Constants.basicAttackShortRange | |||
| BulletAttackRange = Constants.basicAttackShortRange | |||
| ap = Constants.basicApOfTricker | |||
| Speed = Constants.basicBulletMoveSpeed | |||
| IsRemoteAttack = False | |||
| CastTime = BulletAttackRange * 1000 / Speed | |||
| Backswing =Constants.basicBackswing | |||
| RecoveryFromHit =Constants.basicRecoveryFromHit | |||
| Backswing = Constants.basicBackswing | |||
| RecoveryFromHit = Constants.basicRecoveryFromHit | |||
| cd = Constants.basicBackswing | |||
| maxBulletNum = 1 | |||
| @@ -340,4 +340,4 @@ class JumpyDumpty: | |||
| BulletAttackRange = Constants.basicRemoteAttackRange * 2 | |||
| ap = (int)(Constants.basicApOfTricker* 0.6) | |||
| Speed = Constants.basicBulletMoveSpeed* 43 / 37 | |||
| IsRemoteAttack = False | |||
| 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 | |||
| @@ -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使用的接口 | |||
| @@ -263,7 +263,8 @@ class Logic(ILogic): | |||
| return self.__comm.EndAllAction(self.__playerID) | |||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | |||
| return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap) | |||
| with self.__mtxState: | |||
| return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap) | |||
| # Logic内部逻辑 | |||
| def __TryConnection(self) -> bool: | |||
| @@ -286,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": | |||
| @@ -318,15 +310,6 @@ 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!") | |||
| @@ -467,9 +450,21 @@ 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) | |||
| @@ -511,9 +506,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") | |||
| @@ -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 -o& | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0& | |||
| # 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& | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d& | |||
| @@ -4,10 +4,10 @@ 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 & | |||
| 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 & | |||
| 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 | |||
| @@ -20,9 +20,9 @@ do | |||
| j=$((i - 1)) | |||
| 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 & | |||
| 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 & | |||
| 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 | |||
| @@ -33,9 +33,9 @@ do | |||
| j=$((i - 1)) | |||
| 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 & | |||
| 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 & | |||
| 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 | |||
| @@ -59,13 +59,13 @@ | |||
| 下面的 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`:查询当前游戏状态 | |||
| @@ -88,6 +88,14 @@ | |||
| void PrintSelfInfo() const; | |||
| ~~~ | |||
| ### 部分属性解释 stuctures.h | |||
| ~~~c++ | |||
| struct Player | |||
| { | |||
| std::vector<PropType> props;//大小固定为3,空的位置为NullPropType | |||
| } | |||
| ~~~ | |||
| ## 接口一览 | |||
| ~~~c++ | |||
| // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 | |||
| @@ -72,14 +72,13 @@ | |||
| 下面的 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 | |||
| #### 其他 | |||
| @@ -105,6 +104,13 @@ | |||
| def PrintSelfInfo(self) -> None: | |||
| ~~~ | |||
| ### 部分属性解释 stuctures.h | |||
| ~~~python | |||
| class Player: | |||
| def __init__(self, **kwargs) -> None: | |||
| self.prop: List[PropType] = []//大小固定为3,空的位置为NullPropType | |||
| ~~~ | |||
| ## 接口一览 | |||
| ~~~python | |||
| @@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000} | |||
| $$ | |||
| - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | |||
| - 隐藏校门刷新点的区域类型始终为隐藏校门 | |||
| - 任何格子的区域类型(PlaceType)始终不变,所有隐藏校门刷新点的区域类型均为隐藏校门 | |||
| ### 人物 | |||
| - 人物直径为800 | |||
| @@ -360,10 +360,12 @@ $$ | |||
| ### 信息相关 | |||
| - Bgm在没有符合条件的情况下,值为0。 | |||
| - 不能给自己发信息 | |||
| ### 技能 | |||
| - CD冷却计时是在开始使用技能的瞬间开始的 | |||
| - Klee的小炸弹有碰撞体积 | |||
| - 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 | |||
| ### 职业 | |||
| - 学生职业可以重复选择 | |||
| @@ -22,11 +22,6 @@ Q:卡死在第一帧不动 | |||
| A:大概率是你的代码死循环了 | |||
| Q: 怎么开始游戏? | |||
| A: | |||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||
| ## C++ | |||
| Q:显示API项目已卸载 | |||
| @@ -54,6 +49,15 @@ Q:CAPI编译不通过(第二种) | |||
| A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||
| Q:编译好慢啊 | |||
| A: | |||
| 1. 尽量不要改其他文件,甚至连点下保存都别点 | |||
| 2. 不要点重新生成,要点生成 | |||
| 3. 开启下图选项 | |||
|  | |||
| ## Python | |||
| ### grpc版本更新失败 | |||
| @@ -76,4 +80,4 @@ A:初赛结束会调数值及机制,增加新角色 | |||
| Q:初赛后会修改什么呢? | |||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息 | |||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM | |||
| @@ -14,7 +14,7 @@ | |||
| - 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模式 | |||
| @@ -26,7 +26,7 @@ | |||
| - 首先在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`执行比赛代码 | |||
| @@ -5,7 +5,7 @@ | |||
| <TargetFramework>net6.0-windows</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <UseWPF>true</UseWPF> | |||
| <ApplicationIcon>EESAST.ico</ApplicationIcon> | |||
| <ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| @@ -13,7 +13,7 @@ | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Content Include="EESAST.ico" /> | |||
| <Content Include="eesast_software_trans_enlarged.ico" /> | |||
| </ItemGroup> | |||
| <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"/> | |||
| @@ -34,8 +34,6 @@ namespace Client | |||
| { | |||
| public MainWindow() | |||
| { | |||
| unitHeight = unitWidth = unit = 13; | |||
| bonusflag = true; | |||
| timer = new DispatcherTimer | |||
| { | |||
| Interval = new TimeSpan(50000) // 每50ms刷新一次 | |||
| @@ -60,6 +58,10 @@ 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(); | |||
| } | |||
| @@ -194,6 +196,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 +271,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,37 +285,23 @@ namespace Client | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| } | |||
| private void ZoomMap() | |||
| { | |||
| 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)) | |||
| { | |||
| mapPatches[i, j].Width = UpperLayerOfMap.ActualWidth / 50; | |||
| mapPatches[i, j].Height = UpperLayerOfMap.ActualHeight / 50; | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private void ZoomMapAtFirst() | |||
| 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; | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -330,7 +319,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]) | |||
| { | |||
| @@ -697,7 +686,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; | |||
| @@ -709,60 +698,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++) | |||
| { | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| StatusBarsOfSurvivor[i].NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| } | |||
| if (StatusBarsOfHunter != null) | |||
| { | |||
| StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | |||
| } | |||
| if (StatusBarsOfCircumstance != null) | |||
| StatusBarsOfCircumstance.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||
| // 完成窗口信息更新 | |||
| if (StatusBarsOfSurvivor != null) | |||
| { | |||
| for (int i = 0; i < GameData.numOfStudent; i++) | |||
| { | |||
| StatusBarsOfSurvivor[i].SetFontSize(12 * unitFontsize); | |||
| } | |||
| } | |||
| if (StatusBarsOfHunter != null) | |||
| StatusBarsOfHunter.SetFontSize(12 * unitFontsize); | |||
| if (StatusBarsOfCircumstance != null) | |||
| 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); | |||
| @@ -770,7 +751,6 @@ namespace Client | |||
| if (!hasDrawed && mapFlag) | |||
| { | |||
| DrawMap(); | |||
| ZoomMapAtFirst(); | |||
| } | |||
| foreach (var data in listOfHuman) | |||
| { | |||
| @@ -791,7 +771,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), | |||
| @@ -943,7 +923,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), | |||
| @@ -965,7 +945,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), | |||
| @@ -987,7 +967,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), | |||
| @@ -1009,7 +989,7 @@ namespace Client | |||
| { | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| HorizontalAlignment = HorizontalAlignment.Left, | |||
| @@ -1042,7 +1022,7 @@ namespace Client | |||
| isEmergencyOpened = true; | |||
| TextBox icon = new() | |||
| { | |||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||
| FontSize = 9 * unitFontsize, | |||
| Width = unitWidth, | |||
| Height = unitHeight, | |||
| Text = Convert.ToString("🔓"), | |||
| @@ -1056,8 +1036,6 @@ namespace Client | |||
| UpperLayerOfMap.Children.Add(icon); | |||
| } | |||
| } | |||
| //} | |||
| ZoomMap(); | |||
| } | |||
| catch (Exception exc) | |||
| { | |||
| @@ -1440,7 +1418,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[,] { | |||
| @@ -1502,7 +1479,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 }; | |||
| @@ -1,6 +1,5 @@ | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| namespace GameClass.GameObj | |||
| { | |||
| @@ -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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,5 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Numerics; | |||
| using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| @@ -6,7 +6,6 @@ using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| namespace Gaming | |||
| { | |||
| @@ -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; | |||
| } | |||
| @@ -5,6 +5,7 @@ using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using Gaming; | |||
| using Grpc.Core; | |||
| using System.Collections.Concurrent; | |||
| namespace Server | |||
| { | |||
| @@ -12,13 +13,31 @@ namespace Server | |||
| { | |||
| protected readonly ArgumentOptions options; | |||
| private int[,] teamScore; | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = 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>(); | |||
| 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 | |||
| @@ -38,18 +57,20 @@ namespace Server | |||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | |||
| { | |||
| if (request.PlayerId >= spectatorMinPlayerID) | |||
| 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; | |||
| } | |||
| } | |||
| do | |||
| @@ -63,15 +84,32 @@ 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"); | |||
| } | |||
| } | |||
| 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) | |||
| { | |||
| @@ -1,7 +1,6 @@ | |||
| using Grpc.Core; | |||
| using Protobuf; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using System; | |||
| using System.Net.Http.Headers; | |||
| using Gaming; | |||
| @@ -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 | |||