From 939c8323a579943e7625b94e7d272d6ab0fbe3b8 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Sat, 12 Apr 2014 21:32:47 +0900 Subject: [PATCH 1/8] Add problem edge model --- app/models/problem.rb | 5 +++++ app/models/problem_edge.rb | 6 ++++++ db/migrate/20140412105531_create_problem_edges.rb | 10 ++++++++++ db/schema.rb | 9 ++++++++- spec/factories/problem_edges.rb | 8 ++++++++ spec/models/problem_edge_spec.rb | 5 +++++ 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 app/models/problem_edge.rb create mode 100644 db/migrate/20140412105531_create_problem_edges.rb create mode 100644 spec/factories/problem_edges.rb create mode 100644 spec/models/problem_edge_spec.rb diff --git a/app/models/problem.rb b/app/models/problem.rb index 8a35351..7a8876c 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -15,6 +15,11 @@ class Problem < ActiveRecord::Base belongs_to :contest has_many :submissions + has_many :edges_from, class_name: ProblemEdge, foreign_key: :from_problem_id, dependent: :destroy + has_many :edges_to, class_name: ProblemEdge, foreign_key: :to_problem_id, dependent: :destroy + has_many :from_problems, through: :edges_to, source: :to_problem + has_many :to_problems, through: :edges_from, source: :from_problem + before_save :convert_html def prefix diff --git a/app/models/problem_edge.rb b/app/models/problem_edge.rb new file mode 100644 index 0000000..06ada0c --- /dev/null +++ b/app/models/problem_edge.rb @@ -0,0 +1,6 @@ +class ProblemEdge < ActiveRecord::Base + attr_accessible :from_problem_id, :to_problem_id + + belongs_to :from_problem, class_name: Problem, foreign_key: :from_problem_id + belongs_to :to_problem, class_name: Problem, foreign_key: :to_problem_id +end diff --git a/db/migrate/20140412105531_create_problem_edges.rb b/db/migrate/20140412105531_create_problem_edges.rb new file mode 100644 index 0000000..f769ee8 --- /dev/null +++ b/db/migrate/20140412105531_create_problem_edges.rb @@ -0,0 +1,10 @@ +class CreateProblemEdges < ActiveRecord::Migration + def change + create_table :problem_edges do |t| + t.integer :from_problem_id, null: false + t.integer :to_problem_id, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6051c01..18785a2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20131031122342) do +ActiveRecord::Schema.define(:version => 20140412105531) do create_table "attendances", :force => true do |t| t.integer "user_id", :null => false @@ -40,6 +40,13 @@ t.text "output" end + create_table "problem_edges", :force => true do |t| + t.integer "from_problem_id", :null => false + t.integer "to_problem_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "problems", :force => true do |t| t.string "title" t.text "description" diff --git a/spec/factories/problem_edges.rb b/spec/factories/problem_edges.rb new file mode 100644 index 0000000..a9f73a2 --- /dev/null +++ b/spec/factories/problem_edges.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :problem_edge do + from_problem_id 1 + to_problem_id 1 + end +end diff --git a/spec/models/problem_edge_spec.rb b/spec/models/problem_edge_spec.rb new file mode 100644 index 0000000..4e05798 --- /dev/null +++ b/spec/models/problem_edge_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe ProblemEdge do + pending "add some examples to (or delete) #{__FILE__}" +end From 2bf20bd6d7cd434a13a5673ceb288613ebb2f3ac Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Sat, 12 Apr 2014 22:17:44 +0900 Subject: [PATCH 2/8] Add coordinate to problem --- .../20140412131533_add_column_x_and_y_to_problem.rb | 8 ++++++++ db/schema.rb | 4 +++- spec/factories/factory.rb | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20140412131533_add_column_x_and_y_to_problem.rb diff --git a/db/migrate/20140412131533_add_column_x_and_y_to_problem.rb b/db/migrate/20140412131533_add_column_x_and_y_to_problem.rb new file mode 100644 index 0000000..d2fe336 --- /dev/null +++ b/db/migrate/20140412131533_add_column_x_and_y_to_problem.rb @@ -0,0 +1,8 @@ +class AddColumnXAndYToProblem < ActiveRecord::Migration + def up + change_table :problems do |t| + t.integer :x + t.integer :y + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 18785a2..e2c8e5b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140412105531) do +ActiveRecord::Schema.define(:version => 20140412131533) do create_table "attendances", :force => true do |t| t.integer "user_id", :null => false @@ -61,6 +61,8 @@ t.integer "contest_id" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false + t.integer "x" + t.integer "y" end create_table "submissions", :force => true do |t| diff --git a/spec/factories/factory.rb b/spec/factories/factory.rb index 1edb591..5400cd9 100644 --- a/spec/factories/factory.rb +++ b/spec/factories/factory.rb @@ -53,6 +53,8 @@ sequence(:large_output) {|n| (100+n).to_s } small_score 3 large_score 1020 + x Random.rand(800) + y Random.rand(600) end factory :image From 19f33e52e6bdf0ddb38a229cff8af14b013e5b6f Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Thu, 1 May 2014 21:16:09 +0900 Subject: [PATCH 3/8] Add course stub --- .../contests/problems_controller.rb | 2 + app/views/contests/problems/index.html.erb | 167 ++++++++++++++++++ spec/factories/factory.rb | 4 +- 3 files changed, 171 insertions(+), 2 deletions(-) diff --git a/app/controllers/contests/problems_controller.rb b/app/controllers/contests/problems_controller.rb index ddde8bd..194616e 100644 --- a/app/controllers/contests/problems_controller.rb +++ b/app/controllers/contests/problems_controller.rb @@ -20,6 +20,8 @@ def index @users = User.contestants_of(@contest) @current_user.attend(@contest) unless @current_user.attended? @contest + @json = JSON.generate(@problems.map { |p| { id: p.id, title: p.title, text: p.description, x: p.x, y: p.y } }) + respond_to do |format| format.html # index.html.erb format.json { render json: @problems } diff --git a/app/views/contests/problems/index.html.erb b/app/views/contests/problems/index.html.erb index f728742..6e7d737 100644 --- a/app/views/contests/problems/index.html.erb +++ b/app/views/contests/problems/index.html.erb @@ -6,6 +6,10 @@ b ? content_tag(:div, 'Correct!', class: 'notification alert alert-success') : content_tag(:div, 'Wrong...', class: 'notification alert alert-error') } %> +

Problem Course

+
+
+

<%= link_to 'Scoreboard', contest_score_path %>

Problem List

@@ -71,3 +75,166 @@ <% end %> + + + diff --git a/spec/factories/factory.rb b/spec/factories/factory.rb index 5400cd9..31bcc9d 100644 --- a/spec/factories/factory.rb +++ b/spec/factories/factory.rb @@ -53,8 +53,8 @@ sequence(:large_output) {|n| (100+n).to_s } small_score 3 large_score 1020 - x Random.rand(800) - y Random.rand(600) + x {|n| Random.rand(800) } + y {|n| Random.rand(600) } end factory :image From 82f0bb8c333c7d0f9c77b1646eb4c83594a08854 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Thu, 1 May 2014 22:41:03 +0900 Subject: [PATCH 4/8] Fix Problem edges --- app/models/problem.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/problem.rb b/app/models/problem.rb index 7a8876c..d26c4b3 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -15,10 +15,10 @@ class Problem < ActiveRecord::Base belongs_to :contest has_many :submissions - has_many :edges_from, class_name: ProblemEdge, foreign_key: :from_problem_id, dependent: :destroy - has_many :edges_to, class_name: ProblemEdge, foreign_key: :to_problem_id, dependent: :destroy - has_many :from_problems, through: :edges_to, source: :to_problem - has_many :to_problems, through: :edges_from, source: :from_problem + has_many :edges_from, class_name: ProblemEdge, foreign_key: :to_problem_id, dependent: :destroy + has_many :edges_to, class_name: ProblemEdge, foreign_key: :from_problem_id, dependent: :destroy + has_many :from_problems, through: :edges_from, source: :from_problem + has_many :to_problems, through: :edges_to, source: :to_problem before_save :convert_html From 2bac97490e37b0d9b69214f5c319598e6b02bd23 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Thu, 1 May 2014 22:58:15 +0900 Subject: [PATCH 5/8] Show ULPC-like problem course --- .../contests/problems_controller.rb | 5 ++- app/views/contests/problems/index.html.erb | 14 +++----- spec/factories/factory.rb | 35 +++++++++++++++++-- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/controllers/contests/problems_controller.rb b/app/controllers/contests/problems_controller.rb index 194616e..0642152 100644 --- a/app/controllers/contests/problems_controller.rb +++ b/app/controllers/contests/problems_controller.rb @@ -20,7 +20,10 @@ def index @users = User.contestants_of(@contest) @current_user.attend(@contest) unless @current_user.attended? @contest - @json = JSON.generate(@problems.map { |p| { id: p.id, title: p.title, text: p.description, x: p.x, y: p.y } }) + @json_nodes = JSON.generate(@problems.map { |p| { id: p.id, title: p.title, text: p.description, x: p.x, y: p.y } }) + + # FIXME: (>_<) + @json_edges = JSON.generate(ProblemEdge.all.map { |pe| { f: pe.from_problem_id - 1, t: pe.to_problem_id - 1, curve: 0 } }); respond_to do |format| format.html # index.html.erb diff --git a/app/views/contests/problems/index.html.erb b/app/views/contests/problems/index.html.erb index 6e7d737..71b67ed 100644 --- a/app/views/contests/problems/index.html.erb +++ b/app/views/contests/problems/index.html.erb @@ -104,14 +104,8 @@ }; (function () { - var nodes = <%= @json.html_safe %>; - - var edges = [ - { f: 0, t: 1, curve:-0.3 }, - { f: 0, t: 2, curve: 0.3 }, - { f: 1, t: 3, curve:-0.3 }, - { f: 1, t: 4, curve: 0.3 } - ]; + var nodes = <%= @json_nodes.html_safe %>; + var edges = <%= @json_edges.html_safe %>; _(nodes).each(function (n) { n.pos = pt(n.x, n.y); @@ -178,11 +172,11 @@ .style('position', 'relative') .append('svg') .attr('id', 'problem-course') - .attr('width', 800) + .attr('width', 1200) .attr('height', 600); svg.append('rect') - .attr('width', '800px') + .attr('width', '1200px') .attr('height', '600px') .attr('fill', '#f0f2f5'); diff --git a/spec/factories/factory.rb b/spec/factories/factory.rb index 31bcc9d..ca43082 100644 --- a/spec/factories/factory.rb +++ b/spec/factories/factory.rb @@ -38,9 +38,38 @@ introduction 'Waseda University Programming Contest' start_time Time.now end_time Time.new(2114, 6, 2, 16, 0, 0) - problems { |c| c.problems = FactoryGirl.create_list(:problem, 15) } + problems { |c| c.problems = FactoryGirl.create_list(:problem, 22) } after(:create) do |c, evaluator| c.problems.each { |p| p.save } if evaluator.save_problems + # FIXME: (>_<) + edges = [ + [1,3,6], + [2], + [9], + [4], + [7,5], + [9], + [7], + [8], + [9], + [10,12,15], + [11], + [21], + [13,18], + [14], + [21], + [16], + [17], + [18], + [19], + [20], + [21], + [] + ] + + edges.each_with_index do |es,i| + es.each { |e| ProblemEdge.create(from_problem_id: i+1, to_problem_id: e+1).save! } + end end end @@ -53,8 +82,8 @@ sequence(:large_output) {|n| (100+n).to_s } small_score 3 large_score 1020 - x {|n| Random.rand(800) } - y {|n| Random.rand(600) } + sequence(:x) {|n| [nil, 45, 210, 430, 153, 261, 369, 261, 369, 477, 585, 800, 1017, 750, 909, 1080, 680, 780, 880, 950, 1050, 1150, 1233][n] } + sequence(:y) {|n| [nil, 295, 127, 127, 295, 252, 252, 414, 414, 393, 252, 60, 60, 252, 184, 184, 420, 440, 420, 330, 330, 300, 184][n] } end factory :image From 15f2bc7fda73115e08d0b336a34a3da324784de2 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Thu, 8 May 2014 22:38:43 +0900 Subject: [PATCH 6/8] Fix course ID bug --- app/controllers/contests/problems_controller.rb | 3 +-- app/views/contests/problems/index.html.erb | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/controllers/contests/problems_controller.rb b/app/controllers/contests/problems_controller.rb index 0642152..575548e 100644 --- a/app/controllers/contests/problems_controller.rb +++ b/app/controllers/contests/problems_controller.rb @@ -22,8 +22,7 @@ def index @json_nodes = JSON.generate(@problems.map { |p| { id: p.id, title: p.title, text: p.description, x: p.x, y: p.y } }) - # FIXME: (>_<) - @json_edges = JSON.generate(ProblemEdge.all.map { |pe| { f: pe.from_problem_id - 1, t: pe.to_problem_id - 1, curve: 0 } }); + @json_edges = JSON.generate(ProblemEdge.all.map { |pe| { f: pe.from_problem_id, t: pe.to_problem_id, curve: 0 } }); respond_to do |format| format.html # index.html.erb diff --git a/app/views/contests/problems/index.html.erb b/app/views/contests/problems/index.html.erb index 71b67ed..f6d53f0 100644 --- a/app/views/contests/problems/index.html.erb +++ b/app/views/contests/problems/index.html.erb @@ -107,13 +107,16 @@ var nodes = <%= @json_nodes.html_safe %>; var edges = <%= @json_edges.html_safe %>; + var nodesFromID = {}; + _(nodes).each(function (n) { n.pos = pt(n.x, n.y); + nodesFromID[n.id] = n; }); _(edges).each(function (e) { - var t = nodes[e.t].pos; - var f = nodes[e.f].pos; + var t = nodesFromID[e.t].pos; + var f = nodesFromID[e.f].pos; var d = t.sub(f).abs(); var h = t.sub(f).unit().rotate(Math.PI / 2); e.control = t.add(f).div(2).add(h.mul(d*e.curve)); @@ -219,9 +222,9 @@ .style('fill', 'none') .style('stroke-width', 3) .attr('d', function (d) { - var p1 = port(nodes[d.f].pos, d.control, 25); + var p1 = port(nodesFromID[d.f].pos, d.control, 25); var c = d.control; - var p2 = port(nodes[d.t].pos, d.control, 30); + var p2 = port(nodesFromID[d.t].pos, d.control, 30); return 'M '+p1.x+' '+p1.y+' Q '+c.x+' '+c.y+' '+p2.x+' '+p2.y; }) .attr('marker-end', 'url(#arrow-head)'); From 3c3a2b84031822f08d28c00bd747fd06fcecc529 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Fri, 9 May 2014 23:58:26 +0900 Subject: [PATCH 7/8] Show solved problems and visible problems --- .../contests/problems_controller.rb | 30 +++++++++++++++++-- app/models/group.rb | 13 ++++++++ app/views/contests/problems/index.html.erb | 20 ++++++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/app/controllers/contests/problems_controller.rb b/app/controllers/contests/problems_controller.rb index 575548e..31e481a 100644 --- a/app/controllers/contests/problems_controller.rb +++ b/app/controllers/contests/problems_controller.rb @@ -16,11 +16,14 @@ def load_contest # GET /contests/1/problems # GET /contests/1/problems.json def index - @problems = @contest.problems + @problems = @current_user.group.visible_problems_in(@contest) @users = User.contestants_of(@contest) @current_user.attend(@contest) unless @current_user.attended? @contest - @json_nodes = JSON.generate(@problems.map { |p| { id: p.id, title: p.title, text: p.description, x: p.x, y: p.y } }) + group = @current_user.group + visible_problems = group.visible_problems_in(@contest) + solved_problems = group.solved_problems_in(@contest) + @json_nodes = build_json_nodes(@contest.problems, visible_problems, solved_problems) @json_edges = JSON.generate(ProblemEdge.all.map { |pe| { f: pe.from_problem_id, t: pe.to_problem_id, curve: 0 } }); @@ -81,4 +84,27 @@ def check_attendance redirect_to contests_path unless @current_user.attended? @contest end + def build_json_nodes(problems, visible_problems, solved_problems) + def build_node(problem) + { + id: problem.id, + title: problem.title, + text: problem.description, + x: problem.x, + y: problem.y, + } + end + + node_hash = Hash[*problems.flat_map { |p| [p.id, build_node(p)] }] + visible_problems.each do |p| + node_hash[p.id][:visible] = true + end + solved_problems.each do |p| + node_hash[p.id][:visible] = true + node_hash[p.id][:solved] = true + end + + JSON.generate(node_hash.values) + end + end diff --git a/app/models/group.rb b/app/models/group.rb index f470e71..d0565a3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -22,6 +22,19 @@ def attendances_for(contest) Attendance.where(contest_id: contest.id, user_id: user_ids) end + def solved_problems_in(contest) + # FIXME: this coped with only small problems + contest.problems.select { |p| solved_submission_for(p, :small) } + end + + def visible_problems_in(contest) + # FIXME: it may be slow query + ps = contest.problems + solved_problems = solved_problems_in(contest) + root_problems = ps.select { |p| p.from_problems.empty? } + root_problems | solved_problems | solved_problems.flat_map { |p| p.to_problems } + end + def solved_submission_for(problem, type) Submission.where( problem_id: problem.id, diff --git a/app/views/contests/problems/index.html.erb b/app/views/contests/problems/index.html.erb index f6d53f0..cb6acdb 100644 --- a/app/views/contests/problems/index.html.erb +++ b/app/views/contests/problems/index.html.erb @@ -21,7 +21,7 @@ Solved Time - <% @problems.order(:title).each do |problem| %> + <% @problems.each do |problem| %> <%= link_to problem.title, contest_problem_path(problem.contest, problem) %> Small
Large @@ -167,7 +167,19 @@ }; var edgeColor = '#456'; - var nodeColor = '#1abc9c'; + var visibleNodeColor = '#1abc9c'; + var hiddenNodeColor = '#bdc3c7'; + + var nodeOuterColor = function (d) { + return d.visible ? visibleNodeColor : + hiddenNodeColor; + } + + var nodeInnerColor = function (d) { + return d.solved ? 'white' : + d.visible ? visibleNodeColor : + hiddenNodeColor; + } var svg = d3.select('#course-wrapper') .append('div') @@ -201,9 +213,9 @@ .data(nodes) .enter() .append('circle') - .attr('stroke', nodeColor) + .attr('stroke', nodeOuterColor) .attr('stroke-width', 4) - .attr('fill', 'white') + .attr('fill', nodeInnerColor) .attr('r', 20) .attr('cx', function (d) { return d.pos.x; }) .attr('cy', function (d) { return d.pos.y; }) From 758bf29ce63c757a27739a75580a470ebdb92b97 Mon Sep 17 00:00:00 2001 From: Naoki Yaguchi Date: Sat, 10 May 2014 00:19:42 +0900 Subject: [PATCH 8/8] Optimize query --- app/models/group.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index d0565a3..87dda43 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -23,16 +23,15 @@ def attendances_for(contest) end def solved_problems_in(contest) - # FIXME: this coped with only small problems - contest.problems.select { |p| solved_submission_for(p, :small) } + att_ids = Attendance.where(user_id: user_ids).select(:id) + sbm_ids = Submission.where(solved: true, attendance_id: att_ids).select(:problem_id) + Problem.where(id: sbm_ids) end def visible_problems_in(contest) - # FIXME: it may be slow query - ps = contest.problems - solved_problems = solved_problems_in(contest) - root_problems = ps.select { |p| p.from_problems.empty? } - root_problems | solved_problems | solved_problems.flat_map { |p| p.to_problems } + solved_problems = solved_problems_in(contest).select(:id) + edges = ProblemEdge.where(from_problem_id: solved_problems) + Problem.where(id: edges) end def solved_submission_for(problem, type)