basketball 0.0.8 → 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/.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
|