Implement scoring panel web interface for 2019.

This commit is contained in:
Patrick Fairbank
2019-08-03 00:46:44 -07:00
parent b164127f26
commit 692135f721
10 changed files with 547 additions and 131 deletions

View File

@@ -10,7 +10,6 @@ import "github.com/Team254/cheesy-arena/game"
type RealtimeScore struct {
CurrentScore game.Score
Cards map[string]string
AutoCommitted bool
TeleopCommitted bool
FoulsCommitted bool
}

View File

@@ -88,17 +88,6 @@
padding-left: 20px;
text-indent: -20px;
}
.scoring {
font-size: 20px;
font-weight: bold;
color: #333;
}
.scoring-comment {
font-size: 20px;
}
.scoring-message {
color: #f00;
}
.btn-lower-third {
width: 80px;
}

View File

@@ -9,7 +9,7 @@ html {
-moz-user-select: none;
}
body {
background-color: #333;
background-color: #222;
padding: 50px;
color: #fff;
}

View File

@@ -0,0 +1,224 @@
/*
Copyright 2019 Team 254. All Rights Reserved.
Author: pat@patfairbank.com (Patrick Fairbank)
*/
html {
height: 100%;
-webkit-user-select: none;
-moz-user-select: none;
}
body {
height: 100%;
background-color: #222;
}
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
#matchName {
font-size: 2vw;
}
#robots {
margin-bottom: 0.5vw;
display: flex;
font-size: 1.5vw;
color: #ccc;
}
#robotHeader {
margin-right: 1vw;
color: #666;
}
.robot-field {
min-width: 12vw;
height: 2.5vw;
display: flex;
justify-content: space-between;
align-items: center;
margin: 0.4vw 0.2vw;
}
.team {
display: flex;
justify-content: center;
}
.robot-start-level[data-value="0"] {
background-color: #633;
}
.robot-start-level[data-value="1"] {
background-color: #236;
}
.robot-start-level[data-value="2"] {
background-color: #263;
}
.robot-start-level[data-value="3"] {
background-color: #850;
}
.sandstorm-bonus[data-value="false"] {
background-color: #333;
}
.sandstorm-bonus[data-value="true"] {
background-color: #263;
}
.robot-end-level[data-value="0"] {
background-color: #333;
}
.robot-end-level[data-value="1"] {
background-color: #236;
}
.robot-end-level[data-value="2"] {
background-color: #224d4d;
}
.robot-end-level[data-value="3"] {
background-color: #263;
}
.robot-shortcut {
width: 2vw;
margin: 0 0.2vw;
font-size: 1vw;
align-self: flex-start;
}
.team {
font-weight: bold;
}
#scoringElements {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
}
.rocket {
display: flex;
align-items: flex-end;
}
.rocket-outline {
display: flex;
align-items: flex-end;
padding: 5vw 2vw 1vw 2vw;
border: 1px solid #222;
border-radius: 40% 40% 0% 0%;
}
.alliance-color[data-alliance="red"] {
background-color: #633;
}
.alliance-color[data-alliance="blue"] {
background-color: #236;
}
.outer-rocket {
height: 26vw;
margin: 0.2vw;
}
.inner-rocket {
margin: 0.2vw;
}
#centerColumn {
display: flex;
flex-direction: column;
align-items: center;
}
#cargoShipContainer {
display: flex;
flex-direction: column;
align-items: center;
}
#cargoShip {
display: flex;
flex-direction: column;
align-items: center;
padding: 1vw;
border: 1px solid #222;
border-radius: 10%;
}
.cargo-ship-side {
width: 25vw;
display: flex;
justify-content: space-between;
}
.cargo-ship-front {
display: flex;
}
.bay {
position: relative;
width: 7vw;
height: 7vw;
margin: 0.2vw;
border: 1px solid #222;
background-color: #666;
border-radius: 10%;
}
.bay[data-value="0"] .hatch-panel {
display: none;
}
.bay[data-value="1"] .hatch-panel {
display: flex;
}
.bay[data-value="2"] .hatch-panel {
display: flex;
}
.bay[data-value="3"] .hatch-panel {
display: none;
}
.bay[data-value="0"] .cargo {
display: none;
}
.bay[data-value="1"] .cargo {
display: none;
}
.bay[data-value="2"] .cargo {
display: flex;
}
.bay[data-value="3"] .cargo {
display: flex;
}
.shortcut {
position: absolute;
left: 0.3vw;
top: -0.1vw;
font-size: 1.2vw;
color: #fff
}
.hatch-panel {
width: 6vw;
height: 6vw;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
margin: auto auto;
border: 1vw solid #c80;
border-radius: 50%;
}
.cargo {
width: 3vw;
height: 3vw;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
margin: auto auto;
background-color: #c50;
border-radius: 50%;
}
#instructions {
margin-top: 0.3vw;
}
#preMatchMessage, #postMatchMessage {
height: 100%;
display: none;
align-items: center;
font-size: 1.5vw;
color: #c90;
}
#commitMatchScore {
height: 100%;
display: none;
align-items: center;
}
#commitMatchScore>button {
font-size: 1vw;
}

View File

@@ -4,41 +4,66 @@
// Client-side logic for the scoring interface.
var websocket;
var scoreCommitted = false;
var alliance;
// Handles a websocket message to update the teams for the current match.
var handleMatchLoad = function(data) {
$("#matchName").text(data.MatchType + " " + data.Match.DisplayName);
if (alliance === "red") {
$("#team1").text(data.Match.Red1);
$("#team2").text(data.Match.Red2);
$("#team3").text(data.Match.Red3);
} else {
$("#team1").text(data.Match.Blue1);
$("#team2").text(data.Match.Blue2);
$("#team3").text(data.Match.Blue3);
}
};
// Handles a websocket message to update the realtime scoring fields.
var handleRealtimeScore = function(data) {
var realtimeScore;
var score;
if (alliance === "red") {
realtimeScore = data.Red.RealtimeScore;
score = data.Red.Score;
} else {
realtimeScore = data.Blue.RealtimeScore;
score = data.Blue.Score;
}
// Update autonomous period values.
var score = realtimeScore.CurrentScore;
$("#autoRuns").text(score.AutoRuns);
$("#climbs").text(score.Climbs);
$("#parks").text(score.Parks);
for (var i = 0; i < 3; i++) {
var i1 = i + 1;
$("#robotStartLevel" + i1 + ">.value").text(getRobotStartLevelText(score.RobotStartLevels[i]));
$("#robotStartLevel" + i1).attr("data-value", score.RobotStartLevels[i]);
$("#sandstormBonus" + i1 + ">.value").text(score.SandstormBonuses[i] ? "Yes" : "No");
$("#sandstormBonus" + i1).attr("data-value", score.SandstormBonuses[i]);
$("#robotEndLevel" + i1 + ">.value").text(getRobotEndLevelText(score.RobotEndLevels[i]));
$("#robotEndLevel" + i1).attr("data-value", score.RobotEndLevels[i]);
getBay("rocketNearLeft", i).attr("data-value", score.RocketNearLeftBays[i]);
getBay("rocketNearRight", i).attr("data-value", score.RocketNearRightBays[i]);
getBay("rocketFarLeft", i).attr("data-value", score.RocketFarLeftBays[i]);
getBay("rocketFarRight", i).attr("data-value", score.RocketFarRightBays[i]);
}
for (var i = 0; i < 8; i++) {
getBay("cargoShip", i).attr("data-value", score.CargoBays[i]);
}
};
// Update component visibility.
if (!realtimeScore.AutoCommitted) {
$("#autoScoring").fadeTo(0, 1);
$("#teleopScoring").hide();
$("#waitingMessage").hide();
scoreCommitted = false;
} else if (!realtimeScore.TeleopCommitted) {
$("#autoScoring").fadeTo(0, 0.25);
$("#teleopScoring").show();
$("#waitingMessage").hide();
scoreCommitted = false;
} else {
$("#autoScoring").hide();
$("#teleopScoring").hide();
$("#commitMatchScore").hide();
$("#waitingMessage").show();
scoreCommitted = true;
// Handles a websocket message to update the match status.
var handleMatchTime = function(data) {
switch (matchStates[data.MatchState]) {
case "PRE_MATCH":
$("#preMatchMessage").css("display", "flex");
$("#postMatchMessage").hide();
$("#commitMatchScore").hide();
break;
case "POST_MATCH":
$("#preMatchMessage").hide();
$("#postMatchMessage").hide();
$("#commitMatchScore").css("display", "flex");
break;
default:
$("#preMatchMessage").hide();
$("#postMatchMessage").hide();
$("#commitMatchScore").hide();
}
};
@@ -47,25 +72,58 @@ var handleKeyPress = function(event) {
websocket.send(String.fromCharCode(event.keyCode));
};
// Handles a websocket message to update the match status.
var handleMatchTime = function(data) {
if (matchStates[data.MatchState] === "POST_MATCH" && !scoreCommitted) {
$("#commitMatchScore").show();
} else {
$("#commitMatchScore").hide();
}
// Handles an element click and sends the appropriate websocket message.
var handleClick = function(shortcut) {
websocket.send(shortcut);
};
// Sends a websocket message to indicate that the score for this alliance is ready.
var commitMatchScore = function() {
websocket.send("commitMatch");
$("#postMatchMessage").css("display", "flex");
$("#commitMatchScore").hide();
};
// Returns the display text corresponding to the given integer start level value.
var getRobotStartLevelText = function(level) {
switch (level) {
case 1:
return "1";
case 2:
return "2";
case 3:
return "No-Show";
default:
return " ";
}
};
// Returns the display text corresponding to the given integer end level value.
var getRobotEndLevelText = function(level) {
switch (level) {
case 1:
return "1";
case 2:
return "2";
case 3:
return "3";
default:
return "Not On";
}
};
// Returns the bay element matching the given parameters.
var getBay = function(type, index) {
return $("#bay" + bayMappings[type][index]);
}
$(function() {
alliance = window.location.href.split("/").slice(-1)[0];
$(".alliance-color").attr("data-alliance", alliance);
// Set up the websocket back to the server.
websocket = new CheesyWebsocket("/panels/scoring/" + alliance + "/websocket", {
matchLoad: function(event) { handleMatchLoad(event.data); },
matchTime: function(event) { handleMatchTime(event.data); },
realtimeScore: function(event) { handleRealtimeScore(event.data); }
});

View File

@@ -112,6 +112,8 @@
{{"{{/eachMapEntry}}"}}
</script>
{{end}}
{{define "head"}}
{{end}}
{{define "script"}}
<script src="/static/js/match_timing.js"></script>
<script src="/static/js/announcer_display.js"></script>

View File

@@ -136,6 +136,7 @@
<html>
<head>
{{template "head_common" .}}
{{template "head" .}}
</head>
<body>
<div class="container">

View File

@@ -6,80 +6,103 @@
*/}}
{{define "title"}}Scoring Panel{{end}}
{{define "body"}}
<br />
<div class="row">
<div class="text-center" id="waitingMessage" style="display: none;">
<h3>Waiting for the next match...</h3>
<div id="matchName">&nbsp;</div>
<div id="robots">
<div id="robotHeader">
<div class="robot-field">&nbsp;</div>
<div class="robot-field">Start Hab Level</div>
<div class="robot-field">Sandstorm Bonus?</div>
<div class="robot-field">End Hab Level</div>
</div>
<div id="autoScoring" class="col-lg-12 well well-{{.Alliance}}" style="display: none;">
<div class="col-lg-6">
<div>
<h2>Autonomous Period</h2>
<p>Use the following keyboard shortcuts:</p>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 scoring">r/R</div>
<div class="col-lg-8 scoring-comment">Auto runs +/-</div>
</div>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 scoring">Enter</div>
<div class="col-lg-8 scoring-comment">Commit autonomous score</div>
</div>
{{range $i := seq 3}}
<div>
<div id="team{{$i}}" class="team robot-field"></div>
<div id="robotStartLevel{{$i}}" class="robot-start-level robot-field" onclick="handleClick('{{$i}}');">
<div class="robot-shortcut">{{$i}}</div>
<div class="value"></div>
<div class="robot-shortcut"></div>
</div>
<div id="sandstormBonus{{$i}}" class="sandstorm-bonus robot-field" onclick="handleClick('{{add $i 3}}');">
<div class="robot-shortcut">{{add $i 3}}</div>
<div class="value"></div>
<div class="robot-shortcut"></div>
</div>
<div id="robotEndLevel{{$i}}" class="robot-end-level robot-field" onclick="handleClick('{{add $i 6}}');">
<div class="robot-shortcut">{{add $i 6}}</div>
<div class="value"></div>
<div class="robot-shortcut"></div>
</div>
</div>
<div class="col-lg-6">
<div>
<h2>Autonomous Score</h2>
<div class="row">
<div class="col-lg-4 col-lg-offset-1 scoring-comment">Auto runs</div>
<div class="col-lg-2 scoring-comment" id="autoRuns"></div>
</div>
<h3 class="text-center scoring-message">Press Enter to commit autonomous score</h3>
</div>
{{end}}
</div>
<div id="scoringElements">
<div class="rocket">
<div class="rocket-outline alliance-color">
<div class="outer-rocket">{{template "rocketHalf" dict "startBayId" 0 "vars" $}}</div>
<div class="inner-rocket">{{template "rocketHalf" dict "startBayId" 3 "vars" $}}</div>
</div>
</div>
<div id="teleopScoring" class="col-lg-12 well well-{{.Alliance}}" style="display: none;">
<div class="col-lg-6">
<div>
<h2>Teleoperated Period</h2>
<p>Use the following keyboard shortcuts:</p>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 scoring">c/C</div>
<div class="col-lg-8 scoring-comment">Climbs +/- (actual; disregard Levitate)</div>
<div id="centerColumn">
<div id="cargoShipContainer">
<div id="cargoShip" class="alliance-color">
<div class="cargo-ship-side">
{{template "bay" dict "id" 6 "vars" $}}{{template "bay" dict "id" 13 "vars" $}}
</div>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 scoring">p/P</div>
<div class="col-lg-8 scoring-comment">Parks +/-</div>
<div class="cargo-ship-side">
{{template "bay" dict "id" 7 "vars" $}}{{template "bay" dict "id" 12 "vars" $}}
</div>
<div class="row">
<div class="col-lg-3 col-lg-offset-1 scoring">a</div>
<div class="col-lg-8 scoring-comment">Back to autonomous</div>
<div class="cargo-ship-side">
{{template "bay" dict "id" 8 "vars" $}}{{template "bay" dict "id" 11 "vars" $}}
</div>
<div class="cargo-ship-front">
{{template "bay" dict "id" 9 "vars" $}}{{template "bay" dict "id" 10 "vars" $}}
</div>
</div>
</div>
<div class="col-lg-6">
<div>
<h2>Teleoperated Score</h2>
<div class="row">
<div class="col-lg-4 col-lg-offset-1 scoring-comment">Climbs</div>
<div class="col-lg-2 scoring-comment" id="climbs"></div>
</div>
<div class="row">
<div class="col-lg-4 col-lg-offset-1 scoring-comment">Parks</div>
<div class="col-lg-2 scoring-comment" id="parks"></div>
</div>
</div>
<div id="instructions">Click or use the labeled keyboard shortcuts to toggle each element</div>
<div id="preMatchMessage">Set pre-match state of robots and cargo ship</div>
<div id="commitMatchScore">
<button type="button" class="btn btn-success" onclick="commitMatchScore();">
Commit Final Match Score
</button>
</div>
<div id="postMatchMessage">Waiting for the next match...</div>
</div>
<div class="text-center col-lg-12">
<button type="button" class="btn btn-info" id="commitMatchScore" onclick="commitMatchScore();"
style="display: none;">Commit Final Match Score</button>
<div class="rocket">
<div class="rocket-outline alliance-color">
<div class="inner-rocket">{{template "rocketHalf" dict "startBayId" 14 "vars" $}}</div>
<div class="outer-rocket">{{template "rocketHalf" dict "startBayId" 17 "vars" $}}</div>
</div>
</div>
</div>
{{end}}
{{define "head"}}
<link href="/static/css/scoring_panel.css" rel="stylesheet">
{{end}}
{{define "script"}}
<script>
var alliance = "{{.Alliance}}";
</script>
<script src="/static/js/match_timing.js"></script>
<script src="/static/js/scoring_panel.js"></script>
<script>
var bayMappings = {"cargoShip": [], "rocketNearLeft": [], "rocketNearRight": [], "rocketFarLeft": [],
"rocketFarRight": []};
{{range $mapping := .BayMappings}}
{{if eq $.Alliance "red"}}
bayMappings["{{$mapping.RedElement}}"][{{$mapping.RedIndex}}] = {{$mapping.BayId}};
{{else}}
bayMappings["{{$mapping.BlueElement}}"][{{$mapping.BlueIndex}}] = {{$mapping.BayId}};
{{end}}
{{end}}
</script>
{{end}}
{{define "rocketHalf"}}
{{template "bay" dict "id" .startBayId "vars" .vars}}
{{template "bay" dict "id" (add .startBayId 1) "vars" .vars}}
{{template "bay" dict "id" (add .startBayId 2) "vars" .vars}}
{{end}}
{{define "bay"}}
<div id="bay{{.id}}" class="bay" onclick="handleClick('{{(index .vars.BayMappings .id).Shortcut}}');">
<div class="shortcut">{{(index .vars.BayMappings .id).Shortcut}}</div>
<div class="hatch-panel"></div>
<div class="cargo"></div>
</div>
{{end}}

View File

@@ -8,14 +8,49 @@ package web
import (
"fmt"
"github.com/Team254/cheesy-arena/field"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model"
"github.com/Team254/cheesy-arena/websocket"
"github.com/gorilla/mux"
"io"
"log"
"net/http"
"strconv"
)
// Maps a numbered bay on the scoring panel to the field that it represents in the Score model.
type bayMapping struct {
BayId int
Shortcut string
RedElement string
RedIndex int
BlueElement string
BlueIndex int
}
var bayMappings = []*bayMapping{
{0, "q", "rocketNearRight", 2, "rocketFarRight", 2},
{1, "a", "rocketNearRight", 1, "rocketFarRight", 1},
{2, "z", "rocketNearRight", 0, "rocketFarRight", 0},
{3, "w", "rocketNearLeft", 2, "rocketFarLeft", 2},
{4, "s", "rocketNearLeft", 1, "rocketFarLeft", 1},
{5, "x", "rocketNearLeft", 0, "rocketFarLeft", 0},
{6, "e", "cargoShip", 0, "cargoShip", 7},
{7, "d", "cargoShip", 1, "cargoShip", 6},
{8, "c", "cargoShip", 2, "cargoShip", 5},
{9, "v", "cargoShip", 3, "cargoShip", 4},
{10, "b", "cargoShip", 4, "cargoShip", 3},
{11, "n", "cargoShip", 5, "cargoShip", 2},
{12, "j", "cargoShip", 6, "cargoShip", 1},
{13, "i", "cargoShip", 7, "cargoShip", 0},
{14, "o", "rocketFarRight", 2, "rocketNearRight", 2},
{15, "k", "rocketFarRight", 1, "rocketNearRight", 1},
{16, "m", "rocketFarRight", 0, "rocketNearRight", 0},
{17, "p", "rocketFarLeft", 2, "rocketNearLeft", 2},
{18, "l", "rocketFarLeft", 1, "rocketNearLeft", 1},
{19, ",", "rocketFarLeft", 0, "rocketNearLeft", 0},
}
// Renders the scoring interface which enables input of scores in real-time.
func (web *Web) scoringPanelHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
@@ -36,8 +71,9 @@ func (web *Web) scoringPanelHandler(w http.ResponseWriter, r *http.Request) {
}
data := struct {
*model.EventSettings
Alliance string
}{web.arena.EventSettings, alliance}
Alliance string
BayMappings []*bayMapping
}{web.arena.EventSettings, alliance, bayMappings}
err = template.ExecuteTemplate(w, "base_no_navbar", data)
if err != nil {
handleWebErr(w, err)
@@ -57,12 +93,13 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
handleWebErr(w, fmt.Errorf("Invalid alliance '%s'.", alliance))
return
}
var score **field.RealtimeScore
var realtimeScore **field.RealtimeScore
if alliance == "red" {
score = &web.arena.RedRealtimeScore
realtimeScore = &web.arena.RedRealtimeScore
} else {
score = &web.arena.BlueRealtimeScore
realtimeScore = &web.arena.BlueRealtimeScore
}
score := &(*realtimeScore).CurrentScore
ws, err := websocket.NewWebsocket(w, r)
if err != nil {
@@ -72,12 +109,12 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
defer ws.Close()
// Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine.
go ws.HandleNotifiers(web.arena.MatchTimeNotifier, web.arena.RealtimeScoreNotifier,
go ws.HandleNotifiers(web.arena.MatchLoadNotifier, web.arena.MatchTimeNotifier, web.arena.RealtimeScoreNotifier,
web.arena.ReloadDisplaysNotifier)
// Loop, waiting for commands and responding to them, until the client closes the connection.
for {
messageType, _, err := ws.Read()
command, _, err := ws.Read()
if err != nil {
if err == io.EOF {
// Client has closed the connection; nothing to do here.
@@ -88,39 +125,120 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
}
scoreChanged := false
switch messageType {
case "\r":
if (web.arena.MatchState != field.PreMatch && web.arena.MatchState != field.TimeoutActive &&
web.arena.MatchState != field.PostTimeout || web.arena.CurrentMatch.Type == "test") &&
!(*score).AutoCommitted {
(*score).AutoCommitted = true
scoreChanged = true
}
case "a":
if (*score).AutoCommitted {
(*score).AutoCommitted = false
scoreChanged = true
}
case "commitMatch":
if command == "commitMatch" {
if web.arena.MatchState != field.PostMatch {
// Don't allow committing the score until the match is over.
ws.WriteError("Cannot commit score: Match is not over.")
continue
}
if !(*score).TeleopCommitted {
(*score).AutoCommitted = true
(*score).TeleopCommitted = true
if !(*realtimeScore).TeleopCommitted {
(*realtimeScore).TeleopCommitted = true
web.arena.ScoringStatusNotifier.Notify()
scoreChanged = true
}
default:
// Unknown keypress; just swallow the message without doing anything.
continue
} else if number, err := strconv.Atoi(command); err == nil && number >= 1 && number <= 9 {
// Handle per-robot scoring fields.
if number <= 3 {
index := number - 1
score.RobotStartLevels[index]++
if score.RobotStartLevels[index] == 4 {
score.RobotStartLevels[index] = 0
}
scoreChanged = true
} else if number <= 6 && web.arena.MatchState != field.PreMatch {
index := number - 4
score.SandstormBonuses[index] =
!score.SandstormBonuses[index]
scoreChanged = true
} else if web.arena.MatchState != field.PreMatch {
index := number - 7
score.RobotEndLevels[index]++
if score.RobotEndLevels[index] == 4 {
score.RobotEndLevels[index] = 0
}
scoreChanged = true
}
} else {
// Handle cargo bays.
var bayMapping *bayMapping
for _, mapping := range bayMappings {
if mapping.Shortcut == command {
bayMapping = mapping
break
}
}
if bayMapping != nil {
element := bayMapping.RedElement
index := bayMapping.RedIndex
if alliance == "blue" {
element = bayMapping.BlueElement
index = bayMapping.BlueIndex
}
switch element {
case "cargoShip":
scoreChanged = web.toggleCargoShipBay(&score.CargoBays[index], index)
case "rocketNearLeft":
scoreChanged = web.toggleRocketBay(&score.RocketNearLeftBays[index])
case "rocketNearRight":
scoreChanged = web.toggleRocketBay(&score.RocketNearRightBays[index])
case "rocketFarLeft":
scoreChanged = web.toggleRocketBay(&score.RocketFarLeftBays[index])
case "rocketFarRight":
scoreChanged = web.toggleRocketBay(&score.RocketFarRightBays[index])
}
}
}
if scoreChanged {
if web.arena.MatchState == field.PreMatch {
score.CargoBaysPreMatch = score.CargoBays
}
web.arena.RealtimeScoreNotifier.Notify()
}
}
}
// Advances the given cargo ship bay through the states applicable to the current status of the field.
func (web *Web) toggleCargoShipBay(bay *game.BayStatus, index int) bool {
if (index == 3 || index == 4) && web.arena.MatchState == field.PreMatch {
// Only the side bays can be preloaded.
return false
}
if web.arena.MatchState == field.PreMatch {
*bay++
if *bay == game.BayHatchCargo {
// Skip the hatch+cargo state pre-match as it is invalid.
*bay = game.BayCargo
} else if *bay > game.BayCargo {
*bay = game.BayEmpty
}
} else {
if *bay == game.BayCargo {
// If the bay was pre-loaded with cargo, go immediately to hatch+cargo during first toggle.
*bay = game.BayHatchCargo
} else {
*bay++
if *bay == game.BayCargo {
// Skip the cargo-only state during the match as it can't stay in on its own.
*bay = game.BayEmpty
}
}
}
return true
}
// Advances the given rocket bay through the states applicable to the current status of the field.
func (web *Web) toggleRocketBay(bay *game.BayStatus) bool {
if web.arena.MatchState != field.PreMatch {
*bay++
if *bay == game.BayCargo {
// Skip the cargo-only state as it's not applicable to rocket bays.
*bay = game.BayEmpty
}
return true
}
return false
}

View File

@@ -42,8 +42,10 @@ func TestScoringPanelWebsocket(t *testing.T) {
blueWs := websocket.NewTestWebsocket(blueConn)
// Should receive a score update right after connection.
readWebsocketType(t, redWs, "matchLoad")
readWebsocketType(t, redWs, "matchTime")
readWebsocketType(t, redWs, "realtimeScore")
readWebsocketType(t, blueWs, "matchLoad")
readWebsocketType(t, blueWs, "matchTime")
readWebsocketType(t, blueWs, "realtimeScore")