basketball 0.0.8 → 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/.rubocop.yml +9 -19
- data/CHANGELOG.md +1 -39
- data/README.md +75 -93
- data/basketball.gemspec +3 -6
- data/exe/{basketball-season-scheduling → basketball-coordinator} +1 -1
- data/exe/{basketball-draft → basketball-room} +1 -1
- data/lib/basketball/app/coordinator_cli.rb +250 -0
- data/lib/basketball/app/coordinator_repository.rb +114 -0
- data/lib/basketball/app/document_repository.rb +67 -0
- data/lib/basketball/app/file_store.rb +38 -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/{draft/cli.rb → app/room_cli.rb} +74 -80
- data/lib/basketball/app/room_repository.rb +149 -0
- data/lib/basketball/app.rb +20 -0
- data/lib/basketball/draft/assessment.rb +31 -0
- data/lib/basketball/draft/event.rb +3 -2
- data/lib/basketball/draft/front_office.rb +35 -28
- data/lib/basketball/draft/{pick_event.rb → pick.rb} +13 -6
- data/lib/basketball/draft/room.rb +119 -119
- data/lib/basketball/draft/{player_search.rb → scout.rb} +4 -9
- data/lib/basketball/draft/skip.rb +12 -0
- data/lib/basketball/draft.rb +13 -6
- data/lib/basketball/entity.rb +19 -10
- data/lib/basketball/org/league.rb +68 -0
- data/lib/basketball/org/player.rb +26 -0
- data/lib/basketball/{draft → org}/position.rb +3 -2
- data/lib/basketball/org/team.rb +38 -0
- data/lib/basketball/org.rb +12 -0
- data/lib/basketball/season/arena.rb +113 -0
- data/lib/basketball/season/calendar.rb +41 -72
- data/lib/basketball/season/coordinator.rb +186 -128
- data/lib/basketball/season/{preseason_game.rb → exhibition.rb} +2 -1
- data/lib/basketball/season/game.rb +15 -10
- data/lib/basketball/season/matchup.rb +27 -0
- data/lib/basketball/season/opponent.rb +15 -0
- data/lib/basketball/season/{season_game.rb → regular.rb} +2 -1
- data/lib/basketball/season/result.rb +37 -0
- data/lib/basketball/season.rb +12 -13
- data/lib/basketball/value_object.rb +8 -27
- data/lib/basketball/value_object_dsl.rb +30 -0
- data/lib/basketball/version.rb +1 -1
- data/lib/basketball.rb +9 -4
- metadata +37 -44
- data/lib/basketball/draft/league.rb +0 -70
- data/lib/basketball/draft/player.rb +0 -43
- data/lib/basketball/draft/room_serializer.rb +0 -186
- data/lib/basketball/draft/roster.rb +0 -37
- data/lib/basketball/draft/sim_event.rb +0 -23
- data/lib/basketball/draft/skip_event.rb +0 -13
- data/lib/basketball/season/calendar_serializer.rb +0 -94
- data/lib/basketball/season/conference.rb +0 -57
- data/lib/basketball/season/division.rb +0 -43
- data/lib/basketball/season/league.rb +0 -114
- data/lib/basketball/season/league_serializer.rb +0 -99
- data/lib/basketball/season/scheduling_cli.rb +0 -198
- data/lib/basketball/season/team.rb +0 -21
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Can load and save Room objects to JSON files.
|
6
|
+
class RoomRepository < DocumentRepository
|
7
|
+
PICK_EVENT = 'Pick'
|
8
|
+
SKIP_EVENT = 'Skip'
|
9
|
+
|
10
|
+
private_constant :PICK_EVENT, :SKIP_EVENT
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def from_h(hash)
|
15
|
+
front_offices = deserialize_front_offices(hash[:front_offices])
|
16
|
+
players = deserialize_players(hash[:players])
|
17
|
+
events = deserialize_events(hash[:events], players:, front_offices:)
|
18
|
+
|
19
|
+
Draft::Room.new(
|
20
|
+
rounds: hash[:rounds],
|
21
|
+
front_offices:,
|
22
|
+
players:,
|
23
|
+
events:
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h(room)
|
28
|
+
{
|
29
|
+
rounds: room.rounds,
|
30
|
+
front_offices: room.front_offices.map { |fo| serialize_front_office(fo) },
|
31
|
+
players: room.players.map { |p| serialize_player(p) },
|
32
|
+
events: serialize_events(room.events)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Serialization
|
37
|
+
|
38
|
+
def serialize_player(player)
|
39
|
+
{
|
40
|
+
id: player.id,
|
41
|
+
overall: player.overall,
|
42
|
+
position: player.position&.code
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def serialize_front_office(front_office)
|
47
|
+
{
|
48
|
+
id: front_office.id,
|
49
|
+
risk_level: front_office.risk_level
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def serialize_events(events)
|
54
|
+
events.map do |event|
|
55
|
+
case event
|
56
|
+
when Draft::Pick
|
57
|
+
serialize_pick(event)
|
58
|
+
when Draft::Skip
|
59
|
+
serialize_skip(event)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def serialize_pick(event)
|
65
|
+
{
|
66
|
+
type: PICK_EVENT,
|
67
|
+
front_office: event.front_office.id,
|
68
|
+
pick: event.pick,
|
69
|
+
round: event.round,
|
70
|
+
round_pick: event.round_pick,
|
71
|
+
auto: event.auto,
|
72
|
+
player: event.player.id
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def serialize_skip(event)
|
77
|
+
{
|
78
|
+
type: SKIP_EVENT,
|
79
|
+
front_office: event.front_office.id,
|
80
|
+
pick: event.pick,
|
81
|
+
round: event.round,
|
82
|
+
round_pick: event.round_pick
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Deserialization
|
87
|
+
|
88
|
+
def deserialize_player(player_hash)
|
89
|
+
Org::Player.new(
|
90
|
+
id: player_hash[:id],
|
91
|
+
overall: player_hash[:overall],
|
92
|
+
position: Org::Position.new(player_hash[:position])
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def deserialize_front_office(hash)
|
97
|
+
Draft::FrontOffice.new(
|
98
|
+
id: hash[:id],
|
99
|
+
risk_level: hash[:risk_level]
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def deserialize_front_offices(hashes)
|
104
|
+
(hashes || []).map { |fo| deserialize_front_office(fo) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def deserialize_players(hashes)
|
108
|
+
(hashes || []).map { |hash| deserialize_player(hash) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def deserialize_pick(hash, players:, front_office:)
|
112
|
+
player_id = hash[:player]
|
113
|
+
player = players.find { |p| p.id == player_id }
|
114
|
+
|
115
|
+
Draft::Pick.new(
|
116
|
+
front_office:,
|
117
|
+
pick: hash[:pick],
|
118
|
+
round: hash[:round],
|
119
|
+
round_pick: hash[:round_pick],
|
120
|
+
player:,
|
121
|
+
auto: hash[:auto]
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
def deserialize_skip(hash, front_office:)
|
126
|
+
Draft::Skip.new(
|
127
|
+
front_office:,
|
128
|
+
pick: hash[:pick],
|
129
|
+
round: hash[:round],
|
130
|
+
round_pick: hash[:round_pick]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def deserialize_events(hashes, players:, front_offices:)
|
135
|
+
(hashes || []).map do |hash|
|
136
|
+
front_office_id = hash[:front_office]
|
137
|
+
front_office = front_offices.find { |fo| fo.id == front_office_id }
|
138
|
+
|
139
|
+
case hash[:type]
|
140
|
+
when PICK_EVENT
|
141
|
+
deserialize_pick(hash, players:, front_office:)
|
142
|
+
when SKIP_EVENT
|
143
|
+
deserialize_skip(hash, front_office:)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Stores
|
4
|
+
require_relative 'app/file_store'
|
5
|
+
require_relative 'app/in_memory_store'
|
6
|
+
|
7
|
+
# Serialization
|
8
|
+
require_relative 'app/league_serializable'
|
9
|
+
|
10
|
+
# Repositories / Common
|
11
|
+
require_relative 'app/document_repository'
|
12
|
+
|
13
|
+
# Repositories / Implementations
|
14
|
+
require_relative 'app/coordinator_repository'
|
15
|
+
require_relative 'app/league_repository'
|
16
|
+
require_relative 'app/room_repository'
|
17
|
+
|
18
|
+
# Controllers
|
19
|
+
require_relative 'app/coordinator_cli'
|
20
|
+
require_relative 'app/room_cli'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Draft
|
5
|
+
# An assessment is given to a front office when it needs the front office to make a pick.
|
6
|
+
# This is essentially just a data-transfer object to get information to a front office to make a pick.
|
7
|
+
class Assessment
|
8
|
+
attr_reader :drafted_players,
|
9
|
+
:pick,
|
10
|
+
:round_pick,
|
11
|
+
:round,
|
12
|
+
:undrafted_players
|
13
|
+
|
14
|
+
def initialize(
|
15
|
+
drafted_players:,
|
16
|
+
pick:,
|
17
|
+
round_pick:,
|
18
|
+
round:,
|
19
|
+
undrafted_players:
|
20
|
+
)
|
21
|
+
@drafted_players = drafted_players
|
22
|
+
@pick = pick
|
23
|
+
@round = round
|
24
|
+
@round_pick = round_pick
|
25
|
+
@undrafted_players = undrafted_players
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Draft
|
5
|
+
# Describes what all Room events have to have to be considered an "event".
|
5
6
|
class Event < ValueObject
|
6
|
-
|
7
|
+
value_reader :pick, :round, :round_pick, :front_office
|
7
8
|
|
8
9
|
def initialize(front_office:, pick:, round:, round_pick:)
|
9
10
|
super()
|
@@ -17,7 +18,7 @@ module Basketball
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def to_s
|
20
|
-
"##{pick}
|
21
|
+
"[##{pick} R:#{round} P:#{round_pick}] #{front_office}"
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -2,22 +2,34 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Draft
|
5
|
+
# A team will send their front office to a draft room to make draft selections.
|
5
6
|
class FrontOffice < Entity
|
6
|
-
|
7
|
-
|
7
|
+
# The higher the number the more the front office will make more top-player-based decisions
|
8
|
+
# over position-based decisions.
|
9
|
+
DEFAULT_MAX_STAR_LEVEL = 5
|
10
|
+
|
11
|
+
# Riskier front offices may choose to not draft the top player. The higher the number the more
|
12
|
+
# they will not select the top player available.
|
13
|
+
DEFAULT_MAX_RISK_LEVEL = 5
|
14
|
+
|
8
15
|
MAX_POSITIONS = 12
|
9
16
|
|
10
|
-
private_constant :
|
17
|
+
private_constant :DEFAULT_MAX_STAR_LEVEL, :DEFAULT_MAX_RISK_LEVEL, :MAX_POSITIONS
|
11
18
|
|
12
|
-
attr_reader :prioritized_positions, :
|
19
|
+
attr_reader :prioritized_positions, :risk_level, :star_level, :scout
|
13
20
|
|
14
|
-
def initialize(
|
21
|
+
def initialize(
|
22
|
+
id:,
|
23
|
+
prioritized_positions: [],
|
24
|
+
risk_level: rand(0..DEFAULT_MAX_RISK_LEVEL),
|
25
|
+
star_level: rand(0..DEFAULT_MAX_STAR_LEVEL)
|
26
|
+
)
|
15
27
|
super(id)
|
16
28
|
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@depth = depth.to_i
|
29
|
+
@risk_level = risk_level.to_i
|
30
|
+
@star_level = star_level.to_i
|
20
31
|
@prioritized_positions = prioritized_positions
|
32
|
+
@scout = Scout.new
|
21
33
|
|
22
34
|
# fill in the rest of the queue here
|
23
35
|
need_count = MAX_POSITIONS - @prioritized_positions.length
|
@@ -27,51 +39,46 @@ module Basketball
|
|
27
39
|
freeze
|
28
40
|
end
|
29
41
|
|
30
|
-
def pick(
|
42
|
+
def pick(assessment)
|
31
43
|
players = []
|
44
|
+
players = adaptive_search(assessment) if star_level >= assessment.round
|
45
|
+
players = balanced_search(assessment) if players.empty?
|
46
|
+
players = top_players(assessment) if players.empty?
|
32
47
|
|
33
|
-
players
|
34
|
-
players = balanced_search(undrafted_player_search:, drafted_players:) if players.empty?
|
35
|
-
players = top_players(undrafted_player_search:) if players.empty?
|
36
|
-
|
37
|
-
players[0..fuzz].sample
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_s
|
41
|
-
"[#{super}] #{name}"
|
48
|
+
players[0..risk_level].sample
|
42
49
|
end
|
43
50
|
|
44
51
|
private
|
45
52
|
|
46
|
-
def adaptive_search(
|
47
|
-
drafted_positions = drafted_players.map(&:position)
|
53
|
+
def adaptive_search(assessment)
|
54
|
+
drafted_positions = assessment.drafted_players.map(&:position)
|
48
55
|
|
49
|
-
|
56
|
+
scout.top_for(players: assessment.undrafted_players, exclude_positions: drafted_positions)
|
50
57
|
end
|
51
58
|
|
52
|
-
def balanced_search(
|
59
|
+
def balanced_search(assessment)
|
53
60
|
players = []
|
54
61
|
|
55
62
|
# Try to find best pick for exact desired position.
|
56
63
|
# If you cant find one, then move to the next desired position until the end of the queue
|
57
|
-
available_prioritized_positions(drafted_players
|
58
|
-
players =
|
64
|
+
available_prioritized_positions(assessment.drafted_players).each do |position|
|
65
|
+
players = scout.top_for(players: assessment.undrafted_players, position:)
|
59
66
|
|
60
67
|
break if players.any?
|
61
68
|
end
|
62
69
|
|
63
|
-
players = players.any? ? players :
|
70
|
+
players = players.any? ? players : scout.top_for
|
64
71
|
end
|
65
72
|
|
66
73
|
def all_random_positions
|
67
|
-
Position::ALL_VALUES.to_a.shuffle.map { |v| Position.new(v) }
|
74
|
+
Org::Position::ALL_VALUES.to_a.shuffle.map { |v| Org::Position.new(v) }
|
68
75
|
end
|
69
76
|
|
70
77
|
def random_positions_queue
|
71
|
-
all_random_positions + all_random_positions + [Position.random] + [Position.random]
|
78
|
+
all_random_positions + all_random_positions + [Org::Position.random] + [Org::Position.random]
|
72
79
|
end
|
73
80
|
|
74
|
-
def available_prioritized_positions(drafted_players
|
81
|
+
def available_prioritized_positions(drafted_players)
|
75
82
|
drafted_positions = drafted_players.map(&:position)
|
76
83
|
queue = prioritized_positions.dup
|
77
84
|
|
@@ -1,25 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'event'
|
4
|
-
|
5
3
|
module Basketball
|
6
4
|
module Draft
|
7
|
-
|
8
|
-
|
5
|
+
# Room event where a player is selected.
|
6
|
+
class Pick < Event
|
7
|
+
value_reader :player, :auto
|
9
8
|
|
10
|
-
def initialize(front_office:, player:, pick:, round:, round_pick:)
|
9
|
+
def initialize(front_office:, player:, pick:, round:, round_pick:, auto: false)
|
11
10
|
super(front_office:, pick:, round:, round_pick:)
|
12
11
|
|
13
12
|
raise ArgumentError, 'player required' unless player
|
14
13
|
|
15
14
|
@player = player
|
15
|
+
@auto = auto
|
16
16
|
|
17
17
|
freeze
|
18
18
|
end
|
19
19
|
|
20
20
|
def to_s
|
21
|
-
"#{
|
21
|
+
"#{super} #{auto ? 'auto-' : ''}picked #{player}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
super &&
|
26
|
+
player == other.player &&
|
27
|
+
auto == other.auto
|
22
28
|
end
|
29
|
+
alias eql? ==
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|