basketball 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -19
  3. data/CHANGELOG.md +1 -39
  4. data/README.md +72 -93
  5. data/basketball.gemspec +3 -6
  6. data/exe/{basketball-season-scheduling → basketball-coordinator} +1 -1
  7. data/exe/{basketball-draft → basketball-room} +1 -1
  8. data/lib/basketball/app/coordinator_cli.rb +243 -0
  9. data/lib/basketball/app/coordinator_repository.rb +191 -0
  10. data/lib/basketball/app/file_store.rb +22 -0
  11. data/lib/basketball/{draft/cli.rb → app/room_cli.rb} +53 -76
  12. data/lib/basketball/app/room_repository.rb +189 -0
  13. data/lib/basketball/app.rb +12 -0
  14. data/lib/basketball/draft/assessment.rb +31 -0
  15. data/lib/basketball/draft/event.rb +3 -2
  16. data/lib/basketball/draft/front_office.rb +35 -28
  17. data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
  18. data/lib/basketball/draft/room.rb +119 -119
  19. data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
  20. data/lib/basketball/draft/skip.rb +12 -0
  21. data/lib/basketball/draft.rb +13 -6
  22. data/lib/basketball/entity.rb +10 -4
  23. data/lib/basketball/org/league.rb +73 -0
  24. data/lib/basketball/org/player.rb +26 -0
  25. data/lib/basketball/{draft → org}/position.rb +3 -2
  26. data/lib/basketball/org/team.rb +38 -0
  27. data/lib/basketball/org.rb +12 -0
  28. data/lib/basketball/season/arena.rb +112 -0
  29. data/lib/basketball/season/calendar.rb +41 -72
  30. data/lib/basketball/season/coordinator.rb +185 -126
  31. data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
  32. data/lib/basketball/season/game.rb +15 -10
  33. data/lib/basketball/season/matchup.rb +27 -0
  34. data/lib/basketball/season/opponent.rb +15 -0
  35. data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
  36. data/lib/basketball/season/result.rb +37 -0
  37. data/lib/basketball/season.rb +12 -13
  38. data/lib/basketball/value_object.rb +4 -1
  39. data/lib/basketball/version.rb +1 -1
  40. data/lib/basketball.rb +9 -4
  41. metadata +32 -44
  42. data/lib/basketball/draft/league.rb +0 -70
  43. data/lib/basketball/draft/player.rb +0 -43
  44. data/lib/basketball/draft/room_serializer.rb +0 -186
  45. data/lib/basketball/draft/roster.rb +0 -37
  46. data/lib/basketball/draft/sim_event.rb +0 -23
  47. data/lib/basketball/draft/skip_event.rb +0 -13
  48. data/lib/basketball/season/calendar_serializer.rb +0 -94
  49. data/lib/basketball/season/conference.rb +0 -57
  50. data/lib/basketball/season/division.rb +0 -43
  51. data/lib/basketball/season/league.rb +0 -114
  52. data/lib/basketball/season/league_serializer.rb +0 -99
  53. data/lib/basketball/season/scheduling_cli.rb +0 -198
  54. data/lib/basketball/season/team.rb +0 -21
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basketball
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
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-16 00:00:00.000000000 Z
11
+ date: 2023-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: faker
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '3.2'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '3.2'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: slop
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,15 +24,15 @@ dependencies:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
26
  version: '4.10'
41
- description: |2
42
- This library is meant to serve as the domain for a basketball league/season simulator/turn-based game.
43
- It models core ideas such as: players, general managers, draft strategy, drafting, season generation, season simultation,
44
- playoff generation, playoff simulation, and more.
27
+ description: " This library is meant to serve as the domain for a basketball league/season
28
+ simulator/turn-based game. It models core ideas such as: players, general managers,
29
+ draft strategy, drafting, season generation, season simultation, playoff generation,
30
+ playoff simulation, and more.\n"
45
31
  email:
46
32
  - mattruggio@icloud.com
47
33
  executables:
48
- - basketball-draft
49
- - basketball-season-scheduling
34
+ - basketball-coordinator
35
+ - basketball-room
50
36
  extensions: []
51
37
  extra_rdoc_files: []
52
38
  files:
@@ -62,37 +48,39 @@ files:
62
48
  - README.md
63
49
  - Rakefile
64
50
  - basketball.gemspec
65
- - exe/basketball-draft
66
- - exe/basketball-season-scheduling
51
+ - exe/basketball-coordinator
52
+ - exe/basketball-room
67
53
  - lib/basketball.rb
54
+ - lib/basketball/app.rb
55
+ - lib/basketball/app/coordinator_cli.rb
56
+ - lib/basketball/app/coordinator_repository.rb
57
+ - lib/basketball/app/file_store.rb
58
+ - lib/basketball/app/room_cli.rb
59
+ - lib/basketball/app/room_repository.rb
68
60
  - lib/basketball/draft.rb
69
- - lib/basketball/draft/cli.rb
61
+ - lib/basketball/draft/assessment.rb
70
62
  - lib/basketball/draft/event.rb
71
63
  - lib/basketball/draft/front_office.rb
72
- - lib/basketball/draft/league.rb
73
- - lib/basketball/draft/pick_event.rb
74
- - lib/basketball/draft/player.rb
75
- - lib/basketball/draft/player_search.rb
76
- - lib/basketball/draft/position.rb
64
+ - lib/basketball/draft/pick.rb
77
65
  - lib/basketball/draft/room.rb
78
- - lib/basketball/draft/room_serializer.rb
79
- - lib/basketball/draft/roster.rb
80
- - lib/basketball/draft/sim_event.rb
81
- - lib/basketball/draft/skip_event.rb
66
+ - lib/basketball/draft/scout.rb
67
+ - lib/basketball/draft/skip.rb
82
68
  - lib/basketball/entity.rb
69
+ - lib/basketball/org.rb
70
+ - lib/basketball/org/league.rb
71
+ - lib/basketball/org/player.rb
72
+ - lib/basketball/org/position.rb
73
+ - lib/basketball/org/team.rb
83
74
  - lib/basketball/season.rb
75
+ - lib/basketball/season/arena.rb
84
76
  - lib/basketball/season/calendar.rb
85
- - lib/basketball/season/calendar_serializer.rb
86
- - lib/basketball/season/conference.rb
87
77
  - lib/basketball/season/coordinator.rb
88
- - lib/basketball/season/division.rb
78
+ - lib/basketball/season/exhibition.rb
89
79
  - lib/basketball/season/game.rb
90
- - lib/basketball/season/league.rb
91
- - lib/basketball/season/league_serializer.rb
92
- - lib/basketball/season/preseason_game.rb
93
- - lib/basketball/season/scheduling_cli.rb
94
- - lib/basketball/season/season_game.rb
95
- - lib/basketball/season/team.rb
80
+ - lib/basketball/season/matchup.rb
81
+ - lib/basketball/season/opponent.rb
82
+ - lib/basketball/season/regular.rb
83
+ - lib/basketball/season/result.rb
96
84
  - lib/basketball/value_object.rb
97
85
  - lib/basketball/version.rb
98
86
  homepage: https://github.com/mattruggio/basketball
@@ -123,5 +111,5 @@ requirements: []
123
111
  rubygems_version: 3.4.6
124
112
  signing_key:
125
113
  specification_version: 4
126
- summary: Basketball League Game Room
114
+ summary: Basketball Simulation Domain Model
127
115
  test_files: []
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'roster'
4
-
5
- module Basketball
6
- module Draft
7
- class League
8
- class RosterNotFoundError < StandardError; end
9
- class RosterAlreadyAddedError < StandardError; end
10
-
11
- attr_reader :free_agents, :rosters
12
-
13
- def initialize(free_agents: [], front_offices: [])
14
- @rosters = []
15
- @free_agents = []
16
-
17
- front_offices.each { |front_office| add_roster(front_office) }
18
- free_agents.each { |p| register!(player: p) }
19
-
20
- freeze
21
- end
22
-
23
- def roster(front_office)
24
- rosters.find { |r| r == front_office }
25
- end
26
-
27
- def register!(player:, front_office: nil)
28
- raise PlayerRequiredError, 'player is required' unless player
29
-
30
- rosters.each do |roster|
31
- if roster.registered?(player)
32
- raise PlayerAlreadyRegisteredError,
33
- "#{player} already registered to: #{roster.id}"
34
- end
35
- end
36
-
37
- if free_agents.include?(player)
38
- raise PlayerAlreadyRegisteredError,
39
- "#{player} already registered as a free agent"
40
- end
41
-
42
- if front_office
43
- roster = roster(front_office)
44
-
45
- raise RosterNotFoundError, "Roster not found for: #{front_office}" unless roster
46
-
47
- roster.sign!(player)
48
- else
49
- free_agents << player
50
- end
51
-
52
- self
53
- end
54
-
55
- def to_s
56
- (['League'] + rosters.map(&:to_s)).join("\n")
57
- end
58
-
59
- private
60
-
61
- def add_roster(front_office)
62
- raise RosterAlreadyAddedError, "#{front_office} already added" if rosters.include?(front_office)
63
-
64
- rosters << Roster.new(id: front_office.id, name: front_office.name)
65
-
66
- self
67
- end
68
- end
69
- end
70
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Draft
5
- class Player < Entity
6
- STAR_THRESHOLD = 75
7
- OVERALL_STAR_INDICATOR = '⭐'
8
-
9
- private_constant :STAR_THRESHOLD, :OVERALL_STAR_INDICATOR
10
-
11
- attr_reader :first_name, :last_name, :position, :overall
12
-
13
- def initialize(id:, position:, first_name: '', last_name: '', overall: 0)
14
- super(id)
15
-
16
- raise ArgumentError, 'position is required' unless position
17
-
18
- @first_name = first_name.to_s
19
- @last_name = last_name.to_s
20
- @position = position
21
- @overall = overall.to_i
22
-
23
- freeze
24
- end
25
-
26
- def full_name
27
- "#{first_name.strip} #{last_name.strip}".strip
28
- end
29
-
30
- def to_s
31
- "[#{super}] #{full_name} (#{position}) #{overall} #{star_indicators.join(', ')}".strip
32
- end
33
-
34
- private
35
-
36
- def star_indicators
37
- [].tap do |indicators|
38
- indicators << OVERALL_STAR_INDICATOR if overall >= STAR_THRESHOLD
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,186 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'front_office'
4
- require_relative 'player'
5
- require_relative 'pick_event'
6
- require_relative 'sim_event'
7
- require_relative 'skip_event'
8
-
9
- module Basketball
10
- module Draft
11
- class RoomSerializer
12
- EVENT_CLASSES = {
13
- 'PickEvent' => PickEvent,
14
- 'SimEvent' => SimEvent,
15
- 'SkipEvent' => SkipEvent
16
- }.freeze
17
-
18
- private_constant :EVENT_CLASSES
19
-
20
- def to_hash(room)
21
- {
22
- 'info' => serialize_info(room),
23
- 'room' => serialize_room(room),
24
- 'league' => serialize_league(room)
25
- }
26
- end
27
-
28
- def from_hash(json)
29
- front_offices = deserialize_front_offices(json)
30
- players = deserialize_players(json)
31
- events = deserialize_events(json, players, front_offices)
32
-
33
- room_opts = {
34
- players:,
35
- front_offices:,
36
- events:
37
- }
38
-
39
- room_opts[:rounds] = json.dig('room', 'rounds') if json.dig('room', 'rounds')
40
-
41
- Room.new(**room_opts)
42
- end
43
-
44
- def deserialize(string)
45
- json = JSON.parse(string)
46
-
47
- from_hash(json)
48
- end
49
-
50
- def serialize(room)
51
- to_hash(room).to_json
52
- end
53
-
54
- private
55
-
56
- def serialize_room(room)
57
- {
58
- 'rounds' => room.rounds,
59
- 'front_offices' => serialize_front_offices(room),
60
- 'players' => serialize_players(room),
61
- 'events' => serialize_events(room.events)
62
- }
63
- end
64
-
65
- def serialize_info(room)
66
- {
67
- 'total_picks' => room.total_picks,
68
- 'current_round' => room.current_round,
69
- 'current_round_pick' => room.current_round_pick,
70
- 'current_front_office' => room.current_front_office&.id,
71
- 'current_pick' => room.current_pick,
72
- 'remaining_picks' => room.remaining_picks,
73
- 'done' => room.done?
74
- }
75
- end
76
-
77
- def serialize_league(room)
78
- league = room.to_league
79
-
80
- rosters = league.rosters.to_h do |roster|
81
- [
82
- roster.id,
83
- {
84
- 'players' => roster.players.map(&:id)
85
- }
86
- ]
87
- end
88
-
89
- {
90
- 'free_agents' => league.free_agents.map(&:id),
91
- 'rosters' => rosters
92
- }
93
- end
94
-
95
- def serialize_front_offices(room)
96
- room.front_offices.to_h do |front_office|
97
- [
98
- front_office.id,
99
- {
100
- 'name' => front_office.name,
101
- 'fuzz' => front_office.fuzz,
102
- 'depth' => front_office.depth,
103
- 'prioritized_positions' => front_office.prioritized_positions.map(&:code)
104
- }
105
- ]
106
- end
107
- end
108
-
109
- def serialize_players(room)
110
- room.players.to_h do |player|
111
- [
112
- player.id,
113
- {
114
- 'first_name' => player.first_name,
115
- 'last_name' => player.last_name,
116
- 'overall' => player.overall,
117
- 'position' => player.position.code
118
- }
119
- ]
120
- end
121
- end
122
-
123
- def serialize_events(events)
124
- events.map do |event|
125
- {
126
- 'type' => event.class.name.split('::').last,
127
- 'front_office' => event.front_office.id,
128
- 'pick' => event.pick,
129
- 'round' => event.round,
130
- 'round_pick' => event.round_pick
131
- }.tap do |hash|
132
- hash['player'] = event.player.id if event.respond_to?(:player)
133
- end
134
- end
135
- end
136
-
137
- def deserialize_front_offices(json)
138
- (json.dig('room', 'front_offices') || []).map do |id, front_office_hash|
139
- prioritized_positions = (front_office_hash['prioritized_positions'] || []).map do |v|
140
- Position.new(v)
141
- end
142
-
143
- front_office_opts = {
144
- id:,
145
- name: front_office_hash['name'],
146
- prioritized_positions:,
147
- fuzz: front_office_hash['fuzz'],
148
- depth: front_office_hash['depth']
149
- }
150
-
151
- FrontOffice.new(**front_office_opts)
152
- end
153
- end
154
-
155
- def deserialize_players(json)
156
- (json.dig('room', 'players') || []).map do |id, player_hash|
157
- player_opts = {
158
- id:,
159
- first_name: player_hash['first_name'],
160
- last_name: player_hash['last_name'],
161
- overall: player_hash['overall'],
162
- position: Position.new(player_hash['position'])
163
- }
164
-
165
- Player.new(**player_opts)
166
- end
167
- end
168
-
169
- def deserialize_events(json, players, front_offices)
170
- (json.dig('room', 'events') || []).map do |event_hash|
171
- event_opts = event_hash.slice('pick', 'round', 'round_pick').merge(
172
- front_office: front_offices.find { |t| t.id == event_hash['front_office'] }
173
- )
174
-
175
- class_constant = EVENT_CLASSES.fetch(event_hash['type'])
176
-
177
- if [PickEvent, SimEvent].include?(class_constant)
178
- event_opts[:player] = players.find { |p| p.id == event_hash['player'] }
179
- end
180
-
181
- class_constant.new(**event_opts)
182
- end
183
- end
184
- end
185
- end
186
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Draft
5
- class Roster < Entity
6
- class PlayerRequiredError < StandardError; end
7
-
8
- attr_reader :name, :players
9
-
10
- def initialize(id:, name: '', players: [])
11
- super(id)
12
-
13
- @name = name.to_s
14
- @players = players.each { |p| register!(p) }
15
-
16
- freeze
17
- end
18
-
19
- def registered?(player)
20
- players.include?(player)
21
- end
22
-
23
- def sign!(player)
24
- raise PlayerRequiredError, 'player is required' unless player
25
- raise PlayerAlreadyRegisteredError, "#{player} already registered for #{id}" if registered?(player)
26
-
27
- players << player
28
-
29
- self
30
- end
31
-
32
- def to_s
33
- (["[#{super}] #{name} Roster"] + players.map(&:to_s)).join("\n")
34
- end
35
- end
36
- end
37
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Draft
5
- class SimEvent < Event
6
- attr_reader_value :player
7
-
8
- def initialize(front_office:, player:, pick:, round:, round_pick:)
9
- super(front_office:, pick:, round:, round_pick:)
10
-
11
- raise ArgumentError, 'player required' unless player
12
-
13
- @player = player
14
-
15
- freeze
16
- end
17
-
18
- def to_s
19
- "#{player} auto-picked #{super}"
20
- end
21
- end
22
- end
23
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'event'
4
-
5
- module Basketball
6
- module Draft
7
- class SkipEvent < Event
8
- def to_s
9
- "skipped #{super}"
10
- end
11
- end
12
- end
13
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'game'
4
- require_relative 'preseason_game'
5
- require_relative 'season_game'
6
-
7
- module Basketball
8
- module Season
9
- class CalendarSerializer
10
- GAME_CLASSES = {
11
- 'PreseasonGame' => PreseasonGame,
12
- 'SeasonGame' => SeasonGame
13
- }.freeze
14
-
15
- def to_hash(calendar)
16
- teams = calendar.games.flat_map(&:teams).uniq
17
-
18
- {
19
- 'year' => calendar.preseason_start_date.year,
20
- 'teams' => serialize_teams(teams),
21
- 'games' => serialize_games(calendar.games)
22
- }
23
- end
24
-
25
- def from_hash(json)
26
- Calendar.new(
27
- year: json['year'].to_i,
28
- games: deserialize_games(json)
29
- )
30
- end
31
-
32
- def deserialize(string)
33
- json = JSON.parse(string)
34
-
35
- from_hash(json)
36
- end
37
-
38
- def serialize(calendar)
39
- to_hash(calendar).to_json
40
- end
41
-
42
- private
43
-
44
- ## Deserialization
45
-
46
- def deserialize_games(json)
47
- teams = deserialize_teams(json['teams'])
48
-
49
- (json['games'] || []).map do |game_hash|
50
- GAME_CLASSES.fetch(game_hash['type']).new(
51
- date: Date.parse(game_hash['date']),
52
- home_team: teams.fetch(game_hash['home_team']),
53
- away_team: teams.fetch(game_hash['away_team'])
54
- )
55
- end
56
- end
57
-
58
- def deserialize_teams(teams)
59
- (teams || []).to_h do |id, team_hash|
60
- team = Team.new(id:, name: team_hash['name'])
61
-
62
- [
63
- team.id,
64
- team
65
- ]
66
- end
67
- end
68
-
69
- ## Serialization
70
-
71
- def serialize_teams(teams)
72
- teams.to_h do |team|
73
- [
74
- team.id,
75
- {
76
- 'name' => team.name
77
- }
78
- ]
79
- end
80
- end
81
-
82
- def serialize_games(games)
83
- games.sort_by(&:date).map do |game|
84
- {
85
- 'type' => game.class.name.split('::').last,
86
- 'date' => game.date.to_s,
87
- 'home_team' => game.home_team.id,
88
- 'away_team' => game.away_team.id
89
- }
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class Conference < Entity
6
- DIVISIONS_SIZE = 3
7
-
8
- attr_reader :name, :divisions
9
-
10
- def initialize(id:, name: '', divisions: [])
11
- super(id)
12
-
13
- @name = name.to_s
14
- @divisions = []
15
-
16
- divisions.each { |d| register_division!(d) }
17
-
18
- if divisions.length != DIVISIONS_SIZE
19
- raise BadDivisionsSizeError, "#{id} should have exactly #{DIVISIONS_SIZE} divisions"
20
- end
21
-
22
- freeze
23
- end
24
-
25
- def to_s
26
- (["[#{super}] #{name}"] + divisions.map(&:to_s)).join("\n")
27
- end
28
-
29
- def division?(division)
30
- divisions.include?(division)
31
- end
32
-
33
- def teams
34
- divisions.flat_map(&:teams)
35
- end
36
-
37
- def team?(team)
38
- teams.include?(team)
39
- end
40
-
41
- private
42
-
43
- def register_division!(division)
44
- raise ArgumentError, 'division is required' unless division
45
- raise DivisionAlreadyRegisteredError, "#{division} already registered" if division?(division)
46
-
47
- division.teams.each do |team|
48
- raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
49
- end
50
-
51
- divisions << division
52
-
53
- self
54
- end
55
- end
56
- end
57
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Basketball
4
- module Season
5
- class Division < Entity
6
- TEAMS_SIZE = 5
7
-
8
- attr_reader :name, :teams
9
-
10
- def initialize(id:, name: '', teams: [])
11
- super(id)
12
-
13
- @name = name.to_s
14
- @teams = []
15
-
16
- teams.each { |t| register_team!(t) }
17
-
18
- raise BadTeamsSizeError, "#{id} should have exactly #{TEAMS_SIZE} teams" if teams.length != TEAMS_SIZE
19
-
20
- freeze
21
- end
22
-
23
- def to_s
24
- (["[#{super}] #{name}"] + teams.map(&:to_s)).join("\n")
25
- end
26
-
27
- def team?(team)
28
- teams.include?(team)
29
- end
30
-
31
- private
32
-
33
- def register_team!(team)
34
- raise ArgumentError, 'team is required' unless team
35
- raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
36
-
37
- teams << team
38
-
39
- self
40
- end
41
- end
42
- end
43
- end