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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78e2cb0aa9a46a3a771739f12c82d372744b2425cf423bcf647807056bb26758
|
|
4
|
+
data.tar.gz: 1f0a91b2f173c9115e6573c76ea706e4bd644d56232e21da7360dab33d3b1e6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4343141533689b538f7ea5fc87a2a281fc09915d260b6ef5f193b67dfa2d6f9f459ec4f22123a17d857212be9191e49fd926cc8a7248d3aa4fdb0f4e97638385
|
|
7
|
+
data.tar.gz: d198b768cd889f969ae6f27f47da48b3b2ec1990ef340e546e70dce6b2ecf98654e90b43567491799a21a8dfb7b184622656fa9ee2d109c867fdc276099a5ad5
|
|
@@ -11,18 +11,19 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
strategy:
|
|
13
13
|
matrix:
|
|
14
|
-
ruby: [2
|
|
14
|
+
ruby: ['3.2', '3.3', '3.4']
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
18
|
-
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
persist-credentials: false
|
|
20
|
+
- name: Set up Ruby
|
|
19
21
|
uses: ruby/setup-ruby@v1
|
|
20
22
|
with:
|
|
21
23
|
ruby-version: ${{ matrix.ruby }}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
bundle install --jobs 4 --retry 3
|
|
26
|
-
bundle exec rake
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: bundle exec rake
|
|
27
27
|
- name: Linter
|
|
28
28
|
run: bundle exec standardrb
|
|
29
|
+
continue-on-error: true
|
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rich_engine (0.
|
|
4
|
+
rich_engine (0.0.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
@@ -22,7 +22,8 @@ GEM
|
|
|
22
22
|
ffi (1.17.2-x86_64-darwin)
|
|
23
23
|
ffi (1.17.2-x86_64-linux-gnu)
|
|
24
24
|
ffi (1.17.2-x86_64-linux-musl)
|
|
25
|
-
formatador (1.1
|
|
25
|
+
formatador (1.2.1)
|
|
26
|
+
reline
|
|
26
27
|
guard (2.19.1)
|
|
27
28
|
formatador (>= 0.2.4)
|
|
28
29
|
listen (>= 2.7, < 4.0)
|
|
@@ -38,8 +39,9 @@ GEM
|
|
|
38
39
|
guard-minitest (2.4.6)
|
|
39
40
|
guard-compat (~> 1.2)
|
|
40
41
|
minitest (>= 3.0)
|
|
42
|
+
io-console (0.8.1)
|
|
41
43
|
jaro_winkler (1.6.1)
|
|
42
|
-
json (2.
|
|
44
|
+
json (2.15.1)
|
|
43
45
|
kramdown (2.5.1)
|
|
44
46
|
rexml (>= 3.3.9)
|
|
45
47
|
kramdown-parser-gfm (1.1.0)
|
|
@@ -50,9 +52,9 @@ GEM
|
|
|
50
52
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
51
53
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
52
54
|
logger (1.7.0)
|
|
53
|
-
lumberjack (1.2
|
|
55
|
+
lumberjack (1.4.2)
|
|
54
56
|
method_source (1.1.0)
|
|
55
|
-
minitest (5.
|
|
57
|
+
minitest (5.26.0)
|
|
56
58
|
minitest-focus (1.4.0)
|
|
57
59
|
minitest (>= 4, < 6)
|
|
58
60
|
minitest-reporters (1.7.1)
|
|
@@ -61,32 +63,32 @@ GEM
|
|
|
61
63
|
minitest (>= 5.0)
|
|
62
64
|
ruby-progressbar
|
|
63
65
|
nenv (0.3.0)
|
|
64
|
-
nokogiri (1.18.
|
|
66
|
+
nokogiri (1.18.10-aarch64-linux-gnu)
|
|
65
67
|
racc (~> 1.4)
|
|
66
|
-
nokogiri (1.18.
|
|
68
|
+
nokogiri (1.18.10-aarch64-linux-musl)
|
|
67
69
|
racc (~> 1.4)
|
|
68
|
-
nokogiri (1.18.
|
|
70
|
+
nokogiri (1.18.10-arm-linux-gnu)
|
|
69
71
|
racc (~> 1.4)
|
|
70
|
-
nokogiri (1.18.
|
|
72
|
+
nokogiri (1.18.10-arm-linux-musl)
|
|
71
73
|
racc (~> 1.4)
|
|
72
|
-
nokogiri (1.18.
|
|
74
|
+
nokogiri (1.18.10-arm64-darwin)
|
|
73
75
|
racc (~> 1.4)
|
|
74
|
-
nokogiri (1.18.
|
|
76
|
+
nokogiri (1.18.10-x86_64-darwin)
|
|
75
77
|
racc (~> 1.4)
|
|
76
|
-
nokogiri (1.18.
|
|
78
|
+
nokogiri (1.18.10-x86_64-linux-gnu)
|
|
77
79
|
racc (~> 1.4)
|
|
78
|
-
nokogiri (1.18.
|
|
80
|
+
nokogiri (1.18.10-x86_64-linux-musl)
|
|
79
81
|
racc (~> 1.4)
|
|
80
82
|
notiffany (0.1.3)
|
|
81
83
|
nenv (~> 0.1)
|
|
82
84
|
shellany (~> 0.0)
|
|
83
85
|
observer (0.1.2)
|
|
84
|
-
ostruct (0.6.
|
|
86
|
+
ostruct (0.6.3)
|
|
85
87
|
parallel (1.27.0)
|
|
86
|
-
parser (3.3.
|
|
88
|
+
parser (3.3.9.0)
|
|
87
89
|
ast (~> 2.4.1)
|
|
88
90
|
racc
|
|
89
|
-
prism (1.
|
|
91
|
+
prism (1.6.0)
|
|
90
92
|
pry (0.15.2)
|
|
91
93
|
coderay (~> 1.1)
|
|
92
94
|
method_source (~> 1.0)
|
|
@@ -96,13 +98,15 @@ GEM
|
|
|
96
98
|
rb-fsevent (0.11.2)
|
|
97
99
|
rb-inotify (0.11.1)
|
|
98
100
|
ffi (~> 1.0)
|
|
99
|
-
rbs (3.9.
|
|
101
|
+
rbs (3.9.5)
|
|
100
102
|
logger
|
|
101
|
-
regexp_parser (2.
|
|
103
|
+
regexp_parser (2.11.3)
|
|
104
|
+
reline (0.6.2)
|
|
105
|
+
io-console (~> 0.5)
|
|
102
106
|
reverse_markdown (3.0.0)
|
|
103
107
|
nokogiri
|
|
104
|
-
rexml (3.4.
|
|
105
|
-
rubocop (1.
|
|
108
|
+
rexml (3.4.4)
|
|
109
|
+
rubocop (1.80.2)
|
|
106
110
|
json (~> 2.3)
|
|
107
111
|
language_server-protocol (~> 3.17.0.2)
|
|
108
112
|
lint_roller (~> 1.1.0)
|
|
@@ -110,10 +114,10 @@ GEM
|
|
|
110
114
|
parser (>= 3.3.0.2)
|
|
111
115
|
rainbow (>= 2.2.2, < 4.0)
|
|
112
116
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
113
|
-
rubocop-ast (>= 1.
|
|
117
|
+
rubocop-ast (>= 1.46.0, < 2.0)
|
|
114
118
|
ruby-progressbar (~> 1.7)
|
|
115
119
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
116
|
-
rubocop-ast (1.
|
|
120
|
+
rubocop-ast (1.47.1)
|
|
117
121
|
parser (>= 3.3.7.2)
|
|
118
122
|
prism (~> 1.4)
|
|
119
123
|
rubocop-performance (1.25.0)
|
|
@@ -126,9 +130,9 @@ GEM
|
|
|
126
130
|
docile (~> 1.1)
|
|
127
131
|
simplecov-html (~> 0.11)
|
|
128
132
|
simplecov_json_formatter (~> 0.1)
|
|
129
|
-
simplecov-html (0.13.
|
|
133
|
+
simplecov-html (0.13.2)
|
|
130
134
|
simplecov_json_formatter (0.1.4)
|
|
131
|
-
solargraph (0.
|
|
135
|
+
solargraph (0.57.0)
|
|
132
136
|
backport (~> 1.2)
|
|
133
137
|
benchmark (~> 0.4)
|
|
134
138
|
bundler (~> 2.0)
|
|
@@ -140,17 +144,19 @@ GEM
|
|
|
140
144
|
observer (~> 0.1)
|
|
141
145
|
ostruct (~> 0.6)
|
|
142
146
|
parser (~> 3.0)
|
|
143
|
-
|
|
147
|
+
prism (~> 1.4)
|
|
148
|
+
rbs (>= 3.6.1, <= 4.0.0.dev.4)
|
|
144
149
|
reverse_markdown (~> 3.0)
|
|
145
|
-
rubocop (~> 1.
|
|
150
|
+
rubocop (~> 1.76)
|
|
146
151
|
thor (~> 1.0)
|
|
147
152
|
tilt (~> 2.0)
|
|
148
153
|
yard (~> 0.9, >= 0.9.24)
|
|
154
|
+
yard-activesupport-concern (~> 0.0)
|
|
149
155
|
yard-solargraph (~> 0.1)
|
|
150
|
-
standard (1.
|
|
156
|
+
standard (1.51.1)
|
|
151
157
|
language_server-protocol (~> 3.17.0.2)
|
|
152
158
|
lint_roller (~> 1.0)
|
|
153
|
-
rubocop (~> 1.
|
|
159
|
+
rubocop (~> 1.80.2)
|
|
154
160
|
standard-custom (~> 1.0.0)
|
|
155
161
|
standard-performance (~> 1.8)
|
|
156
162
|
standard-custom (1.0.2)
|
|
@@ -161,12 +167,14 @@ GEM
|
|
|
161
167
|
rubocop-performance (~> 1.25.0)
|
|
162
168
|
standardrb (1.0.1)
|
|
163
169
|
standard
|
|
164
|
-
thor (1.
|
|
165
|
-
tilt (2.6.
|
|
166
|
-
unicode-display_width (3.
|
|
167
|
-
unicode-emoji (~> 4.
|
|
168
|
-
unicode-emoji (4.0
|
|
170
|
+
thor (1.4.0)
|
|
171
|
+
tilt (2.6.1)
|
|
172
|
+
unicode-display_width (3.2.0)
|
|
173
|
+
unicode-emoji (~> 4.1)
|
|
174
|
+
unicode-emoji (4.1.0)
|
|
169
175
|
yard (0.9.37)
|
|
176
|
+
yard-activesupport-concern (0.0.1)
|
|
177
|
+
yard (>= 0.8)
|
|
170
178
|
yard-solargraph (0.1.0)
|
|
171
179
|
yard (~> 0.9)
|
|
172
180
|
|
|
@@ -191,6 +199,7 @@ DEPENDENCIES
|
|
|
191
199
|
simplecov
|
|
192
200
|
solargraph
|
|
193
201
|
standardrb
|
|
202
|
+
yard
|
|
194
203
|
|
|
195
204
|
BUNDLED WITH
|
|
196
205
|
2.6.8
|
data/README.md
CHANGED
|
@@ -1,40 +1,341 @@
|
|
|
1
1
|
# RichEngine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
RichEngine is a tiny terminal game engine for Ruby. It gives you a simple game
|
|
4
|
+
loop, a 2D character canvas with colors, non-blocking keyboard input, and a
|
|
5
|
+
handful of helpers (timers, cooldowns, RNG, enums, matrices) so you can ship
|
|
6
|
+
playful ASCII games quickly.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
At its core, you subclass `RichEngine::Game`, implement a few lifecycle hooks,
|
|
9
|
+
and draw to a `Canvas` each frame.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
This README is a tour. For method-by-method reference, see the
|
|
12
|
+
[API docs](https://rubydoc.info/gems/rich_engine) (or run
|
|
13
|
+
`bundle exec yard server` locally).
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
## Quick start: build a simple game
|
|
16
|
+
|
|
17
|
+
Below is a minimal, complete example showing how to:
|
|
18
|
+
- create a game by subclassing `RichEngine::Game`
|
|
19
|
+
- quit on a key press
|
|
20
|
+
- draw text and shapes to the screen
|
|
21
|
+
- use canvas slots to keep a bottom HUD separate from the playfield
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "rich_engine"
|
|
25
|
+
|
|
26
|
+
class MyGame < RichEngine::Game
|
|
27
|
+
using RichEngine::StringColors
|
|
28
|
+
|
|
29
|
+
TITLE = "Catch the Star"
|
|
30
|
+
PLAYER_CHAR = "@"
|
|
31
|
+
PLAYER_COLOR = :yellow
|
|
32
|
+
ITEM_COLORS = [:green, :magenta, :cyan]
|
|
33
|
+
ITEM_CHAR = "*"
|
|
34
|
+
HUD_HEIGHT = 3
|
|
35
|
+
|
|
36
|
+
def on_create
|
|
37
|
+
@score = 0
|
|
38
|
+
@player_x = 2
|
|
39
|
+
@player_y = field_height / 2
|
|
40
|
+
@timer = RichEngine::Cooldown.new(5.0)
|
|
41
|
+
@field = @canvas.slot(x: 0, y: 0, width: @width, height: field_height, bg: RichEngine::UI::Textures.solid.white)
|
|
42
|
+
@hud = @canvas.slot(x: 0, y: field_height, width: @width, height: HUD_HEIGHT)
|
|
43
|
+
spawn_item
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# elapsed_time: seconds since last frame (Float)
|
|
47
|
+
# key: last key pressed (Symbol) or nil
|
|
48
|
+
def on_update(elapsed_time, key)
|
|
49
|
+
quit! if key == :q || key == :esc
|
|
50
|
+
|
|
51
|
+
# Move player with arrow keys
|
|
52
|
+
case key
|
|
53
|
+
when :left then @player_x -= 1
|
|
54
|
+
when :right then @player_x += 1
|
|
55
|
+
when :up then @player_y -= 1
|
|
56
|
+
when :down then @player_y += 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Keep player inside the game field (above the HUD)
|
|
60
|
+
@player_x = @player_x.clamp(0, @width - 1)
|
|
61
|
+
@player_y = @player_y.clamp(0, field_height - 1)
|
|
62
|
+
|
|
63
|
+
# Game over if time runs out
|
|
64
|
+
@timer.update(elapsed_time)
|
|
65
|
+
if @timer.finished?
|
|
66
|
+
@game_over = true
|
|
67
|
+
quit!
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Pick up item
|
|
71
|
+
if @player_x == @item_x && @player_y == @item_y
|
|
72
|
+
@score += 1
|
|
73
|
+
spawn_item
|
|
74
|
+
@timer.reset!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# rendering the frame
|
|
78
|
+
@canvas.clear
|
|
79
|
+
|
|
80
|
+
@field.write_string(ITEM_CHAR, x: @item_x, y: @item_y, fg: @item_color)
|
|
81
|
+
@field.write_string(PLAYER_CHAR, x: @player_x, y: @player_y, fg: PLAYER_COLOR)
|
|
82
|
+
|
|
83
|
+
@hud.write_string(TITLE, x: 0, y: 0, fg: :bright_cyan)
|
|
84
|
+
@hud.write_string("Score: #{@score}", x: 0, y: 1, fg: :bright_yellow)
|
|
85
|
+
@hud.write_string("Time: #{format('%.1f', @timer.get)}s", x: 0, y: 2, fg: :bright_green)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def on_destroy
|
|
89
|
+
puts(@game_over ? "Game over! Final score: #{@score}" : "Thanks for playing! Score: #{@score}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def field_height
|
|
95
|
+
@height - HUD_HEIGHT
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def spawn_item
|
|
99
|
+
@item_x = rand(@width)
|
|
100
|
+
@item_y = rand(field_height)
|
|
101
|
+
@item_color = ITEM_COLORS.sample
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
MyGame.play(width: 50, height: 12)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Notes
|
|
109
|
+
- Hooks:
|
|
110
|
+
- `on_create` runs once at start
|
|
111
|
+
- `on_update(elapsed_time, key)` runs every frame
|
|
112
|
+
- `on_destroy` runs when the game exits.
|
|
113
|
+
- Keys: letters are symbols (e.g., `:q`), plus arrows (`:up`, `:down`, `:left`, `:right`), `:space`, `:enter`, `:esc`, `:pg_up`, `:pg_down`, `:home`, `:end`.
|
|
114
|
+
- Drawing: all drawing happens on `@canvas`, via `write_string`, `draw_rect`,
|
|
115
|
+
`draw_circle`, and `draw_sprite`. Call `@canvas.clear` each frame if you want
|
|
116
|
+
to redraw from scratch.
|
|
117
|
+
- Rendering and frame pacing are handled for you:
|
|
118
|
+
- `Game` flushes the canvas after each frame
|
|
119
|
+
- `Game` auto-sleeps to hit your target FPS (60 by default, but configurable via `target_fps:` on `Game.play`)
|
|
120
|
+
|
|
121
|
+
## Canvas slots (sub-canvases)
|
|
122
|
+
|
|
123
|
+
Slots are sub-regions of a canvas that translate local coordinates and clip drawing automatically. Great for HUDs and side panels.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
canvas = RichEngine::Canvas.new(100, 40)
|
|
127
|
+
hud = canvas.slot(x: 0, y: 35, width: 100, height: 5, bg: " ")
|
|
128
|
+
hud.clear
|
|
129
|
+
hud.write_string("Score: 10", x: 2, y: 1, fg: :bright_yellow)
|
|
130
|
+
|
|
131
|
+
log = canvas.slot(x: 80, y: 0, width: 20, height: 35)
|
|
132
|
+
log.write_string("Hello", x: 1, y: 1) # writes to (81, 1) on the parent canvas
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Colors
|
|
136
|
+
|
|
137
|
+
Colors are emitted as 256-color escape sequences using only the theme-independent
|
|
138
|
+
regions of the palette, so they look the same in every terminal regardless of the
|
|
139
|
+
user's color scheme. Anywhere a color is accepted (`fg:`, `bg:`, `color:`), you
|
|
140
|
+
can pass:
|
|
141
|
+
|
|
142
|
+
- a named color: `:red`, `:bright_cyan`, etc. (see `RichEngine::StringColors::PALETTE`)
|
|
143
|
+
- a hex string: `"#ff8800"` (also `"ff8800"` and shorthand `"#f80"`)
|
|
144
|
+
- an RGB array: `[255, 136, 0]`
|
|
145
|
+
- a raw 256-color index: `208`
|
|
146
|
+
|
|
147
|
+
Hex and RGB values snap to the nearest color in the fixed 256-color palette.
|
|
148
|
+
Note: `write_string` treats arrays as per-character color cycles, so use hex
|
|
149
|
+
strings for custom colors there.
|
|
150
|
+
|
|
151
|
+
`RichEngine::StringColors.contrast_color(color)` returns `:black` or `:white`,
|
|
152
|
+
whichever reads better on top of the given color (like CSS's `contrast-color()`
|
|
153
|
+
function). Handy for labels on dynamic backgrounds.
|
|
154
|
+
|
|
155
|
+
To color strings directly (outside of canvas methods), add
|
|
156
|
+
`using RichEngine::StringColors` to your class and chain away:
|
|
157
|
+
`"hello".fg(:red).bg("#222222").bold`.
|
|
158
|
+
|
|
159
|
+
## Animations
|
|
160
|
+
|
|
161
|
+
`RichEngine::Animation` plays a sequence of string frames (sprites) at a fixed
|
|
162
|
+
frames-per-second. Each frame is a multi-line string; spaces are treated as
|
|
163
|
+
transparency by `Canvas#draw_sprite`.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
# frozen_string_literal: true
|
|
167
|
+
|
|
168
|
+
require "rich_engine"
|
|
169
|
+
|
|
170
|
+
class AnimationExample < RichEngine::Game
|
|
171
|
+
def on_create
|
|
172
|
+
sprites = ["(•‿•)", "(•‿-)", "(-‿-)", "(-‿•)"]
|
|
173
|
+
|
|
174
|
+
@animation = RichEngine::Animation.new(
|
|
175
|
+
frames: sprites,
|
|
176
|
+
fps: 4,
|
|
177
|
+
loop: true,
|
|
178
|
+
fg: :bright_yellow
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def on_update(elapsed_time, key)
|
|
183
|
+
quit! if key == :q
|
|
184
|
+
|
|
185
|
+
@animation.update(elapsed_time)
|
|
186
|
+
|
|
187
|
+
@canvas.clear
|
|
188
|
+
@animation.draw(@canvas, x: 0, y: 0)
|
|
189
|
+
@canvas.write_string("q: quit", x: 0, y: @config[:screen_height] - 1, fg: :black)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
AnimationExample.play(width: 10, height: 3, target_fps: 30)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Playback is controlled with `play!`, `pause!`, `stop!`, `reset!`, and `fps=`.
|
|
197
|
+
|
|
198
|
+
## Helpers you can use
|
|
199
|
+
|
|
200
|
+
All helpers live under `RichEngine::...` and are independent utilities you can
|
|
201
|
+
use inside your game code.
|
|
202
|
+
|
|
203
|
+
### Timer
|
|
204
|
+
|
|
205
|
+
Accumulates elapsed time; you drive it by calling `update(dt)` with the
|
|
206
|
+
`elapsed_time` from `on_update`. `Timer.every(seconds:)` returns a small
|
|
207
|
+
scheduler that fires a block at a fixed interval.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
tick = RichEngine::Timer.new
|
|
211
|
+
|
|
212
|
+
def on_update(dt, _key)
|
|
213
|
+
tick.update(dt)
|
|
214
|
+
if tick.get > 2
|
|
215
|
+
# do something every ~2 seconds
|
|
216
|
+
tick.reset!
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Fixed interval
|
|
221
|
+
spawn = RichEngine::Timer.every(seconds: 0.5)
|
|
222
|
+
def on_update(dt, _key)
|
|
223
|
+
spawn.update(dt)
|
|
224
|
+
spawn.when_ready { spawn_enemy! }
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Cooldown
|
|
229
|
+
|
|
230
|
+
Track a fixed delay and check if it’s ready.
|
|
10
231
|
|
|
11
232
|
```ruby
|
|
12
|
-
|
|
233
|
+
shoot_cd = RichEngine::Cooldown.new(0.25) # seconds
|
|
234
|
+
|
|
235
|
+
def on_update(dt, key)
|
|
236
|
+
shoot_cd.update(dt)
|
|
237
|
+
if key == :space && shoot_cd.ready?
|
|
238
|
+
shoot!
|
|
239
|
+
shoot_cd.reset!
|
|
240
|
+
end
|
|
241
|
+
end
|
|
13
242
|
```
|
|
14
243
|
|
|
15
|
-
|
|
244
|
+
### Chance (random helpers)
|
|
16
245
|
|
|
17
|
-
|
|
246
|
+
```ruby
|
|
247
|
+
RichEngine::Chance.of(0.2) # 20% chance
|
|
248
|
+
RichEngine::Chance.of(20) # also 20% (percent form)
|
|
249
|
+
RichEngine::Chance.of_one_in(10) # 1 in 10 chance
|
|
250
|
+
```
|
|
18
251
|
|
|
19
|
-
|
|
252
|
+
### Enum and Enum::Mixin
|
|
20
253
|
|
|
21
|
-
|
|
254
|
+
Create ergonomic, comparable enums with query methods.
|
|
22
255
|
|
|
23
|
-
|
|
256
|
+
```ruby
|
|
257
|
+
# Standalone enum
|
|
258
|
+
STATE = RichEngine::Enum.new(:state, {idle: 0, running: 1, paused: 2})
|
|
259
|
+
STATE.idle.value #=> 0
|
|
260
|
+
STATE.running > STATE.idle #=> true
|
|
261
|
+
|
|
262
|
+
# In a class via Mixin
|
|
263
|
+
class Player
|
|
264
|
+
include RichEngine::Enum::Mixin
|
|
265
|
+
enum :state, {idle: 0, running: 1, paused: 2}
|
|
24
266
|
|
|
25
|
-
|
|
267
|
+
def initialize
|
|
268
|
+
@state = :idle
|
|
269
|
+
end
|
|
26
270
|
|
|
27
|
-
|
|
271
|
+
def update
|
|
272
|
+
if state.running?
|
|
273
|
+
# ...
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
```
|
|
28
278
|
|
|
29
|
-
|
|
279
|
+
### Matrix (2D grid)
|
|
30
280
|
|
|
31
|
-
|
|
281
|
+
A simple 2D matrix utility with convenience methods.
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
grid = RichEngine::Matrix.new(width: 10, height: 5, fill_with: 0)
|
|
285
|
+
grid[2, 3] = 1
|
|
32
286
|
|
|
33
|
-
|
|
287
|
+
grid.each { |cell| puts cell }
|
|
34
288
|
|
|
35
|
-
|
|
289
|
+
# Fill regions
|
|
290
|
+
grid.fill(x: 0..2, y: 0..1, with: 9)
|
|
36
291
|
|
|
292
|
+
# Zip two matrices into pairs
|
|
293
|
+
other = RichEngine::Matrix.new(width: 10, height: 5, fill_with: :a)
|
|
294
|
+
pairs = grid.zip(other) # => matrix of [left, right]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### UI::Textures
|
|
298
|
+
|
|
299
|
+
Convenience glyphs for shading and blocky fills: `solid` (█), the shades
|
|
300
|
+
(▓ ▒ ░), half blocks, and friends.
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
@canvas.draw_rect(x: 10, y: 6, width: 8, height: 2, char: RichEngine::UI::Textures.solid, color: :magenta)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Examples
|
|
307
|
+
|
|
308
|
+
See the `examples/` folder for more complete samples:
|
|
309
|
+
- `timer.rb` — using timers and intervals
|
|
310
|
+
- `noise.rb` — colorful random output
|
|
311
|
+
- `background.rb` — background fill and drawing
|
|
312
|
+
- `grains_of_sand.rb` — simple cellular-like simulation
|
|
313
|
+
- `command_line_fps.rb` — a raycasting FPS with a radar and target practice
|
|
314
|
+
|
|
315
|
+
## Install and run locally (optional)
|
|
316
|
+
|
|
317
|
+
Add to a Gemfile, then bundle:
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
gem "rich_engine"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
bundle install
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Or install directly:
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
gem install rich_engine
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Then run one of the examples:
|
|
334
|
+
|
|
335
|
+
```sh
|
|
336
|
+
ruby examples/timer.rb
|
|
337
|
+
```
|
|
37
338
|
|
|
38
339
|
## License
|
|
39
340
|
|
|
40
|
-
|
|
341
|
+
MIT
|
data/examples/background.rb
CHANGED