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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -19
  3. data/CHANGELOG.md +1 -39
  4. data/README.md +72 -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 +243 -0
  9. data/lib/basketball/app/coordinator_repository.rb +191 -0
  10. data/lib/basketball/app/file_store.rb +22 -0
  11. data/lib/basketball/{draft/cli.rb → app/room_cli.rb} +53 -76
  12. data/lib/basketball/app/room_repository.rb +189 -0
  13. data/lib/basketball/app.rb +12 -0
  14. data/lib/basketball/draft/assessment.rb +31 -0
  15. data/lib/basketball/draft/event.rb +3 -2
  16. data/lib/basketball/draft/front_office.rb +35 -28
  17. data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
  18. data/lib/basketball/draft/room.rb +119 -119
  19. data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
  20. data/lib/basketball/draft/skip.rb +12 -0
  21. data/lib/basketball/draft.rb +13 -6
  22. data/lib/basketball/entity.rb +10 -4
  23. data/lib/basketball/org/league.rb +73 -0
  24. data/lib/basketball/org/player.rb +26 -0
  25. data/lib/basketball/{draft → org}/position.rb +3 -2
  26. data/lib/basketball/org/team.rb +38 -0
  27. data/lib/basketball/org.rb +12 -0
  28. data/lib/basketball/season/arena.rb +112 -0
  29. data/lib/basketball/season/calendar.rb +41 -72
  30. data/lib/basketball/season/coordinator.rb +185 -126
  31. data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
  32. data/lib/basketball/season/game.rb +15 -10
  33. data/lib/basketball/season/matchup.rb +27 -0
  34. data/lib/basketball/season/opponent.rb +15 -0
  35. data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
  36. data/lib/basketball/season/result.rb +37 -0
  37. data/lib/basketball/season.rb +12 -13
  38. data/lib/basketball/value_object.rb +4 -1
  39. data/lib/basketball/version.rb +1 -1
  40. data/lib/basketball.rb +9 -4
  41. metadata +32 -44
  42. data/lib/basketball/draft/league.rb +0 -70
  43. data/lib/basketball/draft/player.rb +0 -43
  44. data/lib/basketball/draft/room_serializer.rb +0 -186
  45. data/lib/basketball/draft/roster.rb +0 -37
  46. data/lib/basketball/draft/sim_event.rb +0 -23
  47. data/lib/basketball/draft/skip_event.rb +0 -13
  48. data/lib/basketball/season/calendar_serializer.rb +0 -94
  49. data/lib/basketball/season/conference.rb +0 -57
  50. data/lib/basketball/season/division.rb +0 -43
  51. data/lib/basketball/season/league.rb +0 -114
  52. data/lib/basketball/season/league_serializer.rb +0 -99
  53. data/lib/basketball/season/scheduling_cli.rb +0 -198
  54. data/lib/basketball/season/team.rb +0 -21
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basketball
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Ruggio
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-16 00:00:00.000000000 Z
11
+ date: 2023-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: faker
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '3.2'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '3.2'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: slop
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,15 +24,15 @@ dependencies:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
26
  version: '4.10'
41
- description: |2
42
- This library is meant to serve as the domain for a basketball league/season simulator/turn-based game.
43
- It models core ideas such as: players, general managers, draft strategy, drafting, season generation, season simultation,
44
- playoff generation, playoff simulation, and more.
27
+ description: " This library is meant to serve as the domain for a basketball league/season
28
+ simulator/turn-based game. It models core ideas such as: players, general managers,
29
+ draft strategy, drafting, season generation, season simultation, playoff generation,
30
+ playoff simulation, and more.\n"
45
31
  email:
46
32
  - mattruggio@icloud.com
47
33
  executables:
48
- - basketball-draft
49
- - basketball-season-scheduling
34
+ - basketball-coordinator
35
+ - basketball-room
50
36
  extensions: []
51
37
  extra_rdoc_files: []
52
38
  files:
@@ -62,37 +48,39 @@ files:
62
48
  - README.md
63
49
  - Rakefile
64
50
  - basketball.gemspec
65
- - exe/basketball-draft
66
- - exe/basketball-season-scheduling
51
+ - exe/basketball-coordinator
52
+ - exe/basketball-room
67
53
  - lib/basketball.rb
54
+ - lib/basketball/app.rb
55
+ - lib/basketball/app/coordinator_cli.rb
56
+ - lib/basketball/app/coordinator_repository.rb
57
+ - lib/basketball/app/file_store.rb
58
+ - lib/basketball/app/room_cli.rb
59
+ - lib/basketball/app/room_repository.rb
68
60
  - lib/basketball/draft.rb
69
- - lib/basketball/draft/cli.rb
61
+ - lib/basketball/draft/assessment.rb
70
62
  - lib/basketball/draft/event.rb
71
63
  - lib/basketball/draft/front_office.rb
72
- - lib/basketball/draft/league.rb
73
- - lib/basketball/draft/pick_event.rb
74
- - lib/basketball/draft/player.rb
75
- - lib/basketball/draft/player_search.rb
76
- - lib/basketball/draft/position.rb
64
+ - lib/basketball/draft/pick.rb
77
65
  - lib/basketball/draft/room.rb
78
- - lib/basketball/draft/room_serializer.rb
79
- - lib/basketball/draft/roster.rb
80
- - lib/basketball/draft/sim_event.rb
81
- - lib/basketball/draft/skip_event.rb
66
+ - lib/basketball/draft/scout.rb
67
+ - lib/basketball/draft/skip.rb
82
68
  - lib/basketball/entity.rb
69
+ - lib/basketball/org.rb
70
+ - lib/basketball/org/league.rb
71
+ - lib/basketball/org/player.rb
72
+ - lib/basketball/org/position.rb
73
+ - lib/basketball/org/team.rb
83
74
  - lib/basketball/season.rb
75
+ - lib/basketball/season/arena.rb
84
76
  - lib/basketball/season/calendar.rb
85
- - lib/basketball/season/calendar_serializer.rb
86
- - lib/basketball/season/conference.rb
87
77
  - lib/basketball/season/coordinator.rb
88
- - lib/basketball/season/division.rb
78
+ - lib/basketball/season/exhibition.rb
89
79
  - lib/basketball/season/game.rb
90
- - lib/basketball/season/league.rb
91
- - lib/basketball/season/league_serializer.rb
92
- - lib/basketball/season/preseason_game.rb
93
- - lib/basketball/season/scheduling_cli.rb
94
- - lib/basketball/season/season_game.rb
95
- - lib/basketball/season/team.rb
80
+ - lib/basketball/season/matchup.rb
81
+ - lib/basketball/season/opponent.rb
82
+ - lib/basketball/season/regular.rb
83
+ - lib/basketball/season/result.rb
96
84
  - lib/basketball/value_object.rb
97
85
  - lib/basketball/version.rb
98
86
  homepage: https://github.com/mattruggio/basketball
@@ -123,5 +111,5 @@ requirements: []
123
111
  rubygems_version: 3.4.6
124
112
  signing_key:
125
113
  specification_version: 4
126
- summary: Basketball League Game Room
114
+ summary: Basketball Simulation Domain Model
127
115
  test_files: []
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'roster'
4
-
5
- module Basketball
6
- module Draft
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,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Draft
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,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