basketball 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -0
- data/lib/basketball/app/coordinator_cli.rb +14 -7
- data/lib/basketball/app/coordinator_repository.rb +3 -80
- data/lib/basketball/app/document_repository.rb +67 -0
- data/lib/basketball/app/file_store.rb +16 -0
- data/lib/basketball/app/in_memory_store.rb +42 -0
- data/lib/basketball/app/league_repository.rb +20 -0
- data/lib/basketball/app/league_serializable.rb +54 -0
- data/lib/basketball/app/room_cli.rb +26 -9
- data/lib/basketball/app/room_repository.rb +1 -41
- data/lib/basketball/app.rb +10 -2
- data/lib/basketball/draft/room.rb +4 -4
- data/lib/basketball/entity.rb +9 -6
- data/lib/basketball/org/league.rb +3 -8
- data/lib/basketball/season/arena.rb +21 -20
- data/lib/basketball/season/coordinator.rb +3 -4
- data/lib/basketball/value_object.rb +6 -28
- data/lib/basketball/value_object_dsl.rb +30 -0
- data/lib/basketball/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dca22b3ec7251a012db2afed77f3bda87bd8b59bb59e6084fff80096efcb802a
|
4
|
+
data.tar.gz: 55973e68a7aece1571d926f21e5866e045322e1009b9a5139a959d5059e1b150
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af6b695d1ad8bd581822ffe6fe61421c5839a51e880f0d143060656a8e916e11ff8ae4ae5293e9babb2d8f45646b643d30b7753c03141f8d0c321b4010eca14d
|
7
|
+
data.tar.gz: d0addd9247f2a3b945874d628eec04e44cb826db2578d493bd99cc242beb8e78041084bf3fbffa53b6a00f2a2b788abb8540894e1caf9db5b0031a01357d5bc1
|
data/README.md
CHANGED
@@ -20,9 +20,11 @@ Element | Description
|
|
20
20
|
**Draft** | Bounded context (sub-module) dealing with running a round-robin player draft for teams.
|
21
21
|
**Exhibition** | Pre-season game which has no impact to team record.
|
22
22
|
**External Dependency** | Some outside system which this library or portions of this library are dependent on.
|
23
|
+
**File Store** | Implements a store that can interact with the underlying File System.
|
23
24
|
**File System** | Local operating system that the CLI will use for persistence.
|
24
25
|
**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.
|
25
26
|
**Game** | Matches up a date with two teams (home and away) to represent a coordinatord match-up.
|
27
|
+
**League Repository** | Understands how to save and load League objects from JSON files on disk.
|
26
28
|
**League** | Describes a league in terms of structure composed of teams and players.
|
27
29
|
**Match** | When the Coordinator needs an Arena instance to select a game winner, it will send the Arena a Match. A match is Game but also includes the active roster (players) for both teams that will participate in the game.
|
28
30
|
**Org** | Bounded context (sub-module) dealing with overall organizational structure of a sports assocation.
|
@@ -36,6 +38,7 @@ Element | Description
|
|
36
38
|
**Scout** | Knows how to stack rank lists of players.
|
37
39
|
**Season** | Bounded context (sub-module) dealing with calendar and matchup generation.
|
38
40
|
**Skip** | Result event emitted when a front office decides to skip a round.
|
41
|
+
**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.
|
39
42
|
**Team Group** | Set of rosters that together form a cohesive league.
|
40
43
|
**Team** | Member of a league and signs players. Has games assigned and played.
|
41
44
|
|
@@ -11,12 +11,19 @@ module Basketball
|
|
11
11
|
#
|
12
12
|
# exe/basketball-coordinator -o tmp/coordinator.json -ae
|
13
13
|
class CoordinatorCLI
|
14
|
-
attr_reader :opts, :io, :
|
14
|
+
attr_reader :opts, :io, :coordinator_repository
|
15
15
|
|
16
|
-
def initialize(
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize(
|
17
|
+
args:,
|
18
|
+
io: $stdout,
|
19
|
+
coordinator_repository: CoordinatorRepository.new(FileStore.new)
|
20
|
+
)
|
21
|
+
raise ArgumentError, 'coordinator_repository is required' unless coordinator_repository
|
22
|
+
raise ArgumentError, 'io is required' unless io
|
23
|
+
|
24
|
+
@io = io
|
25
|
+
@opts = slop_parse(args)
|
26
|
+
@coordinator_repository = coordinator_repository
|
20
27
|
|
21
28
|
if no_input? && no_output?
|
22
29
|
io.puts('Input and/or output paths are required.')
|
@@ -92,7 +99,7 @@ module Basketball
|
|
92
99
|
def write(coordinator)
|
93
100
|
path = output? ? output : input
|
94
101
|
|
95
|
-
|
102
|
+
coordinator_repository.save(path, coordinator)
|
96
103
|
|
97
104
|
path
|
98
105
|
end
|
@@ -173,7 +180,7 @@ module Basketball
|
|
173
180
|
if input?
|
174
181
|
io.puts("Coordinator loaded from: #{input}")
|
175
182
|
|
176
|
-
|
183
|
+
coordinator_repository.load(input)
|
177
184
|
else
|
178
185
|
io.puts('Input path was not provided, generating fresh coordinator')
|
179
186
|
|
@@ -3,7 +3,9 @@
|
|
3
3
|
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
|
-
class CoordinatorRepository
|
6
|
+
class CoordinatorRepository < DocumentRepository
|
7
|
+
include LeagueSerializable
|
8
|
+
|
7
9
|
GAME_CLASSES = {
|
8
10
|
'Exhibition' => Season::Exhibition,
|
9
11
|
'Regular' => Season::Regular
|
@@ -11,46 +13,8 @@ module Basketball
|
|
11
13
|
|
12
14
|
private_constant :GAME_CLASSES
|
13
15
|
|
14
|
-
attr_reader :store
|
15
|
-
|
16
|
-
def initialize(store: FileStore.new)
|
17
|
-
super()
|
18
|
-
|
19
|
-
@store = store
|
20
|
-
|
21
|
-
freeze
|
22
|
-
end
|
23
|
-
|
24
|
-
def load(path)
|
25
|
-
contents = store.read(path)
|
26
|
-
|
27
|
-
deserialize(contents).tap do |coordinator|
|
28
|
-
coordinator.send('id=', path)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def save(path, coordinator)
|
33
|
-
contents = serialize(coordinator)
|
34
|
-
|
35
|
-
store.write(path, contents)
|
36
|
-
|
37
|
-
coordinator.send('id=', path)
|
38
|
-
|
39
|
-
coordinator
|
40
|
-
end
|
41
|
-
|
42
16
|
private
|
43
17
|
|
44
|
-
def deserialize(string)
|
45
|
-
hash = JSON.parse(string, symbolize_names: true)
|
46
|
-
|
47
|
-
from_h(hash)
|
48
|
-
end
|
49
|
-
|
50
|
-
def serialize(object)
|
51
|
-
to_h(object).to_json
|
52
|
-
end
|
53
|
-
|
54
18
|
def from_h(hash)
|
55
19
|
Season::Coordinator.new(
|
56
20
|
calendar: deserialize_calendar(hash[:calendar]),
|
@@ -71,14 +35,6 @@ module Basketball
|
|
71
35
|
|
72
36
|
# Serialization
|
73
37
|
|
74
|
-
def serialize_player(player)
|
75
|
-
{
|
76
|
-
id: player.id,
|
77
|
-
overall: player.overall,
|
78
|
-
position: player.position&.code
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
38
|
def serialize_games(games)
|
83
39
|
games.map { |game| serialize_game(game) }
|
84
40
|
end
|
@@ -110,19 +66,6 @@ module Basketball
|
|
110
66
|
}
|
111
67
|
end
|
112
68
|
|
113
|
-
def serialize_league(league)
|
114
|
-
{
|
115
|
-
teams: league.teams.map { |team| serialize_team(team) }
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
|
-
def serialize_team(team)
|
120
|
-
{
|
121
|
-
id: team.id,
|
122
|
-
players: team.players.map { |player| serialize_player(player) }
|
123
|
-
}
|
124
|
-
end
|
125
|
-
|
126
69
|
def serialize_results(results)
|
127
70
|
results.map do |result|
|
128
71
|
serialize_result(result)
|
@@ -131,26 +74,6 @@ module Basketball
|
|
131
74
|
|
132
75
|
# Deserialization
|
133
76
|
|
134
|
-
def deserialize_player(player_hash)
|
135
|
-
Org::Player.new(
|
136
|
-
id: player_hash[:id],
|
137
|
-
overall: player_hash[:overall],
|
138
|
-
position: Org::Position.new(player_hash[:position])
|
139
|
-
)
|
140
|
-
end
|
141
|
-
|
142
|
-
def deserialize_league(league_hash)
|
143
|
-
team_hashes = league_hash[:teams] || []
|
144
|
-
|
145
|
-
teams = team_hashes.map do |team_hash|
|
146
|
-
players = (team_hash[:players] || []).map { |player_hash| deserialize_player(player_hash) }
|
147
|
-
|
148
|
-
Org::Team.new(id: team_hash[:id], players:)
|
149
|
-
end
|
150
|
-
|
151
|
-
Org::League.new(teams:)
|
152
|
-
end
|
153
|
-
|
154
77
|
def deserialize_calendar(calendar_hash)
|
155
78
|
Season::Calendar.new(
|
156
79
|
preseason_start_date: Date.parse(calendar_hash[:preseason_start_date]),
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Base class for all repositories which are based on a flat document.
|
6
|
+
# At the very minimum sub-classes should implement #to_h(object) and #from_h(hash).
|
7
|
+
class DocumentRepository
|
8
|
+
attr_reader :store
|
9
|
+
|
10
|
+
def initialize(store = InMemoryStore.new)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@store = store
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(id)
|
17
|
+
contents = store.read(id)
|
18
|
+
|
19
|
+
deserialize(contents).tap do |object|
|
20
|
+
object.send('id=', id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def save(id, object)
|
25
|
+
object.send('id=', id)
|
26
|
+
|
27
|
+
contents = serialize(object)
|
28
|
+
|
29
|
+
store.write(id, contents)
|
30
|
+
|
31
|
+
object
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(object)
|
35
|
+
return false unless object.id
|
36
|
+
|
37
|
+
store.delete(object.id)
|
38
|
+
|
39
|
+
object.send('id=', nil)
|
40
|
+
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def from_h(hash)
|
47
|
+
Entity.new(hash[:id])
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_h(entity)
|
51
|
+
{ id: entity.id }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def deserialize(string)
|
57
|
+
hash = JSON.parse(string, symbolize_names: true)
|
58
|
+
|
59
|
+
from_h(hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
def serialize(object)
|
63
|
+
to_h(object).to_json
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -4,7 +4,15 @@ module Basketball
|
|
4
4
|
module App
|
5
5
|
# Knows how to read and write documents to disk.
|
6
6
|
class FileStore
|
7
|
+
class PathNotFoundError < StandardError; end
|
8
|
+
|
9
|
+
def exist?(path)
|
10
|
+
File.exist?(path)
|
11
|
+
end
|
12
|
+
|
7
13
|
def read(path)
|
14
|
+
raise PathNotFoundError, "'#{path}' not found" unless exist?(path)
|
15
|
+
|
8
16
|
File.read(path)
|
9
17
|
end
|
10
18
|
|
@@ -17,6 +25,14 @@ module Basketball
|
|
17
25
|
|
18
26
|
nil
|
19
27
|
end
|
28
|
+
|
29
|
+
def delete(path)
|
30
|
+
raise PathNotFoundError, "'#{path}' not found" unless exist?(path)
|
31
|
+
|
32
|
+
File.delete(path)
|
33
|
+
|
34
|
+
nil
|
35
|
+
end
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Knows how to read and write documents to a Ruby Hash.
|
6
|
+
class InMemoryStore
|
7
|
+
class KeyNotFoundError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def initialize(data = {})
|
12
|
+
@data = data
|
13
|
+
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def exist?(key)
|
18
|
+
data.key?(key.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def read(key)
|
22
|
+
raise KeyNotFoundError, "'#{key}' not found" unless exist?(key)
|
23
|
+
|
24
|
+
data[key.to_s]
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(key, contents)
|
28
|
+
data[key.to_s] = contents
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(key)
|
34
|
+
raise KeyNotFoundError, "'#{key}' not found" unless exist?(key)
|
35
|
+
|
36
|
+
data.delete(key)
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Knows how to flatten a League instance and rehydrate one from JSON and/or a Ruby hash.
|
6
|
+
class LeagueRepository < DocumentRepository
|
7
|
+
include LeagueSerializable
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def from_h(hash)
|
12
|
+
deserialize_league(hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h(league)
|
16
|
+
serialize_league(league)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Provides methods to serialize/deserialize a League object
|
6
|
+
module LeagueSerializable
|
7
|
+
# Serialization
|
8
|
+
|
9
|
+
def serialize_league(league)
|
10
|
+
{
|
11
|
+
teams: league.teams.map { |team| serialize_team(team) }
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize_team(team)
|
16
|
+
{
|
17
|
+
id: team.id,
|
18
|
+
players: team.players.map { |player| serialize_player(player) }
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize_player(player)
|
23
|
+
{
|
24
|
+
id: player.id,
|
25
|
+
overall: player.overall,
|
26
|
+
position: player.position&.code
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Deserialization
|
31
|
+
|
32
|
+
def deserialize_league(league_hash)
|
33
|
+
team_hashes = league_hash[:teams] || []
|
34
|
+
teams = team_hashes.map { |team_hash| deserialize_team(team_hash) }
|
35
|
+
|
36
|
+
Org::League.new(teams:)
|
37
|
+
end
|
38
|
+
|
39
|
+
def deserialize_team(team_hash)
|
40
|
+
players = (team_hash[:players] || []).map { |player_hash| deserialize_player(player_hash) }
|
41
|
+
|
42
|
+
Org::Team.new(id: team_hash[:id], players:)
|
43
|
+
end
|
44
|
+
|
45
|
+
def deserialize_player(player_hash)
|
46
|
+
Org::Player.new(
|
47
|
+
id: player_hash[:id],
|
48
|
+
overall: player_hash[:overall],
|
49
|
+
position: Org::Position.new(player_hash[:position])
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -10,16 +10,25 @@ module Basketball
|
|
10
10
|
# exe/basketball-room -i tmp/draft-wip.json -s 30 -t 10
|
11
11
|
# exe/basketball-room -i tmp/draft-wip.json -ale
|
12
12
|
#
|
13
|
-
# exe/basketball-room -o tmp/draft-wip.json -ale
|
13
|
+
# exe/basketball-room -o tmp/draft-wip.json -ale -r tmp/draft-league.json
|
14
14
|
class RoomCLI
|
15
15
|
class PlayerNotFound < StandardError; end
|
16
16
|
|
17
|
-
attr_reader :opts,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
attr_reader :opts,
|
18
|
+
:io,
|
19
|
+
:room_repository,
|
20
|
+
:league_repository
|
21
|
+
|
22
|
+
def initialize(
|
23
|
+
args:,
|
24
|
+
io: $stdout,
|
25
|
+
room_repository: RoomRepository.new(FileStore.new),
|
26
|
+
league_repository: LeagueRepository.new(FileStore.new)
|
27
|
+
)
|
28
|
+
@io = io
|
29
|
+
@opts = slop_parse(args)
|
30
|
+
@room_repository = room_repository
|
31
|
+
@league_repository = league_repository
|
23
32
|
|
24
33
|
if opts[:input].to_s.empty? && opts[:output].to_s.empty?
|
25
34
|
io.puts('Input and/or output paths are required.')
|
@@ -39,12 +48,19 @@ module Basketball
|
|
39
48
|
events(room)
|
40
49
|
league(room)
|
41
50
|
query(room)
|
51
|
+
rosters(room)
|
42
52
|
|
43
53
|
self
|
44
54
|
end
|
45
55
|
|
46
56
|
private
|
47
57
|
|
58
|
+
def rosters(room)
|
59
|
+
return if opts[:rosters].to_s.empty?
|
60
|
+
|
61
|
+
league_repository.save(opts[:rosters], room.league)
|
62
|
+
end
|
63
|
+
|
48
64
|
def status(room)
|
49
65
|
io.puts
|
50
66
|
io.puts('Status')
|
@@ -74,6 +90,7 @@ module Basketball
|
|
74
90
|
o.bool '-l', '--league', 'Output all teams and their picks', default: false
|
75
91
|
o.integer '-x', '--skip', 'Number of picks to skip (default is 0).', default: 0
|
76
92
|
o.bool '-e', '--events', 'Output event log.', default: false
|
93
|
+
o.string '-r', '--rosters', 'Path to write the resulting rosters (league) to.'
|
77
94
|
|
78
95
|
o.on '-h', '--help', 'Print out help, like this is doing right now.' do
|
79
96
|
io.puts(o)
|
@@ -141,7 +158,7 @@ module Basketball
|
|
141
158
|
end
|
142
159
|
|
143
160
|
def read
|
144
|
-
|
161
|
+
room_repository.load(opts[:input])
|
145
162
|
end
|
146
163
|
|
147
164
|
# rubocop:disable Metrics/AbcSize
|
@@ -200,7 +217,7 @@ module Basketball
|
|
200
217
|
def write(room)
|
201
218
|
output = output_default_to_input
|
202
219
|
|
203
|
-
|
220
|
+
room_repository.save(output, room)
|
204
221
|
|
205
222
|
io.puts
|
206
223
|
io.puts("Draft written to: #{output}")
|
@@ -3,54 +3,14 @@
|
|
3
3
|
module Basketball
|
4
4
|
module App
|
5
5
|
# Can load and save Room objects to JSON files.
|
6
|
-
class RoomRepository
|
6
|
+
class RoomRepository < DocumentRepository
|
7
7
|
PICK_EVENT = 'Pick'
|
8
8
|
SKIP_EVENT = 'Skip'
|
9
9
|
|
10
10
|
private_constant :PICK_EVENT, :SKIP_EVENT
|
11
11
|
|
12
|
-
attr_reader :store
|
13
|
-
|
14
|
-
def initialize(store: FileStore.new)
|
15
|
-
super()
|
16
|
-
|
17
|
-
@store = store
|
18
|
-
|
19
|
-
freeze
|
20
|
-
end
|
21
|
-
|
22
|
-
def load(path)
|
23
|
-
contents = store.read(path)
|
24
|
-
|
25
|
-
room = deserialize(contents)
|
26
|
-
|
27
|
-
room.send('id=', path)
|
28
|
-
|
29
|
-
room
|
30
|
-
end
|
31
|
-
|
32
|
-
def save(path, room)
|
33
|
-
contents = serialize(room)
|
34
|
-
|
35
|
-
store.write(path, contents)
|
36
|
-
|
37
|
-
room.send('id=', path)
|
38
|
-
|
39
|
-
room
|
40
|
-
end
|
41
|
-
|
42
12
|
private
|
43
13
|
|
44
|
-
def deserialize(string)
|
45
|
-
hash = JSON.parse(string, symbolize_names: true)
|
46
|
-
|
47
|
-
from_h(hash)
|
48
|
-
end
|
49
|
-
|
50
|
-
def serialize(object)
|
51
|
-
to_h(object).to_json
|
52
|
-
end
|
53
|
-
|
54
14
|
def from_h(hash)
|
55
15
|
front_offices = deserialize_front_offices(hash[:front_offices])
|
56
16
|
players = deserialize_players(hash[:players])
|
data/lib/basketball/app.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Stores
|
4
4
|
require_relative 'app/file_store'
|
5
|
+
require_relative 'app/in_memory_store'
|
5
6
|
|
6
|
-
#
|
7
|
+
# Serialization
|
8
|
+
require_relative 'app/league_serializable'
|
9
|
+
|
10
|
+
# Repositories / Common
|
11
|
+
require_relative 'app/document_repository'
|
12
|
+
|
13
|
+
# Repositories / Implementations
|
7
14
|
require_relative 'app/coordinator_repository'
|
15
|
+
require_relative 'app/league_repository'
|
8
16
|
require_relative 'app/room_repository'
|
9
17
|
|
10
18
|
# Controllers
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Draft
|
5
5
|
# Main pick-by-pick iterator object which will round-robin rotate team selections.
|
6
|
-
class Room
|
6
|
+
class Room < Entity
|
7
7
|
class AlreadyPickedError < StandardError; end
|
8
8
|
class EndOfDraftError < StandardError; end
|
9
9
|
class EventOutOfOrderError < StandardError; end
|
@@ -12,9 +12,11 @@ module Basketball
|
|
12
12
|
class UnknownFrontOfficeError < StandardError; end
|
13
13
|
class UnknownPlayerError < StandardError; end
|
14
14
|
|
15
|
-
attr_reader :rounds, :players, :front_offices, :events
|
15
|
+
attr_reader :rounds, :players, :front_offices, :events
|
16
16
|
|
17
17
|
def initialize(front_offices:, rounds:, players: [], events: [])
|
18
|
+
super()
|
19
|
+
|
18
20
|
raise InvalidRoundsError, "#{rounds} should be a positive number" unless rounds.positive?
|
19
21
|
|
20
22
|
@rounds = rounds
|
@@ -157,8 +159,6 @@ module Basketball
|
|
157
159
|
|
158
160
|
private
|
159
161
|
|
160
|
-
attr_writer :id
|
161
|
-
|
162
162
|
def player_events
|
163
163
|
events.select { |e| e.respond_to?(:player) }
|
164
164
|
end
|
data/lib/basketball/entity.rb
CHANGED
@@ -4,16 +4,11 @@ module Basketball
|
|
4
4
|
# Base class for uniquely identifiable classes. Subclasses are simply based on a string-based ID
|
5
5
|
# and comparison/sorting/equality will be done in a case-insensitive manner.
|
6
6
|
class Entity
|
7
|
-
extend Forwardable
|
8
7
|
include Comparable
|
9
8
|
|
10
9
|
attr_reader :id
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(id)
|
15
|
-
raise ArgumentError, 'id is required' if id.to_s.empty?
|
16
|
-
|
11
|
+
def initialize(id = nil)
|
17
12
|
@id = id
|
18
13
|
end
|
19
14
|
|
@@ -30,8 +25,16 @@ module Basketball
|
|
30
25
|
comparable_id.hash
|
31
26
|
end
|
32
27
|
|
28
|
+
def to_s
|
29
|
+
id.to_s
|
30
|
+
end
|
31
|
+
|
33
32
|
def comparable_id
|
34
33
|
id.to_s.upcase
|
35
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_writer :id
|
36
39
|
end
|
37
40
|
end
|
@@ -6,18 +6,18 @@ module Basketball
|
|
6
6
|
# adding teams and players to ensure the all the teams are cohesive, such as:
|
7
7
|
# - preventing duplicate teams
|
8
8
|
# - preventing double-signing players across teams
|
9
|
-
class League
|
9
|
+
class League < Entity
|
10
10
|
class TeamAlreadyRegisteredError < StandardError; end
|
11
11
|
class UnregisteredTeamError < StandardError; end
|
12
12
|
|
13
13
|
attr_reader :teams
|
14
14
|
|
15
15
|
def initialize(teams: [])
|
16
|
+
super()
|
17
|
+
|
16
18
|
@teams = []
|
17
19
|
|
18
20
|
teams.each { |team| register!(team) }
|
19
|
-
|
20
|
-
freeze
|
21
21
|
end
|
22
22
|
|
23
23
|
def to_s
|
@@ -63,11 +63,6 @@ module Basketball
|
|
63
63
|
|
64
64
|
self
|
65
65
|
end
|
66
|
-
|
67
|
-
def ==(other)
|
68
|
-
teams == other.teams
|
69
|
-
end
|
70
|
-
alias eql? ==
|
71
66
|
end
|
72
67
|
end
|
73
68
|
end
|
@@ -4,14 +4,14 @@ module Basketball
|
|
4
4
|
module Season
|
5
5
|
# A very, very, very basic starting point for a "semi-randomized" game simulator.
|
6
6
|
class Arena
|
7
|
-
RANDOM
|
8
|
-
TOP_ONE
|
9
|
-
TOP_TWO
|
10
|
-
TOP_THREE
|
11
|
-
TOP_SIX
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
RANDOM = :random
|
8
|
+
TOP_ONE = :top_one
|
9
|
+
TOP_TWO = :top_two
|
10
|
+
TOP_THREE = :top_three
|
11
|
+
TOP_SIX = :top_six
|
12
|
+
DEFAULT_MAX_HOME_ADVANTAGE = 5
|
13
|
+
|
14
|
+
DEFAULT_STRATEGY_FREQUENCIES = {
|
15
15
|
RANDOM => 10,
|
16
16
|
TOP_ONE => 5,
|
17
17
|
TOP_TWO => 10,
|
@@ -19,17 +19,14 @@ module Basketball
|
|
19
19
|
TOP_SIX => 30
|
20
20
|
}.freeze
|
21
21
|
|
22
|
-
|
23
|
-
:RANDOM,
|
24
|
-
:TOP_ONE,
|
25
|
-
:TOP_TWO,
|
26
|
-
:TOP_SIX,
|
27
|
-
:MAX_HOME_ADVANTAGE
|
22
|
+
attr_reader :lotto, :max_home_advantage
|
28
23
|
|
29
|
-
def initialize
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
def initialize(
|
25
|
+
strategy_frquencies: DEFAULT_STRATEGY_FREQUENCIES,
|
26
|
+
max_home_advantage: DEFAULT_MAX_HOME_ADVANTAGE
|
27
|
+
)
|
28
|
+
@max_home_advantage = max_home_advantage
|
29
|
+
@lotto = make_lotto(strategy_frquencies)
|
33
30
|
|
34
31
|
freeze
|
35
32
|
end
|
@@ -57,7 +54,11 @@ module Basketball
|
|
57
54
|
|
58
55
|
private
|
59
56
|
|
60
|
-
|
57
|
+
def make_lotto(strategy_frquencies)
|
58
|
+
strategy_frquencies.inject([]) do |memo, (name, frequency)|
|
59
|
+
memo + ([name] * frequency)
|
60
|
+
end.shuffle
|
61
|
+
end
|
61
62
|
|
62
63
|
def pick_strategy
|
63
64
|
lotto.sample
|
@@ -89,7 +90,7 @@ module Basketball
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def random_home_advantage
|
92
|
-
rand(0..
|
93
|
+
rand(0..max_home_advantage)
|
93
94
|
end
|
94
95
|
|
95
96
|
def top_one_strategy(matchup)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Season
|
5
5
|
# Main iterator-based object that knows how to manage a calendar and simulate games per day.
|
6
|
-
class Coordinator
|
6
|
+
class Coordinator < Entity
|
7
7
|
extend Forwardable
|
8
8
|
|
9
9
|
class AlreadyPlayedGameError < StandardError; end
|
@@ -18,8 +18,7 @@ module Basketball
|
|
18
18
|
:current_date,
|
19
19
|
:arena,
|
20
20
|
:results,
|
21
|
-
:league
|
22
|
-
:id
|
21
|
+
:league
|
23
22
|
|
24
23
|
def_delegators :calendar,
|
25
24
|
:preseason_start_date,
|
@@ -156,7 +155,7 @@ module Basketball
|
|
156
155
|
|
157
156
|
private
|
158
157
|
|
159
|
-
attr_writer :
|
158
|
+
attr_writer :arena
|
160
159
|
|
161
160
|
def opponent_team(opponent)
|
162
161
|
league.teams.find { |t| t == opponent }
|
@@ -1,37 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'value_object_dsl'
|
4
|
+
|
3
5
|
module Basketball
|
4
6
|
# A Value Object is something that has no specific identity, instead its identity is the sum of
|
5
7
|
# the attribute values. Changing one will change the entire object's identity.
|
6
|
-
# Comes with a very simple DSL for specifying properties along with
|
8
|
+
# Comes with a very simple DSL provided by ValueObjectDSL for specifying properties along with
|
9
|
+
# base equality and sorting methods.
|
7
10
|
class ValueObject
|
8
11
|
include Comparable
|
9
|
-
|
10
|
-
class << self
|
11
|
-
def all_value_keys
|
12
|
-
@all_value_keys ||= ancestors.flat_map do |ancestor|
|
13
|
-
if ancestor < Basketball::ValueObject
|
14
|
-
ancestor.value_keys
|
15
|
-
else
|
16
|
-
[]
|
17
|
-
end
|
18
|
-
end.uniq.sort
|
19
|
-
end
|
20
|
-
|
21
|
-
def all_sorted_value_keys
|
22
|
-
all_value_keys.sort
|
23
|
-
end
|
24
|
-
|
25
|
-
def value_keys
|
26
|
-
@value_keys ||= []
|
27
|
-
end
|
28
|
-
|
29
|
-
def value_reader(*keys)
|
30
|
-
keys.each { |k| value_keys << k.to_sym }
|
31
|
-
|
32
|
-
attr_reader(*keys)
|
33
|
-
end
|
34
|
-
end
|
12
|
+
extend ValueObjectDSL
|
35
13
|
|
36
14
|
def to_s
|
37
15
|
to_h.map { |k, v| "#{k}: #{v}" }.join(', ')
|
@@ -55,7 +33,7 @@ module Basketball
|
|
55
33
|
alias eql? ==
|
56
34
|
|
57
35
|
def hash
|
58
|
-
all_sorted_values.
|
36
|
+
all_sorted_values.hash
|
59
37
|
end
|
60
38
|
|
61
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
|
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.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-
|
11
|
+
date: 2023-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|
@@ -54,7 +54,11 @@ files:
|
|
54
54
|
- lib/basketball/app.rb
|
55
55
|
- lib/basketball/app/coordinator_cli.rb
|
56
56
|
- lib/basketball/app/coordinator_repository.rb
|
57
|
+
- lib/basketball/app/document_repository.rb
|
57
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
|
58
62
|
- lib/basketball/app/room_cli.rb
|
59
63
|
- lib/basketball/app/room_repository.rb
|
60
64
|
- lib/basketball/draft.rb
|
@@ -82,6 +86,7 @@ files:
|
|
82
86
|
- lib/basketball/season/regular.rb
|
83
87
|
- lib/basketball/season/result.rb
|
84
88
|
- lib/basketball/value_object.rb
|
89
|
+
- lib/basketball/value_object_dsl.rb
|
85
90
|
- lib/basketball/version.rb
|
86
91
|
homepage: https://github.com/mattruggio/basketball
|
87
92
|
licenses:
|