Skip to content

Commit 38eca76

Browse files
committed
[feature] Add geometry to config and use it to calculate placement pos
1 parent 5018250 commit 38eca76

File tree

6 files changed

+153
-43
lines changed

6 files changed

+153
-43
lines changed

config/ssl-game-controller.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,23 @@ game:
2222
timeout-duration: 5m
2323
timeouts: 2
2424
break-after: 2m
25-
team-choice-timeout: 200ms
25+
team-choice-timeout: 200ms
26+
default-geometry:
27+
DivA:
28+
field-length: 12.0
29+
field-width: 9.0
30+
penalty-area-depth: 1.2
31+
penalty-area-width: 2.4
32+
placement-offset-touch-line: 0.2
33+
placement-offset-goal-line: 0.2
34+
placement-offset-goal-line-goal-kick: 1.0
35+
placement-offset-defense-area: 1.0
36+
DivB:
37+
field-length: 9.0
38+
field-width: 6.0
39+
penalty-area-depth: 1.0
40+
penalty-area-width: 2.0
41+
placement-offset-touch-line: 0.2
42+
placement-offset-goal-line: 0.2
43+
placement-offset-goal-line-goal-kick: 1.0
44+
placement-offset-defense-area: 1.0

internal/app/controller/config.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,25 @@ type ConfigSpecial struct {
1616
BreakAfter time.Duration `yaml:"break-after"`
1717
}
1818

19+
type ConfigGeometry struct {
20+
FieldLength float64 `yaml:"field-length"`
21+
FieldWidth float64 `yaml:"field-width"`
22+
PenaltyAreaDepth float64 `yaml:"penalty-area-depth"`
23+
PenaltyAreaWidth float64 `yaml:"penalty-area-width"`
24+
PlacementOffsetTouchLine float64 `yaml:"placement-offset-touch-line"`
25+
PlacementOffsetGoalLine float64 `yaml:"placement-offset-goal-line"`
26+
PlacementOffsetGoalLineGoalKick float64 `yaml:"placement-offset-goal-line-goal-kick"`
27+
PlacementOffsetDefenseArea float64 `yaml:"placement-offset-defense-area"`
28+
}
29+
1930
// ConfigGame holds configs that are valid for the whole game
2031
type ConfigGame struct {
21-
YellowCardDuration time.Duration `yaml:"yellow-card-duration"`
22-
DefaultDivision Division `yaml:"default-division"`
23-
Normal ConfigSpecial `yaml:"normal"`
24-
Overtime ConfigSpecial `yaml:"overtime"`
25-
TeamChoiceTimeout time.Duration `yaml:"team-choice-timeout"`
32+
YellowCardDuration time.Duration `yaml:"yellow-card-duration"`
33+
DefaultDivision Division `yaml:"default-division"`
34+
Normal ConfigSpecial `yaml:"normal"`
35+
Overtime ConfigSpecial `yaml:"overtime"`
36+
TeamChoiceTimeout time.Duration `yaml:"team-choice-timeout"`
37+
DefaultGeometry map[Division]*ConfigGeometry `yaml:"default-geometry"`
2638
}
2739

2840
// ConfigPublish holds configs for publishing the state and commands to the teams
@@ -102,6 +114,27 @@ func DefaultConfig() (c Config) {
102114
c.Server.Team.Address = ":10008"
103115
c.Server.AutoRef.TrustedKeysDir = "config/trusted_keys/team"
104116

117+
c.Game.DefaultGeometry = map[Division]*ConfigGeometry{}
118+
c.Game.DefaultGeometry[DivA] = new(ConfigGeometry)
119+
c.Game.DefaultGeometry[DivA].FieldLength = 12
120+
c.Game.DefaultGeometry[DivA].FieldWidth = 9
121+
c.Game.DefaultGeometry[DivA].PenaltyAreaDepth = 1.2
122+
c.Game.DefaultGeometry[DivA].PenaltyAreaWidth = 2.4
123+
c.Game.DefaultGeometry[DivA].PlacementOffsetGoalLine = 0.2
124+
c.Game.DefaultGeometry[DivA].PlacementOffsetGoalLineGoalKick = 1.0
125+
c.Game.DefaultGeometry[DivA].PlacementOffsetTouchLine = 0.2
126+
c.Game.DefaultGeometry[DivA].PlacementOffsetDefenseArea = 1.0
127+
128+
c.Game.DefaultGeometry[DivB] = new(ConfigGeometry)
129+
c.Game.DefaultGeometry[DivB].FieldLength = 9
130+
c.Game.DefaultGeometry[DivB].FieldWidth = 6
131+
c.Game.DefaultGeometry[DivB].PenaltyAreaDepth = 1
132+
c.Game.DefaultGeometry[DivB].PenaltyAreaWidth = 2
133+
c.Game.DefaultGeometry[DivB].PlacementOffsetGoalLine = 0.2
134+
c.Game.DefaultGeometry[DivB].PlacementOffsetGoalLineGoalKick = 1.0
135+
c.Game.DefaultGeometry[DivB].PlacementOffsetTouchLine = 0.2
136+
c.Game.DefaultGeometry[DivB].PlacementOffsetDefenseArea = 1.0
137+
105138
return
106139
}
107140

internal/app/controller/engine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Engine struct {
1818
config ConfigGame
1919
TimeProvider func() time.Time
2020
History History
21+
Geometry ConfigGeometry
2122
}
2223

2324
func NewEngine(config ConfigGame) (e Engine) {
@@ -36,6 +37,7 @@ func (e *Engine) ResetGame() {
3637
e.State.TeamState[TeamYellow].TimeoutsLeft = e.config.Normal.Timeouts
3738
e.RefereeEvents = []RefereeEvent{}
3839
e.State.Division = e.config.DefaultDivision
40+
e.Geometry = *e.config.DefaultGeometry[e.State.Division]
3941
}
4042

4143
// Tick updates the times of the state and removes cards, if necessary

internal/app/controller/placementPos.go

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controller
33
import (
44
"github.com/RoboCup-SSL/ssl-game-controller/pkg/refproto"
55
"log"
6+
"math"
67
)
78

89
// BallPlacementPos determines the ball placement position based on the game event
@@ -17,81 +18,134 @@ func (e *Engine) BallPlacementPos() *Location {
1718

1819
switch event.Type {
1920
case GameEventBallLeftFieldTouchLine:
20-
return validateProtoLocation(event.Details.BallLeftFieldTouchLine.Location)
21+
return e.validateProtoLocation(event.Details.BallLeftFieldTouchLine.Location)
2122
case GameEventBallLeftFieldGoalLine:
22-
// TODO corner
23-
return validateProtoLocation(event.Details.BallLeftFieldGoalLine.Location)
23+
if event.Details.BallLeftFieldGoalLine.Location != nil && e.isGoalKick(event) {
24+
location := mapProtoLocation(event.Details.BallLeftFieldGoalLine.Location)
25+
maxX := e.Geometry.FieldLength/2 - e.Geometry.PlacementOffsetGoalLineGoalKick
26+
if math.Abs(location.X) > maxX {
27+
location.X = math.Copysign(maxX, location.X)
28+
}
29+
return e.validateLocation(location)
30+
}
31+
return e.validateProtoLocation(event.Details.BallLeftFieldGoalLine.Location)
2432
case GameEventIcing:
25-
return validateProtoLocation(event.Details.Icing.KickLocation)
33+
return e.validateProtoLocation(event.Details.Icing.KickLocation)
2634
case GameEventGoal:
2735
return &Location{X: 0.0, Y: 0.0}
2836
case GameEventIndirectGoal:
29-
return validateProtoLocation(event.Details.IndirectGoal.KickLocation)
37+
return e.validateProtoLocation(event.Details.IndirectGoal.KickLocation)
3038
case GameEventChippedGoal:
31-
return validateProtoLocation(event.Details.ChippedGoal.KickLocation)
39+
return e.validateProtoLocation(event.Details.ChippedGoal.KickLocation)
3240
case GameEventBotTippedOver:
33-
return validateProtoLocation(event.Details.BotTippedOver.Location)
41+
return e.validateProtoLocation(event.Details.BotTippedOver.Location)
3442
case GameEventBotInterferedPlacement:
35-
return validateLocation(e.State.PlacementPos)
43+
return e.validateLocation(e.State.PlacementPos)
3644
case GameEventBotCrashDrawn:
37-
return validateProtoLocation(event.Details.BotCrashDrawn.Location)
45+
return e.validateProtoLocation(event.Details.BotCrashDrawn.Location)
3846
case GameEventBotKickedBallTooFast:
39-
return validateProtoLocation(event.Details.BotKickedBallTooFast.Location)
47+
return e.validateProtoLocation(event.Details.BotKickedBallTooFast.Location)
4048
case GameEventBotDribbledBallTooFar:
41-
return validateProtoLocation(event.Details.BotDribbledBallTooFar.Start)
49+
return e.validateProtoLocation(event.Details.BotDribbledBallTooFar.Start)
4250
case GameEventBotCrashUnique:
43-
return validateProtoLocation(event.Details.BotCrashUnique.Location)
51+
return e.validateProtoLocation(event.Details.BotCrashUnique.Location)
4452
case GameEventBotCrashUniqueContinue:
45-
return validateProtoLocation(event.Details.BotCrashUniqueContinue.Location)
53+
return e.validateProtoLocation(event.Details.BotCrashUniqueContinue.Location)
4654
case GameEventBotPushedBot:
47-
return validateProtoLocation(event.Details.BotPushedBot.Location)
55+
return e.validateProtoLocation(event.Details.BotPushedBot.Location)
4856
case GameEventBotPushedBotContinue:
49-
return validateProtoLocation(event.Details.BotPushedBotContinue.Location)
57+
return e.validateProtoLocation(event.Details.BotPushedBotContinue.Location)
5058
case GameEventBotHeldBallDeliberately:
51-
return validateProtoLocation(event.Details.BotHeldBallDeliberately.Location)
59+
return e.validateProtoLocation(event.Details.BotHeldBallDeliberately.Location)
5260
case GameEventAttackerDoubleTouchedBall:
53-
return validateProtoLocation(event.Details.AttackerDoubleTouchedBall.Location)
61+
return e.validateProtoLocation(event.Details.AttackerDoubleTouchedBall.Location)
5462
case GameEventAttackerTooCloseToDefenseArea:
55-
return validateProtoLocation(event.Details.AttackerTooCloseToDefenseArea.Location)
63+
return e.validateProtoLocation(event.Details.AttackerTooCloseToDefenseArea.Location)
5664
case GameEventAttackerInDefenseArea:
57-
return validateProtoLocation(event.Details.AttackerInDefenseArea.Location)
65+
return e.validateProtoLocation(event.Details.AttackerInDefenseArea.Location)
5866
case GameEventAttackerTouchedKeeper:
59-
return validateProtoLocation(event.Details.AttackerTouchedKeeper.Location)
67+
return e.validateProtoLocation(event.Details.AttackerTouchedKeeper.Location)
6068
case GameEventDefenderTooCloseToKickPoint:
61-
return validateLocation(e.State.PlacementPos)
69+
return e.validateLocation(e.State.PlacementPos)
6270
case GameEventDefenderInDefenseAreaPartially:
63-
return validateProtoLocation(event.Details.DefenderInDefenseAreaPartially.Location)
71+
return e.validateProtoLocation(event.Details.DefenderInDefenseAreaPartially.Location)
6472
case GameEventDefenderInDefenseArea:
65-
// TODO penalty mark
66-
return nil
73+
teamInFavor := event.ByTeam().Opposite()
74+
location := Location{}
75+
location.X = (e.Geometry.FieldLength / 2.0) - e.Geometry.PenaltyAreaDepth
76+
if e.State.TeamState[teamInFavor].OnPositiveHalf {
77+
location.X *= -1
78+
}
79+
return &location
6780
case GameEventKeeperHeldBall:
68-
return validateProtoLocation(event.Details.KeeperHeldBall.Location)
81+
return e.validateProtoLocation(event.Details.KeeperHeldBall.Location)
6982
case GameEventKickTimeout:
70-
return validateLocation(e.State.PlacementPos)
83+
return e.validateLocation(e.State.PlacementPos)
7184
case GameEventNoProgressInGame:
72-
return validateProtoLocation(event.Details.NoProgressInGame.Location)
85+
return e.validateProtoLocation(event.Details.NoProgressInGame.Location)
7386
case GameEventPlacementFailedByTeamInFavor:
74-
return validateLocation(e.State.PlacementPos)
87+
return e.validateLocation(e.State.PlacementPos)
7588
case GameEventPlacementFailedByOpponent:
76-
return validateLocation(e.State.PlacementPos)
89+
return e.validateLocation(e.State.PlacementPos)
7790
default:
7891
log.Print("Warn: Unknown game event: ", event.Type)
7992
return nil
8093
}
8194
}
8295

83-
func validateProtoLocation(location *refproto.Location) *Location {
96+
func (e *Engine) isGoalKick(event *GameEvent) bool {
97+
teamInFavor := event.ByTeam().Opposite()
98+
location := mapProtoLocation(event.Details.BallLeftFieldGoalLine.Location)
99+
if e.State.TeamState[teamInFavor].OnPositiveHalf && location.X > 0 {
100+
return true
101+
}
102+
if !e.State.TeamState[teamInFavor].OnPositiveHalf && location.X < 0 {
103+
return true
104+
}
105+
return false
106+
}
107+
108+
func mapProtoLocation(location *refproto.Location) *Location {
109+
return &Location{X: float64(*location.X), Y: float64(*location.Y)}
110+
}
111+
112+
func (e *Engine) validateProtoLocation(location *refproto.Location) *Location {
84113
if location == nil {
85114
return nil
86115
}
87-
return validateLocation(&Location{X: *location.X, Y: *location.Y})
116+
return e.validateLocation(mapProtoLocation(location))
88117
}
89118

90-
func validateLocation(location *Location) *Location {
119+
func (e *Engine) validateLocation(location *Location) *Location {
91120
if location == nil {
92121
return nil
93122
}
94-
// TODO move inside field
95-
// TODO move away from defense area
123+
124+
e.movePositionInsideField(location)
125+
e.movePositionOutOfDefenseArea(location)
126+
96127
return location
97128
}
129+
130+
func (e *Engine) movePositionOutOfDefenseArea(location *Location) {
131+
maxX := e.Geometry.FieldLength/2 - e.Geometry.PenaltyAreaDepth - e.Geometry.PlacementOffsetDefenseArea
132+
minY := e.Geometry.PenaltyAreaWidth + e.Geometry.PlacementOffsetDefenseArea
133+
if math.Abs(location.X) > maxX && math.Abs(location.Y) < minY {
134+
if math.Abs(maxX-math.Abs(location.X)) < math.Abs(minY-math.Abs(location.Y)) {
135+
location.X = math.Copysign(maxX, location.X)
136+
} else {
137+
location.Y = math.Copysign(minY, location.Y)
138+
}
139+
}
140+
}
141+
142+
func (e *Engine) movePositionInsideField(location *Location) {
143+
maxX := e.Geometry.FieldLength/2 - e.Geometry.PlacementOffsetGoalLine
144+
if math.Abs(location.X) > maxX {
145+
location.X = math.Copysign(maxX, location.X)
146+
}
147+
maxY := e.Geometry.FieldWidth/2 - e.Geometry.PlacementOffsetTouchLine
148+
if math.Abs(location.Y) > maxY {
149+
location.Y = math.Copysign(maxY, location.Y)
150+
}
151+
}

internal/app/controller/publisher.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,5 +253,7 @@ func mapLocation(location *Location) *refproto.Referee_Point {
253253
if location == nil {
254254
return nil
255255
}
256-
return &refproto.Referee_Point{X: &location.X, Y: &location.Y}
256+
x := float32(location.X)
257+
y := float32(location.Y)
258+
return &refproto.Referee_Point{X: &x, Y: &y}
257259
}

internal/app/controller/state.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,6 @@ func (s *State) TeamByName(teamName string) Team {
332332

333333
// Location is a two-dimensional coordinate
334334
type Location struct {
335-
X float32
336-
Y float32
335+
X float64
336+
Y float64
337337
}

0 commit comments

Comments
 (0)