Browse Source

Merge pull request #653 from eesast/dev

chore: add player code
main
Changli Tang GitHub 2 years ago
parent
commit
d67e7e7117
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 743660 additions and 391 deletions
  1. +1
    -0
      .github/workflows/format.yml
  2. +1
    -1
      .github/workflows/upload_COS.yml
  3. +9
    -0
      CAPI/go/README.md
  4. +2
    -2
      dependency/proto/Protos.csproj
  5. +19
    -17
      dependency/shell/format.sh
  6. +6
    -1
      docs/GameRules.md
  7. +5
    -0
      experimental/CAPI/README.md
  8. +28
    -0
      experimental/CAPI/go/.gitignore
  9. +15
    -0
      experimental/CAPI/go/API/ai.go
  10. +80
    -0
      experimental/CAPI/go/API/core/api.go
  11. +97
    -0
      experimental/CAPI/go/API/core/communication.go
  12. +202
    -0
      experimental/CAPI/go/API/core/logic.go
  13. +13
    -0
      experimental/CAPI/go/API/go.mod
  14. +19
    -0
      experimental/CAPI/go/API/go.sum
  15. +18
    -0
      experimental/CAPI/go/API/main.go
  16. +3
    -0
      experimental/CAPI/go/API/shell/init.sh
  17. +14
    -0
      experimental/CAPI/go/API/thuai6/iai.go
  18. +15
    -0
      experimental/CAPI/go/API/thuai6/iapi.go
  19. +7
    -0
      experimental/CAPI/go/API/thuai6/state.go
  20. +55
    -0
      experimental/CAPI/go/API/thuai6/structures.go
  21. +38
    -0
      experimental/CAPI/go/API/thuai6/utils.go
  22. +15
    -0
      experimental/CAPI/go/README.md
  23. +9
    -0
      experimental/README.md
  24. +3
    -0
      experimental/dependency/README.md
  25. +204
    -0
      experimental/dependency/proto/Message2Clients.proto
  26. +74
    -0
      experimental/dependency/proto/Message2Server.proto
  27. +132
    -0
      experimental/dependency/proto/MessageType.proto
  28. +33
    -0
      experimental/dependency/proto/Services.proto
  29. +10
    -0
      experimental/dependency/proto/buf.gen.tag.yaml
  30. +14
    -0
      experimental/dependency/proto/buf.gen.yaml
  31. +10
    -0
      experimental/dependency/proto/go_output.sh
  32. +1
    -1
      logic/Client/Client.csproj
  33. +1
    -1
      logic/ClientTest/ClientTest.csproj
  34. +2
    -1
      logic/GameClass/GameObj/Bullet/BombedBullet.cs
  35. +5
    -5
      logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs
  36. +3
    -12
      logic/GameClass/GameObj/Bullet/Bullet.cs
  37. +2
    -5
      logic/GameClass/GameObj/Character/Character.Skill.cs
  38. +40
    -58
      logic/GameClass/GameObj/Character/Character.Student.cs
  39. +133
    -62
      logic/GameClass/GameObj/Character/Character.cs
  40. +2
    -14
      logic/GameClass/GameObj/GameObj.cs
  41. +0
    -4
      logic/GameClass/GameObj/Immovable.cs
  42. +14
    -2
      logic/GameClass/GameObj/Map/Chest.cs
  43. +11
    -37
      logic/GameClass/GameObj/Map/Door.cs
  44. +4
    -17
      logic/GameClass/GameObj/Map/Doorway.cs
  45. +2
    -12
      logic/GameClass/GameObj/Map/EmergencyExit.cs
  46. +2
    -2
      logic/GameClass/GameObj/Map/Map.cs
  47. +14
    -35
      logic/GameClass/GameObj/Moveable.cs
  48. +2
    -2
      logic/GameClass/GameObj/Prop/Gadget.cs
  49. +2
    -2
      logic/GameClass/GameObj/Prop/Item.cs
  50. +4
    -4
      logic/GameEngine/MoveEngine.cs
  51. +21
    -34
      logic/Gaming/ActionManager.cs
  52. +4
    -8
      logic/Gaming/AttackManager.cs
  53. +7
    -17
      logic/Gaming/CharacterManager.cs
  54. +2
    -2
      logic/Gaming/Game.cs
  55. +4
    -4
      logic/Gaming/PropManager.cs
  56. +4
    -4
      logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs
  57. +2
    -2
      logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs
  58. +2
    -1
      logic/Preparation/Interface/ICharacter.cs
  59. +1
    -2
      logic/Preparation/Interface/IGameObj.cs
  60. +4
    -4
      logic/Preparation/Interface/IMoveable.cs
  61. +1
    -6
      logic/Preparation/Interface/ISkill.cs
  62. +1
    -1
      logic/Preparation/Preparation.csproj
  63. +99
    -0
      logic/Preparation/Utility/SafeValue.cs
  64. +1
    -2
      logic/Preparation/Utility/XY.cs
  65. +1
    -1
      logic/Server/CopyInfo.cs
  66. +4
    -4
      logic/Server/RpcServices.cs
  67. +1
    -1
      logic/Server/Server.csproj
  68. +1
    -1
      playback/Playback/Playback.csproj
  69. +3656
    -0
      players/-/player1.cpp
  70. +3656
    -0
      players/-/player2.cpp
  71. +3656
    -0
      players/-/player3.cpp
  72. +3656
    -0
      players/-/player4.cpp
  73. +3656
    -0
      players/-/player5.cpp
  74. +1
    -0
      players/.gitattributes
  75. +222761
    -0
      players/ChatGPA/player1.cpp
  76. +1575
    -0
      players/ChatGPA/player2.cpp
  77. +222762
    -0
      players/ChatGPA/player3.cpp
  78. +222762
    -0
      players/ChatGPA/player4.cpp
  79. +995
    -0
      players/ChatGPA/player5.cpp
  80. +3757
    -0
      players/LQ说什么都队/player1.cpp
  81. +3757
    -0
      players/LQ说什么都队/player2.cpp
  82. +3757
    -0
      players/LQ说什么都队/player3.cpp
  83. +3757
    -0
      players/LQ说什么都队/player4.cpp
  84. +3757
    -0
      players/LQ说什么都队/player5.cpp
  85. +1255
    -0
      players/Mukava Poikaa/player1.cpp
  86. +1255
    -0
      players/Mukava Poikaa/player2.cpp
  87. +1255
    -0
      players/Mukava Poikaa/player3.cpp
  88. +1255
    -0
      players/Mukava Poikaa/player4.cpp
  89. +1255
    -0
      players/Mukava Poikaa/player5.cpp
  90. +2902
    -0
      players/N-A/player1.cpp
  91. +2902
    -0
      players/N-A/player2.cpp
  92. +2902
    -0
      players/N-A/player3.cpp
  93. +2902
    -0
      players/N-A/player4.cpp
  94. +2902
    -0
      players/N-A/player5.cpp
  95. +2477
    -0
      players/PKT48TeamTS/player1.cpp
  96. +2477
    -0
      players/PKT48TeamTS/player2.cpp
  97. +2477
    -0
      players/PKT48TeamTS/player3.cpp
  98. +2477
    -0
      players/PKT48TeamTS/player4.cpp
  99. +2477
    -0
      players/PKT48TeamTS/player5.cpp
  100. +1014
    -0
      players/closeAI/player1.cpp

+ 1
- 0
.github/workflows/format.yml View File

@@ -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:


+ 1
- 1
.github/workflows/upload_COS.yml View File

@@ -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


+ 9
- 0
CAPI/go/README.md View File

@@ -0,0 +1,9 @@
# CAPI: go

## 简介

Go 通信组件与选手接口

## 敬请期待

Go 选手接口目前还在实验当中,参见:[实验性选手 Go 接口](../../experimental/CAPI/go/)

+ 2
- 2
dependency/proto/Protos.csproj View File

@@ -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">


+ 19
- 17
dependency/shell/format.sh View File

@@ -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


+ 6
- 1
docs/GameRules.md View File

@@ -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


+ 5
- 0
experimental/CAPI/README.md View File

@@ -0,0 +1,5 @@
# experimental/CAPI

## 简介

实验性选手接口

+ 28
- 0
experimental/CAPI/go/.gitignore View File

@@ -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

+ 15
- 0
experimental/CAPI/go/API/ai.go View File

@@ -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
}

+ 80
- 0
experimental/CAPI/go/API/core/api.go View File

@@ -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)
}

+ 97
- 0
experimental/CAPI/go/API/core/communication.go View File

@@ -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
}

+ 202
- 0
experimental/CAPI/go/API/core/logic.go View File

@@ -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
}

+ 13
- 0
experimental/CAPI/go/API/go.mod View File

@@ -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
)

+ 19
- 0
experimental/CAPI/go/API/go.sum View File

@@ -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=

+ 18
- 0
experimental/CAPI/go/API/main.go View File

@@ -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")
}

+ 3
- 0
experimental/CAPI/go/API/shell/init.sh View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash

../../../dependency/proto/go_output.sh && go get

+ 14
- 0
experimental/CAPI/go/API/thuai6/iai.go View File

@@ -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

+ 15
- 0
experimental/CAPI/go/API/thuai6/iapi.go View File

@@ -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
}

+ 7
- 0
experimental/CAPI/go/API/thuai6/state.go View File

@@ -0,0 +1,7 @@
package thuai6

type State struct {
StudentSelf *Student
TrickerSelf *Tricker
Guids []int64
}

+ 55
- 0
experimental/CAPI/go/API/thuai6/structures.go View File

@@ -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
}

+ 38
- 0
experimental/CAPI/go/API/thuai6/utils.go View File

@@ -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,
}
)

+ 15
- 0
experimental/CAPI/go/README.md View File

@@ -0,0 +1,15 @@
# experimental/CAPI/go

## 简介

实验性选手 Go 接口

## 运行方法

**注意事项**:Visual Studio Code 的 Go 语言提示**必须**以 `main` package 所在的目录为根目录,否则可能无法进行正确的代码提示。因此以下操作的根目录均为 `/CPI/go/API/`

```bash
$ ./shell/init.sh
$ go build
$ ./API
```

+ 9
- 0
experimental/README.md View File

@@ -0,0 +1,9 @@
# experimental

## 简介

进行实验性功能的探索

## 注意事项

本文件夹内的文件采用某历史版本,与新近版本未必一致。

+ 3
- 0
experimental/dependency/README.md View File

@@ -0,0 +1,3 @@
# experimental/dependency

实验性依赖文件

+ 204
- 0
experimental/dependency/proto/Message2Clients.proto View File

@@ -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;
// }

+ 74
- 0
experimental/dependency/proto/Message2Server.proto View File

@@ -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时发送的消息内容
// }

+ 132
- 0
experimental/dependency/proto/MessageType.proto View File

@@ -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;
}

+ 33
- 0
experimental/dependency/proto/Services.proto View File

@@ -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); // 结束所有动作
}

+ 10
- 0
experimental/dependency/proto/buf.gen.tag.yaml View File

@@ -0,0 +1,10 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: proto
plugins:
- name: gotag
out: .
opt:
- auto=bson

+ 14
- 0
experimental/dependency/proto/buf.gen.yaml View File

@@ -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

+ 10
- 0
experimental/dependency/proto/go_output.sh View File

@@ -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

+ 1
- 1
logic/Client/Client.csproj View File

@@ -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>


+ 1
- 1
logic/ClientTest/ClientTest.csproj View File

@@ -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>


+ 2
- 1
logic/GameClass/GameObj/Bullet/BombedBullet.cs View File

@@ -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)


+ 5
- 5
logic/GameClass/GameObj/Bullet/Bullet.Ghost.cs View File

@@ -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;


+ 3
- 12
logic/GameClass/GameObj/Bullet/Bullet.cs View File

@@ -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;
}


+ 2
- 5
logic/GameClass/GameObj/Character/Character.Skill.cs View File

@@ -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!");
}
}

+ 40
- 58
logic/GameClass/GameObj/Character/Character.Student.cs View File

@@ -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;
}


+ 133
- 62
logic/GameClass/GameObj/Character/Character.cs View File

@@ -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;



+ 2
- 14
logic/GameClass/GameObj/GameObj.cs View File

@@ -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; }


+ 0
- 4
logic/GameClass/GameObj/Immovable.cs View File

@@ -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)
{
}


+ 14
- 2
logic/GameClass/GameObj/Map/Chest.cs View File

@@ -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)


+ 11
- 37
logic/GameClass/GameObj/Map/Door.cs View File

@@ -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)


+ 4
- 17
logic/GameClass/GameObj/Map/Doorway.cs View File

@@ -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;
}


+ 2
- 12
logic/GameClass/GameObj/Map/EmergencyExit.cs View File

@@ -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


+ 2
- 2
logic/GameClass/GameObj/Map/Map.cs View File

@@ -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
{


+ 14
- 35
logic/GameClass/GameObj/Moveable.cs View File

@@ -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>
/// 复活时数据重置


+ 2
- 2
logic/GameClass/GameObj/Prop/Gadget.cs View File

@@ -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


+ 2
- 2
logic/GameClass/GameObj/Prop/Item.cs View File

@@ -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);
}
}



+ 4
- 4
logic/GameEngine/MoveEngine.cs View File

@@ -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);
}
}


+ 21
- 34
logic/Gaming/ActionManager.cs View File

@@ -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();
}
}


+ 4
- 8
logic/Gaming/AttackManager.cs View File

@@ -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;
}
}


+ 7
- 17
logic/Gaming/CharacterManager.cs View File

@@ -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);


+ 2
- 2
logic/Gaming/Game.cs View File

@@ -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();


+ 4
- 4
logic/Gaming/PropManager.cs View File

@@ -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;



+ 4
- 4
logic/Gaming/SkillManager/SkillManager.ActiveSkill.cs View File

@@ -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;
}


+ 2
- 2
logic/Gaming/SkillManager/SkillManager.PassiveSkill.cs View File

@@ -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,


+ 2
- 1
logic/Preparation/Interface/ICharacter.cs View File

@@ -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; }


+ 1
- 2
logic/Preparation/Interface/IGameObj.cs View File

@@ -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); // 忽略碰撞,在具体类中实现
}


+ 4
- 4
logic/Preparation/Interface/IMoveable.cs View File

@@ -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)


+ 1
- 6
logic/Preparation/Interface/ISkill.cs View File

@@ -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


+ 1
- 1
logic/Preparation/Preparation.csproj View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.1" />
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
</ItemGroup>

<ItemGroup>


+ 99
- 0
logic/Preparation/Utility/SafeValue.cs View File

@@ -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);
}
//增加新的写操作可能导致不安全
}
}

+ 1
- 2
logic/Preparation/Utility/XY.cs View File

@@ -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()
{


+ 1
- 1
logic/Server/CopyInfo.cs View File

@@ -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
}


+ 4
- 4
logic/Server/RpcServices.cs View File

@@ -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)


+ 1
- 1
logic/Server/Server.csproj View File

@@ -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">


+ 1
- 1
playback/Playback/Playback.csproj View File

@@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.1" />
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
</ItemGroup>

<ItemGroup>


+ 3656
- 0
players/-/player1.cpp
File diff suppressed because it is too large
View File


+ 3656
- 0
players/-/player2.cpp
File diff suppressed because it is too large
View File


+ 3656
- 0
players/-/player3.cpp
File diff suppressed because it is too large
View File


+ 3656
- 0
players/-/player4.cpp
File diff suppressed because it is too large
View File


+ 3656
- 0
players/-/player5.cpp
File diff suppressed because it is too large
View File


+ 1
- 0
players/.gitattributes View File

@@ -0,0 +1 @@
* linguist-vendored

+ 222761
- 0
players/ChatGPA/player1.cpp
File diff suppressed because it is too large
View File


+ 1575
- 0
players/ChatGPA/player2.cpp
File diff suppressed because it is too large
View File


+ 222762
- 0
players/ChatGPA/player3.cpp
File diff suppressed because it is too large
View File


+ 222762
- 0
players/ChatGPA/player4.cpp
File diff suppressed because it is too large
View File


+ 995
- 0
players/ChatGPA/player5.cpp View File

@@ -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++;
}
}

+ 3757
- 0
players/LQ说什么都队/player1.cpp
File diff suppressed because it is too large
View File


+ 3757
- 0
players/LQ说什么都队/player2.cpp
File diff suppressed because it is too large
View File


+ 3757
- 0
players/LQ说什么都队/player3.cpp
File diff suppressed because it is too large
View File


+ 3757
- 0
players/LQ说什么都队/player4.cpp
File diff suppressed because it is too large
View File


+ 3757
- 0
players/LQ说什么都队/player5.cpp
File diff suppressed because it is too large
View File


+ 1255
- 0
players/Mukava Poikaa/player1.cpp
File diff suppressed because it is too large
View File


+ 1255
- 0
players/Mukava Poikaa/player2.cpp
File diff suppressed because it is too large
View File


+ 1255
- 0
players/Mukava Poikaa/player3.cpp
File diff suppressed because it is too large
View File


+ 1255
- 0
players/Mukava Poikaa/player4.cpp
File diff suppressed because it is too large
View File


+ 1255
- 0
players/Mukava Poikaa/player5.cpp
File diff suppressed because it is too large
View File


+ 2902
- 0
players/N-A/player1.cpp
File diff suppressed because it is too large
View File


+ 2902
- 0
players/N-A/player2.cpp
File diff suppressed because it is too large
View File


+ 2902
- 0
players/N-A/player3.cpp
File diff suppressed because it is too large
View File


+ 2902
- 0
players/N-A/player4.cpp
File diff suppressed because it is too large
View File


+ 2902
- 0
players/N-A/player5.cpp
File diff suppressed because it is too large
View File


+ 2477
- 0
players/PKT48TeamTS/player1.cpp
File diff suppressed because it is too large
View File


+ 2477
- 0
players/PKT48TeamTS/player2.cpp
File diff suppressed because it is too large
View File


+ 2477
- 0
players/PKT48TeamTS/player3.cpp
File diff suppressed because it is too large
View File


+ 2477
- 0
players/PKT48TeamTS/player4.cpp
File diff suppressed because it is too large
View File


+ 2477
- 0
players/PKT48TeamTS/player5.cpp
File diff suppressed because it is too large
View File


+ 1014
- 0
players/closeAI/player1.cpp
File diff suppressed because it is too large
View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save