2014-03-26 11:09:39 +00:00
|
|
|
|
# == Schema Information
|
|
|
|
|
#
|
|
|
|
|
# Table name: contests
|
|
|
|
|
#
|
|
|
|
|
# id :integer not null, primary key
|
2020-03-31 17:27:34 +00:00
|
|
|
|
# contest_type :integer default(0), not null
|
2020-03-18 03:38:17 +00:00
|
|
|
|
# default_time :time
|
|
|
|
|
# end :datetime
|
|
|
|
|
# modulus_3to1 :float(24)
|
|
|
|
|
# modulus_4to0 :float(24)
|
|
|
|
|
# modulus_base :integer
|
|
|
|
|
# modulus_even :float(24)
|
2014-03-26 11:09:39 +00:00
|
|
|
|
# name :string(255)
|
2020-03-18 03:38:17 +00:00
|
|
|
|
# short_name :string(255)
|
2014-03-26 11:09:39 +00:00
|
|
|
|
# start :datetime
|
|
|
|
|
# status :integer
|
2020-03-18 03:38:17 +00:00
|
|
|
|
# weight :integer
|
2014-03-26 11:09:39 +00:00
|
|
|
|
# created_at :datetime
|
|
|
|
|
# updated_at :datetime
|
|
|
|
|
# demos_id :integer
|
|
|
|
|
# rules_id :integer
|
2020-03-18 03:38:17 +00:00
|
|
|
|
# winner_id :integer
|
|
|
|
|
#
|
|
|
|
|
# Indexes
|
|
|
|
|
#
|
|
|
|
|
# index_contests_on_demos_id (demos_id)
|
|
|
|
|
# index_contests_on_rules_id (rules_id)
|
|
|
|
|
# index_contests_on_status (status)
|
|
|
|
|
# index_contests_on_winner_id (winner_id)
|
2014-03-26 11:09:39 +00:00
|
|
|
|
#
|
|
|
|
|
|
2014-03-23 00:22:25 +00:00
|
|
|
|
class Contest < ActiveRecord::Base
|
|
|
|
|
include Extra
|
|
|
|
|
|
|
|
|
|
WEIGHT = 30.0
|
|
|
|
|
STATUS_OPEN = 0
|
|
|
|
|
STATUS_PROGRESS = 1
|
|
|
|
|
STATUS_CLOSED = 3
|
|
|
|
|
TYPE_LADDER = 0
|
|
|
|
|
TYPE_LEAGUE = 1
|
|
|
|
|
TYPE_BRACKET = 2
|
|
|
|
|
|
2019-06-03 19:38:24 +00:00
|
|
|
|
scope :active, -> { where.not(status: STATUS_CLOSED) }
|
|
|
|
|
scope :inactive, -> { where(status: STATUS_CLOSED) }
|
2020-03-28 20:39:41 +00:00
|
|
|
|
scope :joinable, -> { where(table[:status].eq(STATUS_OPEN).and(table[:end].gt(Time.now.utc))) }
|
2019-06-03 19:38:24 +00:00
|
|
|
|
scope :with_contesters, -> {Â includes(:contesters) }
|
|
|
|
|
scope :ordered, -> { order("start DESC") }
|
|
|
|
|
scope :nsls1, -> { where("name LIKE ?", "NSL S1:%") }
|
|
|
|
|
scope :nsls2, -> { where("name LIKE ?", "NSL S2:%") }
|
|
|
|
|
scope :ns1seasons, -> { where("name LIKE ?", "S%:%") }
|
2014-03-23 00:22:25 +00:00
|
|
|
|
|
|
|
|
|
has_many :matches, :dependent => :destroy
|
|
|
|
|
has_many :weeks, :dependent => :destroy
|
2019-06-03 19:38:24 +00:00
|
|
|
|
has_many :contesters, -> { includes(:team) }, :dependent => :destroy
|
2014-03-23 00:22:25 +00:00
|
|
|
|
has_many :predictions, :through => :matches
|
|
|
|
|
has_many :brackets
|
2019-06-03 19:38:24 +00:00
|
|
|
|
has_many :preds_with_score, -> {
|
2020-03-18 04:41:13 +00:00
|
|
|
|
select("predictions.id, predictions.user_id,
|
2019-06-03 19:38:24 +00:00
|
|
|
|
SUM(result) AS correct,
|
|
|
|
|
SUM(result)/COUNT(*)*100 AS score,
|
|
|
|
|
COUNT(*) AS total")
|
|
|
|
|
.where("result IS NOT NULL")
|
|
|
|
|
.group("predictions.user_id")
|
|
|
|
|
.order("correct DESC") },
|
|
|
|
|
:source => :predictions,
|
|
|
|
|
:through => :matches
|
2014-03-23 00:22:25 +00:00
|
|
|
|
has_and_belongs_to_many :maps
|
2020-03-26 02:26:30 +00:00
|
|
|
|
belongs_to :demos, :class_name => "Directory", :optional => true
|
|
|
|
|
belongs_to :winner, :class_name => "Contester", :optional => true
|
|
|
|
|
belongs_to :rules, :class_name => "Article", :optional => true
|
2014-03-23 00:22:25 +00:00
|
|
|
|
|
|
|
|
|
validates_presence_of :name, :start, :end, :status, :default_time
|
|
|
|
|
validates_length_of :name, :in => 1..50
|
|
|
|
|
validates_length_of :short_name, :in => 1..8, :allow_nil => true
|
|
|
|
|
validate :validate_status
|
|
|
|
|
validate :validate_contest_type
|
|
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
|
name
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def status_s
|
|
|
|
|
statuses[status]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def default_s
|
|
|
|
|
"Sunday " + default_time.to_s
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def statuses
|
|
|
|
|
{STATUS_OPEN => "In Progress (signups open)", STATUS_PROGRESS => "In Progress (signups closed)", STATUS_CLOSED => "Closed"}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def types
|
|
|
|
|
{TYPE_LADDER => "Ladder", TYPE_LEAGUE => "League", TYPE_BRACKET => "Bracket"}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def validate_status
|
|
|
|
|
errors.add :status, I18n.t(:invalid_status) unless statuses.include? status
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def validate_contest_type
|
|
|
|
|
errors.add :contest_type, I18n.t(:contests_invalidtype) unless types.include? contest_type
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def recalculate
|
2020-03-30 00:26:32 +00:00
|
|
|
|
Match.where(contest_id: self.id).update_all("diff = null, points1 = null, points2 = null")
|
|
|
|
|
Contester.where(contest_id: self.id).update_all("score = 0, win = 0, loss = 0, draw = 0, extra = 0")
|
2014-03-23 00:22:25 +00:00
|
|
|
|
matches.finished.chrono.each do |match|
|
|
|
|
|
match.recalculate
|
|
|
|
|
match.save
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-04-19 03:04:05 +00:00
|
|
|
|
def elo_score score1, score2, diff, level = self.modulus_base, weight = self.weight, moduluses = [self.modulus_even, self.modulus_3to1, self.modulus_4to0]
|
|
|
|
|
points = (score2-score1).abs
|
|
|
|
|
modulus = moduluses[0].to_f if points <= 1
|
|
|
|
|
modulus = moduluses[1].to_f if points == 2
|
|
|
|
|
modulus = moduluses[2].to_f if points >= 3
|
|
|
|
|
|
|
|
|
|
if score1 == score2
|
|
|
|
|
result = 0.5
|
|
|
|
|
elsif score1 > score2
|
|
|
|
|
result = 1.0
|
|
|
|
|
elsif score2 > score1
|
|
|
|
|
result = 0.0
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
prob = 1.0/(10**(diff.to_f/weight.to_f)+1.0)
|
|
|
|
|
total = (level.to_f*modulus*(result-prob)).round
|
|
|
|
|
return total
|
|
|
|
|
end
|
|
|
|
|
|
2015-05-16 13:54:14 +00:00
|
|
|
|
def update_ranks contester, old_rank, new_rank
|
|
|
|
|
if old_rank < new_rank
|
|
|
|
|
Contester.update_all(["score = score -1, trend = ?", Contester::TREND_UP],
|
|
|
|
|
["contest_id = ? and score > ? and score <= ?",
|
|
|
|
|
self.id, old_rank, new_rank])
|
|
|
|
|
contester.trend = Contester::TREND_DOWN
|
|
|
|
|
elsif old_rank > new_rank
|
|
|
|
|
Contester.update_all(["score = score + 1, trend = ?", Contester::TREND_DOWN],
|
|
|
|
|
["contest_id = ? and score < ? and score >= ?",
|
|
|
|
|
self.id, old_rank, new_rank])
|
|
|
|
|
contester.trend = Contester::TREND_UP
|
2014-03-23 00:22:25 +00:00
|
|
|
|
end
|
2015-05-16 13:54:14 +00:00
|
|
|
|
contester.score = new_rank
|
2014-03-23 00:22:25 +00:00
|
|
|
|
end
|
|
|
|
|
|
2015-05-16 13:54:14 +00:00
|
|
|
|
def ladder_ranks_unique?
|
|
|
|
|
c = Contester.where({:contest_id => self.id})
|
|
|
|
|
c.uniq.pluck(:score).count == c.count
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-28 20:39:41 +00:00
|
|
|
|
def can_join?(cuser)
|
|
|
|
|
cuser and !cuser&.banned?(Ban::TYPE_LEAGUE) and \
|
|
|
|
|
(cuser&.lead_teams.not_in_contest(self).exists?) and \
|
|
|
|
|
Contest.joinable.where(id: self).exists?
|
|
|
|
|
end
|
2015-05-16 13:54:14 +00:00
|
|
|
|
|
2014-03-23 00:22:25 +00:00
|
|
|
|
def can_create? cuser
|
|
|
|
|
cuser and cuser.admin?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def can_update? cuser
|
|
|
|
|
cuser and cuser.admin?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def can_destroy? cuser
|
|
|
|
|
cuser and cuser.admin?
|
|
|
|
|
end
|
2020-03-18 03:38:17 +00:00
|
|
|
|
|
|
|
|
|
def self.params params, cuser
|
|
|
|
|
params.require(:contest).permit(:name, :start, :end, :status, :default_time,
|
|
|
|
|
:contest_type, :winner_id, :demos_id, :short_name,
|
|
|
|
|
:weight, :modulus_base, :modulus_even,
|
|
|
|
|
:modulus_3to1, :modulus_4to0, :rules_id)
|
|
|
|
|
end
|
2014-03-23 00:22:25 +00:00
|
|
|
|
end
|