Refactor Rules to have an ID that is referenced instead of copying details everywhere.

This commit is contained in:
Patrick Fairbank
2020-03-15 21:09:48 -07:00
parent 4c3850e2e4
commit 49758eaafd
18 changed files with 183 additions and 138 deletions

View File

@@ -201,14 +201,16 @@ func (arena *Arena) generateScorePostedMessage() interface{} {
BlueScoreSummary *game.ScoreSummary
RedFouls []game.Foul
BlueFouls []game.Foul
RulesViolated map[int]*game.Rule
RedCards map[string]string
BlueCards map[string]string
SeriesStatus string
SeriesLeader string
}{arena.SavedMatch.CapitalizedType(), arena.SavedMatch, arena.SavedMatchResult.RedScoreSummary(),
arena.SavedMatchResult.BlueScoreSummary(), populateFoulDescriptions(arena.SavedMatchResult.RedScore.Fouls),
populateFoulDescriptions(arena.SavedMatchResult.BlueScore.Fouls), arena.SavedMatchResult.RedCards,
arena.SavedMatchResult.BlueCards, seriesStatus, seriesLeader}
arena.SavedMatchResult.BlueScoreSummary(), arena.SavedMatchResult.RedScore.Fouls,
arena.SavedMatchResult.BlueScore.Fouls,
getRulesViolated(arena.SavedMatchResult.RedScore.Fouls, arena.SavedMatchResult.BlueScore.Fouls),
arena.SavedMatchResult.RedCards, arena.SavedMatchResult.BlueCards, seriesStatus, seriesLeader}
}
func (arena *Arena) generateScoringStatusMessage() interface{} {
@@ -236,17 +238,14 @@ func getAudienceAllianceScoreFields(allianceScore *RealtimeScore,
return fields
}
// Copy the description from the rules to the fouls so that they are available to the announcer.
func populateFoulDescriptions(fouls []game.Foul) []game.Foul {
foulsCopy := make([]game.Foul, len(fouls))
copy(foulsCopy, fouls)
for i := range foulsCopy {
for _, rule := range game.Rules {
if foulsCopy[i].RuleNumber == rule.RuleNumber {
foulsCopy[i].Description = rule.Description
break
}
}
// Produce a map of rules that were violated by either alliance so that they are available to the announcer.
func getRulesViolated(redFouls, blueFouls []game.Foul) map[int]*game.Rule {
rules := make(map[int]*game.Rule)
for _, foul := range redFouls {
rules[foul.RuleId] = game.GetRuleById(foul.RuleId)
}
return foulsCopy
for _, foul := range blueFouls {
rules[foul.RuleId] = game.GetRuleById(foul.RuleId)
}
return rules
}

View File

@@ -1,56 +1,27 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Model of a foul and game-specific rules.
// Model of a foul.
package game
type Foul struct {
Rule
RuleId int
TeamId int
TimeInMatchSec float64
}
type Rule struct {
RuleNumber string
IsTechnical bool
IsRankingPoint bool
Description string
}
// All rules from the 2018 game that carry point penalties.
var Rules = []Rule{
{"S6", false, false, "DRIVE TEAMS may not extend any body part into the CARGO Chute. Momentary encroachment into the Chute is an exception to this rule."},
{"C8", false, false, "Strategies clearly aimed at forcing the opposing ALLIANCE to violate a rule are not in the spirit of FIRST Robotics Competition and not allowed."},
{"G3", true, false, "During the SANDSTORM PERIOD, a ROBOT may not cross the FIELD such that its BUMPERS break the plane defined by their opponents CARGO SHIP LINE."},
{"G4", false, false, "ROBOTS may not have greater-than-momentary or repeated control, i.e. exercise greater-than-momentary or repeated influence, of more than one (1) GAME PIECE at a time, either directly or transitively through other objects."},
{"G5", false, true, "A ROBOT may not remove a GAME PIECE from an opponents ROCKET/CARGO SHIP."},
{"G7", false, false, "ROBOTS may not intentionally eject GAME PIECES from the FIELD."},
{"G8", false, false, "ROBOTS may not deliberately use GAME PIECES in an attempt to ease or amplify the challenge associated with FIELD elements."},
{"G9", false, false, "No more than one ROBOT may be positioned such that its BUMPERS are completely beyond the opponents CARGO SHIP LINE."},
{"G9", true, false, "No more than one ROBOT may be positioned such that its BUMPERS are completely beyond the opponents CARGO SHIP LINE."},
{"G10", false, false, "No part of a ROBOT, except its BUMPERS, may be outside its FRAME PERIMETER if its BUMPERS are completely beyond its opponents CARGO SHIP LINE."},
{"G10", true, false, "No part of a ROBOT, except its BUMPERS, may be outside its FRAME PERIMETER if its BUMPERS are completely beyond its opponents CARGO SHIP LINE."},
{"G12", false, false, "A ROBOT may not break the vertical plane above the ALLIANCE STATION WALL or damage the SANDSTORM."},
{"G13", false, false, "A ROBOT may not contact an opponent ROBOT if that opponent ROBOTS BUMPERS are fully in their HAB ZONE."},
{"G15", false, false, "DRIVE TEAMS, ROBOTS, and OPERATOR CONSOLES are prohibited from the following actions with regards to interaction with ARENA elements: grabbing, grasping, attaching to, hanging, deforming, becoming entangled, damaging, and repositioning GAME PIECE holders."},
{"G16", false, true, "During Qualification MATCHES, ROBOTS may not contact opponents ROCKETS starting at T-minus 20s."},
{"G17", false, false, "Fallen (i.e. tipped over) ROBOTS attempting to right themselves (either by themselves or with assistance from a partner ROBOT) have one ten (10) second grace period in which they may not be contacted by an opponent ROBOT."},
{"G18", false, false, "ROBOTS may not pin an opponents ROBOT for more than five (5) seconds."},
{"G18", true, false, "ROBOTS may not pin an opponents ROBOT for more than five (5) seconds."},
{"G19", true, false, "Strategies aimed at the destruction or inhibition of ROBOTS via attachment, damage, tipping, or entanglements are not allowed."},
{"G20", true, false, "Initiating deliberate or damaging contact with an opponent ROBOT on or inside the vertical extension of its FRAME PERIMETER, including transitively through a GAME PIECE, is not allowed."},
{"G23", false, false, "BUMPERS must be in the BUMPER ZONE during the MATCH unless a ROBOT is completely in its HAB ZONE or supported by a ROBOT completely in its HAB ZONE."},
{"G24", false, false, "ROBOTS may not extend more than 30 in (~76 cm). beyond their FRAME PERIMETER."},
{"H6", false, false, "During the MATCH, DRIVERS, COACHES, and HUMAN PLAYERS may not contact anything outside the ALLIANCE STATION and TECHNICIANS may not contact anything outside their designated area."},
{"H7", false, false, "During the MATCH, team members may only enter GAME PIECES on to the FIELD through their LOADING STATIONS."},
{"H8", false, false, "During a MATCH, COACHES may not touch GAME PIECES unless for safety purposes."},
{"H9", true, false, "During the SANDSTORM PERIOD, COACHES, DRIVERS, HUMAN PLAYERS, and any part of the OPERATOR CONSOLE may not break the vertical planes defined by the STARTING LINES, unless for safety purposes."},
{"H10", true, false, "During the SANDSTORM PERIOD, COACHES, DRIVERS, and HUMAN PLAYERS may not look over the top of the ALLIANCE WALL down to the FIELD to overcome the effect of the SANDSTORM."},
// Returns the rule for which the foul was assigned.
func (foul *Foul) Rule() *Rule {
return GetRuleById(foul.RuleId)
}
// Returns the number of points that the foul adds to the opposing alliance's score.
func (foul *Foul) PointValue() int {
if foul.IsTechnical {
if foul.Rule() == nil {
return 0
}
if foul.Rule().IsTechnical {
return 10
} else {
return 3

62
game/rule.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2020 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Model of a game-specific rule.
package game
type Rule struct {
Id int
RuleNumber string
IsTechnical bool
IsRankingPoint bool
Description string
}
// All rules from the 2018 game that carry point penalties.
var rules = []*Rule{
{1, "S6", false, false, "DRIVE TEAMS may not extend any body part into the CARGO Chute. Momentary encroachment into the Chute is an exception to this rule."},
{2, "C8", false, false, "Strategies clearly aimed at forcing the opposing ALLIANCE to violate a rule are not in the spirit of FIRST Robotics Competition and not allowed."},
{3, "G3", true, false, "During the SANDSTORM PERIOD, a ROBOT may not cross the FIELD such that its BUMPERS break the plane defined by their opponents CARGO SHIP LINE."},
{4, "G4", false, false, "ROBOTS may not have greater-than-momentary or repeated control, i.e. exercise greater-than-momentary or repeated influence, of more than one (1) GAME PIECE at a time, either directly or transitively through other objects."},
{5, "G5", false, true, "A ROBOT may not remove a GAME PIECE from an opponents ROCKET/CARGO SHIP."},
{6, "G7", false, false, "ROBOTS may not intentionally eject GAME PIECES from the FIELD."},
{7, "G8", false, false, "ROBOTS may not deliberately use GAME PIECES in an attempt to ease or amplify the challenge associated with FIELD elements."},
{8, "G9", false, false, "No more than one ROBOT may be positioned such that its BUMPERS are completely beyond the opponents CARGO SHIP LINE."},
{9, "G9", true, false, "No more than one ROBOT may be positioned such that its BUMPERS are completely beyond the opponents CARGO SHIP LINE."},
{10, "G10", false, false, "No part of a ROBOT, except its BUMPERS, may be outside its FRAME PERIMETER if its BUMPERS are completely beyond its opponents CARGO SHIP LINE."},
{11, "G10", true, false, "No part of a ROBOT, except its BUMPERS, may be outside its FRAME PERIMETER if its BUMPERS are completely beyond its opponents CARGO SHIP LINE."},
{12, "G12", false, false, "A ROBOT may not break the vertical plane above the ALLIANCE STATION WALL or damage the SANDSTORM."},
{13, "G13", false, false, "A ROBOT may not contact an opponent ROBOT if that opponent ROBOTS BUMPERS are fully in their HAB ZONE."},
{14, "G15", false, false, "DRIVE TEAMS, ROBOTS, and OPERATOR CONSOLES are prohibited from the following actions with regards to interaction with ARENA elements: grabbing, grasping, attaching to, hanging, deforming, becoming entangled, damaging, and repositioning GAME PIECE holders."},
{15, "G16", false, true, "During Qualification MATCHES, ROBOTS may not contact opponents ROCKETS starting at T-minus 20s."},
{16, "G17", false, false, "Fallen (i.e. tipped over) ROBOTS attempting to right themselves (either by themselves or with assistance from a partner ROBOT) have one ten (10) second grace period in which they may not be contacted by an opponent ROBOT."},
{17, "G18", false, false, "ROBOTS may not pin an opponents ROBOT for more than five (5) seconds."},
{18, "G18", true, false, "ROBOTS may not pin an opponents ROBOT for more than five (5) seconds."},
{19, "G19", true, false, "Strategies aimed at the destruction or inhibition of ROBOTS via attachment, damage, tipping, or entanglements are not allowed."},
{20, "G20", true, false, "Initiating deliberate or damaging contact with an opponent ROBOT on or inside the vertical extension of its FRAME PERIMETER, including transitively through a GAME PIECE, is not allowed."},
{21, "G23", false, false, "BUMPERS must be in the BUMPER ZONE during the MATCH unless a ROBOT is completely in its HAB ZONE or supported by a ROBOT completely in its HAB ZONE."},
{22, "G24", false, false, "ROBOTS may not extend more than 30 in (~76 cm). beyond their FRAME PERIMETER."},
{23, "H6", false, false, "During the MATCH, DRIVERS, COACHES, and HUMAN PLAYERS may not contact anything outside the ALLIANCE STATION and TECHNICIANS may not contact anything outside their designated area."},
{24, "H7", false, false, "During the MATCH, team members may only enter GAME PIECES on to the FIELD through their LOADING STATIONS."},
{25, "H8", false, false, "During a MATCH, COACHES may not touch GAME PIECES unless for safety purposes."},
{26, "H9", true, false, "During the SANDSTORM PERIOD, COACHES, DRIVERS, HUMAN PLAYERS, and any part of the OPERATOR CONSOLE may not break the vertical planes defined by the STARTING LINES, unless for safety purposes."},
{27, "H10", true, false, "During the SANDSTORM PERIOD, COACHES, DRIVERS, and HUMAN PLAYERS may not look over the top of the ALLIANCE WALL down to the FIELD to overcome the effect of the SANDSTORM."},
}
var ruleMap map[int]*Rule
// Returns the rule having the given ID, or nil if no such rule exists.
func GetRuleById(id int) *Rule {
return GetAllRules()[id]
}
// Returns a slice of all defined rules that carry point penalties.
func GetAllRules() map[int]*Rule {
if ruleMap == nil {
ruleMap = make(map[int]*Rule, len(rules))
for _, rule := range rules {
ruleMap[rule.Id] = rule
}
}
return ruleMap
}

24
game/rule_test.go Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2020 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetRuleById(t *testing.T) {
assert.Nil(t, GetRuleById(0))
assert.Equal(t, rules[0], GetRuleById(1))
assert.Equal(t, rules[20], GetRuleById(21))
assert.Nil(t, GetRuleById(1000))
}
func TestGetAllRules(t *testing.T) {
allRules := GetAllRules()
assert.Equal(t, len(rules), len(allRules))
for _, rule := range rules {
assert.Equal(t, rule, allRules[rule.Id])
}
}

View File

@@ -96,7 +96,10 @@ func (score *Score) Summarize(opponentFouls []Foul) *ScoreSummary {
} else {
// Check for the opponent fouls that automatically trigger the ranking point.
for _, foul := range opponentFouls {
if foul.IsRankingPoint {
if foul.Rule() == nil {
continue
}
if foul.Rule().IsRankingPoint {
summary.CompleteRocket = true
break
}

View File

@@ -32,6 +32,10 @@ func TestScoreSummary(t *testing.T) {
assert.Equal(t, false, blueSummary.CompleteRocket)
assert.Equal(t, true, blueSummary.HabDocking)
// Test invalid foul.
redScore.Fouls[0].RuleId = 0
assert.Equal(t, 13, blueScore.Summarize(redScore.Fouls).FoulPoints)
// Test rocket completion boundary conditions.
assert.Equal(t, true, redScore.Summarize(blueScore.Fouls).CompleteRocket)
redScore.RocketFarLeftBays[1] = BayHatch
@@ -41,7 +45,7 @@ func TestScoreSummary(t *testing.T) {
assert.Equal(t, true, redScore.Summarize(blueScore.Fouls).CompleteRocket)
redScore.RocketNearLeftBays[2] = BayHatch
assert.Equal(t, false, redScore.Summarize(blueScore.Fouls).CompleteRocket)
redScore.Fouls[1].IsRankingPoint = true
redScore.Fouls[2].RuleId = 15
assert.Equal(t, true, redScore.Summarize(redScore.Fouls).CompleteRocket)
// Test hab docking boundary conditions.
@@ -119,12 +123,7 @@ func TestScoreEquals(t *testing.T) {
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Fouls[0].RuleNumber = "G1000"
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Fouls[0].IsTechnical = !score2.Fouls[0].IsTechnical
score2.Fouls[0].RuleId = 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))

View File

@@ -7,9 +7,9 @@ package game
func TestScore1() *Score {
fouls := []Foul{
{Rule{"G18", true, false, ""}, 25, 150},
{Rule{"G20", true, false, ""}, 1868, 0},
{Rule{"G22", false, false, ""}, 25, 25.2},
{18, 25, 150},
{20, 1868, 0},
{21, 25, 25.2},
}
return &Score{
RobotStartLevels: [3]int{2, 1, 2},

View File

@@ -50,11 +50,18 @@ var handleRealtimeScore = function(data) {
// Handles a websocket message to populate the final score data.
var handleScorePosted = function(data) {
$.each(data.RedFouls, function(i, foul) {
Object.assign(foul, data.RulesViolated[foul.RuleId]);
});
$.each(data.BlueFouls, function(i, foul) {
Object.assign(foul, data.RulesViolated[foul.RuleId]);
});
$("#scoreMatchName").text(data.MatchType + " Match " + data.Match.DisplayName);
$("#redScoreDetails").html(matchResultTemplate({score: data.RedScoreSummary, fouls: data.RedFouls,
cards: data.RedCards}));
rulesViolated: data.RulesViolated, cards: data.RedCards}));
$("#blueScoreDetails").html(matchResultTemplate({score: data.BlueScoreSummary, fouls: data.BlueFouls,
cards: data.BlueCards}));
rulesViolated: data.RulesViolated, cards: data.BlueCards}));
$("#matchResult").modal("show");
// Activate tooltips above the foul listings.

View File

@@ -51,9 +51,7 @@ var renderResults = function(alliance) {
if (result.score.Fouls != null) {
$.each(result.score.Fouls, function(k, v) {
getInputElement(alliance, "Foul" + k + "Team", v.TeamId).prop("checked", true);
getInputElement(alliance, "Foul" + k + "RuleNumber").val(v.RuleNumber);
getInputElement(alliance, "Foul" + k + "IsTechnical").prop("checked", v.IsTechnical);
getInputElement(alliance, "Foul" + k + "IsRankingPoint").prop("checked", v.IsRankingPoint);
getSelectElement(alliance, "Foul" + k + "RuleId").val(v.RuleId);
getInputElement(alliance, "Foul" + k + "Time").val(v.TimeInMatchSec);
});
}
@@ -99,9 +97,7 @@ var updateResults = function(alliance) {
result.score.Fouls = [];
for (var i = 0; formData[alliance + "Foul" + i + "Time"]; i++) {
var prefix = alliance + "Foul" + i;
var foul = {TeamId: parseInt(formData[prefix + "Team"]), RuleNumber: formData[prefix + "RuleNumber"],
IsTechnical: formData[prefix + "IsTechnical"] === "on",
IsRankingPoint: formData[prefix + "IsRankingPoint"] === "on",
var foul = {TeamId: parseInt(formData[prefix + "Team"]), RuleId: parseInt(formData[prefix + "RuleId"]),
TimeInMatchSec: parseFloat(formData[prefix + "Time"])};
result.score.Fouls.push(foul);
}

View File

@@ -46,15 +46,13 @@ var clearFoul = function() {
// Sends the foul to the server to add it to the list.
var commitFoul = function() {
websocket.send("addFoul", {Alliance: foulTeamButton.attr("data-alliance"),
TeamId: parseInt(foulTeamButton.attr("data-team")), Rule: foulRuleButton.attr("data-rule"),
IsTechnical: foulRuleButton.attr("data-is-technical") === "true",
IsRankingPoint: foulRuleButton.attr("data-is-ranking-point") === "true"});
TeamId: parseInt(foulTeamButton.attr("data-team")), RuleId: parseInt(foulRuleButton.attr("data-rule-id"))});
};
// Removes the foul with the given parameters from the list.
var deleteFoul = function(alliance, team, rule, isTechnical, isRankingPoint, timeSec) {
websocket.send("deleteFoul", {Alliance: alliance, TeamId: parseInt(team), Rule: rule,
IsTechnical: isTechnical, IsRankingPoint: isRankingPoint, TimeInMatchSec: timeSec});
var deleteFoul = function(alliance, team, ruleId, timeSec) {
websocket.send("deleteFoul", {Alliance: alliance, TeamId: parseInt(team), RuleId: parseInt(ruleId),
TimeInMatchSec: timeSec});
};
// Cycles through no card, yellow card, and red card.

View File

@@ -96,7 +96,9 @@
<h4>Fouls</h4>
{{"{{#each fouls}}"}}
<div class="row">
<div class="col-lg-3 col-lg-offset-1">{{"{{#if IsTechnical}}"}}Tech {{"{{/if}}"}}Foul</div>
<div class="col-lg-3 col-lg-offset-1">
{{"{{#if IsTechnical}}"}}Tech {{"{{/if}}"}}Foul{{"{{#if IsRankingPoint}}"}}+RP{{"{{/if}}"}}
</div>
<div class="col-lg-3">Team {{"{{TeamId}}"}}</div>
<div class="col-lg-3" data-toggle="tooltip" title="{{"{{Description}}"}}">{{"{{RuleNumber}}"}}</div>
</div>

View File

@@ -158,22 +158,16 @@
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label">Rule Violated</label>
<div class="col-lg-3">
<input type="text" class="form-control input-sm"
name="{{"{{../alliance}}"}}Foul{{"{{@index}}"}}RuleNumber">
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label">Is Technical</label>
<div class="col-lg-3">
<input type="checkbox" class="input-sm" name="{{"{{../alliance}}"}}Foul{{"{{@index}}"}}IsTechnical">
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label">Free Ranking Point</label>
<div class="col-lg-3">
<input type="checkbox" class="input-sm" name="{{"{{../alliance}}"}}Foul{{"{{@index}}"}}IsRankingPoint">
<label class="col-lg-4 control-label">Rule</label>
<div class="col-lg-7">
<select class="form-control" name="{{"{{../alliance}}"}}Foul{{"{{@index}}"}}RuleId">
{{range $rule := .Rules}}
<option value="{{$rule.Id}}">{{$rule.RuleNumber}}
[{{if $rule.IsTechnical}}Tech {{end}}Foul{{if $rule.IsRankingPoint}}+RP{{end}}]:
{{$rule.Description}}
</option>
{{end}}
</select>
</div>
</div>
<div class="form-group">

View File

@@ -26,10 +26,10 @@
<h4>Fouls</h4>
<table class="table">
{{range $foul := .RedFouls}}
{{template "foul" dict "foul" $foul "color" "red"}}
{{template "foul" dict "foul" $foul "color" "red" "rules" $.Rules}}
{{end}}
{{range $foul := .BlueFouls}}
{{template "foul" dict "foul" $foul "color" "blue"}}
{{template "foul" dict "foul" $foul "color" "blue" "rules" $.Rules}}
{{end}}
</table>
<h4>Yellow/Red Cards</h4>
@@ -58,13 +58,10 @@
</div>
<div class="row">
{{range $rule := .Rules}}
<a class="btn btn-sm
{{if $rule.IsTechnical}}btn-danger{{else if $rule.IsRankingPoint}}btn-primary
{{else}}btn-warning{{end}}
btn-referee btn-rule"
data-rule="{{$rule.RuleNumber}}" data-is-technical="{{$rule.IsTechnical}}"
data-is-ranking-point="{{$rule.IsRankingPoint}}" onclick="setFoulRule(this);"
data-toggle="tooltip" title="{{$rule.Description}}">
<a class="btn btn-sm {{if $rule.IsTechnical}}btn-danger{{else if $rule.IsRankingPoint}}btn-primary
{{else}}btn-warning{{end}} btn-referee btn-rule"
data-rule-id="{{$rule.Id}}" onclick="setFoulRule(this);" data-toggle="tooltip"
title="{{$rule.Description}}">
{{$rule.RuleNumber}}{{if $rule.IsTechnical}}<sup>T</sup>
{{else if $rule.IsRankingPoint}}<sup>RP</sup>{{end}}
</a>
@@ -108,10 +105,13 @@
<tr class="row-{{.color}}">
<td>{{.foul.TeamId}}</td>
<td>
{{.foul.RuleNumber}}{{if .foul.IsTechnical}}<sup>T</sup>{{else if .foul.IsRankingPoint}}<sup>RP</sup>{{end}}
{{$rule := index .rules .foul.RuleId}}
{{$rule.RuleNumber}}{{if $rule.IsTechnical}}<sup>T</sup>{{else if $rule.IsRankingPoint}}<sup>RP</sup>{{end}}
</td>
<td>
<a class="btn btn-sm btn-danger" onclick="deleteFoul('{{.color}}', {{.foul.TeamId}}, '{{.foul.RuleId}}',
{{.foul.TimeInMatchSec}});">Delete</a>
</td>
<td><a class="btn btn-sm btn-danger" onclick="deleteFoul('{{.color}}', {{.foul.TeamId}}, '{{.foul.RuleNumber}}',
{{.foul.IsTechnical}}, {{.foul.IsRankingPoint}}, {{.foul.TimeInMatchSec}});">Delete</a></td>
</tr>
{{end}}
{{define "card"}}

View File

@@ -178,7 +178,7 @@ func TestCommitEliminationTie(t *testing.T) {
MatchId: match.Id,
RedScore: &game.Score{
RocketFarRightBays: [3]game.BayStatus{game.BayHatchCargo, game.BayHatch, game.BayHatch},
Fouls: []game.Foul{{}, {}, {}}},
Fouls: []game.Foul{{RuleId: 1}, {RuleId: 2}, {RuleId: 4}}},
BlueScore: &game.Score{},
}
err := web.commitMatchScore(match, matchResult, false)

View File

@@ -7,6 +7,7 @@ package web
import (
"fmt"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model"
"github.com/gorilla/mux"
"net/http"
@@ -91,7 +92,8 @@ func (web *Web) matchReviewEditGetHandler(w http.ResponseWriter, r *http.Request
*model.EventSettings
Match *model.Match
MatchResultJson *model.MatchResultDb
}{web.arena.EventSettings, match, matchResultJson}
Rules map[int]*game.Rule
}{web.arena.EventSettings, match, matchResultJson, game.GetAllRules()}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)

View File

@@ -63,7 +63,7 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
// Update the score to something else.
postBody := "redScoreJson={\"RobotEndLevels\":[0,3,0]}&blueScoreJson={\"CargoBays\":[0,2,1,2,2,0,1]," +
"\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
"\"Fouls\":[{\"TeamId\":973,\"RuleId\":1}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)
@@ -95,7 +95,7 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
// Update the score to something else.
postBody := "redScoreJson={\"RocketNearLeftBays\":[1,0,2]}&blueScoreJson={\"RocketFarRightBays\":[2,2,2]," +
"\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
"\"Fouls\":[{\"TeamId\":973,\"RuleId\":1}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)

View File

@@ -70,11 +70,11 @@ func (web *Web) refereePanelHandler(w http.ResponseWriter, r *http.Request) {
BlueFouls []game.Foul
RedCards map[string]string
BlueCards map[string]string
Rules []game.Rule
Rules map[int]*game.Rule
EntryEnabled bool
}{web.arena.EventSettings, matchType, match.DisplayName, red1, red2, red3, blue1, blue2, blue3,
web.arena.RedRealtimeScore.CurrentScore.Fouls, web.arena.BlueRealtimeScore.CurrentScore.Fouls,
web.arena.RedRealtimeScore.Cards, web.arena.BlueRealtimeScore.Cards, game.Rules,
web.arena.RedRealtimeScore.Cards, web.arena.BlueRealtimeScore.Cards, game.GetAllRules(),
!(web.arena.RedRealtimeScore.FoulsCommitted && web.arena.BlueRealtimeScore.FoulsCommitted)}
err = template.ExecuteTemplate(w, "referee_panel.html", data)
if err != nil {
@@ -114,11 +114,9 @@ func (web *Web) refereePanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
switch messageType {
case "addFoul":
args := struct {
Alliance string
TeamId int
Rule string
IsTechnical bool
IsRankingPoint bool
Alliance string
TeamId int
RuleId int
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
@@ -127,9 +125,7 @@ func (web *Web) refereePanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
}
// Add the foul to the correct alliance's list.
foul := game.Foul{Rule: game.Rule{RuleNumber: args.Rule, IsTechnical: args.IsTechnical,
IsRankingPoint: args.IsRankingPoint},
TeamId: args.TeamId, TimeInMatchSec: web.arena.MatchTimeSec()}
foul := game.Foul{RuleId: args.RuleId, TeamId: args.TeamId, TimeInMatchSec: web.arena.MatchTimeSec()}
if args.Alliance == "red" {
web.arena.RedRealtimeScore.CurrentScore.Fouls =
append(web.arena.RedRealtimeScore.CurrentScore.Fouls, foul)
@@ -142,9 +138,7 @@ func (web *Web) refereePanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
args := struct {
Alliance string
TeamId int
Rule string
IsTechnical bool
IsRankingPoint bool
RuleId int
TimeInMatchSec float64
}{}
err = mapstructure.Decode(data, &args)
@@ -154,9 +148,7 @@ func (web *Web) refereePanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
}
// Remove the foul from the correct alliance's list.
deleteFoul := game.Foul{Rule: game.Rule{RuleNumber: args.Rule, IsTechnical: args.IsTechnical,
IsRankingPoint: args.IsRankingPoint},
TeamId: args.TeamId, TimeInMatchSec: args.TimeInMatchSec}
deleteFoul := game.Foul{RuleId: args.RuleId, TeamId: args.TeamId, TimeInMatchSec: args.TimeInMatchSec}
var fouls *[]game.Foul
if args.Alliance == "red" {
fouls = &web.arena.RedRealtimeScore.CurrentScore.Fouls

View File

@@ -37,13 +37,12 @@ func TestRefereePanelWebsocket(t *testing.T) {
foulData := struct {
Alliance string
TeamId int
Rule string
IsTechnical bool
RuleId int
TimeInMatchSec float64
}{"red", 256, "G22", false, 0}
}{"red", 256, 1, 0}
ws.Write("addFoul", foulData)
foulData.TeamId = 359
foulData.IsTechnical = true
foulData.RuleId = 3
ws.Write("addFoul", foulData)
foulData.Alliance = "blue"
foulData.TeamId = 1680
@@ -53,17 +52,14 @@ func TestRefereePanelWebsocket(t *testing.T) {
readWebsocketType(t, ws, "reload")
if assert.Equal(t, 2, len(web.arena.RedRealtimeScore.CurrentScore.Fouls)) {
assert.Equal(t, 256, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].TeamId)
assert.Equal(t, "G22", web.arena.RedRealtimeScore.CurrentScore.Fouls[0].RuleNumber)
assert.Equal(t, false, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].IsTechnical)
assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].RuleId)
assert.Equal(t, 0.0, web.arena.RedRealtimeScore.CurrentScore.Fouls[0].TimeInMatchSec)
assert.Equal(t, 359, web.arena.RedRealtimeScore.CurrentScore.Fouls[1].TeamId)
assert.Equal(t, "G22", web.arena.RedRealtimeScore.CurrentScore.Fouls[1].RuleNumber)
assert.Equal(t, true, web.arena.RedRealtimeScore.CurrentScore.Fouls[1].IsTechnical)
assert.Equal(t, 3, web.arena.RedRealtimeScore.CurrentScore.Fouls[1].RuleId)
}
if assert.Equal(t, 1, len(web.arena.BlueRealtimeScore.CurrentScore.Fouls)) {
assert.Equal(t, 1680, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].TeamId)
assert.Equal(t, "G22", web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].RuleNumber)
assert.Equal(t, true, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].IsTechnical)
assert.Equal(t, 3, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].RuleId)
assert.Equal(t, 0.0, web.arena.BlueRealtimeScore.CurrentScore.Fouls[0].TimeInMatchSec)
}
assert.False(t, web.arena.RedRealtimeScore.FoulsCommitted)