You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-elo/match.go

189 lines
4.6 KiB

package elo
import (
"math"
)
type Player interface {
GetElo() float64
SetElo(float64)
}
type Match struct {
PlayerOne Player
PlayerTwo Player
finished bool
strategy StrategyFunc
k float64
deviation float64
scoreWeight float64
ignoreDraws bool
}
// Args p1 and p2 should be non-nil pointers.
func (c *Calculator) NewMatch(p1, p2 Player) *Match {
m := new(Match)
m.PlayerOne = p1
m.PlayerTwo = p2
m.strategy = c.strategy
m.k = c.k
m.deviation = c.deviation
m.scoreWeight = c.scoreWeight
m.ignoreDraws = c.ignoreDraws
return m
}
// Contains each player's odds to win, as a number between 0-1, where
// 0 is a 0% chance to win, and 1 is a 100% chance to win.
type MatchOdds struct {
PlayerOneOdds float64
PlayerTwoOdds float64
}
type MatchResult struct {
// Required when using a non-scored strategy.
// Ignored when using a scored strategy.
Outcome MatchOutcome
// Required when using a scored strategy.
// Ignored when using a non-scored strategy.
PlayerOneScore int
// Required when using a scored strategy.
// Ignored when using a non-scored strategy.
PlayerTwoScore int
}
type MatchOutcome int
const (
OutcomeDraw = MatchOutcome(0)
OutcomePlayerOneWin = MatchOutcome(1)
OutcomePlayerTwoWin = MatchOutcome(2)
)
// Set a strategy to be used for this match only.
func (m *Match) SetStrategy(sf StrategyFunc) {
m.strategy = sf
}
// K must be non-negative. If a negative value is provided, K will be unchanged.
func (m *Match) SetKValue(k float64) {
if k < 0 {
return
}
m.k = k
}
func (m *Match) GetKValue() float64 {
return m.k
}
// Score weight must be non-negative.
// If a negative value is provided, ScoreWeight will be unchanged.
func (m *Match) SetScoreWeight(w float64) {
if w < 0 {
return
}
m.scoreWeight = w
}
func (m *Match) GetScoreWeight() float64 {
return m.scoreWeight
}
// Deviation must be non-negative.
// If a negative value is provided, the deviation will be unchanged.
func (m *Match) SetDeviation(d float64) {
if d < 0 {
return
}
m.deviation = d
}
func (m *Match) GetDeviation() float64 {
return m.deviation
}
func (m *Match) IgnoreDraws(ignore bool) {
m.ignoreDraws = ignore
}
func (m *Match) GetIgnoreDraws() bool {
return m.ignoreDraws
}
func (m Match) GetOdds() *MatchOdds {
R1 := math.Pow(10, m.PlayerOne.GetElo()/m.deviation)
R2 := math.Pow(10, m.PlayerTwo.GetElo()/m.deviation)
E1 := R1 / (R1 + R2)
E2 := R2 / (R1 + R2)
return &MatchOdds{
PlayerOneOdds: E1,
PlayerTwoOdds: E2,
}
}
// Adjusts the Match's player's elo according to who won the match.
// Can only be called once. Any subsequent calls on the same match will result in no changes
// to the players' elo ratings.
//
// Note: Play() uses a reference to the Match's calculator to determine the new elos. If the
// calculator no longer exists, the function will panic.
func (m *Match) Play(result *MatchResult) {
if m.finished ||
((result.Outcome == OutcomeDraw) &&
m.ignoreDraws &&
(result.PlayerOneScore == result.PlayerTwoScore)) {
return
}
n1, n2 := m.strategy(&CalculatorInput{
PlayerOne: m.PlayerOne.GetElo(),
PlayerTwo: m.PlayerTwo.GetElo(),
PlayerOneScore: result.PlayerOneScore,
PlayerTwoScore: result.PlayerTwoScore,
Outcome: result.Outcome,
Deviation: m.deviation,
ScoreWeight: m.scoreWeight,
K: m.k,
})
// fmt.Printf("%+v", CalculatorInput{
// PlayerOne: m.PlayerOne.GetElo(),
// PlayerTwo: m.PlayerTwo.GetElo(),
// PlayerOneScore: result.PlayerOneScore,
// PlayerTwoScore: result.PlayerTwoScore,
// Outcome: result.Outcome,
// Deviation: m.deviation,
// ScoreWeight: m.scoreWeight,
// K: m.k,
// })
m.PlayerOne.SetElo(n1)
m.PlayerTwo.SetElo(n2)
m.finished = true
}
// Returns how much player one stands to gain if they win.
// Equivalent to how much player two will lose if they lose.
//
// Note: May not be accurate when using a scored strategy.
func (m Match) PlayerOneGain() float64 {
n1, _ := m.strategy(&CalculatorInput{
PlayerOne: m.PlayerOne.GetElo(),
PlayerTwo: m.PlayerTwo.GetElo(),
Outcome: OutcomePlayerOneWin,
K: m.k,
Deviation: m.deviation,
})
return n1 - m.PlayerOne.GetElo()
}
// Returns how much player two stands to gain if they win.
// Equivalent to how much player one will lose if they lose.
//
// Note: May not be accurate when using a scored strategy.
func (m Match) PlayerTwoGain() float64 {
_, n2 := m.strategy(&CalculatorInput{
PlayerOne: m.PlayerOne.GetElo(),
PlayerTwo: m.PlayerTwo.GetElo(),
Outcome: OutcomePlayerTwoWin,
K: m.k,
Deviation: m.deviation,
})
return n2 - m.PlayerTwo.GetElo()
}