-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame_play.rb
102 lines (82 loc) · 3.3 KB
/
game_play.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
Dir["#{__dir__}/game_play/*.rb"].each do |file|
require "play_by_play/model/game_play/#{File.basename(file)}"
end
module PlayByPlay
module Model
# Apply Plays to Possession to produce a new Possession. Or: reduces current state + action to a new state.
# Require and include every Play module in game_play.
# GamePlay and its play modules are all pure methods: given a state and and action, they always return the same state, and
# they never change external state.
module GamePlay
def self.play!(possession, play)
unless play.is_a?(Model::Play)
play = Model::Play.new(play.first, *play[1..-1])
end
unless possession.is_a?(Model::Possession)
raise ArgumentError, "possession must be a Model::Possession, but is a #{possession.class}"
end
possession
.merge(play_updates(possession, play))
.merge(seconds_remaining(possession, play))
.merge(opening_tip(possession, play))
.merge(errors!(possession, play))
end
def self.play_updates(possession, play)
send play.type, possession, play
end
def self.errors!(possession, play)
errors = []
unless PlayMatrix.accessible?(possession, play)
errors << "#{play.key} not accessible for #{possession.key}. Accessible plays: #{PlayMatrix.accessible_plays(possession.key)}"
end
if possession.seconds_remaining > 24 && play.key == [ :period_end ]
errors << ":period_end invalid with #{possession.seconds_remaining} seconds remaining"
end
raise(InvalidStateError, errors) unless errors.empty?
{ errors: errors }
end
def self.add_points(possession, points)
instance = possession.team_instance
{ instance.key => { points: instance.points + points } }
end
def self.increment_period_personal_fouls(possession, team)
instance = possession.team_instance(team)
attributes = { period_personal_fouls: instance.period_personal_fouls + 1 }
if possession.seconds_remaining <= 120
attributes = attributes.merge(personal_foul_in_last_two_minutes: true)
end
{ instance.key => instance.merge(attributes) }
end
def self.add_technical_free_throws(possession, play)
new_fts = possession.technical_free_throws.dup
new_fts << possession.other_team(play.team)
if play.flagrant?
new_fts << possession.other_team(play.team)
end
new_fts
end
def self.decrement_free_throws(possession)
if possession.technical_free_throws?
new_fts = possession.technical_free_throws.dup
new_fts.pop
{ technical_free_throws: new_fts }
else
new_fts = possession.free_throws.dup
new_fts.pop
{ free_throws: new_fts }
end
end
def self.opening_tip(possession, play)
unless possession.opening_tip
{ opening_tip: play.team }
end
end
def self.seconds_remaining(possession, play)
return if play.type == :period_end
next_seconds_remaining = possession.seconds_remaining - play.seconds
next_seconds_remaining = 0 if next_seconds_remaining < 0
{ seconds_remaining: next_seconds_remaining }
end
end
end
end