mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4bbf26b31 | ||
|
|
02cb9e4e41 | ||
|
|
e8c7d1103b | ||
|
|
5ee07ee33f | ||
|
|
349313c486 | ||
|
|
1165e71503 | ||
|
|
9914768c1d | ||
|
|
ce9cb085cb | ||
|
|
642116bd62 | ||
|
|
3d566f4866 | ||
|
|
d7c3b9df6c | ||
|
|
8e46617706 | ||
|
|
cf58a2174e | ||
|
|
4bee40e66b | ||
|
|
baaf3b694b | ||
|
|
1acf221ac8 | ||
|
|
600b50d840 | ||
|
|
1c2197e690 | ||
|
|
3ea4e26af3 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.20.x
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.20.x
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: Build
|
||||
@@ -15,4 +15,4 @@ jobs:
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
- name: Check formatting
|
||||
run: go fmt ./...
|
||||
run: test -z "$(go fmt ./...)"
|
||||
|
||||
@@ -34,15 +34,15 @@ var doubleEliminationBracketMatchupTemplates = []matchupTemplate{
|
||||
matchupKey: newMatchupKey(1, 3),
|
||||
displayName: "3",
|
||||
NumWinsToAdvance: 1,
|
||||
redAllianceSource: allianceSource{allianceId: 3},
|
||||
blueAllianceSource: allianceSource{allianceId: 6},
|
||||
redAllianceSource: allianceSource{allianceId: 2},
|
||||
blueAllianceSource: allianceSource{allianceId: 7},
|
||||
},
|
||||
{
|
||||
matchupKey: newMatchupKey(1, 4),
|
||||
displayName: "4",
|
||||
NumWinsToAdvance: 1,
|
||||
redAllianceSource: allianceSource{allianceId: 2},
|
||||
blueAllianceSource: allianceSource{allianceId: 7},
|
||||
redAllianceSource: allianceSource{allianceId: 3},
|
||||
blueAllianceSource: allianceSource{allianceId: 6},
|
||||
},
|
||||
{
|
||||
matchupKey: newMatchupKey(2, 1),
|
||||
@@ -90,28 +90,28 @@ var doubleEliminationBracketMatchupTemplates = []matchupTemplate{
|
||||
matchupKey: newMatchupKey(4, 1),
|
||||
displayName: "11",
|
||||
NumWinsToAdvance: 1,
|
||||
redAllianceSource: newWinnerAllianceSource(3, 1),
|
||||
blueAllianceSource: newWinnerAllianceSource(3, 2),
|
||||
redAllianceSource: newWinnerAllianceSource(2, 3),
|
||||
blueAllianceSource: newWinnerAllianceSource(2, 4),
|
||||
},
|
||||
{
|
||||
matchupKey: newMatchupKey(4, 2),
|
||||
displayName: "12",
|
||||
NumWinsToAdvance: 1,
|
||||
redAllianceSource: newWinnerAllianceSource(2, 3),
|
||||
blueAllianceSource: newWinnerAllianceSource(2, 4),
|
||||
redAllianceSource: newWinnerAllianceSource(3, 2),
|
||||
blueAllianceSource: newWinnerAllianceSource(3, 1),
|
||||
},
|
||||
{
|
||||
matchupKey: newMatchupKey(5, 1),
|
||||
displayName: "13",
|
||||
NumWinsToAdvance: 1,
|
||||
redAllianceSource: newLoserAllianceSource(4, 2),
|
||||
blueAllianceSource: newWinnerAllianceSource(4, 1),
|
||||
redAllianceSource: newLoserAllianceSource(4, 1),
|
||||
blueAllianceSource: newWinnerAllianceSource(4, 2),
|
||||
},
|
||||
{
|
||||
matchupKey: newMatchupKey(6, 1),
|
||||
displayName: "F",
|
||||
NumWinsToAdvance: 2,
|
||||
redAllianceSource: newWinnerAllianceSource(4, 2),
|
||||
redAllianceSource: newWinnerAllianceSource(4, 1),
|
||||
blueAllianceSource: newWinnerAllianceSource(5, 1),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ func TestDoubleEliminationInitial(t *testing.T) {
|
||||
if assert.Equal(t, 4, len(matches)) {
|
||||
assertMatch(t, matches[0], "1", 1, 8)
|
||||
assertMatch(t, matches[1], "2", 4, 5)
|
||||
assertMatch(t, matches[2], "3", 3, 6)
|
||||
assertMatch(t, matches[3], "4", 2, 7)
|
||||
assertMatch(t, matches[2], "3", 2, 7)
|
||||
assertMatch(t, matches[3], "4", 3, 6)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,21 +65,21 @@ func TestDoubleEliminationProgression(t *testing.T) {
|
||||
assertMatch(t, matches[5], "7", 8, 4)
|
||||
}
|
||||
|
||||
scoreMatch(database, "3", game.RedWonMatch)
|
||||
scoreMatch(database, "3", game.BlueWonMatch)
|
||||
assert.Nil(t, bracket.Update(database, &dummyStartTime))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 6, len(matches))
|
||||
|
||||
scoreMatch(database, "4", game.BlueWonMatch)
|
||||
scoreMatch(database, "4", game.RedWonMatch)
|
||||
assert.Nil(t, bracket.Update(database, &dummyStartTime))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 8, len(matches)) {
|
||||
assertMatch(t, matches[4], "5", 1, 5)
|
||||
assertMatch(t, matches[5], "6", 6, 2)
|
||||
assertMatch(t, matches[5], "6", 2, 6)
|
||||
assertMatch(t, matches[6], "7", 8, 4)
|
||||
assertMatch(t, matches[7], "8", 3, 7)
|
||||
assertMatch(t, matches[7], "8", 7, 3)
|
||||
}
|
||||
|
||||
scoreMatch(database, "5", game.BlueWonMatch)
|
||||
@@ -88,7 +88,7 @@ func TestDoubleEliminationProgression(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 8, len(matches))
|
||||
|
||||
scoreMatch(database, "6", game.BlueWonMatch)
|
||||
scoreMatch(database, "6", game.RedWonMatch)
|
||||
assert.Nil(t, bracket.Update(database, &dummyStartTime))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
@@ -102,13 +102,13 @@ func TestDoubleEliminationProgression(t *testing.T) {
|
||||
assertMatch(t, matches[8], "9", 8, 2)
|
||||
}
|
||||
|
||||
scoreMatch(database, "8", game.RedWonMatch)
|
||||
scoreMatch(database, "8", game.BlueWonMatch)
|
||||
assert.Nil(t, bracket.Update(database, &dummyStartTime))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 11, len(matches)) {
|
||||
assertMatch(t, matches[9], "10", 7, 5)
|
||||
assertMatch(t, matches[10], "12", 4, 3)
|
||||
assertMatch(t, matches[10], "11", 4, 3)
|
||||
}
|
||||
|
||||
scoreMatch(database, "9", game.RedWonMatch)
|
||||
@@ -122,11 +122,11 @@ func TestDoubleEliminationProgression(t *testing.T) {
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 12, len(matches)) {
|
||||
assertMatch(t, matches[10], "11", 8, 7)
|
||||
assertMatch(t, matches[11], "12", 4, 3)
|
||||
assertMatch(t, matches[10], "11", 4, 3)
|
||||
assertMatch(t, matches[11], "12", 7, 8)
|
||||
}
|
||||
|
||||
scoreMatch(database, "11", game.BlueWonMatch)
|
||||
scoreMatch(database, "11", game.RedWonMatch)
|
||||
assert.Nil(t, bracket.Update(database, &dummyStartTime))
|
||||
matches, err = database.GetMatchesByType("elimination")
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestMatchupDisplayNames(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "Finals", bracket.FinalsMatchup.LongDisplayName())
|
||||
assert.Equal(t, "F-1", bracket.FinalsMatchup.matchDisplayName(1))
|
||||
assert.Equal(t, "W 12", bracket.FinalsMatchup.RedAllianceSourceDisplayName())
|
||||
assert.Equal(t, "W 11", bracket.FinalsMatchup.RedAllianceSourceDisplayName())
|
||||
assert.Equal(t, "W 13", bracket.FinalsMatchup.BlueAllianceSourceDisplayName())
|
||||
|
||||
match13, err := bracket.GetMatchup(5, 1)
|
||||
@@ -25,8 +25,8 @@ func TestMatchupDisplayNames(t *testing.T) {
|
||||
assert.Equal(t, "Match 13", match13.LongDisplayName())
|
||||
assert.Equal(t, "13", match13.matchDisplayName(1))
|
||||
assert.Equal(t, "13-2", match13.matchDisplayName(2))
|
||||
assert.Equal(t, "L 12", match13.RedAllianceSourceDisplayName())
|
||||
assert.Equal(t, "W 11", match13.BlueAllianceSourceDisplayName())
|
||||
assert.Equal(t, "L 11", match13.RedAllianceSourceDisplayName())
|
||||
assert.Equal(t, "W 12", match13.BlueAllianceSourceDisplayName())
|
||||
|
||||
bracket, err = NewSingleEliminationBracket(8)
|
||||
assert.Nil(t, err)
|
||||
|
||||
7
main.go
7
main.go
@@ -1,14 +1,15 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
|
||||
// Go version 1.20 or newer is required due to how it initializes the PRNG.
|
||||
//go:build go1.20
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena-lite/field"
|
||||
"github.com/Team254/cheesy-arena-lite/web"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const eventDbPath = "./event.db"
|
||||
@@ -16,8 +17,6 @@ const httpPort = 8080
|
||||
|
||||
// Main entry point for the application.
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
arena, err := field.NewArena(eventDbPath)
|
||||
if err != nil {
|
||||
log.Fatalln("Error during startup: ", err)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena-lite/game"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -40,8 +39,6 @@ type Match struct {
|
||||
Status game.MatchStatus
|
||||
}
|
||||
|
||||
var elimRoundNames = map[int]string{1: "F", 2: "SF", 4: "QF", 8: "EF"}
|
||||
|
||||
func (database *Database) CreateMatch(match *Match) error {
|
||||
return database.matchTable.create(match)
|
||||
}
|
||||
@@ -124,7 +121,7 @@ func (match *Match) IsComplete() bool {
|
||||
}
|
||||
|
||||
func (match *Match) CapitalizedType() string {
|
||||
if match.Type == "" {
|
||||
if match.Type == "" || match.Type == "test" {
|
||||
return ""
|
||||
} else if match.Type == "elimination" {
|
||||
return "Playoff"
|
||||
@@ -141,16 +138,6 @@ func (match *Match) TypePrefix() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (match *Match) TbaCode() string {
|
||||
if match.Type == "qualification" {
|
||||
return fmt.Sprintf("qm%s", match.DisplayName)
|
||||
} else if match.Type == "elimination" {
|
||||
return fmt.Sprintf("%s%dm%d", strings.ToLower(elimRoundNames[match.ElimRound]), match.ElimGroup,
|
||||
match.ElimInstance)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Returns true if the match is of a type that allows substitution of teams.
|
||||
func (match *Match) ShouldAllowSubstitution() bool {
|
||||
return match.Type != "qualification"
|
||||
|
||||
@@ -112,18 +112,3 @@ func TestGetMatchesByType(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(matches))
|
||||
}
|
||||
|
||||
func TestTbaCode(t *testing.T) {
|
||||
match := Match{Type: "practice", DisplayName: "3"}
|
||||
assert.Equal(t, "", match.TbaCode())
|
||||
match = Match{Type: "qualification", DisplayName: "26"}
|
||||
assert.Equal(t, "qm26", match.TbaCode())
|
||||
match = Match{Type: "elimination", DisplayName: "EF2-1", ElimRound: 8, ElimGroup: 2, ElimInstance: 1}
|
||||
assert.Equal(t, "ef2m1", match.TbaCode())
|
||||
match = Match{Type: "elimination", DisplayName: "QF3-2", ElimRound: 4, ElimGroup: 3, ElimInstance: 2}
|
||||
assert.Equal(t, "qf3m2", match.TbaCode())
|
||||
match = Match{Type: "elimination", DisplayName: "SF1-3", ElimRound: 2, ElimGroup: 1, ElimInstance: 3}
|
||||
assert.Equal(t, "sf1m3", match.TbaCode())
|
||||
match = Match{Type: "elimination", DisplayName: "F2", ElimRound: 1, ElimGroup: 1, ElimInstance: 2}
|
||||
assert.Equal(t, "f1m2", match.TbaCode())
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,6 +39,7 @@ type TbaMatch struct {
|
||||
Alliances map[string]*TbaAlliance `json:"alliances"`
|
||||
TimeString string `json:"time_string"`
|
||||
TimeUtc string `json:"time_utc"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
type TbaAlliance struct {
|
||||
@@ -54,7 +56,9 @@ type TbaRanking struct {
|
||||
Auto int
|
||||
Endgame int
|
||||
Teleop int
|
||||
WinLossTie string
|
||||
Wins int `json:"wins"`
|
||||
Losses int `json:"losses"`
|
||||
Ties int `json:"ties"`
|
||||
Dqs int `json:"dqs"`
|
||||
Played int `json:"played"`
|
||||
}
|
||||
@@ -101,6 +105,33 @@ type TbaPublishedAward struct {
|
||||
Awardee string `json:"awardee"`
|
||||
}
|
||||
|
||||
type elimMatchKey struct {
|
||||
elimRound int
|
||||
elimGroup int
|
||||
}
|
||||
|
||||
type tbaElimMatchKey struct {
|
||||
compLevel string
|
||||
setNumber int
|
||||
}
|
||||
|
||||
var doubleEliminationMatchKeyMapping = map[elimMatchKey]tbaElimMatchKey{
|
||||
{1, 1}: {"ef", 1},
|
||||
{1, 2}: {"ef", 2},
|
||||
{1, 3}: {"ef", 3},
|
||||
{1, 4}: {"ef", 4},
|
||||
{2, 1}: {"ef", 5},
|
||||
{2, 2}: {"ef", 6},
|
||||
{2, 3}: {"qf", 1},
|
||||
{2, 4}: {"qf", 2},
|
||||
{3, 1}: {"qf", 3},
|
||||
{3, 2}: {"qf", 4},
|
||||
{4, 1}: {"sf", 1},
|
||||
{4, 2}: {"sf", 2},
|
||||
{5, 1}: {"f", 1},
|
||||
{6, 1}: {"f", 2},
|
||||
}
|
||||
|
||||
func NewTbaClient(eventCode, secretId, secret string) *TbaClient {
|
||||
return &TbaClient{BaseUrl: tbaBaseUrl, eventCode: eventCode, secretId: secretId, secret: secret,
|
||||
eventNamesCache: make(map[string]string)}
|
||||
@@ -266,6 +297,10 @@ func (client *TbaClient) PublishMatches(database *model.Database) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eventSettings, err := database.GetEventSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matches := append(qualMatches, elimMatches...)
|
||||
tbaMatches := make([]TbaMatch, len(matches))
|
||||
|
||||
@@ -298,12 +333,16 @@ func (client *TbaClient) PublishMatches(database *model.Database) error {
|
||||
alliances["blue"] = createTbaAlliance([3]int{match.Blue1, match.Blue2, match.Blue3},
|
||||
[3]bool{match.Blue1IsSurrogate, match.Blue2IsSurrogate, match.Blue3IsSurrogate}, blueScore)
|
||||
|
||||
tbaMatches[i] = TbaMatch{"qm", 0, matchNumber, alliances, match.Time.Local().Format("3:04 PM"),
|
||||
match.Time.UTC().Format("2006-01-02T15:04:05")}
|
||||
tbaMatches[i] = TbaMatch{
|
||||
CompLevel: "qm",
|
||||
SetNumber: 0,
|
||||
MatchNumber: matchNumber,
|
||||
Alliances: alliances,
|
||||
TimeString: match.Time.Local().Format("3:04 PM"),
|
||||
TimeUtc: match.Time.UTC().Format("2006-01-02T15:04:05"),
|
||||
}
|
||||
if match.Type == "elimination" {
|
||||
tbaMatches[i].CompLevel = map[int]string{1: "f", 2: "sf", 4: "qf", 8: "ef"}[match.ElimRound]
|
||||
tbaMatches[i].SetNumber = match.ElimGroup
|
||||
tbaMatches[i].MatchNumber = match.ElimInstance
|
||||
setElimMatchKey(&tbaMatches[i], &match, eventSettings.ElimType)
|
||||
}
|
||||
}
|
||||
jsonBody, err := json.Marshal(tbaMatches)
|
||||
@@ -331,14 +370,21 @@ func (client *TbaClient) PublishRankings(database *model.Database) error {
|
||||
}
|
||||
|
||||
// Build a JSON object of TBA-format rankings.
|
||||
breakdowns := []string{"RP", "Auto", "Endgame", "Teleop", "WinLossTie"}
|
||||
breakdowns := []string{"RP", "Auto", "Endgame", "Teleop"}
|
||||
tbaRankings := make([]TbaRanking, len(rankings))
|
||||
for i, ranking := range rankings {
|
||||
tbaRankings[i] = TbaRanking{getTbaTeam(ranking.TeamId), ranking.Rank,
|
||||
float32(ranking.RankingPoints) / float32(ranking.Played), ranking.AutoPoints, ranking.EndgamePoints,
|
||||
ranking.TeleopPoints,
|
||||
fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties), 0,
|
||||
ranking.Played}
|
||||
tbaRankings[i] = TbaRanking{
|
||||
TeamKey: getTbaTeam(ranking.TeamId),
|
||||
Rank: ranking.Rank,
|
||||
RP: float32(ranking.RankingPoints) / float32(ranking.Played),
|
||||
Auto: ranking.AutoPoints,
|
||||
Endgame: ranking.EndgamePoints,
|
||||
Teleop: ranking.TeleopPoints,
|
||||
Wins: ranking.Wins,
|
||||
Losses: ranking.Losses,
|
||||
Ties: ranking.Ties,
|
||||
Played: ranking.Played,
|
||||
}
|
||||
}
|
||||
jsonBody, err := json.Marshal(TbaRankings{breakdowns, tbaRankings})
|
||||
if err != nil {
|
||||
@@ -503,11 +549,20 @@ func (client *TbaClient) PublishAwards(database *model.Database) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the sum of all values in the slice representing different stages for a power cell goal.
|
||||
func sumPowerCells(cells []int) int {
|
||||
var total int
|
||||
for _, cell := range cells {
|
||||
total += cell
|
||||
// Sets the match key attributes on TbaMatch based on the match and bracket type.
|
||||
func setElimMatchKey(tbaMatch *TbaMatch, match *model.Match, elimType string) {
|
||||
if elimType == "single" {
|
||||
tbaMatch.CompLevel = map[int]string{1: "ef", 2: "qf", 3: "sf", 4: "f"}[match.ElimRound]
|
||||
tbaMatch.SetNumber = match.ElimGroup
|
||||
tbaMatch.MatchNumber = match.ElimInstance
|
||||
} else if elimType == "double" {
|
||||
if tbaKey, ok := doubleEliminationMatchKeyMapping[elimMatchKey{match.ElimRound, match.ElimGroup}]; ok {
|
||||
tbaMatch.CompLevel = tbaKey.compLevel
|
||||
tbaMatch.SetNumber = tbaKey.setNumber
|
||||
}
|
||||
tbaMatch.MatchNumber = match.ElimInstance
|
||||
if !strings.HasPrefix(match.DisplayName, "F") {
|
||||
tbaMatch.DisplayName = "Match " + match.DisplayName
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestPublishMatches(t *testing.T) {
|
||||
|
||||
match1 := model.Match{Type: "qualification", DisplayName: "2", Time: time.Unix(600, 0), Red1: 7, Red2: 8, Red3: 9,
|
||||
Blue1: 10, Blue2: 11, Blue3: 12, Status: game.RedWonMatch}
|
||||
match2 := model.Match{Type: "elimination", DisplayName: "SF2-2", ElimRound: 2, ElimGroup: 2, ElimInstance: 2}
|
||||
match2 := model.Match{Type: "elimination", DisplayName: "SF2-2", ElimRound: 3, ElimGroup: 2, ElimInstance: 2}
|
||||
database.CreateMatch(&match1)
|
||||
database.CreateMatch(&match2)
|
||||
matchResult1 := model.BuildTestMatchResult(match1.Id, 1)
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
)
|
||||
|
||||
// Discrete inputs
|
||||
//
|
||||
//go:generate stringer -type=input
|
||||
type input int
|
||||
|
||||
@@ -59,6 +60,7 @@ const (
|
||||
)
|
||||
|
||||
// 16-bit registers
|
||||
//
|
||||
//go:generate stringer -type=register
|
||||
type register int
|
||||
|
||||
@@ -68,6 +70,7 @@ const (
|
||||
)
|
||||
|
||||
// Coils
|
||||
//
|
||||
//go:generate stringer -type=coil
|
||||
type coil int
|
||||
|
||||
@@ -84,6 +87,7 @@ const (
|
||||
)
|
||||
|
||||
// Bitmask for decoding fieldIoConnection into individual ArmorBlock connection statuses.
|
||||
//
|
||||
//go:generate stringer -type=armorBlock
|
||||
type armorBlock int
|
||||
|
||||
|
||||
@@ -120,8 +120,8 @@ body[data-position=right] #inMatch #blueScore {
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
font-size: 550px;
|
||||
line-height: 550px;
|
||||
font-size: 450px;
|
||||
line-height: 450px;
|
||||
text-align: center;
|
||||
}
|
||||
#preMatch .databar {
|
||||
|
||||
@@ -449,6 +449,9 @@ html {
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
}
|
||||
#bracketSvg {
|
||||
width: 100%;
|
||||
}
|
||||
#sponsor {
|
||||
position: fixed;
|
||||
width: 1000px;
|
||||
|
||||
@@ -38,3 +38,6 @@ body {
|
||||
margin: auto auto;
|
||||
text-align: center;
|
||||
}
|
||||
#bracketSvg {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@
|
||||
.label-scoring[data-ready=true] {
|
||||
background-color: #0c6;
|
||||
}
|
||||
.label-saved-match {
|
||||
background-color: #999;
|
||||
}
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -102,7 +105,7 @@ td[data-plc-value="true"] {
|
||||
color: #090;
|
||||
}
|
||||
.btn-game-sound {
|
||||
width: 110px;
|
||||
width: 160px;
|
||||
text-align: left;
|
||||
}
|
||||
input[data-changed="true"], select[data-changed="true"] {
|
||||
|
||||
@@ -34,7 +34,7 @@ h1 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#matchState, #matchTime {
|
||||
font-size: 35px;
|
||||
font-size: 25px;
|
||||
color: #666;
|
||||
}
|
||||
#matchTime {
|
||||
|
||||
@@ -170,7 +170,7 @@ var handleScorePosted = function(data) {
|
||||
$("#finalMatchName").text(data.MatchType + " " + data.Match.DisplayName);
|
||||
|
||||
// Reload the bracket to reflect any changes.
|
||||
$("#bracketSvg").attr("src", "/api/bracket/svg?v=" + new Date().getTime());
|
||||
$("#bracketSvg").attr("src", "/api/bracket/svg?activeMatch=saved&v=" + new Date().getTime());
|
||||
};
|
||||
|
||||
// Handles a websocket message to play a sound to signal match start/stop/etc.
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
var websocket;
|
||||
|
||||
// Handles a websocket message to populate the final score data, which also triggers a bracket update.
|
||||
const handleScorePosted = function(data) {
|
||||
$("#bracketSvg").attr("src", "/api/bracket/svg?v=" + new Date().getTime());
|
||||
// Handles a websocket message to load a new match.
|
||||
const handleMatchLoad = function(data) {
|
||||
$("#bracketSvg").attr("src", "/api/bracket/svg?activeMatch=current&v=" + new Date().getTime());
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Set up the websocket back to the server.
|
||||
websocket = new CheesyWebsocket("/displays/bracket/websocket", {
|
||||
scorePosted: function(event) { handleScorePosted(event.data); },
|
||||
matchLoad: function(event) { handleMatchLoad(event.data); },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -211,8 +211,8 @@
|
||||
.bracket_double #match_3_2 {transform: translate(709px, 504px);}
|
||||
.bracket_double #match_3_1 {transform: translate(709px, 694px);}
|
||||
|
||||
.bracket_double #match_4_2 {transform: translate(1006px, 301px);}
|
||||
.bracket_double #match_4_1 {transform: translate(1006px, 599px);}
|
||||
.bracket_double #match_4_1 {transform: translate(1006px, 301px);}
|
||||
.bracket_double #match_4_2 {transform: translate(1006px, 599px);}
|
||||
|
||||
.bracket_double #match_5_1 {transform: translate(1302px, 567px);}
|
||||
|
||||
@@ -278,6 +278,7 @@
|
||||
<polyline points="617,247 703,247 703,437 617,437"/>
|
||||
<polyline points="1006,390 861,390 861,336 704,336"/>
|
||||
</g>
|
||||
{{if .ShowTemporaryConnectors}}
|
||||
<g id="connectors_temporary">
|
||||
<g id="connector_1_1"{{if (index .Matchups "1_1").IsActive}} class="active"{{end}}>
|
||||
<polyline class="loser" points="411,593 367,593 367,247"/>
|
||||
@@ -374,19 +375,6 @@
|
||||
<text transform="translate(640.2857 472.1667)" class="loser">L</text>
|
||||
</g>
|
||||
<g id="connectors_4_1"{{if (index .Matchups "4_1").IsActive}} class="active"{{end}}>
|
||||
<rect class="coverup" x="955.326" y="589.109" width="68.612" height="200.486"/>
|
||||
<text transform="translate(1223.2803 681.0883)">W</text>
|
||||
<text transform="translate(1223.3726 717.1893)" class="loser">L</text>
|
||||
<line class="loser" x1="1238" y1="689" x2="1238" y2="738"/>
|
||||
<line class="loser" x1="1246.485" y1="729.515" x2="1229.515" y2="746.485"/>
|
||||
<line class="loser" x1="1246.485" y1="746.485" x2="1229.515" y2="729.515"/>
|
||||
<text transform="translate(926.2617 775.3125)">W</text>
|
||||
<text transform="translate(926.2617 585.3125)">W</text>
|
||||
<polyline points="1008,725 984,725 954.488,593.142"/>
|
||||
<polyline class="coverup" points="1008,656 984,656 955.118,784.297"/>
|
||||
<polyline points="1008,655 984,655 955.118,783 898,783"/>
|
||||
</g>
|
||||
<g id="connectors_4_2"{{if (index .Matchups "4_2").IsActive}} class="active"{{end}}>
|
||||
<rect class="coverup" x="699.216" y="241.054" width="312.117" height="199.156"/>
|
||||
<polyline points="698,247 790,247 790,358 1010,358"/>
|
||||
<polyline points="698,437 790,437 790,421 1010,421"/>
|
||||
@@ -396,6 +384,18 @@
|
||||
<text transform="translate(1239.3726 418.7044)" class="loser">L</text>
|
||||
<polyline class="loser" points="1256,391 1256,625 1323,625"/>
|
||||
</g>
|
||||
<g id="connectors_4_2"{{if (index .Matchups "4_2").IsActive}} class="active"{{end}}>
|
||||
<rect class="coverup" x="955.326" y="589.109" width="68.612" height="200.486"/>
|
||||
<text transform="translate(1223.2803 681.0883)">W</text>
|
||||
<text transform="translate(1223.3726 717.1893)" class="loser">L</text>
|
||||
<line class="loser" x1="1238" y1="689" x2="1238" y2="738"/>
|
||||
<line class="loser" x1="1246.485" y1="729.515" x2="1229.515" y2="746.485"/>
|
||||
<line class="loser" x1="1246.485" y1="746.485" x2="1229.515" y2="729.515"/>
|
||||
<text transform="translate(926.2617 775.3125)">W</text>
|
||||
<text transform="translate(926.2617 585.3125)">W</text>
|
||||
<polyline points="955,593 955,655 1008,655"/>
|
||||
<polyline points="955,783 955,725 1008,725"/>
|
||||
</g>
|
||||
<g id="connectors_5_1"{{if (index .Matchups "5_1").IsActive}} class="active"{{end}}>
|
||||
<rect class="coverup" x="1523.794" y="381.275" width="83.494" height="287.877"/>
|
||||
<polyline points="1481,390 1553,390 1553,474 1633,474"/>
|
||||
@@ -419,6 +419,7 @@
|
||||
<text transform="translate(1225.4487 382.1549)">W</text>
|
||||
</g>
|
||||
</g>
|
||||
{{end}}
|
||||
</g>
|
||||
{{else}}
|
||||
<g id="connectors_standardbracket">
|
||||
|
||||
@@ -56,7 +56,11 @@
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="row text-center">
|
||||
<div id="matchState" class="col-lg-2 col-lg-offset-2 well well-sm text-center"> </div>
|
||||
<div class="col-lg-3 well well-sm text-center" style="text-transform: uppercase;">
|
||||
{{if eq .Match.Type "elimination"}}playoff{{else}}{{.Match.Type}}{{end}}
|
||||
{{if ne .Match.Type "test" }}{{.Match.DisplayName}}{{end}}
|
||||
</div>
|
||||
<div id="matchState" class="col-lg-3 well well-sm text-center"> </div>
|
||||
<div id="matchTime" class="col-lg-2 well well-sm text-center"> </div>
|
||||
<div id="redScore" class="col-lg-2 well well-sm well-red text-center"> </div>
|
||||
<div id="blueScore" class="col-lg-2 well well-sm well-blue text-center"> </div>
|
||||
@@ -290,6 +294,14 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p>Shown Match Result</p>
|
||||
<span class="label label-saved-match">
|
||||
{{if .SavedMatch.DisplayName}}{{.SavedMatchType}} {{.SavedMatch.DisplayName}}{{else}}None{{end}}
|
||||
</span>
|
||||
|
||||
<a href="/match_play/clear_result">
|
||||
<b class="btn btn-info btn-xs">Clear</b>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<p>Match Sounds</p>
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"github.com/Team254/cheesy-arena-lite/game"
|
||||
"github.com/Team254/cheesy-arena-lite/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateRankings(t *testing.T) {
|
||||
rand.Seed(1)
|
||||
database := setupTestDb(t)
|
||||
|
||||
setupMatchResultsForRankings(database)
|
||||
|
||||
19
web/api.go
19
web/api.go
@@ -228,19 +228,29 @@ func (web *Web) teamAvatarsApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (web *Web) bracketSvgApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var activeMatch *model.Match
|
||||
showTemporaryConnectors := false
|
||||
if activeMatchValue, ok := r.URL.Query()["activeMatch"]; ok {
|
||||
if activeMatchValue[0] == "current" {
|
||||
activeMatch = web.arena.CurrentMatch
|
||||
} else if activeMatchValue[0] == "saved" {
|
||||
activeMatch = web.arena.SavedMatch
|
||||
showTemporaryConnectors = true
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
if err := web.generateBracketSvg(w); err != nil {
|
||||
if err := web.generateBracketSvg(w, activeMatch, showTemporaryConnectors); err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (web *Web) generateBracketSvg(w io.Writer) error {
|
||||
func (web *Web) generateBracketSvg(w io.Writer, activeMatch *model.Match, showTemporaryConnectors bool) error {
|
||||
alliances, err := web.arena.Database.GetAllAlliances()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activeMatch := web.arena.SavedMatch
|
||||
|
||||
matchups := make(map[string]*allianceMatchup)
|
||||
if web.arena.PlayoffBracket != nil {
|
||||
@@ -297,6 +307,7 @@ func (web *Web) generateBracketSvg(w io.Writer) error {
|
||||
data := struct {
|
||||
BracketType string
|
||||
Matchups map[string]*allianceMatchup
|
||||
}{bracketType, matchups}
|
||||
ShowTemporaryConnectors bool
|
||||
}{bracketType, matchups, showTemporaryConnectors}
|
||||
return template.ExecuteTemplate(w, "bracket", data)
|
||||
}
|
||||
|
||||
@@ -49,5 +49,5 @@ func (web *Web) bracketDisplayWebsocketHandler(w http.ResponseWriter, r *http.Re
|
||||
defer ws.Close()
|
||||
|
||||
// Subscribe the websocket to the notifiers whose messages will be passed on to the client.
|
||||
ws.HandleNotifiers(display.Notifier, web.arena.ScorePostedNotifier, web.arena.ReloadDisplaysNotifier)
|
||||
ws.HandleNotifiers(display.Notifier, web.arena.MatchLoadNotifier, web.arena.ReloadDisplaysNotifier)
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@ func TestBracketDisplayWebsocket(t *testing.T) {
|
||||
|
||||
// Should get a few status updates right after connection.
|
||||
readWebsocketType(t, ws, "displayConfiguration")
|
||||
readWebsocketType(t, ws, "scorePosted")
|
||||
readWebsocketType(t, ws, "matchLoad")
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ func (web *Web) matchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
BlueScore *game.Score
|
||||
AllowSubstitution bool
|
||||
IsReplay bool
|
||||
SavedMatchType string
|
||||
SavedMatch *model.Match
|
||||
PlcArmorBlockStatuses map[string]bool
|
||||
}{
|
||||
web.arena.EventSettings,
|
||||
@@ -102,6 +104,8 @@ func (web *Web) matchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
web.arena.BlueScore,
|
||||
web.arena.CurrentMatch.ShouldAllowSubstitution(),
|
||||
isReplay,
|
||||
web.arena.SavedMatch.CapitalizedType(),
|
||||
web.arena.SavedMatch,
|
||||
web.arena.Plc.GetArmorBlockStatuses(),
|
||||
}
|
||||
err = template.ExecuteTemplate(w, "base", data)
|
||||
@@ -185,6 +189,20 @@ func (web *Web) matchPlayShowResultHandler(w http.ResponseWriter, r *http.Reques
|
||||
http.Redirect(w, r, "/match_play", 303)
|
||||
}
|
||||
|
||||
// Clears the match results display buffer.
|
||||
func (web *Web) matchPlayClearResultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !web.userIsAdmin(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load an empty match to effectively clear the buffer.
|
||||
web.arena.SavedMatch = &model.Match{}
|
||||
web.arena.SavedMatchResult = model.NewMatchResult()
|
||||
web.arena.ScorePostedNotifier.Notify()
|
||||
|
||||
http.Redirect(w, r, "/match_play", 303)
|
||||
}
|
||||
|
||||
// The websocket endpoint for the match play client to send control commands and receive status updates.
|
||||
func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !web.userIsAdmin(w, r) {
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestMatchPlayLoad(t *testing.T) {
|
||||
assert.NotContains(t, recorder.Body.String(), "106")
|
||||
}
|
||||
|
||||
func TestMatchPlayShowResult(t *testing.T) {
|
||||
func TestMatchPlayShowAndClearResult(t *testing.T) {
|
||||
web := setupTestWeb(t)
|
||||
|
||||
recorder := web.getHttpResponse("/match_play/1/show_result")
|
||||
@@ -107,6 +107,11 @@ func TestMatchPlayShowResult(t *testing.T) {
|
||||
assert.Equal(t, 303, recorder.Code)
|
||||
assert.Equal(t, match.Id, web.arena.SavedMatch.Id)
|
||||
assert.Equal(t, match.Id, web.arena.SavedMatchResult.MatchId)
|
||||
|
||||
recorder = web.getHttpResponse("/match_play/clear_result")
|
||||
assert.Equal(t, 303, recorder.Code)
|
||||
assert.Equal(t, model.Match{}, *web.arena.SavedMatch)
|
||||
assert.Equal(t, *model.NewMatchResult(), *web.arena.SavedMatchResult)
|
||||
}
|
||||
|
||||
func TestMatchPlayErrors(t *testing.T) {
|
||||
|
||||
@@ -681,7 +681,7 @@ func (web *Web) alliancesPdfReportHandler(w http.ResponseWriter, r *http.Request
|
||||
// suitable Go library for doing so appears to exist).
|
||||
func (web *Web) bracketPdfReportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
buffer := new(bytes.Buffer)
|
||||
err := web.generateBracketSvg(buffer)
|
||||
err := web.generateBracketSvg(buffer, nil, false)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
|
||||
@@ -27,6 +27,7 @@ func (web *Web) scheduleGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
matchType := getMatchType(r)
|
||||
if matchType == "" {
|
||||
http.Redirect(w, r, "/setup/schedule?matchType=practice", 302)
|
||||
return
|
||||
}
|
||||
|
||||
if matchType != "practice" && matchType != "qualification" {
|
||||
|
||||
@@ -145,6 +145,7 @@ func (web *Web) newHandler() http.Handler {
|
||||
router.HandleFunc("/match_play", web.matchPlayHandler).Methods("GET")
|
||||
router.HandleFunc("/match_play/{matchId}/load", web.matchPlayLoadHandler).Methods("GET")
|
||||
router.HandleFunc("/match_play/{matchId}/show_result", web.matchPlayShowResultHandler).Methods("GET")
|
||||
router.HandleFunc("/match_play/clear_result", web.matchPlayClearResultHandler).Methods("GET")
|
||||
router.HandleFunc("/match_play/websocket", web.matchPlayWebsocketHandler).Methods("GET")
|
||||
router.HandleFunc("/match_review", web.matchReviewHandler).Methods("GET")
|
||||
router.HandleFunc("/match_review/{matchId}/edit", web.matchReviewEditGetHandler).Methods("GET")
|
||||
|
||||
Reference in New Issue
Block a user