basketball 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -2
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/README.md +120 -18
  5. data/basketball.gemspec +1 -1
  6. data/exe/basketball-schedule +7 -0
  7. data/lib/basketball/drafting/cli.rb +15 -15
  8. data/lib/basketball/drafting/engine.rb +43 -42
  9. data/lib/basketball/drafting/engine_serializer.rb +41 -49
  10. data/lib/basketball/drafting/event.rb +10 -10
  11. data/lib/basketball/drafting/front_office.rb +9 -4
  12. data/lib/basketball/drafting/league.rb +70 -0
  13. data/lib/basketball/drafting/pick_event.rb +3 -3
  14. data/lib/basketball/drafting/roster.rb +18 -24
  15. data/lib/basketball/drafting/sim_event.rb +3 -3
  16. data/lib/basketball/drafting.rb +6 -0
  17. data/lib/basketball/scheduling/calendar.rb +121 -0
  18. data/lib/basketball/scheduling/calendar_serializer.rb +84 -0
  19. data/lib/basketball/scheduling/cli.rb +198 -0
  20. data/lib/basketball/scheduling/conference.rb +57 -0
  21. data/lib/basketball/scheduling/coordinator.rb +180 -0
  22. data/lib/basketball/scheduling/division.rb +43 -0
  23. data/lib/basketball/scheduling/game.rb +32 -0
  24. data/lib/basketball/scheduling/league.rb +114 -0
  25. data/lib/basketball/scheduling/league_serializer.rb +90 -0
  26. data/lib/basketball/scheduling/preseason_game.rb +11 -0
  27. data/lib/basketball/scheduling/season_game.rb +8 -0
  28. data/lib/basketball/scheduling/team.rb +21 -0
  29. data/lib/basketball/scheduling.rb +17 -0
  30. data/lib/basketball/value_object.rb +16 -7
  31. data/lib/basketball/version.rb +1 -1
  32. data/lib/basketball.rb +1 -0
  33. metadata +18 -3
  34. data/lib/basketball/drafting/team.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71ab0cd4aa4e2bb10d0a9640e18a756938fbe749842abdbbda9be9f2023be9cf
4
- data.tar.gz: 5d216901cadb3c92d41a1e445956aef4a11fc1d3e418430003d050693c43d3be
3
+ metadata.gz: 262751c76037e2859937529aa23bdd456fd77779a090ed4e844830c428b09e75
4
+ data.tar.gz: 1a904f317fc3c1becfc381155a089d2ed0b041b60d358c1325459a363e6ce0ec
5
5
  SHA512:
6
- metadata.gz: a3812684e58e97711745e31a8696c12c32c79e542661302db86eb7b0fecc3784ede1650ec2d0079b66a641f5e09da657f22f596583630d932e03e55ba7ed3514
7
- data.tar.gz: f1edd69f31a2ff8ca4d5e53bab9bd59c74a091ab276db1a53901aa1e7de17202898ba01a917304f1e7018da1b3ad129fb012a3ac12516e832f4b8fa6cb8cd232
6
+ metadata.gz: 913504954c8c053fac3147468433d12b02ad5d6a7badc563587668c4d3943b898c49c951d69c393affc5f5e07d54257a5a004184e55a73f435af01344afa6baf
7
+ data.tar.gz: 791e48aa9711e33e29b994d0e811a735def017de757bc02c8f348b57f2ce0a42c1e5fbbd6737ed2525ddd9fbf3698c9fb77be991208a1b927e4b27063a2b9f04
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
+ #### 0.0.6 - May 11th, 2023
2
+
3
+ * Added Scheduling module that can generate full schedules for entire league.
4
+ * Drafting::Event does not have identity anymore (no current tangible benefit).
5
+
6
+ #### 0.0.5 - May 5th, 2023
7
+
8
+ * Remove the notion of Team in favor of a flat front office.
1
9
  #### 0.0.4 - May 5th, 2023
2
10
 
3
11
  * Add ability to skip draft picks using `Basketball::Drafting::Engine#skip!`
4
- * Add ability to output event full drafting event log using CLI: `exe/basketball-draft -i tmp/draft-wip.json -l`
5
- * Add ability to skip draft picks using CLI: `exe/basketball-draft -i tmp/draft-wip.json -x 1`
12
+ * Add ability to output event full drafting event log using CLI: `basketball-draft -i tmp/draft-wip.json -l`
13
+ * Add ability to skip draft picks using CLI: `basketball-draft -i tmp/draft-wip.json -x 1`
6
14
 
7
15
  #### 0.0.3 - May 5th, 2023
8
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 team is
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,62 +26,164 @@ Install executable scripts:
26
26
  bundle binstubs basketball
27
27
  ````
28
28
 
29
- ## Main Modules
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
 
33
- ![Basketball Architecture - Overview.png](/docs/architecture/overview.png)
33
+ ![Basketball Architecture - Overview](/docs/architecture/overview.png)
34
34
 
35
35
  ## Drafting Module
36
36
 
37
37
  The drafting module is responsible for providing a turn-based iterator allowing the consumer to either manually pick or simulate picks. Here is a cartoon showing the major components:
38
38
 
39
- ![Basketball Architecture - Drafting.png](/docs/architecture/drafting.png)
39
+ ![Basketball Architecture - Drafting](/docs/architecture/drafting.png)
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
+ **Player** | 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.
40
55
 
41
56
  ### The Drafting CLI
42
57
 
43
- 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:
58
+ The drafting module is meant to be interfaced with 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:
44
59
 
45
- #### Generate a Fresh Draft
60
+ ##### Generate a Fresh Draft
46
61
 
47
62
  ```zsh
48
- exe/basketball-draft -o tmp/draft.json
63
+ basketball-draft -o tmp/draft.json
49
64
  ```
50
65
 
51
- #### N Top Available Players
66
+ ##### N Top Available Players
52
67
 
53
68
  ```zsh
54
- exe/basketball-draft -i tmp/draft.json -t 10
69
+ basketball-draft -i tmp/draft.json -t 10
55
70
  ```
56
71
 
57
- #### N Top Available Players for a Position
72
+ ##### N Top Available Players for a Position
58
73
 
59
74
  ```zsh
60
- exe/basketball-draft -i tmp/draft.json -t 10 -q PG
75
+ basketball-draft -i tmp/draft.json -t 10 -q PG
61
76
  ```
62
77
 
63
- #### Output Current Rosters
78
+ ##### Output Current Rosters
64
79
 
65
80
  ```zsh
66
- exe/basketball-draft -i tmp/draft.json -r
81
+ basketball-draft -i tmp/draft.json -r
67
82
  ```
68
83
 
69
- #### Simulate N picks
84
+ ##### Output Event Log
70
85
 
71
86
  ```zsh
72
- exe/basketball-draft -i tmp/draft.json -s 10
87
+ basketball-draft -i tmp/draft.json -l
73
88
  ```
74
89
 
75
- #### Pick Players
90
+ ##### Simulate N Picks
76
91
 
77
92
  ```zsh
78
- exe/basketball-draft -i tmp/draft.json -p P-100,P-200,P-300
93
+ basketball-draft -i tmp/draft.json -s 10
79
94
  ```
80
95
 
81
- #### Simulate the Rest of the Draft
96
+ ##### Skip N Picks
82
97
 
83
98
  ```zsh
84
- exe/basketball-draft -i tmp/draft.json -a
99
+ basketball-draft -i tmp/draft.json -x 10
100
+ ```
101
+
102
+ ##### Pick Players
103
+
104
+ ```zsh
105
+ basketball-draft -i tmp/draft.json -p P-100,P-200,P-300
106
+ ```
107
+
108
+ ##### Simulate the Rest of the Draft
109
+
110
+ ```zsh
111
+ basketball-draft -i tmp/draft.json -a
112
+ ```
113
+
114
+ ##### Help Menu
115
+
116
+ ```zsh
117
+ basketball-draft -h
118
+ ```
119
+
120
+ ## Scheduling Module
121
+
122
+ The Scheduling module is meant to take a League (conferences/divisions/teams) and turn it into a Calendar. This Calendar creation is atomic - the full calendar will be generated completely all in one call. Here is a cartoon showing the major components:
123
+
124
+ ![Basketball Architecture - Scheduling](/docs/architecture/scheduling.png)
125
+
126
+ Element | Description
127
+ :------------ | :-----------
128
+ **Away Team** | Team object designated as the away team for a Game.
129
+ **Calendar Serializer** | Understands how to serialize and deserialize a Calendar object.
130
+ **Calendar** | Hold a calendar for a year season. Pass in a year and the Calendar will know how to mark important boundary dates (preseason start, preseason end, season start, and season end) and it knows how to ensure Calendar correctness regarding dates.
131
+ **Conference** | Describes a conference in terms of structure; composed of an array of divisions (there can only 3).
132
+ **Coordinator** | Service which can generate a Calendar from a League.
133
+ **Division** | Describes a division in terms of structure; composed of an array of teams (there can only 5).
134
+ **Game** | Matches up a date with two teams (home and away) to represent a scheduled matchup.
135
+ **Home Team** | Team object designated as the home team for a Game.
136
+ **League Serializer** | Understands how to serialize and deserialize a League object.
137
+ **League** | Describes a league in terms of structure; composed of an array conferences (there can only be 2).
138
+ **Scheduling** | Bounded context (sub-module) dealing with matchup and calendar generation.
139
+ **Team** | Identified by an ID and described by a name: represents a basketball team that can be scheduled.
140
+
141
+ ##### Generate League
142
+
143
+ ```zsh
144
+ basketball-schedule -o tmp/league.json
145
+ ```
146
+
147
+ ##### Generate Calendar From League
148
+
149
+ ```zsh
150
+ basketball-schedule -i tmp/league.json -o tmp/calendar.json
151
+ ```
152
+
153
+ ##### Generate Calendar From League For a Specific Year
154
+
155
+ ```zsh
156
+ basketball-schedule -i tmp/league.json -o tmp/calendar.json -y 2005
157
+ ```
158
+
159
+ ##### Output a Generated Calendar's Matchups
160
+
161
+ ```zsh
162
+ basketball-schedule -c tmp/calendar.json
163
+ ```
164
+
165
+ ##### Output a Generated Calendar's Matchups For a Specific Team
166
+
167
+ ```zsh
168
+ basketball-schedule -c tmp/calendar.json -t C0-D0-T0
169
+ ```
170
+
171
+ ##### Output a Generated Calendar's Matchups For a Specific Date
172
+
173
+ ```zsh
174
+ basketball-schedule -c tmp/calendar.json -d 2005-02-03
175
+ ```
176
+
177
+ ##### Output a Generated Calendar's Matchups For a Specific Team and Date
178
+
179
+ ```zsh
180
+ basketball-schedule -c tmp/calendar.json -d 2005-02-03 -t C0-D0-T0
181
+ ```
182
+
183
+ ##### Help Menu
184
+
185
+ ```zsh
186
+ basketball-schedule -h
85
187
  ```
86
188
 
87
189
  ## Contributing
data/basketball.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.email = ['mattruggio@icloud.com']
18
18
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(.github|bin|docs|spec)/}) }
19
19
  s.bindir = 'exe'
20
- s.executables = %w[basketball-draft]
20
+ s.executables = %w[basketball-draft basketball-schedule]
21
21
  s.homepage = 'https://github.com/mattruggio/basketball'
22
22
  s.license = 'MIT'
23
23
  s.metadata = {
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'basketball'
6
+
7
+ Basketball::Scheduling::CLI.new(args: ARGV).invoke!
@@ -7,7 +7,7 @@ require_relative 'position'
7
7
 
8
8
  module Basketball
9
9
  module Drafting
10
- # Example:
10
+ # Examples:
11
11
  # exe/basketball-draft -o tmp/draft.json
12
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
13
  # exe/basketball-draft -i tmp/draft-wip.json -x 2
@@ -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 #{engine.current_round} pick #{engine.current_round_pick} for #{engine.current_team}")
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
- rosters(engine)
61
+ league(engine)
58
62
 
59
63
  query(engine)
60
64
 
@@ -65,7 +69,7 @@ module Basketball
65
69
 
66
70
  def slop_parse(args)
67
71
  Slop.parse(args) do |o|
68
- o.banner = 'Usage: draft [options] ...'
72
+ o.banner = 'Usage: basketball-draft [options] ...'
69
73
 
70
74
  o.string '-i', '--input',
71
75
  'Path to load the engine from. If omitted then a new draft will be generated.'
@@ -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 team rosters.', default: false
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 teams and players')
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
- teams = 30.times.map do |i|
103
- Team.new(
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:, teams:)
122
+ Engine.new(players:, front_offices:)
119
123
  end
120
124
 
121
- def rosters(engine)
125
+ def league(engine)
122
126
  return unless opts[:rosters]
123
127
 
124
128
  io.puts
125
- io.puts('Rosters')
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 'roster'
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 UnknownTeamError < StandardError; end
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: [], teams: [], events: [], rounds: DEFAULT_ROUNDS)
22
- @players_by_id = players.to_h { |p| [p.id, p] }
23
- @teams_by_id = teams.to_h { |t| [t.id, t] }
24
- @events = []
25
- @rounds = rounds.to_i
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 rosters
34
- events_by_team = teams.to_h { |t| [t, []] }
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
- events.each do |event|
37
- events_by_team.fetch(event.team) << event
38
- end
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 teams
50
- teams_by_id.values
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 * teams.length
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 / teams.length.to_f).ceil
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 % teams.length
70
+ mod = current_pick % front_offices.length
71
71
 
72
- mod.positive? ? mod : teams.length
72
+ mod.positive? ? mod : front_offices.length
73
73
  end
74
74
 
75
- def current_team
75
+ def current_front_office
76
76
  return if done?
77
77
 
78
- teams[current_round_pick - 1]
78
+ front_offices[current_round_pick - 1]
79
79
  end
80
80
 
81
81
  def current_pick
@@ -100,8 +100,7 @@ module Basketball
100
100
  return if done?
101
101
 
102
102
  event = SkipEvent.new(
103
- id: SecureRandom.uuid,
104
- team: current_team,
103
+ front_office: current_front_office,
105
104
  pick: current_pick,
106
105
  round: current_round,
107
106
  round_pick: current_round_pick
@@ -117,17 +116,16 @@ module Basketball
117
116
  events = []
118
117
 
119
118
  until done? || (times && counter >= times)
120
- team = current_team
119
+ front_office = current_front_office
121
120
 
122
- player = team.pick(
121
+ player = front_office.pick(
123
122
  undrafted_player_search:,
124
- drafted_players: drafted_players(team),
123
+ drafted_players: drafted_players(front_office),
125
124
  round: current_round
126
125
  )
127
126
 
128
127
  event = SimEvent.new(
129
- id: SecureRandom.uuid,
130
- team:,
128
+ front_office:,
131
129
  player:,
132
130
  pick: current_pick,
133
131
  round: current_round,
@@ -149,8 +147,7 @@ module Basketball
149
147
  return nil if done?
150
148
 
151
149
  event = PickEvent.new(
152
- id: SecureRandom.uuid,
153
- team: current_team,
150
+ front_office: current_front_office,
154
151
  player:,
155
152
  pick: current_pick,
156
153
  round: current_round,
@@ -170,7 +167,7 @@ module Basketball
170
167
 
171
168
  private
172
169
 
173
- attr_reader :players_by_id, :teams_by_id
170
+ attr_reader :players_by_id, :front_offices_by_id
174
171
 
175
172
  def player_events
176
173
  events.select { |e| e.respond_to?(:player) }
@@ -180,9 +177,9 @@ module Basketball
180
177
  events.length + 1
181
178
  end
182
179
 
183
- def drafted_players(team = nil)
180
+ def drafted_players(front_office = nil)
184
181
  player_events.each_with_object([]) do |e, memo|
185
- next unless team.nil? || e.team == team
182
+ next unless front_office.nil? || e.front_office == front_office
186
183
 
187
184
  memo << e.player
188
185
  end
@@ -200,13 +197,17 @@ module Basketball
200
197
  raise UnknownPlayerError, "#{event.player} doesnt exist"
201
198
  end
202
199
 
203
- raise DupeEventError, "#{event} is a dupe" if events.include?(event)
204
- raise EventOutOfOrder, "#{event} team cant pick right now" if event.team != current_team
205
- raise EventOutOfOrder, "#{event} has wrong pick" if event.pick != current_pick
206
- raise EventOutOfOrder, "#{event} has wrong round" if event.round != current_round
207
- raise EventOutOfOrder, "#{event} has wrong round_pick" if event.round_pick != current_round_pick
208
- raise UnknownTeamError, "#{team} doesnt exist" unless teams.include?(event.team)
209
- raise EndOfDraftError, "#{total_picks} pick limit reached" if events.length > total_picks + 1
200
+ if event.front_office != current_front_office
201
+ raise EventOutOfOrder, "#{event} #{event.front_office} cant pick right now"
202
+ end
203
+
204
+ raise UnknownFrontOfficeError, "#{front_office} doesnt exist" unless front_offices.include?(event.front_office)
205
+
206
+ raise DupeEventError, "#{event} is a dupe" if events.include?(event)
207
+ raise EventOutOfOrder, "#{event} has wrong pick" if event.pick != current_pick
208
+ raise EventOutOfOrder, "#{event} has wrong round" if event.round != current_round
209
+ raise EventOutOfOrder, "#{event} has wrong round_pick" if event.round_pick != current_round_pick
210
+ raise EndOfDraftError, "#{total_picks} pick limit reached" if events.length > total_picks + 1
210
211
 
211
212
  events << event
212
213