basketball 0.0.7 → 0.0.9

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