Support remaining pieces in completions and scoreboards
Some checks failed
CI / scan_ruby (push) Failing after 16s
CI / scan_js (push) Successful in 13s
CI / lint (push) Successful in 13s
CI / test (push) Successful in 36s

This commit is contained in:
sto
2025-10-28 15:13:29 +01:00
parent 1fa7bf10ec
commit bbd2cef168
11 changed files with 66 additions and 19 deletions

View File

@@ -109,6 +109,6 @@ class CompletionsController < ApplicationController
end end
def completion_params 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
end end

View File

@@ -15,7 +15,12 @@ class ContestsController < ApplicationController
@title = I18n.t("contests.show.title", name: @contest.name) @title = I18n.t("contests.show.title", name: @contest.name)
@action_name = t("helpers.buttons.edit") @action_name = t("helpers.buttons.edit")
@action_path = edit_contest_path(@contest) @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 filter_contestants_per_category
@puzzles = @contest.puzzles.order(:id) @puzzles = @contest.puzzles.order(:id)
@messages = @contest.messages.order(:time_seconds) @messages = @contest.messages.order(:time_seconds)
@@ -77,7 +82,12 @@ class ContestsController < ApplicationController
I18n.locale = @contest.lang I18n.locale = @contest.lang
@title = I18n.t("contests.scoreboard.title", name: @contest.name) @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 filter_contestants_per_category
@puzzles = @contest.puzzles.order(:id) @puzzles = @contest.puzzles.order(:id)
@action_name = t("helpers.buttons.refresh") @action_name = t("helpers.buttons.refresh")

View File

@@ -5,6 +5,7 @@
# id :integer not null, primary key # id :integer not null, primary key
# display_relative_time :string # display_relative_time :string
# display_time_from_start :string # display_time_from_start :string
# remaining_pieces :integer
# time_seconds :integer # time_seconds :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
@@ -34,10 +35,25 @@ class Completion < ApplicationRecord
belongs_to :message, optional: true belongs_to :message, optional: true
before_save :add_time_seconds 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 :contestant_id, uniqueness: { scope: :puzzle }, if: -> { contest.puzzles.size == 1 }
validates :puzzle_id, uniqueness: { scope: :contestant }, 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 def add_time_seconds
arr = display_time_from_start.split(":") arr = display_time_from_start.split(":")

View File

@@ -9,14 +9,9 @@
= @message.author = @message.author
br br
= @message.text = @message.text
.row .row
.col
h4 = t("completions.singular").capitalize
.row.mb-3
.col .col
.form-floating h4 = t("completions.singular").capitalize
= form.text_field :display_time_from_start, autocomplete: "off", class: "form-control"
= form.label :display_time_from_start, class: "required"
.row.mb-3 .row.mb-3
.col .col
.form-floating .form-floating
@@ -36,6 +31,18 @@
= form.hidden_field :puzzle_id, value: @puzzles.first.id = form.hidden_field :puzzle_id, value: @puzzles.first.id
- else - else
= form.hidden_field :puzzle_id = 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 .row
.col .col
= form.submit submit_text, class: "btn btn-primary" = form.submit submit_text, class: "btn btn-primary"

View File

@@ -44,6 +44,8 @@
- else - else
th scope="col" th scope="col"
= t("activerecord.attributes.completion.display_time") = t("activerecord.attributes.completion.display_time")
th scope="col"
= t("activerecord.attributes.completion.remaining_pieces")
th scope="col" th scope="col"
= t("activerecord.attributes.completion.puzzle") = t("activerecord.attributes.completion.puzzle")
tbody tbody
@@ -54,6 +56,8 @@
- if @contest.puzzles.size > 1 - if @contest.puzzles.size > 1
td td
= completion.display_relative_time = completion.display_relative_time
td
= completion.remaining_pieces
td td
- if !completion.puzzle.brand.blank? - if !completion.puzzle.brand.blank?
| #{completion.puzzle.name} - #{completion.puzzle.brand} | #{completion.puzzle.name} - #{completion.puzzle.brand}

View File

@@ -42,13 +42,13 @@ css:
= contestant.name = contestant.name
- if @contest.puzzles.size > 1 - if @contest.puzzles.size > 1
td td
= contestant.completions.length = contestant.completions.where(remaining_pieces: nil).length
td style="position: relative" 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" .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) = 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-1
.col-5 .col-5
- @contest.puzzles.each do |puzzle| - @contest.puzzles.each do |puzzle|
@@ -89,6 +89,6 @@ css:
= contestant.name = contestant.name
- if @contest.puzzles.size > 1 - if @contest.puzzles.size > 1
td td
= contestant.completions.length = contestant.completions.where(remaining_pieces: nil).length
td 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

View File

@@ -85,9 +85,9 @@ javascript:
td td
= contestant.name = contestant.name
td td
= contestant.completions.length = contestant.completions.where(remaining_pieces: nil).length
td 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 td
a.btn.btn-sm.btn-secondary href=edit_contest_contestant_path(@contest, contestant) a.btn.btn-sm.btn-secondary href=edit_contest_contestant_path(@contest, contestant)
= t("helpers.buttons.open") = t("helpers.buttons.open")

View File

@@ -48,6 +48,8 @@ en:
display_time_from_start: Time since start display_time_from_start: Time since start
display_relative_time: Time for this puzzle display_relative_time: Time for this puzzle
puzzle: 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: contest:
lang: Language for the public scoreboard lang: Language for the public scoreboard
name: Name name: Name

View File

@@ -19,6 +19,8 @@ fr:
display_time_from_start: Temps depuis le début display_time_from_start: Temps depuis le début
display_relative_time: Temps pour ce puzzle display_relative_time: Temps pour ce puzzle
puzzle: 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: contest:
lang: Langue pour le classement public lang: Langue pour le classement public
name: Nom name: Nom

View File

@@ -0,0 +1,5 @@
class AddRemainingPiecesToCompletion < ActiveRecord::Migration[8.0]
def change
add_column :completions, :remaining_pieces, :integer
end
end

3
db/schema.rb generated
View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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| create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "record_type", 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_time_from_start"
t.string "display_relative_time" t.string "display_relative_time"
t.integer "message_id" t.integer "message_id"
t.integer "remaining_pieces"
t.index ["contest_id"], name: "index_completions_on_contest_id" t.index ["contest_id"], name: "index_completions_on_contest_id"
t.index ["contestant_id"], name: "index_completions_on_contestant_id" t.index ["contestant_id"], name: "index_completions_on_contestant_id"
t.index ["message_id"], name: "index_completions_on_message_id" t.index ["message_id"], name: "index_completions_on_message_id"