hlockey 2 → 3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{lib/hlockey/data → data}/election.yaml +16 -16
- data/data/external/names.txt +19948 -0
- data/{lib/hlockey/data → data}/information.yaml +12 -12
- data/data/league.yaml +846 -0
- data/{lib/hlockey/data → data}/links.yaml +2 -2
- data/lib/hlockey/constants.rb +4 -0
- data/lib/hlockey/data.rb +17 -13
- data/lib/hlockey/game/actions.rb +30 -0
- data/lib/hlockey/game/fight.rb +83 -0
- data/lib/hlockey/game.rb +254 -159
- data/lib/hlockey/league.rb +150 -115
- data/lib/hlockey/message.rb +133 -0
- data/lib/hlockey/player.rb +63 -0
- data/lib/hlockey/team.rb +59 -35
- data/lib/hlockey/utils.rb +12 -0
- data/lib/hlockey/version.rb +3 -5
- data/lib/hlockey/weather.rb +114 -0
- data/lib/hlockey.rb +1 -3
- metadata +20 -15
- data/bin/hlockey +0 -104
- data/lib/hlockey/data/league.yaml +0 -846
- data/lib/hlockey/messages.rb +0 -100
data/lib/hlockey/league.rb
CHANGED
@@ -1,115 +1,150 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require(
|
4
|
-
require(
|
5
|
-
require(
|
6
|
-
require(
|
7
|
-
|
8
|
-
module Hlockey
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
1
|
+
require("hlockey/constants")
|
2
|
+
require("hlockey/data")
|
3
|
+
require("hlockey/game")
|
4
|
+
require("hlockey/utils")
|
5
|
+
require("hlockey/version")
|
6
|
+
require("hlockey/weather")
|
7
|
+
|
8
|
+
module Hlockey
|
9
|
+
##
|
10
|
+
# The Hlockey League
|
11
|
+
class League
|
12
|
+
REGULAR_SEASON_DAY_AMT = 38
|
13
|
+
|
14
|
+
# @return [Time]
|
15
|
+
attr_reader(:start_time)
|
16
|
+
|
17
|
+
# @return [Hash<Symbol => Array<Team>>]
|
18
|
+
attr_reader(:divisions)
|
19
|
+
|
20
|
+
# @return [Array<Team>]
|
21
|
+
attr_reader(:teams, :playoff_teams)
|
22
|
+
|
23
|
+
# @return [Integer]
|
24
|
+
attr_reader(:day)
|
25
|
+
|
26
|
+
# @return [Array<Game>]
|
27
|
+
attr_reader(:games, :games_in_progress)
|
28
|
+
|
29
|
+
# @return [Team, nil]
|
30
|
+
attr_reader(:champion_team)
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@start_time, @divisions = Data.league
|
34
|
+
@start_time.localtime
|
35
|
+
@day = 0
|
36
|
+
@games_in_progress = []
|
37
|
+
@games = []
|
38
|
+
@champion_team = nil
|
39
|
+
@last_update_time = @start_time
|
40
|
+
@passed_updates = 0
|
41
|
+
@prng = Random.new(@start_time.to_i)
|
42
|
+
@teams = @divisions.values.reduce(:+)
|
43
|
+
@shuffled_teams = @teams.shuffle(random: @prng)
|
44
|
+
@playoff_teams = []
|
45
|
+
@game_in_matchup = 3
|
46
|
+
@matchup_game_amt = 3
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Updates the league to the current state
|
51
|
+
# This should be called whenever you need the current state of the league
|
52
|
+
def update_state
|
53
|
+
return if @champion_team
|
54
|
+
|
55
|
+
now = Time.at(Time.now.to_i)
|
56
|
+
intervals = (now - @last_update_time).div(UPDATE_FREQUENCY_SECONDS)
|
57
|
+
|
58
|
+
return unless intervals.positive?
|
59
|
+
|
60
|
+
intervals.times do |i|
|
61
|
+
if ((i + @passed_updates) % UPDATES_PER_HOUR).zero?
|
62
|
+
new_games
|
63
|
+
else
|
64
|
+
update_games
|
65
|
+
end
|
66
|
+
|
67
|
+
break if @champion_team
|
68
|
+
end
|
69
|
+
|
70
|
+
@last_update_time = now
|
71
|
+
@passed_updates += intervals
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def new_games
|
77
|
+
if @game_in_matchup != @matchup_game_amt
|
78
|
+
# New game in matchups
|
79
|
+
@games.map! { |game| new_game(game.away, game.home) }
|
80
|
+
@game_in_matchup += 1
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
# New matchups
|
85
|
+
@games.clear
|
86
|
+
@game_in_matchup = 1
|
87
|
+
@day += 1
|
88
|
+
|
89
|
+
case @day <=> REGULAR_SEASON_DAY_AMT + 1
|
90
|
+
when -1 # In regular season
|
91
|
+
(@shuffled_teams.length / 2).times do |i|
|
92
|
+
pair = [@shuffled_teams[i], @shuffled_teams[-i - 1]]
|
93
|
+
pair.reverse! if @day > REGULAR_SEASON_DAY_AMT / 2
|
94
|
+
@games << new_game(*pair)
|
95
|
+
end
|
96
|
+
|
97
|
+
@shuffled_teams.insert(1, @shuffled_teams.pop)
|
98
|
+
when 0 # Playoffs about to start
|
99
|
+
@matchup_game_amt = 5
|
100
|
+
|
101
|
+
# get teams that qualified for playoffs, put in @playoff_teams
|
102
|
+
two_best_teams_each_division = @divisions.values.map do |teams|
|
103
|
+
sort_teams_by_wins(teams).first(2)
|
104
|
+
end.reduce(:+)
|
105
|
+
@playoff_teams = sort_teams_by_wins(two_best_teams_each_division).map(&:clone)
|
106
|
+
|
107
|
+
new_playoff_matchups
|
108
|
+
when 1 # Playoffs started
|
109
|
+
@playoff_teams.select! { |team| team.wins > team.losses }
|
110
|
+
|
111
|
+
if @playoff_teams.length == 1
|
112
|
+
@champion_team = @playoff_teams.first
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
new_playoff_matchups
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def update_games
|
121
|
+
@games_in_progress.each(&:update)
|
122
|
+
@games_in_progress = @games.select(&:in_progress)
|
123
|
+
@divisions.transform_values!(&method(:sort_teams_by_wins))
|
124
|
+
end
|
125
|
+
|
126
|
+
def new_playoff_matchups
|
127
|
+
@playoff_teams.each do |team|
|
128
|
+
team.wins = 0
|
129
|
+
team.losses = 0
|
130
|
+
end
|
131
|
+
|
132
|
+
(@playoff_teams.length / 2).times do |i|
|
133
|
+
@games << new_game(@playoff_teams[i], @playoff_teams[-i - 1])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param home [Team]
|
138
|
+
# @param away [Team]
|
139
|
+
# @return [Game]
|
140
|
+
def new_game(home, away)
|
141
|
+
Game.new(home, away, @prng, Utils.weighted_random(Weather::WEIGHTS, @prng))
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param teams [Array<Team>]
|
145
|
+
# @return [Array<Team>]
|
146
|
+
def sort_teams_by_wins(teams)
|
147
|
+
teams.sort { |a, b| b.wins <=> a.wins }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require("hlockey/version")
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
##
|
5
|
+
# Can be sent by a Game (in Game#stream) or a commonly used text
|
6
|
+
class Message
|
7
|
+
private_class_method(:new)
|
8
|
+
|
9
|
+
# @return [Symbol] an action or event the message represents
|
10
|
+
attr_reader(:event)
|
11
|
+
|
12
|
+
# @param event [Symbol]
|
13
|
+
# @param fields [Array<Symbol>] fields used in string interpolation in #to_s
|
14
|
+
# @param data [Array<Object>] data corresponding to the fields
|
15
|
+
def initialize(event, fields, data)
|
16
|
+
@event = event
|
17
|
+
fields.zip(data) { |f, d| instance_variable_set("@#{f}", d) }
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# These are messages logged to game streams
|
22
|
+
|
23
|
+
[
|
24
|
+
%i[StartOfGame],
|
25
|
+
%i[EndOfGame winning_team],
|
26
|
+
%i[StartOfPeriod period],
|
27
|
+
%i[EndOfPeriod period home away home_score away_score],
|
28
|
+
%i[FaceOff winning_player new_puck_team],
|
29
|
+
%i[Hit puck_holder defender puck_taken new_puck_team],
|
30
|
+
%i[Pass sender receiver interceptor new_puck_team],
|
31
|
+
%i[ShootScore shooter home away home_score away_score],
|
32
|
+
%i[ShootBlock shooter blocker puck_taken new_puck_team],
|
33
|
+
%i[FightStarted home_player away_player],
|
34
|
+
%i[FightAttack attacking_player defending_player blocked],
|
35
|
+
%i[PlayerJoinedFight team player],
|
36
|
+
%i[FightEnded],
|
37
|
+
%i[ChickenedOut prev_player next_player],
|
38
|
+
%i[InclineFavors team],
|
39
|
+
%i[StarsPity team],
|
40
|
+
%i[WavesWashedAway prev_player next_player]
|
41
|
+
].each do |event, *fields|
|
42
|
+
define_method(event) { |*data| new(event, fields, data) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# These are messages used elsewhere
|
46
|
+
|
47
|
+
def SeasonDay(day)
|
48
|
+
"Season #{VERSION} day #{day}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def SeasonStarts(time)
|
52
|
+
time.strftime("Season #{VERSION} starts at %H:%M, %A, %B %d (%Z).")
|
53
|
+
end
|
54
|
+
|
55
|
+
def SeasonChampion(team)
|
56
|
+
"Your season #{VERSION} champions are the #{team}!"
|
57
|
+
end
|
58
|
+
|
59
|
+
def NoGames
|
60
|
+
"no games right now. it is the offseason. join the Hlockey Discord for updates"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
case @event
|
66
|
+
when :StartOfGame
|
67
|
+
"Hocky!"
|
68
|
+
when :EndOfGame
|
69
|
+
"Game over.\n#{@winning_team} win!"
|
70
|
+
when :StartOfPeriod
|
71
|
+
"Start#{of_period}"
|
72
|
+
when :EndOfPeriod
|
73
|
+
"End#{of_period}#{score}"
|
74
|
+
when :FaceOff
|
75
|
+
"#{@winning_player} wins the faceoff!#{possession_change}"
|
76
|
+
when :Hit
|
77
|
+
"#{@defender} hits #{@puck_holder}#{takes}"
|
78
|
+
when :Pass
|
79
|
+
str = "#{@sender} passes to #{@receiver}."
|
80
|
+
str += "..\nIntercepted by #{@interceptor}!#{possession_change}" if @interceptor
|
81
|
+
str
|
82
|
+
when :ShootScore
|
83
|
+
"#{shot} and scores!#{score}"
|
84
|
+
when :ShootBlock
|
85
|
+
"#{shot}...\n#{@blocker} blocks the shot#{takes}"
|
86
|
+
when :FightStarted
|
87
|
+
"#{@home_player} and #{@away_player} start fighting!"
|
88
|
+
when :FightAttack
|
89
|
+
str = "#{@attacking_player} punches #{@defending_player}!"
|
90
|
+
str += "\n#{@defending_player} blocks the punch!" if @blocked
|
91
|
+
str
|
92
|
+
when :PlayerJoinedFight
|
93
|
+
"#{@player} from #{@team} joins the fight!"
|
94
|
+
when :FightEnded
|
95
|
+
"The fight has ended."
|
96
|
+
when :ChickenedOut
|
97
|
+
"#{@prev_player} chickened out!#{replaces_for("game")}"
|
98
|
+
when :InclineFavors
|
99
|
+
"The incline favors #{@team}."
|
100
|
+
when :StarsPity
|
101
|
+
"The stars take pity on #{@team} and give them half a goal."
|
102
|
+
when :WavesWashedAway
|
103
|
+
"#{@prev_player} is washed away by the waves...#{replaces_for("season")}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def of_period
|
110
|
+
" of period #{@period}."
|
111
|
+
end
|
112
|
+
|
113
|
+
def score
|
114
|
+
"\n#{@home} #{@home_score.round(1)}, #{@away} #{@away_score.round(1)}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def shot
|
118
|
+
"#{@shooter} takes a shot"
|
119
|
+
end
|
120
|
+
|
121
|
+
def takes
|
122
|
+
@puck_taken ? " and takes the puck!#{possession_change}" : "!"
|
123
|
+
end
|
124
|
+
|
125
|
+
def possession_change
|
126
|
+
"\n#{@new_puck_team} have possession."
|
127
|
+
end
|
128
|
+
|
129
|
+
def replaces_for(period)
|
130
|
+
"\n#{@next_player} replaces them for the rest of the #{period}."
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require("hlockey/utils")
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
class Player
|
5
|
+
attr_accessor(:stats)
|
6
|
+
attr_reader(:to_s)
|
7
|
+
|
8
|
+
@@cached_chain = nil
|
9
|
+
|
10
|
+
def initialize(prng = Random)
|
11
|
+
@to_s = make_name(prng)
|
12
|
+
# Generate stats
|
13
|
+
@stats = {}
|
14
|
+
%i[offense defense agility].each { |stat| @stats[stat] = prng.rand * 5 }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def make_name(prng)
|
20
|
+
Array.new(2) do
|
21
|
+
combination = "__"
|
22
|
+
next_letter = ""
|
23
|
+
result = ""
|
24
|
+
|
25
|
+
10.times do
|
26
|
+
next_letters = name_chain[combination]
|
27
|
+
break if next_letters.nil?
|
28
|
+
|
29
|
+
cumulative_weights = []
|
30
|
+
next_letters.each_value do |v|
|
31
|
+
cumulative_weights << v + (cumulative_weights.last or 0)
|
32
|
+
end
|
33
|
+
|
34
|
+
next_letter = Utils.weighted_random(next_letters, prng)
|
35
|
+
break if next_letter == "_"
|
36
|
+
|
37
|
+
result += next_letter
|
38
|
+
combination = combination[1] + next_letter
|
39
|
+
end
|
40
|
+
|
41
|
+
result
|
42
|
+
end.join(" ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def name_chain
|
46
|
+
if @@cached_chain.nil?
|
47
|
+
@@cached_chain = {}
|
48
|
+
File.open(File.expand_path("../../data/external/names.txt", __dir__)).each do |n|
|
49
|
+
name = "__#{n.chomp}__"
|
50
|
+
(name.length - 3).times do |i|
|
51
|
+
combination = name[i, 2]
|
52
|
+
next_letter = name[i + 2]
|
53
|
+
@@cached_chain[combination] ||= {}
|
54
|
+
@@cached_chain[combination][next_letter] ||= 0
|
55
|
+
@@cached_chain[combination][next_letter] += 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@@cached_chain
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/hlockey/team.rb
CHANGED
@@ -1,35 +1,59 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Hlockey
|
4
|
-
# Created by League when data is loaded
|
5
|
-
class Team
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
1
|
+
require("hlockey/player")
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
# Created by League when data is loaded
|
5
|
+
class Team
|
6
|
+
attr_accessor(:wins, :losses, :roster)
|
7
|
+
attr_reader(:to_s, :emoji)
|
8
|
+
|
9
|
+
# rand is only used if roster is nil
|
10
|
+
def initialize(name, emoji, roster: nil, random: Random)
|
11
|
+
@to_s = name
|
12
|
+
@emoji = emoji
|
13
|
+
|
14
|
+
if roster.nil?
|
15
|
+
@roster = {}
|
16
|
+
%i[lwing center rwing ldef goalie rdef].each do |pos|
|
17
|
+
@roster[pos] = Player.new(random)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@roster = roster
|
21
|
+
end
|
22
|
+
|
23
|
+
@wins = 0
|
24
|
+
@losses = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def random_player(random, filter_predicate = nil)
|
28
|
+
players = @roster.values
|
29
|
+
players.select!(&filter_predicate) unless filter_predicate.nil?
|
30
|
+
players.sample(random: random)
|
31
|
+
end
|
32
|
+
|
33
|
+
def w_l
|
34
|
+
"#{@wins}-#{@losses}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def worst_player_pos
|
38
|
+
@roster.transform_values do |player|
|
39
|
+
player.stats.values.reduce(:+)
|
40
|
+
end.min_by { |k, v| v }.first
|
41
|
+
end
|
42
|
+
|
43
|
+
# Takes symbol from Team roster & converts it to full position name
|
44
|
+
def self.pos_name(pos)
|
45
|
+
case pos
|
46
|
+
when :lwing
|
47
|
+
"Left wing"
|
48
|
+
when :rwing
|
49
|
+
"Right wing"
|
50
|
+
when :ldef
|
51
|
+
"Left defender"
|
52
|
+
when :rdef
|
53
|
+
"Right defender"
|
54
|
+
else
|
55
|
+
pos.capitalize.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Hlockey
|
2
|
+
module Utils
|
3
|
+
# Does a weighted random with a hash,
|
4
|
+
# where the keys are elements being randomly selected,
|
5
|
+
# and the values are the weights
|
6
|
+
def self.weighted_random(hash, prng = Random)
|
7
|
+
cumulative_weights = []
|
8
|
+
hash.each_value { |v| cumulative_weights << v + (cumulative_weights.last or 0) }
|
9
|
+
hash.keys[cumulative_weights.index { |n| prng.rand(cumulative_weights.last) < n }]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/hlockey/version.rb
CHANGED