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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -19
  3. data/CHANGELOG.md +1 -31
  4. data/README.md +72 -91
  5. data/basketball.gemspec +3 -6
  6. data/exe/{basketball-schedule → 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/app/room_cli.rb +212 -0
  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/{drafting → draft}/event.rb +4 -3
  16. data/lib/basketball/draft/front_office.rb +99 -0
  17. data/lib/basketball/draft/pick.rb +32 -0
  18. data/lib/basketball/draft/room.rb +221 -0
  19. data/lib/basketball/{drafting/player_search.rb → draft/scout.rb} +5 -10
  20. data/lib/basketball/draft/skip.rb +12 -0
  21. data/lib/basketball/draft.rb +16 -0
  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/{drafting → 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 +90 -0
  30. data/lib/basketball/season/coordinator.rb +239 -0
  31. data/lib/basketball/{scheduling/preseason_game.rb → season/exhibition.rb} +3 -2
  32. data/lib/basketball/season/game.rb +37 -0
  33. data/lib/basketball/season/matchup.rb +27 -0
  34. data/lib/basketball/season/opponent.rb +15 -0
  35. data/lib/basketball/season/regular.rb +9 -0
  36. data/lib/basketball/season/result.rb +37 -0
  37. data/lib/basketball/season.rb +16 -0
  38. data/lib/basketball/value_object.rb +4 -1
  39. data/lib/basketball/version.rb +1 -1
  40. data/lib/basketball.rb +11 -6
  41. metadata +40 -52
  42. data/lib/basketball/drafting/cli.rb +0 -235
  43. data/lib/basketball/drafting/engine.rb +0 -221
  44. data/lib/basketball/drafting/engine_serializer.rb +0 -186
  45. data/lib/basketball/drafting/front_office.rb +0 -92
  46. data/lib/basketball/drafting/league.rb +0 -70
  47. data/lib/basketball/drafting/pick_event.rb +0 -25
  48. data/lib/basketball/drafting/player.rb +0 -43
  49. data/lib/basketball/drafting/roster.rb +0 -37
  50. data/lib/basketball/drafting/sim_event.rb +0 -23
  51. data/lib/basketball/drafting/skip_event.rb +0 -13
  52. data/lib/basketball/drafting.rb +0 -9
  53. data/lib/basketball/scheduling/calendar.rb +0 -121
  54. data/lib/basketball/scheduling/calendar_serializer.rb +0 -94
  55. data/lib/basketball/scheduling/cli.rb +0 -198
  56. data/lib/basketball/scheduling/conference.rb +0 -57
  57. data/lib/basketball/scheduling/coordinator.rb +0 -180
  58. data/lib/basketball/scheduling/division.rb +0 -43
  59. data/lib/basketball/scheduling/game.rb +0 -32
  60. data/lib/basketball/scheduling/league.rb +0 -114
  61. data/lib/basketball/scheduling/league_serializer.rb +0 -99
  62. data/lib/basketball/scheduling/season_game.rb +0 -8
  63. data/lib/basketball/scheduling/team.rb +0 -21
  64. data/lib/basketball/scheduling.rb +0 -17
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.7
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-14 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-schedule
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-schedule
51
+ - exe/basketball-coordinator
52
+ - exe/basketball-room
67
53
  - lib/basketball.rb
68
- - lib/basketball/drafting.rb
69
- - lib/basketball/drafting/cli.rb
70
- - lib/basketball/drafting/engine.rb
71
- - lib/basketball/drafting/engine_serializer.rb
72
- - lib/basketball/drafting/event.rb
73
- - lib/basketball/drafting/front_office.rb
74
- - lib/basketball/drafting/league.rb
75
- - lib/basketball/drafting/pick_event.rb
76
- - lib/basketball/drafting/player.rb
77
- - lib/basketball/drafting/player_search.rb
78
- - lib/basketball/drafting/position.rb
79
- - lib/basketball/drafting/roster.rb
80
- - lib/basketball/drafting/sim_event.rb
81
- - lib/basketball/drafting/skip_event.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
60
+ - lib/basketball/draft.rb
61
+ - lib/basketball/draft/assessment.rb
62
+ - lib/basketball/draft/event.rb
63
+ - lib/basketball/draft/front_office.rb
64
+ - lib/basketball/draft/pick.rb
65
+ - lib/basketball/draft/room.rb
66
+ - lib/basketball/draft/scout.rb
67
+ - lib/basketball/draft/skip.rb
82
68
  - lib/basketball/entity.rb
83
- - lib/basketball/scheduling.rb
84
- - lib/basketball/scheduling/calendar.rb
85
- - lib/basketball/scheduling/calendar_serializer.rb
86
- - lib/basketball/scheduling/cli.rb
87
- - lib/basketball/scheduling/conference.rb
88
- - lib/basketball/scheduling/coordinator.rb
89
- - lib/basketball/scheduling/division.rb
90
- - lib/basketball/scheduling/game.rb
91
- - lib/basketball/scheduling/league.rb
92
- - lib/basketball/scheduling/league_serializer.rb
93
- - lib/basketball/scheduling/preseason_game.rb
94
- - lib/basketball/scheduling/season_game.rb
95
- - lib/basketball/scheduling/team.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
74
+ - lib/basketball/season.rb
75
+ - lib/basketball/season/arena.rb
76
+ - lib/basketball/season/calendar.rb
77
+ - lib/basketball/season/coordinator.rb
78
+ - lib/basketball/season/exhibition.rb
79
+ - lib/basketball/season/game.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 Engine
114
+ summary: Basketball Simulation Domain Model
127
115
  test_files: []
@@ -1,235 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'engine'
4
- require_relative 'engine_serializer'
5
- require_relative 'player_search'
6
- require_relative 'position'
7
-
8
- module Basketball
9
- module Drafting
10
- # Examples:
11
- # exe/basketball-draft -o tmp/draft.json
12
- # exe/basketball-draft -i tmp/draft.json -o tmp/draft-wip.json -s 26 -p P-5,P-10 -t 10 -q PG
13
- # exe/basketball-draft -i tmp/draft-wip.json -x 2
14
- # exe/basketball-draft -i tmp/draft-wip.json -r -t 10
15
- # exe/basketball-draft -i tmp/draft-wip.json -t 10 -q SG
16
- # exe/basketball-draft -i tmp/draft-wip.json -s 30 -t 10
17
- # exe/basketball-draft -i tmp/draft-wip.json -a -r
18
- # exe/basketball-draft -i tmp/draft-wip.json -l
19
- class CLI
20
- class PlayerNotFound < StandardError; end
21
-
22
- attr_reader :opts, :serializer, :io
23
-
24
- def initialize(args:, io: $stdout)
25
- @io = io
26
- @serializer = EngineSerializer.new
27
- @opts = slop_parse(args)
28
-
29
- if opts[:input].to_s.empty? && opts[:output].to_s.empty?
30
- io.puts('Input and/or output paths are required.')
31
-
32
- exit
33
- end
34
-
35
- freeze
36
- end
37
-
38
- def invoke!
39
- engine = load_engine
40
-
41
- execute(engine)
42
-
43
- io.puts
44
- io.puts('Status')
45
-
46
- if engine.done?
47
- io.puts('Draft is complete!')
48
- else
49
- current_round = engine.current_round
50
- current_round_pick = engine.current_round_pick
51
- current_front_office = engine.current_front_office
52
-
53
- io.puts("#{engine.remaining_picks} Remaining pick(s)")
54
- io.puts("Up Next: Round #{current_round} pick #{current_round_pick} for #{current_front_office}")
55
- end
56
-
57
- write(engine)
58
-
59
- log(engine)
60
-
61
- league(engine)
62
-
63
- query(engine)
64
-
65
- self
66
- end
67
-
68
- private
69
-
70
- def slop_parse(args)
71
- Slop.parse(args) do |o|
72
- o.banner = 'Usage: basketball-draft [options] ...'
73
-
74
- o.string '-i', '--input',
75
- 'Path to load the engine from. If omitted then a new draft will be generated.'
76
- o.string '-o', '--output', 'Path to write the engine to (if omitted then input path will be used)'
77
- o.integer '-s', '--simulate', 'Number of picks to simulate (default is 0).', default: 0
78
- o.bool '-a', '--simulate-all', 'Simulate the rest of the draft', default: false
79
- o.array '-p', '--picks', 'Comma-separated list of ordered player IDs to pick.', delimiter: ','
80
- o.integer '-t', '--top', 'Output the top rated available players (default is 0).', default: 0
81
- o.string '-q', '--query', "Filter TOP by position: #{Position::ALL_VALUES.join(', ')}."
82
- o.bool '-r', '--rosters', 'Output all front_office rosters.', default: false
83
- o.integer '-x', '--skip', 'Number of picks to skip (default is 0).', default: 0
84
- o.bool '-l', '--log', 'Output event log.', default: false
85
-
86
- o.on '-h', '--help', 'Print out help, like this is doing right now.' do
87
- io.puts(o)
88
- exit
89
- end
90
- end.to_h
91
- end
92
-
93
- def load_engine
94
- if opts[:input].to_s.empty?
95
- io.puts('Input path was not provided, generating fresh front_offices and players')
96
-
97
- generate_draft
98
- else
99
- io.puts("Draft loaded from: #{opts[:input]}")
100
-
101
- read
102
- end
103
- end
104
-
105
- def generate_draft
106
- front_offices = 30.times.map do |i|
107
- FrontOffice.new(
108
- id: "T-#{i + 1}", name: Faker::Team.name
109
- )
110
- end
111
-
112
- players = 450.times.map do |i|
113
- Player.new(
114
- id: "P-#{i + 1}",
115
- first_name: Faker::Name.first_name,
116
- last_name: Faker::Name.last_name,
117
- position: Position.random,
118
- overall: (0..100).to_a.sample
119
- )
120
- end
121
-
122
- Engine.new(players:, front_offices:)
123
- end
124
-
125
- def league(engine)
126
- return unless opts[:rosters]
127
-
128
- io.puts
129
- io.puts(engine.to_league)
130
- end
131
-
132
- def log(engine)
133
- return unless opts[:log]
134
-
135
- io.puts
136
- io.puts('Event Log')
137
-
138
- puts engine.events
139
- end
140
-
141
- # rubocop:disable Metrics/AbcSize
142
- def query(engine)
143
- top = opts[:top]
144
-
145
- return if top <= 0
146
-
147
- search = PlayerSearch.new(engine.undrafted_players)
148
- position = opts[:query].to_s.empty? ? nil : Position.new(opts[:query])
149
- players = search.query(position:).take(opts[:top])
150
-
151
- io.puts
152
- io.print("Top #{top} available players")
153
-
154
- if position
155
- io.puts(" for #{position} position:")
156
- else
157
- io.puts(' for all positions:')
158
- end
159
-
160
- io.puts(players)
161
- end
162
- # rubocop:enable Metrics/AbcSize
163
-
164
- def read
165
- contents = File.read(opts[:input])
166
- serializer.deserialize(contents)
167
- end
168
-
169
- # rubocop:disable Metrics/AbcSize
170
- def execute(engine)
171
- event_count = 0
172
-
173
- io.puts
174
- io.puts('New Events')
175
-
176
- (opts[:picks] || []).each do |id|
177
- break if engine.done?
178
-
179
- player = engine.players.find { |p| p.id == id.to_s.upcase }
180
-
181
- raise PlayerNotFound, "player not found by id: #{id}" unless player
182
-
183
- event = engine.pick!(player)
184
-
185
- io.puts(event)
186
-
187
- event_count += 1
188
- end
189
-
190
- opts[:skip].times do
191
- event = engine.skip!
192
-
193
- io.puts(event)
194
-
195
- event_count += 1
196
- end
197
-
198
- engine.sim!(opts[:simulate]) do |event|
199
- io.puts(event)
200
-
201
- event_count += 1
202
- end
203
-
204
- if opts[:simulate_all]
205
- engine.sim! do |event|
206
- io.puts(event)
207
-
208
- event_count += 1
209
- end
210
- end
211
-
212
- io.puts("Generated #{event_count} new event(s)")
213
-
214
- nil
215
- end
216
- # rubocop:enable Metrics/AbcSize
217
-
218
- def write(engine)
219
- output = opts[:output].to_s.empty? ? opts[:input] : opts[:output]
220
-
221
- contents = serializer.serialize(engine)
222
- out_dir = File.dirname(output)
223
-
224
- FileUtils.mkdir_p(out_dir)
225
-
226
- File.write(output, contents)
227
-
228
- io.puts
229
- io.puts("Draft written to: #{output}")
230
-
231
- nil
232
- end
233
- end
234
- end
235
- end
@@ -1,221 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'league'
4
-
5
- module Basketball
6
- module Drafting
7
- class Engine
8
- class AlreadyPickedError < StandardError; end
9
- class DupeEventError < StandardError; end
10
- class EventOutOfOrderError < StandardError; end
11
- class UnknownPlayerError < StandardError; end
12
- class UnknownFrontOfficeError < StandardError; end
13
- class EndOfDraftError < StandardError; end
14
-
15
- DEFAULT_ROUNDS = 12
16
-
17
- private_constant :DEFAULT_ROUNDS
18
-
19
- attr_reader :events, :rounds
20
-
21
- def initialize(players: [], front_offices: [], events: [], rounds: DEFAULT_ROUNDS)
22
- @players_by_id = players.to_h { |p| [p.id, p] }
23
- @front_offices_by_id = front_offices.to_h { |fo| [fo.id, fo] }
24
- @events = []
25
- @rounds = rounds.to_i
26
-
27
- # Each one will be validated for correctness.
28
- events.each { |e| play!(e) }
29
-
30
- freeze
31
- end
32
-
33
- def to_league
34
- League.new(front_offices:).tap do |league|
35
- player_events.each do |event|
36
- league.register!(player: event.player, front_office: event.front_office)
37
- end
38
-
39
- undrafted_players.each do |player|
40
- league.register!(player:)
41
- end
42
- end
43
- end
44
-
45
- def to_s
46
- events.join("\n")
47
- end
48
-
49
- def front_offices
50
- front_offices_by_id.values
51
- end
52
-
53
- def players
54
- players_by_id.values
55
- end
56
-
57
- def total_picks
58
- rounds * front_offices.length
59
- end
60
-
61
- def current_round
62
- return if done?
63
-
64
- (current_pick / front_offices.length.to_f).ceil
65
- end
66
-
67
- def current_round_pick
68
- return if done?
69
-
70
- mod = current_pick % front_offices.length
71
-
72
- mod.positive? ? mod : front_offices.length
73
- end
74
-
75
- def current_front_office
76
- return if done?
77
-
78
- front_offices[current_round_pick - 1]
79
- end
80
-
81
- def current_pick
82
- return if done?
83
-
84
- internal_current_pick
85
- end
86
-
87
- def remaining_picks
88
- total_picks - internal_current_pick + 1
89
- end
90
-
91
- def done?
92
- internal_current_pick > total_picks
93
- end
94
-
95
- def not_done?
96
- !done?
97
- end
98
-
99
- def skip!
100
- return if done?
101
-
102
- event = SkipEvent.new(
103
- front_office: current_front_office,
104
- pick: current_pick,
105
- round: current_round,
106
- round_pick: current_round_pick
107
- )
108
-
109
- play!(event)
110
-
111
- event
112
- end
113
-
114
- def sim!(times = nil)
115
- counter = 0
116
- events = []
117
-
118
- until done? || (times && counter >= times)
119
- front_office = current_front_office
120
-
121
- player = front_office.pick(
122
- undrafted_player_search:,
123
- drafted_players: drafted_players(front_office),
124
- round: current_round
125
- )
126
-
127
- event = SimEvent.new(
128
- front_office:,
129
- player:,
130
- pick: current_pick,
131
- round: current_round,
132
- round_pick: current_round_pick
133
- )
134
-
135
- play!(event)
136
-
137
- yield(event) if block_given?
138
-
139
- counter += 1
140
- events << event
141
- end
142
-
143
- events
144
- end
145
-
146
- def pick!(player)
147
- return nil if done?
148
-
149
- event = PickEvent.new(
150
- front_office: current_front_office,
151
- player:,
152
- pick: current_pick,
153
- round: current_round,
154
- round_pick: current_round_pick
155
- )
156
-
157
- play!(event)
158
- end
159
-
160
- def undrafted_players
161
- players - drafted_players
162
- end
163
-
164
- def undrafted_player_search
165
- PlayerSearch.new(undrafted_players)
166
- end
167
-
168
- private
169
-
170
- attr_reader :players_by_id, :front_offices_by_id
171
-
172
- def player_events
173
- events.select { |e| e.respond_to?(:player) }
174
- end
175
-
176
- def internal_current_pick
177
- events.length + 1
178
- end
179
-
180
- def drafted_players(front_office = nil)
181
- player_events.each_with_object([]) do |e, memo|
182
- next unless front_office.nil? || e.front_office == front_office
183
-
184
- memo << e.player
185
- end
186
- end
187
-
188
- # rubocop:disable Metrics/AbcSize
189
- # rubocop:disable Metrics/CyclomaticComplexity
190
- # rubocop:disable Metrics/PerceivedComplexity
191
- def play!(event)
192
- if event.respond_to?(:player) && drafted_players.include?(event.player)
193
- raise AlreadyPickedError, "#{player} was already picked"
194
- end
195
-
196
- if event.respond_to?(:player) && !players.include?(event.player)
197
- raise UnknownPlayerError, "#{event.player} doesnt exist"
198
- end
199
-
200
- if event.front_office != current_front_office
201
- raise EventOutOfOrder, "#{event} #{event.front_office} cant pick right now"
202
- end
203
-
204
- raise UnknownFrontOfficeError, "#{front_office} doesnt exist" unless front_offices.include?(event.front_office)
205
-
206
- raise DupeEventError, "#{event} is a dupe" if events.include?(event)
207
- raise EventOutOfOrder, "#{event} has wrong pick" if event.pick != current_pick
208
- raise EventOutOfOrder, "#{event} has wrong round" if event.round != current_round
209
- raise EventOutOfOrder, "#{event} has wrong round_pick" if event.round_pick != current_round_pick
210
- raise EndOfDraftError, "#{total_picks} pick limit reached" if events.length > total_picks + 1
211
-
212
- events << event
213
-
214
- event
215
- end
216
- # rubocop:enable Metrics/AbcSize
217
- # rubocop:enable Metrics/CyclomaticComplexity
218
- # rubocop:enable Metrics/PerceivedComplexity
219
- end
220
- end
221
- end