mirror of https://github.com/gabehf/go-elo.git
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.
189 lines
4.6 KiB
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()
|
|
}
|