| @@ -0,0 +1,29 @@ | |||
| name: "docker" | |||
| on: | |||
| push: | |||
| branches: [main] | |||
| jobs: | |||
| upload_docker_images: | |||
| runs-on: ubuntu-latest | |||
| steps: | |||
| - uses: actions/checkout@v3 | |||
| - name: Log in to DockerHub | |||
| run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} | |||
| #- name: Build base docker image | |||
| # run: docker build -t ${{ secrets.DOCKER_USERNAME }}/thuai6_base:base -f ./dependency/Dockerfile/Dockerfile_base . | |||
| #- name: Push base image to DockerHub | |||
| # run: docker push ${{ secrets.DOCKER_USERNAME }}/thuai6_base:base | |||
| - name: Build cpp_compile docker image | |||
| run: docker build -t ${{ secrets.DOCKER_USERNAME }}/thuai6_cpp:latest -f ./dependency/Dockerfile/Dockerfile_cpp . | |||
| - name: Push cpp_compile image to DockerHub | |||
| run: docker push ${{ secrets.DOCKER_USERNAME }}/thuai6_cpp:latest | |||
| - name: Build run docker image | |||
| run: docker build -t ${{ secrets.DOCKER_USERNAME }}/thuai6_run:latest -f ./dependency/Dockerfile/Dockerfile_run . | |||
| - name: Push run image to DockerHub | |||
| run: docker push ${{ secrets.DOCKER_USERNAME }}/thuai6_run:latest | |||
| @@ -113,6 +113,9 @@ jobs: | |||
| name: my-artifact | |||
| path: ./THUAI6 | |||
| - name: Remove ReadMe.md | |||
| run: rm ./docs/README.md | |||
| - name: Markdown to PDF and HTML | |||
| uses: BaileyJM02/markdown-to-pdf@v1.2.0 | |||
| with: | |||
| @@ -145,7 +148,6 @@ jobs: | |||
| rm ./THUAI6/win/win64/WindowsBase.dll | |||
| rm ./THUAI6/win/win64/Debug/grpc_csharp_ext.x64.dll | |||
| rm ./THUAI6/win/win64/grpc_csharp_ext.x64.dll | |||
| rm -r ./THUAI6/win/CAPI/cpp/grpc | |||
| rm -r ./THUAI6/win/CAPI/cpp/spdlog | |||
| rm -r ./THUAI6/win/CAPI/cpp/tclap | |||
| @@ -162,7 +164,6 @@ jobs: | |||
| rm ./THUAI6/osx/osx64/Debug/System.*.dll | |||
| rm ./THUAI6/win/win64/System.*.dll | |||
| rm ./THUAI6/win/win64/Debug/System.*.dll | |||
| rm ./THUAI6/linux/linux64/*.so | |||
| rm ./THUAI6/linux/linux64/Debug/*.so | |||
| @@ -83,6 +83,8 @@ public: | |||
| virtual bool Attack(double angle) = 0; | |||
| virtual std::vector<int64_t> GetPlayerGUIDs() const = 0; | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const = 0; | |||
| }; | |||
| class IAPI | |||
| @@ -162,6 +164,8 @@ public: | |||
| return grid / numOfGridPerCell; | |||
| } | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0; | |||
| // 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| virtual void Print(std::string str) const = 0; | |||
| @@ -271,6 +275,8 @@ public: | |||
| std::future<bool> Graduate() override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override | |||
| { | |||
| } | |||
| @@ -356,6 +362,8 @@ public: | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override | |||
| { | |||
| } | |||
| @@ -439,6 +447,8 @@ public: | |||
| std::future<bool> Graduate() override; | |||
| [[nodiscard]] virtual std::shared_ptr<const THUAI6::Student> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override; | |||
| void PrintStudent() const override; | |||
| void PrintTricker() const override; | |||
| @@ -509,6 +519,8 @@ public: | |||
| std::future<bool> Attack(double angleInRadian) override; | |||
| [[nodiscard]] std::shared_ptr<const THUAI6::Tricker> GetSelfInfo() const override; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY) const override; | |||
| void Print(std::string str) const override; | |||
| void PrintStudent() const override; | |||
| void PrintTricker() const override; | |||
| @@ -161,6 +161,8 @@ private: | |||
| // 等待 | |||
| void Wait() noexcept; | |||
| [[nodiscard]] bool HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const override; | |||
| public: | |||
| // 构造函数还需要传更多参数,有待补充 | |||
| Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::TrickerType tricker, THUAI6::StudentType student); | |||
| @@ -3,7 +3,7 @@ | |||
| #include <array> | |||
| #include "AI.h" | |||
| #include "constants.h" | |||
| //注意不要使用conio.h,Windows.h等非标准库 | |||
| // 注意不要使用conio.h,Windows.h等非标准库 | |||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| extern const bool asynchronous = false; | |||
| @@ -419,6 +419,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerAPI::GetSelfInfo() const | |||
| return logic.TrickerGetSelfInfo(); | |||
| } | |||
| bool StudentAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| bool TrickerAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| void StudentAPI::Play(IAI& ai) | |||
| { | |||
| ai.play(*this); | |||
| @@ -631,6 +631,18 @@ std::shared_ptr<const THUAI6::Tricker> TrickerDebugAPI::GetSelfInfo() const | |||
| return logic.TrickerGetSelfInfo(); | |||
| } | |||
| bool StudentDebugAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| bool TrickerDebugAPI::HaveView(int gridX, int gridY) const | |||
| { | |||
| auto selfInfo = GetSelfInfo(); | |||
| return logic.HaveView(gridX, gridY, selfInfo->x, selfInfo->y, selfInfo->viewRange); | |||
| } | |||
| void StudentDebugAPI::Print(std::string str) const | |||
| { | |||
| logger->info(str); | |||
| @@ -564,7 +564,7 @@ void Logic::LoadBufferCase(const protobuf::MessageOfObj& item) | |||
| } | |||
| case THUAI6::MessageOfObj::GateMessage: | |||
| { | |||
| if (!AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap)) | |||
| if (AssistFunction::HaveView(viewRange, x, y, item.gate_message().x(), item.gate_message().y(), bufferState->gameMap)) | |||
| { | |||
| auto pos = std::make_pair(AssistFunction::GridToCell(item.gate_message().x()), AssistFunction::GridToCell(item.gate_message().y())); | |||
| if (bufferState->mapInfo->gateState.count(pos) == 0) | |||
| @@ -618,6 +618,7 @@ void Logic::LoadBuffer(const protobuf::MessageToClient& message) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mtxState); | |||
| std::swap(currentState, bufferState); | |||
| counterState = counterBuffer; | |||
| logger->info("Update State!"); | |||
| } | |||
| freshed = true; | |||
| @@ -686,6 +687,11 @@ bool Logic::TryConnection() | |||
| return pComm->TryConnection(playerID); | |||
| } | |||
| bool Logic::HaveView(int gridX, int gridY, int selfX, int selfY, int viewRange) const | |||
| { | |||
| return AssistFunction::HaveView(viewRange, selfX, selfY, gridX, gridY, currentState->gameMap); | |||
| } | |||
| void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool file, bool print, bool warnOnly) | |||
| { | |||
| // 建立日志组件 | |||
| @@ -695,9 +701,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool f | |||
| fileLogger->set_pattern(pattern); | |||
| printLogger->set_pattern(pattern); | |||
| if (file) | |||
| { | |||
| fileLogger->set_level(spdlog::level::trace); | |||
| } | |||
| fileLogger->set_level(spdlog::level::debug); | |||
| else | |||
| fileLogger->set_level(spdlog::level::off); | |||
| if (print) | |||
| @@ -11,7 +11,7 @@ class Setting: | |||
| # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||
| @staticmethod | |||
| def asynchronous() -> bool: | |||
| return True | |||
| return False | |||
| # 选手需要依次将player0到player4的职业都定义 | |||
| @staticmethod | |||
| @@ -132,6 +132,9 @@ class StudentAPI(IStudentAPI, IGameTimer): | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| def Print(self, cont: str) -> None: | |||
| @@ -305,6 +308,9 @@ class TrickerAPI(ITrickerAPI, IGameTimer): | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| def Print(self, cont: str) -> None: | |||
| @@ -16,19 +16,13 @@ class BoolErrorHandler(IErrorHandler): | |||
| class Communication: | |||
| __THUAI6Stub: Services.AvailableServiceStub | |||
| __haveNewMessage: bool | |||
| __message2Client: Message2Clients.MessageToClient | |||
| __mtxMessage: threading.Lock | |||
| __cvMessage: threading.Condition | |||
| def __init__(self, sIP: str, sPort: str): | |||
| aim = sIP + ':' + sPort | |||
| channel = grpc.insecure_channel(aim) | |||
| self.__THUAI6Stub = Services.AvailableServiceStub(channel) | |||
| self.__haveNewMessage = False | |||
| self.__cvMessage = threading.Condition() | |||
| self.__message2Client: Message2Clients.MessageToClient | |||
| def Move(self, time: int, angle: float, playerID: int) -> bool: | |||
| try: | |||
| @@ -304,6 +304,9 @@ class StudentDebugAPI(IStudentAPI, IGameTimer): | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| def Print(self, cont: str) -> None: | |||
| @@ -756,6 +759,9 @@ class TrickerDebugAPI(ITrickerAPI, IGameTimer): | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| return self.__logic.GetGameInfo() | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| return self.__logic.HaveView(gridX, gridY, self.GetSelfInfo().x, self.GetSelfInfo().y, self.GetSelfInfo().viewRange) | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| def Print(self, cont: str) -> None: | |||
| @@ -154,6 +154,10 @@ class ILogic(metaclass=ABCMeta): | |||
| def StartRouseMate(self, mateID: int) -> bool: | |||
| pass | |||
| @abstractmethod | |||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | |||
| pass | |||
| class IAPI(metaclass=ABCMeta): | |||
| @@ -314,6 +318,10 @@ class IAPI(metaclass=ABCMeta): | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| pass | |||
| @abstractmethod | |||
| def HaveView(self, gridX: int, gridY: int) -> bool: | |||
| pass | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @abstractmethod | |||
| @@ -3,21 +3,33 @@ import PyAPI.structures as THUAI6 | |||
| class State: | |||
| def __init__(self, **kwargs) -> None: | |||
| self.teamScore = 0 | |||
| self.self = THUAI6.Student() | |||
| self.students = [] | |||
| self.trickers = [] | |||
| self.props = [] | |||
| self.gameMap = [] | |||
| self.bullets = [] | |||
| self.bombedBullets = [] | |||
| self.mapInfo = THUAI6.GameMap() | |||
| self.gameInfo = THUAI6.GameInfo() | |||
| self.guids = [] | |||
| teamScore: int | |||
| self: Union[THUAI6.Student, THUAI6.Tricker] | |||
| students: List[THUAI6.Student] = [] | |||
| trickers: List[THUAI6.Tricker] = [] | |||
| students: List[THUAI6.Student] | |||
| trickers: List[THUAI6.Tricker] | |||
| props: List[THUAI6.Prop] = [] | |||
| props: List[THUAI6.Prop] | |||
| gameMap: List[List[THUAI6.PlaceType]] = [] | |||
| gameMap: List[List[THUAI6.PlaceType]] | |||
| bullets: List[THUAI6.Bullet] = [] | |||
| bombedBullets: List[THUAI6.BombedBullet] = [] | |||
| bullets: List[THUAI6.Bullet] | |||
| bombedBullets: List[THUAI6.BombedBullet] | |||
| mapInfo: THUAI6.GameMap = THUAI6.GameMap() | |||
| mapInfo: THUAI6.GameMap | |||
| gameInfo: THUAI6.GameInfo = THUAI6.GameInfo() | |||
| gameInfo: THUAI6.GameInfo | |||
| guids: List[int] = [] | |||
| guids: List[int] | |||
| @@ -262,6 +262,9 @@ class Logic(ILogic): | |||
| self.__logger.debug("Called EndAllAction") | |||
| return self.__comm.EndAllAction(self.__playerID) | |||
| def HaveView(self, gridX: int, gridY: int, selfX: int, selfY: int, viewRange: int) -> bool: | |||
| return AssistFunction.HaveView(viewRange, selfX, selfY, gridX, gridY, self.__currentState.gameMap) | |||
| # Logic内部逻辑 | |||
| def __TryConnection(self) -> bool: | |||
| self.__logger.info("Try to connect to server...") | |||
| @@ -473,6 +476,7 @@ class Logic(ILogic): | |||
| if Setting.asynchronous(): | |||
| with self.__mtxState: | |||
| self.__currentState, self.__bufferState = self.__bufferState, self.__currentState | |||
| self.__counterState = self.__counterBuffer | |||
| self.__logger.info("Update state!") | |||
| self.__freshed = True | |||
| else: | |||
| @@ -143,85 +143,95 @@ class HiddenGateState(Enum): | |||
| class Player: | |||
| x: int | |||
| y: int | |||
| speed: int | |||
| viewRange: int | |||
| playerID: int | |||
| guid: int | |||
| radius: int | |||
| score: int | |||
| facingDirection: float | |||
| timeUntilSkillAvailable: List[float] = [] | |||
| playerType: PlayerType | |||
| prop: List[PropType] = [] | |||
| place: PlaceType | |||
| bulletType: BulletType | |||
| playerState: PlayerState | |||
| def __init__(self, **kwargs) -> None: | |||
| self.x: int = 0 | |||
| self.y: int = 0 | |||
| self.speed: int = 0 | |||
| self.viewRange: int = 0 | |||
| self.playerID: int = 0 | |||
| self.guid: int = 0 | |||
| self.radius: int = 0 | |||
| self.score: int = 0 | |||
| self.facingDirection: float = 0.0 | |||
| self.timeUntilSkillAvailable: List[float] = [] | |||
| self.playerType: PlayerType = PlayerType.NullPlayerType | |||
| self.prop: List[PropType] = [] | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.bulletType: BulletType = BulletType.NullBulletType | |||
| self.playerState: PlayerState = PlayerState.NullState | |||
| class Student(Player): | |||
| studentType: StudentType | |||
| determination: int | |||
| addiction: int | |||
| encourageProgress: int | |||
| rouseProgress: int | |||
| learningSpeed: int | |||
| encourageSpeed: int | |||
| dangerAlert: float | |||
| buff: List[StudentBuffType] = [] | |||
| def __init__(self, **kwargs) -> None: | |||
| super().__init__() | |||
| self.studentType: StudentType = StudentType.NullStudentType | |||
| self.determination: int = 0 | |||
| self.addiction: int = 0 | |||
| self.encourageProgress: int = 0 | |||
| self.rouseProgress: int = 0 | |||
| self.learningSpeed: int = 0 | |||
| self.encourageSpeed: int = 0 | |||
| self.dangerAlert: float = 0.0 | |||
| self.buff: List[StudentBuffType] = [] | |||
| class Tricker(Player): | |||
| trickerType: TrickerType | |||
| trickDesire: float | |||
| classVolume: float | |||
| buff: List[TrickerBuffType] = [] | |||
| def __init__(self, **kwargs) -> None: | |||
| super().__init__() | |||
| self.trickerType: TrickerType = TrickerType.NullTrickerType | |||
| self.trickDesire: float = 0.0 | |||
| self.classVolume: float = 0.0 | |||
| self.buff: List[TrickerBuffType] = [] | |||
| class Prop: | |||
| x: int | |||
| y: int | |||
| guid: int | |||
| type: PropType | |||
| place: PlaceType | |||
| facingDirection: float | |||
| def __init__(self, **kwargs) -> None: | |||
| self.x: int = 0 | |||
| self.y: int = 0 | |||
| self.guid: int = 0 | |||
| self.type: PropType = PropType.NullPropType | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.facingDirection: float = 0.0 | |||
| class Bullet: | |||
| bulletType: BulletType | |||
| x: int | |||
| y: int | |||
| facingDirection: float | |||
| guid: int | |||
| team: PlayerType | |||
| place: PlaceType | |||
| bombRange: float | |||
| speed: int | |||
| def __init__(self, **kwargs) -> None: | |||
| self.bulletType: BulletType = BulletType.NullBulletType | |||
| self.x: int = 0 | |||
| self.y: int = 0 | |||
| self.facingDirection: float = 0.0 | |||
| self.guid: int = 0 | |||
| self.team: PlayerType = PlayerType.NullPlayerType | |||
| self.place: PlaceType = PlaceType.NullPlaceType | |||
| self.bombRange: float = 0.0 | |||
| self.speed: int = 0 | |||
| class BombedBullet: | |||
| bulletType: BulletType | |||
| x: int | |||
| y: int | |||
| facingDirection: float | |||
| mappingID: int | |||
| bombRange: float | |||
| def __init__(self, **kwargs) -> None: | |||
| self.bulletType: BulletType = BulletType.NullBulletType | |||
| self.x: int = 0 | |||
| self.y: int = 0 | |||
| self.facingDirection: float = 0.0 | |||
| self.mappingID: int = 0 | |||
| self.bombRange: float = 0.0 | |||
| class GameMap: | |||
| classroomState: Dict[Tuple[int, int], int] = {} | |||
| gateState: Dict[Tuple[int, int], int] = {} | |||
| chestState: Dict[Tuple[int, int], int] = {} | |||
| doorState: Dict[Tuple[int, int], bool] = {} | |||
| doorProgress: Dict[Tuple[int, int], int] = {} | |||
| hiddenGateState: Dict[Tuple[int, int], HiddenGateState] = {} | |||
| def __init__(self, **kwargs) -> None: | |||
| self.classroomState: Dict[Tuple[int, int], int] = {} | |||
| self.gateState: Dict[Tuple[int, int], int] = {} | |||
| self.chestState: Dict[Tuple[int, int], int] = {} | |||
| self.doorState: Dict[Tuple[int, int], bool] = {} | |||
| self.doorProgress: Dict[Tuple[int, int], int] = {} | |||
| self.hiddenGateState: Dict[Tuple[int, int], HiddenGateState] = {} | |||
| class GameInfo: | |||
| gameTime: int | |||
| subjectFinished: int | |||
| studentGraduated: int | |||
| studentQuited: int | |||
| studentScore: int | |||
| trickerScore: int | |||
| def __init__(self, **kwargs) -> None: | |||
| self.gameTime: int = 0 | |||
| self.subjectFinished: int = 0 | |||
| self.studentGraduated: int = 0 | |||
| self.studentQuited: int = 0 | |||
| self.studentScore: int = 0 | |||
| self.trickerScore: int = 0 | |||
| @@ -177,12 +177,10 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.trickDesire = trickerMsg.trick_desire | |||
| tricker.classVolume = trickerMsg.class_volume | |||
| tricker.bulletType = Proto2THUAI6.bulletTypeDict[trickerMsg.bullet_type] | |||
| tricker.timeUntilSkillAvailable.clear() | |||
| for time in trickerMsg.time_until_skill_available: | |||
| tricker.timeUntilSkillAvailable.append(time) | |||
| tricker.place = Proto2THUAI6.placeTypeDict[trickerMsg.place] | |||
| tricker.playerState = Proto2THUAI6.playerStateDict[trickerMsg.player_state] | |||
| tricker.prop.clear() | |||
| for item in trickerMsg.prop: | |||
| tricker.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| tricker.trickerType = Proto2THUAI6.trickerTypeDict[trickerMsg.tricker_type] | |||
| @@ -190,7 +188,6 @@ class Proto2THUAI6(NoInstance): | |||
| tricker.playerID = trickerMsg.player_id | |||
| tricker.viewRange = trickerMsg.view_range | |||
| tricker.radius = trickerMsg.radius | |||
| tricker.buff.clear() | |||
| for buff in trickerMsg.buff: | |||
| tricker.buff.append(Proto2THUAI6.trickerBuffTypeDict[buff]) | |||
| tricker.playerType = THUAI6.PlayerType.TrickerPlayer | |||
| @@ -212,11 +209,9 @@ class Proto2THUAI6(NoInstance): | |||
| student.encourageProgress = studentMsg.treat_progress | |||
| student.rouseProgress = studentMsg.rescue_progress | |||
| student.dangerAlert = studentMsg.danger_alert | |||
| student.timeUntilSkillAvailable.clear() | |||
| for time in studentMsg.time_until_skill_available: | |||
| student.timeUntilSkillAvailable.append(time) | |||
| student.place = Proto2THUAI6.placeTypeDict[studentMsg.place] | |||
| student.prop.clear() | |||
| for item in studentMsg.prop: | |||
| student.prop.append(Proto2THUAI6.propTypeDict[item]) | |||
| student.studentType = Proto2THUAI6.studentTypeDict[studentMsg.student_type] | |||
| @@ -225,7 +220,6 @@ class Proto2THUAI6(NoInstance): | |||
| student.playerID = studentMsg.player_id | |||
| student.viewRange = studentMsg.view_range | |||
| student.radius = studentMsg.radius | |||
| student.buff.clear() | |||
| for buff in studentMsg.buff: | |||
| student.buff.append(Proto2THUAI6.studentBuffTypeDict[buff]) | |||
| student.playerType = THUAI6.PlayerType.StudentPlayer | |||
| @@ -1,7 +1,7 @@ | |||
| #!/usr/bin/env bash | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3 -d & | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4 -d & | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 0 -d -o& | |||
| python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 1 -o& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 2& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 3& | |||
| # python PyAPI/main.py -I 172.22.32.1 -P 8888 -p 4& | |||
| @@ -2,7 +2,7 @@ | |||
| python -m pip install -r ./CAPI/python/requirements.txt | |||
| mkdir -p proto | |||
| mkdir -p ./CAPI/python/proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto MessageType.proto | |||
| python -m grpc_tools.protoc -I./CAPI/proto/ --python_out=./CAPI/python/proto --pyi_out=./CAPI/python/proto Message2Clients.proto | |||
| @@ -0,0 +1,15 @@ | |||
| FROM python:3.9.16-bullseye | |||
| MAINTAINER eesast | |||
| WORKDIR /usr/local | |||
| RUN apt-get update && apt-get install --no-install-recommends -y gcc g++ make wget ca-certificates cmake autoconf automake libtool curl unzip git | |||
| RUN git clone -b v1.46.3 --depth 1 --shallow-submodules https://gitee.com/mirrors/grpc.git && wget -P . https://cloud.tsinghua.edu.cn/f/1f2713efd9e44255abd6/?dl=1 && mv 'index.html?dl=1' third_party.tar.gz | |||
| WORKDIR /usr/local/grpc | |||
| RUN rm -rf third_party && mv ../third_party.tar.gz . && tar -zxvf third_party.tar.gz && mkdir -p cmake/build | |||
| WORKDIR /usr/local/grpc/cmake/build | |||
| RUN cmake -DgRPC_INSTALL=ON \ | |||
| -DgRPC_BUILD_TESTS=OFF \ | |||
| ../.. && make -j$(nproc) && make install | |||
| WORKDIR /usr/local | |||
| RUN git clone https://gitee.com/mirrors/protobuf_source.git ./protobuf | |||
| WORKDIR /usr/local/protobuf | |||
| RUN git checkout 3.20.x && ./autogen.sh && ./configure && make -j$(nproc) && make install && ldconfig | |||
| @@ -1,44 +1,13 @@ | |||
| FROM ubuntu:22.04 | |||
| MAINTAINER eesast.com | |||
| #FROM ubuntu:18.04 | |||
| FROM eesast/thuai6_base:base | |||
| MAINTAINER eesast | |||
| WORKDIR /usr/local | |||
| RUN mkdir /usr/local/PlayerCode | |||
| #安装主要工具 | |||
| RUN apt-get update && apt-get install --no-install-recommends -y gcc g++ make wget ca-certificates cmake autoconf automake libtool curl unzip git | |||
| #安装grpc | |||
| RUN git clone -b v1.46.3 --depth 1 --shallow-submodules https://gitee.com/mirrors/grpc.git | |||
| RUN wget -P . https://cloud.tsinghua.edu.cn/f/1f2713efd9e44255abd6/?dl=1 | |||
| RUN mv 'index.html?dl=1' third_party.tar.gz | |||
| WORKDIR /usr/local/grpc | |||
| RUN rm -rf third_party | |||
| RUN mv ../third_party.tar.gz . | |||
| RUN tar -zxvf third_party.tar.gz | |||
| RUN mkdir -p cmake/build | |||
| WORKDIR /usr/local/grpc/cmake/build | |||
| RUN cmake -DgRPC_INSTALL=ON \ | |||
| -DgRPC_BUILD_TESTS=OFF \ | |||
| ../.. | |||
| RUN make -j$(nproc) | |||
| RUN make install | |||
| #安装protobuf | |||
| WORKDIR /usr/local | |||
| RUN git clone https://gitee.com/mirrors/protobuf_source.git ./protobuf | |||
| WORKDIR /usr/local/protobuf | |||
| RUN git checkout 3.20.x | |||
| RUN ./autogen.sh | |||
| RUN ./configure | |||
| RUN make -j$(nproc) | |||
| RUN make install | |||
| RUN ldconfig | |||
| #RUN git submodule update --init --recursive | |||
| #RUN cmake . | |||
| #RUN cmake --build . --parallel 10 | |||
| #RUN make install | |||
| COPY ./CAPI /usr/local/PlayerCode/CAPI | |||
| COPY ./dependency /usr/local/PlayerCode/dependency | |||
| COPY ./CAPI/cpp /usr/local/PlayerCode/CAPI/cpp | |||
| COPY ./dependency/proto /usr/local/PlayerCode/dependency/proto | |||
| COPY ./dependency/shell /usr/local/PlayerCode/dependency/shell | |||
| WORKDIR /usr/local/PlayerCode/dependency/proto | |||
| RUN ./cpp_output.sh | |||
| RUN bash ../shell/cpp_output.sh | |||
| WORKDIR /usr/local/PlayerCode/CAPI/cpp | |||
| COPY ./dependency/shell/compile.sh . | |||
| ENTRYPOINT ["bash","./compile.sh"] | |||
| @@ -0,0 +1,16 @@ | |||
| FROM eesast/thuai6_base:base | |||
| MAINTAINER eesast | |||
| WORKDIR /usr/local | |||
| RUN mkdir /usr/local/team1 && mkdir /usr/local/team2 && mkdir /usr/local/playback | |||
| COPY ./dependency/shell/run.sh . | |||
| COPY ./CAPI/python /usr/local/PlayerCode/CAPI/python | |||
| COPY ./dependency/proto /usr/local/PlayerCode/dependency/proto | |||
| COPY ./dependency/shell /usr/local/PlayerCode/dependency/shell | |||
| WORKDIR /usr/local/PlayerCode/CAPI/python | |||
| RUN bash ../../dependency/shell/generate_proto.sh | |||
| WORKDIR /usr/local | |||
| RUN wget -P . https://cloud.tsinghua.edu.cn/f/e48940314a6d4cdb8bd0/?dl=1 | |||
| RUN mv 'index.html?dl=1' Server.tar.gz | |||
| RUN tar -zxvf Server.tar.gz | |||
| ENTRYPOINT [ "bash","./run.sh" ] | |||
| @@ -2,3 +2,8 @@ | |||
| 用于存放Docker配置文件Dockerfile | |||
| - 调用runner镜像的方法: | |||
| ```shell | |||
| docker run 镜像名 #编译C++代码 | |||
| docker run 镜像名 #运行程序(支持一个队既提交python又提交cpp) | |||
| ``` | |||
| @@ -0,0 +1,332 @@ | |||
| # Algorithm | |||
| --- | |||
| 天梯分数计算算法 | |||
| 原始记录在:<https://github.com/eesast/THUAI5/discussions/86> | |||
| 内容如下: | |||
| ## THUAI4 | |||
| 关于根据队式每场比赛的分数映射到天梯分数的问题: | |||
| 队式比赛为两队对战,每队得分的区间均为 [0, 2500]。 | |||
| 以 tanh 函数为基础进行设计。 | |||
| 设计原则如下: | |||
| 1. 输的扣少量天梯分,赢的得大量天梯分 | |||
| 2. 本就有极高天梯分数的虐本就天梯分数低的,这种降维打击现象,天梯分数涨幅极小甚至不涨天梯分 | |||
| 3. 如果在某场比赛中,两者表现差不多,即赢的比输的得分高得不多的话,那么天梯分数涨幅也不是很高 | |||
| 4. 如果本来天梯分数很低的,赢了天梯分数很高的,那么他得到的天梯分会较高,而另一个人,天梯分数降分稍多一些 | |||
| 5. 如果天梯分数低的赢了天梯分数高的,但是这场比赛赢得不多的话,会把两人的分数向中间靠拢 | |||
| 6. 总体上,赢的队伍不会降天梯分;输的队伍不会加天梯分 | |||
| 7. 其他条件相同的情况下,在本场游戏中得分越多,加的天梯分数也越高 | |||
| 上述原则可以保证以下两个目的的达成: | |||
| 1. 总体来看,进行的游戏场次越多,所有队伍的平均天梯分数就越高 | |||
| 2. 经过足够多次的游戏场次,实力有一定差距的队伍的天体分数差距逐渐拉开,实力相近的队伍的天梯分数不会差别过大,各支队伍的排名趋近于收敛 | |||
| 用 cpp 代码编写算法代码如下(`cal` 函数): | |||
| ```cpp | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 80.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Tanh delta: " << tanh(delta) << endl; | |||
| { | |||
| const double firstnerGet = 8e-5; // 胜利者天梯得分权值 | |||
| const double secondrGet = 5e-6; // 失败者天梯得分权值 | |||
| double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分 | |||
| resScore.second = orgScore.second - round((2500.0 - competitionScoreLf.second) * (2500.0 - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分,2500 为得分的最大值(THUAI4 每场得分介于 0~2500 之间) | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| ``` | |||
| **特别注意**:此算法是在 THUAI4 的比赛直接得分封顶为 2500 分、最低不低于 0 分的前提下设计的,因此并不一定适用于 THUAI5 的情形。 | |||
| ## THUAI5 | |||
| 今年把得分上限这个东西去掉了。理论上今年可以得很高很高分,但是我估计大部分比赛得分在400-600左右,最高估计1000左右。算法 借 鉴 了THUAI4,算法,换了个激活函数(正态CDF),感觉分数变化相对更好了一些? | |||
| 代码如下: | |||
| ```cpp | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| double PHI(double x) // THUAI3: Sigmoid; THUAI4: Tanh; THUAI5: Normal Distribution CDF | |||
| { | |||
| //double a1 = 0.2548292592; | |||
| //double a2 = -0.284496736; | |||
| //double a3 = 1.421413741; | |||
| //double a4 = -1.453152027; | |||
| //double a5 = 1.061405429; | |||
| //double p = 0.3275911; | |||
| //int sign = 1; | |||
| //if (x < 0) | |||
| // sign = -1; | |||
| //x = fabs(x) / sqrt(2.0); | |||
| //double t = 1.0 / (1.0 + p * x); | |||
| //double y = 1.0 - ((((((a5 * t + a4) * t + a3) * t) + a2) * t) + a1) * t * exp(-x * x); | |||
| //double cdf = 0.5 * (1.0 + sign * y); | |||
| //return (cdf - 0.5) * 2.0; // 化到[-1,1]之间 | |||
| return erf(x / sqrt(2)); | |||
| } | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 90.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Normal CDF delta: " << PHI(delta) << endl; | |||
| { | |||
| const double firstnerGet = 3e-4; // 胜利者天梯得分权值 | |||
| const double secondrGet = 1e-4; // 失败者天梯得分权值 | |||
| double deltaScore = 100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / 100.0; // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (PHI((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - PHI(delta)) * correct); // 胜者所加天梯分 | |||
| if (competitionScoreLf.second < 1000) | |||
| resScore.second = orgScore.second - round((1000.0 - competitionScoreLf.second) * (1000.0 - competitionScoreLf.second) * secondrGet * (1 - PHI(delta)) * correct); // 败者所扣天梯分 | |||
| else | |||
| resScore.second = orgScore.second; // 败者拿1000分,已经很强了,不扣分 | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| void Print(mypair<int> score) | |||
| { | |||
| std::cout << " team1: " << score.first << std::endl | |||
| << "team2: " << score.second << std::endl; | |||
| } | |||
| int main() | |||
| { | |||
| int x, y; | |||
| std::cout << "origin score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto ori = mypair<int>(x, y); | |||
| std::cout << "game score of team 1 and 2: " << std::endl; | |||
| std::cin >> x >> y; | |||
| auto sco = mypair<int>(x, y); | |||
| Print(cal(ori, sco)); | |||
| } | |||
| ``` | |||
| `1000 - score`(x | |||
| `ReLU(1000 - score)`(√ | |||
| 防止真的超过了 1000) | |||
| ## THUAI6 | |||
| 因为今年的对局得分是两局得分之和,所以会出现一定程度的“数值膨胀”,在这里调低了胜者得分权值,同时提高了比赛分差距悬殊阈值和天梯分差距悬殊阈值。同时由于今年得分的上限不好确定,所以负者失分的基础值变为与胜者的得分之差。 | |||
| ```c++ | |||
| #include <iostream> | |||
| #include <algorithm> | |||
| #include <cmath> | |||
| using namespace std; | |||
| template <typename T> | |||
| using mypair = pair<T, T>; | |||
| // orgScore 是天梯中两队的分数;competitionScore 是这次游戏两队的得分 | |||
| mypair<int> cal(mypair<int> orgScore, mypair<int> competitionScore) | |||
| { | |||
| // 调整顺序,让第一个元素成为获胜者,便于计算 | |||
| bool reverse = false; // 记录是否需要调整 | |||
| if (competitionScore.first < competitionScore.second) | |||
| { | |||
| reverse = true; | |||
| } | |||
| else if (competitionScore.first == competitionScore.second) | |||
| { | |||
| if (orgScore.first == orgScore.second) // 完全平局,不改变天梯分数 | |||
| { | |||
| return orgScore; | |||
| } | |||
| if (orgScore.first > orgScore.second) // 本次游戏平局,但一方天梯分数高,另一方天梯分数低,需要将两者向中间略微靠拢,因此天梯分数低的定为获胜者 | |||
| { | |||
| reverse = true; | |||
| } | |||
| else | |||
| { | |||
| reverse = false; | |||
| } | |||
| } | |||
| if (reverse) // 如果需要换,换两者的顺序 | |||
| { | |||
| swap(competitionScore.first, competitionScore.second); | |||
| swap(orgScore.first, orgScore.second); | |||
| } | |||
| // 转成浮点数 | |||
| mypair<double> orgScoreLf; | |||
| mypair<double> competitionScoreLf; | |||
| orgScoreLf.first = orgScore.first; | |||
| orgScoreLf.second = orgScore.second; | |||
| competitionScoreLf.first = competitionScore.first; | |||
| competitionScoreLf.second = competitionScore.second; | |||
| mypair<int> resScore; | |||
| const double deltaWeight = 1000.0; // 差距悬殊判断参数,比赛分差超过此值就可以认定为非常悬殊了,天梯分数增量很小,防止大佬虐菜鸡的现象造成两极分化 | |||
| double delta = (orgScoreLf.first - orgScoreLf.second) / deltaWeight; | |||
| cout << "Tanh delta: " << tanh(delta) << endl; | |||
| { | |||
| const double firstnerGet = 9e-6; // 胜利者天梯得分权值 | |||
| const double secondrGet = 5e-6; // 失败者天梯得分权值 | |||
| double deltaScore = 2100.0; // 两队竞争分差超过多少时就认为非常大 | |||
| double correctRate = (orgScoreLf.first - orgScoreLf.second) / (deltaWeight * 1.2); // 订正的幅度,该值越小,则在势均力敌时天梯分数改变越大 | |||
| double correct = 0.5 * (tanh((competitionScoreLf.first - competitionScoreLf.second - deltaScore) / deltaScore - correctRate) + 1.0); // 一场比赛中,在双方势均力敌时,减小天梯分数的改变量 | |||
| cout << "correct: " << correct << endl; | |||
| resScore.first = orgScore.first + round(competitionScoreLf.first * competitionScoreLf.first * firstnerGet * (1 - tanh(delta)) * correct); // 胜者所加天梯分 | |||
| resScore.second = orgScore.second - round((competitionScoreLf.first - competitionScoreLf.second) * (competitionScoreLf.first - competitionScoreLf.second) * secondrGet * (1 - tanh(delta)) * correct); // 败者所扣天梯分 | |||
| } | |||
| // 如果换过,再换回来 | |||
| if (reverse) | |||
| { | |||
| swap(resScore.first, resScore.second); | |||
| } | |||
| return resScore; | |||
| } | |||
| ``` | |||
| @@ -14,8 +14,8 @@ | |||
| </ItemGroup>--> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.53.0"> | |||
| @@ -2,3 +2,12 @@ | |||
| 本目录用于存放程序所需的shell脚本 | |||
| ## run.sh | |||
| 注意: | |||
| 1. server 和 client 程序要在后台进行 | |||
| 2. 忙等待到 server 结束 | |||
| 3. 结束后生成标志结束的文件 | |||
| @@ -3,21 +3,24 @@ | |||
| i=1 | |||
| flag=1 | |||
| bind=/usr/local/mnt | |||
| while (( $i <= 4 )) | |||
| while (( $i <= 5 )) | |||
| do | |||
| cp -f $bind/player$i.cpp ./API/src | |||
| mv ./API/src/player$i.cpp ./API/src/AI.cpp | |||
| cmake ./CMakeLists.txt && make >compile_log$i.txt 2>&1 | |||
| mv ./capi $bind/capi$i # executable file | |||
| if [ $? -ne 0 ]; then | |||
| if [ -f "${bind}/player${i}.cpp" ]; then | |||
| cp -f $bind/player$i.cpp ./API/src/AI.cpp | |||
| cmake ./CMakeLists.txt && make >compile_log$i.txt 2>&1 | |||
| mv ./capi $bind/capi$i # executable file | |||
| if [ $? -ne 0 ]; then | |||
| flag=0 | |||
| fi | |||
| mv ./compile_log$i.txt $bind/compile_log$i.txt | |||
| elif [ ! -f "${bind}/player${i}.py" ]; then | |||
| flag=0 | |||
| fi | |||
| mv ./compile_log$i.txt $bind/compile_log$i.txt | |||
| let "i++" | |||
| done | |||
| # HTML request to update status. | |||
| if [ $flag -eq 1 ]; then | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"compile_status":"compiled"}' > ../mnt/curl_log.txt | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"compile_status":"compiled"}' > $bind/curl_log.txt | |||
| else | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"compile_status":"failed"}' > ../mnt/curl_log.txt | |||
| fi | |||
| curl $URL -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"compile_status":"failed"}' > $bind/curl_log.txt | |||
| fi | |||
| @@ -0,0 +1,8 @@ | |||
| protoc Message2Clients.proto --cpp_out=. | |||
| protoc MessageType.proto --cpp_out=. | |||
| protoc Message2Server.proto --cpp_out=. | |||
| protoc Services.proto --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` | |||
| protoc Services.proto --cpp_out=. | |||
| chmod -R 755 ./ | |||
| mv -f ./*.h ../../CAPI/cpp/proto | |||
| mv -f ./*.cc ../../CAPI/cpp/proto | |||
| @@ -0,0 +1,22 @@ | |||
| while getopts ':cr' 'OPT'; do | |||
| case ${OPT} in | |||
| 'c') #传入cpp文件并编译 | |||
| #echo v"$VERSION" | |||
| cd /usr/local/PlayerCode/CAPI/cpp | |||
| ./compile.sh | |||
| exit | |||
| ;; | |||
| 'r') #运行 | |||
| #NO_PROMPT='true' | |||
| cd /usr/local | |||
| ./run.sh | |||
| ;; | |||
| 'l') | |||
| ;; | |||
| 'i') | |||
| INSTALL_VERSION="${OPTARG}" | |||
| ;; | |||
| esac | |||
| done | |||
| @@ -0,0 +1,10 @@ | |||
| #!/usr/bin/env bash | |||
| python -m pip install -r requirements.txt | |||
| mkdir -p proto | |||
| python -m grpc_tools.protoc -I../../dependency/proto/ --python_out=./proto --pyi_out=./proto MessageType.proto | |||
| python -m grpc_tools.protoc -I../../dependency/proto/ --python_out=./proto --pyi_out=./proto Message2Clients.proto | |||
| python -m grpc_tools.protoc -I../../dependency/proto/ --python_out=./proto --pyi_out=./proto Message2Server.proto | |||
| python -m grpc_tools.protoc -I../../dependency/proto/ --python_out=./proto --pyi_out=./proto --grpc_python_out=./proto Services.proto | |||
| @@ -0,0 +1,50 @@ | |||
| #!/usr/local | |||
| python_dir=/usr/local/PlayerCode/CAPI/python/PyAPI | |||
| playback_dir=/usr/local/playback | |||
| nice -10 ./Server --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --url $URL --token $TOKEN --fileName $playback_dir/video > $playback_dir/server.log & | |||
| server_pid=$! | |||
| sleep 5 | |||
| for k in {1..2} | |||
| do | |||
| pushd /usr/local/team$k | |||
| if [ $k -eq 1 ]; then | |||
| for i in {1..4} | |||
| 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 & | |||
| 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 & | |||
| else | |||
| echo "ERROR. $i is not found." | |||
| fi | |||
| done | |||
| else | |||
| for i in {5..5} | |||
| 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 & | |||
| 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 & | |||
| else | |||
| echo "ERROR. $i is not found." | |||
| fi | |||
| done | |||
| fi | |||
| popd | |||
| done | |||
| ps -p $server_pid | |||
| while [ $? -eq 0 ] | |||
| do | |||
| sleep 1 | |||
| ps -p $server_pid | |||
| done | |||
| touch $playback_dir/finish.lock | |||
| echo "Finish" | |||
| @@ -13,6 +13,9 @@ | |||
| #### 人物 | |||
| - `std::future<bool> EndAllAction()`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 攻击 | |||
| - `std::future<bool> Attack(double angleInRadian)`:`angleInRadian`为攻击方向 | |||
| @@ -49,6 +52,7 @@ | |||
| - `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const` :返回所有可视捣蛋鬼的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const` :返回所有可视道具的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const` :返回所有可视子弹(攻击)的信息。 | |||
| - `bool HaveView(int gridX, int gridY) const`:判断坐标是否可见 | |||
| #### 查询特定位置物体的信息 | |||
| @@ -56,7 +60,7 @@ | |||
| - `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`:查询特定位置教室作业完成进度 | |||
| @@ -71,9 +75,9 @@ | |||
| - `std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const`:返回整张地图的地形信息。可以写成类似`api.GetFullMap()[x][y]`,其中x为地图自上到下第几行,y为自左向右第几列,注意从0开始 | |||
| ### 辅助函数 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标grid。 | |||
| `static inline int CellToGrid(int cell) noexcept`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数cell。 | |||
| `static inline int GridToCell(int grid) noexcept`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| ~~~c++ | |||
| @@ -123,7 +127,7 @@ | |||
| // 获取视野内可见的道具信息 | |||
| [[nodiscard]] virtual std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const = 0; | |||
| // 获取地图信息,视野外的地图统一为Land | |||
| // 获取地图信息 | |||
| [[nodiscard]] virtual std::vector<std::vector<THUAI6::PlaceType>> GetFullMap() const = 0; | |||
| [[nodiscard]] virtual THUAI6::PlaceType GetPlaceType(int32_t cellX, int32_t cellY) const = 0; | |||
| @@ -155,6 +159,8 @@ | |||
| { | |||
| return grid / numOfGridPerCell; | |||
| } | |||
| [[nodiscard]] virtual bool HaveView(int gridX, int gridY) const = 0; | |||
| // 用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| @@ -8,7 +8,8 @@ | |||
| #### 移动 | |||
| - `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为x轴,水平向右方向为y轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - `def Move(self, timeInMilliseconds: int, angle: float) -> Future[bool]`:移动,`timeInMilliseconds` 为移动时间,单位毫秒;`angleInRadian` 表示移动方向,单位弧度,使用极坐标,**竖直向下方向为 x 轴,水平向右方向为 y 轴**因为移动过程中你会受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| - 5ms以内的移动指令会被禁止,你不应当使用过小的移动指令 | |||
| - `def MoveRight(self, timeInMilliseconds: int) -> Future[bool]`即向右移动,`MoveLeft`、`MoveDown`、`MoveUp`同理 | |||
| #### 使用技能 | |||
| @@ -18,6 +19,9 @@ | |||
| #### 人物 | |||
| - `def EndAllAction(self) -> Future[bool]`:可以使不处在不可行动状态中的玩家终止当前行动 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令和移动指令是无效的,你需要先发出 Stop 指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - EndAllAction() 及 Move 指令调用数总和一帧内不超过 10 次 | |||
| #### 攻击 | |||
| @@ -31,8 +35,8 @@ | |||
| #### 勉励与唤醒 | |||
| - `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家ID的学生。 | |||
| - `def StartRouseMate(self, mateID: int) -> Future[bool]`:唤醒对应玩家ID的沉迷的学生。 | |||
| - `def StartEncourageMate(self, mateID: int) -> Future[bool]`:勉励对应玩家 ID 的学生。 | |||
| - `def StartRouseMate(self, mateID: int) -> Future[bool]`:唤醒对应玩家 ID 的沉迷的学生。 | |||
| #### 地图互动 | |||
| @@ -51,16 +55,17 @@ | |||
| #### 队内信息 | |||
| - `std::future<bool> SendMessage(int64_t, std::string)`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `bool HaveMessage()`:是否有队友发来的尚未接收的信息。 | |||
| - `std::pair<int64_t, std::string> GetMessage()`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | |||
| - `def GetMessage(self) -> Tuple[int, str]`:给同队的队友发送消息。第一个参数指定发送的对象,第二个参数指定发送的内容,不得超过256字节。 | |||
| - `def HaveMessage(self) -> bool`:是否有队友发来的尚未接收的信息。 | |||
| - `def GetMessage(self) -> Tuple[int, str]`:按照消息发送顺序获取来自队友的信息,第一个参数为发送该消息的PlayerID。 | |||
| #### 查询可视范围内的信息 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Student>> GetStudents() const` :对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Tricker>> GetTrickers() const` :返回所有可视捣蛋鬼的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Prop>> GetProps() const` :返回所有可视道具的信息。 | |||
| - `std::vector<std::shared_ptr<const THUAI6::Bullet>> GetBullets() const` :返回所有可视子弹(攻击)的信息。 | |||
| - `def GetStudents(self) -> List[THUAI6.Student]` :对于学生,返回所有学生的信息;对于捣蛋鬼,返回可视学生的信息。 | |||
| - `def GetTrickers(self) -> List[THUAI6.Tricker]` :返回所有可视捣蛋鬼的信息。 | |||
| - `def GetProps(self) -> List[THUAI6.Prop]` :返回所有可视道具的信息。 | |||
| - `def GetBullets(self) -> List[THUAI6.Bullet]` :返回所有可视子弹(攻击)的信息。 | |||
| - `def HaveView(self, gridX: int, gridY: int) -> bool`:判断坐标是否可见 | |||
| #### 查询特定位置物体的信息 | |||
| @@ -68,13 +73,13 @@ | |||
| - `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 | |||
| #### 其他 | |||
| @@ -86,11 +91,11 @@ | |||
| ### 辅助函数 | |||
| `def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标grid。 | |||
| `def CellToGrid(cell: int) -> int`:将地图格数 cell 转换为绝对坐标 grid。 | |||
| `def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数cell。 | |||
| `def GridToCell(grid: int) -> int`:将绝对坐标 grid 转换为地图格数 cell。 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启Debug模式的情况下可以使用 | |||
| 下面为用于DEBUG的输出函数,选手仅在开启 Debug 模式的情况下可以使用 | |||
| ~~~python | |||
| def Print(self, cont: str) -> None: | |||
| @@ -261,6 +266,10 @@ class IAPI(metaclass=ABCMeta): | |||
| @abstractmethod | |||
| def GetGameInfo(self) -> THUAI6.GameInfo: | |||
| pass | |||
| @abstractmethod | |||
| def HaveView(self, gridX: int, gridY: int) -> bool | |||
| pass | |||
| # 用于DEBUG的输出函数,仅在DEBUG模式下有效 | |||
| @@ -317,4 +326,4 @@ class ITrickerAPI(IAPI, metaclass=ABCMeta): | |||
| @abstractmethod | |||
| def GetSelfInfo(self) -> THUAI6.Tricker: | |||
| pass | |||
| ~~~ | |||
| ~~~ | |||
| @@ -1,5 +1,5 @@ | |||
| # 规则 | |||
| V5.1 | |||
| V5.3 | |||
| - [规则](#规则) | |||
| - [简则](#简则) | |||
| - [地图](#地图) | |||
| @@ -140,14 +140,14 @@ $$ | |||
| ### 信息相关 | |||
| - Bgm (在structures.h/.py中的student类或Tricker类中作为其属性) | |||
| 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(int)(警戒半径/二者距离) | |||
| 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(int)(警戒半径/可被发觉的最近的学生距离) | |||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=(int)((警戒半径x学习进度百分比)/二者距离) | |||
| 1. 不详的感觉(dangerAlert):如果捣蛋鬼进入(学生的警戒半径/捣蛋鬼的隐蔽度)的距离,则学生的dangerAlert=(警戒半径/二者距离) | |||
| 2. 期待搞事的感觉(trickDesire):如果有学生进入(捣蛋鬼的警戒半径/学生的隐蔽度)的距离,则捣蛋鬼trickDesire=(警戒半径/可被发觉的最近的学生距离) | |||
| 3. 学习的声音(classVolume): 警戒半径内有人学习时,捣蛋鬼classVolume=((警戒半径x学习进度百分比)/二者距离) | |||
| - 可以向每一个队友发送不超过256字节的信息 | |||
| ### 可视范围 | |||
| - 小于视野半径 | |||
| - 对于在从草地中的物体,物体中心与玩家中心连线上均为草地方可见 | |||
| - 对于中心在草地中的物体,物体中心与玩家中心连线上均为草地方可见 | |||
| - 不在草地的物体,物体中心与玩家中心连线上无墙即可见 | |||
| ### 道具 | |||
| @@ -194,9 +194,9 @@ $$ | |||
| | 隐蔽度 | 1.5 | 1 | 0.8 | 0.75| | |||
| | 警戒范围 | 22,100 | 17000 | 15300 | 17000 | |||
| | 视野范围 | 15600 | 13000 | 13000 | 14300| | |||
| | 开锁门速度 | 4000 | 4000 | 4000 |4000 | | |||
| | 翻窗速度 | 2540 | 2540 | 2794 | 2540| | |||
| | 翻箱速度 | 1000 | 1100 | 1000 |1000| | |||
| | 开锁门速度/ms | 4000 | 4000 | 4000 |4000 | | |||
| | 翻窗速度/s | 2540 | 2540 | 2794 | 2540| | |||
| | 翻箱速度/ms | 1000 | 1100 | 1000 |1000| | |||
| #### Assassin | |||
| - 普通攻击为 CommonAttackOfGhost | |||
| @@ -244,17 +244,17 @@ $$ | |||
| | 学生职业 | 教师Teacher | 健身狂Athlete | 学霸StraightAStudent | 开心果Sunshine | | |||
| | :------------ | :--------------------- | :--------------------- | :--------------------- | :--------------------- | | |||
| | 移动速度 | 2700 | 3150 | 2880 | 3000 | | |||
| | 移动速度/s | 2700 | 3150 | 2880 | 3000 | | |||
| | 最大毅力值 | 30000000 | 3000000 | 3300000 | 3200000 | | |||
| | 最大沉迷度 | 600000 | 54,000 | 78,000 | 66,000 | | |||
| | 学习一科速度 | 0 | 73 | 135 | 123 | | |||
| | 勉励速度 | 80 | 90 | 100 | 120 | | |||
| | 勉励速度/ms | 80 | 90 | 100 | 120 | | |||
| | 隐蔽度 | 0.5 | 0.9 | 0.9 | 0.8 | | |||
| | 警戒范围 | 7500 | 15000 | 13,500 | 15000 | | |||
| | 视野范围 | 9,000 | 11000 | 9,000 | 10000 | | |||
| | 开锁门速度 | 4000 | 4000 | 4000 | 2800 | | |||
| | 翻窗速度 | 1270 | 3048 | 2116 | 2540 | | |||
| | 翻箱速度 | 1000 | 1000 | 1000 | 900 | | |||
| | 开锁门速度/ms | 4000 | 4000 | 4000 | 2800 | | |||
| | 翻窗速度/ms | 1270 | 3048 | 2116 | 2540 | | |||
| | 翻箱速度/ms | 1000 | 1000 | 1000 | 900 | | |||
| #### 运动员 | |||
| - 主动技能 | |||
| @@ -308,18 +308,15 @@ $$ | |||
| - 不鼓励选手面向地图编程,因为移动过程中你可以受到多种干扰使得移动结果不符合你的预期;因此建议小步移动,边移动边考虑之后的行为。 | |||
| ### 人物 | |||
| - EndAllAction()及Move指令调用数总和一帧内不超过10次 | |||
| - 眩晕状态中的玩家不能再次被眩晕 | |||
| ### 初始状态 | |||
| - 玩家出生点固定且一定为空地 | |||
| - 初赛玩家出生点固定且一定为空地 | |||
| ### 道具 | |||
| - 使用钥匙相当于销毁 | |||
| ### 交互 | |||
| - 在指令仍在进行时,重复发出同一类型的交互指令是无效的,你需要先发出Stop指令终止进行的指令 | |||
| - 实际上唤醒或勉励不同的人是有效的 | |||
| - 被唤醒或被勉励不属于交互状态,翻窗属于交互状态 | |||
| ### 学习与毕业 | |||
| @@ -24,12 +24,6 @@ Q: 怎么开始游戏? | |||
| A: | |||
| 需要确保学生阵营和捣蛋鬼阵营的人数都达到Server.cmd中设定的值。人数不足也可以打开WPF,参考使用文档,修改RunGUIClient.cmd的参数,然后运行RunGUIClient.cmd,这样可以通过WPF运行部分客户端,来达到人数限制。 | |||
| Q: Mac怎么用? | |||
| A: | |||
| 安装Windows虚拟机 | |||
| try | |||
| ## C++ | |||
| Q:显示API项目已卸载 | |||
| @@ -37,7 +31,7 @@ Q:显示API项目已卸载 | |||
| A:可能是没有安装C++ | |||
| Q:CAPI.sln编译不通过 | |||
| Q:CAPI.sln编译不通过(第一种) | |||
| 情况1: | |||
|  | |||
| 情况2: | |||
| @@ -52,7 +46,7 @@ A: | |||
|  | |||
| 确保上图项目属性中平台工具集在 v143,C++17 标准 | |||
| Q:CAPI编译不通过 | |||
| Q:CAPI编译不通过(第二种) | |||
|  | |||
| A:查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||
| @@ -68,12 +62,15 @@ A: | |||
| - 可能措施1. | |||
| 首先保证Python版本在3.9及以上 | |||
| - 可能措施2. 更换为国内镜像源 | |||
| 在终端输入 | |||
| `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||
| 在终端输入 `pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple` | |||
| - 可能措施3. 更新pip | |||
| `python -m pip install --upgrade pip` (pip 版本最好为23.1) | |||
| ## 比赛相关 | |||
| Q:职业数值会修改吗? | |||
| A:初赛结束会调数值及机制,增加新角色 | |||
| A:初赛结束会调数值及机制,增加新角色 | |||
| Q:初赛后会修改什么呢? | |||
| A:技能冷却时间等属性设为不可见;出生点随机性或可选性;增强教师等职业,削弱职业;规范Debug信息 | |||
| @@ -65,9 +65,10 @@ int main() | |||
| ### 线程睡眠 | |||
| 由于移动过程中会阻塞人物角色,因此玩家可能要在移动后让线程休眠一段时间,直到移动结束。C++ 标准库中使线程休眠需要包含头文件:`#include <thread>`。示例用法: | |||
| 我们推荐小步移动,不太建议玩家使用线程睡眠超过一帧 | |||
| ```cpp | |||
| std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 休眠 20 毫秒 | |||
| std::this_thread::sleep_for(std::chrono::seconds(2)); // 休眠 2 秒 | |||
| @@ -6,6 +6,11 @@ | |||
| ## 不应当使用Windows特性的库,不建议使用.h结尾的库,建议使用标准库 | |||
| - 不要使用conio.h,Windows.h | |||
| ## 请注意下载器不更新AI.py,AI.cpp和脚本 | |||
| - 最新版AI.cpp和AI.py | |||
|  | |||
|  | |||
| ## C++接口使用说明 | |||
| - Windows:先查看`.\win\CAPI\cpp\`文件夹下是否有`lib`文件夹,没有则https://cloud.tsinghua.edu.cn/d/6972138f641d4e81a446/ 下载并复制粘贴 | |||
| @@ -506,6 +506,7 @@ namespace Downloader | |||
| string secretId = "***"; //"云 API 密钥 SecretId"; | |||
| string secretKey = "***"; //"云 API 密钥 SecretKey"; | |||
| long durationSecond = 1000; // 每次请求签名有效时长,单位为秒 | |||
| QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider( | |||
| secretId, secretKey, durationSecond | |||
| @@ -19,7 +19,7 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -283,6 +283,24 @@ namespace Client | |||
| } | |||
| 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() | |||
| { | |||
| for (int i = 0; i < 50; i++) | |||
| { | |||
| @@ -299,6 +317,7 @@ namespace Client | |||
| } | |||
| } | |||
| } | |||
| private void DrawMap() | |||
| { | |||
| for (int i = 0; i < defaultMap.GetLength(0); i++) | |||
| @@ -560,7 +579,6 @@ namespace Client | |||
| } | |||
| } | |||
| //待修改 | |||
| private bool CanSee(MessageOfStudent msg) | |||
| { | |||
| if (msg.PlayerState == PlayerState.Quit || msg.PlayerState == PlayerState.Graduated) | |||
| @@ -750,7 +768,10 @@ namespace Client | |||
| StatusBarsOfCircumstance.SetValue(data, gateOpened, isEmergencyDrawed, isEmergencyOpened, playerID, isPlaybackMode); | |||
| } | |||
| if (!hasDrawed && mapFlag) | |||
| { | |||
| DrawMap(); | |||
| ZoomMapAtFirst(); | |||
| } | |||
| foreach (var data in listOfHuman) | |||
| { | |||
| if (data.StudentType != StudentType.Robot) | |||
| @@ -1037,7 +1058,6 @@ namespace Client | |||
| } | |||
| //} | |||
| ZoomMap(); | |||
| } | |||
| catch (Exception exc) | |||
| { | |||
| @@ -1051,7 +1071,7 @@ namespace Client | |||
| } | |||
| } | |||
| // 键盘控制,未完善 | |||
| // 键盘控制 | |||
| private void KeyBoardControl(object sender, KeyEventArgs e) | |||
| { | |||
| if (!isPlaybackMode && !isSpectatorMode) | |||
| @@ -1262,7 +1282,6 @@ namespace Client | |||
| } | |||
| } | |||
| // 之后需要修改,现在只具有修改按钮形状的功能,并不能实现暂停/继续 | |||
| private void ClickToPauseOrContinue(object sender, RoutedEventArgs e) | |||
| { | |||
| if (!isClientStocked) | |||
| @@ -1284,6 +1303,7 @@ namespace Client | |||
| } | |||
| } | |||
| } | |||
| // 未复现 | |||
| private void ClickToConnect(object sender, RoutedEventArgs e) | |||
| { | |||
| @@ -1297,24 +1317,28 @@ namespace Client | |||
| else | |||
| WindowState = WindowState.Normal; | |||
| } | |||
| private void ClickToClose(object sender, RoutedEventArgs e) | |||
| { | |||
| Application.Current.Shutdown(); | |||
| } | |||
| private void ClickToMinimize(object sender, RoutedEventArgs e) | |||
| { | |||
| WindowState = WindowState.Minimized; | |||
| } | |||
| private void DragWindow(object sender, RoutedEventArgs e) | |||
| { | |||
| DragMove(); | |||
| } | |||
| // 寻求帮助、访问EESAST(部分功能未复原) | |||
| // 寻求帮助、访问EESAST(部分功能未复现) | |||
| private void ClickForHelp(object sender, RoutedEventArgs e) | |||
| { | |||
| PleaseWait(); | |||
| } | |||
| private void ClickToVisitEESAST(object sender, RoutedEventArgs e) | |||
| { | |||
| try | |||
| @@ -1328,12 +1352,13 @@ namespace Client | |||
| } | |||
| } | |||
| // 配置连接(未复原)、我的AI(THUAI5未实现)、获取更新、天梯信息(可能需要网站协助) | |||
| // 配置连接(未复现)、我的AI(THUAI5未实现)、获取更新、天梯信息(可能需要网站协助) | |||
| private void ClickToSetConnect(object sender, RoutedEventArgs e) | |||
| { | |||
| // ConnectRegister crg = new(); | |||
| // crg.Show(); | |||
| } | |||
| private void ClickToEnterVS(object sender, RoutedEventArgs e) | |||
| { | |||
| // try | |||
| @@ -1354,10 +1379,12 @@ namespace Client | |||
| // } | |||
| PleaseWait(); | |||
| } | |||
| private void ClickForUpdate(object sender, RoutedEventArgs e) | |||
| { | |||
| PleaseWait(); | |||
| } | |||
| private void ClickToCheckLadder(object sender, RoutedEventArgs e) | |||
| { | |||
| PleaseWait(); | |||
| @@ -8,7 +8,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -130,7 +130,7 @@ namespace GameClass.GameObj | |||
| public override bool CanAttack(GameObj target) | |||
| { | |||
| return XY.Distance(this.Position, target.Position) <= BulletBombRange; | |||
| return XY.DistanceFloor3(this.Position, target.Position) <= BulletBombRange; | |||
| } | |||
| public override bool CanBeBombed(GameObjType gameObjType) | |||
| { | |||
| @@ -176,7 +176,7 @@ namespace GameClass.GameObj | |||
| public override bool CanAttack(GameObj target) | |||
| { | |||
| return XY.Distance(this.Position, target.Position) <= BulletBombRange; | |||
| return XY.DistanceFloor3(this.Position, target.Position) <= BulletBombRange; | |||
| } | |||
| public override bool CanBeBombed(GameObjType gameObjType) | |||
| { | |||
| @@ -36,6 +36,7 @@ namespace GameClass.GameObj | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (targetObj == Parent && CanMove) return true; | |||
| if (targetObj.Type == GameObjType.Prop || targetObj.Type == GameObjType.Bullet) | |||
| return true; | |||
| return false; | |||
| @@ -594,7 +594,7 @@ namespace GameClass.GameObj | |||
| { | |||
| return true; | |||
| } | |||
| if (targetObj.Type == GameObjType.Character && XY.Distance(targetObj.Position, this.Position) < this.Radius + targetObj.Radius) | |||
| if (targetObj.Type == GameObjType.Character && XY.DistanceCeil3(targetObj.Position, this.Position) < this.Radius + targetObj.Radius - GameData.adjustLength) | |||
| return true; | |||
| return false; | |||
| } | |||
| @@ -46,7 +46,7 @@ namespace GameClass.GameObj | |||
| public bool Repair(int addDegree, Character character) | |||
| { | |||
| if (DegreeOfRepair == GameData.degreeOfFixedGenerator) return true; | |||
| if (DegreeOfRepair == GameData.degreeOfFixedGenerator) return false; | |||
| int orgDegreeOfRepair = degreeOfRepair; | |||
| DegreeOfRepair += addDegree; | |||
| if (DegreeOfRepair > orgDegreeOfRepair) | |||
| @@ -203,7 +203,7 @@ namespace GameClass.GameObj | |||
| } | |||
| public bool Remove(GameObj gameObj) | |||
| { | |||
| bool flag = false; | |||
| GameObj? ToDel = null; | |||
| GameObjLockDict[gameObj.Type].EnterWriteLock(); | |||
| try | |||
| { | |||
| @@ -211,8 +211,7 @@ namespace GameClass.GameObj | |||
| { | |||
| if (gameObj.ID == obj.ID) | |||
| { | |||
| GameObjDict[gameObj.Type].Remove(obj); | |||
| flag = true; | |||
| ToDel = obj; | |||
| break; | |||
| } | |||
| } | |||
| @@ -221,7 +220,9 @@ namespace GameClass.GameObj | |||
| { | |||
| GameObjLockDict[gameObj.Type].ExitWriteLock(); | |||
| } | |||
| return flag; | |||
| if (ToDel == null) return false; | |||
| GameObjDict[gameObj.Type].Remove(ToDel); | |||
| return true; | |||
| } | |||
| public bool RemoveJustFromMap(GameObj gameObj) | |||
| { | |||
| @@ -75,7 +75,7 @@ namespace GameEngine | |||
| // if (obj.WillCollideWith(square, obj.Position)) | |||
| // tmpMax = 0; | |||
| // else tmpMax = | |||
| // Math.Abs(XYPosition.Distance(obj.Position, square.Position) - obj.Radius - | |||
| // Math.Abs(XYPosition.DistanceFloor3(obj.Position, square.Position) - obj.Radius - | |||
| // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle))))); | |||
| // return tmpMax; | |||
| // } | |||
| @@ -144,7 +144,7 @@ namespace GameEngine | |||
| case ShapeType.Circle: | |||
| { | |||
| // 计算两者之间的距离 | |||
| double mod = XY.Distance(listObj.Position, obj.Position); | |||
| double mod = XY.DistanceFloor3(listObj.Position, obj.Position); | |||
| int orgDeltaX = listObj.Position.x - obj.Position.x; | |||
| int orgDeltaY = listObj.Position.y - obj.Position.y; | |||
| @@ -181,7 +181,7 @@ namespace GameEngine | |||
| while (left < right - 1) | |||
| { | |||
| int mid = (right - left) / 2 + left; | |||
| if (obj.WillCollideWith(listObj, obj.Position + new XY((int)(mid * Math.Cos(moveVec.Angle())), (int)(mid * Math.Sin(moveVec.Angle()))))) | |||
| if (obj.WillCollideWith(listObj, obj.Position + new XY(moveVec, mid))) | |||
| { | |||
| right = mid; | |||
| } | |||
| @@ -1,5 +1,4 @@ | |||
| using System; | |||
| using System.Numerics; | |||
| using System.Threading; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| @@ -88,110 +87,136 @@ namespace GameEngine | |||
| obj.IsMoving = true; | |||
| double moveVecLength = 0.0; | |||
| XY res = new XY(direction, moveVecLength); | |||
| XY res = new(direction, moveVecLength); | |||
| double deltaLen = moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); // 转向,并用deltaLen存储行走的误差 | |||
| IGameObj? collisionObj = null; | |||
| bool isDestroyed = false; | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||
| () => | |||
| bool flag; // 循环标志 | |||
| do | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollision(obj, obj.Position); | |||
| if (collisionObj == null) | |||
| break; | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| res = new XY(direction, moveVecLength); | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| break; | |||
| } | |||
| } while (flag); | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| if (!isDestroyed) | |||
| { | |||
| new FrameRateTaskExecutor<int>( | |||
| () => gameTimer.IsGaming && obj.CanMove && !obj.IsResetting && obj.IsMoving, | |||
| () => | |||
| { | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| moveVecLength = obj.MoveSpeed / GameData.numOfStepPerSecond; | |||
| res = new XY(direction, moveVecLength); | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| // 越界情况处理:如果越界,则与越界方块碰撞 | |||
| bool flag; // 循环标志 | |||
| do | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| return false; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| flag = false; | |||
| collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res); | |||
| if (collisionObj == null) | |||
| break; | |||
| } | |||
| } while (flag); | |||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| return false; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } while (flag); | |||
| return true; | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| bool flag; | |||
| do | |||
| deltaLen += moveVecLength - Math.Sqrt(obj.MovingSetPos(res, GetPlaceType(obj.Position + res))); | |||
| return true; | |||
| }, | |||
| GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond, | |||
| () => | |||
| { | |||
| flag = false; | |||
| if (!isDestroyed) | |||
| int leftTime = moveTime % (GameData.numOfPosGridPerCell / GameData.numOfStepPerSecond); | |||
| bool flag; | |||
| do | |||
| { | |||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| } | |||
| else | |||
| flag = false; | |||
| if (!isDestroyed) | |||
| { | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| moveVecLength = deltaLen + leftTime * obj.MoveSpeed / GameData.numOfPosGridPerCell; | |||
| res = new XY(direction, moveVecLength); | |||
| if ((collisionObj = collisionChecker.CheckCollisionWhenMoving(obj, res)) == null) | |||
| { | |||
| obj.MovingSetPos(res, GetPlaceType(obj.Position + res)); | |||
| } | |||
| else | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| switch (OnCollision(obj, collisionObj, res)) | |||
| { | |||
| case AfterCollision.ContinueCheck: | |||
| flag = true; | |||
| break; | |||
| case AfterCollision.Destroyed: | |||
| Debugger.Output(obj, " collide with " + collisionObj.ToString() + " and has been removed from the game."); | |||
| isDestroyed = true; | |||
| break; | |||
| case AfterCollision.MoveMax: | |||
| MoveMax(obj, res); | |||
| moveVecLength = 0; | |||
| res = new XY(direction, moveVecLength); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } while (flag); | |||
| if (leftTime > 0 && obj.IsMoving) | |||
| { | |||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | |||
| } | |||
| } while (flag); | |||
| if (leftTime > 0 && obj.IsMoving) | |||
| { | |||
| Thread.Sleep(leftTime); // 多移动的在这里补回来 | |||
| } | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = false; // 结束移动 | |||
| EndMove(obj); | |||
| return 0; | |||
| }, | |||
| maxTotalDuration: moveTime | |||
| ) | |||
| { | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| lock (obj.MoveLock) | |||
| obj.IsMoving = false; // 结束移动 | |||
| EndMove(obj); | |||
| return 0; | |||
| }, | |||
| maxTotalDuration: moveTime | |||
| ) | |||
| { | |||
| if (b) | |||
| Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); | |||
| AllowTimeExceed = true, | |||
| MaxTolerantTimeExceedCount = ulong.MaxValue, | |||
| TimeExceedAction = b => | |||
| { | |||
| if (b) | |||
| Console.WriteLine("Fatal Error: The computer runs so slow that the object cannot finish moving during this time!!!!!!"); | |||
| #if DEBUG | |||
| else | |||
| { | |||
| Console.WriteLine("Debug info: Object moving time exceed for once."); | |||
| } | |||
| else | |||
| { | |||
| Console.WriteLine("Debug info: Object moving time exceed for once."); | |||
| } | |||
| #endif | |||
| } | |||
| }.Start(); | |||
| } | |||
| }.Start(); | |||
| } | |||
| } | |||
| ).Start(); | |||
| } | |||
| @@ -1,6 +1,4 @@ | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Net.NetworkInformation; | |||
| using System.Threading; | |||
| using GameClass.GameObj; | |||
| using GameEngine; | |||
| @@ -37,6 +35,7 @@ namespace Gaming | |||
| } | |||
| public bool MovePlayer(Character playerToMove, int moveTimeInMilliseconds, double moveDirection) | |||
| { | |||
| if (moveTimeInMilliseconds < 5) return false; | |||
| if (!playerToMove.Commandable() || !TryToStop()) return false; | |||
| characterManager.SetPlayerState(playerToMove, PlayerStateType.Moving); | |||
| moveEngine.MoveObj(playerToMove, moveTimeInMilliseconds, moveDirection); | |||
| @@ -81,20 +80,19 @@ namespace Gaming | |||
| loopToDo: () => | |||
| { | |||
| if (generatorForFix.Repair(player.FixSpeed * GameData.frameDuration, player)) | |||
| { | |||
| characterManager.SetPlayerState(player); | |||
| gameMap.NumOfRepairedGenerators++; | |||
| } | |||
| if (generatorForFix.DegreeOfRepair == GameData.degreeOfFixedGenerator) | |||
| characterManager.SetPlayerState(player); | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| .Start(); | |||
| --generatorForFix.NumOfFixing; | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| --generatorForFix.NumOfFixing; | |||
| return true; | |||
| } | |||
| @@ -34,6 +34,7 @@ namespace Gaming | |||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | |||
| BulletBomb((Bullet)obj, null); | |||
| obj.CanMove = false; | |||
| } | |||
| ); | |||
| this.characterManager = characterManager; | |||
| @@ -171,17 +172,19 @@ namespace Gaming | |||
| return false; | |||
| Debugger.Output(player, player.CharacterType.ToString() + "Attack in " + player.BulletOfPlayer.ToString()); | |||
| Debugger.Output(player, player.Position.ToString() + " " + player.Radius.ToString() + " " + BulletFactory.BulletRadius(player.BulletOfPlayer).ToString()); | |||
| XY res = player.Position + new XY // 子弹紧贴人物生成。 | |||
| ( | |||
| (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle)), | |||
| (int)((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle)) | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Cos(angle))) * ((Math.Cos(angle) > 0) ? 1 : -1), | |||
| (int)(Math.Abs((player.Radius + BulletFactory.BulletRadius(player.BulletOfPlayer)) * Math.Sin(angle))) * ((Math.Sin(angle) > 0) ? 1 : -1) | |||
| ); | |||
| Bullet? bullet = player.Attack(res, gameMap.GetPlaceType(res)); | |||
| if (bullet != null) | |||
| { | |||
| Debugger.Output(player, "Attack in " + bullet.ToString()); | |||
| player.FacingDirection = new(angle, bullet.BulletAttackRange); | |||
| Debugger.Output(bullet, "Attack in " + bullet.Position.ToString()); | |||
| bullet.AP += player.TryAddAp() ? GameData.ApPropAdd : 0; | |||
| bullet.CanMove = true; | |||
| gameMap.Add(bullet); | |||
| @@ -1,13 +1,9 @@ | |||
| using System; | |||
| using System.Threading; | |||
| using System.Collections.Generic; | |||
| using GameClass.GameObj; | |||
| using Preparation.Utility; | |||
| using GameEngine; | |||
| using Preparation.Interface; | |||
| using Timothy.FrameRateTask; | |||
| using System.Numerics; | |||
| using System.Timers; | |||
| namespace Gaming | |||
| { | |||
| @@ -135,10 +131,10 @@ namespace Gaming | |||
| double bgmVolume = 0; | |||
| foreach (Character person in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!person.IsGhost() && XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| if (!person.IsGhost() && XY.DistanceFloor3(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| { | |||
| if ((double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position) > bgmVolume) | |||
| bgmVolume = newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position); | |||
| if ((double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position) > bgmVolume) | |||
| bgmVolume = newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position); | |||
| } | |||
| } | |||
| newPlayer.AddBgm(BgmType.StudentIsApproaching, bgmVolume); | |||
| @@ -151,11 +147,11 @@ namespace Gaming | |||
| { | |||
| if (!noise) | |||
| { | |||
| if (XY.Distance(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.Distance(newPlayer.Position, person.Position)); | |||
| if (XY.DistanceFloor3(newPlayer.Position, person.Position) <= (newPlayer.AlertnessRadius / person.Concealment)) | |||
| newPlayer.AddBgm(BgmType.GhostIsComing, (double)newPlayer.AlertnessRadius / XY.DistanceFloor3(newPlayer.Position, person.Position)); | |||
| else newPlayer.AddBgm(BgmType.GhostIsComing, 0); | |||
| } | |||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.Distance(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||
| if (newPlayer.CharacterType != CharacterType.Teacher && newPlayer.CharacterType != CharacterType.Robot && !newPlayer.NoHp() && newPlayer.PlayerState != PlayerStateType.Stunned && XY.DistanceFloor3(newPlayer.Position, person.Position) <= GameData.PinningDownRange) | |||
| { | |||
| TimePinningDown += GameData.checkInterval; | |||
| newPlayer.AddScore(GameData.StudentScorePinDown(TimePinningDown) - ScoreAdded); | |||
| @@ -180,10 +176,10 @@ namespace Gaming | |||
| double bgmVolume = 0; | |||
| foreach (Generator generator in gameMap.GameObjDict[GameObjType.Generator]) | |||
| { | |||
| if (XY.Distance(newPlayer.Position, generator.Position) <= newPlayer.AlertnessRadius) | |||
| if (XY.DistanceFloor3(newPlayer.Position, generator.Position) <= newPlayer.AlertnessRadius) | |||
| { | |||
| if (generator.NumOfFixing > 0 && (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position) > bgmVolume) | |||
| bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.Distance(newPlayer.Position, generator.Position); | |||
| if (generator.NumOfFixing > 0 && (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.DistanceFloor3(newPlayer.Position, generator.Position) > bgmVolume) | |||
| bgmVolume = (double)newPlayer.AlertnessRadius * generator.DegreeOfRepair / GameData.degreeOfFixedGenerator / XY.DistanceFloor3(newPlayer.Position, generator.Position); | |||
| } | |||
| } | |||
| newPlayer.AddBgm(BgmType.GeneratorIsBeingFixed, bgmVolume); | |||
| @@ -383,6 +379,7 @@ namespace Gaming | |||
| #if DEBUG | |||
| Debugger.Output(player, "die."); | |||
| #endif | |||
| if (player.PlayerState == PlayerStateType.Deceased) return; | |||
| player.RemoveFromGame(PlayerStateType.Deceased); | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| @@ -51,7 +51,7 @@ namespace Gaming | |||
| { | |||
| if (!person.IsGhost() && player.CharacterType != CharacterType.Robot && !person.NoHp()) | |||
| { | |||
| double dis = XY.Distance(person.Position, player.Position); | |||
| double dis = XY.DistanceFloor3(person.Position, player.Position); | |||
| if (dis >= player.AlertnessRadius) | |||
| { | |||
| person.AddMoveSpeed(GameData.checkIntervalWhenShowTime, dis / player.AlertnessRadius); | |||
| @@ -185,7 +185,7 @@ namespace Gaming | |||
| { | |||
| foreach (Character character in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| if (!character.IsGhost() && !character.NoHp() && XY.Distance(character.Position, player.Position) <= player.ViewRange) | |||
| if (!character.IsGhost() && !character.NoHp() && XY.DistanceFloor3(character.Position, player.Position) <= player.ViewRange) | |||
| { | |||
| if (characterManager.BeStunned(character, GameData.timeOfStudentStunnedWhenHowl)) | |||
| player.AddScore(GameData.TrickerScoreStudentBeStunned(GameData.timeOfStudentStunnedWhenHowl)); | |||
| @@ -24,7 +24,7 @@ namespace Preparation.Interface | |||
| if (targetObj.Shape == ShapeType.Circle) | |||
| { | |||
| return XY.Distance(nextPos, targetObj.Position) < targetObj.Radius + Radius; | |||
| return XY.DistanceCeil3(nextPos, targetObj.Position) < targetObj.Radius + Radius; | |||
| } | |||
| else // Square | |||
| { | |||
| @@ -9,7 +9,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| @@ -9,7 +9,8 @@ namespace Preparation.Utility | |||
| #region 基本常数 | |||
| public const int numOfStepPerSecond = 20; // 每秒行走的步数 | |||
| public const int tolerancesLength = 10; | |||
| public const int tolerancesLength = 3; | |||
| public const int adjustLength = 3; | |||
| public const int frameDuration = 50; // 每帧时长 | |||
| public const int checkInterval = 50; // 检查位置标志、补充子弹的帧时长 | |||
| @@ -62,14 +62,24 @@ namespace Preparation.Utility | |||
| { | |||
| return v1.x != v2.x || v1.y != v2.y; | |||
| } | |||
| public static double Distance(XY p1, XY p2) | |||
| public static double DistanceFloor3(XY p1, XY p2) | |||
| { | |||
| long c = (((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))) * 1000000; | |||
| long t = c / 2 + 1; | |||
| while (t * t > c || (t + 1) * (t + 1) < c) | |||
| while (t * t > c || (t + 1) * (t + 1) <= c) | |||
| t = (c / t + t) / 2; | |||
| return (double)t / 1000.0; | |||
| } | |||
| public static double DistanceCeil3(XY p1, XY p2) | |||
| { | |||
| long c = (((long)(p1.x - p2.x) * (p1.x - p2.x)) + ((long)(p1.y - p2.y) * (p1.y - p2.y))) * 1000000; | |||
| long t = c / 2 + 1; | |||
| while (t * t > c || (t + 1) * (t + 1) <= c) | |||
| t = (c / t + t) / 2; | |||
| if (t * t == c) return (double)t / 1000.0; | |||
| else return (double)(t + 1) / 1000.0; | |||
| } | |||
| public double Length() | |||
| { | |||
| return Math.Sqrt(((long)x * x) + ((long)y * y)); | |||
| @@ -47,4 +47,6 @@ | |||
| ## 开发人员 | |||
| - ......(自己加) | |||
| - 黄淞:底层逻辑 | |||
| - 游笑权:Client | |||
| - 高思研:Server | |||
| @@ -10,7 +10,7 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.53.0"> | |||
| @@ -1,5 +1,5 @@ | |||
| @echo off | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --playbackFile .\test.thuaipb --playbackSpeed 2 | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --playbackFile .\longtest.thuaipb --playbackSpeed 4 | |||
| ping -n 2 127.0.0.1 > NUL | |||
| @@ -7,7 +7,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.22.3" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||