basketball 0.0.8 → 0.0.10

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -19
  3. data/CHANGELOG.md +1 -39
  4. data/README.md +75 -93
  5. data/basketball.gemspec +3 -6
  6. data/exe/{basketball-season-scheduling → basketball-coordinator} +1 -1
  7. data/exe/{basketball-draft → basketball-room} +1 -1
  8. data/lib/basketball/app/coordinator_cli.rb +250 -0
  9. data/lib/basketball/app/coordinator_repository.rb +114 -0
  10. data/lib/basketball/app/document_repository.rb +67 -0
  11. data/lib/basketball/app/file_store.rb +38 -0
  12. data/lib/basketball/app/in_memory_store.rb +42 -0
  13. data/lib/basketball/app/league_repository.rb +20 -0
  14. data/lib/basketball/app/league_serializable.rb +54 -0
  15. data/lib/basketball/{draft/cli.rb → app/room_cli.rb} +74 -80
  16. data/lib/basketball/app/room_repository.rb +149 -0
  17. data/lib/basketball/app.rb +20 -0
  18. data/lib/basketball/draft/assessment.rb +31 -0
  19. data/lib/basketball/draft/event.rb +3 -2
  20. data/lib/basketball/draft/front_office.rb +35 -28
  21. data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
  22. data/lib/basketball/draft/room.rb +119 -119
  23. data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
  24. data/lib/basketball/draft/skip.rb +12 -0
  25. data/lib/basketball/draft.rb +13 -6
  26. data/lib/basketball/entity.rb +19 -10
  27. data/lib/basketball/org/league.rb +68 -0
  28. data/lib/basketball/org/player.rb +26 -0
  29. data/lib/basketball/{draft → org}/position.rb +3 -2
  30. data/lib/basketball/org/team.rb +38 -0
  31. data/lib/basketball/org.rb +12 -0
  32. data/lib/basketball/season/arena.rb +113 -0
  33. data/lib/basketball/season/calendar.rb +41 -72
  34. data/lib/basketball/season/coordinator.rb +186 -128
  35. data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
  36. data/lib/basketball/season/game.rb +15 -10
  37. data/lib/basketball/season/matchup.rb +27 -0
  38. data/lib/basketball/season/opponent.rb +15 -0
  39. data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
  40. data/lib/basketball/season/result.rb +37 -0
  41. data/lib/basketball/season.rb +12 -13
  42. data/lib/basketball/value_object.rb +8 -27
  43. data/lib/basketball/value_object_dsl.rb +30 -0
  44. data/lib/basketball/version.rb +1 -1
  45. data/lib/basketball.rb +9 -4
  46. metadata +37 -44
  47. data/lib/basketball/draft/league.rb +0 -70
  48. data/lib/basketball/draft/player.rb +0 -43
  49. data/lib/basketball/draft/room_serializer.rb +0 -186
  50. data/lib/basketball/draft/roster.rb +0 -37
  51. data/lib/basketball/draft/sim_event.rb +0 -23
  52. data/lib/basketball/draft/skip_event.rb +0 -13
  53. data/lib/basketball/season/calendar_serializer.rb +0 -94
  54. data/lib/basketball/season/conference.rb +0 -57
  55. data/lib/basketball/season/division.rb +0 -43
  56. data/lib/basketball/season/league.rb +0 -114
  57. data/lib/basketball/season/league_serializer.rb +0 -99
  58. data/lib/basketball/season/scheduling_cli.rb +0 -198
  59. data/lib/basketball/season/team.rb +0 -21
@@ -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 Draft
11
- class RoomSerializer
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(room)
21
- {
22
- 'info' => serialize_info(room),
23
- 'room' => serialize_room(room),
24
- 'league' => serialize_league(room)
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
- room_opts = {
34
- players:,
35
- front_offices:,
36
- events:
37
- }
38
-
39
- room_opts[:rounds] = json.dig('room', 'rounds') if json.dig('room', 'rounds')
40
-
41
- Room.new(**room_opts)
42
- end
43
-
44
- def deserialize(string)
45
- json = JSON.parse(string)
46
-
47
- from_hash(json)
48
- end
49
-
50
- def serialize(room)
51
- to_hash(room).to_json
52
- end
53
-
54
- private
55
-
56
- def serialize_room(room)
57
- {
58
- 'rounds' => room.rounds,
59
- 'front_offices' => serialize_front_offices(room),
60
- 'players' => serialize_players(room),
61
- 'events' => serialize_events(room.events)
62
- }
63
- end
64
-
65
- def serialize_info(room)
66
- {
67
- 'total_picks' => room.total_picks,
68
- 'current_round' => room.current_round,
69
- 'current_round_pick' => room.current_round_pick,
70
- 'current_front_office' => room.current_front_office&.id,
71
- 'current_pick' => room.current_pick,
72
- 'remaining_picks' => room.remaining_picks,
73
- 'done' => room.done?
74
- }
75
- end
76
-
77
- def serialize_league(room)
78
- league = room.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(room)
96
- room.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(room)
110
- room.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('room', '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('room', '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('room', '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,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Draft
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 Draft
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
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'event'
4
-
5
- module Basketball
6
- module Draft
7
- class SkipEvent < Event
8
- def to_s
9
- "skipped #{super}"
10
- end
11
- end
12
- end
13
- end
@@ -1,94 +0,0 @@
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 Season
9
- class CalendarSerializer
10
- GAME_CLASSES = {
11
- 'PreseasonGame' => PreseasonGame,
12
- 'SeasonGame' => SeasonGame
13
- }.freeze
14
-
15
- def to_hash(calendar)
16
- teams = calendar.games.flat_map(&:teams).uniq
17
-
18
- {
19
- 'year' => calendar.preseason_start_date.year,
20
- 'teams' => serialize_teams(teams),
21
- 'games' => serialize_games(calendar.games)
22
- }
23
- end
24
-
25
- def from_hash(json)
26
- Calendar.new(
27
- year: json['year'].to_i,
28
- games: deserialize_games(json)
29
- )
30
- end
31
-
32
- def deserialize(string)
33
- json = JSON.parse(string)
34
-
35
- from_hash(json)
36
- end
37
-
38
- def serialize(calendar)
39
- to_hash(calendar).to_json
40
- end
41
-
42
- private
43
-
44
- ## Deserialization
45
-
46
- def deserialize_games(json)
47
- teams = deserialize_teams(json['teams'])
48
-
49
- (json['games'] || []).map do |game_hash|
50
- GAME_CLASSES.fetch(game_hash['type']).new(
51
- date: Date.parse(game_hash['date']),
52
- home_team: teams.fetch(game_hash['home_team']),
53
- away_team: teams.fetch(game_hash['away_team'])
54
- )
55
- end
56
- end
57
-
58
- def deserialize_teams(teams)
59
- (teams || []).to_h do |id, team_hash|
60
- team = Team.new(id:, name: team_hash['name'])
61
-
62
- [
63
- team.id,
64
- team
65
- ]
66
- end
67
- end
68
-
69
- ## Serialization
70
-
71
- def serialize_teams(teams)
72
- teams.to_h do |team|
73
- [
74
- team.id,
75
- {
76
- 'name' => team.name
77
- }
78
- ]
79
- end
80
- end
81
-
82
- def serialize_games(games)
83
- games.sort_by(&:date).map do |game|
84
- {
85
- 'type' => game.class.name.split('::').last,
86
- 'date' => game.date.to_s,
87
- 'home_team' => game.home_team.id,
88
- 'away_team' => game.away_team.id
89
- }
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class Conference < Entity
6
- DIVISIONS_SIZE = 3
7
-
8
- attr_reader :name, :divisions
9
-
10
- def initialize(id:, name: '', divisions: [])
11
- super(id)
12
-
13
- @name = name.to_s
14
- @divisions = []
15
-
16
- divisions.each { |d| register_division!(d) }
17
-
18
- if divisions.length != DIVISIONS_SIZE
19
- raise BadDivisionsSizeError, "#{id} should have exactly #{DIVISIONS_SIZE} divisions"
20
- end
21
-
22
- freeze
23
- end
24
-
25
- def to_s
26
- (["[#{super}] #{name}"] + divisions.map(&:to_s)).join("\n")
27
- end
28
-
29
- def division?(division)
30
- divisions.include?(division)
31
- end
32
-
33
- def teams
34
- divisions.flat_map(&:teams)
35
- end
36
-
37
- def team?(team)
38
- teams.include?(team)
39
- end
40
-
41
- private
42
-
43
- def register_division!(division)
44
- raise ArgumentError, 'division is required' unless division
45
- raise DivisionAlreadyRegisteredError, "#{division} already registered" if division?(division)
46
-
47
- division.teams.each do |team|
48
- raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
49
- end
50
-
51
- divisions << division
52
-
53
- self
54
- end
55
- end
56
- end
57
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class Division < Entity
6
- TEAMS_SIZE = 5
7
-
8
- attr_reader :name, :teams
9
-
10
- def initialize(id:, name: '', teams: [])
11
- super(id)
12
-
13
- @name = name.to_s
14
- @teams = []
15
-
16
- teams.each { |t| register_team!(t) }
17
-
18
- raise BadTeamsSizeError, "#{id} should have exactly #{TEAMS_SIZE} teams" if teams.length != TEAMS_SIZE
19
-
20
- freeze
21
- end
22
-
23
- def to_s
24
- (["[#{super}] #{name}"] + teams.map(&:to_s)).join("\n")
25
- end
26
-
27
- def team?(team)
28
- teams.include?(team)
29
- end
30
-
31
- private
32
-
33
- def register_team!(team)
34
- raise ArgumentError, 'team is required' unless team
35
- raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
36
-
37
- teams << team
38
-
39
- self
40
- end
41
- end
42
- end
43
- end
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class League
6
- class UnknownTeamError < StandardError; end
7
-
8
- class << self
9
- def generate_random; end
10
- end
11
-
12
- CONFERENCES_SIZE = 2
13
-
14
- attr_reader :conferences
15
-
16
- def initialize(conferences: [])
17
- @conferences = []
18
-
19
- conferences.each { |c| register_conference!(c) }
20
-
21
- if conferences.length != CONFERENCES_SIZE
22
- raise BadConferencesSizeError, "there has to be #{CONFERENCES_SIZE} conferences"
23
- end
24
-
25
- freeze
26
- end
27
-
28
- def to_s
29
- (['League'] + conferences.map(&:to_s)).join("\n")
30
- end
31
-
32
- def divisions
33
- conferences.flat_map(&:divisions)
34
- end
35
-
36
- def conference?(conference)
37
- conferences.include?(conference)
38
- end
39
-
40
- def division?(division)
41
- divisions.include?(division)
42
- end
43
-
44
- def team?(team)
45
- teams.include?(team)
46
- end
47
-
48
- def teams
49
- conferences.flat_map do |conference|
50
- conference.divisions.flat_map(&:teams)
51
- end
52
- end
53
-
54
- def conference_for(team)
55
- conferences.find { |c| c.divisions.find { |d| d.teams.include?(team) } }
56
- end
57
-
58
- def division_for(team)
59
- conference_for(team)&.divisions&.find { |d| d.teams.include?(team) }
60
- end
61
-
62
- # Same conference, same division
63
- def division_opponents_for(team)
64
- division = division_for(team)
65
-
66
- return nil unless division
67
-
68
- division.teams - [team]
69
- end
70
-
71
- # Same conference, different division
72
- def cross_division_opponents_for(team)
73
- conference = conference_for(team)
74
- division = division_for(team)
75
-
76
- return nil unless conference && division
77
-
78
- other_divisions = conference.divisions - [division]
79
-
80
- other_divisions.flat_map(&:teams)
81
- end
82
-
83
- # Different conference
84
- def cross_conference_opponents_for(team)
85
- conference = conference_for(team)
86
-
87
- return nil unless conference
88
-
89
- other_conferences = conferences - [conference]
90
-
91
- other_conferences.flat_map { |c| c.divisions.flat_map(&:teams) }
92
- end
93
-
94
- private
95
-
96
- def register_conference!(conference)
97
- raise ArgumentError, 'conference is required' unless conference
98
- raise ConferenceAlreadyRegisteredError, "#{conference} already registered" if conference?(conference)
99
-
100
- conference.divisions.each do |division|
101
- raise DivisionAlreadyRegisteredError, "#{division} already registered" if division?(division)
102
-
103
- division.teams.each do |team|
104
- raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
105
- end
106
- end
107
-
108
- conferences << conference
109
-
110
- self
111
- end
112
- end
113
- end
114
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class LeagueSerializer
6
- def to_hash(league)
7
- {
8
- 'conferences' => serialize_conferences(league.conferences)
9
- }
10
- end
11
-
12
- def from_hash(json)
13
- conferences = deserialize_conferences(json['conferences'])
14
-
15
- League.new(conferences:)
16
- end
17
-
18
- def deserialize(string)
19
- json = JSON.parse(string)
20
-
21
- from_hash(json)
22
- end
23
-
24
- def serialize(league)
25
- to_hash(league).to_json
26
- end
27
-
28
- private
29
-
30
- ## Deserialization
31
-
32
- def deserialize_conferences(conferences)
33
- (conferences || []).map do |conference_id, conference_hash|
34
- Conference.new(
35
- id: conference_id,
36
- name: conference_hash['name'],
37
- divisions: deserialize_divisions(conference_hash['divisions'])
38
- )
39
- end
40
- end
41
-
42
- def deserialize_divisions(divisions)
43
- (divisions || []).map do |division_id, division_hash|
44
- Division.new(
45
- id: division_id,
46
- name: division_hash['name'],
47
- teams: deserialize_teams(division_hash['teams'])
48
- )
49
- end
50
- end
51
-
52
- def deserialize_teams(teams)
53
- (teams || []).map do |team_id, team_hash|
54
- Team.new(
55
- id: team_id,
56
- name: team_hash['name']
57
- )
58
- end
59
- end
60
-
61
- ## Serialization
62
-
63
- def serialize_conferences(conferences)
64
- conferences.to_h do |conference|
65
- [
66
- conference.id,
67
- {
68
- 'name' => conference.name,
69
- 'divisions' => serialize_divisions(conference.divisions)
70
- }
71
- ]
72
- end
73
- end
74
-
75
- def serialize_divisions(divisions)
76
- divisions.to_h do |division|
77
- [
78
- division.id,
79
- {
80
- 'name' => division.name,
81
- 'teams' => serialize_teams(division.teams)
82
- }
83
- ]
84
- end
85
- end
86
-
87
- def serialize_teams(teams)
88
- teams.to_h do |team|
89
- [
90
- team.id,
91
- {
92
- 'name' => team.name
93
- }
94
- ]
95
- end
96
- end
97
- end
98
- end
99
- end