hlockey 1 → 3
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/data/election.yaml +16 -0
- data/data/external/names.txt +19948 -0
- data/data/information.yaml +12 -0
- data/data/league.yaml +846 -0
- data/data/links.yaml +2 -0
- data/lib/hlockey/constants.rb +4 -0
- data/lib/hlockey/data.rb +17 -0
- data/lib/hlockey/game/actions.rb +30 -0
- data/lib/hlockey/game/fight.rb +83 -0
- data/lib/hlockey/game.rb +254 -0
- data/lib/hlockey/league.rb +150 -0
- data/lib/hlockey/message.rb +133 -0
- data/lib/hlockey/player.rb +63 -0
- data/lib/hlockey/team.rb +59 -0
- data/lib/hlockey/utils.rb +12 -0
- data/lib/hlockey/version.rb +3 -0
- data/lib/hlockey/weather.rb +114 -0
- data/lib/hlockey.rb +1 -0
- metadata +26 -15
- data/bin/hlockey +0 -90
- data/lib/data/divisions.yaml +0 -845
- data/lib/data/season.rb +0 -27
- data/lib/game.rb +0 -158
- data/lib/league.rb +0 -140
- data/lib/messages.rb +0 -66
data/data/links.yaml
ADDED
data/lib/hlockey/data.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require("hlockey/team") # Class stored in league data
|
2
|
+
require("yaml")
|
3
|
+
|
4
|
+
module Hlockey
|
5
|
+
##
|
6
|
+
# Module containing methods to load Hlockey data
|
7
|
+
module Data
|
8
|
+
class << self
|
9
|
+
data_dir = File.expand_path("../../data", __dir__)
|
10
|
+
|
11
|
+
Dir.glob("*.yaml", base: data_dir).each do |data_file|
|
12
|
+
category = File.basename(data_file, ".yaml")
|
13
|
+
define_method(category) { YAML.unsafe_load_file("#{data_dir}/#{data_file}") }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Hlockey
|
2
|
+
class Game
|
3
|
+
##
|
4
|
+
# For classes that deal with actions within a Hlockey game.
|
5
|
+
# Any class including this must have instance variables indicated by the readers
|
6
|
+
module Actions
|
7
|
+
# @return [Team] the teams in the game
|
8
|
+
attr_reader(:home, :away)
|
9
|
+
|
10
|
+
# @return [Random] should be the same as the league's
|
11
|
+
attr_reader(:prng)
|
12
|
+
|
13
|
+
# @return [Integer] how many actions have passed
|
14
|
+
attr_reader(:actions)
|
15
|
+
|
16
|
+
# @return [Hash<Symbol => Integer>] the score of each team
|
17
|
+
attr_reader(:score)
|
18
|
+
|
19
|
+
# @return [Boolean] if the sequence of actions is still in progress
|
20
|
+
attr_reader(:in_progress)
|
21
|
+
|
22
|
+
# @param succeed_boost [Numeric] increases chance of action succeeding
|
23
|
+
# @param fail_boost [Numeric] decreases chance of action succeeding
|
24
|
+
# @return [Boolean] if the action succeeded
|
25
|
+
def action_succeeds?(succeed_boost, fail_boost)
|
26
|
+
@prng.rand(10) + succeed_boost - fail_boost > 4
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require("hlockey/game/actions")
|
2
|
+
require("hlockey/message")
|
3
|
+
|
4
|
+
module Hlockey
|
5
|
+
class Game
|
6
|
+
##
|
7
|
+
# A fight within a Hlockey game
|
8
|
+
class Fight
|
9
|
+
include(Actions)
|
10
|
+
|
11
|
+
# @return [Hash<Symbol => Array<Player>>] the players in the fight
|
12
|
+
attr_reader(:players)
|
13
|
+
|
14
|
+
# @param game [Game] the game the fight is a part of
|
15
|
+
def initialize(game)
|
16
|
+
@home = game.home
|
17
|
+
@away = game.away
|
18
|
+
@prng = game.prng
|
19
|
+
@actions = 0
|
20
|
+
@players = {
|
21
|
+
home: [@home.random_player(@prng)],
|
22
|
+
away: [@away.random_player(@prng)]
|
23
|
+
}
|
24
|
+
@score = { home: 0, away: 0 }
|
25
|
+
@in_progress = true
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Message] the message to add to the game stream
|
29
|
+
def next_action
|
30
|
+
@actions += 1
|
31
|
+
|
32
|
+
case @prng.rand(@actions)
|
33
|
+
when 0..3 # attack
|
34
|
+
if rand_is_home?
|
35
|
+
attack(:home, :away)
|
36
|
+
else
|
37
|
+
attack(:away, :home)
|
38
|
+
end
|
39
|
+
when 4..7 # player joins
|
40
|
+
team_joining_sym = rand_is_home? ? :away : :home
|
41
|
+
team_joining = send(team_joining_sym)
|
42
|
+
|
43
|
+
player_joining = team_joining.random_player(
|
44
|
+
@prng,
|
45
|
+
proc { |player| !@players[team_joining_sym].include?(player) }
|
46
|
+
)
|
47
|
+
@players[team_joining_sym] << player_joining
|
48
|
+
|
49
|
+
Message.PlayerJoinedFight(team_joining, player_joining)
|
50
|
+
else # fight ends
|
51
|
+
@in_progress = false
|
52
|
+
|
53
|
+
Message.FightEnded
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# @param attacking_team [Team]
|
60
|
+
# @param defending_team [Team]
|
61
|
+
# @return [Message] returned by #next_action for same purpose
|
62
|
+
def attack(attacking_team, defending_team)
|
63
|
+
attacking_player = @players[attacking_team].sample(random: @prng)
|
64
|
+
defending_player = @players[defending_team].sample(random: @prng)
|
65
|
+
|
66
|
+
blocked = !action_succeeds?(attacking_player.stats[:offense],
|
67
|
+
defending_player.stats[:defense])
|
68
|
+
|
69
|
+
@score[attacking_team] += 1 unless blocked
|
70
|
+
|
71
|
+
Message.FightAttack(attacking_player, defending_player, blocked)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Boolean] if randomly selected team is home team
|
75
|
+
def rand_is_home?
|
76
|
+
home_player_amount = @home.roster.values.length
|
77
|
+
away_player_amount = @away.roster.values.length
|
78
|
+
|
79
|
+
@prng.rand(home_player_amount + away_player_amount) < home_player_amount
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/hlockey/game.rb
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
require("hlockey/game/actions")
|
2
|
+
require("hlockey/game/fight")
|
3
|
+
require("hlockey/constants")
|
4
|
+
require("hlockey/message")
|
5
|
+
|
6
|
+
module Hlockey
|
7
|
+
##
|
8
|
+
# A single game of Hlockey
|
9
|
+
class Game
|
10
|
+
include(Actions)
|
11
|
+
|
12
|
+
ACTIONS_PER_PERIOD = 60
|
13
|
+
NON_OT_PERIODS = 4
|
14
|
+
NON_OT_ACTIONS = ACTIONS_PER_PERIOD * NON_OT_PERIODS
|
15
|
+
TOTAL_ACTIONS = UPDATES_PER_HOUR - ACTIONS_PER_PERIOD
|
16
|
+
TOTAL_PERIODS = TOTAL_ACTIONS / ACTIONS_PER_PERIOD
|
17
|
+
|
18
|
+
# @return [Array<Message>]
|
19
|
+
attr_reader(:stream)
|
20
|
+
|
21
|
+
# @return [Weather]
|
22
|
+
attr_reader(:weather)
|
23
|
+
|
24
|
+
# @param home [Team]
|
25
|
+
# @param away [Team]
|
26
|
+
# @param prng [Random] should be League#prng
|
27
|
+
# @param weather [Class<Weather>]
|
28
|
+
def initialize(home, away, prng, weather)
|
29
|
+
@home = home
|
30
|
+
@away = away
|
31
|
+
@prng = prng
|
32
|
+
@weather = weather.new(self)
|
33
|
+
@stream = [Message.StartOfGame]
|
34
|
+
@score = { home: 0, away: 0 }
|
35
|
+
@in_progress = true
|
36
|
+
@actions = 0
|
37
|
+
@period = 1
|
38
|
+
@faceoff = true
|
39
|
+
@team_with_puck = nil
|
40
|
+
@puckless_team = nil
|
41
|
+
@puck_holder = nil
|
42
|
+
@shooting_chance = 0
|
43
|
+
@fight = nil
|
44
|
+
@fight_chance = 0
|
45
|
+
@total_fight_actions = 0
|
46
|
+
@pre_morale_change_stats = {}
|
47
|
+
|
48
|
+
@weather.on_game_start
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def to_s
|
53
|
+
"#{@home} vs #{@away}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def update
|
57
|
+
return unless @in_progress
|
58
|
+
|
59
|
+
unless @fight.nil?
|
60
|
+
@stream << @fight.next_action
|
61
|
+
if @stream.last.event == :FightEnded
|
62
|
+
@total_fight_actions += @fight.actions
|
63
|
+
|
64
|
+
morale_change_amount = (@fight.score[:home] - @fight.score[:away]) * 0.05
|
65
|
+
boost_morale(@home, morale_change_amount)
|
66
|
+
boost_morale(@away, -morale_change_amount)
|
67
|
+
|
68
|
+
@fight = nil
|
69
|
+
end
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
@stream << Message.StartOfPeriod(@period) if @actions.zero?
|
74
|
+
@actions += 1
|
75
|
+
|
76
|
+
if @actions >= ACTIONS_PER_PERIOD
|
77
|
+
end_period
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
if @faceoff
|
82
|
+
do_faceoff
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
@weather.on_action
|
87
|
+
|
88
|
+
case @prng.rand(5 + @shooting_chance)
|
89
|
+
when 0..4
|
90
|
+
pass
|
91
|
+
when 5..6
|
92
|
+
check
|
93
|
+
else
|
94
|
+
shoot
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def end_period
|
101
|
+
@weather.on_period_end
|
102
|
+
@stream << Message.EndOfPeriod(@period, @home, @away, *@score.values)
|
103
|
+
@actions = 0
|
104
|
+
@period += 1
|
105
|
+
start_faceoff
|
106
|
+
|
107
|
+
if @period >= NON_OT_PERIODS &&
|
108
|
+
!(@score[:home] == @score[:away] &&
|
109
|
+
@period <= TOTAL_PERIODS - (@total_fight_actions / ACTIONS_PER_PERIOD))
|
110
|
+
# Game is over
|
111
|
+
@pre_morale_change_stats.each { |player, stats| player.stats = stats }
|
112
|
+
@weather.on_game_end
|
113
|
+
@in_progress = false
|
114
|
+
winner, loser = @score[:away] > @score[:home] ? [@away, @home] : [@home, @away]
|
115
|
+
@stream << Message.EndOfGame(winner)
|
116
|
+
winner.wins += 1
|
117
|
+
loser.losses += 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def start_faceoff
|
122
|
+
@shooting_chance = 0
|
123
|
+
@faceoff = true
|
124
|
+
@puck_holder = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def do_faceoff
|
128
|
+
if @puck_holder.nil?
|
129
|
+
# Pass opposite team to who wins the puck to switch_team_with_puck,
|
130
|
+
# so @team_with_puck & @puckless_team are set to the correct values.
|
131
|
+
switch_team_with_puck(
|
132
|
+
if action_succeeds?(@home.roster[:center].stats[:offense],
|
133
|
+
@away.roster[:center].stats[:offense])
|
134
|
+
@away
|
135
|
+
else
|
136
|
+
@home
|
137
|
+
end
|
138
|
+
)
|
139
|
+
|
140
|
+
@puck_holder = @team_with_puck.roster[:center]
|
141
|
+
@stream << Message.FaceOff(@puck_holder, @team_with_puck)
|
142
|
+
else
|
143
|
+
pass
|
144
|
+
@faceoff = false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def switch_team_with_puck(team_with_puck = @team_with_puck)
|
149
|
+
@team_with_puck, @puckless_team = if team_with_puck == @home
|
150
|
+
[@away, @home]
|
151
|
+
else
|
152
|
+
[@home, @away]
|
153
|
+
end
|
154
|
+
@shooting_chance = 0
|
155
|
+
end
|
156
|
+
|
157
|
+
def pass
|
158
|
+
sender = @puck_holder
|
159
|
+
receiver = random_puckless_non_goalie(@team_with_puck)
|
160
|
+
interceptor = random_puckless_non_goalie(@puckless_team)
|
161
|
+
|
162
|
+
if !@faceoff && try_take_puck(interceptor, 4) # Pass intercepted
|
163
|
+
@stream << Message.Pass(sender, receiver, interceptor, @team_with_puck)
|
164
|
+
return
|
165
|
+
end
|
166
|
+
|
167
|
+
@stream << Message.Pass(sender, receiver)
|
168
|
+
@puck_holder = receiver
|
169
|
+
@shooting_chance += 1
|
170
|
+
end
|
171
|
+
|
172
|
+
def check
|
173
|
+
defender = random_puckless_non_goalie(@puckless_team)
|
174
|
+
@stream << Message.Hit(@puck_holder, defender,
|
175
|
+
try_take_puck(defender, 0, :defense), @team_with_puck)
|
176
|
+
@fight_chance += 0.1
|
177
|
+
end
|
178
|
+
|
179
|
+
def shoot
|
180
|
+
if @shooting_chance < 5 &&
|
181
|
+
try_block_shot(@puckless_team.roster[@prng.rand(2).zero? ? :ldef : :rdef])
|
182
|
+
return
|
183
|
+
end
|
184
|
+
return if try_block_shot(@puckless_team.roster[:goalie])
|
185
|
+
|
186
|
+
# Goal scored
|
187
|
+
|
188
|
+
scoring_team = @team_with_puck == @home ? :home : :away
|
189
|
+
@score[scoring_team] += 1
|
190
|
+
weather.on_goal(scoring_team)
|
191
|
+
|
192
|
+
@stream << Message.ShootScore(@puck_holder, @home, @away, *@score.values)
|
193
|
+
|
194
|
+
if @prng.rand(3 + @fight_chance) > 5 &&
|
195
|
+
@total_fight_actions < TOTAL_ACTIONS - NON_OT_ACTIONS
|
196
|
+
start_fight
|
197
|
+
end
|
198
|
+
|
199
|
+
start_faceoff
|
200
|
+
@actions = ACTIONS_PER_PERIOD - 1 if @period >= NON_OT_PERIODS # Sudden death OT
|
201
|
+
end
|
202
|
+
|
203
|
+
# @return [Boolean] if taking the puck succeeded
|
204
|
+
def try_take_puck(player, disadvantage = 0, stat = :agility)
|
205
|
+
return false unless action_succeeds?(player.stats[stat] - disadvantage,
|
206
|
+
@puck_holder.stats[stat])
|
207
|
+
|
208
|
+
switch_team_with_puck
|
209
|
+
@puck_holder = player
|
210
|
+
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
# @return [Boolean] if blocking the shot succeeded
|
215
|
+
def try_block_shot(blocker)
|
216
|
+
return false unless action_succeeds?(blocker.stats[:defense],
|
217
|
+
@puck_holder.stats[:offense])
|
218
|
+
|
219
|
+
@shooting_chance += 1
|
220
|
+
@stream << Message.ShootBlock(@puck_holder, blocker,
|
221
|
+
try_take_puck(blocker), @team_with_puck)
|
222
|
+
|
223
|
+
true
|
224
|
+
end
|
225
|
+
|
226
|
+
def start_fight
|
227
|
+
@fight = Fight.new(self)
|
228
|
+
@stream << Message.FightStarted(@fight.players[:home].first,
|
229
|
+
@fight.players[:away].first)
|
230
|
+
end
|
231
|
+
|
232
|
+
# @param team [Team]
|
233
|
+
# @param amount [Numeric]
|
234
|
+
def boost_morale(team, amount)
|
235
|
+
return if amount.zero?
|
236
|
+
|
237
|
+
team.roster.each_value do |player|
|
238
|
+
if @pre_morale_change_stats[player].nil?
|
239
|
+
@pre_morale_change_stats[player] = player.stats.clone
|
240
|
+
end
|
241
|
+
|
242
|
+
player.stats.transform_values! { |stat| stat + amount }
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# @return [Player]
|
247
|
+
def random_puckless_non_goalie(team)
|
248
|
+
team.random_player(
|
249
|
+
@prng,
|
250
|
+
proc { |player| player != team.roster[:goalie] and player != @puck_holder }
|
251
|
+
)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require("hlockey/constants")
|
2
|
+
require("hlockey/data")
|
3
|
+
require("hlockey/game")
|
4
|
+
require("hlockey/utils")
|
5
|
+
require("hlockey/version")
|
6
|
+
require("hlockey/weather")
|
7
|
+
|
8
|
+
module Hlockey
|
9
|
+
##
|
10
|
+
# The Hlockey League
|
11
|
+
class League
|
12
|
+
REGULAR_SEASON_DAY_AMT = 38
|
13
|
+
|
14
|
+
# @return [Time]
|
15
|
+
attr_reader(:start_time)
|
16
|
+
|
17
|
+
# @return [Hash<Symbol => Array<Team>>]
|
18
|
+
attr_reader(:divisions)
|
19
|
+
|
20
|
+
# @return [Array<Team>]
|
21
|
+
attr_reader(:teams, :playoff_teams)
|
22
|
+
|
23
|
+
# @return [Integer]
|
24
|
+
attr_reader(:day)
|
25
|
+
|
26
|
+
# @return [Array<Game>]
|
27
|
+
attr_reader(:games, :games_in_progress)
|
28
|
+
|
29
|
+
# @return [Team, nil]
|
30
|
+
attr_reader(:champion_team)
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@start_time, @divisions = Data.league
|
34
|
+
@start_time.localtime
|
35
|
+
@day = 0
|
36
|
+
@games_in_progress = []
|
37
|
+
@games = []
|
38
|
+
@champion_team = nil
|
39
|
+
@last_update_time = @start_time
|
40
|
+
@passed_updates = 0
|
41
|
+
@prng = Random.new(@start_time.to_i)
|
42
|
+
@teams = @divisions.values.reduce(:+)
|
43
|
+
@shuffled_teams = @teams.shuffle(random: @prng)
|
44
|
+
@playoff_teams = []
|
45
|
+
@game_in_matchup = 3
|
46
|
+
@matchup_game_amt = 3
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Updates the league to the current state
|
51
|
+
# This should be called whenever you need the current state of the league
|
52
|
+
def update_state
|
53
|
+
return if @champion_team
|
54
|
+
|
55
|
+
now = Time.at(Time.now.to_i)
|
56
|
+
intervals = (now - @last_update_time).div(UPDATE_FREQUENCY_SECONDS)
|
57
|
+
|
58
|
+
return unless intervals.positive?
|
59
|
+
|
60
|
+
intervals.times do |i|
|
61
|
+
if ((i + @passed_updates) % UPDATES_PER_HOUR).zero?
|
62
|
+
new_games
|
63
|
+
else
|
64
|
+
update_games
|
65
|
+
end
|
66
|
+
|
67
|
+
break if @champion_team
|
68
|
+
end
|
69
|
+
|
70
|
+
@last_update_time = now
|
71
|
+
@passed_updates += intervals
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def new_games
|
77
|
+
if @game_in_matchup != @matchup_game_amt
|
78
|
+
# New game in matchups
|
79
|
+
@games.map! { |game| new_game(game.away, game.home) }
|
80
|
+
@game_in_matchup += 1
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
# New matchups
|
85
|
+
@games.clear
|
86
|
+
@game_in_matchup = 1
|
87
|
+
@day += 1
|
88
|
+
|
89
|
+
case @day <=> REGULAR_SEASON_DAY_AMT + 1
|
90
|
+
when -1 # In regular season
|
91
|
+
(@shuffled_teams.length / 2).times do |i|
|
92
|
+
pair = [@shuffled_teams[i], @shuffled_teams[-i - 1]]
|
93
|
+
pair.reverse! if @day > REGULAR_SEASON_DAY_AMT / 2
|
94
|
+
@games << new_game(*pair)
|
95
|
+
end
|
96
|
+
|
97
|
+
@shuffled_teams.insert(1, @shuffled_teams.pop)
|
98
|
+
when 0 # Playoffs about to start
|
99
|
+
@matchup_game_amt = 5
|
100
|
+
|
101
|
+
# get teams that qualified for playoffs, put in @playoff_teams
|
102
|
+
two_best_teams_each_division = @divisions.values.map do |teams|
|
103
|
+
sort_teams_by_wins(teams).first(2)
|
104
|
+
end.reduce(:+)
|
105
|
+
@playoff_teams = sort_teams_by_wins(two_best_teams_each_division).map(&:clone)
|
106
|
+
|
107
|
+
new_playoff_matchups
|
108
|
+
when 1 # Playoffs started
|
109
|
+
@playoff_teams.select! { |team| team.wins > team.losses }
|
110
|
+
|
111
|
+
if @playoff_teams.length == 1
|
112
|
+
@champion_team = @playoff_teams.first
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
new_playoff_matchups
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def update_games
|
121
|
+
@games_in_progress.each(&:update)
|
122
|
+
@games_in_progress = @games.select(&:in_progress)
|
123
|
+
@divisions.transform_values!(&method(:sort_teams_by_wins))
|
124
|
+
end
|
125
|
+
|
126
|
+
def new_playoff_matchups
|
127
|
+
@playoff_teams.each do |team|
|
128
|
+
team.wins = 0
|
129
|
+
team.losses = 0
|
130
|
+
end
|
131
|
+
|
132
|
+
(@playoff_teams.length / 2).times do |i|
|
133
|
+
@games << new_game(@playoff_teams[i], @playoff_teams[-i - 1])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param home [Team]
|
138
|
+
# @param away [Team]
|
139
|
+
# @return [Game]
|
140
|
+
def new_game(home, away)
|
141
|
+
Game.new(home, away, @prng, Utils.weighted_random(Weather::WEIGHTS, @prng))
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param teams [Array<Team>]
|
145
|
+
# @return [Array<Team>]
|
146
|
+
def sort_teams_by_wins(teams)
|
147
|
+
teams.sort { |a, b| b.wins <=> a.wins }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require("hlockey/version")
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
##
|
5
|
+
# Can be sent by a Game (in Game#stream) or a commonly used text
|
6
|
+
class Message
|
7
|
+
private_class_method(:new)
|
8
|
+
|
9
|
+
# @return [Symbol] an action or event the message represents
|
10
|
+
attr_reader(:event)
|
11
|
+
|
12
|
+
# @param event [Symbol]
|
13
|
+
# @param fields [Array<Symbol>] fields used in string interpolation in #to_s
|
14
|
+
# @param data [Array<Object>] data corresponding to the fields
|
15
|
+
def initialize(event, fields, data)
|
16
|
+
@event = event
|
17
|
+
fields.zip(data) { |f, d| instance_variable_set("@#{f}", d) }
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# These are messages logged to game streams
|
22
|
+
|
23
|
+
[
|
24
|
+
%i[StartOfGame],
|
25
|
+
%i[EndOfGame winning_team],
|
26
|
+
%i[StartOfPeriod period],
|
27
|
+
%i[EndOfPeriod period home away home_score away_score],
|
28
|
+
%i[FaceOff winning_player new_puck_team],
|
29
|
+
%i[Hit puck_holder defender puck_taken new_puck_team],
|
30
|
+
%i[Pass sender receiver interceptor new_puck_team],
|
31
|
+
%i[ShootScore shooter home away home_score away_score],
|
32
|
+
%i[ShootBlock shooter blocker puck_taken new_puck_team],
|
33
|
+
%i[FightStarted home_player away_player],
|
34
|
+
%i[FightAttack attacking_player defending_player blocked],
|
35
|
+
%i[PlayerJoinedFight team player],
|
36
|
+
%i[FightEnded],
|
37
|
+
%i[ChickenedOut prev_player next_player],
|
38
|
+
%i[InclineFavors team],
|
39
|
+
%i[StarsPity team],
|
40
|
+
%i[WavesWashedAway prev_player next_player]
|
41
|
+
].each do |event, *fields|
|
42
|
+
define_method(event) { |*data| new(event, fields, data) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# These are messages used elsewhere
|
46
|
+
|
47
|
+
def SeasonDay(day)
|
48
|
+
"Season #{VERSION} day #{day}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def SeasonStarts(time)
|
52
|
+
time.strftime("Season #{VERSION} starts at %H:%M, %A, %B %d (%Z).")
|
53
|
+
end
|
54
|
+
|
55
|
+
def SeasonChampion(team)
|
56
|
+
"Your season #{VERSION} champions are the #{team}!"
|
57
|
+
end
|
58
|
+
|
59
|
+
def NoGames
|
60
|
+
"no games right now. it is the offseason. join the Hlockey Discord for updates"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
case @event
|
66
|
+
when :StartOfGame
|
67
|
+
"Hocky!"
|
68
|
+
when :EndOfGame
|
69
|
+
"Game over.\n#{@winning_team} win!"
|
70
|
+
when :StartOfPeriod
|
71
|
+
"Start#{of_period}"
|
72
|
+
when :EndOfPeriod
|
73
|
+
"End#{of_period}#{score}"
|
74
|
+
when :FaceOff
|
75
|
+
"#{@winning_player} wins the faceoff!#{possession_change}"
|
76
|
+
when :Hit
|
77
|
+
"#{@defender} hits #{@puck_holder}#{takes}"
|
78
|
+
when :Pass
|
79
|
+
str = "#{@sender} passes to #{@receiver}."
|
80
|
+
str += "..\nIntercepted by #{@interceptor}!#{possession_change}" if @interceptor
|
81
|
+
str
|
82
|
+
when :ShootScore
|
83
|
+
"#{shot} and scores!#{score}"
|
84
|
+
when :ShootBlock
|
85
|
+
"#{shot}...\n#{@blocker} blocks the shot#{takes}"
|
86
|
+
when :FightStarted
|
87
|
+
"#{@home_player} and #{@away_player} start fighting!"
|
88
|
+
when :FightAttack
|
89
|
+
str = "#{@attacking_player} punches #{@defending_player}!"
|
90
|
+
str += "\n#{@defending_player} blocks the punch!" if @blocked
|
91
|
+
str
|
92
|
+
when :PlayerJoinedFight
|
93
|
+
"#{@player} from #{@team} joins the fight!"
|
94
|
+
when :FightEnded
|
95
|
+
"The fight has ended."
|
96
|
+
when :ChickenedOut
|
97
|
+
"#{@prev_player} chickened out!#{replaces_for("game")}"
|
98
|
+
when :InclineFavors
|
99
|
+
"The incline favors #{@team}."
|
100
|
+
when :StarsPity
|
101
|
+
"The stars take pity on #{@team} and give them half a goal."
|
102
|
+
when :WavesWashedAway
|
103
|
+
"#{@prev_player} is washed away by the waves...#{replaces_for("season")}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def of_period
|
110
|
+
" of period #{@period}."
|
111
|
+
end
|
112
|
+
|
113
|
+
def score
|
114
|
+
"\n#{@home} #{@home_score.round(1)}, #{@away} #{@away_score.round(1)}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def shot
|
118
|
+
"#{@shooter} takes a shot"
|
119
|
+
end
|
120
|
+
|
121
|
+
def takes
|
122
|
+
@puck_taken ? " and takes the puck!#{possession_change}" : "!"
|
123
|
+
end
|
124
|
+
|
125
|
+
def possession_change
|
126
|
+
"\n#{@new_puck_team} have possession."
|
127
|
+
end
|
128
|
+
|
129
|
+
def replaces_for(period)
|
130
|
+
"\n#{@next_player} replaces them for the rest of the #{period}."
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|