393 lines
17 KiB
Go
393 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"slices"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
)
|
|
|
|
type DuelResult struct {
|
|
Player1_NewELO float64
|
|
Player2_NewELO float64
|
|
Commander1_NewELO float64
|
|
Commander2_NewELO float64
|
|
}
|
|
|
|
func (app *application) SetupHooks() {
|
|
default_ELO_value := 100.0
|
|
|
|
// Set default values after creation
|
|
app.pb.OnRecordAfterCreateSuccess("elo_player").BindFunc(func (e *core.RecordEvent) error {
|
|
e.Record.Set("elo_casual", default_ELO_value)
|
|
e.Record.Set("elo_tournament", default_ELO_value)
|
|
e.App.Save(e.Record)
|
|
|
|
log.Println("record created")
|
|
return e.Next()
|
|
})
|
|
|
|
app.pb.OnRecordUpdate("elo").BindFunc(func (e *core.RecordEvent) error {
|
|
// Check if any of these fields has been changed : Player1, Player2, Commandant1, Commandant2, Score1, Score2, Date, Official
|
|
if(e.Record.GetString("player_1") != e.Record.Original().GetString("player_1") || e.Record.GetString("player_2") != e.Record.Original().GetString("player_2") || e.Record.GetString("commandant_1") != e.Record.Original().GetString("commandant_1") || e.Record.GetString("commandant_2") != e.Record.Original().GetString("commandant_2") || e.Record.GetString("score_1") != e.Record.Original().GetString("score_1") || e.Record.GetString("score_2") != e.Record.Original().GetString("score_2") || e.Record.GetString("date") != e.Record.Original().GetString("date") || e.Record.GetString("official") != e.Record.Original().GetString("official")) {
|
|
UpdateRecordELO(e, default_ELO_value)
|
|
UpdateLastELO(e)
|
|
updateNextMatchs(e)
|
|
|
|
}
|
|
|
|
return e.Next()
|
|
})
|
|
|
|
// When a ELO entry has been added
|
|
app.pb.OnRecordCreate("elo").BindFunc(func (e *core.RecordEvent) error {
|
|
UpdateRecordELO(e, default_ELO_value)
|
|
|
|
UpdateLastELO(e)
|
|
|
|
updateNextMatchs(e)
|
|
|
|
return e.Next()
|
|
})
|
|
}
|
|
|
|
func updateNextMatchs(e *core.RecordEvent) {
|
|
official := e.Record.GetBool("official")
|
|
needToRecalculateElo := false
|
|
nextMatchsCount := int64(0)
|
|
|
|
filters := []dbx.Expression{
|
|
// Player1
|
|
dbx.And(dbx.NewExp("date > {:date}", dbx.Params{"date": e.Record.GetString("date")}),dbx.Or(dbx.NewExp("player_1 = {:id}", dbx.Params{"id": e.Record.GetString("player_1")}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": e.Record.GetString("player_1")}))),
|
|
// Player2
|
|
dbx.And(dbx.NewExp("date > {:date}", dbx.Params{"date": e.Record.GetString("date")}),dbx.Or(dbx.NewExp("player_1 = {:id}", dbx.Params{"id": e.Record.GetString("player_2")}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": e.Record.GetString("player_2")}))),
|
|
// Commander1
|
|
dbx.And(dbx.NewExp("date > {:date}", dbx.Params{"date": e.Record.GetString("date")}),dbx.NewExp("official = {:official}", dbx.Params{"official": official}),dbx.Or(dbx.NewExp("commandant_1 = {:id}", dbx.Params{"id": e.Record.GetString("commandant_1")}), dbx.NewExp("commandant_2 = {:id}", dbx.Params{"id": e.Record.GetString("commandant_1")}))),
|
|
// Commander2
|
|
dbx.And(dbx.NewExp("date > {:date}", dbx.Params{"date": e.Record.GetString("date")}),dbx.NewExp("official = {:official}", dbx.Params{"official": official}),dbx.Or(dbx.NewExp("commandant_1 = {:id}", dbx.Params{"id": e.Record.GetString("commandant_2")}), dbx.NewExp("commandant_2 = {:id}", dbx.Params{"id": e.Record.GetString("commandant_2")}))),
|
|
}
|
|
|
|
for _, exp := range filters {
|
|
nextMatchsCount, _ = e.App.CountRecords("elo", exp)
|
|
if nextMatchsCount > 0 {
|
|
needToRecalculateElo = true
|
|
}
|
|
}
|
|
|
|
if needToRecalculateElo {
|
|
eloMatchs := []Elo{}
|
|
log.Println(fmt.Sprintf("Matchs found after %s", e.Record.GetString("id")))
|
|
|
|
playerIdChanged := []string{ e.Record.GetString("player_1"), e.Record.GetString("player_2") }
|
|
newPlayerData := map[string]float64{}
|
|
newPlayerData[e.Record.GetString("player_1")] = e.Record.GetFloat("elo_player_1")
|
|
newPlayerData[e.Record.GetString("player_2")] = e.Record.GetFloat("elo_player_2")
|
|
newTournamentPlayerData := map[string]float64{}
|
|
newTournamentPlayerData[e.Record.GetString("player_1")] = e.Record.GetFloat("elo_tournament_player_1")
|
|
newTournamentPlayerData[e.Record.GetString("player_2")] = e.Record.GetFloat("elo_tournament_player_2")
|
|
newCommanderData := map[string]float64{}
|
|
newCommanderData[e.Record.GetString("commandant_1")] = e.Record.GetFloat("elo_commandant_1")
|
|
newCommanderData[e.Record.GetString("commandant_2")] = e.Record.GetFloat("elo_commandant_2")
|
|
newTournamentCommanderData := map[string]float64{}
|
|
newTournamentCommanderData[e.Record.GetString("commandant_1")] = e.Record.GetFloat("elo_tournament_commandant_1")
|
|
newTournamentCommanderData[e.Record.GetString("commandant_2")] = e.Record.GetFloat("elo_tournament_commandant_2")
|
|
commanderIdChanged := []string{ e.Record.GetString("commandant_1"), e.Record.GetString("commandant_2") }
|
|
|
|
err := e.App.RecordQuery("elo").
|
|
Where(dbx.NewExp("date > {:date}", dbx.Params{"date":e.Record.GetString("date")})).
|
|
OrderBy("date ASC").
|
|
All(&eloMatchs)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
|
|
for _, v := range eloMatchs {
|
|
if slices.Contains(playerIdChanged, v.Player1) || slices.Contains(playerIdChanged, v.Player2) || slices.Contains(commanderIdChanged, v.Commander1) || slices.Contains(commanderIdChanged, v.Commander2) {
|
|
record, _ := e.App.FindRecordById("elo", v.ID)
|
|
|
|
// If Player1 is not in the ID changed, now it is
|
|
if !slices.Contains(playerIdChanged, v.Player1) {
|
|
playerIdChanged = append(playerIdChanged, v.Player1)
|
|
} else {
|
|
record.Set("last_elo_player_1", newPlayerData[v.Player1])
|
|
record.Set("last_tournament_elo_player_1", newTournamentPlayerData[v.Player1])
|
|
}
|
|
|
|
|
|
// If Player2 is not in the ID changed, now it is
|
|
if !slices.Contains(playerIdChanged, v.Player2) {
|
|
playerIdChanged = append(playerIdChanged, v.Player2)
|
|
} else {
|
|
record.Set("last_elo_player_2", newPlayerData[v.Player2])
|
|
record.Set("last_tournament_elo_player_2", newTournamentPlayerData[v.Player2])
|
|
}
|
|
|
|
// If Commander1 is not in the ID changed, now it is
|
|
if !slices.Contains(commanderIdChanged, v.Commander1) {
|
|
commanderIdChanged = append(commanderIdChanged, v.Commander1)
|
|
} else {
|
|
record.Set("last_elo_commandant_1", newCommanderData[v.Commander1])
|
|
record.Set("last_tournament_elo_commandant_1", newTournamentCommanderData[v.Commander1])
|
|
}
|
|
|
|
// If Commander2 is not in the ID changed, now it is
|
|
if !slices.Contains(commanderIdChanged, v.Commander2) {
|
|
commanderIdChanged = append(commanderIdChanged, v.Commander2)
|
|
} else {
|
|
record.Set("last_elo_commandant_2", newCommanderData[v.Commander2])
|
|
record.Set("last_tournament_elo_commandant_2", newTournamentCommanderData[v.Commander2])
|
|
}
|
|
|
|
|
|
tempEloData := CalculateELO(record.GetFloat("last_elo_player_1"), record.GetFloat("last_elo_player_2"), record.GetFloat("last_elo_commandant_1"), record.GetFloat("last_elo_commandant_2"), record.GetInt("score_1"), record.GetInt("score_2"))
|
|
record.Set("elo_player_1", tempEloData.Player1_NewELO)
|
|
record.Set("elo_player_2", tempEloData.Player2_NewELO)
|
|
record.Set("elo_commandant_1", tempEloData.Commander1_NewELO)
|
|
record.Set("elo_commandant_2", tempEloData.Commander2_NewELO)
|
|
tempTournamentEloData := CalculateELO(record.GetFloat("last_tournament_elo_player_1"), record.GetFloat("last_tournament_elo_player_2"), record.GetFloat("last_tournament_elo_commandant_1"), record.GetFloat("last_tournament_elo_commandant_2"), record.GetInt("score_1"), record.GetInt("score_2"))
|
|
record.Set("elo_tournament_player_1", tempTournamentEloData.Player1_NewELO)
|
|
record.Set("elo_tournament_player_2", tempTournamentEloData.Player2_NewELO)
|
|
record.Set("elo_tournament_commandant_1", tempTournamentEloData.Commander1_NewELO)
|
|
record.Set("elo_tournament_commandant_2", tempTournamentEloData.Commander2_NewELO)
|
|
|
|
newPlayerData[v.Player1] = tempEloData.Player1_NewELO
|
|
newPlayerData[v.Player2] = tempEloData.Player2_NewELO
|
|
newTournamentPlayerData[v.Player1] = tempTournamentEloData.Player1_NewELO
|
|
newTournamentPlayerData[v.Player2] = tempTournamentEloData.Player2_NewELO
|
|
newCommanderData[v.Commander1] = tempEloData.Commander1_NewELO
|
|
newCommanderData[v.Commander2] = tempEloData.Commander2_NewELO
|
|
newTournamentCommanderData[v.Commander1] = tempTournamentEloData.Commander1_NewELO
|
|
newTournamentCommanderData[v.Commander2] = tempTournamentEloData.Commander2_NewELO
|
|
|
|
e.App.Save(record)
|
|
}
|
|
}
|
|
|
|
// Update Card and Player with the latest calculated ELO
|
|
UpdateLastELO(e, newTournamentPlayerData[e.Record.GetString("player_1")], newTournamentPlayerData[e.Record.GetString("player_2")], newPlayerData[e.Record.GetString("player_1")], newPlayerData[e.Record.GetString("player_2")], newTournamentCommanderData[e.Record.GetString("commandant_1")], newTournamentCommanderData[e.Record.GetString("commandant_2")], newCommanderData[e.Record.GetString("commandant_1")], newCommanderData[e.Record.GetString("commandant_2")],)
|
|
}
|
|
}
|
|
|
|
func UpdateLastELO(e *core.RecordEvent, args ...float64) {
|
|
player1EloTournament := 0.0
|
|
player2EloTournament := 0.0
|
|
player1Elo := 0.0
|
|
player2Elo := 0.0
|
|
commander1EloTournament := 0.0
|
|
commander2EloTournament := 0.0
|
|
commander1Elo := 0.0
|
|
commander2Elo := 0.0
|
|
|
|
// Check if args have been passed (It happens when updating match while inserting between two existing matchs)
|
|
if len(args) == 8 {
|
|
player1EloTournament = args[0]
|
|
player2EloTournament = args[1]
|
|
player1Elo = args[2]
|
|
player2Elo = args[3]
|
|
commander1EloTournament = args[4]
|
|
commander2EloTournament = args[5]
|
|
commander1Elo = args[6]
|
|
commander2Elo = args[7]
|
|
} else {
|
|
player1EloTournament = e.Record.GetFloat("elo_tournament_player_1")
|
|
player2EloTournament = e.Record.GetFloat("elo_tournament_player_2")
|
|
player1Elo = e.Record.GetFloat("elo_player_1")
|
|
player2Elo = e.Record.GetFloat("elo_player_2")
|
|
commander1EloTournament = e.Record.GetFloat("elo_tournament_commandant_1")
|
|
commander2EloTournament = e.Record.GetFloat("elo_tournament_commandant_2")
|
|
commander1Elo = e.Record.GetFloat("elo_commandant_1")
|
|
commander2Elo = e.Record.GetFloat("elo_commandant_2")
|
|
}
|
|
|
|
recordPlayer1, _ := e.App.FindRecordById("elo_player", e.Record.GetString("player_1"))
|
|
recordPlayer2, _ := e.App.FindRecordById("elo_player", e.Record.GetString("player_2"))
|
|
|
|
if e.Record.GetBool("official") {
|
|
recordPlayer1.Set("elo_tournament", player1EloTournament)
|
|
recordPlayer2.Set("elo_tournament", player2EloTournament)
|
|
}
|
|
|
|
// In any case set the elo_total
|
|
recordPlayer1.Set("elo", player1Elo)
|
|
recordPlayer2.Set("elo", player2Elo)
|
|
|
|
recordCommander1, _ := e.App.FindRecordById("carte", e.Record.GetString("commandant_1"))
|
|
recordCommander2, _ := e.App.FindRecordById("carte", e.Record.GetString("commandant_2"))
|
|
if e.Record.GetBool("official") {
|
|
recordCommander1.Set("elo_tournament", commander1EloTournament)
|
|
recordCommander2.Set("elo_tournament", commander2EloTournament)
|
|
}
|
|
|
|
recordCommander1.Set("elo", commander1Elo)
|
|
recordCommander2.Set("elo", commander2Elo)
|
|
|
|
e.App.Save(recordPlayer1)
|
|
e.App.Save(recordPlayer2)
|
|
e.App.Save(recordCommander1)
|
|
e.App.Save(recordCommander2)
|
|
|
|
}
|
|
|
|
func UpdateRecordELO(e *core.RecordEvent, default_ELO_value float64) {
|
|
date := e.Record.GetString("date")
|
|
official := e.Record.GetBool("official")
|
|
player1Id := e.Record.GetString("player_1")
|
|
player2Id := e.Record.GetString("player_2")
|
|
commander1Id := e.Record.GetString("commandant_1")
|
|
commander2Id := e.Record.GetString("commandant_2")
|
|
score1 := e.Record.GetInt("score_1")
|
|
score2 := e.Record.GetInt("score_2")
|
|
|
|
// Base data in case it's the first match
|
|
player1_ELO := default_ELO_value
|
|
player1_ELOTournament := default_ELO_value
|
|
player2_ELO := default_ELO_value
|
|
player2_ELOTournament := default_ELO_value
|
|
commander1_ELO := default_ELO_value
|
|
commander1_ELOTournament := default_ELO_value
|
|
commander2_ELO := default_ELO_value
|
|
commander2_ELOTournament := default_ELO_value
|
|
|
|
// Update ELO With last match ELO (Tournament or Casual)
|
|
updateEloPlayerWithLastMatch(e, player1Id, date, &player1_ELO, &player1_ELOTournament, official)
|
|
updateEloPlayerWithLastMatch(e, player2Id, date, &player2_ELO, &player2_ELOTournament, official)
|
|
|
|
// Update ELO With last match ELO (Tournament or Casual)
|
|
updateEloCommanderWithLastMatch(e, commander1Id, date, &commander1_ELO, &commander1_ELOTournament, official)
|
|
updateEloCommanderWithLastMatch(e, commander2Id, date, &commander2_ELO, &commander2_ELOTournament, official)
|
|
|
|
newData := CalculateELO(player1_ELO, player2_ELO, commander1_ELO, commander2_ELO, score1, score2)
|
|
e.Record.Set("last_elo_player_1", player1_ELO)
|
|
e.Record.Set("elo_player_1", newData.Player1_NewELO)
|
|
e.Record.Set("last_elo_player_2", player2_ELO)
|
|
e.Record.Set("elo_player_2", newData.Player2_NewELO)
|
|
e.Record.Set("last_elo_commandant_1", commander1_ELO)
|
|
e.Record.Set("elo_commandant_1", newData.Commander1_NewELO)
|
|
e.Record.Set("last_elo_commandant_2", commander2_ELO)
|
|
e.Record.Set("elo_commandant_2", newData.Commander2_NewELO)
|
|
|
|
if official {
|
|
newDataTournament := CalculateELO(player1_ELOTournament, player2_ELOTournament, commander1_ELOTournament, commander2_ELOTournament, score1, score2)
|
|
e.Record.Set("last_elo_tournament_player_1", player1_ELOTournament)
|
|
e.Record.Set("elo_tournament_player_1", newDataTournament.Player1_NewELO)
|
|
e.Record.Set("last_elo_tournament_player_2", player2_ELOTournament)
|
|
e.Record.Set("elo_tournament_player_2", newDataTournament.Player2_NewELO)
|
|
e.Record.Set("last_elo_tournament_commandant_1", commander1_ELOTournament)
|
|
e.Record.Set("elo_tournament_commandant_1", newDataTournament.Commander1_NewELO)
|
|
e.Record.Set("last_elo_tournament_commandant_2", commander2_ELOTournament)
|
|
e.Record.Set("elo_tournament_commandant_2", newDataTournament.Commander2_NewELO)
|
|
}
|
|
}
|
|
|
|
func updateEloPlayerWithLastMatch(e *core.RecordEvent, playerId string, date string, elo *float64, eloTournament *float64, official bool) {
|
|
lastMatch := Elo{}
|
|
lastMatchTournament := Elo{}
|
|
|
|
var err error
|
|
|
|
if official {
|
|
err = e.App.RecordQuery("elo").
|
|
Where(dbx.NewExp("date < {:date}", dbx.Params{"date": date})).
|
|
AndWhere(dbx.NewExp("official = {:official}", dbx.Params{"official": official})).
|
|
AndWhere(dbx.Or(dbx.NewExp("player_1 = {:id}", dbx.Params{"id": playerId}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": playerId}))).
|
|
OrderBy("date DESC").
|
|
One(&lastMatchTournament)
|
|
if err == nil {
|
|
// If a match exists before this one update ELO
|
|
if lastMatchTournament.Player1 == playerId {
|
|
*eloTournament = lastMatchTournament.EloTournamentPlayer1
|
|
} else {
|
|
*eloTournament = lastMatchTournament.EloTournamentPlayer2
|
|
}
|
|
}
|
|
}
|
|
|
|
err = e.App.RecordQuery("elo").
|
|
Where(dbx.NewExp("date < {:date}", dbx.Params{"date": date})).
|
|
AndWhere(dbx.Or(dbx.NewExp("player_1 = {:id}", dbx.Params{"id": playerId}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": playerId}))).
|
|
OrderBy("date DESC").
|
|
One(&lastMatch)
|
|
if err == nil {
|
|
// If a match exists before this one update ELO
|
|
if lastMatch.Player1 == playerId {
|
|
*elo = lastMatch.EloPlayer1
|
|
} else {
|
|
*elo = lastMatch.EloPlayer2
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateEloCommanderWithLastMatch(e *core.RecordEvent, commanderId string, date string, elo *float64, eloTournament *float64, official bool) {
|
|
lastMatch := Elo{}
|
|
lastMatchTournament := Elo{}
|
|
|
|
var err error
|
|
|
|
if official {
|
|
err = e.App.RecordQuery("elo").
|
|
Where(dbx.NewExp("date < {:date}", dbx.Params{"date": date})).
|
|
AndWhere(dbx.NewExp("official = {:official}", dbx.Params{"official": official})).
|
|
AndWhere(dbx.Or(dbx.NewExp("commandant_1 = {:id}", dbx.Params{"id": commanderId}), dbx.NewExp("commandant_2 = {:id}", dbx.Params{"id": commanderId}))).
|
|
OrderBy("date DESC").
|
|
One(&lastMatchTournament)
|
|
|
|
if err == nil {
|
|
// If a match exists before this one update ELO
|
|
if lastMatchTournament.Commander1 == commanderId {
|
|
*eloTournament = lastMatchTournament.EloTournamentCommander1
|
|
} else {
|
|
*eloTournament = lastMatchTournament.EloTournamentCommander2
|
|
}
|
|
}
|
|
}
|
|
|
|
err = e.App.RecordQuery("elo").
|
|
Where(dbx.NewExp("date < {:date}", dbx.Params{"date": date})).
|
|
AndWhere(dbx.Or(dbx.NewExp("commandant_1 = {:id}", dbx.Params{"id": commanderId}), dbx.NewExp("commandant_2 = {:id}", dbx.Params{"id": commanderId}))).
|
|
OrderBy("date DESC").
|
|
One(&lastMatch)
|
|
|
|
if err == nil {
|
|
// If a match exists before this one update ELO
|
|
if lastMatch.Commander1 == commanderId {
|
|
*elo = lastMatch.EloCommander1
|
|
} else {
|
|
*elo = lastMatch.EloCommander2
|
|
}
|
|
}
|
|
}
|
|
|
|
func CalculateELO(player1_ELO float64, player2_ELO float64, commander1_ELO float64, commander2_ELO float64, score1 int, score2 int) DuelResult {
|
|
K_PLAYER := 20.0
|
|
K_COMMANDER := 10.0
|
|
ALPHA := 0.7
|
|
|
|
won_player1 := 0.0
|
|
won_player2 := 0.0
|
|
|
|
if score1 < score2 {
|
|
won_player2 = 1.0
|
|
} else if score1 > score2 {
|
|
won_player1 = 1.0
|
|
} else if score1 == score2 {
|
|
won_player1 = 0.5
|
|
won_player2 = 0.5
|
|
}
|
|
|
|
mixed_ELO1 := ALPHA * commander1_ELO + (1 - ALPHA) * player1_ELO
|
|
mixed_ELO2 := ALPHA * commander2_ELO + (1 - ALPHA) * player2_ELO
|
|
|
|
eloModifier1 := won_player1 - 1.0 / (1 + math.Pow(10, (mixed_ELO2 - mixed_ELO1) / 400.0))
|
|
eloModifier2 := won_player2 - 1.0 / (1 + math.Pow(10, (mixed_ELO1 - mixed_ELO2) / 400.0))
|
|
|
|
return DuelResult{
|
|
Player1_NewELO: player1_ELO + K_PLAYER * eloModifier1,
|
|
Player2_NewELO: player2_ELO + K_PLAYER * eloModifier2,
|
|
Commander1_NewELO: commander1_ELO + K_COMMANDER * eloModifier1,
|
|
Commander2_NewELO: commander2_ELO + K_COMMANDER * eloModifier2,
|
|
}
|
|
}
|