Add classes to encapsulate scoring logic for boilers, rotors, and touchpads.

This commit is contained in:
Patrick Fairbank
2017-09-02 22:50:28 -07:00
parent 11b6b5f856
commit fa70323b85
9 changed files with 530 additions and 0 deletions

42
game/boiler.go Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2017 boiler element.
package game
import (
"time"
)
const (
BoilerAutoGracePeriodSec = 5
BoilerTeleopGracePeriodSec = 5
)
type Boiler struct {
AutoFuelLow int
AutoFuelHigh int
FuelLow int
FuelHigh int
}
// Updates the internal counting state of the boiler given the current state of the hardware counts. Allows the score to
// accumulate before the match, since the counters will be reset in hardware.
func (boiler *Boiler) UpdateState(lowCount, highCount int, matchStartTime, currentTime time.Time) {
autoValidityDuration := time.Duration(MatchTiming.AutoDurationSec+BoilerAutoGracePeriodSec) * time.Second
autoValidityCutoff := matchStartTime.Add(autoValidityDuration)
teleopValidityDuration := time.Duration(MatchTiming.AutoDurationSec+MatchTiming.PauseDurationSec+
MatchTiming.TeleopDurationSec+BoilerTeleopGracePeriodSec) * time.Second
teleopValidityCutoff := matchStartTime.Add(teleopValidityDuration)
if currentTime.Before(autoValidityCutoff) {
boiler.AutoFuelLow = lowCount
boiler.AutoFuelHigh = highCount
boiler.FuelLow = 0
boiler.FuelHigh = 0
} else if currentTime.Before(teleopValidityCutoff) {
boiler.FuelLow = lowCount
boiler.FuelHigh = highCount
}
}

64
game/boiler_test.go Normal file
View File

@@ -0,0 +1,64 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)
var matchStartTime = time.Unix(10, 0)
func TestFuelBeforeMatch(t *testing.T) {
boiler := Boiler{}
boiler.UpdateState(1, 2, matchStartTime, timeAfterStart(-1))
checkBoilerCounts(t, 1, 2, 0, 0, &boiler)
}
func TestAutoFuel(t *testing.T) {
boiler := Boiler{}
boiler.UpdateState(3, 4, matchStartTime, timeAfterStart(1))
checkBoilerCounts(t, 3, 4, 0, 0, &boiler)
boiler.UpdateState(5, 6, matchStartTime, timeAfterStart(10))
checkBoilerCounts(t, 5, 6, 0, 0, &boiler)
boiler.UpdateState(7, 8, matchStartTime, timeAfterStart(19.9))
checkBoilerCounts(t, 7, 8, 0, 0, &boiler)
boiler.UpdateState(9, 10, matchStartTime, timeAfterStart(20.1))
checkBoilerCounts(t, 7, 8, 9, 10, &boiler)
}
func TestTeleopFuel(t *testing.T) {
boiler := Boiler{}
boiler.UpdateState(1, 2, matchStartTime, timeAfterStart(1))
boiler.UpdateState(3, 4, matchStartTime, timeAfterStart(21))
checkBoilerCounts(t, 1, 2, 3, 4, &boiler)
boiler.UpdateState(5, 6, matchStartTime, timeAfterStart(120))
checkBoilerCounts(t, 1, 2, 5, 6, &boiler)
boiler.UpdateState(7, 8, matchStartTime, timeAfterEnd(-1))
checkBoilerCounts(t, 1, 2, 7, 8, &boiler)
boiler.UpdateState(9, 10, matchStartTime, timeAfterEnd(4.9))
checkBoilerCounts(t, 1, 2, 9, 10, &boiler)
boiler.UpdateState(11, 12, matchStartTime, timeAfterEnd(5.1))
checkBoilerCounts(t, 1, 2, 9, 10, &boiler)
}
func checkBoilerCounts(t *testing.T, autoLow, autoHigh, low, high int, boiler *Boiler) {
assert.Equal(t, autoLow, boiler.AutoFuelLow)
assert.Equal(t, autoHigh, boiler.AutoFuelHigh)
assert.Equal(t, low, boiler.FuelLow)
assert.Equal(t, high, boiler.FuelHigh)
}
func timeAfterStart(sec float32) time.Time {
return matchStartTime.Add(time.Duration(1000*sec) * time.Millisecond)
}
func timeAfterEnd(sec float32) time.Time {
matchDuration := time.Duration(MatchTiming.AutoDurationSec+MatchTiming.PauseDurationSec+
MatchTiming.TeleopDurationSec) * time.Second
return matchStartTime.Add(matchDuration).Add(time.Duration(1000*sec) * time.Millisecond)
}

View File

@@ -5,9 +5,16 @@
package game
import "time"
var MatchTiming = struct {
AutoDurationSec int
PauseDurationSec int
TeleopDurationSec int
EndgameTimeLeftSec int
}{15, 2, 135, 30}
func GetMatchEndTime(matchStartTime time.Time) time.Time {
return matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec+MatchTiming.PauseDurationSec+
MatchTiming.TeleopDurationSec) * time.Second)
}

50
game/rotor_set.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2017 rotor elements.
package game
import (
"time"
)
type RotorSet struct {
AutoRotors int
Rotors int
}
// Updates the internal counting state of the rotors given the current state of the sensors.
func (rotorSet *RotorSet) UpdateState(rotors [4]bool, matchStartTime, currentTime time.Time) {
autoValidityCutoff := matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec) * time.Second)
teleopValidityCutoff := autoValidityCutoff.Add(time.Duration(MatchTiming.PauseDurationSec+
MatchTiming.TeleopDurationSec) * time.Second)
if currentTime.After(matchStartTime) {
if currentTime.Before(autoValidityCutoff) {
if rotorSet.AutoRotors == 0 && rotors[0] {
rotorSet.AutoRotors++
}
if rotorSet.AutoRotors == 1 && rotors[1] {
rotorSet.AutoRotors++
}
} else if currentTime.Before(teleopValidityCutoff) {
if rotorSet.totalRotors() == 0 && rotors[0] {
rotorSet.Rotors++
}
if rotorSet.totalRotors() == 1 && rotors[1] {
rotorSet.Rotors++
}
if rotorSet.totalRotors() == 2 && rotors[2] {
rotorSet.Rotors++
}
if rotorSet.totalRotors() == 3 && rotors[3] {
rotorSet.Rotors++
}
}
}
}
func (rotorSet *RotorSet) totalRotors() int {
return rotorSet.AutoRotors + rotorSet.Rotors
}

103
game/rotor_set_test.go Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestRotorsBeforeMatch(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState([4]bool{true, true, true, false}, matchStartTime, timeAfterStart(-1))
checkRotorCounts(t, 0, 0, &rotorSet)
}
func TestAutoRotors(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState([4]bool{false, false, false, false}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{false, true, true, true}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, false, false, false}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, false}, matchStartTime, timeAfterStart(11))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, false}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 2, 1, &rotorSet)
// Check going straight to two.
rotorSet = RotorSet{}
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 2, 0, &rotorSet)
// Check timing threshold.
rotorSet = RotorSet{}
rotorSet.UpdateState([4]bool{true, false, false, false}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterStart(15.1))
checkRotorCounts(t, 1, 1, &rotorSet)
}
func TestTeleopRotors(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState([4]bool{false, false, false, false}, matchStartTime, timeAfterStart(14))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, false, false, false}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterStart(30))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, false}, matchStartTime, timeAfterStart(100))
checkRotorCounts(t, 0, 3, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, true}, matchStartTime, timeAfterStart(120))
checkRotorCounts(t, 0, 4, &rotorSet)
}
func TestRotorsAfterMatch(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState([4]bool{true, false, false, false}, matchStartTime, timeAfterEnd(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterEnd(2))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, false}, matchStartTime, timeAfterEnd(3))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, true, true}, matchStartTime, timeAfterEnd(4))
checkRotorCounts(t, 0, 0, &rotorSet)
}
func TestRotorLatching(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState([4]bool{false, true, false, false}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, false, false, false}, matchStartTime, timeAfterStart(2))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, false, false}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState([4]bool{false, true, false, false}, matchStartTime, timeAfterStart(10))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState([4]bool{true, true, false, false}, matchStartTime, timeAfterStart(10))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, false, true}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, true, false}, matchStartTime, timeAfterStart(30))
checkRotorCounts(t, 2, 1, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, false, true}, matchStartTime, timeAfterStart(50))
checkRotorCounts(t, 2, 2, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, false, false}, matchStartTime, timeAfterEnd(-1))
checkRotorCounts(t, 2, 2, &rotorSet)
rotorSet.UpdateState([4]bool{false, false, false, false}, matchStartTime, timeAfterEnd(1))
checkRotorCounts(t, 2, 2, &rotorSet)
}
func checkRotorCounts(t *testing.T, autoRotors, rotors int, rotorSet *RotorSet) {
assert.Equal(t, autoRotors, rotorSet.AutoRotors)
assert.Equal(t, rotors, rotorSet.Rotors)
}

View File

@@ -74,3 +74,20 @@ func (score *Score) Summarize(opponentFouls []Foul, matchType string) *ScoreSumm
return summary
}
func (score *Score) Equals(other *Score) bool {
if score.AutoMobility != other.AutoMobility || score.AutoRotors != other.AutoRotors ||
score.AutoFuelLow != other.AutoFuelLow || score.AutoFuelHigh != other.AutoFuelHigh ||
score.Rotors != other.Rotors || score.FuelLow != other.FuelLow || score.FuelHigh != other.FuelHigh ||
score.Takeoffs != other.Takeoffs || score.ElimDq != other.ElimDq || len(score.Fouls) != len(other.Fouls) {
return false
}
for i, foul := range score.Fouls {
if foul != other.Fouls[i] {
return false
}
}
return true
}

View File

@@ -71,3 +71,83 @@ func TestScoreSummary(t *testing.T) {
assert.Equal(t, 0, redScore.Summarize(blueScore.Fouls, "elimination").Score)
assert.Equal(t, 0, blueScore.Summarize(redScore.Fouls, "elimination").Score)
}
func TestScoreEquals(t *testing.T) {
score1 := TestScore1()
score2 := TestScore1()
assert.True(t, score1.Equals(score2))
assert.True(t, score2.Equals(score1))
score3 := TestScore2()
assert.False(t, score1.Equals(score3))
assert.False(t, score3.Equals(score1))
score2.AutoMobility += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.AutoRotors += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.AutoFuelLow += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.AutoFuelHigh += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Rotors += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.FuelLow += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.FuelHigh += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Takeoffs += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Fouls = []Foul{}
assert.False(t, score1.Equals(score2))
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
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Fouls[0].TeamId += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.Fouls[0].TimeInMatchSec += 1
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
score2 = TestScore1()
score2.ElimDq = !score2.ElimDq
assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1))
}

67
game/touchpad.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2017 touchpad element.
package game
import (
"time"
)
const (
NotTriggered = iota
Triggered
Held
)
type Touchpad struct {
lastTriggered bool
triggeredTime *time.Time
untriggeredTime *time.Time
}
// Updates the internal timing state of the touchpad given the current state of the sensor.
func (touchpad *Touchpad) UpdateState(triggered bool, currentTime time.Time) {
if triggered && !touchpad.lastTriggered {
touchpad.triggeredTime = &currentTime
touchpad.untriggeredTime = nil
} else if !triggered && touchpad.lastTriggered {
touchpad.untriggeredTime = &currentTime
}
touchpad.lastTriggered = triggered
}
// Determines the scoring status of the touchpad. Returns 0 if not triggered, 1 if triggered but not yet for a full
// second, and 2 if triggered and counting for points.
func (touchpad *Touchpad) GetState(matchStartTime, currentTime time.Time) int {
matchEndTime := GetMatchEndTime(matchStartTime)
if touchpad.triggeredTime != nil && touchpad.triggeredTime.Before(matchEndTime) {
if touchpad.untriggeredTime == nil {
if currentTime.Sub(*touchpad.triggeredTime) >= time.Second {
return Held
} else {
return Triggered
}
} else if touchpad.untriggeredTime.Sub(*touchpad.triggeredTime) >= time.Second &&
touchpad.untriggeredTime.After(matchEndTime) {
return Held
}
}
return NotTriggered
}
func CountTouchpads(touchpads *[3]Touchpad, matchStartTime, currentTime time.Time) int {
matchEndTime := GetMatchEndTime(matchStartTime)
count := 0
for _, touchpad := range touchpads {
if touchpad.GetState(matchEndTime, currentTime) == 2 {
count++
}
}
return count
}

100
game/touchpad_test.go Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestNotTriggered(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(false, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(-1)))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestTriggeredReleasedEarly(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(-5))
assert.Equal(t, Triggered, touchpad.GetState(matchStartTime, timeAfterEnd(-4.9)))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(-3)))
touchpad.UpdateState(false, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(-1.1)))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestTriggeredTooShort(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(-0.5))
touchpad.UpdateState(true, timeAfterEnd(0))
assert.Equal(t, Triggered, touchpad.GetState(matchStartTime, timeAfterEnd(0.2)))
touchpad.UpdateState(false, timeAfterEnd(0.4))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(0.5)))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestTriggeredHeld(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(-5))
touchpad.UpdateState(true, timeAfterEnd(-3))
touchpad.UpdateState(true, timeAfterEnd(1))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestTriggeredReleased(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(-5))
touchpad.UpdateState(true, timeAfterEnd(-3))
touchpad.UpdateState(true, timeAfterEnd(1))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
touchpad.UpdateState(false, timeAfterEnd(3))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(4)))
}
func TestReTriggered(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(-5))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(-3)))
touchpad.UpdateState(false, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(-1.1)))
touchpad.UpdateState(true, timeAfterEnd(-0.1))
assert.Equal(t, Triggered, touchpad.GetState(matchStartTime, timeAfterEnd(0.1)))
assert.Equal(t, Held, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestTriggeredLate(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, timeAfterEnd(-10))
touchpad.UpdateState(true, timeAfterEnd(0.1))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(0.2)))
assert.Equal(t, NotTriggered, touchpad.GetState(matchStartTime, timeAfterEnd(2)))
}
func TestCountTouchpads(t *testing.T) {
var touchpads [3]Touchpad
touchpads[0].UpdateState(true, timeAfterEnd(-5))
touchpads[1].UpdateState(true, timeAfterEnd(-2))
touchpads[2].UpdateState(true, timeAfterEnd(-0.1))
assert.Equal(t, 0, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(-6)))
assert.Equal(t, 0, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(-5.5)))
assert.Equal(t, 1, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(-3)))
assert.Equal(t, 1, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(-1.5)))
assert.Equal(t, 2, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(0)))
assert.Equal(t, 3, CountTouchpads(&touchpads, matchStartTime, timeAfterEnd(1)))
}