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,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Season
|
5
|
+
# A very, very, very basic starting point for a "semi-randomized" game simulator.
|
6
|
+
class Arena
|
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
|
+
RANDOM => 10,
|
16
|
+
TOP_ONE => 5,
|
17
|
+
TOP_TWO => 10,
|
18
|
+
TOP_THREE => 20,
|
19
|
+
TOP_SIX => 30
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
attr_reader :lotto, :max_home_advantage
|
23
|
+
|
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)
|
30
|
+
|
31
|
+
freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
def play(matchup)
|
35
|
+
scores = generate_scores
|
36
|
+
winning_score = scores.max
|
37
|
+
losing_score = scores.min
|
38
|
+
strategy = pick_strategy
|
39
|
+
|
40
|
+
if home_wins?(matchup, strategy)
|
41
|
+
Result.new(
|
42
|
+
game: matchup.game,
|
43
|
+
home_score: winning_score,
|
44
|
+
away_score: losing_score
|
45
|
+
)
|
46
|
+
else
|
47
|
+
Result.new(
|
48
|
+
game: matchup.game,
|
49
|
+
home_score: losing_score,
|
50
|
+
away_score: winning_score
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def make_lotto(strategy_frquencies)
|
58
|
+
strategy_frquencies.inject([]) do |memo, (name, frequency)|
|
59
|
+
memo + ([name] * frequency)
|
60
|
+
end.shuffle
|
61
|
+
end
|
62
|
+
|
63
|
+
def pick_strategy
|
64
|
+
lotto.sample
|
65
|
+
end
|
66
|
+
|
67
|
+
def home_wins?(game, strategy)
|
68
|
+
send("#{strategy}_strategy", game)
|
69
|
+
end
|
70
|
+
|
71
|
+
def top_player_sum(players, amount)
|
72
|
+
players.sort_by(&:overall).reverse.take(amount).sum(&:overall)
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_scores
|
76
|
+
scores = [
|
77
|
+
rand(70..120),
|
78
|
+
rand(70..120)
|
79
|
+
]
|
80
|
+
|
81
|
+
# No ties
|
82
|
+
scores[0] += 1 if scores[0] == scores[1]
|
83
|
+
|
84
|
+
scores
|
85
|
+
end
|
86
|
+
|
87
|
+
def random_strategy(_game)
|
88
|
+
# 60% chance home wins
|
89
|
+
(([0] * 6) + ([1] * 4)).sample.zero?
|
90
|
+
end
|
91
|
+
|
92
|
+
def random_home_advantage
|
93
|
+
rand(0..max_home_advantage)
|
94
|
+
end
|
95
|
+
|
96
|
+
def top_one_strategy(matchup)
|
97
|
+
top_player_sum(matchup.home_players, 1) + random_home_advantage >= top_player_sum(matchup.away_players, 1)
|
98
|
+
end
|
99
|
+
|
100
|
+
def top_two_strategy(matchup)
|
101
|
+
top_player_sum(matchup.home_players, 2) + random_home_advantage >= top_player_sum(matchup.away_players, 2)
|
102
|
+
end
|
103
|
+
|
104
|
+
def top_three_strategy(matchup)
|
105
|
+
top_player_sum(matchup.home_players, 3) + random_home_advantage >= top_player_sum(matchup.away_players, 3)
|
106
|
+
end
|
107
|
+
|
108
|
+
def top_six_strategy(matchup)
|
109
|
+
top_player_sum(matchup.home_players, 6) + random_home_advantage >= top_player_sum(matchup.away_players, 6)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -2,28 +2,34 @@
|
|
2
2
|
|
3
3
|
module Basketball
|
4
4
|
module Season
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# Sets boundaries for preseason and regular season play. Add games as long as they are
|
6
|
+
# within the correct dated boundaries
|
7
|
+
class Calendar
|
8
8
|
class OutOfBoundsError < StandardError; end
|
9
|
+
class TeamAlreadyBookedError < StandardError; end
|
9
10
|
|
10
11
|
attr_reader :preseason_start_date,
|
11
12
|
:preseason_end_date,
|
12
13
|
:season_start_date,
|
13
|
-
:season_end_date
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
14
|
+
:season_end_date,
|
15
|
+
:games
|
16
|
+
|
17
|
+
def initialize(
|
18
|
+
preseason_start_date:,
|
19
|
+
preseason_end_date:,
|
20
|
+
season_start_date:,
|
21
|
+
season_end_date:,
|
22
|
+
games: []
|
23
|
+
)
|
24
|
+
raise ArgumentError, 'preseason_start_date is required' if preseason_start_date.to_s.empty?
|
25
|
+
raise ArgumentError, 'preseason_end_date is required' if preseason_end_date.to_s.empty?
|
26
|
+
raise ArgumentError, 'season_start_date is required' if season_start_date.to_s.empty?
|
27
|
+
raise ArgumentError, 'season_end_date is required' if season_end_date.to_s.empty?
|
28
|
+
|
29
|
+
@preseason_start_date = preseason_start_date
|
30
|
+
@preseason_end_date = preseason_end_date
|
31
|
+
@season_start_date = season_start_date
|
32
|
+
@season_end_date = season_end_date
|
27
33
|
@games = []
|
28
34
|
|
29
35
|
games.each { |game| add!(game) }
|
@@ -40,78 +46,41 @@ module Basketball
|
|
40
46
|
self
|
41
47
|
end
|
42
48
|
|
43
|
-
def
|
44
|
-
games_for(date:,
|
49
|
+
def exhibitions_for(date: nil, opponent: nil)
|
50
|
+
games_for(date:, opponent:).select { |game| game.is_a?(Exhibition) }
|
45
51
|
end
|
46
52
|
|
47
|
-
def
|
48
|
-
games_for(date:,
|
53
|
+
def regulars_for(date: nil, opponent: nil)
|
54
|
+
games_for(date:, opponent:).select { |game| game.is_a?(Regular) }
|
49
55
|
end
|
50
56
|
|
51
|
-
def games_for(date: nil,
|
57
|
+
def games_for(date: nil, opponent: nil)
|
52
58
|
games.select do |game|
|
53
|
-
(date.nil? || game.date == date) &&
|
54
|
-
(team.nil? || (game.home_team == team || game.away_team == team))
|
59
|
+
(date.nil? || game.date == date) && (opponent.nil? || game.for?(opponent))
|
55
60
|
end
|
56
61
|
end
|
57
62
|
|
58
|
-
def available_preseason_dates_for(team)
|
59
|
-
all_preseason_dates - preseason_games_for(team:).map(&:date)
|
60
|
-
end
|
61
|
-
|
62
|
-
def available_season_dates_for(team)
|
63
|
-
all_season_dates - season_games_for(team:).map(&:date)
|
64
|
-
end
|
65
|
-
|
66
|
-
def available_preseason_matchup_dates(team1, team2)
|
67
|
-
available_team_dates = available_preseason_dates_for(team1)
|
68
|
-
available_other_team_dates = available_preseason_dates_for(team2)
|
69
|
-
|
70
|
-
available_team_dates & available_other_team_dates
|
71
|
-
end
|
72
|
-
|
73
|
-
def available_season_matchup_dates(team1, team2)
|
74
|
-
available_team_dates = available_season_dates_for(team1)
|
75
|
-
available_other_team_dates = available_season_dates_for(team2)
|
76
|
-
|
77
|
-
available_team_dates & available_other_team_dates
|
78
|
-
end
|
79
|
-
|
80
|
-
def teams
|
81
|
-
games.flat_map(&:teams)
|
82
|
-
end
|
83
|
-
|
84
|
-
def team(id)
|
85
|
-
teams.find { |t| t == Team.new(id:) }
|
86
|
-
end
|
87
|
-
|
88
63
|
private
|
89
64
|
|
90
|
-
def all_preseason_dates
|
91
|
-
(preseason_start_date..preseason_end_date).to_a
|
92
|
-
end
|
93
|
-
|
94
|
-
def all_season_dates
|
95
|
-
(season_start_date..season_end_date).to_a
|
96
|
-
end
|
97
|
-
|
98
65
|
def assert_free_date(game)
|
99
|
-
if games_for(date: game.date,
|
100
|
-
raise TeamAlreadyBookedError, "#{game.
|
66
|
+
if games_for(date: game.date, opponent: game.home_opponent).any?
|
67
|
+
raise TeamAlreadyBookedError, "#{game.home_opponent} already playing on #{game.date}"
|
101
68
|
end
|
102
69
|
|
103
|
-
return unless games_for(date: game.date,
|
70
|
+
return unless games_for(date: game.date, opponent: game.away_opponent).any?
|
104
71
|
|
105
|
-
raise TeamAlreadyBookedError, "#{game.
|
72
|
+
raise TeamAlreadyBookedError, "#{game.away_opponent} already playing on #{game.date}"
|
106
73
|
end
|
107
74
|
|
108
75
|
def assert_in_bounds(game)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
raise OutOfBoundsError, "#{
|
114
|
-
|
76
|
+
date = game.date
|
77
|
+
|
78
|
+
if game.is_a?(Exhibition)
|
79
|
+
raise OutOfBoundsError, "#{date} is before preseason begins" if date < preseason_start_date
|
80
|
+
raise OutOfBoundsError, "#{date} is after preseason ends" if date > preseason_end_date
|
81
|
+
elsif game.is_a?(Regular)
|
82
|
+
raise OutOfBoundsError, "#{date} is before season begins" if date < season_start_date
|
83
|
+
raise OutOfBoundsError, "#{date} is after season ends" if date > season_end_date
|
115
84
|
else
|
116
85
|
raise ArgumentError, "Dont know what this game type is: #{game.class.name}"
|
117
86
|
end
|
@@ -1,179 +1,237 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'calendar'
|
4
|
-
|
5
3
|
module Basketball
|
6
4
|
module Season
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
5
|
+
# Main iterator-based object that knows how to manage a calendar and simulate games per day.
|
6
|
+
class Coordinator < Entity
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
class AlreadyPlayedGameError < StandardError; end
|
10
|
+
class GameNotCurrentError < StandardError; end
|
11
|
+
class OutOfBoundsError < StandardError; end
|
12
|
+
class PlayedGamesError < StandardError; end
|
13
|
+
class UnknownGameError < StandardError; end
|
14
|
+
class UnknownTeamError < StandardError; end
|
15
|
+
class UnplayedGamesError < StandardError; end
|
16
|
+
|
17
|
+
attr_reader :calendar,
|
18
|
+
:current_date,
|
19
|
+
:arena,
|
20
|
+
:results,
|
21
|
+
:league
|
22
|
+
|
23
|
+
def_delegators :calendar,
|
24
|
+
:preseason_start_date,
|
25
|
+
:preseason_end_date,
|
26
|
+
:season_start_date,
|
27
|
+
:season_end_date,
|
28
|
+
:games,
|
29
|
+
:exhibitions_for,
|
30
|
+
:regulars_for,
|
31
|
+
:games_for
|
32
|
+
|
33
|
+
def initialize(
|
34
|
+
calendar:,
|
35
|
+
current_date:,
|
36
|
+
results: [],
|
37
|
+
league: Org::League.new
|
38
|
+
)
|
39
|
+
super()
|
40
|
+
|
41
|
+
raise ArgumentError, 'calendar is required' unless calendar
|
42
|
+
raise ArgumentError, 'current_date is required' if current_date.to_s.empty?
|
43
|
+
raise ArgumentError, 'league is required' unless league
|
44
|
+
|
45
|
+
@calendar = calendar
|
46
|
+
@current_date = current_date
|
47
|
+
@arena = Arena.new
|
48
|
+
@results = []
|
49
|
+
@league = league
|
50
|
+
|
51
|
+
results.each { |result| replay!(result) }
|
52
|
+
|
53
|
+
assert_current_date
|
54
|
+
assert_all_past_dates_are_played
|
55
|
+
assert_all_future_dates_arent_played
|
56
|
+
assert_all_known_teams
|
22
57
|
end
|
23
58
|
|
24
|
-
|
59
|
+
def sim_rest!(&)
|
60
|
+
events = []
|
61
|
+
|
62
|
+
while not_done?
|
63
|
+
new_events = sim!(&)
|
25
64
|
|
26
|
-
|
27
|
-
# Same Conference, Same Division
|
28
|
-
if league.division_for(team1) == league.division_for(team2)
|
29
|
-
4
|
30
|
-
# Same Conference, Different Division and one of 4/10 that play 3 times
|
31
|
-
elsif league.conference_for(team1) == league.conference_for(team2)
|
32
|
-
3
|
33
|
-
# Different Conference
|
34
|
-
else
|
35
|
-
2
|
65
|
+
events += new_events
|
36
66
|
end
|
67
|
+
|
68
|
+
events
|
37
69
|
end
|
38
70
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
matchups = {}
|
43
|
-
game_counts = league.teams.to_h { |t| [t, 0] }
|
44
|
-
teams = game_counts.keys
|
45
|
-
|
46
|
-
(0...teams.length).each do |i|
|
47
|
-
team1 = teams[i]
|
48
|
-
|
49
|
-
(i + 1...teams.length).each do |j|
|
50
|
-
team2 = teams[j]
|
51
|
-
key = [team1, team2].sort
|
52
|
-
count = base_matchup_count(league, team1, team2)
|
53
|
-
matchups[key] = count
|
54
|
-
game_counts[team1] += count
|
55
|
-
game_counts[team2] += count
|
56
|
-
end
|
71
|
+
def assert_current_date
|
72
|
+
if current_date < preseason_start_date
|
73
|
+
raise OutOfBoundsError, "current date #{current_date} should be on or after #{preseason_start_date}"
|
57
74
|
end
|
58
75
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
76
|
+
return unless current_date > season_end_date
|
77
|
+
|
78
|
+
raise OutOfBoundsError, "current date #{current_date} should be on or after #{preseason_start_date}"
|
79
|
+
end
|
63
80
|
|
64
|
-
|
65
|
-
|
66
|
-
next if game_counts[opponent] == 82
|
81
|
+
def sim!
|
82
|
+
return [] if done?
|
67
83
|
|
68
|
-
|
69
|
-
|
84
|
+
events = []
|
85
|
+
games = games_for(date: current_date)
|
70
86
|
|
71
|
-
|
87
|
+
games.each do |game|
|
88
|
+
home_players = opponent_team(game.home_opponent).players
|
89
|
+
away_players = opponent_team(game.away_opponent).players
|
90
|
+
matchup = Matchup.new(game:, home_players:, away_players:)
|
91
|
+
event = arena.play(matchup)
|
72
92
|
|
73
|
-
|
74
|
-
|
93
|
+
play!(event)
|
94
|
+
|
95
|
+
yield(event) if block_given?
|
96
|
+
|
97
|
+
events << event
|
75
98
|
end
|
76
99
|
|
77
|
-
|
78
|
-
end
|
79
|
-
# rubocop:enable Metrics/AbcSize
|
100
|
+
increment_current_date!
|
80
101
|
|
81
|
-
|
82
|
-
|
83
|
-
# result about 1 out of every 1000 cycles. I have yet to spot the assignment pattern to make
|
84
|
-
# this way more deterministic.
|
85
|
-
def find_fours(league)
|
86
|
-
balanced = false
|
87
|
-
count = 0
|
88
|
-
four_tracker = {}
|
102
|
+
events
|
103
|
+
end
|
89
104
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
raise ArgumentError, 'we spent too much CPU time and didnt resolve fours' if count > 100_000
|
105
|
+
def total_days
|
106
|
+
(season_end_date - preseason_start_date).to_i
|
107
|
+
end
|
94
108
|
|
95
|
-
|
109
|
+
def days_left
|
110
|
+
(season_end_date - current_date).to_i
|
111
|
+
end
|
96
112
|
|
97
|
-
|
98
|
-
|
113
|
+
def total_exhibitions
|
114
|
+
exhibitions_for.length
|
115
|
+
end
|
99
116
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
four_tracker[team] << opponent
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
117
|
+
def exhibitions_left
|
118
|
+
total_exhibitions - exhibition_result_events.length
|
119
|
+
end
|
107
120
|
|
108
|
-
|
121
|
+
def total_regulars
|
122
|
+
regulars_for.length
|
123
|
+
end
|
109
124
|
|
110
|
-
|
111
|
-
|
125
|
+
def regulars_left
|
126
|
+
total_regulars - regular_result_events.length
|
127
|
+
end
|
112
128
|
|
113
|
-
|
129
|
+
def current_games
|
130
|
+
games_for(date: current_date) - results.map(&:game)
|
131
|
+
end
|
114
132
|
|
115
|
-
|
116
|
-
|
133
|
+
def done?
|
134
|
+
current_date == season_end_date && games.length == results.length
|
135
|
+
end
|
117
136
|
|
118
|
-
|
137
|
+
def not_done?
|
138
|
+
!done?
|
119
139
|
end
|
120
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
121
140
|
|
122
|
-
def
|
123
|
-
|
141
|
+
def add!(game)
|
142
|
+
assert_today_or_in_future(game)
|
143
|
+
assert_known_teams(game)
|
124
144
|
|
125
|
-
|
126
|
-
candidates = calendar.available_season_matchup_dates(team1, team2)
|
127
|
-
dates = candidates.sample(count)
|
128
|
-
games = balanced_games(dates, team1, team2)
|
145
|
+
calendar.add!(game)
|
129
146
|
|
130
|
-
|
131
|
-
end
|
147
|
+
self
|
132
148
|
end
|
133
149
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
SeasonGame.new(date:, home_team: team1, away_team: team2)
|
138
|
-
else
|
139
|
-
SeasonGame.new(date:, home_team: team2, away_team: team1)
|
140
|
-
end
|
150
|
+
def result_for(game)
|
151
|
+
results.find do |result|
|
152
|
+
result.game == game
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
156
|
+
private
|
157
|
+
|
158
|
+
attr_writer :arena
|
159
|
+
|
160
|
+
def opponent_team(opponent)
|
161
|
+
league.teams.find { |t| t == opponent }
|
162
|
+
end
|
163
|
+
|
164
|
+
def exhibition_result_events
|
165
|
+
results.select { |e| e.game.is_a?(Exhibition) }
|
166
|
+
end
|
148
167
|
|
149
|
-
|
168
|
+
def regular_result_events
|
169
|
+
results.select { |e| e.game.is_a?(Regular) }
|
170
|
+
end
|
150
171
|
|
151
|
-
|
172
|
+
def increment_current_date!
|
173
|
+
return self if current_date >= season_end_date
|
152
174
|
|
153
|
-
|
154
|
-
break if count > MIN_PRESEASON_GAMES_PER_TEAM
|
155
|
-
next if calendar.preseason_games_for(team: other_team).length >= MAX_PRESEASON_GAMES_PER_TEAM
|
175
|
+
@current_date = current_date + 1
|
156
176
|
|
157
|
-
|
177
|
+
self
|
178
|
+
end
|
158
179
|
|
159
|
-
|
180
|
+
def assert_today_or_in_future(game)
|
181
|
+
return unless game.date <= current_date
|
160
182
|
|
161
|
-
|
162
|
-
|
183
|
+
raise OutOfBoundsError, "#{game.date} is on or before the current date (#{current_date})"
|
184
|
+
end
|
163
185
|
|
164
|
-
|
186
|
+
def assert_known_teams(game)
|
187
|
+
raise UnknownTeamError, "unknown opponent: #{game.home_opponent}" if league.not_registered?(game.home_opponent)
|
165
188
|
|
166
|
-
|
167
|
-
|
168
|
-
|
189
|
+
return unless league.not_registered?(game.away_opponent)
|
190
|
+
|
191
|
+
raise UnknownTeamError, "unknown opponent: #{game.away_opponent}"
|
169
192
|
end
|
170
193
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
194
|
+
def assert_all_past_dates_are_played
|
195
|
+
games_that_should_be_played = games.select { |game| game.date < current_date }
|
196
|
+
|
197
|
+
games_played = results.map(&:game)
|
198
|
+
unplayed_games = games_that_should_be_played - games_played
|
199
|
+
|
200
|
+
return if unplayed_games.empty?
|
201
|
+
|
202
|
+
raise UnplayedGamesError, "#{unplayed_games.length} game(s) not played before #{current_date}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def assert_all_future_dates_arent_played
|
206
|
+
games_that_shouldnt_be_played = results.select do |result|
|
207
|
+
result.date > current_date
|
176
208
|
end
|
209
|
+
|
210
|
+
count = games_that_shouldnt_be_played.length
|
211
|
+
|
212
|
+
return unless games_that_shouldnt_be_played.any?
|
213
|
+
|
214
|
+
raise PlayedGamesError, "#{count} game(s) played after #{current_date}"
|
215
|
+
end
|
216
|
+
|
217
|
+
def play!(result)
|
218
|
+
raise GameNotCurrentError, "#{result} is not for #{current_date}" if result.date != current_date
|
219
|
+
|
220
|
+
replay!(result)
|
221
|
+
end
|
222
|
+
|
223
|
+
def replay!(result)
|
224
|
+
raise AlreadyPlayedGameError, "#{result.game} already played!" if result_for(result.game)
|
225
|
+
|
226
|
+
raise UnknownGameError, "game not added: #{result.game}" unless games.include?(result.game)
|
227
|
+
|
228
|
+
results << result
|
229
|
+
|
230
|
+
result
|
231
|
+
end
|
232
|
+
|
233
|
+
def assert_all_known_teams
|
234
|
+
calendar.games.each { |game| assert_known_teams(game) }
|
177
235
|
end
|
178
236
|
end
|
179
237
|
end
|