Added match play screen with fake match scoring.

This commit is contained in:
Patrick Fairbank
2014-06-24 22:51:10 -07:00
parent d792772f9b
commit ae39a7128b
5 changed files with 285 additions and 1 deletions

189
match_play.go Normal file
View File

@@ -0,0 +1,189 @@
// 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"
"html/template"
"math/rand"
"net/http"
"sort"
"strconv"
)
type MatchPlayListItem struct {
Id int
DisplayName string
Time string
ColorClass string
}
type MatchPlayList []MatchPlayListItem
// 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
}
template, err := template.ParseFiles("templates/match_play.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
matchesByType := map[string]MatchPlayList{"practice": practiceMatches,
"qualification": qualificationMatches, "elimination": eliminationMatches}
if currentMatchType == "" {
currentMatchType = "practice"
}
data := struct {
*EventSettings
MatchesByType map[string]MatchPlayList
CurrentMatchType string
}{eventSettings, matchesByType, currentMatchType}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
return
}
}
func MatchPlayFakeResultHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
matchId, _ := strconv.Atoi(vars["matchId"])
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 := MatchResult{MatchId: match.Id}
matchResult.RedScore = randomScore()
matchResult.BlueScore = randomScore()
err = CommitMatchScore(match, &matchResult)
if err != nil {
handleWebErr(w, err)
return
}
currentMatchType = match.Type
http.Redirect(w, r, "/match_play", 302)
}
func CommitMatchScore(match *Match, matchResult *MatchResult) error {
// Determine the play number for this match.
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
}
// 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"
}
err = db.SaveMatch(match)
if err != nil {
return err
}
// Recalculate all the rankings.
err = db.CalculateRankings()
if err != nil {
return err
}
return nil
}
func (list MatchPlayList) Len() int {
return len(list)
}
func (list MatchPlayList) Less(i, j int) bool {
return list[i].ColorClass == "" && list[j].ColorClass != ""
}
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
matchPlayList[i].Time = match.Time.Format("3:04 PM")
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 = ""
}
}
// Sort the list to put all completed matches at the bottom.
sort.Stable(matchPlayList)
return matchPlayList, nil
}
func randomScore() Score {
cycle := Cycle{rand.Intn(3) + 1, rand.Intn(2) == 1, rand.Intn(2) == 1, rand.Intn(2) == 1, rand.Intn(2) == 1,
rand.Intn(2) == 1}
return Score{rand.Intn(4), rand.Intn(4), rand.Intn(4), rand.Intn(4), rand.Intn(4), 0, 0, []Cycle{cycle}}
}

39
match_play_test.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestMatchPlay(t *testing.T) {
clearDb()
defer clearDb()
var err error
db, err = OpenDatabase(testDbPath)
assert.Nil(t, err)
defer db.Close()
eventSettings, _ = db.GetEventSettings()
match1 := Match{Type: "practice", DisplayName: "1", Status: "complete", Winner: "R"}
match2 := Match{Type: "practice", DisplayName: "2"}
match3 := Match{Type: "qualification", DisplayName: "1", Status: "complete", Winner: "B"}
match4 := Match{Type: "elimination", DisplayName: "SF1-1", Status: "complete", Winner: "T"}
match5 := Match{Type: "elimination", DisplayName: "SF1-2"}
db.CreateMatch(&match1)
db.CreateMatch(&match2)
db.CreateMatch(&match3)
db.CreateMatch(&match4)
db.CreateMatch(&match5)
// Check that all matches are listed on the page.
recorder := getHttpResponse("/match_play")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "P1")
assert.Contains(t, recorder.Body.String(), "P2")
assert.Contains(t, recorder.Body.String(), "Q1")
assert.Contains(t, recorder.Body.String(), "SF1-1")
assert.Contains(t, recorder.Body.String(), "SF1-2")
}

View File

@@ -36,7 +36,7 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Run</a>
<ul class="dropdown-menu">
<li><a href="#">Match Play</a></li>
<li><a href="/match_play">Match Play</a></li>
<li><a href="#">Match Review</a></li>
</ul>
</li>

54
templates/match_play.html Normal file
View File

@@ -0,0 +1,54 @@
{{define "title"}}Match Play{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-4">
<ul class="nav nav-tabs" style="margin-bottom: 15px;">
<li{{if eq .CurrentMatchType "practice" }} class="active"{{end}}>
<a href="#practice" data-toggle="tab">Practice</a>
</li>
<li{{if eq .CurrentMatchType "qualification" }} class="active"{{end}}>
<a href="#qualification" data-toggle="tab">Qualification</a>
</li>
<li{{if eq .CurrentMatchType "elimination" }} class="active"{{end}}>
<a href="#elimination" data-toggle="tab">Elimination</a>
</li>
</ul>
<div class="tab-content">
{{range $type, $matches := .MatchesByType}}
<div class="tab-pane {{if eq $.CurrentMatchType $type }} active{{end}}" id="{{$type}}">
<table class="table table-striped table-hover ">
<thead>
<tr>
<th>Match</th>
<th>Time</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{{range $match := $matches}}
<tr class="{{$match.ColorClass}}">
<td>{{$match.DisplayName}}</td>
<td>{{$match.Time}}</td>
<td class="text-center" style="white-space: nowrap;">
<a href="/match_play/{{$match.Id}}/generate_fake_result">
<b class="btn btn-info btn-xs">Generate Fake Result</b>
</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
</div>
<div class="col-lg-8">
<div class="jumbotron">
<h2>Placeholder for the match play interface.</h2>
<p>For now, use the "Generate Fake Result" buttons to simulate matches being played and scored.</p>
</div>
</div>
</div>
{{end}}
{{define "script"}}
{{end}}

2
web.go
View File

@@ -82,6 +82,8 @@ func newHandler() http.Handler {
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("/match_play", MatchPlayHandler).Methods("GET")
router.HandleFunc("/match_play/{matchId}/generate_fake_result", MatchPlayFakeResultHandler).Methods("GET")
router.HandleFunc("/reports/csv/rankings", RankingsCsvReportHandler)
router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler)
router.HandleFunc("/reports/json/rankings", RankingsJSONReportHandler)