rgame 0.2.0 → 0.3.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/CHANGELOG.md +135 -1
- data/README.md +67 -65
- data/docs/api/README.md +86 -82
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -165
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1032 -265
- data/docs/api/drawing.md +160 -160
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +149 -50
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +226 -148
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +397 -244
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +186 -73
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +395 -146
- data/docs/api/ui.md +883 -54
- data/docs/api/values.md +216 -33
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/ext/rgame_core/app/app.c +33 -3
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +39 -2
- data/ext/rgame_core/example.rb +0 -49
- data/ext/rgame_core/extconf.rb +0 -125
- data/ext/rgame_core/include/rgame/core.h +38 -2
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +30 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +20 -20
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +6 -44
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/renderer.rb +6 -63
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +2 -19
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +3 -3
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +34 -10
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +27 -25
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +6 -5
- data/lib/rgame/engine/components/character_body.rb +20 -61
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +12 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +41 -13
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +4 -6
- data/lib/rgame/engine/debug_overlay.rb +5 -11
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +0 -26
- data/lib/rgame/engine/input/actions.rb +2 -8
- data/lib/rgame/engine/input/input_map.rb +39 -21
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +248 -133
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/players.rb +6 -13
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +4 -4
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +51 -19
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +0 -2
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +246 -71
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/viewports.rb +2 -5
- data/lib/rgame/engine/world_view.rb +5 -4
- data/lib/rgame/engine.rb +31 -25
- data/lib/rgame/game.rb +99 -27
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +20 -24
- data/lib/rgame/util/controls.rb +14 -28
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +4 -14
- data/lib/rgame/util.rb +4 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +152 -11
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
- data/lib/rgame/engine/ui/menu_item.rb +0 -84
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* locale_ext.c — the Ruby binding for RGame::Core.preferred_locales.
|
|
3
|
+
*
|
|
4
|
+
* RGame::Core.preferred_locales # => ["de-AT", "en"]
|
|
5
|
+
*
|
|
6
|
+
* A module function rather than an App method because SDL answers without an
|
|
7
|
+
* app, or even SDL_Init. The reading is locale.c; this file only sizes a buffer
|
|
8
|
+
* and splits the list. Choosing a language from the answer is
|
|
9
|
+
* RGame::Engine::I18n.choose, which the glue calls.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include "ruby/core_ext.h"
|
|
13
|
+
|
|
14
|
+
#include "rgame/core.h"
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The user's preferred locales as Strings, most preferred first, or [] when the
|
|
18
|
+
* OS names none. Never contains nil or an empty String.
|
|
19
|
+
*/
|
|
20
|
+
static VALUE core_preferred_locales(VALUE self) {
|
|
21
|
+
(void)self;
|
|
22
|
+
|
|
23
|
+
char stack[256];
|
|
24
|
+
size_t length = rgame_preferred_locales(stack, sizeof stack);
|
|
25
|
+
if (length < sizeof stack) {
|
|
26
|
+
return rb_str_split(rb_utf8_str_new(stack, (long)length), ",");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Too long for the stack. The environment is read on every call, so the
|
|
30
|
+
* list can grow between two of them: size again until what was written fits. */
|
|
31
|
+
for (;;) {
|
|
32
|
+
VALUE list = rb_utf8_str_new(NULL, (long)length);
|
|
33
|
+
size_t written = rgame_preferred_locales(RSTRING_PTR(list), length + 1);
|
|
34
|
+
if (written <= length) {
|
|
35
|
+
rb_str_set_len(list, (long)written);
|
|
36
|
+
return rb_str_split(list, ",");
|
|
37
|
+
}
|
|
38
|
+
length = written;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
void rgame_init_locale(VALUE mCore) {
|
|
43
|
+
rb_define_module_function(mCore, "preferred_locales", core_preferred_locales, 0);
|
|
44
|
+
}
|
|
@@ -176,7 +176,7 @@ void rgame_init_recording(VALUE mCore) {
|
|
|
176
176
|
|
|
177
177
|
rb_define_alloc_func(cRecording, recording_alloc);
|
|
178
178
|
rb_define_singleton_method(cRecording, "new", recording_s_new, -1);
|
|
179
|
-
|
|
179
|
+
rb_define_private_method(cRecording, "draw_at", recording_draw_at, 4);
|
|
180
180
|
rb_define_method(cRecording, "batch_count", recording_batch_count, 0);
|
|
181
181
|
rb_define_method(cRecording, "vertex_count", recording_vertex_count, 0);
|
|
182
182
|
rb_define_method(cRecording, "empty?", recording_empty_p, 0);
|
|
@@ -374,26 +374,26 @@ void rgame_init_renderer(VALUE mCore) {
|
|
|
374
374
|
rb_define_method(cRenderer, "drawing?", renderer_drawing_p, 0);
|
|
375
375
|
rb_define_method(cRenderer, "app", renderer_app, 0);
|
|
376
376
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
377
|
+
rb_define_private_method(cRenderer, "draw_rect", renderer_draw_rect, 6);
|
|
378
|
+
rb_define_private_method(cRenderer, "draw_quad", renderer_draw_quad, -1);
|
|
379
|
+
rb_define_private_method(cRenderer, "draw_triangle", renderer_draw_triangle, -1);
|
|
380
|
+
rb_define_private_method(cRenderer, "draw_line", renderer_draw_line, -1);
|
|
381
|
+
rb_define_private_method(cRenderer, "draw_circle", renderer_draw_circle, -1);
|
|
382
|
+
rb_define_private_method(cRenderer, "draw_image", renderer_draw_image, 5);
|
|
383
|
+
rb_define_private_method(cRenderer, "draw_image_scaled", renderer_draw_image_scaled, -1);
|
|
384
|
+
rb_define_private_method(cRenderer, "draw_image_rot", renderer_draw_image_rot, -1);
|
|
385
|
+
rb_define_private_method(cRenderer, "draw_text", renderer_draw_text, -1);
|
|
386
|
+
|
|
387
|
+
rb_define_private_method(cRenderer, "push_translate", renderer_push_translate, 2);
|
|
388
|
+
rb_define_private_method(cRenderer, "push_rotate", renderer_push_rotate, 3);
|
|
389
|
+
rb_define_private_method(cRenderer, "push_scale", renderer_push_scale, 2);
|
|
390
|
+
rb_define_private_method(cRenderer, "push_clip", renderer_push_clip, 4);
|
|
391
|
+
rb_define_private_method(cRenderer, "push_layer", renderer_push_layer, 1);
|
|
392
392
|
rb_define_method(cRenderer, "layer", renderer_layer, 0);
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
rb_define_private_method(cRenderer, "next_layer_slot", renderer_next_layer_slot, 1);
|
|
394
|
+
rb_define_private_method(cRenderer, "pop", renderer_pop, 0);
|
|
395
395
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
rb_define_private_method(cRenderer, "begin_record", renderer_begin_record, 0);
|
|
397
|
+
rb_define_private_method(cRenderer, "end_record", renderer_end_record, 0);
|
|
398
|
+
rb_define_private_method(cRenderer, "cancel_record", renderer_cancel_record, 0);
|
|
399
399
|
}
|
data/ext/rgame_util/extconf.rb
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# extconf.rb — mkmf script for the `rgame/util_ext` extension.
|
|
4
|
-
#
|
|
5
|
-
# This is the deliberately minimal half of the project's two extensions. Unlike
|
|
6
|
-
# ext/rgame_core/extconf.rb (which links SDL2 + OpenGL for the engine), the
|
|
7
|
-
# util extension is pure data: RGame::Util::Tensor and future pure-logic helpers
|
|
8
|
-
# that must NOT drag SDL/GL into the process just to be required. So there is no
|
|
9
|
-
# pkg_config, no -lGL — only the C standard + warning flags.
|
|
10
|
-
#
|
|
11
|
-
# That split is the rule for deciding where new code goes: anything depending on
|
|
12
|
-
# SDL/OpenGL (or on something that does) belongs under RGame::Core in
|
|
13
|
-
# ../rgame_core; everything else belongs here under RGame::Util.
|
|
14
|
-
|
|
15
3
|
require 'mkmf'
|
|
16
4
|
|
|
17
|
-
# Match the project's C standard and warning flags. gnu17 (not plain c17) because
|
|
18
|
-
# Ruby's headers lean on GNU extensions; gnu17 is a superset so the code still
|
|
19
|
-
# compiles as c17. Same choice as ext/rgame_core/extconf.rb.
|
|
20
5
|
$CFLAGS << ' -std=gnu17 -Wall -Wextra'
|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# (rather than plain "util_ext") keeps it namespaced alongside the engine's
|
|
25
|
-
# rgame/core_ext.so and off the top of the load path, leaving the bare name
|
|
26
|
-
# "rgame" to lib/rgame.rb.
|
|
7
|
+
$CFLAGS << ' -ffp-contract=off'
|
|
8
|
+
|
|
27
9
|
create_makefile('rgame/util_ext')
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* route_search.c — regions and A* over a solid grid. Pure; see route_search.h.
|
|
3
|
+
*
|
|
4
|
+
* Costs are doubles and are never contracted into fused multiply-adds: two
|
|
5
|
+
* routes of equal cost are told apart by comparing sums, and a compiler that
|
|
6
|
+
* fused one of them would pick a different, equally cheap route on one machine
|
|
7
|
+
* than on another.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "route_search.h"
|
|
11
|
+
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
|
|
15
|
+
#ifdef __clang__
|
|
16
|
+
#pragma STDC FP_CONTRACT OFF
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
static const double DIAGONAL = 1.4142135623730951;
|
|
20
|
+
static const double DIAGONAL_EXTRA = 1.4142135623730951 - 1.0;
|
|
21
|
+
|
|
22
|
+
bool rgame_route_search_init(rgame_route_search *search, const rgame_solid_grid *grid) {
|
|
23
|
+
memset(search, 0, sizeof(*search));
|
|
24
|
+
search->grid = grid;
|
|
25
|
+
search->cell_count = grid->width * grid->height;
|
|
26
|
+
|
|
27
|
+
size_t count = search->cell_count > 0 ? (size_t)search->cell_count : 1;
|
|
28
|
+
search->regions = malloc(count * sizeof(int32_t));
|
|
29
|
+
search->flood = malloc(count * sizeof(int32_t));
|
|
30
|
+
search->cost = malloc(count * sizeof(double));
|
|
31
|
+
search->parent = malloc(count * sizeof(int32_t));
|
|
32
|
+
search->seen = calloc(count, sizeof(uint32_t));
|
|
33
|
+
search->closed = calloc(count, sizeof(uint32_t));
|
|
34
|
+
search->route = malloc(count * sizeof(int32_t));
|
|
35
|
+
|
|
36
|
+
if (!search->regions || !search->flood || !search->cost || !search->parent || !search->seen ||
|
|
37
|
+
!search->closed || !search->route) {
|
|
38
|
+
rgame_route_search_free(search);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void rgame_route_search_free(rgame_route_search *search) {
|
|
45
|
+
free(search->regions);
|
|
46
|
+
free(search->flood);
|
|
47
|
+
free(search->cost);
|
|
48
|
+
free(search->parent);
|
|
49
|
+
free(search->seen);
|
|
50
|
+
free(search->closed);
|
|
51
|
+
free(search->heap_cell);
|
|
52
|
+
free(search->heap_total);
|
|
53
|
+
free(search->heap_remaining);
|
|
54
|
+
free(search->route);
|
|
55
|
+
memset(search, 0, sizeof(*search));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
int rgame_route_neighbours(const rgame_solid_grid *grid, int32_t index, int32_t cells[8],
|
|
59
|
+
double steps[8]) {
|
|
60
|
+
int32_t width = grid->width;
|
|
61
|
+
int32_t col = index % width;
|
|
62
|
+
const uint8_t *solid = grid->cells;
|
|
63
|
+
bool west = col > 0 && !solid[index - 1];
|
|
64
|
+
bool east = col < width - 1 && !solid[index + 1];
|
|
65
|
+
bool north = index >= width && !solid[index - width];
|
|
66
|
+
bool south = index < grid->width * grid->height - width && !solid[index + width];
|
|
67
|
+
|
|
68
|
+
int count = 0;
|
|
69
|
+
if (west) { cells[count] = index - 1; steps[count++] = 1.0; }
|
|
70
|
+
if (east) { cells[count] = index + 1; steps[count++] = 1.0; }
|
|
71
|
+
if (north) { cells[count] = index - width; steps[count++] = 1.0; }
|
|
72
|
+
if (south) { cells[count] = index + width; steps[count++] = 1.0; }
|
|
73
|
+
if (north && west && !solid[index - width - 1]) { cells[count] = index - width - 1; steps[count++] = DIAGONAL; }
|
|
74
|
+
if (north && east && !solid[index - width + 1]) { cells[count] = index - width + 1; steps[count++] = DIAGONAL; }
|
|
75
|
+
if (south && west && !solid[index + width - 1]) { cells[count] = index + width - 1; steps[count++] = DIAGONAL; }
|
|
76
|
+
if (south && east && !solid[index + width + 1]) { cells[count] = index + width + 1; steps[count++] = DIAGONAL; }
|
|
77
|
+
return count;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* --- regions --- */
|
|
81
|
+
|
|
82
|
+
static void claim(rgame_route_search *search, int32_t index, int32_t label, int32_t *top) {
|
|
83
|
+
if (search->grid->cells[index] || search->regions[index] != -1) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
search->regions[index] = label;
|
|
87
|
+
search->flood[(*top)++] = index;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static void label_regions(rgame_route_search *search) {
|
|
91
|
+
const rgame_solid_grid *grid = search->grid;
|
|
92
|
+
int32_t width = grid->width;
|
|
93
|
+
int32_t count = search->cell_count;
|
|
94
|
+
for (int32_t index = 0; index < count; index++) {
|
|
95
|
+
search->regions[index] = -1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
int32_t label = 0;
|
|
99
|
+
for (int32_t seed = 0; seed < count; seed++) {
|
|
100
|
+
if (grid->cells[seed] || search->regions[seed] != -1) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
int32_t top = 0;
|
|
104
|
+
claim(search, seed, label, &top);
|
|
105
|
+
while (top > 0) {
|
|
106
|
+
int32_t index = search->flood[--top];
|
|
107
|
+
int32_t col = index % width;
|
|
108
|
+
if (col > 0) { claim(search, index - 1, label, &top); }
|
|
109
|
+
if (col < width - 1) { claim(search, index + 1, label, &top); }
|
|
110
|
+
if (index >= width) { claim(search, index - width, label, &top); }
|
|
111
|
+
if (index < count - width) { claim(search, index + width, label, &top); }
|
|
112
|
+
}
|
|
113
|
+
label++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
search->labelled = true;
|
|
117
|
+
search->labelled_at = grid->revision;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
int32_t rgame_route_region(rgame_route_search *search, int32_t col, int32_t row) {
|
|
121
|
+
if (!rgame_solid_grid_inside(search->grid, col, row)) {
|
|
122
|
+
return -1;
|
|
123
|
+
}
|
|
124
|
+
if (!search->labelled || search->labelled_at != search->grid->revision) {
|
|
125
|
+
label_regions(search);
|
|
126
|
+
}
|
|
127
|
+
return search->regions[row * search->grid->width + col];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* --- the heap: parallel arrays, ordered by total then remaining --- */
|
|
131
|
+
|
|
132
|
+
static bool heap_reserve(rgame_route_search *search) {
|
|
133
|
+
if (search->heap_size < search->heap_capacity) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
size_t capacity = search->heap_capacity ? search->heap_capacity * 2 : 64;
|
|
137
|
+
int32_t *cells = realloc(search->heap_cell, capacity * sizeof(int32_t));
|
|
138
|
+
if (!cells) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
search->heap_cell = cells;
|
|
142
|
+
double *totals = realloc(search->heap_total, capacity * sizeof(double));
|
|
143
|
+
if (!totals) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
search->heap_total = totals;
|
|
147
|
+
double *remainings = realloc(search->heap_remaining, capacity * sizeof(double));
|
|
148
|
+
if (!remainings) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
search->heap_remaining = remainings;
|
|
152
|
+
search->heap_capacity = capacity;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static bool push(rgame_route_search *search, int32_t index, double total, double remaining) {
|
|
157
|
+
if (!heap_reserve(search)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
int32_t *cell = search->heap_cell;
|
|
161
|
+
double *totals = search->heap_total;
|
|
162
|
+
double *remainings = search->heap_remaining;
|
|
163
|
+
size_t slot = search->heap_size++;
|
|
164
|
+
while (slot > 0) {
|
|
165
|
+
size_t up = (slot - 1) >> 1;
|
|
166
|
+
if (totals[up] < total || (totals[up] == total && remainings[up] <= remaining)) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
cell[slot] = cell[up];
|
|
170
|
+
totals[slot] = totals[up];
|
|
171
|
+
remainings[slot] = remainings[up];
|
|
172
|
+
slot = up;
|
|
173
|
+
}
|
|
174
|
+
cell[slot] = index;
|
|
175
|
+
totals[slot] = total;
|
|
176
|
+
remainings[slot] = remaining;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static int32_t pop(rgame_route_search *search) {
|
|
181
|
+
int32_t *cell = search->heap_cell;
|
|
182
|
+
double *totals = search->heap_total;
|
|
183
|
+
double *remainings = search->heap_remaining;
|
|
184
|
+
int32_t top = cell[0];
|
|
185
|
+
size_t size = --search->heap_size;
|
|
186
|
+
if (size == 0) {
|
|
187
|
+
return top;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
int32_t index = cell[size];
|
|
191
|
+
double total = totals[size];
|
|
192
|
+
double remaining = remainings[size];
|
|
193
|
+
size_t slot = 0;
|
|
194
|
+
size_t child;
|
|
195
|
+
while ((child = (slot << 1) + 1) < size) {
|
|
196
|
+
size_t right = child + 1;
|
|
197
|
+
if (right < size && (totals[right] < totals[child] ||
|
|
198
|
+
(totals[right] == totals[child] && remainings[right] < remainings[child]))) {
|
|
199
|
+
child = right;
|
|
200
|
+
}
|
|
201
|
+
if (total < totals[child] || (total == totals[child] && remaining <= remainings[child])) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
cell[slot] = cell[child];
|
|
205
|
+
totals[slot] = totals[child];
|
|
206
|
+
remainings[slot] = remainings[child];
|
|
207
|
+
slot = child;
|
|
208
|
+
}
|
|
209
|
+
cell[slot] = index;
|
|
210
|
+
totals[slot] = total;
|
|
211
|
+
remainings[slot] = remaining;
|
|
212
|
+
return top;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* --- A* --- */
|
|
216
|
+
|
|
217
|
+
static void next_generation(rgame_route_search *search) {
|
|
218
|
+
if (++search->generation == 0) {
|
|
219
|
+
size_t count = search->cell_count > 0 ? (size_t)search->cell_count : 1;
|
|
220
|
+
memset(search->seen, 0, count * sizeof(uint32_t));
|
|
221
|
+
memset(search->closed, 0, count * sizeof(uint32_t));
|
|
222
|
+
search->generation = 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static bool relax(rgame_route_search *search, int32_t from, int32_t to, double cost, int32_t goal_col,
|
|
227
|
+
int32_t goal_row) {
|
|
228
|
+
uint32_t generation = search->generation;
|
|
229
|
+
if (search->closed[to] == generation) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (search->seen[to] == generation && search->cost[to] <= cost) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
search->seen[to] = generation;
|
|
237
|
+
search->cost[to] = cost;
|
|
238
|
+
search->parent[to] = from;
|
|
239
|
+
int32_t width = search->grid->width;
|
|
240
|
+
int32_t dx = abs(to % width - goal_col);
|
|
241
|
+
int32_t dy = abs(to / width - goal_row);
|
|
242
|
+
double remaining = dx < dy ? (double)dy + DIAGONAL_EXTRA * dx : (double)dx + DIAGONAL_EXTRA * dy;
|
|
243
|
+
return push(search, to, cost + remaining, remaining);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static rgame_route_result search_route(rgame_route_search *search, int32_t start, int32_t goal,
|
|
247
|
+
int32_t goal_col, int32_t goal_row) {
|
|
248
|
+
next_generation(search);
|
|
249
|
+
search->heap_size = 0;
|
|
250
|
+
if (!relax(search, -1, start, 0.0, goal_col, goal_row)) {
|
|
251
|
+
return RGAME_ROUTE_NO_MEMORY;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
int32_t cells[8];
|
|
255
|
+
double steps[8];
|
|
256
|
+
while (search->heap_size > 0) {
|
|
257
|
+
int32_t index = pop(search);
|
|
258
|
+
if (search->closed[index] == search->generation) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (index == goal) {
|
|
262
|
+
return RGAME_ROUTE_FOUND;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
search->closed[index] = search->generation;
|
|
266
|
+
int count = rgame_route_neighbours(search->grid, index, cells, steps);
|
|
267
|
+
double cost = search->cost[index];
|
|
268
|
+
for (int i = 0; i < count; i++) {
|
|
269
|
+
if (!relax(search, index, cells[i], cost + steps[i], goal_col, goal_row)) {
|
|
270
|
+
return RGAME_ROUTE_NO_MEMORY;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/* Unreachable once regions agree the two ends are joined. */
|
|
275
|
+
return RGAME_ROUTE_NONE;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static void collect_route(rgame_route_search *search, int32_t goal) {
|
|
279
|
+
int32_t length = 0;
|
|
280
|
+
for (int32_t index = goal; index != -1; index = search->parent[index]) {
|
|
281
|
+
length++;
|
|
282
|
+
}
|
|
283
|
+
int32_t slot = length;
|
|
284
|
+
for (int32_t index = goal; index != -1; index = search->parent[index]) {
|
|
285
|
+
search->route[--slot] = index;
|
|
286
|
+
}
|
|
287
|
+
search->route_length = length;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
rgame_route_result rgame_route_find(rgame_route_search *search, int32_t from_col, int32_t from_row,
|
|
291
|
+
int32_t to_col, int32_t to_row) {
|
|
292
|
+
search->route_length = 0;
|
|
293
|
+
int32_t from_region = rgame_route_region(search, from_col, from_row);
|
|
294
|
+
if (from_region == -1 || from_region != rgame_route_region(search, to_col, to_row)) {
|
|
295
|
+
return RGAME_ROUTE_NONE;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
int32_t width = search->grid->width;
|
|
299
|
+
int32_t goal = to_row * width + to_col;
|
|
300
|
+
rgame_route_result result = search_route(search, from_row * width + from_col, goal, to_col, to_row);
|
|
301
|
+
if (result == RGAME_ROUTE_FOUND) {
|
|
302
|
+
collect_route(search, goal);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#ifndef RGAME_ROUTE_SEARCH_H
|
|
2
|
+
#define RGAME_ROUTE_SEARCH_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
|
|
8
|
+
#include "solid_grid.h"
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Connected regions and A* routes over a solid grid — pure, no Ruby, no SDL.
|
|
12
|
+
*
|
|
13
|
+
* A search reads its grid and never writes it. Everything it keeps between
|
|
14
|
+
* queries lives in the search, so any number of searches may read one grid —
|
|
15
|
+
* which is what lets many walkers plan over the one store a tile world's
|
|
16
|
+
* blockers also read.
|
|
17
|
+
*
|
|
18
|
+
* The grid is 8-connected with octile costs (1 straight, sqrt 2 diagonal), and a
|
|
19
|
+
* diagonal step is allowed only when both orthogonal cells beside it are open,
|
|
20
|
+
* so a route never cuts the corner of a solid cell. That rule is
|
|
21
|
+
* rgame_route_neighbours, and every search over the grid goes through it.
|
|
22
|
+
*
|
|
23
|
+
* Regions are labelled by an orthogonal flood fill the first time one is asked
|
|
24
|
+
* for, and again whenever the grid's revision has moved since — so an
|
|
25
|
+
* unreachable goal, the answer A* is slowest to give, is a lookup, and it stays
|
|
26
|
+
* right when the grid changes.
|
|
27
|
+
*
|
|
28
|
+
* A search allocates its per-cell buffers once, at init, and marks which
|
|
29
|
+
* entries are current with a generation stamp, so a query costs nothing
|
|
30
|
+
* proportional to the grid. The heap grows to the largest query seen and never
|
|
31
|
+
* shrinks: a repeated query allocates nothing.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
typedef enum {
|
|
35
|
+
RGAME_ROUTE_FOUND,
|
|
36
|
+
RGAME_ROUTE_NONE, /* an end is solid, outside, or in another region */
|
|
37
|
+
RGAME_ROUTE_NO_MEMORY /* the heap could not grow; the search is still usable */
|
|
38
|
+
} rgame_route_result;
|
|
39
|
+
|
|
40
|
+
typedef struct {
|
|
41
|
+
const rgame_solid_grid *grid;
|
|
42
|
+
int32_t cell_count;
|
|
43
|
+
|
|
44
|
+
int32_t *regions; /* per cell; -1 solid */
|
|
45
|
+
int32_t *flood; /* the flood fill's stack */
|
|
46
|
+
bool labelled;
|
|
47
|
+
uint64_t labelled_at; /* the grid revision the labels describe */
|
|
48
|
+
|
|
49
|
+
double *cost;
|
|
50
|
+
int32_t *parent;
|
|
51
|
+
uint32_t *seen;
|
|
52
|
+
uint32_t *closed;
|
|
53
|
+
uint32_t generation; /* stamps; wrapping clears them */
|
|
54
|
+
|
|
55
|
+
int32_t *heap_cell;
|
|
56
|
+
double *heap_total;
|
|
57
|
+
double *heap_remaining;
|
|
58
|
+
size_t heap_size;
|
|
59
|
+
size_t heap_capacity; /* grows, never shrinks */
|
|
60
|
+
|
|
61
|
+
int32_t *route; /* flat indices, start first, after RGAME_ROUTE_FOUND */
|
|
62
|
+
int32_t route_length;
|
|
63
|
+
} rgame_route_search;
|
|
64
|
+
|
|
65
|
+
/* The grid must outlive the search. False, with nothing to free, when
|
|
66
|
+
* allocation fails. */
|
|
67
|
+
bool rgame_route_search_init(rgame_route_search *search, const rgame_solid_grid *grid);
|
|
68
|
+
void rgame_route_search_free(rgame_route_search *search);
|
|
69
|
+
|
|
70
|
+
/* The cells one step from `index` may reach, in the order a search relaxes them
|
|
71
|
+
* (west, east, north, south, then the four diagonals), each with its step cost.
|
|
72
|
+
* Returns how many were written, at most 8. */
|
|
73
|
+
int rgame_route_neighbours(const rgame_solid_grid *grid, int32_t index, int32_t cells[8],
|
|
74
|
+
double steps[8]);
|
|
75
|
+
|
|
76
|
+
/* The region label of a cell — two walkable cells share one exactly when a
|
|
77
|
+
* route joins them — or -1 for a solid cell or one outside the grid. */
|
|
78
|
+
int32_t rgame_route_region(rgame_route_search *search, int32_t col, int32_t row);
|
|
79
|
+
|
|
80
|
+
/* The cheapest route from start to goal, both included, into search->route.
|
|
81
|
+
* Ties between equally cheap routes go to the entry with the smaller remaining
|
|
82
|
+
* estimate, so the same query always returns the same route. */
|
|
83
|
+
rgame_route_result rgame_route_find(rgame_route_search *search, int32_t from_col, int32_t from_row,
|
|
84
|
+
int32_t to_col, int32_t to_row);
|
|
85
|
+
|
|
86
|
+
#endif /* RGAME_ROUTE_SEARCH_H */
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* route_search_ext.c — the Ruby binding for RGame::Util::RouteSearch.
|
|
3
|
+
*
|
|
4
|
+
* The search lives in route_search.{c,h}, pure and covered by the Check suite.
|
|
5
|
+
* What this file adds is lifetime: the C search holds a pointer into the
|
|
6
|
+
* SolidGrid it reads, so the Ruby object holds the grid too and marks it, and
|
|
7
|
+
* the grid cannot be collected while a search over it is alive.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "util_ext.h"
|
|
11
|
+
|
|
12
|
+
#include "route_search.h"
|
|
13
|
+
|
|
14
|
+
typedef struct {
|
|
15
|
+
VALUE grid;
|
|
16
|
+
rgame_route_search search;
|
|
17
|
+
bool ready;
|
|
18
|
+
} route_search_ref;
|
|
19
|
+
|
|
20
|
+
static long live_searches = 0;
|
|
21
|
+
|
|
22
|
+
static void route_search_mark(void *ptr) {
|
|
23
|
+
route_search_ref *ref = ptr;
|
|
24
|
+
rb_gc_mark(ref->grid);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static void route_search_free(void *ptr) {
|
|
28
|
+
route_search_ref *ref = ptr;
|
|
29
|
+
if (ref->ready) {
|
|
30
|
+
rgame_route_search_free(&ref->search);
|
|
31
|
+
}
|
|
32
|
+
xfree(ref);
|
|
33
|
+
live_searches--;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static size_t route_search_memsize(const void *ptr) {
|
|
37
|
+
const route_search_ref *ref = ptr;
|
|
38
|
+
size_t cells = ref->ready ? (size_t)ref->search.cell_count : 0;
|
|
39
|
+
size_t per_cell = 4 * sizeof(int32_t) + sizeof(double) + 2 * sizeof(uint32_t);
|
|
40
|
+
size_t per_entry = sizeof(int32_t) + 2 * sizeof(double);
|
|
41
|
+
return sizeof(*ref) + cells * per_cell + ref->search.heap_capacity * per_entry;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static const rb_data_type_t route_search_data_type = {
|
|
45
|
+
.wrap_struct_name = "rgame_route_search",
|
|
46
|
+
.function = {
|
|
47
|
+
.dmark = route_search_mark,
|
|
48
|
+
.dfree = route_search_free,
|
|
49
|
+
.dsize = route_search_memsize,
|
|
50
|
+
},
|
|
51
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
static rgame_route_search *unwrap(VALUE self) {
|
|
55
|
+
route_search_ref *ref;
|
|
56
|
+
TypedData_Get_Struct(self, route_search_ref, &route_search_data_type, ref);
|
|
57
|
+
return &ref->search;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* RouteSearch.new(grid) — a search over a SolidGrid, which it keeps alive. */
|
|
61
|
+
static VALUE route_search_s_new(VALUE klass, VALUE grid) {
|
|
62
|
+
rgame_solid_grid *cells = rgame_util_solid_grid(grid);
|
|
63
|
+
|
|
64
|
+
route_search_ref *ref;
|
|
65
|
+
VALUE object = TypedData_Make_Struct(klass, route_search_ref, &route_search_data_type, ref);
|
|
66
|
+
live_searches++;
|
|
67
|
+
ref->grid = grid;
|
|
68
|
+
if (!rgame_route_search_init(&ref->search, cells)) {
|
|
69
|
+
rb_memerror();
|
|
70
|
+
}
|
|
71
|
+
ref->ready = true;
|
|
72
|
+
return object;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static VALUE route_search_grid(VALUE self) {
|
|
76
|
+
route_search_ref *ref;
|
|
77
|
+
TypedData_Get_Struct(self, route_search_ref, &route_search_data_type, ref);
|
|
78
|
+
return ref->grid;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* region(col, row) — an Integer label two walkable cells share exactly when a
|
|
82
|
+
* route joins them, or nil for a solid cell or one outside the grid. Labels are
|
|
83
|
+
* recomputed on the first ask after the grid changes. */
|
|
84
|
+
static VALUE route_search_region(VALUE self, VALUE col_value, VALUE row_value) {
|
|
85
|
+
int32_t col = 0;
|
|
86
|
+
int32_t row = 0;
|
|
87
|
+
bool col_fits = rgame_util_cell_coordinate(col_value, &col);
|
|
88
|
+
bool row_fits = rgame_util_cell_coordinate(row_value, &row);
|
|
89
|
+
if (!col_fits || !row_fits) {
|
|
90
|
+
return Qnil;
|
|
91
|
+
}
|
|
92
|
+
int32_t region = rgame_route_region(unwrap(self), col, row);
|
|
93
|
+
return region == -1 ? Qnil : INT2NUM(region);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* find(from_col, from_row, to_col, to_row) — the cheapest route, both ends
|
|
97
|
+
* included, as [[col, row], ...]; nil when either end is solid, outside the
|
|
98
|
+
* grid, or in another region. Equally cheap routes are told apart the same way
|
|
99
|
+
* every time. */
|
|
100
|
+
static VALUE route_search_find(VALUE self, VALUE from_col_value, VALUE from_row_value,
|
|
101
|
+
VALUE to_col_value, VALUE to_row_value) {
|
|
102
|
+
int32_t from_col = 0;
|
|
103
|
+
int32_t from_row = 0;
|
|
104
|
+
int32_t to_col = 0;
|
|
105
|
+
int32_t to_row = 0;
|
|
106
|
+
bool fits = rgame_util_cell_coordinate(from_col_value, &from_col);
|
|
107
|
+
fits = rgame_util_cell_coordinate(from_row_value, &from_row) && fits;
|
|
108
|
+
fits = rgame_util_cell_coordinate(to_col_value, &to_col) && fits;
|
|
109
|
+
fits = rgame_util_cell_coordinate(to_row_value, &to_row) && fits;
|
|
110
|
+
if (!fits) {
|
|
111
|
+
return Qnil;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
rgame_route_search *search = unwrap(self);
|
|
115
|
+
switch (rgame_route_find(search, from_col, from_row, to_col, to_row)) {
|
|
116
|
+
case RGAME_ROUTE_NONE:
|
|
117
|
+
return Qnil;
|
|
118
|
+
case RGAME_ROUTE_NO_MEMORY:
|
|
119
|
+
rb_memerror();
|
|
120
|
+
case RGAME_ROUTE_FOUND:
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
int32_t width = search->grid->width;
|
|
125
|
+
VALUE route = rb_ary_new_capa(search->route_length);
|
|
126
|
+
for (int32_t i = 0; i < search->route_length; i++) {
|
|
127
|
+
int32_t index = search->route[i];
|
|
128
|
+
rb_ary_push(route, rb_assoc_new(INT2FIX(index % width), INT2FIX(index / width)));
|
|
129
|
+
}
|
|
130
|
+
return route;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* RouteSearch.debug_live_searches — how many searches are holding buffers.
|
|
134
|
+
* Test-only, and named so. */
|
|
135
|
+
static VALUE route_search_s_debug_live_searches(VALUE klass) {
|
|
136
|
+
(void)klass;
|
|
137
|
+
return LONG2NUM(live_searches);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void rgame_init_route_search(VALUE mUtil) {
|
|
141
|
+
VALUE cRouteSearch = rb_define_class_under(mUtil, "RouteSearch", rb_cObject);
|
|
142
|
+
|
|
143
|
+
rb_undef_alloc_func(cRouteSearch);
|
|
144
|
+
rb_define_singleton_method(cRouteSearch, "new", route_search_s_new, 1);
|
|
145
|
+
rb_define_singleton_method(cRouteSearch, "debug_live_searches", route_search_s_debug_live_searches, 0);
|
|
146
|
+
|
|
147
|
+
rb_define_method(cRouteSearch, "grid", route_search_grid, 0);
|
|
148
|
+
rb_define_method(cRouteSearch, "region", route_search_region, 2);
|
|
149
|
+
rb_define_method(cRouteSearch, "find", route_search_find, 4);
|
|
150
|
+
}
|