class Match

Model for a tennis match

Overview

First server are the players that serve the first game or two. For a singles match, there is a first server for the first game. For a doubles match, there is a first server for the first two games; one from each team.

Schema Information

Table name: matches

id                      :integer          not null, primary key
created_at              :datetime         not null
updated_at              :datetime         not null
first_team_id           :integer          not null
second_team_id          :integer          not null
scoring                 :string           not null
started                 :boolean          default(FALSE), not null
doubles                 :boolean          default(FALSE), not null
first_player_server_id  :integer
second_player_server_id :integer
title                   :string
team_winner_id          :integer
play_version            :integer

Public Instance Methods

completed?() click to toggle source

Indicate if the match is completed.

# File app/models/match.rb, line 149
def completed?
  team_winner
end
compute_team_winner() click to toggle source

Compute the winner of the match based on the number of sets won by each opponent

  • Returns : Team or nil

# File app/models/match.rb, line 256
def compute_team_winner
  if completed?
    team_winner
  else
    if first_team && second_team
      if sets_won(first_team) == min_sets_to_play
        first_team
      elsif sets_won(second_team) == min_sets_to_play
        second_team
      end
    end
  end
end
first_player() click to toggle source

Get the first player opponent in a singles match

# File app/models/match.rb, line 155
def first_player
  singles_player_of_team first_team
end
first_player=(player) click to toggle source

Set the first player opponent in a singles match. A new team may be created to hold the player

# File app/models/match.rb, line 161
def first_player=(player)
  self.first_team = singles_player_team(player)
end
first_set() click to toggle source

Get the first set of the match

# File app/models/match.rb, line 205
def first_set
  match_sets.first
end
last_set() click to toggle source

Get the last set of the match

# File app/models/match.rb, line 211
def last_set
  match_sets.last
end
max_sets_to_play() click to toggle source

Get the maximum number of sets that will be played in this match. The maximum is 3 for :three_six_game scoring, for example

  • Returns : Integer

# File app/models/match.rb, line 227
def max_sets_to_play
  case scoring.to_sym
  when :two_six_game_ten_point
    3
  when :one_eight_game
    1
  else # :three_six_game
    3
  end
end
min_sets_to_play() click to toggle source

Get the minimum number of sets that will be played in this match The minimum is 2 for :three_six_game scoring, for example

  • Returns : Integer

# File app/models/match.rb, line 218
def min_sets_to_play
  max = max_sets_to_play
  raise Exceptions::ApplicationError, 'Unexpected game count' if max.even?
  (max + 1) / 2
end
near_team_winner?(team) click to toggle source

Determine whether a Team can win the match by winning one more game

  • Args :

  • Returns : Boolean

# File app/models/match.rb, line 275
def near_team_winner?(team)
  unless completed?
    sets_won(team) + 1 == min_sets_to_play
  end
end
next_game_ordinal() click to toggle source

Get the ordinal of the next game to play in the match. The lowest ordinal is 1.

  • Returns : Integer

# File app/models/match.rb, line 241
def next_game_ordinal
  last_set ? last_set.set_games.count + 1 : 0
end
play_match!(action, params = nil) click to toggle source

Execute an action on the match, such as :win_game

  • Args :

    • action -> the action to execute

    • params -> hash of action parameters

  • Raises :

    • UnknownOperation -> if the action is unknown

    • InvalidOperation -> if the action is not permitted

Action

  • :start_play - Start playing

  • :discard_play - Discard all scoring

  • :start_set - Start the next set

  • :start_game - Start the next game

When starting the first game in a singles match, or the first two games in a doubles match, params identifies the player to serve the game.

  • :start_tiebreak - Start game tiebreak

  • :remove_last_change - Back up to the previous state

  • :start_match_tiebreak - Start the match tiebreak

  • :win_game - Win the current game

  • :win_tiebreak - Win the current set tiebreak.

  • :win_match_tiebreak - Win the current match tiebreak.

For the 3 :win_* actions, params identifies the doubles team or singles team to win

Params

  • :version Version number from client.

If provided, it will be compared to the current match play version number. An exception will be raised if the values are not equal. The purpose of the version number is to detect when a client is out of sync.

This options is used with :win_* actions to identify the winning team or player. It is also used with the :start_game action to identify the serving player

# File app/models/match.rb, line 100
def play_match!(action, params = nil)
  method = play_actions.lookup_method(action)
  if method
    ActiveRecord::Base.transaction do
      version = params[:version] if params
      version = version.to_i if version
      if version && version != self.play_version
        raise Exceptions::InvalidOperation,
              "Unable to #{action.to_s.tr('_', ' ')}. " +
                if version < self.play_version
                  'Client is out of sync.'
                else
                  'Unexpected match version number.'
                end
      end
      method[:exec].call params
      # Version number is used to detect when client has is out of sync
      self.play_version = next_version_number
      self.save!
    end
  else
    raise Exceptions::UnknownOperation, "Unknown action: #{action}"
  end
end
play_match?(action) click to toggle source

Indicate if an action can be executed. :win_game can be executed if a game has started, for example.

# File app/models/match.rb, line 127
def play_match?(action)
  methods = play_actions.lookup_method(action)
  if methods
    methods[:query].call
  else
    raise Exceptions::UnknownOperation, "Unknown action: #{action}"
  end
end
scoring_of_set(ordinal) click to toggle source

Get the scoring of a set

  • Args :

    • ordinal -> set ordinal

  • Returns : symbol

    • :six_game

    • :eight_game

    • :ten_point (tiebreak)

# File app/models/match.rb, line 288
def scoring_of_set(ordinal)
  case scoring.to_sym
  when :two_six_game_ten_point
    ordinal == 3 ? :ten_point : :six_game
  when :one_eight_game
    :eight_game
  else # :three_six_game
    :six_game
  end
end
second_player() click to toggle source

Get the second player opponent in a singles match

# File app/models/match.rb, line 167
def second_player
  singles_player_of_team second_team
end
second_player=(player) click to toggle source

Set the second player opponent in a singles match. A new team may be created to hold the player

# File app/models/match.rb, line 173
def second_player=(player)
  self.second_team = singles_player_team(player)
end
sets_won(team) click to toggle source

Calculate the number of sets won by a team

  • Args :

  • Returns : Integer

# File app/models/match.rb, line 196
def sets_won(team)
  match_sets.reduce(0) do |sum, set|
    winner = set.compute_team_winner
    sum + (winner && winner == team ? 1 : 0)
  end
end
state() click to toggle source

Get the state of the match

  • :complete

  • :in_progress

  • :not_started

# File app/models/match.rb, line 181
def state
  if completed?
    :complete
  elsif started
    :in_progress
  else
    :not_started
  end
end
team_of_player(player) click to toggle source

Get the opponent team that includes a particular player

# File app/models/match.rb, line 303
def team_of_player(player)
  if player
    if first_team.include_player?(player)
      first_team
    elsif second_team.include_player?(player)
      second_team
    end
  end
end
tiebreak_set?(ordinal) click to toggle source

Indicate whether a set is a match tiebreak

  • Args :

    • ordinal -> Integer

  • Returns : Boolean

# File app/models/match.rb, line 249
def tiebreak_set?(ordinal)
  scoring_of_set(ordinal) == :ten_point
end
valid_actions() click to toggle source

Generate a hash of valid actions

  • Returns : Hash

Example

{
  start_game: true,
  discard_play: true,
  remove_last_change: true
}
# File app/models/match.rb, line 144
def valid_actions
  play_actions.valid_actions
end