Skip to content

Commit 9ba8658

Browse files
committed
Feature: Implement majority mechanism again
1 parent e0db2d9 commit 9ba8658

27 files changed

+1535
-329
lines changed

config/ssl-game-controller.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ game:
2323
multiple-card-step: 2
2424
multiple-foul-step: 3
2525
multiple-placement-failures: 5
26-
auto-ref-proposal-timeout: 5s
26+
auto-ref-proposal-timeout: 1s
2727
default-division: DIV_A
2828
prepare-timeout: 10s
2929
free-kick-timeout:

internal/app/api/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func stateChanged(s1, s2 *state.State) bool {
284284
if !reflect.DeepEqual(s1.GameEvents, s2.GameEvents) {
285285
return true
286286
}
287-
if !reflect.DeepEqual(s1.ProposedGameEvents, s2.ProposedGameEvents) {
287+
if !reflect.DeepEqual(s1.GameEventProposals, s2.GameEventProposals) {
288288
return true
289289
}
290290
if *s1.Division != *s2.Division {

internal/app/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func DefaultControllerConfig() (c Controller) {
164164
c.Game.MultipleCardStep = 2
165165
c.Game.MultipleFoulStep = 3
166166
c.Game.MultiplePlacementFailures = 5
167-
c.Game.AutoRefProposalTimeout = 5 * time.Second
167+
c.Game.AutoRefProposalTimeout = 1 * time.Second
168168
c.Game.PrepareTimeout = time.Second * 10
169169
c.Game.FreeKickTimeout = map[Division]time.Duration{DivA: time.Second * 5, DivB: time.Second * 10}
170170
c.Game.NoProgressTimeout = map[Division]time.Duration{DivA: time.Second * 5, DivB: time.Second * 10}

internal/app/config/testdata/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ game:
2323
multiple-card-step: 2
2424
multiple-foul-step: 3
2525
multiple-placement-failures: 5
26-
auto-ref-proposal-timeout: 5s
26+
auto-ref-proposal-timeout: 1s
2727
default-division: DIV_A
2828
prepare-timeout: 10s
2929
free-kick-timeout:

internal/app/engine/engine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ func (e *Engine) GetConfig() *Config {
308308

309309
// UpdateConfig updates the current engine config with the given delta
310310
func (e *Engine) UpdateConfig(delta *Config) {
311+
e.mutex.Lock()
312+
defer e.mutex.Unlock()
311313
proto.Merge(&e.config, delta)
312314
log.Printf("Engine config updated to %v", e.config)
313315
if err := e.config.WriteTo(e.engineConfig.ConfigFilename); err != nil {
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package engine
2+
3+
import (
4+
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/geom"
5+
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"
6+
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/statemachine"
7+
"log"
8+
"math"
9+
"sort"
10+
"time"
11+
)
12+
13+
const similarLocationTolerance = 0.5
14+
15+
func (e *Engine) processProposals() {
16+
17+
deadline := e.timeProvider().Add(-e.gameConfig.AutoRefProposalTimeout)
18+
matchingProposals := collectMatchingProposals(e.currentState.GameEventProposals, deadline)
19+
numProposals := len(matchingProposals)
20+
if numProposals == 0 {
21+
return
22+
}
23+
24+
sort.Slice(matchingProposals, func(i, j int) bool {
25+
return goTime(matchingProposals[i].Timestamp).Before(goTime(matchingProposals[j].Timestamp))
26+
})
27+
28+
firstGameEvent := matchingProposals[0].GameEvent
29+
numAutoRefs := e.numAutoRefs(firstGameEvent)
30+
majority := int(math.Floor(float64(numAutoRefs) / 2.0))
31+
32+
if numProposals > majority {
33+
log.Printf("Majority (%v > %v) reached for %v.", numProposals, majority, firstGameEvent.Type)
34+
e.Enqueue(&statemachine.Change{
35+
Change: &statemachine.Change_AcceptGameEventProposals{
36+
AcceptGameEventProposals: &statemachine.AcceptGameEventProposals{
37+
Proposals: matchingProposals,
38+
},
39+
},
40+
})
41+
}
42+
}
43+
44+
func (e *Engine) numAutoRefs(gameEvent *state.GameEvent) (n int) {
45+
autoRefs := make([]string, 0, len(e.gcState.AutoRefState))
46+
for autoRef := range e.gcState.AutoRefState {
47+
autoRefs = append(autoRefs, autoRef)
48+
}
49+
for _, autoRef := range autoRefs {
50+
if e.config.AutoRefConfigs[autoRef].GameEventEnabled[gameEvent.Type.String()] {
51+
n++
52+
}
53+
}
54+
return
55+
}
56+
57+
func collectMatchingProposals(events []*state.GameEventProposal, deadline time.Time) []*state.GameEventProposal {
58+
proposals := [][]*state.GameEventProposal{}
59+
for _, proposal := range events {
60+
if (proposal.Accepted != nil && *proposal.Accepted) ||
61+
goTime(proposal.Timestamp).After(deadline) {
62+
continue
63+
}
64+
if ok, pid := findGroup(proposal, proposals); ok {
65+
proposals[pid] = append(proposals[pid], proposal)
66+
continue
67+
}
68+
proposals = append(proposals, []*state.GameEventProposal{proposal})
69+
}
70+
71+
if len(proposals) == 0 {
72+
return []*state.GameEventProposal{}
73+
}
74+
75+
sort.SliceStable(proposals, func(i, j int) bool {
76+
return len(proposals[i]) > len(proposals[j])
77+
})
78+
79+
return proposals[0]
80+
}
81+
82+
func findGroup(proposal *state.GameEventProposal, proposals [][]*state.GameEventProposal) (bool, int) {
83+
for i, p := range proposals {
84+
if gameEventsSimilar(proposal.GameEvent, p[0].GameEvent) {
85+
return true, i
86+
}
87+
}
88+
return false, -1
89+
}
90+
91+
func gameEventsSimilar(e1, e2 *state.GameEvent) bool {
92+
if *e1.Type != *e2.Type {
93+
return false
94+
}
95+
// check similarity based on details for some events:
96+
switch *e1.Type {
97+
case state.GameEvent_BOT_TOO_FAST_IN_STOP:
98+
return similarTeam(e1.GetBotTooFastInStop().ByTeam, e2.GetBotTooFastInStop().ByTeam) &&
99+
similarBot(e1.GetBotTooFastInStop().ByBot, e2.GetBotTooFastInStop().ByBot)
100+
case state.GameEvent_DEFENDER_TOO_CLOSE_TO_KICK_POINT:
101+
return similarTeam(e1.GetDefenderTooCloseToKickPoint().ByTeam, e2.GetDefenderTooCloseToKickPoint().ByTeam) &&
102+
similarBot(e1.GetDefenderTooCloseToKickPoint().ByBot, e2.GetDefenderTooCloseToKickPoint().ByBot)
103+
case state.GameEvent_BOT_CRASH_DRAWN:
104+
return similarBot(e1.GetBotCrashDrawn().BotYellow, e2.GetBotCrashDrawn().BotYellow) &&
105+
similarBot(e1.GetBotCrashDrawn().BotBlue, e2.GetBotCrashDrawn().BotBlue) &&
106+
similarLocation(e1.GetBotCrashDrawn().Location, e2.GetBotCrashDrawn().Location, similarLocationTolerance)
107+
case state.GameEvent_BOT_CRASH_UNIQUE:
108+
return similarTeam(e1.GetBotCrashUnique().ByTeam, e2.GetBotCrashUnique().ByTeam) &&
109+
similarBot(e1.GetBotCrashUnique().Violator, e2.GetBotCrashUnique().Violator) &&
110+
similarBot(e1.GetBotCrashUnique().Victim, e2.GetBotCrashUnique().Victim) &&
111+
similarLocation(e1.GetBotCrashUnique().Location, e2.GetBotCrashUnique().Location, similarLocationTolerance)
112+
case state.GameEvent_BOT_PUSHED_BOT:
113+
return similarTeam(e1.GetBotPushedBot().ByTeam, e2.GetBotPushedBot().ByTeam) &&
114+
similarBot(e1.GetBotPushedBot().Violator, e2.GetBotPushedBot().Violator) &&
115+
similarBot(e1.GetBotPushedBot().Victim, e2.GetBotPushedBot().Victim) &&
116+
similarLocation(e1.GetBotPushedBot().Location, e2.GetBotPushedBot().Location, similarLocationTolerance)
117+
case state.GameEvent_DEFENDER_IN_DEFENSE_AREA:
118+
return similarTeam(e1.GetDefenderInDefenseArea().ByTeam, e2.GetDefenderInDefenseArea().ByTeam) &&
119+
similarBot(e1.GetDefenderInDefenseArea().ByBot, e2.GetDefenderInDefenseArea().ByBot)
120+
case state.GameEvent_ATTACKER_TOUCHED_BALL_IN_DEFENSE_AREA:
121+
return similarTeam(e1.GetAttackerTouchedBallInDefenseArea().ByTeam, e2.GetAttackerTouchedBallInDefenseArea().ByTeam) &&
122+
similarBot(e1.GetAttackerTouchedBallInDefenseArea().ByBot, e2.GetAttackerTouchedBallInDefenseArea().ByBot)
123+
case state.GameEvent_BOT_KICKED_BALL_TOO_FAST:
124+
return similarTeam(e1.GetBotKickedBallTooFast().ByTeam, e2.GetBotKickedBallTooFast().ByTeam)
125+
case state.GameEvent_BOT_DRIBBLED_BALL_TOO_FAR:
126+
return similarTeam(e1.GetBotDribbledBallTooFar().ByTeam, e2.GetBotDribbledBallTooFar().ByTeam)
127+
case state.GameEvent_ATTACKER_DOUBLE_TOUCHED_BALL:
128+
return similarTeam(e1.GetAttackerDoubleTouchedBall().ByTeam, e2.GetAttackerDoubleTouchedBall().ByTeam)
129+
case state.GameEvent_ATTACKER_TOO_CLOSE_TO_DEFENSE_AREA:
130+
return similarTeam(e1.GetAttackerTooCloseToDefenseArea().ByTeam, e2.GetAttackerTooCloseToDefenseArea().ByTeam) &&
131+
similarBot(e1.GetAttackerTooCloseToDefenseArea().ByBot, e2.GetAttackerTooCloseToDefenseArea().ByBot)
132+
case state.GameEvent_BOT_INTERFERED_PLACEMENT:
133+
return similarTeam(e1.GetBotInterferedPlacement().ByTeam, e2.GetBotInterferedPlacement().ByTeam) &&
134+
similarBot(e1.GetBotInterferedPlacement().ByBot, e2.GetBotInterferedPlacement().ByBot)
135+
}
136+
137+
// all others are considered similar based on the type only
138+
return true
139+
}
140+
141+
func similarTeam(t1, t2 *state.Team) bool {
142+
return (t1 == nil && t2 == nil) || (t1 != nil && t2 != nil && *t1 == *t2)
143+
}
144+
145+
func similarBot(b1, b2 *uint32) bool {
146+
return (b1 == nil && b2 == nil) || (b1 != nil && b2 != nil && *b1 == *b2)
147+
}
148+
149+
func similarLocation(l1, l2 *geom.Vector2, tolerance float64) bool {
150+
return (l1 == nil && l2 == nil) || (l1 != nil && l2 != nil && l1.DistanceTo(l2) <= tolerance)
151+
}

0 commit comments

Comments
 (0)