PepperBet/PepperBet/Glicko.vb
moosecrab f35bb019cc Moved the Glicko class to a separate file
Removed some unused 'custom upset' code
2020-02-25 14:40:02 -08:00

225 lines
7.3 KiB
VB.net
Executable File

Public Class Glicko
'This constant defines how RD increases over time, higher=faster
Private Const c = 0.6
'Minimum RD (to ensure liquidity)
Private Const RDmin = 30
'Defined as part of the Glicko system
Private Const q = 0.005756462732485
Enum Tiers
Unknown
Potato
B
A
S
X
End Enum
Property K As Double
Private _RD As Double
Property RD As Double
Set(value As Double)
If value < RDmin Then
_RD = RDmin
Else
_RD = value
End If
End Set
Get
Return _RD
End Get
End Property
Property Name As String
Property LastMatch As Long
Property Tier As Tiers = Tiers.Unknown
ReadOnly Property TierString As String
Get
Select Case Me.Tier
Case Tiers.A
Return " A "
Case Tiers.B
Return " B "
Case Tiers.Potato
Return " P "
Case Tiers.S
Return " S"
Case Tiers.X
Return " X "
Case Tiers.Unknown
Return "U "
End Select
Return ""
End Get
End Property
ReadOnly Property TierLetter As String
Get
Select Case Me.Tier
Case Tiers.A
Return "A"
Case Tiers.B
Return "B"
Case Tiers.Potato
Return "P"
Case Tiers.S
Return "S"
Case Tiers.X
Return "X"
Case Tiers.Unknown
Return "U"
End Select
Return ""
End Get
End Property
''' <summary>
''' Updates this Glicko rating after a match. MAKE SURE TO COPY THE OPPONENT BEFORE UPDATING
''' </summary>
''' <param name="opp">Opponent that was faced</param>
''' <param name="win">Did this Glicko win? Draw=0.5</param>
''' <param name="t">Match number of this match</param>
''' <remarks></remarks>
Private Sub Update(opp() As Glicko, win() As Double, t As Long)
Dim E As Double = 0
Dim g As Double = 0
Dim dsum As Double = 0
Dim rsum As Double = 0
For i = 0 To opp.Count - 1
g = 1 / Math.Sqrt(1 + (3 * Math.Pow(q, 2) * Math.Pow(opp(i).RD, 2)) / Math.Pow(Math.PI, 2))
E = 1 / (1 + Math.Pow(10, (g * (Me.K - opp(i).K)) / -400))
dsum += Math.Pow(g, 2) * E * (1 - E)
rsum += g * (win(i) - E)
Next
Dim dsq As Double = Math.Pow(Math.Pow(q, 2) * dsum, -1)
Me.K = Me.K + (q / ((1 / Math.Pow(Me.RD, 2)) + (1 / dsq))) * rsum
Me.RD = Math.Max(Math.Sqrt(Math.Pow((1 / Math.Pow(Me.RD, 2)) + (1 / dsq), -1)), 30)
Me.LastMatch = t
End Sub
Public Shared Sub UpdateBatch(a As Glicko, b As Glicko, Acount As Integer, Bcount As Integer, t As Long, Optional Dcount As Integer = 0)
Dim total As Integer = Acount + Bcount + Dcount
Dim Aresults(total - 1) As Double
Dim aopps(total - 1) As Glicko
Dim Bresults(total - 1) As Double
Dim bopps(total - 1) As Glicko
Dim upset As Boolean = False
Dim confidence As Double = Conf(a, b)
Dim tempb As New Glicko("", -1)
tempb.K = b.K
tempb.RD = b.RD
For i = 0 To total - 1
aopps(i) = tempb
bopps(i) = a
Next
For i = 0 To Acount - 1
Aresults(i) = 1
Next
For i = Acount To Acount + Bcount - 1
Bresults(i) = 1
Next
For i = Acount + Bcount To total - 1
Aresults(i) = 0.5
Bresults(i) = 0.5
Next
If (Aresults.Average > 0.5 And b.K > a.K) Or (Bresults.Average > 0.5 And a.K > b.K) Then
upset = True
End If
b.Update(bopps, Bresults, t)
a.Update(aopps, Aresults, t)
'My custom RD-increasing function in case of upsets
'Note this increases after Glicko's decrease, so they oppose each other
'If upset = True Then
' 'U-factor is upset factor, the maximum possible increase in RD for a character if we trusted the results 100% accurately
' Dim aUfactor As Double = u * a.RD * (confidence ^ e)
' Dim bUfactor As Double = u * b.RD * (confidence ^ e)
' 'F-factor is the fluke factor, more rounds means it's less of a fluke and we should increase more
' Dim Ffactor As Double = 1 - (1 / (Aresults.Count + f))
' 'C-factor is the closeness factor, close matches are weighted less (5-0 more increase than 5-4)
' Dim Cfactor As Double = (Math.Max(Aresults.Average, Bresults.Average) - 0.5) * 2
' a.RD = Math.Min(350, a.RD + (aUfactor * Ffactor * Cfactor))
' b.RD = Math.Min(350, b.RD + (bUfactor * Ffactor * Cfactor))
'End If
End Sub
''' <summary>
''' Time-decays RD in preperation for a match
''' </summary>
''' <param name="t">Current match number</param>
''' <remarks></remarks>
Public Sub Prep(t As Long)
Me.RD = Math.Min(350, Math.Sqrt(Math.Pow(Me.RD, 2) + Math.Pow(c, 2) * (t - Me.LastMatch)))
End Sub
Public Sub New(name As String, t As Long)
Me.K = 1500
Me.RD = 350
Me.Name = name
Me.LastMatch = t
End Sub
Public Function GenerateXMLItem() As XElement
Dim ret As New XElement("glicko")
ret.SetAttributeValue("name", Me.Name)
ret.SetAttributeValue("k", Me.K)
ret.SetAttributeValue("rd", Me.RD)
ret.SetAttributeValue("lastmatch", Me.LastMatch)
ret.SetAttributeValue("tier", CInt(Me.Tier))
Return ret
End Function
Private Shared Function erf(x As Double) As Double
'Erf(x) approximation, max error 3e-7
Dim a1 As Double = 0.0705230784
Dim a2 As Double = 0.0422820123
Dim a3 As Double = 0.0092705272
Dim a4 As Double = 0.0001520143
Dim a5 As Double = 0.0002765672
Dim a6 As Double = 0.0000430638
a1 = a1 * x
a2 = a2 * (x ^ 2)
a3 = a3 * (x ^ 3)
a4 = a4 * (x ^ 4)
a5 = a5 * (x ^ 5)
a6 = a6 * (x ^ 6)
Return 1 - (1 / ((1 + a1 + a2 + a3 + a4 + a5 + a6) ^ 16))
End Function
Private Shared Function CDF(x As Double, mu As Double, sigma As Double) As Double
Return 0.5 * (1 + erf((x - mu) / (sigma * Math.Sqrt(2))))
End Function
Private Shared Function StdNorm(x As Double) As Double
Return Math.Exp(-0.5 * (x ^ 2)) / (Math.Sqrt(2 * Math.PI))
End Function
Private Shared Function Norm(x As Double, mu As Double, sigma As Double) As Double
Return (1 / sigma) * StdNorm((x - mu) / sigma)
End Function
Public Shared Function Conf(a As Glicko, b As Glicko) As Double
Dim sum As Double
For i = 0 To 3000 Step 1
sum += Math.Max(Norm(i, a.K, a.RD) - Norm(i, b.K, b.RD), 0)
Next
Return sum
End Function
End Class