| @@ -10,6 +10,7 @@ jobs: | |||||
| source: '.' | source: '.' | ||||
| extensions: 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,i,ixx,ipp,i++' | extensions: 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,i,ixx,ipp,i++' | ||||
| clangFormatVersion: 14 | clangFormatVersion: 14 | ||||
| exclude: './players' | |||||
| inplace: False | inplace: False | ||||
| dotnet-format-checking: | dotnet-format-checking: | ||||
| @@ -112,7 +112,7 @@ jobs: | |||||
| - name: Pip Install paramiko | - name: Pip Install paramiko | ||||
| run: pip install paramiko | run: pip install paramiko | ||||
| - uses: actions/download-artifact@v2 | |||||
| - uses: actions/download-artifact@v3 | |||||
| with: | with: | ||||
| name: my-artifact | name: my-artifact | ||||
| path: ./THUAI6 | path: ./THUAI6 | ||||
| @@ -0,0 +1,9 @@ | |||||
| # CAPI: go | |||||
| ## 简介 | |||||
| Go 通信组件与选手接口 | |||||
| ## 敬请期待 | |||||
| Go 选手接口目前还在实验当中,参见:[实验性选手 Go 接口](../../experimental/CAPI/go/) | |||||
| @@ -14,8 +14,8 @@ | |||||
| </ItemGroup>--> | </ItemGroup>--> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| <PackageReference Include="Google.Protobuf.Tools" Version="3.23.3" /> | |||||
| <PackageReference Include="Grpc" Version="2.46.6" /> | <PackageReference Include="Grpc" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | <PackageReference Include="Grpc.Core" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | <PackageReference Include="Grpc.Tools" Version="2.54.0"> | ||||
| @@ -4,23 +4,25 @@ cd ../.. | |||||
| for i in {1..3} | for i in {1..3} | ||||
| do | do | ||||
| find . -iname "*.c" \ | |||||
| -or -iname "*.h" \ | |||||
| -or -iname "*.C" \ | |||||
| -or -iname "*.H" \ | |||||
| -or -iname "*.cpp" \ | |||||
| -or -iname "*.hpp" \ | |||||
| -or -iname "*.cc" \ | |||||
| -or -iname "*.hh" \ | |||||
| -or -iname "*.c++" \ | |||||
| -or -iname "*.h++" \ | |||||
| -or -iname "*.cxx" \ | |||||
| -or -iname "*.hxx" \ | |||||
| -or -iname "*.i" \ | |||||
| -or -iname "*.ixx" \ | |||||
| -or -iname "*.ipp" \ | |||||
| -or -iname "*.i++" \ | |||||
| | xargs clang-format -i | |||||
| find . \ | |||||
| -not -path "./players/*" -and \( \ | |||||
| -iname "*.c" \ | |||||
| -or -iname "*.h" \ | |||||
| -or -iname "*.C" \ | |||||
| -or -iname "*.H" \ | |||||
| -or -iname "*.cpp" \ | |||||
| -or -iname "*.hpp" \ | |||||
| -or -iname "*.cc" \ | |||||
| -or -iname "*.hh" \ | |||||
| -or -iname "*.c++" \ | |||||
| -or -iname "*.h++" \ | |||||
| -or -iname "*.cxx" \ | |||||
| -or -iname "*.hxx" \ | |||||
| -or -iname "*.i" \ | |||||
| -or -iname "*.ixx" \ | |||||
| -or -iname "*.ipp" \ | |||||
| -or -iname "*.i++" \ | |||||
| \) | xargs clang-format -i | |||||
| done | done | ||||
| pushd logic && dotnet format && popd | pushd logic && dotnet format && popd | ||||
| @@ -277,7 +277,12 @@ $$ | |||||
| - 主动技能 | - 主动技能 | ||||
| 0. 惩罚 Punish | 0. 惩罚 Punish | ||||
| - CD:45s | - CD:45s | ||||
| - “使用瞬间,在视野距离/3范围内(不是可视范围)的翻窗、开锁门、攻击前后摇、使用技能期间的捣蛋鬼会被眩晕(3070+$\frac{500×已受伤害}{基本伤害(1500000)×2^{开局老师数量-1}}$)ms” | |||||
| - 使用瞬间,在视野距离/3范围内(不是可视范围)的翻窗、开锁门、攻击前后摇、使用技能期间的捣蛋鬼会被眩晕 | |||||
| $$ | |||||
| (3070+\frac{500×已受伤害}{基本伤害(1500000)×2^{开局老师数量-1}})ms | |||||
| $$ | |||||
| - 其眩晕得分为正常眩晕得分/2^开局老师数量-1^ | - 其眩晕得分为正常眩晕得分/2^开局老师数量-1^ | ||||
| 1. 喝茶 HaveTea | 1. 喝茶 HaveTea | ||||
| - CD:90s | - CD:90s | ||||
| @@ -0,0 +1,5 @@ | |||||
| # experimental/CAPI | |||||
| ## 简介 | |||||
| 实验性选手接口 | |||||
| @@ -0,0 +1,28 @@ | |||||
| # If you prefer the allow list template instead of the deny list, see community template: | |||||
| # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore | |||||
| # | |||||
| # Binaries for programs and plugins | |||||
| *.exe | |||||
| *.exe~ | |||||
| *.dll | |||||
| *.so | |||||
| *.dylib | |||||
| # Test binary, built with `go test -c` | |||||
| *.test | |||||
| # Output of the go coverage tool, specifically when used with LiteIDE | |||||
| *.out | |||||
| # Dependency directories (remove the comment below to include it) | |||||
| # vendor/ | |||||
| # Go workspace file | |||||
| go.work | |||||
| # Protocol Buffers generated code directory | |||||
| /API/proto/ | |||||
| # Executables | |||||
| /API/API | |||||
| /API/API.exe | |||||
| @@ -0,0 +1,15 @@ | |||||
| package main | |||||
| import ( | |||||
| thuai6 "API/thuai6" | |||||
| ) | |||||
| const Asynchronous = false | |||||
| func GetStudentType() []thuai6.StudentType { | |||||
| return []thuai6.StudentType{thuai6.Athlete, thuai6.Teacher, thuai6.StraightAStudent, thuai6.Sunshine} | |||||
| } | |||||
| func GetTrickerType() thuai6.TrickerType { | |||||
| return thuai6.Assassin | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| package core | |||||
| import ( | |||||
| thuai6 "API/thuai6" | |||||
| ) | |||||
| type ILogic interface { | |||||
| Move(time int64, angle float64) bool | |||||
| TryConnection() bool | |||||
| GetStudentSelfInfo() *thuai6.Student | |||||
| GetTrickerSelfInfo() *thuai6.Tricker | |||||
| } | |||||
| type StudentAPI struct { | |||||
| logic ILogic | |||||
| } | |||||
| type TrickerAPI struct { | |||||
| logic ILogic | |||||
| } | |||||
| func NewStudentAPI(logic ILogic) *StudentAPI { | |||||
| return &StudentAPI{ | |||||
| logic: logic, | |||||
| } | |||||
| } | |||||
| func NewTrickerAPI(logic ILogic) *TrickerAPI { | |||||
| return &TrickerAPI{ | |||||
| logic: logic, | |||||
| } | |||||
| } | |||||
| func (api *StudentAPI) Move(timeInMilliseconds int64, angleInRadian float64) <-chan bool { | |||||
| res := make(chan bool, 1) | |||||
| go func() { | |||||
| res <- api.logic.Move(timeInMilliseconds, angleInRadian) | |||||
| }() | |||||
| return res | |||||
| } | |||||
| func (api *StudentAPI) GetSelfInfo() *thuai6.Student { | |||||
| return api.logic.GetStudentSelfInfo() | |||||
| } | |||||
| func (api *StudentAPI) StartTimer() { | |||||
| // Nothing | |||||
| } | |||||
| func (api *StudentAPI) EndTimer() { | |||||
| // Nothing | |||||
| } | |||||
| func (api *StudentAPI) Play(ai thuai6.IAI) { | |||||
| ai.StudentPlay(api) | |||||
| } | |||||
| func (api *TrickerAPI) Move(timeInMilliseconds int64, angleInRadian float64) <-chan bool { | |||||
| res := make(chan bool, 1) | |||||
| go func() { | |||||
| res <- api.logic.Move(timeInMilliseconds, angleInRadian) | |||||
| }() | |||||
| return res | |||||
| } | |||||
| func (api *TrickerAPI) GetSelfInfo() *thuai6.Tricker { | |||||
| return api.logic.GetTrickerSelfInfo() | |||||
| } | |||||
| func (api *TrickerAPI) StartTimer() { | |||||
| // Nothing | |||||
| } | |||||
| func (api *TrickerAPI) EndTimer() { | |||||
| // Nothing | |||||
| } | |||||
| func (api *TrickerAPI) Play(ai thuai6.IAI) { | |||||
| ai.TrickerPlay(api) | |||||
| } | |||||
| @@ -0,0 +1,97 @@ | |||||
| package core | |||||
| import ( | |||||
| pb "API/proto" | |||||
| thuai6 "API/thuai6" | |||||
| "context" | |||||
| "sync" | |||||
| "google.golang.org/grpc" | |||||
| "google.golang.org/grpc/credentials/insecure" | |||||
| ) | |||||
| type Communication struct { | |||||
| stub pb.AvailableServiceClient | |||||
| messageToClient *pb.MessageToClient | |||||
| haveNewMessage bool | |||||
| mtxMessage sync.Mutex | |||||
| cvMessage *sync.Cond | |||||
| } | |||||
| func NewCommunication(ip string, port string) (*Communication, error) { | |||||
| addr := ip + ":" + port | |||||
| conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| stub := pb.NewAvailableServiceClient(conn) | |||||
| comm := &Communication{stub: stub, messageToClient: nil, haveNewMessage: false, mtxMessage: sync.Mutex{}} | |||||
| comm.cvMessage = sync.NewCond(&comm.mtxMessage) | |||||
| return comm, nil | |||||
| } | |||||
| func (self *Communication) TryConnection(playerID int64) bool { | |||||
| res, err := self.stub.TryConnection(context.Background(), &pb.IDMsg{PlayerId: playerID}) | |||||
| if err != nil { | |||||
| // TODO: do loggings | |||||
| return false | |||||
| } | |||||
| return res.GetActSuccess() | |||||
| } | |||||
| func (self *Communication) AddPlayer(playerID int64, playerType thuai6.PlayerType, studentType thuai6.StudentType, trickerType thuai6.TrickerType) { | |||||
| var playerMsg pb.PlayerMsg | |||||
| playerMsg.PlayerId = playerID | |||||
| playerMsg.PlayerType = thuai6.PlayerTypeToProto[playerType] | |||||
| if playerType == thuai6.StudentPlayer { | |||||
| playerMsg.JobType = &pb.PlayerMsg_StudentType{StudentType: thuai6.StudentTypeToProto[studentType]} | |||||
| } else if playerType == thuai6.TrickerPlayer { | |||||
| playerMsg.JobType = &pb.PlayerMsg_TrickerType{TrickerType: thuai6.TrickerTypeToProto[trickerType]} | |||||
| } else { | |||||
| // TODO: Report ERROR | |||||
| } | |||||
| go func() { | |||||
| msgReader, err := self.stub.AddPlayer(context.Background(), &playerMsg) | |||||
| if err != nil { | |||||
| // TODO: Report ERROR | |||||
| } | |||||
| for { | |||||
| msg, err := msgReader.Recv() | |||||
| if err != nil { | |||||
| break | |||||
| } | |||||
| func() { | |||||
| self.mtxMessage.Lock() | |||||
| defer self.mtxMessage.Unlock() | |||||
| self.messageToClient = msg | |||||
| self.haveNewMessage = true | |||||
| self.cvMessage.Signal() | |||||
| }() | |||||
| } | |||||
| }() | |||||
| } | |||||
| func (self *Communication) Move(time int64, angle float64, playerID int64) bool { | |||||
| res, err := self.stub.Move(context.Background(), &pb.MoveMsg{ | |||||
| TimeInMilliseconds: time, | |||||
| Angle: angle, | |||||
| PlayerId: playerID, | |||||
| }) | |||||
| if err != nil { | |||||
| // TODO: do loggings | |||||
| return false | |||||
| } | |||||
| return res.GetActSuccess() | |||||
| } | |||||
| func (self *Communication) GetMessageToClient() pb.MessageToClient { | |||||
| self.mtxMessage.Lock() | |||||
| defer self.mtxMessage.Unlock() | |||||
| for !self.haveNewMessage { | |||||
| self.cvMessage.Wait() | |||||
| } | |||||
| self.haveNewMessage = false | |||||
| return *self.messageToClient | |||||
| } | |||||
| @@ -0,0 +1,202 @@ | |||||
| package core | |||||
| import ( | |||||
| "errors" | |||||
| "sync" | |||||
| "sync/atomic" | |||||
| main "API" | |||||
| pb "API/proto" | |||||
| thuai6 "API/thuai6" | |||||
| ) | |||||
| type Logic struct { | |||||
| communication *Communication | |||||
| playerType thuai6.PlayerType | |||||
| playerID int64 | |||||
| studentType thuai6.StudentType | |||||
| trickerType thuai6.TrickerType | |||||
| timer thuai6.IGameTimer | |||||
| mtxAI sync.Mutex | |||||
| mtxState sync.Mutex | |||||
| mtxBuffer sync.Mutex | |||||
| cvBuffer *sync.Cond | |||||
| cvAI *sync.Cond | |||||
| state [2]thuai6.State | |||||
| currentState *thuai6.State | |||||
| bufferState *thuai6.State | |||||
| counterState int32 | |||||
| counterBuffer int32 | |||||
| gameState thuai6.GameState | |||||
| aiStart bool | |||||
| bufferUpdated bool | |||||
| aiLoop int32 | |||||
| freshed int32 | |||||
| } | |||||
| func NewLogic(playerType thuai6.PlayerType, id int64, tricker thuai6.TrickerType, student thuai6.StudentType) *Logic { | |||||
| logic := &Logic{ | |||||
| communication: nil, | |||||
| playerType: playerType, | |||||
| playerID: id, | |||||
| studentType: student, | |||||
| trickerType: tricker, | |||||
| timer: nil, | |||||
| mtxAI: sync.Mutex{}, | |||||
| mtxState: sync.Mutex{}, | |||||
| mtxBuffer: sync.Mutex{}, | |||||
| cvBuffer: nil, | |||||
| cvAI: nil, | |||||
| state: [2]thuai6.State{}, | |||||
| counterState: 0, | |||||
| counterBuffer: 0, | |||||
| gameState: thuai6.NullGameState, | |||||
| aiStart: false, | |||||
| bufferUpdated: true, | |||||
| aiLoop: 1, | |||||
| freshed: 0, | |||||
| } | |||||
| logic.currentState = &logic.state[0] | |||||
| logic.bufferState = &logic.state[1] | |||||
| logic.cvBuffer = sync.NewCond(&logic.mtxBuffer) | |||||
| logic.cvAI = sync.NewCond(&logic.mtxAI) | |||||
| return logic | |||||
| } | |||||
| func (logic *Logic) GetStudentSelfInfo() *thuai6.Student { | |||||
| logic.mtxState.Lock() | |||||
| defer logic.mtxState.Unlock() | |||||
| return logic.currentState.StudentSelf | |||||
| } | |||||
| func (logic *Logic) GetTrickerSelfInfo() *thuai6.Tricker { | |||||
| logic.mtxState.Lock() | |||||
| defer logic.mtxState.Unlock() | |||||
| return logic.currentState.TrickerSelf | |||||
| } | |||||
| func (logic *Logic) Move(time int64, angle float64) bool { | |||||
| return logic.communication.Move(time, angle, logic.playerID) | |||||
| } | |||||
| func (logic *Logic) TryConnection() bool { | |||||
| return logic.communication.TryConnection(logic.playerID) | |||||
| } | |||||
| func (logic *Logic) Wait() { | |||||
| atomic.StoreInt32(&logic.freshed, 0) | |||||
| logic.mtxBuffer.Lock() | |||||
| defer logic.mtxBuffer.Unlock() | |||||
| for atomic.LoadInt32(&logic.freshed) == 0 { | |||||
| logic.cvBuffer.Wait() | |||||
| } | |||||
| } | |||||
| func (logic *Logic) Update() { | |||||
| if main.Asynchronous == false { | |||||
| logic.mtxBuffer.Lock() | |||||
| defer logic.mtxBuffer.Unlock() | |||||
| for logic.bufferUpdated == false { | |||||
| logic.cvBuffer.Wait() | |||||
| } | |||||
| // Go 的 defer 是函数推出时执行而非 block 退出时执行,故包装一层 func | |||||
| func() { | |||||
| logic.mtxState.Lock() | |||||
| defer logic.mtxState.Unlock() | |||||
| var tmpState = logic.currentState | |||||
| logic.currentState = logic.bufferState | |||||
| logic.bufferState = tmpState | |||||
| logic.counterState = logic.counterBuffer | |||||
| }() | |||||
| logic.bufferUpdated = false | |||||
| } | |||||
| } | |||||
| func (logic *Logic) ProcessMessage() <-chan int { | |||||
| endChan := make(chan int, 1) | |||||
| go func() { | |||||
| logic.communication.AddPlayer(logic.playerID, logic.playerType, logic.studentType, logic.trickerType) | |||||
| for logic.gameState != thuai6.GameEnd { | |||||
| clientMsg := logic.communication.GetMessageToClient() | |||||
| logic.gameState = thuai6.GameStateToTHUAI6[clientMsg.GameState] | |||||
| switch logic.gameState { | |||||
| case thuai6.GameStart: | |||||
| playerGUIDs := make([]int64, 0) | |||||
| for _, obj := range clientMsg.ObjMessage { | |||||
| switch obj.MessageOfObj.(type) { | |||||
| case *(pb.MessageOfObj_StudentMessage): | |||||
| playerGUIDs = append(playerGUIDs, obj.GetStudentMessage().GetGuid()) | |||||
| } | |||||
| } | |||||
| for _, obj := range clientMsg.ObjMessage { | |||||
| switch obj.MessageOfObj.(type) { | |||||
| case *(pb.MessageOfObj_TrickerMessage): | |||||
| playerGUIDs = append(playerGUIDs, obj.GetTrickerMessage().GetGuid()) | |||||
| } | |||||
| } | |||||
| logic.currentState.Guids = playerGUIDs | |||||
| logic.bufferState.Guids = playerGUIDs | |||||
| // TODO: LoadBuffer | |||||
| break | |||||
| case thuai6.GameEnd: | |||||
| } | |||||
| } | |||||
| // TODO: ProcessMessage | |||||
| endChan <- 0 | |||||
| }() | |||||
| return endChan | |||||
| } | |||||
| func (logic *Logic) Main(createAI thuai6.CreateAIFunc, ip string, port string) error { | |||||
| comm, err := NewCommunication(ip, port) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| logic.communication = comm | |||||
| if logic.playerType == thuai6.StudentPlayer { | |||||
| logic.timer = NewStudentAPI(logic) | |||||
| } else if logic.playerType == thuai6.TrickerPlayer { | |||||
| logic.timer = NewTrickerAPI(logic) | |||||
| } else { | |||||
| return errors.New("Invalid player type") | |||||
| } | |||||
| aiThread := func() { | |||||
| // Go 的 defer 是函数推出时执行而非 block 退出时执行,故包装一层 func | |||||
| func() { | |||||
| logic.mtxAI.Lock() | |||||
| defer logic.mtxAI.Unlock() | |||||
| for logic.aiStart == false { | |||||
| logic.cvAI.Wait() | |||||
| } | |||||
| }() | |||||
| ai := createAI(logic.playerID) | |||||
| for atomic.LoadInt32(&logic.aiLoop) != 0 { | |||||
| if main.Asynchronous { | |||||
| logic.Wait() | |||||
| } else { | |||||
| logic.Update() | |||||
| } | |||||
| logic.timer.StartTimer() | |||||
| logic.timer.Play(ai) | |||||
| logic.timer.EndTimer() | |||||
| } | |||||
| } | |||||
| if logic.TryConnection() == false { | |||||
| atomic.StoreInt32(&logic.aiLoop, 0) | |||||
| return errors.New("Connection failed.") | |||||
| } | |||||
| aiEnd := make(chan int, 1) | |||||
| go func() { | |||||
| aiThread() | |||||
| aiEnd <- 1 | |||||
| }() | |||||
| <-logic.ProcessMessage() | |||||
| <-aiEnd | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| module API | |||||
| go 1.18 | |||||
| require ( | |||||
| github.com/golang/protobuf v1.5.2 // indirect | |||||
| golang.org/x/net v0.8.0 // indirect | |||||
| golang.org/x/sys v0.6.0 // indirect | |||||
| golang.org/x/text v0.8.0 // indirect | |||||
| google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect | |||||
| google.golang.org/grpc v1.54.0 // indirect | |||||
| google.golang.org/protobuf v1.30.0 // indirect | |||||
| ) | |||||
| @@ -0,0 +1,19 @@ | |||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | |||||
| github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | |||||
| github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | |||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |||||
| golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= | |||||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | |||||
| golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= | |||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||
| golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= | |||||
| golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | |||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||||
| google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= | |||||
| google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= | |||||
| google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= | |||||
| google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= | |||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | |||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | |||||
| google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= | |||||
| google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | |||||
| @@ -0,0 +1,18 @@ | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "log" | |||||
| ) | |||||
| var ch = make(chan int, 1) | |||||
| func Func() { | |||||
| log.Println(0) | |||||
| ch <- 1 | |||||
| log.Fatalln(1) | |||||
| } | |||||
| func main() { | |||||
| fmt.Println("THUAI6") | |||||
| } | |||||
| @@ -0,0 +1,3 @@ | |||||
| #!/usr/bin/env bash | |||||
| ../../../dependency/proto/go_output.sh && go get | |||||
| @@ -0,0 +1,14 @@ | |||||
| package thuai6 | |||||
| type IGameTimer interface { | |||||
| StartTimer() | |||||
| EndTimer() | |||||
| Play(ai IAI) | |||||
| } | |||||
| type IAI interface { | |||||
| StudentPlay(api IStudentAPI) | |||||
| TrickerPlay(api ITrickerAPI) | |||||
| } | |||||
| type CreateAIFunc = func(playerID int64) IAI | |||||
| @@ -0,0 +1,15 @@ | |||||
| package thuai6 | |||||
| type IAPI interface { | |||||
| Move(timeInMilliseconds int64, angleInRadian float64) <-chan bool | |||||
| } | |||||
| type IStudentAPI interface { | |||||
| IAPI | |||||
| GetSelfInfo() *Student | |||||
| } | |||||
| type ITrickerAPI interface { | |||||
| IAPI | |||||
| GetSelfInfo() *Tricker | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| package thuai6 | |||||
| type State struct { | |||||
| StudentSelf *Student | |||||
| TrickerSelf *Tricker | |||||
| Guids []int64 | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| package thuai6 | |||||
| type GameState int8 | |||||
| const ( | |||||
| NullGameState GameState = 0 | |||||
| GameStart GameState = 1 | |||||
| GameRunning GameState = 2 | |||||
| GameEnd GameState = 3 | |||||
| ) | |||||
| type PlayerType int8 | |||||
| const ( | |||||
| NullPlayerType PlayerType = 0 | |||||
| StudentPlayer PlayerType = 1 | |||||
| TrickerPlayer PlayerType = 2 | |||||
| ) | |||||
| type StudentType int8 | |||||
| const ( | |||||
| NullStudentType StudentType = 0 | |||||
| Athlete StudentType = 1 | |||||
| Teacher StudentType = 2 | |||||
| StraightAStudent StudentType = 3 | |||||
| Robot StudentType = 4 | |||||
| TechOtaku StudentType = 5 | |||||
| Sunshine StudentType = 6 | |||||
| ) | |||||
| type TrickerType int8 | |||||
| const ( | |||||
| NullTrickerType TrickerType = 0 | |||||
| Assassin TrickerType = 1 | |||||
| Klee TrickerType = 2 | |||||
| ANoisyPerson TrickerType = 3 | |||||
| Idol TrickerType = 4 | |||||
| ) | |||||
| type Player struct { | |||||
| x int32 | |||||
| y int32 | |||||
| } | |||||
| type Student struct { | |||||
| Player | |||||
| determination int32 | |||||
| } | |||||
| type Tricker struct { | |||||
| Player | |||||
| trickDesire float64 | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package thuai6 | |||||
| import ( | |||||
| pb "API/proto" | |||||
| ) | |||||
| var ( | |||||
| PlayerTypeToProto map[PlayerType]pb.PlayerType = map[PlayerType]pb.PlayerType{ | |||||
| NullPlayerType: pb.PlayerType_NULL_PLAYER_TYPE, | |||||
| StudentPlayer: pb.PlayerType_STUDENT_PLAYER, | |||||
| TrickerPlayer: pb.PlayerType_TRICKER_PLAYER, | |||||
| } | |||||
| StudentTypeToProto map[StudentType]pb.StudentType = map[StudentType]pb.StudentType{ | |||||
| NullStudentType: pb.StudentType_NULL_STUDENT_TYPE, | |||||
| Athlete: pb.StudentType_ATHLETE, | |||||
| Teacher: pb.StudentType_TEACHER, | |||||
| StraightAStudent: pb.StudentType_STRAIGHT_A_STUDENT, | |||||
| Robot: pb.StudentType_ROBOT, | |||||
| TechOtaku: pb.StudentType_TECH_OTAKU, | |||||
| Sunshine: pb.StudentType_SUNSHINE, | |||||
| } | |||||
| TrickerTypeToProto map[TrickerType]pb.TrickerType = map[TrickerType]pb.TrickerType{ | |||||
| NullTrickerType: pb.TrickerType_NULL_TRICKER_TYPE, | |||||
| Assassin: pb.TrickerType_ASSASSIN, | |||||
| Klee: pb.TrickerType_KLEE, | |||||
| ANoisyPerson: pb.TrickerType_A_NOISY_PERSON, | |||||
| Idol: pb.TrickerType_IDOL, | |||||
| } | |||||
| GameStateToTHUAI6 map[pb.GameState]GameState = map[pb.GameState]GameState{ | |||||
| pb.GameState_NULL_GAME_STATE: NullGameState, | |||||
| pb.GameState_GAME_START: GameStart, | |||||
| pb.GameState_GAME_RUNNING: GameRunning, | |||||
| pb.GameState_GAME_END: GameEnd, | |||||
| } | |||||
| ) | |||||
| @@ -0,0 +1,15 @@ | |||||
| # experimental/CAPI/go | |||||
| ## 简介 | |||||
| 实验性选手 Go 接口 | |||||
| ## 运行方法 | |||||
| **注意事项**:Visual Studio Code 的 Go 语言提示**必须**以 `main` package 所在的目录为根目录,否则可能无法进行正确的代码提示。因此以下操作的根目录均为 `/CPI/go/API/` | |||||
| ```bash | |||||
| $ ./shell/init.sh | |||||
| $ go build | |||||
| $ ./API | |||||
| ``` | |||||
| @@ -0,0 +1,9 @@ | |||||
| # experimental | |||||
| ## 简介 | |||||
| 进行实验性功能的探索 | |||||
| ## 注意事项 | |||||
| 本文件夹内的文件采用某历史版本,与新近版本未必一致。 | |||||
| @@ -0,0 +1,3 @@ | |||||
| # experimental/dependency | |||||
| 实验性依赖文件 | |||||
| @@ -0,0 +1,204 @@ | |||||
| // Message2Client | |||||
| syntax = "proto3"; | |||||
| package protobuf; | |||||
| import "MessageType.proto"; | |||||
| message MessageOfStudent | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 speed = 3; | |||||
| int32 determination = 4; // 剩余的学习毅力,相当于血量 | |||||
| int32 addiction = 5; // 沉迷程度,相当于淘汰进度 | |||||
| repeated double time_until_skill_available = 6; | |||||
| PlaceType place = 7; | |||||
| repeated PropType prop = 8; | |||||
| PlayerState player_state = 9; | |||||
| int64 guid = 10; | |||||
| BulletType bullet_type = 12; | |||||
| int32 learning_speed = 13; // 修理电机的速度 | |||||
| int32 treat_speed = 14; // 治疗的速度 | |||||
| int64 player_id = 15; | |||||
| int32 view_range = 16; // 视野距离 | |||||
| int32 radius = 17; // 半径 | |||||
| double danger_alert = 19; // 危险警报,在捣蛋鬼靠近时会有预警 | |||||
| int32 score = 20; | |||||
| int32 treat_progress = 21; // 治疗进度 | |||||
| int32 rescue_progress = 22; // 救援进度 | |||||
| StudentType student_type = 23; | |||||
| double facing_direction = 24; | |||||
| repeated StudentBuffType buff = 25; | |||||
| } | |||||
| message MessageOfTricker | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 speed = 3; | |||||
| repeated double time_until_skill_available = 5; | |||||
| PlaceType place = 6; | |||||
| repeated PropType prop = 7; | |||||
| TrickerType tricker_type = 8; | |||||
| int64 guid = 9; | |||||
| int32 score = 10; | |||||
| int64 player_id = 11; | |||||
| int32 view_range = 12; // 视野距离 | |||||
| int32 radius = 13; // 半径 | |||||
| PlayerState player_state = 14; | |||||
| double trick_desire = 15;//bgm | |||||
| double class_volume = 16;//bgm | |||||
| double facing_direction = 17; | |||||
| BulletType bullet_type = 18; | |||||
| repeated TrickerBuffType buff = 19; | |||||
| } | |||||
| message MessageOfBullet | |||||
| { | |||||
| BulletType type = 1; | |||||
| int32 x = 2; | |||||
| int32 y = 3; | |||||
| double facing_direction = 4; | |||||
| int64 guid = 5; | |||||
| PlayerType team = 6; | |||||
| PlaceType place = 7; | |||||
| double bomb_range = 8; | |||||
| int32 speed = 9; | |||||
| } | |||||
| message MessageOfBombedBullet //for Unity,直接继承自THUAI5 | |||||
| { | |||||
| BulletType type = 1; | |||||
| int32 x = 2; | |||||
| int32 y = 3; | |||||
| double facing_direction = 4; | |||||
| int64 mapping_id = 5; | |||||
| double bomb_range = 6; | |||||
| } | |||||
| message MessageOfProp // 可拾取道具的信息 | |||||
| { | |||||
| PropType type = 1; | |||||
| int32 x = 2; | |||||
| int32 y = 3; | |||||
| double facing_direction = 4; | |||||
| int64 guid = 5; | |||||
| PlaceType place = 6; | |||||
| } | |||||
| message MessageOfPickedProp //for Unity,直接继承自THUAI5 | |||||
| { | |||||
| PropType type = 1; | |||||
| int32 x = 2; | |||||
| int32 y = 3; | |||||
| double facing_direction = 4; | |||||
| int64 mapping_id = 5; | |||||
| } | |||||
| message MessageOfClassroom | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 progress = 3; | |||||
| } | |||||
| message MessageOfGate | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 progress = 3; | |||||
| } | |||||
| message MessageOfHiddenGate | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| bool opened = 3; | |||||
| } | |||||
| message MessageOfDoor | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| bool is_open = 3; | |||||
| int32 progress = 4; | |||||
| } | |||||
| message MessageOfChest | |||||
| { | |||||
| int32 x = 1; | |||||
| int32 y = 2; | |||||
| int32 progress = 3; | |||||
| } | |||||
| message MessageOfMap | |||||
| { | |||||
| message Row | |||||
| { | |||||
| repeated PlaceType col = 1; | |||||
| } | |||||
| repeated Row row = 2; | |||||
| } | |||||
| message MessageOfNews | |||||
| { | |||||
| string news = 1; | |||||
| int64 from_id = 2; | |||||
| int64 to_id = 3; | |||||
| } | |||||
| message MessageOfObj | |||||
| { | |||||
| oneof message_of_obj | |||||
| { | |||||
| MessageOfStudent student_message = 1; | |||||
| MessageOfTricker tricker_message = 2; | |||||
| MessageOfProp prop_message = 3; | |||||
| MessageOfBullet bullet_message = 4; | |||||
| MessageOfBombedBullet bombed_bullet_message = 5; | |||||
| MessageOfClassroom classroom_message = 6; | |||||
| MessageOfDoor door_message = 7; | |||||
| MessageOfGate gate_message = 8; | |||||
| MessageOfChest chest_message = 9; | |||||
| MessageOfHiddenGate hidden_gate_message = 10; | |||||
| MessageOfNews news_message = 11; | |||||
| MessageOfMap map_message = 12; | |||||
| } | |||||
| } | |||||
| message MessageOfAll | |||||
| { | |||||
| int32 game_time = 1; | |||||
| int32 subject_finished = 2; // 完成的科目数 | |||||
| int32 student_graduated = 3; // 已经毕业的学生数 | |||||
| int32 student_quited = 4; // 已经退学的学生数 | |||||
| int32 student_score = 5; | |||||
| int32 tricker_score = 6; | |||||
| } | |||||
| message MessageToClient | |||||
| { | |||||
| repeated MessageOfObj obj_message = 1; | |||||
| GameState game_state = 2; | |||||
| MessageOfAll all_message = 3; | |||||
| } | |||||
| message MoveRes // 如果打算设计撞墙保留平行速度分量,且需要返回值则可用这个(大概没啥用) | |||||
| { | |||||
| int64 actual_speed = 1; | |||||
| double actual_angle = 2; | |||||
| bool act_success = 3; | |||||
| } | |||||
| message BoolRes // 用于只需要判断执行操作是否成功的行为,如捡起道具、使用道具 | |||||
| { | |||||
| bool act_success = 1; | |||||
| } | |||||
| // message MsgRes // 用于获取队友发来的消息 | |||||
| // { | |||||
| // bool have_message = 1; // 是否有待接收的消息 | |||||
| // int64 from_player_id = 2; | |||||
| // string message_received = 3; | |||||
| // } | |||||
| @@ -0,0 +1,74 @@ | |||||
| // Message2Server | |||||
| syntax = "proto3"; | |||||
| package protobuf; | |||||
| import "MessageType.proto"; | |||||
| message PlayerMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| oneof job_type | |||||
| { | |||||
| StudentType student_type = 2; | |||||
| TrickerType tricker_type = 3; | |||||
| } | |||||
| PlayerType player_type = 4; | |||||
| } | |||||
| message MoveMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| double angle = 2; | |||||
| int64 time_in_milliseconds = 3; | |||||
| } | |||||
| message PropMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| PropType prop_type = 2; | |||||
| } | |||||
| message SendMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| int64 to_player_id = 2; | |||||
| string message = 3; | |||||
| } | |||||
| message AttackMsg // 相当于攻击 | |||||
| { | |||||
| int64 player_id = 1; | |||||
| double angle = 2; | |||||
| } | |||||
| message IDMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| } | |||||
| message TreatAndRescueMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| int64 to_player_id = 2; | |||||
| } | |||||
| message SkillMsg | |||||
| { | |||||
| int64 player_id = 1; | |||||
| int32 skill_id = 2; | |||||
| } | |||||
| // 基本继承于THUAI5,为了使发送的信息尽可能不被浪费,暂定不发这类大包。 | |||||
| // message MessageToServer | |||||
| // { | |||||
| // MessageType messageType = 1; | |||||
| // int64 playerID = 2; // 消息发送者的playerID | |||||
| // PlayerType playerType = 3; | |||||
| // HumanType humanType= 4; | |||||
| // TrickerType trickerType = 5; | |||||
| // double angle = 6; // messageType为Move, Attack时的角度 | |||||
| // PropType propType = 7; // messageType为PickProp时要捡起的道具类型,防止多个道具堆叠时出现问题 | |||||
| // int64 timeInMilliseconds = 8;//时间参数 | |||||
| // int64 ToPlayerID = 9; // messageType为Send时要发送的对象的ID | |||||
| // string message = 10; // messageType为Send时发送的消息内容 | |||||
| // } | |||||
| @@ -0,0 +1,132 @@ | |||||
| // MessageType | |||||
| syntax = "proto3"; | |||||
| package protobuf; | |||||
| enum BulletType | |||||
| { | |||||
| NULL_BULLET_TYPE = 0; | |||||
| FLYING_KNIFE = 1; | |||||
| COMMON_ATTACK_OF_TRICKER = 2; | |||||
| BOMB_BOMB = 3; | |||||
| JUMPY_DUMPTY = 4; | |||||
| ATOM_BOMB = 5; | |||||
| } | |||||
| enum PlaceType // 地图中的所有物件类型 | |||||
| { | |||||
| NULL_PLACE_TYPE = 0; | |||||
| // 地图情况,其中Gate是总体的大门,HiddenGate是地窖 | |||||
| LAND = 1; | |||||
| WALL = 2; | |||||
| GRASS = 3; | |||||
| CLASSROOM = 4; | |||||
| GATE = 5; | |||||
| HIDDEN_GATE = 6; | |||||
| WINDOW = 7; | |||||
| DOOR3 = 8; | |||||
| DOOR5 = 9; | |||||
| DOOR6 = 10; | |||||
| CHEST = 11; | |||||
| // 待补充有特殊效果的地形 | |||||
| } | |||||
| enum ShapeType // 形状类型 | |||||
| { | |||||
| NULL_SHAPE_TYPE = 0; | |||||
| CIRCLE = 1; // 人类、屠夫、可拾取道具等为圆形 | |||||
| SQUARE = 2; // 地形均为方形 | |||||
| } | |||||
| enum PropType // 地图中的可拾取道具类型 | |||||
| { | |||||
| NULL_PROP_TYPE = 0; | |||||
| ADD_SPEED = 1; | |||||
| ADD_LIFE_OR_CLAIRAUDIENCE = 2; | |||||
| ADD_HP_OR_AP = 3; | |||||
| SHIELD_OR_SPEAR = 4; | |||||
| KEY3 = 5; | |||||
| KEY5 = 6; | |||||
| KEY6 = 7; | |||||
| RECOVERY_FROM_DIZZINESS = 8; | |||||
| } | |||||
| enum StudentBuffType // 人类可用的增益效果类型 | |||||
| { | |||||
| NULL_SBUFF_TYPE = 0; | |||||
| STUDENT_ADD_SPEED = 1; | |||||
| ADD_LIFE = 2; | |||||
| SHIELD = 3; | |||||
| STUDENT_INVISIBLE = 4; | |||||
| } | |||||
| enum PlayerState | |||||
| { | |||||
| NULL_STATUS = 0; | |||||
| IDLE = 1; // 正常状态 | |||||
| LEARNING = 2; // 学习状态,相当于在修机器 | |||||
| ADDICTED = 3; // 血条归零后原地沉迷游戏 | |||||
| QUIT = 4; // 退学状态,相当于寄了 | |||||
| GRADUATED = 5; // 毕业状态,相当于逃脱了 | |||||
| TREATED = 6; | |||||
| RESCUED = 7; | |||||
| STUNNED = 8; | |||||
| TREATING = 9; | |||||
| RESCUING = 10; | |||||
| SWINGING = 11; // 后摇 | |||||
| ATTACKING = 12; // 前摇 | |||||
| LOCKING = 13; | |||||
| RUMMAGING = 14; | |||||
| CLIMBING = 15; // 翻窗 | |||||
| OPENING_A_CHEST =16; | |||||
| USING_SPECIAL_SKILL = 17; | |||||
| OPENING_A_GATE =18; | |||||
| } | |||||
| enum TrickerBuffType // 屠夫可用的增益效果类型 | |||||
| { | |||||
| NULL_TBUFF_TYPE = 0; | |||||
| TRICKER_ADD_SPEED = 1; | |||||
| SPEAR = 2; | |||||
| ADD_AP = 3; | |||||
| CLAIRAUDIENCE = 4; | |||||
| TRICKER_INVISIBLE = 5; | |||||
| } | |||||
| // 特别说明:由于Student阵营和Tricker阵营有显著的隔离,且暂定职业、主动技能和被动效果相互绑定,故不按照THUAI5的方式区分ActiveSkillType和CharacterType,而是选择了按照阵营来给不同阵营赋予不同的职业(及技能)。 | |||||
| enum PlayerType | |||||
| { | |||||
| NULL_PLAYER_TYPE = 0; | |||||
| STUDENT_PLAYER = 1; | |||||
| TRICKER_PLAYER = 2; | |||||
| } | |||||
| enum StudentType | |||||
| { | |||||
| NULL_STUDENT_TYPE = 0; | |||||
| ATHLETE = 1; | |||||
| TEACHER = 2; | |||||
| STRAIGHT_A_STUDENT = 3; | |||||
| ROBOT = 4; | |||||
| TECH_OTAKU =5; | |||||
| SUNSHINE = 6; | |||||
| } | |||||
| enum TrickerType | |||||
| { | |||||
| NULL_TRICKER_TYPE = 0; | |||||
| ASSASSIN = 1; | |||||
| KLEE = 2; | |||||
| A_NOISY_PERSON = 3; | |||||
| IDOL = 4; | |||||
| } | |||||
| // 游戏进行状态 | |||||
| enum GameState | |||||
| { | |||||
| NULL_GAME_STATE = 0; | |||||
| GAME_START = 1; | |||||
| GAME_RUNNING = 2; | |||||
| GAME_END = 3; | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| syntax = "proto3"; | |||||
| package protobuf; | |||||
| import "Message2Clients.proto"; | |||||
| import "Message2Server.proto"; | |||||
| service AvailableService | |||||
| { | |||||
| rpc TryConnection (IDMsg) returns(BoolRes); | |||||
| // 游戏开局调用一次的服务 | |||||
| rpc AddPlayer (PlayerMsg) returns(stream MessageToClient); // 连接上后等待游戏开始,server会定时通过该服务向所有client发送消息。 | |||||
| // 游戏过程中玩家执行操作的服务 | |||||
| rpc Move (MoveMsg) returns (MoveRes); | |||||
| rpc PickProp (PropMsg) returns (BoolRes); | |||||
| rpc UseProp (PropMsg) returns (BoolRes); | |||||
| rpc ThrowProp (PropMsg) returns (BoolRes); | |||||
| rpc UseSkill (SkillMsg) returns (BoolRes); | |||||
| rpc SendMessage (SendMsg) returns (BoolRes); | |||||
| // rpc GetMessage (IDMsg) returns (stream MsgRes); | |||||
| rpc StartLearning (IDMsg) returns (BoolRes); // 开始修理机器 | |||||
| rpc StartRescueMate (TreatAndRescueMsg) returns (BoolRes); // 开始救人 | |||||
| rpc StartTreatMate (TreatAndRescueMsg) returns (BoolRes); // 开始治疗 | |||||
| rpc Attack (AttackMsg) returns (BoolRes); // 攻击 | |||||
| rpc Graduate (IDMsg) returns (BoolRes); // 相当于逃跑 | |||||
| rpc OpenDoor (IDMsg) returns (BoolRes); // 开门 | |||||
| rpc CloseDoor (IDMsg) returns (BoolRes); // 关门 | |||||
| rpc SkipWindow (IDMsg) returns (BoolRes); // 窗户 | |||||
| rpc StartOpenGate (IDMsg) returns (BoolRes); // 开闸门 | |||||
| rpc StartOpenChest (IDMsg) returns (BoolRes); | |||||
| rpc EndAllAction (IDMsg) returns (BoolRes); // 结束所有动作 | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| version: v1 | |||||
| managed: | |||||
| enabled: true | |||||
| go_package_prefix: | |||||
| default: proto | |||||
| plugins: | |||||
| - name: gotag | |||||
| out: . | |||||
| opt: | |||||
| - auto=bson | |||||
| @@ -0,0 +1,14 @@ | |||||
| version: v1 | |||||
| managed: | |||||
| enabled: true | |||||
| go_package_prefix: | |||||
| default: job-API/proto | |||||
| plugins: | |||||
| - name: go | |||||
| out: proto | |||||
| opt: paths=source_relative | |||||
| - name: go-grpc | |||||
| out: proto | |||||
| opt: | |||||
| - paths=source_relative | |||||
| - require_unimplemented_servers=false | |||||
| @@ -0,0 +1,10 @@ | |||||
| #!/usr/bin/env bash | |||||
| set -e | |||||
| PROTO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" | |||||
| pushd "${PROTO_DIR}" | |||||
| buf generate | |||||
| rm -rf ../../CAPI/go/API/proto | |||||
| mv -f proto ../../CAPI/go/API | |||||
| popd | |||||
| @@ -15,7 +15,7 @@ | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | <PackageReference Include="CommandLineParser" Version="2.9.1" /> | ||||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | <PackageReference Include="FrameRateTask" Version="1.2.0" /> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| <PackageReference Include="Grpc" Version="2.46.6" /> | <PackageReference Include="Grpc" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | <PackageReference Include="Grpc.Core" Version="2.46.6" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| @@ -8,7 +8,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| <PackageReference Include="Grpc" Version="2.46.6" /> | <PackageReference Include="Grpc" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | <PackageReference Include="Grpc.Core" Version="2.46.6" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| @@ -8,7 +8,8 @@ namespace GameClass.GameObj | |||||
| public override ShapeType Shape => ShapeType.Circle; | public override ShapeType Shape => ShapeType.Circle; | ||||
| public override bool IsRigid => false; | public override bool IsRigid => false; | ||||
| public long MappingID { get; } | public long MappingID { get; } | ||||
| public Bullet bulletHasBombed; | |||||
| public readonly Bullet bulletHasBombed; | |||||
| public readonly XY facingDirection; | |||||
| public BombedBullet(Bullet bullet) : | public BombedBullet(Bullet bullet) : | ||||
| base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) | base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) | ||||
| @@ -8,7 +8,7 @@ namespace GameClass.GameObj | |||||
| public CommonAttackOfGhost(Character player, XY pos, int radius = GameData.bulletRadius) : | public CommonAttackOfGhost(Character player, XY pos, int radius = GameData.bulletRadius) : | ||||
| base(player, radius, pos) | base(player, radius, pos) | ||||
| { | { | ||||
| ap = GameData.basicApOfGhost; | |||||
| AP.Set(GameData.basicApOfGhost); | |||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double AttackDistance => GameData.basicAttackShortRange; | public override double AttackDistance => GameData.basicAttackShortRange; | ||||
| @@ -45,7 +45,7 @@ namespace GameClass.GameObj | |||||
| public Strike(Character player, XY pos, int radius = GameData.bulletRadius) : | public Strike(Character player, XY pos, int radius = GameData.bulletRadius) : | ||||
| base(player, radius, pos) | base(player, radius, pos) | ||||
| { | { | ||||
| ap = GameData.basicApOfGhost * 16 / 15; | |||||
| AP.Set(GameData.basicApOfGhost * 16 / 15); | |||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double AttackDistance => GameData.basicAttackShortRange * 20 / 22; | public override double AttackDistance => GameData.basicAttackShortRange * 20 / 22; | ||||
| @@ -83,7 +83,7 @@ namespace GameClass.GameObj | |||||
| public FlyingKnife(Character player, XY pos, int radius = GameData.bulletRadius) : | public FlyingKnife(Character player, XY pos, int radius = GameData.bulletRadius) : | ||||
| base(player, radius, pos) | base(player, radius, pos) | ||||
| { | { | ||||
| ap = GameData.basicApOfGhost * 4 / 5; | |||||
| AP.Set(GameData.basicApOfGhost * 4 / 5); | |||||
| } | } | ||||
| public override double BulletBombRange => 0; | public override double BulletBombRange => 0; | ||||
| public override double AttackDistance => GameData.basicRemoteAttackRange * 13; | public override double AttackDistance => GameData.basicRemoteAttackRange * 13; | ||||
| @@ -123,7 +123,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| public BombBomb(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | public BombBomb(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | ||||
| { | { | ||||
| ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | |||||
| AP.Set((int)(GameData.basicApOfGhost * 6.0 / 5)); | |||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange; | public override double BulletBombRange => GameData.basicBulletBombRange; | ||||
| public override double AttackDistance => GameData.basicAttackShortRange; | public override double AttackDistance => GameData.basicAttackShortRange; | ||||
| @@ -163,7 +163,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| public JumpyDumpty(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | public JumpyDumpty(Character player, XY pos, int radius = GameData.bulletRadius) : base(player, radius, pos) | ||||
| { | { | ||||
| ap = (int)(GameData.basicApOfGhost * 0.6); | |||||
| AP.Set((int)(GameData.basicApOfGhost * 0.6)); | |||||
| } | } | ||||
| public override double BulletBombRange => GameData.basicBulletBombRange / 2; | public override double BulletBombRange => GameData.basicBulletBombRange / 2; | ||||
| public override double AttackDistance => GameData.basicAttackShortRange * 18 / 22; | public override double AttackDistance => GameData.basicAttackShortRange * 18 / 22; | ||||
| @@ -11,16 +11,7 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| public abstract double BulletBombRange { get; } | public abstract double BulletBombRange { get; } | ||||
| public abstract double AttackDistance { get; } | public abstract double AttackDistance { get; } | ||||
| protected int ap; | |||||
| public int AP | |||||
| { | |||||
| get => Interlocked.CompareExchange(ref ap, 0, 0); | |||||
| } | |||||
| public void AddAP(int addAp) | |||||
| { | |||||
| Interlocked.Add(ref ap, addAp); | |||||
| } | |||||
| public AtomicInt AP { get; } | |||||
| public abstract int Speed { get; } | public abstract int Speed { get; } | ||||
| public abstract bool IsRemoteAttack { get; } | public abstract bool IsRemoteAttack { get; } | ||||
| public abstract int CastTime { get; } | public abstract int CastTime { get; } | ||||
| @@ -52,8 +43,8 @@ namespace GameClass.GameObj | |||||
| public Bullet(Character player, int radius, XY Position) : | public Bullet(Character player, int radius, XY Position) : | ||||
| base(Position, radius, GameObjType.Bullet) | base(Position, radius, GameObjType.Bullet) | ||||
| { | { | ||||
| this.ReSetCanMove(true); | |||||
| this.MoveSpeed = this.Speed; | |||||
| this.CanMove.Set(true); | |||||
| this.MoveSpeed.Set(this.Speed); | |||||
| this.hasSpear = player.TryUseSpear(); | this.hasSpear = player.TryUseSpear(); | ||||
| this.Parent = player; | this.Parent = player; | ||||
| } | } | ||||
| @@ -31,12 +31,12 @@ namespace GameClass.GameObj | |||||
| protected Character(XY initPos, int initRadius, CharacterType characterType) : | protected Character(XY initPos, int initRadius, CharacterType characterType) : | ||||
| base(initPos, initRadius, GameObjType.Character) | base(initPos, initRadius, GameObjType.Character) | ||||
| { | { | ||||
| this.ReSetCanMove(true); | |||||
| this.CanMove.Set(true); | |||||
| this.score = 0; | this.score = 0; | ||||
| this.buffManager = new BuffManager(); | this.buffManager = new BuffManager(); | ||||
| this.occupation = OccupationFactory.FindIOccupation(characterType); | this.occupation = OccupationFactory.FindIOccupation(characterType); | ||||
| this.MaxHp = this.hp = Occupation.MaxHp; | this.MaxHp = this.hp = Occupation.MaxHp; | ||||
| this.MoveSpeed = this.OrgMoveSpeed = Occupation.MoveSpeed; | |||||
| this.MoveSpeed.Set(this.orgMoveSpeed = Occupation.MoveSpeed); | |||||
| this.BulletOfPlayer = this.OriBulletOfPlayer = Occupation.InitBullet; | this.BulletOfPlayer = this.OriBulletOfPlayer = Occupation.InitBullet; | ||||
| this.concealment = Occupation.Concealment; | this.concealment = Occupation.Concealment; | ||||
| this.alertnessRadius = Occupation.AlertnessRadius; | this.alertnessRadius = Occupation.AlertnessRadius; | ||||
| @@ -50,9 +50,6 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| this.ActiveSkillDictionary.Add(activeSkill, SkillFactory.FindActiveSkill(activeSkill)); | this.ActiveSkillDictionary.Add(activeSkill, SkillFactory.FindActiveSkill(activeSkill)); | ||||
| } | } | ||||
| // UsePassiveSkill(); //这一过程放到gamestart时进行 | |||||
| Debugger.Output(this, "constructed!"); | Debugger.Output(this, "constructed!"); | ||||
| } | } | ||||
| } | } | ||||
| @@ -32,83 +32,60 @@ namespace GameClass.GameObj | |||||
| /// </summary> | /// </summary> | ||||
| protected readonly int orgFixSpeed; | protected readonly int orgFixSpeed; | ||||
| private readonly object treatLock = new(); | |||||
| protected int treatSpeed = GameData.basicTreatSpeed; | protected int treatSpeed = GameData.basicTreatSpeed; | ||||
| public int TreatSpeed | public int TreatSpeed | ||||
| { | { | ||||
| get => treatSpeed; | |||||
| get | |||||
| { | |||||
| lock (treatLock) | |||||
| return treatSpeed; | |||||
| } | |||||
| set | set | ||||
| { | { | ||||
| lock (gameObjLock) | |||||
| lock (treatLock) | |||||
| { | { | ||||
| treatSpeed = value; | treatSpeed = value; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public int OrgTreatSpeed { get; protected set; } | |||||
| protected readonly int orgTreatSpeed; | |||||
| public int MaxGamingAddiction { get; protected set; } | |||||
| private int gamingAddiction; | |||||
| public int GamingAddiction | |||||
| private readonly object addictionLock = new(); | |||||
| private int maxGamingAddiction; | |||||
| public int MaxGamingAddiction | |||||
| { | { | ||||
| get => gamingAddiction; | |||||
| set | |||||
| get | |||||
| { | { | ||||
| if (value > 0) | |||||
| lock (gameObjLock) | |||||
| gamingAddiction = value <= MaxGamingAddiction ? value : MaxGamingAddiction; | |||||
| else | |||||
| lock (gameObjLock) | |||||
| gamingAddiction = 0; | |||||
| lock (addictionLock) | |||||
| return maxGamingAddiction; | |||||
| } | } | ||||
| } | |||||
| private int selfHealingTimes = 1;//剩余的自愈次数 | |||||
| public int SelfHealingTimes | |||||
| { | |||||
| get => selfHealingTimes; | |||||
| set | |||||
| protected set | |||||
| { | { | ||||
| lock (gameObjLock) | |||||
| selfHealingTimes = (value > 0) ? value : 0; | |||||
| } | |||||
| } | |||||
| private int degreeOfTreatment = 0; | |||||
| public int DegreeOfTreatment | |||||
| { | |||||
| get => degreeOfTreatment; | |||||
| private set | |||||
| { | |||||
| degreeOfTreatment = value; | |||||
| lock (addictionLock) | |||||
| { | |||||
| if (value < gamingAddiction) gamingAddiction = value; | |||||
| maxGamingAddiction = value; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| public void SetDegreeOfTreatment0() | |||||
| { | |||||
| DegreeOfTreatment = 0; | |||||
| } | |||||
| public bool SetDegreeOfTreatment(int value, Student whoTreatYou) | |||||
| private int gamingAddiction; | |||||
| public int GamingAddiction | |||||
| { | { | ||||
| if (value <= 0) { degreeOfTreatment = 0; return false; } | |||||
| if (value >= MaxHp - HP) | |||||
| get | |||||
| { | { | ||||
| whoTreatYou.AddScore(GameData.StudentScoreTreat(MaxHp - HP)); | |||||
| HP = MaxHp; | |||||
| degreeOfTreatment = 0; | |||||
| return true; | |||||
| lock (addictionLock) | |||||
| return gamingAddiction; | |||||
| } | } | ||||
| if (value >= GameData.basicTreatmentDegree) | |||||
| set | |||||
| { | { | ||||
| whoTreatYou.AddScore(GameData.StudentScoreTreat(GameData.basicTreatmentDegree)); | |||||
| HP += GameData.basicTreatmentDegree; | |||||
| DegreeOfTreatment = 0; | |||||
| return true; | |||||
| if (value > 0) | |||||
| lock (addictionLock) | |||||
| gamingAddiction = value <= maxGamingAddiction ? value : maxGamingAddiction; | |||||
| else | |||||
| lock (addictionLock) | |||||
| gamingAddiction = 0; | |||||
| } | } | ||||
| DegreeOfTreatment = value; | |||||
| return false; | |||||
| } | |||||
| public bool AddDegreeOfTreatment(int value, Student student) | |||||
| { | |||||
| return SetDegreeOfTreatment(value + degreeOfTreatment, student); | |||||
| } | } | ||||
| private int timeOfRescue = 0; | private int timeOfRescue = 0; | ||||
| @@ -128,19 +105,24 @@ namespace GameClass.GameObj | |||||
| public Student(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, characterType) | public Student(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, characterType) | ||||
| { | { | ||||
| this.orgFixSpeed = this.fixSpeed = ((IStudentType)Occupation).FixSpeed; | this.orgFixSpeed = this.fixSpeed = ((IStudentType)Occupation).FixSpeed; | ||||
| this.TreatSpeed = this.OrgTreatSpeed = ((IStudentType)Occupation).TreatSpeed; | |||||
| this.TreatSpeed = this.orgTreatSpeed = ((IStudentType)Occupation).TreatSpeed; | |||||
| this.MaxGamingAddiction = ((IStudentType)Occupation).MaxGamingAddiction; | this.MaxGamingAddiction = ((IStudentType)Occupation).MaxGamingAddiction; | ||||
| } | } | ||||
| } | } | ||||
| public class Golem : Student, IGolem | public class Golem : Student, IGolem | ||||
| { | { | ||||
| private readonly object parentLock = new(); | |||||
| private Character? parent; // 主人 | private Character? parent; // 主人 | ||||
| public Character? Parent | public Character? Parent | ||||
| { | { | ||||
| get => parent; | |||||
| get | |||||
| { | |||||
| lock (parentLock) | |||||
| return parent; | |||||
| } | |||||
| set | set | ||||
| { | { | ||||
| lock (gameObjLock) | |||||
| lock (parentLock) | |||||
| { | { | ||||
| parent = value; | parent = value; | ||||
| } | } | ||||
| @@ -8,15 +8,8 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | public partial class Character : Moveable, ICharacter // 负责人LHR摆烂终了 | ||||
| { | { | ||||
| private readonly ReaderWriterLockSlim hpReaderWriterLock = new(); | |||||
| public ReaderWriterLockSlim HPReadWriterLock => hpReaderWriterLock; | |||||
| private readonly object vampireLock = new(); | |||||
| public object VampireLock => vampire; | |||||
| private readonly object inventoryLock = new(); | |||||
| public object InventoryLock => inventoryLock; | |||||
| #region 装弹、攻击相关的基本属性及方法 | #region 装弹、攻击相关的基本属性及方法 | ||||
| private readonly object attackLock = new(); | |||||
| /// <summary> | /// <summary> | ||||
| /// 装弹冷却 | /// 装弹冷却 | ||||
| /// </summary> | /// </summary> | ||||
| @@ -25,7 +18,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| return cd; | return cd; | ||||
| } | } | ||||
| @@ -36,7 +29,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| return orgCD; | return orgCD; | ||||
| } | } | ||||
| } | } | ||||
| @@ -47,14 +40,14 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| return bulletOfPlayer; | return bulletOfPlayer; | ||||
| } | } | ||||
| } | } | ||||
| set | set | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| bulletOfPlayer = value; | bulletOfPlayer = value; | ||||
| cd = orgCD = (BulletFactory.BulletCD(value)); | cd = orgCD = (BulletFactory.BulletCD(value)); | ||||
| @@ -69,7 +62,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| return maxBulletNum; | return maxBulletNum; | ||||
| } | } | ||||
| @@ -78,17 +71,17 @@ namespace GameClass.GameObj | |||||
| private int bulletNum; | private int bulletNum; | ||||
| private int updateTimeOfBulletNum = 0; | private int updateTimeOfBulletNum = 0; | ||||
| public int UpdateBulletNum(int time) | |||||
| public int UpdateBulletNum(int time)//通过该函数获取真正的bulletNum | |||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| if (bulletNum < maxBulletNum) | |||||
| if (bulletNum < maxBulletNum && time - updateTimeOfBulletNum >= cd) | |||||
| { | { | ||||
| int add = Math.Min(maxBulletNum - bulletNum, (time - updateTimeOfBulletNum) / cd); | int add = Math.Min(maxBulletNum - bulletNum, (time - updateTimeOfBulletNum) / cd); | ||||
| updateTimeOfBulletNum += add * cd; | updateTimeOfBulletNum += add * cd; | ||||
| return (bulletNum += add); | return (bulletNum += add); | ||||
| } | } | ||||
| return maxBulletNum; | |||||
| return bulletNum; | |||||
| } | } | ||||
| } | } | ||||
| @@ -98,7 +91,7 @@ namespace GameClass.GameObj | |||||
| /// <returns>攻击操作发出的子弹</returns> | /// <returns>攻击操作发出的子弹</returns> | ||||
| public Bullet? Attack(double angle, int time) | public Bullet? Attack(double angle, int time) | ||||
| { | { | ||||
| lock (actionLock) | |||||
| lock (attackLock) | |||||
| { | { | ||||
| if (bulletOfPlayer == BulletType.Null) | if (bulletOfPlayer == BulletType.Null) | ||||
| return null; | return null; | ||||
| @@ -114,8 +107,8 @@ namespace GameClass.GameObj | |||||
| ); | ); | ||||
| Bullet? bullet = BulletFactory.GetBullet(this, res, this.bulletOfPlayer); | Bullet? bullet = BulletFactory.GetBullet(this, res, this.bulletOfPlayer); | ||||
| if (bullet == null) return null; | if (bullet == null) return null; | ||||
| if (TryAddAp()) bullet.AddAP(GameData.ApPropAdd); | |||||
| facingDirection = new(angle, bullet.AttackDistance); | |||||
| if (TryAddAp()) bullet.AP.Add(GameData.ApPropAdd); | |||||
| FacingDirection = new(angle, bullet.AttackDistance); | |||||
| return bullet; | return bullet; | ||||
| } | } | ||||
| else | else | ||||
| @@ -140,7 +133,7 @@ namespace GameClass.GameObj | |||||
| if (!(bouncer?.TeamID == this.TeamID)) | if (!(bouncer?.TeamID == this.TeamID)) | ||||
| { | { | ||||
| if (hasSpear || !HasShield) | if (hasSpear || !HasShield) | ||||
| _ = TrySubHp(subHP); | |||||
| _ = SubHp(subHP); | |||||
| if (hp <= 0) | if (hp <= 0) | ||||
| TryActivatingLIFE(); | TryActivatingLIFE(); | ||||
| } | } | ||||
| @@ -194,6 +187,9 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| #endregion | #endregion | ||||
| #region 血量相关的基本属性及方法 | #region 血量相关的基本属性及方法 | ||||
| private readonly ReaderWriterLockSlim hpReaderWriterLock = new(); | |||||
| public ReaderWriterLockSlim HPReadWriterLock => hpReaderWriterLock; | |||||
| private long maxHp; | private long maxHp; | ||||
| public long MaxHp | public long MaxHp | ||||
| { | { | ||||
| @@ -222,7 +218,8 @@ namespace GameClass.GameObj | |||||
| HPReadWriterLock.ExitWriteLock(); | HPReadWriterLock.ExitWriteLock(); | ||||
| } | } | ||||
| } | } | ||||
| } // 最大血量 | |||||
| } | |||||
| // 最大血量 | |||||
| protected long hp; | protected long hp; | ||||
| public long HP | public long HP | ||||
| { | { | ||||
| @@ -238,22 +235,23 @@ namespace GameClass.GameObj | |||||
| HPReadWriterLock.ExitReadLock(); | HPReadWriterLock.ExitReadLock(); | ||||
| } | } | ||||
| } | } | ||||
| set | |||||
| } | |||||
| public long SetHP(long value) | |||||
| { | |||||
| HPReadWriterLock.EnterWriteLock(); | |||||
| try | |||||
| { | { | ||||
| HPReadWriterLock.EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| if (value > 0) | |||||
| { | |||||
| hp = value <= maxHp ? value : maxHp; | |||||
| } | |||||
| else | |||||
| hp = 0; | |||||
| } | |||||
| finally | |||||
| if (value > 0) | |||||
| { | { | ||||
| HPReadWriterLock.ExitWriteLock(); | |||||
| return hp = value <= maxHp ? value : maxHp; | |||||
| } | } | ||||
| else | |||||
| return hp = 0; | |||||
| } | |||||
| finally | |||||
| { | |||||
| HPReadWriterLock.ExitWriteLock(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -261,7 +259,7 @@ namespace GameClass.GameObj | |||||
| /// 尝试减血 | /// 尝试减血 | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="sub">减血量</param> | /// <param name="sub">减血量</param> | ||||
| public long TrySubHp(long sub) | |||||
| public long SubHp(long sub) | |||||
| { | { | ||||
| HPReadWriterLock.EnterWriteLock(); | HPReadWriterLock.EnterWriteLock(); | ||||
| try | try | ||||
| @@ -284,6 +282,23 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public long AddHP(long add) | |||||
| { | |||||
| HPReadWriterLock.EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| long previousHp = hp; | |||||
| return (hp = (hp + add > maxHp) ? maxHp : hp + add) - previousHp; | |||||
| } | |||||
| finally | |||||
| { | |||||
| HPReadWriterLock.ExitWriteLock(); | |||||
| } | |||||
| } | |||||
| private readonly object vampireLock = new(); | |||||
| public object VampireLock => vampire; | |||||
| private double vampire = 0; // 回血率:0-1之间 | private double vampire = 0; // 回血率:0-1之间 | ||||
| public double Vampire | public double Vampire | ||||
| { | { | ||||
| @@ -306,8 +321,65 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public double OriVampire { get; protected set; } | public double OriVampire { get; protected set; } | ||||
| private readonly object treatLock = new(); | |||||
| private int degreeOfTreatment = 0; | |||||
| public int DegreeOfTreatment | |||||
| { | |||||
| get | |||||
| { | |||||
| HPReadWriterLock.EnterReadLock(); | |||||
| try | |||||
| { | |||||
| return degreeOfTreatment; | |||||
| } | |||||
| finally | |||||
| { | |||||
| HPReadWriterLock.ExitReadLock(); | |||||
| } | |||||
| } | |||||
| } | |||||
| public void SetDegreeOfTreatment0() | |||||
| { | |||||
| HPReadWriterLock.EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| degreeOfTreatment = 0; | |||||
| } | |||||
| finally | |||||
| { | |||||
| HPReadWriterLock.ExitWriteLock(); | |||||
| } | |||||
| } | |||||
| public bool AddDegreeOfTreatment(int value, Student whoTreatYou) | |||||
| { | |||||
| HPReadWriterLock.EnterWriteLock(); | |||||
| try | |||||
| { | |||||
| if (value >= maxHp - hp) | |||||
| { | |||||
| whoTreatYou.AddScore(GameData.StudentScoreTreat(maxHp - hp)); | |||||
| hp = maxHp; | |||||
| degreeOfTreatment = 0; | |||||
| return true; | |||||
| } | |||||
| if (value >= GameData.basicTreatmentDegree) | |||||
| { | |||||
| whoTreatYou.AddScore(GameData.StudentScoreTreat(GameData.basicTreatmentDegree)); | |||||
| hp += GameData.basicTreatmentDegree; | |||||
| degreeOfTreatment = 0; | |||||
| return true; | |||||
| } | |||||
| degreeOfTreatment = value; | |||||
| } | |||||
| finally | |||||
| { | |||||
| HPReadWriterLock.ExitWriteLock(); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| #endregion | #endregion | ||||
| #region 状态相关的基本属性与方法 | |||||
| #region 查询状态相关的基本属性与方法 | |||||
| private PlayerStateType playerState = PlayerStateType.Null; | private PlayerStateType playerState = PlayerStateType.Null; | ||||
| public PlayerStateType PlayerState | public PlayerStateType PlayerState | ||||
| { | { | ||||
| @@ -371,6 +443,8 @@ namespace GameClass.GameObj | |||||
| || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Charmed | || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Charmed | ||||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | ||||
| } | } | ||||
| #endregion | |||||
| #region 更改状态相关的属性和方法 | |||||
| private GameObj? whatInteractingWith = null; | private GameObj? whatInteractingWith = null; | ||||
| public GameObj? WhatInteractingWith | public GameObj? WhatInteractingWith | ||||
| { | { | ||||
| @@ -383,19 +457,6 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public bool StartThread(long stateNum, RunningStateType runningState) | |||||
| { | |||||
| lock (ActionLock) | |||||
| { | |||||
| if (this.StateNum == stateNum) | |||||
| { | |||||
| this.runningState = runningState; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private long ChangePlayerState(RunningStateType running, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | private long ChangePlayerState(RunningStateType running, PlayerStateType value = PlayerStateType.Null, GameObj? gameObj = null) | ||||
| { | { | ||||
| //只能被SetPlayerState引用 | //只能被SetPlayerState引用 | ||||
| @@ -492,13 +553,7 @@ namespace GameClass.GameObj | |||||
| if (value == PlayerStateType.Rescued) return -1; | if (value == PlayerStateType.Rescued) return -1; | ||||
| Door door = (Door)lastObj!; | Door door = (Door)lastObj!; | ||||
| door.StopOpen(); | door.StopOpen(); | ||||
| ReleaseTool(door.DoorNum switch | |||||
| { | |||||
| 3 => PropType.Key3, | |||||
| 5 => PropType.Key5, | |||||
| _ => PropType.Key6, | |||||
| } | |||||
| ); | |||||
| ReleaseTool(door.KeyType); | |||||
| return ChangePlayerState(runningState, value, gameObj); | return ChangePlayerState(runningState, value, gameObj); | ||||
| case PlayerStateType.UsingSkill: | case PlayerStateType.UsingSkill: | ||||
| { | { | ||||
| @@ -573,13 +628,26 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public bool StartThread(long stateNum, RunningStateType runningState) | |||||
| { | |||||
| lock (ActionLock) | |||||
| { | |||||
| if (this.StateNum == stateNum) | |||||
| { | |||||
| this.runningState = runningState; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public bool TryToRemoveFromGame(PlayerStateType playerStateType) | public bool TryToRemoveFromGame(PlayerStateType playerStateType) | ||||
| { | { | ||||
| lock (actionLock) | lock (actionLock) | ||||
| { | { | ||||
| if (SetPlayerState(RunningStateType.RunningForcibly, playerStateType) == -1) return false; | if (SetPlayerState(RunningStateType.RunningForcibly, playerStateType) == -1) return false; | ||||
| TryToRemove(); | TryToRemove(); | ||||
| ReSetCanMove(false); | |||||
| CanMove.Set(false); | |||||
| position = GameData.PosWhoDie; | position = GameData.PosWhoDie; | ||||
| } | } | ||||
| return true; | return true; | ||||
| @@ -619,6 +687,9 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| #region 道具和buff相关属性、方法 | #region 道具和buff相关属性、方法 | ||||
| private readonly object inventoryLock = new(); | |||||
| public object InventoryLock => inventoryLock; | |||||
| private Gadget[] propInventory = new Gadget[GameData.maxNumOfPropInPropInventory] | private Gadget[] propInventory = new Gadget[GameData.maxNumOfPropInPropInventory] | ||||
| {new NullProp(), new NullProp(),new NullProp() }; | {new NullProp(), new NullProp(),new NullProp() }; | ||||
| public Gadget[] PropInventory | public Gadget[] PropInventory | ||||
| @@ -639,7 +710,7 @@ namespace GameClass.GameObj | |||||
| /// 使用物品栏中的道具 | /// 使用物品栏中的道具 | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns>被使用的道具</returns> | /// <returns>被使用的道具</returns> | ||||
| public Gadget UseProp(int indexing) | |||||
| public Gadget ConsumeProp(int indexing) | |||||
| { | { | ||||
| if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory) | if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory) | ||||
| return new NullProp(); | return new NullProp(); | ||||
| @@ -652,7 +723,7 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public Gadget UseProp(PropType propType) | |||||
| public Gadget ConsumeProp(PropType propType) | |||||
| { | { | ||||
| if (propType == PropType.Null) | if (propType == PropType.Null) | ||||
| { | { | ||||
| @@ -687,7 +758,7 @@ namespace GameClass.GameObj | |||||
| return new NullProp(); | return new NullProp(); | ||||
| } | } | ||||
| public bool UseTool(PropType propType) | |||||
| public bool UseTool(PropType propType)//占用道具,使其不能重复使用和被消耗 | |||||
| { | { | ||||
| lock (inventoryLock) | lock (inventoryLock) | ||||
| { | { | ||||
| @@ -731,7 +802,7 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| public void AddMoveSpeed(int buffTime, double add = 1.0) => buffManager.AddMoveSpeed(add, buffTime, newVal => | public void AddMoveSpeed(int buffTime, double add = 1.0) => buffManager.AddMoveSpeed(add, buffTime, newVal => | ||||
| { MoveSpeed = newVal < GameData.characterMaxSpeed ? newVal : GameData.characterMaxSpeed; }, | |||||
| { MoveSpeed.Set(newVal < GameData.characterMaxSpeed ? newVal : GameData.characterMaxSpeed); }, | |||||
| OrgMoveSpeed); | OrgMoveSpeed); | ||||
| public bool HasFasterSpeed => buffManager.HasFasterSpeed; | public bool HasFasterSpeed => buffManager.HasFasterSpeed; | ||||
| @@ -26,26 +26,14 @@ namespace GameClass.GameObj | |||||
| protected XY position; | protected XY position; | ||||
| public abstract XY Position { get; } | public abstract XY Position { get; } | ||||
| protected XY facingDirection = new(1, 0); | |||||
| public abstract XY FacingDirection { get; } | |||||
| public abstract bool CanMove { get; } | |||||
| public abstract bool IsRigid { get; } | public abstract bool IsRigid { get; } | ||||
| public abstract ShapeType Shape { get; } | public abstract ShapeType Shape { get; } | ||||
| protected int isRemoved = 0; | |||||
| public bool IsRemoved | |||||
| { | |||||
| get | |||||
| { | |||||
| return (Interlocked.CompareExchange(ref isRemoved, 0, 0) == 1); | |||||
| } | |||||
| } | |||||
| public AtomicBool IsRemoved { get; } = new AtomicBool(false); | |||||
| public virtual bool TryToRemove() | public virtual bool TryToRemove() | ||||
| { | { | ||||
| return Interlocked.Exchange(ref isRemoved, 1) == 0; | |||||
| return IsRemoved.TrySet(true); | |||||
| } | } | ||||
| public int Radius { get; } | public int Radius { get; } | ||||
| @@ -7,10 +7,6 @@ namespace GameClass.GameObj | |||||
| public override XY Position => position; | public override XY Position => position; | ||||
| public override XY FacingDirection => facingDirection; | |||||
| public override bool CanMove => false; | |||||
| public Immovable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) | public Immovable(XY initPos, int initRadius, GameObjType initType) : base(initPos, initRadius, initType) | ||||
| { | { | ||||
| } | } | ||||
| @@ -20,9 +20,21 @@ namespace GameClass.GameObj | |||||
| public Gadget[] PropInChest => propInChest; | public Gadget[] PropInChest => propInChest; | ||||
| private long openStartTime = 0; | private long openStartTime = 0; | ||||
| public long OpenStartTime => openStartTime; | |||||
| public long OpenStartTime | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (gameObjLock) return openStartTime; | |||||
| } | |||||
| } | |||||
| private Character? whoOpen = null; | private Character? whoOpen = null; | ||||
| public Character? WhoOpen => whoOpen; | |||||
| public Character? WhoOpen | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (gameObjLock) return whoOpen; | |||||
| } | |||||
| } | |||||
| public bool Open(Character character) | public bool Open(Character character) | ||||
| { | { | ||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| @@ -13,23 +13,16 @@ namespace GameClass.GameObj | |||||
| public Door(XY initPos, PlaceType placeType) : | public Door(XY initPos, PlaceType placeType) : | ||||
| base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Door) | base(initPos, GameData.numOfPosGridPerCell / 2, GameObjType.Door) | ||||
| { | { | ||||
| switch (placeType) | |||||
| keyType = placeType switch | |||||
| { | { | ||||
| case PlaceType.Door3: | |||||
| doorNum = 3; | |||||
| break; | |||||
| case PlaceType.Door5: | |||||
| doorNum = 5; | |||||
| break; | |||||
| case PlaceType.Door6: | |||||
| default: | |||||
| doorNum = 6; | |||||
| break; | |||||
| } | |||||
| PlaceType.Door3 => PropType.Key3, | |||||
| PlaceType.Door5 => PropType.Key5, | |||||
| _ => PropType.Key6, | |||||
| }; | |||||
| } | } | ||||
| private readonly int doorNum; | |||||
| public int DoorNum => doorNum; | |||||
| private readonly PropType keyType; | |||||
| public PropType KeyType => keyType; | |||||
| public override bool IsRigid => !isOpen; | public override bool IsRigid => !isOpen; | ||||
| public override ShapeType Shape => ShapeType.Square; | public override ShapeType Shape => ShapeType.Square; | ||||
| @@ -54,21 +47,7 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| private int lockDegree = 0; | |||||
| public int LockDegree | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (gameObjLock) | |||||
| return lockDegree; | |||||
| } | |||||
| set | |||||
| { | |||||
| value = (value > GameData.degreeOfLockingOrOpeningTheDoor) ? GameData.degreeOfLockingOrOpeningTheDoor : value; | |||||
| lock (gameObjLock) | |||||
| lockDegree = value; | |||||
| } | |||||
| } | |||||
| public AtomicInt LockDegree { get; } = new AtomicInt(0); | |||||
| private long openStartTime = 0; | private long openStartTime = 0; | ||||
| public long OpenStartTime | public long OpenStartTime | ||||
| @@ -119,7 +98,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| if (!isOpen) return false; | if (!isOpen) return false; | ||||
| if (whoLockOrOpen != null) return false; | if (whoLockOrOpen != null) return false; | ||||
| lockDegree = 0; | |||||
| LockDegree.Set(0); | |||||
| whoLockOrOpen = character; | whoLockOrOpen = character; | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -128,7 +107,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| { | { | ||||
| if (lockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||||
| if (LockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||||
| isOpen = false; | isOpen = false; | ||||
| whoLockOrOpen = null; | whoLockOrOpen = null; | ||||
| } | } | ||||
| @@ -157,12 +136,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| if (character.PlayerState == PlayerStateType.OpeningTheDoor) | if (character.PlayerState == PlayerStateType.OpeningTheDoor) | ||||
| { | { | ||||
| character.ReleaseTool(DoorNum switch | |||||
| { | |||||
| 3 => PropType.Key3, | |||||
| 5 => PropType.Key5, | |||||
| _ => PropType.Key6, | |||||
| }); | |||||
| character.ReleaseTool(KeyType); | |||||
| character.SetPlayerStateNaturally(); | character.SetPlayerStateNaturally(); | ||||
| } | } | ||||
| else if (character.PlayerState == PlayerStateType.LockingTheDoor) | else if (character.PlayerState == PlayerStateType.LockingTheDoor) | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Google.Protobuf.WellKnownTypes; | |||||
| using Preparation.Interface; | |||||
| using Preparation.Interface; | |||||
| using Preparation.Utility; | using Preparation.Utility; | ||||
| using System; | using System; | ||||
| @@ -24,20 +23,7 @@ namespace GameClass.GameObj | |||||
| return false; | return false; | ||||
| } | } | ||||
| private bool powerSupply = false; | |||||
| public bool PowerSupply | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (gameObjLock) | |||||
| return powerSupply; | |||||
| } | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| powerSupply = value; | |||||
| } | |||||
| } | |||||
| public AtomicBool PowerSupply { get; } = new(false); | |||||
| private long openStartTime = 0; | private long openStartTime = 0; | ||||
| public long OpenStartTime | public long OpenStartTime | ||||
| @@ -50,9 +36,10 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| public bool TryToOpen() | public bool TryToOpen() | ||||
| { | { | ||||
| if (!PowerSupply) return false; | |||||
| lock (gameObjLock) | lock (gameObjLock) | ||||
| { | { | ||||
| if (!powerSupply || openStartTime > 0) return false; | |||||
| if (openStartTime > 0) return false; | |||||
| openStartTime = Environment.TickCount64; | openStartTime = Environment.TickCount64; | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -17,24 +17,14 @@ namespace GameClass.GameObj | |||||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | public override bool IgnoreCollideExecutor(IGameObj targetObj) | ||||
| { | { | ||||
| if (!canOpen) return true; | |||||
| if (!CanOpen) return true; | |||||
| if (!IsOpen) return false; | if (!IsOpen) return false; | ||||
| if (targetObj.Type != GameObjType.Character) | if (targetObj.Type != GameObjType.Character) | ||||
| return true; // 非玩家不碰撞 | return true; // 非玩家不碰撞 | ||||
| return false; | return false; | ||||
| } | } | ||||
| private bool canOpen = false; | |||||
| public bool CanOpen | |||||
| { | |||||
| get => canOpen; | |||||
| set | |||||
| { | |||||
| lock (gameObjLock) | |||||
| canOpen = value; | |||||
| } | |||||
| } | |||||
| public AtomicBool CanOpen { get; } = new(false); | |||||
| private bool isOpen = false; | private bool isOpen = false; | ||||
| public bool IsOpen | public bool IsOpen | ||||
| @@ -26,7 +26,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| Random r = new Random(Environment.TickCount); | Random r = new Random(Environment.TickCount); | ||||
| EmergencyExit emergencyExit = (EmergencyExit)(GameObjDict[GameObjType.EmergencyExit][r.Next(0, GameObjDict[GameObjType.EmergencyExit].Count)]); | EmergencyExit emergencyExit = (EmergencyExit)(GameObjDict[GameObjType.EmergencyExit][r.Next(0, GameObjDict[GameObjType.EmergencyExit].Count)]); | ||||
| emergencyExit.CanOpen = true; | |||||
| emergencyExit.CanOpen.Set(true); | |||||
| Preparation.Utility.Debugger.Output(emergencyExit, emergencyExit.Position.ToString()); | Preparation.Utility.Debugger.Output(emergencyExit, emergencyExit.Position.ToString()); | ||||
| } | } | ||||
| finally | finally | ||||
| @@ -41,7 +41,7 @@ namespace GameClass.GameObj | |||||
| try | try | ||||
| { | { | ||||
| foreach (Doorway doorway in GameObjDict[GameObjType.Doorway]) | foreach (Doorway doorway in GameObjDict[GameObjType.Doorway]) | ||||
| doorway.PowerSupply = true; | |||||
| doorway.PowerSupply.Set(true); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| @@ -8,7 +8,7 @@ namespace GameClass.GameObj | |||||
| { | { | ||||
| protected readonly object actionLock = new(); | protected readonly object actionLock = new(); | ||||
| public object ActionLock => actionLock; | public object ActionLock => actionLock; | ||||
| //player.actionLock>其他.actionLock | |||||
| //player.actionLock>其他.actionLock/其他Lock,应当避免两个player的actionlock互锁 | |||||
| private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | ||||
| public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | ||||
| //规定moveReaderWriterLock<actionLock | //规定moveReaderWriterLock<actionLock | ||||
| @@ -52,21 +52,22 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| public override XY FacingDirection | |||||
| protected XY facingDirection = new(1, 0); | |||||
| public XY FacingDirection | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| lock (actionLock) | lock (actionLock) | ||||
| return facingDirection; | return facingDirection; | ||||
| } | } | ||||
| set | |||||
| { | |||||
| lock (actionLock) | |||||
| facingDirection = value; | |||||
| } | |||||
| } | } | ||||
| private int isMoving = 0; | |||||
| public bool IsMoving | |||||
| { | |||||
| get => (Interlocked.CompareExchange(ref isMoving, 0, 0) == 1); | |||||
| set => Interlocked.Exchange(ref isMoving, value ? 1 : 0); | |||||
| } | |||||
| public AtomicBool IsMoving { get; } = new(false); | |||||
| // 移动,改变坐标 | // 移动,改变坐标 | ||||
| public long MovingSetPos(XY moveVec, long stateNo) | public long MovingSetPos(XY moveVec, long stateNo) | ||||
| @@ -93,41 +94,19 @@ namespace GameClass.GameObj | |||||
| } | } | ||||
| } | } | ||||
| private int canMove; | |||||
| public override bool CanMove | |||||
| { | |||||
| get => (Interlocked.CompareExchange(ref canMove, 0, 0) == 1); | |||||
| } | |||||
| public AtomicBool CanMove { get; } | |||||
| public void ReSetCanMove(bool value) | |||||
| { | |||||
| Interlocked.Exchange(ref canMove, (value ? 1 : 0)); | |||||
| } | |||||
| public bool IsAvailableForMove => !IsMoving && CanMove && !IsRemoved; // 是否能接收移动指令 | |||||
| public bool IsAvailableForMove // 是否能接收移动指令 | |||||
| { | |||||
| get | |||||
| { | |||||
| lock (actionLock) | |||||
| { | |||||
| return !IsMoving && CanMove && !IsRemoved; | |||||
| } | |||||
| } | |||||
| } | |||||
| protected int moveSpeed; | |||||
| /// <summary> | /// <summary> | ||||
| /// 移动速度 | /// 移动速度 | ||||
| /// </summary> | /// </summary> | ||||
| public int MoveSpeed | |||||
| { | |||||
| get => Interlocked.CompareExchange(ref moveSpeed, 0, 0); | |||||
| set => Interlocked.Exchange(ref moveSpeed, value); | |||||
| } | |||||
| public AtomicInt MoveSpeed { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// 原初移动速度 | /// 原初移动速度 | ||||
| /// </summary> | /// </summary> | ||||
| public int OrgMoveSpeed { get; protected set; } | |||||
| protected int orgMoveSpeed; | |||||
| public int OrgMoveSpeed => orgMoveSpeed; | |||||
| /* /// <summary> | /* /// <summary> | ||||
| /// 复活时数据重置 | /// 复活时数据重置 | ||||
| @@ -24,8 +24,8 @@ namespace GameClass.GameObj | |||||
| public Gadget(XY initPos, int radius = GameData.propRadius) : | public Gadget(XY initPos, int radius = GameData.propRadius) : | ||||
| base(initPos, radius, GameObjType.Gadget) | base(initPos, radius, GameObjType.Gadget) | ||||
| { | { | ||||
| this.ReSetCanMove(false); | |||||
| this.MoveSpeed = GameData.propMoveSpeed; | |||||
| this.CanMove.Set(false); | |||||
| this.MoveSpeed.Set(GameData.propMoveSpeed); | |||||
| } | } | ||||
| } | } | ||||
| public abstract class Tool : Gadget | public abstract class Tool : Gadget | ||||
| @@ -17,8 +17,8 @@ namespace GameClass.GameObj | |||||
| public Item(XY initPos, int radius = GameData.propRadius) : | public Item(XY initPos, int radius = GameData.propRadius) : | ||||
| base(initPos, radius, GameObjType.Item) | base(initPos, radius, GameObjType.Item) | ||||
| { | { | ||||
| this.ReSetCanMove(false); | |||||
| this.MoveSpeed = 0; | |||||
| this.CanMove.Set(false); | |||||
| this.MoveSpeed.Set(0); | |||||
| } | } | ||||
| } | } | ||||
| @@ -102,7 +102,7 @@ namespace GameEngine | |||||
| lock (obj.ActionLock) | lock (obj.ActionLock) | ||||
| { | { | ||||
| if (!obj.IsAvailableForMove) { EndMove(obj); return; } | if (!obj.IsAvailableForMove) { EndMove(obj); return; } | ||||
| obj.IsMoving = true; | |||||
| obj.IsMoving.Set(true); | |||||
| } | } | ||||
| new Thread | new Thread | ||||
| @@ -139,7 +139,7 @@ namespace GameEngine | |||||
| if (isEnded) | if (isEnded) | ||||
| { | { | ||||
| obj.IsMoving = false; | |||||
| obj.IsMoving.Set(false); | |||||
| EndMove(obj); | EndMove(obj); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -184,7 +184,7 @@ namespace GameEngine | |||||
| } | } | ||||
| if (isEnded) | if (isEnded) | ||||
| { | { | ||||
| obj.IsMoving = false; | |||||
| obj.IsMoving.Set(false); | |||||
| EndMove(obj); | EndMove(obj); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -224,7 +224,7 @@ namespace GameEngine | |||||
| } | } | ||||
| } while (flag); | } while (flag); | ||||
| } | } | ||||
| obj.IsMoving = false; // 结束移动 | |||||
| obj.IsMoving.Set(false); // 结束移动 | |||||
| EndMove(obj); | EndMove(obj); | ||||
| } | } | ||||
| } | } | ||||
| @@ -321,7 +321,7 @@ namespace Gaming | |||||
| if (playerRescued.AddTimeOfRescue(GameData.checkInterval)) | if (playerRescued.AddTimeOfRescue(GameData.checkInterval)) | ||||
| { | { | ||||
| playerRescued.SetPlayerStateNaturally(); | playerRescued.SetPlayerStateNaturally(); | ||||
| playerRescued.HP = playerRescued.MaxHp / 2; | |||||
| playerRescued.SetHP(playerRescued.MaxHp / 2); | |||||
| player.AddScore(GameData.StudentScoreRescue); | player.AddScore(GameData.StudentScoreRescue); | ||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -450,12 +450,12 @@ namespace Gaming | |||||
| player.ReSetPos(windowToPlayer + windowForClimb.Position); | player.ReSetPos(windowToPlayer + windowForClimb.Position); | ||||
| } | } | ||||
| player.MoveSpeed = player.SpeedOfClimbingThroughWindows; | |||||
| player.MoveSpeed.Set(player.SpeedOfClimbingThroughWindows); | |||||
| moveEngine.MoveObj(player, (int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2), (-1 * windowToPlayer).Angle(), stateNum); | moveEngine.MoveObj(player, (int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2), (-1 * windowToPlayer).Angle(), stateNum); | ||||
| Thread.Sleep((int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2)); | Thread.Sleep((int)(GameData.numOfPosGridPerCell * 3 * 1000 / player.MoveSpeed / 2)); | ||||
| player.MoveSpeed = player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed); | |||||
| player.MoveSpeed.Set(player.ReCalculateBuff(BuffType.AddSpeed, player.OrgMoveSpeed, GameData.MaxSpeed, GameData.MinSpeed)); | |||||
| lock (player.ActionLock) | lock (player.ActionLock) | ||||
| { | { | ||||
| @@ -479,19 +479,12 @@ namespace Gaming | |||||
| Door? doorToLock = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | Door? doorToLock = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | ||||
| if (doorToLock == null) return false; | if (doorToLock == null) return false; | ||||
| PropType propType = doorToLock.DoorNum switch | |||||
| { | |||||
| 3 => PropType.Key3, | |||||
| 5 => PropType.Key5, | |||||
| _ => PropType.Key6, | |||||
| }; | |||||
| if (!player.UseTool(propType)) return false; | |||||
| if (!player.UseTool(doorToLock.KeyType)) return false; | |||||
| long stateNum = player.SetPlayerState(RunningStateType.Waiting, PlayerStateType.LockingTheDoor, doorToLock); | long stateNum = player.SetPlayerState(RunningStateType.Waiting, PlayerStateType.LockingTheDoor, doorToLock); | ||||
| if (stateNum == -1) | if (stateNum == -1) | ||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToLock.KeyType); | |||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -502,25 +495,26 @@ namespace Gaming | |||||
| player.ThreadNum.WaitOne(); | player.ThreadNum.WaitOne(); | ||||
| if (!player.StartThread(stateNum, RunningStateType.RunningActively)) | if (!player.StartThread(stateNum, RunningStateType.RunningActively)) | ||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToLock.KeyType); | |||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| return; | return; | ||||
| } | } | ||||
| if (!doorToLock.TryLock(player)) | if (!doorToLock.TryLock(player)) | ||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToLock.KeyType); | |||||
| player.ResetPlayerState(stateNum); | player.ResetPlayerState(stateNum); | ||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| return; | return; | ||||
| } | } | ||||
| Thread.Sleep(GameData.checkInterval); | Thread.Sleep(GameData.checkInterval); | ||||
| new FrameRateTaskExecutor<int>( | new FrameRateTaskExecutor<int>( | ||||
| loopCondition: () => stateNum == player.StateNum && gameMap.Timer.IsGaming && doorToLock.LockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||||
| loopCondition: () => stateNum == player.StateNum && gameMap.Timer.IsGaming, | |||||
| loopToDo: () => | loopToDo: () => | ||||
| { | { | ||||
| if ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) != null) | if ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) != null) | ||||
| return false; | return false; | ||||
| doorToLock.LockDegree += GameData.checkInterval * player.SpeedOfOpeningOrLocking; | |||||
| if (doorToLock.LockDegree.Add(GameData.checkInterval * player.SpeedOfOpeningOrLocking) >= GameData.basicSpeedOfOpeningOrLocking) | |||||
| return false; | |||||
| return true; | return true; | ||||
| }, | }, | ||||
| timeInterval: GameData.checkInterval, | timeInterval: GameData.checkInterval, | ||||
| @@ -528,7 +522,7 @@ namespace Gaming | |||||
| ) | ) | ||||
| .Start(); | .Start(); | ||||
| doorToLock.StopLock(); | doorToLock.StopLock(); | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToLock.KeyType); | |||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| player.ResetPlayerState(stateNum); | player.ResetPlayerState(stateNum); | ||||
| } | } | ||||
| @@ -541,22 +535,15 @@ namespace Gaming | |||||
| public bool OpenDoor(Character player) | public bool OpenDoor(Character player) | ||||
| { | { | ||||
| if (player.CharacterType == CharacterType.Robot) return false; | if (player.CharacterType == CharacterType.Robot) return false; | ||||
| Door? doorToLock = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | |||||
| if (doorToLock == null) return false; | |||||
| PropType propType = doorToLock.DoorNum switch | |||||
| { | |||||
| 3 => PropType.Key3, | |||||
| 5 => PropType.Key5, | |||||
| _ => PropType.Key6, | |||||
| }; | |||||
| Door? doorToOpen = (Door?)gameMap.OneForInteract(player.Position, GameObjType.Door); | |||||
| if (doorToOpen == null) return false; | |||||
| if (!player.UseTool(propType)) return false; | |||||
| if (!player.UseTool(doorToOpen.KeyType)) return false; | |||||
| long stateNum = player.SetPlayerState(RunningStateType.Waiting, PlayerStateType.OpeningTheDoor, doorToLock); | |||||
| long stateNum = player.SetPlayerState(RunningStateType.Waiting, PlayerStateType.OpeningTheDoor, doorToOpen); | |||||
| if (stateNum == -1) | if (stateNum == -1) | ||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToOpen.KeyType); | |||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -567,13 +554,13 @@ namespace Gaming | |||||
| player.ThreadNum.WaitOne(); | player.ThreadNum.WaitOne(); | ||||
| if (!player.StartThread(stateNum, RunningStateType.RunningSleepily)) | if (!player.StartThread(stateNum, RunningStateType.RunningSleepily)) | ||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToOpen.KeyType); | |||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| return; | return; | ||||
| } | } | ||||
| if (!doorToLock.TryOpen(player)) | |||||
| if (!doorToOpen.TryOpen(player)) | |||||
| { | { | ||||
| player.ReleaseTool(propType); | |||||
| player.ReleaseTool(doorToOpen.KeyType); | |||||
| if (player.ResetPlayerState(stateNum)) | if (player.ResetPlayerState(stateNum)) | ||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| return; | return; | ||||
| @@ -582,8 +569,8 @@ namespace Gaming | |||||
| if (player.ResetPlayerState(stateNum)) | if (player.ResetPlayerState(stateNum)) | ||||
| { | { | ||||
| doorToLock.StopOpen(); | |||||
| player.ReleaseTool(propType); | |||||
| doorToOpen.StopOpen(); | |||||
| player.ReleaseTool(doorToOpen.KeyType); | |||||
| player.ThreadNum.Release(); | player.ThreadNum.Release(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -33,7 +33,7 @@ namespace Gaming | |||||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | ||||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | ||||
| BulletBomb((Bullet)obj, null); | BulletBomb((Bullet)obj, null); | ||||
| obj.ReSetCanMove(false); | |||||
| obj.CanMove.Set(false); | |||||
| } | } | ||||
| ); | ); | ||||
| this.characterManager = characterManager; | this.characterManager = characterManager; | ||||
| @@ -89,7 +89,7 @@ namespace Gaming | |||||
| { | { | ||||
| if (gameMap.Remove(bullet)) | if (gameMap.Remove(bullet)) | ||||
| { | { | ||||
| bullet.ReSetCanMove(false); | |||||
| bullet.CanMove.Set(false); | |||||
| if (bullet.BulletBombRange > 0) | if (bullet.BulletBombRange > 0) | ||||
| { | { | ||||
| BombedBullet bombedBullet = new(bullet); | BombedBullet bombedBullet = new(bullet); | ||||
| @@ -255,16 +255,12 @@ namespace Gaming | |||||
| } | } | ||||
| if (bullet != null) | if (bullet != null) | ||||
| { | { | ||||
| #if DEBUG | |||||
| Console.WriteLine($"playerID:{player.ID} successfully attacked!"); | |||||
| #endif | |||||
| Debugger.Output($"playerID:{player.ID} successfully attacked!"); | |||||
| return true; | return true; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| #if DEBUG | |||||
| Console.WriteLine($"playerID:{player.ID} has no bullets so that he can't attack!"); | |||||
| #endif | |||||
| Debugger.Output($"playerID:{player.ID} has no bullets so that he can't attack!"); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -306,32 +306,26 @@ namespace Gaming | |||||
| /// <returns>人物在受到攻击后死了吗</returns> | /// <returns>人物在受到攻击后死了吗</returns> | ||||
| public void BeAttacked(Student student, Bullet bullet) | public void BeAttacked(Student student, Bullet bullet) | ||||
| { | { | ||||
| #if DEBUG | |||||
| Debugger.Output(student, "is being shot!"); | Debugger.Output(student, "is being shot!"); | ||||
| #endif | |||||
| if (!bullet.Parent!.IsGhost()) return; | if (!bullet.Parent!.IsGhost()) return; | ||||
| if (student.CharacterType == CharacterType.StraightAStudent) | if (student.CharacterType == CharacterType.StraightAStudent) | ||||
| { | { | ||||
| ((WriteAnswers)student.FindActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0; | |||||
| ((WriteAnswers)student.FindActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation.Set(0); | |||||
| } | } | ||||
| student.SetDegreeOfTreatment0(); | student.SetDegreeOfTreatment0(); | ||||
| if (student.NoHp()) return; // 原来已经死了 | if (student.NoHp()) return; // 原来已经死了 | ||||
| #if DEBUG | |||||
| Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString()); | Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString()); | ||||
| #endif | |||||
| if (student.TryUseShield()) | if (student.TryUseShield()) | ||||
| { | { | ||||
| if (bullet.HasSpear) | if (bullet.HasSpear) | ||||
| { | { | ||||
| long subHp = student.TrySubHp(bullet.AP); | |||||
| #if DEBUG | |||||
| long subHp = student.SubHp(bullet.AP); | |||||
| Debugger.Output(this, "is being shot! Now his hp is" + student.HP.ToString()); | Debugger.Output(this, "is being shot! Now his hp is" + student.HP.ToString()); | ||||
| #endif | |||||
| bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp) + GameData.ScorePropUseSpear); | bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp) + GameData.ScorePropUseSpear); | ||||
| bullet.Parent.HP = (long)(bullet.Parent.HP + (bullet.Parent.Vampire * subHp)); | |||||
| bullet.Parent.AddHP((long)bullet.Parent.Vampire * subHp); | |||||
| } | } | ||||
| else return; | else return; | ||||
| } | } | ||||
| @@ -340,17 +334,13 @@ namespace Gaming | |||||
| long subHp; | long subHp; | ||||
| if (bullet.HasSpear) | if (bullet.HasSpear) | ||||
| { | { | ||||
| subHp = student.TrySubHp(bullet.AP + GameData.ApSpearAdd); | |||||
| #if DEBUG | |||||
| subHp = student.SubHp(bullet.AP + GameData.ApSpearAdd); | |||||
| Debugger.Output(this, "is being shot with Spear! Now his hp is" + student.HP.ToString()); | Debugger.Output(this, "is being shot with Spear! Now his hp is" + student.HP.ToString()); | ||||
| #endif | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| subHp = student.TrySubHp(bullet.AP); | |||||
| #if DEBUG | |||||
| subHp = student.SubHp(bullet.AP); | |||||
| Debugger.Output(this, "is being shot! Now his hp is" + student.HP.ToString()); | Debugger.Output(this, "is being shot! Now his hp is" + student.HP.ToString()); | ||||
| #endif | |||||
| } | } | ||||
| bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp)); | bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp)); | ||||
| if (student.CharacterType == CharacterType.Teacher) | if (student.CharacterType == CharacterType.Teacher) | ||||
| @@ -358,7 +348,7 @@ namespace Gaming | |||||
| student.AddScore(subHp * GameData.factorOfScoreWhenTeacherAttacked / GameData.basicApOfGhost / FactorTeacher); | student.AddScore(subHp * GameData.factorOfScoreWhenTeacherAttacked / GameData.basicApOfGhost / FactorTeacher); | ||||
| } | } | ||||
| bullet.Parent.HP = (int)(bullet.Parent.HP + (bullet.Parent.Vampire * subHp)); | |||||
| bullet.Parent.AddHP((long)(bullet.Parent.Vampire * subHp)); | |||||
| } | } | ||||
| if (student.HP <= 0) | if (student.HP <= 0) | ||||
| student.TryActivatingLIFE(); // 如果有复活甲 | student.TryActivatingLIFE(); // 如果有复活甲 | ||||
| @@ -393,7 +383,7 @@ namespace Gaming | |||||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | ||||
| { | { | ||||
| Gadget? prop = player.UseProp(i); | |||||
| Gadget? prop = player.ConsumeProp(i); | |||||
| if (prop != null) | if (prop != null) | ||||
| { | { | ||||
| prop.ReSetPos(player.Position); | prop.ReSetPos(player.Position); | ||||
| @@ -238,7 +238,7 @@ namespace Gaming | |||||
| Character? player = gameMap.FindPlayerToAction(playerID); | Character? player = gameMap.FindPlayerToAction(playerID); | ||||
| if (player != null) | if (player != null) | ||||
| { | { | ||||
| propManager.UseProp(player, propType); | |||||
| propManager.ConsumeProp(player, propType); | |||||
| } | } | ||||
| } | } | ||||
| public void ThrowProp(long playerID, PropType propType = PropType.Null) | public void ThrowProp(long playerID, PropType propType = PropType.Null) | ||||
| @@ -326,7 +326,7 @@ namespace Gaming | |||||
| { | { | ||||
| foreach (Character player in gameMap.GameObjDict[GameObjType.Character]) | foreach (Character player in gameMap.GameObjDict[GameObjType.Character]) | ||||
| { | { | ||||
| player.ReSetCanMove(false); | |||||
| player.CanMove.Set(false); | |||||
| } | } | ||||
| } | } | ||||
| gameMap.GameObjDict[keyValuePair.Key].Clear(); | gameMap.GameObjDict[keyValuePair.Key].Clear(); | ||||
| @@ -17,11 +17,11 @@ namespace Gaming | |||||
| private readonly CharacterManager characterManager; | private readonly CharacterManager characterManager; | ||||
| private readonly List<XY> availableCellForGenerateProp; | private readonly List<XY> availableCellForGenerateProp; | ||||
| public void UseProp(Character player, PropType propType) | |||||
| public void ConsumeProp(Character player, PropType propType) | |||||
| { | { | ||||
| if (player.CharacterType == CharacterType.Robot || player.IsRemoved) | if (player.CharacterType == CharacterType.Robot || player.IsRemoved) | ||||
| return; | return; | ||||
| Gadget prop = player.UseProp(propType); | |||||
| Gadget prop = player.ConsumeProp(propType); | |||||
| switch (prop.GetPropType()) | switch (prop.GetPropType()) | ||||
| { | { | ||||
| case PropType.ShieldOrSpear: | case PropType.ShieldOrSpear: | ||||
| @@ -46,7 +46,7 @@ namespace Gaming | |||||
| if (!player.IsGhost()) | if (!player.IsGhost()) | ||||
| if (player.HP < player.MaxHp) | if (player.HP < player.MaxHp) | ||||
| { | { | ||||
| player.HP += GameData.basicTreatmentDegree / 2; | |||||
| player.AddHP(GameData.basicTreatmentDegree / 2); | |||||
| player.AddScore(GameData.ScorePropAddHp); | player.AddScore(GameData.ScorePropAddHp); | ||||
| } | } | ||||
| else player.AddAp(GameData.PropDuration); | else player.AddAp(GameData.PropDuration); | ||||
| @@ -117,7 +117,7 @@ namespace Gaming | |||||
| { | { | ||||
| if (!gameMap.Timer.IsGaming || player.IsRemoved) | if (!gameMap.Timer.IsGaming || player.IsRemoved) | ||||
| return; | return; | ||||
| Gadget prop = player.UseProp(propType); | |||||
| Gadget prop = player.ConsumeProp(propType); | |||||
| if (prop.GetPropType() == PropType.Null) | if (prop.GetPropType() == PropType.Null) | ||||
| return; | return; | ||||
| @@ -196,7 +196,7 @@ namespace Gaming | |||||
| } | } | ||||
| if (homingMissile != null) | if (homingMissile != null) | ||||
| { | { | ||||
| homingMissile.ReSetCanMove(true); | |||||
| homingMissile.CanMove.Set(true); | |||||
| attackManager.moveEngine.MoveObj(homingMissile, GameData.checkIntervalWhenSparksNSplash - 1, (whoAttacked.Position - homingMissile.Position).Angle(), ++homingMissile.StateNum); | attackManager.moveEngine.MoveObj(homingMissile, GameData.checkIntervalWhenSparksNSplash - 1, (whoAttacked.Position - homingMissile.Position).Angle(), ++homingMissile.StateNum); | ||||
| } | } | ||||
| }, | }, | ||||
| @@ -227,7 +227,7 @@ namespace Gaming | |||||
| if (generator.Repair(((WriteAnswers)activeSkill).DegreeOfMeditation, player)) | if (generator.Repair(((WriteAnswers)activeSkill).DegreeOfMeditation, player)) | ||||
| gameMap.AddNumOfRepairedGenerators(); | gameMap.AddNumOfRepairedGenerators(); | ||||
| Debugger.Output(player, "uses WriteAnswers in" + generator.ToString() + "with " + (((WriteAnswers)activeSkill).DegreeOfMeditation).ToString()); | Debugger.Output(player, "uses WriteAnswers in" + generator.ToString() + "with " + (((WriteAnswers)activeSkill).DegreeOfMeditation).ToString()); | ||||
| ((WriteAnswers)activeSkill).DegreeOfMeditation = 0; | |||||
| ((WriteAnswers)activeSkill).DegreeOfMeditation.Set(0); | |||||
| } | } | ||||
| }, | }, | ||||
| () => | () => | ||||
| @@ -421,7 +421,7 @@ namespace Gaming | |||||
| if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character)) | if ((character.PlayerState == PlayerStateType.Addicted) && gameMap.CanSee(player, character)) | ||||
| { | { | ||||
| character.SetPlayerStateNaturally(); | character.SetPlayerStateNaturally(); | ||||
| character.HP = GameData.RemainHpWhenAddLife; | |||||
| character.SetHP(GameData.RemainHpWhenAddLife); | |||||
| ((Student)character).SetTimeOfRescue(0); | ((Student)character).SetTimeOfRescue(0); | ||||
| player.AddScore(GameData.StudentScoreRescue); | player.AddScore(GameData.StudentScoreRescue); | ||||
| break; | break; | ||||
| @@ -452,7 +452,7 @@ namespace Gaming | |||||
| if ((character.HP < character.MaxHp) && gameMap.CanSee(player, character)) | if ((character.HP < character.MaxHp) && gameMap.CanSee(player, character)) | ||||
| { | { | ||||
| player.AddScore(GameData.StudentScoreTreat(GameData.addHpWhenEncourage)); | player.AddScore(GameData.StudentScoreTreat(GameData.addHpWhenEncourage)); | ||||
| character.HP += GameData.addHpWhenEncourage; | |||||
| character.AddHP(GameData.addHpWhenEncourage); | |||||
| ((Student)character).SetDegreeOfTreatment0(); | ((Student)character).SetDegreeOfTreatment0(); | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -25,8 +25,8 @@ namespace Gaming // 被动技能开局时就释放,持续到游戏结束 | |||||
| () => gameMap.Timer.IsGaming && !player.IsRemoved, | () => gameMap.Timer.IsGaming && !player.IsRemoved, | ||||
| () => | () => | ||||
| { | { | ||||
| if (player.Commandable() && player.PlayerState != PlayerStateType.Fixing) activeSkill.DegreeOfMeditation += learningDegree * GameData.frameDuration; | |||||
| else activeSkill.DegreeOfMeditation = 0; | |||||
| if (player.Commandable() && player.PlayerState != PlayerStateType.Fixing) activeSkill.DegreeOfMeditation.Add(learningDegree * GameData.frameDuration); | |||||
| else activeSkill.DegreeOfMeditation.Set(0); | |||||
| //Debugger.Output(player, "with " + (((WriteAnswers)activeSkill).DegreeOfMeditation).ToString()); | //Debugger.Output(player, "with " + (((WriteAnswers)activeSkill).DegreeOfMeditation).ToString()); | ||||
| }, | }, | ||||
| timeInterval: GameData.frameDuration, | timeInterval: GameData.frameDuration, | ||||
| @@ -6,7 +6,8 @@ namespace Preparation.Interface | |||||
| public interface ICharacter : IMoveable | public interface ICharacter : IMoveable | ||||
| { | { | ||||
| public long TeamID { get; } | public long TeamID { get; } | ||||
| public long HP { get; set; } | |||||
| public long HP { get; } | |||||
| public long AddHP(long add); | |||||
| public long Score { get; } | public long Score { get; } | ||||
| public void AddScore(long add); | public void AddScore(long add); | ||||
| public double Vampire { get; } | public double Vampire { get; } | ||||
| @@ -7,10 +7,9 @@ namespace Preparation.Interface | |||||
| public GameObjType Type { get; } | public GameObjType Type { get; } | ||||
| public long ID { get; } | public long ID { get; } | ||||
| public XY Position { get; } // if Square, Pos equals the center | public XY Position { get; } // if Square, Pos equals the center | ||||
| public XY FacingDirection { get; } | |||||
| public bool IsRigid { get; } | public bool IsRigid { get; } | ||||
| public AtomicBool IsRemoved { get; } | |||||
| public ShapeType Shape { get; } | public ShapeType Shape { get; } | ||||
| public bool CanMove { get; } | |||||
| public int Radius { get; } // if Square, Radius equals half length of one side | public int Radius { get; } // if Square, Radius equals half length of one side | ||||
| public bool IgnoreCollideExecutor(IGameObj targetObj); // 忽略碰撞,在具体类中实现 | public bool IgnoreCollideExecutor(IGameObj targetObj); // 忽略碰撞,在具体类中实现 | ||||
| } | } | ||||
| @@ -6,15 +6,15 @@ namespace Preparation.Interface | |||||
| { | { | ||||
| public interface IMoveable : IGameObj | public interface IMoveable : IGameObj | ||||
| { | { | ||||
| public XY FacingDirection { get; set; } | |||||
| object ActionLock { get; } | object ActionLock { get; } | ||||
| public int MoveSpeed { get; } | |||||
| public bool IsMoving { get; set; } | |||||
| public bool IsRemoved { get; } | |||||
| public AtomicInt MoveSpeed { get; } | |||||
| public AtomicBool CanMove { get; } | |||||
| public AtomicBool IsMoving { get; } | |||||
| public bool IsAvailableForMove { get; } | public bool IsAvailableForMove { get; } | ||||
| public long StateNum { get; } | public long StateNum { get; } | ||||
| public Semaphore ThreadNum { get; } | public Semaphore ThreadNum { get; } | ||||
| public long MovingSetPos(XY moveVec, long stateNum); | public long MovingSetPos(XY moveVec, long stateNum); | ||||
| public void ReSetCanMove(bool value); | |||||
| public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | ||||
| { | { | ||||
| if (targetObj == null) | if (targetObj == null) | ||||
| @@ -174,12 +174,7 @@ namespace Preparation.Interface | |||||
| public override int SkillCD => GameData.commonSkillCD; | public override int SkillCD => GameData.commonSkillCD; | ||||
| public override int DurationTime => 0; | public override int DurationTime => 0; | ||||
| private int degreeOfMeditation = 0; | |||||
| public int DegreeOfMeditation | |||||
| { | |||||
| get => Interlocked.CompareExchange(ref degreeOfMeditation, 0, 0); | |||||
| set => Interlocked.Exchange(ref degreeOfMeditation, value); | |||||
| } | |||||
| public AtomicInt DegreeOfMeditation { get; } = new(0); | |||||
| } | } | ||||
| public class SummonGolem : ActiveSkill | public class SummonGolem : ActiveSkill | ||||
| @@ -9,7 +9,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -0,0 +1,99 @@ | |||||
| using System; | |||||
| using System.Threading; | |||||
| namespace Preparation.Utility | |||||
| { | |||||
| //理论上结构体最好不可变,这里采用了可变结构。 | |||||
| //其对应属性不应当有set访问器,避免不安全的=赋值 | |||||
| public struct AtomicInt | |||||
| { | |||||
| private int v; | |||||
| public AtomicInt(int x) | |||||
| { | |||||
| v = x; | |||||
| } | |||||
| public override string ToString() => Interlocked.CompareExchange(ref v, -1, -1).ToString(); | |||||
| public int Get() => Interlocked.CompareExchange(ref v, -1, -1); | |||||
| public static implicit operator int(AtomicInt aint) => Interlocked.CompareExchange(ref aint.v, -1, -1); | |||||
| public int Set(int value) => Interlocked.Exchange(ref v, value); | |||||
| public int Add(int x) => Interlocked.Add(ref v, x); | |||||
| public int Sub(int x) => Interlocked.Add(ref v, -x); | |||||
| public int Inc() => Interlocked.Increment(ref v); | |||||
| public int Dec() => Interlocked.Decrement(ref v); | |||||
| public void CompareExchange(int newV, int compareTo) => Interlocked.CompareExchange(ref v, newV, compareTo); | |||||
| /// <returns>返回操作前的值</returns> | |||||
| public int CompareExReturnOri(int newV, int compareTo) => Interlocked.CompareExchange(ref v, newV, compareTo); | |||||
| } | |||||
| public struct AtomicBool | |||||
| { | |||||
| private int v;//v==0为false,v!=0(v==1或v==-1)为true | |||||
| public AtomicBool(bool x) | |||||
| { | |||||
| v = x ? 1 : 0; | |||||
| } | |||||
| public override string ToString() => (Interlocked.CompareExchange(ref v, -2, -2) == 0) ? "false" : "true"; | |||||
| public bool Get() => (Interlocked.CompareExchange(ref v, -1, -1) != 0); | |||||
| public static implicit operator bool(AtomicBool abool) => (Interlocked.CompareExchange(ref abool.v, -1, -1) != 0); | |||||
| public bool Set(bool value) => (Interlocked.Exchange(ref v, value ? 1 : 0) != 0); | |||||
| /// <returns>赋值前的值是否与将赋予的值不相同</returns> | |||||
| public bool TrySet(bool value) | |||||
| { | |||||
| return (Interlocked.CompareExchange(ref v, value ? 1 : 0, value ? 0 : 1) ^ (value ? 1 : 0)) != 0; | |||||
| } | |||||
| public bool Invert() => Interlocked.Add(ref v, -1) != 0; | |||||
| public bool And(bool x) => Interlocked.And(ref v, x ? 1 : 0) != 0; | |||||
| public bool Or(bool x) => Interlocked.Or(ref v, x ? 1 : 0) != 0; | |||||
| } | |||||
| public struct IntProgressContinuously | |||||
| { | |||||
| private long endT = long.MaxValue; | |||||
| private long needT; | |||||
| public IntProgressContinuously(long needTime) | |||||
| { | |||||
| this.needT = needTime; | |||||
| } | |||||
| public long GetEndTime() => Interlocked.CompareExchange(ref endT, -2, -2); | |||||
| public long GetNeedTime() => Interlocked.CompareExchange(ref needT, -2, -2); | |||||
| public override string ToString() => "EndTime:" + Interlocked.CompareExchange(ref endT, -2, -2).ToString() + " ms, NeedTime:" + Interlocked.CompareExchange(ref needT, -2, -2).ToString() + " ms"; | |||||
| public long GetProgress() | |||||
| { | |||||
| long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - Environment.TickCount64; | |||||
| if (cutime <= 0) return Interlocked.CompareExchange(ref needT, -2, -2); | |||||
| return Interlocked.CompareExchange(ref needT, -2, -2) - cutime; | |||||
| } | |||||
| public double GetProgressDouble() | |||||
| { | |||||
| long cutime = Interlocked.CompareExchange(ref endT, -2, -2) - Environment.TickCount64; | |||||
| if (cutime <= 0) return 1; | |||||
| return 1.0 - ((double)cutime / Interlocked.CompareExchange(ref needT, -2, -2)); | |||||
| } | |||||
| public bool Start(long needTime) | |||||
| { | |||||
| //规定只有Start可以修改needT,且需要先访问endTime,从而避免锁(某种程度上endTime可以认为是needTime的锁) | |||||
| if (Interlocked.CompareExchange(ref endT, Environment.TickCount64 + needTime, long.MaxValue) != long.MaxValue) return false; | |||||
| if (needTime <= 2) Debugger.Output("Warning:the field of IntProgressContinuously is " + needTime.ToString() + ",which is too small."); | |||||
| Interlocked.Exchange(ref this.needT, needTime); | |||||
| return true; | |||||
| } | |||||
| public bool Start() | |||||
| { | |||||
| long needTime = Interlocked.CompareExchange(ref needT, -2, -2); | |||||
| if (Interlocked.CompareExchange(ref endT, Environment.TickCount64 + needTime, long.MaxValue) != long.MaxValue) return false; | |||||
| return true; | |||||
| } | |||||
| public void Set0() => Interlocked.Exchange(ref endT, long.MaxValue); | |||||
| public void TryStop() | |||||
| { | |||||
| if (Environment.TickCount64 < Interlocked.CompareExchange(ref endT, -2, -2)) | |||||
| Interlocked.Exchange(ref endT, long.MaxValue); | |||||
| } | |||||
| //增加新的写操作可能导致不安全 | |||||
| } | |||||
| } | |||||
| @@ -2,7 +2,6 @@ | |||||
| namespace Preparation.Utility | namespace Preparation.Utility | ||||
| { | { | ||||
| public struct XY | public struct XY | ||||
| { | { | ||||
| public int x; | public int x; | ||||
| @@ -89,7 +88,7 @@ namespace Preparation.Utility | |||||
| return Math.Atan2(y, x); | return Math.Atan2(y, x); | ||||
| } | } | ||||
| public override bool Equals(object? obj) => obj is null || obj is XY ? false : this == (XY)obj; | |||||
| public override bool Equals(object? obj) => obj is not null && obj is XY xy && this == xy; | |||||
| public override int GetHashCode() | public override int GetHashCode() | ||||
| { | { | ||||
| @@ -203,7 +203,7 @@ namespace Server | |||||
| Type = Transformation.ToBulletType(bombedBullet.bulletHasBombed.TypeOfBullet), | Type = Transformation.ToBulletType(bombedBullet.bulletHasBombed.TypeOfBullet), | ||||
| X = bombedBullet.bulletHasBombed.Position.x, | X = bombedBullet.bulletHasBombed.Position.x, | ||||
| Y = bombedBullet.bulletHasBombed.Position.y, | Y = bombedBullet.bulletHasBombed.Position.y, | ||||
| FacingDirection = bombedBullet.FacingDirection.Angle(), | |||||
| FacingDirection = bombedBullet.facingDirection.Angle(), | |||||
| MappingId = bombedBullet.MappingID, | MappingId = bombedBullet.MappingID, | ||||
| BombRange = bombedBullet.bulletHasBombed.BulletBombRange | BombRange = bombedBullet.bulletHasBombed.BulletBombRange | ||||
| } | } | ||||
| @@ -90,7 +90,7 @@ namespace Server | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| catch (Exception) | |||||
| catch | |||||
| { | { | ||||
| // Console.WriteLine(ex); | // Console.WriteLine(ex); | ||||
| } | } | ||||
| @@ -145,13 +145,13 @@ namespace Server | |||||
| semaDict[request.PlayerId].Item1.Wait(); | semaDict[request.PlayerId].Item1.Wait(); | ||||
| try | try | ||||
| { | { | ||||
| if (currentGameInfo != null) | |||||
| if (currentGameInfo != null && !exitFlag) | |||||
| { | { | ||||
| await responseStream.WriteAsync(currentGameInfo); | await responseStream.WriteAsync(currentGameInfo); | ||||
| //Console.WriteLine("Send!"); | //Console.WriteLine("Send!"); | ||||
| } | } | ||||
| } | } | ||||
| catch (Exception) | |||||
| catch | |||||
| { | { | ||||
| if (!exitFlag) | if (!exitFlag) | ||||
| { | { | ||||
| @@ -305,7 +305,7 @@ namespace Server | |||||
| public override Task<BoolRes> UseProp(PropMsg request, ServerCallContext context) | public override Task<BoolRes> UseProp(PropMsg request, ServerCallContext context) | ||||
| { | { | ||||
| #if DEBUG | #if DEBUG | ||||
| Console.WriteLine($"UseProp ID: {request.PlayerId}"); | |||||
| Console.WriteLine($"ConsumeProp ID: {request.PlayerId}"); | |||||
| #endif | #endif | ||||
| BoolRes boolRes = new(); | BoolRes boolRes = new(); | ||||
| if (request.PlayerId >= spectatorMinPlayerID) | if (request.PlayerId >= spectatorMinPlayerID) | ||||
| @@ -10,7 +10,7 @@ | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | <PackageReference Include="CommandLineParser" Version="2.9.1" /> | ||||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | <PackageReference Include="FrameRateTask" Version="1.2.0" /> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| <PackageReference Include="Grpc" Version="2.46.6" /> | <PackageReference Include="Grpc" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | <PackageReference Include="Grpc.Core" Version="2.46.6" /> | ||||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | <PackageReference Include="Grpc.Tools" Version="2.54.0"> | ||||
| @@ -7,7 +7,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -0,0 +1 @@ | |||||
| * linguist-vendored | |||||
| @@ -0,0 +1,995 @@ | |||||
| #include <array> | |||||
| #include <cmath> | |||||
| #include <queue> | |||||
| #include <sstream> | |||||
| #include <stack> | |||||
| #include <thread> | |||||
| #include <utility> | |||||
| #include <vector> | |||||
| #include "AI.h" | |||||
| #include "constants.h" | |||||
| #define PI 3.14159265358979323846 | |||||
| // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新 | |||||
| extern const bool asynchronous = false; | |||||
| // 选手需要依次将player0到player4的职业在这里定义 | |||||
| extern const std::array<THUAI6::StudentType, 4> studentType = { | |||||
| THUAI6::StudentType::StraightAStudent, | |||||
| THUAI6::StudentType::StraightAStudent, | |||||
| THUAI6::StudentType::StraightAStudent, | |||||
| THUAI6::StudentType::StraightAStudent | |||||
| }; | |||||
| extern const THUAI6::TrickerType trickerType = THUAI6::TrickerType::Assassin; | |||||
| //可以在AI.cpp内部声明变量与函数 | |||||
| struct CellPos { | |||||
| int x, y; | |||||
| }; | |||||
| struct GridPos { | |||||
| int x, y; | |||||
| }; | |||||
| class Action; | |||||
| class BFS; | |||||
| class Communication; | |||||
| class Escape; | |||||
| class GameState; | |||||
| class Action { | |||||
| public: | |||||
| enum class Direction { | |||||
| null, up, down, left, right, | |||||
| up_left, up_right, down_left, down_right | |||||
| }; //移动方式 | |||||
| static const std::vector <Direction> allMove; //所有的移动方式 | |||||
| static int isAtCellFalseCnt; | |||||
| static int classNo; //要去的教室位置 | |||||
| static CellPos lastNextPos; | |||||
| static CellPos lastDesPos; | |||||
| static Direction lastMove; | |||||
| static CellPos PredictPos; | |||||
| static bool isValidMove(CellPos judgePos, Direction comeDir, GameState state, IStudentAPI& api); | |||||
| static bool isValidMove(CellPos judgePos, Direction comeDir, GameState state, ITrickerAPI& api); | |||||
| static double getAngle(GridPos grid1, GridPos grid2); //计算两点间方位角 | |||||
| static void Move(CellPos des, IStudentAPI& api); | |||||
| static void Move(CellPos des, ITrickerAPI& api); | |||||
| static void Move(Direction directon, IStudentAPI& api); | |||||
| static void Move(Direction direction, ITrickerAPI& api); | |||||
| static CellPos directionToPos(CellPos nowPos, Direction direction); //根据当前坐标和移动方式计算出下一个坐标 | |||||
| static Direction posToDirection(CellPos nowPos, CellPos nextPos); //目标点的坐标转化为移动方式 | |||||
| }; | |||||
| class BFS { | |||||
| public: | |||||
| static bool vis[Constants::rows][Constants::cols]; | |||||
| static CellPos now; | |||||
| static CellPos nxt; | |||||
| static CellPos pre[Constants::rows][Constants::cols]; | |||||
| static std::pair<CellPos, int> bfs(CellPos start, CellPos destination, ITrickerAPI& api); | |||||
| static std::pair<std::pair<CellPos, CellPos>, int> bfs(CellPos start, THUAI6::PlaceType destination, ITrickerAPI& api); //return pair<next_point,destination> | |||||
| }; | |||||
| class Communication { | |||||
| public: | |||||
| static void receiveMessage(IStudentAPI& api, int64_t myID); | |||||
| static void sendClassroomPos(IStudentAPI& api, int64_t myID, CellPos classroomPos); | |||||
| static void sendTrickerPos(IStudentAPI& api, int64_t myID); | |||||
| /* | |||||
| 通信协议:AA BB CC DD | |||||
| (AA, BB)为学完的教室的坐标 | |||||
| (CC, DD)为看到的捣蛋鬼的坐标 | |||||
| 若AA, BB均为-1,则忽略教室坐标,只读捣蛋鬼坐标 | |||||
| 若CC, DD均为-1,则忽略捣蛋鬼坐标,只读教室坐标 | |||||
| */ | |||||
| }; | |||||
| class Escape { | |||||
| public: | |||||
| //static std::pair<CellPos, Action::Direction> minimax(CellPos start, THUAI6::PlaceType destination); //规避算法 | |||||
| }; | |||||
| class GameState { | |||||
| public: | |||||
| static int readCnt; | |||||
| static int clsFinishCnt; | |||||
| THUAI6::PlaceType gameMap[Constants::rows][Constants::cols]; //存图信息 | |||||
| std::vector <CellPos> ClassRoom; | |||||
| std::vector <CellPos> Gate; | |||||
| std::vector <CellPos> HiddenGate; | |||||
| std::vector <CellPos> Window; | |||||
| std::vector <CellPos> Door3; | |||||
| std::vector <CellPos> Door5; | |||||
| std::vector <CellPos> Door6; | |||||
| std::vector <CellPos> Chest; | |||||
| std::vector <std::pair<GridPos,int>> ValidMoveBuffer; | |||||
| CellPos studentPos[4]; //四个学生的坐标 | |||||
| GridPos studentGrid[4]; | |||||
| CellPos trickerPos; //捣蛋鬼的坐标 | |||||
| bool isAtCell(GridPos myPos, GridPos cellPos) const; //判断人物是否完全在格点内 | |||||
| bool isAtPosition(CellPos myPos, CellPos pos) const; | |||||
| bool isAtPos(CellPos myPos, CellPos pos) const; //判断character坐标与pos坐标是否在一个九宫格内 | |||||
| bool isAttackable(GridPos trickerPos, GridPos studentPos) const; //判断是否可以攻击 | |||||
| bool isClassroomFinished(CellPos classroomPos, IStudentAPI& studentapi) const; //判断教室是否学完了 | |||||
| bool isDoor(CellPos pos) const; | |||||
| bool isGateFinished(CellPos gatePos, IStudentAPI& api) const; //判断大门是否开了 | |||||
| bool isTrickerVisible() const; //判断tricker是否可见 | |||||
| bool isType(CellPos pos, THUAI6::PlaceType type) const; | |||||
| bool canReach(CellPos pos, ITrickerAPI& api) const; | |||||
| bool readyToGraduate() const; //判断当前局面是否可毕业 | |||||
| double gridDistance(GridPos grid1, GridPos grid2) const; //计算两点间以grid计的距离 | |||||
| void deleteStudiedClassroom(CellPos classroomPos); //把学完的教室删除,变成墙 | |||||
| void getMap(IStudentAPI& api); //获取地图格点信息 | |||||
| void getMap(ITrickerAPI& api); //获取地图格点信息 | |||||
| std::pair<std::pair<bool, std::pair<CellPos, GridPos>>, std::pair<double, double>> getClosestStudent(GridPos trickerPos, ITrickerAPI& api); //找到视野中最近的学生 | |||||
| //CellPos isGrassAround(CellPos, IStudentAPI& api) const; | |||||
| }; | |||||
| const std::vector <Action::Direction> Action::allMove = { | |||||
| Direction::up, | |||||
| Direction::down, | |||||
| Direction::left, | |||||
| Direction::right, | |||||
| Direction::up_left, | |||||
| Direction::up_right, | |||||
| Direction::down_left, | |||||
| Direction::down_right | |||||
| }; | |||||
| int Action::isAtCellFalseCnt = 0; | |||||
| int Action::classNo = 0; | |||||
| CellPos Action::lastNextPos = { -1,-1 }; | |||||
| Action::Direction Action::lastMove = Direction::null; | |||||
| CellPos Action::lastDesPos = { -1,-1 }; | |||||
| bool BFS::vis[Constants::rows][Constants::cols] = { false }; | |||||
| CellPos BFS::now = { -1,-1 }; | |||||
| CellPos BFS::nxt = { -1,-1 }; | |||||
| CellPos BFS::pre[Constants::rows][Constants::cols] = { {-1,-1} }; | |||||
| int GameState::readCnt = 0; | |||||
| int GameState::clsFinishCnt = 0; | |||||
| GameState gameState; | |||||
| bool Action::isValidMove(CellPos judgePos, Direction comeDir, GameState state, IStudentAPI& api) | |||||
| { | |||||
| auto students = api.GetStudents(); | |||||
| for (auto it = students.begin(); it != students.end(); it++) | |||||
| { | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y + 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y - 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| } | |||||
| auto trickers = api.GetTrickers(); | |||||
| for (auto it = trickers.begin(); it != trickers.end(); it++) | |||||
| { | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y + 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y - 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| } | |||||
| switch (comeDir) | |||||
| { | |||||
| case Action::Direction::up_left: { | |||||
| if (state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Grass)return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Grass)return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::up_right: { | |||||
| if (state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Grass)return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Grass)return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::down_right: { | |||||
| if (state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Grass)return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Grass)return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::down_left: { | |||||
| if (state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Grass)return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Land && state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Grass)return false; | |||||
| break; | |||||
| } | |||||
| default:break; | |||||
| } | |||||
| return state.gameMap[judgePos.x][judgePos.y] == THUAI6::PlaceType::Land | |||||
| || state.gameMap[judgePos.x][judgePos.y] == THUAI6::PlaceType::Grass | |||||
| || state.gameMap[judgePos.x][judgePos.y] == THUAI6::PlaceType::Window | |||||
| || (state.isDoor(judgePos) && api.IsDoorOpen(judgePos.x, judgePos.y)); | |||||
| } | |||||
| bool Action::isValidMove(CellPos judgePos, Direction comeDir, GameState state, ITrickerAPI& api) | |||||
| { | |||||
| for (int i=0; i<gameState.ValidMoveBuffer.size(); i++) { | |||||
| auto it = &gameState.ValidMoveBuffer[i].first; | |||||
| if (api.HaveView(it->x, it->y)|| (api.GetFrameCount()- gameState.ValidMoveBuffer[i].second)>=100) { | |||||
| gameState.ValidMoveBuffer.erase(gameState.ValidMoveBuffer.begin()+i); | |||||
| i--; | |||||
| } | |||||
| else { | |||||
| state.gameMap[api.GridToCell(it->x)][api.GridToCell(it->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x + 390)][api.GridToCell(it->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x - 390)][api.GridToCell(it->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x)][api.GridToCell(it->y + 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x)][api.GridToCell(it->y - 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x + 280)][api.GridToCell(it->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x + 280)][api.GridToCell(it->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x - 280)][api.GridToCell(it->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell(it->x - 280)][api.GridToCell(it->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| } | |||||
| } | |||||
| auto students = api.GetStudents(); | |||||
| for (auto it = students.begin(); it != students.end(); it++) | |||||
| { | |||||
| if ((*it)->determination>0) | |||||
| continue; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 390)][api.GridToCell((*it)->y)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y + 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x)][api.GridToCell((*it)->y - 390)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x + 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y + 280)] = THUAI6::PlaceType::Wall; | |||||
| state.gameMap[api.GridToCell((*it)->x - 280)][api.GridToCell((*it)->y - 280)] = THUAI6::PlaceType::Wall; | |||||
| gameState.ValidMoveBuffer.push_back({ { (*it)->x ,(*it)->y },api.GetFrameCount()}); | |||||
| } | |||||
| switch (comeDir) | |||||
| { | |||||
| case Action::Direction::up_left: { | |||||
| if (state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Land )return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Land )return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::up_right: { | |||||
| if (state.gameMap[judgePos.x + 1][judgePos.y] != THUAI6::PlaceType::Land )return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Land )return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::down_right: { | |||||
| if (state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Land )return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y - 1] != THUAI6::PlaceType::Land )return false; | |||||
| break; | |||||
| } | |||||
| case Action::Direction::down_left: { | |||||
| if (state.gameMap[judgePos.x - 1][judgePos.y] != THUAI6::PlaceType::Land )return false; | |||||
| if (state.gameMap[judgePos.x][judgePos.y + 1] != THUAI6::PlaceType::Land )return false; | |||||
| break; | |||||
| } | |||||
| default:break; | |||||
| } | |||||
| return state.canReach(judgePos, api); | |||||
| } | |||||
| double Action::getAngle(GridPos grid1, GridPos grid2) | |||||
| { | |||||
| return atan2(grid2.y - grid1.y, grid2.x - grid1.x); | |||||
| } | |||||
| void Action::Move(CellPos destination, IStudentAPI& api) | |||||
| { | |||||
| int desX = api.CellToGrid(destination.x); | |||||
| int desY = api.CellToGrid(destination.y); | |||||
| auto self = api.GetSelfInfo(); | |||||
| api.Move(50, getAngle({ self->x,self->y }, { desX,desY })); | |||||
| } | |||||
| void Action::Move(CellPos destination, ITrickerAPI& api) | |||||
| { | |||||
| int desX = api.CellToGrid(destination.x); | |||||
| int desY = api.CellToGrid(destination.y); | |||||
| auto self = api.GetSelfInfo(); | |||||
| api.Move(50, getAngle({ self->x,self->y }, { desX,desY })); | |||||
| } | |||||
| void Action::Move(Direction direction, IStudentAPI& api) | |||||
| { | |||||
| auto self = api.GetSelfInfo(); | |||||
| int x = api.GridToCell(self->x); | |||||
| int y = api.GridToCell(self->y); | |||||
| Move(Action::directionToPos({ x, y }, direction), api); | |||||
| } | |||||
| void Action::Move(Direction direction, ITrickerAPI& api) | |||||
| { | |||||
| auto self = api.GetSelfInfo(); | |||||
| int x = api.GridToCell(self->x); | |||||
| int y = api.GridToCell(self->y); | |||||
| Move(Action::directionToPos({ x, y }, direction), api); | |||||
| } | |||||
| CellPos Action::directionToPos(CellPos nowPos, Direction direction) | |||||
| { | |||||
| switch (direction) | |||||
| { | |||||
| case Action::Direction::up: | |||||
| return { nowPos.x - 1, nowPos.y }; | |||||
| case Action::Direction::down: | |||||
| return { nowPos.x + 1, nowPos.y }; | |||||
| case Action::Direction::left: | |||||
| return { nowPos.x, nowPos.y - 1 }; | |||||
| case Action::Direction::right: | |||||
| return { nowPos.x, nowPos.y + 1 }; | |||||
| case Action::Direction::up_left: | |||||
| return { nowPos.x - 1, nowPos.y - 1 }; | |||||
| case Action::Direction::up_right: | |||||
| return { nowPos.x - 1, nowPos.y + 1 }; | |||||
| case Action::Direction::down_left: | |||||
| return { nowPos.x + 1, nowPos.y - 1 }; | |||||
| case Action::Direction::down_right: | |||||
| return { nowPos.x + 1, nowPos.y + 1 }; | |||||
| } | |||||
| return { -1,-1 }; | |||||
| } | |||||
| Action::Direction Action::posToDirection(CellPos nowPos, CellPos nextPos) | |||||
| { | |||||
| if (nextPos.x == nowPos.x + 1 && nextPos.y == nowPos.y) | |||||
| return Action::Direction::down; | |||||
| if (nextPos.x == nowPos.x - 1 && nextPos.y == nowPos.y) | |||||
| return Action::Direction::up; | |||||
| if (nextPos.x == nowPos.x && nextPos.y == nowPos.y + 1) | |||||
| return Action::Direction::right; | |||||
| if (nextPos.x == nowPos.x && nextPos.y == nowPos.y - 1) | |||||
| return Action::Direction::left; | |||||
| if (nextPos.x == nowPos.x + 1 && nextPos.y == nowPos.y + 1) | |||||
| return Action::Direction::down_right; | |||||
| if (nextPos.x == nowPos.x - 1 && nextPos.y == nowPos.y + 1) | |||||
| return Action::Direction::up_right; | |||||
| if (nextPos.x == nowPos.x + 1 && nextPos.y == nowPos.y - 1) | |||||
| return Action::Direction::down_left; | |||||
| if (nextPos.x == nowPos.x - 1 && nextPos.y == nowPos.y - 1) | |||||
| return Action::Direction::up_left; | |||||
| return Action::Direction::null; | |||||
| } | |||||
| std::pair<CellPos, int> BFS::bfs(CellPos start, CellPos destination, ITrickerAPI& api) | |||||
| { | |||||
| int length = 0; | |||||
| std::queue<CellPos> posQ; | |||||
| memset(vis, false, sizeof(vis)); | |||||
| vis[start.x][start.y] = true; | |||||
| posQ.push(start); | |||||
| while (!posQ.empty()) | |||||
| { | |||||
| now = posQ.front(); | |||||
| posQ.pop(); | |||||
| for (auto move = Action::allMove.begin(); move != Action::allMove.end(); move++) | |||||
| { | |||||
| nxt = Action::directionToPos(now, *move); | |||||
| if (nxt.x == destination.x && nxt.y == destination.y && !gameState.canReach(nxt, api)) | |||||
| { | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| now = nxt; | |||||
| break; | |||||
| } | |||||
| if (!Action::isValidMove(nxt, *move, gameState, api) || vis[nxt.x][nxt.y]) | |||||
| continue; | |||||
| if (nxt.x == destination.x && nxt.y == destination.y) | |||||
| { | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| now = nxt; | |||||
| break; | |||||
| } | |||||
| vis[nxt.x][nxt.y] = true; | |||||
| posQ.push(nxt); | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| } | |||||
| if (now.x == nxt.x && now.y == nxt.y) | |||||
| { | |||||
| while (pre[now.x][now.y].x != start.x || pre[now.x][now.y].y != start.y) | |||||
| { | |||||
| now = pre[now.x][now.y]; | |||||
| length++; | |||||
| } | |||||
| return { now, length + 1 }; | |||||
| } | |||||
| } | |||||
| return { {-1,-1},-1 }; | |||||
| } | |||||
| std::pair<std::pair<CellPos, CellPos>,int> BFS::bfs(CellPos start, THUAI6::PlaceType destination, ITrickerAPI& api) | |||||
| { | |||||
| int length = 0; | |||||
| std::queue<CellPos> posQ; | |||||
| if (gameState.gameMap[start.x][start.y] == destination) { | |||||
| return { {start,start},0 }; | |||||
| } | |||||
| memset(vis, false, sizeof(vis)); | |||||
| vis[start.x][start.y] = true; | |||||
| posQ.push(start); | |||||
| while (!posQ.empty()) | |||||
| { | |||||
| now = posQ.front(); | |||||
| posQ.pop(); | |||||
| for (auto move = Action::allMove.begin(); move != Action::allMove.end(); move++) | |||||
| { | |||||
| nxt = Action::directionToPos(now, *move); | |||||
| if (gameState.gameMap[nxt.x][nxt.y] == destination && !gameState.canReach(nxt, api)) | |||||
| { | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| now = nxt; | |||||
| break; | |||||
| } | |||||
| if (!Action::isValidMove(nxt, *move, gameState, api) || vis[nxt.x][nxt.y]) | |||||
| continue; | |||||
| if (gameState.gameMap[nxt.x][nxt.y] == destination) | |||||
| { | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| now = nxt; | |||||
| break; | |||||
| } | |||||
| vis[nxt.x][nxt.y] = true; | |||||
| posQ.push(nxt); | |||||
| pre[nxt.x][nxt.y] = now; | |||||
| } | |||||
| if (now.x == nxt.x && now.y == nxt.y) | |||||
| { | |||||
| while (pre[now.x][now.y].x != start.x || pre[now.x][now.y].y != start.y) { | |||||
| length++; | |||||
| now = pre[now.x][now.y]; | |||||
| } | |||||
| return { { now,nxt },length }; | |||||
| } | |||||
| } | |||||
| return { { {-1,-1},{-1,-1} },1000 }; | |||||
| } | |||||
| void Communication::sendClassroomPos(IStudentAPI& api, int64_t myID, CellPos classroomPos) { | |||||
| auto message = fmt::format("{} {} {} {}", classroomPos.x, classroomPos.y, -1, -1); | |||||
| for (int id = 0; id <= 3; id++) | |||||
| if (id != myID) | |||||
| api.SendTextMessage(id, message); | |||||
| } | |||||
| void Communication::sendTrickerPos(IStudentAPI& api, int64_t myID) { | |||||
| auto trickers = api.GetTrickers(); | |||||
| if (trickers.empty()) | |||||
| return; | |||||
| CellPos trickerPos = { api.GridToCell(trickers[0]->x), api.GridToCell(trickers[0]->y) }; | |||||
| auto message = fmt::format("{} {} {} {}", -1, -1, trickerPos.x, trickerPos.y); | |||||
| for (int i = 0; i <= 3; i++) | |||||
| if (i != myID) | |||||
| api.SendTextMessage(i, message); | |||||
| } | |||||
| void Communication::receiveMessage(IStudentAPI& api, int64_t myID) { | |||||
| CellPos classroomPos, trickerPos; | |||||
| std::pair<int64_t, std::string> message; | |||||
| std::stringstream ss; | |||||
| gameState.trickerPos = { -1, -1 }; | |||||
| auto trickers = api.GetTrickers(); | |||||
| if (!trickers.empty()) | |||||
| gameState.trickerPos = { api.GridToCell(trickers[0]->x), api.GridToCell(trickers[0]->y) }; | |||||
| while (api.HaveMessage()) { | |||||
| ss.clear(); | |||||
| message = api.GetMessage(); | |||||
| ss.str(message.second); | |||||
| ss >> classroomPos.x >> classroomPos.y >> trickerPos.x >> trickerPos.y; | |||||
| if (classroomPos.x != -1 && classroomPos.y != -1) | |||||
| gameState.deleteStudiedClassroom(classroomPos); | |||||
| if (trickerPos.x != -1 && trickerPos.y != -1) | |||||
| gameState.trickerPos = trickerPos; | |||||
| } | |||||
| } | |||||
| inline bool GameState::isAtCell(GridPos myPos, GridPos cellPos) const { | |||||
| return abs(myPos.x - cellPos.x) < 100 && abs(myPos.y - cellPos.y) < 100; | |||||
| } | |||||
| inline bool GameState::isAtPosition(CellPos character, CellPos pos) const { | |||||
| return (abs(character.x - pos.x) <= 1 && abs(character.y - pos.y) <= 1); | |||||
| } | |||||
| inline bool GameState::isAtPos(CellPos character, CellPos pos) const { | |||||
| return (abs(character.x - pos.x) <= 1 && abs(character.y - pos.y) == 0) || (abs(character.y - pos.y) <= 1 && abs(character.x - pos.x) == 0); | |||||
| } | |||||
| inline bool GameState::isAttackable(GridPos trickerPos, GridPos studentPos) const { | |||||
| return gridDistance(trickerPos, studentPos) <= 1500; | |||||
| } | |||||
| inline bool GameState::isClassroomFinished(CellPos classroomPos, IStudentAPI& api) const { | |||||
| return api.GetClassroomProgress(classroomPos.x, classroomPos.y) >= Constants::maxClassroomProgress; | |||||
| } | |||||
| inline bool GameState::isDoor(CellPos pos) const { | |||||
| return gameMap[pos.x][pos.y] == THUAI6::PlaceType::Door3 | |||||
| || gameMap[pos.x][pos.y] == THUAI6::PlaceType::Door5 | |||||
| || gameMap[pos.x][pos.y] == THUAI6::PlaceType::Door6; | |||||
| } | |||||
| inline bool GameState::isGateFinished(CellPos gatePos, IStudentAPI& api) const { | |||||
| return api.GetGateProgress(gatePos.x, gatePos.y) >= Constants::maxGateProgress; | |||||
| } | |||||
| inline bool GameState::isTrickerVisible() const { | |||||
| return trickerPos.x == -1 && trickerPos.y == -1; | |||||
| } | |||||
| inline bool GameState::isType(CellPos pos, THUAI6::PlaceType type) const { | |||||
| return gameMap[pos.x][pos.y] == type; | |||||
| } | |||||
| inline bool GameState::canReach(CellPos pos, ITrickerAPI& api) const { | |||||
| return gameMap[pos.x][pos.y] == THUAI6::PlaceType::Window | |||||
| || gameMap[pos.x][pos.y] == THUAI6::PlaceType::Grass | |||||
| || gameMap[pos.x][pos.y] == THUAI6::PlaceType::Land | |||||
| || (isDoor(pos) && api.IsDoorOpen(pos.x, pos.y)); | |||||
| } | |||||
| inline bool GameState::readyToGraduate() const { | |||||
| return clsFinishCnt >= Constants::numOfRequiredClassroomForGate; | |||||
| } | |||||
| double GameState::gridDistance(GridPos grid1, GridPos grid2) const { | |||||
| return sqrt(pow((grid1.x - grid2.x), 2) + pow((grid1.y - grid2.y), 2)); | |||||
| } | |||||
| void GameState::deleteStudiedClassroom(CellPos classroomPos) { | |||||
| for (auto it = gameState.ClassRoom.begin(); it != gameState.ClassRoom.end(); it++) | |||||
| if (it->x == classroomPos.x && it->y == classroomPos.y) | |||||
| { | |||||
| ClassRoom.erase(it); | |||||
| gameState.gameMap[classroomPos.x][classroomPos.y] = THUAI6::PlaceType::Wall; | |||||
| clsFinishCnt++; | |||||
| break; | |||||
| } | |||||
| } | |||||
| void GameState::getMap(IStudentAPI& api) { | |||||
| for (int i = 0; i < Constants::rows; i++) { | |||||
| for (int j = 0; j < Constants::cols; j++) { | |||||
| gameMap[i][j] = (api.GetFullMap()[i][j]); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::ClassRoom) ClassRoom.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Gate) Gate.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::HiddenGate) HiddenGate.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Window) Window.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Chest) Chest.push_back({ i, j }); | |||||
| } | |||||
| } | |||||
| } | |||||
| void GameState::getMap(ITrickerAPI& api) { | |||||
| for (int i = 0; i < Constants::rows; i++) { | |||||
| for (int j = 0; j < Constants::cols; j++) { | |||||
| gameMap[i][j] = (api.GetFullMap()[i][j]); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::ClassRoom) ClassRoom.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Gate) Gate.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::HiddenGate) HiddenGate.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Window) Window.push_back({ i, j }); | |||||
| if (gameMap[i][j] == THUAI6::PlaceType::Chest) Chest.push_back({ i, j }); | |||||
| } | |||||
| } | |||||
| } | |||||
| std::pair<std::pair<bool, std::pair<CellPos,GridPos>>, std::pair<double, double>> GameState::getClosestStudent(GridPos trickerPos, ITrickerAPI& api) | |||||
| { | |||||
| auto students = api.GetStudents(); | |||||
| double minDistance = 1e5; | |||||
| CellPos studentPos = { -1,-1 }; | |||||
| GridPos studentGrid = { -1,-1 }; | |||||
| bool updated[4] = { false }; | |||||
| bool inSight = false; | |||||
| double angle[5] = { -1,-1,-1,-1,-1 }; | |||||
| int picked = 4; | |||||
| for (auto it = students.begin(); it != students.end(); it++) | |||||
| { | |||||
| if ((*it)->determination<=0) { | |||||
| gameState.studentPos[(*it)->playerID].x = -1; | |||||
| gameState.studentPos[(*it)->playerID].y = -1; | |||||
| gameState.studentGrid[(*it)->playerID].x = -1; | |||||
| gameState.studentGrid[(*it)->playerID].y = -1; | |||||
| continue; | |||||
| } | |||||
| angle[(*it)->playerID] = (*it)->facingDirection; | |||||
| gameState.studentPos[(*it)->playerID].x = api.GridToCell((*it)->x); | |||||
| gameState.studentPos[(*it)->playerID].y = api.GridToCell((*it)->y); | |||||
| gameState.studentGrid[(*it)->playerID].x = (*it)->x; | |||||
| gameState.studentGrid[(*it)->playerID].y = (*it)->y; | |||||
| updated[(*it)->playerID] = true; | |||||
| } | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (minDistance > gridDistance(trickerPos, { api.CellToGrid(gameState.studentPos[i].x),api.CellToGrid(gameState.studentPos[i].y) }) && gameState.studentPos[i].x >= 0) | |||||
| { | |||||
| minDistance = gridDistance(trickerPos, { api.CellToGrid(gameState.studentPos[i].x),api.CellToGrid(gameState.studentPos[i].y) }); | |||||
| studentPos = gameState.studentPos[i]; | |||||
| studentGrid = gameState.studentGrid[i]; | |||||
| inSight = updated[i]; | |||||
| picked = i; | |||||
| } | |||||
| } | |||||
| return { {inSight,{studentPos,studentGrid}},{minDistance,angle[picked]} }; | |||||
| } | |||||
| void AI::play(IStudentAPI& api) {} | |||||
| int AttackCount = 0; | |||||
| int needNew = 0; | |||||
| bool del=false; | |||||
| GridPos lastGrid = { -1,-1 }; | |||||
| int stuck = 0; | |||||
| int classcount = 0; | |||||
| int pointing = -1; | |||||
| //bool attackToGrass = false; | |||||
| std::vector <CellPos> traversal; | |||||
| std::vector <CellPos> gates; | |||||
| void AI::play(ITrickerAPI& api) | |||||
| { | |||||
| if (gameState.readCnt < 1) | |||||
| { | |||||
| gameState.getMap(api); | |||||
| gameState.readCnt++; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| //studentPos赋初值,代表视野没有,也没记住 | |||||
| gameState.studentPos[i] = { -1,-1 }; | |||||
| gameState.studentGrid[i] = { -1,-1 }; | |||||
| } | |||||
| if (gameState.gameMap[5][9] == THUAI6::PlaceType::Grass) { | |||||
| traversal.push_back({ 6,24 }); | |||||
| traversal.push_back({ 8,31 });//class | |||||
| traversal.push_back({ 10,38 });//class | |||||
| traversal.push_back({ 12,46 }); | |||||
| traversal.push_back({ 19,41 });//class | |||||
| //traversal.push_back({ 18,46 }); | |||||
| //traversal.push_back({ 23,40 }); | |||||
| traversal.push_back({ 21,27 }); | |||||
| traversal.push_back({ 28,26 });//class | |||||
| traversal.push_back({ 33,40 });//class | |||||
| //traversal.push_back({ 39,38 }); | |||||
| traversal.push_back({ 44,32 });//class | |||||
| traversal.push_back({ 40,12 });//class | |||||
| //traversal.push_back({ 46,9 }); | |||||
| //traversal.push_back({ 36,7 }); | |||||
| traversal.push_back({ 30,7 });//class | |||||
| traversal.push_back({ 22,18 });//class | |||||
| traversal.push_back({ 18,5 });//class | |||||
| traversal.push_back({ 8,16 }); | |||||
| traversal.push_back({ 7,21 }); | |||||
| gates.push_back({ 46,45 }); | |||||
| gates.push_back({ 5,6 }); | |||||
| } | |||||
| else if (gameState.gameMap[5][9] == THUAI6::PlaceType::Wall) { | |||||
| traversal.push_back({ 16,20 }); | |||||
| traversal.push_back({ 16,16 });//class | |||||
| traversal.push_back({ 20,16 }); | |||||
| traversal.push_back({ 29,16 }); | |||||
| traversal.push_back({ 33,16 });//class | |||||
| traversal.push_back({ 33,20 }); | |||||
| traversal.push_back({ 33,29 }); | |||||
| traversal.push_back({ 33,33 });//class | |||||
| traversal.push_back({ 29,33 }); | |||||
| traversal.push_back({ 20,33 }); | |||||
| traversal.push_back({ 16,33 });//class | |||||
| traversal.push_back({ 16,29 }); | |||||
| traversal.push_back({ 6,13 });//class | |||||
| traversal.push_back({ 6,36 });//class | |||||
| traversal.push_back({ 13,43 });//class | |||||
| traversal.push_back({ 34,45 }); | |||||
| traversal.push_back({ 43,36 });//class | |||||
| traversal.push_back({ 43,13 });//class | |||||
| traversal.push_back({ 36,6 });//class | |||||
| traversal.push_back({ 15,4 }); | |||||
| gates.push_back({ 47,6 }); | |||||
| gates.push_back({ 2,43 }); | |||||
| } | |||||
| else { | |||||
| traversal = gameState.ClassRoom; | |||||
| } | |||||
| api.Print("Map read"); | |||||
| } | |||||
| if (traversal.size()) | |||||
| Action::classNo = Action::classNo % traversal.size(); | |||||
| else | |||||
| Action::classNo = -1; | |||||
| auto self = api.GetSelfInfo(); | |||||
| GridPos myGrid = { self->x,self->y }; | |||||
| CellPos myCell = { api.GridToCell(self->x), api.GridToCell(self->y) }; | |||||
| api.Print(fmt::format("mypos {} {}", myCell.x, myCell.y)); | |||||
| if (self->playerState == THUAI6::PlayerState::Swinging || self->playerState == THUAI6::PlayerState::Attacking || self->playerState == THUAI6::PlayerState::Stunned) { | |||||
| gameState.getClosestStudent(myGrid, api); | |||||
| api.Print("not idle"); | |||||
| return; | |||||
| } | |||||
| if (gameState.isAtCell(myGrid, { api.CellToGrid(myCell.x),api.CellToGrid(myCell.y) }) || Action::isAtCellFalseCnt > 15) | |||||
| //if (gameState.isAtCell(myGrid, { api.CellToGrid(myCell.x),api.CellToGrid(myCell.y) })) | |||||
| { | |||||
| //在格点上或者连续10次进不到格点 | |||||
| bool notAtCell = Action::isAtCellFalseCnt > 15; | |||||
| Action::isAtCellFalseCnt = 0; | |||||
| api.Print(fmt::format("notAtCell {}", notAtCell)); | |||||
| if (!notAtCell) { | |||||
| if (abs(myGrid.x - lastGrid.x) <= 5 && abs(myGrid.y - lastGrid.y) <= 5) { | |||||
| stuck++; | |||||
| api.Print(fmt::format("stuck {}", stuck)); | |||||
| } | |||||
| else { | |||||
| stuck = 0; | |||||
| api.Print("stuck zero"); | |||||
| } | |||||
| if (stuck >= 5) { | |||||
| if (pointing <= 1) { | |||||
| auto angle = Action::getAngle({ api.CellToGrid(Action::directionToPos(myCell, Action::lastMove).x), api.CellToGrid(Action::directionToPos(myCell, Action::lastMove).y) }, { api.CellToGrid(myCell.x), api.CellToGrid(myCell.y) }); | |||||
| angle = angle + pointing * PI / 4; | |||||
| api.Attack(angle); | |||||
| pointing++; | |||||
| } | |||||
| else { | |||||
| pointing = -1; | |||||
| stuck = 0; | |||||
| } | |||||
| api.Print(fmt::format("{} {} set to wall", Action::lastNextPos.x, Action::lastNextPos.y)); | |||||
| stuck = 0; | |||||
| gameState.gameMap[Action::lastNextPos.x][Action::lastNextPos.y] = THUAI6::PlaceType::Wall; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == Action::lastNextPos.x && gameState.studentPos[i].y == Action::lastNextPos.y) { | |||||
| gameState.studentPos[i].x = -1; | |||||
| gameState.studentPos[i].y = -1; | |||||
| gameState.studentGrid[i].x = -1; | |||||
| gameState.studentGrid[i].y = -1; | |||||
| } | |||||
| } | |||||
| } | |||||
| lastGrid = myGrid; | |||||
| } | |||||
| else{ | |||||
| if (gameState.gameMap[Action::lastNextPos.x][Action::lastNextPos.y] == THUAI6::PlaceType::Grass && AttackCount < 5) { | |||||
| GridPos target = { api.CellToGrid(Action::lastNextPos.x),api.CellToGrid(Action::lastNextPos.y) }; | |||||
| api.Attack(Action::getAngle(myGrid, target)); | |||||
| api.Print(fmt::format("attacknum {}", AttackCount)); | |||||
| api.Print(fmt::format("attackto {} {}", Action::lastNextPos.x, Action::lastNextPos.y)); | |||||
| AttackCount++; | |||||
| } | |||||
| else { | |||||
| AttackCount = 0; | |||||
| del = true; | |||||
| //Action::lastNextPos = Action::directionToPos(Action::lastNextPos, Action::lastMove); | |||||
| api.Print(fmt::format("delete {} {}", Action::lastNextPos.x, Action::lastNextPos.y)); | |||||
| gameState.gameMap[Action::lastNextPos.x][Action::lastNextPos.y] = THUAI6::PlaceType::Wall; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == Action::lastNextPos.x && gameState.studentPos[i].y == Action::lastNextPos.y) { | |||||
| gameState.studentPos[i].x = -1; | |||||
| gameState.studentPos[i].y = -1; | |||||
| gameState.studentGrid[i].x = -1; | |||||
| gameState.studentGrid[i].y = -1; | |||||
| } | |||||
| } | |||||
| } | |||||
| Action::lastNextPos = Action::directionToPos(Action::lastNextPos, Action::lastMove); | |||||
| return; | |||||
| } | |||||
| auto closestStudent = gameState.getClosestStudent(myGrid, api); | |||||
| bool inSight = closestStudent.first.first; | |||||
| auto stuCellPos = closestStudent.first.second.first; | |||||
| GridPos stuGridPos = closestStudent.first.second.second; | |||||
| auto distance = closestStudent.second.first; | |||||
| api.Print(fmt::format("insight {} stupos {} {} distance {}", inSight, stuCellPos.x, stuCellPos.y, distance)); | |||||
| if (distance < Constants::basicTrickerViewRange)//如果视野里有学生,且未沉迷,则判断攻击条件,不满足则追击学生 | |||||
| { | |||||
| Action::lastDesPos = { stuCellPos.x,stuCellPos.y }; | |||||
| bool isClose; | |||||
| if (inSight) { | |||||
| isClose = gameState.isAtPos(myCell, stuCellPos); | |||||
| api.UseSkill(0); | |||||
| //if ((!api.GetSelfInfo()->buff.empty())&&api.GetSelfInfo()->buff[0]==THUAI6::TrickerBuffType::Invisible) | |||||
| //api.Print("invisible"); | |||||
| api.UseSkill(1); | |||||
| if (api.GetSelfInfo()->bulletType == THUAI6::BulletType::FlyingKnife) { | |||||
| //api.Print("knife"); | |||||
| api.Attack(Action::getAngle(myGrid, stuGridPos)); | |||||
| } | |||||
| } | |||||
| else { | |||||
| isClose = gameState.isAtCell(myGrid, {api.CellToGrid(stuCellPos.x),api.CellToGrid(stuCellPos.y)}) || (gameState.isAtPosition(myCell, stuCellPos) && notAtCell); | |||||
| } | |||||
| api.Print(fmt::format("isclose {}", isClose)); | |||||
| if (isClose) { | |||||
| if (inSight) { | |||||
| api.Attack(Action::getAngle(myGrid, stuGridPos)); | |||||
| } | |||||
| /* | |||||
| else if (notAtCell && AttackCount < 3) { | |||||
| api.Attack(Action::getAngle(myGrid, stuGridPos)); | |||||
| AttackCount++; | |||||
| } | |||||
| else if (del) { | |||||
| //AttackCount = 0; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == stuCellPos.x && gameState.studentPos[i].y == stuCellPos.y) { | |||||
| gameState.studentPos[i].x = -1; | |||||
| gameState.studentPos[i].y = -1; | |||||
| } | |||||
| } | |||||
| del = false; | |||||
| }*/ | |||||
| else { | |||||
| if (gameState.gameMap[myCell.x][myCell.y] != THUAI6::PlaceType::Grass) { | |||||
| int newx, newy; | |||||
| /* | |||||
| if (gameState.gameMap[stuCellPos.x][stuCellPos.y] == THUAI6::PlaceType::Grass) { | |||||
| newx = stuCellPos.x; | |||||
| newy = stuCellPos.y; | |||||
| } | |||||
| for (int i = -1; i <= 1; i = i + 2) { | |||||
| if (gameState.gameMap[stuCellPos.x + i][stuCellPos.y] == THUAI6::PlaceType::Grass) { | |||||
| newx = stuCellPos.x + i; | |||||
| newy = stuCellPos.y; | |||||
| } | |||||
| } | |||||
| for (int j = -1; j <= 1; j = j + 2) { | |||||
| if (gameState.gameMap[stuCellPos.x][stuCellPos.y + j] == THUAI6::PlaceType::Grass) { | |||||
| newx = stuCellPos.x; | |||||
| newy = stuCellPos.y + j; | |||||
| } | |||||
| } | |||||
| */ | |||||
| api.Print("bfsing"); | |||||
| auto result = BFS::bfs(myCell, THUAI6::PlaceType::Grass, api); | |||||
| api.Print(fmt::format("newdist {}", result.second)); | |||||
| if (result.second < 5&&result.second>=0) { | |||||
| newx = result.first.second.x; | |||||
| newy = result.first.second.y; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == stuCellPos.x && gameState.studentPos[i].y == stuCellPos.y) { | |||||
| gameState.studentPos[i].x = newx; | |||||
| gameState.studentPos[i].y = newy; | |||||
| gameState.studentGrid[i].x = api.CellToGrid(newx); | |||||
| gameState.studentGrid[i].y = api.CellToGrid(newy); | |||||
| } | |||||
| } | |||||
| api.Print(fmt::format("newxy {} {}", newx, newy)); | |||||
| } | |||||
| //needNew=3; | |||||
| //return; | |||||
| } | |||||
| else { | |||||
| api.Print("not bfsing"); | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == stuCellPos.x && gameState.studentPos[i].y == stuCellPos.y) { | |||||
| gameState.studentPos[i].x = -1; | |||||
| gameState.studentPos[i].y = -1; | |||||
| gameState.studentGrid[i].x = -1; | |||||
| gameState.studentGrid[i].y = -1; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| api.Print(fmt::format("my pos {} {}", myCell.x, myCell.y)); | |||||
| api.Print(fmt::format("stu pos {} {}", stuCellPos.x, stuCellPos.y)); | |||||
| auto result = BFS::bfs(myCell, stuCellPos, api); | |||||
| auto nextPos = result.first; | |||||
| api.Print(fmt::format("next {} {}", nextPos.x, nextPos.y)); | |||||
| if (nextPos.x == -1) { | |||||
| for (int i = 0; i < 4; i++) { | |||||
| if (gameState.studentPos[i].x == stuCellPos.x && gameState.studentPos[i].y == stuCellPos.y) { | |||||
| gameState.studentPos[i].x = -1; | |||||
| gameState.studentPos[i].y = -1; | |||||
| } | |||||
| } | |||||
| return; | |||||
| } | |||||
| if (gameState.isType(nextPos, THUAI6::PlaceType::Window)) | |||||
| api.SkipWindow(); | |||||
| Action::Move(nextPos, api); | |||||
| Action::lastNextPos = nextPos; | |||||
| Action::lastMove = Action::posToDirection(nextPos, myCell); | |||||
| } | |||||
| } | |||||
| else//视野里没有学生,就遍历教室并前往 | |||||
| { | |||||
| if (classcount >= 7) { | |||||
| auto classroomCellPos = gates[0]; | |||||
| GridPos classroomGridPos = { api.CellToGrid(classroomCellPos.x),api.CellToGrid(classroomCellPos.y) }; | |||||
| api.Print(fmt::format("class pos {} {}", classroomCellPos.x, classroomCellPos.y)); | |||||
| auto result = BFS::bfs(myCell, classroomCellPos, api); | |||||
| auto nextPos = result.first; | |||||
| api.Print(fmt::format("next pos {} {}", nextPos.x, nextPos.y)); | |||||
| if (nextPos.x == -1) { | |||||
| return; | |||||
| } | |||||
| if (!gameState.isAtPosition(myCell, classroomCellPos)) | |||||
| { | |||||
| if (gameState.isType(nextPos, THUAI6::PlaceType::Window)) | |||||
| api.SkipWindow(); | |||||
| Action::Move(nextPos, api); | |||||
| Action::lastNextPos = nextPos; | |||||
| Action::lastMove = Action::posToDirection(nextPos, myCell); | |||||
| } | |||||
| return; | |||||
| } | |||||
| auto classroomCellPos = traversal[Action::classNo]; | |||||
| GridPos classroomGridPos = { api.CellToGrid(classroomCellPos.x),api.CellToGrid(classroomCellPos.y) }; | |||||
| if (gameState.gameMap[classroomCellPos.x][classroomCellPos.y] == THUAI6::PlaceType::ClassRoom) { | |||||
| if (api.GetClassroomProgress(classroomCellPos.x, classroomCellPos.y) >= Constants::maxClassroomProgress) { | |||||
| traversal.erase(traversal.begin() + Action::classNo); | |||||
| classcount++; | |||||
| } | |||||
| //else if (api.GetClassroomProgress(classroomCellPos.x, classroomCellPos.y) == 0 && api.HaveView(classroomCellPos.x, classroomCellPos.y)) | |||||
| else if (api.GetClassroomProgress(classroomCellPos.x, classroomCellPos.y) == 0 && api.HaveView(classroomGridPos.x, classroomGridPos.y)) | |||||
| Action::classNo++; | |||||
| else | |||||
| { | |||||
| api.Print(fmt::format("class pos {} {}", classroomCellPos.x, classroomCellPos.y)); | |||||
| auto result = BFS::bfs(myCell, classroomCellPos, api); | |||||
| auto nextPos = result.first; | |||||
| api.Print(fmt::format("next pos {} {}", nextPos.x, nextPos.y)); | |||||
| if (nextPos.x == -1) { | |||||
| Action::classNo++; | |||||
| return; | |||||
| } | |||||
| if (!gameState.isAtPosition(myCell, classroomCellPos)) | |||||
| { | |||||
| if (gameState.isType(nextPos, THUAI6::PlaceType::Window)) | |||||
| api.SkipWindow(); | |||||
| Action::Move(nextPos, api); | |||||
| Action::lastNextPos = nextPos; | |||||
| Action::lastMove = Action::posToDirection(nextPos, myCell); | |||||
| } | |||||
| else | |||||
| { | |||||
| //if (api.GetClassroomProgress(classroomCellPos.x, classroomCellPos.y) > 0) | |||||
| //api.Attack(Action::getAngle(myGrid, classroomGridPos)); | |||||
| //else | |||||
| Action::classNo++; | |||||
| } | |||||
| } | |||||
| } | |||||
| else if (gameState.gameMap[classroomCellPos.x][classroomCellPos.y] == THUAI6::PlaceType::Grass) { | |||||
| api.Print(fmt::format("grass pos {} {}", classroomCellPos.x, classroomCellPos.y)); | |||||
| auto result = BFS::bfs(myCell, classroomCellPos, api); | |||||
| auto nextPos = result.first; | |||||
| api.Print(fmt::format("next pos {} {}", nextPos.x, nextPos.y)); | |||||
| if (nextPos.x == -1) { | |||||
| Action::classNo++; | |||||
| return; | |||||
| } | |||||
| if (!gameState.isAtCell(myGrid, classroomGridPos)) | |||||
| { | |||||
| if (gameState.isType(nextPos, THUAI6::PlaceType::Window)) | |||||
| api.SkipWindow(); | |||||
| Action::Move(nextPos, api); | |||||
| Action::lastNextPos = nextPos; | |||||
| Action::lastMove = Action::posToDirection(nextPos, myCell); | |||||
| } | |||||
| else | |||||
| { | |||||
| Action::classNo++; | |||||
| } | |||||
| } | |||||
| else { | |||||
| Action::classNo++; | |||||
| } | |||||
| } | |||||
| //if (needNew > 0) { | |||||
| //needNew--; | |||||
| //} | |||||
| } | |||||
| else | |||||
| { | |||||
| auto self = api.GetSelfInfo(); | |||||
| GridPos myGrid = { self->x,self->y }; | |||||
| CellPos myCell = { api.GridToCell(self->x), api.GridToCell(self->y) }; | |||||
| gameState.getClosestStudent(myGrid, api); | |||||
| Action::Move(Action::lastNextPos, api); | |||||
| Action::isAtCellFalseCnt++; | |||||
| } | |||||
| } | |||||