Skip to content

Commit

Permalink
Implement punishment for excessive bot substitution
Browse files Browse the repository at this point in the history
  • Loading branch information
g3force committed Mar 30, 2024
1 parent 83af744 commit 7fd47ef
Show file tree
Hide file tree
Showing 27 changed files with 1,192 additions and 840 deletions.
36 changes: 36 additions & 0 deletions frontend/src/components/team/BotSubstitutionsInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
import {computed, inject} from "vue";
import NumberInput from "@/components/common/NumberInput.vue";
import {useMatchStateStore} from "@/store/matchState";
import type {Team} from "@/proto/ssl_gc_common";
import type {ControlApi} from "@/providers/controlApi";
const props = defineProps<{
team: Team,
}>()
const store = useMatchStateStore()
const control = inject<ControlApi>('control-api')
const model = computed(() => {
return store.matchState.teamState![props.team].botSubstitutionsLeft!
})
const updateValue = (value: number | undefined) => {
if (value !== undefined) {
control?.UpdateTeamState({
forTeam: props.team,
botSubstitutionsLeft: value,
})
}
}
</script>

<template>
<NumberInput
:modelValue="model"
label="Bot substitutions left"
@update:model-value="updateValue"
/>
</template>
1 change: 1 addition & 0 deletions frontend/src/helpers/texts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const gameEventNames = new Map<GameEvent_Type, string>([
[GameEvent_Type.MULTIPLE_CARDS, "Multiple cards"],
[GameEvent_Type.MULTIPLE_FOULS, "Multiple fouls"],
[GameEvent_Type.BOT_SUBSTITUTION, "Bot substitution"],
[GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION, "Excessive bot substitution"],
[GameEvent_Type.TOO_MANY_ROBOTS, "Too many bots on field"],
[GameEvent_Type.CHALLENGE_FLAG, "Challenge flag"],
[GameEvent_Type.CHALLENGE_FLAG_HANDLED, "Challenge flag handled"],
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/proto/ssl_gc_change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export interface Change_UpdateTeamState {
canPlaceBall?: boolean;
/** The number of challenge flags that the team has left */
challengeFlagsLeft?: number;
/** The number of bot substitutions left by the team in this halftime */
botSubstitutionsLeft?: number;
/** Does the team want to substitute a robot in the next possible situation? */
requestsBotSubstitution?: boolean;
/** Does the team want to take a timeout in the next possible situation? */
Expand Down Expand Up @@ -508,6 +510,7 @@ export const Change_UpdateTeamState = {
ballPlacementFailures: isSet(object.ballPlacementFailures) ? Number(object.ballPlacementFailures) : undefined,
canPlaceBall: isSet(object.canPlaceBall) ? Boolean(object.canPlaceBall) : undefined,
challengeFlagsLeft: isSet(object.challengeFlagsLeft) ? Number(object.challengeFlagsLeft) : undefined,
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : undefined,
requestsBotSubstitution: isSet(object.requestsBotSubstitution)
? Boolean(object.requestsBotSubstitution)
: undefined,
Expand Down Expand Up @@ -535,6 +538,7 @@ export const Change_UpdateTeamState = {
message.ballPlacementFailures !== undefined && (obj.ballPlacementFailures = message.ballPlacementFailures);
message.canPlaceBall !== undefined && (obj.canPlaceBall = message.canPlaceBall);
message.challengeFlagsLeft !== undefined && (obj.challengeFlagsLeft = message.challengeFlagsLeft);
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = message.botSubstitutionsLeft);
message.requestsBotSubstitution !== undefined && (obj.requestsBotSubstitution = message.requestsBotSubstitution);
message.requestsTimeout !== undefined && (obj.requestsTimeout = message.requestsTimeout);
message.requestsChallenge !== undefined && (obj.requestsChallenge = message.requestsChallenge);
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/proto/ssl_gc_game_event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface GameEvent {
| { $case: "multipleCards"; multipleCards: GameEvent_MultipleCards }
| { $case: "multipleFouls"; multipleFouls: GameEvent_MultipleFouls }
| { $case: "botSubstitution"; botSubstitution: GameEvent_BotSubstitution }
| { $case: "excessiveBotSubstitution"; excessiveBotSubstitution: GameEvent_ExcessiveBotSubstitution }
| { $case: "tooManyRobots"; tooManyRobots: GameEvent_TooManyRobots }
| { $case: "challengeFlag"; challengeFlag: GameEvent_ChallengeFlag }
| { $case: "challengeFlagHandled"; challengeFlagHandled: GameEvent_ChallengeFlagHandled }
Expand Down Expand Up @@ -125,6 +126,8 @@ export enum GameEvent_Type {
BOT_TOO_FAST_IN_STOP = "BOT_TOO_FAST_IN_STOP",
/** BOT_INTERFERED_PLACEMENT - triggered by autoRef */
BOT_INTERFERED_PLACEMENT = "BOT_INTERFERED_PLACEMENT",
/** EXCESSIVE_BOT_SUBSTITUTION - triggered by GC */
EXCESSIVE_BOT_SUBSTITUTION = "EXCESSIVE_BOT_SUBSTITUTION",
/** POSSIBLE_GOAL - triggered by autoRef */
POSSIBLE_GOAL = "POSSIBLE_GOAL",
/** GOAL - triggered by GC */
Expand Down Expand Up @@ -244,6 +247,9 @@ export function gameEvent_TypeFromJSON(object: any): GameEvent_Type {
case 20:
case "BOT_INTERFERED_PLACEMENT":
return GameEvent_Type.BOT_INTERFERED_PLACEMENT;
case 48:
case "EXCESSIVE_BOT_SUBSTITUTION":
return GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION;
case 39:
case "POSSIBLE_GOAL":
return GameEvent_Type.POSSIBLE_GOAL;
Expand Down Expand Up @@ -374,6 +380,8 @@ export function gameEvent_TypeToJSON(object: GameEvent_Type): string {
return "BOT_TOO_FAST_IN_STOP";
case GameEvent_Type.BOT_INTERFERED_PLACEMENT:
return "BOT_INTERFERED_PLACEMENT";
case GameEvent_Type.EXCESSIVE_BOT_SUBSTITUTION:
return "EXCESSIVE_BOT_SUBSTITUTION";
case GameEvent_Type.POSSIBLE_GOAL:
return "POSSIBLE_GOAL";
case GameEvent_Type.GOAL:
Expand Down Expand Up @@ -828,6 +836,12 @@ export interface GameEvent_BotSubstitution {
byTeam?: Team;
}

/** A foul for excessive bot substitutions */
export interface GameEvent_ExcessiveBotSubstitution {
/** the team that substitutes robots */
byTeam?: Team;
}

/** A challenge flag, requested by a team previously, is flagged */
export interface GameEvent_ChallengeFlag {
/** the team that requested the challenge flag */
Expand Down Expand Up @@ -990,6 +1004,11 @@ export const GameEvent = {
? { $case: "multipleFouls", multipleFouls: GameEvent_MultipleFouls.fromJSON(object.multipleFouls) }
: isSet(object.botSubstitution)
? { $case: "botSubstitution", botSubstitution: GameEvent_BotSubstitution.fromJSON(object.botSubstitution) }
: isSet(object.excessiveBotSubstitution)
? {
$case: "excessiveBotSubstitution",
excessiveBotSubstitution: GameEvent_ExcessiveBotSubstitution.fromJSON(object.excessiveBotSubstitution),
}
: isSet(object.tooManyRobots)
? { $case: "tooManyRobots", tooManyRobots: GameEvent_TooManyRobots.fromJSON(object.tooManyRobots) }
: isSet(object.challengeFlag)
Expand Down Expand Up @@ -1166,6 +1185,10 @@ export const GameEvent = {
message.event?.$case === "botSubstitution" && (obj.botSubstitution = message.event?.botSubstitution
? GameEvent_BotSubstitution.toJSON(message.event?.botSubstitution)
: undefined);
message.event?.$case === "excessiveBotSubstitution" &&
(obj.excessiveBotSubstitution = message.event?.excessiveBotSubstitution
? GameEvent_ExcessiveBotSubstitution.toJSON(message.event?.excessiveBotSubstitution)
: undefined);
message.event?.$case === "tooManyRobots" && (obj.tooManyRobots = message.event?.tooManyRobots
? GameEvent_TooManyRobots.toJSON(message.event?.tooManyRobots)
: undefined);
Expand Down Expand Up @@ -1890,6 +1913,18 @@ export const GameEvent_BotSubstitution = {
},
};

export const GameEvent_ExcessiveBotSubstitution = {
fromJSON(object: any): GameEvent_ExcessiveBotSubstitution {
return { byTeam: isSet(object.byTeam) ? teamFromJSON(object.byTeam) : Team.UNKNOWN };
},

toJSON(message: GameEvent_ExcessiveBotSubstitution): unknown {
const obj: any = {};
message.byTeam !== undefined && (obj.byTeam = teamToJSON(message.byTeam));
return obj;
},
};

export const GameEvent_ChallengeFlag = {
fromJSON(object: any): GameEvent_ChallengeFlag {
return { byTeam: isSet(object.byTeam) ? teamFromJSON(object.byTeam) : Team.UNKNOWN };
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/proto/ssl_gc_referee_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ export interface Referee_TeamInfo {
ballPlacementFailuresReached?: boolean;
/** The team is allowed to substitute one or more robots currently */
botSubstitutionAllowed?: boolean;
/** The number of bot substitutions left by the team in this halftime */
botSubstitutionsLeft?: number;
}

/**
Expand Down Expand Up @@ -573,6 +575,7 @@ export const Referee_TeamInfo = {
? Boolean(object.ballPlacementFailuresReached)
: false,
botSubstitutionAllowed: isSet(object.botSubstitutionAllowed) ? Boolean(object.botSubstitutionAllowed) : false,
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : 0,
};
},

Expand All @@ -599,6 +602,7 @@ export const Referee_TeamInfo = {
message.ballPlacementFailuresReached !== undefined &&
(obj.ballPlacementFailuresReached = message.ballPlacementFailuresReached);
message.botSubstitutionAllowed !== undefined && (obj.botSubstitutionAllowed = message.botSubstitutionAllowed);
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = Math.round(message.botSubstitutionsLeft));
return obj;
},
};
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/proto/ssl_gc_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export interface TeamInfo {
requestsEmergencyStopSince?: Date;
challengeFlags?: number;
botSubstitutionAllowed?: boolean;
botSubstitutionsLeft?: number;
}

export interface State {
Expand Down Expand Up @@ -427,6 +428,7 @@ export const TeamInfo = {
: undefined,
challengeFlags: isSet(object.challengeFlags) ? Number(object.challengeFlags) : 0,
botSubstitutionAllowed: isSet(object.botSubstitutionAllowed) ? Boolean(object.botSubstitutionAllowed) : false,
botSubstitutionsLeft: isSet(object.botSubstitutionsLeft) ? Number(object.botSubstitutionsLeft) : 0,
};
},

Expand Down Expand Up @@ -468,6 +470,7 @@ export const TeamInfo = {
(obj.requestsEmergencyStopSince = message.requestsEmergencyStopSince.toISOString());
message.challengeFlags !== undefined && (obj.challengeFlags = Math.round(message.challengeFlags));
message.botSubstitutionAllowed !== undefined && (obj.botSubstitutionAllowed = message.botSubstitutionAllowed);
message.botSubstitutionsLeft !== undefined && (obj.botSubstitutionsLeft = Math.round(message.botSubstitutionsLeft));
return obj;
},
};
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/views/TeamSettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {useMatchStateStore} from "@/store/matchState";
import {useGcStateStore} from "@/store/gcState";
import {teams} from "@/helpers";
import type {Team} from "@/proto/ssl_gc_common";
import BotSubstitutionsInput from "@/components/team/BotSubstitutionsInput.vue";
const store = useMatchStateStore()
const gcStore = useGcStateStore()
Expand Down Expand Up @@ -81,6 +82,12 @@ const redCards = (team: Team) => {
</q-item-section>
</q-item>

<q-item v-ripple>
<q-item-section>
<BotSubstitutionsInput :team="team"/>
</q-item-section>
</q-item>

<q-item v-ripple clickable @click="() => $router.push(`/team-settings/${team}/details`)">
<q-item-section class="text-center">
<q-item-label>{{ fouls(team) }}</q-item-label>
Expand Down
4 changes: 4 additions & 0 deletions internal/app/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ type Game struct {
EmergencyStopGracePeriod time.Duration `yaml:"emergency-stop-grace-period"`
PreparationTimeAfterHalt time.Duration `yaml:"preparation-time-after-halt"`
PreparationTimeBeforeResume time.Duration `yaml:"preparation-time-before-resume"`
BotSubstitutionBudget int32 `yaml:"bot-substitution-budget"`
BotSubstitutionTime time.Duration `yaml:"bot-substitution-time"`
}

// Network holds configs for network communication
Expand Down Expand Up @@ -200,6 +202,8 @@ func DefaultControllerConfig() (c Controller) {
c.Game.EmergencyStopGracePeriod = 10 * time.Second
c.Game.PreparationTimeAfterHalt = 10 * time.Second
c.Game.PreparationTimeBeforeResume = 2 * time.Second
c.Game.BotSubstitutionBudget = 5
c.Game.BotSubstitutionTime = 10 * time.Second

c.Game.Normal.HalfDuration = 5 * time.Minute
c.Game.Normal.HalfTimeDuration = 5 * time.Minute
Expand Down
2 changes: 2 additions & 0 deletions internal/app/config/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ game:
emergency-stop-grace-period: 10s
preparation-time-after-halt: 10s
preparation-time-before-resume: 2s
bot-substitution-budget: 5
bot-substitution-time: 10s
normal:
half-duration: 5m
half-time-duration: 5m
Expand Down
4 changes: 4 additions & 0 deletions internal/app/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ func initializeAddedTeamInfoFields(teamInfo *state.TeamInfo) {
if teamInfo.BotSubstitutionAllowed == nil {
teamInfo.BotSubstitutionAllowed = new(bool)
}
if teamInfo.BotSubstitutionsLeft == nil {
teamInfo.BotSubstitutionsLeft = new(int32)
}
}

// Stop stops the go routine that processes the change queue
Expand Down Expand Up @@ -456,6 +459,7 @@ func (e *Engine) createInitialState() (s *state.State) {
s.TeamInfo(team).TimeoutTimeLeft = durationpb.New(e.gameConfig.Normal.TimeoutDuration)
*s.TeamInfo(team).MaxAllowedBots = e.gameConfig.MaxBots[e.gameConfig.DefaultDivision]
*s.TeamInfo(team).ChallengeFlags = e.gameConfig.ChallengeFlags
*s.TeamInfo(team).BotSubstitutionsLeft = e.gameConfig.BotSubstitutionBudget
}
s.NextCommand = state.NewCommand(state.Command_KICKOFF, *s.FirstKickoffTeam)
s.PlacementPos = geom.NewVector2(0.0, 0.0)
Expand Down
25 changes: 25 additions & 0 deletions internal/app/engine/process_bot_substitution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package engine

import (
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"
"time"
)

func (e *Engine) processBotSubstitution() {
if e.currentState.GameState.IsHalted() {
for _, team := range state.BothTeams() {
if !*e.currentState.TeamInfo(team).BotSubstitutionAllowed {
continue
}
events := e.currentState.FindGameEventsByTeam(state.GameEvent_BOT_SUBSTITUTION, team)
if len(events) == 0 {
continue
}
botSubstitutionEvent := events[len(events)-1]
eventCreated := time.UnixMicro(int64(*botSubstitutionEvent.CreatedTimestamp))
if e.timeProvider().Sub(eventCreated) > e.gameConfig.BotSubstitutionTime {
e.Enqueue(createBotSubstitutionEventChange(team))
}
}
}
}
7 changes: 6 additions & 1 deletion internal/app/engine/process_continue_perform.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ func (e *Engine) performContinueAction(action *ContinueAction) {
logWillNotContinue("No team for timeout specified")
}
case ContinueAction_BOT_SUBSTITUTION:
e.Enqueue(createBotSubstitutionEventChange(*action.ForTeam))
if action.ForTeam.Known() {
e.Enqueue(createBotSubstitutionEventChange(*action.ForTeam))
} else {
e.Enqueue(createBotSubstitutionEventChange(state.Team_YELLOW))
e.Enqueue(createBotSubstitutionEventChange(state.Team_BLUE))
}
case ContinueAction_NEXT_STAGE:
e.Enqueue(createStageChange(e.currentState.Stage.Next()))
case ContinueAction_END_GAME:
Expand Down
1 change: 1 addition & 0 deletions internal/app/engine/process_tick.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func (e *Engine) processTick() {
e.processProposals()
e.processTrackerSources()
e.processEmergencyStop()
e.processBotSubstitution()

stateCopy := e.currentState.Clone()
hookOut := HookOut{State: stateCopy}
Expand Down
2 changes: 2 additions & 0 deletions internal/app/publish/messagegenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func updateTeam(teamInfo *state.Referee_TeamInfo, teamState *state.TeamInfo) {
*teamInfo.MaxAllowedBots = unsigned32(*teamState.MaxAllowedBots)
*teamInfo.BotSubstitutionIntent = teamState.RequestsBotSubstitutionSince != nil
*teamInfo.BotSubstitutionAllowed = *teamState.BotSubstitutionAllowed
*teamInfo.BotSubstitutionsLeft = unsigned32(*teamState.BotSubstitutionsLeft)
timeoutTime := teamState.TimeoutTimeLeft.AsDuration()
*teamInfo.TimeoutTime = mapTime(timeoutTime)
}
Expand Down Expand Up @@ -174,6 +175,7 @@ func newTeamInfo() (t *state.Referee_TeamInfo) {
t.MaxAllowedBots = new(uint32)
t.BotSubstitutionIntent = new(bool)
t.BotSubstitutionAllowed = new(bool)
t.BotSubstitutionsLeft = new(uint32)
return
}

Expand Down
Loading

0 comments on commit 7fd47ef

Please sign in to comment.