From bbd2cef168e78beaba4352accc92d496b658a8a2 Mon Sep 17 00:00:00 2001 From: sto Date: Tue, 28 Oct 2025 15:13:29 +0100 Subject: [PATCH] Support remaining pieces in completions and scoreboards --- app/controllers/completions_controller.rb | 2 +- app/controllers/contests_controller.rb | 14 +++++++++++-- app/models/completion.rb | 18 +++++++++++++++- app/views/completions/_form.html.slim | 21 ++++++++++++------- app/views/contestants/_form.html.slim | 4 ++++ app/views/contests/scoreboard.html.slim | 10 ++++----- app/views/contests/show.html.slim | 4 ++-- config/locales/en.yml | 2 ++ config/locales/fr.yml | 2 ++ ...1431_add_remaining_pieces_to_completion.rb | 5 +++++ db/schema.rb | 3 ++- 11 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 db/migrate/20251028131431_add_remaining_pieces_to_completion.rb diff --git a/app/controllers/completions_controller.rb b/app/controllers/completions_controller.rb index d54f4fd..1c4b2e5 100644 --- a/app/controllers/completions_controller.rb +++ b/app/controllers/completions_controller.rb @@ -109,6 +109,6 @@ class CompletionsController < ApplicationController end def completion_params - params.expect(completion: [ :display_time_from_start, :contestant_id, :message_id, :puzzle_id ]) + params.expect(completion: [ :display_time_from_start, :remaining_pieces, :contestant_id, :message_id, :puzzle_id ]) end end diff --git a/app/controllers/contests_controller.rb b/app/controllers/contests_controller.rb index 72fa5ef..0212027 100644 --- a/app/controllers/contests_controller.rb +++ b/app/controllers/contests_controller.rb @@ -15,7 +15,12 @@ class ContestsController < ApplicationController @title = I18n.t("contests.show.title", name: @contest.name) @action_name = t("helpers.buttons.edit") @action_path = edit_contest_path(@contest) - @contestants = @contest.contestants.sort_by { |contestant| [ -contestant.completions.size, contestant.time_seconds ] } + @contestants = @contest.contestants.sort_by { |contestant| [ + -contestant.completions.where(remaining_pieces: nil).size, + (contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds, + contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000, + contestant.time_seconds + ] } filter_contestants_per_category @puzzles = @contest.puzzles.order(:id) @messages = @contest.messages.order(:time_seconds) @@ -77,7 +82,12 @@ class ContestsController < ApplicationController I18n.locale = @contest.lang @title = I18n.t("contests.scoreboard.title", name: @contest.name) - @contestants = @contest.contestants.sort_by { |contestant| [ -contestant.completions.size, contestant.time_seconds ] } + @contestants = @contest.contestants.sort_by { |contestant| [ + -contestant.completions.where(remaining_pieces: nil).size, + (contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds, + contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000, + contestant.time_seconds + ] } filter_contestants_per_category @puzzles = @contest.puzzles.order(:id) @action_name = t("helpers.buttons.refresh") diff --git a/app/models/completion.rb b/app/models/completion.rb index 9bbf682..ec1bf26 100644 --- a/app/models/completion.rb +++ b/app/models/completion.rb @@ -5,6 +5,7 @@ # id :integer not null, primary key # display_relative_time :string # display_time_from_start :string +# remaining_pieces :integer # time_seconds :integer # created_at :datetime not null # updated_at :datetime not null @@ -34,10 +35,25 @@ class Completion < ApplicationRecord belongs_to :message, optional: true before_save :add_time_seconds + before_save :nullify_display_time - validates :display_time_from_start, presence: true, format: { with: /\A(((\d\d|\d):\d\d|\d\d|\d):\d\d|\d\d|\d)\z/ } + validates :display_time_from_start, presence: true, format: { with: /\A(((\d\d|\d):\d\d|\d\d|\d):\d\d|\d\d|\d)\z/ }, if: -> { remaining_pieces == nil } validates :contestant_id, uniqueness: { scope: :puzzle }, if: -> { contest.puzzles.size == 1 } validates :puzzle_id, uniqueness: { scope: :contestant }, if: -> { contest.puzzles.size > 1 } + validate :remaining_pieces_is_correct + + def remaining_pieces_is_correct + if self.remaining_pieces && self.remaining_pieces > self.puzzle.pieces + errors.add(:remaining_pieces, "Cannot be greater than the number of pieces for this puzzle") + end + end + + def nullify_display_time + if self.remaining_pieces + self.display_time_from_start = nil + self.display_relative_time = nil + end + end def add_time_seconds arr = display_time_from_start.split(":") diff --git a/app/views/completions/_form.html.slim b/app/views/completions/_form.html.slim index 8e840f6..96e7696 100644 --- a/app/views/completions/_form.html.slim +++ b/app/views/completions/_form.html.slim @@ -9,14 +9,9 @@ = @message.author br = @message.text - .row - .col - h4 = t("completions.singular").capitalize - .row.mb-3 + .row .col - .form-floating - = form.text_field :display_time_from_start, autocomplete: "off", class: "form-control" - = form.label :display_time_from_start, class: "required" + h4 = t("completions.singular").capitalize .row.mb-3 .col .form-floating @@ -36,6 +31,18 @@ = form.hidden_field :puzzle_id, value: @puzzles.first.id - else = form.hidden_field :puzzle_id + .row.mb-3 + .col + .form-floating + = form.text_field :display_time_from_start, autocomplete: "off", class: "form-control" + = form.label :display_time_from_start, class: "required" + .row.mb-3 + .col + .form-floating + = form.text_field :remaining_pieces, autocomplete: "off", class: "form-control" + = form.label :remaining_pieces + .form-text + = t("activerecord.attributes.completion.remaining_pieces_description") .row .col = form.submit submit_text, class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/contestants/_form.html.slim b/app/views/contestants/_form.html.slim index 19609a2..0d1ea77 100644 --- a/app/views/contestants/_form.html.slim +++ b/app/views/contestants/_form.html.slim @@ -44,6 +44,8 @@ - else th scope="col" = t("activerecord.attributes.completion.display_time") + th scope="col" + = t("activerecord.attributes.completion.remaining_pieces") th scope="col" = t("activerecord.attributes.completion.puzzle") tbody @@ -54,6 +56,8 @@ - if @contest.puzzles.size > 1 td = completion.display_relative_time + td + = completion.remaining_pieces td - if !completion.puzzle.brand.blank? | #{completion.puzzle.name} - #{completion.puzzle.brand} diff --git a/app/views/contests/scoreboard.html.slim b/app/views/contests/scoreboard.html.slim index 55a1e51..a48dd7a 100644 --- a/app/views/contests/scoreboard.html.slim +++ b/app/views/contests/scoreboard.html.slim @@ -42,13 +42,13 @@ css: = contestant.name - if @contest.puzzles.size > 1 td - = contestant.completions.length + = contestant.completions.where(remaining_pieces: nil).length td style="position: relative" - - if index > 0 && contestant.time_seconds > 0 + - if index > 0 && contestant.time_seconds > 0 && contestant.completions.where(remaining_pieces: nil).size > 0 .relative-time style="position:absolute; margin: 1px 0 0 64px; font-size: 14px; color: grey" |> + = display_time(contestant.time_seconds - @contestants[index - 1].time_seconds) - = contestant.display_time + = contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? "#{contestant.completions.map{|completion| completion.puzzle.pieces}.sum - contestant.completions[-1].remaining_pieces}p" : contestant.display_time .col-1 .col-5 - @contest.puzzles.each do |puzzle| @@ -89,6 +89,6 @@ css: = contestant.name - if @contest.puzzles.size > 1 td - = contestant.completions.length + = contestant.completions.where(remaining_pieces: nil).length td - = contestant.display_time \ No newline at end of file + = contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? "#{contestant.completions.map{|completion| completion.puzzle.pieces}.sum - contestant.completions[-1].remaining_pieces}p" : contestant.display_time \ No newline at end of file diff --git a/app/views/contests/show.html.slim b/app/views/contests/show.html.slim index abe5cf4..e784e01 100644 --- a/app/views/contests/show.html.slim +++ b/app/views/contests/show.html.slim @@ -85,9 +85,9 @@ javascript: td = contestant.name td - = contestant.completions.length + = contestant.completions.where(remaining_pieces: nil).length td - = contestant.display_time + = contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? "#{contestant.completions.map{|completion| completion.puzzle.pieces}.sum - contestant.completions[-1].remaining_pieces}p" : contestant.display_time td a.btn.btn-sm.btn-secondary href=edit_contest_contestant_path(@contest, contestant) = t("helpers.buttons.open") diff --git a/config/locales/en.yml b/config/locales/en.yml index 31a0554..ace9a3e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -48,6 +48,8 @@ en: display_time_from_start: Time since start display_relative_time: Time for this puzzle puzzle: Puzzle + remaining_pieces: Remaining pieces + remaining_pieces_description: When this field is filled, the above time will not be taken into account contest: lang: Language for the public scoreboard name: Name diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ae24d03..41c6298 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -19,6 +19,8 @@ fr: display_time_from_start: Temps depuis le début display_relative_time: Temps pour ce puzzle puzzle: Puzzle + remaining_pieces: Nombre de pièces restantes + remaining_pieces_description: Si ce champ est rempli, le temps ci-dessus ne sera pas pris en compte contest: lang: Langue pour le classement public name: Nom diff --git a/db/migrate/20251028131431_add_remaining_pieces_to_completion.rb b/db/migrate/20251028131431_add_remaining_pieces_to_completion.rb new file mode 100644 index 0000000..393db9c --- /dev/null +++ b/db/migrate/20251028131431_add_remaining_pieces_to_completion.rb @@ -0,0 +1,5 @@ +class AddRemainingPiecesToCompletion < ActiveRecord::Migration[8.0] + def change + add_column :completions, :remaining_pieces, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index e722692..e5e6b66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_14_115208) do +ActiveRecord::Schema[8.0].define(version: 2025_10_28_131431) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -64,6 +64,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_14_115208) do t.string "display_time_from_start" t.string "display_relative_time" t.integer "message_id" + t.integer "remaining_pieces" t.index ["contest_id"], name: "index_completions_on_contest_id" t.index ["contestant_id"], name: "index_completions_on_contestant_id" t.index ["message_id"], name: "index_completions_on_message_id"