hlockey 0 → 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 +62 -40
- 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 -12
- data/lib/data/names_markov_chain.rb +0 -634
- data/lib/game.rb +0 -153
- data/lib/league.rb +0 -130
- data/lib/messages.rb +0 -64
- data/lib/player.rb +0 -41
- data/lib/team.rb +0 -46
data/lib/game.rb
DELETED
@@ -1,153 +0,0 @@
|
|
1
|
-
require_relative "./messages"
|
2
|
-
|
3
|
-
class Game
|
4
|
-
attr_reader :stream, :in_progress
|
5
|
-
|
6
|
-
def initialize home, away, prng
|
7
|
-
@stream = [Messages.StartOfGame(to_s)]
|
8
|
-
@in_progress = true
|
9
|
-
@home, @away, @prng = [home, away, prng]
|
10
|
-
@score = {
|
11
|
-
:home => 0,
|
12
|
-
:away => 0
|
13
|
-
}
|
14
|
-
@actions = 0
|
15
|
-
@period = 1
|
16
|
-
@face_off = true
|
17
|
-
@team_with_puck = nil
|
18
|
-
@puckless_team = nil
|
19
|
-
@puck_holder = nil
|
20
|
-
@shooting_chance = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_s
|
24
|
-
puts "#{home.emoji} #{home.name} vs #{away.name} #{away.emoji}"
|
25
|
-
end
|
26
|
-
|
27
|
-
def update
|
28
|
-
return unless @in_progress
|
29
|
-
|
30
|
-
@stream << Messages.StartOfPeriod(@period) if @actions == 0
|
31
|
-
@actions += 1
|
32
|
-
|
33
|
-
if @face_off
|
34
|
-
if @puck_holder == nil
|
35
|
-
# Pass opposite team to who wins the puck to switch_team_with_puck,
|
36
|
-
# so @team_with_puck & @puckless_team are set to the correct values.
|
37
|
-
switch_team_with_puck(
|
38
|
-
action_succeeds?(@home.roster[:center].stats[:offense],
|
39
|
-
@away.roster[:center].stats[:offense]) ? @away : @home)
|
40
|
-
|
41
|
-
@puck_holder = @team_with_puck.roster[:center]
|
42
|
-
@stream << Messages.FaceOff(@puck_holder.name)
|
43
|
-
else
|
44
|
-
pass
|
45
|
-
@face_off = false
|
46
|
-
end
|
47
|
-
return
|
48
|
-
end
|
49
|
-
|
50
|
-
case @prng.rand 5 + @shooting_chance
|
51
|
-
when 0..4
|
52
|
-
pass
|
53
|
-
when 5..6 # Check
|
54
|
-
defender = puckless_non_goalie
|
55
|
-
@stream << Messages.Hit(@puck_holder.name, defender.name,
|
56
|
-
try_take_puck(defender), 0, :defense)
|
57
|
-
else # Shoot
|
58
|
-
unless @shooting_chance < 5 and
|
59
|
-
try_block_shot @puckless_team.roster[@prng.rand(2) == 0 ? :ldef : :rdef] or
|
60
|
-
try_block_shot @puckless_team.roster[:goalie]
|
61
|
-
@score[@team_with_puck == @home ? :home : :away] += 1
|
62
|
-
|
63
|
-
@stream << Messages.Shoot(shooter,
|
64
|
-
nil, false,
|
65
|
-
@home.name, @away.name,
|
66
|
-
*@score.values)
|
67
|
-
|
68
|
-
@shooting_chance = 0
|
69
|
-
@face_off = true
|
70
|
-
@actions = 60 if @period > 3 # Sudden death overtime
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
if @actions == 60
|
75
|
-
@stream << Messages.EndOfPeriod(@period, @home.name, @away.name, *@score.values)
|
76
|
-
@actions = 0
|
77
|
-
@period += 1
|
78
|
-
if @period > 3 and not(@score[:home] == @score[:away] and @period < 12)
|
79
|
-
# Game is over
|
80
|
-
@in_progress = false
|
81
|
-
|
82
|
-
winner, loser = case @score[:home] <=> @score[:away]
|
83
|
-
when 1
|
84
|
-
[@home, @away]
|
85
|
-
when -1
|
86
|
-
[@away, @home]
|
87
|
-
else # Tie
|
88
|
-
@stream << Messages.EndOfGame
|
89
|
-
@home.losses += 1
|
90
|
-
@away.losses += 1
|
91
|
-
return
|
92
|
-
end
|
93
|
-
|
94
|
-
@stream << Messages.EndOfGame(winner.name)
|
95
|
-
winner.wins += 1
|
96
|
-
loser.losses += 1
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
def action_succeeds? helping_stat, hindering_stat
|
103
|
-
@prng.rand(10) + helping_stat - hindering_stat > 4
|
104
|
-
end
|
105
|
-
|
106
|
-
def switch_team_with_puck team_with_puck = @team_with_puck
|
107
|
-
@team_with_puck, @puckless_team = team_with_puck == @home ?
|
108
|
-
[@away, @home] : [@home, @away]
|
109
|
-
@shooting_chance = 0
|
110
|
-
end
|
111
|
-
|
112
|
-
def pass
|
113
|
-
sender_name = @puck_holder.name
|
114
|
-
receiver = puckless_non_goalie(@team_with_puck)
|
115
|
-
interceptor = puckless_non_goalie
|
116
|
-
|
117
|
-
if not @face_off and try_take_puck interceptor, 2 # Pass intercepted
|
118
|
-
@stream << Message.Pass(sender_name, receiver.name, interceptor)
|
119
|
-
return
|
120
|
-
end
|
121
|
-
|
122
|
-
@stream << Message.Pass(sender_name, receiver.name)
|
123
|
-
@puck_holder = receiver
|
124
|
-
@shooting_chance += 1
|
125
|
-
end
|
126
|
-
|
127
|
-
def try_take_puck player, dis = 0, stat = :agility
|
128
|
-
return false unless action_succeeds?(player.stats[stat] - dis,
|
129
|
-
@puck_holder.stats[stat])
|
130
|
-
|
131
|
-
switch_team_with_puck
|
132
|
-
@puck_holder = player
|
133
|
-
|
134
|
-
true
|
135
|
-
end
|
136
|
-
|
137
|
-
def try_block_shot blocker
|
138
|
-
return false unless action_succeeds?(blocker.stats[:defense],
|
139
|
-
@puck_holder.stats[:offense])
|
140
|
-
|
141
|
-
@shooting_chance += 1
|
142
|
-
@stream << Message.Shoot(@puck_holder, blocker, try_take_puck(blocker))
|
143
|
-
|
144
|
-
true
|
145
|
-
end
|
146
|
-
|
147
|
-
def puckless_non_goalie team = @puckless_team
|
148
|
-
team.roster.values.select do |p|
|
149
|
-
p != team.roster[:goalie] and p != @puck_holder
|
150
|
-
end.sample random: @prng
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
data/lib/league.rb
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
require_relative "./game"
|
2
|
-
require_relative "./team"
|
3
|
-
|
4
|
-
class League
|
5
|
-
attr_reader :day, :divisions, :games_in_progress, :playoff_teams, :champion_team
|
6
|
-
|
7
|
-
def initialize season, start_time
|
8
|
-
@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
|
-
@games_in_progress = []
|
40
|
-
@games = []
|
41
|
-
@champion_team = nil
|
42
|
-
@last_update_time = start_time
|
43
|
-
@passed_updates = 0
|
44
|
-
@prng = Random.new 69420 * season
|
45
|
-
@shuffled_teams = @divisions.values.reduce(:+).shuffle random: @prng
|
46
|
-
@playoff_teams = nil
|
47
|
-
@game_in_matchup = 3
|
48
|
-
end
|
49
|
-
|
50
|
-
def update_state
|
51
|
-
return if @champion_team
|
52
|
-
|
53
|
-
now = Time.now
|
54
|
-
five_sec_intervals = (now - @last_update_time).floor / 5
|
55
|
-
|
56
|
-
if five_sec_intervals > 0
|
57
|
-
five_sec_intervals.times do |i|
|
58
|
-
if (i + @passed_updates) % 720 == 0
|
59
|
-
new_games
|
60
|
-
else
|
61
|
-
update_games
|
62
|
-
end
|
63
|
-
|
64
|
-
return if @champion_team
|
65
|
-
end
|
66
|
-
|
67
|
-
@last_update_time = now
|
68
|
-
@passed_updates += five_sec_intervals
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
def new_games
|
74
|
-
if @game_in_matchup != (@day > 38 ? 5 : 3)
|
75
|
-
# New game in matchups
|
76
|
-
@games.map! do |game| Game.new(game.away, game.home, @prng) end
|
77
|
-
@game_in_matchup += 1
|
78
|
-
return
|
79
|
-
end
|
80
|
-
|
81
|
-
# New matchups
|
82
|
-
@games.clear
|
83
|
-
@game_in_matchup = 1
|
84
|
-
@day += 1
|
85
|
-
|
86
|
-
case @day <=> 39
|
87
|
-
when -1
|
88
|
-
(@shuffled_teams.length / 2).times do |i|
|
89
|
-
pair = [@shuffled_teams[i], @shuffled_teams[-i]]
|
90
|
-
@games << Game.new(*(@day > 19 ? pair : pair.reverse), @prng)
|
91
|
-
end
|
92
|
-
|
93
|
-
@shuffled_teams.insert 1, @shuffled_teams.pop
|
94
|
-
when 0
|
95
|
-
@playoff_teams = Team.sort_teams(
|
96
|
-
@divisions.values.map do |teams|
|
97
|
-
Team.sort_teams(teams).first(2)
|
98
|
-
end.reduce(:+).map &:copy
|
99
|
-
)
|
100
|
-
|
101
|
-
new_playoff_matchups
|
102
|
-
when 1
|
103
|
-
@playoff_teams = Team.sort_teams(@playoff_teams).first(@playoff_teams.length / 2)
|
104
|
-
|
105
|
-
if @playoff_teams.length == 1
|
106
|
-
@champion_team = @playoff_teams[0]
|
107
|
-
return
|
108
|
-
end
|
109
|
-
|
110
|
-
new_playoff_matchups
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def update_games
|
115
|
-
@games_in_progress = @games.select do |game| game.in_progress end
|
116
|
-
@games_in_progress.each do |game| game.update end
|
117
|
-
end
|
118
|
-
|
119
|
-
def new_playoff_matchups
|
120
|
-
@playoff_teams.each do |team|
|
121
|
-
team.wins = 0
|
122
|
-
team.losses = 0
|
123
|
-
end
|
124
|
-
|
125
|
-
(0...@playoff_teams.length).step 2 do |i|
|
126
|
-
@games << Game.new(*@playoff_teams[i, 2], @prng)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
data/lib/messages.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
class Messages
|
2
|
-
def initialize event, fields, data
|
3
|
-
@event = event
|
4
|
-
data_fields.zip data do |(f, d)|
|
5
|
-
instance_variable_set "@" + f, d
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
private_class_method :new
|
10
|
-
|
11
|
-
[
|
12
|
-
[:StartOfGame, :title],
|
13
|
-
[:EndOfGame, :winning_team],
|
14
|
-
[:StartOfPeriod, :period],
|
15
|
-
[:EndOfPeriod, :period, :home, :away, :home_score, :away_score],
|
16
|
-
[:FaceOff, :winning_player],
|
17
|
-
[:Hit, :puck_holder, :defender, :puck_taken],
|
18
|
-
[:Pass, :sender, :receiver, :interceptor],
|
19
|
-
[:Shoot, :shooter, :blocker, :puck_taken, :home, :away, :home_score, :away_score]
|
20
|
-
].each do |event, *fields|
|
21
|
-
define_method event do |*data|
|
22
|
-
new event, fields, data
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_s
|
27
|
-
case @event
|
28
|
-
when :StartOfGame
|
29
|
-
@title + "\nHocky!"
|
30
|
-
when :EndOfGame
|
31
|
-
"Game over.\n#{@winning_team or "Nobody"} wins!"
|
32
|
-
when :StartOfPeriod
|
33
|
-
"Start" + of_period
|
34
|
-
when :EndOfPeriod
|
35
|
-
"End" + of_period + score
|
36
|
-
when :FaceOff
|
37
|
-
@winning_player + " wins the faceoff!"
|
38
|
-
when :Hit
|
39
|
-
"#{@defender} hits #{@puck_holder + takes}!"
|
40
|
-
when :Pass
|
41
|
-
"#{@sender} passes to #{@receiver}#{@interceptor ?
|
42
|
-
"... intercepted by #{@interceptor}!" : "."}"
|
43
|
-
when :Shoot
|
44
|
-
"#{@shooter} takes a shot... #{@blocker ?
|
45
|
-
"#{@blocker} blocks the shot#{takes}!" : "and scores!" + score}"
|
46
|
-
else
|
47
|
-
raise "Unknown message"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
def of_period
|
53
|
-
" of period #{@period}."
|
54
|
-
end
|
55
|
-
|
56
|
-
def score
|
57
|
-
"\n#{@home} #{@home_score}, #{@away} #{@away_score}"
|
58
|
-
end
|
59
|
-
|
60
|
-
def takes
|
61
|
-
@puck_taken ? " and takes the puck" : ""
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
data/lib/player.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require_relative "./data/names_markov_chain"
|
2
|
-
|
3
|
-
class Player
|
4
|
-
attr_reader :name, :stats
|
5
|
-
|
6
|
-
@@prng = Random.new 8010897121101114 # This never changes
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@name = random_name + " " + random_name
|
10
|
-
@stats = [:offense, :defense, :agility].each_with_object Hash.new do |stat, hash|
|
11
|
-
hash[stat] = @@prng.rand * 5
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def random_name
|
17
|
-
combination = "__"
|
18
|
-
next_letter = ""
|
19
|
-
result = ""
|
20
|
-
|
21
|
-
10.times do
|
22
|
-
next_letters = NamesMarkovChain[combination]
|
23
|
-
break if next_letters.nil?
|
24
|
-
|
25
|
-
cumulative_weights = []
|
26
|
-
next_letters.values.each do |v|
|
27
|
-
cumulative_weights << v + (cumulative_weights.last or 0)
|
28
|
-
end
|
29
|
-
rand_num = @@prng.rand cumulative_weights.last
|
30
|
-
|
31
|
-
next_letter = next_letters.keys[cumulative_weights.index do |n| rand_num < n end]
|
32
|
-
break if next_letter == "_"
|
33
|
-
|
34
|
-
result += next_letter
|
35
|
-
combination = combination[1] + next_letter
|
36
|
-
end
|
37
|
-
|
38
|
-
result
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
data/lib/team.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require_relative "./player"
|
2
|
-
|
3
|
-
class Team
|
4
|
-
attr_accessor :wins, :losses
|
5
|
-
attr_reader :name, :emoji, :roster
|
6
|
-
|
7
|
-
def initialize name, emoji, roster_idxs = {:lwing => 0,
|
8
|
-
:center => 1,
|
9
|
-
:rwing => 2,
|
10
|
-
:ldef => 3,
|
11
|
-
:goalie => 4,
|
12
|
-
:rdef => 5}
|
13
|
-
@wins = 0
|
14
|
-
@losses = 0
|
15
|
-
@name, @emoji = [name, emoji]
|
16
|
-
@roster = {}
|
17
|
-
players = []
|
18
|
-
6.times do players << Player.new end
|
19
|
-
roster_idxs.each do |pos, i|
|
20
|
-
@roster[pos] = players[i]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_s
|
25
|
-
"#{@emoji} #{@name}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def print_win_loss
|
29
|
-
puts " #{to_s.ljust 26} #{@wins}-#{@losses}"
|
30
|
-
end
|
31
|
-
|
32
|
-
def print_roster
|
33
|
-
puts to_s
|
34
|
-
@roster.each do |pos, player|
|
35
|
-
puts " #{pos.to_s.ljust 6}: #{player.name}"
|
36
|
-
player.stats.each do |stat, value|
|
37
|
-
puts " #{stat}: #{value.round 1}"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.sort_teams teams
|
43
|
-
teams.sort do |a, b| a.wins - a.losses <=> b.wins - b.losses end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|