basketball 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|