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