basketball 0.0.4 → 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 +5 -2
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +35 -8
- data/lib/basketball/drafting/cli.rb +13 -13
- data/lib/basketball/drafting/engine.rb +43 -39
- data/lib/basketball/drafting/engine_serializer.rb +40 -47
- 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 -24
- data/lib/basketball/drafting/sim_event.rb +2 -2
- data/lib/basketball/version.rb +1 -1
- metadata +3 -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,8 +1,11 @@
|
|
1
|
+
#### 0.0.5 - May 5th, 2023
|
2
|
+
|
3
|
+
* Remove the notion of Team in favor of a flat front office.
|
1
4
|
#### 0.0.4 - May 5th, 2023
|
2
5
|
|
3
6
|
* Add ability to skip draft picks using `Basketball::Drafting::Engine#skip!`
|
4
|
-
* Add ability to output event full drafting event log using CLI: `
|
5
|
-
* Add ability to skip draft picks using CLI: `
|
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`
|
6
9
|
|
7
10
|
#### 0.0.3 - May 5th, 2023
|
8
11
|
|
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
|
@@ -46,15 +46,19 @@ module Basketball
|
|
46
46
|
if engine.done?
|
47
47
|
io.puts('Draft is complete!')
|
48
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
|
+
|
49
53
|
io.puts("#{engine.remaining_picks} Remaining pick(s)")
|
50
|
-
io.puts("Up Next: Round #{
|
54
|
+
io.puts("Up Next: Round #{current_round} pick #{current_round_pick} for #{current_front_office}")
|
51
55
|
end
|
52
56
|
|
53
57
|
write(engine)
|
54
58
|
|
55
59
|
log(engine)
|
56
60
|
|
57
|
-
|
61
|
+
league(engine)
|
58
62
|
|
59
63
|
query(engine)
|
60
64
|
|
@@ -75,7 +79,7 @@ module Basketball
|
|
75
79
|
o.array '-p', '--picks', 'Comma-separated list of ordered player IDs to pick.', delimiter: ','
|
76
80
|
o.integer '-t', '--top', 'Output the top rated available players (default is 0).', default: 0
|
77
81
|
o.string '-q', '--query', "Filter TOP by position: #{Position::ALL_VALUES.join(', ')}."
|
78
|
-
o.bool '-r', '--rosters', 'Output all
|
82
|
+
o.bool '-r', '--rosters', 'Output all front_office rosters.', default: false
|
79
83
|
o.integer '-x', '--skip', 'Number of picks to skip (default is 0).', default: 0
|
80
84
|
o.bool '-l', '--log', 'Output event log.', default: false
|
81
85
|
|
@@ -88,7 +92,7 @@ module Basketball
|
|
88
92
|
|
89
93
|
def load_engine
|
90
94
|
if opts[:input].to_s.empty?
|
91
|
-
io.puts('Input path was not provided, generating fresh
|
95
|
+
io.puts('Input path was not provided, generating fresh front_offices and players')
|
92
96
|
|
93
97
|
generate_draft
|
94
98
|
else
|
@@ -99,8 +103,8 @@ module Basketball
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def generate_draft
|
102
|
-
|
103
|
-
|
106
|
+
front_offices = 30.times.map do |i|
|
107
|
+
FrontOffice.new(
|
104
108
|
id: "T-#{i + 1}", name: Faker::Team.name
|
105
109
|
)
|
106
110
|
end
|
@@ -115,18 +119,14 @@ module Basketball
|
|
115
119
|
)
|
116
120
|
end
|
117
121
|
|
118
|
-
Engine.new(players:,
|
122
|
+
Engine.new(players:, front_offices:)
|
119
123
|
end
|
120
124
|
|
121
|
-
def
|
125
|
+
def league(engine)
|
122
126
|
return unless opts[:rosters]
|
123
127
|
|
124
128
|
io.puts
|
125
|
-
io.puts(
|
126
|
-
|
127
|
-
engine.rosters.each do |roster|
|
128
|
-
io.puts(roster)
|
129
|
-
end
|
129
|
+
io.puts(engine.to_league)
|
130
130
|
end
|
131
131
|
|
132
132
|
def log(engine)
|
@@ -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
|
@@ -101,7 +101,7 @@ module Basketball
|
|
101
101
|
|
102
102
|
event = SkipEvent.new(
|
103
103
|
id: SecureRandom.uuid,
|
104
|
-
|
104
|
+
front_office: current_front_office,
|
105
105
|
pick: current_pick,
|
106
106
|
round: current_round,
|
107
107
|
round_pick: current_round_pick
|
@@ -117,17 +117,17 @@ module Basketball
|
|
117
117
|
events = []
|
118
118
|
|
119
119
|
until done? || (times && counter >= times)
|
120
|
-
|
120
|
+
front_office = current_front_office
|
121
121
|
|
122
|
-
player =
|
122
|
+
player = front_office.pick(
|
123
123
|
undrafted_player_search:,
|
124
|
-
drafted_players: drafted_players(
|
124
|
+
drafted_players: drafted_players(front_office),
|
125
125
|
round: current_round
|
126
126
|
)
|
127
127
|
|
128
128
|
event = SimEvent.new(
|
129
129
|
id: SecureRandom.uuid,
|
130
|
-
|
130
|
+
front_office:,
|
131
131
|
player:,
|
132
132
|
pick: current_pick,
|
133
133
|
round: current_round,
|
@@ -150,7 +150,7 @@ module Basketball
|
|
150
150
|
|
151
151
|
event = PickEvent.new(
|
152
152
|
id: SecureRandom.uuid,
|
153
|
-
|
153
|
+
front_office: current_front_office,
|
154
154
|
player:,
|
155
155
|
pick: current_pick,
|
156
156
|
round: current_round,
|
@@ -170,7 +170,7 @@ module Basketball
|
|
170
170
|
|
171
171
|
private
|
172
172
|
|
173
|
-
attr_reader :players_by_id, :
|
173
|
+
attr_reader :players_by_id, :front_offices_by_id
|
174
174
|
|
175
175
|
def player_events
|
176
176
|
events.select { |e| e.respond_to?(:player) }
|
@@ -180,9 +180,9 @@ module Basketball
|
|
180
180
|
events.length + 1
|
181
181
|
end
|
182
182
|
|
183
|
-
def drafted_players(
|
183
|
+
def drafted_players(front_office = nil)
|
184
184
|
player_events.each_with_object([]) do |e, memo|
|
185
|
-
next unless
|
185
|
+
next unless front_office.nil? || e.front_office == front_office
|
186
186
|
|
187
187
|
memo << e.player
|
188
188
|
end
|
@@ -200,13 +200,17 @@ module Basketball
|
|
200
200
|
raise UnknownPlayerError, "#{event.player} doesnt exist"
|
201
201
|
end
|
202
202
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
raise
|
208
|
-
|
209
|
-
raise
|
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
|
210
214
|
|
211
215
|
events << event
|
212
216
|
|
@@ -2,7 +2,6 @@
|
|
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'
|
8
7
|
require_relative 'skip_event'
|
@@ -19,14 +18,14 @@ module Basketball
|
|
19
18
|
private_constant :EVENT_CLASSES
|
20
19
|
|
21
20
|
def deserialize(string)
|
22
|
-
json
|
23
|
-
|
24
|
-
players
|
25
|
-
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)
|
26
25
|
|
27
26
|
engine_opts = {
|
28
27
|
players:,
|
29
|
-
|
28
|
+
front_offices:,
|
30
29
|
events:
|
31
30
|
}
|
32
31
|
|
@@ -39,7 +38,7 @@ module Basketball
|
|
39
38
|
{
|
40
39
|
info: serialize_info(engine),
|
41
40
|
engine: serialize_engine(engine),
|
42
|
-
|
41
|
+
league: serialize_league(engine)
|
43
42
|
}.to_json
|
44
43
|
end
|
45
44
|
|
@@ -48,7 +47,7 @@ module Basketball
|
|
48
47
|
def serialize_engine(engine)
|
49
48
|
{
|
50
49
|
rounds: engine.rounds,
|
51
|
-
|
50
|
+
front_offices: serialize_front_offices(engine),
|
52
51
|
players: serialize_players(engine),
|
53
52
|
events: serialize_events(engine.events)
|
54
53
|
}
|
@@ -59,37 +58,40 @@ module Basketball
|
|
59
58
|
total_picks: engine.total_picks,
|
60
59
|
current_round: engine.current_round,
|
61
60
|
current_round_pick: engine.current_round_pick,
|
62
|
-
|
61
|
+
current_front_office: engine.current_front_office&.id,
|
63
62
|
current_pick: engine.current_pick,
|
64
63
|
remaining_picks: engine.remaining_picks,
|
65
|
-
done: engine.done
|
66
|
-
undrafted_players: engine.undrafted_players.map(&:id)
|
64
|
+
done: engine.done?
|
67
65
|
}
|
68
66
|
end
|
69
67
|
|
70
|
-
def
|
71
|
-
engine.
|
68
|
+
def serialize_league(engine)
|
69
|
+
league = engine.to_league
|
70
|
+
|
71
|
+
rosters = league.rosters.to_h do |roster|
|
72
72
|
[
|
73
73
|
roster.id,
|
74
74
|
{
|
75
|
-
events: roster.events.map(&:id),
|
76
75
|
players: roster.players.map(&:id)
|
77
76
|
}
|
78
77
|
]
|
79
78
|
end
|
79
|
+
|
80
|
+
{
|
81
|
+
free_agents: league.free_agents.map(&:id),
|
82
|
+
rosters:
|
83
|
+
}
|
80
84
|
end
|
81
85
|
|
82
|
-
def
|
83
|
-
engine.
|
86
|
+
def serialize_front_offices(engine)
|
87
|
+
engine.front_offices.to_h do |front_office|
|
84
88
|
[
|
85
|
-
|
89
|
+
front_office.id,
|
86
90
|
{
|
87
|
-
name:
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
prioritized_positions: team.front_office.prioritized_positions
|
92
|
-
}
|
91
|
+
name: front_office.name,
|
92
|
+
fuzz: front_office.fuzz,
|
93
|
+
depth: front_office.depth,
|
94
|
+
prioritized_positions: front_office.prioritized_positions.map(&:code)
|
93
95
|
}
|
94
96
|
]
|
95
97
|
end
|
@@ -114,7 +116,7 @@ module Basketball
|
|
114
116
|
{
|
115
117
|
type: event.class.name.split('::').last,
|
116
118
|
id: event.id,
|
117
|
-
|
119
|
+
front_office: event.front_office.id,
|
118
120
|
pick: event.pick,
|
119
121
|
round: event.round,
|
120
122
|
round_pick: event.round_pick
|
@@ -124,30 +126,21 @@ module Basketball
|
|
124
126
|
end
|
125
127
|
end
|
126
128
|
|
127
|
-
def
|
128
|
-
(json.dig(:engine, :
|
129
|
-
|
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 = {
|
130
136
|
id:,
|
131
|
-
name:
|
137
|
+
name: front_office_hash[:name],
|
138
|
+
prioritized_positions:,
|
139
|
+
fuzz: front_office_hash[:fuzz],
|
140
|
+
depth: front_office_hash[:depth]
|
132
141
|
}
|
133
142
|
|
134
|
-
|
135
|
-
front_office_hash = team_hash[:front_office] || {}
|
136
|
-
|
137
|
-
prioritized_positions = (front_office_hash[:prioritized_positions] || []).map do |v|
|
138
|
-
Position.new(v)
|
139
|
-
end
|
140
|
-
|
141
|
-
front_office_opts = {
|
142
|
-
prioritized_positions:,
|
143
|
-
fuzz: front_office_hash[:fuzz],
|
144
|
-
depth: front_office_hash[:depth]
|
145
|
-
}
|
146
|
-
|
147
|
-
team_opts[:front_office] = FrontOffice.new(**front_office_opts)
|
148
|
-
end
|
149
|
-
|
150
|
-
Team.new(**team_opts)
|
143
|
+
FrontOffice.new(**front_office_opts)
|
151
144
|
end
|
152
145
|
end
|
153
146
|
|
@@ -162,10 +155,10 @@ module Basketball
|
|
162
155
|
end
|
163
156
|
end
|
164
157
|
|
165
|
-
def deserialize_events(json, players,
|
158
|
+
def deserialize_events(json, players, front_offices)
|
166
159
|
(json.dig(:engine, :events) || []).map do |event_hash|
|
167
160
|
event_opts = event_hash.slice(:id, :pick, :round, :round_pick).merge(
|
168
|
-
|
161
|
+
front_office: front_offices.find { |t| t.id == event_hash[:front_office] }
|
169
162
|
)
|
170
163
|
|
171
164
|
class_constant = EVENT_CLASSES.fetch(event_hash[:type])
|
@@ -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,41 +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
|
-
super()
|
16
|
-
|
17
|
-
raise ArgumentError, 'team is required' unless team
|
18
|
-
|
19
|
-
other_teams_pick_event_ids = events.reject { |e| e.team == team }.map(&:id)
|
20
|
-
|
21
|
-
if other_teams_pick_event_ids.any?
|
22
|
-
raise WrongTeamEventError,
|
23
|
-
"Event(s): #{other_teams_pick_event_ids.join(',')} has wrong team"
|
24
|
-
end
|
25
|
-
|
26
|
-
@team = team
|
27
|
-
@events = events
|
17
|
+
freeze
|
28
18
|
end
|
29
19
|
|
30
|
-
def
|
31
|
-
|
20
|
+
def registered?(player)
|
21
|
+
players.include?(player)
|
32
22
|
end
|
33
23
|
|
34
|
-
def
|
35
|
-
|
24
|
+
def sign!(player)
|
25
|
+
raise PlayerRequiredError, 'player is required' unless player
|
26
|
+
raise PlayerAlreadyRegisteredError, "#{player} already registered for #{id}" if registered?(player)
|
27
|
+
|
28
|
+
players << player
|
29
|
+
|
30
|
+
self
|
36
31
|
end
|
37
32
|
|
38
33
|
def to_s
|
39
|
-
([
|
34
|
+
(["[#{super}] #{name} Roster"] + players.map(&:to_s)).join("\n")
|
40
35
|
end
|
41
36
|
end
|
42
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,6 +209,7 @@ 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
|
@@ -216,7 +217,6 @@ files:
|
|
216
217
|
- lib/basketball/drafting/roster.rb
|
217
218
|
- lib/basketball/drafting/sim_event.rb
|
218
219
|
- lib/basketball/drafting/skip_event.rb
|
219
|
-
- lib/basketball/drafting/team.rb
|
220
220
|
- lib/basketball/entity.rb
|
221
221
|
- lib/basketball/value_object.rb
|
222
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
|