basketball 0.0.8 → 0.0.9
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 +4 -4
- data/.rubocop.yml +9 -19
- data/CHANGELOG.md +1 -39
- data/README.md +72 -93
- data/basketball.gemspec +3 -6
- data/exe/{basketball-season-scheduling → basketball-coordinator} +1 -1
- data/exe/{basketball-draft → basketball-room} +1 -1
- data/lib/basketball/app/coordinator_cli.rb +243 -0
- data/lib/basketball/app/coordinator_repository.rb +191 -0
- data/lib/basketball/app/file_store.rb +22 -0
- data/lib/basketball/{draft/cli.rb → app/room_cli.rb} +53 -76
- data/lib/basketball/app/room_repository.rb +189 -0
- data/lib/basketball/app.rb +12 -0
- data/lib/basketball/draft/assessment.rb +31 -0
- data/lib/basketball/draft/event.rb +3 -2
- data/lib/basketball/draft/front_office.rb +35 -28
- data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
- data/lib/basketball/draft/room.rb +119 -119
- data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
- data/lib/basketball/draft/skip.rb +12 -0
- data/lib/basketball/draft.rb +13 -6
- data/lib/basketball/entity.rb +10 -4
- data/lib/basketball/org/league.rb +73 -0
- data/lib/basketball/org/player.rb +26 -0
- data/lib/basketball/{draft → org}/position.rb +3 -2
- data/lib/basketball/org/team.rb +38 -0
- data/lib/basketball/org.rb +12 -0
- data/lib/basketball/season/arena.rb +112 -0
- data/lib/basketball/season/calendar.rb +41 -72
- data/lib/basketball/season/coordinator.rb +185 -126
- data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
- data/lib/basketball/season/game.rb +15 -10
- data/lib/basketball/season/matchup.rb +27 -0
- data/lib/basketball/season/opponent.rb +15 -0
- data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
- data/lib/basketball/season/result.rb +37 -0
- data/lib/basketball/season.rb +12 -13
- data/lib/basketball/value_object.rb +4 -1
- data/lib/basketball/version.rb +1 -1
- data/lib/basketball.rb +9 -4
- metadata +32 -44
- data/lib/basketball/draft/league.rb +0 -70
- data/lib/basketball/draft/player.rb +0 -43
- data/lib/basketball/draft/room_serializer.rb +0 -186
- data/lib/basketball/draft/roster.rb +0 -37
- data/lib/basketball/draft/sim_event.rb +0 -23
- data/lib/basketball/draft/skip_event.rb +0 -13
- data/lib/basketball/season/calendar_serializer.rb +0 -94
- data/lib/basketball/season/conference.rb +0 -57
- data/lib/basketball/season/division.rb +0 -43
- data/lib/basketball/season/league.rb +0 -114
- data/lib/basketball/season/league_serializer.rb +0 -99
- data/lib/basketball/season/scheduling_cli.rb +0 -198
- data/lib/basketball/season/team.rb +0 -21
@@ -1,179 +1,238 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'calendar'
|
4
|
-
|
5
3
|
module Basketball
|
6
4
|
module Season
|
7
|
-
#
|
8
|
-
# play each other. This is a reasonable naive first pass at some underlying match-making algorithms
|
9
|
-
# but could definitely use some help with the complexity/runtime/etc.
|
5
|
+
# Main iterator-based object that knows how to manage a calendar and simulate games per day.
|
10
6
|
class Coordinator
|
11
|
-
|
12
|
-
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
class AlreadyPlayedGameError < StandardError; end
|
10
|
+
class GameNotCurrentError < StandardError; end
|
11
|
+
class OutOfBoundsError < StandardError; end
|
12
|
+
class PlayedGamesError < StandardError; end
|
13
|
+
class UnknownGameError < StandardError; end
|
14
|
+
class UnknownTeamError < StandardError; end
|
15
|
+
class UnplayedGamesError < StandardError; end
|
16
|
+
|
17
|
+
attr_reader :calendar,
|
18
|
+
:current_date,
|
19
|
+
:arena,
|
20
|
+
:results,
|
21
|
+
:league,
|
22
|
+
:id
|
23
|
+
|
24
|
+
def_delegators :calendar,
|
25
|
+
:preseason_start_date,
|
26
|
+
:preseason_end_date,
|
27
|
+
:season_start_date,
|
28
|
+
:season_end_date,
|
29
|
+
:games,
|
30
|
+
:exhibitions_for,
|
31
|
+
:regulars_for,
|
32
|
+
:games_for
|
33
|
+
|
34
|
+
def initialize(
|
35
|
+
calendar:,
|
36
|
+
current_date:,
|
37
|
+
results: [],
|
38
|
+
league: Org::League.new
|
39
|
+
)
|
40
|
+
super()
|
41
|
+
|
42
|
+
raise ArgumentError, 'calendar is required' unless calendar
|
43
|
+
raise ArgumentError, 'current_date is required' if current_date.to_s.empty?
|
44
|
+
raise ArgumentError, 'league is required' unless league
|
45
|
+
|
46
|
+
@calendar = calendar
|
47
|
+
@current_date = current_date
|
48
|
+
@arena = Arena.new
|
49
|
+
@results = []
|
50
|
+
@league = league
|
51
|
+
|
52
|
+
results.each { |result| replay!(result) }
|
53
|
+
|
54
|
+
assert_current_date
|
55
|
+
assert_all_past_dates_are_played
|
56
|
+
assert_all_future_dates_arent_played
|
57
|
+
assert_all_known_teams
|
58
|
+
end
|
13
59
|
|
14
|
-
|
15
|
-
|
60
|
+
def sim_rest!(&)
|
61
|
+
events = []
|
16
62
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
63
|
+
while not_done?
|
64
|
+
new_events = sim!(&)
|
65
|
+
|
66
|
+
events += new_events
|
21
67
|
end
|
22
|
-
end
|
23
68
|
|
24
|
-
|
69
|
+
events
|
70
|
+
end
|
25
71
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
4
|
30
|
-
# Same Conference, Different Division and one of 4/10 that play 3 times
|
31
|
-
elsif league.conference_for(team1) == league.conference_for(team2)
|
32
|
-
3
|
33
|
-
# Different Conference
|
34
|
-
else
|
35
|
-
2
|
72
|
+
def assert_current_date
|
73
|
+
if current_date < preseason_start_date
|
74
|
+
raise OutOfBoundsError, "current date #{current_date} should be on or after #{preseason_start_date}"
|
36
75
|
end
|
76
|
+
|
77
|
+
return unless current_date > season_end_date
|
78
|
+
|
79
|
+
raise OutOfBoundsError, "current date #{current_date} should be on or after #{preseason_start_date}"
|
37
80
|
end
|
38
81
|
|
39
|
-
|
40
|
-
|
41
|
-
def matchup_plan(league)
|
42
|
-
matchups = {}
|
43
|
-
game_counts = league.teams.to_h { |t| [t, 0] }
|
44
|
-
teams = game_counts.keys
|
45
|
-
|
46
|
-
(0...teams.length).each do |i|
|
47
|
-
team1 = teams[i]
|
48
|
-
|
49
|
-
(i + 1...teams.length).each do |j|
|
50
|
-
team2 = teams[j]
|
51
|
-
key = [team1, team2].sort
|
52
|
-
count = base_matchup_count(league, team1, team2)
|
53
|
-
matchups[key] = count
|
54
|
-
game_counts[team1] += count
|
55
|
-
game_counts[team2] += count
|
56
|
-
end
|
57
|
-
end
|
82
|
+
def sim!
|
83
|
+
return [] if done?
|
58
84
|
|
59
|
-
|
60
|
-
|
61
|
-
find_fours(league).each do |team, opponents|
|
62
|
-
next if game_counts[team] == 82
|
85
|
+
events = []
|
86
|
+
games = games_for(date: current_date)
|
63
87
|
|
64
|
-
|
65
|
-
|
66
|
-
|
88
|
+
games.each do |game|
|
89
|
+
home_players = opponent_team(game.home_opponent).players
|
90
|
+
away_players = opponent_team(game.away_opponent).players
|
91
|
+
matchup = Matchup.new(game:, home_players:, away_players:)
|
92
|
+
event = arena.play(matchup)
|
67
93
|
|
68
|
-
|
69
|
-
game_counts[opponent] += 1
|
94
|
+
play!(event)
|
70
95
|
|
71
|
-
|
96
|
+
yield(event) if block_given?
|
72
97
|
|
73
|
-
|
74
|
-
end
|
98
|
+
events << event
|
75
99
|
end
|
76
100
|
|
77
|
-
|
78
|
-
end
|
79
|
-
# rubocop:enable Metrics/AbcSize
|
101
|
+
increment_current_date!
|
80
102
|
|
81
|
-
|
82
|
-
|
83
|
-
# result about 1 out of every 1000 cycles. I have yet to spot the assignment pattern to make
|
84
|
-
# this way more deterministic.
|
85
|
-
def find_fours(league)
|
86
|
-
balanced = false
|
87
|
-
count = 0
|
88
|
-
four_tracker = {}
|
103
|
+
events
|
104
|
+
end
|
89
105
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
raise ArgumentError, 'we spent too much CPU time and didnt resolve fours' if count > 100_000
|
106
|
+
def total_days
|
107
|
+
(season_end_date - preseason_start_date).to_i
|
108
|
+
end
|
94
109
|
|
95
|
-
|
110
|
+
def days_left
|
111
|
+
(season_end_date - current_date).to_i
|
112
|
+
end
|
96
113
|
|
97
|
-
|
98
|
-
|
114
|
+
def total_exhibitions
|
115
|
+
exhibitions_for.length
|
116
|
+
end
|
99
117
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
four_tracker[team] << opponent
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
118
|
+
def exhibitions_left
|
119
|
+
total_exhibitions - exhibition_result_events.length
|
120
|
+
end
|
107
121
|
|
108
|
-
|
122
|
+
def total_regulars
|
123
|
+
regulars_for.length
|
124
|
+
end
|
109
125
|
|
110
|
-
|
111
|
-
|
126
|
+
def regulars_left
|
127
|
+
total_regulars - regular_result_events.length
|
128
|
+
end
|
112
129
|
|
113
|
-
|
130
|
+
def current_games
|
131
|
+
games_for(date: current_date) - results.map(&:game)
|
132
|
+
end
|
114
133
|
|
115
|
-
|
116
|
-
|
134
|
+
def done?
|
135
|
+
current_date == season_end_date && games.length == results.length
|
136
|
+
end
|
117
137
|
|
118
|
-
|
138
|
+
def not_done?
|
139
|
+
!done?
|
119
140
|
end
|
120
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
121
141
|
|
122
|
-
def
|
123
|
-
|
142
|
+
def add!(game)
|
143
|
+
assert_today_or_in_future(game)
|
144
|
+
assert_known_teams(game)
|
124
145
|
|
125
|
-
|
126
|
-
candidates = calendar.available_season_matchup_dates(team1, team2)
|
127
|
-
dates = candidates.sample(count)
|
128
|
-
games = balanced_games(dates, team1, team2)
|
146
|
+
calendar.add!(game)
|
129
147
|
|
130
|
-
|
131
|
-
end
|
148
|
+
self
|
132
149
|
end
|
133
150
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
SeasonGame.new(date:, home_team: team1, away_team: team2)
|
138
|
-
else
|
139
|
-
SeasonGame.new(date:, home_team: team2, away_team: team1)
|
140
|
-
end
|
151
|
+
def result_for(game)
|
152
|
+
results.find do |result|
|
153
|
+
result.game == game
|
141
154
|
end
|
142
155
|
end
|
143
156
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
157
|
+
private
|
158
|
+
|
159
|
+
attr_writer :id, :arena
|
160
|
+
|
161
|
+
def opponent_team(opponent)
|
162
|
+
league.teams.find { |t| t == opponent }
|
163
|
+
end
|
164
|
+
|
165
|
+
def exhibition_result_events
|
166
|
+
results.select { |e| e.game.is_a?(Exhibition) }
|
167
|
+
end
|
148
168
|
|
149
|
-
|
169
|
+
def regular_result_events
|
170
|
+
results.select { |e| e.game.is_a?(Regular) }
|
171
|
+
end
|
150
172
|
|
151
|
-
|
173
|
+
def increment_current_date!
|
174
|
+
return self if current_date >= season_end_date
|
152
175
|
|
153
|
-
|
154
|
-
break if count > MIN_PRESEASON_GAMES_PER_TEAM
|
155
|
-
next if calendar.preseason_games_for(team: other_team).length >= MAX_PRESEASON_GAMES_PER_TEAM
|
176
|
+
@current_date = current_date + 1
|
156
177
|
|
157
|
-
|
178
|
+
self
|
179
|
+
end
|
158
180
|
|
159
|
-
|
181
|
+
def assert_today_or_in_future(game)
|
182
|
+
return unless game.date <= current_date
|
160
183
|
|
161
|
-
|
162
|
-
|
184
|
+
raise OutOfBoundsError, "#{game.date} is on or before the current date (#{current_date})"
|
185
|
+
end
|
163
186
|
|
164
|
-
|
187
|
+
def assert_known_teams(game)
|
188
|
+
raise UnknownTeamError, "unknown opponent: #{game.home_opponent}" if league.not_registered?(game.home_opponent)
|
165
189
|
|
166
|
-
|
167
|
-
|
168
|
-
|
190
|
+
return unless league.not_registered?(game.away_opponent)
|
191
|
+
|
192
|
+
raise UnknownTeamError, "unknown opponent: #{game.away_opponent}"
|
169
193
|
end
|
170
194
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
195
|
+
def assert_all_past_dates_are_played
|
196
|
+
games_that_should_be_played = games.select { |game| game.date < current_date }
|
197
|
+
|
198
|
+
games_played = results.map(&:game)
|
199
|
+
unplayed_games = games_that_should_be_played - games_played
|
200
|
+
|
201
|
+
return if unplayed_games.empty?
|
202
|
+
|
203
|
+
raise UnplayedGamesError, "#{unplayed_games.length} game(s) not played before #{current_date}"
|
204
|
+
end
|
205
|
+
|
206
|
+
def assert_all_future_dates_arent_played
|
207
|
+
games_that_shouldnt_be_played = results.select do |result|
|
208
|
+
result.date > current_date
|
176
209
|
end
|
210
|
+
|
211
|
+
count = games_that_shouldnt_be_played.length
|
212
|
+
|
213
|
+
return unless games_that_shouldnt_be_played.any?
|
214
|
+
|
215
|
+
raise PlayedGamesError, "#{count} game(s) played after #{current_date}"
|
216
|
+
end
|
217
|
+
|
218
|
+
def play!(result)
|
219
|
+
raise GameNotCurrentError, "#{result} is not for #{current_date}" if result.date != current_date
|
220
|
+
|
221
|
+
replay!(result)
|
222
|
+
end
|
223
|
+
|
224
|
+
def replay!(result)
|
225
|
+
raise AlreadyPlayedGameError, "#{result.game} already played!" if result_for(result.game)
|
226
|
+
|
227
|
+
raise UnknownGameError, "game not added: #{result.game}" unless games.include?(result.game)
|
228
|
+
|
229
|
+
results << result
|
230
|
+
|
231
|
+
result
|
232
|
+
end
|
233
|
+
|
234
|
+
def assert_all_known_teams
|
235
|
+
calendar.games.each { |game| assert_known_teams(game) }
|
177
236
|
end
|
178
237
|
end
|
179
238
|
end
|
@@ -2,30 +2,35 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Season
|
5
|
+
# Base class describing what all games have in common.
|
5
6
|
class Game < ValueObject
|
6
|
-
|
7
|
+
value_reader :date, :home_opponent, :away_opponent
|
7
8
|
|
8
|
-
def initialize(date:,
|
9
|
+
def initialize(date:, home_opponent:, away_opponent:)
|
9
10
|
super()
|
10
11
|
|
11
12
|
raise ArgumentError, 'date is required' unless date
|
12
|
-
raise ArgumentError, '
|
13
|
-
raise ArgumentError, '
|
14
|
-
raise ArgumentError, 'teams cannot play themselves' if
|
13
|
+
raise ArgumentError, 'home_opponent is required' unless home_opponent
|
14
|
+
raise ArgumentError, 'away_opponent is required' unless away_opponent
|
15
|
+
raise ArgumentError, 'teams cannot play themselves' if home_opponent == away_opponent
|
15
16
|
|
16
|
-
@date
|
17
|
-
@
|
18
|
-
@
|
17
|
+
@date = date
|
18
|
+
@home_opponent = home_opponent
|
19
|
+
@away_opponent = away_opponent
|
19
20
|
|
20
21
|
freeze
|
21
22
|
end
|
22
23
|
|
24
|
+
def for?(team)
|
25
|
+
teams.include?(team)
|
26
|
+
end
|
27
|
+
|
23
28
|
def teams
|
24
|
-
[
|
29
|
+
[home_opponent, away_opponent]
|
25
30
|
end
|
26
31
|
|
27
32
|
def to_s
|
28
|
-
"#{date} - #{
|
33
|
+
"#{date} - #{away_opponent} at #{home_opponent}"
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Season
|
5
|
+
# A Matchup is a late materialization of a game. While a game is a skeleton for a future
|
6
|
+
# matchup, it does not materialize until game time (rosters could change right up until game time).
|
7
|
+
class Matchup
|
8
|
+
class PlayersOnBothTeamsError < StandardError; end
|
9
|
+
|
10
|
+
attr_reader :game, :home_players, :away_players
|
11
|
+
|
12
|
+
def initialize(game:, home_players: [], away_players: [])
|
13
|
+
raise ArgumentError, 'game is required' unless game
|
14
|
+
|
15
|
+
@game = game
|
16
|
+
@home_players = home_players.uniq
|
17
|
+
@away_players = away_players.uniq
|
18
|
+
|
19
|
+
if home_players.intersect?(away_players)
|
20
|
+
raise PlayersOnBothTeamsError, 'players cannot be on both home and away team'
|
21
|
+
end
|
22
|
+
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Season
|
5
|
+
# Represents a team without a roster. Equal to a team by identity.
|
6
|
+
# A team's roster will not be known until the last minute (when it is game time).
|
7
|
+
class Opponent < Entity
|
8
|
+
def initialize(id:)
|
9
|
+
super(id)
|
10
|
+
|
11
|
+
freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Season
|
5
|
+
# Base class describing the end result of a game. This should/could be sub-classed to include
|
6
|
+
# more sport-specific information.
|
7
|
+
class Result
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
class CannotTieError < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :game, :home_score, :away_score
|
13
|
+
|
14
|
+
def_delegators :game, :date, :home_opponent, :away_opponent, :teams
|
15
|
+
|
16
|
+
def initialize(game:, home_score:, away_score:)
|
17
|
+
raise ArgumentError, 'game is required' unless game
|
18
|
+
raise CannotTieError, "#{game} ended in a tie" if home_score == away_score
|
19
|
+
|
20
|
+
@game = game
|
21
|
+
@home_score = home_score.to_i
|
22
|
+
@away_score = away_score.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{game.date} - #{away_opponent} (#{away_score}) at #{home_opponent} (#{home_score})"
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
game == other.game &&
|
31
|
+
home_score == other.home_score &&
|
32
|
+
away_score == other.away_score
|
33
|
+
end
|
34
|
+
alias eql? ==
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/basketball/season.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# Common
|
4
|
+
require_relative 'season/arena'
|
5
|
+
require_relative 'season/calendar'
|
6
|
+
require_relative 'season/result'
|
7
|
+
require_relative 'season/game'
|
8
|
+
require_relative 'season/matchup'
|
9
|
+
require_relative 'season/opponent'
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class DivisionAlreadyRegisteredError < StandardError; end
|
9
|
-
class TeamAlreadyRegisteredError < StandardError; end
|
11
|
+
# Game Subclasses
|
12
|
+
require_relative 'season/exhibition'
|
13
|
+
require_relative 'season/regular'
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
class BadTeamsSizeError < StandardError; end
|
14
|
-
|
15
|
-
class UnknownTeamError < StandardError; end
|
16
|
-
end
|
17
|
-
end
|
15
|
+
# Specific
|
16
|
+
require_relative 'season/coordinator'
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Basketball
|
4
|
+
# A Value Object is something that has no specific identity, instead its identity is the sum of
|
5
|
+
# the attribute values. Changing one will change the entire object's identity.
|
6
|
+
# Comes with a very simple DSL for specifying properties along with base equality and sorting methods.
|
4
7
|
class ValueObject
|
5
8
|
include Comparable
|
6
9
|
|
@@ -23,7 +26,7 @@ module Basketball
|
|
23
26
|
@value_keys ||= []
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
29
|
+
def value_reader(*keys)
|
27
30
|
keys.each { |k| value_keys << k.to_sym }
|
28
31
|
|
29
32
|
attr_reader(*keys)
|
data/lib/basketball/version.rb
CHANGED
data/lib/basketball.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'date'
|
4
4
|
require 'fileutils'
|
5
5
|
require 'forwardable'
|
6
6
|
require 'json'
|
7
|
-
require 'securerandom'
|
8
7
|
require 'slop'
|
9
8
|
|
10
|
-
#
|
9
|
+
# Generic
|
11
10
|
require_relative 'basketball/entity'
|
12
11
|
require_relative 'basketball/value_object'
|
13
12
|
|
14
|
-
#
|
13
|
+
# Dependent on Generic
|
14
|
+
require_relative 'basketball/org'
|
15
|
+
|
16
|
+
# Dependent on Org
|
15
17
|
require_relative 'basketball/draft'
|
16
18
|
require_relative 'basketball/season'
|
19
|
+
|
20
|
+
# Dependent on All
|
21
|
+
require_relative 'basketball/app'
|