rich_engine 0.0.0 → 0.1.0
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/.github/workflows/tests-and-linter.yml +9 -8
- data/.gitignore +7 -1
- data/.yardopts +6 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +42 -33
- data/README.md +318 -17
- data/examples/background.rb +1 -1
- data/examples/command_line_fps.rb +620 -0
- data/lib/rich_engine/animation.rb +137 -0
- data/lib/rich_engine/canvas/slot.rb +72 -0
- data/lib/rich_engine/canvas.rb +109 -4
- data/lib/rich_engine/chance.rb +17 -0
- data/lib/rich_engine/cooldown.rb +23 -4
- data/lib/rich_engine/enum/mixin.rb +34 -0
- data/lib/rich_engine/enum/value.rb +31 -0
- data/lib/rich_engine/enum.rb +26 -2
- data/lib/rich_engine/game.rb +68 -5
- data/lib/rich_engine/io.rb +39 -16
- data/lib/rich_engine/matrix.rb +57 -0
- data/lib/rich_engine/string_colors.rb +218 -125
- data/lib/rich_engine/terminal/cursor.rb +15 -0
- data/lib/rich_engine/terminal.rb +19 -0
- data/lib/rich_engine/timer/every.rb +15 -0
- data/lib/rich_engine/timer.rb +17 -0
- data/lib/rich_engine/ui/textures.rb +32 -0
- data/lib/rich_engine/version.rb +2 -1
- data/lib/rich_engine.rb +8 -0
- data/mise.toml +1 -1
- data/rich_engine.gemspec +1 -1
- metadata +12 -4
|
@@ -1,183 +1,276 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RichEngine
|
|
4
|
+
# A refinement that adds color and style methods to String, plus helpers
|
|
5
|
+
# for resolving color specs.
|
|
6
|
+
#
|
|
7
|
+
# Colors are emitted as 256-color (8-bit) escape sequences using only the
|
|
8
|
+
# theme-independent regions of the palette: the 6x6x6 color cube (16-231)
|
|
9
|
+
# and the grayscale ramp (232-255). Unlike the classic 16 ANSI colors,
|
|
10
|
+
# these render the same RGB values in every terminal.
|
|
11
|
+
#
|
|
12
|
+
# Anywhere a color is accepted, you can pass:
|
|
13
|
+
#
|
|
14
|
+
# - a named color (`Symbol`): `:red`, `:bright_cyan`, ... (see {PALETTE})
|
|
15
|
+
# - a hex string: `"#ff8800"` (also `"ff8800"` and shorthand `"#f80"`)
|
|
16
|
+
# - an RGB array: `[255, 136, 0]`
|
|
17
|
+
# - a raw 256-color index (`Integer`): `208`
|
|
18
|
+
#
|
|
19
|
+
# Hex and RGB values snap to the nearest color in the fixed 256-color
|
|
20
|
+
# palette.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# using RichEngine::StringColors
|
|
24
|
+
#
|
|
25
|
+
# "hello".fg(:red) # named color
|
|
26
|
+
# "hello".fg("#ff8800").bold # custom color, chained with a style
|
|
27
|
+
# "hello".bg([0, 0, 215]) # RGB background
|
|
4
28
|
module StringColors
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
29
|
+
# Named colors mapped to fixed color-cube/grayscale indices.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash{Symbol => Integer}]
|
|
32
|
+
PALETTE = {
|
|
33
|
+
black: 16,
|
|
34
|
+
red: 160,
|
|
35
|
+
green: 40,
|
|
36
|
+
yellow: 184,
|
|
37
|
+
orange: 208,
|
|
38
|
+
blue: 20,
|
|
39
|
+
magenta: 164,
|
|
40
|
+
cyan: 44,
|
|
41
|
+
white: 231,
|
|
42
|
+
dark_gray: 238,
|
|
43
|
+
dark_grey: 238,
|
|
44
|
+
gray: 244,
|
|
45
|
+
grey: 244,
|
|
46
|
+
light_gray: 188,
|
|
47
|
+
light_grey: 188,
|
|
48
|
+
bright_red: 196,
|
|
49
|
+
bright_green: 46,
|
|
50
|
+
bright_yellow: 226,
|
|
51
|
+
bright_blue: 21,
|
|
52
|
+
bright_magenta: 201,
|
|
53
|
+
bright_cyan: 51
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
# Channel intensities used by the 6x6x6 color cube.
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<Integer>]
|
|
59
|
+
CUBE_LEVELS = [0, 95, 135, 175, 215, 255].freeze
|
|
60
|
+
|
|
61
|
+
# Resolves a color spec into a 256-color index.
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# index_for(:red) # => 160 (named palette color)
|
|
65
|
+
# index_for("#ff8800") # => 208 (nearest cube color)
|
|
66
|
+
# index_for([255, 136, 0]) # => 208 (nearest cube color)
|
|
67
|
+
# index_for(208) # => 208 (used as-is)
|
|
68
|
+
#
|
|
69
|
+
# @param color [Symbol, String, Array<Integer>, Integer] a color spec
|
|
70
|
+
# @return [Integer] an index into the 256-color palette
|
|
71
|
+
# @raise [ArgumentError] if the spec is not one of the supported types
|
|
72
|
+
# @raise [KeyError] if a named color is not in {PALETTE}
|
|
73
|
+
def self.index_for(color)
|
|
74
|
+
case color
|
|
75
|
+
when Symbol then PALETTE.fetch(color)
|
|
76
|
+
when Integer then color
|
|
77
|
+
when Array then rgb_to_index(*color)
|
|
78
|
+
when String then rgb_to_index(*hex_to_rgb(color))
|
|
79
|
+
else
|
|
80
|
+
raise ArgumentError, "invalid color: #{color.inspect}"
|
|
54
81
|
end
|
|
82
|
+
end
|
|
55
83
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
# Returns `:black` or `:white`, whichever has the higher WCAG contrast
|
|
85
|
+
# ratio against the given color, like CSS's `contrast-color()` function.
|
|
86
|
+
# Ties go to `:white`.
|
|
87
|
+
#
|
|
88
|
+
# @example Readable labels on a dynamic background
|
|
89
|
+
# label_color = StringColors.contrast_color(bg_color)
|
|
90
|
+
# canvas.write_string("Score", x: 0, y: 0, fg: label_color, bg: bg_color)
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# contrast_color(:yellow) # => :black
|
|
94
|
+
# contrast_color("#0000d7") # => :white
|
|
95
|
+
#
|
|
96
|
+
# @param color [Symbol, String, Array<Integer>, Integer] a color spec
|
|
97
|
+
# (raw indices 0-15 raise, since their RGB values are theme-dependent)
|
|
98
|
+
# @return [Symbol] `:black` or `:white`
|
|
99
|
+
def self.contrast_color(color)
|
|
100
|
+
luminance = relative_luminance(rgb_for(color))
|
|
101
|
+
white_contrast = 1.05 / (luminance + 0.05)
|
|
102
|
+
black_contrast = (luminance + 0.05) / 0.05
|
|
103
|
+
|
|
104
|
+
(white_contrast >= black_contrast) ? :white : :black
|
|
105
|
+
end
|
|
59
106
|
|
|
60
|
-
|
|
61
|
-
|
|
107
|
+
# Resolves a color spec into `[r, g, b]` channel values.
|
|
108
|
+
#
|
|
109
|
+
# @api private
|
|
110
|
+
def self.rgb_for(color)
|
|
111
|
+
case color
|
|
112
|
+
when Symbol then index_to_rgb(PALETTE.fetch(color))
|
|
113
|
+
when Integer then index_to_rgb(color)
|
|
114
|
+
when Array then color
|
|
115
|
+
when String then hex_to_rgb(color)
|
|
116
|
+
else
|
|
117
|
+
raise ArgumentError, "invalid color: #{color.inspect}"
|
|
62
118
|
end
|
|
119
|
+
end
|
|
63
120
|
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
# Converts a 256-color index (16-255) back into `[r, g, b]` values.
|
|
122
|
+
#
|
|
123
|
+
# @api private
|
|
124
|
+
def self.index_to_rgb(index)
|
|
125
|
+
if index.between?(16, 231)
|
|
126
|
+
cube = index - 16
|
|
127
|
+
[cube / 36, (cube % 36) / 6, cube % 6].map { |level| CUBE_LEVELS[level] }
|
|
128
|
+
elsif index.between?(232, 255)
|
|
129
|
+
level = 8 + (10 * (index - 232))
|
|
130
|
+
[level, level, level]
|
|
131
|
+
else
|
|
132
|
+
raise ArgumentError, "color index #{index} is theme-dependent; use 16-255"
|
|
66
133
|
end
|
|
134
|
+
end
|
|
67
135
|
|
|
68
|
-
|
|
69
|
-
|
|
136
|
+
# WCAG 2 relative luminance of an sRGB color, from 0.0 (black) to 1.0
|
|
137
|
+
# (white). See https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
138
|
+
#
|
|
139
|
+
# @api private
|
|
140
|
+
def self.relative_luminance(rgb)
|
|
141
|
+
r, g, b = rgb.map do |channel|
|
|
142
|
+
value = channel / 255.0
|
|
143
|
+
(value <= 0.04045) ? value / 12.92 : ((value + 0.055) / 1.055)**2.4
|
|
70
144
|
end
|
|
71
145
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
146
|
+
(0.2126 * r) + (0.7152 * g) + (0.0722 * b)
|
|
147
|
+
end
|
|
75
148
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
149
|
+
# Parses `"#rrggbb"`, `"rrggbb"`, or `"#rgb"` into `[r, g, b]` values.
|
|
150
|
+
#
|
|
151
|
+
# @api private
|
|
152
|
+
def self.hex_to_rgb(hex)
|
|
153
|
+
digits = hex.delete_prefix("#")
|
|
154
|
+
digits = digits.each_char.map { |c| c * 2 }.join if digits.length == 3
|
|
155
|
+
digits.scan(/../).map { |pair| pair.to_i(16) }
|
|
156
|
+
end
|
|
79
157
|
|
|
80
|
-
|
|
81
|
-
|
|
158
|
+
# Snaps `[r, g, b]` values to the nearest cube/grayscale index.
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
def self.rgb_to_index(red, green, blue)
|
|
162
|
+
if red == green && green == blue
|
|
163
|
+
gray_index(red)
|
|
164
|
+
else
|
|
165
|
+
r, g, b = [red, green, blue].map { |channel| nearest_cube_level(channel) }
|
|
166
|
+
16 + (36 * r) + (6 * g) + b
|
|
82
167
|
end
|
|
168
|
+
end
|
|
83
169
|
|
|
84
|
-
|
|
170
|
+
# Index of the cube level (0-5) closest to the given channel value.
|
|
171
|
+
#
|
|
172
|
+
# @api private
|
|
173
|
+
def self.nearest_cube_level(channel)
|
|
174
|
+
CUBE_LEVELS.each_index.min_by { |i| (CUBE_LEVELS[i] - channel).abs }
|
|
175
|
+
end
|
|
85
176
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
177
|
+
# The grayscale ramp (232-255) covers intensities 8, 18, ... 238. Levels
|
|
178
|
+
# near the extremes snap to the cube's black (16) and white (231).
|
|
179
|
+
#
|
|
180
|
+
# @api private
|
|
181
|
+
def self.gray_index(level)
|
|
182
|
+
return 16 if level < 4
|
|
183
|
+
return 231 if level > 243
|
|
89
184
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
185
|
+
232 + ((level - 8) / 10.0).round.clamp(0, 23)
|
|
186
|
+
end
|
|
93
187
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
188
|
+
refine String do
|
|
189
|
+
# Colors the string's foreground.
|
|
190
|
+
#
|
|
191
|
+
# @example
|
|
192
|
+
# "hello".fg(:red)
|
|
193
|
+
# "hello".fg("#ff8800")
|
|
194
|
+
#
|
|
195
|
+
# @param color [Symbol, String, Array<Integer>, Integer] a color spec
|
|
196
|
+
# (see {StringColors}); `:transparent` replaces the text with spaces
|
|
197
|
+
# @return [String] the string wrapped in escape sequences
|
|
198
|
+
def fg(color)
|
|
199
|
+
return transparent if color == :transparent
|
|
97
200
|
|
|
98
|
-
|
|
99
|
-
color(43)
|
|
201
|
+
"\e[38;5;#{StringColors.index_for(color)}m#{self}\e[39m"
|
|
100
202
|
end
|
|
101
203
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
204
|
+
# Colors the string's background.
|
|
205
|
+
#
|
|
206
|
+
# @example
|
|
207
|
+
# "hello".bg(:cyan)
|
|
208
|
+
# "hello".bg("#222222")
|
|
209
|
+
#
|
|
210
|
+
# @param color [Symbol, String, Array<Integer>, Integer] a color spec
|
|
211
|
+
# (see {StringColors}); `:transparent` keeps the terminal's default
|
|
212
|
+
# background
|
|
213
|
+
# @return [String] the string wrapped in escape sequences
|
|
214
|
+
def bg(color)
|
|
215
|
+
return on_transparent if color == :transparent
|
|
105
216
|
|
|
106
|
-
|
|
107
|
-
color(45)
|
|
217
|
+
"\e[48;5;#{StringColors.index_for(color)}m#{self}\e[49m"
|
|
108
218
|
end
|
|
109
219
|
|
|
110
|
-
|
|
111
|
-
color(46)
|
|
112
|
-
end
|
|
220
|
+
# Colors
|
|
113
221
|
|
|
114
|
-
|
|
115
|
-
|
|
222
|
+
# Replaces every character with a space.
|
|
223
|
+
#
|
|
224
|
+
# @return [String]
|
|
225
|
+
def transparent
|
|
226
|
+
gsub(/./, " ")
|
|
116
227
|
end
|
|
117
228
|
|
|
229
|
+
# Renders the string on the terminal's default background.
|
|
230
|
+
#
|
|
231
|
+
# @return [String]
|
|
118
232
|
def on_transparent
|
|
119
|
-
|
|
233
|
+
"\e[49m#{self}\e[49m"
|
|
120
234
|
end
|
|
121
235
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def on_bright_yellow
|
|
135
|
-
color(103)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def on_bright_blue
|
|
139
|
-
color(104)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def on_bright_magenta
|
|
143
|
-
color(105)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def on_bright_cyan
|
|
147
|
-
color(106)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def on_bright_white
|
|
151
|
-
color(107)
|
|
236
|
+
# @!macro [attach] palette_color
|
|
237
|
+
# @!method $1
|
|
238
|
+
# Colors the foreground `$1` (equivalent to `fg(:$1)`).
|
|
239
|
+
# @return [String]
|
|
240
|
+
# @!method on_$1
|
|
241
|
+
# Colors the background `$1` (equivalent to `bg(:$1)`).
|
|
242
|
+
# @return [String]
|
|
243
|
+
PALETTE.each_key do |name|
|
|
244
|
+
define_method(name) { fg(name) }
|
|
245
|
+
define_method("on_#{name}") { bg(name) }
|
|
152
246
|
end
|
|
153
247
|
|
|
154
248
|
# STYLES
|
|
155
249
|
|
|
250
|
+
# @return [String] the string styled bold
|
|
156
251
|
def bold
|
|
157
252
|
"\e[1m#{self}\e[22m"
|
|
158
253
|
end
|
|
159
254
|
|
|
255
|
+
# @return [String] the string styled italic
|
|
160
256
|
def italic
|
|
161
257
|
"\e[3m#{self}\e[23m"
|
|
162
258
|
end
|
|
163
259
|
|
|
260
|
+
# @return [String] the string underlined
|
|
164
261
|
def underline
|
|
165
262
|
"\e[4m#{self}\e[24m"
|
|
166
263
|
end
|
|
167
264
|
|
|
265
|
+
# @return [String] the string styled blinking
|
|
168
266
|
def blink
|
|
169
267
|
"\e[5m#{self}\e[25m"
|
|
170
268
|
end
|
|
171
269
|
|
|
270
|
+
# @return [String] the string with foreground and background swapped
|
|
172
271
|
def reverse_color
|
|
173
272
|
"\e[7m#{self}\e[27m"
|
|
174
273
|
end
|
|
175
|
-
|
|
176
|
-
private
|
|
177
|
-
|
|
178
|
-
def color(n)
|
|
179
|
-
"\e[#{n}m#{self}\e[0m"
|
|
180
|
-
end
|
|
181
274
|
end
|
|
182
275
|
end
|
|
183
276
|
end
|
|
@@ -2,17 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
module RichEngine
|
|
4
4
|
module Terminal
|
|
5
|
+
# Internal plumbing for controlling the terminal cursor: visibility and
|
|
6
|
+
# positioning.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
5
9
|
module Cursor
|
|
6
10
|
extend self
|
|
7
11
|
|
|
12
|
+
# Hides the cursor.
|
|
13
|
+
#
|
|
14
|
+
# @return [void]
|
|
8
15
|
def hide
|
|
9
16
|
system("tput civis")
|
|
10
17
|
end
|
|
11
18
|
|
|
19
|
+
# Shows the cursor.
|
|
20
|
+
#
|
|
21
|
+
# @return [void]
|
|
12
22
|
def display
|
|
13
23
|
system("tput cnorm")
|
|
14
24
|
end
|
|
15
25
|
|
|
26
|
+
# Moves the cursor to the given screen position.
|
|
27
|
+
#
|
|
28
|
+
# @param x [Integer] the column to move to
|
|
29
|
+
# @param y [Integer] the row to move to
|
|
30
|
+
# @return [void]
|
|
16
31
|
def goto(x, y)
|
|
17
32
|
$stdout.goto(x, y)
|
|
18
33
|
end
|
data/lib/rich_engine/terminal.rb
CHANGED
|
@@ -3,25 +3,44 @@
|
|
|
3
3
|
require_relative "terminal/cursor"
|
|
4
4
|
|
|
5
5
|
module RichEngine
|
|
6
|
+
# Internal plumbing used by {Game} to prepare and restore the terminal:
|
|
7
|
+
# clearing the screen, toggling cursor visibility, and toggling input echo.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
6
10
|
module Terminal
|
|
7
11
|
module_function
|
|
8
12
|
|
|
13
|
+
# Clears the screen.
|
|
14
|
+
#
|
|
15
|
+
# @return [void]
|
|
9
16
|
def clear
|
|
10
17
|
$stdout.clear_screen
|
|
11
18
|
end
|
|
12
19
|
|
|
20
|
+
# Hides the terminal cursor.
|
|
21
|
+
#
|
|
22
|
+
# @return [void]
|
|
13
23
|
def hide_cursor
|
|
14
24
|
Cursor.hide
|
|
15
25
|
end
|
|
16
26
|
|
|
27
|
+
# Shows the terminal cursor.
|
|
28
|
+
#
|
|
29
|
+
# @return [void]
|
|
17
30
|
def display_cursor
|
|
18
31
|
Cursor.display
|
|
19
32
|
end
|
|
20
33
|
|
|
34
|
+
# Stops typed characters from being echoed to the screen.
|
|
35
|
+
#
|
|
36
|
+
# @return [void]
|
|
21
37
|
def disable_echo
|
|
22
38
|
$stdin.echo = false
|
|
23
39
|
end
|
|
24
40
|
|
|
41
|
+
# Resumes echoing typed characters to the screen.
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
25
44
|
def enable_echo
|
|
26
45
|
$stdin.echo = true
|
|
27
46
|
end
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module RichEngine
|
|
4
4
|
class Timer
|
|
5
|
+
# A scheduler that fires at a fixed interval, created via {Timer.every}.
|
|
5
6
|
class Every
|
|
7
|
+
# @param interval [Integer, Float] seconds between firings.
|
|
6
8
|
def initialize(interval)
|
|
7
9
|
@interval = interval
|
|
8
10
|
@ready = false
|
|
9
11
|
@timer = Timer.new
|
|
10
12
|
end
|
|
11
13
|
|
|
14
|
+
# Accumulates elapsed time and marks the scheduler ready once the
|
|
15
|
+
# interval is reached.
|
|
16
|
+
#
|
|
17
|
+
# @param elapsed_time [Float] seconds since the last frame.
|
|
18
|
+
# @return [void]
|
|
12
19
|
def update(elapsed_time)
|
|
13
20
|
@timer.update(elapsed_time)
|
|
14
21
|
|
|
@@ -17,6 +24,10 @@ module RichEngine
|
|
|
17
24
|
end
|
|
18
25
|
end
|
|
19
26
|
|
|
27
|
+
# Runs the block and resets the timer when the interval has elapsed.
|
|
28
|
+
#
|
|
29
|
+
# @yield called once each time the interval is reached.
|
|
30
|
+
# @return [void]
|
|
20
31
|
def when_ready(&block)
|
|
21
32
|
if @ready
|
|
22
33
|
block.call
|
|
@@ -24,6 +35,10 @@ module RichEngine
|
|
|
24
35
|
end
|
|
25
36
|
end
|
|
26
37
|
|
|
38
|
+
# Changes the firing interval and resets the timer.
|
|
39
|
+
#
|
|
40
|
+
# @param interval [Integer, Float] the new interval in seconds.
|
|
41
|
+
# @return [void]
|
|
27
42
|
def interval=(interval)
|
|
28
43
|
@interval = interval
|
|
29
44
|
reset!
|
data/lib/rich_engine/timer.rb
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RichEngine
|
|
4
|
+
# Accumulates elapsed time; drive it by calling {#update} each frame.
|
|
4
5
|
class Timer
|
|
6
|
+
# Returns a small scheduler that fires a block at a fixed interval.
|
|
7
|
+
#
|
|
8
|
+
# @param seconds [Integer, Float] the interval between firings.
|
|
9
|
+
# @return [RichEngine::Timer::Every] the interval scheduler.
|
|
10
|
+
# @example
|
|
11
|
+
# spawn = RichEngine::Timer.every(seconds: 0.5)
|
|
12
|
+
# spawn.update(dt)
|
|
13
|
+
# spawn.when_ready { spawn_enemy! }
|
|
5
14
|
def self.every(seconds: 1, &block)
|
|
6
15
|
Every.new(seconds)
|
|
7
16
|
end
|
|
@@ -10,14 +19,22 @@ module RichEngine
|
|
|
10
19
|
@timer = 0
|
|
11
20
|
end
|
|
12
21
|
|
|
22
|
+
# Adds the elapsed time to the accumulated total.
|
|
23
|
+
#
|
|
24
|
+
# @param dt [Float] seconds since the last frame.
|
|
25
|
+
# @return [Float] the new accumulated time.
|
|
13
26
|
def update(dt)
|
|
14
27
|
@timer += dt
|
|
15
28
|
end
|
|
16
29
|
|
|
30
|
+
# @return [Float] the accumulated time in seconds.
|
|
17
31
|
def get
|
|
18
32
|
@timer
|
|
19
33
|
end
|
|
20
34
|
|
|
35
|
+
# Resets the accumulated time back to zero.
|
|
36
|
+
#
|
|
37
|
+
# @return [Integer] zero.
|
|
21
38
|
def reset!
|
|
22
39
|
@timer = 0
|
|
23
40
|
end
|
|
@@ -1,44 +1,76 @@
|
|
|
1
1
|
module RichEngine
|
|
2
|
+
# Reusable UI building blocks for games.
|
|
2
3
|
module UI
|
|
4
|
+
# Convenience glyphs for shading and blocky fills.
|
|
3
5
|
module Textures
|
|
4
6
|
extend self
|
|
5
7
|
|
|
8
|
+
# The empty glyph (space).
|
|
9
|
+
#
|
|
10
|
+
# @return [String] " "
|
|
6
11
|
def empty
|
|
7
12
|
" "
|
|
8
13
|
end
|
|
9
14
|
|
|
15
|
+
# The solid block glyph.
|
|
16
|
+
#
|
|
17
|
+
# @return [String] "█"
|
|
10
18
|
def solid
|
|
11
19
|
"█"
|
|
12
20
|
end
|
|
13
21
|
|
|
22
|
+
# The light shade glyph.
|
|
23
|
+
#
|
|
24
|
+
# @return [String] "▓"
|
|
14
25
|
def light_shade
|
|
15
26
|
"▓"
|
|
16
27
|
end
|
|
17
28
|
|
|
29
|
+
# The medium shade glyph.
|
|
30
|
+
#
|
|
31
|
+
# @return [String] "▒"
|
|
18
32
|
def medium_shade
|
|
19
33
|
"▒"
|
|
20
34
|
end
|
|
21
35
|
|
|
36
|
+
# The dark shade glyph.
|
|
37
|
+
#
|
|
38
|
+
# @return [String] "░"
|
|
22
39
|
def dark_shade
|
|
23
40
|
"░"
|
|
24
41
|
end
|
|
25
42
|
|
|
43
|
+
# The upper half block glyph.
|
|
44
|
+
#
|
|
45
|
+
# @return [String] "▀"
|
|
26
46
|
def top_half
|
|
27
47
|
"▀"
|
|
28
48
|
end
|
|
29
49
|
|
|
50
|
+
# The lower half block glyph.
|
|
51
|
+
#
|
|
52
|
+
# @return [String] "▄"
|
|
30
53
|
def bottom_half
|
|
31
54
|
"▄"
|
|
32
55
|
end
|
|
33
56
|
|
|
57
|
+
# The left half block glyph.
|
|
58
|
+
#
|
|
59
|
+
# @return [String] "▌"
|
|
34
60
|
def left_half
|
|
35
61
|
"▌"
|
|
36
62
|
end
|
|
37
63
|
|
|
64
|
+
# The right half block glyph.
|
|
65
|
+
#
|
|
66
|
+
# @return [String] "▐"
|
|
38
67
|
def right_half
|
|
39
68
|
"▐"
|
|
40
69
|
end
|
|
41
70
|
|
|
71
|
+
# The plaid (half-shade diagonal) glyph.
|
|
72
|
+
#
|
|
73
|
+
# @return [String] "▞"
|
|
42
74
|
def plaid
|
|
43
75
|
"▞"
|
|
44
76
|
end
|
data/lib/rich_engine/version.rb
CHANGED
data/lib/rich_engine.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "rich_engine/animation"
|
|
3
4
|
require_relative "rich_engine/canvas"
|
|
4
5
|
require_relative "rich_engine/chance"
|
|
5
6
|
require_relative "rich_engine/cooldown"
|
|
@@ -13,5 +14,12 @@ require_relative "rich_engine/timer/every"
|
|
|
13
14
|
require_relative "rich_engine/ui/textures"
|
|
14
15
|
require_relative "rich_engine/version"
|
|
15
16
|
|
|
17
|
+
# A tiny terminal game engine for Ruby. It provides a simple game loop, a 2D
|
|
18
|
+
# character canvas with colors, non-blocking keyboard input, and a handful of
|
|
19
|
+
# helpers (timers, cooldowns, RNG, enums, matrices) so you can ship playful
|
|
20
|
+
# ASCII games quickly.
|
|
21
|
+
#
|
|
22
|
+
# At its core, you subclass {RichEngine::Game}, implement a few lifecycle hooks,
|
|
23
|
+
# and draw to a {RichEngine::Canvas} each frame.
|
|
16
24
|
module RichEngine
|
|
17
25
|
end
|
data/mise.toml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
[tools]
|
|
2
|
-
ruby = "3.
|
|
2
|
+
ruby = "3.2"
|
data/rich_engine.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "A Ruby engine for terminal games."
|
|
12
12
|
spec.homepage = "https://github.com/MatheusRich/rich_engine"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|