| @@ -4,6 +4,10 @@ | |||||
| #include "API.h" | #include "API.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| class IAI | class IAI | ||||
| { | { | ||||
| public: | public: | ||||
| @@ -22,6 +22,10 @@ | |||||
| #include "structures.h" | #include "structures.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| const constexpr int numOfGridPerCell = 1000; | const constexpr int numOfGridPerCell = 1000; | ||||
| class IAI; | class IAI; | ||||
| @@ -14,6 +14,10 @@ | |||||
| #include <queue> | #include <queue> | ||||
| #include <atomic> | #include <atomic> | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| class Logic; | class Logic; | ||||
| class Communication | class Communication | ||||
| @@ -8,6 +8,10 @@ | |||||
| #include <utility> | #include <utility> | ||||
| #include <optional> | #include <optional> | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| template<typename Elem> | template<typename Elem> | ||||
| class ConcurrentQueue | class ConcurrentQueue | ||||
| { | { | ||||
| @@ -6,6 +6,10 @@ | |||||
| #define SCCI static const constexpr inline | #define SCCI static const constexpr inline | ||||
| #endif | #endif | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| namespace Constants | namespace Constants | ||||
| { | { | ||||
| SCCI int frameDuration = 50; // 每帧毫秒数 | SCCI int frameDuration = 50; // 每帧毫秒数 | ||||
| @@ -31,6 +31,10 @@ | |||||
| #include "Communication.h" | #include "Communication.h" | ||||
| #include "ConcurrentQueue.hpp" | #include "ConcurrentQueue.hpp" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| // 封装了通信组件和对AI对象进行操作 | // 封装了通信组件和对AI对象进行操作 | ||||
| class Logic : public ILogic | class Logic : public ILogic | ||||
| { | { | ||||
| @@ -49,9 +53,6 @@ private: | |||||
| THUAI6::TrickerType trickerType; | THUAI6::TrickerType trickerType; | ||||
| THUAI6::StudentType studentType; | THUAI6::StudentType studentType; | ||||
| // GUID信息 | |||||
| std::vector<int64_t> playerGUIDs; | |||||
| std::unique_ptr<IGameTimer> timer; | std::unique_ptr<IGameTimer> timer; | ||||
| std::thread tAI; // 用于运行AI的线程 | std::thread tAI; // 用于运行AI的线程 | ||||
| @@ -9,6 +9,10 @@ | |||||
| #include "structures.h" | #include "structures.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| // 存储场上的状态 | // 存储场上的状态 | ||||
| struct State | struct State | ||||
| { | { | ||||
| @@ -8,6 +8,10 @@ | |||||
| #include <vector> | #include <vector> | ||||
| #include <string> | #include <string> | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| namespace THUAI6 | namespace THUAI6 | ||||
| { | { | ||||
| @@ -13,6 +13,10 @@ | |||||
| #include "structures.h" | #include "structures.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| namespace AssistFunction | namespace AssistFunction | ||||
| { | { | ||||
| @@ -5,7 +5,7 @@ | |||||
| #include "constants.h" | #include "constants.h" | ||||
| // 注意不要使用conio.h,Windows.h等非标准库 | // 注意不要使用conio.h,Windows.h等非标准库 | ||||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 | |||||
| extern const bool asynchronous = false; | extern const bool asynchronous = false; | ||||
| // 选手需要依次将player0到player4的职业在这里定义 | // 选手需要依次将player0到player4的职业在这里定义 | ||||
| @@ -1,6 +1,11 @@ | |||||
| #include <optional> | #include <optional> | ||||
| #include "AI.h" | #include "AI.h" | ||||
| #include "API.h" | #include "API.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| #define PI 3.14159265358979323846 | #define PI 3.14159265358979323846 | ||||
| int StudentAPI::GetFrameCount() const | int StudentAPI::GetFrameCount() const | ||||
| @@ -5,6 +5,10 @@ | |||||
| #include <mutex> | #include <mutex> | ||||
| #include <condition_variable> | #include <condition_variable> | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| using grpc::ClientContext; | using grpc::ClientContext; | ||||
| Communication::Communication(std::string sIP, std::string sPort) | Communication::Communication(std::string sIP, std::string sPort) | ||||
| @@ -4,6 +4,11 @@ | |||||
| #include "API.h" | #include "API.h" | ||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| #include "structures.h" | #include "structures.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| #define PI 3.14159265358979323846 | #define PI 3.14159265358979323846 | ||||
| StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) : | StudentDebugAPI::StudentDebugAPI(ILogic& logic, bool file, bool print, bool warnOnly, int64_t playerID) : | ||||
| @@ -8,6 +8,10 @@ | |||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| #include "Communication.h" | #include "Communication.h" | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| extern const bool asynchronous; | extern const bool asynchronous; | ||||
| Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student) : | 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: | case THUAI6::GameState::GameStart: | ||||
| logger->info("Game Start!"); | 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()) | for (const auto& item : clientMsg.obj_message()) | ||||
| if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) | if (Proto2THUAI6::messageOfObjDict[item.message_of_obj_case()] == THUAI6::MessageOfObj::MapMessage) | ||||
| @@ -368,16 +361,6 @@ void Logic::ProcessMessage() | |||||
| break; | break; | ||||
| case THUAI6::GameState::GameRunning: | 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); | LoadBuffer(clientMsg); | ||||
| break; | break; | ||||
| @@ -605,9 +588,16 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message) | |||||
| bufferState->props.clear(); | bufferState->props.clear(); | ||||
| bufferState->bullets.clear(); | bufferState->bullets.clear(); | ||||
| bufferState->bombedBullets.clear(); | bufferState->bombedBullets.clear(); | ||||
| bufferState->guids.clear(); | |||||
| logger->debug("Buffer cleared!"); | 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()); | bufferState->gameInfo = Proto2THUAI6::Protobuf2THUAI6GameInfo(message.all_message()); | ||||
| LoadBufferSelf(message); | LoadBufferSelf(message); | ||||
| for (const auto& item : message.obj_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 | 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); | return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap); | ||||
| } | } | ||||
| @@ -4,6 +4,10 @@ | |||||
| #include <tclap/CmdLine.h> | #include <tclap/CmdLine.h> | ||||
| #include <array> | #include <array> | ||||
| #undef GetMessage | |||||
| #undef SendMessage | |||||
| #undef PeekMessage | |||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||
| #pragma warning(disable : 4996) | #pragma warning(disable : 4996) | ||||
| #endif | #endif | ||||
| @@ -25,6 +25,8 @@ | |||||
| #include <grpcpp/impl/codegen/stub_options.h> | #include <grpcpp/impl/codegen/stub_options.h> | ||||
| #include <grpcpp/impl/codegen/sync_stream.h> | #include <grpcpp/impl/codegen/sync_stream.h> | ||||
| #undef SendMessage | |||||
| namespace protobuf | namespace protobuf | ||||
| { | { | ||||
| @@ -8,7 +8,7 @@ import time | |||||
| class Setting: | class Setting: | ||||
| # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||||
| # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 | |||||
| @staticmethod | @staticmethod | ||||
| def asynchronous() -> bool: | def asynchronous() -> bool: | ||||
| return False | return False | ||||
| @@ -301,13 +301,13 @@ class SummonGolem: | |||||
| class CommonAttackOfTricker: | class CommonAttackOfTricker: | ||||
| BulletBombRange = 0 | BulletBombRange = 0 | ||||
| BulletAttackRange = Constants.Constants.basicAttackShortRange | |||||
| BulletAttackRange = Constants.basicAttackShortRange | |||||
| ap = Constants.basicApOfTricker | ap = Constants.basicApOfTricker | ||||
| Speed = Constants.basicBulletMoveSpeed | Speed = Constants.basicBulletMoveSpeed | ||||
| IsRemoteAttack = False | IsRemoteAttack = False | ||||
| CastTime = BulletAttackRange * 1000 / Speed | CastTime = BulletAttackRange * 1000 / Speed | ||||
| Backswing =Constants.basicBackswing | |||||
| RecoveryFromHit =Constants.basicRecoveryFromHit | |||||
| Backswing = Constants.basicBackswing | |||||
| RecoveryFromHit = Constants.basicRecoveryFromHit | |||||
| cd = Constants.basicBackswing | cd = Constants.basicBackswing | ||||
| maxBulletNum = 1 | maxBulletNum = 1 | ||||
| @@ -340,4 +340,4 @@ class JumpyDumpty: | |||||
| BulletAttackRange = Constants.basicRemoteAttackRange * 2 | BulletAttackRange = Constants.basicRemoteAttackRange * 2 | ||||
| ap = (int)(Constants.basicApOfTricker* 0.6) | ap = (int)(Constants.basicApOfTricker* 0.6) | ||||
| Speed = Constants.basicBulletMoveSpeed* 43 / 37 | Speed = Constants.basicBulletMoveSpeed* 43 / 37 | ||||
| IsRemoteAttack = False | |||||
| IsRemoteAttack = False | |||||
| @@ -3,6 +3,7 @@ from typing import List, Union, Callable, Tuple | |||||
| import threading | import threading | ||||
| import logging | import logging | ||||
| import copy | import copy | ||||
| import platform | |||||
| import proto.MessageType_pb2 as MessageType | import proto.MessageType_pb2 as MessageType | ||||
| import proto.Message2Server_pb2 as Message2Server | import proto.Message2Server_pb2 as Message2Server | ||||
| import proto.Message2Clients_pb2 as Message2Clients | import proto.Message2Clients_pb2 as Message2Clients | ||||
| @@ -22,7 +23,6 @@ class Logic(ILogic): | |||||
| # ID | # ID | ||||
| self.__playerID: int = playerID | self.__playerID: int = playerID | ||||
| self.__playerGUIDs: List[int] = [] | |||||
| self.__playerType: THUAI6.PlayerType = playerType | self.__playerType: THUAI6.PlayerType = playerType | ||||
| @@ -214,7 +214,7 @@ class Logic(ILogic): | |||||
| def GetPlayerGUIDs(self) -> List[int]: | def GetPlayerGUIDs(self) -> List[int]: | ||||
| with self.__mtxState: | with self.__mtxState: | ||||
| return copy.deepcopy(self.__playerGUIDs) | |||||
| return copy.deepcopy(self.__currentState.guids) | |||||
| # IStudentAPI使用的接口 | # IStudentAPI使用的接口 | ||||
| @@ -263,7 +263,8 @@ class Logic(ILogic): | |||||
| return self.__comm.EndAllAction(self.__playerID) | return self.__comm.EndAllAction(self.__playerID) | ||||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | 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内部逻辑 | # Logic内部逻辑 | ||||
| def __TryConnection(self) -> bool: | def __TryConnection(self) -> bool: | ||||
| @@ -286,15 +287,6 @@ class Logic(ILogic): | |||||
| if self.__gameState == THUAI6.GameState.GameStart: | if self.__gameState == THUAI6.GameState.GameStart: | ||||
| # 读取玩家的GUID | # 读取玩家的GUID | ||||
| self.__logger.info("Game start!") | 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: | for obj in clientMsg.obj_message: | ||||
| if obj.WhichOneof("message_of_obj") == "map_message": | if obj.WhichOneof("message_of_obj") == "map_message": | ||||
| @@ -318,15 +310,6 @@ class Logic(ILogic): | |||||
| elif self.__gameState == THUAI6.GameState.GameRunning: | elif self.__gameState == THUAI6.GameState.GameRunning: | ||||
| # 读取玩家的GUID | # 读取玩家的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) | self.__LoadBuffer(clientMsg) | ||||
| else: | else: | ||||
| self.__logger.error("Unknown GameState!") | self.__logger.error("Unknown GameState!") | ||||
| @@ -467,9 +450,21 @@ class Logic(ILogic): | |||||
| self.__bufferState.students.clear() | self.__bufferState.students.clear() | ||||
| self.__bufferState.trickers.clear() | self.__bufferState.trickers.clear() | ||||
| self.__bufferState.props.clear() | self.__bufferState.props.clear() | ||||
| self.__bufferState.bullets.clear() | |||||
| self.__bufferState.bombedBullets.clear() | |||||
| self.__bufferState.guids.clear() | |||||
| self.__logger.debug("Buffer cleared!") | 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( | self.__bufferState.gameInfo = Proto2THUAI6.Protobuf2THUAI6GameInfo( | ||||
| message.all_message) | message.all_message) | ||||
| self.__LoadBufferSelf(message) | self.__LoadBufferSelf(message) | ||||
| for item in message.obj_message: | for item in message.obj_message: | ||||
| self.__LoadBufferCase(item) | self.__LoadBufferCase(item) | ||||
| @@ -511,9 +506,16 @@ class Logic(ILogic): | |||||
| formatter = logging.Formatter( | formatter = logging.Formatter( | ||||
| "[%(name)s] [%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", '%H:%M:%S') | "[%(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( | fileHandler = logging.FileHandler(os.path.dirname( | ||||
| os.path.dirname(os.path.realpath(__file__))) + "/logs/logic" + str(self.__playerID) + "-log.txt", "w+", encoding="utf-8") | 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 | #!/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 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 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 | playback_dir=/usr/local/playback | ||||
| if [ $EXPOSED -eq 1 ]; then | 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=$! | server_pid=$! | ||||
| else | 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=$! | server_pid=$! | ||||
| fi | fi | ||||
| sleep 5 | sleep 5 | ||||
| @@ -20,9 +20,9 @@ do | |||||
| j=$((i - 1)) | j=$((i - 1)) | ||||
| if [ -f "./player$i.py" ]; then | if [ -f "./player$i.py" ]; then | ||||
| cp -f ./player$i.py $python_dir/AI.py | 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 | 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 | else | ||||
| echo "ERROR. $i is not found." | echo "ERROR. $i is not found." | ||||
| fi | fi | ||||
| @@ -33,9 +33,9 @@ do | |||||
| j=$((i - 1)) | j=$((i - 1)) | ||||
| if [ -f "./player$i.py" ]; then | if [ -f "./player$i.py" ]; then | ||||
| cp -f ./player$i.py $python_dir/AI.py | 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 | 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 | else | ||||
| echo "ERROR. $i is not found." | echo "ERROR. $i is not found." | ||||
| fi | fi | ||||
| @@ -59,13 +59,13 @@ | |||||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | ||||
| - `THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY)` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | - `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 GetChestProgress(int32_t cellX, int32_t cellY) const`:查询特定位置箱子开启进度 | ||||
| - `int32_t GetGateProgress(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 GetClassroomProgress(int32_t cellX, int32_t cellY) const`:查询特定位置教室作业完成进度 | ||||
| - `int32_t GetDoorProgress(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`:查询当前游戏状态 | - `std::shared_ptr<const THUAI6::GameInfo> GetGameInfo() const`:查询当前游戏状态 | ||||
| @@ -88,6 +88,14 @@ | |||||
| void PrintSelfInfo() const; | void PrintSelfInfo() const; | ||||
| ~~~ | ~~~ | ||||
| ### 部分属性解释 stuctures.h | |||||
| ~~~c++ | |||||
| struct Player | |||||
| { | |||||
| std::vector<PropType> props;//大小固定为3,空的位置为NullPropType | |||||
| } | |||||
| ~~~ | |||||
| ## 接口一览 | ## 接口一览 | ||||
| ~~~c++ | ~~~c++ | ||||
| // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 | // 指挥本角色进行移动,`timeInMilliseconds` 为移动时间,单位为毫秒;`angleInRadian` 表示移动的方向,单位是弧度,使用极坐标——竖直向下方向为 x 轴,水平向右方向为 y 轴 | ||||
| @@ -72,14 +72,13 @@ | |||||
| 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | 下面的 CellX 和 CellY 指的是地图格数,而非绝对坐标。 | ||||
| - `def GetPlaceType(self, cellX: int, cellY: int) -> THUAI6.PlaceType` :返回某一位置场地种类信息。场地种类详见 structure.h 。 | - `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 GetChestProgress(self, cellX: int, cellY: int) -> int`:查询特定位置箱子开启进度 | ||||
| - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | - `def GetGateProgress(self, cellX: int, cellY: int) -> int`:查询特定位置校门开启进度 | ||||
| - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | - `def GetClassroomProgress(self, cellX: int, cellY: int) -> int`:查询特定位置教室作业完成进度 | ||||
| - `def GetDoorProgress(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: | def PrintSelfInfo(self) -> None: | ||||
| ~~~ | ~~~ | ||||
| ### 部分属性解释 stuctures.h | |||||
| ~~~python | |||||
| class Player: | |||||
| def __init__(self, **kwargs) -> None: | |||||
| self.prop: List[PropType] = []//大小固定为3,空的位置为NullPropType | |||||
| ~~~ | |||||
| ## 接口一览 | ## 接口一览 | ||||
| ~~~python | ~~~python | ||||
| @@ -64,7 +64,7 @@ CellX=\frac{x}{1000},CellY=\frac{y}{1000} | |||||
| $$ | $$ | ||||
| - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | - 格子有对应区域类型:陆地、墙、草地、教室、校门、隐藏校门、门、窗、箱子 | ||||
| - 隐藏校门刷新点的区域类型始终为隐藏校门 | |||||
| - 任何格子的区域类型(PlaceType)始终不变,所有隐藏校门刷新点的区域类型均为隐藏校门 | |||||
| ### 人物 | ### 人物 | ||||
| - 人物直径为800 | - 人物直径为800 | ||||
| @@ -360,10 +360,12 @@ $$ | |||||
| ### 信息相关 | ### 信息相关 | ||||
| - Bgm在没有符合条件的情况下,值为0。 | - Bgm在没有符合条件的情况下,值为0。 | ||||
| - 不能给自己发信息 | |||||
| ### 技能 | ### 技能 | ||||
| - CD冷却计时是在开始使用技能的瞬间开始的 | - CD冷却计时是在开始使用技能的瞬间开始的 | ||||
| - Klee的小炸弹有碰撞体积 | - Klee的小炸弹有碰撞体积 | ||||
| - 除了切换攻击类型的技能,在不能执行指令的状态下(包括翻窗)均不能使用技能 | |||||
| ### 职业 | ### 职业 | ||||
| - 学生职业可以重复选择 | - 学生职业可以重复选择 | ||||
| @@ -22,11 +22,6 @@ Q:卡死在第一帧不动 | |||||
| A:大概率是你的代码死循环了 | A:大概率是你的代码死循环了 | ||||
| Q: 怎么开始游戏? | |||||
| A: | |||||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||||
| ## C++ | ## C++ | ||||
| Q:显示API项目已卸载 | Q:显示API项目已卸载 | ||||
| @@ -54,6 +49,15 @@ Q:CAPI编译不通过(第二种) | |||||
| A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | ||||
| Q:编译好慢啊 | |||||
| A: | |||||
| 1. 尽量不要改其他文件,甚至连点下保存都别点 | |||||
| 2. 不要点重新生成,要点生成 | |||||
| 3. 开启下图选项 | |||||
|  | |||||
| ## Python | ## Python | ||||
| ### grpc版本更新失败 | ### grpc版本更新失败 | ||||
| @@ -76,4 +80,4 @@ A:初赛结束会调数值及机制,增加新角色 | |||||
| Q:初赛后会修改什么呢? | Q:初赛后会修改什么呢? | ||||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息 | |||||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息;提供不同格式的信息传递;增加职业;优化游戏结束条件;角色毅力值清空不在使捣蛋鬼产生BGM | |||||
| @@ -14,7 +14,7 @@ | |||||
| - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | ||||
| - Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。 | - Linux:首先自行安装`gRPC`,具体方法可以参考官方教程https://grpc.io/docs/languages/cpp/quickstart/。 | ||||
| - 然后在`CAPI\cpp\API\src\AI.cpp`中编写代码 | - 然后在`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::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态 | ||||
| - 选手可以在`AI.cpp`内`void AI::play`外新增函数和变量 | - 选手可以在`AI.cpp`内`void AI::play`外新增函数和变量 | ||||
| - Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式 | - Windows:然后用Visual Studio打开`CAPI\cpp\CAPI.sln`编译,注意使用Debug模式 | ||||
| @@ -26,7 +26,7 @@ | |||||
| - 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件 | - 首先在Python环境下运行`GeneratePythonProto.cmd`,以安装必要的包、并生成对应的grpc python文件 | ||||
| - 然后在`CAPI\python\PyAPI\AI.py`中编写代码 | - 然后在`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::play(IStudentAPI& api)`或`AI::play(ITrickerAPI& api)`(除非执行该函数超过一帧50ms),获取的信息都是这一帧的开始的状态 | ||||
| - 选手可以在`AI.py`内新增函数和变量 | - 选手可以在`AI.py`内新增函数和变量 | ||||
| - Windows:最后通过运行`RunPython.cmd`执行比赛代码 | - Windows:最后通过运行`RunPython.cmd`执行比赛代码 | ||||
| @@ -5,7 +5,7 @@ | |||||
| <TargetFramework>net6.0-windows</TargetFramework> | <TargetFramework>net6.0-windows</TargetFramework> | ||||
| <Nullable>enable</Nullable> | <Nullable>enable</Nullable> | ||||
| <UseWPF>true</UseWPF> | <UseWPF>true</UseWPF> | ||||
| <ApplicationIcon>EESAST.ico</ApplicationIcon> | |||||
| <ApplicationIcon>eesast_software_trans_enlarged.ico</ApplicationIcon> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -13,7 +13,7 @@ | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <Content Include="EESAST.ico" /> | |||||
| <Content Include="eesast_software_trans_enlarged.ico" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -5,7 +5,8 @@ | |||||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||
| xmlns:local="clr-namespace:Client" | xmlns:local="clr-namespace:Client" | ||||
| mc:Ignorable="d" | 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> | <Window.Resources> | ||||
| <ImageBrush x:Key="Logo" ImageSource="Logo.png"/> | <ImageBrush x:Key="Logo" ImageSource="Logo.png"/> | ||||
| @@ -34,8 +34,6 @@ namespace Client | |||||
| { | { | ||||
| public MainWindow() | public MainWindow() | ||||
| { | { | ||||
| unitHeight = unitWidth = unit = 13; | |||||
| bonusflag = true; | |||||
| timer = new DispatcherTimer | timer = new DispatcherTimer | ||||
| { | { | ||||
| Interval = new TimeSpan(50000) // 每50ms刷新一次 | Interval = new TimeSpan(50000) // 每50ms刷新一次 | ||||
| @@ -60,6 +58,10 @@ namespace Client | |||||
| listOfGate = new List<MessageOfGate>(); | listOfGate = new List<MessageOfGate>(); | ||||
| listOfHiddenGate = new List<MessageOfHiddenGate>(); | listOfHiddenGate = new List<MessageOfHiddenGate>(); | ||||
| WindowStartupLocation = WindowStartupLocation.CenterScreen; | WindowStartupLocation = WindowStartupLocation.CenterScreen; | ||||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||||
| unitFontsize = unit / 13; | |||||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||||
| ReactToCommandline(); | ReactToCommandline(); | ||||
| } | } | ||||
| @@ -194,6 +196,7 @@ namespace Client | |||||
| 0 => PlayerType.NullPlayerType, | 0 => PlayerType.NullPlayerType, | ||||
| 1 => PlayerType.StudentPlayer, | 1 => PlayerType.StudentPlayer, | ||||
| 2 => PlayerType.TrickerPlayer, | 2 => PlayerType.TrickerPlayer, | ||||
| _ => PlayerType.NullPlayerType | |||||
| }; | }; | ||||
| playerMsg.PlayerType = playerType; | playerMsg.PlayerType = playerType; | ||||
| if (Convert.ToInt64(comInfo[3]) == 1) | if (Convert.ToInt64(comInfo[3]) == 1) | ||||
| @@ -268,9 +271,9 @@ namespace Client | |||||
| { | { | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 10, | |||||
| Width = 20, | |||||
| Height = 20, | |||||
| FontSize = 7 * unitFontsize, | |||||
| Width = unitWidth, | |||||
| Height = unitHeight, | |||||
| Text = text, | Text = text, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| VerticalAlignment = VerticalAlignment.Top, | VerticalAlignment = VerticalAlignment.Top, | ||||
| @@ -282,37 +285,23 @@ namespace Client | |||||
| UpperLayerOfMap.Children.Add(icon); | 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 i = 0; i < 50; i++) | ||||
| { | { | ||||
| for (int j = 0; j < 50; j++) | for (int j = 0; j < 50; j++) | ||||
| { | { | ||||
| if (mapPatches[i, j] != null) | 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].HorizontalAlignment = HorizontalAlignment.Left; | ||||
| mapPatches[i, j].VerticalAlignment = VerticalAlignment.Top; | 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, | Height = unitHeight, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| VerticalAlignment = VerticalAlignment.Top, | 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]) | switch (defaultMap[i, j]) | ||||
| { | { | ||||
| @@ -697,7 +686,7 @@ namespace Client | |||||
| MaxButton.Content = "🗖"; | MaxButton.Content = "🗖"; | ||||
| foreach (var obj in listOfHuman) | 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)); | IStudentType occupation = (IStudentType)OccupationFactory.FindIOccupation(Transformation.ToStudentType(obj.StudentType)); | ||||
| totalLife[obj.PlayerId] = occupation.MaxHp; | totalLife[obj.PlayerId] = occupation.MaxHp; | ||||
| @@ -709,60 +698,52 @@ namespace Client | |||||
| coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | coolTime[i, obj.PlayerId] = iActiveSkill.SkillCD; | ||||
| ++i; | ++i; | ||||
| } | } | ||||
| isDataFixed[obj.PlayerId] = true; | |||||
| } | } | ||||
| } | } | ||||
| foreach (var obj in listOfButcher) | 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) | 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) | if (StatusBarsOfHunter != null) | ||||
| { | { | ||||
| StatusBarsOfHunter.SetFontSize(12 * UpperLayerOfMap.ActualHeight / 650); | |||||
| StatusBarsOfHunter.NewData(totalLife, totalDeath, coolTime); | 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) | if (!isClientStocked) | ||||
| { | { | ||||
| unit = Math.Sqrt(UpperLayerOfMap.ActualHeight * UpperLayerOfMap.ActualWidth) / 50; | |||||
| unitHeight = UpperLayerOfMap.ActualHeight / 50; | |||||
| unitWidth = UpperLayerOfMap.ActualWidth / 50; | |||||
| try | 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(); | 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) | foreach (var data in listOfAll) | ||||
| { | { | ||||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | ||||
| @@ -770,7 +751,6 @@ namespace Client | |||||
| if (!hasDrawed && mapFlag) | if (!hasDrawed && mapFlag) | ||||
| { | { | ||||
| DrawMap(); | DrawMap(); | ||||
| ZoomMapAtFirst(); | |||||
| } | } | ||||
| foreach (var data in listOfHuman) | foreach (var data in listOfHuman) | ||||
| { | { | ||||
| @@ -791,7 +771,7 @@ namespace Client | |||||
| icon.Fill = Brushes.Gray; | icon.Fill = Brushes.Gray; | ||||
| TextBox num = new() | TextBox num = new() | ||||
| { | { | ||||
| FontSize = 7 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 7 * unitFontsize, | |||||
| Width = 2 * radiusTimes * unitWidth, | Width = 2 * radiusTimes * unitWidth, | ||||
| Height = 2 * radiusTimes * unitHeight, | Height = 2 * radiusTimes * unitHeight, | ||||
| Text = Convert.ToString(data.PlayerId), | Text = Convert.ToString(data.PlayerId), | ||||
| @@ -943,7 +923,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfFixedGenerator); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -965,7 +945,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedChest); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -987,7 +967,7 @@ namespace Client | |||||
| int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | int deg = (int)(100.0 * data.Progress / Preparation.Utility.GameData.degreeOfOpenedDoorway); | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 8 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 8 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString(deg), | Text = Convert.ToString(deg), | ||||
| @@ -1009,7 +989,7 @@ namespace Client | |||||
| { | { | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 9 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| HorizontalAlignment = HorizontalAlignment.Left, | HorizontalAlignment = HorizontalAlignment.Left, | ||||
| @@ -1042,7 +1022,7 @@ namespace Client | |||||
| isEmergencyOpened = true; | isEmergencyOpened = true; | ||||
| TextBox icon = new() | TextBox icon = new() | ||||
| { | { | ||||
| FontSize = 9 * UpperLayerOfMap.ActualHeight / 650, | |||||
| FontSize = 9 * unitFontsize, | |||||
| Width = unitWidth, | Width = unitWidth, | ||||
| Height = unitHeight, | Height = unitHeight, | ||||
| Text = Convert.ToString("🔓"), | Text = Convert.ToString("🔓"), | ||||
| @@ -1056,8 +1036,6 @@ namespace Client | |||||
| UpperLayerOfMap.Children.Add(icon); | UpperLayerOfMap.Children.Add(icon); | ||||
| } | } | ||||
| } | } | ||||
| //} | |||||
| ZoomMap(); | |||||
| } | } | ||||
| catch (Exception exc) | catch (Exception exc) | ||||
| { | { | ||||
| @@ -1440,7 +1418,6 @@ namespace Client | |||||
| private MessageOfTricker? butcher = null; | private MessageOfTricker? butcher = null; | ||||
| private bool humanOrButcher;//true for human | private bool humanOrButcher;//true for human | ||||
| private bool bonusflag; | |||||
| private bool mapFlag = false; | private bool mapFlag = false; | ||||
| private bool hasDrawed = false; | private bool hasDrawed = false; | ||||
| public int[,] defaultMap = new int[,] { | public int[,] defaultMap = new int[,] { | ||||
| @@ -1502,7 +1479,8 @@ namespace Client | |||||
| bool isSpectatorMode = false; | bool isSpectatorMode = false; | ||||
| bool isEmergencyOpened = false; | bool isEmergencyOpened = false; | ||||
| bool isEmergencyDrawed = 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 radiusTimes = 1.0 * Preparation.Utility.GameData.characterRadius / Preparation.Utility.GameData.numOfPosGridPerCell; | ||||
| const double bulletRadiusTimes = 1.0 * GameData.bulletRadius / 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 }; | 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.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using System; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| @@ -3,7 +3,6 @@ using System.Threading; | |||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using System; | using System; | ||||
| using GameClass.GameObj; | |||||
| namespace GameClass.GameObj | namespace GameClass.GameObj | ||||
| { | { | ||||
| @@ -13,29 +12,47 @@ namespace GameClass.GameObj | |||||
| private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | private readonly Dictionary<uint, XY> birthPointList; // 出生点列表 | ||||
| public Dictionary<uint, XY> BirthPointList => birthPointList; | public Dictionary<uint, XY> BirthPointList => birthPointList; | ||||
| private object lockForNum = new(); | |||||
| private readonly object lockForNum = new(); | |||||
| private void WhenStudentNumChange() | private void WhenStudentNumChange() | ||||
| { | { | ||||
| if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) | if (numOfDeceasedStudent + numOfEscapedStudent == GameData.numOfStudent) | ||||
| { | { | ||||
| Timer.IsGaming = false; | 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 | 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; | break; | ||||
| } | } | ||||
| } | } | ||||
| finally | 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; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Numerics; | |||||
| using System.Threading; | using System.Threading; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| @@ -6,7 +6,6 @@ using Preparation.Utility; | |||||
| using GameEngine; | using GameEngine; | ||||
| using Preparation.Interface; | using Preparation.Interface; | ||||
| using Timothy.FrameRateTask; | using Timothy.FrameRateTask; | ||||
| using System.Numerics; | |||||
| namespace Gaming | namespace Gaming | ||||
| { | { | ||||
| @@ -17,44 +17,31 @@ namespace Gaming | |||||
| switch (activeSkillType) | switch (activeSkillType) | ||||
| { | { | ||||
| case ActiveSkillType.BecomeInvisible: | case ActiveSkillType.BecomeInvisible: | ||||
| BecomeInvisible(character); | |||||
| break; | |||||
| return BecomeInvisible(character); | |||||
| case ActiveSkillType.UseKnife: | case ActiveSkillType.UseKnife: | ||||
| UseKnife(character); | |||||
| break; | |||||
| return UseKnife(character); | |||||
| case ActiveSkillType.Howl: | case ActiveSkillType.Howl: | ||||
| Howl(character); | |||||
| break; | |||||
| return Howl(character); | |||||
| case ActiveSkillType.CanBeginToCharge: | case ActiveSkillType.CanBeginToCharge: | ||||
| CanBeginToCharge(character); | |||||
| break; | |||||
| return CanBeginToCharge(character); | |||||
| case ActiveSkillType.Inspire: | case ActiveSkillType.Inspire: | ||||
| Inspire(character); | |||||
| break; | |||||
| return Inspire(character); | |||||
| case ActiveSkillType.Encourage: | case ActiveSkillType.Encourage: | ||||
| Encourage(character); | |||||
| break; | |||||
| return Encourage(character); | |||||
| case ActiveSkillType.Punish: | case ActiveSkillType.Punish: | ||||
| Punish(character); | |||||
| break; | |||||
| return Punish(character); | |||||
| case ActiveSkillType.JumpyBomb: | case ActiveSkillType.JumpyBomb: | ||||
| JumpyBomb(character); | |||||
| break; | |||||
| return JumpyBomb(character); | |||||
| case ActiveSkillType.WriteAnswers: | case ActiveSkillType.WriteAnswers: | ||||
| WriteAnswers(character); | |||||
| break; | |||||
| return WriteAnswers(character); | |||||
| case ActiveSkillType.SummonGolem: | case ActiveSkillType.SummonGolem: | ||||
| SummonGolem(character); | |||||
| break; | |||||
| return SummonGolem(character); | |||||
| case ActiveSkillType.UseRobot: | case ActiveSkillType.UseRobot: | ||||
| UseRobot(character); | |||||
| break; | |||||
| return UseRobot(character); | |||||
| case ActiveSkillType.Rouse: | case ActiveSkillType.Rouse: | ||||
| Rouse(character); | |||||
| break; | |||||
| return Rouse(character); | |||||
| case ActiveSkillType.ShowTime: | case ActiveSkillType.ShowTime: | ||||
| ShowTime(character); | |||||
| break; | |||||
| return ShowTime(character); | |||||
| default: | default: | ||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ using System.Threading; | |||||
| using Timothy.FrameRateTask; | using Timothy.FrameRateTask; | ||||
| using Gaming; | using Gaming; | ||||
| using Grpc.Core; | using Grpc.Core; | ||||
| using System.Collections.Concurrent; | |||||
| namespace Server | namespace Server | ||||
| { | { | ||||
| @@ -12,13 +13,31 @@ namespace Server | |||||
| { | { | ||||
| protected readonly ArgumentOptions options; | protected readonly ArgumentOptions options; | ||||
| private int[,] teamScore; | private int[,] teamScore; | ||||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||||
| private ConcurrentDictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||||
| private object semaDictLock = new(); | private object semaDictLock = new(); | ||||
| private MessageToClient? currentGameInfo = new(); | private MessageToClient? currentGameInfo = new(); | ||||
| private MessageOfObj currentMapMsg = new(); | |||||
| private uint spectatorMinPlayerID = 2023; | private uint spectatorMinPlayerID = 2023; | ||||
| private List<uint> spectatorList = new List<uint>(); | private List<uint> spectatorList = new List<uint>(); | ||||
| public int TeamCount => options.TeamCount; | public int TeamCount => options.TeamCount; | ||||
| private MessageWriter? mwr = null; | 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 bool IsGaming { get; set; } | ||||
| private int[] finalScore; | private int[] finalScore; | ||||
| public int[] FinalScore | public int[] FinalScore | ||||
| @@ -38,18 +57,20 @@ namespace Server | |||||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | 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 | do | ||||
| @@ -63,15 +84,32 @@ namespace Server | |||||
| //Console.WriteLine("Send!"); | //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) | catch (Exception) | ||||
| { | { | ||||
| //Console.WriteLine(ex); | |||||
| // Console.WriteLine(ex); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| semaDict[request.PlayerId].Item2.Release(); | |||||
| try | |||||
| { | |||||
| semaDict[request.PlayerId].Item2.Release(); | |||||
| } | |||||
| catch { } | |||||
| } | } | ||||
| } while (IsGaming == true); | |||||
| } while (IsGaming); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -79,6 +117,16 @@ namespace Server | |||||
| public void ReportGame(MessageToClient? msg) | public void ReportGame(MessageToClient? msg) | ||||
| { | { | ||||
| currentGameInfo = 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) | foreach (var kvp in semaDict) | ||||
| { | { | ||||
| @@ -1,7 +1,6 @@ | |||||
| using Grpc.Core; | using Grpc.Core; | ||||
| using Protobuf; | using Protobuf; | ||||
| using System.Threading; | using System.Threading; | ||||
| using Timothy.FrameRateTask; | |||||
| using System; | using System; | ||||
| using System.Net.Http.Headers; | using System.Net.Http.Headers; | ||||
| using Gaming; | using Gaming; | ||||
| @@ -1,6 +1,2 @@ | |||||
| @echo off | @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 | |||||