brawlset/backend/hooks.go

297 lines
13 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, e.Record.GetFloat("elo_player_1"), e.Record.GetFloat("elo_player_2"), e.Record.GetFloat("elo_commandant_1"), e.Record.GetFloat("elo_commandant_2"))
updateNextMatchs(e, DuelResult{
Player1_NewELO: e.Record.GetFloat("elo_player_1"),
Player2_NewELO: e.Record.GetFloat("elo_player_2"),
Commander1_NewELO: e.Record.GetFloat("elo_commandant_1"),
Commander2_NewELO: e.Record.GetFloat("elo_commandant_2"),
})
}
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, e.Record.GetFloat("elo_player_1"), e.Record.GetFloat("elo_player_2"), e.Record.GetFloat("elo_commandant_1"), e.Record.GetFloat("elo_commandant_2"))
updateNextMatchs(e, DuelResult{
Player1_NewELO: e.Record.GetFloat("elo_player_1"),
Player2_NewELO: e.Record.GetFloat("elo_player_2"),
Commander1_NewELO: e.Record.GetFloat("elo_commandant_1"),
Commander2_NewELO: e.Record.GetFloat("elo_commandant_2"),
})
return e.Next()
})
}
func updateNextMatchs(e *core.RecordEvent, newData DuelResult) {
official := e.Record.GetBool("official")
needToRecalculateElo := false
nextMatchsCount, _ := e.App.CountRecords("elo", dbx.And(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("player_1 = {:id}", dbx.Params{"id": e.Record.GetString("player_1")}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": e.Record.GetString("player_1")}))))
if nextMatchsCount > 0 {
needToRecalculateElo = true
}
nextMatchsCount, _ = e.App.CountRecords("elo", dbx.And(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("player_1 = {:id}", dbx.Params{"id": e.Record.GetString("player_2")}), dbx.NewExp("player_2 = {:id}", dbx.Params{"id": e.Record.GetString("player_2")}))))
if nextMatchsCount > 0 {
needToRecalculateElo = true
}
nextMatchsCount, _ = e.App.CountRecords("elo", dbx.And(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")}))))
if nextMatchsCount > 0 {
needToRecalculateElo = true
}
nextMatchsCount, _ = e.App.CountRecords("elo", dbx.And(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")}))))
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")] = newData.Player1_NewELO
newPlayerData[e.Record.GetString("player_2")] = newData.Player2_NewELO
newCommanderData := map[string]float64{}
newCommanderData[e.Record.GetString("commandant_1")] = newData.Commander1_NewELO
newCommanderData[e.Record.GetString("commandant_2")] = newData.Commander2_NewELO
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")})).
AndWhere(dbx.NewExp("official = {:official}",dbx.Params{"official": official})).
OrderBy("date ASC").
All(&eloMatchs)
if err != nil {
log.Println(err)
}
for _, v := range eloMatchs {
// Check if Match is concerned by the ELO recalculate
if slices.Contains(playerIdChanged, v.Player1) || slices.Contains(playerIdChanged, v.Player2) {
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])
}
// 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])
}
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)
newPlayerData[v.Player1] = tempEloData.Player1_NewELO
newPlayerData[v.Player2] = tempEloData.Player2_NewELO
e.App.Save(record)
}
if 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(commanderIdChanged, v.Commander1) {
commanderIdChanged = append(commanderIdChanged, v.Commander1)
} else {
record.Set("last_elo_commandant_1", newCommanderData[v.Commander1])
}
// If Player2 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])
}
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_commandant_1", tempEloData.Commander1_NewELO)
record.Set("elo_commandant_2", tempEloData.Commander2_NewELO)
newCommanderData[v.Commander1] = tempEloData.Commander1_NewELO
newCommanderData[v.Commander2] = tempEloData.Commander2_NewELO
e.App.Save(record)
}
}
UpdateLastELO(e, newPlayerData[e.Record.GetString("player_1")], newPlayerData[e.Record.GetString("player_2")], newCommanderData[e.Record.GetString("commandant_1")], newCommanderData[e.Record.GetString("commandant_2")])
}
}
func UpdateLastELO(e *core.RecordEvent, player1_ELO float64, player2_ELO float64, commander1_ELO float64, commander2_ELO float64) {
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", player1_ELO)
recordPlayer2.Set("elo_tournament", player2_ELO)
} else {
recordPlayer1.Set("elo_casual", player1_ELO)
recordPlayer2.Set("elo_casual", player2_ELO)
}
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", commander1_ELO)
recordCommander2.Set("elo_tournament", commander2_ELO)
} else {
recordCommander1.Set("elo_casual", commander1_ELO)
recordCommander2.Set("elo_casual", commander2_ELO)
}
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
player2_ELO := default_ELO_value
commander1_ELO := default_ELO_value
commander2_ELO := default_ELO_value
// Update ELO With last match ELO (Tournament or Casual)
updateEloPlayerWithLastMatch(e, player1Id, date, &player1_ELO, official)
updateEloPlayerWithLastMatch(e, player2Id, date, &player2_ELO, official)
// Update ELO With last match ELO (Tournament or Casual)
updateEloCommanderWithLastMatch(e, commander1Id, date, &commander1_ELO, official)
updateEloCommanderWithLastMatch(e, commander2Id, date, &commander2_ELO, 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)
}
func updateEloPlayerWithLastMatch(e *core.RecordEvent, playerId string, date string, elo *float64, official bool) {
lastMatch := Elo{}
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(&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, official bool) {
lastMatch := Elo{}
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(&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 := 30.0
K_COMMANDER := 20.0
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
}
eloModifier_player1 := won_player1 - 1.0 / (1 + math.Pow(10, (player2_ELO - player1_ELO) / 400.0))
eloModifier_player2 := won_player2 - 1.0 / (1 + math.Pow(10, (player1_ELO - player2_ELO) / 400.0))
eloModifier_commander1 := won_player1 - 1.0 / (1 + math.Pow(10, (commander2_ELO - commander1_ELO) / 400.0))
eloModifier_commander2 := won_player2 - 1.0 / (1 + math.Pow(10, (commander1_ELO - commander2_ELO) / 400.0))
return DuelResult{
Player1_NewELO: player1_ELO + K_PLAYER * eloModifier_player1,
Player2_NewELO: player2_ELO + K_PLAYER * eloModifier_player2,
Commander1_NewELO: commander1_ELO + K_COMMANDER * eloModifier_commander1,
Commander2_NewELO: commander2_ELO + K_COMMANDER * eloModifier_commander2,
}
}