basketball 0.0.14 → 0.0.16

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