Update game-specific models for 2018.

This commit is contained in:
Patrick Fairbank
2018-03-25 20:15:00 -07:00
parent cadfc5b095
commit e7cf700ced
32 changed files with 1134 additions and 976 deletions

View File

@@ -70,6 +70,11 @@ type Arena struct {
AllianceSelectionNotifier *Notifier AllianceSelectionNotifier *Notifier
LowerThirdNotifier *Notifier LowerThirdNotifier *Notifier
ReloadDisplaysNotifier *Notifier ReloadDisplaysNotifier *Notifier
scale *game.Seesaw
redSwitch *game.Seesaw
blueSwitch *game.Seesaw
redVault *game.Vault
blueVault *game.Vault
} }
type ArenaStatus struct { type ArenaStatus struct {
@@ -202,6 +207,12 @@ func (arena *Arena) LoadMatch(match *model.Match) error {
arena.BlueRealtimeScore = NewRealtimeScore() arena.BlueRealtimeScore = NewRealtimeScore()
arena.Plc.ResetCounts() arena.Plc.ResetCounts()
arena.FieldReset = false arena.FieldReset = false
arena.scale = new(game.Seesaw)
arena.redSwitch = new(game.Seesaw)
arena.blueSwitch = new(game.Seesaw)
arena.redVault = new(game.Vault)
arena.blueVault = new(game.Vault)
game.ResetPowerUps()
// Notify any listeners about the new match. // Notify any listeners about the new match.
arena.MatchLoadTeamsNotifier.Notify(nil) arena.MatchLoadTeamsNotifier.Notify(nil)
@@ -460,14 +471,12 @@ func (arena *Arena) Run() {
// Calculates the red alliance score summary for the given realtime snapshot. // Calculates the red alliance score summary for the given realtime snapshot.
func (arena *Arena) RedScoreSummary() *game.ScoreSummary { func (arena *Arena) RedScoreSummary() *game.ScoreSummary {
return arena.RedRealtimeScore.CurrentScore.Summarize(arena.BlueRealtimeScore.CurrentScore.Fouls, return arena.RedRealtimeScore.CurrentScore.Summarize(arena.BlueRealtimeScore.CurrentScore.Fouls)
arena.CurrentMatch.Type)
} }
// Calculates the blue alliance score summary for the given realtime snapshot. // Calculates the blue alliance score summary for the given realtime snapshot.
func (arena *Arena) BlueScoreSummary() *game.ScoreSummary { func (arena *Arena) BlueScoreSummary() *game.ScoreSummary {
return arena.BlueRealtimeScore.CurrentScore.Summarize(arena.RedRealtimeScore.CurrentScore.Fouls, return arena.BlueRealtimeScore.CurrentScore.Summarize(arena.RedRealtimeScore.CurrentScore.Fouls)
arena.CurrentMatch.Type)
} }
func (arena *Arena) GetStatus() *ArenaStatus { func (arena *Arena) GetStatus() *ArenaStatus {
@@ -604,54 +613,48 @@ func (arena *Arena) handlePlcInput() {
arena.handleEstop("B2", blueEstops[1]) arena.handleEstop("B2", blueEstops[1])
arena.handleEstop("B3", blueEstops[2]) arena.handleEstop("B3", blueEstops[2])
matchStartTime := arena.MatchStartTime if arena.MatchState == PreMatch || arena.MatchState == PostMatch {
currentTime := time.Now() // Don't do anything if we're outside the match, otherwise we may overwrite manual edits.
if arena.MatchState == PreMatch {
// Set a match start time in the future.
matchStartTime = currentTime.Add(time.Second)
}
matchEndTime := game.GetMatchEndTime(matchStartTime)
inGracePeriod := currentTime.Before(matchEndTime.Add(game.BoilerTeleopGracePeriodSec * time.Second))
if arena.MatchState == PostMatch && (!inGracePeriod || arena.matchAborted) {
// Don't do anything if we're past the end of the match, otherwise we may overwrite manual edits.
return return
} }
matchStartTime := arena.MatchStartTime
currentTime := time.Now()
teleopStartTime := game.GetTeleopStartTime(matchStartTime)
redScore := &arena.RedRealtimeScore.CurrentScore redScore := &arena.RedRealtimeScore.CurrentScore
oldRedScore := *redScore oldRedScore := *redScore
blueScore := &arena.BlueRealtimeScore.CurrentScore blueScore := &arena.BlueRealtimeScore.CurrentScore
oldBlueScore := *blueScore oldBlueScore := *blueScore
// Handle balls. // Handle scale and switch ownership.
redLow, redHigh, blueLow, blueHigh := arena.Plc.GetBalls() scale, redSwitch, blueSwitch := arena.Plc.GetScaleAndSwitches()
arena.RedRealtimeScore.boiler.UpdateState(redLow, redHigh, matchStartTime, currentTime) arena.scale.UpdateState(scale, currentTime)
redScore.AutoFuelLow = arena.RedRealtimeScore.boiler.AutoFuelLow arena.redSwitch.UpdateState(redSwitch, currentTime)
redScore.AutoFuelHigh = arena.RedRealtimeScore.boiler.AutoFuelHigh arena.blueSwitch.UpdateState(blueSwitch, currentTime)
redScore.FuelLow = arena.RedRealtimeScore.boiler.FuelLow if arena.MatchState == AutoPeriod {
redScore.FuelHigh = arena.RedRealtimeScore.boiler.FuelHigh redScore.AutoOwnershipPoints = 2 * int(arena.redSwitch.GetRedSeconds(matchStartTime, currentTime)+
arena.BlueRealtimeScore.boiler.UpdateState(blueLow, blueHigh, matchStartTime, currentTime) arena.scale.GetRedSeconds(matchStartTime, currentTime))
blueScore.AutoFuelLow = arena.BlueRealtimeScore.boiler.AutoFuelLow blueScore.AutoOwnershipPoints = 2 * int(arena.blueSwitch.GetBlueSeconds(matchStartTime, currentTime)+
blueScore.AutoFuelHigh = arena.BlueRealtimeScore.boiler.AutoFuelHigh arena.scale.GetBlueSeconds(matchStartTime, currentTime))
blueScore.FuelLow = arena.BlueRealtimeScore.boiler.FuelLow } else {
blueScore.FuelHigh = arena.BlueRealtimeScore.boiler.FuelHigh redScore.TeleopOwnershipPoints = int(arena.redSwitch.GetRedSeconds(teleopStartTime, currentTime) +
arena.scale.GetRedSeconds(teleopStartTime, currentTime))
// Handle rotors. blueScore.TeleopOwnershipPoints = int(arena.blueSwitch.GetBlueSeconds(teleopStartTime, currentTime) +
redRotor1, redOtherRotors, blueRotor1, blueOtherRotors := arena.Plc.GetRotors() arena.scale.GetBlueSeconds(teleopStartTime, currentTime))
arena.RedRealtimeScore.rotorSet.UpdateState(redRotor1, redOtherRotors, matchStartTime, currentTime)
redScore.AutoRotors = arena.RedRealtimeScore.rotorSet.AutoRotors
redScore.Rotors = arena.RedRealtimeScore.rotorSet.Rotors
arena.BlueRealtimeScore.rotorSet.UpdateState(blueRotor1, blueOtherRotors, matchStartTime, currentTime)
blueScore.AutoRotors = arena.BlueRealtimeScore.rotorSet.AutoRotors
blueScore.Rotors = arena.BlueRealtimeScore.rotorSet.Rotors
// Handle touchpads.
redTouchpads, blueTouchpads := arena.Plc.GetTouchpads()
for i := 0; i < 3; i++ {
arena.RedRealtimeScore.touchpads[i].UpdateState(redTouchpads[i], matchStartTime, currentTime)
arena.BlueRealtimeScore.touchpads[i].UpdateState(blueTouchpads[i], matchStartTime, currentTime)
} }
redScore.Takeoffs = game.CountTouchpads(&arena.RedRealtimeScore.touchpads, currentTime)
blueScore.Takeoffs = game.CountTouchpads(&arena.BlueRealtimeScore.touchpads, currentTime) // Handle vaults.
redForceCubes, redLevitateCubes, redBoostCubes, blueForceCubes, blueLevitateCubes, blueBoostCubes :=
arena.Plc.GetVaults()
arena.redVault.UpdateCubes(redForceCubes, redLevitateCubes, redBoostCubes)
arena.blueVault.UpdateCubes(blueForceCubes, blueLevitateCubes, blueBoostCubes)
redForce, redLevitate, redBoost, blueForce, blueLevitate, blueBoost := arena.Plc.GetPowerUpButtons()
arena.redVault.UpdateButtons(redForce, redLevitate, redBoost, currentTime)
arena.blueVault.UpdateButtons(blueForce, blueLevitate, blueBoost, currentTime)
redScore.VaultCubes = arena.redVault.GetNumCubes()
redScore.Levitate = arena.redVault.LevitatePlayed
blueScore.VaultCubes = arena.blueVault.GetNumCubes()
blueScore.Levitate = arena.blueVault.LevitatePlayed
if !oldRedScore.Equals(redScore) || !oldBlueScore.Equals(blueScore) { if !oldRedScore.Equals(redScore) || !oldBlueScore.Equals(blueScore) {
arena.RealtimeScoreNotifier.Notify(nil) arena.RealtimeScoreNotifier.Notify(nil)
@@ -662,65 +665,28 @@ func (arena *Arena) handlePlcInput() {
func (arena *Arena) handlePlcOutput() { func (arena *Arena) handlePlcOutput() {
if arena.FieldTestMode != "" { if arena.FieldTestMode != "" {
// PLC output is being manually overridden. // PLC output is being manually overridden.
if arena.FieldTestMode == "flash" { // TODO(patrick): Update for 2018.
blinkState := arena.Plc.GetCycleState(2, 0, 1) /*
arena.Plc.SetTouchpadLights([3]bool{blinkState, blinkState, blinkState}, if arena.FieldTestMode == "flash" {
[3]bool{blinkState, blinkState, blinkState}) blinkState := arena.Plc.GetCycleState(2, 0, 1)
} else if arena.FieldTestMode == "cycle" { arena.Plc.SetTouchpadLights([3]bool{blinkState, blinkState, blinkState},
arena.Plc.SetTouchpadLights( [3]bool{blinkState, blinkState, blinkState})
[3]bool{arena.Plc.GetCycleState(3, 2, 1), arena.Plc.GetCycleState(3, 1, 1), arena.Plc.GetCycleState(3, 0, 1)}, } else if arena.FieldTestMode == "cycle" {
[3]bool{arena.Plc.GetCycleState(3, 0, 1), arena.Plc.GetCycleState(3, 1, 1), arena.Plc.GetCycleState(3, 2, 1)}) arena.Plc.SetTouchpadLights(
} else if arena.FieldTestMode == "chase" { [3]bool{arena.Plc.GetCycleState(3, 2, 1), arena.Plc.GetCycleState(3, 1, 1), arena.Plc.GetCycleState(3, 0, 1)},
arena.Plc.SetTouchpadLights( [3]bool{arena.Plc.GetCycleState(3, 0, 1), arena.Plc.GetCycleState(3, 1, 1), arena.Plc.GetCycleState(3, 2, 1)})
[3]bool{arena.Plc.GetCycleState(12, 2, 2), arena.Plc.GetCycleState(12, 1, 2), arena.Plc.GetCycleState(12, 0, 2)}, } else if arena.FieldTestMode == "chase" {
[3]bool{arena.Plc.GetCycleState(12, 3, 2), arena.Plc.GetCycleState(12, 4, 2), arena.Plc.GetCycleState(12, 5, 2)}) arena.Plc.SetTouchpadLights(
} else if arena.FieldTestMode == "slowChase" { [3]bool{arena.Plc.GetCycleState(12, 2, 2), arena.Plc.GetCycleState(12, 1, 2), arena.Plc.GetCycleState(12, 0, 2)},
arena.Plc.SetTouchpadLights( [3]bool{arena.Plc.GetCycleState(12, 3, 2), arena.Plc.GetCycleState(12, 4, 2), arena.Plc.GetCycleState(12, 5, 2)})
[3]bool{arena.Plc.GetCycleState(6, 2, 8), arena.Plc.GetCycleState(6, 1, 8), arena.Plc.GetCycleState(6, 0, 8)}, } else if arena.FieldTestMode == "slowChase" {
[3]bool{arena.Plc.GetCycleState(6, 3, 8), arena.Plc.GetCycleState(6, 4, 8), arena.Plc.GetCycleState(6, 5, 8)}) arena.Plc.SetTouchpadLights(
} [3]bool{arena.Plc.GetCycleState(6, 2, 8), arena.Plc.GetCycleState(6, 1, 8), arena.Plc.GetCycleState(6, 0, 8)},
return [3]bool{arena.Plc.GetCycleState(6, 3, 8), arena.Plc.GetCycleState(6, 4, 8), arena.Plc.GetCycleState(6, 5, 8)})
}
return
*/
} }
// Handle balls.
matchEndTime := game.GetMatchEndTime(arena.MatchStartTime)
inGracePeriod := time.Now().Before(matchEndTime.Add(game.BoilerTeleopGracePeriodSec * time.Second))
if arena.MatchTimeSec() > 0 || arena.MatchState == PostMatch && !arena.matchAborted && inGracePeriod {
arena.Plc.SetBoilerMotors(true)
} else {
arena.Plc.SetBoilerMotors(false)
}
// Handle rotors.
redScore := &arena.RedRealtimeScore.CurrentScore
blueScore := &arena.BlueRealtimeScore.CurrentScore
if arena.MatchTimeSec() > 0 {
arena.Plc.SetRotorMotors(redScore.AutoRotors+redScore.Rotors, blueScore.AutoRotors+blueScore.Rotors)
} else {
arena.Plc.SetRotorMotors(0, 0)
}
arena.Plc.SetRotorLights(redScore.AutoRotors, blueScore.AutoRotors)
// Handle touchpads.
var redTouchpads, blueTouchpads [3]bool
currentTime := time.Now()
blinkStopTime := matchEndTime.Add(-time.Duration(game.MatchTiming.EndgameTimeLeftSec-2) * time.Second)
blinkState := arena.Plc.GetCycleState(2, 0, 1)
if arena.MatchState == EndgamePeriod && currentTime.Before(blinkStopTime) {
// Blink the touchpads at the endgame start point.
for i := 0; i < 3; i++ {
redTouchpads[i] = blinkState
blueTouchpads[i] = blinkState
}
} else {
for i := 0; i < 3; i++ {
redState := arena.RedRealtimeScore.touchpads[i].GetState(currentTime)
redTouchpads[i] = redState == game.Held || redState == game.Triggered && blinkState
blueState := arena.BlueRealtimeScore.touchpads[i].GetState(currentTime)
blueTouchpads[i] = blueState == game.Held || blueState == game.Triggered && blinkState
}
}
arena.Plc.SetTouchpadLights(redTouchpads, blueTouchpads)
} }
func (arena *Arena) handleEstop(station string, state bool) { func (arena *Arena) handleEstop(station string, state bool) {

View File

@@ -17,9 +17,9 @@ type Plc struct {
address string address string
handler *modbus.TCPClientHandler handler *modbus.TCPClientHandler
client modbus.Client client modbus.Client
Inputs [15]bool Inputs [37]bool
Counters [10]uint16 Counters [0]uint16
Coils [24]bool Coils [8]bool
cycleCounter int cycleCounter int
resetCountCycles int resetCountCycles int
} }
@@ -34,60 +34,55 @@ const (
// Discrete inputs // Discrete inputs
const ( const (
fieldEstop = iota fieldEstop = iota
scaleNear
scaleFar
redEstop1 redEstop1
redEstop2 redEstop2
redEstop3 redEstop3
redRotor1 redSwitchNear
redTouchpad1 redSwitchFar
redTouchpad2 redForceCube1
redTouchpad3 redForceCube2
redForceCube3
redForceButton
redLevitateCube1
redLevitateCube2
redLevitateCube3
redLevitateButton
redBoostCube1
redBoostCube2
redBoostCube3
redBoostButton
blueEstop1 blueEstop1
blueEstop2 blueEstop2
blueEstop3 blueEstop3
blueRotor1 blueSwitchNear
blueTouchpad1 blueSwitchFar
blueTouchpad2 blueForceCube1
blueTouchpad3 blueForceCube2
blueForceCube3
blueForceButton
blueLevitate1
blueLevitate2
blueLevitate3
blueLevitateButton
blueBoostCube1
blueBoostCube2
blueBoostCube3
blueBoostButton
) )
// 16-bit registers // 16-bit registers
const ( const ()
redRotor2Count = iota
redRotor3Count
redRotor4Count
redLowBoilerCount
redHighBoilerCount
blueRotor2Count
blueRotor3Count
blueRotor4Count
blueLowBoilerCount
blueHighBoilerCount
)
// Coils // Coils
const ( const (
redSerializer = iota redForceLight = iota
redBallLift redLevitateLight
redRotorMotor1 redBoostLight
redRotorMotor2 blueForceLight
redRotorMotor3 blueLevitateLight
redRotorMotor4 blueBoostLight
redAutoLight1
redAutoLight2
redTouchpadLight1
redTouchpadLight2
redTouchpadLight3
blueSerializer
blueBallLift
blueRotorMotor1
blueRotorMotor2
blueRotorMotor3
blueRotorMotor4
blueAutoLight1
blueAutoLight2
blueTouchpadLight1
blueTouchpadLight2
blueTouchpadLight3
resetCounts resetCounts
heartbeat heartbeat
) )
@@ -153,77 +148,66 @@ func (plc *Plc) GetTeamEstops() ([3]bool, [3]bool) {
return redEstops, blueEstops return redEstops, blueEstops
} }
// Returns the count of the red and blue low and high boilers. // Returns the state of the scale and the red and blue switches.
func (plc *Plc) GetBalls() (int, int, int, int) { func (plc *Plc) GetScaleAndSwitches() ([2]bool, [2]bool, [2]bool) {
return int(plc.Counters[redLowBoilerCount]), int(plc.Counters[redHighBoilerCount]), var scale, redSwitch, blueSwitch [2]bool
int(plc.Counters[blueLowBoilerCount]), int(plc.Counters[blueHighBoilerCount])
scale[0] = plc.Inputs[scaleNear]
scale[1] = plc.Inputs[scaleFar]
redSwitch[0] = plc.Inputs[redSwitchNear]
redSwitch[1] = plc.Inputs[redSwitchFar]
blueSwitch[0] = plc.Inputs[blueSwitchNear]
blueSwitch[1] = plc.Inputs[blueSwitchFar]
return scale, redSwitch, blueSwitch
} }
// Returns the state of red and blue activated rotors. // Returns the state of the red and blue vault power cube sensors.
func (plc *Plc) GetRotors() (bool, [3]int, bool, [3]int) { func (plc *Plc) GetVaults() ([3]bool, [3]bool, [3]bool, [3]bool, [3]bool, [3]bool) {
var redOtherRotors, blueOtherRotors [3]int var redForce, redLevitate, redBoost, blueForce, blueLevitate, blueBoost [3]bool
redOtherRotors[0] = int(plc.Counters[redRotor2Count]) redForce[0] = plc.Inputs[redForceCube1]
redOtherRotors[1] = int(plc.Counters[redRotor3Count]) redForce[1] = plc.Inputs[redForceCube2]
redOtherRotors[2] = int(plc.Counters[redRotor4Count]) redForce[2] = plc.Inputs[redForceCube3]
blueOtherRotors[0] = int(plc.Counters[blueRotor2Count]) redLevitate[0] = plc.Inputs[redLevitateCube1]
blueOtherRotors[1] = int(plc.Counters[blueRotor3Count]) redLevitate[1] = plc.Inputs[redLevitateCube2]
blueOtherRotors[2] = int(plc.Counters[blueRotor4Count]) redLevitate[2] = plc.Inputs[redLevitateCube3]
redBoost[0] = plc.Inputs[redBoostCube1]
redBoost[1] = plc.Inputs[redBoostCube2]
redBoost[2] = plc.Inputs[redBoostCube3]
blueForce[0] = plc.Inputs[blueForceCube1]
blueForce[1] = plc.Inputs[blueForceCube2]
blueForce[2] = plc.Inputs[blueForceCube3]
blueLevitate[0] = plc.Inputs[blueLevitate1]
blueLevitate[1] = plc.Inputs[blueLevitate2]
blueLevitate[2] = plc.Inputs[blueLevitate3]
blueBoost[0] = plc.Inputs[blueBoostCube1]
blueBoost[1] = plc.Inputs[blueBoostCube2]
blueBoost[2] = plc.Inputs[blueBoostCube3]
return plc.Inputs[redRotor1], redOtherRotors, plc.Inputs[blueRotor1], blueOtherRotors return redForce, redLevitate, redBoost, blueForce, blueLevitate, blueBoost
} }
func (plc *Plc) GetTouchpads() ([3]bool, [3]bool) { // Returns the state of the red and blue power up buttons on the vaults.
var redTouchpads, blueTouchpads [3]bool func (plc *Plc) GetPowerUpButtons() (bool, bool, bool, bool, bool, bool) {
redTouchpads[0] = plc.Inputs[redTouchpad1] return plc.Inputs[redForceButton], plc.Inputs[redLevitateButton], plc.Inputs[redBoostButton],
redTouchpads[1] = plc.Inputs[redTouchpad2] plc.Inputs[blueForceButton], plc.Inputs[blueLevitateButton], plc.Inputs[blueBoostButton]
redTouchpads[2] = plc.Inputs[redTouchpad3]
blueTouchpads[0] = plc.Inputs[blueTouchpad1]
blueTouchpads[1] = plc.Inputs[blueTouchpad2]
blueTouchpads[2] = plc.Inputs[blueTouchpad3]
return redTouchpads, blueTouchpads
} }
// Resets the ball and rotor gear tooth counts to zero. // Resets the counter counts to zero.
func (plc *Plc) ResetCounts() { func (plc *Plc) ResetCounts() {
plc.Coils[resetCounts] = true plc.Coils[resetCounts] = true
plc.resetCountCycles = 0 plc.resetCountCycles = 0
} }
func (plc *Plc) SetBoilerMotors(on bool) { // Sets the state of the lights inside the power up buttons on the vaults.
plc.Coils[redSerializer] = on func (plc *Plc) SetPowerUpLights(redForce, redLevitate, redBoost, blueForce, blueLevitate, blueBoost bool) {
plc.Coils[redBallLift] = on plc.Coils[redForceLight] = redForce
plc.Coils[blueSerializer] = on plc.Coils[redLevitateLight] = redLevitate
plc.Coils[blueBallLift] = on plc.Coils[redBoostLight] = redBoost
} plc.Coils[blueForceLight] = blueForce
plc.Coils[blueLevitateLight] = blueLevitate
// Turns on/off the rotor motors based on how many rotors each alliance has. plc.Coils[blueBoostLight] = blueBoost
func (plc *Plc) SetRotorMotors(redRotors, blueRotors int) {
plc.Coils[redRotorMotor1] = redRotors >= 1
plc.Coils[redRotorMotor2] = redRotors >= 2
plc.Coils[redRotorMotor3] = redRotors >= 3
plc.Coils[redRotorMotor4] = redRotors == 4
plc.Coils[blueRotorMotor1] = blueRotors >= 1
plc.Coils[blueRotorMotor2] = blueRotors >= 2
plc.Coils[blueRotorMotor3] = blueRotors >= 3
plc.Coils[blueRotorMotor4] = blueRotors == 4
}
// Turns on/off the auto rotor lights based on how many auto rotors each alliance has.
func (plc *Plc) SetRotorLights(redAutoRotors, blueAutoRotors int) {
plc.Coils[redAutoLight1] = redAutoRotors >= 1
plc.Coils[redAutoLight2] = redAutoRotors == 2
plc.Coils[blueAutoLight1] = blueAutoRotors >= 1
plc.Coils[blueAutoLight2] = blueAutoRotors == 2
}
func (plc *Plc) SetTouchpadLights(redTouchpads, blueTouchpads [3]bool) {
plc.Coils[redTouchpadLight1] = redTouchpads[0]
plc.Coils[redTouchpadLight2] = redTouchpads[1]
plc.Coils[redTouchpadLight3] = redTouchpads[2]
plc.Coils[blueTouchpadLight1] = blueTouchpads[0]
plc.Coils[blueTouchpadLight2] = blueTouchpads[1]
plc.Coils[blueTouchpadLight3] = blueTouchpads[2]
} }
func (plc *Plc) GetCycleState(max, index, duration int) bool { func (plc *Plc) GetCycleState(max, index, duration int) bool {
@@ -255,6 +239,10 @@ func (plc *Plc) resetConnection() {
} }
func (plc *Plc) readInputs() bool { func (plc *Plc) readInputs() bool {
if len(plc.Inputs) == 0 {
return true
}
inputs, err := plc.client.ReadDiscreteInputs(0, uint16(len(plc.Inputs))) inputs, err := plc.client.ReadDiscreteInputs(0, uint16(len(plc.Inputs)))
if err != nil { if err != nil {
log.Printf("PLC error reading inputs: %v", err) log.Printf("PLC error reading inputs: %v", err)
@@ -270,6 +258,10 @@ func (plc *Plc) readInputs() bool {
} }
func (plc *Plc) readCounters() bool { func (plc *Plc) readCounters() bool {
if len(plc.Counters) == 0 {
return true
}
registers, err := plc.client.ReadHoldingRegisters(0, uint16(len(plc.Counters))) registers, err := plc.client.ReadHoldingRegisters(0, uint16(len(plc.Counters)))
if err != nil { if err != nil {
log.Printf("PLC error reading registers: %v", err) log.Printf("PLC error reading registers: %v", err)

View File

@@ -12,9 +12,6 @@ type RealtimeScore struct {
Cards map[string]string Cards map[string]string
TeleopCommitted bool TeleopCommitted bool
FoulsCommitted bool FoulsCommitted bool
boiler game.Boiler
rotorSet game.RotorSet
touchpads [3]game.Touchpad
} }
func NewRealtimeScore() *RealtimeScore { func NewRealtimeScore() *RealtimeScore {

View File

@@ -1,42 +0,0 @@
// 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.AutoFuelLow
boiler.FuelHigh = highCount - boiler.AutoFuelHigh
}
}

View File

@@ -1,64 +0,0 @@
// 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, 11, matchStartTime, timeAfterStart(20.1))
checkBoilerCounts(t, 7, 8, 2, 3, &boiler)
}
func TestTeleopFuel(t *testing.T) {
boiler := Boiler{}
boiler.UpdateState(1, 2, matchStartTime, timeAfterStart(1))
boiler.UpdateState(3, 5, matchStartTime, timeAfterStart(21))
checkBoilerCounts(t, 1, 2, 2, 3, &boiler)
boiler.UpdateState(5, 7, matchStartTime, timeAfterStart(120))
checkBoilerCounts(t, 1, 2, 4, 5, &boiler)
boiler.UpdateState(7, 9, matchStartTime, timeAfterEnd(-1))
checkBoilerCounts(t, 1, 2, 6, 7, &boiler)
boiler.UpdateState(9, 11, matchStartTime, timeAfterEnd(4.9))
checkBoilerCounts(t, 1, 2, 8, 9, &boiler)
boiler.UpdateState(11, 13, matchStartTime, timeAfterEnd(5.1))
checkBoilerCounts(t, 1, 2, 8, 9, &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

@@ -14,6 +14,14 @@ var MatchTiming = struct {
EndgameTimeLeftSec int EndgameTimeLeftSec int
}{15, 2, 135, 30} }{15, 2, 135, 30}
func GetAutoEndTime(matchStartTime time.Time) time.Time {
return matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec))
}
func GetTeleopStartTime(matchStartTime time.Time) time.Time {
return matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec + MatchTiming.PauseDurationSec))
}
func GetMatchEndTime(matchStartTime time.Time) time.Time { func GetMatchEndTime(matchStartTime time.Time) time.Time {
return matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec+MatchTiming.PauseDurationSec+ return matchStartTime.Add(time.Duration(MatchTiming.AutoDurationSec+MatchTiming.PauseDurationSec+
MatchTiming.TeleopDurationSec) * time.Second) MatchTiming.TeleopDurationSec) * time.Second)

89
game/power_up.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2018 power ups.
package game
import (
"time"
)
const powerUpDurationSec = 10
// Power up type/effect enum.
const (
force = iota
boost
)
// Power up state enum.
const (
queued = iota
active
expired
)
type PowerUp struct {
alliance int
kind int
level int
startTime time.Time
}
var powerUpUses []*PowerUp
func ResetPowerUps() {
powerUpUses = powerUpUses[:0]
}
func (powerUp *PowerUp) GetState(currentTime time.Time) int {
if powerUp.startTime.After(currentTime) {
return queued
}
if powerUp.getEndTime().After(currentTime) {
return active
}
return expired
}
func (powerUp *PowerUp) getEndTime() time.Time {
return powerUp.startTime.Add(powerUpDurationSec * time.Second)
}
// Returns the current active power up, or nil if there isn't one.
func getActivePowerUp(currentTime time.Time) *PowerUp {
for _, powerUp := range powerUpUses {
if powerUp.GetState(currentTime) == active {
return powerUp
}
}
return nil
}
// Activates the given power up if it can be played, or if not, queues it if the active power up belongs to the other
// alliance. Returns the power up if successful and nil if it cannot be played.
func maybeActivatePowerUp(powerUp *PowerUp, currentTime time.Time) *PowerUp {
canActivate := false
if len(powerUpUses) == 0 {
canActivate = true
powerUp.startTime = currentTime
} else {
lastPowerUp := powerUpUses[len(powerUpUses)-1]
lastPowerUpState := lastPowerUp.GetState(currentTime)
if lastPowerUpState == expired {
canActivate = true
powerUp.startTime = currentTime
} else if lastPowerUpState == active && lastPowerUp.alliance != powerUp.alliance {
canActivate = true
powerUp.startTime = lastPowerUp.getEndTime()
}
}
if canActivate {
powerUpUses = append(powerUpUses, powerUp)
return powerUp
}
return nil
}

66
game/power_up_test.go Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2018 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 TestPowerUpGetState(t *testing.T) {
powerUp := PowerUp{startTime: timeAfterStart(30)}
assert.Equal(t, queued, powerUp.GetState(timeAfterStart(25)))
assert.Equal(t, queued, powerUp.GetState(timeAfterStart(29.9)))
assert.Equal(t, active, powerUp.GetState(timeAfterStart(30.1)))
assert.Equal(t, active, powerUp.GetState(timeAfterStart(39.9)))
assert.Equal(t, expired, powerUp.GetState(timeAfterStart(40.1)))
}
func TestPowerUpActivate(t *testing.T) {
powerUp1 := new(PowerUp)
if assert.NotNil(t, maybeActivatePowerUp(powerUp1, timeAfterStart(30))) {
assert.Equal(t, timeAfterStart(30), powerUp1.startTime)
}
powerUp2 := new(PowerUp)
if assert.NotNil(t, maybeActivatePowerUp(powerUp2, timeAfterStart(45))) {
assert.Equal(t, timeAfterStart(45), powerUp2.startTime)
}
assert.Nil(t, getActivePowerUp(timeAfterStart(29.9)))
assert.Equal(t, powerUp1, getActivePowerUp(timeAfterStart(30.1)))
assert.Equal(t, powerUp1, getActivePowerUp(timeAfterStart(39.9)))
assert.Nil(t, getActivePowerUp(timeAfterStart(42)))
assert.Equal(t, powerUp2, getActivePowerUp(timeAfterStart(45.1)))
assert.Equal(t, powerUp2, getActivePowerUp(timeAfterStart(54.9)))
assert.Nil(t, getActivePowerUp(timeAfterStart(55.1)))
}
func TestPowerUpQueue(t *testing.T) {
powerUp1 := &PowerUp{alliance: redAlliance}
maybeActivatePowerUp(powerUp1, timeAfterStart(60))
powerUp2 := &PowerUp{alliance: redAlliance}
assert.Nil(t, maybeActivatePowerUp(powerUp2, timeAfterStart(65)))
powerUp2.alliance = blueAlliance
if assert.NotNil(t, maybeActivatePowerUp(powerUp2, timeAfterStart(65))) {
assert.Equal(t, timeAfterStart(70), powerUp2.startTime)
}
assert.Equal(t, powerUp1, getActivePowerUp(timeAfterStart(69.9)))
assert.Equal(t, powerUp2, getActivePowerUp(timeAfterStart(70.1)))
}
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

@@ -9,11 +9,10 @@ import "math/rand"
type RankingFields struct { type RankingFields struct {
RankingPoints int RankingPoints int
MatchPoints int ParkClimbPoints int
AutoPoints int AutoPoints int
RotorPoints int OwnershipPoints int
TakeoffPoints int VaultPoints int
PressurePoints int
Random float64 Random float64
Wins int Wins int
Losses int Losses int
@@ -49,19 +48,18 @@ func (fields *RankingFields) AddScoreSummary(ownScore *ScoreSummary, opponentSco
} else { } else {
fields.Losses += 1 fields.Losses += 1
} }
if ownScore.PressureGoalReached { if ownScore.AutoQuest {
fields.RankingPoints += 1 fields.RankingPoints += 1
} }
if ownScore.RotorGoalReached { if ownScore.FaceTheBoss {
fields.RankingPoints += 1 fields.RankingPoints += 1
} }
// Assign tiebreaker points. // Assign tiebreaker points.
fields.MatchPoints += ownScore.Score fields.ParkClimbPoints += ownScore.ParkClimbPoints
fields.AutoPoints += ownScore.AutoPoints fields.AutoPoints += ownScore.AutoPoints
fields.RotorPoints += ownScore.RotorPoints fields.OwnershipPoints += ownScore.OwnershipPoints
fields.TakeoffPoints += ownScore.TakeoffPoints fields.VaultPoints += ownScore.VaultPoints
fields.PressurePoints += ownScore.PressurePoints
// Store a random value to be used as the last tiebreaker if necessary. // Store a random value to be used as the last tiebreaker if necessary.
fields.Random = rand.Float64() fields.Random = rand.Float64()
@@ -79,22 +77,19 @@ func (rankings Rankings) Less(i, j int) bool {
// Use cross-multiplication to keep it in integer math. // Use cross-multiplication to keep it in integer math.
if a.RankingPoints*b.Played == b.RankingPoints*a.Played { if a.RankingPoints*b.Played == b.RankingPoints*a.Played {
if a.MatchPoints*b.Played == b.MatchPoints*a.Played { if a.ParkClimbPoints*b.Played == b.ParkClimbPoints*a.Played {
if a.AutoPoints*b.Played == b.AutoPoints*a.Played { if a.AutoPoints*b.Played == b.AutoPoints*a.Played {
if a.RotorPoints*b.Played == b.RotorPoints*a.Played { if a.OwnershipPoints*b.Played == b.OwnershipPoints*a.Played {
if a.TakeoffPoints*b.Played == b.TakeoffPoints*a.Played { if a.VaultPoints*b.Played == b.VaultPoints*a.Played {
if a.PressurePoints*b.Played == b.PressurePoints*a.Played { return a.Random > b.Random
return a.Random > b.Random
}
return a.PressurePoints*b.Played > b.PressurePoints*a.Played
} }
return a.TakeoffPoints*b.Played > b.TakeoffPoints*a.Played return a.VaultPoints*b.Played > b.VaultPoints*a.Played
} }
return a.RotorPoints*b.Played > b.RotorPoints*a.Played return a.OwnershipPoints*b.Played > b.OwnershipPoints*a.Played
} }
return a.AutoPoints*b.Played > b.AutoPoints*a.Played return a.AutoPoints*b.Played > b.AutoPoints*a.Played
} }
return a.MatchPoints*b.Played > b.MatchPoints*a.Played return a.ParkClimbPoints*b.Played > b.ParkClimbPoints*a.Played
} }
return a.RankingPoints*b.Played > b.RankingPoints*a.Played return a.RankingPoints*b.Played > b.RankingPoints*a.Played
} }

View File

@@ -14,65 +14,61 @@ func TestAddScoreSummary(t *testing.T) {
rand.Seed(0) rand.Seed(0)
redScore := TestScore1() redScore := TestScore1()
blueScore := TestScore2() blueScore := TestScore2()
redSummary := redScore.Summarize(blueScore.Fouls, "qualification") redSummary := redScore.Summarize(blueScore.Fouls)
blueSummary := blueScore.Summarize(redScore.Fouls, "qualification") blueSummary := blueScore.Summarize(redScore.Fouls)
rankingFields := RankingFields{} rankingFields := RankingFields{}
// Add a loss. // Add a loss.
rankingFields.AddScoreSummary(redSummary, blueSummary, false) rankingFields.AddScoreSummary(redSummary, blueSummary, false)
assert.Equal(t, RankingFields{1, 190, 80, 100, 50, 40, 0.9451961492941164, 0, 1, 0, 0, 1}, rankingFields) assert.Equal(t, RankingFields{1, 90, 17, 59, 15, 0.9451961492941164, 0, 1, 0, 0, 1}, rankingFields)
// Add a win. // Add a win.
rankingFields.AddScoreSummary(blueSummary, redSummary, false) rankingFields.AddScoreSummary(blueSummary, redSummary, false)
assert.Equal(t, RankingFields{4, 623, 213, 300, 200, 58, 0.24496508529377975, 1, 1, 0, 0, 2}, rankingFields) assert.Equal(t, RankingFields{4, 125, 52, 152, 45, 0.24496508529377975, 1, 1, 0, 0, 2}, rankingFields)
// Add a tie. // Add a tie.
rankingFields.AddScoreSummary(redSummary, redSummary, false) rankingFields.AddScoreSummary(redSummary, redSummary, false)
assert.Equal(t, RankingFields{6, 813, 293, 400, 250, 98, 0.6559562651954052, 1, 1, 1, 0, 3}, rankingFields) assert.Equal(t, RankingFields{6, 215, 69, 211, 60, 0.6559562651954052, 1, 1, 1, 0, 3}, rankingFields)
// Add a disqualification. // Add a disqualification.
rankingFields.AddScoreSummary(blueSummary, redSummary, true) rankingFields.AddScoreSummary(blueSummary, redSummary, true)
assert.Equal(t, RankingFields{6, 813, 293, 400, 250, 98, 0.6559562651954052, 1, 1, 1, 1, 4}, rankingFields) assert.Equal(t, RankingFields{6, 215, 69, 211, 60, 0.6559562651954052, 1, 1, 1, 1, 4}, rankingFields)
} }
func TestSortRankings(t *testing.T) { func TestSortRankings(t *testing.T) {
// Check tiebreakers. // Check tiebreakers.
rankings := make(Rankings, 14) rankings := make(Rankings, 12)
rankings[0] = &Ranking{1, 0, RankingFields{50, 50, 50, 50, 50, 50, 0.49, 3, 2, 1, 0, 10}} rankings[0] = &Ranking{1, 0, RankingFields{50, 50, 50, 50, 50, 0.49, 3, 2, 1, 0, 10}}
rankings[1] = &Ranking{2, 0, RankingFields{50, 50, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}} rankings[1] = &Ranking{2, 0, RankingFields{50, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
rankings[2] = &Ranking{3, 0, RankingFields{50, 50, 50, 50, 50, 49, 0.50, 3, 2, 1, 0, 10}} rankings[2] = &Ranking{3, 0, RankingFields{50, 50, 50, 50, 49, 0.50, 3, 2, 1, 0, 10}}
rankings[3] = &Ranking{4, 0, RankingFields{50, 50, 50, 50, 50, 51, 0.50, 3, 2, 1, 0, 10}} rankings[3] = &Ranking{4, 0, RankingFields{50, 50, 50, 50, 51, 0.50, 3, 2, 1, 0, 10}}
rankings[4] = &Ranking{5, 0, RankingFields{50, 50, 50, 50, 49, 50, 0.50, 3, 2, 1, 0, 10}} rankings[4] = &Ranking{5, 0, RankingFields{50, 50, 50, 49, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[5] = &Ranking{6, 0, RankingFields{50, 50, 50, 50, 51, 50, 0.50, 3, 2, 1, 0, 10}} rankings[5] = &Ranking{6, 0, RankingFields{50, 50, 50, 51, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[6] = &Ranking{7, 0, RankingFields{50, 50, 50, 49, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[6] = &Ranking{7, 0, RankingFields{50, 50, 49, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[7] = &Ranking{8, 0, RankingFields{50, 50, 50, 51, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[7] = &Ranking{8, 0, RankingFields{50, 50, 51, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[8] = &Ranking{9, 0, RankingFields{50, 50, 49, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[8] = &Ranking{9, 0, RankingFields{50, 49, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[9] = &Ranking{10, 0, RankingFields{50, 50, 51, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[9] = &Ranking{10, 0, RankingFields{50, 51, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[10] = &Ranking{11, 0, RankingFields{50, 49, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[10] = &Ranking{11, 0, RankingFields{49, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[11] = &Ranking{12, 0, RankingFields{50, 51, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}} rankings[11] = &Ranking{12, 0, RankingFields{51, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[12] = &Ranking{13, 0, RankingFields{49, 50, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
rankings[13] = &Ranking{14, 0, RankingFields{51, 50, 50, 50, 50, 50, 0.50, 3, 2, 1, 0, 10}}
sort.Sort(rankings) sort.Sort(rankings)
assert.Equal(t, 14, rankings[0].TeamId) assert.Equal(t, 12, rankings[0].TeamId)
assert.Equal(t, 12, rankings[1].TeamId) assert.Equal(t, 10, rankings[1].TeamId)
assert.Equal(t, 10, rankings[2].TeamId) assert.Equal(t, 8, rankings[2].TeamId)
assert.Equal(t, 8, rankings[3].TeamId) assert.Equal(t, 6, rankings[3].TeamId)
assert.Equal(t, 6, rankings[4].TeamId) assert.Equal(t, 4, rankings[4].TeamId)
assert.Equal(t, 4, rankings[5].TeamId) assert.Equal(t, 2, rankings[5].TeamId)
assert.Equal(t, 2, rankings[6].TeamId) assert.Equal(t, 1, rankings[6].TeamId)
assert.Equal(t, 1, rankings[7].TeamId) assert.Equal(t, 3, rankings[7].TeamId)
assert.Equal(t, 3, rankings[8].TeamId) assert.Equal(t, 5, rankings[8].TeamId)
assert.Equal(t, 5, rankings[9].TeamId) assert.Equal(t, 7, rankings[9].TeamId)
assert.Equal(t, 7, rankings[10].TeamId) assert.Equal(t, 9, rankings[10].TeamId)
assert.Equal(t, 9, rankings[11].TeamId) assert.Equal(t, 11, rankings[11].TeamId)
assert.Equal(t, 11, rankings[12].TeamId)
assert.Equal(t, 13, rankings[13].TeamId)
// Check with unequal number of matches played. // Check with unequal number of matches played.
rankings = make(Rankings, 3) rankings = make(Rankings, 3)
rankings[0] = &Ranking{1, 0, RankingFields{10, 25, 25, 25, 25, 25, 0.49, 3, 2, 1, 0, 5}} rankings[0] = &Ranking{1, 0, RankingFields{10, 25, 25, 25, 25, 0.49, 3, 2, 1, 0, 5}}
rankings[1] = &Ranking{2, 0, RankingFields{19, 50, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 9}} rankings[1] = &Ranking{2, 0, RankingFields{19, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 9}}
rankings[2] = &Ranking{3, 0, RankingFields{20, 50, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}} rankings[2] = &Ranking{3, 0, RankingFields{20, 50, 50, 50, 50, 0.51, 3, 2, 1, 0, 10}}
sort.Sort(rankings) sort.Sort(rankings)
assert.Equal(t, 2, rankings[0].TeamId) assert.Equal(t, 2, rankings[0].TeamId)
assert.Equal(t, 3, rankings[1].TeamId) assert.Equal(t, 3, rankings[1].TeamId)

View File

@@ -1,58 +0,0 @@
// Copyright 2017 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2017 rotor elements.
package game
import (
"time"
)
const rotorGearToothCount = 15
type RotorSet struct {
AutoRotors int
Rotors int
counterOffsets [3]int
}
// Updates the internal counting state of the rotors given the current state of the sensors.
func (rotorSet *RotorSet) UpdateState(rotor1 bool, otherRotors [3]int, 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 && rotor1 {
rotorSet.AutoRotors++
rotorSet.counterOffsets[0] = otherRotors[0]
}
if rotorSet.AutoRotors == 1 && otherRotors[0]-rotorSet.counterOffsets[0] >= rotorGearToothCount {
rotorSet.AutoRotors++
rotorSet.counterOffsets[1] = otherRotors[1]
}
} else if currentTime.Before(teleopValidityCutoff) {
if rotorSet.totalRotors() == 0 && rotor1 {
rotorSet.Rotors++
rotorSet.counterOffsets[0] = otherRotors[0]
}
if rotorSet.totalRotors() == 1 && otherRotors[0]-rotorSet.counterOffsets[0] >= rotorGearToothCount {
rotorSet.Rotors++
rotorSet.counterOffsets[1] = otherRotors[1]
}
if rotorSet.totalRotors() == 2 && otherRotors[1]-rotorSet.counterOffsets[1] >= rotorGearToothCount {
rotorSet.Rotors++
rotorSet.counterOffsets[2] = otherRotors[2]
}
if rotorSet.totalRotors() == 3 && otherRotors[2]-rotorSet.counterOffsets[2] >= rotorGearToothCount {
rotorSet.Rotors++
}
}
}
}
func (rotorSet *RotorSet) totalRotors() int {
return rotorSet.AutoRotors + rotorSet.Rotors
}

View File

@@ -1,134 +0,0 @@
// 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(true, [3]int{15, 15, 0}, matchStartTime, timeAfterStart(-1))
checkRotorCounts(t, 0, 0, &rotorSet)
}
func TestRotorCountThreshold(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState(true, [3]int{10, 0, 0}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState(true, [3]int{14, 0, 0}, matchStartTime, timeAfterStart(21))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(22))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 13, 0}, matchStartTime, timeAfterStart(23))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 16, 0}, matchStartTime, timeAfterStart(24))
checkRotorCounts(t, 0, 3, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 16, 50}, matchStartTime, timeAfterStart(25))
checkRotorCounts(t, 0, 4, &rotorSet)
}
func TestAutoRotors(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(false, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(false, [3]int{15, 15, 15}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 0}, matchStartTime, timeAfterStart(11))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 0}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 2, 1, &rotorSet)
// Check timing threshold.
rotorSet = RotorSet{}
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(15.1))
checkRotorCounts(t, 1, 1, &rotorSet)
}
func TestTeleopRotors(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(false, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(14))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(30))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 0}, matchStartTime, timeAfterStart(100))
checkRotorCounts(t, 0, 3, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 15}, matchStartTime, timeAfterStart(120))
checkRotorCounts(t, 0, 4, &rotorSet)
}
func TestRotorsAfterMatch(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterEnd(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterEnd(2))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 0}, matchStartTime, timeAfterEnd(3))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 15, 15}, matchStartTime, timeAfterEnd(4))
checkRotorCounts(t, 0, 0, &rotorSet)
}
func TestRotorLatching(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(false, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(1))
checkRotorCounts(t, 0, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(2))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 0, 0}, matchStartTime, timeAfterStart(5))
checkRotorCounts(t, 1, 0, &rotorSet)
rotorSet.UpdateState(false, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(10))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState(true, [3]int{15, 0, 0}, matchStartTime, timeAfterStart(10))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 0, 15}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 2, 0, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 15, 0}, matchStartTime, timeAfterStart(30))
checkRotorCounts(t, 2, 1, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 0, 15}, matchStartTime, timeAfterStart(50))
checkRotorCounts(t, 2, 2, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 0, 0}, matchStartTime, timeAfterEnd(-1))
checkRotorCounts(t, 2, 2, &rotorSet)
rotorSet.UpdateState(false, [3]int{0, 0, 0}, matchStartTime, timeAfterEnd(1))
checkRotorCounts(t, 2, 2, &rotorSet)
}
func TestRotorActivationOrder(t *testing.T) {
rotorSet := RotorSet{}
rotorSet.UpdateState(true, [3]int{0, 25, 50}, matchStartTime, timeAfterStart(20))
checkRotorCounts(t, 0, 1, &rotorSet)
rotorSet.UpdateState(true, [3]int{20, 25, 50}, matchStartTime, timeAfterStart(21))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState(true, [3]int{20, 39, 50}, matchStartTime, timeAfterStart(22))
checkRotorCounts(t, 0, 2, &rotorSet)
rotorSet.UpdateState(true, [3]int{20, 40, 70}, matchStartTime, timeAfterStart(23))
checkRotorCounts(t, 0, 3, &rotorSet)
rotorSet.UpdateState(true, [3]int{20, 40, 84}, matchStartTime, timeAfterStart(24))
checkRotorCounts(t, 0, 3, &rotorSet)
rotorSet.UpdateState(true, [3]int{20, 40, 85}, matchStartTime, timeAfterStart(25))
checkRotorCounts(t, 0, 4, &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

@@ -6,33 +6,32 @@
package game package game
type Score struct { type Score struct {
AutoMobility int AutoRuns int
AutoRotors int AutoEndSwitchOwnership bool
AutoFuelLow int AutoOwnershipPoints int
AutoFuelHigh int TeleopOwnershipPoints int
Rotors int VaultCubes int
FuelLow int Levitate bool
FuelHigh int Parks int
Takeoffs int Climbs int
Fouls []Foul Fouls []Foul
ElimDq bool ElimDq bool
} }
type ScoreSummary struct { type ScoreSummary struct {
AutoMobilityPoints int AutoRunPoints int
AutoPoints int AutoPoints int
RotorPoints int OwnershipPoints int
TakeoffPoints int VaultPoints int
PressurePoints int ParkClimbPoints int
BonusPoints int FoulPoints int
FoulPoints int Score int
Score int AutoQuest bool
PressureGoalReached bool FaceTheBoss bool
RotorGoalReached bool
} }
// Calculates and returns the summary fields used for ranking and display. // Calculates and returns the summary fields used for ranking and display.
func (score *Score) Summarize(opponentFouls []Foul, matchType string) *ScoreSummary { func (score *Score) Summarize(opponentFouls []Foul) *ScoreSummary {
summary := new(ScoreSummary) summary := new(ScoreSummary)
// Leave the score at zero if the team was disqualified. // Leave the score at zero if the team was disqualified.
@@ -41,27 +40,24 @@ func (score *Score) Summarize(opponentFouls []Foul, matchType string) *ScoreSumm
} }
// Calculate autonomous score. // Calculate autonomous score.
summary.AutoMobilityPoints = 5 * score.AutoMobility summary.AutoRunPoints = 5 * score.AutoRuns
summary.AutoPoints = summary.AutoMobilityPoints + 60*score.AutoRotors + score.AutoFuelHigh + summary.AutoPoints = summary.AutoRunPoints + score.AutoOwnershipPoints
score.AutoFuelLow/3
// Calculate teleop score. // Calculate teleop score.
summary.RotorPoints = 60*score.AutoRotors + 40*score.Rotors summary.OwnershipPoints = score.AutoOwnershipPoints + score.TeleopOwnershipPoints
summary.TakeoffPoints = 50 * score.Takeoffs summary.VaultPoints = 5 * score.VaultCubes
summary.PressurePoints = (9*score.AutoFuelHigh + 3*score.AutoFuelLow + 3*score.FuelHigh + score.FuelLow) / 9 climbs := score.Climbs
if score.Levitate && score.Climbs < 3 {
climbs++
}
summary.ParkClimbPoints = 5*score.Parks + 30*climbs
// Calculate bonuses. // Calculate bonuses.
if summary.PressurePoints >= 40 { if score.AutoRuns == 3 && score.AutoEndSwitchOwnership {
summary.PressureGoalReached = true summary.AutoQuest = true
if matchType == "elimination" {
summary.BonusPoints += 20
}
} }
if score.AutoRotors+score.Rotors == 4 { if climbs == 3 {
summary.RotorGoalReached = true summary.FaceTheBoss = true
if matchType == "elimination" {
summary.BonusPoints += 100
}
} }
// Calculate penalty points. // Calculate penalty points.
@@ -69,17 +65,18 @@ func (score *Score) Summarize(opponentFouls []Foul, matchType string) *ScoreSumm
summary.FoulPoints += foul.PointValue() summary.FoulPoints += foul.PointValue()
} }
summary.Score = summary.AutoMobilityPoints + summary.RotorPoints + summary.TakeoffPoints + summary.PressurePoints + summary.Score = summary.AutoRunPoints + summary.OwnershipPoints + summary.VaultPoints + summary.ParkClimbPoints +
summary.BonusPoints + summary.FoulPoints summary.FoulPoints
return summary return summary
} }
func (score *Score) Equals(other *Score) bool { func (score *Score) Equals(other *Score) bool {
if score.AutoMobility != other.AutoMobility || score.AutoRotors != other.AutoRotors || if score.AutoRuns != other.AutoRuns || score.AutoEndSwitchOwnership != other.AutoEndSwitchOwnership ||
score.AutoFuelLow != other.AutoFuelLow || score.AutoFuelHigh != other.AutoFuelHigh || score.AutoOwnershipPoints != other.AutoOwnershipPoints ||
score.Rotors != other.Rotors || score.FuelLow != other.FuelLow || score.FuelHigh != other.FuelHigh || score.TeleopOwnershipPoints != other.TeleopOwnershipPoints || score.VaultCubes != other.VaultCubes ||
score.Takeoffs != other.Takeoffs || score.ElimDq != other.ElimDq || len(score.Fouls) != len(other.Fouls) { score.Levitate != other.Levitate || score.Parks != other.Parks || score.Climbs != other.Climbs ||
score.ElimDq != other.ElimDq || len(score.Fouls) != len(other.Fouls) {
return false return false
} }

View File

@@ -12,64 +12,50 @@ func TestScoreSummary(t *testing.T) {
redScore := TestScore1() redScore := TestScore1()
blueScore := TestScore2() blueScore := TestScore2()
redSummary := redScore.Summarize(blueScore.Fouls, "qualification") redSummary := redScore.Summarize(blueScore.Fouls)
assert.Equal(t, 0, redSummary.AutoMobilityPoints) assert.Equal(t, 5, redSummary.AutoRunPoints)
assert.Equal(t, 80, redSummary.AutoPoints) assert.Equal(t, 17, redSummary.AutoPoints)
assert.Equal(t, 100, redSummary.RotorPoints) assert.Equal(t, 59, redSummary.OwnershipPoints)
assert.Equal(t, 50, redSummary.TakeoffPoints) assert.Equal(t, 15, redSummary.VaultPoints)
assert.Equal(t, 40, redSummary.PressurePoints) assert.Equal(t, 90, redSummary.ParkClimbPoints)
assert.Equal(t, 0, redSummary.BonusPoints)
assert.Equal(t, 0, redSummary.FoulPoints) assert.Equal(t, 0, redSummary.FoulPoints)
assert.Equal(t, 190, redSummary.Score) assert.Equal(t, 169, redSummary.Score)
assert.Equal(t, true, redSummary.PressureGoalReached) assert.Equal(t, false, redSummary.AutoQuest)
assert.Equal(t, false, redSummary.RotorGoalReached) assert.Equal(t, true, redSummary.FaceTheBoss)
blueSummary := blueScore.Summarize(redScore.Fouls, "qualification") blueSummary := blueScore.Summarize(redScore.Fouls)
assert.Equal(t, 10, blueSummary.AutoMobilityPoints) assert.Equal(t, 15, blueSummary.AutoRunPoints)
assert.Equal(t, 133, blueSummary.AutoPoints) assert.Equal(t, 35, blueSummary.AutoPoints)
assert.Equal(t, 200, blueSummary.RotorPoints) assert.Equal(t, 93, blueSummary.OwnershipPoints)
assert.Equal(t, 150, blueSummary.TakeoffPoints) assert.Equal(t, 30, blueSummary.VaultPoints)
assert.Equal(t, 18, blueSummary.PressurePoints) assert.Equal(t, 35, blueSummary.ParkClimbPoints)
assert.Equal(t, 0, blueSummary.BonusPoints)
assert.Equal(t, 55, blueSummary.FoulPoints) assert.Equal(t, 55, blueSummary.FoulPoints)
assert.Equal(t, 433, blueSummary.Score) assert.Equal(t, 228, blueSummary.Score)
assert.Equal(t, false, blueSummary.PressureGoalReached) assert.Equal(t, true, blueSummary.AutoQuest)
assert.Equal(t, true, blueSummary.RotorGoalReached) assert.Equal(t, false, blueSummary.FaceTheBoss)
// Test pressure boundary conditions. // Test Auto Quest boundary conditions.
redScore.AutoFuelHigh = 19 blueScore.AutoEndSwitchOwnership = false
assert.Equal(t, false, redScore.Summarize(blueScore.Fouls, "qualification").PressureGoalReached) assert.Equal(t, false, blueScore.Summarize(redScore.Fouls).AutoQuest)
redScore.FuelLow = 18 blueScore.AutoEndSwitchOwnership = true
assert.Equal(t, true, redScore.Summarize(blueScore.Fouls, "qualification").PressureGoalReached) blueScore.AutoRuns = 2
redScore.AutoFuelLow = 1 assert.Equal(t, false, blueScore.Summarize(redScore.Fouls).AutoQuest)
assert.Equal(t, false, redScore.Summarize(blueScore.Fouls, "qualification").PressureGoalReached)
redScore.FuelHigh = 56
assert.Equal(t, true, redScore.Summarize(blueScore.Fouls, "qualification").PressureGoalReached)
// Test rotor boundary conditions. // Test Face the Boss boundary conditions.
blueScore.AutoRotors = 1 redScore.Levitate = false
assert.Equal(t, false, blueScore.Summarize(blueScore.Fouls, "qualification").RotorGoalReached) assert.Equal(t, false, redScore.Summarize(blueScore.Fouls).FaceTheBoss)
blueScore.Rotors = 3 redScore.Climbs = 3
assert.Equal(t, true, blueScore.Summarize(blueScore.Fouls, "qualification").RotorGoalReached) assert.Equal(t, true, redScore.Summarize(blueScore.Fouls).FaceTheBoss)
redScore.Climbs = 1
// Test elimination bonus. redScore.Parks = 2
redSummary = redScore.Summarize(blueScore.Fouls, "elimination") assert.Equal(t, false, redScore.Summarize(blueScore.Fouls).FaceTheBoss)
blueSummary = blueScore.Summarize(redScore.Fouls, "elimination")
assert.Equal(t, 20, redSummary.BonusPoints)
assert.Equal(t, 210, redSummary.Score)
assert.Equal(t, 100, blueSummary.BonusPoints)
assert.Equal(t, 513, blueSummary.Score)
redScore.Rotors = 3
redSummary = redScore.Summarize(blueScore.Fouls, "elimination")
assert.Equal(t, 120, redSummary.BonusPoints)
assert.Equal(t, 0, redScore.Summarize(blueScore.Fouls, "qualification").BonusPoints)
assert.Equal(t, 0, blueScore.Summarize(blueScore.Fouls, "qualification").BonusPoints)
// Test elimination disqualification. // Test elimination disqualification.
redScore.ElimDq = true redScore.ElimDq = true
assert.Equal(t, 0, redScore.Summarize(blueScore.Fouls).Score)
assert.NotEqual(t, 0, blueScore.Summarize(blueScore.Fouls).Score)
blueScore.ElimDq = true blueScore.ElimDq = true
assert.Equal(t, 0, redScore.Summarize(blueScore.Fouls, "elimination").Score) assert.Equal(t, 0, blueScore.Summarize(redScore.Fouls).Score)
assert.Equal(t, 0, blueScore.Summarize(redScore.Fouls, "elimination").Score)
} }
func TestScoreEquals(t *testing.T) { func TestScoreEquals(t *testing.T) {
@@ -82,42 +68,42 @@ func TestScoreEquals(t *testing.T) {
assert.False(t, score1.Equals(score3)) assert.False(t, score1.Equals(score3))
assert.False(t, score3.Equals(score1)) assert.False(t, score3.Equals(score1))
score2.AutoMobility += 1 score2.AutoRuns += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.AutoRotors += 1 score2.AutoEndSwitchOwnership = !score2.AutoEndSwitchOwnership
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.AutoFuelLow += 1 score2.AutoOwnershipPoints += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.AutoFuelHigh += 1 score2.TeleopOwnershipPoints += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.Rotors += 1 score2.VaultCubes += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.FuelLow += 1 score2.Levitate = !score2.Levitate
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.FuelHigh += 1 score2.Parks += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))
score2 = TestScore1() score2 = TestScore1()
score2.Takeoffs += 1 score2.Climbs += 1
assert.False(t, score1.Equals(score2)) assert.False(t, score1.Equals(score2))
assert.False(t, score2.Equals(score1)) assert.False(t, score2.Equals(score1))

150
game/seesaw.go Normal file
View File

@@ -0,0 +1,150 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2018 scale and switch elements.
package game
import (
"time"
)
const (
neitherAlliance = iota
redAlliance
blueAlliance
)
type Seesaw struct {
kind int
nearIsRed bool
ownerships []*Ownership
}
type Ownership struct {
seesaw *Seesaw
ownedBy int
startTime time.Time
endTime *time.Time
}
// Sets which side of the scale or switch belongs to which alliance. A value of true indicates that the side nearest the
// scoring table is red.
func (seesaw *Seesaw) SetRandomization(nearIsRed bool) {
seesaw.nearIsRed = nearIsRed
}
// Updates the internal timing state of the scale or switch given the current state of the sensors.
func (seesaw *Seesaw) UpdateState(state [2]bool, currentTime time.Time) {
ownedBy := neitherAlliance
// Check if there is an active force power up for this seesaw.
currentPowerUp := getActivePowerUp(currentTime)
if currentPowerUp != nil && currentPowerUp.kind == force &&
(seesaw.kind == neitherAlliance && currentPowerUp.level >= 2 ||
(seesaw.kind == currentPowerUp.alliance && (currentPowerUp.level == 1 || currentPowerUp.level == 3))) {
ownedBy = currentPowerUp.alliance
} else {
// Determine current ownership from sensor state.
if state[0] && !state[1] && seesaw.nearIsRed || state[1] && !state[0] && !seesaw.nearIsRed {
ownedBy = redAlliance
} else if state[0] && !state[1] && !seesaw.nearIsRed || state[1] && !state[0] && seesaw.nearIsRed {
ownedBy = blueAlliance
}
}
// Update data if ownership has changed since last cycle.
currentOwnership := seesaw.getCurrentOwnership()
if currentOwnership != nil && ownedBy != currentOwnership.ownedBy ||
currentOwnership == nil && ownedBy != neitherAlliance {
if currentOwnership != nil {
currentOwnership.endTime = &currentTime
}
if ownedBy != neitherAlliance {
newOwnership := &Ownership{seesaw: seesaw, ownedBy: ownedBy, startTime: currentTime}
seesaw.ownerships = append(seesaw.ownerships, newOwnership)
}
}
}
// Returns the auto and teleop period scores for the red alliance.
func (seesaw *Seesaw) GetRedSeconds(startTime, endTime time.Time) float64 {
return seesaw.getAllianceSeconds(redAlliance, startTime, endTime)
}
// Returns the auto and teleop period scores for the blue alliance.
func (seesaw *Seesaw) GetBlueSeconds(startTime, endTime time.Time) float64 {
return seesaw.getAllianceSeconds(blueAlliance, startTime, endTime)
}
func (seesaw *Seesaw) getCurrentOwnership() *Ownership {
if len(seesaw.ownerships) > 0 {
lastOwnership := seesaw.ownerships[len(seesaw.ownerships)-1]
if lastOwnership.endTime == nil {
return lastOwnership
}
}
return nil
}
func (seesaw *Seesaw) getAllianceSeconds(ownedBy int, startTime, endTime time.Time) float64 {
var seconds float64
for _, ownership := range seesaw.ownerships {
if ownership.ownedBy == ownedBy {
seconds += ownership.getSeconds(startTime, endTime, false)
}
}
return seconds
}
// Returns the scoring value for the ownership period, whether it is past or current.
func (ownership *Ownership) getSeconds(startTime, endTime time.Time, ignoreBoost bool) float64 {
var ownershipStartTime, ownershipEndTime time.Time
if ownership.startTime.Before(startTime) {
ownershipStartTime = startTime
} else {
ownershipStartTime = ownership.startTime
}
if ownership.endTime == nil || ownership.endTime.After(endTime) {
ownershipEndTime = endTime
} else {
ownershipEndTime = *ownership.endTime
}
if ownershipStartTime.After(ownershipEndTime) {
return 0
}
ownershipSeconds := ownershipEndTime.Sub(ownershipStartTime).Seconds()
// Find the boost power up applicable to this seesaw and alliance, if it exists.
var boostPowerUp *PowerUp
for _, powerUp := range powerUpUses {
if powerUp.kind == boost && ownership.ownedBy == powerUp.alliance {
if ownership.seesaw.kind == neitherAlliance && powerUp.level >= 2 ||
ownership.seesaw.kind != neitherAlliance && (powerUp.level == 1 || powerUp.level == 3) {
boostPowerUp = powerUp
break
}
}
}
if boostPowerUp != nil {
// Adjust for the boost.
var boostStartTime, boostEndTime time.Time
if boostPowerUp.startTime.Before(ownershipStartTime) {
boostStartTime = ownershipStartTime
} else {
boostStartTime = boostPowerUp.startTime
}
if boostPowerUp.getEndTime().After(ownershipEndTime) {
boostEndTime = ownershipEndTime
} else {
boostEndTime = boostPowerUp.getEndTime()
}
if boostEndTime.After(boostStartTime) {
ownershipSeconds += boostEndTime.Sub(boostStartTime).Seconds()
}
}
return ownershipSeconds
}

170
game/seesaw_test.go Normal file
View File

@@ -0,0 +1,170 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestOwnership(t *testing.T) {
ownership := Ownership{nil, redAlliance, timeAfterStart(1), nil}
assert.Equal(t, 0, ownership.getSeconds(timeAfterStart(0), timeAfterStart(0), true))
assert.Equal(t, 0.5, ownership.getSeconds(timeAfterStart(0), timeAfterStart(1.5), true))
assert.Equal(t, 8.75, ownership.getSeconds(timeAfterStart(0), timeAfterStart(9.75), true))
// Check with truncated start.
assert.Equal(t, 2.5, ownership.getSeconds(timeAfterStart(1.5), timeAfterStart(4), true))
assert.Equal(t, 5, ownership.getSeconds(timeAfterStart(5), timeAfterStart(10), true))
// Check with end time.
endTime := timeAfterStart(13.5)
ownership.endTime = &endTime
assert.Equal(t, 12.5, ownership.getSeconds(timeAfterStart(0), timeAfterStart(15), true))
assert.Equal(t, 4, ownership.getSeconds(timeAfterStart(9.5), timeAfterStart(20), true))
// Check invalid/corner cases.
assert.Equal(t, 0, ownership.getSeconds(timeAfterStart(2), timeAfterStart(1), true))
}
func TestSecondCounting(t *testing.T) {
ResetPowerUps()
redSwitch := &Seesaw{kind: redAlliance}
redSwitch.SetRandomization(true)
// Test that there is no accumulation before the start of the match.
redSwitch.UpdateState([2]bool{true, false}, timeAfterStart(-20))
redSwitch.UpdateState([2]bool{false, false}, timeAfterStart(-12))
redSwitch.UpdateState([2]bool{false, true}, timeAfterStart(-9))
redSwitch.UpdateState([2]bool{false, false}, timeAfterStart(-3))
assert.Equal(t, 0, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(0)))
assert.Equal(t, 0, redSwitch.GetBlueSeconds(timeAfterStart(0), timeAfterStart(0)))
// Test autonomous.
redSwitch.UpdateState([2]bool{true, false}, timeAfterStart(1))
assert.Equal(t, 1, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(2)))
assert.Equal(t, 5.5, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(6.5)))
redSwitch.UpdateState([2]bool{false, false}, timeAfterStart(8.1))
assert.Equal(t, 7.1, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(8.5)))
assert.Equal(t, 7.1, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(10)))
redSwitch.UpdateState([2]bool{false, true}, timeAfterStart(10))
assert.Equal(t, 7.1, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(13)))
redSwitch.UpdateState([2]bool{false, false}, timeAfterStart(13.5))
redSwitch.UpdateState([2]bool{true, false}, timeAfterStart(13.9))
assert.Equal(t, 8.2, redSwitch.GetRedSeconds(timeAfterStart(0), timeAfterStart(15)))
// Test teleop.
assert.Equal(t, 3, redSwitch.GetRedSeconds(timeAfterStart(17), timeAfterStart(20)))
redSwitch.UpdateState([2]bool{false, false}, timeAfterStart(30.8))
assert.Equal(t, 13.8, redSwitch.GetRedSeconds(timeAfterStart(17), timeAfterStart(34)))
redSwitch.UpdateState([2]bool{false, true}, timeAfterStart(35))
assert.Equal(t, 13.8, redSwitch.GetRedSeconds(timeAfterStart(17), timeAfterEnd(-10)))
redSwitch.UpdateState([2]bool{true, false}, timeAfterEnd(-5.1))
assert.Equal(t, 18.9, redSwitch.GetRedSeconds(timeAfterStart(17), timeAfterEnd(0)))
assert.Equal(t, 111.9, redSwitch.GetBlueSeconds(timeAfterStart(17), timeAfterEnd(0)))
}
func TestForce(t *testing.T) {
ResetPowerUps()
blueSwitch := &Seesaw{kind: blueAlliance}
blueSwitch.SetRandomization(true)
scale := &Seesaw{kind: neitherAlliance}
scale.SetRandomization(true)
// Force switch only.
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(0))
scale.UpdateState([2]bool{true, false}, timeAfterStart(0))
powerUp := &PowerUp{alliance: blueAlliance, kind: force, level: 1}
maybeActivatePowerUp(powerUp, timeAfterStart(2.5))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(2.5))
scale.UpdateState([2]bool{true, false}, timeAfterStart(2.5))
assert.Equal(t, 2.5, blueSwitch.GetBlueSeconds(timeAfterStart(0), timeAfterStart(5)))
assert.Equal(t, 0, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(5)))
assert.Equal(t, 10, blueSwitch.GetBlueSeconds(timeAfterStart(0), timeAfterStart(12.5)))
assert.Equal(t, 0, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(12.5)))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(12.5))
scale.UpdateState([2]bool{true, false}, timeAfterStart(12.5))
assert.Equal(t, 10, blueSwitch.GetBlueSeconds(timeAfterStart(0), timeAfterStart(15)))
assert.Equal(t, 0, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(15)))
// Force scale only.
powerUp = &PowerUp{alliance: blueAlliance, kind: force, level: 2}
maybeActivatePowerUp(powerUp, timeAfterStart(20))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(20))
scale.UpdateState([2]bool{true, false}, timeAfterStart(20))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(30))
scale.UpdateState([2]bool{true, false}, timeAfterStart(30))
assert.Equal(t, 0, blueSwitch.GetBlueSeconds(timeAfterStart(20), timeAfterStart(40)))
assert.Equal(t, 10, scale.GetBlueSeconds(timeAfterStart(20), timeAfterStart(40)))
// Force both switch and scale.
powerUp = &PowerUp{alliance: blueAlliance, kind: force, level: 3}
maybeActivatePowerUp(powerUp, timeAfterStart(50))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(50))
scale.UpdateState([2]bool{true, false}, timeAfterStart(50))
blueSwitch.UpdateState([2]bool{true, false}, timeAfterStart(60))
scale.UpdateState([2]bool{true, false}, timeAfterStart(60))
assert.Equal(t, 10, blueSwitch.GetBlueSeconds(timeAfterStart(50), timeAfterStart(70)))
assert.Equal(t, 10, scale.GetBlueSeconds(timeAfterStart(50), timeAfterStart(70)))
}
func TestBoost(t *testing.T) {
ResetPowerUps()
blueSwitch := &Seesaw{kind: blueAlliance}
blueSwitch.SetRandomization(true)
scale := &Seesaw{kind: neitherAlliance}
scale.SetRandomization(false)
// Test within continuous ownership period.
blueSwitch.UpdateState([2]bool{false, true}, timeAfterStart(20))
scale.UpdateState([2]bool{true, false}, timeAfterStart(20))
powerUp := &PowerUp{alliance: blueAlliance, kind: boost, level: 2}
maybeActivatePowerUp(powerUp, timeAfterStart(25))
assert.Equal(t, 5, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(25)))
assert.Equal(t, 6, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(25.5)))
assert.Equal(t, 7.5, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(26.25)))
assert.Equal(t, 15, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(30)))
assert.Equal(t, 25, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(35)))
assert.Equal(t, 30, scale.GetBlueSeconds(timeAfterStart(0), timeAfterStart(40)))
assert.Equal(t, 20, blueSwitch.GetBlueSeconds(timeAfterStart(0), timeAfterStart(40)))
// Test with no ownership at the start.
ResetPowerUps()
blueSwitch.UpdateState([2]bool{false, false}, timeAfterStart(44))
scale.UpdateState([2]bool{false, false}, timeAfterStart(44))
powerUp = &PowerUp{alliance: blueAlliance, kind: boost, level: 3}
maybeActivatePowerUp(powerUp, timeAfterStart(45))
assert.Equal(t, 0, blueSwitch.GetBlueSeconds(timeAfterStart(45), timeAfterStart(50)))
assert.Equal(t, 0, scale.GetBlueSeconds(timeAfterStart(45), timeAfterStart(50)))
blueSwitch.UpdateState([2]bool{false, true}, timeAfterStart(50))
scale.UpdateState([2]bool{true, false}, timeAfterStart(50))
assert.Equal(t, 10, blueSwitch.GetBlueSeconds(timeAfterStart(45), timeAfterStart(55)))
assert.Equal(t, 15, blueSwitch.GetBlueSeconds(timeAfterStart(45), timeAfterStart(60)))
assert.Equal(t, 10, scale.GetBlueSeconds(timeAfterStart(45), timeAfterStart(55)))
assert.Equal(t, 15, scale.GetBlueSeconds(timeAfterStart(45), timeAfterStart(60)))
// Test with interrupted ownership.
ResetPowerUps()
scale.UpdateState([2]bool{false, true}, timeAfterStart(65))
assert.Equal(t, 5, scale.GetRedSeconds(timeAfterStart(65), timeAfterStart(70)))
powerUp = &PowerUp{alliance: redAlliance, kind: boost, level: 2}
maybeActivatePowerUp(powerUp, timeAfterStart(70))
scale.UpdateState([2]bool{false, false}, timeAfterStart(72.5))
assert.Equal(t, 10, scale.GetRedSeconds(timeAfterStart(65), timeAfterStart(72.5)))
assert.Equal(t, 10, scale.GetRedSeconds(timeAfterStart(65), timeAfterStart(77.5)))
scale.UpdateState([2]bool{false, true}, timeAfterStart(77.5))
assert.Equal(t, 15, scale.GetRedSeconds(timeAfterStart(65), timeAfterStart(80)))
assert.Equal(t, 20, scale.GetRedSeconds(timeAfterStart(65), timeAfterStart(85)))
// Test with just the switch.
blueSwitch.UpdateState([2]bool{false, true}, timeAfterStart(100))
scale.UpdateState([2]bool{true, false}, timeAfterStart(100))
powerUp = &PowerUp{alliance: blueAlliance, kind: boost, level: 1}
maybeActivatePowerUp(powerUp, timeAfterStart(100))
assert.Equal(t, 20, blueSwitch.GetBlueSeconds(timeAfterStart(100), timeAfterStart(110)))
assert.Equal(t, 10, scale.GetBlueSeconds(timeAfterStart(100), timeAfterStart(110)))
}

View File

@@ -8,17 +8,17 @@ package game
func TestScore1() *Score { func TestScore1() *Score {
fouls := []Foul{{Rule{"G22", false}, 25, 25.2}, {Rule{"G18", true}, 25, 150}, fouls := []Foul{{Rule{"G22", false}, 25, 25.2}, {Rule{"G18", true}, 25, 150},
{Rule{"G20", true}, 1868, 0}} {Rule{"G20", true}, 1868, 0}}
return &Score{0, 1, 2, 20, 1, 12, 55, 1, fouls, false} return &Score{1, true, 12, 47, 3, true, 0, 2, fouls, false}
} }
func TestScore2() *Score { func TestScore2() *Score {
return &Score{2, 2, 10, 0, 2, 65, 24, 3, []Foul{}, false} return &Score{3, true, 20, 73, 6, false, 1, 1, []Foul{}, false}
} }
func TestRanking1() *Ranking { func TestRanking1() *Ranking {
return &Ranking{254, 1, RankingFields{20, 625, 90, 554, 10, 50, 0.254, 3, 2, 1, 0, 10}} return &Ranking{254, 1, RankingFields{20, 625, 90, 554, 10, 0.254, 3, 2, 1, 0, 10}}
} }
func TestRanking2() *Ranking { func TestRanking2() *Ranking {
return &Ranking{1114, 2, RankingFields{18, 700, 625, 90, 554, 9, 0.1114, 1, 3, 2, 0, 10}} return &Ranking{1114, 2, RankingFields{18, 700, 625, 90, 554, 0.1114, 1, 3, 2, 0, 10}}
} }

View File

@@ -1,70 +0,0 @@
// 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, matchStartTime, currentTime time.Time) {
matchEndTime := GetMatchEndTime(matchStartTime)
if triggered && !touchpad.lastTriggered && currentTime.Before(matchEndTime) {
touchpad.triggeredTime = &currentTime
touchpad.untriggeredTime = nil
} else if !triggered && touchpad.lastTriggered {
if currentTime.Before(matchEndTime) || touchpad.GetState(currentTime) == Triggered {
touchpad.triggeredTime = nil
}
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(currentTime time.Time) int {
if touchpad.triggeredTime != nil {
if touchpad.untriggeredTime != nil {
if touchpad.untriggeredTime.Sub(*touchpad.triggeredTime) >= time.Second {
return Held
} else {
return NotTriggered
}
}
if currentTime.Sub(*touchpad.triggeredTime) >= time.Second {
return Held
} else {
return Triggered
}
}
return NotTriggered
}
func CountTouchpads(touchpads *[3]Touchpad, currentTime time.Time) int {
count := 0
for _, touchpad := range touchpads {
if touchpad.GetState(currentTime) == Held {
count++
}
}
return count
}

View File

@@ -1,105 +0,0 @@
// 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, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(-1)))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(2)))
}
func TestTriggeredReleasedEarly(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-5))
assert.Equal(t, Triggered, touchpad.GetState(timeAfterEnd(-4.9)))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(-3)))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(-1.1)))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(2)))
}
func TestTriggeredTooShort(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-0.5))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(0))
assert.Equal(t, Triggered, touchpad.GetState(timeAfterEnd(0.2)))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(0.4))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(0.5)))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(2)))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(3))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(5)))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(6))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(8)))
}
func TestTriggeredHeld(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-5))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-3))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(1))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(2)))
}
func TestTriggeredReleased(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-5))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-3))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(1))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(2)))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(3))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(4)))
}
func TestReTriggered(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-5))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(-3)))
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-1))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(-1.1)))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(-0.1))
assert.Equal(t, Triggered, touchpad.GetState(timeAfterEnd(0.1)))
assert.Equal(t, Held, touchpad.GetState(timeAfterEnd(2)))
}
func TestTriggeredLate(t *testing.T) {
touchpad := Touchpad{}
touchpad.UpdateState(false, matchStartTime, timeAfterEnd(-10))
touchpad.UpdateState(true, matchStartTime, timeAfterEnd(0.1))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(0.2)))
assert.Equal(t, NotTriggered, touchpad.GetState(timeAfterEnd(2)))
}
func TestCountTouchpads(t *testing.T) {
var touchpads [3]Touchpad
touchpads[0].UpdateState(true, matchStartTime, timeAfterEnd(-5))
touchpads[1].UpdateState(true, matchStartTime, timeAfterEnd(-2))
touchpads[2].UpdateState(true, matchStartTime, timeAfterEnd(-0.1))
assert.Equal(t, 0, CountTouchpads(&touchpads, timeAfterEnd(-6)))
assert.Equal(t, 0, CountTouchpads(&touchpads, timeAfterEnd(-5.5)))
assert.Equal(t, 1, CountTouchpads(&touchpads, timeAfterEnd(-3)))
assert.Equal(t, 1, CountTouchpads(&touchpads, timeAfterEnd(-1.5)))
assert.Equal(t, 2, CountTouchpads(&touchpads, timeAfterEnd(0)))
assert.Equal(t, 3, CountTouchpads(&touchpads, timeAfterEnd(1)))
}

62
game/vault.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Scoring logic for the 2018 vault element.
package game
import (
"time"
)
type Vault struct {
alliance int
numForceCubes int
numLevitateCubes int
numBoostCubes int
LevitatePlayed bool
ForcePowerUp *PowerUp
BoostPowerUp *PowerUp
}
// Updates the state of the vault given the state of the individual power cube sensors.
func (vault *Vault) UpdateCubes(forceCubes, levitateCubes, boostCubes [3]bool) {
vault.numForceCubes = countCubes(forceCubes)
vault.numLevitateCubes = countCubes(levitateCubes)
vault.numBoostCubes = countCubes(boostCubes)
}
// Updates the state of the vault given the state of the power up buttons.
func (vault *Vault) UpdateButtons(forceButton, levitateButton, boostButton bool, currentTime time.Time) {
if levitateButton && vault.numLevitateCubes == 3 && !vault.LevitatePlayed {
vault.LevitatePlayed = true
}
if forceButton && vault.numForceCubes > 0 && vault.ForcePowerUp == nil {
vault.ForcePowerUp = maybeActivatePowerUp(&PowerUp{kind: force, alliance: vault.alliance,
level: vault.numForceCubes}, currentTime)
}
if boostButton && vault.numBoostCubes > 0 && vault.BoostPowerUp == nil {
vault.BoostPowerUp = maybeActivatePowerUp(&PowerUp{kind: boost, alliance: vault.alliance,
level: vault.numBoostCubes}, currentTime)
}
}
// Returns the total count of power cubes that have been placed in the vault.
func (vault *Vault) GetNumCubes() int {
return vault.numForceCubes + vault.numLevitateCubes + vault.numBoostCubes
}
func countCubes(cubes [3]bool) int {
if cubes[0] {
if cubes[1] {
if cubes[2] {
return 3
}
return 2
}
return 1
}
return 0
}

156
game/vault_test.go Normal file
View File

@@ -0,0 +1,156 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package game
import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestVaultNumCubes(t *testing.T) {
vault := Vault{}
assert.Equal(t, 0, vault.GetNumCubes())
vault.UpdateCubes([3]bool{true, false, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
assert.Equal(t, 1, vault.GetNumCubes())
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, false, true}, [3]bool{true, false, false})
assert.Equal(t, 2, vault.GetNumCubes())
vault.UpdateCubes([3]bool{false, true, true}, [3]bool{false, false, true}, [3]bool{true, true, false})
assert.Equal(t, 2, vault.GetNumCubes())
vault.UpdateCubes([3]bool{true, true, false}, [3]bool{true, true, false}, [3]bool{true, true, true})
assert.Equal(t, 7, vault.GetNumCubes())
vault.UpdateCubes([3]bool{true, true, true}, [3]bool{true, true, true}, [3]bool{true, true, true})
assert.Equal(t, 9, vault.GetNumCubes())
}
func TestVaultLevitate(t *testing.T) {
vault := Vault{}
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, true, false, time.Now())
assert.False(t, vault.LevitatePlayed)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, true, false, time.Now())
assert.False(t, vault.LevitatePlayed)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, true, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, true, false, time.Now())
assert.False(t, vault.LevitatePlayed)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, true, true}, [3]bool{false, false, false})
vault.UpdateButtons(true, false, true, time.Now())
assert.False(t, vault.LevitatePlayed)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, true, true}, [3]bool{false, false, false})
vault.UpdateButtons(false, true, false, time.Now())
assert.True(t, vault.LevitatePlayed)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{true, true, true}, [3]bool{false, false, false})
vault.UpdateButtons(false, false, false, time.Now())
assert.True(t, vault.LevitatePlayed)
}
func TestVaultForce(t *testing.T) {
vault := Vault{alliance: blueAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(true, false, false, time.Now())
assert.Nil(t, vault.ForcePowerUp)
vault.UpdateCubes([3]bool{true, true, true}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, true, true, time.Now())
assert.Nil(t, vault.ForcePowerUp)
// Activation with one cube.
vault.UpdateCubes([3]bool{true, false, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(true, false, false, time.Now())
if assert.NotNil(t, vault.ForcePowerUp) {
assert.Equal(t, blueAlliance, vault.ForcePowerUp.alliance)
assert.Equal(t, force, vault.ForcePowerUp.kind)
assert.Equal(t, 1, vault.ForcePowerUp.level)
}
// Activation with two cubes.
vault = Vault{alliance: redAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{true, true, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(true, false, false, time.Now())
if assert.NotNil(t, vault.ForcePowerUp) {
assert.Equal(t, redAlliance, vault.ForcePowerUp.alliance)
assert.Equal(t, force, vault.ForcePowerUp.kind)
assert.Equal(t, 2, vault.ForcePowerUp.level)
}
// Activation with three cubes.
vault = Vault{alliance: blueAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{true, true, true}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(true, false, false, time.Now())
assert.NotNil(t, vault.ForcePowerUp)
if assert.NotNil(t, vault.ForcePowerUp) {
assert.Equal(t, blueAlliance, vault.ForcePowerUp.alliance)
assert.Equal(t, force, vault.ForcePowerUp.kind)
assert.Equal(t, 3, vault.ForcePowerUp.level)
}
vault.UpdateCubes([3]bool{true, true, true}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, false, false, time.Now())
assert.NotNil(t, vault.ForcePowerUp)
}
func TestVaultBoost(t *testing.T) {
vault := Vault{alliance: blueAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{false, false, false})
vault.UpdateButtons(false, false, true, time.Now())
assert.Nil(t, vault.BoostPowerUp)
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{true, true, true})
vault.UpdateButtons(true, true, false, time.Now())
assert.Nil(t, vault.BoostPowerUp)
// Activation with one cube.
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{true, false, false})
vault.UpdateButtons(false, false, true, time.Now())
if assert.NotNil(t, vault.BoostPowerUp) {
assert.Equal(t, blueAlliance, vault.BoostPowerUp.alliance)
assert.Equal(t, boost, vault.BoostPowerUp.kind)
assert.Equal(t, 1, vault.BoostPowerUp.level)
}
// Activation with two cubes.
vault = Vault{alliance: redAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{true, true, false})
vault.UpdateButtons(false, false, true, time.Now())
if assert.NotNil(t, vault.BoostPowerUp) {
assert.Equal(t, redAlliance, vault.BoostPowerUp.alliance)
assert.Equal(t, boost, vault.BoostPowerUp.kind)
assert.Equal(t, 2, vault.BoostPowerUp.level)
}
// Activation with three cubes.
vault = Vault{alliance: blueAlliance}
ResetPowerUps()
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{true, true, true})
vault.UpdateButtons(false, false, true, time.Now())
assert.NotNil(t, vault.BoostPowerUp)
if assert.NotNil(t, vault.BoostPowerUp) {
assert.Equal(t, blueAlliance, vault.BoostPowerUp.alliance)
assert.Equal(t, boost, vault.BoostPowerUp.kind)
assert.Equal(t, 3, vault.BoostPowerUp.level)
}
vault.UpdateCubes([3]bool{false, false, false}, [3]bool{false, false, false}, [3]bool{true, true, true})
vault.UpdateButtons(false, false, false, time.Now())
assert.NotNil(t, vault.BoostPowerUp)
}

View File

@@ -96,12 +96,12 @@ func (database *Database) TruncateMatchResults() error {
// Calculates and returns the summary fields used for ranking and display for the red alliance. // Calculates and returns the summary fields used for ranking and display for the red alliance.
func (matchResult *MatchResult) RedScoreSummary() *game.ScoreSummary { func (matchResult *MatchResult) RedScoreSummary() *game.ScoreSummary {
return matchResult.RedScore.Summarize(matchResult.BlueScore.Fouls, matchResult.MatchType) return matchResult.RedScore.Summarize(matchResult.BlueScore.Fouls)
} }
// Calculates and returns the summary fields used for ranking and display for the blue alliance. // Calculates and returns the summary fields used for ranking and display for the blue alliance.
func (matchResult *MatchResult) BlueScoreSummary() *game.ScoreSummary { func (matchResult *MatchResult) BlueScoreSummary() *game.ScoreSummary {
return matchResult.BlueScore.Summarize(matchResult.RedScore.Fouls, matchResult.MatchType) return matchResult.BlueScore.Summarize(matchResult.RedScore.Fouls)
} }
// Checks the score for disqualifications or a tie and adjusts it appropriately. // Checks the score for disqualifications or a tie and adjusts it appropriately.

View File

@@ -25,7 +25,7 @@ func TestMatchResultCrud(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, matchResult, matchResult2) assert.Equal(t, matchResult, matchResult2)
matchResult.BlueScore.AutoMobility = 12 matchResult.BlueScore.AutoRuns = 12
db.SaveMatchResult(matchResult) db.SaveMatchResult(matchResult)
matchResult2, err = db.GetMatchResultForMatch(254) matchResult2, err = db.GetMatchResultForMatch(254)
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -10,7 +10,6 @@ import (
"crypto/md5" "crypto/md5"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Team254/cheesy-arena/game"
"github.com/Team254/cheesy-arena/model" "github.com/Team254/cheesy-arena/model"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -71,14 +70,13 @@ type TbaRanking struct {
TeamKey string `json:"team_key"` TeamKey string `json:"team_key"`
Rank int `json:"rank"` Rank int `json:"rank"`
RP float32 `json:"RP"` RP float32 `json:"RP"`
Match int `json:"Match"` ParkClimb int
Auto int `json:"Auto"` Auto int
Rotor int `json:"Rotor"` Ownership int
Touchpad int `json:"Touchpad"` Vault int
Pressure int `json:"Pressure"` WinLossTie string `json:"W-L-T"`
WinLossTie string `json:"W-L-T"` Dqs int `json:"dqs"`
Dqs int `json:"dqs"` Played int `json:"played"`
Played int `json:"played"`
} }
type TbaRankings struct { type TbaRankings struct {
@@ -301,8 +299,8 @@ func (client *TbaClient) PublishRankings(database *model.Database) error {
tbaRankings := make([]TbaRanking, len(rankings)) tbaRankings := make([]TbaRanking, len(rankings))
for i, ranking := range rankings { for i, ranking := range rankings {
tbaRankings[i] = TbaRanking{getTbaTeam(ranking.TeamId), ranking.Rank, tbaRankings[i] = TbaRanking{getTbaTeam(ranking.TeamId), ranking.Rank,
float32(ranking.RankingPoints) / float32(ranking.Played), ranking.MatchPoints, ranking.AutoPoints, float32(ranking.RankingPoints) / float32(ranking.Played), ranking.ParkClimbPoints, ranking.AutoPoints,
ranking.RotorPoints, ranking.TakeoffPoints, ranking.PressurePoints, ranking.OwnershipPoints, ranking.VaultPoints,
fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties), ranking.Disqualifications, fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties), ranking.Disqualifications,
ranking.Played} ranking.Played}
} }
@@ -427,49 +425,51 @@ func (client *TbaClient) postRequest(resource string, action string, body []byte
func createTbaScoringBreakdown(match *model.Match, matchResult *model.MatchResult, alliance string) *TbaScoreBreakdown { func createTbaScoringBreakdown(match *model.Match, matchResult *model.MatchResult, alliance string) *TbaScoreBreakdown {
var breakdown TbaScoreBreakdown var breakdown TbaScoreBreakdown
var score *game.Score // TODO(patrick): Implement for 2018.
var scoreSummary *game.ScoreSummary /*
if alliance == "red" { var score *game.Score
score = matchResult.RedScore var scoreSummary *game.ScoreSummary
scoreSummary = matchResult.RedScoreSummary() if alliance == "red" {
} else { score = matchResult.RedScore
score = matchResult.BlueScore scoreSummary = matchResult.RedScoreSummary()
scoreSummary = matchResult.BlueScoreSummary() } else {
} score = matchResult.BlueScore
scoreSummary = matchResult.BlueScoreSummary()
breakdown.AutoFuelHigh = score.AutoFuelHigh
breakdown.AutoFuelLow = score.AutoFuelLow
breakdown.AutoFuelPoints = score.AutoFuelHigh + score.AutoFuelLow/3
breakdown.Rotor1Auto = score.AutoRotors >= 1
breakdown.Rotor2Auto = score.AutoRotors >= 2
breakdown.AutoRotorPoints = 60 * score.AutoRotors
breakdown.AutoMobilityPoints = scoreSummary.AutoMobilityPoints
breakdown.AutoPoints = scoreSummary.AutoPoints
breakdown.TeleopFuelHigh = score.FuelHigh
breakdown.TeleopFuelLow = score.FuelLow
breakdown.TeleopFuelPoints = scoreSummary.PressurePoints - breakdown.AutoFuelPoints
totalRotors := score.AutoRotors + score.Rotors
breakdown.Rotor1Engaged = totalRotors >= 1
breakdown.Rotor2Engaged = totalRotors >= 2
breakdown.Rotor3Engaged = totalRotors >= 3
breakdown.Rotor4Engaged = totalRotors >= 4
breakdown.TeleopRotorPoints = scoreSummary.RotorPoints - breakdown.AutoRotorPoints
breakdown.TeleopTakeoffPoints = scoreSummary.TakeoffPoints
breakdown.TeleopPoints = breakdown.TeleopFuelPoints + breakdown.TeleopRotorPoints +
breakdown.TeleopTakeoffPoints + scoreSummary.BonusPoints
if match.Type == "elimination" {
if scoreSummary.PressureGoalReached {
breakdown.KPaBonusPoints = 20
} }
if scoreSummary.RotorGoalReached {
breakdown.RotorBonusPoints = 100
}
} else {
breakdown.KPaRankingPointAchieved = scoreSummary.PressureGoalReached
breakdown.RotorRankingPointAchieved = scoreSummary.RotorGoalReached
}
breakdown.FoulPoints = scoreSummary.FoulPoints
breakdown.TotalPoints = scoreSummary.Score
breakdown.AutoFuelHigh = score.AutoFuelHigh
breakdown.AutoFuelLow = score.AutoFuelLow
breakdown.AutoFuelPoints = score.AutoFuelHigh + score.AutoFuelLow/3
breakdown.Rotor1Auto = score.AutoRotors >= 1
breakdown.Rotor2Auto = score.AutoRotors >= 2
breakdown.AutoRotorPoints = 60 * score.AutoRotors
breakdown.AutoMobilityPoints = scoreSummary.AutoMobilityPoints
breakdown.AutoPoints = scoreSummary.AutoPoints
breakdown.TeleopFuelHigh = score.FuelHigh
breakdown.TeleopFuelLow = score.FuelLow
breakdown.TeleopFuelPoints = scoreSummary.PressurePoints - breakdown.AutoFuelPoints
totalRotors := score.AutoRotors + score.Rotors
breakdown.Rotor1Engaged = totalRotors >= 1
breakdown.Rotor2Engaged = totalRotors >= 2
breakdown.Rotor3Engaged = totalRotors >= 3
breakdown.Rotor4Engaged = totalRotors >= 4
breakdown.TeleopRotorPoints = scoreSummary.RotorPoints - breakdown.AutoRotorPoints
breakdown.TeleopTakeoffPoints = scoreSummary.TakeoffPoints
breakdown.TeleopPoints = breakdown.TeleopFuelPoints + breakdown.TeleopRotorPoints +
breakdown.TeleopTakeoffPoints + scoreSummary.BonusPoints
if match.Type == "elimination" {
if scoreSummary.PressureGoalReached {
breakdown.KPaBonusPoints = 20
}
if scoreSummary.RotorGoalReached {
breakdown.RotorBonusPoints = 100
}
} else {
breakdown.KPaRankingPointAchieved = scoreSummary.PressureGoalReached
breakdown.RotorRankingPointAchieved = scoreSummary.RotorGoalReached
}
breakdown.FoulPoints = scoreSummary.FoulPoints
breakdown.TotalPoints = scoreSummary.Score
*/
return &breakdown return &breakdown
} }

View File

@@ -1,3 +1,3 @@
Rank,TeamId,RankingPoints,MatchPoints,AutoPoints,RotorPoints,TakeoffPoints,PressurePoints,Wins,Losses,Ties,Disqualifications,Played Rank,TeamId,RankingPoints,ParkClimbPoints,AutoPoints,OwnershipPoints,VaultPoints,Wins,Losses,Ties,Disqualifications,Played
{{range $ranking := .}}{{$ranking.Rank}},{{$ranking.TeamId}},{{$ranking.RankingPoints}},{{$ranking.MatchPoints}},{{$ranking.AutoPoints}},{{$ranking.RotorPoints}},{{$ranking.TakeoffPoints}},{{$ranking.PressurePoints}},{{$ranking.Wins}},{{$ranking.Losses}},{{$ranking.Ties}},{{$ranking.Disqualifications}},{{$ranking.Played}} {{range $ranking := .}}{{$ranking.Rank}},{{$ranking.TeamId}},{{$ranking.RankingPoints}},{{$ranking.ParkClimbPoints}},{{$ranking.AutoPoints}},{{$ranking.OwnershipPoints}},{{$ranking.VaultPoints}},{{$ranking.Wins}},{{$ranking.Losses}},{{$ranking.Ties}},{{$ranking.Disqualifications}},{{$ranking.Played}}
{{end}} {{end}}
1 Rank,TeamId,RankingPoints,MatchPoints,AutoPoints,RotorPoints,TakeoffPoints,PressurePoints,Wins,Losses,Ties,Disqualifications,Played Rank,TeamId,RankingPoints,ParkClimbPoints,AutoPoints,OwnershipPoints,VaultPoints,Wins,Losses,Ties,Disqualifications,Played
2 {{range $ranking := .}}{{$ranking.Rank}},{{$ranking.TeamId}},{{$ranking.RankingPoints}},{{$ranking.MatchPoints}},{{$ranking.AutoPoints}},{{$ranking.RotorPoints}},{{$ranking.TakeoffPoints}},{{$ranking.PressurePoints}},{{$ranking.Wins}},{{$ranking.Losses}},{{$ranking.Ties}},{{$ranking.Disqualifications}},{{$ranking.Played}} {{range $ranking := .}}{{$ranking.Rank}},{{$ranking.TeamId}},{{$ranking.RankingPoints}},{{$ranking.ParkClimbPoints}},{{$ranking.AutoPoints}},{{$ranking.OwnershipPoints}},{{$ranking.VaultPoints}},{{$ranking.Wins}},{{$ranking.Losses}},{{$ranking.Ties}},{{$ranking.Disqualifications}},{{$ranking.Played}}
3 {{end}} {{end}}

View File

@@ -133,7 +133,7 @@ func TestCommitMatch(t *testing.T) {
web.arena.Database.CreateMatch(match) web.arena.Database.CreateMatch(match)
matchResult = model.NewMatchResult() matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id matchResult.MatchId = match.Id
matchResult.BlueScore = &game.Score{AutoMobility: 2} matchResult.BlueScore = &game.Score{AutoRuns: 2}
err = web.commitMatchScore(match, matchResult, false) err = web.commitMatchScore(match, matchResult, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, matchResult.PlayNumber) assert.Equal(t, 1, matchResult.PlayNumber)
@@ -142,7 +142,7 @@ func TestCommitMatch(t *testing.T) {
matchResult = model.NewMatchResult() matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id matchResult.MatchId = match.Id
matchResult.RedScore = &game.Score{AutoMobility: 1} matchResult.RedScore = &game.Score{AutoRuns: 1}
err = web.commitMatchScore(match, matchResult, false) err = web.commitMatchScore(match, matchResult, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, matchResult.PlayNumber) assert.Equal(t, 2, matchResult.PlayNumber)
@@ -177,7 +177,7 @@ func TestCommitEliminationTie(t *testing.T) {
match := &model.Match{Id: 0, Type: "qualification", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5, Blue3: 6} match := &model.Match{Id: 0, Type: "qualification", Red1: 1, Red2: 2, Red3: 3, Blue1: 4, Blue2: 5, Blue3: 6}
web.arena.Database.CreateMatch(match) web.arena.Database.CreateMatch(match)
matchResult := &model.MatchResult{MatchId: match.Id, RedScore: &game.Score{FuelHigh: 15, Fouls: []game.Foul{{}}}, matchResult := &model.MatchResult{MatchId: match.Id, RedScore: &game.Score{VaultCubes: 1, Fouls: []game.Foul{{}}},
BlueScore: &game.Score{}} BlueScore: &game.Score{}}
err := web.commitMatchScore(match, matchResult, false) err := web.commitMatchScore(match, matchResult, false)
assert.Nil(t, err) assert.Nil(t, err)
@@ -233,7 +233,7 @@ func TestCommitCards(t *testing.T) {
err = web.commitMatchScore(match, matchResult, false) err = web.commitMatchScore(match, matchResult, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 0, matchResult.RedScoreSummary().Score) assert.Equal(t, 0, matchResult.RedScoreSummary().Score)
assert.Equal(t, 533, matchResult.BlueScoreSummary().Score) assert.NotEqual(t, 0, matchResult.BlueScoreSummary().Score)
} }
func TestMatchPlayWebsocketCommands(t *testing.T) { func TestMatchPlayWebsocketCommands(t *testing.T) {
@@ -303,12 +303,12 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
readWebsocketType(t, ws, "status") readWebsocketType(t, ws, "status")
readWebsocketType(t, ws, "setAudienceDisplay") readWebsocketType(t, ws, "setAudienceDisplay")
assert.Equal(t, field.PostMatch, web.arena.MatchState) assert.Equal(t, field.PostMatch, web.arena.MatchState)
web.arena.RedRealtimeScore.CurrentScore.AutoMobility = 1 web.arena.RedRealtimeScore.CurrentScore.AutoRuns = 1
web.arena.BlueRealtimeScore.CurrentScore.AutoFuelLow = 2 web.arena.BlueRealtimeScore.CurrentScore.VaultCubes = 2
ws.Write("commitResults", nil) ws.Write("commitResults", nil)
readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay
assert.Equal(t, 1, web.arena.SavedMatchResult.RedScore.AutoMobility) assert.Equal(t, 1, web.arena.SavedMatchResult.RedScore.AutoRuns)
assert.Equal(t, 2, web.arena.SavedMatchResult.BlueScore.AutoFuelLow) assert.Equal(t, 2, web.arena.SavedMatchResult.BlueScore.VaultCubes)
assert.Equal(t, field.PreMatch, web.arena.MatchState) assert.Equal(t, field.PreMatch, web.arena.MatchState)
ws.Write("discardResults", nil) ws.Write("discardResults", nil)
readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay readWebsocketMultiple(t, ws, 3) // reload, realtimeScore, setAllianceStationDisplay

View File

@@ -49,8 +49,8 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
recorder := web.getHttpResponse("/match_review") recorder := web.getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code) assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "QF4-3") assert.Contains(t, recorder.Body.String(), "QF4-3")
assert.Contains(t, recorder.Body.String(), "210") // The red score assert.Contains(t, recorder.Body.String(), "169") // The red score
assert.Contains(t, recorder.Body.String(), "533") // The blue score assert.Contains(t, recorder.Body.String(), "228") // The blue score
// Check response for non-existent match. // Check response for non-existent match.
recorder = web.getHttpResponse(fmt.Sprintf("/match_review/%d/edit", 12345)) recorder = web.getHttpResponse(fmt.Sprintf("/match_review/%d/edit", 12345))
@@ -62,7 +62,7 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "QF4-3") assert.Contains(t, recorder.Body.String(), "QF4-3")
// Update the score to something else. // Update the score to something else.
postBody := "redScoreJson={\"AutoMobility\":3}&blueScoreJson={\"Rotors\":3," + postBody := "redScoreJson={\"AutoRuns\":3}&blueScoreJson={\"VaultCubes\":3," +
"\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}" "\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody) recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code) assert.Equal(t, 303, recorder.Code)
@@ -71,8 +71,8 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
recorder = web.getHttpResponse("/match_review") recorder = web.getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code) assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "QF4-3") assert.Contains(t, recorder.Body.String(), "QF4-3")
assert.Contains(t, recorder.Body.String(), "20") // The red score assert.Contains(t, recorder.Body.String(), "20") // The red score
assert.Contains(t, recorder.Body.String(), "120") // The blue score assert.Contains(t, recorder.Body.String(), "15") // The blue score
} }
func TestMatchReviewCreateNewResult(t *testing.T) { func TestMatchReviewCreateNewResult(t *testing.T) {
@@ -94,7 +94,7 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "QF4-3") assert.Contains(t, recorder.Body.String(), "QF4-3")
// Update the score to something else. // Update the score to something else.
postBody := "redScoreJson={\"AutoRotors\":1}&blueScoreJson={\"FuelHigh\":30," + postBody := "redScoreJson={\"AutoOwnershipPoints\":60}&blueScoreJson={\"VaultCubes\":2," +
"\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}" "\"Fouls\":[{\"TeamId\":973,\"Rule\":\"G22\"}]}&redCardsJson={\"105\":\"yellow\"}&blueCardsJson={}"
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody) recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code) assert.Equal(t, 303, recorder.Code)

View File

@@ -53,8 +53,8 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
} }
// The widths of the table columns in mm, stored here so that they can be referenced for each row. // The widths of the table columns in mm, stored here so that they can be referenced for each row.
colWidths := map[string]float64{"Rank": 13, "Team": 21, "RP": 18, "Match": 18, "Auto": 18, "Rotor": 18, colWidths := map[string]float64{"Rank": 13, "Team": 21, "RP": 18, "Park/Climb": 18, "Auto": 18, "Ownership": 18,
"Takeoff": 18, "Pressure": 18, "W-L-T": 18, "DQ": 18, "Played": 18} "Vault": 18, "W-L-T": 18, "DQ": 18, "Played": 18}
rowHeight := 6.5 rowHeight := 6.5
pdf := gofpdf.New("P", "mm", "Letter", "font") pdf := gofpdf.New("P", "mm", "Letter", "font")
@@ -67,11 +67,10 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
pdf.CellFormat(colWidths["Rank"], rowHeight, "Rank", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Rank"], rowHeight, "Rank", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Team"], rowHeight, "Team", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Team"], rowHeight, "Team", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["RP"], rowHeight, "RP", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["RP"], rowHeight, "RP", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Match"], rowHeight, "Match", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Park/Climb"], rowHeight, "Park/Climb", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Auto"], rowHeight, "Auto", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Auto"], rowHeight, "Auto", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Rotor"], rowHeight, "Rotor", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Ownership"], rowHeight, "Rotor", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Takeoff"], rowHeight, "Takeoff", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["Vault"], rowHeight, "Takeoff", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Pressure"], rowHeight, "Pressure", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["W-L-T"], rowHeight, "W-L-T", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["W-L-T"], rowHeight, "W-L-T", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["DQ"], rowHeight, "DQ", "1", 0, "C", true, 0, "") pdf.CellFormat(colWidths["DQ"], rowHeight, "DQ", "1", 0, "C", true, 0, "")
pdf.CellFormat(colWidths["Played"], rowHeight, "Played", "1", 1, "C", true, 0, "") pdf.CellFormat(colWidths["Played"], rowHeight, "Played", "1", 1, "C", true, 0, "")
@@ -82,11 +81,10 @@ func (web *Web) rankingsPdfReportHandler(w http.ResponseWriter, r *http.Request)
pdf.SetFont("Arial", "", 10) pdf.SetFont("Arial", "", 10)
pdf.CellFormat(colWidths["Team"], rowHeight, strconv.Itoa(ranking.TeamId), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["Team"], rowHeight, strconv.Itoa(ranking.TeamId), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["RP"], rowHeight, strconv.Itoa(ranking.RankingPoints), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["RP"], rowHeight, strconv.Itoa(ranking.RankingPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Match"], rowHeight, strconv.Itoa(ranking.MatchPoints), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["Park/Climb"], rowHeight, strconv.Itoa(ranking.ParkClimbPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Auto"], rowHeight, strconv.Itoa(ranking.AutoPoints), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["Auto"], rowHeight, strconv.Itoa(ranking.AutoPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Rotor"], rowHeight, strconv.Itoa(ranking.RotorPoints), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["Ownership"], rowHeight, strconv.Itoa(ranking.OwnershipPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Takeoff"], rowHeight, strconv.Itoa(ranking.TakeoffPoints), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["Vault"], rowHeight, strconv.Itoa(ranking.VaultPoints), "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["Pressure"], rowHeight, strconv.Itoa(ranking.PressurePoints), "1", 0, "C", false, 0, "")
record := fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties) record := fmt.Sprintf("%d-%d-%d", ranking.Wins, ranking.Losses, ranking.Ties)
pdf.CellFormat(colWidths["W-L-T"], rowHeight, record, "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["W-L-T"], rowHeight, record, "1", 0, "C", false, 0, "")
pdf.CellFormat(colWidths["DQ"], rowHeight, strconv.Itoa(ranking.Disqualifications), "1", 0, "C", false, 0, "") pdf.CellFormat(colWidths["DQ"], rowHeight, strconv.Itoa(ranking.Disqualifications), "1", 0, "C", false, 0, "")

View File

@@ -22,8 +22,8 @@ func TestRankingsCsvReport(t *testing.T) {
recorder := web.getHttpResponse("/reports/csv/rankings") recorder := web.getHttpResponse("/reports/csv/rankings")
assert.Equal(t, 200, recorder.Code) assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "text/plain", recorder.HeaderMap["Content-Type"][0]) assert.Equal(t, "text/plain", recorder.HeaderMap["Content-Type"][0])
expectedBody := "Rank,TeamId,RankingPoints,MatchPoints,AutoPoints,RotorPoints,TakeoffPoints,PressurePoints,Wins," + expectedBody := "Rank,TeamId,RankingPoints,ParkClimbPoints,AutoPoints,OwnershipPoints,VaultPoints,Wins," +
"Losses,Ties,Disqualifications,Played\n1,254,20,625,90,554,10,50,3,2,1,0,10\n2,1114,18,700,625,90,554,9,1,3," + "Losses,Ties,Disqualifications,Played\n1,254,20,625,90,554,10,3,2,1,0,10\n2,1114,18,700,625,90,554,1,3," +
"2,0,10\n\n" "2,0,10\n\n"
assert.Equal(t, expectedBody, recorder.Body.String()) assert.Equal(t, expectedBody, recorder.Body.String())
} }

View File

@@ -147,14 +147,14 @@ func (web *Web) scoringDisplayWebsocketHandler(w http.ResponseWriter, r *http.Re
switch messageType { switch messageType {
case "mobility": case "mobility":
if !autoCommitted { if !autoCommitted {
if (*score).CurrentScore.AutoMobility < 3 { if (*score).CurrentScore.AutoRuns < 3 {
(*score).CurrentScore.AutoMobility++ (*score).CurrentScore.AutoRuns++
} }
} }
case "undoMobility": case "undoMobility":
if !autoCommitted { if !autoCommitted {
if (*score).CurrentScore.AutoMobility > 0 { if (*score).CurrentScore.AutoRuns > 0 {
(*score).CurrentScore.AutoMobility-- (*score).CurrentScore.AutoRuns--
} }
} }
case "commit": case "commit":

View File

@@ -64,8 +64,8 @@ func TestScoringDisplayWebsocket(t *testing.T) {
readWebsocketType(t, blueWs, "score") readWebsocketType(t, blueWs, "score")
} }
assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.AutoMobility) assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.AutoRuns)
assert.Equal(t, 2, web.arena.BlueRealtimeScore.CurrentScore.AutoMobility) assert.Equal(t, 2, web.arena.BlueRealtimeScore.CurrentScore.AutoRuns)
redWs.Write("mobility", nil) redWs.Write("mobility", nil)
for i := 0; i < 1; i++ { for i := 0; i < 1; i++ {
@@ -76,8 +76,8 @@ func TestScoringDisplayWebsocket(t *testing.T) {
} }
// Make sure auto scores haven't changed in teleop. // Make sure auto scores haven't changed in teleop.
assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.AutoMobility) assert.Equal(t, 1, web.arena.RedRealtimeScore.CurrentScore.AutoRuns)
assert.Equal(t, 2, web.arena.BlueRealtimeScore.CurrentScore.AutoMobility) assert.Equal(t, 2, web.arena.BlueRealtimeScore.CurrentScore.AutoRuns)
// Test committing logic. // Test committing logic.
redWs.Write("commitMatch", nil) redWs.Write("commitMatch", nil)

View File

@@ -72,49 +72,52 @@ func (web *Web) fieldTestPostHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// TODO(patrick): Update for 2018.
mode := r.PostFormValue("mode") mode := r.PostFormValue("mode")
switch mode { /*
case "boiler": switch mode {
web.arena.Plc.SetBoilerMotors(true) case "boiler":
web.arena.Plc.SetRotorMotors(0, 0) web.arena.Plc.SetBoilerMotors(true)
web.arena.Plc.SetRotorLights(0, 0) web.arena.Plc.SetRotorMotors(0, 0)
web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false}) web.arena.Plc.SetRotorLights(0, 0)
case "rotor1": web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false})
web.arena.Plc.SetBoilerMotors(false) case "rotor1":
web.arena.Plc.SetRotorMotors(1, 1) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(1, 1) web.arena.Plc.SetRotorMotors(1, 1)
web.arena.Plc.SetTouchpadLights([3]bool{true, false, false}, [3]bool{true, false, false}) web.arena.Plc.SetRotorLights(1, 1)
case "rotor2": web.arena.Plc.SetTouchpadLights([3]bool{true, false, false}, [3]bool{true, false, false})
web.arena.Plc.SetBoilerMotors(false) case "rotor2":
web.arena.Plc.SetRotorMotors(2, 2) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(2, 2) web.arena.Plc.SetRotorMotors(2, 2)
web.arena.Plc.SetTouchpadLights([3]bool{false, true, false}, [3]bool{false, true, false}) web.arena.Plc.SetRotorLights(2, 2)
case "rotor3": web.arena.Plc.SetTouchpadLights([3]bool{false, true, false}, [3]bool{false, true, false})
web.arena.Plc.SetBoilerMotors(false) case "rotor3":
web.arena.Plc.SetRotorMotors(3, 3) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(2, 2) web.arena.Plc.SetRotorMotors(3, 3)
web.arena.Plc.SetTouchpadLights([3]bool{false, false, true}, [3]bool{false, false, true}) web.arena.Plc.SetRotorLights(2, 2)
case "rotor4": web.arena.Plc.SetTouchpadLights([3]bool{false, false, true}, [3]bool{false, false, true})
web.arena.Plc.SetBoilerMotors(false) case "rotor4":
web.arena.Plc.SetRotorMotors(4, 4) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(2, 2) web.arena.Plc.SetRotorMotors(4, 4)
web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false}) web.arena.Plc.SetRotorLights(2, 2)
case "red": web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false})
web.arena.Plc.SetBoilerMotors(false) case "red":
web.arena.Plc.SetRotorMotors(4, 0) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(2, 0) web.arena.Plc.SetRotorMotors(4, 0)
web.arena.Plc.SetTouchpadLights([3]bool{true, true, true}, [3]bool{false, false, false}) web.arena.Plc.SetRotorLights(2, 0)
case "blue": web.arena.Plc.SetTouchpadLights([3]bool{true, true, true}, [3]bool{false, false, false})
web.arena.Plc.SetBoilerMotors(false) case "blue":
web.arena.Plc.SetRotorMotors(0, 4) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(0, 2) web.arena.Plc.SetRotorMotors(0, 4)
web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{true, true, true}) web.arena.Plc.SetRotorLights(0, 2)
default: web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{true, true, true})
web.arena.Plc.SetBoilerMotors(false) default:
web.arena.Plc.SetRotorMotors(0, 0) web.arena.Plc.SetBoilerMotors(false)
web.arena.Plc.SetRotorLights(0, 0) web.arena.Plc.SetRotorMotors(0, 0)
web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false}) web.arena.Plc.SetRotorLights(0, 0)
} web.arena.Plc.SetTouchpadLights([3]bool{false, false, false}, [3]bool{false, false, false})
}
*/
web.arena.FieldTestMode = mode web.arena.FieldTestMode = mode
http.Redirect(w, r, "/setup/field", 303) http.Redirect(w, r, "/setup/field", 303)