hlockey 1 → 2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/hlockey +45 -31
- data/lib/hlockey/data/election.yaml +16 -0
- data/lib/hlockey/data/information.yaml +12 -0
- data/lib/hlockey/data/league.yaml +846 -0
- data/lib/hlockey/data/links.yaml +2 -0
- data/lib/hlockey/data.rb +13 -0
- data/lib/hlockey/game.rb +159 -0
- data/lib/hlockey/league.rb +115 -0
- data/lib/hlockey/messages.rb +100 -0
- data/lib/hlockey/team.rb +35 -0
- data/lib/hlockey/version.rb +5 -0
- data/lib/hlockey.rb +3 -0
- metadata +17 -11
- 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/hlockey/data.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('yaml')
|
4
|
+
|
5
|
+
module Hlockey
|
6
|
+
# Loads data for the current Hlockey season.
|
7
|
+
# +category+ can be 'league', 'election', 'information', or 'links'.
|
8
|
+
# If it is anything else, there will be an error.
|
9
|
+
def Hlockey.load_data(category)
|
10
|
+
# If this is only used on data included with the gem, it should be safe
|
11
|
+
YAML.unsafe_load_file(File.expand_path("data/#{category}.yaml", __dir__))
|
12
|
+
end
|
13
|
+
end
|
data/lib/hlockey/game.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('hlockey/messages')
|
4
|
+
|
5
|
+
module Hlockey
|
6
|
+
class Game
|
7
|
+
attr_reader(:home, :away, :stream, :score, :in_progress)
|
8
|
+
|
9
|
+
def initialize(home, away, prng)
|
10
|
+
@home = home
|
11
|
+
@away = away
|
12
|
+
@prng = prng
|
13
|
+
@stream = [Messages.StartOfGame]
|
14
|
+
@score = { home: 0, away: 0 }
|
15
|
+
@in_progress = true
|
16
|
+
@actions = 0
|
17
|
+
@period = 1
|
18
|
+
@face_off = true
|
19
|
+
@team_with_puck = nil
|
20
|
+
@puckless_team = nil
|
21
|
+
@puck_holder = nil
|
22
|
+
@shooting_chance = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{@home} vs #{@away}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
return unless @in_progress
|
31
|
+
|
32
|
+
@stream << Messages.StartOfPeriod(@period) if @actions.zero?
|
33
|
+
@actions += 1
|
34
|
+
|
35
|
+
if @actions == 60
|
36
|
+
@stream << Messages.EndOfPeriod(@period, @home, @away, *@score.values)
|
37
|
+
@actions = 0
|
38
|
+
@period += 1
|
39
|
+
start_faceoff
|
40
|
+
|
41
|
+
if @period > 3 && !(@score[:home] == @score[:away] && @period < 12)
|
42
|
+
# Game is over
|
43
|
+
@in_progress = false
|
44
|
+
|
45
|
+
winner, loser = @score[:away] > @score[:home] ?
|
46
|
+
[@away, @home] : [@home, @away]
|
47
|
+
|
48
|
+
@stream << Messages.EndOfGame(winner)
|
49
|
+
winner.wins += 1
|
50
|
+
loser.losses += 1
|
51
|
+
end
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
if @face_off
|
56
|
+
if @puck_holder.nil?
|
57
|
+
# Pass opposite team to who wins the puck to switch_team_with_puck,
|
58
|
+
# so @team_with_puck & @puckless_team are set to the correct values.
|
59
|
+
switch_team_with_puck(
|
60
|
+
action_succeeds?(@home.roster[:center].stats[:offense],
|
61
|
+
@away.roster[:center].stats[:offense]) ? @away : @home
|
62
|
+
)
|
63
|
+
|
64
|
+
@puck_holder = @team_with_puck.roster[:center]
|
65
|
+
@stream << Messages.FaceOff(@puck_holder, @team_with_puck)
|
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
|
+
check
|
78
|
+
else # Shoot
|
79
|
+
unless @shooting_chance < 5 &&
|
80
|
+
try_block_shot(@puckless_team.roster[@prng.rand(2).zero? ?
|
81
|
+
:ldef : :rdef]) ||
|
82
|
+
try_block_shot(@puckless_team.roster[:goalie])
|
83
|
+
@score[@team_with_puck == @home ? :home : :away] += 1
|
84
|
+
|
85
|
+
@stream << Messages.ShootScore(@puck_holder, @home, @away, *@score.values)
|
86
|
+
|
87
|
+
start_faceoff
|
88
|
+
@actions = 59 if @period > 3 # Sudden death overtime
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def action_succeeds?(helping_stat, hindering_stat)
|
96
|
+
@prng.rand(10) + helping_stat - hindering_stat > 4
|
97
|
+
end
|
98
|
+
|
99
|
+
def start_faceoff
|
100
|
+
@shooting_chance = 0
|
101
|
+
@face_off = true
|
102
|
+
@puck_holder = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def switch_team_with_puck(team_with_puck = @team_with_puck)
|
106
|
+
@team_with_puck, @puckless_team = team_with_puck == @home ?
|
107
|
+
[@away, @home] : [@home, @away]
|
108
|
+
@shooting_chance = 0
|
109
|
+
end
|
110
|
+
|
111
|
+
def pass
|
112
|
+
sender = @puck_holder
|
113
|
+
receiver = puckless_non_goalie(@team_with_puck)
|
114
|
+
interceptor = puckless_non_goalie
|
115
|
+
|
116
|
+
if !@face_off && try_take_puck(interceptor, 4) # Pass intercepted
|
117
|
+
@stream << Messages.Pass(sender, receiver, interceptor, @team_with_puck)
|
118
|
+
return
|
119
|
+
end
|
120
|
+
|
121
|
+
@stream << Messages.Pass(sender, receiver)
|
122
|
+
@puck_holder = receiver
|
123
|
+
@shooting_chance += 1
|
124
|
+
end
|
125
|
+
|
126
|
+
def check
|
127
|
+
defender = puckless_non_goalie
|
128
|
+
@stream << Messages.Hit(@puck_holder, defender,
|
129
|
+
try_take_puck(defender, 0, :defense), @team_with_puck)
|
130
|
+
end
|
131
|
+
|
132
|
+
def try_take_puck(player, dis = 0, stat = :agility)
|
133
|
+
return false unless action_succeeds?(player.stats[stat] - dis,
|
134
|
+
@puck_holder.stats[stat])
|
135
|
+
|
136
|
+
switch_team_with_puck
|
137
|
+
@puck_holder = player
|
138
|
+
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
def try_block_shot(blocker)
|
143
|
+
return false unless action_succeeds?(blocker.stats[:defense],
|
144
|
+
@puck_holder.stats[:offense])
|
145
|
+
|
146
|
+
@shooting_chance += 1
|
147
|
+
@stream << Messages.ShootBlock(@puck_holder, blocker,
|
148
|
+
try_take_puck(blocker), @team_with_puck)
|
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
|
159
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('hlockey/data')
|
4
|
+
require('hlockey/game')
|
5
|
+
require('hlockey/team')
|
6
|
+
require('hlockey/version')
|
7
|
+
|
8
|
+
module Hlockey
|
9
|
+
class League
|
10
|
+
attr_reader(:start_time, :divisions, :teams, :day,
|
11
|
+
:games, :games_in_progress, :playoff_teams, :champion_team)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@start_time, @divisions = Hlockey.load_data('league')
|
15
|
+
@start_time.localtime
|
16
|
+
@day = 0
|
17
|
+
@games_in_progress = []
|
18
|
+
@games = []
|
19
|
+
@champion_team = nil
|
20
|
+
@last_update_time = @start_time
|
21
|
+
@passed_updates = 0
|
22
|
+
@prng = Random.new(69_420 * VERSION.to_i)
|
23
|
+
@teams = @divisions.values.reduce(:+)
|
24
|
+
@shuffled_teams = teams.shuffle(random: @prng)
|
25
|
+
@playoff_teams = nil
|
26
|
+
@game_in_matchup = 3
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_state
|
30
|
+
return if @champion_team
|
31
|
+
|
32
|
+
now = Time.at(Time.now.to_i)
|
33
|
+
five_sec_intervals = (now - @last_update_time).div(5)
|
34
|
+
|
35
|
+
return unless five_sec_intervals.positive?
|
36
|
+
|
37
|
+
five_sec_intervals.times do |i|
|
38
|
+
if ((i + @passed_updates) % 720).zero?
|
39
|
+
new_games
|
40
|
+
else
|
41
|
+
update_games
|
42
|
+
end
|
43
|
+
|
44
|
+
break if @champion_team
|
45
|
+
end
|
46
|
+
|
47
|
+
@last_update_time = now
|
48
|
+
@passed_updates += five_sec_intervals
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def new_games
|
54
|
+
if @game_in_matchup != (@day > 38 ? 5 : 3)
|
55
|
+
# New game in matchups
|
56
|
+
@games.map! { |game| Game.new(game.away, game.home, @prng) }
|
57
|
+
@game_in_matchup += 1
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
# New matchups
|
62
|
+
@games.clear
|
63
|
+
@game_in_matchup = 1
|
64
|
+
@day += 1
|
65
|
+
|
66
|
+
case @day <=> 39
|
67
|
+
when -1
|
68
|
+
(@shuffled_teams.length / 2).times do |i|
|
69
|
+
pair = [@shuffled_teams[i], @shuffled_teams[-i - 1]]
|
70
|
+
@games << Game.new(*(@day > 19 ? pair : pair.reverse), @prng)
|
71
|
+
end
|
72
|
+
|
73
|
+
@shuffled_teams.insert 1, @shuffled_teams.pop
|
74
|
+
when 0
|
75
|
+
@playoff_teams = sort_teams_by_wins(
|
76
|
+
@divisions.values.map do |teams|
|
77
|
+
sort_teams_by_wins(teams).first(2)
|
78
|
+
end.reduce(:+)
|
79
|
+
).map(&:clone)
|
80
|
+
|
81
|
+
new_playoff_matchups
|
82
|
+
when 1
|
83
|
+
@playoff_teams.select! { |team| team.wins > team.losses }
|
84
|
+
|
85
|
+
if @playoff_teams.length == 1
|
86
|
+
@champion_team = @playoff_teams[0]
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
new_playoff_matchups
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def update_games
|
95
|
+
@games_in_progress.each(&:update)
|
96
|
+
@games_in_progress = @games.select(&:in_progress)
|
97
|
+
@divisions.transform_values!(&method(:sort_teams_by_wins))
|
98
|
+
end
|
99
|
+
|
100
|
+
def new_playoff_matchups
|
101
|
+
@playoff_teams.each do |team|
|
102
|
+
team.wins = 0
|
103
|
+
team.losses = 0
|
104
|
+
end
|
105
|
+
|
106
|
+
(@playoff_teams.length / 2).times do |i|
|
107
|
+
@games << Game.new(@playoff_teams[i], @playoff_teams[-i - 1], @prng)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def sort_teams_by_wins(teams)
|
112
|
+
teams.sort { |a, b| b.wins <=> a.wins }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('hlockey/version')
|
4
|
+
|
5
|
+
module Hlockey
|
6
|
+
class Messages
|
7
|
+
private_class_method(:new)
|
8
|
+
|
9
|
+
def initialize(event, fields, data)
|
10
|
+
@event = event
|
11
|
+
fields.zip(data) do |(f, d)|
|
12
|
+
instance_variable_set("@#{f}", d)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# These are messages logged to game streams
|
18
|
+
|
19
|
+
[
|
20
|
+
%i[StartOfGame],
|
21
|
+
%i[EndOfGame winning_team],
|
22
|
+
%i[StartOfPeriod period],
|
23
|
+
%i[EndOfPeriod period home away home_score away_score],
|
24
|
+
%i[FaceOff winning_player new_puck_team],
|
25
|
+
%i[Hit puck_holder defender puck_taken new_puck_team],
|
26
|
+
%i[Pass sender receiver interceptor new_puck_team],
|
27
|
+
%i[ShootScore shooter home away home_score away_score],
|
28
|
+
%i[ShootBlock shooter blocker puck_taken new_puck_team]
|
29
|
+
].each do |event, *fields|
|
30
|
+
define_method(event) do |*data|
|
31
|
+
new(event, fields, data)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# These are messages used elsewhere
|
36
|
+
|
37
|
+
def SeasonDay(day)
|
38
|
+
"Season #{VERSION} day #{day}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def SeasonStarts(time)
|
42
|
+
time.strftime("Season #{VERSION} starts at %H:%M, %A, %B %d (%Z).")
|
43
|
+
end
|
44
|
+
|
45
|
+
def SeasonChampion(team)
|
46
|
+
"Your season #{VERSION} champions are the #{team}!"
|
47
|
+
end
|
48
|
+
|
49
|
+
def NoGames
|
50
|
+
'no games right now. it is the offseason. join the Hlockey Discord for updates'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
case @event
|
56
|
+
when :StartOfGame
|
57
|
+
'Hocky!'
|
58
|
+
when :EndOfGame
|
59
|
+
"Game over.\n#{@winning_team} win!"
|
60
|
+
when :StartOfPeriod
|
61
|
+
"Start#{of_period}"
|
62
|
+
when :EndOfPeriod
|
63
|
+
"End#{of_period}#{score}"
|
64
|
+
when :FaceOff
|
65
|
+
"#{@winning_player} wins the faceoff!#{possession_change}"
|
66
|
+
when :Hit
|
67
|
+
"#{@defender} hits #{@puck_holder}#{takes}"
|
68
|
+
when :Pass
|
69
|
+
"#{@sender} passes to #{@receiver}." +
|
70
|
+
"#{"..\nIntercepted by #{@interceptor}!#{possession_change}" if @interceptor}"
|
71
|
+
when :ShootScore
|
72
|
+
"#{shot} and scores!#{score}"
|
73
|
+
when :ShootBlock
|
74
|
+
"#{shot}...\n#{@blocker} blocks the shot#{takes}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def of_period
|
81
|
+
" of period #{@period}."
|
82
|
+
end
|
83
|
+
|
84
|
+
def score
|
85
|
+
"\n#{@home} #{@home_score}, #{@away} #{@away_score}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def shot
|
89
|
+
"#{@shooter} takes a shot"
|
90
|
+
end
|
91
|
+
|
92
|
+
def takes
|
93
|
+
@puck_taken ? " and takes the puck!#{possession_change}" : '!'
|
94
|
+
end
|
95
|
+
|
96
|
+
def possession_change
|
97
|
+
"\n#{@new_puck_team} have possession."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/hlockey/team.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
# Created by League when data is loaded
|
5
|
+
class Team
|
6
|
+
private_class_method(:new)
|
7
|
+
attr_accessor(:wins, :losses, :roster)
|
8
|
+
attr_reader(:to_s, :emoji)
|
9
|
+
|
10
|
+
def w_l
|
11
|
+
"#{@wins}-#{@losses}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Created by League when data is loaded
|
15
|
+
class Player
|
16
|
+
attr_accessor(:stats, :to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Takes symbol from Team roster & converts it to full position name
|
21
|
+
def Hlockey.pos_name(pos)
|
22
|
+
case pos
|
23
|
+
when :lwing
|
24
|
+
'Left wing'
|
25
|
+
when :rwing
|
26
|
+
'Right wing'
|
27
|
+
when :ldef
|
28
|
+
'Left defender'
|
29
|
+
when :rdef
|
30
|
+
'Right defender'
|
31
|
+
else
|
32
|
+
pos.capitalize.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/hlockey.rb
ADDED
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hlockey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lavender Perry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description: Hlockey library and console application.
|
14
14
|
email: endie2@protonmail.com
|
15
15
|
executables:
|
16
16
|
- hlockey
|
@@ -18,16 +18,22 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- bin/hlockey
|
21
|
-
- lib/
|
22
|
-
- lib/data
|
23
|
-
- lib/
|
24
|
-
- lib/
|
25
|
-
- lib/
|
26
|
-
|
21
|
+
- lib/hlockey.rb
|
22
|
+
- lib/hlockey/data.rb
|
23
|
+
- lib/hlockey/data/election.yaml
|
24
|
+
- lib/hlockey/data/information.yaml
|
25
|
+
- lib/hlockey/data/league.yaml
|
26
|
+
- lib/hlockey/data/links.yaml
|
27
|
+
- lib/hlockey/game.rb
|
28
|
+
- lib/hlockey/league.rb
|
29
|
+
- lib/hlockey/messages.rb
|
30
|
+
- lib/hlockey/team.rb
|
31
|
+
- lib/hlockey/version.rb
|
32
|
+
homepage: https://hlockey.herokuapp.com
|
27
33
|
licenses:
|
28
34
|
- LicenseRef-LICENSE.md
|
29
35
|
metadata:
|
30
|
-
source_code_uri: https://github.com/
|
36
|
+
source_code_uri: https://github.com/Hlockey/hlockey
|
31
37
|
post_install_message:
|
32
38
|
rdoc_options: []
|
33
39
|
require_paths:
|
@@ -46,5 +52,5 @@ requirements: []
|
|
46
52
|
rubygems_version: 3.3.7
|
47
53
|
signing_key:
|
48
54
|
specification_version: 4
|
49
|
-
summary: Hlockey season
|
55
|
+
summary: Hlockey season 2.
|
50
56
|
test_files: []
|