basketball 0.0.14 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/README.md +4 -111
- data/basketball.gemspec +1 -9
- data/lib/basketball/app/coordinator_repository.rb +2 -6
- data/lib/basketball/app/league_repository.rb +92 -3
- data/lib/basketball/app/room_repository.rb +2 -1
- data/lib/basketball/app/standings_repository.rb +69 -0
- data/lib/basketball/app.rb +1 -7
- data/lib/basketball/org/league.rb +0 -2
- data/lib/basketball/season/coordinator.rb +10 -31
- data/lib/basketball/season/record.rb +12 -6
- data/lib/basketball/season/standings.rb +16 -5
- data/lib/basketball/version.rb +1 -1
- data/lib/basketball.rb +0 -1
- metadata +5 -27
- data/exe/basketball +0 -91
- data/exe/basketball-draft-room +0 -7
- data/exe/basketball-season-coordinator +0 -7
- data/lib/basketball/app/coordinator_cli.rb +0 -227
- data/lib/basketball/app/league_serializable.rb +0 -99
- data/lib/basketball/app/room_cli.rb +0 -216
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5be10590a625ef061cb6f7a80379710013ec3625fe215fa65bfddeda35bb1f5
|
4
|
+
data.tar.gz: 5eff4fd535bddae0bf080f2fd0be83b4cdc8da7cc28c04667a1ec7cfd84f1f55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41e8aac1b7270794a5557b9e94b3b23bd0f5f7169c5c38f87401eaf12e6865efd4a1b8293a8cf6b6a41813ecc476750f538abc511ae4554e94c3cc732c6a5872
|
7
|
+
data.tar.gz: fd0fcc3117888b9c475efce63e5947de6f66950b3af98af9f4c551403749ab34fc6f989d3b48964bc29180be19c3af55931e5bb4ef67377f05dd83994ab82284
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -12,12 +12,9 @@ Element | Description
|
|
12
12
|
:------------ | :-----------
|
13
13
|
**Arena** | Determines exhibition and regular season game outcomes.
|
14
14
|
**Assessment** | When the Room needs to know who a Front Office wants to select, the Room will send the Front Office an Assessment. The Assessment is a report of where the team currently stands: players picked, players available, and round information.
|
15
|
-
**basketball-draft-room** | Command-line executable script showing an example of how to consume the draft room aggregate root.
|
16
|
-
**basketball-season-coordinator** | Command-line executable script showing an example of how to consume the season coordinator aggregate root.
|
17
15
|
**Calendar** | Stores important boundary dates (exhibition start, exhibition end, season start, and season end).
|
18
16
|
**Conference** | A collection of Divisions.
|
19
|
-
**Coordinator
|
20
|
-
**Coordinator Repository** | Understands how to save and load Coordinator objects from JSON files on disk.
|
17
|
+
**Coordinator Repository** | Understands how to save and load Coordinator objects.
|
21
18
|
**Coordinator** | Object which can take a League, Calendar, Games, and an Arena and provide an iterable interface to enumerate through days and simulate games as results.
|
22
19
|
**Detail** | Re-representation of a Result object but from a specific team's perspective.
|
23
20
|
**Division** | A collection of teams.
|
@@ -25,7 +22,7 @@ Element | Description
|
|
25
22
|
**Exhibition** | Pre-season game which has no impact to team record.
|
26
23
|
**External Dependency** | Some outside system which this library or portions of this library are dependent on.
|
27
24
|
**File Store** | Implements a store that can interact with the underlying File System.
|
28
|
-
**File System** | Local operating system that
|
25
|
+
**File System** | Local operating system that repositories can use as their underlying persistence layer.
|
29
26
|
**Front Office** | Identifiable as a team, contains logic for how to auto-pick draft selections. Meant to be subclassed and extended to include more intricate player selection logic as the base will simply randomly select a player.
|
30
27
|
**Game** | Matches up a date with two teams (home and away) to represent a coordinatord match-up.
|
31
28
|
**League Repository** | Understands how to save and load League objects from JSON files on disk.
|
@@ -37,14 +34,14 @@ Element | Description
|
|
37
34
|
**Record** | Represents a team's overall record.
|
38
35
|
**Regular** | Game that counts towards regular season record.
|
39
36
|
**Result** | The outcome of a game (typically with a home and away score).
|
40
|
-
**Room
|
41
|
-
**Room Repository** | Understands how to save and load Room objects from JSON files on disk.
|
37
|
+
**Room Repository** | Understands how to save and load Room objects.
|
42
38
|
**Room** | Main object responsible for providing an iterable interface capable of executing a draft, pick by pick.
|
43
39
|
**Scheduler** | Knows how to take a League and a year and generate a game-populated calendar.
|
44
40
|
**Scout** | Knows how to stack rank lists of players.
|
45
41
|
**Season** | Bounded context (sub-module) dealing with calendar and matchup generation.
|
46
42
|
**Skip** | Result event emitted when a front office decides to skip a round.
|
47
43
|
**Standings** | Synthesizes teams and results into team standings with win/loss records and more.
|
44
|
+
**Standings Repository** | Understands how to save and load Standings objects.
|
48
45
|
**Store** | Interface for the underlying Repository persistence layer. While a Document Repository is mainly responsible for serialization/de-serialization, the store actually knows how to read/write the data.
|
49
46
|
**Team Group** | Set of rosters that together form a cohesive league.
|
50
47
|
**Team** | Member of a league and signs players. Has games assigned and played.
|
@@ -72,70 +69,6 @@ The input for the main object `Basketball::Draft::Room` is an array of teams (`B
|
|
72
69
|
* **Basketball::Draft::Room#pick!(player)**: Pick an exact player for the current front office.
|
73
70
|
* **Basketball::Draft::Room#sim_rest!**: Simulate the rest of the picks.
|
74
71
|
|
75
|
-
#### Command Line Interface
|
76
|
-
|
77
|
-
This library ships with an example of how the Draft module could be used implemented as a command-line executable script. The script is file-based and will de-serialize into a Room object, execute operations, then serialize and write it back to disk.
|
78
|
-
|
79
|
-
###### Generate a Fresh Draft
|
80
|
-
|
81
|
-
```zsh
|
82
|
-
basketball-draft-room -o tmp/draft.json
|
83
|
-
```
|
84
|
-
|
85
|
-
###### N Top Available Players
|
86
|
-
|
87
|
-
```zsh
|
88
|
-
basketball-draft-room -i tmp/draft.json -t 10
|
89
|
-
```
|
90
|
-
|
91
|
-
###### N Top Available Players for a Position
|
92
|
-
|
93
|
-
```zsh
|
94
|
-
basketball-draft-room -i tmp/draft.json -t 10 -q PG
|
95
|
-
```
|
96
|
-
|
97
|
-
###### Output League
|
98
|
-
|
99
|
-
```zsh
|
100
|
-
basketball-draft-room -i tmp/draft.json -l
|
101
|
-
```
|
102
|
-
|
103
|
-
###### Output Event Log
|
104
|
-
|
105
|
-
```zsh
|
106
|
-
basketball-draft-room -i tmp/draft.json -e
|
107
|
-
```
|
108
|
-
|
109
|
-
###### Simulate N Picks
|
110
|
-
|
111
|
-
```zsh
|
112
|
-
basketball-draft-room -i tmp/draft.json -s 10
|
113
|
-
```
|
114
|
-
|
115
|
-
###### Skip N Picks
|
116
|
-
|
117
|
-
```zsh
|
118
|
-
basketball-draft-room -i tmp/draft.json -x 10
|
119
|
-
```
|
120
|
-
|
121
|
-
###### Pick Players
|
122
|
-
|
123
|
-
```zsh
|
124
|
-
basketball-draft-room -i tmp/draft.json -p P-100,P-200,P-300
|
125
|
-
```
|
126
|
-
|
127
|
-
###### Simulate the Rest of the Draft
|
128
|
-
|
129
|
-
```zsh
|
130
|
-
basketball-draft-room -i tmp/draft.json -a
|
131
|
-
```
|
132
|
-
|
133
|
-
###### Help Menu
|
134
|
-
|
135
|
-
```zsh
|
136
|
-
basketball-draft-room -h
|
137
|
-
```
|
138
|
-
|
139
72
|
### Season Module
|
140
73
|
|
141
74
|
The Season module knows how to execute a calendar of games for a League and generate results. The main object is the `Basketball::Season::Coordinator` class. Once instantiated there are four main methods:
|
@@ -143,46 +76,6 @@ The Season module knows how to execute a calendar of games for a League and gene
|
|
143
76
|
* **Basketball::Season::Coordinator#sim!**: Simulate the next day of games.
|
144
77
|
* **Basketball::Season::Coordinator#sim_rest!**: Simulate the rest of the games.
|
145
78
|
|
146
|
-
#### Command Line Interface
|
147
|
-
|
148
|
-
This library ships with an example of how the Season module could be used implemented as a command-line executable script. The script is file-based and will de-serialize into a Coordinator object, execute operations, then serialize and write it back to disk.
|
149
|
-
|
150
|
-
###### Generate Random coordinator
|
151
|
-
|
152
|
-
```zsh
|
153
|
-
exe/basketball-season-coordinator -o tmp/coordinator.json
|
154
|
-
```
|
155
|
-
|
156
|
-
###### Sim One Day and Save to New File
|
157
|
-
|
158
|
-
```zsh
|
159
|
-
exe/basketball-season-coordinator -i tmp/coordinator.json -o tmp/coordinator2.json -d 1
|
160
|
-
```
|
161
|
-
|
162
|
-
###### Output Event Log
|
163
|
-
|
164
|
-
```zsh
|
165
|
-
exe/basketball-season-coordinator -i tmp/coordinator.json -e
|
166
|
-
```
|
167
|
-
|
168
|
-
###### Sim Two Days and Save To Input File
|
169
|
-
|
170
|
-
```zsh
|
171
|
-
exe/basketball-season-coordinator -i tmp/coordinator.json -d 2
|
172
|
-
```
|
173
|
-
|
174
|
-
###### Sim Rest of Calendar
|
175
|
-
|
176
|
-
```zsh
|
177
|
-
exe/basketball-season-coordinator -i tmp/coordinator.json -a
|
178
|
-
```
|
179
|
-
|
180
|
-
###### Help Menu
|
181
|
-
|
182
|
-
```zsh
|
183
|
-
basketball-season-coordinator -h
|
184
|
-
```
|
185
|
-
|
186
79
|
## Contributing
|
187
80
|
|
188
81
|
### Development Environment Configuration
|
data/basketball.gemspec
CHANGED
@@ -15,13 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.email = ['mattruggio@icloud.com']
|
16
16
|
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(.github|bin|docs|spec)/}) }
|
17
17
|
s.bindir = 'exe'
|
18
|
-
|
19
|
-
s.executables = %w[
|
20
|
-
basketball
|
21
|
-
basketball-season-coordinator
|
22
|
-
basketball-draft-room
|
23
|
-
]
|
24
|
-
|
18
|
+
s.executables = %w[]
|
25
19
|
s.homepage = 'https://github.com/mattruggio/basketball'
|
26
20
|
s.license = 'MIT'
|
27
21
|
s.metadata = {
|
@@ -34,6 +28,4 @@ Gem::Specification.new do |s|
|
|
34
28
|
}
|
35
29
|
|
36
30
|
s.required_ruby_version = '>= 3.2.1'
|
37
|
-
|
38
|
-
s.add_dependency('slop', '~>4.10')
|
39
31
|
end
|
@@ -4,8 +4,6 @@ module Basketball
|
|
4
4
|
module App
|
5
5
|
# Knows how to flatten a Coordinator instance and rehydrate one from JSON and/or a Ruby hash.
|
6
6
|
class CoordinatorRepository < DocumentRepository
|
7
|
-
include LeagueSerializable
|
8
|
-
|
9
7
|
GAME_CLASSES = {
|
10
8
|
'Exhibition' => Season::Exhibition,
|
11
9
|
'Regular' => Season::Regular
|
@@ -19,8 +17,7 @@ module Basketball
|
|
19
17
|
Season::Coordinator.new(
|
20
18
|
calendar: deserialize_calendar(hash[:calendar]),
|
21
19
|
current_date: Date.parse(hash[:current_date]),
|
22
|
-
results: deserialize_results(hash[:results])
|
23
|
-
league: deserialize_league(hash[:league])
|
20
|
+
results: deserialize_results(hash[:results])
|
24
21
|
)
|
25
22
|
end
|
26
23
|
|
@@ -29,8 +26,7 @@ module Basketball
|
|
29
26
|
id: coordinator.id,
|
30
27
|
calendar: serialize_calendar(coordinator.calendar),
|
31
28
|
current_date: coordinator.current_date.to_s,
|
32
|
-
results: serialize_results(coordinator.results)
|
33
|
-
league: serialize_league(coordinator.league)
|
29
|
+
results: serialize_results(coordinator.results)
|
34
30
|
}
|
35
31
|
end
|
36
32
|
|
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module App
|
5
|
-
# Knows how to flatten a League
|
5
|
+
# Knows how to flatten a League instances and rehydrate one from JSON and/or a Ruby hash.
|
6
6
|
class LeagueRepository < DocumentRepository
|
7
|
-
include LeagueSerializable
|
8
|
-
|
9
7
|
private
|
10
8
|
|
11
9
|
def from_h(hash)
|
@@ -15,6 +13,97 @@ module Basketball
|
|
15
13
|
def to_h(league)
|
16
14
|
serialize_league(league)
|
17
15
|
end
|
16
|
+
|
17
|
+
# Serialization
|
18
|
+
|
19
|
+
def serialize_league(league)
|
20
|
+
{
|
21
|
+
id: league.id,
|
22
|
+
conferences: serialize_conferences(league.conferences)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize_conferences(conferences)
|
27
|
+
conferences.map do |conference|
|
28
|
+
{
|
29
|
+
id: conference.id,
|
30
|
+
divisions: serialize_divisions(conference.divisions)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize_divisions(divisions)
|
36
|
+
divisions.map do |division|
|
37
|
+
{
|
38
|
+
id: division.id,
|
39
|
+
teams: serialize_teams(division.teams)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def serialize_teams(teams)
|
45
|
+
teams.map do |team|
|
46
|
+
{
|
47
|
+
id: team.id,
|
48
|
+
players: serialize_players(team.players)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def serialize_players(players)
|
54
|
+
players.map do |player|
|
55
|
+
{
|
56
|
+
id: player.id,
|
57
|
+
overall: player.overall,
|
58
|
+
position: player.position&.code
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Deserialization
|
64
|
+
|
65
|
+
def deserialize_league(league_hash)
|
66
|
+
Org::League.new(
|
67
|
+
conferences: deserialize_conferences(league_hash[:conferences])
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def deserialize_conferences(conference_hashes)
|
72
|
+
(conference_hashes || []).map do |conference_hash|
|
73
|
+
Org::Conference.new(
|
74
|
+
id: conference_hash[:id],
|
75
|
+
divisions: deserialize_divisions(conference_hash[:divisions])
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def deserialize_divisions(division_hashes)
|
81
|
+
(division_hashes || []).map do |division_hash|
|
82
|
+
Org::Division.new(
|
83
|
+
id: division_hash[:id],
|
84
|
+
teams: deserialize_teams(division_hash[:teams])
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def deserialize_teams(team_hashes)
|
90
|
+
(team_hashes || []).map do |team_hash|
|
91
|
+
Org::Team.new(
|
92
|
+
id: team_hash[:id],
|
93
|
+
players: deserialize_players(team_hash[:players])
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def deserialize_players(player_hashes)
|
99
|
+
(player_hashes || []).map do |player_hash|
|
100
|
+
Org::Player.new(
|
101
|
+
id: player_hash[:id],
|
102
|
+
overall: player_hash[:overall],
|
103
|
+
position: Org::Position.new(player_hash[:position])
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
18
107
|
end
|
19
108
|
end
|
20
109
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module App
|
5
|
-
# Can load and save Room objects
|
5
|
+
# Can load and save Room objects as JSON Documents.
|
6
6
|
class RoomRepository < DocumentRepository
|
7
7
|
PICK_EVENT = 'Pick'
|
8
8
|
SKIP_EVENT = 'Skip'
|
@@ -26,6 +26,7 @@ module Basketball
|
|
26
26
|
|
27
27
|
def to_h(room)
|
28
28
|
{
|
29
|
+
id: room.id,
|
29
30
|
rounds: room.rounds,
|
30
31
|
front_offices: room.front_offices.map { |fo| serialize_front_office(fo) },
|
31
32
|
players: room.players.map { |p| serialize_player(p) },
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Can load and save Standings objects as JSON Documents.
|
6
|
+
class StandingsRepository < DocumentRepository
|
7
|
+
private
|
8
|
+
|
9
|
+
def from_h(hash)
|
10
|
+
Season::Standings.new(
|
11
|
+
records: deserialize_records(hash[:records])
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h(standings)
|
16
|
+
{
|
17
|
+
id: standings.id,
|
18
|
+
records: serialize_records(standings.records)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Serialization
|
23
|
+
|
24
|
+
def serialize_records(records)
|
25
|
+
(records || []).map do |record|
|
26
|
+
{
|
27
|
+
id: record.id,
|
28
|
+
details: serialize_details(record.details)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serialize_details(details)
|
34
|
+
(details || []).map do |detail|
|
35
|
+
{
|
36
|
+
date: detail.date.to_s,
|
37
|
+
home: detail.home,
|
38
|
+
opponent: detail.opponent.id,
|
39
|
+
opponent_score: detail.opponent_score,
|
40
|
+
score: detail.score
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Deserialization
|
46
|
+
|
47
|
+
def deserialize_records(record_hashes)
|
48
|
+
(record_hashes || []).map do |record_hash|
|
49
|
+
Season::Record.new(
|
50
|
+
id: record_hash[:id],
|
51
|
+
details: deserialize_details(record_hash[:details])
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def deserialize_details(detail_hashes)
|
57
|
+
(detail_hashes || []).map do |detail_hash|
|
58
|
+
Season::Detail.new(
|
59
|
+
date: Date.parse(detail_hash[:date]),
|
60
|
+
home: detail_hash[:home],
|
61
|
+
opponent: Season::Opponent.new(id: detail_hash[:opponent]),
|
62
|
+
opponent_score: detail_hash[:opponent_score].to_i,
|
63
|
+
score: detail_hash[:score].to_i
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/basketball/app.rb
CHANGED
@@ -4,9 +4,6 @@
|
|
4
4
|
require_relative 'app/file_store'
|
5
5
|
require_relative 'app/in_memory_store'
|
6
6
|
|
7
|
-
# Serialization
|
8
|
-
require_relative 'app/league_serializable'
|
9
|
-
|
10
7
|
# Repositories / Common
|
11
8
|
require_relative 'app/document_repository'
|
12
9
|
|
@@ -14,7 +11,4 @@ require_relative 'app/document_repository'
|
|
14
11
|
require_relative 'app/coordinator_repository'
|
15
12
|
require_relative 'app/league_repository'
|
16
13
|
require_relative 'app/room_repository'
|
17
|
-
|
18
|
-
# Controllers
|
19
|
-
require_relative 'app/coordinator_cli'
|
20
|
-
require_relative 'app/room_cli'
|
14
|
+
require_relative 'app/standings_repository'
|
@@ -11,14 +11,12 @@ module Basketball
|
|
11
11
|
class OutOfBoundsError < StandardError; end
|
12
12
|
class PlayedGamesError < StandardError; end
|
13
13
|
class UnknownGameError < StandardError; end
|
14
|
-
class UnknownTeamError < StandardError; end
|
15
14
|
class UnplayedGamesError < StandardError; end
|
16
15
|
|
17
16
|
attr_reader :calendar,
|
18
17
|
:current_date,
|
19
18
|
:arena,
|
20
|
-
:results
|
21
|
-
:league
|
19
|
+
:results
|
22
20
|
|
23
21
|
def_delegators :calendar,
|
24
22
|
:exhibition_start_date,
|
@@ -30,37 +28,29 @@ module Basketball
|
|
30
28
|
:regulars_for,
|
31
29
|
:games_for
|
32
30
|
|
33
|
-
def initialize(
|
34
|
-
calendar:,
|
35
|
-
current_date:,
|
36
|
-
results: [],
|
37
|
-
league: Org::League.new
|
38
|
-
)
|
31
|
+
def initialize(calendar:, current_date:, results: [])
|
39
32
|
super()
|
40
33
|
|
41
34
|
raise ArgumentError, 'calendar is required' unless calendar
|
42
35
|
raise ArgumentError, 'current_date is required' if current_date.to_s.empty?
|
43
|
-
raise ArgumentError, 'league is required' unless league
|
44
36
|
|
45
37
|
@calendar = calendar
|
46
38
|
@current_date = current_date
|
47
39
|
@arena = Arena.new
|
48
40
|
@results = []
|
49
|
-
@league = league
|
50
41
|
|
51
42
|
results.each { |result| replay!(result) }
|
52
43
|
|
53
44
|
assert_current_date
|
54
45
|
assert_all_past_dates_are_played
|
55
46
|
assert_all_future_dates_arent_played
|
56
|
-
assert_all_known_teams
|
57
47
|
end
|
58
48
|
|
59
|
-
def sim_rest!(&)
|
49
|
+
def sim_rest!(league, &)
|
60
50
|
events = []
|
61
51
|
|
62
52
|
while not_done?
|
63
|
-
new_events = sim!(&)
|
53
|
+
new_events = sim!(league, &)
|
64
54
|
|
65
55
|
events += new_events
|
66
56
|
end
|
@@ -78,15 +68,17 @@ module Basketball
|
|
78
68
|
raise OutOfBoundsError, "current date #{current_date} should be on or after #{exhibition_start_date}"
|
79
69
|
end
|
80
70
|
|
81
|
-
def sim!
|
71
|
+
def sim!(league)
|
72
|
+
raise ArgumentError, 'league is required' unless league
|
73
|
+
|
82
74
|
return [] if done?
|
83
75
|
|
84
76
|
events = []
|
85
77
|
games = games_for(date: current_date)
|
86
78
|
|
87
79
|
games.each do |game|
|
88
|
-
home_players = opponent_team(game.home_opponent).players
|
89
|
-
away_players = opponent_team(game.away_opponent).players
|
80
|
+
home_players = opponent_team(league, game.home_opponent).players
|
81
|
+
away_players = opponent_team(league, game.away_opponent).players
|
90
82
|
matchup = Matchup.new(game:, home_players:, away_players:)
|
91
83
|
event = arena.play(matchup)
|
92
84
|
|
@@ -140,7 +132,6 @@ module Basketball
|
|
140
132
|
|
141
133
|
def add!(game)
|
142
134
|
assert_today_or_in_future(game)
|
143
|
-
assert_known_teams(game)
|
144
135
|
|
145
136
|
calendar.add!(game)
|
146
137
|
|
@@ -165,7 +156,7 @@ module Basketball
|
|
165
156
|
|
166
157
|
attr_writer :arena
|
167
158
|
|
168
|
-
def opponent_team(opponent)
|
159
|
+
def opponent_team(league, opponent)
|
169
160
|
league.teams.find { |t| t == opponent }
|
170
161
|
end
|
171
162
|
|
@@ -191,14 +182,6 @@ module Basketball
|
|
191
182
|
raise OutOfBoundsError, "#{game.date} is on or before the current date (#{current_date})"
|
192
183
|
end
|
193
184
|
|
194
|
-
def assert_known_teams(game)
|
195
|
-
raise UnknownTeamError, "unknown opponent: #{game.home_opponent}" unless league.team?(game.home_opponent)
|
196
|
-
|
197
|
-
return if league.team?(game.away_opponent)
|
198
|
-
|
199
|
-
raise UnknownTeamError, "unknown opponent: #{game.away_opponent}"
|
200
|
-
end
|
201
|
-
|
202
185
|
def assert_all_past_dates_are_played
|
203
186
|
games_that_should_be_played = games.select { |game| game.date < current_date }
|
204
187
|
|
@@ -237,10 +220,6 @@ module Basketball
|
|
237
220
|
|
238
221
|
result
|
239
222
|
end
|
240
|
-
|
241
|
-
def assert_all_known_teams
|
242
|
-
calendar.games.each { |game| assert_known_teams(game) }
|
243
|
-
end
|
244
223
|
end
|
245
224
|
end
|
246
225
|
end
|
@@ -8,7 +8,7 @@ module Basketball
|
|
8
8
|
class DetailAlreadyAddedError < StandardError; end
|
9
9
|
class OpponentNotFoundError < StandardError; end
|
10
10
|
|
11
|
-
def initialize(id:)
|
11
|
+
def initialize(id:, details: [])
|
12
12
|
super(id)
|
13
13
|
|
14
14
|
@details_by_date = {}
|
@@ -53,9 +53,15 @@ module Basketball
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def win_percentage
|
56
|
+
return 0 unless game_count.positive?
|
57
|
+
|
56
58
|
(win_count.to_f / game_count).round(3)
|
57
59
|
end
|
58
60
|
|
61
|
+
def win_percentage_display
|
62
|
+
format('%.3f', win_percentage)
|
63
|
+
end
|
64
|
+
|
59
65
|
def game_count
|
60
66
|
details.length
|
61
67
|
end
|
@@ -69,17 +75,13 @@ module Basketball
|
|
69
75
|
end
|
70
76
|
|
71
77
|
def to_s
|
72
|
-
"[#{super}] #{win_count}-#{loss_count} (#{
|
78
|
+
"[#{super}] #{win_count}-#{loss_count} (#{win_percentage_display})"
|
73
79
|
end
|
74
80
|
|
75
81
|
def <=>(other)
|
76
82
|
[win_count, win_percentage] <=> [other.win_count, other.win_percentage]
|
77
83
|
end
|
78
84
|
|
79
|
-
private
|
80
|
-
|
81
|
-
attr_reader :details_by_date
|
82
|
-
|
83
85
|
def add!(detail)
|
84
86
|
raise DetailAlreadyAddedError, "#{detail} already added for date" if detail_for(detail.date)
|
85
87
|
|
@@ -87,6 +89,10 @@ module Basketball
|
|
87
89
|
|
88
90
|
self
|
89
91
|
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
attr_reader :details_by_date
|
90
96
|
end
|
91
97
|
end
|
92
98
|
end
|
@@ -3,18 +3,29 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Season
|
5
5
|
# Represents a League with each team's win/loss details.
|
6
|
-
class Standings
|
6
|
+
class Standings < Entity
|
7
7
|
class TeamAlreadyRegisteredError < StandardError; end
|
8
8
|
class TeamNotRegisteredError < StandardError; end
|
9
9
|
|
10
|
-
def initialize
|
10
|
+
def initialize(records: [])
|
11
|
+
super()
|
12
|
+
|
11
13
|
@records_by_id = {}
|
14
|
+
|
15
|
+
records.each { |record| add!(record) }
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
15
|
-
raise
|
18
|
+
def add!(record)
|
19
|
+
raise ArgumentError, 'record is required' unless record
|
20
|
+
raise TeamAlreadyRegisteredError, "#{team} already registered!" if team?(record)
|
21
|
+
|
22
|
+
records_by_id[record.id] = record
|
16
23
|
|
17
|
-
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def register!(team)
|
28
|
+
add!(Record.new(id: team.id))
|
18
29
|
|
19
30
|
self
|
20
31
|
end
|
data/lib/basketball/version.rb
CHANGED