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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +32 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +72 -0
- data/MIT-LICENSE +20 -0
- data/README.md +419 -0
- data/Rakefile +4 -0
- data/lib/sports-manager.rb +59 -0
- data/lib/sports_manager/algorithms/filtering/no_overlap.rb +52 -0
- data/lib/sports_manager/algorithms/ordering/multiple_matches_participant.rb +78 -0
- data/lib/sports_manager/bye_match.rb +62 -0
- data/lib/sports_manager/constraint_builder.rb +30 -0
- data/lib/sports_manager/constraints/all_different_constraint.rb +24 -0
- data/lib/sports_manager/constraints/match_constraint.rb +37 -0
- data/lib/sports_manager/constraints/multi_category_constraint.rb +49 -0
- data/lib/sports_manager/constraints/next_round_constraint.rb +48 -0
- data/lib/sports_manager/constraints/no_overlapping_constraint.rb +55 -0
- data/lib/sports_manager/double_team.rb +7 -0
- data/lib/sports_manager/group.rb +42 -0
- data/lib/sports_manager/group_builder.rb +72 -0
- data/lib/sports_manager/helper.rb +228 -0
- data/lib/sports_manager/json_helper.rb +129 -0
- data/lib/sports_manager/match.rb +91 -0
- data/lib/sports_manager/match_builder.rb +112 -0
- data/lib/sports_manager/matches/algorithms/single_elimination_algorithm.rb +94 -0
- data/lib/sports_manager/matches/next_round.rb +38 -0
- data/lib/sports_manager/matches_generator.rb +33 -0
- data/lib/sports_manager/nil_team.rb +24 -0
- data/lib/sports_manager/participant.rb +23 -0
- data/lib/sports_manager/single_team.rb +7 -0
- data/lib/sports_manager/solution_drawer/cli/solution_table.rb +38 -0
- data/lib/sports_manager/solution_drawer/cli/table.rb +94 -0
- data/lib/sports_manager/solution_drawer/cli.rb +75 -0
- data/lib/sports_manager/solution_drawer/mermaid/bye_node.rb +39 -0
- data/lib/sports_manager/solution_drawer/mermaid/gantt.rb +126 -0
- data/lib/sports_manager/solution_drawer/mermaid/graph.rb +111 -0
- data/lib/sports_manager/solution_drawer/mermaid/node.rb +55 -0
- data/lib/sports_manager/solution_drawer/mermaid/node_style.rb +89 -0
- data/lib/sports_manager/solution_drawer/mermaid/solution_gantt.rb +57 -0
- data/lib/sports_manager/solution_drawer/mermaid/solution_graph.rb +76 -0
- data/lib/sports_manager/solution_drawer/mermaid.rb +65 -0
- data/lib/sports_manager/solution_drawer.rb +23 -0
- data/lib/sports_manager/team.rb +47 -0
- data/lib/sports_manager/team_builder.rb +31 -0
- data/lib/sports_manager/timeslot.rb +37 -0
- data/lib/sports_manager/timeslot_builder.rb +50 -0
- data/lib/sports_manager/tournament/setting.rb +45 -0
- data/lib/sports_manager/tournament.rb +69 -0
- data/lib/sports_manager/tournament_builder.rb +123 -0
- data/lib/sports_manager/tournament_day/validator.rb +69 -0
- data/lib/sports_manager/tournament_day.rb +50 -0
- data/lib/sports_manager/tournament_generator.rb +183 -0
- data/lib/sports_manager/tournament_problem_builder.rb +106 -0
- data/lib/sports_manager/tournament_solution/bye_fixture.rb +21 -0
- data/lib/sports_manager/tournament_solution/fixture.rb +39 -0
- data/lib/sports_manager/tournament_solution/serializer.rb +107 -0
- data/lib/sports_manager/tournament_solution/solution.rb +85 -0
- data/lib/sports_manager/tournament_solution.rb +34 -0
- data/lib/sports_manager/version.rb +5 -0
- data/sports-manager.gemspec +35 -0
- metadata +120 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class MatchesGenerator
|
5
|
+
def self.call(subscriptions_ids)
|
6
|
+
new(subscriptions_ids).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(subscriptions_ids)
|
10
|
+
@subscriptions_ids = subscriptions_ids
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
generate_matches(@subscriptions_ids)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :subscriptions
|
20
|
+
|
21
|
+
def generate_matches(subscriptions_ids)
|
22
|
+
list = subscriptions_ids.dup
|
23
|
+
size = subscriptions_ids.size
|
24
|
+
|
25
|
+
number_of_matches = size.even? ? (size / 2) : ((size / 2) + 1)
|
26
|
+
|
27
|
+
number_of_matches.times.map do
|
28
|
+
match = [list.shift, list.pop].compact
|
29
|
+
match unless match.empty?
|
30
|
+
end.compact
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
# Public: A representation of no Team used by next round matches that don't
|
5
|
+
# have a winner set yet.
|
6
|
+
class NilTeam
|
7
|
+
attr_reader :category, :participants
|
8
|
+
|
9
|
+
def initialize(category:)
|
10
|
+
@category = category
|
11
|
+
@participants = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
''
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
return false unless instance_of? other.class
|
20
|
+
|
21
|
+
category == other.category
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
# Public: A team's player in a tournament
|
5
|
+
class Participant
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :id, :name
|
9
|
+
|
10
|
+
alias eql? <=>
|
11
|
+
|
12
|
+
def initialize(id:, name:)
|
13
|
+
@id = id
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
return unless instance_of?(other.class)
|
19
|
+
|
20
|
+
id <=> other.id
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class CLI
|
6
|
+
class SolutionTable
|
7
|
+
TIME_TEMPLATE = '%d/%m at %H:%M'
|
8
|
+
|
9
|
+
attr_reader :fixtures
|
10
|
+
|
11
|
+
def initialize(solution)
|
12
|
+
@fixtures = solution.fixtures
|
13
|
+
end
|
14
|
+
|
15
|
+
def draw
|
16
|
+
Table.draw(formatted_fixtures)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def formatted_fixtures
|
22
|
+
fixtures.map(&method(:serialized_fixture))
|
23
|
+
end
|
24
|
+
|
25
|
+
def serialized_fixture(fixture)
|
26
|
+
{
|
27
|
+
category: fixture.category,
|
28
|
+
id: fixture.match_id,
|
29
|
+
round: fixture.round,
|
30
|
+
participants: fixture.title,
|
31
|
+
court: fixture.court,
|
32
|
+
time: fixture.slot.strftime(TIME_TEMPLATE)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class CLI
|
6
|
+
# Public: creates a table based on a list of hashes containing the
|
7
|
+
# name of the columns and their values
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# data = [
|
11
|
+
# { a: [1, 2, 3], b: 10_000, c: 'ABCDEFGHIJKLMNOP' },
|
12
|
+
# { a: [4, 5, 6, 7], b: 20_000_000, c: 'QRSTUVWXYZ' }
|
13
|
+
# ]
|
14
|
+
#
|
15
|
+
# a | b | c
|
16
|
+
# -------------|----------|-----------------
|
17
|
+
# 1, 2, 3 | 10000 | ABCDEFGHIJKLMNOP
|
18
|
+
# 4, 5, 6, 7 | 20000000 | QRSTUVWXYZ
|
19
|
+
class Table
|
20
|
+
attr_reader :data
|
21
|
+
|
22
|
+
SECTION_SEPARATOR = '-|-'
|
23
|
+
COLUMN_SEPARATOR = ' | '
|
24
|
+
ROW_SEPARATOR = '-'
|
25
|
+
|
26
|
+
def self.draw(data)
|
27
|
+
new(data).draw
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(data)
|
31
|
+
@data = data
|
32
|
+
end
|
33
|
+
|
34
|
+
def draw
|
35
|
+
[header_row, separator, *content_rows].join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def header
|
41
|
+
@header ||= data.flat_map(&:keys).uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def header_row
|
45
|
+
header
|
46
|
+
.map { |column| column.to_s.center(widths[column]) }
|
47
|
+
.join(COLUMN_SEPARATOR)
|
48
|
+
end
|
49
|
+
|
50
|
+
def separator
|
51
|
+
header
|
52
|
+
.map { |column| ROW_SEPARATOR * widths[column] }
|
53
|
+
.join(SECTION_SEPARATOR)
|
54
|
+
end
|
55
|
+
|
56
|
+
def content_rows
|
57
|
+
data
|
58
|
+
.map(&method(:row_content))
|
59
|
+
.map { |row| row.join(COLUMN_SEPARATOR) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def row_content(row)
|
63
|
+
row.map do |(column, value)|
|
64
|
+
column_padding = widths[column]
|
65
|
+
|
66
|
+
Array(value)
|
67
|
+
.join(', ')
|
68
|
+
.ljust(column_padding)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Internal: sets the longest width for each column
|
73
|
+
def widths
|
74
|
+
@widths ||= data
|
75
|
+
.flat_map(&:to_a)
|
76
|
+
.each_with_object(headers_width) do |(key, value), columns_width|
|
77
|
+
column_size = columns_width[key].to_i
|
78
|
+
value_size = value.to_s.size
|
79
|
+
|
80
|
+
columns_width[key] = [column_size, value_size].max
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: sets the starting width for each column based on
|
85
|
+
# their names
|
86
|
+
def headers_width
|
87
|
+
header.each_with_object({}) do |column, width|
|
88
|
+
width[column.to_sym] = column.to_s.size
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
# Public: Outputs the tournament's timetable solutions to stdout
|
6
|
+
# TODO: allow to pass which stdout will be used. puts|Logger|other
|
7
|
+
class CLI
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :tournament_solution
|
11
|
+
|
12
|
+
def_delegators :tournament_solution, :solutions,
|
13
|
+
:total_solutions, :solved?
|
14
|
+
|
15
|
+
NO_SOLUTION = 'No solution found'
|
16
|
+
START_NUMBER = 1
|
17
|
+
|
18
|
+
def self.draw(tournament_solution)
|
19
|
+
new(tournament_solution).draw
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(tournament_solution)
|
23
|
+
@tournament_solution = tournament_solution
|
24
|
+
end
|
25
|
+
|
26
|
+
def draw
|
27
|
+
no_solution || draw_solutions
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def no_solution
|
33
|
+
return false if solved?
|
34
|
+
|
35
|
+
puts NO_SOLUTION
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw_solutions
|
41
|
+
puts(
|
42
|
+
output_header,
|
43
|
+
output_solutions,
|
44
|
+
output_footer
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def output_header
|
49
|
+
"Tournament Timetable:\n\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def output_solutions
|
53
|
+
solutions_tables.join("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def output_footer
|
57
|
+
"Total solutions: #{total_solutions}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def solutions_tables
|
61
|
+
solutions.map.with_index(START_NUMBER, &method(:draw_solution))
|
62
|
+
end
|
63
|
+
|
64
|
+
def draw_solution(solution, number)
|
65
|
+
table_data = SolutionTable.new(solution).draw
|
66
|
+
|
67
|
+
<<~MESSAGE
|
68
|
+
Solution #{number}
|
69
|
+
#{table_data}
|
70
|
+
|
71
|
+
MESSAGE
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class Mermaid
|
6
|
+
class ByeNode
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :fixture
|
10
|
+
|
11
|
+
def_delegators :fixture, :match_id, :category, :title
|
12
|
+
|
13
|
+
def initialize(fixture)
|
14
|
+
@fixture = fixture
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
"#{category}_#{match_id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def definition
|
22
|
+
"#{name}[\"#{description}\"]:::#{style_class}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def style_class
|
26
|
+
'court'
|
27
|
+
end
|
28
|
+
|
29
|
+
def description
|
30
|
+
"#{match_id}\\n#{title}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def links?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class Mermaid
|
6
|
+
class Gantt
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :configurations
|
10
|
+
|
11
|
+
DISPLAY_MODE = {
|
12
|
+
compact: 'compact',
|
13
|
+
empty: nil
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
FIELDS = %i[
|
17
|
+
display_mode
|
18
|
+
title
|
19
|
+
date_format
|
20
|
+
axis_format
|
21
|
+
tick_interval
|
22
|
+
sections
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
DEFAULT_CONFIGURATIONS = {
|
26
|
+
display_mode: :compact,
|
27
|
+
title: 'Tournament Schedule',
|
28
|
+
date_format: 'DD/MM HH:mm',
|
29
|
+
axis_format: '%H:%M',
|
30
|
+
tick_interval: '1hour',
|
31
|
+
sections: ''
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
TEMPLATE = <<~GANTT.chomp
|
35
|
+
---
|
36
|
+
displayMode: %<display_mode>s
|
37
|
+
---
|
38
|
+
gantt
|
39
|
+
title %<title>s
|
40
|
+
dateFormat %<date_format>s
|
41
|
+
axisFormat %<axis_format>s
|
42
|
+
tickInterval %<tick_interval>s
|
43
|
+
|
44
|
+
%<sections>s
|
45
|
+
GANTT
|
46
|
+
|
47
|
+
TASK_TEMPLATE = ' %<id>s: %<time>s, %<interval>s'
|
48
|
+
|
49
|
+
def self.draw(configurations = {})
|
50
|
+
new
|
51
|
+
.add_configurations(configurations)
|
52
|
+
.draw
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@configurations = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def draw
|
60
|
+
format(TEMPLATE, gantt_configurations)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_display_mode(mode = :compact)
|
64
|
+
configurations[:display_mode] = DISPLAY_MODE[mode.to_sym]
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_title(title)
|
70
|
+
configurations[:title] = title
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_date_format(format)
|
76
|
+
configurations[:date_format] = format
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_axis_format(format)
|
82
|
+
configurations[:axis_format] = format
|
83
|
+
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_tick_interval(format)
|
88
|
+
configurations[:tick_interval] = format
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_sections(sections_tasks)
|
94
|
+
configurations[:sections] = sections_tasks
|
95
|
+
.map { |(section, tasks)| build_section(section, tasks) }
|
96
|
+
.join("\n")
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_configurations(configurations)
|
102
|
+
configurations.slice(*FIELDS).each do |field, value|
|
103
|
+
public_send("add_#{field}", value)
|
104
|
+
end
|
105
|
+
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def gantt_configurations
|
112
|
+
DEFAULT_CONFIGURATIONS
|
113
|
+
.merge(configurations)
|
114
|
+
.slice(*FIELDS)
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_section(section, tasks)
|
118
|
+
section_name = " section #{section}"
|
119
|
+
section_tasks = tasks.map { |fields| format(TASK_TEMPLATE, fields) }
|
120
|
+
|
121
|
+
[section_name, section_tasks].join("\n")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class Mermaid
|
6
|
+
class Graph
|
7
|
+
attr_reader :configurations
|
8
|
+
|
9
|
+
FIELDS = %i[
|
10
|
+
subgraphs
|
11
|
+
class_definitions
|
12
|
+
subgraph_colorscheme
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
TEMPLATE = <<~GRAPH.chomp
|
16
|
+
graph LR
|
17
|
+
%<class_definitions>s
|
18
|
+
%<subgraph_colorscheme>s
|
19
|
+
%<subgraphs>s
|
20
|
+
GRAPH
|
21
|
+
|
22
|
+
SUBGRAPH = <<~GRAPH.chomp
|
23
|
+
subgraph %<name>s
|
24
|
+
direction LR
|
25
|
+
|
26
|
+
%<nodes>s
|
27
|
+
end
|
28
|
+
GRAPH
|
29
|
+
|
30
|
+
STYLE = <<~GRAPH.chomp
|
31
|
+
classDef %<class_definition>s
|
32
|
+
GRAPH
|
33
|
+
|
34
|
+
# TODO: move to class and make dynamic based on input
|
35
|
+
DEFAULT_STYLE_CONFIGURATIONS = {
|
36
|
+
class_definitions: [
|
37
|
+
'court0 fill:#a9f9a9',
|
38
|
+
'court1 fill:#4ff7de',
|
39
|
+
'court_default fill:#aff7de'
|
40
|
+
],
|
41
|
+
subgraph_colorscheme: [
|
42
|
+
'COURT_0:::court0',
|
43
|
+
'COURT_1:::court1',
|
44
|
+
'COURT:::court_default',
|
45
|
+
'COURT_0 --- COURT_1 --- COURT'
|
46
|
+
]
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
def self.draw(configurations = {})
|
50
|
+
new
|
51
|
+
.add_configurations(configurations)
|
52
|
+
.draw
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@configurations = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def draw
|
60
|
+
format(TEMPLATE, graph_configurations)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_configurations(configurations)
|
64
|
+
DEFAULT_STYLE_CONFIGURATIONS
|
65
|
+
.merge(configurations)
|
66
|
+
.slice(*FIELDS)
|
67
|
+
.each { |field, value| public_send("add_#{field}", value) }
|
68
|
+
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_subgraphs(subgraphs)
|
73
|
+
configurations[:subgraphs] = subgraphs
|
74
|
+
.map { |(name, nodes)| build_subgraph(name, nodes) }
|
75
|
+
.join("\n")
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_class_definitions(class_definitions)
|
81
|
+
configurations[:class_definitions] = class_definitions
|
82
|
+
.map { |class_definition| format(STYLE, class_definition: class_definition) }
|
83
|
+
.join("\n")
|
84
|
+
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_subgraph_colorscheme(subgraph_colorscheme)
|
89
|
+
nodes = subgraph_colorscheme.join("\n ")
|
90
|
+
colorscheme = format(SUBGRAPH, name: 'colorscheme', nodes: nodes)
|
91
|
+
|
92
|
+
configurations[:subgraph_colorscheme] = colorscheme
|
93
|
+
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def graph_configurations
|
100
|
+
configurations.slice(*FIELDS)
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_subgraph(name, nodes)
|
104
|
+
subgraph_nodes = nodes.join("\n ")
|
105
|
+
|
106
|
+
format(SUBGRAPH, name: name, nodes: subgraph_nodes)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SportsManager
|
4
|
+
class SolutionDrawer
|
5
|
+
class Mermaid
|
6
|
+
class Node
|
7
|
+
extend Forwardable
|
8
|
+
TIME_TEMPLATE = '%d/%m %H:%M'
|
9
|
+
|
10
|
+
attr_reader :fixture
|
11
|
+
|
12
|
+
def_delegators :fixture, :match_id, :category, :title, :court
|
13
|
+
|
14
|
+
def self.for(fixture)
|
15
|
+
node_class = fixture.playable? ? self : ByeNode
|
16
|
+
|
17
|
+
node_class.new(fixture)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(fixture)
|
21
|
+
@fixture = fixture
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
"#{category}_#{match_id}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: my_node[This is a node]:::awesome_style
|
29
|
+
def definition
|
30
|
+
"#{name}[#{description}]:::#{style_class}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def style_class
|
34
|
+
"court#{court}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
"#{match_id}\\n#{title}\\n#{slot}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def slot
|
42
|
+
fixture.slot.strftime(TIME_TEMPLATE)
|
43
|
+
end
|
44
|
+
|
45
|
+
def links?
|
46
|
+
fixture.dependencies?
|
47
|
+
end
|
48
|
+
|
49
|
+
def depends_on?(node)
|
50
|
+
fixture.depends_on?(node.fixture)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|