hlockey 1 → 3
Sign up to get free protection for your applications and to get access to all the features.
- 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/lib/data/season.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Season
|
4
|
-
NUMBER = 1
|
5
|
-
START_TIME = Time.utc(2022, 7, 4, 17).localtime.freeze
|
6
|
-
ELECTION = {
|
7
|
-
'Bribery': {
|
8
|
-
'Eat More RAM': 'Add an online interface to the league.',
|
9
|
-
'Weather': 'Move all the stadiums outdoors.',
|
10
|
-
'We Do A Little Losing': 'Recognize the losingest teams with an Underbracket.'
|
11
|
-
},
|
12
|
-
'Treasure': {
|
13
|
-
'Vengence': 'All your team\'s stats are boosted by your team\'s losses * 0.005.',
|
14
|
-
'Dave': 'The worst stat on your team is set to 4.5.',
|
15
|
-
'Nice': 'Boost your worst player by 0.69 in every stat.',
|
16
|
-
'Convenient Math Error': 'Your team starts the next season with 5 wins.'
|
17
|
-
},
|
18
|
-
'Coaching': {
|
19
|
-
'Player Swapping': 'Swap the positions of 2 players on your team.',
|
20
|
-
'Stat Swapping': 'Swap 2 stats of a player on your team.',
|
21
|
-
'Draft': 'Replace a player with a random new player.',
|
22
|
-
'Small Gamble': 'All your team\'s stats go up or down by 0.5 at random.',
|
23
|
-
'Big Gamble': 'All your team\'s stats go up or down by 1.0 at random.'
|
24
|
-
}
|
25
|
-
}.freeze
|
26
|
-
ELECTION_FORM = 'https://forms.gle/saLp3ucxg2ERsY9L7'
|
27
|
-
end
|
data/lib/game.rb
DELETED
@@ -1,158 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative('./messages')
|
4
|
-
|
5
|
-
class Game
|
6
|
-
attr_reader(:home, :away, :stream, :in_progress)
|
7
|
-
|
8
|
-
def initialize(home, away, prng)
|
9
|
-
@home = home
|
10
|
-
@away = away
|
11
|
-
@prng = prng
|
12
|
-
@stream = [Messages.StartOfGame(to_s)]
|
13
|
-
@in_progress = true
|
14
|
-
@score = { home: 0, away: 0 }
|
15
|
-
@actions = 0
|
16
|
-
@period = 1
|
17
|
-
@face_off = true
|
18
|
-
@team_with_puck = nil
|
19
|
-
@puckless_team = nil
|
20
|
-
@puck_holder = nil
|
21
|
-
@shooting_chance = 0
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_s
|
25
|
-
"#{@home.emoji} #{@home.name} vs #{@away.name} #{@away.emoji}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def update
|
29
|
-
return unless @in_progress
|
30
|
-
|
31
|
-
@stream << Messages.StartOfPeriod(@period) if @actions.zero?
|
32
|
-
@actions += 1
|
33
|
-
|
34
|
-
if @actions == 60
|
35
|
-
@stream << Messages.EndOfPeriod(@period, @home.name, @away.name, *@score.values)
|
36
|
-
@actions = 0
|
37
|
-
@period += 1
|
38
|
-
start_faceoff
|
39
|
-
|
40
|
-
if @period > 3 && !(@score[:home] == @score[:away] && @period < 12)
|
41
|
-
# Game is over
|
42
|
-
@in_progress = false
|
43
|
-
|
44
|
-
winner, loser = @score[:away] > @score[:home] ? [@away, @home] : [@home, @away]
|
45
|
-
|
46
|
-
@stream << Messages.EndOfGame(winner.name)
|
47
|
-
winner.wins += 1
|
48
|
-
loser.losses += 1
|
49
|
-
end
|
50
|
-
return
|
51
|
-
end
|
52
|
-
|
53
|
-
if @face_off
|
54
|
-
if @puck_holder.nil?
|
55
|
-
# Pass opposite team to who wins the puck to switch_team_with_puck,
|
56
|
-
# so @team_with_puck & @puckless_team are set to the correct values.
|
57
|
-
switch_team_with_puck(if action_succeeds?(@home.roster[:center].stats[:offense],
|
58
|
-
@away.roster[:center].stats[:offense])
|
59
|
-
@away
|
60
|
-
else
|
61
|
-
@home
|
62
|
-
end)
|
63
|
-
|
64
|
-
@puck_holder = @team_with_puck.roster[:center]
|
65
|
-
@stream << Messages.FaceOff(@puck_holder)
|
66
|
-
else
|
67
|
-
pass
|
68
|
-
@face_off = false
|
69
|
-
end
|
70
|
-
return
|
71
|
-
end
|
72
|
-
|
73
|
-
case @prng.rand 5 + @shooting_chance
|
74
|
-
when 0..4
|
75
|
-
pass
|
76
|
-
when 5..6 # Check
|
77
|
-
defender = puckless_non_goalie
|
78
|
-
@stream << Messages.Hit(@puck_holder, defender,
|
79
|
-
try_take_puck(defender, 0, :defense))
|
80
|
-
else # Shoot
|
81
|
-
unless @shooting_chance < 5 &&
|
82
|
-
try_block_shot(@puckless_team.roster[@prng.rand(2).zero? ? :ldef : :rdef]) ||
|
83
|
-
try_block_shot(@puckless_team.roster[:goalie])
|
84
|
-
@score[@team_with_puck == @home ? :home : :away] += 1
|
85
|
-
|
86
|
-
@stream << Messages.Shoot(@puck_holder,
|
87
|
-
nil, false,
|
88
|
-
@home.name, @away.name,
|
89
|
-
*@score.values)
|
90
|
-
|
91
|
-
start_faceoff
|
92
|
-
@actions = 59 if @period > 3 # Sudden death overtime
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def action_succeeds?(helping_stat, hindering_stat)
|
100
|
-
@prng.rand(10) + helping_stat - hindering_stat > 4
|
101
|
-
end
|
102
|
-
|
103
|
-
def start_faceoff
|
104
|
-
@shooting_chance = 0
|
105
|
-
@face_off = true
|
106
|
-
@puck_holder = nil
|
107
|
-
end
|
108
|
-
|
109
|
-
def switch_team_with_puck(team_with_puck = @team_with_puck)
|
110
|
-
@team_with_puck, @puckless_team = if team_with_puck == @home
|
111
|
-
[@away, @home]
|
112
|
-
else
|
113
|
-
[@home, @away]
|
114
|
-
end
|
115
|
-
@shooting_chance = 0
|
116
|
-
end
|
117
|
-
|
118
|
-
def pass
|
119
|
-
sender = @puck_holder
|
120
|
-
receiver = puckless_non_goalie(@team_with_puck)
|
121
|
-
interceptor = puckless_non_goalie
|
122
|
-
|
123
|
-
if !@face_off && try_take_puck(interceptor, 4) # Pass intercepted
|
124
|
-
@stream << Messages.Pass(sender, receiver, interceptor)
|
125
|
-
return
|
126
|
-
end
|
127
|
-
|
128
|
-
@stream << Messages.Pass(sender, receiver)
|
129
|
-
@puck_holder = receiver
|
130
|
-
@shooting_chance += 1
|
131
|
-
end
|
132
|
-
|
133
|
-
def try_take_puck(player, dis = 0, stat = :agility)
|
134
|
-
return false unless action_succeeds?(player.stats[stat] - dis,
|
135
|
-
@puck_holder.stats[stat])
|
136
|
-
|
137
|
-
switch_team_with_puck
|
138
|
-
@puck_holder = player
|
139
|
-
|
140
|
-
true
|
141
|
-
end
|
142
|
-
|
143
|
-
def try_block_shot(blocker)
|
144
|
-
return false unless action_succeeds?(blocker.stats[:defense],
|
145
|
-
@puck_holder.stats[:offense])
|
146
|
-
|
147
|
-
@shooting_chance += 1
|
148
|
-
@stream << Messages.Shoot(@puck_holder, blocker, try_take_puck(blocker))
|
149
|
-
|
150
|
-
true
|
151
|
-
end
|
152
|
-
|
153
|
-
def puckless_non_goalie(team = @puckless_team)
|
154
|
-
team.roster.values.select do |p|
|
155
|
-
p != team.roster[:goalie] and p != @puck_holder
|
156
|
-
end.sample(random: @prng)
|
157
|
-
end
|
158
|
-
end
|
data/lib/league.rb
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require('yaml')
|
4
|
-
require_relative('./game')
|
5
|
-
|
6
|
-
class League
|
7
|
-
attr_reader(:divisions, :day, :games_in_progress, :playoff_teams, :champion_team)
|
8
|
-
|
9
|
-
class Team
|
10
|
-
private_class_method(:new)
|
11
|
-
|
12
|
-
attr_accessor(:wins, :losses)
|
13
|
-
attr_reader(:name, :emoji, :roster)
|
14
|
-
|
15
|
-
def to_s
|
16
|
-
"#{@emoji} #{@name}"
|
17
|
-
end
|
18
|
-
|
19
|
-
def print_win_loss
|
20
|
-
puts(" #{to_s.ljust 26} #{@wins}-#{@losses}")
|
21
|
-
end
|
22
|
-
|
23
|
-
def print_roster
|
24
|
-
puts(to_s)
|
25
|
-
@roster.each do |pos, player|
|
26
|
-
puts(" #{pos.to_s.ljust(6)}: #{player}")
|
27
|
-
player.stats.each { |stat, value| puts(" #{stat}: #{value.round 1}") }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.sort(teams)
|
32
|
-
teams.sort { |a, b| b.wins - b.losses <=> a.wins - a.losses }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def initialize(season, start_time)
|
37
|
-
# The YAML file is included with the program,
|
38
|
-
# so this is as safe as anything else
|
39
|
-
@divisions = YAML.unsafe_load_file(File.expand_path('data/divisions.yaml',
|
40
|
-
File.dirname(__FILE__)))
|
41
|
-
|
42
|
-
@day = 0
|
43
|
-
@games_in_progress = []
|
44
|
-
@games = []
|
45
|
-
@champion_team = nil
|
46
|
-
@last_update_time = start_time
|
47
|
-
@passed_updates = 0
|
48
|
-
@prng = Random.new(69_420 * season)
|
49
|
-
@shuffled_teams = @divisions.values.reduce(:+).shuffle(random: @prng)
|
50
|
-
@playoff_teams = nil
|
51
|
-
@game_in_matchup = 3
|
52
|
-
end
|
53
|
-
|
54
|
-
def update_state
|
55
|
-
return if @champion_team
|
56
|
-
|
57
|
-
now = Time.at(Time.now.to_i)
|
58
|
-
five_sec_intervals = (now - @last_update_time).div(5)
|
59
|
-
|
60
|
-
return unless five_sec_intervals.positive?
|
61
|
-
|
62
|
-
five_sec_intervals.times do |i|
|
63
|
-
if ((i + @passed_updates) % 720).zero?
|
64
|
-
new_games
|
65
|
-
else
|
66
|
-
update_games
|
67
|
-
end
|
68
|
-
|
69
|
-
break if @champion_team
|
70
|
-
end
|
71
|
-
|
72
|
-
@last_update_time = now
|
73
|
-
@passed_updates += five_sec_intervals
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def new_games
|
79
|
-
if @game_in_matchup != (@day > 38 ? 5 : 3)
|
80
|
-
# New game in matchups
|
81
|
-
@games.map! { |game| Game.new(game.away, game.home, @prng) }
|
82
|
-
@game_in_matchup += 1
|
83
|
-
return
|
84
|
-
end
|
85
|
-
|
86
|
-
# New matchups
|
87
|
-
@games.clear
|
88
|
-
@game_in_matchup = 1
|
89
|
-
@day += 1
|
90
|
-
|
91
|
-
case @day <=> 39
|
92
|
-
when -1
|
93
|
-
(@shuffled_teams.length / 2).times do |i|
|
94
|
-
pair = [@shuffled_teams[i], @shuffled_teams[-i - 1]]
|
95
|
-
@games << Game.new(*(@day > 19 ? pair : pair.reverse), @prng)
|
96
|
-
end
|
97
|
-
|
98
|
-
@shuffled_teams.insert 1, @shuffled_teams.pop
|
99
|
-
when 0
|
100
|
-
@playoff_teams = Team.sort(
|
101
|
-
@divisions.values.map do |teams|
|
102
|
-
Team.sort(teams).first(2)
|
103
|
-
end.reduce(:+)
|
104
|
-
).map(&:clone)
|
105
|
-
|
106
|
-
new_playoff_matchups
|
107
|
-
when 1
|
108
|
-
@playoff_teams = Team.sort(@playoff_teams).first(@playoff_teams.length / 2)
|
109
|
-
|
110
|
-
if @playoff_teams.length == 1
|
111
|
-
@champion_team = @playoff_teams[0]
|
112
|
-
return
|
113
|
-
end
|
114
|
-
|
115
|
-
new_playoff_matchups
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def update_games
|
120
|
-
@games_in_progress.each(&:update)
|
121
|
-
@games_in_progress = @games.select(&:in_progress)
|
122
|
-
end
|
123
|
-
|
124
|
-
def new_playoff_matchups
|
125
|
-
@playoff_teams.each do |team|
|
126
|
-
team.wins = 0
|
127
|
-
team.losses = 0
|
128
|
-
end
|
129
|
-
|
130
|
-
(0...@playoff_teams.length).step(2) do |i|
|
131
|
-
@games << Game.new(*@playoff_teams[i, 2], @prng)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
class Player
|
136
|
-
private_class_method(:new)
|
137
|
-
|
138
|
-
attr_reader(:stats, :to_s)
|
139
|
-
end
|
140
|
-
end
|
data/lib/messages.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Messages
|
4
|
-
def initialize(event, fields, data)
|
5
|
-
@event = event
|
6
|
-
fields.zip(data) do |(f, d)|
|
7
|
-
instance_variable_set("@#{f}", d)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
private_class_method(:new)
|
12
|
-
|
13
|
-
class << self
|
14
|
-
[
|
15
|
-
%i[StartOfGame title],
|
16
|
-
%i[EndOfGame winning_team],
|
17
|
-
%i[StartOfPeriod period],
|
18
|
-
%i[EndOfPeriod period home away home_score away_score],
|
19
|
-
%i[FaceOff winning_player],
|
20
|
-
%i[Hit puck_holder defender puck_taken],
|
21
|
-
%i[Pass sender receiver interceptor],
|
22
|
-
%i[Shoot shooter blocker puck_taken home away home_score away_score]
|
23
|
-
].each do |event, *fields|
|
24
|
-
define_method event do |*data|
|
25
|
-
new(event, fields, data)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_s
|
31
|
-
case @event
|
32
|
-
when :StartOfGame
|
33
|
-
"#{@title}\nHocky!"
|
34
|
-
when :EndOfGame
|
35
|
-
"Game over.\n#{@winning_team} wins!"
|
36
|
-
when :StartOfPeriod
|
37
|
-
"Start#{of_period}"
|
38
|
-
when :EndOfPeriod
|
39
|
-
"End#{of_period}#{score}"
|
40
|
-
when :FaceOff
|
41
|
-
"#{@winning_player} wins the faceoff!"
|
42
|
-
when :Hit
|
43
|
-
"#{@defender} hits #{@puck_holder.to_s + takes}!"
|
44
|
-
when :Pass
|
45
|
-
"#{@sender} passes to #{@receiver}" +
|
46
|
-
(@interceptor ? "... intercepted by #{@interceptor}!" : '.')
|
47
|
-
when :Shoot
|
48
|
-
"#{@shooter} takes a shot... " +
|
49
|
-
(@blocker ? "#{@blocker} blocks the shot#{takes}!" : "and scores!#{score}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def of_period
|
56
|
-
" of period #{@period}."
|
57
|
-
end
|
58
|
-
|
59
|
-
def score
|
60
|
-
"\n#{@home} #{@home_score}, #{@away} #{@away_score}"
|
61
|
-
end
|
62
|
-
|
63
|
-
def takes
|
64
|
-
@puck_taken ? ' and takes the puck' : ''
|
65
|
-
end
|
66
|
-
end
|