Skip to content

Commit

Permalink
Delay reconfiguring teams in alliance stations until a new button is …
Browse files Browse the repository at this point in the history
…clicked (fixes #159).
  • Loading branch information
patfair committed Oct 5, 2023
1 parent 3e13154 commit 7d265cb
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 37 deletions.
60 changes: 44 additions & 16 deletions field/arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,28 +302,39 @@ func (arena *Arena) LoadNextMatch(startScheduledBreak bool) error {
}

// Assigns the given team to the given station, also substituting it into the match record.
func (arena *Arena) SubstituteTeam(teamId int, station string) error {
func (arena *Arena) SubstituteTeams(red1, red2, red3, blue1, blue2, blue3 int) error {
if !arena.CurrentMatch.ShouldAllowSubstitution() {
return fmt.Errorf("Can't substitute teams for qualification matches.")
}
err := arena.assignTeam(teamId, station)
if err != nil {

if err := arena.validateTeams(red1, red2, red3, blue1, blue2, blue3); err != nil {
return err
}
if err := arena.assignTeam(red1, "R1"); err != nil {
return err
}
switch station {
case "R1":
arena.CurrentMatch.Red1 = teamId
case "R2":
arena.CurrentMatch.Red2 = teamId
case "R3":
arena.CurrentMatch.Red3 = teamId
case "B1":
arena.CurrentMatch.Blue1 = teamId
case "B2":
arena.CurrentMatch.Blue2 = teamId
case "B3":
arena.CurrentMatch.Blue3 = teamId
if err := arena.assignTeam(red2, "R2"); err != nil {
return err
}
if err := arena.assignTeam(red3, "R3"); err != nil {
return err
}
if err := arena.assignTeam(blue1, "B1"); err != nil {
return err
}
if err := arena.assignTeam(blue2, "B2"); err != nil {
return err
}
if err := arena.assignTeam(blue3, "B3"); err != nil {
return err
}

arena.CurrentMatch.Red1 = red1
arena.CurrentMatch.Red2 = red2
arena.CurrentMatch.Red3 = red3
arena.CurrentMatch.Blue1 = blue1
arena.CurrentMatch.Blue2 = blue2
arena.CurrentMatch.Blue3 = blue3
arena.setupNetwork([6]*model.Team{arena.AllianceStations["R1"].Team, arena.AllianceStations["R2"].Team,
arena.AllianceStations["R3"].Team, arena.AllianceStations["B1"].Team, arena.AllianceStations["B2"].Team,
arena.AllianceStations["B3"].Team})
Expand Down Expand Up @@ -611,6 +622,23 @@ func (arena *Arena) BlueScoreSummary() *game.ScoreSummary {
return arena.BlueRealtimeScore.CurrentScore.Summarize(&arena.RedRealtimeScore.CurrentScore)
}

// Checks that the given teams are present in the database, allowing team ID 0 which indicates an empty spot.
func (arena *Arena) validateTeams(teamIds ...int) error {
for _, teamId := range teamIds {
if teamId == 0 {
continue
}
team, err := arena.Database.GetTeamById(teamId)
if err != nil {
return err
}
if team == nil {
return fmt.Errorf("Team %d is not present at the event.", teamId)
}
}
return nil
}

// Loads a team into an alliance station, cleaning up the previous team there if there is one.
func (arena *Arena) assignTeam(teamId int, station string) error {
// Reject invalid station values.
Expand Down
16 changes: 11 additions & 5 deletions field/arena_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func TestLoadNextMatch(t *testing.T) {

// Test match should be followed by another, empty test match.
assert.Equal(t, 0, arena.CurrentMatch.Id)
err := arena.SubstituteTeam(1114, "R1")
err := arena.SubstituteTeams(1114, 0, 0, 0, 0, 0)
assert.Nil(t, err)
arena.CurrentMatch.Status = game.TieMatch
err = arena.LoadNextMatch(false)
Expand Down Expand Up @@ -442,7 +442,7 @@ func TestSubstituteTeam(t *testing.T) {
arena.Database.CreateTeam(&model.Team{Id: 107})

// Substitute teams into test match.
err := arena.SubstituteTeam(101, "B1")
err := arena.SubstituteTeams(0, 0, 0, 101, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 101, arena.CurrentMatch.Blue1)
assert.Equal(t, 101, arena.AllianceStations["B1"].Team.Id)
Expand All @@ -455,7 +455,7 @@ func TestSubstituteTeam(t *testing.T) {
match := model.Match{Type: model.Practice, Red1: 101, Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
arena.Database.CreateMatch(&match)
arena.LoadMatch(&match)
err = arena.SubstituteTeam(107, "R1")
err = arena.SubstituteTeams(107, 102, 103, 104, 105, 106)
assert.Nil(t, err)
assert.Equal(t, 107, arena.CurrentMatch.Red1)
assert.Equal(t, 107, arena.AllianceStations["R1"].Team.Id)
Expand All @@ -466,14 +466,20 @@ func TestSubstituteTeam(t *testing.T) {
match = model.Match{Type: model.Qualification, Red1: 101, Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
arena.Database.CreateMatch(&match)
arena.LoadMatch(&match)
err = arena.SubstituteTeam(107, "R1")
err = arena.SubstituteTeams(107, 102, 103, 104, 105, 106)
if assert.NotNil(t, err) {
assert.Contains(t, err.Error(), "Can't substitute teams for qualification matches.")
}
match = model.Match{Type: model.Playoff, Red1: 101, Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
arena.Database.CreateMatch(&match)
arena.LoadMatch(&match)
assert.Nil(t, arena.SubstituteTeam(107, "R1"))
assert.Nil(t, arena.SubstituteTeams(107, 102, 103, 104, 105, 106))

// Check that loading a nonexistent team fails.
err = arena.SubstituteTeams(101, 102, 103, 104, 105, 108)
if assert.NotNil(t, err) {
assert.Equal(t, err.Error(), "Team 108 is not present at the event.")
}
}

func TestArenaTimeout(t *testing.T) {
Expand Down
23 changes: 20 additions & 3 deletions static/js/match_play.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,18 @@ const showResult = function(matchId) {
websocket.send("showResult", { matchId: matchId });
}

// Sends a websocket message to load a team into an alliance station.
const substituteTeam = function(team, position) {
websocket.send("substituteTeam", { team: parseInt(team), position: position })
// Sends a websocket message to load all teams into their respective alliance stations.
const substituteTeams = function(team, position) {
const teams = {
Red1: getTeamNumber("R1"),
Red2: getTeamNumber("R2"),
Red3: getTeamNumber("R3"),
Blue1: getTeamNumber("B1"),
Blue2: getTeamNumber("B2"),
Blue3: getTeamNumber("B3"),
};

websocket.send("substituteTeams", teams);
};

// Sends a websocket message to toggle the bypass status for an alliance station.
Expand Down Expand Up @@ -90,6 +99,12 @@ const setTestMatchName = function() {
websocket.send("setTestMatchName", $("#testMatchName").val());
};

// Returns the integer team number entered into the team number input box for the given station, or 0 if it is empty.
const getTeamNumber = function(station) {
const teamId = $(`#status${station} .team-number`).val().trim();
return teamId ? parseInt(teamId) : 0;
}

// Handles a websocket message to update the team connection status.
const handleArenaStatus = function(data) {
// Update the team status view.
Expand Down Expand Up @@ -237,6 +252,8 @@ const handleMatchLoad = function(data) {
});
$("#playoffRedAllianceInfo").html(formatPlayoffAllianceInfo(data.Match.PlayoffRedAlliance, data.RedOffFieldTeams));
$("#playoffBlueAllianceInfo").html(formatPlayoffAllianceInfo(data.Match.PlayoffBlueAlliance, data.BlueOffFieldTeams));

$("#substituteTeams").prop("disabled", true);
}

// Handles a websocket message to update the match time countdown.
Expand Down
6 changes: 5 additions & 1 deletion templates/match_play.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
onclick="abortMatch();" disabled>
Abort Match
</button>
<button type="button" id="substituteTeams" class="btn btn-warning btn-lg btn-match-play"
onclick="substituteTeams();" disabled>
Substitute Teams
</button>
<button type="button" id="signalReset" class="btn btn-success btn-lg btn-match-play"
onclick="signalReset();" disabled>
Signal Reset
Expand Down Expand Up @@ -269,7 +273,7 @@ <h4 class="modal-title">Confirm</h4>
<div class="col-lg-1">{{.position}} </div>
<div class="col-lg-3">
<input type="number" class="team-number form-control input-sm"
onblur="substituteTeam($(this).val(), '{{.color}}{{.position}}');">
onchange="$('#substituteTeams').prop('disabled', false);">
</div>
<div class="col-lg-2 col-no-padding"><div class="ds-status"></div></div>
<div class="col-lg-2 col-no-padding"><div class="radio-status"></div></div>
Expand Down
5 changes: 3 additions & 2 deletions web/field_monitor_display_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func TestFieldMonitorDisplay(t *testing.T) {

func TestFieldMonitorDisplayWebsocket(t *testing.T) {
web := setupTestWeb(t)
assert.Nil(t, web.arena.SubstituteTeam(254, "B1"))
web.arena.Database.CreateTeam(&model.Team{Id: 254})
assert.Nil(t, web.arena.SubstituteTeams(0, 0, 0, 254, 0, 0))

server, wsUrl := web.startTestServer()
defer server.Close()
Expand Down Expand Up @@ -50,7 +51,7 @@ func TestFieldMonitorDisplayWebsocket(t *testing.T) {
func TestFieldMonitorFtaDisplayWebsocket(t *testing.T) {
web := setupTestWeb(t)
web.arena.Database.CreateTeam(&model.Team{Id: 254})
assert.Nil(t, web.arena.SubstituteTeam(254, "B1"))
assert.Nil(t, web.arena.SubstituteTeams(0, 0, 0, 254, 0, 0))

server, wsUrl := web.startTestServer()
defer server.Close()
Expand Down
12 changes: 8 additions & 4 deletions web/match_play.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,21 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
web.arena.SavedMatch = match
web.arena.SavedMatchResult = matchResult
web.arena.ScorePostedNotifier.Notify()
case "substituteTeam":
case "substituteTeams":
args := struct {
Team int
Position string
Red1 int
Red2 int
Red3 int
Blue1 int
Blue2 int
Blue3 int
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
ws.WriteError(err.Error())
continue
}
err = web.arena.SubstituteTeam(args.Team, args.Position)
err = web.arena.SubstituteTeams(args.Red1, args.Red2, args.Red3, args.Blue1, args.Blue2, args.Blue3)
if err != nil {
ws.WriteError(err.Error())
continue
Expand Down
11 changes: 5 additions & 6 deletions web/match_play_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ func TestCommitCards(t *testing.T) {

func TestMatchPlayWebsocketCommands(t *testing.T) {
web := setupTestWeb(t)
web.arena.Database.CreateTeam(&model.Team{Id: 254})

server, wsUrl := web.startTestServer()
defer server.Close()
Expand All @@ -265,14 +266,12 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
assert.Contains(t, readWebsocketError(t, ws), "Invalid message type")

// Test match setup commands.
ws.Write("substituteTeam", nil)
assert.Contains(t, readWebsocketError(t, ws), "Invalid alliance station")
ws.Write("substituteTeam", map[string]any{"team": 254, "position": "B5"})
assert.Contains(t, readWebsocketError(t, ws), "Invalid alliance station")
ws.Write("substituteTeam", map[string]any{"team": 254, "position": "B1"})
ws.Write("substituteTeams", map[string]int{"Red1": 0, "Red2": 0, "Red3": 0, "Blue1": 1, "Blue2": 0, "Blue3": 0})
assert.Equal(t, readWebsocketError(t, ws), "Team 1 is not present at the event.")
ws.Write("substituteTeams", map[string]int{"Red1": 0, "Red2": 0, "Red3": 0, "Blue1": 254, "Blue2": 0, "Blue3": 0})
readWebsocketType(t, ws, "matchLoad")
assert.Equal(t, 254, web.arena.CurrentMatch.Blue1)
ws.Write("substituteTeam", map[string]any{"team": 0, "position": "B1"})
ws.Write("substituteTeams", map[string]int{"Red1": 0, "Red2": 0, "Red3": 0, "Blue1": 0, "Blue2": 0, "Blue3": 0})
readWebsocketType(t, ws, "matchLoad")
assert.Equal(t, 0, web.arena.CurrentMatch.Blue1)
ws.Write("toggleBypass", nil)
Expand Down

0 comments on commit 7d265cb

Please sign in to comment.