2014-06-24 22:51:10 -07:00
|
|
|
// Copyright 2014 Team 254. All Rights Reserved.
|
|
|
|
|
// Author: pat@patfairbank.com (Patrick Fairbank)
|
|
|
|
|
//
|
|
|
|
|
// Web routes for controlling match play.
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/gorilla/mux"
|
2014-07-06 00:34:40 -07:00
|
|
|
"github.com/mitchellh/mapstructure"
|
|
|
|
|
"io"
|
|
|
|
|
"log"
|
2014-06-24 22:51:10 -07:00
|
|
|
"net/http"
|
|
|
|
|
"sort"
|
|
|
|
|
"strconv"
|
2014-07-27 16:41:09 -07:00
|
|
|
"text/template"
|
2014-07-30 22:55:14 -07:00
|
|
|
"time"
|
2014-06-24 22:51:10 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type MatchPlayListItem struct {
|
|
|
|
|
Id int
|
|
|
|
|
DisplayName string
|
|
|
|
|
Time string
|
2014-06-29 14:20:47 -07:00
|
|
|
Status string
|
2014-06-24 22:51:10 -07:00
|
|
|
ColorClass string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type MatchPlayList []MatchPlayListItem
|
|
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
type MatchTimeMessage struct {
|
|
|
|
|
MatchState int
|
|
|
|
|
MatchTimeSec int
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-24 22:51:10 -07:00
|
|
|
// Global var to hold the current active tournament so that its matches are displayed by default.
|
|
|
|
|
var currentMatchType string
|
|
|
|
|
|
|
|
|
|
// Shows the match play control interface.
|
|
|
|
|
func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
practiceMatches, err := buildMatchPlayList("practice")
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
qualificationMatches, err := buildMatchPlayList("qualification")
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
eliminationMatches, err := buildMatchPlayList("elimination")
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
template := template.New("").Funcs(templateHelpers)
|
|
|
|
|
_, err = template.ParseFiles("templates/match_play.html", "templates/base.html")
|
2014-06-24 22:51:10 -07:00
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
matchesByType := map[string]MatchPlayList{"practice": practiceMatches,
|
|
|
|
|
"qualification": qualificationMatches, "elimination": eliminationMatches}
|
|
|
|
|
if currentMatchType == "" {
|
|
|
|
|
currentMatchType = "practice"
|
|
|
|
|
}
|
2014-08-20 23:13:19 -07:00
|
|
|
allowSubstitution := mainArena.currentMatch.Type != "qualification"
|
2014-07-10 23:03:03 -07:00
|
|
|
matchResult, err := db.GetMatchResultForMatch(mainArena.currentMatch.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
isReplay := matchResult != nil
|
2014-06-24 22:51:10 -07:00
|
|
|
data := struct {
|
|
|
|
|
*EventSettings
|
2014-07-06 00:34:40 -07:00
|
|
|
MatchesByType map[string]MatchPlayList
|
|
|
|
|
CurrentMatchType string
|
|
|
|
|
Match *Match
|
|
|
|
|
AllowSubstitution bool
|
2014-07-10 23:03:03 -07:00
|
|
|
IsReplay bool
|
|
|
|
|
}{eventSettings, matchesByType, currentMatchType, mainArena.currentMatch, allowSubstitution, isReplay}
|
2014-06-24 22:51:10 -07:00
|
|
|
err = template.ExecuteTemplate(w, "base", data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
func MatchPlayLoadHandler(w http.ResponseWriter, r *http.Request) {
|
2014-06-29 14:20:47 -07:00
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
matchId, _ := strconv.Atoi(vars["matchId"])
|
2014-07-06 00:34:40 -07:00
|
|
|
var match *Match
|
|
|
|
|
var err error
|
|
|
|
|
if matchId == 0 {
|
|
|
|
|
err = mainArena.LoadTestMatch()
|
|
|
|
|
} else {
|
|
|
|
|
match, err = db.GetMatchById(matchId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if match == nil {
|
|
|
|
|
handleWebErr(w, fmt.Errorf("Invalid match ID %d.", matchId))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = mainArena.LoadMatch(match)
|
2014-06-29 14:20:47 -07:00
|
|
|
}
|
2014-07-04 16:55:29 -07:00
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
currentMatchType = mainArena.currentMatch.Type
|
2014-06-29 14:20:47 -07:00
|
|
|
|
2014-07-27 16:41:09 -07:00
|
|
|
http.Redirect(w, r, "/match_play", 302)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads the results for the given match into the display buffer.
|
|
|
|
|
func MatchPlayShowResultHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
matchId, _ := strconv.Atoi(vars["matchId"])
|
2014-08-02 19:43:45 -07:00
|
|
|
match, err := db.GetMatchById(matchId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if match == nil {
|
|
|
|
|
handleWebErr(w, fmt.Errorf("Invalid match ID %d.", matchId))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
matchResult, err := db.GetMatchResultForMatch(match.Id)
|
2014-07-27 16:41:09 -07:00
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if matchResult == nil {
|
|
|
|
|
handleWebErr(w, fmt.Errorf("No result found for match ID %d.", matchId))
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-08-02 19:43:45 -07:00
|
|
|
mainArena.savedMatch = match
|
2014-07-27 16:41:09 -07:00
|
|
|
mainArena.savedMatchResult = matchResult
|
|
|
|
|
mainArena.scorePostedNotifier.Notify(nil)
|
|
|
|
|
|
2014-06-29 14:20:47 -07:00
|
|
|
http.Redirect(w, r, "/match_play", 302)
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
// The websocket endpoint for the match play client to send control commands and receive status updates.
|
|
|
|
|
func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
websocket, err := NewWebsocket(w, r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
handleWebErr(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer websocket.Close()
|
|
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
matchTimeListener := mainArena.matchTimeNotifier.Listen()
|
|
|
|
|
defer close(matchTimeListener)
|
|
|
|
|
robotStatusListener := mainArena.robotStatusNotifier.Listen()
|
|
|
|
|
defer close(robotStatusListener)
|
2014-08-02 19:43:45 -07:00
|
|
|
audienceDisplayListener := mainArena.audienceDisplayNotifier.Listen()
|
|
|
|
|
defer close(audienceDisplayListener)
|
2014-08-03 17:59:59 -07:00
|
|
|
scoringStatusListener := mainArena.scoringStatusNotifier.Listen()
|
|
|
|
|
defer close(scoringStatusListener)
|
2014-08-08 12:39:08 -07:00
|
|
|
allianceStationDisplayListener := mainArena.allianceStationDisplayNotifier.Listen()
|
|
|
|
|
defer close(allianceStationDisplayListener)
|
2014-07-10 23:03:03 -07:00
|
|
|
|
|
|
|
|
// Send the various notifications immediately upon connection.
|
2014-08-03 17:59:59 -07:00
|
|
|
var data interface{}
|
2014-07-06 00:34:40 -07:00
|
|
|
err = websocket.Write("status", mainArena)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-07-10 23:03:03 -07:00
|
|
|
err = websocket.Write("matchTiming", mainArena.matchTiming)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-08-03 17:59:59 -07:00
|
|
|
data = MatchTimeMessage{mainArena.MatchState, int(mainArena.lastMatchTimeSec)}
|
2014-07-10 23:03:03 -07:00
|
|
|
err = websocket.Write("matchTime", data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-08-02 19:43:45 -07:00
|
|
|
err = websocket.Write("setAudienceDisplay", mainArena.audienceDisplayScreen)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-08-03 17:59:59 -07:00
|
|
|
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
|
|
|
|
|
}
|
2014-08-08 12:39:08 -07:00
|
|
|
err = websocket.Write("setAllianceStationDisplay", mainArena.allianceStationDisplayScreen)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
// Spin off a goroutine to listen for notifications and pass them on through the websocket.
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
var messageType string
|
|
|
|
|
var message interface{}
|
|
|
|
|
select {
|
|
|
|
|
case matchTimeSec, ok := <-matchTimeListener:
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
2014-07-06 00:34:40 -07:00
|
|
|
}
|
2014-07-10 23:03:03 -07:00
|
|
|
messageType = "matchTime"
|
|
|
|
|
message = MatchTimeMessage{mainArena.MatchState, matchTimeSec.(int)}
|
|
|
|
|
case _, ok := <-robotStatusListener:
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
messageType = "status"
|
|
|
|
|
message = mainArena
|
2014-08-02 19:43:45 -07:00
|
|
|
case _, ok := <-audienceDisplayListener:
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
messageType = "setAudienceDisplay"
|
|
|
|
|
message = mainArena.audienceDisplayScreen
|
2014-08-03 17:59:59 -07:00
|
|
|
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}
|
2014-08-08 12:39:08 -07:00
|
|
|
case _, ok := <-allianceStationDisplayListener:
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
messageType = "setAllianceStationDisplay"
|
|
|
|
|
message = mainArena.allianceStationDisplayScreen
|
2014-07-06 00:34:40 -07:00
|
|
|
}
|
2014-07-10 23:03:03 -07:00
|
|
|
err = websocket.Write(messageType, message)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// The client has probably closed the connection; nothing to do here.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
2014-07-06 00:34:40 -07:00
|
|
|
|
|
|
|
|
// Loop, waiting for commands and responding to them, until the client closes the connection.
|
|
|
|
|
for {
|
|
|
|
|
messageType, data, err := websocket.Read()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
// Client has closed the connection; nothing to do here.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch messageType {
|
|
|
|
|
case "substituteTeam":
|
|
|
|
|
args := struct {
|
|
|
|
|
Team int
|
|
|
|
|
Position string
|
|
|
|
|
}{}
|
|
|
|
|
err = mapstructure.Decode(data, &args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = mainArena.SubstituteTeam(args.Team, args.Position)
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "toggleBypass":
|
|
|
|
|
station, ok := data.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if _, ok := mainArena.AllianceStations[station]; !ok {
|
|
|
|
|
websocket.WriteError(fmt.Sprintf("Invalid alliance station '%s'.", station))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
mainArena.AllianceStations[station].Bypass = !mainArena.AllianceStations[station].Bypass
|
|
|
|
|
case "startMatch":
|
|
|
|
|
err = mainArena.StartMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "abortMatch":
|
|
|
|
|
err = mainArena.AbortMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "commitResults":
|
2014-08-03 20:59:07 -07:00
|
|
|
err = CommitCurrentMatchScore()
|
2014-07-06 00:34:40 -07:00
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = mainArena.ResetMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = mainArena.LoadNextMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = websocket.Write("reload", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
continue // Skip sending the status update, as the client is about to terminate and reload.
|
|
|
|
|
case "discardResults":
|
|
|
|
|
err = mainArena.ResetMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = mainArena.LoadNextMatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
websocket.WriteError(err.Error())
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = websocket.Write("reload", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
continue // Skip sending the status update, as the client is about to terminate and reload.
|
2014-08-02 19:43:45 -07:00
|
|
|
case "setAudienceDisplay":
|
|
|
|
|
screen, ok := data.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
mainArena.audienceDisplayScreen = screen
|
|
|
|
|
mainArena.audienceDisplayNotifier.Notify(nil)
|
|
|
|
|
continue
|
2014-08-08 12:39:08 -07:00
|
|
|
case "setAllianceStationDisplay":
|
|
|
|
|
screen, ok := data.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
mainArena.allianceStationDisplayScreen = screen
|
|
|
|
|
mainArena.allianceStationDisplayNotifier.Notify(nil)
|
|
|
|
|
continue
|
2014-07-06 00:34:40 -07:00
|
|
|
default:
|
|
|
|
|
websocket.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send out the status again after handling the command, as it most likely changed as a result.
|
|
|
|
|
err = websocket.Write("status", mainArena)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Websocket error: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-03 20:59:07 -07:00
|
|
|
// Saves the given match and result to the database, supplanting any previous result for the match.
|
2014-06-24 22:51:10 -07:00
|
|
|
func CommitMatchScore(match *Match, matchResult *MatchResult) error {
|
2014-08-18 22:58:02 -07:00
|
|
|
if match.Type == "elimination" {
|
2014-08-20 03:07:34 -07:00
|
|
|
// Adjust the score if necessary for an elimination DQ or tie.
|
|
|
|
|
matchResult.CorrectEliminationScore()
|
2014-08-18 22:58:02 -07:00
|
|
|
}
|
|
|
|
|
|
2014-08-03 20:59:07 -07:00
|
|
|
// Store the result in the buffer to be shown in the audience display.
|
|
|
|
|
mainArena.savedMatch = match
|
|
|
|
|
mainArena.savedMatchResult = matchResult
|
|
|
|
|
mainArena.scorePostedNotifier.Notify(nil)
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
if match.Type == "test" {
|
|
|
|
|
// Do nothing since this is a test match and doesn't exist in the database.
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-28 22:03:30 -07:00
|
|
|
if matchResult.PlayNumber == 0 {
|
|
|
|
|
// Determine the play number for this new match result.
|
|
|
|
|
prevMatchResult, err := db.GetMatchResultForMatch(match.Id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if prevMatchResult != nil {
|
|
|
|
|
matchResult.PlayNumber = prevMatchResult.PlayNumber + 1
|
|
|
|
|
} else {
|
|
|
|
|
matchResult.PlayNumber = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save the match result record to the database.
|
|
|
|
|
err = db.CreateMatchResult(matchResult)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2014-06-24 22:51:10 -07:00
|
|
|
} else {
|
2014-06-28 22:03:30 -07:00
|
|
|
// We are updating a match result record that already exists.
|
|
|
|
|
err := db.SaveMatchResult(matchResult)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2014-06-24 22:51:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update and save the match record to the database.
|
|
|
|
|
match.Status = "complete"
|
|
|
|
|
redScore := matchResult.RedScoreSummary()
|
|
|
|
|
blueScore := matchResult.BlueScoreSummary()
|
|
|
|
|
if redScore.Score > blueScore.Score {
|
|
|
|
|
match.Winner = "R"
|
|
|
|
|
} else if redScore.Score < blueScore.Score {
|
|
|
|
|
match.Winner = "B"
|
|
|
|
|
} else {
|
|
|
|
|
match.Winner = "T"
|
|
|
|
|
}
|
2014-06-28 22:03:30 -07:00
|
|
|
err := db.SaveMatch(match)
|
2014-06-24 22:51:10 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-20 03:07:34 -07:00
|
|
|
if match.Type != "practice" {
|
|
|
|
|
db.CalculateTeamCards(match.Type)
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-30 22:55:14 -07:00
|
|
|
if match.Type == "qualification" {
|
|
|
|
|
// Recalculate all the rankings.
|
|
|
|
|
err = db.CalculateRankings()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if match.Type == "elimination" {
|
|
|
|
|
// Generate any subsequent elimination matches.
|
|
|
|
|
_, err = db.UpdateEliminationSchedule(time.Now().Add(time.Second * elimMatchSpacingSec))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if eventSettings.TbaPublishingEnabled && match.Type != "practice" {
|
|
|
|
|
// Publish asynchronously to The Blue Alliance.
|
|
|
|
|
go func() {
|
|
|
|
|
err = PublishMatches()
|
|
|
|
|
if err != nil {
|
2014-09-05 22:17:12 -07:00
|
|
|
log.Printf("Failed to publish matches: %s", err.Error())
|
2014-07-30 22:55:14 -07:00
|
|
|
}
|
|
|
|
|
if match.Type == "qualification" {
|
|
|
|
|
err = PublishRankings()
|
|
|
|
|
if err != nil {
|
2014-09-05 22:17:12 -07:00
|
|
|
log.Printf("Failed to publish rankings: %s", err.Error())
|
2014-07-30 22:55:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
2014-06-24 22:51:10 -07:00
|
|
|
}
|
|
|
|
|
|
2014-08-28 19:32:18 -07:00
|
|
|
// Back up the database, but don't error out if it fails.
|
2014-08-28 21:44:22 -07:00
|
|
|
err = db.Backup(fmt.Sprintf("post_%s_match_%s", match.Type, match.DisplayName))
|
2014-08-28 19:32:18 -07:00
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-24 22:51:10 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-03 20:59:07 -07:00
|
|
|
// Saves the realtime result as the final score for the match currently loaded into the arena.
|
|
|
|
|
func CommitCurrentMatchScore() error {
|
|
|
|
|
matchResult := MatchResult{MatchId: mainArena.currentMatch.Id,
|
|
|
|
|
RedScore: mainArena.redRealtimeScore.CurrentScore, BlueScore: mainArena.blueRealtimeScore.CurrentScore,
|
2014-08-20 03:07:34 -07:00
|
|
|
RedFouls: mainArena.redRealtimeScore.Fouls, BlueFouls: mainArena.blueRealtimeScore.Fouls,
|
|
|
|
|
RedCards: mainArena.redRealtimeScore.Cards, BlueCards: mainArena.blueRealtimeScore.Cards}
|
2014-08-03 20:59:07 -07:00
|
|
|
return CommitMatchScore(mainArena.currentMatch, &matchResult)
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-24 22:51:10 -07:00
|
|
|
func (list MatchPlayList) Len() int {
|
|
|
|
|
return len(list)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (list MatchPlayList) Less(i, j int) bool {
|
2014-06-29 14:20:47 -07:00
|
|
|
return list[i].Status != "complete" && list[j].Status == "complete"
|
2014-06-24 22:51:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (list MatchPlayList) Swap(i, j int) {
|
|
|
|
|
list[i], list[j] = list[j], list[i]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildMatchPlayList(matchType string) (MatchPlayList, error) {
|
|
|
|
|
matches, err := db.GetMatchesByType(matchType)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return MatchPlayList{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefix := ""
|
|
|
|
|
if matchType == "practice" {
|
|
|
|
|
prefix = "P"
|
|
|
|
|
} else if matchType == "qualification" {
|
|
|
|
|
prefix = "Q"
|
|
|
|
|
}
|
|
|
|
|
matchPlayList := make(MatchPlayList, len(matches))
|
|
|
|
|
for i, match := range matches {
|
|
|
|
|
matchPlayList[i].Id = match.Id
|
|
|
|
|
matchPlayList[i].DisplayName = prefix + match.DisplayName
|
2014-08-21 00:12:49 -07:00
|
|
|
matchPlayList[i].Time = match.Time.Local().Format("3:04 PM")
|
2014-06-29 14:20:47 -07:00
|
|
|
matchPlayList[i].Status = match.Status
|
2014-06-24 22:51:10 -07:00
|
|
|
switch match.Winner {
|
|
|
|
|
case "R":
|
|
|
|
|
matchPlayList[i].ColorClass = "danger"
|
|
|
|
|
case "B":
|
|
|
|
|
matchPlayList[i].ColorClass = "info"
|
|
|
|
|
case "T":
|
|
|
|
|
matchPlayList[i].ColorClass = "warning"
|
|
|
|
|
default:
|
|
|
|
|
matchPlayList[i].ColorClass = ""
|
|
|
|
|
}
|
2014-07-04 16:55:29 -07:00
|
|
|
if mainArena.currentMatch != nil && matchPlayList[i].Id == mainArena.currentMatch.Id {
|
2014-06-29 14:20:47 -07:00
|
|
|
matchPlayList[i].ColorClass = "success"
|
|
|
|
|
}
|
2014-06-24 22:51:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort the list to put all completed matches at the bottom.
|
|
|
|
|
sort.Stable(matchPlayList)
|
|
|
|
|
|
|
|
|
|
return matchPlayList, nil
|
|
|
|
|
}
|