basketball 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -2
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +35 -8
- data/lib/basketball/drafting/cli.rb +40 -13
- data/lib/basketball/drafting/engine.rb +71 -41
- data/lib/basketball/drafting/engine_serializer.rb +51 -52
- data/lib/basketball/drafting/event.rb +8 -8
- data/lib/basketball/drafting/front_office.rb +9 -4
- data/lib/basketball/drafting/league.rb +71 -0
- data/lib/basketball/drafting/pick_event.rb +2 -2
- data/lib/basketball/drafting/roster.rb +19 -20
- data/lib/basketball/drafting/sim_event.rb +2 -2
- data/lib/basketball/drafting/skip_event.rb +13 -0
- data/lib/basketball/version.rb +1 -1
- metadata +4 -3
- data/lib/basketball/drafting/team.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 793bfe7afef52aa72d3f89c94541fc7d8f24b350d63cb7c75d7215c285695b74
|
4
|
+
data.tar.gz: 46a3ac7199fb534ff4827c8fe48bbe371c7f87bc623e579ffdce2310132a1263
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf8c46719f3669a31c932d7dc2742c287666fcb305c90c596c87b79c37d4fb95aafa446c70575ed4d8d3750a3f3672646e9ee49c6a0cf95e4dd7eb092831670e
|
7
|
+
data.tar.gz: db01d3bfc4a8c1448875c5a099d0a33fd60ad60471ae5cda5f9eeeaf8a1fb2daa3e52d5ca2d8138867e42aaf6f54d259cdad7d3628cfbaa2d7bcf134fe5dc4b9
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
+
#### 0.0.5 - May 5th, 2023
|
2
|
+
|
3
|
+
* Remove the notion of Team in favor of a flat front office.
|
4
|
+
#### 0.0.4 - May 5th, 2023
|
5
|
+
|
6
|
+
* Add ability to skip draft picks using `Basketball::Drafting::Engine#skip!`
|
7
|
+
* Add ability to output event full drafting event log using CLI: `basketball-draft -i tmp/draft-wip.json -l`
|
8
|
+
* Add ability to skip draft picks using CLI: `basketball-draft -i tmp/draft-wip.json -x 1`
|
9
|
+
|
1
10
|
#### 0.0.3 - May 5th, 2023
|
2
11
|
|
3
|
-
* Drafting::Engine#sim
|
4
|
-
* Added Drafting::Engine#undrafted_player_search
|
12
|
+
* `Drafting::Engine#sim!` should return events
|
13
|
+
* Added `Drafting::Engine#undrafted_player_search`
|
5
14
|
|
6
15
|
#### 0.0.2 - May 4th, 2023
|
7
16
|
|
data/CODE_OF_CONDUCT.md
CHANGED
@@ -56,7 +56,7 @@ further defined and clarified by project maintainers.
|
|
56
56
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
58
|
reported. All complaints will be reviewed and investigated and will result in a response that
|
59
|
-
is deemed necessary and appropriate to the circumstances. The project
|
59
|
+
is deemed necessary and appropriate to the circumstances. The project front_office is
|
60
60
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
61
61
|
Further details of specific enforcement policies may be posted separately.
|
62
62
|
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ Install executable scripts:
|
|
26
26
|
bundle binstubs basketball
|
27
27
|
````
|
28
28
|
|
29
|
-
##
|
29
|
+
## Sub-Modules
|
30
30
|
|
31
31
|
This library is broken down into several bounded contexts that can be consumed either via its Ruby API's or CLI through provided executable scripts:
|
32
32
|
|
@@ -38,6 +38,21 @@ The drafting module is responsible for providing a turn-based iterator allowing
|
|
38
38
|
|
39
39
|
![Basketball Architecture - Drafting.png](/docs/architecture/drafting.png)
|
40
40
|
|
41
|
+
Element | Description
|
42
|
+
:------------ | :-----------
|
43
|
+
**Drafting** | Bounded context (sub-module) dealing with executing an asynchronous draft.
|
44
|
+
**Engine** | Aggregate root responsible for providing an iterable interface capable of executing a draft, pick by pick.
|
45
|
+
**Event** | Represents one cycle execution result from the Engine.
|
46
|
+
**External Ruby App** | An example consumer for the Drafting context.
|
47
|
+
**Front Office** | Identifiable as a team, contains configuration for how to auto-pick draft selections.
|
48
|
+
**League** | Set of rosters that together form a cohesive league.
|
49
|
+
**Pick Event** | Result event emitted when a player is manually selected.
|
50
|
+
**Playe ** | Identitiable as a person able to be drafted.
|
51
|
+
**Position** | Value object based on position code: PG, SG, SF, PF, and C.
|
52
|
+
**Roster** | Identifiable as a team, set of players that make up a single team.
|
53
|
+
**Sim Event** | Result event emitted when a player is automatically selected by a front office.
|
54
|
+
**Skip Event** | Result event emitted when a front office decides to skip a round.
|
55
|
+
|
41
56
|
### The Drafting CLI
|
42
57
|
|
43
58
|
The drafting module is meant to be interfaces using its Ruby API by consuming applications. It also ships with a CLI which a user can interact with to emulate "the draft process". Technically speaking, the CLI provides an example application built on top of the Drafting module. Each time a CLI command is executed, its results will be resaved, so the output file can then be used as the next command's input file to string together commands. The following sections are example CLI interactions:
|
@@ -45,43 +60,55 @@ The drafting module is meant to be interfaces using its Ruby API by consuming ap
|
|
45
60
|
#### Generate a Fresh Draft
|
46
61
|
|
47
62
|
```zsh
|
48
|
-
|
63
|
+
basketball-draft -o tmp/draft.json
|
49
64
|
```
|
50
65
|
|
51
66
|
#### N Top Available Players
|
52
67
|
|
53
68
|
```zsh
|
54
|
-
|
69
|
+
basketball-draft -i tmp/draft.json -t 10
|
55
70
|
```
|
56
71
|
|
57
72
|
#### N Top Available Players for a Position
|
58
73
|
|
59
74
|
```zsh
|
60
|
-
|
75
|
+
basketball-draft -i tmp/draft.json -t 10 -q PG
|
61
76
|
```
|
62
77
|
|
63
78
|
#### Output Current Rosters
|
64
79
|
|
65
80
|
```zsh
|
66
|
-
|
81
|
+
basketball-draft -i tmp/draft.json -r
|
82
|
+
```
|
83
|
+
|
84
|
+
#### Output Event Log
|
85
|
+
|
86
|
+
```zsh
|
87
|
+
basketball-draft -i tmp/draft.json -l
|
67
88
|
```
|
68
89
|
|
69
90
|
#### Simulate N picks
|
70
91
|
|
71
92
|
```zsh
|
72
|
-
|
93
|
+
basketball-draft -i tmp/draft.json -s 10
|
94
|
+
```
|
95
|
+
|
96
|
+
#### Skip N picks
|
97
|
+
|
98
|
+
```zsh
|
99
|
+
basketball-draft -i tmp/draft.json -x 10
|
73
100
|
```
|
74
101
|
|
75
102
|
#### Pick Players
|
76
103
|
|
77
104
|
```zsh
|
78
|
-
|
105
|
+
basketball-draft -i tmp/draft.json -p P-100,P-200,P-300
|
79
106
|
```
|
80
107
|
|
81
108
|
#### Simulate the Rest of the Draft
|
82
109
|
|
83
110
|
```zsh
|
84
|
-
|
111
|
+
basketball-draft -i tmp/draft.json -a
|
85
112
|
```
|
86
113
|
|
87
114
|
## Contributing
|
@@ -9,12 +9,16 @@ module Basketball
|
|
9
9
|
module Drafting
|
10
10
|
# Example:
|
11
11
|
# exe/basketball-draft -o tmp/draft.json
|
12
|
-
# exe/basketball-draft -i tmp/draft.json -o tmp/draft-wip.json -s
|
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
|
13
14
|
# exe/basketball-draft -i tmp/draft-wip.json -r -t 10
|
14
15
|
# exe/basketball-draft -i tmp/draft-wip.json -t 10 -q SG
|
15
16
|
# exe/basketball-draft -i tmp/draft-wip.json -s 30 -t 10
|
16
17
|
# exe/basketball-draft -i tmp/draft-wip.json -a -r
|
18
|
+
# exe/basketball-draft -i tmp/draft-wip.json -l
|
17
19
|
class CLI
|
20
|
+
class PlayerNotFound < StandardError; end
|
21
|
+
|
18
22
|
attr_reader :opts, :serializer, :io
|
19
23
|
|
20
24
|
def initialize(args:, io: $stdout)
|
@@ -42,13 +46,19 @@ module Basketball
|
|
42
46
|
if engine.done?
|
43
47
|
io.puts('Draft is complete!')
|
44
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
|
+
|
45
53
|
io.puts("#{engine.remaining_picks} Remaining pick(s)")
|
46
|
-
io.puts("Round #{
|
54
|
+
io.puts("Up Next: Round #{current_round} pick #{current_round_pick} for #{current_front_office}")
|
47
55
|
end
|
48
56
|
|
49
57
|
write(engine)
|
50
58
|
|
51
|
-
|
59
|
+
log(engine)
|
60
|
+
|
61
|
+
league(engine)
|
52
62
|
|
53
63
|
query(engine)
|
54
64
|
|
@@ -69,7 +79,9 @@ module Basketball
|
|
69
79
|
o.array '-p', '--picks', 'Comma-separated list of ordered player IDs to pick.', delimiter: ','
|
70
80
|
o.integer '-t', '--top', 'Output the top rated available players (default is 0).', default: 0
|
71
81
|
o.string '-q', '--query', "Filter TOP by position: #{Position::ALL_VALUES.join(', ')}."
|
72
|
-
o.bool '-r', '--rosters', 'Output all
|
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
|
73
85
|
|
74
86
|
o.on '-h', '--help', 'Print out help, like this is doing right now.' do
|
75
87
|
io.puts(o)
|
@@ -80,7 +92,7 @@ module Basketball
|
|
80
92
|
|
81
93
|
def load_engine
|
82
94
|
if opts[:input].to_s.empty?
|
83
|
-
io.puts('Input path was not provided, generating fresh
|
95
|
+
io.puts('Input path was not provided, generating fresh front_offices and players')
|
84
96
|
|
85
97
|
generate_draft
|
86
98
|
else
|
@@ -91,8 +103,8 @@ module Basketball
|
|
91
103
|
end
|
92
104
|
|
93
105
|
def generate_draft
|
94
|
-
|
95
|
-
|
106
|
+
front_offices = 30.times.map do |i|
|
107
|
+
FrontOffice.new(
|
96
108
|
id: "T-#{i + 1}", name: Faker::Team.name
|
97
109
|
)
|
98
110
|
end
|
@@ -107,18 +119,23 @@ module Basketball
|
|
107
119
|
)
|
108
120
|
end
|
109
121
|
|
110
|
-
Engine.new(players:,
|
122
|
+
Engine.new(players:, front_offices:)
|
111
123
|
end
|
112
124
|
|
113
|
-
def
|
125
|
+
def league(engine)
|
114
126
|
return unless opts[:rosters]
|
115
127
|
|
116
128
|
io.puts
|
117
|
-
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')
|
118
137
|
|
119
|
-
engine.
|
120
|
-
io.puts(roster)
|
121
|
-
end
|
138
|
+
puts engine.events
|
122
139
|
end
|
123
140
|
|
124
141
|
# rubocop:disable Metrics/AbcSize
|
@@ -161,6 +178,8 @@ module Basketball
|
|
161
178
|
|
162
179
|
player = engine.players.find { |p| p.id == id.to_s.upcase }
|
163
180
|
|
181
|
+
raise PlayerNotFound, "player not found by id: #{id}" unless player
|
182
|
+
|
164
183
|
event = engine.pick!(player)
|
165
184
|
|
166
185
|
io.puts(event)
|
@@ -168,6 +187,14 @@ module Basketball
|
|
168
187
|
event_count += 1
|
169
188
|
end
|
170
189
|
|
190
|
+
opts[:skip].times do
|
191
|
+
event = engine.skip!
|
192
|
+
|
193
|
+
io.puts(event)
|
194
|
+
|
195
|
+
event_count += 1
|
196
|
+
end
|
197
|
+
|
171
198
|
engine.sim!(opts[:simulate]) do |event|
|
172
199
|
io.puts(event)
|
173
200
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'league'
|
4
4
|
|
5
5
|
module Basketball
|
6
6
|
module Drafting
|
@@ -9,7 +9,7 @@ module Basketball
|
|
9
9
|
class DupeEventError < StandardError; end
|
10
10
|
class EventOutOfOrderError < StandardError; end
|
11
11
|
class UnknownPlayerError < StandardError; end
|
12
|
-
class
|
12
|
+
class UnknownFrontOfficeError < StandardError; end
|
13
13
|
class EndOfDraftError < StandardError; end
|
14
14
|
|
15
15
|
DEFAULT_ROUNDS = 12
|
@@ -18,11 +18,11 @@ module Basketball
|
|
18
18
|
|
19
19
|
attr_reader :events, :rounds
|
20
20
|
|
21
|
-
def initialize(players: [],
|
22
|
-
@players_by_id
|
23
|
-
@
|
24
|
-
@events
|
25
|
-
@rounds
|
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
26
|
|
27
27
|
# Each one will be validated for correctness.
|
28
28
|
events.each { |e| play!(e) }
|
@@ -30,15 +30,15 @@ module Basketball
|
|
30
30
|
freeze
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
|
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
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
events_by_team.map do |team, events|
|
41
|
-
Roster.new(team:, events:)
|
39
|
+
undrafted_players.each do |player|
|
40
|
+
league.register!(player:)
|
41
|
+
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -46,8 +46,8 @@ module Basketball
|
|
46
46
|
events.join("\n")
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
|
49
|
+
def front_offices
|
50
|
+
front_offices_by_id.values
|
51
51
|
end
|
52
52
|
|
53
53
|
def players
|
@@ -55,27 +55,27 @@ module Basketball
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def total_picks
|
58
|
-
rounds *
|
58
|
+
rounds * front_offices.length
|
59
59
|
end
|
60
60
|
|
61
61
|
def current_round
|
62
62
|
return if done?
|
63
63
|
|
64
|
-
(current_pick /
|
64
|
+
(current_pick / front_offices.length.to_f).ceil
|
65
65
|
end
|
66
66
|
|
67
67
|
def current_round_pick
|
68
68
|
return if done?
|
69
69
|
|
70
|
-
mod = current_pick %
|
70
|
+
mod = current_pick % front_offices.length
|
71
71
|
|
72
|
-
mod.positive? ? mod :
|
72
|
+
mod.positive? ? mod : front_offices.length
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
75
|
+
def current_front_office
|
76
76
|
return if done?
|
77
77
|
|
78
|
-
|
78
|
+
front_offices[current_round_pick - 1]
|
79
79
|
end
|
80
80
|
|
81
81
|
def current_pick
|
@@ -96,22 +96,38 @@ module Basketball
|
|
96
96
|
!done?
|
97
97
|
end
|
98
98
|
|
99
|
+
def skip!
|
100
|
+
return if done?
|
101
|
+
|
102
|
+
event = SkipEvent.new(
|
103
|
+
id: SecureRandom.uuid,
|
104
|
+
front_office: current_front_office,
|
105
|
+
pick: current_pick,
|
106
|
+
round: current_round,
|
107
|
+
round_pick: current_round_pick
|
108
|
+
)
|
109
|
+
|
110
|
+
play!(event)
|
111
|
+
|
112
|
+
event
|
113
|
+
end
|
114
|
+
|
99
115
|
def sim!(times = nil)
|
100
116
|
counter = 0
|
101
117
|
events = []
|
102
118
|
|
103
119
|
until done? || (times && counter >= times)
|
104
|
-
|
120
|
+
front_office = current_front_office
|
105
121
|
|
106
|
-
player =
|
122
|
+
player = front_office.pick(
|
107
123
|
undrafted_player_search:,
|
108
|
-
drafted_players: drafted_players(
|
124
|
+
drafted_players: drafted_players(front_office),
|
109
125
|
round: current_round
|
110
126
|
)
|
111
127
|
|
112
128
|
event = SimEvent.new(
|
113
129
|
id: SecureRandom.uuid,
|
114
|
-
|
130
|
+
front_office:,
|
115
131
|
player:,
|
116
132
|
pick: current_pick,
|
117
133
|
round: current_round,
|
@@ -134,7 +150,7 @@ module Basketball
|
|
134
150
|
|
135
151
|
event = PickEvent.new(
|
136
152
|
id: SecureRandom.uuid,
|
137
|
-
|
153
|
+
front_office: current_front_office,
|
138
154
|
player:,
|
139
155
|
pick: current_pick,
|
140
156
|
round: current_round,
|
@@ -154,15 +170,19 @@ module Basketball
|
|
154
170
|
|
155
171
|
private
|
156
172
|
|
157
|
-
attr_reader :players_by_id, :
|
173
|
+
attr_reader :players_by_id, :front_offices_by_id
|
174
|
+
|
175
|
+
def player_events
|
176
|
+
events.select { |e| e.respond_to?(:player) }
|
177
|
+
end
|
158
178
|
|
159
179
|
def internal_current_pick
|
160
180
|
events.length + 1
|
161
181
|
end
|
162
182
|
|
163
|
-
def drafted_players(
|
164
|
-
|
165
|
-
next unless
|
183
|
+
def drafted_players(front_office = nil)
|
184
|
+
player_events.each_with_object([]) do |e, memo|
|
185
|
+
next unless front_office.nil? || e.front_office == front_office
|
166
186
|
|
167
187
|
memo << e.player
|
168
188
|
end
|
@@ -172,15 +192,25 @@ module Basketball
|
|
172
192
|
# rubocop:disable Metrics/CyclomaticComplexity
|
173
193
|
# rubocop:disable Metrics/PerceivedComplexity
|
174
194
|
def play!(event)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
195
|
+
if event.respond_to?(:player) && drafted_players.include?(event.player)
|
196
|
+
raise AlreadyPickedError, "#{player} was already picked"
|
197
|
+
end
|
198
|
+
|
199
|
+
if event.respond_to?(:player) && !players.include?(event.player)
|
200
|
+
raise UnknownPlayerError, "#{event.player} doesnt exist"
|
201
|
+
end
|
202
|
+
|
203
|
+
if event.front_office != current_front_office
|
204
|
+
raise EventOutOfOrder, "#{event} #{event.front_office} cant pick right now"
|
205
|
+
end
|
206
|
+
|
207
|
+
raise UnknownFrontOfficeError, "#{front_office} doesnt exist" unless front_offices.include?(event.front_office)
|
208
|
+
|
209
|
+
raise DupeEventError, "#{event} is a dupe" if events.include?(event)
|
210
|
+
raise EventOutOfOrder, "#{event} has wrong pick" if event.pick != current_pick
|
211
|
+
raise EventOutOfOrder, "#{event} has wrong round" if event.round != current_round
|
212
|
+
raise EventOutOfOrder, "#{event} has wrong round_pick" if event.round_pick != current_round_pick
|
213
|
+
raise EndOfDraftError, "#{total_picks} pick limit reached" if events.length > total_picks + 1
|
184
214
|
|
185
215
|
events << event
|
186
216
|
|
@@ -2,29 +2,30 @@
|
|
2
2
|
|
3
3
|
require_relative 'front_office'
|
4
4
|
require_relative 'player'
|
5
|
-
require_relative 'team'
|
6
5
|
require_relative 'pick_event'
|
7
6
|
require_relative 'sim_event'
|
7
|
+
require_relative 'skip_event'
|
8
8
|
|
9
9
|
module Basketball
|
10
10
|
module Drafting
|
11
11
|
class EngineSerializer
|
12
12
|
EVENT_CLASSES = {
|
13
13
|
'PickEvent' => PickEvent,
|
14
|
-
'SimEvent' => SimEvent
|
14
|
+
'SimEvent' => SimEvent,
|
15
|
+
'SkipEvent' => SkipEvent
|
15
16
|
}.freeze
|
16
17
|
|
17
18
|
private_constant :EVENT_CLASSES
|
18
19
|
|
19
20
|
def deserialize(string)
|
20
|
-
json
|
21
|
-
|
22
|
-
players
|
23
|
-
events
|
21
|
+
json = JSON.parse(string, symbolize_names: true)
|
22
|
+
front_offices = deserialize_front_offices(json)
|
23
|
+
players = deserialize_players(json)
|
24
|
+
events = deserialize_events(json, players, front_offices)
|
24
25
|
|
25
26
|
engine_opts = {
|
26
27
|
players:,
|
27
|
-
|
28
|
+
front_offices:,
|
28
29
|
events:
|
29
30
|
}
|
30
31
|
|
@@ -37,7 +38,7 @@ module Basketball
|
|
37
38
|
{
|
38
39
|
info: serialize_info(engine),
|
39
40
|
engine: serialize_engine(engine),
|
40
|
-
|
41
|
+
league: serialize_league(engine)
|
41
42
|
}.to_json
|
42
43
|
end
|
43
44
|
|
@@ -46,7 +47,7 @@ module Basketball
|
|
46
47
|
def serialize_engine(engine)
|
47
48
|
{
|
48
49
|
rounds: engine.rounds,
|
49
|
-
|
50
|
+
front_offices: serialize_front_offices(engine),
|
50
51
|
players: serialize_players(engine),
|
51
52
|
events: serialize_events(engine.events)
|
52
53
|
}
|
@@ -57,37 +58,40 @@ module Basketball
|
|
57
58
|
total_picks: engine.total_picks,
|
58
59
|
current_round: engine.current_round,
|
59
60
|
current_round_pick: engine.current_round_pick,
|
60
|
-
|
61
|
+
current_front_office: engine.current_front_office&.id,
|
61
62
|
current_pick: engine.current_pick,
|
62
63
|
remaining_picks: engine.remaining_picks,
|
63
|
-
done: engine.done
|
64
|
-
undrafted_players: engine.undrafted_players.map(&:id)
|
64
|
+
done: engine.done?
|
65
65
|
}
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
69
|
-
engine.
|
68
|
+
def serialize_league(engine)
|
69
|
+
league = engine.to_league
|
70
|
+
|
71
|
+
rosters = league.rosters.to_h do |roster|
|
70
72
|
[
|
71
73
|
roster.id,
|
72
74
|
{
|
73
|
-
|
74
|
-
players: roster.events.map { |event| event.player.id }
|
75
|
+
players: roster.players.map(&:id)
|
75
76
|
}
|
76
77
|
]
|
77
78
|
end
|
79
|
+
|
80
|
+
{
|
81
|
+
free_agents: league.free_agents.map(&:id),
|
82
|
+
rosters:
|
83
|
+
}
|
78
84
|
end
|
79
85
|
|
80
|
-
def
|
81
|
-
engine.
|
86
|
+
def serialize_front_offices(engine)
|
87
|
+
engine.front_offices.to_h do |front_office|
|
82
88
|
[
|
83
|
-
|
89
|
+
front_office.id,
|
84
90
|
{
|
85
|
-
name:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
prioritized_positions: team.front_office.prioritized_positions
|
90
|
-
}
|
91
|
+
name: front_office.name,
|
92
|
+
fuzz: front_office.fuzz,
|
93
|
+
depth: front_office.depth,
|
94
|
+
prioritized_positions: front_office.prioritized_positions.map(&:code)
|
91
95
|
}
|
92
96
|
]
|
93
97
|
end
|
@@ -112,39 +116,31 @@ module Basketball
|
|
112
116
|
{
|
113
117
|
type: event.class.name.split('::').last,
|
114
118
|
id: event.id,
|
115
|
-
|
116
|
-
team: event.team.id,
|
119
|
+
front_office: event.front_office.id,
|
117
120
|
pick: event.pick,
|
118
121
|
round: event.round,
|
119
122
|
round_pick: event.round_pick
|
120
|
-
}
|
123
|
+
}.tap do |hash|
|
124
|
+
hash[:player] = event.player.id if event.respond_to?(:player)
|
125
|
+
end
|
121
126
|
end
|
122
127
|
end
|
123
128
|
|
124
|
-
def
|
125
|
-
(json.dig(:engine, :
|
126
|
-
|
129
|
+
def deserialize_front_offices(json)
|
130
|
+
(json.dig(:engine, :front_offices) || []).map do |id, front_office_hash|
|
131
|
+
prioritized_positions = (front_office_hash[:prioritized_positions] || []).map do |v|
|
132
|
+
Position.new(v)
|
133
|
+
end
|
134
|
+
|
135
|
+
front_office_opts = {
|
127
136
|
id:,
|
128
|
-
name:
|
137
|
+
name: front_office_hash[:name],
|
138
|
+
prioritized_positions:,
|
139
|
+
fuzz: front_office_hash[:fuzz],
|
140
|
+
depth: front_office_hash[:depth]
|
129
141
|
}
|
130
142
|
|
131
|
-
|
132
|
-
front_office_hash = team_hash[:front_office] || {}
|
133
|
-
|
134
|
-
prioritized_positions = (front_office_hash[:prioritized_positions] || []).map do |v|
|
135
|
-
Position.new(v)
|
136
|
-
end
|
137
|
-
|
138
|
-
front_office_opts = {
|
139
|
-
prioritized_positions:,
|
140
|
-
fuzz: front_office_hash[:fuzz],
|
141
|
-
depth: front_office_hash[:depth]
|
142
|
-
}
|
143
|
-
|
144
|
-
team_opts[:front_office] = FrontOffice.new(**front_office_opts)
|
145
|
-
end
|
146
|
-
|
147
|
-
Team.new(**team_opts)
|
143
|
+
FrontOffice.new(**front_office_opts)
|
148
144
|
end
|
149
145
|
end
|
150
146
|
|
@@ -159,15 +155,18 @@ module Basketball
|
|
159
155
|
end
|
160
156
|
end
|
161
157
|
|
162
|
-
def deserialize_events(json, players,
|
158
|
+
def deserialize_events(json, players, front_offices)
|
163
159
|
(json.dig(:engine, :events) || []).map do |event_hash|
|
164
160
|
event_opts = event_hash.slice(:id, :pick, :round, :round_pick).merge(
|
165
|
-
|
166
|
-
team: teams.find { |t| t.id == event_hash[:team] }
|
161
|
+
front_office: front_offices.find { |t| t.id == event_hash[:front_office] }
|
167
162
|
)
|
168
163
|
|
169
164
|
class_constant = EVENT_CLASSES.fetch(event_hash[:type])
|
170
165
|
|
166
|
+
if [PickEvent, SimEvent].include?(class_constant)
|
167
|
+
event_opts[:player] = players.find { |p| p.id == event_hash[:player] }
|
168
|
+
end
|
169
|
+
|
171
170
|
class_constant.new(**event_opts)
|
172
171
|
end
|
173
172
|
end
|
@@ -3,21 +3,21 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
5
|
class Event < Entity
|
6
|
-
attr_reader :pick, :round, :round_pick, :
|
6
|
+
attr_reader :pick, :round, :round_pick, :front_office
|
7
7
|
|
8
|
-
def initialize(id:,
|
8
|
+
def initialize(id:, front_office:, pick:, round:, round_pick:)
|
9
9
|
super(id)
|
10
10
|
|
11
|
-
raise ArgumentError, '
|
11
|
+
raise ArgumentError, 'front_office required' unless front_office
|
12
12
|
|
13
|
-
@
|
14
|
-
@pick
|
15
|
-
@round
|
16
|
-
@round_pick
|
13
|
+
@front_office = front_office
|
14
|
+
@pick = pick.to_i
|
15
|
+
@round = round.to_i
|
16
|
+
@round_pick = round_pick.to_i
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_s
|
20
|
-
"##{pick} overall (R#{round}:P#{round_pick}) by #{
|
20
|
+
"##{pick} overall (R#{round}:P#{round_pick}) by #{front_office}"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -2,18 +2,19 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
|
-
class FrontOffice <
|
5
|
+
class FrontOffice < Entity
|
6
6
|
MAX_DEPTH = 3
|
7
7
|
MAX_FUZZ = 2
|
8
8
|
MAX_POSITIONS = 12
|
9
9
|
|
10
10
|
private_constant :MAX_DEPTH, :MAX_FUZZ, :MAX_POSITIONS
|
11
11
|
|
12
|
-
|
12
|
+
attr_reader :prioritized_positions, :fuzz, :depth, :name
|
13
13
|
|
14
|
-
def initialize(prioritized_positions: [], fuzz: rand(0..MAX_FUZZ), depth: rand(0..MAX_DEPTH))
|
15
|
-
super()
|
14
|
+
def initialize(id:, name: '', prioritized_positions: [], fuzz: rand(0..MAX_FUZZ), depth: rand(0..MAX_DEPTH))
|
15
|
+
super(id)
|
16
16
|
|
17
|
+
@name = name
|
17
18
|
@fuzz = fuzz.to_i
|
18
19
|
@depth = depth.to_i
|
19
20
|
@prioritized_positions = prioritized_positions
|
@@ -36,6 +37,10 @@ module Basketball
|
|
36
37
|
players[0..fuzz].sample
|
37
38
|
end
|
38
39
|
|
40
|
+
def to_s
|
41
|
+
"[#{super}] #{name}"
|
42
|
+
end
|
43
|
+
|
39
44
|
private
|
40
45
|
|
41
46
|
def adaptive_search(undrafted_player_search:, drafted_players:)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'roster'
|
4
|
+
|
5
|
+
module Basketball
|
6
|
+
module Drafting
|
7
|
+
class League
|
8
|
+
class PlayerAlreadyRegisteredError < StandardError; end
|
9
|
+
class RosterNotFoundError < StandardError; end
|
10
|
+
class RosterAlreadyAddedError < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :free_agents, :rosters
|
13
|
+
|
14
|
+
def initialize(free_agents: [], front_offices: [])
|
15
|
+
@rosters = []
|
16
|
+
@free_agents = []
|
17
|
+
|
18
|
+
front_offices.each { |front_office| add_roster(front_office) }
|
19
|
+
free_agents.each { |p| register!(player: p) }
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def roster(front_office)
|
25
|
+
rosters.find { |r| r == front_office }
|
26
|
+
end
|
27
|
+
|
28
|
+
def register!(player:, front_office: nil)
|
29
|
+
raise PlayerRequiredError, 'player is required' unless player
|
30
|
+
|
31
|
+
rosters.each do |roster|
|
32
|
+
if roster.registered?(player)
|
33
|
+
raise PlayerAlreadyRegisteredError,
|
34
|
+
"#{player} already registered to: #{roster.id}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if free_agents.include?(player)
|
39
|
+
raise PlayerAlreadyRegisteredError,
|
40
|
+
"#{player} already registered as a free agent"
|
41
|
+
end
|
42
|
+
|
43
|
+
if front_office
|
44
|
+
roster = roster(front_office)
|
45
|
+
|
46
|
+
raise RosterNotFoundError, "Roster not found for: #{front_office}" unless roster
|
47
|
+
|
48
|
+
roster.sign!(player)
|
49
|
+
else
|
50
|
+
free_agents << player
|
51
|
+
end
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
(['League'] + rosters.map(&:to_s)).join("\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def add_roster(front_office)
|
63
|
+
raise RosterAlreadyAddedError, "#{front_office} already added" if rosters.include?(front_office)
|
64
|
+
|
65
|
+
rosters << Roster.new(id: front_office.id, name: front_office.name)
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -7,8 +7,8 @@ module Basketball
|
|
7
7
|
class PickEvent < Event
|
8
8
|
attr_reader :player
|
9
9
|
|
10
|
-
def initialize(id:,
|
11
|
-
super(id:,
|
10
|
+
def initialize(id:, front_office:, player:, pick:, round:, round_pick:)
|
11
|
+
super(id:, front_office:, pick:, round:, round_pick:)
|
12
12
|
|
13
13
|
raise ArgumentError, 'player required' unless player
|
14
14
|
|
@@ -2,37 +2,36 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Drafting
|
5
|
-
class Roster <
|
6
|
-
|
5
|
+
class Roster < Entity
|
6
|
+
class PlayerAlreadyRegisteredError < StandardError; end
|
7
|
+
class PlayerRequiredError < StandardError; end
|
7
8
|
|
8
|
-
|
9
|
+
attr_reader :name, :players
|
9
10
|
|
10
|
-
|
11
|
+
def initialize(id:, name: '', players: [])
|
12
|
+
super(id)
|
11
13
|
|
12
|
-
|
14
|
+
@name = name.to_s
|
15
|
+
@players = players.each { |p| register!(p) }
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
raise ArgumentError, 'team is required' unless team
|
17
|
+
freeze
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
+
def registered?(player)
|
21
|
+
players.include?(player)
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
24
|
+
def sign!(player)
|
25
|
+
raise PlayerRequiredError, 'player is required' unless player
|
26
|
+
raise PlayerAlreadyRegisteredError, "#{player} already registered for #{id}" if registered?(player)
|
25
27
|
|
26
|
-
|
27
|
-
@events = events
|
28
|
-
end
|
28
|
+
players << player
|
29
29
|
|
30
|
-
|
31
|
-
events.map(&:player)
|
30
|
+
self
|
32
31
|
end
|
33
32
|
|
34
33
|
def to_s
|
35
|
-
([
|
34
|
+
(["[#{super}] #{name} Roster"] + players.map(&:to_s)).join("\n")
|
36
35
|
end
|
37
36
|
end
|
38
37
|
end
|
@@ -5,8 +5,8 @@ module Basketball
|
|
5
5
|
class SimEvent < Event
|
6
6
|
attr_reader :player
|
7
7
|
|
8
|
-
def initialize(id:,
|
9
|
-
super(id:,
|
8
|
+
def initialize(id:, front_office:, player:, pick:, round:, round_pick:)
|
9
|
+
super(id:, front_office:, pick:, round:, round_pick:)
|
10
10
|
|
11
11
|
raise ArgumentError, 'player required' unless player
|
12
12
|
|
data/lib/basketball/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: basketball
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
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-
|
11
|
+
date: 2023-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faker
|
@@ -209,13 +209,14 @@ files:
|
|
209
209
|
- lib/basketball/drafting/engine_serializer.rb
|
210
210
|
- lib/basketball/drafting/event.rb
|
211
211
|
- lib/basketball/drafting/front_office.rb
|
212
|
+
- lib/basketball/drafting/league.rb
|
212
213
|
- lib/basketball/drafting/pick_event.rb
|
213
214
|
- lib/basketball/drafting/player.rb
|
214
215
|
- lib/basketball/drafting/player_search.rb
|
215
216
|
- lib/basketball/drafting/position.rb
|
216
217
|
- lib/basketball/drafting/roster.rb
|
217
218
|
- lib/basketball/drafting/sim_event.rb
|
218
|
-
- lib/basketball/drafting/
|
219
|
+
- lib/basketball/drafting/skip_event.rb
|
219
220
|
- lib/basketball/entity.rb
|
220
221
|
- lib/basketball/value_object.rb
|
221
222
|
- lib/basketball/version.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Basketball
|
4
|
-
module Drafting
|
5
|
-
class Team < Entity
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
attr_reader :name, :front_office
|
9
|
-
|
10
|
-
def_delegators :front_office, :pick
|
11
|
-
|
12
|
-
def initialize(id:, name: '', front_office: FrontOffice.new)
|
13
|
-
super(id)
|
14
|
-
|
15
|
-
raise ArgumentError, 'front_office is required' unless front_office
|
16
|
-
|
17
|
-
@name = name.to_s
|
18
|
-
@front_office = front_office
|
19
|
-
|
20
|
-
freeze
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_s
|
24
|
-
"[#{super}] #{name}"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|