basketball 0.0.14 → 0.0.16

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.
metadata CHANGED
@@ -1,39 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basketball
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.16
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-06-09 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: slop
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '4.10'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '4.10'
11
+ date: 2023-06-12 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description: " This library is meant to serve as the domain for a basketball league/season
28
14
  simulator/turn-based game. It models core ideas such as: players, general managers,
29
15
  draft strategy, drafting, season generation, season simultation, playoff generation,
30
16
  playoff simulation, and more.\n"
31
17
  email:
32
18
  - mattruggio@icloud.com
33
- executables:
34
- - basketball
35
- - basketball-season-coordinator
36
- - basketball-draft-room
19
+ executables: []
37
20
  extensions: []
38
21
  extra_rdoc_files: []
39
22
  files:
@@ -49,20 +32,15 @@ files:
49
32
  - README.md
50
33
  - Rakefile
51
34
  - basketball.gemspec
52
- - exe/basketball
53
- - exe/basketball-draft-room
54
- - exe/basketball-season-coordinator
55
35
  - lib/basketball.rb
56
36
  - lib/basketball/app.rb
57
- - lib/basketball/app/coordinator_cli.rb
58
37
  - lib/basketball/app/coordinator_repository.rb
59
38
  - lib/basketball/app/document_repository.rb
60
39
  - lib/basketball/app/file_store.rb
61
40
  - lib/basketball/app/in_memory_store.rb
62
41
  - lib/basketball/app/league_repository.rb
63
- - lib/basketball/app/league_serializable.rb
64
- - lib/basketball/app/room_cli.rb
65
42
  - lib/basketball/app/room_repository.rb
43
+ - lib/basketball/app/standings_repository.rb
66
44
  - lib/basketball/draft.rb
67
45
  - lib/basketball/draft/assessment.rb
68
46
  - lib/basketball/draft/event.rb
data/exe/basketball DELETED
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'basketball'
6
-
7
- # This is an example integration script tying all the contexts together.
8
- # This is definitely subject to change as this library gets extended further.
9
-
10
- store = Basketball::App::FileStore.new
11
- room_repository = Basketball::App::RoomRepository.new(store)
12
- league_repository = Basketball::App::LeagueRepository.new(store)
13
- coordinator_repository = Basketball::App::CoordinatorRepository.new(store)
14
- scheduler = Basketball::Season::Scheduler.new
15
-
16
- seed_path = ARGV[0].to_s.empty? ? File.join('spec', 'fixtures', 'seed.json') : ARGV[0]
17
- output_dir = ARGV[1].to_s.empty? ? 'tmp' : ARGV[1]
18
- seed = JSON.parse(File.read(seed_path), symbolize_names: true)
19
-
20
- league_path = File.join(output_dir, '00-league.json')
21
-
22
- File.write(league_path, seed[:league].to_json)
23
-
24
- front_offices = seed.dig(:league, :conferences).flat_map do |c|
25
- c[:divisions].flat_map do |d|
26
- d[:teams].flat_map do |t|
27
- { id: t[:id] }
28
- end
29
- end
30
- end
31
-
32
- room = {
33
- rounds: 12,
34
- front_offices:,
35
- players: seed[:players]
36
- }
37
-
38
- room_input_path = File.join(output_dir, '01-room-input.json')
39
-
40
- File.write(room_input_path, room.to_json)
41
-
42
- room = room_repository.load(room_input_path)
43
-
44
- room.sim_rest!
45
-
46
- room_output_path = File.join(output_dir, '02-room-output.json')
47
-
48
- room_repository.save(room_output_path, room)
49
-
50
- league = league_repository.load(league_path)
51
-
52
- room.teams.each do |team|
53
- team.players.each do |player|
54
- league.sign!(team:, player:)
55
- end
56
- end
57
-
58
- scheduler_input_path = File.join(output_dir, '03-scheduler-input.json')
59
-
60
- league_repository.save(scheduler_input_path, league)
61
-
62
- calendar = scheduler.schedule(league:)
63
- current_date = calendar.exhibition_start_date
64
- coordinator = Basketball::Season::Coordinator.new(league:, calendar:, current_date:)
65
-
66
- coordinator_input_path = File.join(output_dir, '04-coordinator-input.json')
67
-
68
- coordinator_repository.save(coordinator_input_path, coordinator)
69
-
70
- coordinator.sim_rest!
71
-
72
- coordinator_output_path = File.join(output_dir, '05-coordinator-output.json')
73
-
74
- coordinator_repository.save(coordinator_output_path, coordinator)
75
-
76
- preseason_standings = Basketball::Season::Standings.new
77
- season_standings = Basketball::Season::Standings.new
78
-
79
- league.teams.each do |team|
80
- preseason_standings.register!(team)
81
- season_standings.register!(team)
82
- end
83
-
84
- coordinator.exhibition_results.each { |result| preseason_standings.accept!(result) }
85
- coordinator.regular_results.each { |result| season_standings.accept!(result) }
86
-
87
- puts 'Preseason Standings'
88
- puts preseason_standings
89
- puts '--------------------------------------------'
90
- puts 'Season Standings'
91
- puts season_standings
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'basketball'
6
-
7
- Basketball::App::RoomCLI.new(args: ARGV).invoke!
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'basketball'
6
-
7
- Basketball::App::CoordinatorCLI.new(args: ARGV).invoke!
@@ -1,227 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module App
5
- # Examples:
6
- # exe/basketball-season-coordinator -o tmp/coordinator.json
7
- # exe/basketball-season-coordinator -i tmp/coordinator.json -o tmp/coordinator2.json -d 1
8
- # exe/basketball-season-coordinator -i tmp/coordinator2.json -e
9
- # exe/basketball-season-coordinator -i tmp/coordinator2.json -o tmp/coordinator2.json -d 2
10
- # exe/basketball-season-coordinator -i tmp/coordinator2.json -a
11
- #
12
- # exe/basketball-season-coordinator -o tmp/coordinator.json -ae
13
- class CoordinatorCLI
14
- attr_reader :opts, :io, :coordinator_repository
15
-
16
- def initialize(
17
- args:,
18
- io: $stdout,
19
- coordinator_repository: CoordinatorRepository.new(FileStore.new)
20
- )
21
- raise ArgumentError, 'coordinator_repository is required' unless coordinator_repository
22
- raise ArgumentError, 'io is required' unless io
23
-
24
- @io = io
25
- @opts = slop_parse(args)
26
- @coordinator_repository = coordinator_repository
27
-
28
- if no_input? && no_output?
29
- io.puts('Input and/or output paths are required.')
30
-
31
- exit
32
- end
33
-
34
- freeze
35
- end
36
-
37
- def invoke!
38
- coordinator = read
39
-
40
- execute(coordinator)
41
- output_status(coordinator)
42
- write(coordinator)
43
- events(coordinator)
44
-
45
- self
46
- end
47
-
48
- private
49
-
50
- def output_status(coordinator)
51
- io.puts
52
- io.puts('Status')
53
-
54
- if coordinator.done?
55
- io.puts('Coordinator is complete!')
56
- else
57
- io.puts("#{coordinator.days_left} Remaining day(s) (#{coordinator.total_days} total)")
58
- io.puts("Currently on: #{coordinator.current_date}")
59
- io.puts("#{coordinator.exhibitions_left} Remaining exhibition (#{coordinator.total_exhibitions} total)")
60
- io.puts("#{coordinator.regulars_left} Remaining season (#{coordinator.total_regulars} total)")
61
- end
62
- end
63
-
64
- def execute(coordinator)
65
- event_count = 0
66
-
67
- io.puts
68
- io.puts('New Events')
69
-
70
- days&.times do
71
- coordinator.sim! do |event|
72
- io.puts(event)
73
-
74
- event_count += 1
75
- end
76
- end
77
-
78
- if sim_all
79
- coordinator.sim_rest! do |event|
80
- io.puts(event)
81
-
82
- event_count += 1
83
- end
84
- end
85
-
86
- io.puts("Generated #{event_count} new event(s)")
87
-
88
- nil
89
- end
90
-
91
- def days
92
- opts[:days]
93
- end
94
-
95
- def sim_all
96
- opts[:sim_all]
97
- end
98
-
99
- def write(coordinator)
100
- path = output? ? output : input
101
-
102
- coordinator_repository.save(path, coordinator)
103
-
104
- path
105
- end
106
-
107
- def make_league
108
- Org::League.new(
109
- conferences: 2.times.map do |i|
110
- conference_id = "C#{i}"
111
-
112
- Org::Conference.new(
113
- id: conference_id,
114
- divisions: 3.times.map do |j|
115
- division_id = "#{conference_id}-D#{j}"
116
-
117
- Org::Division.new(
118
- id: division_id,
119
- teams: 5.times.map do |k|
120
- team_id = "#{division_id}-T#{k}"
121
-
122
- Org::Team.new(
123
- id: team_id,
124
- players: 12.times.map do |l|
125
- player_id = "#{team_id}-P#{l}"
126
-
127
- Org::Player.new(
128
- id: player_id,
129
- overall: rand(20..100),
130
- position: Org::Position.random
131
- )
132
- end
133
- )
134
- end
135
- )
136
- end
137
- )
138
- end
139
- )
140
- end
141
-
142
- def make_coordinator
143
- league = make_league
144
- year = Time.now.year
145
- calendar = Season::Scheduler.new.schedule(league:, year:)
146
- current_date = calendar.games.min_by(&:date).date
147
-
148
- Season::Coordinator.new(
149
- calendar:,
150
- current_date:,
151
- league:
152
- )
153
- end
154
-
155
- def read
156
- coordinator =
157
- if input?
158
- io.puts("Coordinator loaded from: #{input}")
159
-
160
- coordinator_repository.load(input)
161
- else
162
- io.puts('Input path was not provided, generating fresh coordinator')
163
-
164
- make_coordinator
165
- end
166
-
167
- io.puts("Current Date: #{coordinator.current_date}")
168
-
169
- coordinator
170
- end
171
-
172
- def input
173
- opts[:input]
174
- end
175
-
176
- def input?
177
- !no_input?
178
- end
179
-
180
- def no_input?
181
- input.to_s.empty?
182
- end
183
-
184
- def output
185
- opts[:output]
186
- end
187
-
188
- def no_output?
189
- output.to_s.empty?
190
- end
191
-
192
- def output?
193
- !no_output?
194
- end
195
-
196
- def events(coordinator)
197
- return unless opts[:events]
198
-
199
- io.puts
200
- io.puts('Event Log')
201
-
202
- puts coordinator.results
203
- end
204
-
205
- def slop_parse(args)
206
- Slop.parse(args) do |o|
207
- o.banner = 'Usage: basketball-season-coordinator [options] ...'
208
-
209
- input_description = <<~DESC
210
- Path to load the Coordinator from. If omitted then a new coordinator will be created.
211
- DESC
212
-
213
- o.string '-i', '--input', input_description.chomp
214
- o.string '-o', '--output', 'Path to save updated coordinator. If omitted then the input path will be used.'
215
- o.integer '-d', '--days', 'Number of days to simulate'
216
- o.bool '-a', '--sim-all', 'Simulate the rest of the coordinator', default: false
217
- o.bool '-e', '--events', 'Output event log.', default: false
218
-
219
- o.on '-h', '--help', 'Print out help, like this is doing right now.' do
220
- io.puts(o)
221
- exit
222
- end
223
- end.to_h
224
- end
225
- end
226
- end
227
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module App
5
- # Provides methods to serialize/deserialize a League object
6
- module LeagueSerializable
7
- # Serialization
8
-
9
- def serialize_league(league)
10
- {
11
- id: league.id,
12
- conferences: serialize_conferences(league.conferences)
13
- }
14
- end
15
-
16
- def serialize_conferences(conferences)
17
- conferences.map do |conference|
18
- {
19
- id: conference.id,
20
- divisions: serialize_divisions(conference.divisions)
21
- }
22
- end
23
- end
24
-
25
- def serialize_divisions(divisions)
26
- divisions.map do |division|
27
- {
28
- id: division.id,
29
- teams: serialize_teams(division.teams)
30
- }
31
- end
32
- end
33
-
34
- def serialize_teams(teams)
35
- teams.map do |team|
36
- {
37
- id: team.id,
38
- players: serialize_players(team.players)
39
- }
40
- end
41
- end
42
-
43
- def serialize_players(players)
44
- players.map do |player|
45
- {
46
- id: player.id,
47
- overall: player.overall,
48
- position: player.position&.code
49
- }
50
- end
51
- end
52
-
53
- # Deserialization
54
-
55
- def deserialize_league(league_hash)
56
- Org::League.new(
57
- conferences: deserialize_conferences(league_hash[:conferences])
58
- )
59
- end
60
-
61
- def deserialize_conferences(conference_hashes)
62
- (conference_hashes || []).map do |conference_hash|
63
- Org::Conference.new(
64
- id: conference_hash[:id],
65
- divisions: deserialize_divisions(conference_hash[:divisions])
66
- )
67
- end
68
- end
69
-
70
- def deserialize_divisions(division_hashes)
71
- (division_hashes || []).map do |division_hash|
72
- Org::Division.new(
73
- id: division_hash[:id],
74
- teams: deserialize_teams(division_hash[:teams])
75
- )
76
- end
77
- end
78
-
79
- def deserialize_teams(team_hashes)
80
- (team_hashes || []).map do |team_hash|
81
- Org::Team.new(
82
- id: team_hash[:id],
83
- players: deserialize_players(team_hash[:players])
84
- )
85
- end
86
- end
87
-
88
- def deserialize_players(player_hashes)
89
- (player_hashes || []).map do |player_hash|
90
- Org::Player.new(
91
- id: player_hash[:id],
92
- overall: player_hash[:overall],
93
- position: Org::Position.new(player_hash[:position])
94
- )
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module App
5
- # Examples:
6
- # exe/basketball-draft-room -o tmp/draft.json
7
- # exe/basketball-draft-room -i tmp/draft.json -o tmp/draft-wip.json -s 26 -p P-5,P-10 -l 10
8
- # exe/basketball-draft-room -i tmp/draft-wip.json -x 2
9
- # exe/basketball-draft-room -i tmp/draft-wip.json -g -l 10
10
- # exe/basketball-draft-room -i tmp/draft-wip.json -s 30 -l 10
11
- # exe/basketball-draft-room -i tmp/draft-wip.json -ate
12
- class RoomCLI
13
- class PlayerNotFound < StandardError; end
14
-
15
- attr_reader :opts,
16
- :io,
17
- :room_repository
18
-
19
- def initialize(
20
- args:,
21
- io: $stdout,
22
- room_repository: RoomRepository.new(FileStore.new)
23
- )
24
- @io = io
25
- @opts = slop_parse(args)
26
- @room_repository = room_repository
27
-
28
- if opts[:input].to_s.empty? && opts[:output].to_s.empty?
29
- io.puts('Input and/or output paths are required.')
30
-
31
- exit
32
- end
33
-
34
- freeze
35
- end
36
-
37
- def invoke!
38
- room = load_room
39
-
40
- execute(room)
41
- status(room)
42
- write(room)
43
- events(room)
44
- teams(room)
45
- query(room)
46
-
47
- self
48
- end
49
-
50
- private
51
-
52
- def status(room)
53
- io.puts
54
- io.puts('Status')
55
-
56
- if room.done?
57
- io.puts('Draft is complete!')
58
- else
59
- round = room.round
60
- round_pick = room.round_pick
61
- front_office = room.front_office
62
-
63
- io.puts("#{room.remaining_picks} Remaining pick(s)")
64
- io.puts("Up Next: Round #{round} pick #{round_pick} for #{front_office}")
65
- end
66
- end
67
-
68
- def slop_parse(args)
69
- Slop.parse(args) do |o|
70
- o.banner = 'Usage: basketball-draft-room [options] ...'
71
-
72
- o.string '-i', '--input', 'Path to load the Room from. If omitted then a new draft will be generated.'
73
- o.string '-o', '--output', 'Path to write the room to (if omitted then input path will be used)'
74
- o.integer '-s', '--simulate', 'Number of picks to simulate (default is 0).', default: 0
75
- o.bool '-a', '--simulate-all', 'Simulate the rest of the draft', default: false
76
- o.array '-p', '--picks', 'Comma-separated list of ordered player IDs to pick.', delimiter: ','
77
- o.integer '-l', '--list', 'List the top rated available players (default is 0).', default: 0
78
- o.bool '-t', '--teams', 'Output all teams and their picks', default: false
79
- o.integer '-x', '--skip', 'Number of picks to skip (default is 0).', default: 0
80
- o.bool '-e', '--events', 'Output event log.', default: false
81
-
82
- o.on '-h', '--help', 'Print out help, like this is doing right now.' do
83
- io.puts(o)
84
- exit
85
- end
86
- end.to_h
87
- end
88
-
89
- def load_room
90
- if opts[:input].to_s.empty?
91
- io.puts('Input path was not provided, generating fresh front_offices and players')
92
-
93
- generate_draft
94
- else
95
- io.puts("Draft loaded from: #{opts[:input]}")
96
-
97
- read
98
- end
99
- end
100
-
101
- def generate_draft
102
- front_offices = 30.times.map do |i|
103
- Draft::FrontOffice.new(
104
- id: "T-#{i + 1}"
105
- )
106
- end
107
-
108
- players = 450.times.map do |i|
109
- Org::Player.new(
110
- id: "P-#{i + 1}",
111
- overall: (20..100).to_a.sample,
112
- position: Org::Position.random
113
- )
114
- end
115
-
116
- Draft::Room.new(rounds: 12, players:, front_offices:)
117
- end
118
-
119
- def teams(room)
120
- return unless opts[:teams]
121
-
122
- io.puts
123
- io.puts(room.teams)
124
- end
125
-
126
- def events(room)
127
- return unless opts[:events]
128
-
129
- io.puts
130
- io.puts('Event Log')
131
-
132
- puts room.events
133
- end
134
-
135
- def query(room)
136
- list = opts[:list]
137
-
138
- return if list <= 0
139
-
140
- players = room.undrafted_players.sort_by(&:overall).reverse.take(opts[:list])
141
-
142
- io.puts
143
- io.puts("Top #{list} available players")
144
- io.puts(players)
145
- end
146
-
147
- def read
148
- room_repository.load(opts[:input])
149
- end
150
-
151
- # rubocop:disable Metrics/AbcSize
152
- def execute(room)
153
- event_count = 0
154
-
155
- io.puts
156
- io.puts('New Events')
157
-
158
- (opts[:picks] || []).each do |id|
159
- break if room.done?
160
-
161
- player = room.players.find { |p| p.id == id.to_s.upcase }
162
-
163
- raise PlayerNotFound, "player not found by id: #{id}" unless player
164
-
165
- event = room.pick!(player)
166
-
167
- io.puts(event)
168
-
169
- event_count += 1
170
- end
171
-
172
- opts[:skip].times do
173
- event = room.skip!
174
-
175
- io.puts(event)
176
-
177
- event_count += 1
178
- end
179
-
180
- opts[:simulate].times do
181
- room.sim!
182
-
183
- event_count += 1
184
- end
185
-
186
- if opts[:simulate_all]
187
- room.sim_rest! do |event|
188
- io.puts(event)
189
-
190
- event_count += 1
191
- end
192
- end
193
-
194
- io.puts("Generated #{event_count} new event(s)")
195
-
196
- nil
197
- end
198
- # rubocop:enable Metrics/AbcSize
199
-
200
- def output_default_to_input
201
- opts[:output].to_s.empty? ? opts[:input] : opts[:output]
202
- end
203
-
204
- def write(room)
205
- output = output_default_to_input
206
-
207
- room_repository.save(output, room)
208
-
209
- io.puts
210
- io.puts("Draft written to: #{output}")
211
-
212
- nil
213
- end
214
- end
215
- end
216
- end