Skip to content

Instantly share code, notes, and snippets.

@BCoulange
Created February 24, 2022 08:31
Show Gist options
  • Save BCoulange/97211e82c69588d115dd634f7f2e0ff5 to your computer and use it in GitHub Desktop.
Save BCoulange/97211e82c69588d115dd634f7f2e0ff5 to your computer and use it in GitHub Desktop.
require "json"
require "date"
require 'active_support'
require 'active_support/core_ext'
EPSILON = 0.016
FOX_PARAMS = [
{
rank_sup: -1800,
number_of_games: 10,
one_rank_up: 6,
two_ranks_up: 8,
one_rank_down: 1000,
two_ranks_down: 1000
},
{
rank_sup: -1700,
number_of_games: 10,
one_rank_up: 6,
two_ranks_up: 8,
one_rank_down: 7,
two_ranks_down: 1000
},
{
rank_sup: -1600,
number_of_games: 10,
one_rank_up: 6,
two_ranks_up: 8,
one_rank_down: 7,
two_ranks_down: 9
},
{
rank_sup: -1300,
number_of_games: 12,
one_rank_up: 7,
two_ranks_up: 10,
one_rank_down: 8,
two_ranks_down: 10
},
{
rank_sup: -1000,
number_of_games: 14,
one_rank_up: 8,
two_ranks_up: 12,
one_rank_down: 10,
two_ranks_down: 12
},
{
rank_sup: -600,
number_of_games: 16,
one_rank_up: 10,
two_ranks_up: 14,
one_rank_down: 11,
two_ranks_down: 14
},
{
rank_sup: -300,
number_of_games: 18,
one_rank_up: 11,
two_ranks_up: 15,
one_rank_down: 12,
two_ranks_down: 16
},
{
rank_sup: 200,
number_of_games: 19,
one_rank_up: 12,
two_ranks_up: 16,
one_rank_down: 13,
two_ranks_down: 17
},
{
rank_sup: 400,
number_of_games: 20,
one_rank_up: 14,
two_ranks_up: 18,
one_rank_down: 13,
two_ranks_down: 17
},
{
rank_sup: 700,
number_of_games: 20,
one_rank_up: 1000,
two_ranks_up: 1000,
one_rank_down: 13,
two_ranks_down: 17
}
]
RANK_PARAMS = [
{
rank_sup: -1950,
con: 116,
a: 200
},
{
rank_sup: -1850,
con: 110,
a: 110
},
{
rank_sup: -1750,
con: 105,
a: 190
},
{
rank_sup: -1650,
con: 100,
a: 185
},
{
rank_sup: -1550,
con: 95,
a: 180
},
{
rank_sup: -1450,
con: 90,
a: 175
},
{
rank_sup: -1350,
con: 85,
a: 170
},
{
rank_sup: -1250,
con: 80,
a: 165
},
{
rank_sup: -1150,
con: 75,
a: 160
},
{
rank_sup: -1050,
con: 70,
a: 155
},
{
rank_sup: -950,
con: 65,
a: 150
},
{
rank_sup: -850,
con: 60,
a: 145
},
{
rank_sup: -750,
con: 55,
a: 140
},
{
rank_sup: -650,
con: 51,
a: 135
},
{
rank_sup: -550,
con: 47,
a: 130
},
{
rank_sup: -450,
con: 43,
a: 125
},
{
rank_sup: -350,
con: 39,
a: 120
},
{
rank_sup: -250,
con: 35,
a: 115
},
{
rank_sup: -150,
con: 31,
a: 110
},
{
rank_sup: 50,
con: 27,
a: 105
},
{
rank_sup: 50,
con: 24,
a: 100
},
{
rank_sup: 150,
con: 21,
a: 95
},
{
rank_sup: 250,
con: 18,
a: 90
},
{
rank_sup: 350,
con: 15,
a: 85
},
{
rank_sup: 450,
con: 13,
a: 80
},
{
rank_sup: 550,
con: 11,
a: 75
},
{
rank_sup: 650,
con: 10,
a: 70
}
]
def get_params(rank)
# get the params
sel = RANK_PARAMS.select{|el| rank < el[:rank_sup]}
if sel.size == 0
return RANK_PARAMS[-1]
else
return sel[0]
end
end
def get_fox_params(rank)
# get the params
sel = FOX_PARAMS.select{|el| rank < el[:rank_sup]}
if sel.size == 0
return FOX_PARAMS[-1]
else
return sel[0]
end
end
def proba_win(rank_a,rank_b)
output = {}
lower_rank,upper_rank = rank_a,rank_b
if rank_a > rank_b
lower_rank,upper_rank = rank_b,rank_a
end
# rank diff
d = (upper_rank - lower_rank).abs
# get the params
lower_params = get_params lower_rank
upper_params = get_params upper_rank
# lower proba
lower_proba = (1 / (1 + Math::E**(d.to_f/lower_params[:a].to_f)) - EPSILON/2)
lower_proba = 0.0 if lower_proba < 0
upper_proba = 1 - EPSILON - lower_proba
if rank_a > rank_b
output[rank_a] = upper_proba
output[rank_b] = lower_proba
else
output[rank_b] = upper_proba
output[rank_a] = lower_proba
end
return output
end
def rank_evolution(a_rank, b_rank, a_win)
output = {}
probas = proba_win(a_rank,b_rank)
a_params = get_params a_rank
b_params = get_params b_rank
output[:a] = a_rank + a_params[:con] * ((a_win ? 1 : 0) - probas[a_rank])
output[:b] = b_rank + b_params[:con] * ((a_win ? 0 : 1) - probas[b_rank])
return output
end
def simu_rank_variability(rank,s,a_var)
return rank + s + Random.rand(a_var*2) - a_var
end
def simu_tournoi(initial_rank,initial_real_rank, number_of_turnament,this_opts = {})
opts = {
number_of_players: 52,
min_rank: -2000,
max_rank: 500,
nb_players_rounds: 4,
s: 0,
a_var: 50,
duration_days: 365,
real_rank_evo_per_day: 0
}.merge(this_opts)
games_per_day = (opts[:nb_players_rounds].to_f - 1) * number_of_turnament / opts[:duration_days].to_f
d = Date.today
initial_date = d
output = [{rank: initial_rank, game_index: 0, date: d, real_rank: initial_real_rank}]
game_index = 1
d = d + (1/games_per_day).day
# rank
rank = initial_rank
real_rank = initial_real_rank
puts "initial_rank: #{initial_rank}"
number_of_turnament.times do |i|
# get the players
player_ranks = (opts[:number_of_players]-1).times.map{ opts[:min_rank] + Random.rand(opts[:max_rank] - opts[:min_rank])} + [rank]
# sort them
player_ranks.sort!
my_round = player_ranks.each_slice(opts[:nb_players_rounds]).to_a.select{|el| el.include?(rank)}.first
# play all the matchs
my_round.delete_at my_round.delete(rank)
my_round.each do |opponent|
# ajust ranks
this_play_rank = simu_rank_variability(real_rank, opts[:s], opts[:a_var])
this_play_opponent = simu_rank_variability(opponent, opts[:s], opts[:a_var])
pro = proba_win this_play_rank, this_play_opponent
i_am_the_winner = Random.rand <= pro[this_play_rank]
# rank evo
rank_evo = rank_evolution(rank, opponent, i_am_the_winner)
rank = rank_evo[:a]
puts "vs #{opponent}... #{i_am_the_winner ? "A win!" : "A loss"}! -> #{rank}"
output << {rank: opponent, game_index: game_index,date: d, real_rank: real_rank}
game_index += 1
d = d + (1/games_per_day).day
real_rank = initial_real_rank + opts[:real_rank_evo_per_day] * (d - initial_date).to_f
end
end
return output
end
def simu_fox(initial_rank,initial_real_rank, number_of_games, this_opts = {})
opts = {
var_match_making: 100,
s: -100,
s_opp_var: 50,
s_opp_center: -50,
a_var: 200,
real_rank_evo_per_day: 0,
duration_days: 365
}.merge(this_opts)
games_per_day = number_of_games.to_f / opts[:duration_days].to_f
d = Date.today
initial_date = d
output = [{rank: initial_rank, game_index: 0, date: d, real_rank: initial_real_rank}]
d = d + (1/games_per_day).day
rank = initial_rank
real_rank = initial_real_rank
puts "initial_rank: #{initial_rank}"
result_buffer = []
number_of_games.times do |i|
#
current_fox_rules = get_fox_params(rank)
# find an opponent
opponent = rank + (2*Random.rand(opts[:a_var]) - opts[:a_var])
# ajust ranks
this_play_rank = simu_rank_variability(real_rank, opts[:s], opts[:a_var])
this_play_opponent = simu_rank_variability(opponent, (opts[:s_opp_center] + 2*Random.rand(opts[:s_opp_var]) - opts[:s_opp_var]), opts[:a_var])
pro = proba_win this_play_rank, this_play_opponent
i_am_the_winner = Random.rand <= pro[this_play_rank]
if i_am_the_winner
result_buffer << 1
else
result_buffer << 0
end
if result_buffer.size > current_fox_rules[:number_of_games]
result_buffer.shift
end
# check rank change
if result_buffer.select{|el| el == 1}.size >= current_fox_rules[:one_rank_up]
rank = rank + 100
result_buffer = []
elsif result_buffer.select{|el| el == 0}.size >= current_fox_rules[:one_rank_down]
rank = rank - 100
result_buffer = []
end
output << {rank: rank, game_index: i+1, date: d, real_rank: real_rank}
d = d + (1/games_per_day).day
real_rank = initial_real_rank + opts[:real_rank_evo_per_day] * (d - initial_date).to_f
end
return output
end
def simu_ogs(initial_rank,initial_real_rank, number_of_games, this_opts = {})
opts = {
var_match_making: 100,
s: -100,
s_opp_var: 50,
s_opp_center: -50,
a_var: 200,
real_rank_evo_per_day: 0,
duration_days: 365
}.merge(this_opts)
games_per_day = number_of_games.to_f / opts[:duration_days].to_f
d = Date.today
initial_date = d
output = [{rank: initial_rank, game_index: 0, date: d, real_rank: initial_real_rank}]
d = d + (1/games_per_day).day
rank = initial_rank
real_rank = initial_real_rank
puts "initial_rank: #{initial_rank}"
number_of_games.times do |i|
# find an opponent
opponent = rank + (2*Random.rand(opts[:a_var]) - opts[:a_var])
# ajust ranks
this_play_rank = simu_rank_variability(real_rank, opts[:s], opts[:a_var])
this_play_opponent = simu_rank_variability(opponent, (opts[:s_opp_center] + 2*Random.rand(opts[:s_opp_var]) - opts[:s_opp_var]), opts[:a_var])
pro = proba_win this_play_rank, this_play_opponent
i_am_the_winner = Random.rand <= pro[this_play_rank]
# rank evo
rank_evo = rank_evolution(rank, opponent, i_am_the_winner)
rank = rank_evo[:a]
puts "vs #{opponent}... #{i_am_the_winner ? "A win!" : "A loss"}! -> #{rank}"
output << {rank: rank, game_index: i+1, date: d, real_rank: real_rank}
d = d + (1/games_per_day).day
real_rank = initial_real_rank + opts[:real_rank_evo_per_day] * (d - initial_date).to_f
end
return output
end
# 68 matchs sur OGS entre 01/01 et 23/02 -> 53 jours -> 1.28 matchs par jour
# 8 matchs FFG (2 vagues) -> 53 jours -> 0.08 matchs par jour
# 138 matchs sur FOX entre 19/01 et 23/02 -> 35 jours -> 3.9 matchs par jour
#
# 1 an : 12 vagues -> 48 matches
# 1 an FOX + OGS -> 1890 matches
initial_rank = -1500
real_rank = -900
real_rank_evo_per_day = 1
ratio_ogs_tournoi = 2
puts "----- Tournoi -----"
stats_tournoi = simu_tournoi initial_rank, real_rank, 12, {
real_rank_evo_per_day: real_rank_evo_per_day,
number_of_players: 40
}
stats = []
stats_tournoi.each do |el|
stats << {
rank: el[:rank],
date: el[:date],
type: "tournoi"
}
stats << {
rank: el[:real_rank],
date: el[:date],
type: "real"
}
end
puts "----- OGS ----- "
stats_ogs = simu_ogs initial_rank, real_rank, 12*3*ratio_ogs_tournoi, {
real_rank_evo_per_day: real_rank_evo_per_day
}
stats_ogs.each do |el|
stats << {
rank: el[:rank],
date: el[:date],
type: "ogs"
}
stats << {
rank: el[:real_rank],
date: el[:date],
type: "real"
}
end
puts "----- FOX ----- "
stats_ogs = simu_fox initial_rank, real_rank, 12*3*ratio_ogs_tournoi, {
real_rank_evo_per_day: real_rank_evo_per_day
}
stats_ogs.each do |el|
stats << {
rank: el[:rank],
date: el[:date],
type: "fox"
}
stats << {
rank: el[:real_rank],
date: el[:date],
type: "real"
}
end
File.open("/Users/bco-mba/Downloads/stat_go.json", "w+") do |f|
f.write stats.to_json
end
[-1000,-900,-700,-100].each do |a|
[-1000,-900,-700,-100].each do |b|
puts "#{a} vs #{b} : #{proba_win(a,b)[a]*100}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment