hlockey 0.1 → 1
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/bin/hlockey +41 -33
- data/lib/data/divisions.yaml +845 -0
- data/lib/data/season.rb +27 -0
- data/lib/game.rb +35 -28
- data/lib/league.rb +68 -58
- data/lib/messages.rb +25 -25
- metadata +5 -6
- data/lib/data/names_markov_chain.rb +0 -634
- data/lib/player.rb +0 -41
- data/lib/team.rb +0 -46
data/lib/data/season.rb
ADDED
@@ -0,0 +1,27 @@
|
|
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
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative('./messages')
|
2
4
|
|
3
5
|
class Game
|
4
|
-
attr_reader
|
6
|
+
attr_reader(:home, :away, :stream, :in_progress)
|
5
7
|
|
6
|
-
def initialize
|
7
|
-
@home
|
8
|
+
def initialize(home, away, prng)
|
9
|
+
@home = home
|
10
|
+
@away = away
|
11
|
+
@prng = prng
|
8
12
|
@stream = [Messages.StartOfGame(to_s)]
|
9
13
|
@in_progress = true
|
10
|
-
@score = {
|
11
|
-
:home => 0,
|
12
|
-
:away => 0
|
13
|
-
}
|
14
|
+
@score = { home: 0, away: 0 }
|
14
15
|
@actions = 0
|
15
16
|
@period = 1
|
16
17
|
@face_off = true
|
@@ -27,7 +28,7 @@ class Game
|
|
27
28
|
def update
|
28
29
|
return unless @in_progress
|
29
30
|
|
30
|
-
@stream << Messages.StartOfPeriod(@period) if @actions
|
31
|
+
@stream << Messages.StartOfPeriod(@period) if @actions.zero?
|
31
32
|
@actions += 1
|
32
33
|
|
33
34
|
if @actions == 60
|
@@ -36,7 +37,7 @@ class Game
|
|
36
37
|
@period += 1
|
37
38
|
start_faceoff
|
38
39
|
|
39
|
-
if @period > 3
|
40
|
+
if @period > 3 && !(@score[:home] == @score[:away] && @period < 12)
|
40
41
|
# Game is over
|
41
42
|
@in_progress = false
|
42
43
|
|
@@ -50,12 +51,15 @@ class Game
|
|
50
51
|
end
|
51
52
|
|
52
53
|
if @face_off
|
53
|
-
if @puck_holder
|
54
|
+
if @puck_holder.nil?
|
54
55
|
# Pass opposite team to who wins the puck to switch_team_with_puck,
|
55
56
|
# so @team_with_puck & @puckless_team are set to the correct values.
|
56
|
-
switch_team_with_puck(
|
57
|
-
|
58
|
-
|
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)
|
59
63
|
|
60
64
|
@puck_holder = @team_with_puck.roster[:center]
|
61
65
|
@stream << Messages.FaceOff(@puck_holder)
|
@@ -74,9 +78,9 @@ class Game
|
|
74
78
|
@stream << Messages.Hit(@puck_holder, defender,
|
75
79
|
try_take_puck(defender, 0, :defense))
|
76
80
|
else # Shoot
|
77
|
-
unless @shooting_chance < 5
|
78
|
-
|
79
|
-
|
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])
|
80
84
|
@score[@team_with_puck == @home ? :home : :away] += 1
|
81
85
|
|
82
86
|
@stream << Messages.Shoot(@puck_holder,
|
@@ -85,13 +89,14 @@ class Game
|
|
85
89
|
*@score.values)
|
86
90
|
|
87
91
|
start_faceoff
|
88
|
-
@actions =
|
92
|
+
@actions = 59 if @period > 3 # Sudden death overtime
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
92
96
|
|
93
97
|
private
|
94
|
-
|
98
|
+
|
99
|
+
def action_succeeds?(helping_stat, hindering_stat)
|
95
100
|
@prng.rand(10) + helping_stat - hindering_stat > 4
|
96
101
|
end
|
97
102
|
|
@@ -101,9 +106,12 @@ class Game
|
|
101
106
|
@puck_holder = nil
|
102
107
|
end
|
103
108
|
|
104
|
-
def switch_team_with_puck
|
105
|
-
@team_with_puck, @puckless_team = team_with_puck == @home
|
106
|
-
|
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
|
107
115
|
@shooting_chance = 0
|
108
116
|
end
|
109
117
|
|
@@ -112,7 +120,7 @@ class Game
|
|
112
120
|
receiver = puckless_non_goalie(@team_with_puck)
|
113
121
|
interceptor = puckless_non_goalie
|
114
122
|
|
115
|
-
if
|
123
|
+
if !@face_off && try_take_puck(interceptor, 4) # Pass intercepted
|
116
124
|
@stream << Messages.Pass(sender, receiver, interceptor)
|
117
125
|
return
|
118
126
|
end
|
@@ -122,7 +130,7 @@ class Game
|
|
122
130
|
@shooting_chance += 1
|
123
131
|
end
|
124
132
|
|
125
|
-
def try_take_puck
|
133
|
+
def try_take_puck(player, dis = 0, stat = :agility)
|
126
134
|
return false unless action_succeeds?(player.stats[stat] - dis,
|
127
135
|
@puck_holder.stats[stat])
|
128
136
|
|
@@ -132,7 +140,7 @@ class Game
|
|
132
140
|
true
|
133
141
|
end
|
134
142
|
|
135
|
-
def try_block_shot
|
143
|
+
def try_block_shot(blocker)
|
136
144
|
return false unless action_succeeds?(blocker.stats[:defense],
|
137
145
|
@puck_holder.stats[:offense])
|
138
146
|
|
@@ -142,10 +150,9 @@ class Game
|
|
142
150
|
true
|
143
151
|
end
|
144
152
|
|
145
|
-
def puckless_non_goalie
|
153
|
+
def puckless_non_goalie(team = @puckless_team)
|
146
154
|
team.roster.values.select do |p|
|
147
155
|
p != team.roster[:goalie] and p != @puck_holder
|
148
|
-
end.sample
|
156
|
+
end.sample(random: @prng)
|
149
157
|
end
|
150
158
|
end
|
151
|
-
|
data/lib/league.rb
CHANGED
@@ -1,48 +1,52 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('yaml')
|
4
|
+
require_relative('./game')
|
3
5
|
|
4
6
|
class League
|
5
|
-
attr_reader
|
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__)))
|
6
41
|
|
7
|
-
def initialize season, start_time
|
8
42
|
@day = 0
|
9
|
-
@divisions = {
|
10
|
-
"Wet Warm": [
|
11
|
-
Team.new("Antalya Pirates", "🌊"),
|
12
|
-
Team.new("Baden Hallucinations", "🍄"),
|
13
|
-
Team.new("Kópavogur Seals", "🦭"),
|
14
|
-
Team.new("Lagos Soup", "🥣"),
|
15
|
-
Team.new("Pica Acid", "🧪")
|
16
|
-
],
|
17
|
-
"Dry Warm": [
|
18
|
-
Team.new("Dawson City Impostors", "🔪"),
|
19
|
-
Team.new("Erlangen Ohms", "🇴"),
|
20
|
-
Team.new("Pompei Eruptions", "🌋"),
|
21
|
-
Team.new("Rio de Janeiro Directors", "🎦"),
|
22
|
-
Team.new("Wyrzysk Rockets", "🚀")
|
23
|
-
],
|
24
|
-
"Wet Cool": [
|
25
|
-
Team.new("Cape Town Transplants", "🌱"),
|
26
|
-
Team.new("Manbij Fish", "🐠"),
|
27
|
-
Team.new("Nagqu Paint", "🎨"),
|
28
|
-
Team.new("Nice Backflippers", "🔄"),
|
29
|
-
Team.new("Orcadas Base Fog", "🌁")
|
30
|
-
],
|
31
|
-
"Dry Cool": [
|
32
|
-
Team.new("Baghdad Abacuses", "🧮"),
|
33
|
-
Team.new("Jakarta Architects", "📐"),
|
34
|
-
Team.new("Kyoto Payphones", "📳"),
|
35
|
-
Team.new("Stony Brook Reapers", "💀"),
|
36
|
-
Team.new("Sydney Thinkers", "🤔")
|
37
|
-
]
|
38
|
-
}
|
39
43
|
@games_in_progress = []
|
40
44
|
@games = []
|
41
45
|
@champion_team = nil
|
42
46
|
@last_update_time = start_time
|
43
47
|
@passed_updates = 0
|
44
|
-
@prng = Random.new
|
45
|
-
@shuffled_teams = @divisions.values.reduce(:+).shuffle
|
48
|
+
@prng = Random.new(69_420 * season)
|
49
|
+
@shuffled_teams = @divisions.values.reduce(:+).shuffle(random: @prng)
|
46
50
|
@playoff_teams = nil
|
47
51
|
@game_in_matchup = 3
|
48
52
|
end
|
@@ -50,30 +54,31 @@ class League
|
|
50
54
|
def update_state
|
51
55
|
return if @champion_team
|
52
56
|
|
53
|
-
now = Time.now
|
54
|
-
five_sec_intervals = (now - @last_update_time).
|
57
|
+
now = Time.at(Time.now.to_i)
|
58
|
+
five_sec_intervals = (now - @last_update_time).div(5)
|
55
59
|
|
56
|
-
|
57
|
-
five_sec_intervals.times do |i|
|
58
|
-
if (i + @passed_updates) % 720 == 0
|
59
|
-
new_games
|
60
|
-
else
|
61
|
-
update_games
|
62
|
-
end
|
60
|
+
return unless five_sec_intervals.positive?
|
63
61
|
|
64
|
-
|
62
|
+
five_sec_intervals.times do |i|
|
63
|
+
if ((i + @passed_updates) % 720).zero?
|
64
|
+
new_games
|
65
|
+
else
|
66
|
+
update_games
|
65
67
|
end
|
66
68
|
|
67
|
-
|
68
|
-
@passed_updates += five_sec_intervals
|
69
|
+
break if @champion_team
|
69
70
|
end
|
71
|
+
|
72
|
+
@last_update_time = now
|
73
|
+
@passed_updates += five_sec_intervals
|
70
74
|
end
|
71
75
|
|
72
76
|
private
|
77
|
+
|
73
78
|
def new_games
|
74
79
|
if @game_in_matchup != (@day > 38 ? 5 : 3)
|
75
80
|
# New game in matchups
|
76
|
-
@games.map!
|
81
|
+
@games.map! { |game| Game.new(game.away, game.home, @prng) }
|
77
82
|
@game_in_matchup += 1
|
78
83
|
return
|
79
84
|
end
|
@@ -92,15 +97,15 @@ class League
|
|
92
97
|
|
93
98
|
@shuffled_teams.insert 1, @shuffled_teams.pop
|
94
99
|
when 0
|
95
|
-
@playoff_teams = Team.
|
100
|
+
@playoff_teams = Team.sort(
|
96
101
|
@divisions.values.map do |teams|
|
97
|
-
Team.
|
98
|
-
end.reduce(:+)
|
99
|
-
)
|
102
|
+
Team.sort(teams).first(2)
|
103
|
+
end.reduce(:+)
|
104
|
+
).map(&:clone)
|
100
105
|
|
101
106
|
new_playoff_matchups
|
102
107
|
when 1
|
103
|
-
@playoff_teams = Team.
|
108
|
+
@playoff_teams = Team.sort(@playoff_teams).first(@playoff_teams.length / 2)
|
104
109
|
|
105
110
|
if @playoff_teams.length == 1
|
106
111
|
@champion_team = @playoff_teams[0]
|
@@ -112,8 +117,8 @@ class League
|
|
112
117
|
end
|
113
118
|
|
114
119
|
def update_games
|
115
|
-
@games_in_progress
|
116
|
-
@games_in_progress
|
120
|
+
@games_in_progress.each(&:update)
|
121
|
+
@games_in_progress = @games.select(&:in_progress)
|
117
122
|
end
|
118
123
|
|
119
124
|
def new_playoff_matchups
|
@@ -122,9 +127,14 @@ class League
|
|
122
127
|
team.losses = 0
|
123
128
|
end
|
124
129
|
|
125
|
-
(0...@playoff_teams.length).step
|
130
|
+
(0...@playoff_teams.length).step(2) do |i|
|
126
131
|
@games << Game.new(*@playoff_teams[i, 2], @prng)
|
127
132
|
end
|
128
133
|
end
|
129
|
-
end
|
130
134
|
|
135
|
+
class Player
|
136
|
+
private_class_method(:new)
|
137
|
+
|
138
|
+
attr_reader(:stats, :to_s)
|
139
|
+
end
|
140
|
+
end
|
data/lib/messages.rb
CHANGED
@@ -1,26 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Messages
|
2
|
-
def initialize
|
4
|
+
def initialize(event, fields, data)
|
3
5
|
@event = event
|
4
|
-
fields.zip
|
5
|
-
instance_variable_set
|
6
|
+
fields.zip(data) do |(f, d)|
|
7
|
+
instance_variable_set("@#{f}", d)
|
6
8
|
end
|
7
9
|
end
|
8
10
|
|
9
|
-
private_class_method
|
11
|
+
private_class_method(:new)
|
10
12
|
|
11
13
|
class << self
|
12
14
|
[
|
13
|
-
[
|
14
|
-
[
|
15
|
-
[
|
16
|
-
[
|
17
|
-
[
|
18
|
-
[
|
19
|
-
[
|
20
|
-
[
|
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]
|
21
23
|
].each do |event, *fields|
|
22
24
|
define_method event do |*data|
|
23
|
-
new
|
25
|
+
new(event, fields, data)
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -28,29 +30,28 @@ class Messages
|
|
28
30
|
def to_s
|
29
31
|
case @event
|
30
32
|
when :StartOfGame
|
31
|
-
@title
|
33
|
+
"#{@title}\nHocky!"
|
32
34
|
when :EndOfGame
|
33
|
-
"Game over.\n#{@winning_team
|
35
|
+
"Game over.\n#{@winning_team} wins!"
|
34
36
|
when :StartOfPeriod
|
35
|
-
"Start"
|
37
|
+
"Start#{of_period}"
|
36
38
|
when :EndOfPeriod
|
37
|
-
"End
|
39
|
+
"End#{of_period}#{score}"
|
38
40
|
when :FaceOff
|
39
41
|
"#{@winning_player} wins the faceoff!"
|
40
42
|
when :Hit
|
41
43
|
"#{@defender} hits #{@puck_holder.to_s + takes}!"
|
42
44
|
when :Pass
|
43
|
-
"#{@sender} passes to #{@receiver}
|
44
|
-
"... intercepted by #{@interceptor}!" :
|
45
|
+
"#{@sender} passes to #{@receiver}" +
|
46
|
+
(@interceptor ? "... intercepted by #{@interceptor}!" : '.')
|
45
47
|
when :Shoot
|
46
|
-
"#{@shooter} takes a shot...
|
47
|
-
"#{@blocker} blocks the shot#{takes}!" : "and scores
|
48
|
-
else
|
49
|
-
raise "Unknown message"
|
48
|
+
"#{@shooter} takes a shot... " +
|
49
|
+
(@blocker ? "#{@blocker} blocks the shot#{takes}!" : "and scores!#{score}")
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
private
|
54
|
+
|
54
55
|
def of_period
|
55
56
|
" of period #{@period}."
|
56
57
|
end
|
@@ -60,7 +61,6 @@ class Messages
|
|
60
61
|
end
|
61
62
|
|
62
63
|
def takes
|
63
|
-
@puck_taken ?
|
64
|
+
@puck_taken ? ' and takes the puck' : ''
|
64
65
|
end
|
65
66
|
end
|
66
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hlockey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '1'
|
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-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Hockey sports sim.
|
14
14
|
email: endie2@protonmail.com
|
@@ -18,12 +18,11 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- bin/hlockey
|
21
|
-
- lib/data/
|
21
|
+
- lib/data/divisions.yaml
|
22
|
+
- lib/data/season.rb
|
22
23
|
- lib/game.rb
|
23
24
|
- lib/league.rb
|
24
25
|
- lib/messages.rb
|
25
|
-
- lib/player.rb
|
26
|
-
- lib/team.rb
|
27
26
|
homepage: https://github.com/Lavender-Perry/hlockey
|
28
27
|
licenses:
|
29
28
|
- LicenseRef-LICENSE.md
|
@@ -47,5 +46,5 @@ requirements: []
|
|
47
46
|
rubygems_version: 3.3.7
|
48
47
|
signing_key:
|
49
48
|
specification_version: 4
|
50
|
-
summary: Hlockey season
|
49
|
+
summary: Hlockey season 1.
|
51
50
|
test_files: []
|