mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Added tracking of score commit status in match play screen.
This commit is contained in:
2
arena.go
2
arena.go
@@ -69,6 +69,7 @@ type Arena struct {
|
||||
matchTimeNotifier *Notifier
|
||||
robotStatusNotifier *Notifier
|
||||
matchLoadTeamsNotifier *Notifier
|
||||
scoringStatusNotifier *Notifier
|
||||
realtimeScoreNotifier *Notifier
|
||||
scorePostedNotifier *Notifier
|
||||
audienceDisplayNotifier *Notifier
|
||||
@@ -101,6 +102,7 @@ func (arena *Arena) Setup() {
|
||||
arena.matchTimeNotifier = NewNotifier()
|
||||
arena.robotStatusNotifier = NewNotifier()
|
||||
arena.matchLoadTeamsNotifier = NewNotifier()
|
||||
arena.scoringStatusNotifier = NewNotifier()
|
||||
arena.realtimeScoreNotifier = NewNotifier()
|
||||
arena.scorePostedNotifier = NewNotifier()
|
||||
arena.audienceDisplayNotifier = NewNotifier()
|
||||
|
||||
@@ -581,6 +581,7 @@ func ScoringDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Commit last cycle.
|
||||
(*score).CurrentScore.Cycles = append((*score).CurrentScore.Cycles, (*score).CurrentCycle)
|
||||
}
|
||||
mainArena.scoringStatusNotifier.Notify(nil)
|
||||
case "undo":
|
||||
if !(*score).AutoCommitted && len((*score).undoAutoScores) > 0 {
|
||||
(*score).CurrentScore = (*score).undoAutoScores[len((*score).undoAutoScores)-1]
|
||||
@@ -739,6 +740,7 @@ func RefereeDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
case "commitMatch":
|
||||
mainArena.redRealtimeScore.FoulsCommitted = true
|
||||
mainArena.blueRealtimeScore.FoulsCommitted = true
|
||||
mainArena.scoringStatusNotifier.Notify(nil)
|
||||
default:
|
||||
websocket.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
|
||||
continue
|
||||
|
||||
@@ -159,8 +159,11 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(robotStatusListener)
|
||||
audienceDisplayListener := mainArena.audienceDisplayNotifier.Listen()
|
||||
defer close(audienceDisplayListener)
|
||||
scoringStatusListener := mainArena.scoringStatusNotifier.Listen()
|
||||
defer close(scoringStatusListener)
|
||||
|
||||
// Send the various notifications immediately upon connection.
|
||||
var data interface{}
|
||||
err = websocket.Write("status", mainArena)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
@@ -171,7 +174,7 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
data := MatchTimeMessage{mainArena.MatchState, int(mainArena.lastMatchTimeSec)}
|
||||
data = MatchTimeMessage{mainArena.MatchState, int(mainArena.lastMatchTimeSec)}
|
||||
err = websocket.Write("matchTime", data)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
@@ -182,6 +185,17 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
data = struct {
|
||||
RefereeScoreReady bool
|
||||
RedScoreReady bool
|
||||
BlueScoreReady bool
|
||||
}{mainArena.redRealtimeScore.FoulsCommitted && mainArena.blueRealtimeScore.FoulsCommitted,
|
||||
mainArena.redRealtimeScore.TeleopCommitted, mainArena.blueRealtimeScore.TeleopCommitted}
|
||||
err = websocket.Write("scoringStatus", data)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Spin off a goroutine to listen for notifications and pass them on through the websocket.
|
||||
go func() {
|
||||
@@ -207,6 +221,17 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
messageType = "setAudienceDisplay"
|
||||
message = mainArena.audienceDisplayScreen
|
||||
case _, ok := <-scoringStatusListener:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
messageType = "scoringStatus"
|
||||
message = struct {
|
||||
RefereeScoreReady bool
|
||||
RedScoreReady bool
|
||||
BlueScoreReady bool
|
||||
}{mainArena.redRealtimeScore.FoulsCommitted && mainArena.blueRealtimeScore.FoulsCommitted,
|
||||
mainArena.redRealtimeScore.TeleopCommitted, mainArena.blueRealtimeScore.TeleopCommitted}
|
||||
}
|
||||
err = websocket.Write(messageType, message)
|
||||
if err != nil {
|
||||
|
||||
@@ -173,6 +173,7 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
|
||||
readWebsocketType(t, ws, "matchTiming")
|
||||
readWebsocketType(t, ws, "matchTime")
|
||||
readWebsocketType(t, ws, "setAudienceDisplay")
|
||||
readWebsocketType(t, ws, "scoringStatus")
|
||||
|
||||
// Test that a server-side error is communicated to the client.
|
||||
ws.Write("nonexistenttype", nil)
|
||||
@@ -257,6 +258,7 @@ func TestMatchPlayWebsocketNotifications(t *testing.T) {
|
||||
readWebsocketType(t, ws, "matchTiming")
|
||||
readWebsocketType(t, ws, "matchTime")
|
||||
readWebsocketType(t, ws, "setAudienceDisplay")
|
||||
readWebsocketType(t, ws, "scoringStatus")
|
||||
|
||||
mainArena.AllianceStations["R1"].Bypass = true
|
||||
mainArena.AllianceStations["R2"].Bypass = true
|
||||
@@ -273,6 +275,8 @@ func TestMatchPlayWebsocketNotifications(t *testing.T) {
|
||||
assert.Equal(t, 0, matchTime.MatchTimeSec)
|
||||
_, ok := messages["setAudienceDisplay"]
|
||||
assert.True(t, ok)
|
||||
mainArena.scoringStatusNotifier.Notify(nil)
|
||||
readWebsocketType(t, ws, "scoringStatus")
|
||||
|
||||
// Should get a tick notification when an integer second threshold is crossed.
|
||||
mainArena.matchStartTime = time.Now().Add(-time.Second + 10*time.Millisecond) // Not crossed yet
|
||||
|
||||
@@ -68,6 +68,12 @@
|
||||
.btn-match-play {
|
||||
width: 165px;
|
||||
}
|
||||
.label-scoring {
|
||||
background-color: #e66;
|
||||
}
|
||||
.label-scoring[data-ready=true] {
|
||||
background-color: #0c6;
|
||||
}
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// Client-side logic for the match play page.
|
||||
|
||||
var websocket;
|
||||
var scoreIsReady;
|
||||
|
||||
var substituteTeam = function(team, position) {
|
||||
websocket.send("substituteTeam", { team: parseInt(team), position: position })
|
||||
@@ -33,6 +34,17 @@ var setAudienceDisplay = function() {
|
||||
websocket.send("setAudienceDisplay", $("input[name=audienceDisplay]:checked").val());
|
||||
};
|
||||
|
||||
var confirmCommit = function(isReplay) {
|
||||
if (isReplay || !scoreIsReady) {
|
||||
// Show the appropriate message(s) in the confirmation dialog.
|
||||
$("#confirmCommitReplay").css("display", isReplay ? "block" : "none");
|
||||
$("#confirmCommitNotReady").css("display", scoreIsReady ? "none" : "block");
|
||||
$("#confirmCommitResults").modal("show");
|
||||
} else {
|
||||
commitResults();
|
||||
}
|
||||
};
|
||||
|
||||
var handleStatus = function(data) {
|
||||
// Update the team status view.
|
||||
$.each(data.AllianceStations, function(station, stationStatus) {
|
||||
@@ -99,6 +111,13 @@ var handleSetAudienceDisplay = function(data) {
|
||||
$("input[name=audienceDisplay][value=" + data + "]").prop("checked", true);
|
||||
};
|
||||
|
||||
var handleScoringStatus = function(data) {
|
||||
scoreIsReady = data.RefereeScoreReady && data.RedScoreReady && data.BlueScoreReady;
|
||||
$("#refereeScoreStatus").attr("data-ready", data.RefereeScoreReady);
|
||||
$("#redScoreStatus").attr("data-ready", data.RedScoreReady);
|
||||
$("#blueScoreStatus").attr("data-ready", data.BlueScoreReady);
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Activate tooltips above the status headers.
|
||||
$("[data-toggle=tooltip]").tooltip({"placement": "top"});
|
||||
@@ -108,6 +127,7 @@ $(function() {
|
||||
status: function(event) { handleStatus(event.data); },
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); },
|
||||
matchTime: function(event) { handleMatchTime(event.data); },
|
||||
setAudienceDisplay: function(event) { handleSetAudienceDisplay(event.data); }
|
||||
setAudienceDisplay: function(event) { handleSetAudienceDisplay(event.data); },
|
||||
scoringStatus: function(event) { handleScoringStatus(event.data); }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,8 +87,7 @@
|
||||
Abort Match
|
||||
</button>
|
||||
<button type="button" id="commitResults" class="btn btn-info btn-lg btn-match-play"
|
||||
onclick="{{if .IsReplay}}$('#confirmCommitResults').modal('show');{{else}}commitResults();{{end}}"
|
||||
disabled>
|
||||
onclick="confirmCommit({{.IsReplay}});" disabled>
|
||||
Commit Results
|
||||
</button>
|
||||
<button type="button" id="discardResults" class="btn btn-danger btn-lg btn-match-play"
|
||||
@@ -98,35 +97,43 @@
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-lg-3 well">
|
||||
Audience Display
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="blank" onclick="setAudienceDisplay();">Blank
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="intro" onclick="setAudienceDisplay();">Intro
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="match" onclick="setAudienceDisplay();">Match
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="score" onclick="setAudienceDisplay();">Final Score
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="logo" onclick="setAudienceDisplay();">Logo
|
||||
</label>
|
||||
<div class="col-lg-6 well">
|
||||
<div class="col-lg-6">
|
||||
Audience Display
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="blank" onclick="setAudienceDisplay();">Blank
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="intro" onclick="setAudienceDisplay();">Intro
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="match" onclick="setAudienceDisplay();">Match
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="score" onclick="setAudienceDisplay();">Final Score
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="audienceDisplay" value="logo" onclick="setAudienceDisplay();">Logo
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<p>Scoring Status</p>
|
||||
<p><span class="label label-scoring" id="refereeScoreStatus">Referee</span><br />
|
||||
<span class="label label-scoring" id="redScoreStatus">Red Scoring</span><br />
|
||||
<span class="label label-scoring" id="blueScoreStatus">Blue Scoring</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,13 +146,15 @@
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>This is a replay of Match {{.Match.DisplayName}}. Are you sure you want to overwrite the previous
|
||||
results?</p>
|
||||
<p id="confirmCommitReplay">This is a replay of Match {{.Match.DisplayName}}. Are you sure you want to
|
||||
overwrite the previous results?</p>
|
||||
<p id="confirmCommitNotReady">Not all scoring sources are ready yet. Are you sure you want to
|
||||
commit the results?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form class="form-horizontal" action="/setup/teams/clear" method="POST">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="discardResults();">Overwrite Results</button>
|
||||
<button type="button" class="btn btn-primary" onclick="commitResults();">Commit Results</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user