ruby2d 0.12.0 → 1.0.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/LICENSE.md +7 -0
- data/README.md +141 -0
- data/USAGE.md +2435 -0
- data/assets/Rakefile +637 -0
- data/assets/build_support/CMakeLists.txt +94 -0
- data/assets/build_support/mruby/build_config.rb +34 -0
- data/assets/build_support/mruby/build_config_wasm.rb +30 -0
- data/assets/build_support/sdl_test.c +174 -0
- data/assets/deps.yaml +16 -0
- data/assets/platform/include/SDL3/SDL.h +91 -0
- data/assets/platform/include/SDL3/SDL_assert.h +705 -0
- data/assets/platform/include/SDL3/SDL_asyncio.h +550 -0
- data/assets/platform/include/SDL3/SDL_atomic.h +692 -0
- data/assets/platform/include/SDL3/SDL_audio.h +2370 -0
- data/assets/platform/include/SDL3/SDL_begin_code.h +553 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_bits.h +47 -27
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_blendmode.h +59 -55
- data/assets/platform/include/SDL3/SDL_camera.h +535 -0
- data/assets/platform/include/SDL3/SDL_clipboard.h +331 -0
- data/assets/{include/SDL2/close_code.h → platform/include/SDL3/SDL_close_code.h} +9 -8
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_copying.h +3 -1
- data/assets/platform/include/SDL3/SDL_cpuinfo.h +374 -0
- data/assets/platform/include/SDL3/SDL_dialog.h +343 -0
- data/assets/platform/include/SDL3/SDL_dlopennote.h +234 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_egl.h +11 -8
- data/assets/platform/include/SDL3/SDL_endian.h +645 -0
- data/assets/platform/include/SDL3/SDL_error.h +226 -0
- data/assets/platform/include/SDL3/SDL_events.h +1645 -0
- data/assets/platform/include/SDL3/SDL_filesystem.h +537 -0
- data/assets/platform/include/SDL3/SDL_gamepad.h +1656 -0
- data/assets/platform/include/SDL3/SDL_gpu.h +4608 -0
- data/assets/platform/include/SDL3/SDL_guid.h +106 -0
- data/assets/platform/include/SDL3/SDL_haptic.h +1461 -0
- data/assets/platform/include/SDL3/SDL_hidapi.h +571 -0
- data/assets/platform/include/SDL3/SDL_hints.h +5001 -0
- data/assets/platform/include/SDL3/SDL_init.h +507 -0
- data/assets/platform/include/SDL3/SDL_intrin.h +410 -0
- data/assets/platform/include/SDL3/SDL_iostream.h +1379 -0
- data/assets/platform/include/SDL3/SDL_joystick.h +1385 -0
- data/assets/platform/include/SDL3/SDL_keyboard.h +608 -0
- data/assets/platform/include/SDL3/SDL_keycode.h +347 -0
- data/assets/platform/include/SDL3/SDL_loadso.h +145 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_locale.h +40 -24
- data/assets/platform/include/SDL3/SDL_log.h +541 -0
- data/assets/platform/include/SDL3/SDL_main.h +704 -0
- data/assets/platform/include/SDL3/SDL_main_impl.h +151 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_messagebox.h +85 -48
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_metal.h +29 -29
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_misc.h +13 -12
- data/assets/platform/include/SDL3/SDL_mouse.h +813 -0
- data/assets/platform/include/SDL3/SDL_mutex.h +1117 -0
- data/assets/platform/include/SDL3/SDL_oldnames.h +1339 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl.h +1247 -278
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl_glext.h +5 -1
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles.h +6 -7
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2.h +10 -11
- data/assets/platform/include/SDL3/SDL_pen.h +198 -0
- data/assets/platform/include/SDL3/SDL_pixels.h +1441 -0
- data/assets/{include/SDL2/SDL_test_log.h → platform/include/SDL3/SDL_platform.h} +25 -26
- data/assets/platform/include/SDL3/SDL_platform_defines.h +497 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_power.h +40 -20
- data/assets/platform/include/SDL3/SDL_process.h +441 -0
- data/assets/platform/include/SDL3/SDL_properties.h +572 -0
- data/assets/platform/include/SDL3/SDL_rect.h +528 -0
- data/assets/platform/include/SDL3/SDL_render.h +3028 -0
- data/assets/platform/include/SDL3/SDL_revision.h +59 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_scancode.h +59 -68
- data/assets/platform/include/SDL3/SDL_sensor.h +321 -0
- data/assets/platform/include/SDL3/SDL_stdinc.h +6186 -0
- data/assets/platform/include/SDL3/SDL_storage.h +686 -0
- data/assets/platform/include/SDL3/SDL_surface.h +1769 -0
- data/assets/platform/include/SDL3/SDL_system.h +840 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test.h +16 -22
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_assert.h +27 -34
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_common.h +109 -52
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_compare.h +22 -14
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_crc32.h +27 -30
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_font.h +50 -49
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_fuzzer.h +64 -79
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_harness.h +47 -30
- data/assets/platform/include/SDL3/SDL_test_log.h +83 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_md5.h +21 -28
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_memory.h +19 -16
- data/assets/platform/include/SDL3/SDL_thread.h +602 -0
- data/assets/platform/include/SDL3/SDL_time.h +249 -0
- data/assets/platform/include/SDL3/SDL_timer.h +454 -0
- data/assets/platform/include/SDL3/SDL_touch.h +184 -0
- data/assets/platform/include/SDL3/SDL_tray.h +544 -0
- data/assets/platform/include/SDL3/SDL_version.h +198 -0
- data/assets/platform/include/SDL3/SDL_video.h +3497 -0
- data/assets/platform/include/SDL3/SDL_vulkan.h +287 -0
- data/assets/platform/include/SDL3_image/SDL_image.h +3328 -0
- data/assets/platform/include/SDL3_mixer/SDL_mixer.h +3369 -0
- data/assets/platform/include/SDL3_ttf/SDL_textengine.h +181 -0
- data/assets/platform/include/SDL3_ttf/SDL_ttf.h +2833 -0
- data/assets/{include → platform/include}/mrbconf.h +42 -31
- data/assets/{include → platform/include}/mruby/array.h +3 -4
- data/assets/{include → platform/include}/mruby/boxing_nan.h +10 -0
- data/assets/platform/include/mruby/boxing_no.h +59 -0
- data/assets/{include → platform/include}/mruby/boxing_word.h +72 -26
- data/assets/platform/include/mruby/class.h +157 -0
- data/assets/{include → platform/include}/mruby/common.h +30 -1
- data/assets/{include → platform/include}/mruby/compile.h +52 -74
- data/assets/{include → platform/include}/mruby/data.h +2 -2
- data/assets/{include → platform/include}/mruby/debug.h +12 -5
- data/assets/{include → platform/include}/mruby/dump.h +7 -9
- data/assets/{include → platform/include}/mruby/endian.h +1 -1
- data/assets/platform/include/mruby/error.h +173 -0
- data/assets/platform/include/mruby/gc.h +79 -0
- data/assets/{include → platform/include}/mruby/hash.h +1 -7
- data/assets/platform/include/mruby/internal.h +282 -0
- data/assets/platform/include/mruby/irep.h +143 -0
- data/assets/{include → platform/include}/mruby/istruct.h +1 -1
- data/assets/platform/include/mruby/khash.h +455 -0
- data/assets/platform/include/mruby/mempool.h +19 -0
- data/assets/{include → platform/include}/mruby/numeric.h +7 -10
- data/assets/platform/include/mruby/object.h +51 -0
- data/assets/{include → platform/include}/mruby/opcode.h +5 -5
- data/assets/{include → platform/include}/mruby/ops.h +19 -6
- data/assets/platform/include/mruby/presym/id.h +1447 -0
- data/assets/{include → platform/include}/mruby/presym/scanning.h +10 -7
- data/assets/platform/include/mruby/presym/table.h +2893 -0
- data/assets/platform/include/mruby/presym.h +65 -0
- data/assets/platform/include/mruby/proc.h +184 -0
- data/assets/{include → platform/include}/mruby/range.h +0 -2
- data/assets/{include → platform/include}/mruby/string.h +33 -53
- data/assets/platform/include/mruby/throw.h +81 -0
- data/assets/{include → platform/include}/mruby/value.h +64 -29
- data/assets/{include → platform/include}/mruby/variable.h +6 -23
- data/assets/{include → platform/include}/mruby/version.h +7 -7
- data/assets/{include → platform/include}/mruby.h +238 -116
- data/assets/platform/macos-arm64/bin/mrbc +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_image.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/macos-arm64/lib/libmruby.a +0 -0
- data/assets/platform/wasm/lib/libFLAC.a +0 -0
- data/assets/platform/wasm/lib/libSDL3.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_image.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/wasm/lib/libfreetype.a +0 -0
- data/assets/platform/wasm/lib/libharfbuzz.a +0 -0
- data/assets/platform/wasm/lib/libmruby.a +0 -0
- data/assets/platform/wasm/lib/libogg.a +0 -0
- data/assets/platform/wasm/lib/libplutosvg.a +0 -0
- data/assets/platform/wasm/lib/libplutovg.a +0 -0
- data/assets/platform/wasm/lib/libpng.a +0 -0
- data/assets/platform/wasm/lib/libpng16.a +0 -0
- data/assets/platform/wasm/lib/libvorbis.a +0 -0
- data/assets/platform/wasm/lib/libvorbisfile.a +0 -0
- data/assets/platform/wasm/lib/libzlibstatic.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/bin/mrbc.exe +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_image.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libmruby.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/bin/mrbc.exe +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_image.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libmruby.a +0 -0
- data/assets/resources/fonts/outfit/OFL.txt +93 -0
- data/assets/resources/fonts/outfit/outfit.ttf +0 -0
- data/assets/resources/fonts/roboto_mono/OFL.txt +91 -0
- data/assets/resources/fonts/roboto_mono/roboto_mono.ttf +0 -0
- data/assets/resources/icons/icon.icns +0 -0
- data/assets/resources/icons/icon.png +0 -0
- data/assets/resources/spritesheets/License.txt +28 -0
- data/assets/resources/spritesheets/spritesheet-backgrounds.png +0 -0
- data/assets/resources/spritesheets/spritesheet-backgrounds.xml +16 -0
- data/assets/resources/spritesheets/spritesheet-characters.png +0 -0
- data/assets/resources/spritesheets/spritesheet-characters.xml +47 -0
- data/assets/resources/spritesheets/spritesheet-enemies.png +0 -0
- data/assets/resources/spritesheets/spritesheet-enemies.xml +62 -0
- data/assets/resources/spritesheets/spritesheet-tiles.png +0 -0
- data/assets/resources/spritesheets/spritesheet-tiles.xml +316 -0
- data/assets/{wasm → resources/web}/template.html +1 -1
- data/assets/target.rb +131 -0
- data/bin/ruby2d +281 -82
- data/examples/astar.rb +269 -0
- data/examples/asteroids.rb +403 -0
- data/examples/bezier_editor.rb +96 -0
- data/examples/boids.rb +191 -0
- data/examples/bouncing_balls.rb +119 -0
- data/examples/breakout.rb +186 -0
- data/examples/constellations.rb +100 -0
- data/examples/dashboard.rb +133 -0
- data/examples/double_pendulum.rb +254 -0
- data/examples/falling_sand.rb +291 -0
- data/examples/fireworks.rb +110 -0
- data/examples/fluid_dynamics.rb +340 -0
- data/examples/fourier_epicycles.rb +142 -0
- data/examples/fractal_tree.rb +326 -0
- data/examples/game_of_life.rb +148 -0
- data/examples/gamepads.rb +333 -0
- data/examples/hill_driver.rb +613 -0
- data/examples/logo_animation.rb +251 -0
- data/examples/lorenz_attractor.rb +115 -0
- data/examples/mandelbrot.rb +207 -0
- data/examples/marching_squares.rb +200 -0
- data/examples/maze.rb +323 -0
- data/examples/nbody.rb +338 -0
- data/examples/outrun.rb +391 -0
- data/examples/pixel_paint.rb +313 -0
- data/examples/platformer.rb +335 -0
- data/examples/ray_casting_maze.rb +175 -0
- data/examples/shadow_casting.rb +151 -0
- data/examples/shape_recognizer.rb +908 -0
- data/examples/snake.rb +144 -0
- data/examples/sprite_sheets.rb +246 -0
- data/examples/swarm.rb +206 -0
- data/examples/tsp_annealing.rb +104 -0
- data/examples/ui_controls.rb +163 -0
- data/examples/wireframe_3d.rb +139 -0
- data/ext/ruby2d/audio.c +324 -0
- data/ext/ruby2d/canvas.c +1715 -494
- data/ext/ruby2d/ext.c +541 -0
- data/ext/ruby2d/extconf.rb +122 -145
- data/ext/ruby2d/font.c +566 -20
- data/ext/ruby2d/fps.c +92 -0
- data/ext/ruby2d/image.c +466 -41
- data/ext/ruby2d/ruby2d.c +374 -1890
- data/ext/ruby2d/ruby2d.h +840 -460
- data/ext/ruby2d/shapes.c +931 -101
- data/ext/ruby2d/text.c +586 -21
- data/ext/ruby2d/window.c +1693 -283
- data/lib/ruby2d/atlas_parser.rb +194 -0
- data/lib/ruby2d/audio.rb +73 -0
- data/lib/ruby2d/benchmark.rb +259 -0
- data/lib/ruby2d/bitmap_text.rb +155 -0
- data/lib/ruby2d/button.rb +402 -0
- data/lib/ruby2d/canvas.rb +703 -279
- data/lib/ruby2d/circle.rb +206 -29
- data/lib/ruby2d/cli/browser.rb +314 -0
- data/lib/ruby2d/cli/build.rb +471 -257
- data/lib/ruby2d/cli/colorize.rb +7 -5
- data/lib/ruby2d/cli/examples.rb +127 -0
- data/lib/ruby2d/cli/launch.rb +24 -39
- data/lib/ruby2d/cli/lib_files.rb +48 -0
- data/lib/ruby2d/cli/messages.rb +40 -0
- data/lib/ruby2d/cli/setup.rb +273 -0
- data/lib/ruby2d/cli/static_server.rb +160 -0
- data/lib/ruby2d/cli/usage.rb +255 -0
- data/lib/ruby2d/color.rb +191 -69
- data/lib/ruby2d/core.rb +37 -19
- data/lib/ruby2d/deps_help.rb +89 -0
- data/lib/ruby2d/dsl.rb +51 -12
- data/lib/ruby2d/ellipse.rb +228 -0
- data/lib/ruby2d/exceptions.rb +0 -2
- data/lib/ruby2d/font.rb +34 -74
- data/lib/ruby2d/gamepad.rb +323 -0
- data/lib/ruby2d/gem_paths.rb +19 -0
- data/lib/ruby2d/image.rb +155 -52
- data/lib/ruby2d/interactive.rb +103 -0
- data/lib/ruby2d/json_parser.rb +268 -0
- data/lib/ruby2d/line.rb +194 -85
- data/lib/ruby2d/mruby_compat.rb +12 -0
- data/lib/ruby2d/polygon.rb +297 -0
- data/lib/ruby2d/polyline.rb +368 -0
- data/lib/ruby2d/quad.rb +358 -94
- data/lib/ruby2d/rectangle.rb +77 -19
- data/lib/ruby2d/renderable.rb +411 -11
- data/lib/ruby2d/sprite.rb +471 -180
- data/lib/ruby2d/sprite_sheet.rb +64 -0
- data/lib/ruby2d/square.rb +29 -17
- data/lib/ruby2d/text.rb +196 -65
- data/lib/ruby2d/tileset.rb +139 -81
- data/lib/ruby2d/triangle.rb +324 -89
- data/lib/ruby2d/version.rb +1 -3
- data/lib/ruby2d/vertices.rb +62 -85
- data/lib/ruby2d/warnings.rb +29 -0
- data/lib/ruby2d/window/class_methods.rb +218 -0
- data/lib/ruby2d/window/gamepad_events.rb +141 -0
- data/lib/ruby2d/window/key_events.rb +74 -0
- data/lib/ruby2d/window/mouse_events.rb +171 -0
- data/lib/ruby2d/window/object_events.rb +168 -0
- data/lib/ruby2d/window.rb +632 -585
- data/lib/ruby2d.rb +14 -19
- metadata +275 -428
- data/assets/app.icns +0 -0
- data/assets/include/GLES2/gl2.h +0 -656
- data/assets/include/GLES2/gl2ext.h +0 -3949
- data/assets/include/GLES2/gl2ext_angle.h +0 -701
- data/assets/include/GLES2/gl2platform.h +0 -27
- data/assets/include/GLES3/gl3.h +0 -1192
- data/assets/include/GLES3/gl31.h +0 -1507
- data/assets/include/GLES3/gl32.h +0 -1808
- data/assets/include/GLES3/gl3platform.h +0 -27
- data/assets/include/KHR/khrplatform.h +0 -290
- data/assets/include/SDL2/SDL.h +0 -233
- data/assets/include/SDL2/SDL_assert.h +0 -326
- data/assets/include/SDL2/SDL_atomic.h +0 -415
- data/assets/include/SDL2/SDL_audio.h +0 -1500
- data/assets/include/SDL2/SDL_clipboard.h +0 -141
- data/assets/include/SDL2/SDL_config.h +0 -61
- data/assets/include/SDL2/SDL_config_android.h +0 -194
- data/assets/include/SDL2/SDL_config_emscripten.h +0 -218
- data/assets/include/SDL2/SDL_config_iphoneos.h +0 -217
- data/assets/include/SDL2/SDL_config_macosx.h +0 -277
- data/assets/include/SDL2/SDL_config_minimal.h +0 -95
- data/assets/include/SDL2/SDL_config_ngage.h +0 -89
- data/assets/include/SDL2/SDL_config_os2.h +0 -207
- data/assets/include/SDL2/SDL_config_pandora.h +0 -141
- data/assets/include/SDL2/SDL_config_windows.h +0 -331
- data/assets/include/SDL2/SDL_config_wingdk.h +0 -253
- data/assets/include/SDL2/SDL_config_winrt.h +0 -220
- data/assets/include/SDL2/SDL_config_xbox.h +0 -235
- data/assets/include/SDL2/SDL_cpuinfo.h +0 -594
- data/assets/include/SDL2/SDL_endian.h +0 -348
- data/assets/include/SDL2/SDL_error.h +0 -163
- data/assets/include/SDL2/SDL_events.h +0 -1166
- data/assets/include/SDL2/SDL_filesystem.h +0 -149
- data/assets/include/SDL2/SDL_gamecontroller.h +0 -1074
- data/assets/include/SDL2/SDL_gesture.h +0 -117
- data/assets/include/SDL2/SDL_guid.h +0 -100
- data/assets/include/SDL2/SDL_haptic.h +0 -1341
- data/assets/include/SDL2/SDL_hidapi.h +0 -451
- data/assets/include/SDL2/SDL_hints.h +0 -2569
- data/assets/include/SDL2/SDL_image.h +0 -2173
- data/assets/include/SDL2/SDL_joystick.h +0 -1066
- data/assets/include/SDL2/SDL_keyboard.h +0 -353
- data/assets/include/SDL2/SDL_keycode.h +0 -358
- data/assets/include/SDL2/SDL_loadso.h +0 -115
- data/assets/include/SDL2/SDL_log.h +0 -404
- data/assets/include/SDL2/SDL_main.h +0 -275
- data/assets/include/SDL2/SDL_mixer.h +0 -2784
- data/assets/include/SDL2/SDL_mouse.h +0 -465
- data/assets/include/SDL2/SDL_mutex.h +0 -471
- data/assets/include/SDL2/SDL_name.h +0 -33
- data/assets/include/SDL2/SDL_pixels.h +0 -644
- data/assets/include/SDL2/SDL_platform.h +0 -261
- data/assets/include/SDL2/SDL_quit.h +0 -58
- data/assets/include/SDL2/SDL_rect.h +0 -376
- data/assets/include/SDL2/SDL_render.h +0 -1919
- data/assets/include/SDL2/SDL_revision.h +0 -6
- data/assets/include/SDL2/SDL_rwops.h +0 -841
- data/assets/include/SDL2/SDL_sensor.h +0 -322
- data/assets/include/SDL2/SDL_shape.h +0 -155
- data/assets/include/SDL2/SDL_stdinc.h +0 -830
- data/assets/include/SDL2/SDL_surface.h +0 -997
- data/assets/include/SDL2/SDL_system.h +0 -623
- data/assets/include/SDL2/SDL_syswm.h +0 -386
- data/assets/include/SDL2/SDL_test_images.h +0 -78
- data/assets/include/SDL2/SDL_test_random.h +0 -115
- data/assets/include/SDL2/SDL_thread.h +0 -464
- data/assets/include/SDL2/SDL_timer.h +0 -222
- data/assets/include/SDL2/SDL_touch.h +0 -150
- data/assets/include/SDL2/SDL_ttf.h +0 -2316
- data/assets/include/SDL2/SDL_types.h +0 -29
- data/assets/include/SDL2/SDL_version.h +0 -204
- data/assets/include/SDL2/SDL_video.h +0 -2150
- data/assets/include/SDL2/SDL_vulkan.h +0 -215
- data/assets/include/SDL2/begin_code.h +0 -187
- data/assets/include/mruby/boxing_no.h +0 -58
- data/assets/include/mruby/class.h +0 -108
- data/assets/include/mruby/error.h +0 -147
- data/assets/include/mruby/gc.h +0 -94
- data/assets/include/mruby/irep.h +0 -147
- data/assets/include/mruby/khash.h +0 -284
- data/assets/include/mruby/object.h +0 -43
- data/assets/include/mruby/presym/disable.h +0 -70
- data/assets/include/mruby/presym/enable.h +0 -37
- data/assets/include/mruby/presym.h +0 -40
- data/assets/include/mruby/proc.h +0 -211
- data/assets/include/mruby/throw.h +0 -66
- data/assets/macos/universal/bin/mrbc +0 -0
- data/assets/macos/universal/lib/libFLAC.a +0 -0
- data/assets/macos/universal/lib/libSDL2.a +0 -0
- data/assets/macos/universal/lib/libSDL2_image.a +0 -0
- data/assets/macos/universal/lib/libSDL2_mixer.a +0 -0
- data/assets/macos/universal/lib/libSDL2_ttf.a +0 -0
- data/assets/macos/universal/lib/libavif.a +0 -0
- data/assets/macos/universal/lib/libbrotlicommon-static.a +0 -0
- data/assets/macos/universal/lib/libbrotlidec-static.a +0 -0
- data/assets/macos/universal/lib/libfreetype.a +0 -0
- data/assets/macos/universal/lib/libgraphite2.a +0 -0
- data/assets/macos/universal/lib/libharfbuzz.a +0 -0
- data/assets/macos/universal/lib/libhwy.a +0 -0
- data/assets/macos/universal/lib/libjpeg.a +0 -0
- data/assets/macos/universal/lib/libjxl.a +0 -0
- data/assets/macos/universal/lib/libmodplug.a +0 -0
- data/assets/macos/universal/lib/libmpg123.a +0 -0
- data/assets/macos/universal/lib/libmruby.a +0 -0
- data/assets/macos/universal/lib/libogg.a +0 -0
- data/assets/macos/universal/lib/libpng.a +0 -0
- data/assets/macos/universal/lib/libtiff.a +0 -0
- data/assets/macos/universal/lib/libvorbis.a +0 -0
- data/assets/macos/universal/lib/libvorbisfile.a +0 -0
- data/assets/macos/universal/lib/libwebp.a +0 -0
- data/assets/macos/universal/lib/libzstd.a +0 -0
- data/assets/test_media/README.md +0 -3
- data/assets/test_media/bitstream_vera/COPYRIGHT.txt +0 -124
- data/assets/test_media/bitstream_vera/vera.ttf +0 -0
- data/assets/test_media/boom.png +0 -0
- data/assets/test_media/coin.png +0 -0
- data/assets/test_media/colors.png +0 -0
- data/assets/test_media/controller.png +0 -0
- data/assets/test_media/dial.wav +0 -0
- data/assets/test_media/hero.png +0 -0
- data/assets/test_media/image.bmp +0 -0
- data/assets/test_media/image.jpg +0 -0
- data/assets/test_media/image.png +0 -0
- data/assets/test_media/music.flac +0 -0
- data/assets/test_media/music.mp3 +0 -0
- data/assets/test_media/music.ogg +0 -0
- data/assets/test_media/music.wav +0 -0
- data/assets/test_media/originals/boom.pxm +0 -0
- data/assets/test_media/originals/coin.pxm +0 -0
- data/assets/test_media/originals/controller.sketch +0 -0
- data/assets/test_media/originals/hero.pxm +0 -0
- data/assets/test_media/originals/image.pxm +0 -0
- data/assets/test_media/originals/music.caf +0 -0
- data/assets/test_media/originals/texture_atlas.pxm +0 -0
- data/assets/test_media/rondo_alla_turka.ogg +0 -0
- data/assets/test_media/sound.flac +0 -0
- data/assets/test_media/sound.mp3 +0 -0
- data/assets/test_media/sound.ogg +0 -0
- data/assets/test_media/sound.wav +0 -0
- data/assets/test_media/sprite_sheet.png +0 -0
- data/assets/test_media/texture_atlas.png +0 -0
- data/assets/wasm/libmruby.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/bin/mrbc.exe +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libLerc.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_image.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_ttf.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlicommon.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlidec.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbz2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libdeflate.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libfreetype.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libglew32.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libgraphite2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libharfbuzz.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libhwy.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjbig.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjpeg.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjxl.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/liblzma.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmruby.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libpng.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libssp.a +0 -1
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libtiff.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libwebp.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libz.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libzstd.a +0 -0
- data/assets/windows/mingw-w64-x86_64/bin/mrbc.exe +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libLerc.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_image.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_ttf.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbrotlicommon.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbrotlidec.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbz2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libdeflate.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libfreetype.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libglew32.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libgraphite2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libharfbuzz.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libhwy.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjbig.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjpeg.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjxl.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/liblzma.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmruby.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libpng.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libssp.a +0 -1
- data/assets/windows/mingw-w64-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libtiff.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libwebp.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libz.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libzstd.a +0 -0
- data/assets/xcode/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
- data/assets/xcode/ios/Assets.xcassets/Contents.json +0 -6
- data/assets/xcode/ios/Base.lproj/LaunchScreen.storyboard +0 -27
- data/assets/xcode/ios/Info.plist +0 -43
- data/assets/xcode/ios/MRuby.framework/Headers/mrbconf.h +0 -143
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/array.h +0 -280
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/class.h +0 -97
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/common.h +0 -77
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/compile.h +0 -195
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/data.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/debug.h +0 -67
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/dump.h +0 -196
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/error.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/gc.h +0 -91
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/hash.h +0 -215
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/irep.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/istruct.h +0 -47
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/khash.h +0 -274
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/numeric.h +0 -161
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/object.h +0 -44
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/opcode.h +0 -69
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/ops.h +0 -117
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/proc.h +0 -131
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/range.h +0 -49
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/re.h +0 -16
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/string.h +0 -444
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/throw.h +0 -55
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/value.h +0 -309
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/variable.h +0 -136
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/version.h +0 -110
- data/assets/xcode/ios/MRuby.framework/Headers/mruby.h +0 -1282
- data/assets/xcode/ios/MRuby.framework/Info.plist +0 -20
- data/assets/xcode/ios/MRuby.framework/MRuby +0 -0
- data/assets/xcode/ios/MyApp.xcodeproj/project.pbxproj +0 -368
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
- data/assets/xcode/ios/main.c +0 -1
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json +0 -17
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json +0 -17
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json +0 -32
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/LaunchImage.launchimage/Contents.json +0 -22
- data/assets/xcode/tvos/Info.plist +0 -30
- data/assets/xcode/tvos/MRuby.framework/Headers/mrbconf.h +0 -143
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/array.h +0 -280
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/class.h +0 -97
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/common.h +0 -77
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/compile.h +0 -195
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/data.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/debug.h +0 -67
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/dump.h +0 -196
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/error.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/gc.h +0 -91
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/hash.h +0 -215
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/irep.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/istruct.h +0 -47
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/khash.h +0 -274
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/numeric.h +0 -161
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/object.h +0 -44
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/opcode.h +0 -69
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/ops.h +0 -117
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/proc.h +0 -131
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/range.h +0 -49
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/re.h +0 -16
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/string.h +0 -444
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/throw.h +0 -55
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/value.h +0 -309
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/variable.h +0 -136
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/version.h +0 -110
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby.h +0 -1282
- data/assets/xcode/tvos/MRuby.framework/Info.plist +0 -20
- data/assets/xcode/tvos/MRuby.framework/MRuby +0 -0
- data/assets/xcode/tvos/MyApp.xcodeproj/project.pbxproj +0 -350
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
- data/assets/xcode/tvos/main.c +0 -1
- data/ext/ruby2d/common.c +0 -185
- data/ext/ruby2d/controllers.c +0 -110
- data/ext/ruby2d/gl.c +0 -398
- data/ext/ruby2d/gl2.c +0 -86
- data/ext/ruby2d/gl3.c +0 -304
- data/ext/ruby2d/gles.c +0 -302
- data/ext/ruby2d/input.c +0 -48
- data/ext/ruby2d/music.c +0 -127
- data/ext/ruby2d/sound.c +0 -132
- data/lib/ruby2d/cli/console.rb +0 -65
- data/lib/ruby2d/cli/enable_console.rb +0 -7
- data/lib/ruby2d/cli/platform.rb +0 -17
- data/lib/ruby2d/music.rb +0 -77
- data/lib/ruby2d/pixel.rb +0 -18
- data/lib/ruby2d/pixmap.rb +0 -39
- data/lib/ruby2d/pixmap_atlas.rb +0 -56
- data/lib/ruby2d/sound.rb +0 -61
- data/lib/ruby2d/texture.rb +0 -38
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2ext.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2platform.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_khrplatform.h +0 -0
- /data/assets/{include → platform/include}/mruby/re.h +0 -0
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
# Shape recognizer
|
|
2
|
+
# A two-layer neural net learns triangles, circles, and squares on a 16×16 grid.
|
|
3
|
+
#
|
|
4
|
+
# On startup it trains on a stream of procedurally generated shapes
|
|
5
|
+
# (random sizes, rotations, brush widths, and small position jitter);
|
|
6
|
+
# the left grid shows each training shape as the network sees it, and a
|
|
7
|
+
# yellow box marks its true class on the right — so you can watch the
|
|
8
|
+
# receptive fields resolve and the predictions catch up to ground truth
|
|
9
|
+
# in real time. After training, draw with the mouse on the left grid:
|
|
10
|
+
# the middle panel shows the network in action (each hidden unit is
|
|
11
|
+
# rendered as its 16×16 receptive field, lines pulse with activations
|
|
12
|
+
# and weights), and the right panel shows the predicted class with a
|
|
13
|
+
# confidence bar. Press `c` to clear the grid; `r` to retrain from
|
|
14
|
+
# scratch.
|
|
15
|
+
#
|
|
16
|
+
# A note on what makes this work: an MLP can't learn translation
|
|
17
|
+
# invariance from raw pixels (that's CNNs' job), so training shapes are
|
|
18
|
+
# centered with small jitter and the user's drawing is auto-centered (by
|
|
19
|
+
# centroid) before the network sees it. With that, ~99% accuracy on
|
|
20
|
+
# centered shapes; ~85% if you draw way off-center and rely on the
|
|
21
|
+
# centroid shift.
|
|
22
|
+
|
|
23
|
+
require 'ruby2d'
|
|
24
|
+
|
|
25
|
+
# === Tunables ===
|
|
26
|
+
|
|
27
|
+
WIDTH = 1200 # window width in pixels
|
|
28
|
+
HEIGHT = 600 # window height in pixels
|
|
29
|
+
|
|
30
|
+
GRID = 16 # input is GRID × GRID = 256 cells
|
|
31
|
+
INPUT_DIM = GRID * GRID
|
|
32
|
+
GRID_CENTER = (GRID - 1) / 2.0 # 7.5
|
|
33
|
+
HIDDEN = 16 # hidden-layer width
|
|
34
|
+
NUM_CLASSES = 3
|
|
35
|
+
CLASSES = %w[triangle circle square].freeze
|
|
36
|
+
CLASS_COLORS = %w[#fb923c #38bdf8 #a3e635].freeze # orange / cyan / lime
|
|
37
|
+
|
|
38
|
+
# Training (online: a fresh example is generated each step, so the network
|
|
39
|
+
# can't memorize a fixed set — it has to learn shape-ness)
|
|
40
|
+
TRAIN_TOTAL_STEPS = 18_000
|
|
41
|
+
LEARNING_RATE = 0.06
|
|
42
|
+
TRAIN_PER_FRAME = 70
|
|
43
|
+
POSITION_JITTER = 1.5 # training-shape position offset from center, in cells
|
|
44
|
+
TRAIN_BRUSHES = [0.7, 0.8, 0.9, 1.0].freeze # brackets USER_BRUSH so it's mid-distribution, not at the upper edge
|
|
45
|
+
USER_BRUSH = 0.9 # brush radius the user paints with
|
|
46
|
+
|
|
47
|
+
# Pacing for the two training visuals, in updates per second. Training
|
|
48
|
+
# itself runs at TRAIN_PER_FRAME each frame regardless; these only set how
|
|
49
|
+
# fast the visualization reads, and being wall-clock rates they read the
|
|
50
|
+
# same whether the build does 15 fps or 150. The thumbnails are the
|
|
51
|
+
# expensive panel — a repaint touches all 16 receptive fields — so they
|
|
52
|
+
# advance more slowly than the shape slideshow, which costs nothing.
|
|
53
|
+
SHAPE_UPDATES_PER_SEC = 8 # training shapes shown per second
|
|
54
|
+
RF_UPDATES_PER_SEC = 3 # receptive-field repaints per second
|
|
55
|
+
|
|
56
|
+
# Drawing area (left panel)
|
|
57
|
+
DRAW_X = 40
|
|
58
|
+
DRAW_Y = 110
|
|
59
|
+
DRAW_CELL = 18
|
|
60
|
+
DRAW_W = GRID * DRAW_CELL
|
|
61
|
+
DRAW_RIGHT = DRAW_X + DRAW_W
|
|
62
|
+
|
|
63
|
+
# Network panel — hidden units rendered as 16×16 receptive-field thumbnails.
|
|
64
|
+
# HIDDEN_TOP/BOTTOM sit far enough apart that HIDDEN_SPACING (= 34 here)
|
|
65
|
+
# is strictly greater than RF_SIZE (= 32), so adjacent thumbnails clear
|
|
66
|
+
# each other with a 2 px gutter — leaving room for the active-unit stroke.
|
|
67
|
+
RF_X = 470
|
|
68
|
+
RF_CELL = 2
|
|
69
|
+
RF_SIZE = GRID * RF_CELL
|
|
70
|
+
HIDDEN_TOP = 60 # center y of the topmost thumbnail
|
|
71
|
+
HIDDEN_BOTTOM = 570 # center y of the bottommost thumbnail
|
|
72
|
+
HIDDEN_SPACING = (HIDDEN_BOTTOM - HIDDEN_TOP).to_f / (HIDDEN - 1)
|
|
73
|
+
|
|
74
|
+
# Output panel — class icons sized to roughly the same 48 px bbox: a
|
|
75
|
+
# 24-radius circle is the baseline; the square matches at side 48; the
|
|
76
|
+
# triangle gets a larger circumradius (pointy shapes look smaller for
|
|
77
|
+
# the same enclosing radius) and is shifted down by TRI_OFFSET so its
|
|
78
|
+
# bounding box (rather than its centroid) sits on cy — otherwise it
|
|
79
|
+
# reads as floating high relative to the bar centerline.
|
|
80
|
+
OUT_X = 880
|
|
81
|
+
OUT_Y = [150, 300, 450].freeze
|
|
82
|
+
OUT_RADIUS = 24 # circle radius
|
|
83
|
+
TRI_RADIUS = 30 # triangle circumradius
|
|
84
|
+
TRI_OFFSET = TRI_RADIUS / 4.0 # shift to bbox-center the triangle on cy
|
|
85
|
+
SQUARE_SIDE = 48 # square side
|
|
86
|
+
ICON_HALF = 26 # max half-extent across icons (drives line endpoints + highlight)
|
|
87
|
+
BAR_X = 920
|
|
88
|
+
BAR_W = 240
|
|
89
|
+
BAR_H = 18
|
|
90
|
+
TARGET_PAD = 6 # padding around the target-class highlight box
|
|
91
|
+
|
|
92
|
+
# Loss sparkline (under the drawing area, fills left-to-right as training progresses)
|
|
93
|
+
SPARKLINE_X = DRAW_X
|
|
94
|
+
SPARKLINE_Y = DRAW_Y + DRAW_W + 60
|
|
95
|
+
SPARKLINE_W = DRAW_W
|
|
96
|
+
SPARKLINE_H = 70
|
|
97
|
+
SPARKLINE_HISTORY = 200 # number of samples to plot
|
|
98
|
+
SPARKLINE_SAMPLE_EVERY = TRAIN_TOTAL_STEPS / SPARKLINE_HISTORY # one sample every ~90 steps
|
|
99
|
+
|
|
100
|
+
# Smoothing factor for the prediction bars/icons. Each frame the displayed
|
|
101
|
+
# softmax moves SMOOTH_RATE of the way toward the raw network output, so
|
|
102
|
+
# rapid training-shape switching doesn't read as flicker on the right panel.
|
|
103
|
+
SMOOTH_RATE = 0.25
|
|
104
|
+
|
|
105
|
+
IDLE_PULSE_RATE = 1.5 # rad/s — input lines breathe at ~4 s/cycle while idle
|
|
106
|
+
|
|
107
|
+
# When true, post-training overlays the user's ink centroid (yellow dot)
|
|
108
|
+
# and the grid center (faint cross) on the drawing area, making the
|
|
109
|
+
# auto-centering shift visible. Off by default — useful but noisy.
|
|
110
|
+
SHOW_CENTROID_HINT = false
|
|
111
|
+
|
|
112
|
+
# === Window ===
|
|
113
|
+
|
|
114
|
+
set title: 'Ruby 2D ▸ Examples ▸ Shape recognizer', width: WIDTH, height: HEIGHT
|
|
115
|
+
set close_on_esc: true
|
|
116
|
+
set background: '#0b1220'
|
|
117
|
+
|
|
118
|
+
canvas = Canvas.new(x: 0, y: 0, width: WIDTH, height: HEIGHT, fill: [0, 0, 0, 0])
|
|
119
|
+
|
|
120
|
+
# The receptive-field thumbnails get their own canvas, below the main one.
|
|
121
|
+
# They only change as W1 moves, so they repaint at RF_UPDATES_PER_SEC
|
|
122
|
+
# while training and once when it finishes — not every frame. Their own
|
|
123
|
+
# layer is what makes that possible: the main canvas is cleared and
|
|
124
|
+
# repainted each frame for the drawing grid and the active-unit outlines
|
|
125
|
+
# that sit on top of these thumbnails, and a shared canvas would take the
|
|
126
|
+
# thumbnails with it.
|
|
127
|
+
rf_canvas = Canvas.new(x: RF_X, y: 0, width: RF_SIZE, height: HEIGHT,
|
|
128
|
+
fill: [0, 0, 0, 0], z: -1)
|
|
129
|
+
|
|
130
|
+
# === Palettes ===
|
|
131
|
+
|
|
132
|
+
GRAY_PAL = Array.new(32) do |i|
|
|
133
|
+
t = i / 31.0
|
|
134
|
+
[0.05 + t * 0.93, 0.07 + t * 0.88, 0.12 + t * 0.83, 1.0]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Diverging palette: blue (negative weights) → near-black (zero) → orange (positive)
|
|
138
|
+
DIVERGING_PAL = Array.new(32) do |i|
|
|
139
|
+
t = (i - 15.5) / 15.5 # roughly -1..1
|
|
140
|
+
if t.negative?
|
|
141
|
+
a = -t
|
|
142
|
+
[0.05 + a * 0.05, 0.10 + a * 0.30, 0.18 + a * 0.55, 1.0]
|
|
143
|
+
else
|
|
144
|
+
[0.18 + t * 0.65, 0.12 + t * 0.30, 0.06 + t * 0.04, 1.0]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Every canvas cell the panels paint sits at a fixed position — only which
|
|
149
|
+
# color bucket it lands in changes from frame to frame. Building the
|
|
150
|
+
# rectangles once and re-bucketing references costs no allocation, where
|
|
151
|
+
# rebuilding them inline allocated ~4,400 arrays every frame.
|
|
152
|
+
DRAW_RECTS = Array.new(GRID * GRID) do |n|
|
|
153
|
+
[DRAW_X + (n % GRID) * DRAW_CELL, DRAW_Y + (n / GRID) * DRAW_CELL,
|
|
154
|
+
DRAW_CELL, DRAW_CELL]
|
|
155
|
+
end
|
|
156
|
+
RF_RECTS = Array.new(HIDDEN) do |i|
|
|
157
|
+
ty = (HIDDEN_TOP + i * HIDDEN_SPACING - RF_SIZE / 2.0).round
|
|
158
|
+
# x is relative to `rf_canvas`, which starts at RF_X; y is absolute
|
|
159
|
+
# because that canvas spans the full window height.
|
|
160
|
+
Array.new(GRID * GRID) do |n|
|
|
161
|
+
[(n % GRID) * RF_CELL, ty + (n / GRID) * RF_CELL, RF_CELL, RF_CELL]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
draw_buckets = Array.new(GRAY_PAL.size) { [] }
|
|
165
|
+
thumb_buckets = Array.new(DIVERGING_PAL.size) { [] }
|
|
166
|
+
|
|
167
|
+
CLASS_RGB = CLASS_COLORS.map do |hex|
|
|
168
|
+
[hex[1..2].to_i(16) / 255.0, hex[3..4].to_i(16) / 255.0, hex[5..6].to_i(16) / 255.0]
|
|
169
|
+
end
|
|
170
|
+
DIM_RGB = [0.12, 0.16, 0.22].freeze
|
|
171
|
+
|
|
172
|
+
# === Network ===
|
|
173
|
+
|
|
174
|
+
def rand_normal(stddev)
|
|
175
|
+
u = rand
|
|
176
|
+
u = 1e-9 if u.zero?
|
|
177
|
+
Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math::PI * rand) * stddev
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def fresh_weights
|
|
181
|
+
he1 = Math.sqrt(2.0 / INPUT_DIM)
|
|
182
|
+
he2 = Math.sqrt(2.0 / HIDDEN)
|
|
183
|
+
[
|
|
184
|
+
Array.new(HIDDEN * INPUT_DIM) { rand_normal(he1) },
|
|
185
|
+
Array.new(HIDDEN, 0.0),
|
|
186
|
+
Array.new(NUM_CLASSES * HIDDEN) { rand_normal(he2) },
|
|
187
|
+
Array.new(NUM_CLASSES, 0.0)
|
|
188
|
+
]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Forward pass with sparse input — `non_zero` is the list of pixel indices
|
|
192
|
+
# above threshold. Drawings are mostly empty cells, so skipping zeros makes
|
|
193
|
+
# both training and per-frame inference much faster.
|
|
194
|
+
def forward(x, non_zero, w1, b1, w2, b2)
|
|
195
|
+
h = Array.new(HIDDEN)
|
|
196
|
+
# The two `non_zero` sweeps — here and in `train_step` — are the hottest
|
|
197
|
+
# loops in the file: HIDDEN passes over every inked pixel, twice per
|
|
198
|
+
# training step. A block per element would cost more than the multiply
|
|
199
|
+
# and add inside it, so these two spell out the loop.
|
|
200
|
+
nz = non_zero.length
|
|
201
|
+
HIDDEN.times do |i|
|
|
202
|
+
s = b1[i]
|
|
203
|
+
base = i * INPUT_DIM
|
|
204
|
+
t = 0
|
|
205
|
+
while t < nz
|
|
206
|
+
j = non_zero[t]
|
|
207
|
+
s += w1[base + j] * x[j]
|
|
208
|
+
t += 1
|
|
209
|
+
end
|
|
210
|
+
h[i] = s.positive? ? s : 0.0
|
|
211
|
+
end
|
|
212
|
+
z = Array.new(NUM_CLASSES)
|
|
213
|
+
NUM_CLASSES.times do |k|
|
|
214
|
+
s = b2[k]
|
|
215
|
+
base = k * HIDDEN
|
|
216
|
+
HIDDEN.times { |j| s += w2[base + j] * h[j] }
|
|
217
|
+
z[k] = s
|
|
218
|
+
end
|
|
219
|
+
m = z.max
|
|
220
|
+
ez = z.map { |v| Math.exp(v - m) }
|
|
221
|
+
sum = ez.sum
|
|
222
|
+
[h, ez.map { |v| v / sum }]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# One SGD step with cross-entropy loss on a softmax output. Returns the loss.
|
|
226
|
+
def train_step(x, non_zero, target, w1, b1, w2, b2, lr)
|
|
227
|
+
h, y = forward(x, non_zero, w1, b1, w2, b2)
|
|
228
|
+
|
|
229
|
+
dz2 = y.dup
|
|
230
|
+
dz2[target] -= 1.0
|
|
231
|
+
|
|
232
|
+
dh = Array.new(HIDDEN, 0.0)
|
|
233
|
+
HIDDEN.times do |j|
|
|
234
|
+
next if h[j].zero? # ReLU gate: gradient is zero where the unit was inactive
|
|
235
|
+
s = 0.0
|
|
236
|
+
NUM_CLASSES.times { |k| s += w2[k * HIDDEN + j] * dz2[k] }
|
|
237
|
+
dh[j] = s
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
NUM_CLASSES.times do |k|
|
|
241
|
+
base = k * HIDDEN
|
|
242
|
+
g = lr * dz2[k]
|
|
243
|
+
HIDDEN.times { |j| w2[base + j] -= g * h[j] }
|
|
244
|
+
b2[k] -= g
|
|
245
|
+
end
|
|
246
|
+
nz = non_zero.length
|
|
247
|
+
HIDDEN.times do |i|
|
|
248
|
+
g = dh[i]
|
|
249
|
+
next if g.zero?
|
|
250
|
+
base = i * INPUT_DIM
|
|
251
|
+
lg = lr * g
|
|
252
|
+
t = 0
|
|
253
|
+
while t < nz
|
|
254
|
+
j = non_zero[t]
|
|
255
|
+
w1[base + j] -= lg * x[j]
|
|
256
|
+
t += 1
|
|
257
|
+
end
|
|
258
|
+
b1[i] -= lg
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
-Math.log(y[target] + 1e-9)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# === Shape rasterization ===
|
|
265
|
+
|
|
266
|
+
def stamp(grid, fx, fy, br)
|
|
267
|
+
x0 = (fx - br).floor
|
|
268
|
+
x1 = (fx + br).ceil
|
|
269
|
+
y0 = (fy - br).floor
|
|
270
|
+
y1 = (fy + br).ceil
|
|
271
|
+
# `while`, not ranges: a training example stamps ~140 times, and each
|
|
272
|
+
# `(a..b).each` pair allocated a Range per row. Comparing squared
|
|
273
|
+
# distances also keeps the square root off the rejected cells.
|
|
274
|
+
br2 = br * br
|
|
275
|
+
y = y0
|
|
276
|
+
while y <= y1
|
|
277
|
+
if y >= 0 && y < GRID
|
|
278
|
+
dy = y - fy
|
|
279
|
+
dy2 = dy * dy
|
|
280
|
+
row = y * GRID
|
|
281
|
+
x = x0
|
|
282
|
+
while x <= x1
|
|
283
|
+
if x >= 0 && x < GRID
|
|
284
|
+
dx = x - fx
|
|
285
|
+
d2 = dx * dx + dy2
|
|
286
|
+
if d2 <= br2
|
|
287
|
+
v = 1.0 - Math.sqrt(d2) / br
|
|
288
|
+
idx = row + x
|
|
289
|
+
grid[idx] += (1.0 - grid[idx]) * v
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
x += 1
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
y += 1
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def stroke(grid, x0, y0, x1, y1, br)
|
|
300
|
+
# Space the stamps at half the brush radius: close enough that the disks
|
|
301
|
+
# always overlap, and no closer. A fixed rate independent of `br` either
|
|
302
|
+
# leaves gaps for a wide brush or (as a flat 4x per cell did here)
|
|
303
|
+
# stamps four times over the same ground for a narrow one.
|
|
304
|
+
steps = (Math.sqrt((x1 - x0)**2 + (y1 - y0)**2) / (br * 0.5)).ceil
|
|
305
|
+
steps = 1 if steps < 1
|
|
306
|
+
(steps + 1).times do |i|
|
|
307
|
+
t = i.to_f / steps
|
|
308
|
+
stamp(grid, x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, br)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def jitter
|
|
313
|
+
GRID_CENTER + (rand - 0.5) * POSITION_JITTER * 2
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Hand-drawn shapes are rarely closed cleanly — the most common artifact
|
|
317
|
+
# is a small gap where the user starts and ends the stroke (especially on
|
|
318
|
+
# circles). Each generator occasionally drops a piece of its outline so
|
|
319
|
+
# the network sees "almost-closed" shapes during training and doesn't
|
|
320
|
+
# treat a closed boundary as a hard requirement. Triangles and squares
|
|
321
|
+
# get a small gap probability too, otherwise the network would learn
|
|
322
|
+
# "has a gap" ≈ "is a circle" and start mis-classifying open polygons.
|
|
323
|
+
|
|
324
|
+
def gen_triangle(grid, br)
|
|
325
|
+
cx = jitter; cy = jitter
|
|
326
|
+
r = 4.5 + rand * 2.5
|
|
327
|
+
rot = rand * 2 * Math::PI
|
|
328
|
+
pts = 3.times.map do |i|
|
|
329
|
+
a = rot + i * 2 * Math::PI / 3
|
|
330
|
+
[cx + Math.cos(a) * r, cy + Math.sin(a) * r]
|
|
331
|
+
end
|
|
332
|
+
skip_last = rand < 0.15
|
|
333
|
+
3.times do |i|
|
|
334
|
+
next if skip_last && i == 2
|
|
335
|
+
x0, y0 = pts[i]; x1, y1 = pts[(i + 1) % 3]
|
|
336
|
+
stroke(grid, x0, y0, x1, y1, br)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def gen_circle(grid, br)
|
|
341
|
+
cx = jitter; cy = jitter
|
|
342
|
+
r = 3.5 + rand * 3.0
|
|
343
|
+
segs = 32
|
|
344
|
+
gap_start = rand < 0.50 ? rand(segs) : -1
|
|
345
|
+
gap_len = gap_start >= 0 ? 1 + rand(3) : 0
|
|
346
|
+
segs.times do |i|
|
|
347
|
+
next if gap_len > 0 && (((i - gap_start) % segs) < gap_len)
|
|
348
|
+
a0 = i * 2 * Math::PI / segs
|
|
349
|
+
a1 = (i + 1) * 2 * Math::PI / segs
|
|
350
|
+
stroke(grid, cx + Math.cos(a0) * r, cy + Math.sin(a0) * r,
|
|
351
|
+
cx + Math.cos(a1) * r, cy + Math.sin(a1) * r, br)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def gen_square(grid, br)
|
|
356
|
+
cx = jitter; cy = jitter
|
|
357
|
+
half = 3.5 + rand * 2.5
|
|
358
|
+
rot = rand * Math::PI / 2 # square has 4-fold rotational symmetry
|
|
359
|
+
c = Math.cos(rot); s = Math.sin(rot)
|
|
360
|
+
pts = [[-half, -half], [half, -half], [half, half], [-half, half]].map do |x, y|
|
|
361
|
+
[cx + x * c - y * s, cy + x * s + y * c]
|
|
362
|
+
end
|
|
363
|
+
skip_last = rand < 0.10
|
|
364
|
+
4.times do |i|
|
|
365
|
+
next if skip_last && i == 3
|
|
366
|
+
x0, y0 = pts[i]; x1, y1 = pts[(i + 1) % 4]
|
|
367
|
+
stroke(grid, x0, y0, x1, y1, br)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
GENERATORS = [method(:gen_triangle), method(:gen_circle), method(:gen_square)].freeze
|
|
372
|
+
|
|
373
|
+
def make_example(k)
|
|
374
|
+
grid = Array.new(INPUT_DIM, 0.0)
|
|
375
|
+
GENERATORS[k].call(grid, TRAIN_BRUSHES.sample)
|
|
376
|
+
non_zero = []
|
|
377
|
+
j = 0
|
|
378
|
+
while j < INPUT_DIM
|
|
379
|
+
non_zero << j if grid[j] > 0.001
|
|
380
|
+
j += 1
|
|
381
|
+
end
|
|
382
|
+
[grid, non_zero, k]
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Ink-weighted centroid of a 16×16 grid in cell coordinates, or nil if
|
|
386
|
+
# the grid is essentially empty.
|
|
387
|
+
def grid_centroid(grid)
|
|
388
|
+
total = 0.0; cx = 0.0; cy = 0.0
|
|
389
|
+
GRID.times do |r|
|
|
390
|
+
GRID.times do |c|
|
|
391
|
+
v = grid[r * GRID + c]
|
|
392
|
+
next if v < 0.001
|
|
393
|
+
total += v; cx += c * v; cy += r * v
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
return nil if total < 1e-6
|
|
397
|
+
[cx / total, cy / total]
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Shift a 16×16 grid so its ink centroid lands at the grid center. The MLP
|
|
401
|
+
# can't learn translation invariance from raw pixels — auto-centering at
|
|
402
|
+
# inference time is what lets a user draw anywhere on the grid and still
|
|
403
|
+
# get a good prediction.
|
|
404
|
+
def centered_input(grid)
|
|
405
|
+
c = grid_centroid(grid)
|
|
406
|
+
return grid if c.nil?
|
|
407
|
+
cx, cy = c
|
|
408
|
+
dx = (GRID_CENTER - cx).round
|
|
409
|
+
dy = (GRID_CENTER - cy).round
|
|
410
|
+
return grid if dx.zero? && dy.zero?
|
|
411
|
+
|
|
412
|
+
out = Array.new(INPUT_DIM, 0.0)
|
|
413
|
+
GRID.times do |r|
|
|
414
|
+
nr = r + dy
|
|
415
|
+
next if nr.negative? || nr >= GRID
|
|
416
|
+
GRID.times do |c|
|
|
417
|
+
nc = c + dx
|
|
418
|
+
next if nc.negative? || nc >= GRID
|
|
419
|
+
out[nr * GRID + nc] = grid[r * GRID + c]
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
out
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# === Mutable state ===
|
|
426
|
+
|
|
427
|
+
w1, b1, w2, b2 = fresh_weights
|
|
428
|
+
state = { steps: 0, loss: 0.0, done: false }
|
|
429
|
+
|
|
430
|
+
user_grid = Array.new(INPUT_DIM, 0.0)
|
|
431
|
+
last_mouse = nil
|
|
432
|
+
mouse_held = false
|
|
433
|
+
|
|
434
|
+
current_h = Array.new(HIDDEN, 0.0)
|
|
435
|
+
current_y = Array.new(NUM_CLASSES, 1.0 / NUM_CLASSES)
|
|
436
|
+
display_y = Array.new(NUM_CLASSES, 1.0 / NUM_CLASSES) # smoothed softmax for the bars/icons
|
|
437
|
+
|
|
438
|
+
# Most recently captured training example — what the drawing panel
|
|
439
|
+
# shows and what the prediction bars are computed from while training.
|
|
440
|
+
display_grid = Array.new(INPUT_DIM, 0.0)
|
|
441
|
+
display_nz = []
|
|
442
|
+
display_class = nil
|
|
443
|
+
rf_dirty = true # receptive-field thumbnails need repainting
|
|
444
|
+
shape_accum = 0.0 # fractional progress toward the next shape swap
|
|
445
|
+
rf_accum = 0.0 # fractional progress toward the next thumbnail repaint
|
|
446
|
+
|
|
447
|
+
loss_history = [] # appended every SPARKLINE_SAMPLE_EVERY training steps
|
|
448
|
+
elapsed = 0.0 # seconds since show, drives the idle pulse
|
|
449
|
+
|
|
450
|
+
# === Persistent renderables ===
|
|
451
|
+
|
|
452
|
+
header_text = Text.new('Training…', x: DRAW_X, y: 70, size: 16, color: '#cbd5e1')
|
|
453
|
+
network_label = Text.new('Network', y: 20, size: 16, color: '#cbd5e1')
|
|
454
|
+
network_label.x = RF_X + RF_SIZE / 2 - network_label.width / 2
|
|
455
|
+
prediction_label = Text.new('Prediction', y: 70, size: 16, color: '#cbd5e1')
|
|
456
|
+
prediction_label.x = OUT_X - prediction_label.width / 2
|
|
457
|
+
hint = Text.new('R retrain',
|
|
458
|
+
x: DRAW_X, y: DRAW_Y + DRAW_W + 10, size: 12, color: '#64748b')
|
|
459
|
+
|
|
460
|
+
# Drawing-area frame
|
|
461
|
+
[
|
|
462
|
+
[DRAW_X - 1, DRAW_Y - 1, DRAW_RIGHT + 1, DRAW_Y - 1],
|
|
463
|
+
[DRAW_X - 1, DRAW_Y + DRAW_W + 1, DRAW_RIGHT + 1, DRAW_Y + DRAW_W + 1],
|
|
464
|
+
[DRAW_X - 1, DRAW_Y - 1, DRAW_X - 1, DRAW_Y + DRAW_W + 1],
|
|
465
|
+
[DRAW_RIGHT + 1, DRAW_Y - 1, DRAW_RIGHT + 1, DRAW_Y + DRAW_W + 1]
|
|
466
|
+
].each do |x1, y1, x2, y2|
|
|
467
|
+
Line.new(x1: x1, y1: y1, x2: x2, y2: y2, stroke_width: 1, color: '#1e293b')
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Loss-sparkline frame and label
|
|
471
|
+
Text.new('Loss', x: SPARKLINE_X, y: SPARKLINE_Y - 22, size: 12, color: '#94a3b8')
|
|
472
|
+
[
|
|
473
|
+
[SPARKLINE_X - 1, SPARKLINE_Y - 1,
|
|
474
|
+
SPARKLINE_X + SPARKLINE_W + 1, SPARKLINE_Y - 1],
|
|
475
|
+
[SPARKLINE_X - 1, SPARKLINE_Y + SPARKLINE_H + 1,
|
|
476
|
+
SPARKLINE_X + SPARKLINE_W + 1, SPARKLINE_Y + SPARKLINE_H + 1],
|
|
477
|
+
[SPARKLINE_X - 1, SPARKLINE_Y - 1,
|
|
478
|
+
SPARKLINE_X - 1, SPARKLINE_Y + SPARKLINE_H + 1],
|
|
479
|
+
[SPARKLINE_X + SPARKLINE_W + 1, SPARKLINE_Y - 1,
|
|
480
|
+
SPARKLINE_X + SPARKLINE_W + 1, SPARKLINE_Y + SPARKLINE_H + 1]
|
|
481
|
+
].each do |x1, y1, x2, y2|
|
|
482
|
+
Line.new(x1: x1, y1: y1, x2: x2, y2: y2, stroke_width: 1, color: '#1e293b')
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Input → hidden: a visual abstraction. Each input pixel actually connects
|
|
486
|
+
# to every hidden unit (256 × 16 = 4096 connections), which would be
|
|
487
|
+
# unreadable on screen. Instead we draw one summary line per hidden unit;
|
|
488
|
+
# its thickness/opacity track that unit's post-ReLU activation each frame
|
|
489
|
+
# — i.e. how strongly the unit's receptive-field pattern matches what's
|
|
490
|
+
# on the grid.
|
|
491
|
+
input_lines = HIDDEN.times.map do |i|
|
|
492
|
+
src_y = DRAW_Y + (i + 0.5) * DRAW_W / HIDDEN.to_f
|
|
493
|
+
dst_y = HIDDEN_TOP + i * HIDDEN_SPACING
|
|
494
|
+
Line.new(x1: DRAW_RIGHT + 6, y1: src_y, x2: RF_X - 6, y2: dst_y,
|
|
495
|
+
stroke_width: 1, color: [0.55, 0.7, 0.95, 0.20])
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Hidden → output: every connection is drawn (HIDDEN × NUM_CLASSES = 48).
|
|
499
|
+
# Orange = positive weight — when this hidden unit fires, it pushes this
|
|
500
|
+
# class up. Blue = negative weight — firing pushes the class down.
|
|
501
|
+
# Thickness/opacity scale with |w · h|, the connection's actual
|
|
502
|
+
# contribution this frame, so you can see which paths are driving the
|
|
503
|
+
# current prediction rather than just which weights happen to be large.
|
|
504
|
+
output_lines = []
|
|
505
|
+
NUM_CLASSES.times do |k|
|
|
506
|
+
HIDDEN.times do |i|
|
|
507
|
+
src_y = HIDDEN_TOP + i * HIDDEN_SPACING
|
|
508
|
+
output_lines << Line.new(x1: RF_X + RF_SIZE + 6, y1: src_y,
|
|
509
|
+
x2: OUT_X - ICON_HALF - 4, y2: OUT_Y[k],
|
|
510
|
+
stroke_width: 1, color: [0.6, 0.7, 0.85, 0.20])
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Output: bar background, colored fill, big class circle, label
|
|
515
|
+
NUM_CLASSES.times do |k|
|
|
516
|
+
Rectangle.new(x: BAR_X, y: OUT_Y[k] - BAR_H / 2,
|
|
517
|
+
width: BAR_W, height: BAR_H, color: '#1e293b')
|
|
518
|
+
end
|
|
519
|
+
out_bars = NUM_CLASSES.times.map do |k|
|
|
520
|
+
Rectangle.new(x: BAR_X, y: OUT_Y[k] - BAR_H / 2,
|
|
521
|
+
width: 0, height: BAR_H, color: CLASS_COLORS[k])
|
|
522
|
+
end
|
|
523
|
+
# One icon per class. Sized for visual balance — the triangle gets a
|
|
524
|
+
# larger circumradius than the circle/square's enclosing radius so it
|
|
525
|
+
# carries similar visual weight, and is shifted down by TRI_OFFSET so
|
|
526
|
+
# its bounding box (not its centroid, which sits 1/4 R above the base)
|
|
527
|
+
# centers on cy and lines up with the rest.
|
|
528
|
+
out_icons = NUM_CLASSES.times.map do |k|
|
|
529
|
+
cx = OUT_X
|
|
530
|
+
cy = OUT_Y[k]
|
|
531
|
+
dim = DIM_RGB + [1.0]
|
|
532
|
+
case k
|
|
533
|
+
when 0 # triangle, point up, equilateral, bbox-centered at (cx, cy)
|
|
534
|
+
s60 = Math.sin(Math::PI / 3)
|
|
535
|
+
Triangle.new(x1: cx, y1: cy - TRI_RADIUS + TRI_OFFSET,
|
|
536
|
+
x2: cx - TRI_RADIUS * s60, y2: cy + TRI_RADIUS / 2.0 + TRI_OFFSET,
|
|
537
|
+
x3: cx + TRI_RADIUS * s60, y3: cy + TRI_RADIUS / 2.0 + TRI_OFFSET,
|
|
538
|
+
color: dim)
|
|
539
|
+
when 1
|
|
540
|
+
Circle.new(x: cx, y: cy, radius: OUT_RADIUS, color: dim, sectors: 32)
|
|
541
|
+
when 2
|
|
542
|
+
Square.new(x: cx - SQUARE_SIDE / 2, y: cy - SQUARE_SIDE / 2,
|
|
543
|
+
size: SQUARE_SIDE, color: dim)
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
result_text = Text.new('—', y: 510, size: 30, color: '#cbd5e1', visible: false)
|
|
548
|
+
status = Text.new('Training…', x: DRAW_X, y: HEIGHT - 28, size: 13, color: '#94a3b8')
|
|
549
|
+
|
|
550
|
+
# Repaint every hidden unit's receptive field: the 16×16 slice of W1
|
|
551
|
+
# weighting how each input pixel feeds that unit, normalized per unit so
|
|
552
|
+
# the strongest weight uses the full palette. Driven by `rf_dirty` rather
|
|
553
|
+
# than run every frame — see `rf_canvas`.
|
|
554
|
+
draw_receptive_fields = lambda do
|
|
555
|
+
rf_canvas.clear
|
|
556
|
+
thumb_buckets.each(&:clear)
|
|
557
|
+
HIDDEN.times do |i|
|
|
558
|
+
base = i * INPUT_DIM
|
|
559
|
+
wmax = 0.0
|
|
560
|
+
INPUT_DIM.times do |j|
|
|
561
|
+
a = w1[base + j].abs
|
|
562
|
+
wmax = a if a > wmax
|
|
563
|
+
end
|
|
564
|
+
wmax = 1e-6 if wmax < 1e-6
|
|
565
|
+
rects = RF_RECTS[i]
|
|
566
|
+
j = 0
|
|
567
|
+
while j < INPUT_DIM
|
|
568
|
+
v = w1[base + j] / wmax # normalized to -1..1
|
|
569
|
+
idx = ((v + 1.0) * 0.5 * 31).round.clamp(0, 31)
|
|
570
|
+
thumb_buckets[idx] << rects[j]
|
|
571
|
+
j += 1
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
thumb_buckets.each_with_index do |rects, i|
|
|
575
|
+
next if rects.empty?
|
|
576
|
+
|
|
577
|
+
rf_canvas.fill_rectangles(rectangles: rects, color: DIVERGING_PAL[i])
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
reset = lambda do
|
|
582
|
+
rf_dirty = true
|
|
583
|
+
shape_accum = 0.0
|
|
584
|
+
rf_accum = 0.0
|
|
585
|
+
user_grid.fill(0.0)
|
|
586
|
+
last_mouse = nil
|
|
587
|
+
mouse_held = false
|
|
588
|
+
w1, b1, w2, b2 = fresh_weights
|
|
589
|
+
state[:steps] = 0
|
|
590
|
+
state[:loss] = 0.0
|
|
591
|
+
state[:done] = false
|
|
592
|
+
display_grid = Array.new(INPUT_DIM, 0.0)
|
|
593
|
+
display_nz = []
|
|
594
|
+
display_class = nil
|
|
595
|
+
display_y.fill(1.0 / NUM_CLASSES)
|
|
596
|
+
loss_history.clear
|
|
597
|
+
status.content = 'Training…'
|
|
598
|
+
header_text.content = 'Training…'
|
|
599
|
+
header_text.color = '#cbd5e1'
|
|
600
|
+
hint.content = 'R retrain'
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# === Input ===
|
|
604
|
+
|
|
605
|
+
def user_grid_coords(event)
|
|
606
|
+
return nil unless event.x.between?(DRAW_X, DRAW_RIGHT - 1) &&
|
|
607
|
+
event.y.between?(DRAW_Y, DRAW_Y + DRAW_W - 1)
|
|
608
|
+
[(event.x - DRAW_X) / DRAW_CELL.to_f, (event.y - DRAW_Y) / DRAW_CELL.to_f]
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
on :mouse_down do |event|
|
|
612
|
+
next unless event.button?(:left)
|
|
613
|
+
next unless state[:done] # the grid is showing training shapes — not a canvas yet
|
|
614
|
+
pos = user_grid_coords(event)
|
|
615
|
+
next unless pos
|
|
616
|
+
mouse_held = true
|
|
617
|
+
stamp(user_grid, *pos, USER_BRUSH)
|
|
618
|
+
last_mouse = pos
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
on :mouse_up do
|
|
622
|
+
mouse_held = false
|
|
623
|
+
last_mouse = nil
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
on :mouse_move do |event|
|
|
627
|
+
next unless mouse_held
|
|
628
|
+
pos = user_grid_coords(event)
|
|
629
|
+
next unless pos
|
|
630
|
+
if last_mouse
|
|
631
|
+
stroke(user_grid, *last_mouse, *pos, USER_BRUSH)
|
|
632
|
+
else
|
|
633
|
+
stamp(user_grid, *pos, USER_BRUSH)
|
|
634
|
+
end
|
|
635
|
+
last_mouse = pos
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
on(key_down: :c) do
|
|
639
|
+
user_grid.fill(0.0)
|
|
640
|
+
last_mouse = nil
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
on(key_down: :r) { reset.call }
|
|
644
|
+
|
|
645
|
+
# === Update ===
|
|
646
|
+
|
|
647
|
+
update do |dt|
|
|
648
|
+
elapsed += dt
|
|
649
|
+
|
|
650
|
+
# --- Train (online: fresh example each step) ---
|
|
651
|
+
last_x = nil; last_nz = nil; last_class = nil
|
|
652
|
+
unless state[:done]
|
|
653
|
+
TRAIN_PER_FRAME.times do
|
|
654
|
+
break if state[:steps] >= TRAIN_TOTAL_STEPS
|
|
655
|
+
x, nz, target = make_example(rand(NUM_CLASSES))
|
|
656
|
+
l = train_step(x, nz, target, w1, b1, w2, b2, LEARNING_RATE)
|
|
657
|
+
state[:loss] = state[:loss].zero? ? l : state[:loss] * 0.99 + l * 0.01
|
|
658
|
+
state[:steps] += 1
|
|
659
|
+
loss_history << state[:loss] if (state[:steps] % SPARKLINE_SAMPLE_EVERY).zero?
|
|
660
|
+
last_x = x; last_nz = nz; last_class = target
|
|
661
|
+
end
|
|
662
|
+
if state[:steps] >= TRAIN_TOTAL_STEPS
|
|
663
|
+
state[:done] = true
|
|
664
|
+
rf_dirty = true # last repaint: the weights stop moving here
|
|
665
|
+
status.content = format('Trained — final loss %.3f. Draw a shape!', state[:loss])
|
|
666
|
+
header_text.content = 'Draw a shape'
|
|
667
|
+
header_text.color = '#cbd5e1'
|
|
668
|
+
hint.content = 'C clear · R retrain'
|
|
669
|
+
else
|
|
670
|
+
pct = (state[:steps] * 100.0 / TRAIN_TOTAL_STEPS).to_i
|
|
671
|
+
status.content = format('Training… %d%% loss %.3f', pct, state[:loss])
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Both panels advance on wall-clock accumulators, so the visualization
|
|
675
|
+
# reads at the same speed everywhere. Training runs far faster than the
|
|
676
|
+
# eye and at very different speeds per platform — pacing these by
|
|
677
|
+
# frames or by step count made them strobe on one build and crawl on
|
|
678
|
+
# another. Surplus is dropped rather than carried: at most one shape
|
|
679
|
+
# and one repaint per frame, however long the frame was.
|
|
680
|
+
shape_accum += SHAPE_UPDATES_PER_SEC * dt
|
|
681
|
+
if last_x && (display_class.nil? || shape_accum >= 1.0)
|
|
682
|
+
shape_accum = 0.0
|
|
683
|
+
display_grid = last_x
|
|
684
|
+
display_nz = last_nz
|
|
685
|
+
display_class = last_class
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
rf_accum += RF_UPDATES_PER_SEC * dt
|
|
689
|
+
if rf_accum >= 1.0
|
|
690
|
+
rf_accum = 0.0
|
|
691
|
+
rf_dirty = true
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
if !state[:done] && display_class
|
|
695
|
+
header_text.content = "Training on #{CLASSES[display_class].upcase}"
|
|
696
|
+
header_text.color = CLASS_COLORS[display_class]
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# --- Forward pass ---
|
|
701
|
+
# While training, classify the displayed training shape (so the user
|
|
702
|
+
# sees the network's current verdict on the very example it just took
|
|
703
|
+
# a gradient step on). After training, classify the user's drawing.
|
|
704
|
+
if state[:done]
|
|
705
|
+
centered = centered_input(user_grid)
|
|
706
|
+
forward_grid = centered
|
|
707
|
+
forward_nz = []
|
|
708
|
+
INPUT_DIM.times { |j| forward_nz << j if centered[j] > 0.001 }
|
|
709
|
+
panel_grid = user_grid
|
|
710
|
+
else
|
|
711
|
+
forward_grid = display_grid
|
|
712
|
+
forward_nz = display_nz
|
|
713
|
+
panel_grid = display_grid
|
|
714
|
+
end
|
|
715
|
+
current_h, current_y = forward(forward_grid, forward_nz, w1, b1, w2, b2)
|
|
716
|
+
|
|
717
|
+
# Predictions are meaningful while a training shape is on screen, or
|
|
718
|
+
# after training when the user has drawn enough ink. Otherwise the
|
|
719
|
+
# bars/lines/icons would reflect the learned biases on an empty grid
|
|
720
|
+
# — visually misleading — so we keep them in a neutral resting state.
|
|
721
|
+
show_prediction = if state[:done]
|
|
722
|
+
forward_nz.size > 4
|
|
723
|
+
else
|
|
724
|
+
!display_class.nil?
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# Ease the displayed softmax toward the network's raw output. With a
|
|
728
|
+
# training shape changing several times a second, the raw
|
|
729
|
+
# current_y would jump on each switch; this turns the jumps into
|
|
730
|
+
# smooth animation. Snap back to uniform when there's nothing to
|
|
731
|
+
# predict so the bars don't drift on bias alone.
|
|
732
|
+
if show_prediction
|
|
733
|
+
NUM_CLASSES.times { |k| display_y[k] += (current_y[k] - display_y[k]) * SMOOTH_RATE }
|
|
734
|
+
else
|
|
735
|
+
display_y.fill(1.0 / NUM_CLASSES)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# --- Render canvas: drawing grid + receptive-field thumbnails ---
|
|
739
|
+
canvas.clear
|
|
740
|
+
|
|
741
|
+
draw_buckets.each(&:clear)
|
|
742
|
+
n = 0
|
|
743
|
+
while n < INPUT_DIM
|
|
744
|
+
idx = (panel_grid[n] * 31).round.clamp(0, 31)
|
|
745
|
+
draw_buckets[idx] << DRAW_RECTS[n]
|
|
746
|
+
n += 1
|
|
747
|
+
end
|
|
748
|
+
draw_buckets.each_with_index do |rects, i|
|
|
749
|
+
next if rects.empty?
|
|
750
|
+
canvas.fill_rectangles(rectangles: rects, color: GRAY_PAL[i])
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
# Each thumbnail visualizes one hidden unit's "receptive field": the
|
|
754
|
+
# 16×16 slice of W1 weighting how every input pixel feeds into that
|
|
755
|
+
# unit. Orange cells are pixels that push the unit's activation up
|
|
756
|
+
# when ink is present; blue cells push it down. Right after random
|
|
757
|
+
# init the thumbnails look like noise; as training progresses you'll
|
|
758
|
+
# see them resolve into shape detectors — patterns the unit has
|
|
759
|
+
# learned to fire on. This is the network's self-discovered
|
|
760
|
+
# vocabulary, the features it built without anyone telling it what
|
|
761
|
+
# to look for.
|
|
762
|
+
if rf_dirty
|
|
763
|
+
draw_receptive_fields.call
|
|
764
|
+
rf_dirty = false
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Outline thumbnails whose unit is firing on the current input, so the
|
|
768
|
+
# eye can pick out which features are matching. Stroke is drawn on the
|
|
769
|
+
# thumbnail's own edge (not padded out) so adjacent active units don't
|
|
770
|
+
# touch in the 2 px gutter between thumbnails.
|
|
771
|
+
h_max = current_h.max
|
|
772
|
+
h_max = 1.0 if h_max < 1e-6
|
|
773
|
+
if show_prediction
|
|
774
|
+
HIDDEN.times do |i|
|
|
775
|
+
a = current_h[i] / h_max
|
|
776
|
+
next if a < 0.05
|
|
777
|
+
ty = (HIDDEN_TOP + i * HIDDEN_SPACING - RF_SIZE / 2.0).round
|
|
778
|
+
canvas.stroke_rectangle(x: RF_X, y: ty, width: RF_SIZE, height: RF_SIZE,
|
|
779
|
+
stroke_width: 1, color: [0.95, 0.85, 0.40, 0.2 + a * 0.6])
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# Box the target class while training so you can see what label the
|
|
784
|
+
# network is being pushed toward this step (vs. its current guess,
|
|
785
|
+
# shown in the bars and icon tints).
|
|
786
|
+
if !state[:done] && display_class
|
|
787
|
+
cy = OUT_Y[display_class]
|
|
788
|
+
canvas.stroke_rectangle(
|
|
789
|
+
x: OUT_X - ICON_HALF - TARGET_PAD,
|
|
790
|
+
y: cy - ICON_HALF - TARGET_PAD,
|
|
791
|
+
width: 2 * (ICON_HALF + TARGET_PAD),
|
|
792
|
+
height: 2 * (ICON_HALF + TARGET_PAD),
|
|
793
|
+
stroke_width: 2,
|
|
794
|
+
color: [0.98, 0.85, 0.30, 0.85]
|
|
795
|
+
)
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Centroid hint — once trained, mark grid center with a faint cross
|
|
799
|
+
# and the user's ink centroid with a small dot. The visible offset
|
|
800
|
+
# between them is exactly what centered_input compensates for before
|
|
801
|
+
# the network sees the drawing, which is otherwise invisible to the user.
|
|
802
|
+
if SHOW_CENTROID_HINT && state[:done]
|
|
803
|
+
cen_x = DRAW_X + (GRID_CENTER + 0.5) * DRAW_CELL
|
|
804
|
+
cen_y = DRAW_Y + (GRID_CENTER + 0.5) * DRAW_CELL
|
|
805
|
+
canvas.draw_line(x1: cen_x - 8, y1: cen_y, x2: cen_x + 8, y2: cen_y,
|
|
806
|
+
stroke_width: 1, color: [0.95, 0.85, 0.30, 0.35])
|
|
807
|
+
canvas.draw_line(x1: cen_x, y1: cen_y - 8, x2: cen_x, y2: cen_y + 8,
|
|
808
|
+
stroke_width: 1, color: [0.95, 0.85, 0.30, 0.35])
|
|
809
|
+
if forward_nz.size > 4 && (centroid = grid_centroid(user_grid))
|
|
810
|
+
cgx, cgy = centroid
|
|
811
|
+
canvas.fill_ellipse(x: DRAW_X + (cgx + 0.5) * DRAW_CELL,
|
|
812
|
+
y: DRAW_Y + (cgy + 0.5) * DRAW_CELL,
|
|
813
|
+
xradius: 3.5, yradius: 3.5,
|
|
814
|
+
color: [0.95, 0.85, 0.30, 0.9])
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
# Loss curve — fills the panel left-to-right as samples accumulate.
|
|
819
|
+
# Y is normalized to the running max so early high losses don't
|
|
820
|
+
# squash the late curve into a flat line.
|
|
821
|
+
if loss_history.size >= 2
|
|
822
|
+
max_l = loss_history.max
|
|
823
|
+
max_l = 1e-6 if max_l < 1e-6
|
|
824
|
+
step = SPARKLINE_W.to_f / (SPARKLINE_HISTORY - 1)
|
|
825
|
+
pts = loss_history.each_with_index.map do |l, i|
|
|
826
|
+
[SPARKLINE_X + i * step,
|
|
827
|
+
SPARKLINE_Y + SPARKLINE_H - 4 - (l / max_l) * (SPARKLINE_H - 8)]
|
|
828
|
+
end
|
|
829
|
+
canvas.draw_polyline(points: pts, stroke_width: 1.5, color: '#38bdf8')
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
# --- Update connection lines based on current activations & weights ---
|
|
833
|
+
# When show_prediction is false, fall back to the lines' resting style
|
|
834
|
+
# rather than letting bias-driven activations on an empty grid drive
|
|
835
|
+
# the animation.
|
|
836
|
+
if show_prediction
|
|
837
|
+
HIDDEN.times do |i|
|
|
838
|
+
a = current_h[i] / h_max
|
|
839
|
+
line = input_lines[i]
|
|
840
|
+
line.stroke_width = 0.6 + a * 3.4
|
|
841
|
+
line.color = [0.55, 0.75, 1.0, 0.10 + a * 0.55]
|
|
842
|
+
end
|
|
843
|
+
NUM_CLASSES.times do |k|
|
|
844
|
+
base = k * HIDDEN
|
|
845
|
+
HIDDEN.times do |i|
|
|
846
|
+
w = w2[base + i]
|
|
847
|
+
contribution = (w * current_h[i]).abs
|
|
848
|
+
line = output_lines[k * HIDDEN + i]
|
|
849
|
+
line.stroke_width = 0.5 + (contribution * 1.6).clamp(0, 4.5)
|
|
850
|
+
alpha = 0.10 + (contribution * 0.9).clamp(0, 0.7)
|
|
851
|
+
line.color = if w >= 0
|
|
852
|
+
[0.96, 0.60, 0.32, alpha]
|
|
853
|
+
else
|
|
854
|
+
[0.32, 0.65, 0.96, alpha]
|
|
855
|
+
end
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
else
|
|
859
|
+
# Idle pulse — input lines breathe slowly so the network reads as
|
|
860
|
+
# alive while it waits for a drawing. Output lines stay flat so the
|
|
861
|
+
# eye isn't pulled in two directions.
|
|
862
|
+
pulse = 0.5 + 0.5 * Math.sin(elapsed * IDLE_PULSE_RATE)
|
|
863
|
+
input_lines.each do |line|
|
|
864
|
+
line.stroke_width = 0.8 + 0.4 * pulse
|
|
865
|
+
line.color = [0.55, 0.7, 0.95, 0.10 + 0.10 * pulse]
|
|
866
|
+
end
|
|
867
|
+
output_lines.each do |line|
|
|
868
|
+
line.stroke_width = 1
|
|
869
|
+
line.color = [0.6, 0.7, 0.85, 0.20]
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# --- Update output panel ---
|
|
874
|
+
# Drive bars, icon tints, and the predicted-class pick from display_y
|
|
875
|
+
# rather than current_y, so everything moves at the same smoothed
|
|
876
|
+
# cadence and a momentary tie between two classes doesn't make the
|
|
877
|
+
# label hop back and forth between them.
|
|
878
|
+
predicted = 0
|
|
879
|
+
best = -1.0
|
|
880
|
+
NUM_CLASSES.times do |k|
|
|
881
|
+
p = show_prediction ? display_y[k] : 0.0
|
|
882
|
+
out_bars[k].width = (BAR_W * p).round
|
|
883
|
+
rb, gb, bb = CLASS_RGB[k]
|
|
884
|
+
rd, gd, bdd = DIM_RGB
|
|
885
|
+
out_icons[k].color = [rd + (rb - rd) * p, gd + (gb - gd) * p,
|
|
886
|
+
bdd + (bb - bdd) * p, 1.0]
|
|
887
|
+
if display_y[k] > best
|
|
888
|
+
best = display_y[k]
|
|
889
|
+
predicted = k
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# The result label sits directly under whichever icon was picked, so
|
|
894
|
+
# the eye associates the word with its shape. Hidden entirely when
|
|
895
|
+
# there's nothing to predict — a stray dash on screen reads as a
|
|
896
|
+
# leftover placeholder, not a "neutral" state.
|
|
897
|
+
if show_prediction
|
|
898
|
+
result_text.content = CLASSES[predicted].upcase
|
|
899
|
+
result_text.color = best > 0.5 ? CLASS_COLORS[predicted] : '#94a3b8'
|
|
900
|
+
result_text.x = OUT_X - result_text.width / 2
|
|
901
|
+
result_text.y = OUT_Y[predicted] + ICON_HALF + 6
|
|
902
|
+
result_text.show
|
|
903
|
+
else
|
|
904
|
+
result_text.hide
|
|
905
|
+
end
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
show
|