0.0
The project is in a healthy, maintained state
This gem generates a tournament schedule based on a list of variables.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

~> 0.0.1
 Project Readme

Sports Manager Gem

The sports-manager gem is a powerful tool designed to generate and manage tournament schedules. It handles complex scheduling tasks, considering various constraints such as court availability, game length, rest breaks, and participant availability. Under the hood, it leverages the csp-resolver gem to solve these complex Constraint Satisfaction Problems (CSPs).

Getting Started

Requirements

  • Ruby >= 2.5.8

Installing

You can install using the following command:

gem install "sports-manager"

Or add the following line to your Gemfile:

gem "sports-manager"

Then install it:

$ bundle install

Usage

Setting Up a Tournament

To set up a tournament, you need to provide the following information:

require 'sports-manager'

days = {
  '2023-09-09': { start: 9, end: 20 },
  '2023-09-10': { start: 9, end: 13 }
}

courts = 2
game_length = 60
rest_break = 30
single_day_matches = false
subscriptions = {
  mens_single: [
    { id: 1, name: 'João' },      { id: 2, name: 'Marcelo' },
    { id: 3, name: 'José' },      { id: 4, name: 'Pedro' },
    { id: 5, name: 'Carlos' },    { id: 6, name: 'Leandro' },
    { id: 7, name: 'Leonardo' },  { id: 8, name: 'Cláudio' },
    { id: 9, name: 'Alexandre' }, { id: 10, name: 'Daniel' },
    { id: 11, name: 'Marcos' },   { id: 12, name: 'Henrique' },
    { id: 13, name: 'Joaquim' },  { id: 14, name: 'Alex' },
    { id: 15, name: 'Bruno' },    { id: 16, name: 'Fábio' }
  ]
}
matches = {
  mens_single: [
    [1, 16],
    [2, 15],
    [3, 14],
    [4, 13],
    [5, 12],
    [6, 11],
    [7, 10],
    [8, 9]
  ]
}

solution = SportsManager::TournamentGenerator.new(format: :cli)
  .add_days(days)
  .add_courts(courts)
  .add_game_length(game_length)
  .add_rest_break(rest_break)
  .enable_single_day_matches(single_day_matches)
  .add_subscriptions(subscriptions)
  .single_elimination_algorithm
  .add_matches(matches)

You can also pass already generated matches to the generator, it's useful when your already have the matches generated by another system, but you still want to generate the schedule.

params = {
  when: {
    '2023-09-09': { start: 9, end: 20 },
    '2023-09-10': { start: 9, end: 13 }
  },
  courts: 2,
  game_length: 60,
  rest_brake: 30,
  single_day_matches: false,
  subscriptions: {
    mens_single: [
      { id: 1, name: 'João' },      { id: 2, name: 'Marcelo' },
      { id: 3, name: 'José' },      { id: 4, name: 'Pedro' },
      { id: 5, name: 'Carlos' },    { id: 6, name: 'Leandro' },
      { id: 7, name: 'Leonardo' },  { id: 8, name: 'Cláudio' },
      { id: 9, name: 'Alexandre' }, { id: 10, name: 'Daniel' },
      { id: 11, name: 'Marcos' },   { id: 12, name: 'Henrique' },
      { id: 13, name: 'Joaquim' },  { id: 14, name: 'Alex' },
      { id: 15, name: 'Bruno' },    { id: 16, name: 'Fábio' }
    ]
  },
  matches: {
    mens_single: [
      { id: 1, participants: [1, 16], },
      { id: 2, participants: [2, 15], },
      { id: 3, participants: [3, 14], },
      { id: 4, participants: [4, 13], },
      { id: 5, participants: [5, 12], },
      { id: 6, participants: [6, 11], },
      { id: 7, participants: [7, 10], },
      { id: 8, participants: [8, 9], },
      { id: 9, depends_on: [1, 2], round: 1 },
      { id: 10, depends_on: [3, 4], round: 1 },
      { id: 11, depends_on: [5, 6], round: 1 },
      { id: 12, depends_on: [7, 8], round: 1 },
      { id: 13, depends_on: [9, 10], round: 2 },
      { id: 14, depends_on: [11, 12], round: 2},
      { id: 15, depends_on: [13, 14],  round: 2},
    ]
  }
}

Configuration methods

  • add_days(days): Adds the tournament days.
  • add_day(day, start, end): Adds a single tournament day.
  • add_courts(courts): Adds the number of available courts.
  • add_game_length(game_length): Adds the duration of each game in minutes.
  • add_rest_break(rest_break): Adds the rest time between player matches in minutes.
  • enable_single_day_matches(single_day_matches): Sets if all matches should be on the same day.
  • add_subscriptions(subscriptions): Adds the players or teams participating in each category.
  • add_subscription(category, subscription): Adds a single player or team to a category.
  • add_subscriptions_per_category(subscriptions_per_category): Adds the players or teams participating per category.
  • add_matches(matches): Adds the first matchups for each category.
  • add_match(category, match): Adds a single match to a category.
  • add_matches_per_category(category, matches_per_category): Adds the first matchups per category.
  • single_elimination_algorithm: Sets the single elimination algorithm(this option is already default).

Running Example Tournaments

The gem comes with predefined example tournaments:

solution = SportsManager::TournamentGenerator.example(:simple)
solution = SportsManager::TournamentGenerator.example(:complex)
#complete, minimal, ...

Output Formats

You can choose different output formats:

# CLI format (default)
SportsManager::TournamentGenerator.new(format: :cli)

# Mermaid format (for visual diagrams)
SportsManager::TournamentGenerator.new(format: :mermaid)

Output examples

require 'sports-manager'

days = {
  '2023-09-09': { start: 9, end: 20 },
  '2023-09-10': { start: 9, end: 13 }
}

courts = 2
game_length = 60
rest_break = 30
single_day_matches = false
subscriptions = {
  mens_single: [
    { id: 1, name: 'João' },      { id: 2, name: 'Marcelo' },
    { id: 3, name: 'José' },      { id: 4, name: 'Pedro' },
    { id: 5, name: 'Carlos' },    { id: 6, name: 'Leandro' },
    { id: 7, name: 'Leonardo' },  { id: 8, name: 'Cláudio' },
    { id: 9, name: 'Alexandre' }, { id: 10, name: 'Daniel' },
    { id: 11, name: 'Marcos' },   { id: 12, name: 'Henrique' },
    { id: 13, name: 'Joaquim' },  { id: 14, name: 'Alex' },
    { id: 15, name: 'Bruno' },    { id: 16, name: 'Fábio' }
  ]
}
matches = {
  mens_single: [
    [1, 16],
    [2, 15],
    [3, 14],
    [4, 13],
    [5, 12],
    [6, 11],
    [7, 10],
    [8, 9]
  ]
}

 solution = SportsManager::TournamentGenerator.new(format: :cli)
  .add_days(days)
  .add_courts(courts)
  .add_game_length(game_length)
  .add_rest_break(rest_break)
  .enable_single_day_matches(single_day_matches)
  .add_subscriptions(subscriptions)
  .single_elimination_algorithm
  .add_matches(matches)
  .call
Tournament Timetable:

Solution 1
 category   | id | round |     participants      | court |      time     
------------|----|-------|-----------------------|-------|---------------
mens_single | 1  | 0     | João vs. Fábio        | 0     | 09/09 at 09:00
mens_single | 2  | 0     | Marcelo vs. Bruno     | 1     | 09/09 at 09:00
mens_single | 3  | 0     | José vs. Alex         | 0     | 09/09 at 10:00
mens_single | 4  | 0     | Pedro vs. Joaquim     | 1     | 09/09 at 10:00
mens_single | 5  | 0     | Carlos vs. Henrique   | 0     | 09/09 at 11:00
mens_single | 6  | 0     | Leandro vs. Marcos    | 1     | 09/09 at 11:00
mens_single | 7  | 0     | Leonardo vs. Daniel   | 0     | 09/09 at 12:00
mens_single | 8  | 0     | Cláudio vs. Alexandre | 1     | 09/09 at 12:00
mens_single | 9  | 1     | M1 vs. M2             | 0     | 09/09 at 13:00
mens_single | 10 | 1     | M3 vs. M4             | 1     | 09/09 at 13:00
mens_single | 11 | 1     | M5 vs. M6             | 0     | 09/09 at 14:00
mens_single | 12 | 1     | M7 vs. M8             | 1     | 09/09 at 14:00
mens_single | 13 | 2     | M9 vs. M10            | 0     | 09/09 at 15:00
mens_single | 14 | 2     | M11 vs. M12           | 1     | 09/09 at 15:30
mens_single | 15 | 2     | M13 vs. M14           | 0     | 09/09 at 17:00

Total solutions: 1
require 'sports-manager'

days = {
  '2023-09-09': { start: 9, end: 20 },
  '2023-09-10': { start: 9, end: 13 }
}

courts = 2
game_length = 60
rest_break = 30
single_day_matches = false
subscriptions = {
  mens_single: [
    { id: 1, name: 'João' },      { id: 2, name: 'Marcelo' },
    { id: 3, name: 'José' },      { id: 4, name: 'Pedro' },
    { id: 5, name: 'Carlos' },    { id: 6, name: 'Leandro' },
    { id: 7, name: 'Leonardo' },  { id: 8, name: 'Cláudio' },
    { id: 9, name: 'Alexandre' }, { id: 10, name: 'Daniel' },
    { id: 11, name: 'Marcos' },   { id: 12, name: 'Henrique' },
    { id: 13, name: 'Joaquim' },  { id: 14, name: 'Alex' },
    { id: 15, name: 'Bruno' },    { id: 16, name: 'Fábio' }
  ]
}
matches = {
  mens_single: [
    [1, 16],
    [2, 15],
    [3, 14],
    [4, 13],
    [5, 12],
    [6, 11],
    [7, 10],
    [8, 9]
  ]
}

 solution = SportsManager::TournamentGenerator.new(format: :mermaid)
  .add_days(days)
  .add_courts(courts)
  .add_game_length(game_length)
  .add_rest_break(rest_break)
  .enable_single_day_matches(single_day_matches)
  .add_subscriptions(subscriptions)
  .single_elimination_algorithm
  .add_matches(matches)
  .call
Solutions:
--------------------------------------------------------------------------------
Solutions 1
Gantt:
---
displayMode: compact
---
gantt
  title Tournament Schedule
  dateFormat DD/MM HH:mm
  axisFormat %H:%M
  tickInterval 1hour

  section 0
    MS M1: 09/09 09:00, 1h
    MS M3: 09/09 10:00, 1h
    MS M5: 09/09 11:00, 1h
    MS M7: 09/09 12:00, 1h
    MS M9: 09/09 13:00, 1h
    MS M11: 09/09 14:00, 1h
    MS M13: 09/09 15:00, 1h
    MS M15: 09/09 17:00, 1h
  section 1
    MS M2: 09/09 09:00, 1h
    MS M4: 09/09 10:00, 1h
    MS M6: 09/09 11:00, 1h
    MS M8: 09/09 12:00, 1h
    MS M10: 09/09 13:00, 1h
    MS M12: 09/09 14:00, 1h
    MS M14: 09/09 15:30, 1h
Graph:
graph LR
classDef court0 fill:#A9F9A9, color:#000000
classDef court1 fill:#4FF7DE, color:#000000
subgraph colorscheme
  direction LR

  COURT0:::court0
  COURT1:::court1
end
subgraph mens_single
  direction LR

  mens_single_1[1\nJoão vs. Fábio\n09/09 09:00]:::court0
  mens_single_2[2\nMarcelo vs. Bruno\n09/09 09:00]:::court1
  mens_single_3[3\nJosé vs. Alex\n09/09 10:00]:::court0
  mens_single_4[4\nPedro vs. Joaquim\n09/09 10:00]:::court1
  mens_single_5[5\nCarlos vs. Henrique\n09/09 11:00]:::court0
  mens_single_6[6\nLeandro vs. Marcos\n09/09 11:00]:::court1
  mens_single_7[7\nLeonardo vs. Daniel\n09/09 12:00]:::court0
  mens_single_8[8\nCláudio vs. Alexandre\n09/09 12:00]:::court1
  mens_single_9[9\nM1 vs. M2\n09/09 13:00]:::court0
  mens_single_10[10\nM3 vs. M4\n09/09 13:00]:::court1
  mens_single_11[11\nM5 vs. M6\n09/09 14:00]:::court0
  mens_single_12[12\nM7 vs. M8\n09/09 14:00]:::court1
  mens_single_13[13\nM9 vs. M10\n09/09 15:00]:::court0
  mens_single_14[14\nM11 vs. M12\n09/09 15:30]:::court1
  mens_single_15[15\nM13 vs. M14\n09/09 17:00]:::court0
  mens_single_1 --> mens_single_9
  mens_single_2 --> mens_single_9
  mens_single_3 --> mens_single_10
  mens_single_4 --> mens_single_10
  mens_single_5 --> mens_single_11
  mens_single_6 --> mens_single_11
  mens_single_7 --> mens_single_12
  mens_single_8 --> mens_single_12
  mens_single_9 --> mens_single_13
  mens_single_10 --> mens_single_13
  mens_single_11 --> mens_single_14
  mens_single_12 --> mens_single_14
  mens_single_13 --> mens_single_15
  mens_single_14 --> mens_single_15
end
--------------------------------------------------------------------------------
Total solutions: 1
graph LR
classDef court0 fill:#A9F9A9, color:#000000
classDef court1 fill:#4FF7DE, color:#000000
subgraph colorscheme
  direction LR

  COURT0:::court0
  COURT1:::court1
end
subgraph mens_single
  direction LR

  mens_single_1[1\nJoão vs. Fábio\n09/09 09:00]:::court0
  mens_single_2[2\nMarcelo vs. Bruno\n09/09 09:00]:::court1
  mens_single_3[3\nJosé vs. Alex\n09/09 10:00]:::court0
  mens_single_4[4\nPedro vs. Joaquim\n09/09 10:00]:::court1
  mens_single_5[5\nCarlos vs. Henrique\n09/09 11:00]:::court0
  mens_single_6[6\nLeandro vs. Marcos\n09/09 11:00]:::court1
  mens_single_7[7\nLeonardo vs. Daniel\n09/09 12:00]:::court0
  mens_single_8[8\nCláudio vs. Alexandre\n09/09 12:00]:::court1
  mens_single_9[9\nM1 vs. M2\n09/09 13:00]:::court0
  mens_single_10[10\nM3 vs. M4\n09/09 13:00]:::court1
  mens_single_11[11\nM5 vs. M6\n09/09 14:00]:::court0
  mens_single_12[12\nM7 vs. M8\n09/09 14:00]:::court1
  mens_single_13[13\nM9 vs. M10\n09/09 15:00]:::court0
  mens_single_14[14\nM11 vs. M12\n09/09 15:30]:::court1
  mens_single_15[15\nM13 vs. M14\n09/09 17:00]:::court0
  mens_single_1 --> mens_single_9
  mens_single_2 --> mens_single_9
  mens_single_3 --> mens_single_10
  mens_single_4 --> mens_single_10
  mens_single_5 --> mens_single_11
  mens_single_6 --> mens_single_11
  mens_single_7 --> mens_single_12
  mens_single_8 --> mens_single_12
  mens_single_9 --> mens_single_13
  mens_single_10 --> mens_single_13
  mens_single_11 --> mens_single_14
  mens_single_12 --> mens_single_14
  mens_single_13 --> mens_single_15
  mens_single_14 --> mens_single_15
end
Loading

Contributing

See our CONTRIBUTING guidelines.

Code of Conduct

We expect that everyone participating in any way with this project follows our Code of Conduct.

License

This project is licensed under the MIT License.