mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Added alliance selection page.
This commit is contained in:
@@ -198,6 +198,8 @@ func SchedulePdfReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
matchType = "Qualification"
|
||||
} else if matchType == "practice" {
|
||||
matchType = "Practice"
|
||||
} else if matchType == "elimination" {
|
||||
matchType = "Elimination"
|
||||
}
|
||||
|
||||
// Render match info row.
|
||||
|
||||
250
setup_alliance_selection.go
Normal file
250
setup_alliance_selection.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Web routes for conducting the alliance selection process.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RankedTeam struct {
|
||||
Rank int
|
||||
TeamId int
|
||||
Picked bool
|
||||
}
|
||||
|
||||
// Global vars to hold the alliances that are in the process of being selected.
|
||||
var cachedAlliances [][]*AllianceTeam
|
||||
var cachedRankedTeams []*RankedTeam
|
||||
|
||||
// Shows the alliance selection page.
|
||||
func AllianceSelectionGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
renderAllianceSelection(w, r, "")
|
||||
}
|
||||
|
||||
// Updates the cache with the latest input from the client.
|
||||
func AllianceSelectionPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !canModifyAllianceSelection() {
|
||||
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
|
||||
return
|
||||
}
|
||||
|
||||
// Reset picked state for each team in preparation for reconstructing it.
|
||||
for _, team := range cachedRankedTeams {
|
||||
team.Picked = false
|
||||
}
|
||||
|
||||
// Iterate through all selections and update the alliances.
|
||||
for i, alliance := range cachedAlliances {
|
||||
for j, spot := range alliance {
|
||||
teamString := r.PostFormValue(fmt.Sprintf("selection%d_%d", i, j))
|
||||
if teamString == "" {
|
||||
spot.TeamId = 0
|
||||
} else {
|
||||
teamId, err := strconv.Atoi(teamString)
|
||||
if err != nil {
|
||||
renderAllianceSelection(w, r, fmt.Sprintf("Invalid team number value '%s'.", teamString))
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, team := range cachedRankedTeams {
|
||||
if team.TeamId == teamId {
|
||||
if team.Picked {
|
||||
renderAllianceSelection(w, r, fmt.Sprintf("Team %d is already part of an alliance.", teamId))
|
||||
return
|
||||
}
|
||||
found = true
|
||||
team.Picked = true
|
||||
spot.TeamId = teamId
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
renderAllianceSelection(w, r, fmt.Sprintf("Team %d is not present at this event.", teamId))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/setup/alliance_selection", 302)
|
||||
}
|
||||
|
||||
// Sets up the empty alliances and populates the ranked team list.
|
||||
func AllianceSelectionStartHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if len(cachedAlliances) != 0 {
|
||||
renderAllianceSelection(w, r, "Can't start alliance selection when it is already in progress.")
|
||||
return
|
||||
}
|
||||
if !canModifyAllianceSelection() {
|
||||
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a blank alliance set matching the event configuration.
|
||||
cachedAlliances = make([][]*AllianceTeam, eventSettings.NumElimAlliances)
|
||||
teamsPerAlliance := 3
|
||||
if eventSettings.SelectionRound3Order != "" {
|
||||
teamsPerAlliance = 4
|
||||
}
|
||||
for i := 0; i < eventSettings.NumElimAlliances; i++ {
|
||||
cachedAlliances[i] = make([]*AllianceTeam, teamsPerAlliance)
|
||||
for j := 0; j < teamsPerAlliance; j++ {
|
||||
cachedAlliances[i][j] = &AllianceTeam{AllianceId: i + 1, PickPosition: j}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the ranked list of teams.
|
||||
rankings, err := db.GetAllRankings()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
cachedRankedTeams = make([]*RankedTeam, len(rankings))
|
||||
for i, ranking := range rankings {
|
||||
cachedRankedTeams[i] = &RankedTeam{i + 1, ranking.TeamId, false}
|
||||
}
|
||||
http.Redirect(w, r, "/setup/alliance_selection", 302)
|
||||
}
|
||||
|
||||
// Resets the alliance selection process back to the starting point.
|
||||
func AllianceSelectionResetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !canModifyAllianceSelection() {
|
||||
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
|
||||
return
|
||||
}
|
||||
|
||||
cachedAlliances = [][]*AllianceTeam{}
|
||||
cachedRankedTeams = []*RankedTeam{}
|
||||
http.Redirect(w, r, "/setup/alliance_selection", 302)
|
||||
}
|
||||
|
||||
// Saves the selected alliances to the database and generates the first round of elimination matches.
|
||||
func AllianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !canModifyAllianceSelection() {
|
||||
renderAllianceSelection(w, r, "Alliance selection has already been finalized.")
|
||||
return
|
||||
}
|
||||
|
||||
location, _ := time.LoadLocation("Local")
|
||||
startTime, err := time.ParseInLocation("2006-01-02 03:04:05 PM", r.PostFormValue("startTime"), location)
|
||||
if err != nil {
|
||||
renderAllianceSelection(w, r, "Must specify a valid start time for the elimination rounds.")
|
||||
return
|
||||
}
|
||||
matchSpacingSec, err := strconv.Atoi(r.PostFormValue("matchSpacingSec"))
|
||||
if err != nil || matchSpacingSec <= 0 {
|
||||
renderAllianceSelection(w, r, "Must specify a valid match spacing for the elimination rounds.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check that all spots are filled.
|
||||
for _, alliance := range cachedAlliances {
|
||||
for _, team := range alliance {
|
||||
if team.TeamId <= 0 {
|
||||
renderAllianceSelection(w, r, "Can't finalize alliance selection until all spots have been filled.")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save alliances to the database.
|
||||
for _, alliance := range cachedAlliances {
|
||||
for _, team := range alliance {
|
||||
err := db.CreateAllianceTeam(team)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the first round of elimination matches.
|
||||
_, err = db.UpdateEliminationSchedule(startTime, matchSpacingSec)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/setup/alliance_selection", 302)
|
||||
}
|
||||
|
||||
func renderAllianceSelection(w http.ResponseWriter, r *http.Request, errorMessage string) {
|
||||
template, err := template.ParseFiles("templates/alliance_selection.html", "templates/base.html")
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
nextRow, nextCol := determineNextCell()
|
||||
data := struct {
|
||||
*EventSettings
|
||||
Alliances [][]*AllianceTeam
|
||||
RankedTeams []*RankedTeam
|
||||
NextRow int
|
||||
NextCol int
|
||||
ErrorMessage string
|
||||
}{eventSettings, cachedAlliances, cachedRankedTeams, nextRow, nextCol, errorMessage}
|
||||
err = template.ExecuteTemplate(w, "base", data)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if it is safe to change the alliance selection (i.e. no elimination matches exist yet).
|
||||
func canModifyAllianceSelection() bool {
|
||||
matches, err := db.GetMatchesByType("elimination")
|
||||
if err != nil || len(matches) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns the row and column of the next alliance selection spot that should have keyboard autofocus.
|
||||
func determineNextCell() (int, int) {
|
||||
// Check the first two columns.
|
||||
for i, alliance := range cachedAlliances {
|
||||
if alliance[0].TeamId == 0 {
|
||||
return i, 0
|
||||
}
|
||||
if alliance[1].TeamId == 0 {
|
||||
return i, 1
|
||||
}
|
||||
}
|
||||
|
||||
// Check the third column.
|
||||
if eventSettings.SelectionRound2Order == "F" {
|
||||
for i, alliance := range cachedAlliances {
|
||||
if alliance[2].TeamId == 0 {
|
||||
return i, 2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := len(cachedAlliances) - 1; i >= 0; i-- {
|
||||
if cachedAlliances[i][2].TeamId == 0 {
|
||||
return i, 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the fourth column.
|
||||
if eventSettings.SelectionRound3Order == "F" {
|
||||
for i, alliance := range cachedAlliances {
|
||||
if alliance[3].TeamId == 0 {
|
||||
return i, 3
|
||||
}
|
||||
}
|
||||
} else if eventSettings.SelectionRound3Order == "L" {
|
||||
for i := len(cachedAlliances) - 1; i >= 0; i-- {
|
||||
if cachedAlliances[i][3].TeamId == 0 {
|
||||
return i, 3
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1, -1
|
||||
}
|
||||
261
setup_alliance_selection_test.go
Normal file
261
setup_alliance_selection_test.go
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetupAllianceSelection(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
cachedAlliances = [][]*AllianceTeam{}
|
||||
cachedRankedTeams = []*RankedTeam{}
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
eventSettings.NumElimAlliances = 15
|
||||
eventSettings.SelectionRound3Order = "L"
|
||||
for i := 1; i <= 10; i++ {
|
||||
db.CreateRanking(&Ranking{TeamId: 100 + i, Rank: i})
|
||||
}
|
||||
|
||||
// Check that there are no alliance placeholders to start.
|
||||
recorder := getHttpResponse("/setup/alliance_selection")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.NotContains(t, recorder.Body.String(), "Captain")
|
||||
assert.NotContains(t, recorder.Body.String(), ">110<")
|
||||
|
||||
// Start the alliance selection.
|
||||
recorder = postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
if assert.Equal(t, 15, len(cachedAlliances)) {
|
||||
assert.Equal(t, 4, len(cachedAlliances[0]))
|
||||
}
|
||||
recorder = getHttpResponse("/setup/alliance_selection")
|
||||
assert.Contains(t, recorder.Body.String(), "Captain")
|
||||
assert.Contains(t, recorder.Body.String(), ">110<")
|
||||
|
||||
// Reset the alliance selection.
|
||||
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
assert.NotContains(t, recorder.Body.String(), "Captain")
|
||||
assert.NotContains(t, recorder.Body.String(), ">110<")
|
||||
eventSettings.NumElimAlliances = 3
|
||||
eventSettings.SelectionRound3Order = ""
|
||||
recorder = postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
if assert.Equal(t, 3, len(cachedAlliances)) {
|
||||
assert.Equal(t, 3, len(cachedAlliances[0]))
|
||||
}
|
||||
|
||||
// Update one team at a time.
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=110")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
assert.Equal(t, 110, cachedAlliances[0][0].TeamId)
|
||||
recorder = getHttpResponse("/setup/alliance_selection")
|
||||
assert.Contains(t, recorder.Body.String(), "\"110\"")
|
||||
assert.NotContains(t, recorder.Body.String(), ">110<")
|
||||
|
||||
// Update multiple teams at a time.
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&selection1_0=103")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
assert.Equal(t, 101, cachedAlliances[0][0].TeamId)
|
||||
assert.Equal(t, 102, cachedAlliances[0][1].TeamId)
|
||||
assert.Equal(t, 103, cachedAlliances[1][0].TeamId)
|
||||
recorder = getHttpResponse("/setup/alliance_selection")
|
||||
assert.Contains(t, recorder.Body.String(), ">110<")
|
||||
|
||||
// Update remainder of teams.
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&"+
|
||||
"selection0_2=103&selection1_0=104&selection1_1=105&selection1_2=106&selection2_0=107&selection2_1=108&"+
|
||||
"selection2_2=109")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/alliance_selection")
|
||||
assert.Contains(t, recorder.Body.String(), ">110<")
|
||||
|
||||
// Finalize alliance selection.
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize",
|
||||
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
alliances, err := db.GetAllAlliances()
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 3, len(alliances)) {
|
||||
assert.Equal(t, 101, alliances[0][0].TeamId)
|
||||
assert.Equal(t, 105, alliances[1][1].TeamId)
|
||||
assert.Equal(t, 109, alliances[2][2].TeamId)
|
||||
}
|
||||
matches, err := db.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 6, len(matches))
|
||||
}
|
||||
|
||||
func TestSetupAllianceSelectionErrors(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
cachedAlliances = [][]*AllianceTeam{}
|
||||
cachedRankedTeams = []*RankedTeam{}
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
eventSettings.NumElimAlliances = 2
|
||||
for i := 1; i <= 6; i++ {
|
||||
db.CreateRanking(&Ranking{TeamId: 100 + i, Rank: i})
|
||||
}
|
||||
|
||||
// Start an alliance selection that is already underway.
|
||||
recorder := postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already in progress")
|
||||
|
||||
// Select invalid teams.
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=asdf")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "Invalid team number")
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=100")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "not present at this event")
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection1_1=101")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already part of an alliance")
|
||||
|
||||
// Finalize early and without required parameters.
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize",
|
||||
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "until all spots have been filled")
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&"+
|
||||
"selection0_2=103&selection1_0=104&selection1_1=105&selection1_2=106")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=asdf&matchSpacingSec=100")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "valid start time")
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "valid match spacing")
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize",
|
||||
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
|
||||
// Do other things after finalization.
|
||||
recorder = postHttpResponse("/setup/alliance_selection/finalize",
|
||||
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already been finalized")
|
||||
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already been finalized")
|
||||
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=asdf")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already been finalized")
|
||||
cachedAlliances = [][]*AllianceTeam{}
|
||||
cachedRankedTeams = []*RankedTeam{}
|
||||
recorder = postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "already been finalized")
|
||||
}
|
||||
|
||||
func TestSetupAllianceSelectionAutofocus(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
cachedAlliances = [][]*AllianceTeam{}
|
||||
cachedRankedTeams = []*RankedTeam{}
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
eventSettings.NumElimAlliances = 2
|
||||
|
||||
// Straight draft.
|
||||
eventSettings.SelectionRound2Order = "F"
|
||||
eventSettings.SelectionRound3Order = "F"
|
||||
recorder := postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
i, j := determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 0, j)
|
||||
cachedAlliances[0][0].TeamId = 1
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 1, j)
|
||||
cachedAlliances[0][1].TeamId = 2
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 0, j)
|
||||
cachedAlliances[1][0].TeamId = 3
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 1, j)
|
||||
cachedAlliances[1][1].TeamId = 4
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 2, j)
|
||||
cachedAlliances[0][2].TeamId = 5
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 2, j)
|
||||
cachedAlliances[1][2].TeamId = 6
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 3, j)
|
||||
cachedAlliances[0][3].TeamId = 7
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 3, j)
|
||||
cachedAlliances[1][3].TeamId = 8
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, -1, i)
|
||||
assert.Equal(t, -1, j)
|
||||
|
||||
// Double-serpentine draft.
|
||||
eventSettings.SelectionRound2Order = "L"
|
||||
eventSettings.SelectionRound3Order = "L"
|
||||
recorder = postHttpResponse("/setup/alliance_selection/reset", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = postHttpResponse("/setup/alliance_selection/start", "")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 0, j)
|
||||
cachedAlliances[0][0].TeamId = 1
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 1, j)
|
||||
cachedAlliances[0][1].TeamId = 2
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 0, j)
|
||||
cachedAlliances[1][0].TeamId = 3
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 1, j)
|
||||
cachedAlliances[1][1].TeamId = 4
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 2, j)
|
||||
cachedAlliances[1][2].TeamId = 5
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 2, j)
|
||||
cachedAlliances[0][2].TeamId = 6
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 1, i)
|
||||
assert.Equal(t, 3, j)
|
||||
cachedAlliances[1][3].TeamId = 7
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, 0, i)
|
||||
assert.Equal(t, 3, j)
|
||||
cachedAlliances[0][3].TeamId = 8
|
||||
i, j = determineNextCell()
|
||||
assert.Equal(t, -1, i)
|
||||
assert.Equal(t, -1, j)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func ScheduleGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
cachedScheduleBlocks = append(cachedScheduleBlocks, ScheduleBlock{startTime, 10, 360})
|
||||
cachedMatchType = "practice"
|
||||
}
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches, "")
|
||||
renderSchedule(w, r, "")
|
||||
}
|
||||
|
||||
// Generates the schedule and presents it for review without saving it to the database.
|
||||
@@ -36,8 +36,7 @@ func ScheduleGeneratePostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
scheduleBlocks, err := getScheduleBlocks(r)
|
||||
if err != nil {
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
|
||||
"Incomplete or invalid schedule block parameters specified.")
|
||||
renderSchedule(w, r, "Incomplete or invalid schedule block parameters specified.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,19 +47,18 @@ func ScheduleGeneratePostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if len(teams) == 0 {
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
|
||||
"No team list is configured. Set up the list of teams at the event before generating the schedule.")
|
||||
renderSchedule(w, r, "No team list is configured. Set up the list of teams at the event before "+
|
||||
"generating the schedule.")
|
||||
return
|
||||
}
|
||||
if len(teams) < 18 {
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
|
||||
fmt.Sprintf("There are only %d teams. There must be at least 18 teams to generate a schedule.", len(teams)))
|
||||
renderSchedule(w, r, fmt.Sprintf("There are only %d teams. There must be at least 18 teams to generate "+
|
||||
"a schedule.", len(teams)))
|
||||
return
|
||||
}
|
||||
matches, err := BuildRandomSchedule(teams, scheduleBlocks, r.PostFormValue("matchType"))
|
||||
if err != nil {
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
|
||||
fmt.Sprintf("Error generating schedule: %s.", err.Error()))
|
||||
renderSchedule(w, r, fmt.Sprintf("Error generating schedule: %s.", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,9 +94,8 @@ func ScheduleSavePostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if len(existingMatches) > 0 {
|
||||
renderSchedule(w, r, cachedMatchType, cachedScheduleBlocks, cachedMatches, cachedTeamFirstMatches,
|
||||
fmt.Sprintf("Can't save schedule because a schedule of %d %s matches already exists. Clear it first "+
|
||||
" on the Settings page.", len(existingMatches), cachedMatchType))
|
||||
renderSchedule(w, r, fmt.Sprintf("Can't save schedule because a schedule of %d %s matches already "+
|
||||
"exists. Clear it first on the Settings page.", len(existingMatches), cachedMatchType))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,8 +109,7 @@ func ScheduleSavePostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/setup/schedule", 302)
|
||||
}
|
||||
|
||||
func renderSchedule(w http.ResponseWriter, r *http.Request, matchType string, scheduleBlocks []ScheduleBlock,
|
||||
matches []Match, teamFirstMatches map[int]string, errorMessage string) {
|
||||
func renderSchedule(w http.ResponseWriter, r *http.Request, errorMessage string) {
|
||||
teams, err := db.GetAllTeams()
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
@@ -132,7 +128,8 @@ func renderSchedule(w http.ResponseWriter, r *http.Request, matchType string, sc
|
||||
Matches []Match
|
||||
TeamFirstMatches map[int]string
|
||||
ErrorMessage string
|
||||
}{eventSettings, matchType, scheduleBlocks, len(teams), matches, teamFirstMatches, errorMessage}
|
||||
}{eventSettings, cachedMatchType, cachedScheduleBlocks, len(teams), cachedMatches, cachedTeamFirstMatches,
|
||||
errorMessage}
|
||||
err = template.ExecuteTemplate(w, "base", data)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
|
||||
161
templates/alliance_selection.html
Normal file
161
templates/alliance_selection.html
Normal file
@@ -0,0 +1,161 @@
|
||||
{{define "title"}}Alliance Selection{{end}}
|
||||
{{define "body"}}
|
||||
<div class="row">
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert alert-dismissable alert-danger">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
{{.ErrorMessage}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if len .Alliances | eq 0}}
|
||||
<div class="col-lg-3">
|
||||
<form action="/setup/alliance_selection/start" method="POST">
|
||||
<legend>Alliance Selection</legend>
|
||||
<button type="submit" class="btn btn-info">Start Alliance Selection</button>
|
||||
</form>
|
||||
</div>
|
||||
{{else}}
|
||||
<form action="/setup/alliance_selection" method="POST">
|
||||
<div class="col-lg-3 ">
|
||||
<legend>Alliance Selection</legend>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-info">Update</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-danger"
|
||||
onclick="$('#confirmResetAllianceSelection').modal('show'); return false;">
|
||||
Reset Alliance Selection
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-primary"
|
||||
onclick="$('#confirmFinalizeAllianceSelection').modal('show'); return false;">
|
||||
Finalize Alliance Selection
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alliance #</th>
|
||||
<th>Captain</th>
|
||||
<th>Pick 1</th>
|
||||
<th>Pick 2</th>
|
||||
{{if index .Alliances 0 | len | eq 4}}
|
||||
<th>Pick 3</th>
|
||||
{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $i, $alliance := .Alliances}}
|
||||
<tr>
|
||||
<td class="col-lg-2">{{(index $alliance 0).AllianceId}}</td>
|
||||
{{range $j, $team := $alliance}}
|
||||
{{if eq $team.TeamId 0}}
|
||||
<td class="col-lg-2">
|
||||
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}" value=""
|
||||
{{if and (eq $i $.NextRow) (eq $j $.NextCol)}}autofocus{{end}}
|
||||
oninput="$(this).parent().addClass('has-warning');" />
|
||||
</td>
|
||||
{{else}}
|
||||
<td class="col-lg-2">
|
||||
<input type="text" class="form-control input-sm" name="selection{{$i}}_{{$j}}"
|
||||
value="{{$team.TeamId}}" oninput="$(this).parent().addClass('has-warning');" />
|
||||
</td>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
Hint: Press 'Enter' after entering each team number for easiest use.
|
||||
</div>
|
||||
</form>
|
||||
<div class="col-lg-2">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Team</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $team := .RankedTeams}}
|
||||
{{if not $team.Picked}}
|
||||
<tr>
|
||||
<td>{{$team.Rank}}</td>
|
||||
<td>{{$team.TeamId}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="confirmResetAllianceSelection" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to reset the alliance selection process?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form class="form-horizontal" action="/setup/alliance_selection/reset" method="POST">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger">Reset Alliance Selection</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirmFinalizeAllianceSelection" class="modal" style="top: 20%;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Confirm</h4>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/setup/alliance_selection/finalize" method="POST">
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to finalize the alliance selection process?</p>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-6 control-label">Elimination Round Start Time</label>
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group date" id="startTimePicker" data-date-format="YYYY-MM-DD hh:mm:ss A">
|
||||
<input type="text" class="form-control input-sm" name="startTime" />
|
||||
<span class="input-group-addon input-sm">
|
||||
<span class="glyphicon glyphicon-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-6 control-label">Elimination Round Cycle Time (sec)</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" class="form-control input-sm" name="matchSpacingSec" value="480" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Finalize Alliance Selection</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
<script>
|
||||
$(function() {
|
||||
$("#displayBackgroundColor").colorpicker();
|
||||
var startTime = moment(new Date()).hour(13).minute(0).second(0);
|
||||
$("#startTimePicker").datetimepicker().data("DateTimePicker").setDate(startTime);
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -23,7 +23,7 @@
|
||||
<li><a href="/setup/settings">Settings</a></li>
|
||||
<li><a href="/setup/teams">Team List</a></li>
|
||||
<li><a href="/setup/schedule">Match Scheduling</a></li>
|
||||
<li><a href="#">Alliance Selection</a></li>
|
||||
<li><a href="/setup/alliance_selection">Alliance Selection</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
|
||||
5
web.go
5
web.go
@@ -77,6 +77,11 @@ func newHandler() http.Handler {
|
||||
router.HandleFunc("/setup/schedule", ScheduleGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/schedule/generate", ScheduleGeneratePostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/schedule/save", ScheduleSavePostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/alliance_selection", AllianceSelectionGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/alliance_selection", AllianceSelectionPostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/alliance_selection/start", AllianceSelectionStartHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/alliance_selection/reset", AllianceSelectionResetHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/alliance_selection/finalize", AllianceSelectionFinalizeHandler).Methods("POST")
|
||||
router.HandleFunc("/reports/csv/rankings", RankingsCsvReportHandler)
|
||||
router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler)
|
||||
router.HandleFunc("/reports/json/rankings", RankingsJSONReportHandler)
|
||||
|
||||
Reference in New Issue
Block a user