Skip to content

Commit 7fd47ef

Browse files
committed
Implement punishment for excessive bot substitution
See: https://github.com/RoboCup-SSL/ssl-rules/pull/78/files
1 parent 83af744 commit 7fd47ef

27 files changed

+1192
-840
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script setup lang="ts">
2+
import {computed, inject} from "vue";
3+
import NumberInput from "@/components/common/NumberInput.vue";
4+
import {useMatchStateStore} from "@/store/matchState";
5+
import type {Team} from "@/proto/ssl_gc_common";
6+
import type {ControlApi} from "@/providers/controlApi";
7+
8+
const props = defineProps<{
9+
team: Team,
10+
}>()
11+
12+
const store = useMatchStateStore()
13+
const control = inject<ControlApi>('control-api')
14+
15+
const model = computed(() => {
16+
return store.matchState.teamState![props.team].botSubstitutionsLeft!
17+
})
18+
19+
const updateValue = (value: number | undefined) => {
20+
if (value !== undefined) {
21+
control?.UpdateTeamState({
22+
forTeam: props.team,
23+
botSubstitutionsLeft: value,
24+
})
25+
}
26+
}
27+
28+
</script>
29+
30+
<template>
31+
<NumberInput
32+
:modelValue="model"
33+
label="Bot substitutions left"
34+
@update:model-value="updateValue"
35+
/>
36+
</template>

frontend/src/helpers/texts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export const gameEventNames = new Map<GameEvent_Type, string>([
119119
[GameEvent_Type.MULTIPLE_CARDS, "Multiple cards"],
120120
[GameEvent_Type.MULTIPLE_FOULS, "Multiple fouls"],
121121
[GameEvent_Type.BOT_SUBSTITUTION, "Bot substitution"],
122+
[GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION, "Excessive bot substitution"],
122123
[GameEvent_Type.TOO_MANY_ROBOTS, "Too many bots on field"],
123124
[GameEvent_Type.CHALLENGE_FLAG, "Challenge flag"],
124125
[GameEvent_Type.CHALLENGE_FLAG_HANDLED, "Challenge flag handled"],

frontend/src/proto/ssl_gc_change.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ export interface Change_UpdateTeamState {
150150
canPlaceBall?: boolean;
151151
/** The number of challenge flags that the team has left */
152152
challengeFlagsLeft?: number;
153+
/** The number of bot substitutions left by the team in this halftime */
154+
botSubstitutionsLeft?: number;
153155
/** Does the team want to substitute a robot in the next possible situation? */
154156
requestsBotSubstitution?: boolean;
155157
/** Does the team want to take a timeout in the next possible situation? */
@@ -508,6 +510,7 @@ export const Change_UpdateTeamState = {
508510
ballPlacementFailures: isSet(object.ballPlacementFailures) ? Number(object.ballPlacementFailures) : undefined,
509511
canPlaceBall: isSet(object.canPlaceBall) ? Boolean(object.canPlaceBall) : undefined,
510512
challengeFlagsLeft: isSet(object.challengeFlagsLeft) ? Number(object.challengeFlagsLeft) : undefined,
513+
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : undefined,
511514
requestsBotSubstitution: isSet(object.requestsBotSubstitution)
512515
? Boolean(object.requestsBotSubstitution)
513516
: undefined,
@@ -535,6 +538,7 @@ export const Change_UpdateTeamState = {
535538
message.ballPlacementFailures !== undefined && (obj.ballPlacementFailures = message.ballPlacementFailures);
536539
message.canPlaceBall !== undefined && (obj.canPlaceBall = message.canPlaceBall);
537540
message.challengeFlagsLeft !== undefined && (obj.challengeFlagsLeft = message.challengeFlagsLeft);
541+
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = message.botSubstitutionsLeft);
538542
message.requestsBotSubstitution !== undefined && (obj.requestsBotSubstitution = message.requestsBotSubstitution);
539543
message.requestsTimeout !== undefined && (obj.requestsTimeout = message.requestsTimeout);
540544
message.requestsChallenge !== undefined && (obj.requestsChallenge = message.requestsChallenge);

frontend/src/proto/ssl_gc_game_event.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface GameEvent {
5858
| { $case: "multipleCards"; multipleCards: GameEvent_MultipleCards }
5959
| { $case: "multipleFouls"; multipleFouls: GameEvent_MultipleFouls }
6060
| { $case: "botSubstitution"; botSubstitution: GameEvent_BotSubstitution }
61+
| { $case: "excessiveBotSubstitution"; excessiveBotSubstitution: GameEvent_ExcessiveBotSubstitution }
6162
| { $case: "tooManyRobots"; tooManyRobots: GameEvent_TooManyRobots }
6263
| { $case: "challengeFlag"; challengeFlag: GameEvent_ChallengeFlag }
6364
| { $case: "challengeFlagHandled"; challengeFlagHandled: GameEvent_ChallengeFlagHandled }
@@ -125,6 +126,8 @@ export enum GameEvent_Type {
125126
BOT_TOO_FAST_IN_STOP = "BOT_TOO_FAST_IN_STOP",
126127
/** BOT_INTERFERED_PLACEMENT - triggered by autoRef */
127128
BOT_INTERFERED_PLACEMENT = "BOT_INTERFERED_PLACEMENT",
129+
/** EXCESSIVE_BOT_SUBSTITUTION - triggered by GC */
130+
EXCESSIVE_BOT_SUBSTITUTION = "EXCESSIVE_BOT_SUBSTITUTION",
128131
/** POSSIBLE_GOAL - triggered by autoRef */
129132
POSSIBLE_GOAL = "POSSIBLE_GOAL",
130133
/** GOAL - triggered by GC */
@@ -244,6 +247,9 @@ export function gameEvent_TypeFromJSON(object: any): GameEvent_Type {
244247
case 20:
245248
case "BOT_INTERFERED_PLACEMENT":
246249
return GameEvent_Type.BOT_INTERFERED_PLACEMENT;
250+
case 48:
251+
case "EXCESSIVE_BOT_SUBSTITUTION":
252+
return GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION;
247253
case 39:
248254
case "POSSIBLE_GOAL":
249255
return GameEvent_Type.POSSIBLE_GOAL;
@@ -374,6 +380,8 @@ export function gameEvent_TypeToJSON(object: GameEvent_Type): string {
374380
return "BOT_TOO_FAST_IN_STOP";
375381
case GameEvent_Type.BOT_INTERFERED_PLACEMENT:
376382
return "BOT_INTERFERED_PLACEMENT";
383+
case GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION:
384+
return "EXCESSIVE_BOT_SUBSTITUTION";
377385
case GameEvent_Type.POSSIBLE_GOAL:
378386
return "POSSIBLE_GOAL";
379387
case GameEvent_Type.GOAL:
@@ -828,6 +836,12 @@ export interface GameEvent_BotSubstitution {
828836
byTeam?: Team;
829837
}
830838

839+
/** A foul for excessive bot substitutions */
840+
export interface GameEvent_ExcessiveBotSubstitution {
841+
/** the team that substitutes robots */
842+
byTeam?: Team;
843+
}
844+
831845
/** A challenge flag, requested by a team previously, is flagged */
832846
export interface GameEvent_ChallengeFlag {
833847
/** the team that requested the challenge flag */
@@ -990,6 +1004,11 @@ export const GameEvent = {
9901004
? { $case: "multipleFouls", multipleFouls: GameEvent_MultipleFouls.fromJSON(object.multipleFouls) }
9911005
: isSet(object.botSubstitution)
9921006
? { $case: "botSubstitution", botSubstitution: GameEvent_BotSubstitution.fromJSON(object.botSubstitution) }
1007+
: isSet(object.excessiveBotSubstitution)
1008+
? {
1009+
$case: "excessiveBotSubstitution",
1010+
excessiveBotSubstitution: GameEvent_ExcessiveBotSubstitution.fromJSON(object.excessiveBotSubstitution),
1011+
}
9931012
: isSet(object.tooManyRobots)
9941013
? { $case: "tooManyRobots", tooManyRobots: GameEvent_TooManyRobots.fromJSON(object.tooManyRobots) }
9951014
: isSet(object.challengeFlag)
@@ -1166,6 +1185,10 @@ export const GameEvent = {
11661185
message.event?.$case === "botSubstitution" && (obj.botSubstitution = message.event?.botSubstitution
11671186
? GameEvent_BotSubstitution.toJSON(message.event?.botSubstitution)
11681187
: undefined);
1188+
message.event?.$case === "excessiveBotSubstitution" &&
1189+
(obj.excessiveBotSubstitution = message.event?.excessiveBotSubstitution
1190+
? GameEvent_ExcessiveBotSubstitution.toJSON(message.event?.excessiveBotSubstitution)
1191+
: undefined);
11691192
message.event?.$case === "tooManyRobots" && (obj.tooManyRobots = message.event?.tooManyRobots
11701193
? GameEvent_TooManyRobots.toJSON(message.event?.tooManyRobots)
11711194
: undefined);
@@ -1890,6 +1913,18 @@ export const GameEvent_BotSubstitution = {
18901913
},
18911914
};
18921915

1916+
export const GameEvent_ExcessiveBotSubstitution = {
1917+
fromJSON(object: any): GameEvent_ExcessiveBotSubstitution {
1918+
return { byTeam: isSet(object.byTeam) ? teamFromJSON(object.byTeam) : Team.UNKNOWN };
1919+
},
1920+
1921+
toJSON(message: GameEvent_ExcessiveBotSubstitution): unknown {
1922+
const obj: any = {};
1923+
message.byTeam !== undefined && (obj.byTeam = teamToJSON(message.byTeam));
1924+
return obj;
1925+
},
1926+
};
1927+
18931928
export const GameEvent_ChallengeFlag = {
18941929
fromJSON(object: any): GameEvent_ChallengeFlag {
18951930
return { byTeam: isSet(object.byTeam) ? teamFromJSON(object.byTeam) : Team.UNKNOWN };

frontend/src/proto/ssl_gc_referee_message.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@ export interface Referee_TeamInfo {
465465
ballPlacementFailuresReached?: boolean;
466466
/** The team is allowed to substitute one or more robots currently */
467467
botSubstitutionAllowed?: boolean;
468+
/** The number of bot substitutions left by the team in this halftime */
469+
botSubstitutionsLeft?: number;
468470
}
469471

470472
/**
@@ -573,6 +575,7 @@ export const Referee_TeamInfo = {
573575
? Boolean(object.ballPlacementFailuresReached)
574576
: false,
575577
botSubstitutionAllowed: isSet(object.botSubstitutionAllowed) ? Boolean(object.botSubstitutionAllowed) : false,
578+
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : 0,
576579
};
577580
},
578581

@@ -599,6 +602,7 @@ export const Referee_TeamInfo = {
599602
message.ballPlacementFailuresReached !== undefined &&
600603
(obj.ballPlacementFailuresReached = message.ballPlacementFailuresReached);
601604
message.botSubstitutionAllowed !== undefined && (obj.botSubstitutionAllowed = message.botSubstitutionAllowed);
605+
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = Math.round(message.botSubstitutionsLeft));
602606
return obj;
603607
},
604608
};

frontend/src/proto/ssl_gc_state.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export interface TeamInfo {
231231
requestsEmergencyStopSince?: Date;
232232
challengeFlags?: number;
233233
botSubstitutionAllowed?: boolean;
234+
botSubstitutionsLeft?: number;
234235
}
235236

236237
export interface State {
@@ -427,6 +428,7 @@ export const TeamInfo = {
427428
: undefined,
428429
challengeFlags: isSet(object.challengeFlags) ? Number(object.challengeFlags) : 0,
429430
botSubstitutionAllowed: isSet(object.botSubstitutionAllowed) ? Boolean(object.botSubstitutionAllowed) : false,
431+
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : 0,
430432
};
431433
},
432434

@@ -468,6 +470,7 @@ export const TeamInfo = {
468470
(obj.requestsEmergencyStopSince = message.requestsEmergencyStopSince.toISOString());
469471
message.challengeFlags !== undefined && (obj.challengeFlags = Math.round(message.challengeFlags));
470472
message.botSubstitutionAllowed !== undefined && (obj.botSubstitutionAllowed = message.botSubstitutionAllowed);
473+
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = Math.round(message.botSubstitutionsLeft));
471474
return obj;
472475
},
473476
};

frontend/src/views/TeamSettingsView.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {useMatchStateStore} from "@/store/matchState";
1010
import {useGcStateStore} from "@/store/gcState";
1111
import {teams} from "@/helpers";
1212
import type {Team} from "@/proto/ssl_gc_common";
13+
import BotSubstitutionsInput from "@/components/team/BotSubstitutionsInput.vue";
1314
1415
const store = useMatchStateStore()
1516
const gcStore = useGcStateStore()
@@ -81,6 +82,12 @@ const redCards = (team: Team) => {
8182
</q-item-section>
8283
</q-item>
8384

85+
<q-item v-ripple>
86+
<q-item-section>
87+
<BotSubstitutionsInput :team="team"/>
88+
</q-item-section>
89+
</q-item>
90+
8491
<q-item v-ripple clickable @click="() => $router.push(`/team-settings/${team}/details`)">
8592
<q-item-section class="text-center">
8693
<q-item-label>{{ fouls(team) }}</q-item-label>

internal/app/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ type Game struct {
6767
EmergencyStopGracePeriod time.Duration `yaml:"emergency-stop-grace-period"`
6868
PreparationTimeAfterHalt time.Duration `yaml:"preparation-time-after-halt"`
6969
PreparationTimeBeforeResume time.Duration `yaml:"preparation-time-before-resume"`
70+
BotSubstitutionBudget int32 `yaml:"bot-substitution-budget"`
71+
BotSubstitutionTime time.Duration `yaml:"bot-substitution-time"`
7072
}
7173

7274
// Network holds configs for network communication
@@ -200,6 +202,8 @@ func DefaultControllerConfig() (c Controller) {
200202
c.Game.EmergencyStopGracePeriod = 10 * time.Second
201203
c.Game.PreparationTimeAfterHalt = 10 * time.Second
202204
c.Game.PreparationTimeBeforeResume = 2 * time.Second
205+
c.Game.BotSubstitutionBudget = 5
206+
c.Game.BotSubstitutionTime = 10 * time.Second
203207

204208
c.Game.Normal.HalfDuration = 5 * time.Minute
205209
c.Game.Normal.HalfTimeDuration = 5 * time.Minute

internal/app/config/testdata/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ game:
5252
emergency-stop-grace-period: 10s
5353
preparation-time-after-halt: 10s
5454
preparation-time-before-resume: 2s
55+
bot-substitution-budget: 5
56+
bot-substitution-time: 10s
5557
normal:
5658
half-duration: 5m
5759
half-time-duration: 5m

internal/app/engine/engine.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ func initializeAddedTeamInfoFields(teamInfo *state.TeamInfo) {
249249
if teamInfo.BotSubstitutionAllowed == nil {
250250
teamInfo.BotSubstitutionAllowed = new(bool)
251251
}
252+
if teamInfo.BotSubstitutionsLeft == nil {
253+
teamInfo.BotSubstitutionsLeft = new(int32)
254+
}
252255
}
253256

254257
// Stop stops the go routine that processes the change queue
@@ -456,6 +459,7 @@ func (e *Engine) createInitialState() (s *state.State) {
456459
s.TeamInfo(team).TimeoutTimeLeft = durationpb.New(e.gameConfig.Normal.TimeoutDuration)
457460
*s.TeamInfo(team).MaxAllowedBots = e.gameConfig.MaxBots[e.gameConfig.DefaultDivision]
458461
*s.TeamInfo(team).ChallengeFlags = e.gameConfig.ChallengeFlags
462+
*s.TeamInfo(team).BotSubstitutionsLeft = e.gameConfig.BotSubstitutionBudget
459463
}
460464
s.NextCommand = state.NewCommand(state.Command_KICKOFF, *s.FirstKickoffTeam)
461465
s.PlacementPos = geom.NewVector2(0.0, 0.0)

0 commit comments

Comments
 (0)