Init ladder prototype. Challenge stuff is still mostly broken.

This commit is contained in:
simplefl 2015-05-16 15:54:14 +02:00
parent 7cce65dfd7
commit 9a3ad378f5
11 changed files with 185 additions and 104 deletions

View file

@ -18,10 +18,13 @@ class ChallengesController < ApplicationController
end end
def new def new
#No new challenges for now.
raise AccessError
@challenge = Challenge.new @challenge = Challenge.new
@challenge.user = cuser @challenge.user = cuser
@challenge.contester2 = Contester.active.find params[:id] @challenge.contester2 = Contester.active.find params[:id]
@challenge.get_contester1 contest = @challenge.contester2.contest
@challenge.contester1 = @challenge.user.active_contesters.of_contest(contest).first
raise AccessError unless @challenge.can_create? cuser raise AccessError unless @challenge.can_create? cuser
end end
@ -62,7 +65,8 @@ class ChallengesController < ApplicationController
def destroy def destroy
raise AccessError unless @challenge.can_destroy? cuser raise AccessError unless @challenge.can_destroy? cuser
@challenge.destroy @challenge.destroy
return_to #return_to FIX ME from challenge side
render text: t(:challenges_cleared)
end end
private private

View file

@ -22,6 +22,9 @@ class ContestersController < ApplicationController
@contester = Contester.new params[:contester] @contester = Contester.new params[:contester]
@contester.user = cuser @contester.user = cuser
raise AccessError unless @contester.can_create? cuser raise AccessError unless @contester.can_create? cuser
if @contester.contest.contest_type == Contest::TYPE_LADDER
@contester.score = @contester.contest.contesters.active.count + 1
end
if @contester.save if @contester.save
flash[:notice] = t(:contests_join) flash[:notice] = t(:contests_join)
@ -34,9 +37,20 @@ class ContestersController < ApplicationController
def update def update
raise AccessError unless @contester.can_update? cuser raise AccessError unless @contester.can_update? cuser
if @contester.contest.contest_type == Contest::TYPE_LADDER
old_rank = @contester.score
new_rank = params[:contester][:score].to_i
raise Error, t(:rank_invalid) unless new_rank > 0 and
new_rank <= @contester.contest.contesters.active.count
if old_rank != new_rank
@contester.contest.update_ranks(@contester, old_rank, new_rank)
end
end
if @contester.update_attributes params[:contester] if @contester.update_attributes params[:contester]
flash[:notice] = t(:contests_contester_update) flash[:notice] = t(:contests_contester_update)
redirect_to @contester redirect_to @contester.contest
else else
render :edit render :edit
end end

View file

@ -36,7 +36,7 @@ class Challenge < ActiveRecord::Base
attr_protected :id, :updated_at, :created_at, :default_time, :user_id, :status attr_protected :id, :updated_at, :created_at, :default_time, :user_id, :status
validates_presence_of :contester1, :contester2, :server, :map1 validates_presence_of :contester1, :contester2
validates_presence_of :map2, :on => :update validates_presence_of :map2, :on => :update
#validates_datetime :match_time, :default_time #validates_datetime :match_time, :default_time
#validates_length_of [:details, :response], :maximum => 255, :allow_blank => true, :allow_nil => true #validates_length_of [:details, :response], :maximum => 255, :allow_blank => true, :allow_nil => true
@ -111,102 +111,102 @@ class Challenge < ActiveRecord::Base
def validate_teams def validate_teams
if contester1.team == contester2.team if contester1.team == contester2.team
errors.add_to_base I18n.t(:challenges_yourself) errors.add :base, I18n.t(:challenges_yourself)
end end
if contester1.contest != contester2.contest if contester1.contest != contester2.contest
errors.add_to_base I18n.t(:challenges_opponent_contest) errors.add :base, I18n.t(:challenges_opponent_contest)
end end
if !contester2.active or !contester2.team.active if !contester2.active or !contester2.team.active
errors.add_to_base I18n.t(:challenges_opponent_inactive) errors.add :base, I18n.t(:challenges_opponent_inactive)
end end
if !contester1.active or !contester1.team.active if !contester1.active or !contester1.team.active
errors.add_to_base I18n.t(:challenges_inactive) errors.add :base, I18n.t(:challenges_inactive)
end end
end end
def validate_contest def validate_contest
if contester1.contest.end.past? or contester1.contest.status == Contest::STATUS_CLOSED if contester1.contest.end.past? or contester1.contest.status == Contest::STATUS_CLOSED
errors.add_to_base I18n.t(:contests_closed) errors.add :base, I18n.t(:contests_closed)
end end
if contester1.contest.contest_type != Contest::TYPE_LADDER and !match if contester1.contest.contest_type != Contest::TYPE_LADDER and !match
errors.add_to_base I18n.t(:contests_notladder) errors.add :base, I18n.t(:contests_notladder)
end end
end end
def validate_mandatory def validate_mandatory
return unless mandatory # return unless mandatory
if contester2.score < contester1.score # if contester2.score < contester1.score
errors.add_to_base I18n.t(:challenges_mandatory) # errors.add :base, I18n.t(:challenges_mandatory)
end # end
if Challenge.pending.count(:conditions => \ # if Challenge.pending.count(:conditions => \
["contester1_id = ? AND contester2_id = ? AND mandatory = true AND default_time < UTC_TIMESTAMP()", # ["contester1_id = ? AND contester2_id = ? AND mandatory = true AND default_time < UTC_TIMESTAMP()",
contester1.id, contester2.id]) > 0 # contester1.id, contester2.id]) > 0
errors.add_to_base I18n.t(:challenges_mandatory_handled) # errors.add :base, I18n.t(:challenges_mandatory_handled)
end # end
if Match.of_contester(contester2).on_week(match_time).count > 0 # if Match.of_contester(contester2).on_week(match_time).count > 0
errors.add_to_base I18n.t(:challenges_opponent_week) # errors.add :base, I18n.t(:challenges_opponent_week)
end # end
if Challenge.of_contester(contester2).mandatory.on_week(match_time).count > 0 # if Challenge.of_contester(contester2).mandatory.on_week(match_time).count > 0
errors.add_to_base I18n.t(:challenges_opponent_mandatory_week) # errors.add :base, I18n.t(:challenges_opponent_mandatory_week)
end # end
if Challenge.of_contester(contester2).mandatory.on_week(default_time).count > 0 # if Challenge.of_contester(contester2).mandatory.on_week(default_time).count > 0
errors.add_to_base I18n.t(:challenges_opponent_mandatory_week_defaulttime) # errors.add :base, I18n.t(:challenges_opponent_mandatory_week_defaulttime)
end # end
if Match.of_contester(contester2).around(default_time).count > 0 # if Match.of_contester(contester2).around(default_time).count > 0
errors.add_to_base I18n.t(:challenges_opponent_defaulttime) # errors.add :base, I18n.t(:challenges_opponent_defaulttime)
end # end
end end
def validate_match_time def validate_match_time
if (match_time-get_margin).past? # if (match_time-get_margin).past?
if get_margin > 86400 # if get_margin > 86400
errors.add_to_base I18n.t(:matches_time1) + get_margin / 60 / 60 / 24 + I18n.t(:matches_time2) # errors.add :base, I18n.t(:matches_time1) + get_margin / 60 / 60 / 24 + I18n.t(:matches_time2)
else # else
errors.add_to_base I18n.t(:matches_time1) + get_margin / 60 + I18n.t(:matches_time3) # errors.add :base, I18n.t(:matches_time1) + get_margin / 60 + I18n.t(:matches_time3)
end # end
end # end
if Challenge.of_contester(contester2).around(match_time).pending.count > 0 # if Challenge.of_contester(contester2).around(match_time).pending.count > 0
errors.add_to_base I18n.t(:challenges_opponent_specifictime) # errors.add :base, I18n.t(:challenges_opponent_specifictime)
end # end
if Match.of_contester(contester2).around(match_time).count > 0 # if Match.of_contester(contester2).around(match_time).count > 0
errors.add_to_base I18n.t(:challenges_opponent_match_specifictime) # errors.add :base, I18n.t(:challenges_opponent_match_specifictime)
end # end
if match_time > contester1.contest.end # if match_time > contester1.contest.end
errors.add_to_base I18n.t(:contests_end) # errors.add :base, I18n.t(:contests_end)
end # end
end end
def validate_server def validate_server
unless server and server.official # unless server and server.official
errors.add_to_base I18n.t(:servers_notavailable) # errors.add :base, I18n.t(:servers_notavailable)
end # end
unless server.is_free match_time # unless server.is_free match_time
errors.add_to_base I18n.t(:servers_notfree_specifictime) # errors.add :base, I18n.t(:servers_notfree_specifictime)
end # end
if !server.is_free default_time # if !server.is_free default_time
errors.add_to_base I18n.t(:servers_notfree_defaulttime) # errors.add :base, I18n.t(:servers_notfree_defaulttime)
end # end
end end
def validate_map1 def validate_map1
unless contester1.contest.maps.exists?(map1) unless contester1.contest.maps.exists?(map1)
errors.add_to_base I18n.t(:contests_map_notavailable) errors.add :base, I18n.t(:contests_map_notavailable)
end end
end end
def validate_map2 def validate_map2
unless contester2.contest.maps.exists?(map2) unless contester2.contest.maps.exists?(map2)
errors.add_to_base I18n.t(:contests_map_notavailable) errors.add :base, I18n.t(:contests_map_notavailable)
end end
end end
def validate_status def validate_status
if mandatory and ![STATUS_ACCEPTED, STATUS_DEFAULT, STATUS_FORFEIT].include? status if mandatory and ![STATUS_ACCEPTED, STATUS_DEFAULT, STATUS_FORFEIT].include? status
errors.add_to_base I18n.t(:challenges_mandatory_invalidresult) errors.add :base, I18n.t(:challenges_mandatory_invalidresult)
end end
unless statuses.include? status unless statuses.include? status
errors.add_to_base I18n.t(:challenges_mandatory_invalidresult) errors.add :base, I18n.t(:challenges_mandatory_invalidresult)
end end
end end
@ -251,10 +251,10 @@ class Challenge < ActiveRecord::Base
end end
def can_update? cuser def can_update? cuser
cuser and (contester2.team.is_leader? cuser or cuser.admin?) and status == STATUS_PENDING and autodefault.future? cuser and (contester2.team.is_leader? cuser or cuser.admin?) and status == STATUS_PENDING# and autodefault.future?
end end
def can_destroy? cuser def can_destroy? cuser
cuser and (contester1.team.is_leader? cuser or cuser.admin?) and status == STATUS_PENDING and autodefault.future? cuser and (contester1.team.is_leader? cuser or cuser.admin?) and status == STATUS_PENDING# and autodefault.future?
end end
end end

View file

@ -107,23 +107,27 @@ class Contest < ActiveRecord::Base
end end
end end
def elo_score score1, score2, diff, level = self.modulus_base, weight = self.weight, moduluses = [self.modulus_even, self.modulus_3to1, self.modulus_4to0] def update_ranks contester, old_rank, new_rank
points = (score2-score1).abs if old_rank < new_rank
modulus = moduluses[0].to_f if points <= 1 Contester.update_all(["score = score -1, trend = ?", Contester::TREND_UP],
modulus = moduluses[1].to_f if points == 2 ["contest_id = ? and score > ? and score <= ?",
modulus = moduluses[2].to_f if points >= 3 self.id, old_rank, new_rank])
if score1 == score2 contester.trend = Contester::TREND_DOWN
result = 0.5 elsif old_rank > new_rank
elsif score1 > score2 Contester.update_all(["score = score + 1, trend = ?", Contester::TREND_DOWN],
result = 1.0 ["contest_id = ? and score < ? and score >= ?",
elsif score2 > score1 self.id, old_rank, new_rank])
result = 0.0 contester.trend = Contester::TREND_UP
end end
prob = 1.0/(10**(diff.to_f/weight.to_f)+1.0) contester.score = new_rank
total = (level.to_f*modulus*(result-prob)).round
return total
end end
def ladder_ranks_unique?
c = Contester.where({:contest_id => self.id})
c.uniq.pluck(:score).count == c.count
end
def can_create? cuser def can_create? cuser
cuser and cuser.admin? cuser and cuser.admin?
end end

View file

@ -27,6 +27,8 @@ class Contester < ActiveRecord::Base
attr_accessor :user attr_accessor :user
scope :active, :include => :team, :conditions => {"contesters.active" => true} scope :active, :include => :team, :conditions => {"contesters.active" => true}
# ranked is used for ladder. lower score the higher the rank
scope :ranked, :select => "contesters.*", :order => "score ASC, win DESC, loss ASC"
scope :ordered, :select => "contesters.*, (score + extra) AS total_score", :order => "total_score DESC, score DESC, win DESC, loss ASC" scope :ordered, :select => "contesters.*, (score + extra) AS total_score", :order => "total_score DESC, score DESC, win DESC, loss ASC"
scope :chronological, :order => "created_at DESC" scope :chronological, :order => "created_at DESC"
scope :of_contest, lambda { |contest| {:conditions => {"contesters.contest_id" => contest.id}} } scope :of_contest, lambda { |contest| {:conditions => {"contesters.contest_id" => contest.id}} }

View file

@ -9,30 +9,12 @@
<%= f.hidden_field :contester2_id %> <%= f.hidden_field :contester2_id %>
<div class="contentWide"> <div class="contentWide">
<% if @challenge.errors.empty? or @challenge.server %> <% if @challenge.errors.empty? %>
<h3 class="center"> <h3 class="center">
<%= namelink @challenge.contester1.team %> <%= namelink @challenge.contester1.team %>
vs vs
<%= namelink @challenge.contester2.team %> <%= namelink @challenge.contester2.team %>
</h3> </h3>
<p>
<%= f.label :match_time %><br />
<%= f.datetime_select :match_time %>
</p>
<p>
<%= f.check_box :mandatory %>
<%= f.label :mandatory %>
( Default time will be following Sunday <%= @challenge.contester1.contest.default_time.strftime("%H:%M") %> )
</p>
<p>
<%= f.label :server_id %><br />
<%= f.select :server_id, Server.hlds.active.collect{|c| [c.name, c.id]} %>
</p>
<p>
<%= f.label :map1_id, "Your Map" %><br />
<%= f.select :map1_id, @challenge.contester1.contest.maps.basic.collect{|m| [m.name, m.id]} %>
</p>
<p> <p>
<%= f.label :details, "Information for the opponent" %><br /> <%= f.label :details, "Information for the opponent" %><br />
<%= f.text_area :details, :cols => 40, :rows => 7 %> <%= f.text_area :details, :cols => 40, :rows => 7 %>

View file

@ -0,0 +1,60 @@
<table class="contest striped">
<thead>
<tr>
<th class="rank">Rank</th>
<th class="movement"></th>
<th class="flag"></th>
<th class="team">Team</th>
<th class="awards"></th>
<th class="win">Win</th>
<th class="loss">Loss</th>
<th class="draw">Draw</th>
<% if actions and cuser %>
<th class="actions"></th>
<% end %>
</tr>
</thead>
<tbody>
<% contesters = contest.contesters.active.ranked
contesters.each_with_index do |contester, rank| %>
<tr>
<td><%= h rank + 1%>.</td>
<% if contester.trend == Contester::TREND_UP %>
<td><%= icon 'chevron-up' %></td>
<% elsif contester.trend == Contester::TREND_DOWN %>
<td><%= icon 'chevron-down' %></td>
<% elsif contester.trend == Contester::TREND_FLAT %>
<td><%= icon 'minus' %></td>
<% else %>
<td></td>
<% end %>
<td><%= flag contester.team.country %></td>
<td><%= link_to (h contester.team.name), contester %></td>
<td><%= icon 'trophy' if contester == contester.contest.winner %></td>
<td><%= h contester.win %></td>
<td><%= h contester.loss %></td>
<td><%= h contester.draw %></td>
<% if actions and cuser%>
<td class="actions">
<% if false %>
<% current = cuser.active_contesters.of_contest(contest).first %>
<% if current %>
<% challenge = Challenge.new
challenge.contester1 = current
challenge.contester2 = contester %>
<% if challenge.can_create? cuser %>
<%= link_to 'C', controller: 'challenges', id: contester, action: 'new' %>
<% end %>
<% end %>
<% end %>
<% if cuser and cuser.admin? %>
<%= link_to icon('pencil'), edit_contester_path(contester) %>
<%= link_to icon('times'), contester, confirm: 'Are you sure?', method: :delete %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>

View file

@ -9,10 +9,17 @@
<%= f.label :extra, "Extra Points" %> <%= f.label :extra, "Extra Points" %>
<%= f.text_field :extra %> <%= f.text_field :extra %>
</div> </div>
<% if @contester.contest.contest_type == Contest::TYPE_LADDER %>
<div class="fields horizontal">
<%= f.label :rank %>
<%= f.text_field :score %>
</div>
<% else %>
<div class="fields horizontal"> <div class="fields horizontal">
<%= f.label :score %> <%= f.label :score %>
<%= f.text_field :score %> <%= f.text_field :score %>
</div> </div>
<% end %>
<div class="fields horizontal"> <div class="fields horizontal">
<%= f.label :win %> <%= f.label :win %>
<%= f.text_field :win %> <%= f.text_field :win %>

View file

@ -5,7 +5,12 @@
<% if contest.contest_type == Contest::TYPE_BRACKET %> <% if contest.contest_type == Contest::TYPE_BRACKET %>
<%= render partial: "bracket", locals: { contest: contest } %> <%= render partial: "bracket", locals: { contest: contest } %>
<% elsif contest.contest_type == Contest::TYPE_LADDER %>
<%= render partial: "contesters/ladder",
locals: { actions: true, contest: contest } %>
<% else %> <% else %>
<%= render partial: "contesters/list", locals: { contesters: contest.contesters.active.ordered, actions: true } %> <%= render partial: "contesters/list",
locals: { contesters: contest.contesters.active.ordered,
actions: true, contest: contest } %>
<% end %> <% end %>
<% end %> <% end %>

View file

@ -11,6 +11,9 @@
<div class="wide centered"> <div class="wide centered">
<% if contest.contest_type == Contest::TYPE_BRACKET %> <% if contest.contest_type == Contest::TYPE_BRACKET %>
<%= render :partial => "bracket", :locals => {:contest => contest} %> <%= render :partial => "bracket", :locals => {:contest => contest} %>
<% elsif contest.contest_type == Contest::TYPE_LADDER %>
<%= render partial: "contesters/ladder",
locals: { actions: false, contest: contest } %>
<% else %> <% else %>
<%= render :partial => "contesters/list", :locals => {:contesters => contest.contesters.active.ordered, :actions => false} %> <%= render :partial => "contesters/list", :locals => {:contesters => contest.contesters.active.ordered, :actions => false} %>
<% end %> <% end %>

View file

@ -20,9 +20,6 @@
<dd>Sunday: <%= Time.use_zone(timezone_offset) { @contest.default_time.strftime("%H:%M %Z") } %></dd> <dd>Sunday: <%= Time.use_zone(timezone_offset) { @contest.default_time.strftime("%H:%M %Z") } %></dd>
</dl> </dl>
<% if @contest.contest_type == Contest::TYPE_LADDER %>
<%= link_to 'Scoring', "/contests/#{@contest}/score", class: 'button' %>
<% end %>
<% if cuser and cuser.admin? %> <% if cuser and cuser.admin? %>
<%= link_to 'Edit Contest', edit_contest_path(@contest), class: 'button' %> <%= link_to 'Edit Contest', edit_contest_path(@contest), class: 'button' %>
<% end %> <% end %>
@ -32,6 +29,9 @@
<div class="standings"> <div class="standings">
<% if @contest.contest_type == Contest::TYPE_BRACKET %> <% if @contest.contest_type == Contest::TYPE_BRACKET %>
<%= render partial: 'bracket', locals: { contest: @contest } %> <%= render partial: 'bracket', locals: { contest: @contest } %>
<% elsif @contest.contest_type == Contest::TYPE_LADDER %>
<%= render partial: "contesters/ladder",
locals: { actions: true, contest: @contest } %>
<% else %> <% else %>
<%= render partial: 'normal' %> <%= render partial: 'normal' %>
<% end %> <% end %>