1
2
3
4
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
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# Friendly Ruby sugar layered over the generated raylib bindings (see
# tools/gen_raylib.rb). The generated API is the complete, positional surface
# (e.g. Rl.draw_text(text, x, y, size, color), Rl.init_window(w, h, title), all
# the Rl::Color / Rl::Vector2 / ... structs, Rl::KEY_* constants, etc.).
#
# This file adds the niceties from docs/API_SPEC.md: block-scoped Begin/End
# pairs, the web-safe main loop, keyword-arg helpers for a few common calls,
# symbol keys, and convenience aliases.
module Rl
# Symbol -> keycode (letters/digits map to ASCII; named keys to KEY_* consts).
SYMBOL_KEYS = {}
('a'..'z').each { |c| SYMBOL_KEYS[c.to_sym] = c.upcase.ord }
('0'..'9').each { |c| SYMBOL_KEYS[c.to_sym] = c.ord }
{
space: :KEY_SPACE, enter: :KEY_ENTER, escape: :KEY_ESCAPE, tab: :KEY_TAB,
backspace: :KEY_BACKSPACE, up: :KEY_UP, down: :KEY_DOWN, left: :KEY_LEFT,
right: :KEY_RIGHT, left_shift: :KEY_LEFT_SHIFT, left_control: :KEY_LEFT_CONTROL
}.each { |sym, const| SYMBOL_KEYS[sym] = const_get(const) if const_defined?(const) }
# Function keys F1-F12 (raylib keycodes 290-301). Added so :f2 etc. resolve —
# the KEY_F* constants aren't all exposed to mruby, so use the int values.
{ f1: 290, f2: 291, f3: 292, f4: 293, f5: 294, f6: 295, f7: 296, f8: 297,
f9: 298, f10: 299, f11: 300, f12: 301 }.each { |sym, kc| SYMBOL_KEYS[sym] = kc }
class << self
def resolve_key(key)
case key
when Integer then key
when Symbol then SYMBOL_KEYS.fetch(key) { raise ArgumentError, "unknown key #{key.inspect}" }
else raise ArgumentError, "key must be Integer or Symbol"
end
end
# --- convenience aliases (spec-style names) ---
alias_method :target_fps=, :set_target_fps
alias_method :master_volume=, :set_master_volume
alias_method :frame_time, :get_frame_time
alias_method :time, :get_time
alias_method :fps, :get_fps
alias_method :screen_width, :get_screen_width
alias_method :screen_height, :get_screen_height
alias_method :mouse_x, :get_mouse_x
alias_method :mouse_y, :get_mouse_y
alias_method :mouse_position, :get_mouse_position
alias_method :mouse_wheel, :get_mouse_wheel_move
# The generator only suffixes `?` on Is* predicates; WindowShouldClose binds
# as `window_should_close`. Provide the spec's `?` form (API_SPEC.md) — the
# desktop seam below relies on it.
alias_method :window_should_close?, :window_should_close
def platform = _is_web ? :web : :desktop
def web? = _is_web
def desktop? = !_is_web
# --- input with symbol-key support (override the generated int-only ones) ---
alias_method :_c_key_down?, :key_down?
alias_method :_c_key_pressed?, :key_pressed?
alias_method :_c_key_released?, :key_released?
alias_method :_c_key_up?, :key_up?
def key_down?(k) = _c_key_down?(resolve_key(k))
def key_pressed?(k) = _c_key_pressed?(resolve_key(k))
def key_released?(k) = _c_key_released?(resolve_key(k))
def key_up?(k) = _c_key_up?(resolve_key(k))
# --- the single sanctioned loop (web-safe seam) ---
# Also the bridge drain point: agent/console commands are queued off-frame
# and run here, on the main thread, before the game block (P6). Desktop only
# for R1; the web drain is wired in R4.
#
# Screenshot mode (desktop only): gate on JAMSTACK_SCREENSHOT=<path>. After
# JAMSTACK_SCREENSHOT_FRAMES frames (default 30) the game block has rendered
# and swapped, so the framebuffer holds a settled frame; take_screenshot writes
# a pixel-exact PNG to <path>, then the loop breaks + close_window exits clean.
# Generic — works on ANY game script with zero per-script changes (see
# bin/screenshot). JAMSTACK_SCREENSHOT_DELAY=<seconds> adds a wall-clock wait
# before the capture (for async/asset settle). Set JAMSTACK_SCREENSHOT_ONCE=0
# to keep running after capture (default exits).
def while_window_open(&block)
::Jamstack::Log.setup
if ::Jamstack::Bridge.enabled?
::Jamstack::Bridge.start
::Jamstack::Live.start
end
if _is_web
# Web: no TCP/.live (browser sandbox); eval arrives via the jamstack_eval
# C export. Still advance the frame counter and log loop exceptions.
_run_web_loop do
::Jamstack::Log.tick!
begin
block.call
rescue Exception => e
::Jamstack::Log.exception(e, tag: "loop")
raise
end
end
else
ss_path = ::Jamstack.getenv('JAMSTACK_SCREENSHOT')
ss_frames = 30
ss_delay = 0.0
ss_once = 1
if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_FRAMES')); ss_frames = v.to_i; end
if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_DELAY')); ss_delay = v.to_f; end
if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_ONCE')); ss_once = v.to_i; end
ss_done = false
frame_no = 0
until window_should_close?
::Jamstack::Log.tick!
::Jamstack::Bridge.drain
::Jamstack::Live.poll
begin
block.call
rescue Exception => e
::Jamstack::Log.exception(e, tag: "loop")
raise
end
frame_no += 1
next unless ss_path && !ss_done && frame_no >= ss_frames
sleep(ss_delay) if ss_delay > 0.0
take_screenshot(ss_path)
ss_done = true
::Jamstack::Log.info("screenshot -> #{ss_path} (frame #{frame_no})") rescue nil
break if ss_once != 0
end
close_window
end
end
# --- block-scoped Begin/End pairs (API_SPEC 1.3), exception-safe ---
def draw(clear_color: RAYWHITE)
begin_drawing
clear_background(clear_color)
begin; yield; ensure end_drawing; end
end
def scissor_mode(x:, y:, width:, height:)
begin_scissor_mode(x, y, width, height)
begin; yield; ensure end_scissor_mode; end
end
def mode_2d(camera)
begin_mode2d(camera)
begin; yield; ensure end_mode2d; end
end
def mode_3d(camera)
begin_mode3d(camera)
begin; yield; ensure end_mode3d; end
end
def texture_mode(render_texture)
begin_texture_mode(render_texture)
begin; yield; ensure end_texture_mode; end
end
def blend_mode(mode)
begin_blend_mode(mode)
begin; yield; ensure end_blend_mode; end
end
def shader_mode(shader)
begin_shader_mode(shader)
begin; yield; ensure end_shader_mode; end
end
# --- keyword-arg helpers for common many-arg calls (API_SPEC 1.4/1.5) ---
alias_method :_c_draw_text, :draw_text
def draw_text(text:, x:, y:, font_size:, color:)
_c_draw_text(text.to_s, x, y, font_size, color)
end
alias_method :_c_draw_texture_pro, :draw_texture_pro
def draw_texture_pro(texture:, source:, dest:, origin: Vector2.new(0, 0),
rotation: 0, tint: WHITE)
_c_draw_texture_pro(texture, source, dest, origin, rotation, tint)
end
end
end
|