rgame 0.1.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 +228 -0
- data/README.md +142 -243
- data/docs/api/README.md +141 -90
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -166
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1045 -254
- data/docs/api/drawing.md +187 -119
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +165 -45
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +344 -85
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +503 -123
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +197 -64
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +400 -147
- data/docs/api/ui.md +927 -0
- data/docs/api/values.md +234 -19
- 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/README.md +6 -5
- data/ext/rgame_core/app/app.c +215 -11
- 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 +113 -2
- data/ext/rgame_core/example.rb +18 -56
- data/ext/rgame_core/extconf.rb +16 -113
- data/ext/rgame_core/graphics/canvas.c +45 -4
- data/ext/rgame_core/graphics/canvas.h +65 -10
- data/ext/rgame_core/graphics/clip.c +22 -13
- data/ext/rgame_core/include/rgame/core.h +151 -5
- data/ext/rgame_core/input/gamepad.c +57 -3
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +46 -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 +42 -19
- data/ext/rgame_util/color_ext.c +12 -3
- 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 -42
- 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/input.rb +35 -41
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/recording.rb +3 -1
- data/lib/rgame/core/renderer.rb +75 -84
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +77 -65
- 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 +55 -10
- 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 +35 -1
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +31 -23
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +45 -0
- data/lib/rgame/engine/components/character_body.rb +21 -41
- 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 +22 -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 +70 -29
- 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 +45 -0
- data/lib/rgame/engine/debug_overlay.rb +23 -18
- 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 +76 -22
- data/lib/rgame/engine/input/actions.rb +63 -12
- data/lib/rgame/engine/input/input_map.rb +196 -0
- data/lib/rgame/engine/layout.rb +82 -0
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +364 -80
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/player.rb +69 -0
- data/lib/rgame/engine/player_layer.rb +70 -0
- data/lib/rgame/engine/players.rb +205 -0
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +29 -7
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +53 -8
- 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 +82 -0
- 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 +290 -0
- 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/view.rb +76 -0
- data/lib/rgame/engine/viewports.rb +171 -0
- data/lib/rgame/engine/world_view.rb +71 -0
- data/lib/rgame/engine.rb +43 -25
- data/lib/rgame/game.rb +164 -22
- 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 +106 -44
- 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 +123 -0
- data/lib/rgame/util.rb +5 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +176 -20
- 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/camera_view.rb +0 -28
- 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/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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* solid_grid.c — the solidity store of a tile grid. Pure; see solid_grid.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "solid_grid.h"
|
|
6
|
+
|
|
7
|
+
#include <stdlib.h>
|
|
8
|
+
|
|
9
|
+
bool rgame_solid_grid_size_ok(int64_t width, int64_t height) {
|
|
10
|
+
return width >= 0 && height >= 0 && (height == 0 || width <= INT32_MAX / height);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
bool rgame_solid_grid_init(rgame_solid_grid *grid, int32_t width, int32_t height) {
|
|
14
|
+
if (!rgame_solid_grid_size_ok(width, height)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
size_t count = (size_t)width * (size_t)height;
|
|
18
|
+
uint8_t *cells = NULL;
|
|
19
|
+
if (count > 0) {
|
|
20
|
+
cells = calloc(count, 1);
|
|
21
|
+
if (!cells) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
grid->width = width;
|
|
26
|
+
grid->height = height;
|
|
27
|
+
grid->cells = cells;
|
|
28
|
+
grid->revision = 0;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void rgame_solid_grid_free(rgame_solid_grid *grid) {
|
|
33
|
+
free(grid->cells);
|
|
34
|
+
grid->cells = NULL;
|
|
35
|
+
grid->width = 0;
|
|
36
|
+
grid->height = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bool rgame_solid_grid_inside(const rgame_solid_grid *grid, int32_t col, int32_t row) {
|
|
40
|
+
return col >= 0 && row >= 0 && col < grid->width && row < grid->height;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool rgame_solid_grid_solid(const rgame_solid_grid *grid, int32_t col, int32_t row) {
|
|
44
|
+
return rgame_solid_grid_inside(grid, col, row) && grid->cells[row * grid->width + col];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
bool rgame_solid_grid_set(rgame_solid_grid *grid, int32_t col, int32_t row, bool solid) {
|
|
48
|
+
if (!rgame_solid_grid_inside(grid, col, row)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
uint8_t *cell = &grid->cells[row * grid->width + col];
|
|
52
|
+
uint8_t value = solid ? 1 : 0;
|
|
53
|
+
if (*cell != value) {
|
|
54
|
+
*cell = value;
|
|
55
|
+
grid->revision++;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#ifndef RGAME_SOLID_GRID_H
|
|
2
|
+
#define RGAME_SOLID_GRID_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Which cells of a tile grid are solid — pure, no Ruby, no SDL, no GL.
|
|
9
|
+
*
|
|
10
|
+
* This is the one store of a tile world's solidity: the blockers that stop a
|
|
11
|
+
* walker and the route search that plans one both read it, so a cell changed
|
|
12
|
+
* here is changed for both at once and the two cannot disagree about a wall.
|
|
13
|
+
*
|
|
14
|
+
* `revision` is how a reader that keeps something derived from the cells (a
|
|
15
|
+
* search's region labels) knows the cells have moved under it. It changes on
|
|
16
|
+
* every write that changes a cell and on no other, so a derived value keyed to
|
|
17
|
+
* it is rebuilt exactly when it has to be.
|
|
18
|
+
*
|
|
19
|
+
* Anything outside the grid is open, which is what a tile map answers past its
|
|
20
|
+
* edges.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
typedef struct {
|
|
24
|
+
int32_t width;
|
|
25
|
+
int32_t height;
|
|
26
|
+
uint8_t *cells; /* width * height, row-major; 1 solid, 0 open */
|
|
27
|
+
uint64_t revision; /* moves whenever a cell changes, and only then — and 64
|
|
28
|
+
bits, so it never wraps back onto a value a reader kept */
|
|
29
|
+
} rgame_solid_grid;
|
|
30
|
+
|
|
31
|
+
/* Whether a grid of this size can exist: neither side negative, and the cell
|
|
32
|
+
* count within int32_t, so every in-bounds flat index fits the type the search
|
|
33
|
+
* stores. Zero on either side is a valid, empty grid. */
|
|
34
|
+
bool rgame_solid_grid_size_ok(int64_t width, int64_t height);
|
|
35
|
+
|
|
36
|
+
/* Every cell open, revision 0. False — with nothing to free — for a size that is
|
|
37
|
+
* not ok or when allocation fails. */
|
|
38
|
+
bool rgame_solid_grid_init(rgame_solid_grid *grid, int32_t width, int32_t height);
|
|
39
|
+
void rgame_solid_grid_free(rgame_solid_grid *grid);
|
|
40
|
+
|
|
41
|
+
bool rgame_solid_grid_inside(const rgame_solid_grid *grid, int32_t col, int32_t row);
|
|
42
|
+
|
|
43
|
+
/* False outside the grid. */
|
|
44
|
+
bool rgame_solid_grid_solid(const rgame_solid_grid *grid, int32_t col, int32_t row);
|
|
45
|
+
|
|
46
|
+
/* False, changing nothing, for a cell outside the grid. */
|
|
47
|
+
bool rgame_solid_grid_set(rgame_solid_grid *grid, int32_t col, int32_t row, bool solid);
|
|
48
|
+
|
|
49
|
+
#endif /* RGAME_SOLID_GRID_H */
|