sports-manager 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +32 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/CONTRIBUTING.md +72 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +419 -0
  8. data/Rakefile +4 -0
  9. data/lib/sports-manager.rb +59 -0
  10. data/lib/sports_manager/algorithms/filtering/no_overlap.rb +52 -0
  11. data/lib/sports_manager/algorithms/ordering/multiple_matches_participant.rb +78 -0
  12. data/lib/sports_manager/bye_match.rb +62 -0
  13. data/lib/sports_manager/constraint_builder.rb +30 -0
  14. data/lib/sports_manager/constraints/all_different_constraint.rb +24 -0
  15. data/lib/sports_manager/constraints/match_constraint.rb +37 -0
  16. data/lib/sports_manager/constraints/multi_category_constraint.rb +49 -0
  17. data/lib/sports_manager/constraints/next_round_constraint.rb +48 -0
  18. data/lib/sports_manager/constraints/no_overlapping_constraint.rb +55 -0
  19. data/lib/sports_manager/double_team.rb +7 -0
  20. data/lib/sports_manager/group.rb +42 -0
  21. data/lib/sports_manager/group_builder.rb +72 -0
  22. data/lib/sports_manager/helper.rb +228 -0
  23. data/lib/sports_manager/json_helper.rb +129 -0
  24. data/lib/sports_manager/match.rb +91 -0
  25. data/lib/sports_manager/match_builder.rb +112 -0
  26. data/lib/sports_manager/matches/algorithms/single_elimination_algorithm.rb +94 -0
  27. data/lib/sports_manager/matches/next_round.rb +38 -0
  28. data/lib/sports_manager/matches_generator.rb +33 -0
  29. data/lib/sports_manager/nil_team.rb +24 -0
  30. data/lib/sports_manager/participant.rb +23 -0
  31. data/lib/sports_manager/single_team.rb +7 -0
  32. data/lib/sports_manager/solution_drawer/cli/solution_table.rb +38 -0
  33. data/lib/sports_manager/solution_drawer/cli/table.rb +94 -0
  34. data/lib/sports_manager/solution_drawer/cli.rb +75 -0
  35. data/lib/sports_manager/solution_drawer/mermaid/bye_node.rb +39 -0
  36. data/lib/sports_manager/solution_drawer/mermaid/gantt.rb +126 -0
  37. data/lib/sports_manager/solution_drawer/mermaid/graph.rb +111 -0
  38. data/lib/sports_manager/solution_drawer/mermaid/node.rb +55 -0
  39. data/lib/sports_manager/solution_drawer/mermaid/node_style.rb +89 -0
  40. data/lib/sports_manager/solution_drawer/mermaid/solution_gantt.rb +57 -0
  41. data/lib/sports_manager/solution_drawer/mermaid/solution_graph.rb +76 -0
  42. data/lib/sports_manager/solution_drawer/mermaid.rb +65 -0
  43. data/lib/sports_manager/solution_drawer.rb +23 -0
  44. data/lib/sports_manager/team.rb +47 -0
  45. data/lib/sports_manager/team_builder.rb +31 -0
  46. data/lib/sports_manager/timeslot.rb +37 -0
  47. data/lib/sports_manager/timeslot_builder.rb +50 -0
  48. data/lib/sports_manager/tournament/setting.rb +45 -0
  49. data/lib/sports_manager/tournament.rb +69 -0
  50. data/lib/sports_manager/tournament_builder.rb +123 -0
  51. data/lib/sports_manager/tournament_day/validator.rb +69 -0
  52. data/lib/sports_manager/tournament_day.rb +50 -0
  53. data/lib/sports_manager/tournament_generator.rb +183 -0
  54. data/lib/sports_manager/tournament_problem_builder.rb +106 -0
  55. data/lib/sports_manager/tournament_solution/bye_fixture.rb +21 -0
  56. data/lib/sports_manager/tournament_solution/fixture.rb +39 -0
  57. data/lib/sports_manager/tournament_solution/serializer.rb +107 -0
  58. data/lib/sports_manager/tournament_solution/solution.rb +85 -0
  59. data/lib/sports_manager/tournament_solution.rb +34 -0
  60. data/lib/sports_manager/version.rb +5 -0
  61. data/sports-manager.gemspec +35 -0
  62. metadata +120 -0
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csp-resolver'
4
+ require 'forwardable'
5
+ require 'ostruct'
6
+ require_relative 'sports_manager/version'
7
+ require_relative 'sports_manager/helper'
8
+ require_relative 'sports_manager/team'
9
+ require_relative 'sports_manager/single_team'
10
+ require_relative 'sports_manager/double_team'
11
+ require_relative 'sports_manager/json_helper'
12
+ require_relative 'sports_manager/matches/algorithms/single_elimination_algorithm'
13
+ require_relative 'sports_manager/algorithms/ordering/multiple_matches_participant'
14
+ require_relative 'sports_manager/algorithms/filtering/no_overlap'
15
+ require_relative 'sports_manager/constraints/all_different_constraint'
16
+ require_relative 'sports_manager/constraints/no_overlapping_constraint'
17
+ require_relative 'sports_manager/constraints/match_constraint'
18
+ require_relative 'sports_manager/constraints/multi_category_constraint'
19
+ require_relative 'sports_manager/constraints/next_round_constraint'
20
+ require_relative 'sports_manager/tournament_problem_builder'
21
+ require_relative 'sports_manager/tournament_solution'
22
+ require_relative 'sports_manager/participant'
23
+ require_relative 'sports_manager/solution_drawer'
24
+ require_relative 'sports_manager/matches_generator'
25
+ require_relative 'sports_manager/tournament_builder'
26
+ require_relative 'sports_manager/constraint_builder'
27
+ require_relative 'sports_manager/tournament_solution/bye_fixture'
28
+ require_relative 'sports_manager/tournament_solution/fixture'
29
+ require_relative 'sports_manager/tournament_solution/solution'
30
+ require_relative 'sports_manager/tournament_solution/serializer'
31
+ require_relative 'sports_manager/solution_drawer/cli'
32
+ require_relative 'sports_manager/tournament'
33
+ require_relative 'sports_manager/tournament_day'
34
+ require_relative 'sports_manager/tournament/setting'
35
+ require_relative 'sports_manager/group_builder'
36
+ require_relative 'sports_manager/match'
37
+ require_relative 'sports_manager/matches/next_round'
38
+ require_relative 'sports_manager/group'
39
+ require_relative 'sports_manager/solution_drawer/cli/solution_table'
40
+ require_relative 'sports_manager/tournament_day/validator'
41
+ require_relative 'sports_manager/timeslot_builder'
42
+ require_relative 'sports_manager/timeslot'
43
+ require_relative 'sports_manager/bye_match'
44
+ require_relative 'sports_manager/nil_team'
45
+ require_relative 'sports_manager/match_builder'
46
+ require_relative 'sports_manager/team_builder'
47
+ require_relative 'sports_manager/solution_drawer/cli/table'
48
+ require_relative 'sports_manager/tournament_generator'
49
+ require_relative 'sports_manager/solution_drawer/mermaid/gantt'
50
+ require_relative 'sports_manager/solution_drawer/mermaid/graph'
51
+ require_relative 'sports_manager/solution_drawer/mermaid/node'
52
+ require_relative 'sports_manager/solution_drawer/mermaid/node_style'
53
+ require_relative 'sports_manager/solution_drawer/mermaid/bye_node'
54
+ require_relative 'sports_manager/solution_drawer/mermaid/solution_graph'
55
+ require_relative 'sports_manager/solution_drawer/mermaid/solution_gantt'
56
+ require_relative 'sports_manager/solution_drawer/mermaid'
57
+
58
+ module SportsManager
59
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Algorithms
5
+ module Filtering
6
+ class NoOverlap
7
+ extend Forwardable
8
+
9
+ attr_reader :tournament
10
+
11
+ def_delegators :tournament, :match_time
12
+
13
+ def self.for(dependency:, problem: nil) # rubocop:disable Lint/UnusedMethodArgument
14
+ new(dependency)
15
+ end
16
+
17
+ def initialize(tournament)
18
+ @tournament = tournament
19
+ end
20
+
21
+ def call(values:, assignment_values: [])
22
+ unassigned_values = values - assignment_values
23
+
24
+ unassigned_values.reject do |timeslot|
25
+ timeslot_range = to_range(timeslot.slot)
26
+
27
+ assignment_values.any? do |assignment_value|
28
+ next if timeslot.court != assignment_value.court
29
+
30
+ assigned_range = to_range(assignment_value.slot)
31
+
32
+ overlap?(timeslot_range, assigned_range)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def overlap?(time_range1, time_range2)
40
+ time_range1.cover?(time_range2.first) ||
41
+ time_range2.cover?(time_range1.first)
42
+ end
43
+
44
+ def to_range(value)
45
+ end_value = value + (match_time * 60)
46
+
47
+ (value...end_value)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Algorithms
5
+ module Ordering
6
+ class MultipleMatchesParticipant
7
+ attr_reader :tournament
8
+
9
+ def self.for(dependency:, problem: nil) # rubocop:disable Lint/UnusedMethodArgument
10
+ new(dependency)
11
+ end
12
+
13
+ def initialize(tournament)
14
+ @tournament = tournament
15
+ end
16
+
17
+ def call(variables)
18
+ variables.sort_by(&method(:sort))
19
+ end
20
+
21
+ private
22
+
23
+ def sort(variable)
24
+ participants = variable.participants
25
+
26
+ multi_matches = boolean_to_integer multi_matches?(participants)
27
+
28
+ duplicate_ids = sorted_duplicates(participants)
29
+ duplicate_ids_size = minimum(duplicate_ids.size)
30
+ number_of_participants = minimum(participants.size)
31
+
32
+ [
33
+ multi_matches,
34
+ duplicate_ids_size,
35
+ duplicate_ids,
36
+ number_of_participants,
37
+ variable.id
38
+ ]
39
+ end
40
+
41
+ def multi_matches?(participants)
42
+ !(multiple_participants & participants).empty?
43
+ end
44
+
45
+ def sorted_duplicates(participants)
46
+ participants
47
+ .select { |participant| multiple_participants.include? participant }
48
+ .map(&:id)
49
+ .sort
50
+ end
51
+
52
+ def multiple_participants
53
+ tournament.multi_tournament_participants
54
+ end
55
+
56
+ # Internal: Gives preference for positive finite values over zero
57
+ # or infinity values when ordering.
58
+ def minimum(number)
59
+ values = [number, Float::INFINITY]
60
+
61
+ values.min_by do |value|
62
+ [
63
+ boolean_to_integer(value.positive?),
64
+ boolean_to_integer(value.finite?)
65
+ ]
66
+ end
67
+ end
68
+
69
+ # TODO: permit set as ascending or descending and swap -1 and 1
70
+ # Internal: Truthy values should have a precedence over falsey in
71
+ # ascending order
72
+ def boolean_to_integer(truthyness)
73
+ truthyness ? -1 : 1
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ # Public: a Match where it has only one team.
5
+ # This match is a placeholder used to create the next rounds.
6
+ # The team in this match will automatically play on the next round.
7
+ class ByeMatch
8
+ attr_reader :id, :category, :team1, :team2, :round, :teams, :depends_on
9
+
10
+ def initialize(category:, id: nil, team1: nil, team2: nil, round: 0, depends_on: []) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
11
+ @id = id
12
+ @category = category
13
+ @team1 = team1
14
+ @team2 = team2
15
+ @round = round
16
+ @teams = [team1, team2].compact
17
+ @depends_on = []
18
+ end
19
+
20
+ def playable?
21
+ false
22
+ end
23
+
24
+ def participants
25
+ @participants ||= teams.map(&:participants).flatten
26
+ end
27
+
28
+ def dependencies?
29
+ false
30
+ end
31
+
32
+ def dependencies
33
+ []
34
+ end
35
+
36
+ def playable_dependencies
37
+ []
38
+ end
39
+
40
+ def previous_matches?
41
+ false
42
+ end
43
+
44
+ def previous_matches
45
+ []
46
+ end
47
+
48
+ def title
49
+ teams_names.join.concat(' | BYE')
50
+ end
51
+
52
+ def teams_names
53
+ teams.map(&:name).reject(&:empty?)
54
+ end
55
+
56
+ def ==(other)
57
+ return false unless instance_of?(other.class)
58
+
59
+ id == other.id && category == other.category && round == other.round
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ class ConstraintBuilder
5
+ attr_reader :tournament, :constraints
6
+
7
+ DEFAULT_CONSTRAINTS = [
8
+ Constraints::AllDifferentConstraint,
9
+ Constraints::NoOverlappingConstraint,
10
+ Constraints::MatchConstraint,
11
+ Constraints::MultiCategoryConstraint,
12
+ Constraints::NextRoundConstraint
13
+ ].freeze
14
+
15
+ def self.build(tournament:, csp:, constraints: nil)
16
+ new(tournament: tournament, constraints: constraints).build(csp)
17
+ end
18
+
19
+ def initialize(tournament:, constraints: nil)
20
+ @tournament = tournament
21
+ @constraints = constraints || DEFAULT_CONSTRAINTS
22
+ end
23
+
24
+ def build(csp)
25
+ constraints.map do |constraint|
26
+ constraint.for_tournament(tournament: tournament, csp: csp)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Constraints
5
+ # Public: Constraint to all timeslot assignments be different for
6
+ # each match
7
+ class AllDifferentConstraint < ::CSP::Constraint
8
+ attr_reader :matches
9
+
10
+ def self.for_tournament(tournament:, csp:)
11
+ csp.add_constraint(new(tournament.matches.values.flatten))
12
+ end
13
+
14
+ def initialize(matches)
15
+ super
16
+ @matches = matches
17
+ end
18
+
19
+ def satisfies?(assignment)
20
+ assignment.values == assignment.values.uniq
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Constraints
5
+ # Public: Restrict match to be set only after the previous matches
6
+ # it depends on are scheduled.
7
+ class MatchConstraint < ::CSP::Constraint
8
+ attr_reader :target_match, :matches
9
+
10
+ def self.for_tournament(tournament:, csp:)
11
+ tournament.matches.each do |(_category, matches)|
12
+ matches.select(&:previous_matches?).each do |match|
13
+ csp.add_constraint(
14
+ new(target_match: match, matches: match.previous_matches)
15
+ )
16
+ end
17
+ end
18
+ end
19
+
20
+ def initialize(target_match:, matches:)
21
+ super([target_match] + matches)
22
+ @target_match = target_match
23
+ @matches = matches
24
+ end
25
+
26
+ def satisfies?(assignment)
27
+ return true unless variables.all? { |variable| assignment.key?(variable) }
28
+
29
+ target_time = assignment[target_match]
30
+
31
+ matches
32
+ .map { |match| assignment[match] }
33
+ .all? { |time| time < target_time }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Constraints
5
+ class MultiCategoryConstraint < ::CSP::Constraint
6
+ attr_reader :target_participant, :matches,
7
+ :match_time, :break_time, :minimum_match_gap
8
+
9
+ MINUTE = 60
10
+
11
+ def self.for_tournament(tournament:, csp:)
12
+ tournament.multi_tournament_participants.each do |participant|
13
+ matches = tournament.find_participant_matches(participant)
14
+
15
+ constraint = new(
16
+ target_participant: participant,
17
+ matches: matches,
18
+ match_time: tournament.match_time,
19
+ break_time: tournament.break_time
20
+ )
21
+
22
+ csp.add_constraint(constraint)
23
+ end
24
+ end
25
+
26
+ def initialize(target_participant:, matches:, match_time:, break_time:)
27
+ super(matches)
28
+ @target_participant = target_participant
29
+ @matches = matches
30
+ @match_time = match_time
31
+ @break_time = break_time
32
+ @minimum_match_gap = match_time + break_time
33
+ end
34
+
35
+ # TODO: See if needs review in case of solution of split timeslots under match_time
36
+ def satisfies?(assignment)
37
+ return true unless variables.all? { |variable| assignment.key?(variable) }
38
+
39
+ timeslots = assignment.slice(*variables).values.map(&:slot)
40
+
41
+ timeslots.combination(2).all? do |timeslot1, timeslot2|
42
+ diff_in_minutes = (timeslot1 - timeslot2).abs / MINUTE
43
+
44
+ diff_in_minutes >= minimum_match_gap
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Constraints
5
+ class NextRoundConstraint < ::CSP::Constraint
6
+ attr_reader :target_match, :matches,
7
+ :match_time, :break_time, :minimum_match_gap
8
+
9
+ MINUTE = 60
10
+
11
+ def self.for_tournament(tournament:, csp:)
12
+ tournament
13
+ .matches.values.flatten
14
+ .select(&:previous_matches?)
15
+ .each do |match|
16
+ csp.add_constraint new(
17
+ target_match: match,
18
+ matches: match.previous_matches,
19
+ match_time: tournament.match_time,
20
+ break_time: tournament.break_time
21
+ )
22
+ end
23
+ end
24
+
25
+ def initialize(target_match:, matches:, match_time:, break_time:)
26
+ super([target_match] + matches)
27
+ @target_match = target_match
28
+ @matches = matches
29
+ @match_time = match_time
30
+ @break_time = break_time
31
+ @minimum_match_gap = match_time + break_time
32
+ end
33
+
34
+ def satisfies?(assignment)
35
+ return true unless variables.all? { |variable| assignment.key?(variable) }
36
+
37
+ match_time = assignment[target_match].slot
38
+ matches_timeslots = assignment.slice(*matches).values.map(&:slot)
39
+
40
+ matches_timeslots.all? do |timeslot|
41
+ diff_in_minutes = (timeslot - match_time).abs / MINUTE
42
+
43
+ diff_in_minutes >= minimum_match_gap
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ module Constraints
5
+ class NoOverlappingConstraint < ::CSP::Constraint
6
+ attr_reader :matches, :match_time
7
+
8
+ def self.for_tournament(tournament:, csp:)
9
+ csp.add_constraint(
10
+ new(
11
+ matches: tournament.matches.values.flatten,
12
+ match_time: tournament.match_time
13
+ )
14
+ )
15
+ end
16
+
17
+ def initialize(matches:, match_time:)
18
+ super(matches)
19
+ @matches = matches
20
+ @match_time = match_time
21
+ end
22
+
23
+ def satisfies?(assignment)
24
+ return true unless variables.all? { |variable| assignment.key?(variable) }
25
+
26
+ variables
27
+ .map { |variable| assignment[variable] }
28
+ .group_by(&:court)
29
+ .none? { |_, timeslots| timeslots_overlap?(timeslots: timeslots) }
30
+ end
31
+
32
+ private
33
+
34
+ def timeslots_overlap?(timeslots:)
35
+ timeslots
36
+ .map(&:slot)
37
+ .map(&method(:to_range))
38
+ .combination(2)
39
+ .any?(&method(:overlap?))
40
+ end
41
+
42
+ def overlap?(*ranges)
43
+ time_range1, time_range2 = ranges.flatten
44
+ time_range1.cover?(time_range2.first) ||
45
+ time_range2.cover?(time_range1.first)
46
+ end
47
+
48
+ def to_range(value)
49
+ end_value = value + (match_time * 60)
50
+
51
+ (value...end_value)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ # TODO: keep only team
5
+ class DoubleTeam < Team
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ # TODO: change the name Group. In terms of sports tournament it means something else.
5
+ # Public: A category with teams and matches between them
6
+ class Group
7
+ attr_reader :category, :all_matches, :teams
8
+
9
+ def self.for(category:, subscriptions:, matches:, tournament_type:)
10
+ GroupBuilder.new(category: category, subscriptions: subscriptions, matches: matches,
11
+ tournament_type: tournament_type).build
12
+ end
13
+
14
+ def initialize(category: nil, matches: nil, teams: nil)
15
+ @category = category
16
+ @all_matches = matches
17
+ @teams = teams
18
+ end
19
+
20
+ def participants
21
+ @participants ||= teams.map(&:participants).flatten
22
+ end
23
+
24
+ def matches
25
+ @matches ||= all_matches.select(&:playable?)
26
+ end
27
+
28
+ def first_round_matches
29
+ find_matches(0)
30
+ end
31
+
32
+ def find_matches(round_number)
33
+ matches.select { |match| match.round == round_number }
34
+ end
35
+
36
+ def find_participant_matches(participant)
37
+ matches.select do |match|
38
+ match.participants.include? participant
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SportsManager
4
+ # Public: Builds a group with matches, teams, and participants from a payload
5
+ #
6
+ # category - The matches' category.
7
+ # subscriptions - The players subscribed. Doubles are represented as a nested list.
8
+ # matches - A list of matches. Each match is represented by a list of
9
+ # players that will be participaing in it.
10
+ #
11
+ # Example 1: A Team of one player
12
+ # Mixed single category with subscribed players: João, Marcelo, Bruno, and Fábio.
13
+ # The initial matches will be:
14
+ # * João vs Fábio
15
+ # * Marcelo vs Bruno
16
+ #
17
+ # category: :mixed_single
18
+ # subscriptions: [
19
+ # { id: 1, name: "João"},
20
+ # { id: 2, name: "Marcelo" },
21
+ # { id: 15, name: 'Bruno' },
22
+ # { id: 16, name: 'Fábio' }
23
+ # ]
24
+ # matches: [
25
+ # [1,16],
26
+ # [2,15]
27
+ # ]
28
+ #
29
+ # Example 2: A team of two players
30
+ # Women's double category with subscribed players: Laura, Karina, Camila,
31
+ # Bruna, Carolina, Patricia, Jéssica, and Daniela.
32
+ #
33
+ # The initial matches will be:
34
+ # * Laura and Karina vs Jéssica and Daniela
35
+ # * Camila and Bruna vs Carolina and Patricia
36
+ #
37
+ # category: :womens_double
38
+ # subscriptions: [
39
+ # [{ id: 17, name: 'Laura' }, { id: 18, name: 'Karina' }],
40
+ # [{ id: 19, name: 'Camila' }, { id: 20, name: 'Bruna' }],
41
+ # [{ id: 29, name: 'Carolina' }, { id: 30, name: 'Patricia' }],
42
+ # [{ id: 31, name: 'Jéssica' }, { id: 32, name: 'Daniela' }]
43
+ # ]
44
+ # matches: [
45
+ # [[17, 18], [31, 32]],
46
+ # [[19, 20], [29, 30]]
47
+ # ]
48
+ class GroupBuilder
49
+ attr_reader :category, :subscriptions, :matches, :tournament_type
50
+
51
+ def initialize(category:, subscriptions:, matches:, tournament_type:)
52
+ @category = category
53
+ @subscriptions = subscriptions
54
+ @matches = matches
55
+ @tournament_type = tournament_type
56
+ end
57
+
58
+ def build
59
+ Group.new(category: category, matches: builded_matches, teams: teams)
60
+ end
61
+
62
+ private
63
+
64
+ def builded_matches
65
+ MatchBuilder.new(category: category, matches: matches, teams: teams, tournament_type: tournament_type).build
66
+ end
67
+
68
+ def teams
69
+ TeamBuilder.new(category: category, subscriptions: subscriptions).build
70
+ end
71
+ end
72
+ end