Add page to select playoff defenses.

This commit is contained in:
Patrick Fairbank
2016-08-13 19:13:47 -07:00
parent e0c5acb8f0
commit 3d7810b395
9 changed files with 229 additions and 4 deletions

View File

@@ -75,6 +75,7 @@ type Arena struct {
allianceSelectionNotifier *Notifier
lowerThirdNotifier *Notifier
reloadDisplaysNotifier *Notifier
defenseSelectionNotifier *Notifier
audienceDisplayScreen string
allianceStationDisplays map[string]string
allianceStationDisplayScreen string
@@ -123,6 +124,7 @@ func (arena *Arena) Setup() {
arena.allianceSelectionNotifier = NewNotifier()
arena.lowerThirdNotifier = NewNotifier()
arena.reloadDisplaysNotifier = NewNotifier()
arena.defenseSelectionNotifier = NewNotifier()
// Load empty match as current.
arena.MatchState = PRE_MATCH
@@ -226,6 +228,7 @@ func (arena *Arena) LoadMatch(match *Match) error {
arena.realtimeScoreNotifier.Notify(nil)
arena.allianceStationDisplayScreen = "match"
arena.allianceStationDisplayNotifier.Notify(nil)
arena.defenseSelectionNotifier.Notify(nil)
return nil
}

View File

@@ -19,5 +19,5 @@ func TestFtaDisplay(t *testing.T) {
recorder := getHttpResponse("/displays/fta")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "FTA Display - Untitled Event - Cheesy Arena")
assert.Contains(t, recorder.Body.String(), "Field Monitor - Untitled Event - Cheesy Arena")
}

View File

@@ -46,6 +46,8 @@ type Match struct {
}
var placeableDefenses = []string{"CDF", "M", "R", "RW", "RT"}
var defenseNames = map[string]string{"LB": "Low Bar", "CDF": "Cheval de Frise", "M": "Moat",
"R": "Ramparts", "RW": "Rock Wall", "RT": "Rough Terrain"}
func (database *Database) CreateMatch(match *Match) error {
return database.matchMap.Insert(match)

138
setup_defense_selection.go Normal file
View File

@@ -0,0 +1,138 @@
// Copyright 2016 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web routes for conducting the team defense selection process.
package main
import (
"fmt"
"net/http"
"strconv"
"text/template"
)
// Shows the defense selection page.
func DefenseSelectionGetHandler(w http.ResponseWriter, r *http.Request) {
if !UserIsAdmin(w, r) {
return
}
renderDefenseSelection(w, r, "")
}
// Updates the cache with the latest input from the client.
func DefenseSelectionPostHandler(w http.ResponseWriter, r *http.Request) {
if !UserIsAdmin(w, r) {
return
}
matchId, _ := strconv.Atoi(r.PostFormValue("matchId"))
match, err := db.GetMatchById(matchId)
if err != nil {
handleWebErr(w, err)
return
}
redErr := validateDefenseSelection([]string{r.PostFormValue("redDefense2"),
r.PostFormValue("redDefense3"), r.PostFormValue("redDefense4"), r.PostFormValue("redDefense5")})
if redErr == nil {
match.RedDefense1 = "LB"
match.RedDefense2 = r.PostFormValue("redDefense2")
match.RedDefense3 = r.PostFormValue("redDefense3")
match.RedDefense4 = r.PostFormValue("redDefense4")
match.RedDefense5 = r.PostFormValue("redDefense5")
}
blueErr := validateDefenseSelection([]string{r.PostFormValue("blueDefense2"),
r.PostFormValue("blueDefense3"), r.PostFormValue("blueDefense4"), r.PostFormValue("blueDefense5")})
if blueErr == nil {
match.BlueDefense1 = "LB"
match.BlueDefense2 = r.PostFormValue("blueDefense2")
match.BlueDefense3 = r.PostFormValue("blueDefense3")
match.BlueDefense4 = r.PostFormValue("blueDefense4")
match.BlueDefense5 = r.PostFormValue("blueDefense5")
}
if redErr == nil || blueErr == nil {
err = db.SaveMatch(match)
if err != nil {
handleWebErr(w, err)
return
}
mainArena.defenseSelectionNotifier.Notify(nil)
}
if redErr != nil {
renderDefenseSelection(w, r, redErr.Error())
return
}
if blueErr != nil {
renderDefenseSelection(w, r, blueErr.Error())
return
}
http.Redirect(w, r, "/setup/defense_selection", 302)
}
func renderDefenseSelection(w http.ResponseWriter, r *http.Request, errorMessage string) {
template := template.New("").Funcs(templateHelpers)
_, err := template.ParseFiles("templates/setup_defense_selection.html")
if err != nil {
handleWebErr(w, err)
return
}
matches, err := db.GetMatchesByType("elimination")
if err != nil {
handleWebErr(w, err)
return
}
var unplayedMatches []Match
for _, match := range matches {
if match.Status != "complete" {
unplayedMatches = append(unplayedMatches, match)
}
}
data := struct {
*EventSettings
Matches []Match
DefenseNames map[string]string
ErrorMessage string
}{eventSettings, unplayedMatches, defenseNames, errorMessage}
err = template.ExecuteTemplate(w, "setup_defense_selection.html", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// Takes a slice of the defenses in positions 2-5 and returns an error if they are not valid.
func validateDefenseSelection(defenses []string) error {
// Build map to track which defenses have been used.
defenseCounts := make(map[string]int)
for _, defense := range placeableDefenses {
defenseCounts[defense] = 0
}
numBlankDefenses := 0
for _, defense := range defenses {
if defense == "" {
numBlankDefenses++
continue
}
defenseCount, ok := defenseCounts[defense]
if !ok {
return fmt.Errorf("Invalid defense type: %s", defense)
}
if defenseCount != 0 {
return fmt.Errorf("Defense used more than once: %s", defense)
}
defenseCounts[defense]++
}
if numBlankDefenses > 0 && numBlankDefenses < 4 {
return fmt.Errorf("Cannot leave defenses blank.")
}
return nil
}

View File

@@ -44,6 +44,7 @@
<li><a href="/setup/alliance_selection">Alliance Selection</a></li>
<li><a href="/setup/lower_thirds">Lower Thirds</a></li>
<li><a href="/setup/sponsor_slides">Sponsor Slides</a></li>
<li><a href="/setup/defense_selection">Playoff Defense Selection</a></li>
</ul>
</li>
<li class="dropdown">
@@ -84,7 +85,7 @@
<li><a href="/displays/alliance_station">Alliance Station</a></li>
<li><a href="/displays/announcer">Announcer</a></li>
<li><a href="/displays/audience">Audience</a></li>
<li><a href="/displays/fta">FTA</a></li>
<li><a href="/displays/fta">Field Monitor</a></li>
<li><a href="/displays/pit">Pit</a></li>
<li><a href="/displays/referee">Referee</a></li>
<li><a href="/displays/scoring/red">Scoring &ndash; Red</a></li>

View File

@@ -4,7 +4,7 @@
Display showing team diagnostics for FTA/FTAA use.
*/}}
{{define "title"}}FTA Display{{end}}
{{define "title"}}Field Monitor{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-12">

View File

@@ -153,7 +153,6 @@
{{define "script"}}
<script>
$(function() {
$("#displayBackgroundColor").colorpicker();
var startTime = moment(new Date()).hour(13).minute(0).second(0);
$("#startTimePicker").datetimepicker().data("DateTimePicker").setDate(startTime);
});

View File

@@ -0,0 +1,80 @@
{{/*
Copyright 2016 Team 254. All Rights Reserved.
Author: pat@patfairbank.com (Patrick Fairbank)
UI for controlling the team defense selection process.
*/}}
<!DOCTYPE html>
<html>
<head>
<title>Playoff Defense Selection - {{.EventSettings.Name}} - Cheesy Arena</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="shortcut icon" href="/static/img/favicon.ico">
<link rel="apple-touch-icon" href="/static/img/apple-icon.png">
<link href="/static/css/lib/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/cheesy-arena.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container">
<div class="row">
<legend>Playoff Defense Selection</legend>
{{if .ErrorMessage}}
<div class="alert alert-dismissable alert-danger">
<button type="button" class="close" data-dismiss="alert">×</button>
{{.ErrorMessage}}
</div>
{{end}}
{{range $match := .Matches}}
<div class="well">
<form method="POST">
<input type="hidden" name="matchId" value="{{$match.Id}}" />
<legend>{{$match.DisplayName}}</legend>
<div class="row well well-darkred">
<div class="col-lg-2"><h4>{{$match.Red1}}, {{$match.Red2}}, {{$match.Red3}}</h4></div>
<div class="col-lg-2">
<select class="form-control" disabled="true">
<option value="LB">{{index $.DefenseNames "LB"}}</option>
</select>
</div>
{{template "defense" dict "name" "redDefense2" "value" $match.RedDefense2 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "redDefense3" "value" $match.RedDefense3 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "redDefense4" "value" $match.RedDefense4 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "redDefense5" "value" $match.RedDefense5 "defenseNames" $.DefenseNames}}
</div>
<div class="row well well-darkblue">
<div class="col-lg-2"><h4>{{$match.Blue1}}, {{$match.Blue2}}, {{$match.Blue3}}</h4></div>
<div class="col-lg-2">
<select class="form-control" disabled="true">
<option value="LB">{{index $.DefenseNames "LB"}}</option>
</select>
</div>
{{template "defense" dict "name" "blueDefense2" "value" $match.BlueDefense2 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "blueDefense3" "value" $match.BlueDefense3 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "blueDefense4" "value" $match.BlueDefense4 "defenseNames" $.DefenseNames}}
{{template "defense" dict "name" "blueDefense5" "value" $match.BlueDefense5 "defenseNames" $.DefenseNames}}
</div>
<div class="text-center">
<button type="submit" class="btn btn-danger">Save Selections</button>
</div>
</form>
</div>
{{end}}
</div>
</div>
<script src="/static/js/lib/bootstrap.min.js"></script>
</body>
</html>
{{define "defense"}}
<div class="col-lg-2">
<select class="form-control" name="{{.name}}">
<option value=""></option>
<option value="CDF"{{if eq .value "CDF"}} selected{{end}}>{{index .defenseNames "CDF"}}</option>
<option value="M"{{if eq .value "M"}} selected{{end}}>{{index .defenseNames "M"}}</option>
<option value="R"{{if eq .value "R"}} selected{{end}}>{{index .defenseNames "R"}}</option>
<option value="RW"{{if eq .value "RW"}} selected{{end}}>{{index .defenseNames "RW"}}</option>
<option value="RT"{{if eq .value "RT"}} selected{{end}}>{{index .defenseNames "RT"}}</option>
</select>
</div>
{{end}}

2
web.go
View File

@@ -182,6 +182,8 @@ func newHandler() http.Handler {
router.HandleFunc("/setup/sponsor_slides", SponsorSlidesGetHandler).Methods("GET")
router.HandleFunc("/setup/sponsor_slides", SponsorSlidesPostHandler).Methods("POST")
router.HandleFunc("/api/sponsor_slides", SponsorSlidesApiHandler).Methods("GET")
router.HandleFunc("/setup/defense_selection", DefenseSelectionGetHandler).Methods("GET")
router.HandleFunc("/setup/defense_selection", DefenseSelectionPostHandler).Methods("POST")
router.HandleFunc("/match_play", MatchPlayHandler).Methods("GET")
router.HandleFunc("/match_play/{matchId}/load", MatchPlayLoadHandler).Methods("GET")
router.HandleFunc("/match_play/{matchId}/show_result", MatchPlayShowResultHandler).Methods("GET")