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,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
|