hlockey 2 → 4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/election.yaml +21 -0
- data/data/external/names.txt +19948 -0
- data/data/information.yaml +24 -0
- data/data/league.yaml +1526 -0
- data/data/links.yaml +3 -0
- data/data/previous_election_results.yaml +59 -0
- data/lib/hlockey/constants.rb +4 -0
- data/lib/hlockey/data.rb +17 -13
- data/lib/hlockey/game/actions.rb +27 -0
- data/lib/hlockey/game/fight.rb +99 -0
- data/lib/hlockey/game.rb +321 -159
- data/lib/hlockey/league.rb +254 -115
- data/lib/hlockey/message.rb +182 -0
- data/lib/hlockey/player.rb +73 -0
- data/lib/hlockey/team.rb +52 -35
- data/lib/hlockey/utils.rb +15 -0
- data/lib/hlockey/version.rb +3 -5
- data/lib/hlockey/weather.rb +168 -0
- data/lib/hlockey.rb +1 -3
- metadata +36 -16
- data/bin/hlockey +0 -104
- data/lib/hlockey/data/election.yaml +0 -16
- data/lib/hlockey/data/information.yaml +0 -12
- data/lib/hlockey/data/league.yaml +0 -846
- data/lib/hlockey/data/links.yaml +0 -2
- data/lib/hlockey/messages.rb +0 -100
data/lib/hlockey/league.rb
CHANGED
@@ -1,115 +1,254 @@
|
|
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
|
-
end
|
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
|
+
GAMES_IN_REGULAR_SEASON = 114
|
13
|
+
|
14
|
+
# @return [Time]
|
15
|
+
attr_reader(:start_time)
|
16
|
+
|
17
|
+
# @return [Hash<Symbol => Array<Team>>]
|
18
|
+
attr_reader(:divisions)
|
19
|
+
|
20
|
+
# @return [Integer]
|
21
|
+
attr_reader(:day)
|
22
|
+
|
23
|
+
# @return [Array<Game>]
|
24
|
+
attr_reader(:games, :games_in_progress)
|
25
|
+
|
26
|
+
# @return [Array<String>]
|
27
|
+
attr_reader(:alerts)
|
28
|
+
|
29
|
+
# @return [Team, nil]
|
30
|
+
attr_reader(:champion_team)
|
31
|
+
|
32
|
+
# @return [Array<Team>]
|
33
|
+
attr_reader(:teams, :playoff_teams)
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@start_time, @divisions = Data.league
|
37
|
+
@start_time.localtime
|
38
|
+
@day = 0
|
39
|
+
@games = []
|
40
|
+
@games_in_progress = []
|
41
|
+
@alerts = []
|
42
|
+
@champion_team = nil
|
43
|
+
@last_update_time = @start_time
|
44
|
+
@passed_updates = 0
|
45
|
+
@prng = Random.new(@start_time.to_i)
|
46
|
+
@teams = @divisions.values.flatten
|
47
|
+
@sorted_teams_by_wins = @teams.shuffle(random: @prng)
|
48
|
+
@playoff_teams = []
|
49
|
+
# warm vs warm / cool vs cool
|
50
|
+
rotated_divisions = game_divisions.values.rotate
|
51
|
+
@shuffled_division_pairs = Array.new(2) do |i|
|
52
|
+
(rotated_divisions[i] + rotated_divisions[-i - 1]).shuffle(random: @prng)
|
53
|
+
end
|
54
|
+
|
55
|
+
@game_in_matchup = 3
|
56
|
+
@matchup_game_amt = 3
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Updates the league to the current state
|
61
|
+
# This should be called whenever you need the current state of the league
|
62
|
+
def update_state
|
63
|
+
return if @champion_team
|
64
|
+
|
65
|
+
now = Time.at(Time.now.to_i)
|
66
|
+
intervals = (now - @last_update_time).div(UPDATE_FREQUENCY_SECONDS)
|
67
|
+
|
68
|
+
return unless intervals.positive?
|
69
|
+
|
70
|
+
intervals.times do |i|
|
71
|
+
if ((i + @passed_updates) % UPDATES_PER_HOUR).zero?
|
72
|
+
update_teams
|
73
|
+
new_games
|
74
|
+
else
|
75
|
+
update_games
|
76
|
+
end
|
77
|
+
|
78
|
+
break if @champion_team
|
79
|
+
end
|
80
|
+
|
81
|
+
@last_update_time = now
|
82
|
+
@passed_updates += intervals
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def update_teams
|
88
|
+
return if @day.zero?
|
89
|
+
|
90
|
+
# Boost Sleepers roster
|
91
|
+
unless @day > 41 # Unless Sleepers are playing
|
92
|
+
@teams.last.roster.each_value do |player|
|
93
|
+
stat_change_amount = (10 - player.stats.values.reduce(:+)) / 129
|
94
|
+
player.stats.transform_values! { |stat| stat + stat_change_amount }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Update team statuses
|
99
|
+
games_remaining = GAMES_IN_REGULAR_SEASON - ((@day - 1) * 3 + @game_in_matchup)
|
100
|
+
return if games_remaining.negative?
|
101
|
+
|
102
|
+
if games_remaining.zero?
|
103
|
+
@playoff_teams = sort_teams_by_wins(playoff_qualifiers)
|
104
|
+
@playoff_teams.each { |team| team.status = :qualified }
|
105
|
+
(@teams[...-1] - @playoff_teams).each { |team| team.status = :partying }
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
set_to_qualify = playoff_qualifiers
|
110
|
+
@teams[...-1].each_with_index do |team, i|
|
111
|
+
next unless games_remaining < set_to_qualify[i / 5].wins - team.wins &&
|
112
|
+
games_remaining < set_to_qualify.last.wins - team.wins
|
113
|
+
|
114
|
+
team.status = :partying
|
115
|
+
end
|
116
|
+
|
117
|
+
threat_wins = sort_teams_by_wins(@teams - set_to_qualify).first.wins
|
118
|
+
set_to_qualify.each_with_index do |team, i|
|
119
|
+
division_threat_wins = if i < 4
|
120
|
+
sort_teams_by_wins(@divisions.values[i])[1].wins
|
121
|
+
else # Team is not a division leader, so use itself
|
122
|
+
team.wins
|
123
|
+
end
|
124
|
+
next unless games_remaining < team.wins - threat_wins ||
|
125
|
+
games_remaining < team.wins - division_threat_wins
|
126
|
+
|
127
|
+
team.status = :qualified
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def new_games
|
132
|
+
if @game_in_matchup != @matchup_game_amt
|
133
|
+
# New game in matchups
|
134
|
+
@games.map! { |game| new_game(game.away, game.home) }
|
135
|
+
@game_in_matchup += 1
|
136
|
+
return
|
137
|
+
end
|
138
|
+
|
139
|
+
# New matchups
|
140
|
+
@games.clear
|
141
|
+
@game_in_matchup = 1
|
142
|
+
@day += 1
|
143
|
+
|
144
|
+
case @day
|
145
|
+
when 1..27
|
146
|
+
@shuffled_division_pairs.each { |p| new_matchups(p, reverse: @day.even?) }
|
147
|
+
when 28..38
|
148
|
+
@shuffled_division_pairs.transpose.each_with_index do |pair, i|
|
149
|
+
@games << new_game(*pair, reverse: i.even?)
|
150
|
+
end
|
151
|
+
@shuffled_division_pairs.first.rotate!
|
152
|
+
when 39
|
153
|
+
@matchup_game_amt = 5
|
154
|
+
@playoff_teams = sort_teams_by_wins(playoff_qualifiers).map do |team|
|
155
|
+
cloned_team = team.clone
|
156
|
+
cloned_team.status = :playoffs
|
157
|
+
cloned_team
|
158
|
+
end
|
159
|
+
new_playoff_matchups
|
160
|
+
when 40, 41
|
161
|
+
@playoff_teams.select! { |team| team.wins > team.losses }
|
162
|
+
new_playoff_matchups
|
163
|
+
when 42
|
164
|
+
@matchup_game_amt = 1
|
165
|
+
champion = @playoff_teams.find { |team| team.wins > team.losses }
|
166
|
+
champion.wins = 0
|
167
|
+
champion.losses = 0
|
168
|
+
@playoff_teams = [@teams.last, champion]
|
169
|
+
@alerts = ["The Sleepers have awoken..."]
|
170
|
+
@games = [Game.new(*@playoff_teams, @prng, Stars)]
|
171
|
+
when 43, 44
|
172
|
+
@games = [Game.new(*@playoff_teams, @prng, Stars)]
|
173
|
+
when 45
|
174
|
+
return unless @champion_team.nil?
|
175
|
+
|
176
|
+
sleepers, @champion_team = @playoff_teams
|
177
|
+
@alerts = []
|
178
|
+
|
179
|
+
if sleepers.wins > @champion_team.wins
|
180
|
+
@alerts << "The Sleepers have beat your champions."
|
181
|
+
@alerts << "The Sleepers are evolving!"
|
182
|
+
sleepers.to_s = "Sleepersz"
|
183
|
+
@alerts << "Sleepers -> Sleepersz"
|
184
|
+
return
|
185
|
+
end
|
186
|
+
|
187
|
+
@alerts << "The Sleepers have lost."
|
188
|
+
sleepers.roster.values.zip([18, 4, 13, 19, 6, 0]) do |player, team_idx|
|
189
|
+
player.team = @teams[team_idx]
|
190
|
+
player.team.shadows << player
|
191
|
+
@alerts << "#{player} has adventured into the #{player.team} shadows."
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def update_games
|
197
|
+
@games_in_progress.each(&:update)
|
198
|
+
@games_in_progress = @games.select(&:in_progress)
|
199
|
+
@divisions.transform_values!(&method(:sort_teams_by_wins))
|
200
|
+
end
|
201
|
+
|
202
|
+
def new_playoff_matchups
|
203
|
+
@playoff_teams.each do |team|
|
204
|
+
team.wins = 0
|
205
|
+
team.losses = 0
|
206
|
+
end
|
207
|
+
|
208
|
+
new_matchups(@playoff_teams, rotate: false)
|
209
|
+
end
|
210
|
+
|
211
|
+
# @param matchup_teams[Array<Team>]
|
212
|
+
# @param rotate [Boolean]
|
213
|
+
# @param reverse [Boolean]
|
214
|
+
def new_matchups(matchup_teams, rotate: true, reverse: false)
|
215
|
+
(matchup_teams.length / 2).times do |i|
|
216
|
+
@games << new_game(matchup_teams[i], matchup_teams[-i - 1], reverse: reverse)
|
217
|
+
end
|
218
|
+
matchup_teams.insert(1, matchup_teams.pop) if rotate
|
219
|
+
end
|
220
|
+
|
221
|
+
# @param home [Team]
|
222
|
+
# @param away [Team]
|
223
|
+
# @param reverse [Boolean]
|
224
|
+
def new_game(home, away, reverse: false)
|
225
|
+
teams = [home, away]
|
226
|
+
teams.reverse! if reverse
|
227
|
+
|
228
|
+
Game.new(*teams, @prng, Weather::WEATHERS.sample(random: @prng))
|
229
|
+
end
|
230
|
+
|
231
|
+
# @return [Array<Team>]
|
232
|
+
def playoff_qualifiers
|
233
|
+
top_divs = game_divisions.values.map { |teams| sort_teams_by_wins(teams).first }
|
234
|
+
# Prevents tied teams from being unfairly decided by which division they're in
|
235
|
+
@sorted_teams_by_wins = sort_teams_by_wins(@sorted_teams_by_wins)
|
236
|
+
rest = (@sorted_teams_by_wins - top_divs).first(4)
|
237
|
+
|
238
|
+
top_divs + rest
|
239
|
+
end
|
240
|
+
|
241
|
+
# @param teams [Array<Team>]
|
242
|
+
# @return [Array<Team>]
|
243
|
+
def sort_teams_by_wins(teams)
|
244
|
+
# A stable sort (Ruby's normal sort isn't always stable)
|
245
|
+
# t.wins is negative because it sorts the opposite way than I want otherwise
|
246
|
+
teams.sort_by.with_index { |t, i| [-t.wins, i] }
|
247
|
+
end
|
248
|
+
|
249
|
+
# @return [Hash<Symbol => Array<Team>]
|
250
|
+
def game_divisions
|
251
|
+
@divisions.except(:"Sleepy Tired")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,182 @@
|
|
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
|
+
@colorize = ->(_color, string) { string }
|
10
|
+
|
11
|
+
# @return [Symbol] an action or event the message represents
|
12
|
+
attr_reader(:event)
|
13
|
+
|
14
|
+
# @param event [Symbol]
|
15
|
+
# @param fields [Array<Symbol>] fields used in string interpolation in #to_s
|
16
|
+
# @param data [Array<Object>] data corresponding to the fields
|
17
|
+
def initialize(event, fields, data)
|
18
|
+
self.class.attr_reader(*fields)
|
19
|
+
|
20
|
+
@event = event
|
21
|
+
fields.zip(data) { |f, d| instance_variable_set("@#{f}", d) }
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Variable/method controlling how coloring players/teams is done
|
26
|
+
# The default is to not color things at all
|
27
|
+
|
28
|
+
attr_writer(:colorize)
|
29
|
+
|
30
|
+
# @param obj [Team, Player]
|
31
|
+
# @return String
|
32
|
+
def color(obj)
|
33
|
+
col = obj.instance_of?(Team) ? obj.color : obj.team.color
|
34
|
+
|
35
|
+
@colorize.call(col, obj.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
# These are messages logged to game streams
|
39
|
+
[
|
40
|
+
%i[StartOfGame],
|
41
|
+
%i[EndOfGame winning_team],
|
42
|
+
%i[StartOfPeriod period],
|
43
|
+
%i[EndOfPeriod period home away home_score away_score],
|
44
|
+
%i[FaceOff winning_player new_puck_team],
|
45
|
+
%i[Hit puck_holder defender puck_taken new_puck_team shooting_chance],
|
46
|
+
%i[Pass sender receiver shooting_chance interceptor new_puck_team],
|
47
|
+
%i[ShootScore shooter home away home_score away_score audacity],
|
48
|
+
%i[ShootBlock shooter blocker puck_taken new_puck_team shooting_chance audacity],
|
49
|
+
%i[Partying player],
|
50
|
+
%i[FightStarted home_player away_player],
|
51
|
+
%i[FightAttack attacking_player defending_player blocked],
|
52
|
+
%i[PlayerJoinedFight team player],
|
53
|
+
%i[FightEnded],
|
54
|
+
%i[MoraleChange team amount],
|
55
|
+
%i[ChickenedOut prev_player next_player],
|
56
|
+
%i[InclineFavors team],
|
57
|
+
%i[StarsAlign team],
|
58
|
+
%i[WavesWashedAway prev_player next_player]
|
59
|
+
].each do |event, *fields|
|
60
|
+
define_method(event) { |*data| new(event, fields, data) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# These are messages used elsewhere
|
64
|
+
|
65
|
+
def SeasonDay(day)
|
66
|
+
"Season #{VERSION} day #{day}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def SeasonStarts(time)
|
70
|
+
time.strftime("Season #{VERSION} starts at %H:%M, %A, %B %d (%Z).")
|
71
|
+
end
|
72
|
+
|
73
|
+
def SeasonChampion(team)
|
74
|
+
"Your season #{VERSION} champions are the #{team}!"
|
75
|
+
end
|
76
|
+
|
77
|
+
def NoGames
|
78
|
+
"no games right now. it is the offseason. join the Hlockey Discord for updates"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
case @event
|
84
|
+
when :StartOfGame
|
85
|
+
"Hocky!"
|
86
|
+
when :EndOfGame
|
87
|
+
"Game over.\n#{color(@winning_team)} win!"
|
88
|
+
when :StartOfPeriod
|
89
|
+
"Start#{of_period}"
|
90
|
+
when :EndOfPeriod
|
91
|
+
"End#{of_period}#{score}"
|
92
|
+
when :FaceOff
|
93
|
+
"#{color(@winning_player)} wins the faceoff!#{possession_change}"
|
94
|
+
when :Hit
|
95
|
+
str = "#{color(@defender)} hits #{color(@puck_holder)}"
|
96
|
+
str += takes + score_chance if @puck_taken
|
97
|
+
str
|
98
|
+
when :Pass
|
99
|
+
str = "#{color(@sender)} passes to #{color(@receiver)}."
|
100
|
+
unless @interceptor.nil?
|
101
|
+
str += "..\nIntercepted by #{color(@interceptor)}!#{possession_change}"
|
102
|
+
end
|
103
|
+
str += score_chance
|
104
|
+
str
|
105
|
+
when :ShootScore
|
106
|
+
"#{shot} and scores!#{score}"
|
107
|
+
when :ShootBlock
|
108
|
+
"#{shot}...\n#{color(@blocker)} blocks the shot#{takes}#{score_chance}"
|
109
|
+
when :Partying
|
110
|
+
"#{color(@player)} is partying!"
|
111
|
+
when :FightStarted
|
112
|
+
"#{color(@home_player)} and #{color(@away_player)} start fighting!"
|
113
|
+
when :FightAttack
|
114
|
+
str = "#{color(@attacking_player)} punches #{color(@defending_player)}!"
|
115
|
+
str += "\n#{color(@defending_player)} blocks the punch!" if @blocked
|
116
|
+
str
|
117
|
+
when :PlayerJoinedFight
|
118
|
+
"#{color(@player)} from #{color(@team)} joins the fight!"
|
119
|
+
when :FightEnded
|
120
|
+
"The fight has ended."
|
121
|
+
when :MoraleChange
|
122
|
+
"#{color(@team)} #{@amount.negative? ? "loses" : "gains"} #{@amount.abs} morale."
|
123
|
+
when :ChickenedOut
|
124
|
+
"#{color(@prev_player)} chickened out!#{replaces("game")}"
|
125
|
+
when :InclineFavors
|
126
|
+
"The incline favors #{color(@team)}."
|
127
|
+
when :StarsAlign
|
128
|
+
"The stars align for #{color(@team)}. They get half a goal."
|
129
|
+
when :WavesWashedAway
|
130
|
+
"#{color(@prev_player)} is washed away by the waves...#{replaces}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def color(obj)
|
137
|
+
self.class.color(obj)
|
138
|
+
end
|
139
|
+
|
140
|
+
def score_chance
|
141
|
+
return "" if @shooting_chance.nil?
|
142
|
+
|
143
|
+
chance_str = case @shooting_chance
|
144
|
+
when 0..2
|
145
|
+
"none"
|
146
|
+
when 3, 4
|
147
|
+
"low"
|
148
|
+
when 5, 6
|
149
|
+
"medium"
|
150
|
+
else
|
151
|
+
"high"
|
152
|
+
end
|
153
|
+
"\nChance of scoring: #{chance_str} (#{@shooting_chance})"
|
154
|
+
end
|
155
|
+
|
156
|
+
def of_period
|
157
|
+
" of period #{@period}."
|
158
|
+
end
|
159
|
+
|
160
|
+
def score
|
161
|
+
"\n#{color(@home)} #{@home_score.round(2)}, #{color(@away)} #{@away_score.round(2)}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def shot
|
165
|
+
"#{color(@shooter)} takes a#{"n audacious" if @audacity} shot"
|
166
|
+
end
|
167
|
+
|
168
|
+
def takes
|
169
|
+
@puck_taken ? " and takes the puck!#{possession_change}" : "!"
|
170
|
+
end
|
171
|
+
|
172
|
+
def possession_change
|
173
|
+
"\n#{color(@new_puck_team)} have possession."
|
174
|
+
end
|
175
|
+
|
176
|
+
def replaces(period = nil)
|
177
|
+
str = "\n#{color(@next_player)} replaces them"
|
178
|
+
str += " for the rest of the #{period}." unless period.nil?
|
179
|
+
str
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require("hlockey/utils")
|
2
|
+
|
3
|
+
module Hlockey
|
4
|
+
class Player
|
5
|
+
attr_accessor(:team, :stats)
|
6
|
+
attr_reader(:to_s)
|
7
|
+
|
8
|
+
@cached_chain = nil
|
9
|
+
|
10
|
+
# @param team [Team]
|
11
|
+
# @param prng [#rand]
|
12
|
+
def initialize(team, prng = Random)
|
13
|
+
@to_s = make_name(prng)
|
14
|
+
@team = team
|
15
|
+
# Generate stats
|
16
|
+
@stats = {}
|
17
|
+
%i[offense defense agility].each { |stat| @stats[stat] = prng.rand * 5 }
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hash<Symbol => String>]
|
21
|
+
def stat_strings
|
22
|
+
@stats.transform_values do |stat|
|
23
|
+
stat.instance_of?(String) ? stat : stat.round(2).to_s.ljust(4, "0")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def make_name(prng)
|
30
|
+
Array.new(2) do
|
31
|
+
combination = "__"
|
32
|
+
next_letter = ""
|
33
|
+
result = ""
|
34
|
+
|
35
|
+
10.times do
|
36
|
+
next_letters = name_chain[combination]
|
37
|
+
break if next_letters.nil?
|
38
|
+
|
39
|
+
cumulative_weights = []
|
40
|
+
next_letters.each_value do |v|
|
41
|
+
cumulative_weights << v + (cumulative_weights.last or 0)
|
42
|
+
end
|
43
|
+
|
44
|
+
next_letter = Utils.weighted_random(next_letters, prng)
|
45
|
+
break if next_letter == "_"
|
46
|
+
|
47
|
+
result += next_letter
|
48
|
+
combination = combination[1] + next_letter
|
49
|
+
end
|
50
|
+
|
51
|
+
result
|
52
|
+
end.join(" ")
|
53
|
+
end
|
54
|
+
|
55
|
+
def name_chain
|
56
|
+
if @cached_chain.nil?
|
57
|
+
@cached_chain = {}
|
58
|
+
File.open(File.expand_path("../../data/external/names.txt", __dir__)).each do |n|
|
59
|
+
name = "__#{n.chomp}__"
|
60
|
+
(name.length - 3).times do |i|
|
61
|
+
combination = name[i, 2]
|
62
|
+
next_letter = name[i + 2]
|
63
|
+
@cached_chain[combination] ||= {}
|
64
|
+
@cached_chain[combination][next_letter] ||= 0
|
65
|
+
@cached_chain[combination][next_letter] += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@cached_chain
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|