-
Notifications
You must be signed in to change notification settings - Fork 151
/
rater.rb
121 lines (97 loc) · 3.38 KB
/
rater.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
module Rater
class EloRater
DefaultValue = 1000
def default_attributes
{ value: DefaultValue }
end
def description
"Elo (1v1 only)"
end
def validate_game game
if game.min_number_of_teams != 2 ||
game.max_number_of_teams != 2 ||
game.min_number_of_players_per_team != 1 ||
game.max_number_of_players_per_team != 1
game.errors.add(:rating_type, "Elo can only be used with 1v1 games")
end
end
def update_ratings game, teams
winning_teams = teams.select{|team| team.rank == Team::FIRST_PLACE_RANK}
if winning_teams.size > 1
first_rating, second_rating = winning_teams
.map(&:players)
.map(&:first)
.map{ |player| player.ratings.find_or_create(game) }
first_elo = to_elo(first_rating)
second_elo = to_elo(second_rating)
first_elo.plays_draw(second_elo)
else
winner = winning_teams.first.players.first
loser = teams.detect{|team| team.rank != Team::FIRST_PLACE_RANK}.players.first
first_rating = winner.ratings.find_or_create(game)
second_rating = loser.ratings.find_or_create(game)
first_elo = to_elo(first_rating)
second_elo = to_elo(second_rating)
first_elo.wins_from(second_elo)
end
_update_rating_from_elo(first_rating, first_elo)
_update_rating_from_elo(second_rating, second_elo)
end
def to_elo rating
Elo::Player.new(
rating: rating.value,
games_played: rating.player.results.where(game_id: rating.game.id).count,
pro: rating.pro?
)
end
def _update_rating_from_elo(rating, elo)
Rating.transaction do
rating.update_attributes!(value: elo.rating, pro: elo.pro?)
rating.history_events.create!(value: elo.rating)
end
end
end
class TrueSkillRater
DefaultValue = 0
DefaultMean = 25
DefaultDeviation = 25.0/3.0
def default_attributes
{ value: DefaultValue, trueskill_mean: DefaultMean, trueskill_deviation: DefaultDeviation }
end
def description
"Trueskill"
end
def validate_game game
end
def update_ratings game, teams
ratings_to_ranks = teams.sort_by(&:rank).each_with_object({}){ |team, hash| hash[team.players.map{|player| player.ratings.find_or_create(game)}] = team.rank }
ratings_to_trueskill = {}
trueskills_to_rank = ratings_to_ranks.each_with_object({}) do |(ratings, rank), hash|
trueskills = ratings.map do |rating|
ratings_to_trueskill[rating] = to_trueskill(rating)
end
hash[trueskills] = rank
end
graph = Saulabs::TrueSkill::FactorGraph.new trueskills_to_rank
graph.update_skills
ratings_to_trueskill.each do |rating, trueskill|
_update_rating_from_trueskill rating, trueskill
end
end
def to_trueskill rating
Saulabs::TrueSkill::Rating.new(
rating.trueskill_mean,
rating.trueskill_deviation
)
end
def _update_rating_from_trueskill rating, trueskill
Rating.transaction do
attributes = { value: (trueskill.mean - (3.0 * trueskill.deviation)) * 100,
trueskill_mean: trueskill.mean,
trueskill_deviation: trueskill.deviation }
rating.update_attributes! attributes
rating.history_events.create! attributes
end
end
end
end