module Scoring class ApplyAction Result = Struct.new(:score, :set_won, :match_won, :winner, keyword_init: true) def initialize(session:, action:) @session = session @match = session.match @action = action.to_s @rules = Rules.from_match(@match) end def call score = @session.score_state || @session.create_score_state!( home_sets: 0, away_sets: 0, home_points: 0, away_points: 0, current_set: 1, set_partials: [] ) case @action when "home_point" apply_point(score, :home) when "away_point" apply_point(score, :away) when "home_undo" apply_undo(score, :home) when "away_undo" apply_undo(score, :away) when "close_set" apply_close_set(score) else raise ArgumentError, "Azione non valida" end end private def apply_point(score, side) if side == :home score.home_points += 1 else score.away_points += 1 end score.save! broadcast(score) winner = @rules.set_winner_from_points( home_points: score.home_points, away_points: score.away_points, current_set: score.current_set ) Result.new(score: score, set_won: winner.present?, match_won: false, winner: winner) end def apply_undo(score, side) if side == :home score.home_points = [score.home_points - 1, 0].max else score.away_points = [score.away_points - 1, 0].max end score.save! broadcast(score) Result.new(score: score, set_won: false, match_won: false, winner: nil) end def apply_close_set(score) winner = @rules.set_winner_from_points( home_points: score.home_points, away_points: score.away_points, current_set: score.current_set ) winner ||= score.home_points > score.away_points ? :home : :away if score.home_points != score.away_points return Result.new(score: score, set_won: false, match_won: false, winner: nil) unless winner close_set_for(score, winner) end def close_set_for(score, winner) partials = Array(score.set_partials) partials << { "set" => score.current_set, "home" => score.home_points, "away" => score.away_points } if winner == :home score.home_sets += 1 else score.away_sets += 1 end match_winner = @rules.match_winner(home_sets: score.home_sets, away_sets: score.away_sets) score.assign_attributes( set_partials: partials, home_points: 0, away_points: 0, current_set: score.current_set + 1 ) score.save! broadcast(score) Result.new( score: score, set_won: true, match_won: match_winner.present?, winner: match_winner || winner ) end def broadcast(score) SessionChannel.broadcast_message(@session, score.as_cable_payload) Streams::Overlay::Refresh.call(@session) end end end