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