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() { // Set default values after creation app.pb.OnRecordAfterCreateSuccess("elo_player").BindFunc(func (e *core.RecordEvent) error { e.Record.Set("elo_casual", 100) e.Record.Set("elo_tournament", 100) e.App.Save(e.Record) log.Println("record created") return e.Next() }) // When a ELO entry has been added app.pb.OnRecordCreate("elo").BindFunc(func (e *core.RecordEvent) error { 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 := 100.0 player2_ELO := 100.0 commander1_ELO := 100.0 commander2_ELO := 100.0 // 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) recordPlayer1, _ := e.App.FindRecordById("elo_player", player1Id) recordPlayer2, _ := e.App.FindRecordById("elo_player", player2Id) if official { recordPlayer1.Set("elo_tournament", newData.Player1_NewELO) recordPlayer2.Set("elo_tournament", newData.Player2_NewELO) } else { recordPlayer1.Set("elo_casual", newData.Player1_NewELO) recordPlayer2.Set("elo_casual", newData.Player2_NewELO) } recordCommander1, _ := e.App.FindRecordById("carte", commander1Id) recordCommander2, _ := e.App.FindRecordById("carte", commander2Id) if official { recordCommander1.Set("elo_tournament", newData.Commander1_NewELO) recordCommander2.Set("elo_tournament", newData.Commander2_NewELO) } else { recordCommander1.Set("elo_casual", newData.Commander1_NewELO) recordCommander2.Set("elo_casual", newData.Commander2_NewELO) } e.App.Save(recordPlayer1) e.App.Save(recordPlayer2) e.App.Save(recordCommander1) e.App.Save(recordCommander2) updateNextMatchs(e, newData, official) return e.Next() }) } func updateNextMatchs(e *core.RecordEvent, newData DuelResult, official bool) { 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) } } recordPlayer1, _ := e.App.FindRecordById("elo_player", e.Record.GetString("player_1")) recordPlayer2, _ := e.App.FindRecordById("elo_player", e.Record.GetString("player_2")) if official { recordPlayer1.Set("elo_tournament", newPlayerData[e.Record.GetString("player_1")]) recordPlayer2.Set("elo_tournament", newPlayerData[e.Record.GetString("player_2")]) } else { recordPlayer1.Set("elo_casual", newPlayerData[e.Record.GetString("player_1")]) recordPlayer2.Set("elo_casual", newPlayerData[e.Record.GetString("player_2")]) } recordCommander1, _ := e.App.FindRecordById("carte", e.Record.GetString("commandant_1")) recordCommander2, _ := e.App.FindRecordById("carte", e.Record.GetString("commandant_2")) if official { recordCommander1.Set("elo_tournament", newCommanderData[e.Record.GetString("commandant_1")]) recordCommander2.Set("elo_tournament", newCommanderData[e.Record.GetString("commandant_2")]) } else { recordCommander1.Set("elo_casual", newCommanderData[e.Record.GetString("commandant_1")]) recordCommander2.Set("elo_casual", newCommanderData[e.Record.GetString("commandant_2")]) } e.App.Save(recordPlayer1) e.App.Save(recordPlayer2) e.App.Save(recordCommander1) e.App.Save(recordCommander2) } } 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, } }