mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Added basic structure of match control interface.
This commit is contained in:
@@ -19,6 +19,7 @@ type MatchPlayListItem struct {
|
||||
Id int
|
||||
DisplayName string
|
||||
Time string
|
||||
Status string
|
||||
ColorClass string
|
||||
}
|
||||
|
||||
@@ -27,6 +28,8 @@ type MatchPlayList []MatchPlayListItem
|
||||
// Global var to hold the current active tournament so that its matches are displayed by default.
|
||||
var currentMatchType string
|
||||
|
||||
var currentMatch *Match
|
||||
|
||||
// Shows the match play control interface.
|
||||
func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
practiceMatches, err := buildMatchPlayList("practice")
|
||||
@@ -55,11 +58,15 @@ func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if currentMatchType == "" {
|
||||
currentMatchType = "practice"
|
||||
}
|
||||
if currentMatch == nil {
|
||||
currentMatch = new(Match)
|
||||
}
|
||||
data := struct {
|
||||
*EventSettings
|
||||
MatchesByType map[string]MatchPlayList
|
||||
CurrentMatchType string
|
||||
}{eventSettings, matchesByType, currentMatchType}
|
||||
Match *Match
|
||||
}{eventSettings, matchesByType, currentMatchType, currentMatch}
|
||||
err = template.ExecuteTemplate(w, "base", data)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
@@ -67,6 +74,26 @@ func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func MatchPlayQueueHandler(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
|
||||
}
|
||||
|
||||
// TODO(pat): Disallow if there is a match currently being played or there are uncommitted results.
|
||||
currentMatch = match
|
||||
currentMatchType = match.Type
|
||||
|
||||
http.Redirect(w, r, "/match_play", 302)
|
||||
}
|
||||
|
||||
func MatchPlayFakeResultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
matchId, _ := strconv.Atoi(vars["matchId"])
|
||||
@@ -149,7 +176,7 @@ func (list MatchPlayList) Len() int {
|
||||
}
|
||||
|
||||
func (list MatchPlayList) Less(i, j int) bool {
|
||||
return list[i].ColorClass == "" && list[j].ColorClass != ""
|
||||
return list[i].Status != "complete" && list[j].Status == "complete"
|
||||
}
|
||||
|
||||
func (list MatchPlayList) Swap(i, j int) {
|
||||
@@ -173,6 +200,7 @@ func buildMatchPlayList(matchType string) (MatchPlayList, error) {
|
||||
matchPlayList[i].Id = match.Id
|
||||
matchPlayList[i].DisplayName = prefix + match.DisplayName
|
||||
matchPlayList[i].Time = match.Time.Format("3:04 PM")
|
||||
matchPlayList[i].Status = match.Status
|
||||
switch match.Winner {
|
||||
case "R":
|
||||
matchPlayList[i].ColorClass = "danger"
|
||||
@@ -183,6 +211,9 @@ func buildMatchPlayList(matchType string) (MatchPlayList, error) {
|
||||
default:
|
||||
matchPlayList[i].ColorClass = ""
|
||||
}
|
||||
if currentMatch != nil && matchPlayList[i].Id == currentMatch.Id {
|
||||
matchPlayList[i].ColorClass = "success"
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the list to put all completed matches at the bottom.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
@@ -37,3 +38,52 @@ func TestMatchPlay(t *testing.T) {
|
||||
assert.Contains(t, recorder.Body.String(), "SF1-1")
|
||||
assert.Contains(t, recorder.Body.String(), "SF1-2")
|
||||
}
|
||||
|
||||
func TestMatchPlayQueue(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
|
||||
match := Match{Type: "elimination", DisplayName: "QF4-3", Status: "complete", Winner: "R", Red1: 101,
|
||||
Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
|
||||
db.CreateMatch(&match)
|
||||
recorder := getHttpResponse("/match_play")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.NotContains(t, recorder.Body.String(), "101")
|
||||
assert.NotContains(t, recorder.Body.String(), "102")
|
||||
assert.NotContains(t, recorder.Body.String(), "103")
|
||||
assert.NotContains(t, recorder.Body.String(), "104")
|
||||
assert.NotContains(t, recorder.Body.String(), "105")
|
||||
assert.NotContains(t, recorder.Body.String(), "106")
|
||||
|
||||
// Queue the match and check for the team numbers again.
|
||||
recorder = getHttpResponse(fmt.Sprintf("/match_play/%d/queue", match.Id))
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/match_play")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "101")
|
||||
assert.Contains(t, recorder.Body.String(), "102")
|
||||
assert.Contains(t, recorder.Body.String(), "103")
|
||||
assert.Contains(t, recorder.Body.String(), "104")
|
||||
assert.Contains(t, recorder.Body.String(), "105")
|
||||
assert.Contains(t, recorder.Body.String(), "106")
|
||||
}
|
||||
|
||||
func TestMatchPlayErrors(t *testing.T) {
|
||||
clearDb()
|
||||
defer clearDb()
|
||||
var err error
|
||||
db, err = OpenDatabase(testDbPath)
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
|
||||
// Queue an invalid match.
|
||||
recorder := getHttpResponse("/match_play/1114/queue")
|
||||
assert.Equal(t, 500, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "Invalid match")
|
||||
}
|
||||
|
||||
@@ -16,3 +16,22 @@
|
||||
.well-darkblue {
|
||||
background-color: #c4e3f3;
|
||||
}
|
||||
.col-no-padding {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.ds-status, .robot-status, .battery-status, .bypass-button {
|
||||
background-color: #aaa;
|
||||
color: #000;
|
||||
border: 1px solid #999;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
width: 40px;
|
||||
height: 27px;
|
||||
margin: 2px;
|
||||
font-family: Arial;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.bypass-button {
|
||||
background-color: #0e8;
|
||||
}
|
||||
@@ -33,6 +33,9 @@
|
||||
<a href="/match_play/{{$match.Id}}/generate_fake_result">
|
||||
<b class="btn btn-info btn-xs">Generate Fake Result</b>
|
||||
</a>
|
||||
<a href="/match_play/{{$match.Id}}/queue">
|
||||
<b class="btn btn-info btn-xs">Queue</b>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -42,13 +45,65 @@
|
||||
{{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 class="col-lg-8 text-center">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 well well-darkblue">
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-4">Blue Teams</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station">DS</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Robot">R</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Battery">B</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">1 </div>
|
||||
{{template "matchPlayTeam" .Match.Blue1}}
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">2 </div>
|
||||
{{template "matchPlayTeam" .Match.Blue2}}
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">3 </div>
|
||||
{{template "matchPlayTeam" .Match.Blue3}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 well well-darkred">
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-4">Red Teams</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Driver Station">DS</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Robot">R</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Battery">B</div>
|
||||
<div class="col-lg-2" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">3 </div>
|
||||
{{template "matchPlayTeam" .Match.Red3}}
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">2 </div>
|
||||
{{template "matchPlayTeam" .Match.Red2}}
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-lg-1">1 </div>
|
||||
{{template "matchPlayTeam" .Match.Red1}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
<script>
|
||||
$("[data-toggle=tooltip]").tooltip({"placement": "top"});
|
||||
</script>
|
||||
{{end}}
|
||||
{{define "matchPlayTeam"}}
|
||||
<div class="col-lg-3">
|
||||
<input type="text" class="form-control input-sm" value="{{.}}">
|
||||
</div>
|
||||
<div class="col-lg-2 col-no-padding"><div class="ds-status" id="team{{.}}Ds"></div></div>
|
||||
<div class="col-lg-2 col-no-padding"><div class="robot-status" id="team{{.}}Robot"></div></div>
|
||||
<div class="col-lg-2 col-no-padding"><div class="battery-status" id="team{{.}}Battery"></div></div>
|
||||
<div class="col-lg-2 col-no-padding"><div class="bypass-button" id="team{{.}}Bypass"></div></div>
|
||||
{{end}}
|
||||
|
||||
22
web.go
22
web.go
@@ -11,9 +11,6 @@ import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const httpPort = 8080
|
||||
@@ -39,24 +36,6 @@ func ServeWebInterface() {
|
||||
http.Handle("/", newHandler())
|
||||
log.Printf("Serving HTTP requests on port %d", httpPort)
|
||||
|
||||
// Open in Default Web Browser
|
||||
// Necessary to Authenticate
|
||||
url := "http://localhost:" + strconv.Itoa(httpPort)
|
||||
var err error
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
case "windows":
|
||||
err = exec.Command(`rundll32.exe`, "url.dll,FileProtocolHandler", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
|
||||
// Start Server
|
||||
http.ListenAndServe(fmt.Sprintf(":%d", httpPort), nil)
|
||||
}
|
||||
@@ -83,6 +62,7 @@ func newHandler() http.Handler {
|
||||
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}/queue", MatchPlayQueueHandler).Methods("GET")
|
||||
router.HandleFunc("/match_play/{matchId}/generate_fake_result", MatchPlayFakeResultHandler).Methods("GET")
|
||||
router.HandleFunc("/match_review", MatchReviewHandler).Methods("GET")
|
||||
router.HandleFunc("/match_review/{matchId}/edit", MatchReviewEditGetHandler).Methods("GET")
|
||||
|
||||
Reference in New Issue
Block a user