basketball 0.0.4 → 0.0.6
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/CHANGELOG.md +10 -2
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +120 -18
- data/basketball.gemspec +1 -1
- data/exe/basketball-schedule +7 -0
- data/lib/basketball/drafting/cli.rb +15 -15
- data/lib/basketball/drafting/engine.rb +43 -42
- data/lib/basketball/drafting/engine_serializer.rb +41 -49
- data/lib/basketball/drafting/event.rb +10 -10
- data/lib/basketball/drafting/front_office.rb +9 -4
- data/lib/basketball/drafting/league.rb +70 -0
- data/lib/basketball/drafting/pick_event.rb +3 -3
- data/lib/basketball/drafting/roster.rb +18 -24
- data/lib/basketball/drafting/sim_event.rb +3 -3
- data/lib/basketball/drafting.rb +6 -0
- data/lib/basketball/scheduling/calendar.rb +121 -0
- data/lib/basketball/scheduling/calendar_serializer.rb +84 -0
- data/lib/basketball/scheduling/cli.rb +198 -0
- data/lib/basketball/scheduling/conference.rb +57 -0
- data/lib/basketball/scheduling/coordinator.rb +180 -0
- data/lib/basketball/scheduling/division.rb +43 -0
- data/lib/basketball/scheduling/game.rb +32 -0
- data/lib/basketball/scheduling/league.rb +114 -0
- data/lib/basketball/scheduling/league_serializer.rb +90 -0
- data/lib/basketball/scheduling/preseason_game.rb +11 -0
- data/lib/basketball/scheduling/season_game.rb +8 -0
- data/lib/basketball/scheduling/team.rb +21 -0
- data/lib/basketball/scheduling.rb +17 -0
- data/lib/basketball/value_object.rb +16 -7
- data/lib/basketball/version.rb +1 -1
- data/lib/basketball.rb +1 -0
- metadata +18 -3
- data/lib/basketball/drafting/team.rb +0 -28
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require_relative 'front_office'
|
4
4
|
require_relative 'player'
|
5
|
-
require_relative 'team'
|
6
5
|
require_relative 'pick_event'
|
7
6
|
require_relative 'sim_event'
|
8
7
|
require_relative 'skip_event'
|
@@ -19,14 +18,14 @@ module Basketball
|
|
19
18
|
private_constant :EVENT_CLASSES
|
20
19
|
|
21
20
|
def deserialize(string)
|
22
|
-
json
|
23
|
-
|
24
|
-
players
|
25
|
-
events
|
21
|
+
json = JSON.parse(string, symbolize_names: true)
|
22
|
+
front_offices = deserialize_front_offices(json)
|
23
|
+
players = deserialize_players(json)
|
24
|
+
events = deserialize_events(json, players, front_offices)
|
26
25
|
|
27
26
|
engine_opts = {
|
28
27
|
players:,
|
29
|
-
|
28
|
+
front_offices:,
|
30
29
|
events:
|
31
30
|
}
|
32
31
|
|
@@ -39,7 +38,7 @@ module Basketball
|
|
39
38
|
{
|
40
39
|
info: serialize_info(engine),
|
41
40
|
engine: serialize_engine(engine),
|
42
|
-
|
41
|
+
league: serialize_league(engine)
|
43
42
|
}.to_json
|
44
43
|
end
|
45
44
|
|
@@ -48,7 +47,7 @@ module Basketball
|
|
48
47
|
def serialize_engine(engine)
|
49
48
|
{
|
50
49
|
rounds: engine.rounds,
|
51
|
-
|
50
|
+
front_offices: serialize_front_offices(engine),
|
52
51
|
players: serialize_players(engine),
|
53
52
|
events: serialize_events(engine.events)
|
54
53
|
}
|
@@ -59,37 +58,40 @@ module Basketball
|
|
59
58
|
total_picks: engine.total_picks,
|
60
59
|
current_round: engine.current_round,
|
61
60
|
current_round_pick: engine.current_round_pick,
|
62
|
-
|
61
|
+
current_front_office: engine.current_front_office&.id,
|
63
62
|
current_pick: engine.current_pick,
|
64
63
|
remaining_picks: engine.remaining_picks,
|
65
|
-
done: engine.done
|
66
|
-
undrafted_players: engine.undrafted_players.map(&:id)
|
64
|
+
done: engine.done?
|
67
65
|
}
|
68
66
|
end
|
69
67
|
|
70
|
-
def
|
71
|
-
engine.
|
68
|
+
def serialize_league(engine)
|
69
|
+
league = engine.to_league
|
70
|
+
|
71
|
+
rosters = league.rosters.to_h do |roster|
|
72
72
|
[
|
73
73
|
roster.id,
|
74
74
|
{
|
75
|
-
events: roster.events.map(&:id),
|
76
75
|
players: roster.players.map(&:id)
|
77
76
|
}
|
78
77
|
]
|
79
78
|
end
|
79
|
+
|
80
|
+
{
|
81
|
+
free_agents: league.free_agents.map(&:id),
|
82
|
+
rosters:
|
83
|
+
}
|
80
84
|
end
|
81
85
|
|
82
|
-
def
|
83
|
-
engine.
|
86
|
+
def serialize_front_offices(engine)
|
87
|
+
engine.front_offices.to_h do |front_office|
|
84
88
|
[
|
85
|
-
|
89
|
+
front_office.id,
|
86
90
|
{
|
87
|
-
name:
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
prioritized_positions: team.front_office.prioritized_positions
|
92
|
-
}
|
91
|
+
name: front_office.name,
|
92
|
+
fuzz: front_office.fuzz,
|
93
|
+
depth: front_office.depth,
|
94
|
+
prioritized_positions: front_office.prioritized_positions.map(&:code)
|
93
95
|
}
|
94
96
|
]
|
95
97
|
end
|
@@ -113,8 +115,7 @@ module Basketball
|
|
113
115
|
events.map do |event|
|
114
116
|
{
|
115
117
|
type: event.class.name.split('::').last,
|
116
|
-
|
117
|
-
team: event.team.id,
|
118
|
+
front_office: event.front_office.id,
|
118
119
|
pick: event.pick,
|
119
120
|
round: event.round,
|
120
121
|
round_pick: event.round_pick
|
@@ -124,30 +125,21 @@ module Basketball
|
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
|
-
def
|
128
|
-
(json.dig(:engine, :
|
129
|
-
|
128
|
+
def deserialize_front_offices(json)
|
129
|
+
(json.dig(:engine, :front_offices) || []).map do |id, front_office_hash|
|
130
|
+
prioritized_positions = (front_office_hash[:prioritized_positions] || []).map do |v|
|
131
|
+
Position.new(v)
|
132
|
+
end
|
133
|
+
|
134
|
+
front_office_opts = {
|
130
135
|
id:,
|
131
|
-
name:
|
136
|
+
name: front_office_hash[:name],
|
137
|
+
prioritized_positions:,
|
138
|
+
fuzz: front_office_hash[:fuzz],
|
139
|
+
depth: front_office_hash[:depth]
|
132
140
|
}
|
133
141
|
|
134
|
-
|
135
|
-
front_office_hash = team_hash[:front_office] || {}
|
136
|
-
|
137
|
-
prioritized_positions = (front_office_hash[:prioritized_positions] || []).map do |v|
|
138
|
-
Position.new(v)
|
139
|
-
end
|
140
|
-
|
141
|
-
front_office_opts = {
|
142
|
-
prioritized_positions:,
|
143
|
-
fuzz: front_office_hash[:fuzz],
|
144
|
-
depth: front_office_hash[:depth]
|
145
|
-
}
|
146
|
-
|
147
|
-
team_opts[:front_office] = FrontOffice.new(**front_office_opts)
|
148
|
-
end
|
149
|
-
|
150
|
-
Team.new(**team_opts)
|
142
|
+
FrontOffice.new(**front_office_opts)
|
151
143
|
end
|
152
144
|
end
|
153
145
|
|
@@ -162,10 +154,10 @@ module Basketball
|
|
162
154
|
end
|
163
155
|
end
|
164
156
|
|
165
|
-
def deserialize_events(json, players,
|
157
|
+
def deserialize_events(json, players, front_offices)
|
166
158
|
(json.dig(:engine, :events) || []).map do |event_hash|
|
167
|
-
event_opts = event_hash.slice(:
|
168
|
-
|
159
|
+
event_opts = event_hash.slice(:pick, :round, :round_pick).merge(
|
160
|
+
front_office: front_offices.find { |t| t.id == event_hash[:front_office] }
|
169
161
|
)
|
170
162
|
|
171
163
|
class_constant = EVENT_CLASSES.fetch(event_hash[:type])
|
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
|
-
class Event <
|
6
|
-
|
5
|
+
class Event < ValueObject
|
6
|
+
attr_reader_value :pick, :round, :round_pick, :front_office
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
super(
|
8
|
+
def initialize(front_office:, pick:, round:, round_pick:)
|
9
|
+
super()
|
10
10
|
|
11
|
-
raise ArgumentError, '
|
11
|
+
raise ArgumentError, 'front_office required' unless front_office
|
12
12
|
|
13
|
-
@
|
14
|
-
@pick
|
15
|
-
@round
|
16
|
-
@round_pick
|
13
|
+
@front_office = front_office
|
14
|
+
@pick = pick.to_i
|
15
|
+
@round = round.to_i
|
16
|
+
@round_pick = round_pick.to_i
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_s
|
20
|
-
"##{pick} overall (R#{round}:P#{round_pick}) by #{
|
20
|
+
"##{pick} overall (R#{round}:P#{round_pick}) by #{front_office}"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -2,18 +2,19 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
|
-
class FrontOffice <
|
5
|
+
class FrontOffice < Entity
|
6
6
|
MAX_DEPTH = 3
|
7
7
|
MAX_FUZZ = 2
|
8
8
|
MAX_POSITIONS = 12
|
9
9
|
|
10
10
|
private_constant :MAX_DEPTH, :MAX_FUZZ, :MAX_POSITIONS
|
11
11
|
|
12
|
-
|
12
|
+
attr_reader :prioritized_positions, :fuzz, :depth, :name
|
13
13
|
|
14
|
-
def initialize(prioritized_positions: [], fuzz: rand(0..MAX_FUZZ), depth: rand(0..MAX_DEPTH))
|
15
|
-
super()
|
14
|
+
def initialize(id:, name: '', prioritized_positions: [], fuzz: rand(0..MAX_FUZZ), depth: rand(0..MAX_DEPTH))
|
15
|
+
super(id)
|
16
16
|
|
17
|
+
@name = name
|
17
18
|
@fuzz = fuzz.to_i
|
18
19
|
@depth = depth.to_i
|
19
20
|
@prioritized_positions = prioritized_positions
|
@@ -36,6 +37,10 @@ module Basketball
|
|
36
37
|
players[0..fuzz].sample
|
37
38
|
end
|
38
39
|
|
40
|
+
def to_s
|
41
|
+
"[#{super}] #{name}"
|
42
|
+
end
|
43
|
+
|
39
44
|
private
|
40
45
|
|
41
46
|
def adaptive_search(undrafted_player_search:, drafted_players:)
|
@@ -0,0 +1,70 @@
|
|
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
|
@@ -5,10 +5,10 @@ require_relative 'event'
|
|
5
5
|
module Basketball
|
6
6
|
module Drafting
|
7
7
|
class PickEvent < Event
|
8
|
-
|
8
|
+
attr_reader_value :player
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
super(
|
10
|
+
def initialize(front_office:, player:, pick:, round:, round_pick:)
|
11
|
+
super(front_office:, pick:, round:, round_pick:)
|
12
12
|
|
13
13
|
raise ArgumentError, 'player required' unless player
|
14
14
|
|
@@ -2,41 +2,35 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
|
-
class Roster <
|
6
|
-
|
5
|
+
class Roster < Entity
|
6
|
+
class PlayerRequiredError < StandardError; end
|
7
7
|
|
8
|
-
|
8
|
+
attr_reader :name, :players
|
9
9
|
|
10
|
-
|
10
|
+
def initialize(id:, name: '', players: [])
|
11
|
+
super(id)
|
11
12
|
|
12
|
-
|
13
|
+
@name = name.to_s
|
14
|
+
@players = players.each { |p| register!(p) }
|
13
15
|
|
14
|
-
|
15
|
-
super()
|
16
|
-
|
17
|
-
raise ArgumentError, 'team is required' unless team
|
18
|
-
|
19
|
-
other_teams_pick_event_ids = events.reject { |e| e.team == team }.map(&:id)
|
20
|
-
|
21
|
-
if other_teams_pick_event_ids.any?
|
22
|
-
raise WrongTeamEventError,
|
23
|
-
"Event(s): #{other_teams_pick_event_ids.join(',')} has wrong team"
|
24
|
-
end
|
25
|
-
|
26
|
-
@team = team
|
27
|
-
@events = events
|
16
|
+
freeze
|
28
17
|
end
|
29
18
|
|
30
|
-
def
|
31
|
-
|
19
|
+
def registered?(player)
|
20
|
+
players.include?(player)
|
32
21
|
end
|
33
22
|
|
34
|
-
def
|
35
|
-
|
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
|
36
30
|
end
|
37
31
|
|
38
32
|
def to_s
|
39
|
-
([
|
33
|
+
(["[#{super}] #{name} Roster"] + players.map(&:to_s)).join("\n")
|
40
34
|
end
|
41
35
|
end
|
42
36
|
end
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
5
|
class SimEvent < Event
|
6
|
-
|
6
|
+
attr_reader_value :player
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
super(
|
8
|
+
def initialize(front_office:, player:, pick:, round:, round_pick:)
|
9
|
+
super(front_office:, pick:, round:, round_pick:)
|
10
10
|
|
11
11
|
raise ArgumentError, 'player required' unless player
|
12
12
|
|
data/lib/basketball/drafting.rb
CHANGED
@@ -0,0 +1,121 @@
|
|
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
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'game'
|
4
|
+
require_relative 'preseason_game'
|
5
|
+
require_relative 'season_game'
|
6
|
+
|
7
|
+
module Basketball
|
8
|
+
module Scheduling
|
9
|
+
class CalendarSerializer
|
10
|
+
GAME_CLASSES = {
|
11
|
+
'PreseasonGame' => PreseasonGame,
|
12
|
+
'SeasonGame' => SeasonGame
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def deserialize(string)
|
16
|
+
json = JSON.parse(string, symbolize_names: true)
|
17
|
+
|
18
|
+
Calendar.new(
|
19
|
+
year: json[:year].to_i,
|
20
|
+
games: deserialize_games(json)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize(calendar)
|
25
|
+
{
|
26
|
+
year: calendar.preseason_start_date.year,
|
27
|
+
teams: serialize_teams(calendar.games.flat_map(&:teams).uniq),
|
28
|
+
games: serialize_games(calendar.games)
|
29
|
+
}.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
## Deserialization
|
35
|
+
|
36
|
+
def deserialize_games(json)
|
37
|
+
teams = deserialize_teams(json[:teams])
|
38
|
+
|
39
|
+
(json[:games] || []).map do |game_hash|
|
40
|
+
GAME_CLASSES.fetch(game_hash[:type]).new(
|
41
|
+
date: Date.parse(game_hash[:date]),
|
42
|
+
home_team: teams.fetch(game_hash[:home_team]),
|
43
|
+
away_team: teams.fetch(game_hash[:away_team])
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def deserialize_teams(teams)
|
49
|
+
(teams || []).to_h do |id, team_hash|
|
50
|
+
team = Team.new(id:, name: team_hash[:name])
|
51
|
+
|
52
|
+
[
|
53
|
+
team.id,
|
54
|
+
team
|
55
|
+
]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
## Serialization
|
60
|
+
|
61
|
+
def serialize_teams(teams)
|
62
|
+
teams.to_h do |team|
|
63
|
+
[
|
64
|
+
team.id,
|
65
|
+
{
|
66
|
+
name: team.name
|
67
|
+
}
|
68
|
+
]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def serialize_games(games)
|
73
|
+
games.sort_by(&:date).map do |game|
|
74
|
+
{
|
75
|
+
type: game.class.name.split('::').last,
|
76
|
+
date: game.date,
|
77
|
+
home_team: game.home_team.id,
|
78
|
+
away_team: game.away_team.id
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|