Remove game-specific scoring

This commit is contained in:
Ken Schenke
2020-04-14 19:38:14 -05:00
parent 53caa27208
commit f075b7bb8d
59 changed files with 298 additions and 3004 deletions

7
web/alliance_selection.go Normal file → Executable file
View File

@@ -194,13 +194,6 @@ func (web *Web) allianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.
return
}
// Reset yellow cards.
err = tournament.CalculateTeamCards(web.arena.Database, "elimination")
if err != nil {
handleWebErr(w, err)
return
}
// Back up the database.
err = web.arena.Database.Backup(web.arena.EventSettings.Name, "post_alliance_selection")
if err != nil {

View File

@@ -75,7 +75,7 @@ func TestAllianceSelection(t *testing.T) {
assert.Contains(t, recorder.Body.String(), ">110<")
// Finalize alliance selection.
web.arena.Database.CreateTeam(&model.Team{Id: 254, YellowCard: true})
web.arena.Database.CreateTeam(&model.Team{Id: 254})
recorder = web.postHttpResponse("/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 303, recorder.Code)
alliances, err := web.arena.Database.GetAllAlliances()
@@ -88,8 +88,6 @@ func TestAllianceSelection(t *testing.T) {
matches, err := web.arena.Database.GetMatchesByType("elimination")
assert.Nil(t, err)
assert.Equal(t, 6, len(matches))
team, _ := web.arena.Database.GetTeamById(254)
assert.False(t, team.YellowCard)
}
func TestAllianceSelectionErrors(t *testing.T) {

4
web/api.go Normal file → Executable file
View File

@@ -54,8 +54,8 @@ func (web *Web) matchesApiHandler(w http.ResponseWriter, r *http.Request) {
var matchResultWithSummary *MatchResultWithSummary
if matchResult != nil {
matchResultWithSummary = &MatchResultWithSummary{MatchResult: *matchResult}
matchResultWithSummary.RedSummary = matchResult.RedScoreSummary(true)
matchResultWithSummary.BlueSummary = matchResult.BlueScoreSummary(true)
matchResultWithSummary.RedSummary = matchResult.RedScoreSummary()
matchResultWithSummary.BlueSummary = matchResult.BlueScoreSummary()
}
matchesWithResults[i].Result = matchResultWithSummary
}

31
web/match_play.go Normal file → Executable file
View File

@@ -76,10 +76,13 @@ func (web *Web) matchPlayHandler(w http.ResponseWriter, r *http.Request) {
MatchesByType map[string]MatchPlayList
CurrentMatchType string
Match *model.Match
RedScore *game.Score
BlueScore *game.Score
AllowSubstitution bool
IsReplay bool
PlcArmorBlockStatuses map[string]bool
}{web.arena.EventSettings, web.arena.Plc.IsEnabled(), matchesByType, currentMatchType, web.arena.CurrentMatch,
web.arena.RedScore, web.arena.BlueScore,
web.arena.CurrentMatch.ShouldAllowSubstitution(), isReplay, web.arena.Plc.GetArmorBlockStatuses()}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
@@ -177,7 +180,7 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
// Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine.
go ws.HandleNotifiers(web.arena.MatchTimingNotifier, web.arena.ArenaStatusNotifier, web.arena.MatchTimeNotifier,
web.arena.RealtimeScoreNotifier, web.arena.ScoringStatusNotifier, web.arena.AudienceDisplayModeNotifier,
web.arena.RealtimeScoreNotifier, web.arena.AudienceDisplayModeNotifier,
web.arena.AllianceStationDisplayModeNotifier, web.arena.EventStatusNotifier)
// Loop, waiting for commands and responding to them, until the client closes the connection.
@@ -308,6 +311,15 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
ws.WriteError(err.Error())
continue
}
case "updateRealtimeScore":
args := data.(map[string]interface{})
web.arena.BlueScore.AutoPoints = int(args["blueAuto"].(float64))
web.arena.RedScore.AutoPoints = int(args["redAuto"].(float64))
web.arena.BlueScore.TeleopPoints = int(args["blueTeleop"].(float64))
web.arena.RedScore.TeleopPoints = int(args["redTeleop"].(float64))
web.arena.BlueScore.EndgamePoints = int(args["blueEndgame"].(float64))
web.arena.RedScore.EndgamePoints = int(args["redEndgame"].(float64))
web.arena.RealtimeScoreNotifier.Notify()
default:
ws.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
continue
@@ -326,11 +338,6 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchResult, isMatchReviewEdit bool) error {
var updatedRankings game.Rankings
if match.Type == "elimination" {
// Adjust the score if necessary for an elimination DQ.
matchResult.CorrectEliminationScore()
}
if match.Type != "test" {
if matchResult.PlayNumber == 0 {
// Determine the play number for this new match result.
@@ -359,8 +366,8 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
// Update and save the match record to the database.
match.ScoreCommittedAt = time.Now()
redScore := matchResult.RedScoreSummary(true)
blueScore := matchResult.BlueScoreSummary(true)
redScore := matchResult.RedScoreSummary()
blueScore := matchResult.BlueScoreSummary()
if redScore.Score > blueScore.Score {
match.Status = model.RedWonMatch
} else if redScore.Score < blueScore.Score {
@@ -373,11 +380,6 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
return err
}
if match.ShouldUpdateCards() {
// Regenerate the residual yellow cards that teams may carry.
tournament.CalculateTeamCards(web.arena.Database, match.Type)
}
if match.ShouldUpdateRankings() {
// Recalculate all the rankings.
rankings, err := tournament.CalculateRankings(web.arena.Database, isMatchReviewEdit)
@@ -458,8 +460,7 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
func (web *Web) getCurrentMatchResult() *model.MatchResult {
return &model.MatchResult{MatchId: web.arena.CurrentMatch.Id, MatchType: web.arena.CurrentMatch.Type,
RedScore: &web.arena.RedRealtimeScore.CurrentScore, BlueScore: &web.arena.BlueRealtimeScore.CurrentScore,
RedCards: web.arena.RedRealtimeScore.Cards, BlueCards: web.arena.BlueRealtimeScore.Cards}
RedScore: web.arena.RedScore, BlueScore: web.arena.BlueScore}
}
// Saves the realtime result as the final score for the match currently loaded into the arena.

View File

@@ -9,7 +9,6 @@ import (
"github.com/Team254/cheesy-arena/field"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model"
"github.com/Team254/cheesy-arena/tournament"
"github.com/Team254/cheesy-arena/websocket"
gorillawebsocket "github.com/gorilla/websocket"
"github.com/mitchellh/mapstructure"
@@ -133,7 +132,7 @@ func TestCommitMatch(t *testing.T) {
web.arena.Database.CreateMatch(match)
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueScore = &game.Score{ExitedInitiationLine: [3]bool{true, false, false}}
matchResult.BlueScore = &game.Score{AutoPoints: 10}
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 1, matchResult.PlayNumber)
@@ -142,7 +141,7 @@ func TestCommitMatch(t *testing.T) {
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.RedScore = &game.Score{ExitedInitiationLine: [3]bool{true, false, true}}
matchResult.RedScore = &game.Score{AutoPoints: 20}
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 2, matchResult.PlayNumber)
@@ -175,10 +174,8 @@ func TestCommitEliminationTie(t *testing.T) {
match := &model.Match{Id: 0, Type: "qualification", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5, Blue3: 6}
web.arena.Database.CreateMatch(match)
matchResult := &model.MatchResult{
MatchId: match.Id,
RedScore: &game.Score{
TeleopCellsInner: [4]int{1, 2, 0, 0},
Fouls: []game.Foul{{RuleId: 1}, {RuleId: 2}, {RuleId: 4}}},
MatchId: match.Id,
RedScore: &game.Score{},
BlueScore: &game.Score{},
}
err := web.commitMatchScore(match, matchResult, true)
@@ -192,54 +189,6 @@ func TestCommitEliminationTie(t *testing.T) {
assert.Equal(t, model.TieMatch, match.Status) // No elimination tiebreakers.
}
func TestCommitCards(t *testing.T) {
web := setupTestWeb(t)
// Check that a yellow card sticks with a team.
team := &model.Team{Id: 5}
web.arena.Database.CreateTeam(team)
match := &model.Match{Id: 0, Type: "qualification", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5, Blue3: 6}
web.arena.Database.CreateMatch(match)
matchResult := model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueCards = map[string]string{"5": "yellow"}
err := web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.True(t, team.YellowCard)
// Check that editing a match result removes a yellow card from a team.
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.False(t, team.YellowCard)
// Check that a red card causes a yellow card to stick with a team.
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueCards = map[string]string{"5": "red"}
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
team, _ = web.arena.Database.GetTeamById(5)
assert.True(t, team.YellowCard)
// Check that a red card in eliminations zeroes out the score.
tournament.CreateTestAlliances(web.arena.Database, 2)
match.Type = "elimination"
match.ElimRedAlliance = 1
match.ElimBlueAlliance = 2
web.arena.Database.SaveMatch(match)
matchResult = model.BuildTestMatchResult(match.Id, 10)
matchResult.MatchType = match.Type
matchResult.RedCards = map[string]string{"1": "red"}
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
assert.Equal(t, 0, matchResult.RedScoreSummary(true).Score)
assert.NotEqual(t, 0, matchResult.BlueScoreSummary(true).Score)
}
func TestMatchPlayWebsocketCommands(t *testing.T) {
web := setupTestWeb(t)
@@ -255,7 +204,6 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
readWebsocketType(t, ws, "arenaStatus")
readWebsocketType(t, ws, "matchTime")
readWebsocketType(t, ws, "realtimeScore")
readWebsocketType(t, ws, "scoringStatus")
readWebsocketType(t, ws, "audienceDisplayMode")
readWebsocketType(t, ws, "allianceStationDisplayMode")
readWebsocketType(t, ws, "eventStatus")
@@ -310,12 +258,12 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
readWebsocketType(t, ws, "audienceDisplayMode")
readWebsocketType(t, ws, "allianceStationDisplayMode")
assert.Equal(t, field.PostMatch, web.arena.MatchState)
web.arena.RedRealtimeScore.CurrentScore.TeleopCellsOuter = [4]int{1, 1, 1, 4}
web.arena.BlueRealtimeScore.CurrentScore.ExitedInitiationLine = [3]bool{true, false, true}
web.arena.RedScore.TeleopPoints = 30
web.arena.BlueScore.EndgamePoints = 45
ws.Write("commitResults", nil)
readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay
assert.Equal(t, [4]int{1, 1, 1, 4}, web.arena.SavedMatchResult.RedScore.TeleopCellsOuter)
assert.Equal(t, [3]bool{true, false, true}, web.arena.SavedMatchResult.BlueScore.ExitedInitiationLine)
assert.Equal(t, 30, web.arena.SavedMatchResult.RedScore.TeleopPoints)
assert.Equal(t, 45, web.arena.SavedMatchResult.BlueScore.EndgamePoints)
assert.Equal(t, field.PreMatch, web.arena.MatchState)
ws.Write("discardResults", nil)
readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay
@@ -347,7 +295,6 @@ func TestMatchPlayWebsocketNotifications(t *testing.T) {
readWebsocketType(t, ws, "arenaStatus")
readWebsocketType(t, ws, "matchTime")
readWebsocketType(t, ws, "realtimeScore")
readWebsocketType(t, ws, "scoringStatus")
readWebsocketType(t, ws, "audienceDisplayMode")
readWebsocketType(t, ws, "allianceStationDisplayMode")
readWebsocketType(t, ws, "eventStatus")
@@ -376,8 +323,6 @@ func TestMatchPlayWebsocketNotifications(t *testing.T) {
assert.Equal(t, true, statusReceived)
assert.Equal(t, field.AutoPeriod, matchTime.MatchState)
assert.Equal(t, 3, matchTime.MatchTimeSec)
web.arena.ScoringStatusNotifier.Notify()
readWebsocketType(t, ws, "scoringStatus")
// Should get a tick notification when an integer second threshold is crossed.
web.arena.MatchStartTime = time.Now().Add(-time.Second - 10*time.Millisecond) // Crossed

17
web/match_review.go Normal file → Executable file
View File

@@ -7,7 +7,6 @@ package web
import (
"fmt"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model"
"github.com/gorilla/mux"
"net/http"
@@ -92,8 +91,7 @@ func (web *Web) matchReviewEditGetHandler(w http.ResponseWriter, r *http.Request
*model.EventSettings
Match *model.Match
MatchResultJson *model.MatchResultDb
Rules map[int]*game.Rule
}{web.arena.EventSettings, match, matchResultJson, game.GetAllRules()}
}{web.arena.EventSettings, match, matchResultJson}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
@@ -115,8 +113,7 @@ func (web *Web) matchReviewEditPostHandler(w http.ResponseWriter, r *http.Reques
matchResultJson := model.MatchResultDb{Id: matchResult.Id, MatchId: match.Id, PlayNumber: matchResult.PlayNumber,
MatchType: matchResult.MatchType, RedScoreJson: r.PostFormValue("redScoreJson"),
BlueScoreJson: r.PostFormValue("blueScoreJson"), RedCardsJson: r.PostFormValue("redCardsJson"),
BlueCardsJson: r.PostFormValue("blueCardsJson")}
BlueScoreJson: r.PostFormValue("blueScoreJson")}
// Deserialize the JSON using the same mechanism as to store scoring information in the database.
matchResult, err = matchResultJson.Deserialize()
@@ -127,10 +124,8 @@ func (web *Web) matchReviewEditPostHandler(w http.ResponseWriter, r *http.Reques
if isCurrent {
// If editing the current match, just save it back to memory.
web.arena.RedRealtimeScore.CurrentScore = *matchResult.RedScore
web.arena.BlueRealtimeScore.CurrentScore = *matchResult.BlueScore
web.arena.RedRealtimeScore.Cards = matchResult.RedCards
web.arena.BlueRealtimeScore.Cards = matchResult.BlueCards
*web.arena.RedScore = *matchResult.RedScore
*web.arena.BlueScore = *matchResult.BlueScore
http.Redirect(w, r, "/match_play", 303)
} else {
@@ -193,8 +188,8 @@ func (web *Web) buildMatchReviewList(matchType string) ([]MatchReviewListItem, e
return []MatchReviewListItem{}, err
}
if matchResult != nil {
matchReviewList[i].RedScore = matchResult.RedScoreSummary(true).Score
matchReviewList[i].BlueScore = matchResult.BlueScoreSummary(true).Score
matchReviewList[i].RedScore = matchResult.RedScoreSummary().Score
matchReviewList[i].BlueScore = matchResult.BlueScoreSummary().Score
}
switch match.Status {
case model.RedWonMatch:

View File

@@ -49,8 +49,8 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
recorder := web.getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), ">QF4-3<")
assert.Contains(t, recorder.Body.String(), ">217<") // The red score
assert.Contains(t, recorder.Body.String(), ">252<") // The blue score
assert.Contains(t, recorder.Body.String(), ">155<") // The red score
assert.Contains(t, recorder.Body.String(), ">80<") // The blue score
// Check response for non-existent match.
recorder = web.getHttpResponse(fmt.Sprintf("/match_review/%d/edit", 12345))
@@ -62,8 +62,8 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), " QF4-3 ")
// Update the score to something else.
postBody := "redScoreJson={\"EndgameStatuses\":[0,2,1]}&blueScoreJson={\"AutoCellsOuter\":[3, 4]," +
"\"Fouls\":[{\"TeamId\":973,\"RuleId\":1}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
postBody := "redScoreJson={\"AutoPoints\":45,\"TeleopPoints\":80,\"EndgamePoints\":10}" +
"&blueScoreJson={\"AutoPoints\":15,\"TeleopPoints\":60,\"EndgamePoints\":50}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)
@@ -71,8 +71,8 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
recorder = web.getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), ">QF4-3<")
assert.Contains(t, recorder.Body.String(), ">33<") // The red score
assert.Contains(t, recorder.Body.String(), ">28<") // The blue score
assert.Contains(t, recorder.Body.String(), ">135<") // The red score
assert.Contains(t, recorder.Body.String(), ">125<") // The blue score
}
func TestMatchReviewCreateNewResult(t *testing.T) {
@@ -94,8 +94,8 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), " QF4-3 ")
// Update the score to something else.
postBody := "redScoreJson={\"TeleopCellsBottom\":[5,1,7,2]}&blueScoreJson={\"TeleopCellsInner\":[2,2,2,2]," +
"\"Fouls\":[{\"TeamId\":973,\"RuleId\":1}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
postBody := "redScoreJson={\"AutoPoints\":10,\"TeleopPoints\":20,\"EndgamePoints\":30}" +
"&blueScoreJson={\"AutoPoints\":40,\"TeleopPoints\":50,\"EndgamePoints\":60}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)
@@ -103,6 +103,6 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
recorder = web.getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), ">QF4-3<")
assert.Contains(t, recorder.Body.String(), ">18<") // The red score
assert.Contains(t, recorder.Body.String(), ">24<") // The blue score
assert.Contains(t, recorder.Body.String(), ">60<") // The red score
assert.Contains(t, recorder.Body.String(), ">150<") // The blue score
}

View File

@@ -1,225 +0,0 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web handlers for the referee interface.
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/mitchellh/mapstructure"
"io"
"log"
"net/http"
"strconv"
)
// Renders the referee interface for assigning fouls.
func (web *Web) refereePanelHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
template, err := web.parseFiles("templates/referee_panel.html")
if err != nil {
handleWebErr(w, err)
return
}
match := web.arena.CurrentMatch
matchType := match.CapitalizedType()
red1 := web.arena.AllianceStations["R1"].Team
if red1 == nil {
red1 = &model.Team{}
}
red2 := web.arena.AllianceStations["R2"].Team
if red2 == nil {
red2 = &model.Team{}
}
red3 := web.arena.AllianceStations["R3"].Team
if red3 == nil {
red3 = &model.Team{}
}
blue1 := web.arena.AllianceStations["B1"].Team
if blue1 == nil {
blue1 = &model.Team{}
}
blue2 := web.arena.AllianceStations["B2"].Team
if blue2 == nil {
blue2 = &model.Team{}
}
blue3 := web.arena.AllianceStations["B3"].Team
if blue3 == nil {
blue3 = &model.Team{}
}
data := struct {
*model.EventSettings
MatchType string
MatchDisplayName string
Red1 *model.Team
Red2 *model.Team
Red3 *model.Team
Blue1 *model.Team
Blue2 *model.Team
Blue3 *model.Team
RedFouls []game.Foul
BlueFouls []game.Foul
RedCards map[string]string
BlueCards map[string]string
Rules map[int]*game.Rule
EntryEnabled bool
}{web.arena.EventSettings, matchType, match.DisplayName, red1, red2, red3, blue1, blue2, blue3,
web.arena.RedRealtimeScore.CurrentScore.Fouls, web.arena.BlueRealtimeScore.CurrentScore.Fouls,
web.arena.RedRealtimeScore.Cards, web.arena.BlueRealtimeScore.Cards, game.GetAllRules(),
!(web.arena.RedRealtimeScore.FoulsCommitted && web.arena.BlueRealtimeScore.FoulsCommitted)}
err = template.ExecuteTemplate(w, "referee_panel.html", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// The websocket endpoint for the refereee interface client to send control commands and receive status updates.
func (web *Web) refereePanelWebsocketHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
ws, err := websocket.NewWebsocket(w, r)
if err != nil {
handleWebErr(w, err)
return
}
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.MatchLoadNotifier, web.arena.ReloadDisplaysNotifier)
// Loop, waiting for commands and responding to them, until the client closes the connection.
for {
messageType, data, err := ws.Read()
if err != nil {
if err == io.EOF {
// Client has closed the connection; nothing to do here.
return
}
log.Println(err)
return
}
switch messageType {
case "addFoul":
args := struct {
Alliance string
TeamId int
RuleId int
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
ws.WriteError(err.Error())
continue
}
// Add the foul to the correct alliance's list.
foul := game.Foul{RuleId: args.RuleId, TeamId: args.TeamId, TimeInMatchSec: web.arena.MatchTimeSec()}
if args.Alliance == "red" {
web.arena.RedRealtimeScore.CurrentScore.Fouls =
append(web.arena.RedRealtimeScore.CurrentScore.Fouls, foul)
} else {
web.arena.BlueRealtimeScore.CurrentScore.Fouls =
append(web.arena.BlueRealtimeScore.CurrentScore.Fouls, foul)
}
web.arena.RealtimeScoreNotifier.Notify()
case "deleteFoul":
args := struct {
Alliance string
TeamId int
RuleId int
TimeInMatchSec float64
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
ws.WriteError(err.Error())
continue
}
// Remove the foul from the correct alliance's list.
deleteFoul := game.Foul{RuleId: args.RuleId, TeamId: args.TeamId, TimeInMatchSec: args.TimeInMatchSec}
var fouls *[]game.Foul
if args.Alliance == "red" {
fouls = &web.arena.RedRealtimeScore.CurrentScore.Fouls
} else {
fouls = &web.arena.BlueRealtimeScore.CurrentScore.Fouls
}
for i, foul := range *fouls {
if foul == deleteFoul {
*fouls = append((*fouls)[:i], (*fouls)[i+1:]...)
break
}
}
web.arena.RealtimeScoreNotifier.Notify()
case "card":
args := struct {
Alliance string
TeamId int
Card string
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
ws.WriteError(err.Error())
continue
}
// Set the card in the correct alliance's score.
var cards map[string]string
if args.Alliance == "red" {
cards = web.arena.RedRealtimeScore.Cards
} else {
cards = web.arena.BlueRealtimeScore.Cards
}
cards[strconv.Itoa(args.TeamId)] = args.Card
continue
case "signalVolunteers":
if web.arena.MatchState != field.PostMatch {
// Don't allow clearing the field until the match is over.
continue
}
web.arena.FieldVolunteers = true
continue // Don't reload.
case "signalReset":
if web.arena.MatchState != field.PostMatch {
// Don't allow clearing the field until the match is over.
continue
}
web.arena.FieldReset = true
web.arena.AllianceStationDisplayMode = "fieldReset"
web.arena.AllianceStationDisplayModeNotifier.Notify()
continue // Don't reload.
case "commitMatch":
if web.arena.MatchState != field.PostMatch {
// Don't allow committing the fouls until the match is over.
continue
}
web.arena.RedRealtimeScore.FoulsCommitted = true
web.arena.BlueRealtimeScore.FoulsCommitted = true
web.arena.FieldReset = true
web.arena.AllianceStationDisplayMode = "fieldReset"
web.arena.AllianceStationDisplayModeNotifier.Notify()
web.arena.ScoringStatusNotifier.Notify()
default:
ws.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
continue
}
// Force a reload of the client to render the updated foul list.
err = ws.WriteNotifier(web.arena.ReloadDisplaysNotifier)
if err != nil {
log.Println(err)
return
}
}
}

View File

@@ -1,119 +0,0 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package web
import (
"github.com/Team254/cheesy-arena/field"
"github.com/Team254/cheesy-arena/websocket"
gorillawebsocket "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestRefereePanel(t *testing.T) {
web := setupTestWeb(t)
recorder := web.getHttpResponse("/panels/referee")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "Referee Panel - Untitled Event - Cheesy Arena")
}
func TestRefereePanelWebsocket(t *testing.T) {
web := setupTestWeb(t)
server, wsUrl := web.startTestServer()
defer server.Close()
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/panels/referee/websocket", nil)
assert.Nil(t, err)
defer conn.Close()
ws := websocket.NewTestWebsocket(conn)
// Should get a few status updates right after connection.
readWebsocketType(t, ws, "matchLoad")
// Test foul addition.
foulData := struct {
Alliance string
TeamId int
RuleId int
TimeInMatchSec float64
}{"red", 256, 1, 0}
ws.Write("addFoul", foulData)
foulData.TeamId = 359
foulData.RuleId = 3
ws.Write("addFoul", foulData)
foulData.Alliance = "blue"
foulData.TeamId = 1680
ws.Write("addFoul", foulData)
readWebsocketType(t, ws, "reload")
readWebsocketType(t, ws, "reload")
readWebsocketType(t, ws, "reload")
if assert.Equal(t, 2, len(web.arena.RedRealtimeScore.CurrentScore.Fouls)) {
assert.Equal(t, 256, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].TeamId)
assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].RuleId)
assert.Equal(t, 0.0, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].TimeInMatchSec)
assert.Equal(t, 359, web.arena.RedRealtimeScore.CurrentScore.Fouls[1].TeamId)
assert.Equal(t, 3, web.arena.RedRealtimeScore.CurrentScore.Fouls[1].RuleId)
}
if assert.Equal(t, 1, len(web.arena.BlueRealtimeScore.CurrentScore.Fouls)) {
assert.Equal(t, 1680, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].TeamId)
assert.Equal(t, 3, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].RuleId)
assert.Equal(t, 0.0, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].TimeInMatchSec)
}
assert.False(t, web.arena.RedRealtimeScore.FoulsCommitted)
assert.False(t, web.arena.BlueRealtimeScore.FoulsCommitted)
// Test foul deletion.
ws.Write("deleteFoul", foulData)
readWebsocketType(t, ws, "reload")
assert.Equal(t, 0, len(web.arena.BlueRealtimeScore.CurrentScore.Fouls))
foulData.Alliance = "red"
foulData.TeamId = 359
foulData.TimeInMatchSec = 29 // Make it not match.
ws.Write("deleteFoul", foulData)
readWebsocketType(t, ws, "reload")
assert.Equal(t, 2, len(web.arena.RedRealtimeScore.CurrentScore.Fouls))
foulData.TimeInMatchSec = 0
ws.Write("deleteFoul", foulData)
readWebsocketType(t, ws, "reload")
assert.Equal(t, 1, len(web.arena.RedRealtimeScore.CurrentScore.Fouls))
// Test card setting.
cardData := struct {
Alliance string
TeamId int
Card string
}{"red", 256, "yellow"}
ws.Write("card", cardData)
cardData.Alliance = "blue"
cardData.TeamId = 1680
cardData.Card = "red"
ws.Write("card", cardData)
time.Sleep(time.Millisecond * 10) // Allow some time for the command to be processed.
if assert.Equal(t, 1, len(web.arena.RedRealtimeScore.Cards)) {
assert.Equal(t, "yellow", web.arena.RedRealtimeScore.Cards["256"])
}
if assert.Equal(t, 1, len(web.arena.BlueRealtimeScore.Cards)) {
assert.Equal(t, "red", web.arena.BlueRealtimeScore.Cards["1680"])
}
// Test field reset and match committing.
web.arena.MatchState = field.PostMatch
ws.Write("signalReset", nil)
time.Sleep(time.Millisecond * 10)
assert.Equal(t, "fieldReset", web.arena.AllianceStationDisplayMode)
assert.False(t, web.arena.RedRealtimeScore.FoulsCommitted)
assert.False(t, web.arena.BlueRealtimeScore.FoulsCommitted)
web.arena.AllianceStationDisplayMode = "logo"
ws.Write("commitMatch", nil)
readWebsocketType(t, ws, "reload")
assert.Equal(t, "fieldReset", web.arena.AllianceStationDisplayMode)
assert.True(t, web.arena.RedRealtimeScore.FoulsCommitted)
assert.True(t, web.arena.BlueRealtimeScore.FoulsCommitted)
// Should refresh the page when the next match is loaded.
web.arena.MatchLoadNotifier.Notify()
readWebsocketType(t, ws, "matchLoad")
}

2
web/reports.go Normal file → Executable file
View File

@@ -63,7 +63,6 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
pdf.CellFormat(colWidths["Endgame"], rowHeight, "Endgame", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Teleop"], rowHeight, "Teleop", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["W-L-T"], rowHeight, "W-L-T", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["DQ"], rowHeight, "DQ", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Played"], rowHeight, "Played", "1", 1, "C", true, 0, "")
for _, ranking := range rankings {
// Render ranking info row.
@@ -77,7 +76,6 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
pdf.CellFormat(colWidths["Teleop"], rowHeight, strconv.Itoa(ranking.TeleopPoints), "1", 0, "C", false, 0, "")
record := fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties)
pdf.CellFormat(colWidths["W-L-T"], rowHeight, record, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["DQ"], rowHeight, strconv.Itoa(ranking.Disqualifications), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Played"], rowHeight, strconv.Itoa(ranking.Played), "1", 1, "C", false, 0, "")
}

View File

@@ -23,7 +23,7 @@ func TestRankingsCsvReport(t *testing.T) {
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "text/plain", recorder.HeaderMap["Content-Type"][0])
expectedBody := "Rank,TeamId,RankingPoints,AutoPoints,EndgamePoints,TeleopPoints,Wins,Losses,Ties," +
"Disqualifications,Played\n1,254,20,625,90,554,3,2,1,0,10\n2,1114,18,700,625,90,1,3,2,0,10\n\n"
"Played\n1,254,20,625,90,554,3,2,1,10\n2,1114,18,700,625,90,1,3,2,10\n\n"
assert.Equal(t, expectedBody, recorder.Body.String())
}

View File

@@ -1,237 +0,0 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web handlers for scoring interface.
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"
"strings"
)
// 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) {
return
}
vars := mux.Vars(r)
alliance := vars["alliance"]
if alliance != "red" && alliance != "blue" {
handleWebErr(w, fmt.Errorf("Invalid alliance '%s'.", alliance))
return
}
template, err := web.parseFiles("templates/scoring_panel.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
data := struct {
*model.EventSettings
PlcIsEnabled bool
Alliance string
}{web.arena.EventSettings, web.arena.Plc.IsEnabled(), alliance}
err = template.ExecuteTemplate(w, "base_no_navbar", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// The websocket endpoint for the scoring interface client to send control commands and receive status updates.
func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
vars := mux.Vars(r)
alliance := vars["alliance"]
if alliance != "red" && alliance != "blue" {
handleWebErr(w, fmt.Errorf("Invalid alliance '%s'.", alliance))
return
}
var realtimeScore **field.RealtimeScore
if alliance == "red" {
realtimeScore = &web.arena.RedRealtimeScore
} else {
realtimeScore = &web.arena.BlueRealtimeScore
}
ws, err := websocket.NewWebsocket(w, r)
if err != nil {
handleWebErr(w, err)
return
}
defer ws.Close()
web.arena.ScoringPanelRegistry.RegisterPanel(alliance, ws)
web.arena.ScoringStatusNotifier.Notify()
defer web.arena.ScoringStatusNotifier.Notify()
defer web.arena.ScoringPanelRegistry.UnregisterPanel(alliance, ws)
// Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine.
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 {
command, _, err := ws.Read()
if err != nil {
if err == io.EOF {
// Client has closed the connection; nothing to do here.
return
}
log.Println(err)
return
}
score := &(*realtimeScore).CurrentScore
scoreChanged := false
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
}
web.arena.ScoringPanelRegistry.SetScoreCommitted(alliance, ws)
web.arena.ScoringStatusNotifier.Notify()
} else if number, err := strconv.Atoi(command); err == nil && number >= 1 && number <= 6 {
// Handle per-robot scoring fields.
if number <= 3 {
index := number - 1
score.ExitedInitiationLine[index] = !score.ExitedInitiationLine[index]
scoreChanged = true
} else {
index := number - 4
score.EndgameStatuses[index]++
if score.EndgameStatuses[index] == 3 {
score.EndgameStatuses[index] = 0
}
scoreChanged = true
}
} else {
switch strings.ToUpper(command) {
case "Q":
if decrementGoal(score.AutoCellsInner[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "A":
if decrementGoal(score.AutoCellsOuter[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "Z":
if decrementGoal(score.AutoCellsBottom[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "W":
if incrementGoal(score.AutoCellsInner[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "S":
if incrementGoal(score.AutoCellsOuter[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "X":
if incrementGoal(score.AutoCellsBottom[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "E":
if decrementGoal(score.TeleopCellsInner[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "D":
if decrementGoal(score.TeleopCellsOuter[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "C":
if decrementGoal(score.TeleopCellsBottom[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "R":
if incrementGoal(score.TeleopCellsInner[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "F":
if incrementGoal(score.TeleopCellsOuter[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "V":
if incrementGoal(score.TeleopCellsBottom[:],
score.CellCountingStage(web.arena.MatchState >= field.TeleopPeriod)) {
scoreChanged = true
}
case "O":
if score.ControlPanelStatus >= game.ControlPanelRotation {
score.ControlPanelStatus = game.ControlPanelNone
} else if score.StageAtCapacity(game.Stage2, true) {
score.ControlPanelStatus = game.ControlPanelRotation
}
scoreChanged = true
case "K":
if score.ControlPanelStatus == game.ControlPanelRotation {
controlPanel := &(*realtimeScore).ControlPanel
controlPanel.CurrentColor++
if controlPanel.CurrentColor == 5 {
controlPanel.CurrentColor = 1
}
scoreChanged = true
}
case "P":
if score.ControlPanelStatus == game.ControlPanelPosition {
score.ControlPanelStatus = game.ControlPanelRotation
} else if score.StageAtCapacity(game.Stage3, true) {
score.ControlPanelStatus = game.ControlPanelPosition
}
scoreChanged = true
case "L":
score.RungIsLevel = !score.RungIsLevel
scoreChanged = true
}
}
if scoreChanged {
web.arena.RealtimeScoreNotifier.Notify()
}
}
}
// Increments the power cell count for the given goal, if the preconditions are met.
func incrementGoal(goal []int, currentStage game.Stage) bool {
if int(currentStage) < len(goal) {
goal[currentStage]++
return true
}
return false
}
// Decrements the power cell count for the given goal, if the preconditions are met.
func decrementGoal(goal []int, currentStage game.Stage) bool {
if int(currentStage) < len(goal) && goal[currentStage] > 0 {
goal[currentStage]--
return true
}
return false
}

View File

@@ -1,117 +0,0 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package web
import (
"github.com/Team254/cheesy-arena/field"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/websocket"
gorillawebsocket "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestScoringPanel(t *testing.T) {
web := setupTestWeb(t)
recorder := web.getHttpResponse("/panels/scoring/invalidalliance")
assert.Equal(t, 500, recorder.Code)
assert.Contains(t, recorder.Body.String(), "Invalid alliance")
recorder = web.getHttpResponse("/panels/scoring/red")
assert.Equal(t, 200, recorder.Code)
recorder = web.getHttpResponse("/panels/scoring/blue")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "Scoring Panel - Untitled Event - Cheesy Arena")
}
func TestScoringPanelWebsocket(t *testing.T) {
web := setupTestWeb(t)
server, wsUrl := web.startTestServer()
defer server.Close()
_, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/panels/scoring/blorpy/websocket", nil)
assert.NotNil(t, err)
redConn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/panels/scoring/red/websocket", nil)
assert.Nil(t, err)
defer redConn.Close()
redWs := websocket.NewTestWebsocket(redConn)
assert.Equal(t, 1, web.arena.ScoringPanelRegistry.GetNumPanels("red"))
assert.Equal(t, 0, web.arena.ScoringPanelRegistry.GetNumPanels("blue"))
blueConn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/panels/scoring/blue/websocket", nil)
assert.Nil(t, err)
defer blueConn.Close()
blueWs := websocket.NewTestWebsocket(blueConn)
assert.Equal(t, 1, web.arena.ScoringPanelRegistry.GetNumPanels("red"))
assert.Equal(t, 1, web.arena.ScoringPanelRegistry.GetNumPanels("blue"))
// Should get a few status updates 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")
// Send some autonomous period scoring commands.
web.arena.MatchState = field.AutoPeriod
redWs.Write("1", nil)
redWs.Write("3", nil)
redWs.Write("w", nil)
redWs.Write("X", nil)
redWs.Write("x", nil)
redWs.Write("z", nil)
for i := 0; i < 6; i++ {
readWebsocketType(t, redWs, "realtimeScore")
readWebsocketType(t, blueWs, "realtimeScore")
}
assert.Equal(t, [3]bool{true, false, true}, web.arena.RedRealtimeScore.CurrentScore.ExitedInitiationLine)
assert.Equal(t, [2]int{1, 0}, web.arena.RedRealtimeScore.CurrentScore.AutoCellsBottom)
assert.Equal(t, [2]int{0, 0}, web.arena.RedRealtimeScore.CurrentScore.AutoCellsOuter)
assert.Equal(t, [2]int{1, 0}, web.arena.RedRealtimeScore.CurrentScore.AutoCellsInner)
// Send some teleoperated period scoring commands.
web.arena.MatchState = field.TeleopPeriod
blueWs.Write("f", nil)
blueWs.Write("F", nil)
blueWs.Write("o", nil)
blueWs.Write("5", nil)
blueWs.Write("5", nil)
blueWs.Write("L", nil)
blueWs.Write("k", nil)
for i := 0; i < 6; i++ {
readWebsocketType(t, redWs, "realtimeScore")
readWebsocketType(t, blueWs, "realtimeScore")
}
assert.Equal(t, [4]int{2, 0, 0, 0}, web.arena.BlueRealtimeScore.CurrentScore.TeleopCellsOuter)
assert.Equal(t, [3]game.EndgameStatus{game.EndgameNone, game.EndgameHang, game.EndgameNone},
web.arena.BlueRealtimeScore.CurrentScore.EndgameStatuses)
assert.Equal(t, true, web.arena.BlueRealtimeScore.CurrentScore.RungIsLevel)
// Test committing logic.
redWs.Write("commitMatch", nil)
readWebsocketType(t, redWs, "error")
blueWs.Write("commitMatch", nil)
readWebsocketType(t, blueWs, "error")
assert.Equal(t, 0, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("red"))
assert.Equal(t, 0, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("blue"))
web.arena.MatchState = field.PostMatch
redWs.Write("commitMatch", nil)
blueWs.Write("commitMatch", nil)
time.Sleep(time.Millisecond * 10) // Allow some time for the commands to be processed.
assert.Equal(t, 1, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("red"))
assert.Equal(t, 1, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("blue"))
// Load another match to reset the results.
web.arena.ResetMatch()
web.arena.LoadTestMatch()
readWebsocketType(t, redWs, "matchLoad")
readWebsocketType(t, redWs, "realtimeScore")
readWebsocketType(t, blueWs, "matchLoad")
readWebsocketType(t, blueWs, "realtimeScore")
assert.Equal(t, field.NewRealtimeScore(), web.arena.RedRealtimeScore)
assert.Equal(t, field.NewRealtimeScore(), web.arena.BlueRealtimeScore)
assert.Equal(t, 0, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("red"))
assert.Equal(t, 0, web.arena.ScoringPanelRegistry.GetNumScoreCommitted("blue"))
}

3
web/setup_settings.go Normal file → Executable file
View File

@@ -75,9 +75,6 @@ func (web *Web) settingsPostHandler(w http.ResponseWriter, r *http.Request) {
eventSettings.PauseDurationSec, _ = strconv.Atoi(r.PostFormValue("pauseDurationSec"))
eventSettings.TeleopDurationSec, _ = strconv.Atoi(r.PostFormValue("teleopDurationSec"))
eventSettings.WarningRemainingDurationSec, _ = strconv.Atoi(r.PostFormValue("warningRemainingDurationSec"))
eventSettings.Stage1Capacity, _ = strconv.Atoi(r.PostFormValue("stage1Capacity"))
eventSettings.Stage2Capacity, _ = strconv.Atoi(r.PostFormValue("stage2Capacity"))
eventSettings.Stage3Capacity, _ = strconv.Atoi(r.PostFormValue("stage3Capacity"))
if eventSettings.Ap2TeamChannel != 0 && eventSettings.Ap2TeamChannel == eventSettings.ApTeamChannel {
web.renderSettings(w, r, "Cannot use same channel for both access points.")

4
web/web.go Normal file → Executable file
View File

@@ -144,10 +144,6 @@ func (web *Web) newHandler() http.Handler {
router.HandleFunc("/match_review", web.matchReviewHandler).Methods("GET")
router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditGetHandler).Methods("GET")
router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditPostHandler).Methods("POST")
router.HandleFunc("/panels/scoring/{alliance}", web.scoringPanelHandler).Methods("GET")
router.HandleFunc("/panels/scoring/{alliance}/websocket", web.scoringPanelWebsocketHandler).Methods("GET")
router.HandleFunc("/panels/referee", web.refereePanelHandler).Methods("GET")
router.HandleFunc("/panels/referee/websocket", web.refereePanelWebsocketHandler).Methods("GET")
router.HandleFunc("/reports/csv/rankings", web.rankingsCsvReportHandler).Methods("GET")
router.HandleFunc("/reports/pdf/rankings", web.rankingsPdfReportHandler).Methods("GET")
router.HandleFunc("/reports/csv/schedule/{type}", web.scheduleCsvReportHandler).Methods("GET")