| @@ -10,6 +10,7 @@ jobs: | |||
| source: '.' | |||
| extensions: 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,i,ixx,ipp,i++' | |||
| clangFormatVersion: 14 | |||
| exclude: './players' | |||
| inplace: False | |||
| dotnet-format-checking: | |||
| @@ -112,7 +112,7 @@ jobs: | |||
| - name: Pip Install paramiko | |||
| run: pip install paramiko | |||
| - uses: actions/download-artifact@v2 | |||
| - uses: actions/download-artifact@v3 | |||
| with: | |||
| name: my-artifact | |||
| path: ./THUAI6 | |||
| @@ -0,0 +1,9 @@ | |||
| # CAPI: go | |||
| ## 简介 | |||
| Go 通信组件与选手接口 | |||
| ## 敬请期待 | |||
| Go 选手接口目前还在实验当中,参见:[实验性选手 Go 接口](../../experimental/CAPI/go/) | |||
| @@ -14,8 +14,8 @@ | |||
| </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.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | |||
| @@ -4,23 +4,25 @@ cd ../.. | |||
| for i in {1..3} | |||
| 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 | |||
| pushd logic && dotnet format && popd | |||
| @@ -277,7 +277,12 @@ $$ | |||
| - 主动技能 | |||
| 0. 惩罚 Punish | |||
| - CD:45s | |||
| - “使用瞬间,在视野距离/3范围内(不是可视范围)的翻窗、开锁门、攻击前后摇、使用技能期间的捣蛋鬼会被眩晕(3070+$\frac{500×已受伤害}{基本伤害(1500000)×2^{开局老师数量-1}}$)ms” | |||
| - 使用瞬间,在视野距离/3范围内(不是可视范围)的翻窗、开锁门、攻击前后摇、使用技能期间的捣蛋鬼会被眩晕 | |||
| $$ | |||
| (3070+\frac{500×已受伤害}{基本伤害(1500000)×2^{开局老师数量-1}})ms | |||
| $$ | |||
| - 其眩晕得分为正常眩晕得分/2^开局老师数量-1^ | |||
| 1. 喝茶 HaveTea | |||
| - 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> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <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.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -8,7 +8,7 @@ | |||
| </PropertyGroup> | |||
| <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.Core" Version="2.46.6" /> | |||
| </ItemGroup> | |||
| @@ -8,7 +8,8 @@ namespace GameClass.GameObj | |||
| public override ShapeType Shape => ShapeType.Circle; | |||
| public override bool IsRigid => false; | |||
| public long MappingID { get; } | |||
| public Bullet bulletHasBombed; | |||
| public readonly Bullet bulletHasBombed; | |||
| public readonly XY facingDirection; | |||
| public BombedBullet(Bullet bullet) : | |||
| base(bullet.Position, bullet.Radius, GameObjType.BombedBullet) | |||
| @@ -8,7 +8,7 @@ namespace GameClass.GameObj | |||
| public CommonAttackOfGhost(Character player, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost; | |||
| AP.Set(GameData.basicApOfGhost); | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| public override double AttackDistance => GameData.basicAttackShortRange; | |||
| @@ -45,7 +45,7 @@ namespace GameClass.GameObj | |||
| public Strike(Character player, XY pos, int radius = GameData.bulletRadius) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost * 16 / 15; | |||
| AP.Set(GameData.basicApOfGhost * 16 / 15); | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| 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) : | |||
| base(player, radius, pos) | |||
| { | |||
| ap = GameData.basicApOfGhost * 4 / 5; | |||
| AP.Set(GameData.basicApOfGhost * 4 / 5); | |||
| } | |||
| public override double BulletBombRange => 0; | |||
| 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) | |||
| { | |||
| ap = (int)(GameData.basicApOfGhost * 6.0 / 5); | |||
| AP.Set((int)(GameData.basicApOfGhost * 6.0 / 5)); | |||
| } | |||
| public override double BulletBombRange => GameData.basicBulletBombRange; | |||
| 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) | |||
| { | |||
| ap = (int)(GameData.basicApOfGhost * 0.6); | |||
| AP.Set((int)(GameData.basicApOfGhost * 0.6)); | |||
| } | |||
| public override double BulletBombRange => GameData.basicBulletBombRange / 2; | |||
| public override double AttackDistance => GameData.basicAttackShortRange * 18 / 22; | |||
| @@ -11,16 +11,7 @@ namespace GameClass.GameObj | |||
| /// </summary> | |||
| public abstract double BulletBombRange { 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 bool IsRemoteAttack { get; } | |||
| public abstract int CastTime { get; } | |||
| @@ -52,8 +43,8 @@ namespace GameClass.GameObj | |||
| public Bullet(Character player, int radius, XY Position) : | |||
| 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.Parent = player; | |||
| } | |||
| @@ -31,12 +31,12 @@ namespace GameClass.GameObj | |||
| protected Character(XY initPos, int initRadius, CharacterType characterType) : | |||
| base(initPos, initRadius, GameObjType.Character) | |||
| { | |||
| this.ReSetCanMove(true); | |||
| this.CanMove.Set(true); | |||
| this.score = 0; | |||
| this.buffManager = new BuffManager(); | |||
| this.occupation = OccupationFactory.FindIOccupation(characterType); | |||
| 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.concealment = Occupation.Concealment; | |||
| this.alertnessRadius = Occupation.AlertnessRadius; | |||
| @@ -50,9 +50,6 @@ namespace GameClass.GameObj | |||
| { | |||
| this.ActiveSkillDictionary.Add(activeSkill, SkillFactory.FindActiveSkill(activeSkill)); | |||
| } | |||
| // UsePassiveSkill(); //这一过程放到gamestart时进行 | |||
| Debugger.Output(this, "constructed!"); | |||
| } | |||
| } | |||
| @@ -32,83 +32,60 @@ namespace GameClass.GameObj | |||
| /// </summary> | |||
| protected readonly int orgFixSpeed; | |||
| private readonly object treatLock = new(); | |||
| protected int treatSpeed = GameData.basicTreatSpeed; | |||
| public int TreatSpeed | |||
| { | |||
| get => treatSpeed; | |||
| get | |||
| { | |||
| lock (treatLock) | |||
| return treatSpeed; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| lock (treatLock) | |||
| { | |||
| 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; | |||
| @@ -128,19 +105,24 @@ namespace GameClass.GameObj | |||
| public Student(XY initPos, int initRadius, CharacterType characterType) : base(initPos, initRadius, characterType) | |||
| { | |||
| 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; | |||
| } | |||
| } | |||
| public class Golem : Student, IGolem | |||
| { | |||
| private readonly object parentLock = new(); | |||
| private Character? parent; // 主人 | |||
| public Character? Parent | |||
| { | |||
| get => parent; | |||
| get | |||
| { | |||
| lock (parentLock) | |||
| return parent; | |||
| } | |||
| set | |||
| { | |||
| lock (gameObjLock) | |||
| lock (parentLock) | |||
| { | |||
| parent = value; | |||
| } | |||
| @@ -8,15 +8,8 @@ namespace GameClass.GameObj | |||
| { | |||
| 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 装弹、攻击相关的基本属性及方法 | |||
| private readonly object attackLock = new(); | |||
| /// <summary> | |||
| /// 装弹冷却 | |||
| /// </summary> | |||
| @@ -25,7 +18,7 @@ namespace GameClass.GameObj | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| { | |||
| return cd; | |||
| } | |||
| @@ -36,7 +29,7 @@ namespace GameClass.GameObj | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| return orgCD; | |||
| } | |||
| } | |||
| @@ -47,14 +40,14 @@ namespace GameClass.GameObj | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| { | |||
| return bulletOfPlayer; | |||
| } | |||
| } | |||
| set | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| { | |||
| bulletOfPlayer = value; | |||
| cd = orgCD = (BulletFactory.BulletCD(value)); | |||
| @@ -69,7 +62,7 @@ namespace GameClass.GameObj | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| { | |||
| return maxBulletNum; | |||
| } | |||
| @@ -78,17 +71,17 @@ namespace GameClass.GameObj | |||
| private int bulletNum; | |||
| 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); | |||
| updateTimeOfBulletNum += add * cd; | |||
| return (bulletNum += add); | |||
| } | |||
| return maxBulletNum; | |||
| return bulletNum; | |||
| } | |||
| } | |||
| @@ -98,7 +91,7 @@ namespace GameClass.GameObj | |||
| /// <returns>攻击操作发出的子弹</returns> | |||
| public Bullet? Attack(double angle, int time) | |||
| { | |||
| lock (actionLock) | |||
| lock (attackLock) | |||
| { | |||
| if (bulletOfPlayer == BulletType.Null) | |||
| return null; | |||
| @@ -114,8 +107,8 @@ namespace GameClass.GameObj | |||
| ); | |||
| Bullet? bullet = BulletFactory.GetBullet(this, res, this.bulletOfPlayer); | |||
| 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; | |||
| } | |||
| else | |||
| @@ -140,7 +133,7 @@ namespace GameClass.GameObj | |||
| if (!(bouncer?.TeamID == this.TeamID)) | |||
| { | |||
| if (hasSpear || !HasShield) | |||
| _ = TrySubHp(subHP); | |||
| _ = SubHp(subHP); | |||
| if (hp <= 0) | |||
| TryActivatingLIFE(); | |||
| } | |||
| @@ -194,6 +187,9 @@ namespace GameClass.GameObj | |||
| } | |||
| #endregion | |||
| #region 血量相关的基本属性及方法 | |||
| private readonly ReaderWriterLockSlim hpReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim HPReadWriterLock => hpReaderWriterLock; | |||
| private long maxHp; | |||
| public long MaxHp | |||
| { | |||
| @@ -222,7 +218,8 @@ namespace GameClass.GameObj | |||
| HPReadWriterLock.ExitWriteLock(); | |||
| } | |||
| } | |||
| } // 最大血量 | |||
| } | |||
| // 最大血量 | |||
| protected long hp; | |||
| public long HP | |||
| { | |||
| @@ -238,22 +235,23 @@ namespace GameClass.GameObj | |||
| 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> | |||
| /// <param name="sub">减血量</param> | |||
| public long TrySubHp(long sub) | |||
| public long SubHp(long sub) | |||
| { | |||
| HPReadWriterLock.EnterWriteLock(); | |||
| 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之间 | |||
| public double Vampire | |||
| { | |||
| @@ -306,8 +321,65 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| 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 | |||
| #region 状态相关的基本属性与方法 | |||
| #region 查询状态相关的基本属性与方法 | |||
| private PlayerStateType playerState = PlayerStateType.Null; | |||
| public PlayerStateType PlayerState | |||
| { | |||
| @@ -371,6 +443,8 @@ namespace GameClass.GameObj | |||
| || playerState == PlayerStateType.Stunned || playerState == PlayerStateType.Charmed | |||
| || playerState == PlayerStateType.Null || playerState == PlayerStateType.Moving); | |||
| } | |||
| #endregion | |||
| #region 更改状态相关的属性和方法 | |||
| private GameObj? whatInteractingWith = null; | |||
| 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) | |||
| { | |||
| //只能被SetPlayerState引用 | |||
| @@ -492,13 +553,7 @@ namespace GameClass.GameObj | |||
| if (value == PlayerStateType.Rescued) return -1; | |||
| Door door = (Door)lastObj!; | |||
| door.StopOpen(); | |||
| ReleaseTool(door.DoorNum switch | |||
| { | |||
| 3 => PropType.Key3, | |||
| 5 => PropType.Key5, | |||
| _ => PropType.Key6, | |||
| } | |||
| ); | |||
| ReleaseTool(door.KeyType); | |||
| return ChangePlayerState(runningState, value, gameObj); | |||
| 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) | |||
| { | |||
| lock (actionLock) | |||
| { | |||
| if (SetPlayerState(RunningStateType.RunningForcibly, playerStateType) == -1) return false; | |||
| TryToRemove(); | |||
| ReSetCanMove(false); | |||
| CanMove.Set(false); | |||
| position = GameData.PosWhoDie; | |||
| } | |||
| return true; | |||
| @@ -619,6 +687,9 @@ namespace GameClass.GameObj | |||
| } | |||
| #region 道具和buff相关属性、方法 | |||
| private readonly object inventoryLock = new(); | |||
| public object InventoryLock => inventoryLock; | |||
| private Gadget[] propInventory = new Gadget[GameData.maxNumOfPropInPropInventory] | |||
| {new NullProp(), new NullProp(),new NullProp() }; | |||
| public Gadget[] PropInventory | |||
| @@ -639,7 +710,7 @@ namespace GameClass.GameObj | |||
| /// 使用物品栏中的道具 | |||
| /// </summary> | |||
| /// <returns>被使用的道具</returns> | |||
| public Gadget UseProp(int indexing) | |||
| public Gadget ConsumeProp(int indexing) | |||
| { | |||
| if (indexing < 0 || indexing >= GameData.maxNumOfPropInPropInventory) | |||
| return new NullProp(); | |||
| @@ -652,7 +723,7 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| public Gadget UseProp(PropType propType) | |||
| public Gadget ConsumeProp(PropType propType) | |||
| { | |||
| if (propType == PropType.Null) | |||
| { | |||
| @@ -687,7 +758,7 @@ namespace GameClass.GameObj | |||
| return new NullProp(); | |||
| } | |||
| public bool UseTool(PropType propType) | |||
| public bool UseTool(PropType propType)//占用道具,使其不能重复使用和被消耗 | |||
| { | |||
| lock (inventoryLock) | |||
| { | |||
| @@ -731,7 +802,7 @@ namespace GameClass.GameObj | |||
| } | |||
| 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); | |||
| public bool HasFasterSpeed => buffManager.HasFasterSpeed; | |||
| @@ -26,26 +26,14 @@ namespace GameClass.GameObj | |||
| protected XY position; | |||
| 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 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() | |||
| { | |||
| return Interlocked.Exchange(ref isRemoved, 1) == 0; | |||
| return IsRemoved.TrySet(true); | |||
| } | |||
| public int Radius { get; } | |||
| @@ -7,10 +7,6 @@ namespace GameClass.GameObj | |||
| 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) | |||
| { | |||
| } | |||
| @@ -20,9 +20,21 @@ namespace GameClass.GameObj | |||
| public Gadget[] PropInChest => propInChest; | |||
| private long openStartTime = 0; | |||
| public long OpenStartTime => openStartTime; | |||
| public long OpenStartTime | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) return openStartTime; | |||
| } | |||
| } | |||
| private Character? whoOpen = null; | |||
| public Character? WhoOpen => whoOpen; | |||
| public Character? WhoOpen | |||
| { | |||
| get | |||
| { | |||
| lock (gameObjLock) return whoOpen; | |||
| } | |||
| } | |||
| public bool Open(Character character) | |||
| { | |||
| lock (gameObjLock) | |||
| @@ -13,23 +13,16 @@ namespace GameClass.GameObj | |||
| public Door(XY initPos, PlaceType placeType) : | |||
| 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 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; | |||
| public long OpenStartTime | |||
| @@ -119,7 +98,7 @@ namespace GameClass.GameObj | |||
| { | |||
| if (!isOpen) return false; | |||
| if (whoLockOrOpen != null) return false; | |||
| lockDegree = 0; | |||
| LockDegree.Set(0); | |||
| whoLockOrOpen = character; | |||
| return true; | |||
| } | |||
| @@ -128,7 +107,7 @@ namespace GameClass.GameObj | |||
| { | |||
| lock (gameObjLock) | |||
| { | |||
| if (lockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||
| if (LockDegree >= GameData.degreeOfLockingOrOpeningTheDoor) | |||
| isOpen = false; | |||
| whoLockOrOpen = null; | |||
| } | |||
| @@ -157,12 +136,7 @@ namespace GameClass.GameObj | |||
| { | |||
| if (character.PlayerState == PlayerStateType.OpeningTheDoor) | |||
| { | |||
| character.ReleaseTool(DoorNum switch | |||
| { | |||
| 3 => PropType.Key3, | |||
| 5 => PropType.Key5, | |||
| _ => PropType.Key6, | |||
| }); | |||
| character.ReleaseTool(KeyType); | |||
| character.SetPlayerStateNaturally(); | |||
| } | |||
| else if (character.PlayerState == PlayerStateType.LockingTheDoor) | |||
| @@ -1,5 +1,4 @@ | |||
| using Google.Protobuf.WellKnownTypes; | |||
| using Preparation.Interface; | |||
| using Preparation.Interface; | |||
| using Preparation.Utility; | |||
| using System; | |||
| @@ -24,20 +23,7 @@ namespace GameClass.GameObj | |||
| 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; | |||
| public long OpenStartTime | |||
| @@ -50,9 +36,10 @@ namespace GameClass.GameObj | |||
| } | |||
| public bool TryToOpen() | |||
| { | |||
| if (!PowerSupply) return false; | |||
| lock (gameObjLock) | |||
| { | |||
| if (!powerSupply || openStartTime > 0) return false; | |||
| if (openStartTime > 0) return false; | |||
| openStartTime = Environment.TickCount64; | |||
| return true; | |||
| } | |||
| @@ -17,24 +17,14 @@ namespace GameClass.GameObj | |||
| public override bool IgnoreCollideExecutor(IGameObj targetObj) | |||
| { | |||
| if (!canOpen) return true; | |||
| if (!CanOpen) return true; | |||
| if (!IsOpen) return false; | |||
| if (targetObj.Type != GameObjType.Character) | |||
| return true; // 非玩家不碰撞 | |||
| 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; | |||
| public bool IsOpen | |||
| @@ -26,7 +26,7 @@ namespace GameClass.GameObj | |||
| { | |||
| Random r = new Random(Environment.TickCount); | |||
| 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()); | |||
| } | |||
| finally | |||
| @@ -41,7 +41,7 @@ namespace GameClass.GameObj | |||
| try | |||
| { | |||
| foreach (Doorway doorway in GameObjDict[GameObjType.Doorway]) | |||
| doorway.PowerSupply = true; | |||
| doorway.PowerSupply.Set(true); | |||
| } | |||
| finally | |||
| { | |||
| @@ -8,7 +8,7 @@ namespace GameClass.GameObj | |||
| { | |||
| protected readonly object actionLock = new(); | |||
| public object ActionLock => actionLock; | |||
| //player.actionLock>其他.actionLock | |||
| //player.actionLock>其他.actionLock/其他Lock,应当避免两个player的actionlock互锁 | |||
| private readonly ReaderWriterLockSlim moveReaderWriterLock = new(); | |||
| public ReaderWriterLockSlim MoveReaderWriterLock => moveReaderWriterLock; | |||
| //规定moveReaderWriterLock<actionLock | |||
| @@ -52,21 +52,22 @@ namespace GameClass.GameObj | |||
| } | |||
| } | |||
| public override XY FacingDirection | |||
| protected XY facingDirection = new(1, 0); | |||
| public XY FacingDirection | |||
| { | |||
| get | |||
| { | |||
| lock (actionLock) | |||
| 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) | |||
| @@ -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> | |||
| public int MoveSpeed | |||
| { | |||
| get => Interlocked.CompareExchange(ref moveSpeed, 0, 0); | |||
| set => Interlocked.Exchange(ref moveSpeed, value); | |||
| } | |||
| public AtomicInt MoveSpeed { get; } | |||
| /// <summary> | |||
| /// 原初移动速度 | |||
| /// </summary> | |||
| public int OrgMoveSpeed { get; protected set; } | |||
| protected int orgMoveSpeed; | |||
| public int OrgMoveSpeed => orgMoveSpeed; | |||
| /* /// <summary> | |||
| /// 复活时数据重置 | |||
| @@ -24,8 +24,8 @@ namespace GameClass.GameObj | |||
| public Gadget(XY initPos, int radius = GameData.propRadius) : | |||
| 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 | |||
| @@ -17,8 +17,8 @@ namespace GameClass.GameObj | |||
| public Item(XY initPos, int radius = GameData.propRadius) : | |||
| 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) | |||
| { | |||
| if (!obj.IsAvailableForMove) { EndMove(obj); return; } | |||
| obj.IsMoving = true; | |||
| obj.IsMoving.Set(true); | |||
| } | |||
| new Thread | |||
| @@ -139,7 +139,7 @@ namespace GameEngine | |||
| if (isEnded) | |||
| { | |||
| obj.IsMoving = false; | |||
| obj.IsMoving.Set(false); | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| @@ -184,7 +184,7 @@ namespace GameEngine | |||
| } | |||
| if (isEnded) | |||
| { | |||
| obj.IsMoving = false; | |||
| obj.IsMoving.Set(false); | |||
| EndMove(obj); | |||
| return; | |||
| } | |||
| @@ -224,7 +224,7 @@ namespace GameEngine | |||
| } | |||
| } while (flag); | |||
| } | |||
| obj.IsMoving = false; // 结束移动 | |||
| obj.IsMoving.Set(false); // 结束移动 | |||
| EndMove(obj); | |||
| } | |||
| } | |||
| @@ -321,7 +321,7 @@ namespace Gaming | |||
| if (playerRescued.AddTimeOfRescue(GameData.checkInterval)) | |||
| { | |||
| playerRescued.SetPlayerStateNaturally(); | |||
| playerRescued.HP = playerRescued.MaxHp / 2; | |||
| playerRescued.SetHP(playerRescued.MaxHp / 2); | |||
| player.AddScore(GameData.StudentScoreRescue); | |||
| return false; | |||
| } | |||
| @@ -450,12 +450,12 @@ namespace Gaming | |||
| 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); | |||
| 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) | |||
| { | |||
| @@ -479,19 +479,12 @@ namespace Gaming | |||
| 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, | |||
| }; | |||
| if (!player.UseTool(propType)) return false; | |||
| if (!player.UseTool(doorToLock.KeyType)) return false; | |||
| long stateNum = player.SetPlayerState(RunningStateType.Waiting, PlayerStateType.LockingTheDoor, doorToLock); | |||
| if (stateNum == -1) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToLock.KeyType); | |||
| return false; | |||
| } | |||
| @@ -502,25 +495,26 @@ namespace Gaming | |||
| player.ThreadNum.WaitOne(); | |||
| if (!player.StartThread(stateNum, RunningStateType.RunningActively)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToLock.KeyType); | |||
| player.ThreadNum.Release(); | |||
| return; | |||
| } | |||
| if (!doorToLock.TryLock(player)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToLock.KeyType); | |||
| player.ResetPlayerState(stateNum); | |||
| player.ThreadNum.Release(); | |||
| return; | |||
| } | |||
| Thread.Sleep(GameData.checkInterval); | |||
| new FrameRateTaskExecutor<int>( | |||
| loopCondition: () => stateNum == player.StateNum && gameMap.Timer.IsGaming && doorToLock.LockDegree < GameData.degreeOfLockingOrOpeningTheDoor, | |||
| loopCondition: () => stateNum == player.StateNum && gameMap.Timer.IsGaming, | |||
| loopToDo: () => | |||
| { | |||
| if ((gameMap.PartInTheSameCell(doorToLock.Position, GameObjType.Character)) != null) | |||
| return false; | |||
| doorToLock.LockDegree += GameData.checkInterval * player.SpeedOfOpeningOrLocking; | |||
| if (doorToLock.LockDegree.Add(GameData.checkInterval * player.SpeedOfOpeningOrLocking) >= GameData.basicSpeedOfOpeningOrLocking) | |||
| return false; | |||
| return true; | |||
| }, | |||
| timeInterval: GameData.checkInterval, | |||
| @@ -528,7 +522,7 @@ namespace Gaming | |||
| ) | |||
| .Start(); | |||
| doorToLock.StopLock(); | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToLock.KeyType); | |||
| player.ThreadNum.Release(); | |||
| player.ResetPlayerState(stateNum); | |||
| } | |||
| @@ -541,22 +535,15 @@ namespace Gaming | |||
| public bool OpenDoor(Character player) | |||
| { | |||
| 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) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToOpen.KeyType); | |||
| return false; | |||
| } | |||
| @@ -567,13 +554,13 @@ namespace Gaming | |||
| player.ThreadNum.WaitOne(); | |||
| if (!player.StartThread(stateNum, RunningStateType.RunningSleepily)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToOpen.KeyType); | |||
| player.ThreadNum.Release(); | |||
| return; | |||
| } | |||
| if (!doorToLock.TryOpen(player)) | |||
| if (!doorToOpen.TryOpen(player)) | |||
| { | |||
| player.ReleaseTool(propType); | |||
| player.ReleaseTool(doorToOpen.KeyType); | |||
| if (player.ResetPlayerState(stateNum)) | |||
| player.ThreadNum.Release(); | |||
| return; | |||
| @@ -582,8 +569,8 @@ namespace Gaming | |||
| if (player.ResetPlayerState(stateNum)) | |||
| { | |||
| doorToLock.StopOpen(); | |||
| player.ReleaseTool(propType); | |||
| doorToOpen.StopOpen(); | |||
| player.ReleaseTool(doorToOpen.KeyType); | |||
| player.ThreadNum.Release(); | |||
| } | |||
| } | |||
| @@ -33,7 +33,7 @@ namespace Gaming | |||
| Debugger.Output(obj, " end move at " + obj.Position.ToString() + " At time: " + Environment.TickCount64); | |||
| if (obj.CanMove && ((Bullet)obj).TypeOfBullet != BulletType.JumpyDumpty) | |||
| BulletBomb((Bullet)obj, null); | |||
| obj.ReSetCanMove(false); | |||
| obj.CanMove.Set(false); | |||
| } | |||
| ); | |||
| this.characterManager = characterManager; | |||
| @@ -89,7 +89,7 @@ namespace Gaming | |||
| { | |||
| if (gameMap.Remove(bullet)) | |||
| { | |||
| bullet.ReSetCanMove(false); | |||
| bullet.CanMove.Set(false); | |||
| if (bullet.BulletBombRange > 0) | |||
| { | |||
| BombedBullet bombedBullet = new(bullet); | |||
| @@ -255,16 +255,12 @@ namespace Gaming | |||
| } | |||
| if (bullet != null) | |||
| { | |||
| #if DEBUG | |||
| Console.WriteLine($"playerID:{player.ID} successfully attacked!"); | |||
| #endif | |||
| Debugger.Output($"playerID:{player.ID} successfully attacked!"); | |||
| return true; | |||
| } | |||
| 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; | |||
| } | |||
| } | |||
| @@ -306,32 +306,26 @@ namespace Gaming | |||
| /// <returns>人物在受到攻击后死了吗</returns> | |||
| public void BeAttacked(Student student, Bullet bullet) | |||
| { | |||
| #if DEBUG | |||
| Debugger.Output(student, "is being shot!"); | |||
| #endif | |||
| if (!bullet.Parent!.IsGhost()) return; | |||
| if (student.CharacterType == CharacterType.StraightAStudent) | |||
| { | |||
| ((WriteAnswers)student.FindActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation = 0; | |||
| ((WriteAnswers)student.FindActiveSkill(ActiveSkillType.WriteAnswers)).DegreeOfMeditation.Set(0); | |||
| } | |||
| student.SetDegreeOfTreatment0(); | |||
| if (student.NoHp()) return; // 原来已经死了 | |||
| #if DEBUG | |||
| Debugger.Output(bullet, " 's AP is " + bullet.AP.ToString()); | |||
| #endif | |||
| if (student.TryUseShield()) | |||
| { | |||
| 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()); | |||
| #endif | |||
| 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; | |||
| } | |||
| @@ -340,17 +334,13 @@ namespace Gaming | |||
| long subHp; | |||
| 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()); | |||
| #endif | |||
| } | |||
| 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()); | |||
| #endif | |||
| } | |||
| bullet.Parent.AddScore(GameData.TrickerScoreAttackStudent(subHp)); | |||
| if (student.CharacterType == CharacterType.Teacher) | |||
| @@ -358,7 +348,7 @@ namespace Gaming | |||
| 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) | |||
| student.TryActivatingLIFE(); // 如果有复活甲 | |||
| @@ -393,7 +383,7 @@ namespace Gaming | |||
| for (int i = 0; i < GameData.maxNumOfPropInPropInventory; i++) | |||
| { | |||
| Gadget? prop = player.UseProp(i); | |||
| Gadget? prop = player.ConsumeProp(i); | |||
| if (prop != null) | |||
| { | |||
| prop.ReSetPos(player.Position); | |||
| @@ -238,7 +238,7 @@ namespace Gaming | |||
| Character? player = gameMap.FindPlayerToAction(playerID); | |||
| if (player != null) | |||
| { | |||
| propManager.UseProp(player, propType); | |||
| propManager.ConsumeProp(player, propType); | |||
| } | |||
| } | |||
| public void ThrowProp(long playerID, PropType propType = PropType.Null) | |||
| @@ -326,7 +326,7 @@ namespace Gaming | |||
| { | |||
| foreach (Character player in gameMap.GameObjDict[GameObjType.Character]) | |||
| { | |||
| player.ReSetCanMove(false); | |||
| player.CanMove.Set(false); | |||
| } | |||
| } | |||
| gameMap.GameObjDict[keyValuePair.Key].Clear(); | |||
| @@ -17,11 +17,11 @@ namespace Gaming | |||
| private readonly CharacterManager characterManager; | |||
| 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) | |||
| return; | |||
| Gadget prop = player.UseProp(propType); | |||
| Gadget prop = player.ConsumeProp(propType); | |||
| switch (prop.GetPropType()) | |||
| { | |||
| case PropType.ShieldOrSpear: | |||
| @@ -46,7 +46,7 @@ namespace Gaming | |||
| if (!player.IsGhost()) | |||
| if (player.HP < player.MaxHp) | |||
| { | |||
| player.HP += GameData.basicTreatmentDegree / 2; | |||
| player.AddHP(GameData.basicTreatmentDegree / 2); | |||
| player.AddScore(GameData.ScorePropAddHp); | |||
| } | |||
| else player.AddAp(GameData.PropDuration); | |||
| @@ -117,7 +117,7 @@ namespace Gaming | |||
| { | |||
| if (!gameMap.Timer.IsGaming || player.IsRemoved) | |||
| return; | |||
| Gadget prop = player.UseProp(propType); | |||
| Gadget prop = player.ConsumeProp(propType); | |||
| if (prop.GetPropType() == PropType.Null) | |||
| return; | |||
| @@ -196,7 +196,7 @@ namespace Gaming | |||
| } | |||
| if (homingMissile != null) | |||
| { | |||
| homingMissile.ReSetCanMove(true); | |||
| homingMissile.CanMove.Set(true); | |||
| 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)) | |||
| gameMap.AddNumOfRepairedGenerators(); | |||
| 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)) | |||
| { | |||
| character.SetPlayerStateNaturally(); | |||
| character.HP = GameData.RemainHpWhenAddLife; | |||
| character.SetHP(GameData.RemainHpWhenAddLife); | |||
| ((Student)character).SetTimeOfRescue(0); | |||
| player.AddScore(GameData.StudentScoreRescue); | |||
| break; | |||
| @@ -452,7 +452,7 @@ namespace Gaming | |||
| if ((character.HP < character.MaxHp) && gameMap.CanSee(player, character)) | |||
| { | |||
| player.AddScore(GameData.StudentScoreTreat(GameData.addHpWhenEncourage)); | |||
| character.HP += GameData.addHpWhenEncourage; | |||
| character.AddHP(GameData.addHpWhenEncourage); | |||
| ((Student)character).SetDegreeOfTreatment0(); | |||
| break; | |||
| } | |||
| @@ -25,8 +25,8 @@ namespace Gaming // 被动技能开局时就释放,持续到游戏结束 | |||
| () => 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()); | |||
| }, | |||
| timeInterval: GameData.frameDuration, | |||
| @@ -6,7 +6,8 @@ namespace Preparation.Interface | |||
| public interface ICharacter : IMoveable | |||
| { | |||
| public long TeamID { get; } | |||
| public long HP { get; set; } | |||
| public long HP { get; } | |||
| public long AddHP(long add); | |||
| public long Score { get; } | |||
| public void AddScore(long add); | |||
| public double Vampire { get; } | |||
| @@ -7,10 +7,9 @@ namespace Preparation.Interface | |||
| public GameObjType Type { get; } | |||
| public long ID { get; } | |||
| public XY Position { get; } // if Square, Pos equals the center | |||
| public XY FacingDirection { get; } | |||
| public bool IsRigid { get; } | |||
| public AtomicBool IsRemoved { get; } | |||
| public ShapeType Shape { get; } | |||
| public bool CanMove { get; } | |||
| public int Radius { get; } // if Square, Radius equals half length of one side | |||
| public bool IgnoreCollideExecutor(IGameObj targetObj); // 忽略碰撞,在具体类中实现 | |||
| } | |||
| @@ -6,15 +6,15 @@ namespace Preparation.Interface | |||
| { | |||
| public interface IMoveable : IGameObj | |||
| { | |||
| public XY FacingDirection { get; set; } | |||
| 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 long StateNum { get; } | |||
| public Semaphore ThreadNum { get; } | |||
| public long MovingSetPos(XY moveVec, long stateNum); | |||
| public void ReSetCanMove(bool value); | |||
| public bool WillCollideWith(IGameObj? targetObj, XY nextPos) // 检查下一位置是否会和目标物碰撞 | |||
| { | |||
| if (targetObj == null) | |||
| @@ -174,12 +174,7 @@ namespace Preparation.Interface | |||
| public override int SkillCD => GameData.commonSkillCD; | |||
| 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 | |||
| @@ -9,7 +9,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||
| </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 | |||
| { | |||
| public struct XY | |||
| { | |||
| public int x; | |||
| @@ -89,7 +88,7 @@ namespace Preparation.Utility | |||
| 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() | |||
| { | |||
| @@ -203,7 +203,7 @@ namespace Server | |||
| Type = Transformation.ToBulletType(bombedBullet.bulletHasBombed.TypeOfBullet), | |||
| X = bombedBullet.bulletHasBombed.Position.x, | |||
| Y = bombedBullet.bulletHasBombed.Position.y, | |||
| FacingDirection = bombedBullet.FacingDirection.Angle(), | |||
| FacingDirection = bombedBullet.facingDirection.Angle(), | |||
| MappingId = bombedBullet.MappingID, | |||
| BombRange = bombedBullet.bulletHasBombed.BulletBombRange | |||
| } | |||
| @@ -90,7 +90,7 @@ namespace Server | |||
| return; | |||
| } | |||
| } | |||
| catch (Exception) | |||
| catch | |||
| { | |||
| // Console.WriteLine(ex); | |||
| } | |||
| @@ -145,13 +145,13 @@ namespace Server | |||
| semaDict[request.PlayerId].Item1.Wait(); | |||
| try | |||
| { | |||
| if (currentGameInfo != null) | |||
| if (currentGameInfo != null && !exitFlag) | |||
| { | |||
| await responseStream.WriteAsync(currentGameInfo); | |||
| //Console.WriteLine("Send!"); | |||
| } | |||
| } | |||
| catch (Exception) | |||
| catch | |||
| { | |||
| if (!exitFlag) | |||
| { | |||
| @@ -305,7 +305,7 @@ namespace Server | |||
| public override Task<BoolRes> UseProp(PropMsg request, ServerCallContext context) | |||
| { | |||
| #if DEBUG | |||
| Console.WriteLine($"UseProp ID: {request.PlayerId}"); | |||
| Console.WriteLine($"ConsumeProp ID: {request.PlayerId}"); | |||
| #endif | |||
| BoolRes boolRes = new(); | |||
| if (request.PlayerId >= spectatorMinPlayerID) | |||
| @@ -10,7 +10,7 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="CommandLineParser" Version="2.9.1" /> | |||
| <PackageReference Include="FrameRateTask" Version="1.2.0" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||
| <PackageReference Include="Grpc" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Core" Version="2.46.6" /> | |||
| <PackageReference Include="Grpc.Tools" Version="2.54.0"> | |||
| @@ -7,7 +7,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.1" /> | |||
| <PackageReference Include="Google.Protobuf" Version="3.23.3" /> | |||
| </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++; | |||
| } | |||
| } | |||