basketball 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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