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, } }