Created
February 24, 2022 08:31
-
-
Save BCoulange/97211e82c69588d115dd634f7f2e0ff5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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