basketball 0.0.7 → 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 -31
- data/README.md +72 -91
- data/basketball.gemspec +3 -6
- data/exe/{basketball-schedule → 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/app/room_cli.rb +212 -0
- 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/{drafting → draft}/event.rb +4 -3
- data/lib/basketball/draft/front_office.rb +99 -0
- data/lib/basketball/draft/pick.rb +32 -0
- data/lib/basketball/draft/room.rb +221 -0
- data/lib/basketball/{drafting/player_search.rb → draft/scout.rb} +5 -10
- data/lib/basketball/draft/skip.rb +12 -0
- data/lib/basketball/draft.rb +16 -0
- 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/{drafting → 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 +90 -0
- data/lib/basketball/season/coordinator.rb +239 -0
- data/lib/basketball/{scheduling/preseason_game.rb → season/exhibition.rb} +3 -2
- data/lib/basketball/season/game.rb +37 -0
- data/lib/basketball/season/matchup.rb +27 -0
- data/lib/basketball/season/opponent.rb +15 -0
- data/lib/basketball/season/regular.rb +9 -0
- data/lib/basketball/season/result.rb +37 -0
- data/lib/basketball/season.rb +16 -0
- data/lib/basketball/value_object.rb +4 -1
- data/lib/basketball/version.rb +1 -1
- data/lib/basketball.rb +11 -6
- metadata +40 -52
- data/lib/basketball/drafting/cli.rb +0 -235
- data/lib/basketball/drafting/engine.rb +0 -221
- data/lib/basketball/drafting/engine_serializer.rb +0 -186
- data/lib/basketball/drafting/front_office.rb +0 -92
- data/lib/basketball/drafting/league.rb +0 -70
- data/lib/basketball/drafting/pick_event.rb +0 -25
- data/lib/basketball/drafting/player.rb +0 -43
- data/lib/basketball/drafting/roster.rb +0 -37
- data/lib/basketball/drafting/sim_event.rb +0 -23
- data/lib/basketball/drafting/skip_event.rb +0 -13
- data/lib/basketball/drafting.rb +0 -9
- data/lib/basketball/scheduling/calendar.rb +0 -121
- data/lib/basketball/scheduling/calendar_serializer.rb +0 -94
- data/lib/basketball/scheduling/cli.rb +0 -198
- data/lib/basketball/scheduling/conference.rb +0 -57
- data/lib/basketball/scheduling/coordinator.rb +0 -180
- data/lib/basketball/scheduling/division.rb +0 -43
- data/lib/basketball/scheduling/game.rb +0 -32
- data/lib/basketball/scheduling/league.rb +0 -114
- data/lib/basketball/scheduling/league_serializer.rb +0 -99
- data/lib/basketball/scheduling/season_game.rb +0 -8
- data/lib/basketball/scheduling/team.rb +0 -21
- data/lib/basketball/scheduling.rb +0 -17
@@ -1,186 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'front_office'
|
4
|
-
require_relative 'player'
|
5
|
-
require_relative 'pick_event'
|
6
|
-
require_relative 'sim_event'
|
7
|
-
require_relative 'skip_event'
|
8
|
-
|
9
|
-
module Basketball
|
10
|
-
module Drafting
|
11
|
-
class EngineSerializer
|
12
|
-
EVENT_CLASSES = {
|
13
|
-
'PickEvent' => PickEvent,
|
14
|
-
'SimEvent' => SimEvent,
|
15
|
-
'SkipEvent' => SkipEvent
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
private_constant :EVENT_CLASSES
|
19
|
-
|
20
|
-
def to_hash(engine)
|
21
|
-
{
|
22
|
-
'info' => serialize_info(engine),
|
23
|
-
'engine' => serialize_engine(engine),
|
24
|
-
'league' => serialize_league(engine)
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
|
-
def from_hash(json)
|
29
|
-
front_offices = deserialize_front_offices(json)
|
30
|
-
players = deserialize_players(json)
|
31
|
-
events = deserialize_events(json, players, front_offices)
|
32
|
-
|
33
|
-
engine_opts = {
|
34
|
-
players:,
|
35
|
-
front_offices:,
|
36
|
-
events:
|
37
|
-
}
|
38
|
-
|
39
|
-
engine_opts[:rounds] = json.dig('engine', 'rounds') if json.dig('engine', 'rounds')
|
40
|
-
|
41
|
-
Engine.new(**engine_opts)
|
42
|
-
end
|
43
|
-
|
44
|
-
def deserialize(string)
|
45
|
-
json = JSON.parse(string)
|
46
|
-
|
47
|
-
from_hash(json)
|
48
|
-
end
|
49
|
-
|
50
|
-
def serialize(engine)
|
51
|
-
to_hash(engine).to_json
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def serialize_engine(engine)
|
57
|
-
{
|
58
|
-
'rounds' => engine.rounds,
|
59
|
-
'front_offices' => serialize_front_offices(engine),
|
60
|
-
'players' => serialize_players(engine),
|
61
|
-
'events' => serialize_events(engine.events)
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def serialize_info(engine)
|
66
|
-
{
|
67
|
-
'total_picks' => engine.total_picks,
|
68
|
-
'current_round' => engine.current_round,
|
69
|
-
'current_round_pick' => engine.current_round_pick,
|
70
|
-
'current_front_office' => engine.current_front_office&.id,
|
71
|
-
'current_pick' => engine.current_pick,
|
72
|
-
'remaining_picks' => engine.remaining_picks,
|
73
|
-
'done' => engine.done?
|
74
|
-
}
|
75
|
-
end
|
76
|
-
|
77
|
-
def serialize_league(engine)
|
78
|
-
league = engine.to_league
|
79
|
-
|
80
|
-
rosters = league.rosters.to_h do |roster|
|
81
|
-
[
|
82
|
-
roster.id,
|
83
|
-
{
|
84
|
-
'players' => roster.players.map(&:id)
|
85
|
-
}
|
86
|
-
]
|
87
|
-
end
|
88
|
-
|
89
|
-
{
|
90
|
-
'free_agents' => league.free_agents.map(&:id),
|
91
|
-
'rosters' => rosters
|
92
|
-
}
|
93
|
-
end
|
94
|
-
|
95
|
-
def serialize_front_offices(engine)
|
96
|
-
engine.front_offices.to_h do |front_office|
|
97
|
-
[
|
98
|
-
front_office.id,
|
99
|
-
{
|
100
|
-
'name' => front_office.name,
|
101
|
-
'fuzz' => front_office.fuzz,
|
102
|
-
'depth' => front_office.depth,
|
103
|
-
'prioritized_positions' => front_office.prioritized_positions.map(&:code)
|
104
|
-
}
|
105
|
-
]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def serialize_players(engine)
|
110
|
-
engine.players.to_h do |player|
|
111
|
-
[
|
112
|
-
player.id,
|
113
|
-
{
|
114
|
-
'first_name' => player.first_name,
|
115
|
-
'last_name' => player.last_name,
|
116
|
-
'overall' => player.overall,
|
117
|
-
'position' => player.position.code
|
118
|
-
}
|
119
|
-
]
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def serialize_events(events)
|
124
|
-
events.map do |event|
|
125
|
-
{
|
126
|
-
'type' => event.class.name.split('::').last,
|
127
|
-
'front_office' => event.front_office.id,
|
128
|
-
'pick' => event.pick,
|
129
|
-
'round' => event.round,
|
130
|
-
'round_pick' => event.round_pick
|
131
|
-
}.tap do |hash|
|
132
|
-
hash['player'] = event.player.id if event.respond_to?(:player)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def deserialize_front_offices(json)
|
138
|
-
(json.dig('engine', 'front_offices') || []).map do |id, front_office_hash|
|
139
|
-
prioritized_positions = (front_office_hash['prioritized_positions'] || []).map do |v|
|
140
|
-
Position.new(v)
|
141
|
-
end
|
142
|
-
|
143
|
-
front_office_opts = {
|
144
|
-
id:,
|
145
|
-
name: front_office_hash['name'],
|
146
|
-
prioritized_positions:,
|
147
|
-
fuzz: front_office_hash['fuzz'],
|
148
|
-
depth: front_office_hash['depth']
|
149
|
-
}
|
150
|
-
|
151
|
-
FrontOffice.new(**front_office_opts)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def deserialize_players(json)
|
156
|
-
(json.dig('engine', 'players') || []).map do |id, player_hash|
|
157
|
-
player_opts = {
|
158
|
-
id:,
|
159
|
-
first_name: player_hash['first_name'],
|
160
|
-
last_name: player_hash['last_name'],
|
161
|
-
overall: player_hash['overall'],
|
162
|
-
position: Position.new(player_hash['position'])
|
163
|
-
}
|
164
|
-
|
165
|
-
Player.new(**player_opts)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
def deserialize_events(json, players, front_offices)
|
170
|
-
(json.dig('engine', 'events') || []).map do |event_hash|
|
171
|
-
event_opts = event_hash.slice('pick', 'round', 'round_pick').merge(
|
172
|
-
front_office: front_offices.find { |t| t.id == event_hash['front_office'] }
|
173
|
-
)
|
174
|
-
|
175
|
-
class_constant = EVENT_CLASSES.fetch(event_hash['type'])
|
176
|
-
|
177
|
-
if [PickEvent, SimEvent].include?(class_constant)
|
178
|
-
event_opts[:player] = players.find { |p| p.id == event_hash['player'] }
|
179
|
-
end
|
180
|
-
|
181
|
-
class_constant.new(**event_opts)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
@@ -1,92 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Drafting
|
5
|
-
class FrontOffice < Entity
|
6
|
-
MAX_DEPTH = 3
|
7
|
-
MAX_FUZZ = 2
|
8
|
-
MAX_POSITIONS = 12
|
9
|
-
|
10
|
-
private_constant :MAX_DEPTH, :MAX_FUZZ, :MAX_POSITIONS
|
11
|
-
|
12
|
-
attr_reader :prioritized_positions, :fuzz, :depth, :name
|
13
|
-
|
14
|
-
def initialize(id:, name: '', prioritized_positions: [], fuzz: rand(0..MAX_FUZZ), depth: rand(0..MAX_DEPTH))
|
15
|
-
super(id)
|
16
|
-
|
17
|
-
@name = name
|
18
|
-
@fuzz = fuzz.to_i
|
19
|
-
@depth = depth.to_i
|
20
|
-
@prioritized_positions = prioritized_positions
|
21
|
-
|
22
|
-
# fill in the rest of the queue here
|
23
|
-
need_count = MAX_POSITIONS - @prioritized_positions.length
|
24
|
-
|
25
|
-
@prioritized_positions += random_positions_queue[0...need_count]
|
26
|
-
|
27
|
-
freeze
|
28
|
-
end
|
29
|
-
|
30
|
-
def pick(undrafted_player_search:, drafted_players:, round:)
|
31
|
-
players = []
|
32
|
-
|
33
|
-
players = adaptive_search(undrafted_player_search:, drafted_players:) if depth >= round
|
34
|
-
players = balanced_search(undrafted_player_search:, drafted_players:) if players.empty?
|
35
|
-
players = top_players(undrafted_player_search:) if players.empty?
|
36
|
-
|
37
|
-
players[0..fuzz].sample
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_s
|
41
|
-
"[#{super}] #{name}"
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def adaptive_search(undrafted_player_search:, drafted_players:)
|
47
|
-
drafted_positions = drafted_players.map(&:position)
|
48
|
-
|
49
|
-
undrafted_player_search.query(exclude_positions: drafted_positions)
|
50
|
-
end
|
51
|
-
|
52
|
-
def balanced_search(undrafted_player_search:, drafted_players:)
|
53
|
-
players = []
|
54
|
-
|
55
|
-
# Try to find best pick for exact desired position.
|
56
|
-
# If you cant find one, then move to the next desired position until the end of the queue
|
57
|
-
available_prioritized_positions(drafted_players:).each do |position|
|
58
|
-
players = undrafted_player_search.query(position:)
|
59
|
-
|
60
|
-
break if players.any?
|
61
|
-
end
|
62
|
-
|
63
|
-
players = players.any? ? players : undrafted_player_search.query
|
64
|
-
end
|
65
|
-
|
66
|
-
def all_random_positions
|
67
|
-
Position::ALL_VALUES.to_a.shuffle.map { |v| Position.new(v) }
|
68
|
-
end
|
69
|
-
|
70
|
-
def random_positions_queue
|
71
|
-
all_random_positions + all_random_positions + [Position.random] + [Position.random]
|
72
|
-
end
|
73
|
-
|
74
|
-
def available_prioritized_positions(drafted_players:)
|
75
|
-
drafted_positions = drafted_players.map(&:position)
|
76
|
-
queue = prioritized_positions.dup
|
77
|
-
|
78
|
-
drafted_positions.each do |drafted_position|
|
79
|
-
index = queue.index(drafted_position)
|
80
|
-
|
81
|
-
next unless index
|
82
|
-
|
83
|
-
queue.delete_at(index)
|
84
|
-
|
85
|
-
queue << drafted_position
|
86
|
-
end
|
87
|
-
|
88
|
-
queue
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'roster'
|
4
|
-
|
5
|
-
module Basketball
|
6
|
-
module Drafting
|
7
|
-
class League
|
8
|
-
class RosterNotFoundError < StandardError; end
|
9
|
-
class RosterAlreadyAddedError < StandardError; end
|
10
|
-
|
11
|
-
attr_reader :free_agents, :rosters
|
12
|
-
|
13
|
-
def initialize(free_agents: [], front_offices: [])
|
14
|
-
@rosters = []
|
15
|
-
@free_agents = []
|
16
|
-
|
17
|
-
front_offices.each { |front_office| add_roster(front_office) }
|
18
|
-
free_agents.each { |p| register!(player: p) }
|
19
|
-
|
20
|
-
freeze
|
21
|
-
end
|
22
|
-
|
23
|
-
def roster(front_office)
|
24
|
-
rosters.find { |r| r == front_office }
|
25
|
-
end
|
26
|
-
|
27
|
-
def register!(player:, front_office: nil)
|
28
|
-
raise PlayerRequiredError, 'player is required' unless player
|
29
|
-
|
30
|
-
rosters.each do |roster|
|
31
|
-
if roster.registered?(player)
|
32
|
-
raise PlayerAlreadyRegisteredError,
|
33
|
-
"#{player} already registered to: #{roster.id}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
if free_agents.include?(player)
|
38
|
-
raise PlayerAlreadyRegisteredError,
|
39
|
-
"#{player} already registered as a free agent"
|
40
|
-
end
|
41
|
-
|
42
|
-
if front_office
|
43
|
-
roster = roster(front_office)
|
44
|
-
|
45
|
-
raise RosterNotFoundError, "Roster not found for: #{front_office}" unless roster
|
46
|
-
|
47
|
-
roster.sign!(player)
|
48
|
-
else
|
49
|
-
free_agents << player
|
50
|
-
end
|
51
|
-
|
52
|
-
self
|
53
|
-
end
|
54
|
-
|
55
|
-
def to_s
|
56
|
-
(['League'] + rosters.map(&:to_s)).join("\n")
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def add_roster(front_office)
|
62
|
-
raise RosterAlreadyAddedError, "#{front_office} already added" if rosters.include?(front_office)
|
63
|
-
|
64
|
-
rosters << Roster.new(id: front_office.id, name: front_office.name)
|
65
|
-
|
66
|
-
self
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'event'
|
4
|
-
|
5
|
-
module Basketball
|
6
|
-
module Drafting
|
7
|
-
class PickEvent < Event
|
8
|
-
attr_reader_value :player
|
9
|
-
|
10
|
-
def initialize(front_office:, player:, pick:, round:, round_pick:)
|
11
|
-
super(front_office:, pick:, round:, round_pick:)
|
12
|
-
|
13
|
-
raise ArgumentError, 'player required' unless player
|
14
|
-
|
15
|
-
@player = player
|
16
|
-
|
17
|
-
freeze
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_s
|
21
|
-
"#{player} picked #{super}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Drafting
|
5
|
-
class Player < Entity
|
6
|
-
STAR_THRESHOLD = 75
|
7
|
-
OVERALL_STAR_INDICATOR = '⭐'
|
8
|
-
|
9
|
-
private_constant :STAR_THRESHOLD, :OVERALL_STAR_INDICATOR
|
10
|
-
|
11
|
-
attr_reader :first_name, :last_name, :position, :overall
|
12
|
-
|
13
|
-
def initialize(id:, position:, first_name: '', last_name: '', overall: 0)
|
14
|
-
super(id)
|
15
|
-
|
16
|
-
raise ArgumentError, 'position is required' unless position
|
17
|
-
|
18
|
-
@first_name = first_name.to_s
|
19
|
-
@last_name = last_name.to_s
|
20
|
-
@position = position
|
21
|
-
@overall = overall.to_i
|
22
|
-
|
23
|
-
freeze
|
24
|
-
end
|
25
|
-
|
26
|
-
def full_name
|
27
|
-
"#{first_name.strip} #{last_name.strip}".strip
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_s
|
31
|
-
"[#{super}] #{full_name} (#{position}) #{overall} #{star_indicators.join(', ')}".strip
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def star_indicators
|
37
|
-
[].tap do |indicators|
|
38
|
-
indicators << OVERALL_STAR_INDICATOR if overall >= STAR_THRESHOLD
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Drafting
|
5
|
-
class Roster < Entity
|
6
|
-
class PlayerRequiredError < StandardError; end
|
7
|
-
|
8
|
-
attr_reader :name, :players
|
9
|
-
|
10
|
-
def initialize(id:, name: '', players: [])
|
11
|
-
super(id)
|
12
|
-
|
13
|
-
@name = name.to_s
|
14
|
-
@players = players.each { |p| register!(p) }
|
15
|
-
|
16
|
-
freeze
|
17
|
-
end
|
18
|
-
|
19
|
-
def registered?(player)
|
20
|
-
players.include?(player)
|
21
|
-
end
|
22
|
-
|
23
|
-
def sign!(player)
|
24
|
-
raise PlayerRequiredError, 'player is required' unless player
|
25
|
-
raise PlayerAlreadyRegisteredError, "#{player} already registered for #{id}" if registered?(player)
|
26
|
-
|
27
|
-
players << player
|
28
|
-
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_s
|
33
|
-
(["[#{super}] #{name} Roster"] + players.map(&:to_s)).join("\n")
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Drafting
|
5
|
-
class SimEvent < Event
|
6
|
-
attr_reader_value :player
|
7
|
-
|
8
|
-
def initialize(front_office:, player:, pick:, round:, round_pick:)
|
9
|
-
super(front_office:, pick:, round:, round_pick:)
|
10
|
-
|
11
|
-
raise ArgumentError, 'player required' unless player
|
12
|
-
|
13
|
-
@player = player
|
14
|
-
|
15
|
-
freeze
|
16
|
-
end
|
17
|
-
|
18
|
-
def to_s
|
19
|
-
"#{player} auto-picked #{super}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/lib/basketball/drafting.rb
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Scheduling
|
5
|
-
class Calendar < ValueObject
|
6
|
-
class TeamAlreadyBookedError < StandardError; end
|
7
|
-
class InvalidGameOrderError < StandardError; end
|
8
|
-
class OutOfBoundsError < StandardError; end
|
9
|
-
|
10
|
-
attr_reader :preseason_start_date,
|
11
|
-
:preseason_end_date,
|
12
|
-
:season_start_date,
|
13
|
-
:season_end_date
|
14
|
-
|
15
|
-
attr_reader_value :year, :games
|
16
|
-
|
17
|
-
def initialize(year:, games: [])
|
18
|
-
super()
|
19
|
-
|
20
|
-
raise ArgumentError, 'year is required' unless year
|
21
|
-
|
22
|
-
@year = year
|
23
|
-
@preseason_start_date = Date.new(year, 9, 30)
|
24
|
-
@preseason_end_date = Date.new(year, 10, 14)
|
25
|
-
@season_start_date = Date.new(year, 10, 18)
|
26
|
-
@season_end_date = Date.new(year + 1, 4, 29)
|
27
|
-
@games = []
|
28
|
-
|
29
|
-
games.each { |game| add!(game) }
|
30
|
-
|
31
|
-
freeze
|
32
|
-
end
|
33
|
-
|
34
|
-
def add!(game)
|
35
|
-
assert_in_bounds(game)
|
36
|
-
assert_free_date(game)
|
37
|
-
|
38
|
-
@games << game
|
39
|
-
|
40
|
-
self
|
41
|
-
end
|
42
|
-
|
43
|
-
def preseason_games_for(date: nil, team: nil)
|
44
|
-
games_for(date:, team:).select { |game| game.is_a?(PreseasonGame) }
|
45
|
-
end
|
46
|
-
|
47
|
-
def season_games_for(date: nil, team: nil)
|
48
|
-
games_for(date:, team:).select { |game| game.is_a?(SeasonGame) }
|
49
|
-
end
|
50
|
-
|
51
|
-
def games_for(date: nil, team: nil)
|
52
|
-
games.select do |game|
|
53
|
-
(date.nil? || game.date == date) &&
|
54
|
-
(team.nil? || (game.home_team == team || game.away_team == team))
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def available_preseason_dates_for(team)
|
59
|
-
all_preseason_dates - preseason_games_for(team:).map(&:date)
|
60
|
-
end
|
61
|
-
|
62
|
-
def available_season_dates_for(team)
|
63
|
-
all_season_dates - season_games_for(team:).map(&:date)
|
64
|
-
end
|
65
|
-
|
66
|
-
def available_preseason_matchup_dates(team1, team2)
|
67
|
-
available_team_dates = available_preseason_dates_for(team1)
|
68
|
-
available_other_team_dates = available_preseason_dates_for(team2)
|
69
|
-
|
70
|
-
available_team_dates & available_other_team_dates
|
71
|
-
end
|
72
|
-
|
73
|
-
def available_season_matchup_dates(team1, team2)
|
74
|
-
available_team_dates = available_season_dates_for(team1)
|
75
|
-
available_other_team_dates = available_season_dates_for(team2)
|
76
|
-
|
77
|
-
available_team_dates & available_other_team_dates
|
78
|
-
end
|
79
|
-
|
80
|
-
def teams
|
81
|
-
games.flat_map(&:teams)
|
82
|
-
end
|
83
|
-
|
84
|
-
def team(id)
|
85
|
-
teams.find { |t| t == Team.new(id:) }
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def all_preseason_dates
|
91
|
-
(preseason_start_date..preseason_end_date).to_a
|
92
|
-
end
|
93
|
-
|
94
|
-
def all_season_dates
|
95
|
-
(season_start_date..season_end_date).to_a
|
96
|
-
end
|
97
|
-
|
98
|
-
def assert_free_date(game)
|
99
|
-
if games_for(date: game.date, team: game.home_team).any?
|
100
|
-
raise TeamAlreadyBookedError, "#{game.home_team} already playing on #{game.date}"
|
101
|
-
end
|
102
|
-
|
103
|
-
return unless games_for(date: game.date, team: game.away_team).any?
|
104
|
-
|
105
|
-
raise TeamAlreadyBookedError, "#{game.away_team} already playing on #{game.date}"
|
106
|
-
end
|
107
|
-
|
108
|
-
def assert_in_bounds(game)
|
109
|
-
if game.is_a?(PreseasonGame)
|
110
|
-
raise OutOfBoundsError, "#{game.date} is before preseason begins" if game.date < preseason_start_date
|
111
|
-
raise OutOfBoundsError, "#{game.date} is after preseason ends" if game.date > preseason_end_date
|
112
|
-
elsif game.is_a?(SeasonGame)
|
113
|
-
raise OutOfBoundsError, "#{game.date} is before season begins" if game.date < season_start_date
|
114
|
-
raise OutOfBoundsError, "#{game.date} is after season ends" if game.date > season_end_date
|
115
|
-
else
|
116
|
-
raise ArgumentError, "Dont know what this game type is: #{game.class.name}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|