hlockey 0.1 → 1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|