basketball 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -19
  3. data/CHANGELOG.md +1 -39
  4. data/README.md +75 -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 +250 -0
  9. data/lib/basketball/app/coordinator_repository.rb +114 -0
  10. data/lib/basketball/app/document_repository.rb +67 -0
  11. data/lib/basketball/app/file_store.rb +38 -0
  12. data/lib/basketball/app/in_memory_store.rb +42 -0
  13. data/lib/basketball/app/league_repository.rb +20 -0
  14. data/lib/basketball/app/league_serializable.rb +54 -0
  15. data/lib/basketball/{draft/cli.rb → app/room_cli.rb} +74 -80
  16. data/lib/basketball/app/room_repository.rb +149 -0
  17. data/lib/basketball/app.rb +20 -0
  18. data/lib/basketball/draft/assessment.rb +31 -0
  19. data/lib/basketball/draft/event.rb +3 -2
  20. data/lib/basketball/draft/front_office.rb +35 -28
  21. data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
  22. data/lib/basketball/draft/room.rb +119 -119
  23. data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
  24. data/lib/basketball/draft/skip.rb +12 -0
  25. data/lib/basketball/draft.rb +13 -6
  26. data/lib/basketball/entity.rb +19 -10
  27. data/lib/basketball/org/league.rb +68 -0
  28. data/lib/basketball/org/player.rb +26 -0
  29. data/lib/basketball/{draft → org}/position.rb +3 -2
  30. data/lib/basketball/org/team.rb +38 -0
  31. data/lib/basketball/org.rb +12 -0
  32. data/lib/basketball/season/arena.rb +113 -0
  33. data/lib/basketball/season/calendar.rb +41 -72
  34. data/lib/basketball/season/coordinator.rb +186 -128
  35. data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
  36. data/lib/basketball/season/game.rb +15 -10
  37. data/lib/basketball/season/matchup.rb +27 -0
  38. data/lib/basketball/season/opponent.rb +15 -0
  39. data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
  40. data/lib/basketball/season/result.rb +37 -0
  41. data/lib/basketball/season.rb +12 -13
  42. data/lib/basketball/value_object.rb +8 -27
  43. data/lib/basketball/value_object_dsl.rb +30 -0
  44. data/lib/basketball/version.rb +1 -1
  45. data/lib/basketball.rb +9 -4
  46. metadata +37 -44
  47. data/lib/basketball/draft/league.rb +0 -70
  48. data/lib/basketball/draft/player.rb +0 -43
  49. data/lib/basketball/draft/room_serializer.rb +0 -186
  50. data/lib/basketball/draft/roster.rb +0 -37
  51. data/lib/basketball/draft/sim_event.rb +0 -23
  52. data/lib/basketball/draft/skip_event.rb +0 -13
  53. data/lib/basketball/season/calendar_serializer.rb +0 -94
  54. data/lib/basketball/season/conference.rb +0 -57
  55. data/lib/basketball/season/division.rb +0 -43
  56. data/lib/basketball/season/league.rb +0 -114
  57. data/lib/basketball/season/league_serializer.rb +0 -99
  58. data/lib/basketball/season/scheduling_cli.rb +0 -198
  59. data/lib/basketball/season/team.rb +0 -21
@@ -2,30 +2,35 @@
2
2
 
3
3
  module Basketball
4
4
  module Season
5
+ # Base class describing what all games have in common.
5
6
  class Game < ValueObject
6
- attr_reader_value :date, :home_team, :away_team
7
+ value_reader :date, :home_opponent, :away_opponent
7
8
 
8
- def initialize(date:, home_team:, away_team:)
9
+ def initialize(date:, home_opponent:, away_opponent:)
9
10
  super()
10
11
 
11
12
  raise ArgumentError, 'date is required' unless date
12
- raise ArgumentError, 'home_team is required' unless home_team
13
- raise ArgumentError, 'away_team is required' unless away_team
14
- raise ArgumentError, 'teams cannot play themselves' if home_team == away_team
13
+ raise ArgumentError, 'home_opponent is required' unless home_opponent
14
+ raise ArgumentError, 'away_opponent is required' unless away_opponent
15
+ raise ArgumentError, 'teams cannot play themselves' if home_opponent == away_opponent
15
16
 
16
- @date = date
17
- @home_team = home_team
18
- @away_team = away_team
17
+ @date = date
18
+ @home_opponent = home_opponent
19
+ @away_opponent = away_opponent
19
20
 
20
21
  freeze
21
22
  end
22
23
 
24
+ def for?(team)
25
+ teams.include?(team)
26
+ end
27
+
23
28
  def teams
24
- [home_team, away_team]
29
+ [home_opponent, away_opponent]
25
30
  end
26
31
 
27
32
  def to_s
28
- "#{date} - #{away_team} at #{home_team}"
33
+ "#{date} - #{away_opponent} at #{home_opponent}"
29
34
  end
30
35
  end
31
36
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basketball
4
+ module Season
5
+ # A Matchup is a late materialization of a game. While a game is a skeleton for a future
6
+ # matchup, it does not materialize until game time (rosters could change right up until game time).
7
+ class Matchup
8
+ class PlayersOnBothTeamsError < StandardError; end
9
+
10
+ attr_reader :game, :home_players, :away_players
11
+
12
+ def initialize(game:, home_players: [], away_players: [])
13
+ raise ArgumentError, 'game is required' unless game
14
+
15
+ @game = game
16
+ @home_players = home_players.uniq
17
+ @away_players = away_players.uniq
18
+
19
+ if home_players.intersect?(away_players)
20
+ raise PlayersOnBothTeamsError, 'players cannot be on both home and away team'
21
+ end
22
+
23
+ freeze
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basketball
4
+ module Season
5
+ # Represents a team without a roster. Equal to a team by identity.
6
+ # A team's roster will not be known until the last minute (when it is game time).
7
+ class Opponent < Entity
8
+ def initialize(id:)
9
+ super(id)
10
+
11
+ freeze
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Basketball
4
4
  module Season
5
- class SeasonGame < Game
5
+ # A regular season game.
6
+ class Regular < Game
6
7
  end
7
8
  end
8
9
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basketball
4
+ module Season
5
+ # Base class describing the end result of a game. This should/could be sub-classed to include
6
+ # more sport-specific information.
7
+ class Result
8
+ extend Forwardable
9
+
10
+ class CannotTieError < StandardError; end
11
+
12
+ attr_reader :game, :home_score, :away_score
13
+
14
+ def_delegators :game, :date, :home_opponent, :away_opponent, :teams
15
+
16
+ def initialize(game:, home_score:, away_score:)
17
+ raise ArgumentError, 'game is required' unless game
18
+ raise CannotTieError, "#{game} ended in a tie" if home_score == away_score
19
+
20
+ @game = game
21
+ @home_score = home_score.to_i
22
+ @away_score = away_score.to_i
23
+ end
24
+
25
+ def to_s
26
+ "#{game.date} - #{away_opponent} (#{away_score}) at #{home_opponent} (#{home_score})"
27
+ end
28
+
29
+ def ==(other)
30
+ game == other.game &&
31
+ home_score == other.home_score &&
32
+ away_score == other.away_score
33
+ end
34
+ alias eql? ==
35
+ end
36
+ end
37
+ end
@@ -1,17 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'season/scheduling_cli'
3
+ # Common
4
+ require_relative 'season/arena'
5
+ require_relative 'season/calendar'
6
+ require_relative 'season/result'
7
+ require_relative 'season/game'
8
+ require_relative 'season/matchup'
9
+ require_relative 'season/opponent'
4
10
 
5
- module Basketball
6
- module Season
7
- class ConferenceAlreadyRegisteredError < StandardError; end
8
- class DivisionAlreadyRegisteredError < StandardError; end
9
- class TeamAlreadyRegisteredError < StandardError; end
11
+ # Game Subclasses
12
+ require_relative 'season/exhibition'
13
+ require_relative 'season/regular'
10
14
 
11
- class BadConferencesSizeError < StandardError; end
12
- class BadDivisionsSizeError < StandardError; end
13
- class BadTeamsSizeError < StandardError; end
14
-
15
- class UnknownTeamError < StandardError; end
16
- end
17
- end
15
+ # Specific
16
+ require_relative 'season/coordinator'
@@ -1,34 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'value_object_dsl'
4
+
3
5
  module Basketball
6
+ # A Value Object is something that has no specific identity, instead its identity is the sum of
7
+ # the attribute values. Changing one will change the entire object's identity.
8
+ # Comes with a very simple DSL provided by ValueObjectDSL for specifying properties along with
9
+ # base equality and sorting methods.
4
10
  class ValueObject
5
11
  include Comparable
6
-
7
- class << self
8
- def all_value_keys
9
- @all_value_keys ||= ancestors.flat_map do |ancestor|
10
- if ancestor < Basketball::ValueObject
11
- ancestor.value_keys
12
- else
13
- []
14
- end
15
- end.uniq.sort
16
- end
17
-
18
- def all_sorted_value_keys
19
- all_value_keys.sort
20
- end
21
-
22
- def value_keys
23
- @value_keys ||= []
24
- end
25
-
26
- def attr_reader_value(*keys)
27
- keys.each { |k| value_keys << k.to_sym }
28
-
29
- attr_reader(*keys)
30
- end
31
- end
12
+ extend ValueObjectDSL
32
13
 
33
14
  def to_s
34
15
  to_h.map { |k, v| "#{k}: #{v}" }.join(', ')
@@ -52,7 +33,7 @@ module Basketball
52
33
  alias eql? ==
53
34
 
54
35
  def hash
55
- all_sorted_values.map(&:hash).hash
36
+ all_sorted_values.hash
56
37
  end
57
38
 
58
39
  def all_sorted_values
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basketball
4
+ # Class-level methods that extend core Ruby attr_* methods.
5
+ module ValueObjectDSL
6
+ def all_value_keys
7
+ @all_value_keys ||= ancestors.flat_map do |ancestor|
8
+ if ancestor < Basketball::ValueObject
9
+ ancestor.value_keys
10
+ else
11
+ []
12
+ end
13
+ end.uniq.sort
14
+ end
15
+
16
+ def all_sorted_value_keys
17
+ all_value_keys.sort
18
+ end
19
+
20
+ def value_keys
21
+ @value_keys ||= []
22
+ end
23
+
24
+ def value_reader(*keys)
25
+ keys.each { |k| value_keys << k.to_sym }
26
+
27
+ attr_reader(*keys)
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basketball
4
- VERSION = '0.0.8'
4
+ VERSION = '0.0.10'
5
5
  end
data/lib/basketball.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faker'
3
+ require 'date'
4
4
  require 'fileutils'
5
5
  require 'forwardable'
6
6
  require 'json'
7
- require 'securerandom'
8
7
  require 'slop'
9
8
 
10
- # Top-level
9
+ # Generic
11
10
  require_relative 'basketball/entity'
12
11
  require_relative 'basketball/value_object'
13
12
 
14
- # Submodules
13
+ # Dependent on Generic
14
+ require_relative 'basketball/org'
15
+
16
+ # Dependent on Org
15
17
  require_relative 'basketball/draft'
16
18
  require_relative 'basketball/season'
19
+
20
+ # Dependent on All
21
+ require_relative 'basketball/app'
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.10
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-06-01 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,38 +48,45 @@ 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/document_repository.rb
58
+ - lib/basketball/app/file_store.rb
59
+ - lib/basketball/app/in_memory_store.rb
60
+ - lib/basketball/app/league_repository.rb
61
+ - lib/basketball/app/league_serializable.rb
62
+ - lib/basketball/app/room_cli.rb
63
+ - lib/basketball/app/room_repository.rb
68
64
  - lib/basketball/draft.rb
69
- - lib/basketball/draft/cli.rb
65
+ - lib/basketball/draft/assessment.rb
70
66
  - lib/basketball/draft/event.rb
71
67
  - 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
68
+ - lib/basketball/draft/pick.rb
77
69
  - 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
70
+ - lib/basketball/draft/scout.rb
71
+ - lib/basketball/draft/skip.rb
82
72
  - lib/basketball/entity.rb
73
+ - lib/basketball/org.rb
74
+ - lib/basketball/org/league.rb
75
+ - lib/basketball/org/player.rb
76
+ - lib/basketball/org/position.rb
77
+ - lib/basketball/org/team.rb
83
78
  - lib/basketball/season.rb
79
+ - lib/basketball/season/arena.rb
84
80
  - lib/basketball/season/calendar.rb
85
- - lib/basketball/season/calendar_serializer.rb
86
- - lib/basketball/season/conference.rb
87
81
  - lib/basketball/season/coordinator.rb
88
- - lib/basketball/season/division.rb
82
+ - lib/basketball/season/exhibition.rb
89
83
  - 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
84
+ - lib/basketball/season/matchup.rb
85
+ - lib/basketball/season/opponent.rb
86
+ - lib/basketball/season/regular.rb
87
+ - lib/basketball/season/result.rb
96
88
  - lib/basketball/value_object.rb
89
+ - lib/basketball/value_object_dsl.rb
97
90
  - lib/basketball/version.rb
98
91
  homepage: https://github.com/mattruggio/basketball
99
92
  licenses:
@@ -123,5 +116,5 @@ requirements: []
123
116
  rubygems_version: 3.4.6
124
117
  signing_key:
125
118
  specification_version: 4
126
- summary: Basketball League Game Room
119
+ summary: Basketball Simulation Domain Model
127
120
  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