From 20d5b4057b44ffcf92478b2a8e9476ace2fdc0f5 Mon Sep 17 00:00:00 2001 From: Amir Rajan Date: Tue, 22 Sep 2020 06:27:46 -0500 Subject: synced with 1.22 --- docs/docs.html | 11936 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 7291 insertions(+), 4645 deletions(-) (limited to 'docs/docs.html') diff --git a/docs/docs.html b/docs/docs.html index ce8e1b6..b21caff 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -1,19 +1,20 @@ DragonRuby Game Toolkit Documentation - - +
-

Table Of Contents

+

Table Of Contents

-
+
  • Learn Ruby Optional - Beginner Ruby Primer - automation.rb
  • +
  • Learn Ruby Optional - Beginner Ruby Primer - main.rb
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - printing.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - strings.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - looping.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - functions.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - main.rb
  • +
  • Learn Ruby Optional - Intermediate Ruby Primer - repl.rb
  • +
  • Rendering Basics - Labels - main.rb
  • +
  • Rendering Basics - Lines - main.rb
  • +
  • Rendering Basics - Solids Borders - main.rb
  • +
  • Rendering Basics - Sprites - main.rb
  • +
  • Rendering Basics - Sounds - main.rb
  • +
  • Input Basics - Keyboard - main.rb
  • +
  • Input Basics - Mouse - main.rb
  • +
  • Input Basics - Mouse Point To Rect - main.rb
  • +
  • Input Basics - Mouse Rect To Rect - main.rb
  • +
  • Input Basics - Controller - main.rb
  • +
  • Rendering Sprites - Animation Using Separate Pngs - main.rb
  • +
  • Rendering Sprites - Animation Using Sprite Sheet - main.rb
  • +
  • Rendering Sprites - Animation States - main.rb
  • +
  • Rendering Sprites - Color And Rotation - main.rb
  • +
  • Physics And Collisions - Simple - main.rb
  • +
  • Physics And Collisions - Moving Objects - main.rb
  • +
  • Physics And Collisions - Entities - main.rb
  • +
  • Physics And Collisions - Box Collision - main.rb
  • +
  • Physics And Collisions - Box Collision 2 - main.rb
  • +
  • Physics And Collisions - Jump Physics - main.rb
  • +
  • Mouse - Mouse Click - main.rb
  • +
  • Mouse - Mouse Move - main.rb
  • +
  • Mouse - Mouse Move Paint App - main.rb
  • +
  • Mouse - Coordinate Systems - main.rb
  • +
  • Save Load - Save Load Game - main.rb
  • +
  • Advanced Rendering - Simple Render Targets - main.rb
  • +
  • Advanced Rendering - Render Targets With Alphas - main.rb
  • +
  • Advanced Rendering - Render Target Viewports - main.rb
  • +
  • Advanced Rendering - Render Primitive Hierarchies - main.rb
  • +
  • Advanced Rendering - Render Primitives As Hash - main.rb
  • +
  • Tweening Lerping Easing Functions - Easing Functions - main.rb
  • +
  • Tweening Lerping Easing Functions - Cubic Bezier - main.rb
  • +
  • Tweening Lerping Easing Functions - Easing Using Spline - main.rb
  • +
  • Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb
  • +
  • Performance - Sprites As Hash - main.rb
  • +
  • Performance - Sprites As Entities - main.rb
  • +
  • Performance - Sprites As Strict Entities - main.rb
  • +
  • Performance - Sprites As Classes - main.rb
  • +
  • Performance - Static Sprites As Classes - main.rb
  • +
  • Performance - Static Sprites As Classes With Custom Drawing - main.rb
  • +
  • Performance - Collision Limits - main.rb
  • +
  • Advanced Debugging - Trace Debugging - main.rb
  • +
  • Advanced Debugging - Trace Debugging Classes - main.rb
  • +
  • Advanced Debugging - Unit Tests - exception_raising_tests.rb
  • +
  • Advanced Debugging - Unit Tests - gen_docs.rb
  • +
  • Advanced Debugging - Unit Tests - geometry_tests.rb
  • +
  • Advanced Debugging - Unit Tests - http_tests.rb
  • +
  • Advanced Debugging - Unit Tests - object_to_primitive_tests.rb
  • +
  • Advanced Debugging - Unit Tests - parsing_tests.rb
  • +
  • Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb
  • +
  • Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb
  • +
  • Http - Retrieve Images - main.rb
  • +
  • 12 C Extensions - Basics - main.rb
  • +
  • 3d - 3d Cube - main.rb
  • +
  • Arcade - Dueling Starships - main.rb
  • +
  • arcade/flappy dragon/credits.txt
  • +
  • arcade/flappy dragon/main.rb
  • +
  • Arcade - Pong - main.rb
  • +
  • Arcade - Snakemoji - main.rb
  • +
  • Arcade - Solar System - main.rb
  • +
  • Crafting - Craft Game Starting Point - main.rb
  • +
  • Dev Tools - Add Buttons To Console - main.rb
  • +
  • Dev Tools - Animation Creator Starting Point - main.rb
  • +
  • Dev Tools - Tile Editor Starting Point - main.rb
  • +
  • Lowrez - Resolution 64x64 - lowrez.rb
  • +
  • Lowrez - Resolution 64x64 - main.rb
  • +
  • Platformer - Clepto Frog - main.rb
  • +
  • Platformer - Clepto Frog - map.rb
  • +
  • Platformer - Gorillas Basic - credits.txt
  • +
  • Platformer - Gorillas Basic - main.rb
  • +
  • Platformer - Gorillas Basic - repl.rb
  • +
  • Platformer - Gorillas Basic - tests.rb
  • +
  • Platformer - Gorillas Basic - Tests - building_generation_tests.rb
  • +
  • Platformer - The Little Probe - main.rb
  • +
  • Platformer - The Little Probe - Data - level.txt
  • +
  • Platformer - The Little Probe - Data - level_lava.txt
  • +
  • Rpg Narrative - Choose Your Own Adventure - decision.rb
  • +
  • Rpg Narrative - Choose Your Own Adventure - main.rb
  • +
  • Rpg Narrative - Return Of Serenity - lowrez_simulator.rb
  • +
  • Rpg Narrative - Return Of Serenity - main.rb
  • +
  • Rpg Narrative - Return Of Serenity - repl.rb
  • +
  • Rpg Narrative - Return Of Serenity - require.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_anka.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_day_one.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_final_decision.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_final_message.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb
  • +
  • Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb
  • +
  • Rpg Roguelike - Roguelike Line Of Sight - constants.rb
  • +
  • Rpg Roguelike - Roguelike Line Of Sight - legend.rb
  • +
  • Rpg Roguelike - Roguelike Line Of Sight - main.rb
  • +
  • Rpg Roguelike - Roguelike Line Of Sight - sprite_lookup.rb
  • +
  • Rpg Roguelike - Roguelike Starting Point - main.rb
  • +
  • Rpg Tactical - Hexagonal Grid - main.rb
  • +
  • Rpg Tactical - Isometric Grid - main.rb
  • +
  • Rpg Topdown - Topdown Starting Point - main.rb
  • +
  • args.rb
  • +
  • assert.rb
  • +
  • attr_gtk.rb
  • +
  • attr_sprite.rb
  • +
  • console.rb
  • +
  • console_color.rb
  • +
  • console_font_style.rb
  • +
  • console_menu.rb
  • +
  • console_prompt.rb
  • +
  • controller.rb
  • +
  • controller/config.rb
  • +
  • controller/keys.rb
  • +
  • directional_input_helper_methods.rb
  • +
  • easing.rb
  • +
  • geometry.rb
  • +
  • grid.rb
  • +
  • inputs.rb
  • +
  • log.rb
  • +
  • numeric.rb
  • +
  • runtime/framerate_diagnostics.rb
  • +
  • string.rb
  • +
  • tests.rb
  • +
  • trace.rb
  • +
    -

    DragonRuby Game Toolkit Live Docs

    +

    DragonRuby Game Toolkit Live Docs

    The information contained here is all available via the DragonRuby Console. You can Open the DragonRuby Console by pressing [`] [~] [²] [^] [º] or [§] within your game.

    @@ -416,76 +432,41 @@ Note that you can also run DragonRuby without X11 at all: if you run it from a v

    There is a lot more you can do with DragonRuby, but now you've already got just about everything you need to make a simple game. After all, even the most fancy games are just creating objects and moving them around. Experiment a little. Add a few more things and have them interact in small ways. Want something to go away? Just don't add it to args.output anymore.

    -

    IMPORTANT: Go Through All Of The Sample Apps! Study Them Thoroughly!!

    +

    IMPORTANT: Go through all of the sample apps! Study them thoroughly!! No really, you should definitely do this!

    Now that you've completed the Hello World tutorial. Head over to the `samples` directory. It is very very important that you study the sample apps thoroughly! Go through them in order. Here is a short description of each sample app.

    +

    Guided Samples

    +
      +
    1. samples/00_learn_ruby_optional: This directory contains sample apps that will help you learn the language.
    2. +
    3. samples/01_rendering_basics: This set of samples will show you how to render basic primitives such as labels, solids, borders, lines, sprites, and how to play sounds.
    4. +
    5. samples/02_input_basics: This set of samples show you how to accept input from the mouse, keyboard, and controllers.
    6. +
    7. samples/03_rendering_sprites: This set of samples shows you all the different ways to render sprites (including how to use a sprite sheet).
    8. +
    9. samples/04_physics_and_collision: This set of samples shows how to do various types of collisions and physics.
    10. +
    11. samples/05_mouse: This set of samples show more advanced usages of the mouse.
    12. +
    13. samples/06_save_load: This set of samples show how to save and load game data.
    14. +
    15. samples/07_advanced_rendering: This set of samples show how to programmatically render sprites using render targets.
    16. +
    17. samples/08_tweening_lerping_easing_functions: This set of samples show how to perform animations.
    18. +
    19. samples/09_performance: This set of samples show how to handle performance issues when a large number of sprites on the screen.
    20. +
    21. samples/10_advanced_debugging: This set of samples show how advanced debugging techniques and testing techniques.
    22. +
    23. samples/11_http: This set of samples show how use http.
    24. +
    +

    Sample Games

    +

    +There are samples that contain the path samples/99_*. The sample apps that are prefixed with 99_ show non-trivial implemementations for a real game: +

      -
    1. 00_beginner_ruby_primer: This is an interactive tutorial that shows how to render solids, animated sprites, labels.
    2. -
    3. 00_intermediate_ruby_primer: This is a set of sample Ruby snippets that give you a high level introduction to the programming language.
    4. -
    5. 01_api_01_labels: Various ways to render labels.
    6. -
    7. 01_api_02_lines: Various ways to render lines.
    8. -
    9. 01_api_03_rects: Sample app shows various ways to render solids and borders.
    10. -
    11. 01_api_04_sprites: Sample app shows various ways to render sprites.
    12. -
    13. 01_api_05_keyboard: Hows how to get keyboard input from the user.
    14. -
    15. 01_api_06_mouse: Hows how to get mouse mouse position.
    16. -
    17. 01_api_07_point_to_rect: How to get mouse input from the user and shows collision/hit detection.
    18. -
    19. 01_api_08_rect_to_rect: Hit detection/collision between two rectangles.
    20. -
    21. 01_api_10_controller: Interaction with a USB/Bluetooth controller.
    22. -
    23. 01_api_99_tech_demo: All the different render primitives along with using render_targets.
    24. -
    25. 02_collision_01_simple: Collision detection with dynamically moving bodies.
    26. -
    27. 02_collision_02_moving_objects: Collision detection between many primitives, simple platformer physics, and keyboard input.
    28. -
    29. 02_collision_03_entities: Collision with entities and serves as a small introduction to ECS (entity component system).
    30. -
    31. 02_collision_04_ramp_with_debugging: How ramp trajectory can be calculated.
    32. -
    33. 02_collision_05_ramp_with_debugging_two: How ramp trajectory can be calculated.
    34. -
    35. 02_sprite_animation_and_keyboard_input: How to animate a sprite based off of keyboard input.
    36. -
    37. 03_mouse_click: How to determine what direction/vector a mouse was clicked relative to a player.
    38. -
    39. 04_sounds: How to play sounds and work with buttons.
    40. -
    41. 05_mouse_move: How to determine what direction/vector a mouse was clicked relative to a player.
    42. -
    43. 05_mouse_move_paint_app: Represents a simple paint app.
    44. -
    45. 05_mouse_move_tile_editor: A starting point for a tile editor.
    46. -
    47. 06_coordinate_systems: Shows the two origin systems within Game Toolkit where the origin is in the center and where the origin is at the bottom left.
    48. -
    49. 07_render_targets: Shows a powerful concept called render_targets. You can use this to programatically create sprites (it's also useful for representing parts of a scene as if it was a view port/camera).
    50. -
    51. 07_render_targets_advanced: Advanced usage of render_targets.
    52. -
    53. 08_platformer_collisions: Axis aligned collision along with platformer physics.
    54. -
    55. 08_platformer_collisions_metroidvania: How to save map data and place sprites live within a game.
    56. -
    57. 08_platformer_jumping_inertia: Jump physics and how inertia affects collision.
    58. -
    59. 09_controller_analog_usage_advanced_sprites: Extended properties of a sprite and how to change the rotation anchor point and render a subset/tile of a sprite.
    60. -
    61. 09_sprite_animation_using_tile_sheet: How to perform sprite animates using a tile sheet.
    62. -
    63. 10_save_load_game: Save and load game data.
    64. -
    65. 11_coersion_of_primitives: How primitives of one specific type can be rendered as another primitive type.
    66. -
    67. 11_hash_primitives: How primitives can be represented using a Hash.
    68. -
    69. 12_controller_input_sprite_sheet_animations: How to leverage vectors to move a player around the screen.
    70. -
    71. 12_top_down_area: How to render a top down map and how to manage collision of a player.
    72. -
    73. 13_01_easing_functions: How to use lerping functions to define animations/movement.
    74. -
    75. 13_02_cubic_bezier: How to create a bezier curve using lines.
    76. -
    77. 13_03_easing_using_spline: How a collection of bezier curves can be used to define an animation.
    78. -
    79. 13_04_parametric_enemy_movement: How to define the movement of enemies and projectiles using lerping/parametric functions.
    80. -
    81. 14_sprite_limits: Upper limit for how many sprites can be rendered to the screen.
    82. -
    83. 14_sprite_limits_static_references: Upper limit for how many sprites can be rendered to the screen using static output collections (which are updated by reference as opposed to by value).
    84. -
    85. 15_collision_limits: How many collisions can be processed across many primitives.
    86. -
    87. 18_moddable_game: How you can make a game where content is authored by the player (modding support).
    88. -
    89. 19_lowrez_jam: How to use render_targets to create a low resolution game.
    90. -
    91. 20_roguelike_starting_point: A starting point for a roguelike and explores concepts such as line of sight.
    92. -
    93. 20_roguelike_starting_point_two: A starting point for a roguelike where sprites are provided from a tile map/tile sheet.
    94. -
    95. 21_mailbox_usage: How to do interprocess communication.
    96. -
    97. 22_trace_debugging: Debugging techniques and tracing execution through your game.
    98. -
    99. 22_trace_debugging_classes: Debugging techniques and tracing execution through your game.
    100. -
    101. 23_hexagonal_grid: How to make a tactical grid/map made of hexagons.
    102. -
    103. 23_isometric_grid: How to make a tactical grid/map made of isometric sprites.
    104. -
    105. 24_http_example: How to make http requests.
    106. -
    107. 25_3d_experiment_01_square: How to create 3D objects.
    108. -
    109. 26_jam_craft: Starting point for crafting game. It also shows how to customize the mouse cursor.
    110. -
    111. 99_sample_game_basic_gorillas: Reference implementation of a full game. Topics covered: physics, keyboard input, collision, sprite animation.
    112. -
    113. 99_sample_game_clepto_frog: Reference implementation of a full game. Topics covered: camera control, spring/rope physics, scene orchestration.
    114. -
    115. 99_sample_game_dueling_starships: Reference implementation that shows local multiplayer. Topics covered: vectors, particles, friction, inertia.
    116. -
    117. 99_sample_game_flappy_dragon: Reference implementation that is a clone of Flappy Bird. Topics covered: scene orchestration, collision, sound, sprite animations, lerping.
    118. -
    119. 99_sample_game_pong: Reference implementation of pong.
    120. -
    121. 99_sample_game_return_of_serenity: Reference implementation of low resolution story based game.
    122. -
    123. 99_sample_game_the_little_probe: Reference implementation of a full game. Topics covered: Arbitrary collision detection, loading map data, bounce/ball physics.
    124. -
    125. 99_sample_nddnug_workshop: Reference implementation of a full game. Topics covered: vectors, controller input, sound, trig functions.
    126. -
    127. 99_sample_snakemoji: Shows that Ruby supports coding with emojis.
    128. -
    129. 99_zz_gtk_unit_tests: A collection of unit tests that exercise parts of DragonRuby's API.
    130. +
    131. 3D Cube: Shows how to do faux 3D in DragonRuby.
    132. +
    133. Dueling Starships: A two player top-down versus game where each player controls a ship.
    134. +
    135. Flappy Dragon: DragonRuby's clone of Flappy Bird.
    136. +
    137. Pong: A simple implementation of the game Pong.
    138. +
    139. Snakemoji: The classic game of Snake but with all of the code written using emojis (sometimes you just have to have a little fun).
    140. +
    141. Solar System: A simulation of our solar system.
    142. +
    143. Crafting Starting Point: A starting point for those that want to build a crafting game.
    144. +
    145. Dev Tools: A set of sample apps that show how you can extend DragonRuby's Console, starting point for a tile editor, and a starting point for a paint app.
    146. +
    147. LOWREZ: Sample apps that show how to render at different resolutions.
    148. +
    149. RPG: Various sample apps that show how to create narrative, topdown, tactical grid-based, and roguelike RPGs.
    150. +
    151. Platformers: Various sample apps that show how to create different kinds of physics/collision based platformers.

    Deploying To Itch.io

    @@ -513,11 +494,11 @@ Point your text editor at mygame/metadata/game_metadata.txt and make it look lik

    NOTE: Remove the # at the beginning of each line.

    -
    vid=bob
    -vtitle=Bob The Game Developer
    -meid=mygame
    -metitle=My Game
    -rsion=0.1
    +
    devid=bob
    +devtitle=Bob The Game Developer
    +gameid=mygame
    +gametitle=My Game
    +version=0.1
     

    The devid property is the username you use to log into Itch.io. The devtitle is your name or company name (it can contain spaces). The gameid is the Project URL value. The gametitle is the name of your game (it can contain spaces). The version can be any major.minor number format. @@ -526,7 +507,7 @@ The devid property is the username you use to log into Itch.io. The

    Open up the terminal and run this from the command line:

    -
    dragonruby-publish --only-package mygame
    +
    ./dragonruby-publish --only-package mygame
     

    (if you're on Windows, don't put the "./" on the front. That's a Mac and Linux thing.) @@ -540,7 +521,7 @@ For the HTML version of your game after you upload it. Check the checkbox labele

    For subsequent updates you can use an automated deployment to Itch.io:

    -
    dragonruby-publish mygame
    +
    ./dragonruby-publish mygame
     

    DragonRuby will package _and publish_ your game to itch.io! Tell your friends to go to your game's very own webpage and buy it! @@ -548,11 +529,11 @@ DragonRuby will package _and publish_ your game to itch.io! Tell your friends to

    If you make changes to your game, just re-run dragonruby-publish and it'll update the downloads for you.

    -

    DragonRuby's Philosophy

    +

    DragonRuby's Philosophy

    The following tenants of DragonRuby are what set us apart from other game engines. Given that Game Toolkit is a relatively new engine, there are definitely features that are missing. So having a big check list of "all the cool things" is not this engine's forte. This is compensated with a strong commitment to the following principals.

    -

    Challenge The Status Quo

    +

    Challenge The Status Quo

    Game engines of today are in a local maximum and don't take into consideration the challenges of this day and age. Unity and GameMaker specifically rot your brain. It's not sufficient to say:

    @@ -564,14 +545,14 @@ But that's how we've always done it.

    It's a hard pill to swallow, but forget blindly accepted best practices and try to figure out the underlying motivation for a specific approach to game development. Collaborate with us.

    -

    Continuity of Design

    +

    Continuity of Design

    There is a programming idiom in software called "the pit of success". The term normalizes up front pain as a necessity in the (hopes that the investment will yield dividends "when you become successful"). This results in more "Enterprise TM" code upfront, and makes it more difficult to get started when you are new to programming.

    DragonRuby's philosophy is to provide a spectrum across the "make it fast" vs "make it right" spectrum and provide incremental, intuitive transitions between points on that spectrum. This is captured in how render primitives can be represented as tuples/arrays, hashes, open structs/entities, and then finally classes (as opposed to forcing devs to use classes upfront).

    -

    Release Often And Soon

    +

    Release Often And Soon

    The biggest mistake game devs make is spending too much time in isolation building their game. Release something, however small, and release it quickly.

    @@ -586,29 +567,29 @@ Remember: Real artists ship.

    -

    Sustainable And Ethical Monetization

    +

    Sustainable And Ethical Monetization

    We all aspire to put food on the table doing what we love. Whether it is building games, writing tools to support game development, or anything in between.

    Charge a fair amount of money for the things you create. It's expected and encouraged within the community. Give what you create away for free to those that can't afford it.

    -

    Sustainable And Ethical Open Source

    +

    Sustainable And Ethical Open Source

    This goes hand in hand with sustainable and ethical monetization. The current state of open source is not sustainable. There is an immense amount of contributor burnout. Users of open source expect everything to be free, and few give back. This is a problem we want to fix (we're still trying to figure out the best solution).

    So, don't be "that guy" in the Discord that says "DragonRuby should be free and open source!" You will be personally flogged by Amir.

    -

    People Over Entities

    +

    People Over Entities

    We prioritize the endorsement of real people over faceless entities. This game engine, and other products we create, are not insignificant line items of a large company. And you aren't a generic "commodity" or "corporate resource". So be active in the community Discord and you'll reap the benefits as more devs use DragonRuby.

    -

    Building A Game Should Be Fun And Bring Happiness

    +

    Building A Game Should Be Fun And Bring Happiness

    We will prioritize the removal of pain. The aesthetics of Ruby make it such a joy to work with, and we want to capture that within the engine.

    -

    Real World Application Drives Features

    +

    Real World Application Drives Features

    We are bombarded by marketing speak day in and day out. We don't do that here. There are things that are really great in the engine, and things that need a lot of work. Collaborate with us so we can help you reach your goals. Ask for features you actually need as opposed to anything speculative.

    @@ -903,11 +884,9 @@ Under DragonRuby LLP, we offer a number of products (with more on the way):

    • Game Toolkit (GTK): A 2D game engine that is compatible with modern - gaming platforms. [Home Page]() [FAQ Page]()
    • + gaming platforms.
    • RubyMotion (RM): A compiler toolchain that allows you to build native, cross-platform mobile - apps. [Home Page]() [FAQ Page]()
    • -
    • Commandline Toolkit (CTK): A zero dependency, zero installation Ruby - environment that works on Windows, Mac, and Linux. [Home Page]() [FAQ Page]()
    • + apps. http://rubymotion.com

    All of the products above leverage a shared core called DragonRuby. @@ -925,25 +904,25 @@ NOTE: Devs who use DragonRuby are "Dragon Riders/Riders of Dragons". That's a ba

    The response to this question requires a few subparts. First we need to clarify some terms. Specifically _language specification_ vs _runtime_.

    -

    Okay... so what is the difference between a language specification and a runtime?

    +

    Okay... so what is the difference between a language specification and a runtime?

    A runtime is an _implementation_ of a language specification. When people say "Ruby," they are usually referring to "the Ruby 3.0+ language specification implemented via the CRuby/MRI Runtime."

    But, there are many Ruby Runtimes: CRuby/MRI, JRuby, Truffle, Rubinius, Artichoke, and (last but certainly not least) DragonRuby.

    -

    Okay... what language specification does DragonRuby use then?

    +

    Okay... what language specification does DragonRuby use then?

    DragonRuby's goal is to be compliant with the ISO/IEC 30170:2012 standard. It's syntax is Ruby 2.x compatible, but also contains semantic changes that help it natively interface with platform specific libraries.

    -

    So... why another runtime?

    +

    So... why another runtime?

    The elevator pitch is:

    DragonRuby is a Multilevel Cross-platform Runtime. The "multiple levels" within the runtime allows us to target platforms no other Ruby can target: PC, Mac, Linux, Raspberry Pi, WASM, iOS, Android, Nintendo Switch, PS4, Xbox, and Scadia.

    -

    What does Multilevel Cross-platform mean?

    +

    What does Multilevel Cross-platform mean?

    There are complexities associated with targeting all the platforms we support. Because of this, the runtime had to be architected in such a way that new platforms could be easily added (which lead to us partitioning the runtime internally):

    @@ -968,11 +947,71 @@ Levels 1 through 3 are fairly commonplace in many runtime implementations (with

    These levels allow us to stay up to date with open source implementations of Ruby; provide fast, native code execution on proprietary platforms; ensure good separation between these two worlds; and provides a means to add new platforms without going insane.

    -

    Cool cool. So given that I understand everything to this point, can we answer the original question? What is DragonRuby?

    +

    Cool cool. So given that I understand everything to this point, can we answer the original question? What is DragonRuby?

    DragonRuby is a Ruby runtime implementation that takes all the lessons we've learned from MRI/CRuby, and merges it with the latest and greatest compiler and OSS technologies.

    -

    Frequent Comments

    +

    How is DragonRuby different than MRI?

    +

    +DragonRuby supports a subset of MRI apis. Our target is to support all of mRuby's standard lib. There are challenges to this given the number of platforms we are trying to support (specifically console). +

    +

    Does DragonRuby support Gems?

    +

    +DragonRuby does not support gems because that requires the installation of MRI Ruby on the developer's machine (which is a non-starter given that we want DragonRuby to be a zero dependency runtime). While this seems easy for Mac and Linux, it is much harder on Windows and Raspberry Pi. mRuby has taken the approach of having a git repository for compatible gems and we will most likely follow suite: https://github.com/mruby/mgem-list. +

    +

    Does DragonRuby have a REPL/IRB?

    +

    +You can use DragonRuby's Console within the game to inspect object and execute small pieces of code. For more complex pieces of code create a file called repl.rb and put it in mygame/app/repl.rb: +

    +
      +
    • Any code you write in there will be executed when you change the file. You can organize different pieces of code using the repl method:
    • +
    +
    repl do
    +  puts "hello world"
    +  puts 1 + 1
    +end
    +
    +
      +
    • If you use the `repl` method, the code will be executed and the DragonRuby Console will automatically open so you can see the results (on Mac and Linux, the results will also be printed to the terminal).
    • +
    • All puts statements will also be saved to logs/log.txt. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can tail this file.
    • +
    +

    +4. To ignore code in repl.rb, instead of commenting it out, prefix repl with the letter x and it'll be ignored. +

    +
    xrepl do # <------- line is prefixed with an "x"
    +  puts "hello world"
    +  puts 1 + 1
    +end
    +
    +# This code will be executed when you save the file.
    +repl do
    +  puts "Hello"
    +end
    +
    +repl do
    +  puts "This code will also be executed."
    +end
    +
    +# use xrepl to "comment out" code
    +xrepl do
    +  puts "This code will not be executed because of the x infront of repl".
    +end
    +
    +

    Does DragonRuby support pry or have any other debugging facilities?

    +

    +pry is a gem that assumes you are using the MRI Runtime (which is incompatible with DragonRuby). Eventually DragonRuby will have a pry based experience that is compatible with a debugging infrastructure called LLDB. Take the time to read about LLDB as it shows the challenges in creating something that is compatible. +

    +

    +You can use DragonRuby's replay capabilities to troubleshoot: +

    +
      +
    1. DragonRuby is hot loaded which gives you a very fast feedback loop (if the game throws an exception, it's because of the code you just added).
    2. +
    3. Use ./dragonruby mygame --record to create a game play recording that you can use to find the exception (you can replay a recoding by executing ./dragonruby mygame --replay last_replay.txt or through the DragonRuby Console using $gtk.recording.start_replay "last_replay.txt".
    4. +
    5. DragonRuby also ships with a unit testing facility. You can invoke the following command to run a test: ./dragonruby . --eval some_ruby_file.rb --no-tick.
    6. +
    7. Get into the habit of adding debugging facilities within the game itself. You can add drawing primitives to args.outputs.debug that will render on top of your game but will be ignored in a production release.
    8. +
    9. Debugging something that runs at 60fps is (imo) not that helpful. The exception you are seeing could have been because of a change that occurred many frames ago.
    10. +
    +

    Frequent Comments About Ruby as a Language Choice

    But Ruby is dead.

    Let's check the official source for the answer to this question: isrubydead.com: https://isrubydead.com/. @@ -1014,6 +1053,9 @@ If you have ideas on how we can do this, email us!

    If the reason above isn't sufficient, then definitely use something else.

    +

    +All this being said, we do have parts of the engine open sourced on GitHub: https://github.com/dragonruby/dragonruby-game-toolkit-contrib/ +

    DragonRuby is for pay. You should offer a free version.

    If you can afford to pay for DragonRuby, you should (and will). We don't go around telling writers that they should give us their books for free, and only require payment if we read the entire thing. It's time we stop asking that of software products. @@ -1057,11 +1099,15 @@ That won't happen if the development world stop asking for free stuff and non-tr

    But, in the event that sad day comes, our partnership bylaws state that _all_ DragonRuby IP that can be legally open sourced, will be released under a permissive license.

    -

    GTK::Runtime

    +

    DOCS: GTK::Runtime

    The GTK::Runtime class is the core of DragonRuby. It is globally accessible via $gtk.

    -

    GTK::Runtime#calcstringbox

    +

    DOCS: GTK::Runtime#reset

    +

    +This function will reset Kernel.tick_count to 0 and will remove all data from args.state. +

    +

    DOCS: GTK::Runtime#calcstringbox

    This function returns the width and height of a string.

    @@ -1070,15 +1116,21 @@ This function returns the width and height of a string. args.state.string_size_font_size ||= args.gtk.calcstringbox "Hello World" end
    -

    GTK::Runtime#reset

    +

    DOCS: GTK::Runtime#write_file

    -This function will reset Kernel.tick_count to 0 and will remove all data from args.state. +This function takes in two parameters. The first paramter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method overwrites whatever is currently in the file. Use GTK::Runtime#append_file to append to the file as opposed to overwriting.

    -

    Array

    +
    def tick args
    +  if args.inputs.mouse.click
    +    args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
    +  end
    +end
    +
    +

    DOCS: Array

    The Array class has been extend to provide methods that will help in common game development tasks. Array is one of the most powerful classes in Ruby and a very fundamental component of Game Toolkit.

    -

    Array#map

    +

    DOCS: Array#map

    The function given a block returns a new Enumerable of values.

    @@ -1116,7 +1168,7 @@ Example of using Array#map in conjunction with args.state -

    Array#each

    +

    DOCS: Array#each

    The function, given a block, invokes the block for each item in the Array. Array#each is synonymous to foreach constructs in other languages.

    @@ -1153,7 +1205,7 @@ Example of using Array#each in conjunction with args.state -

    Array#reject_nil

    +

    DOCS: Array#reject_nil

    Returns an Enumerable rejecting items that are nil, this is an alias for Array#compact:

    @@ -1165,7 +1217,7 @@ Returns an Enumerable rejecting items that are nil, th # => [1, 4, false, :a] end
    -

    Array#reject_false

    +

    DOCS: Array#reject_false

    Returns an `Enumerable` rejecting items that are `nil` or `false`.

    @@ -1175,7 +1227,7 @@ Returns an `Enumerable` rejecting items that are `nil` or `false`. # => [1, 4, :a] end
    -

    Array#product

    +

    DOCS: Array#product

    Returns all combinations of values between two arrays.

    @@ -1195,7 +1247,7 @@ end # => [[0, :a], [0, :b], [1, :a], [1, :b]] end -

    Array#map_2d

    +

    DOCS: Array#map_2d

    Assuming the array is an array of arrays, Given a block, each 2D array index invoked against the block. A 2D array is a common way to store data/layout for a stage.

    @@ -1221,11 +1273,11 @@ Assuming the array is an array of arrays, Given a block, each 2D array index inv puts occupied_tiles end -

    Array#include_any?

    +

    DOCS: Array#include_any?

    Given a collection of items, the function will return true if any of self's items exists in the collection of items passed in:

    -

    Array#any_intersect_rect?

    +

    DOCS: Array#any_intersect_rect?

    Assuming the array contains objects that respond to left, right, top, bottom, this method returns true if any of the elements within the array intersect the object being passed in. You are given an optional parameter called tolerance which informs how close to the other rectangles the elements need to be for it to be considered intersecting.

    @@ -1287,7 +1339,7 @@ The default tolerance is set to 0.1, which means that the primitive puts "" end -

    GTK::Outputs

    +

    DOCS: GTK::Outputs

    Outputs is how you render primitives to the screen. The minimal setup for rendering something to the screen is via a tick method defined in mygame/app/main.rb

    @@ -1295,7 +1347,7 @@ Outputs is how you render primitives to the screen. The minimal setup for render # code goes here end -

    GTK::Outputs#solids

    +

    DOCS: GTK::Outputs#solids

    Add primitives to this collection to render a solid to the screen.

    @@ -1369,7 +1421,7 @@ def tick args args.outputs.solids << Square.new(10, 10, 32) end -

    GTK::Outputs#borders

    +

    DOCS: GTK::Outputs#borders

    Add primitives to this collection to render an unfilled solid to the screen. Take a look at the documentation for Outputs#solids.

    @@ -1392,7 +1444,7 @@ You have to use args.outputs.borders: args.outputs.borders << [100, 100, 160, 90] end -

    GTK::Mouse

    +

    DOCS: GTK::Mouse

    The mouse is accessible via args.inputs.mouse:

    @@ -1425,7 +1477,7 @@ The mouse has the following properties.
  • args.inputs.mouse.button_middle: Returns true if the middle mouse button is down.
  • args.inputs.mouse.button_bits: Gives the bits for each mouse button and its current state.
  • -

    GTK::MousePoint

    +

    DOCS: GTK::MousePoint

    The GTK::MousePoint has the following properties.

    @@ -1442,7 +1494,7 @@ The GTK::MousePoint has the following properties.
  • created_at: The tick (args.state.tick_count) that this structure was created.
  • global_created_at: The global tick (Kernel.global_tick_count) that this structure was created.
  • -

    GTK::OpenEntity

    +

    DOCS: GTK::OpenEntity

    GTK::OpenEntity is accessible within the DragonRuby's top level tick function via the args.state property.

    @@ -1468,7 +1520,7 @@ For example: ] end -

    GTK::OpenEntity#as_hash

    +

    DOCS: GTK::OpenEntity#as_hash

    Returns a reference to the GTK::OpenEntity as a Hash. This property is useful when you want to treat args.state as a Hash and invoke methods such as Hash#each.

    @@ -1491,7 +1543,7 @@ Example: end end -

    Numeric#frame_index

    +

    DOCS: Numeric#frame_index

    This function is helpful for determining the index of frame-by-frame sprite animation. The numeric value self represents the moment the animation started.

    @@ -1554,7 +1606,7 @@ Example using named parameters: ] end -

    Numeric#elapsed_time

    +

    DOCS: Numeric#elapsed_time

    For a given number, the elapsed frames since that number is returned. `Kernel.tick_count` is used to determine how many frames have elapsed. An optional numeric argument can be passed in which will be used instead of `Kernel.tick_count`.

    @@ -1595,7 +1647,7 @@ And here is an example where the override parameter is passed in: end end -

    Numeric#elapsed?

    +

    DOCS: Numeric#elapsed?

    Returns true if Numeric#elapsed_time is greater than the number. An optional parameter can be passed into elapsed? which is added to the number before evaluating whether elapsed? is true.

    @@ -1659,7 +1711,7 @@ Example usage (with optional parameter): args.state.box_queue -= boxes_to_destroy end -

    Numeric#created?

    +

    DOCS: Numeric#created?

    Returns true if Numeric#elapsed_time == 0. Essentially communicating that number is equal to the current frame.

    @@ -1683,15 +1735,15 @@ Example usage: args.state.box_queue -= boxes_to_spawn_this_frame end -

    Kernel

    +

    DOCS: Kernel

    Kernel in the DragonRuby Runtime has patches for how standard out is handled and also contains a unit of time in games called a tick.

    -

    Kernel::tick_count

    +

    DOCS: Kernel::tick_count

    Returns the current tick of the game. This value is reset if you call $gtk.reset.

    -

    Kernel::global_tick_count

    +

    DOCS: Kernel::global_tick_count

    Returns the current tick of the application from the point it was started. This value is never reset.

    @@ -1699,8 +1751,9 @@ Returns the current tick of the application from the point it was started. This

    Follows is a source code listing for all files that have been open sourced. This code can be found in the ./samples directory and online at https://github.com/DragonRuby/dragonruby-game-toolkit-contrib/.

    -

    00_learn_ruby_optional/00_beginner_ruby_primer/app/automation.rb

    -
    # ==========================================================================
    +

    Learn Ruby Optional - Beginner Ruby Primer - automation.rb

    +
    # ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/automation.rb
    +# ==========================================================================
     #  _    _ ________     __  _      _____  _____ _______ ______ _   _ _ _ _ _
     # | |  | |  ____\ \   / / | |    |_   _|/ ____|__   __|  ____| \ | | | | | |
     # | |__| | |__   \ \_/ /  | |      | | | (___    | |  | |__  |  \| | | | | |
    @@ -1822,8 +1875,9 @@ $gtk.schedule_callback 400 do
     end
     
     
    -

    00_learn_ruby_optional/00_beginner_ruby_primer/app/main.rb

    -
    # ==========================================================================
    +

    Learn Ruby Optional - Beginner Ruby Primer - main.rb

    +
    # ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/main.rb
    +# ==========================================================================
     #  _    _ ________     __  _      _____  _____ _______ ______ _   _ _ _ _ _
     # | |  | |  ____\ \   / / | |    |_   _|/ ____|__   __|  ____| \ | | | | | |
     # | |__| | |__   \ \_/ /  | |      | | | (___    | |  | |__  |  \| | | | | |
    @@ -2142,17 +2196,599 @@ def outputs
     end
     
     
    -

    00_learn_ruby_optional/00_intermediate_ruby_primer/app/main.rb

    -
    def tick args
    +

    Learn Ruby Optional - Intermediate Ruby Primer - printing.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/01_printing.txt
    +# ====================================================================================
    +# Commenting Code
    +# ====================================================================================
    +#
    +# Prefixing text with a pound sign (#) is how you comment code in Ruby. Example:
    +#
    +# I am commented code. And so are the lines above.
    +#
    +# I you want more than a quick primer on Ruby, check out https://poignant.guide/. It's
    +# an entertaining read. Otherwise, go to the next txt file.
    +#
    +# Follow along by visiting:
    +# https://s3.amazonaws.com/s3.dragonruby.org/dragonruby-gtk-intermediate.mp4
    +
    +# ====================================================================================
    +#  Printing to the Console:
    +# ====================================================================================
    +#
    +# Every time you save repl.rb file, DragonRuby runs the code within it. Copy this text
    +# to repl.rb and save to see Hello World printed to the console.
    +
    +repl do
    +  puts '* RUBY PRIMER: Printing to the console using the ~puts~ function.'
    +  puts '===='
    +  puts '======'
    +  puts '================================'
    +  puts 'Hello World'
    +  puts '================================'
    +  puts '======'
    +  puts '===='
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - strings.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/02_strings.txt
    +# ====================================================================================
    +#  Strings
    +# ====================================================================================
    +#
    +# Here is how you work with strings in Ruby. Take the text
    +# in this file and paste it into repl.rb and save:
    +
    +repl do
    +  puts '* RUBY PRIMER: strings'
    +  message = "Hello World"
    +  puts "The value of message is: " + message
    +  puts "Any value can be interpolated within a string using \#{}."
    +  puts "Interpolated message: #{message}."
    +  puts 'This #{message} is not interpolated because the string uses single quotes.'
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/03_numbers.txt
    +# ====================================================================================
    +#  Numerics
    +# ====================================================================================
    +#
    +# Here is how you work with numbers in Ruby. Take the text
    +# in this file and paste it into repl.rb and save:
    +
    +repl do
    +  puts '* RUBY PRIMER: Fixnum and Floats'
    +  a = 10
    +  puts "The value of a is: #{a}"
    +  puts "a + 1 is: #{a + 1}"
    +  puts "a / 3 is: #{a / 3}"
    +  puts ''
    +
    +  b = 10.12
    +  puts "The value of b is: #{b}"
    +  puts "b + 1 is: #{b + 1}"
    +  puts "b as an integer is: #{b.to_i}"
    +  puts ''
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/04_booleans.txt
    +# ====================================================================================
    +#  Booleans
    +# ====================================================================================
    +#
    +# Here is how you work with numbers in Ruby. Take the text
    +# in this file and paste it into repl.rb and save:
    +
    +repl do
    +  puts '* RUBY PRIMER: TrueClass, FalseClass, NilClass (truthy / falsey values)'
    +  puts "Anything that *isn't* false or nil is true."
    +
    +  c = 30
    +  puts "The value of c is #{c}."
    +
    +  if c
    +    puts "This if statement ran because c is truthy."
    +  end
    +
    +  d = false
    +  puts "The value if d is #{d}. The type for d is #{d.class}."
    +
    +  if !d
    +    puts "This if statement ran because d is falsey, using the not operator (!)."
    +  end
    +
    +  e = nil
    +  puts "Nil is also considered falsey. The value of e is: #{e} (a blank string when printed). Which is of type #{e.class}."
    +
    +  if !e
    +    puts "This if statement ran because e is nil and the if statement applied the NOT operator. !e yields a type of #{(!e).class}."
    +  end
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/05_conditionals.txt
    +# ====================================================================================
    +#  Conditionals
    +# ====================================================================================
    +#
    +# Here is how you create conditionals in Ruby. Take the text
    +# in this file and paste it into repl.rb and save:
    +
    +repl do
    +  puts "* RUBY PRIMER: Conditionals"
    +end
    +
    +# ====================================================================================
    +#  if
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: if statement"
    +  i_am_one = 1
    +  if i_am_one
    +    puts "This was printed because i_am_one is truthy."
    +  end
    +end
    +
    +# ====================================================================================
    +#  if/else
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: if/else statement"
    +  i_am_false = false
    +  if i_am_false
    +    puts "This will NOT get printed because i_am_false is false."
    +  else
    +    puts "This was printed because i_am_false is false."
    +  end
    +end
    +
    +
    +# ====================================================================================
    +#  if/elsif/else
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: if/elsif/else statement"
    +  i_am_false = false
    +  i_am_true  = true
    +  if i_am_false
    +    puts "This will NOT get printed because i_am_false is false."
    +  elsif i_am_true
    +    puts "This was printed because i_am_true is true."
    +  else
    +    puts "This will NOT get printed i_am_true was true."
    +  end
    +end
    +
    +# ====================================================================================
    +#  case
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO case statement"
    +  i_am_one = 1 # change this value to see different results
    +
    +  case i_am_one
    +  when 10
    +    puts "the value of i_am_one is 10"
    +  when 9
    +    puts "the value of i_am_one is 9"
    +  when 5
    +    puts "the value of i_am_one is 5"
    +  when 1
    +    puts "the value of i_am_one is 1"
    +  else
    +    puts "Value wasn't cased."
    +  end
    +end
    +
    +# ====================================================================================
    +#  comparison operators
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Different types of comparisons"
    +  if 4 == 4
    +    puts "4 equals 4 (==)"
    +  end
    +
    +  if 4 != 3
    +    puts "4 does not equal 3 (!=)"
    +  end
    +
    +  if 3 < 4
    +    puts "3 is less than 4 (<)"
    +  end
    +
    +  if 4 > 3
    +    puts "4 is greater than 3 (>)"
    +  end
    +end
    +
    +# ====================================================================================
    +#  and/or conditionals
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: AND, OR operator (&&, ||)"
    +  if (4 > 3) || (3 < 4) || false
    +    puts "print this if 4 is greater than 3 OR 3 is less than 4 OR false is true (||)"
    +  end
    +
    +  if (4 > 3) && (3 < 4)
    +    puts "print this if 4 is greater than 3 AND 3 is less than 4 (&&)"
    +  end
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - looping.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/06_looping.txt
    +# ====================================================================================
    +#  Looping
    +# ====================================================================================
    +#
    +# Looping looks a whole lot different than other languages.
    +# But it's pretty awesome when you get used to it.
    +
    +repl do
    +  puts "* RUBY PRIMER: Loops"
    +end
    +
    +# ====================================================================================
    +#  times
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: ~Numeric#times~ (for loop)"
    +  3.times do |i|
    +    puts i
    +  end
    +end
    +
    +# ====================================================================================
    +#  foreach
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: ~Array#each~ (for each loop)"
    +  array = ["a", "b", "c", "d"]
    +  array.each do |char|
    +    puts char
    +  end
    +
    +  puts "** INFO: ~Array#each_with_index~ (for each loop)"
    +  array = ["a", "b", "c", "d"]
    +  array.each do |char, i|
    +    puts "index #{i}: #{char}"
    +  end
    +end
    +
    +# ====================================================================================
    +#  ranges
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: range block exclusive (three dots)"
    +  (0...3).each do |i|
    +    puts i
    +  end
    +
    +  puts "** INFO: range block inclusive (two dots)"
    +  (0..3).each do |i|
    +    puts i
    +  end
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - functions.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/07_functions.txt
    +# ====================================================================================
    +# Functions
    +# ====================================================================================
    +
    +# The last statement of a function is implictly returned. Parenthesis for functions
    +# are optional as long as the statement can be envaluated disambiguously.
    +
    +repl do
    +  puts "* RUBY PRIMER: Functions"
    +end
    +
    +# ====================================================================================
    +# Functions single parameter
    +# ====================================================================================
    +
    +repl do
    +  puts "* INFO: Function with one parameter"
    +
    +  # function definition
    +  def add_one_to n
    +    n + 1
    +  end
    +
    +  # Parenthesis are optional in Ruby as long as the
    +  # parsing is disambiguous. Here are a couple of variations.
    +  # Generally speaking, don't put parenthesis is you don't have to.
    +
    +  # Conventional Usage of Parenthesis.
    +  puts add_one_to(3)
    +
    +  # DragonRuby's recommended use of parenthesis (inner function has parenthesis).
    +  puts (add_one_to 3)
    +
    +  # Full parens.
    +  puts(add_one_to(3))
    +
    +  # Outer function has parenthesis
    +  puts(add_one_to 3)
    +end
    +
    +# ====================================================================================
    +# Functions with default parameter values
    +# ====================================================================================
    +
    +repl do
    +  puts "* INFO: Function with default value"
    +  def function_with_default_value v = 10
    +    v * 10
    +  end
    +
    +  puts "Passing the argument three yields: #{function_with_default_value 3}"
    +  puts "Passing no argument yields: #{function_with_default_value}"
    +end
    +
    +# ====================================================================================
    +# Nil default parameter value and ||= operator.
    +# ====================================================================================
    +
    +repl do
    +  puts "* INFO: Using the OR EQUAL operator (||=)"
    +  def function_with_nil_default_with_local a = nil
    +    result   = a
    +    result ||= "DEFAULT_VALUE_OF_A_IS_NIL_OR_FALSE"
    +    "value is #{result}."
    +  end
    +
    +  puts "Passing 'hi' as the argument yields: #{function_with_nil_default_with_local 'hi'}"
    +  puts "Passing nil: #{function_with_nil_default_with_local}"
    +end
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/08_arrays.txt
    +# ====================================================================================
    +# Arrays
    +# ====================================================================================
    +
    +# Arrays are incredibly powerful in Ruby. Learn to use them well.
    +
    +repl do
    +  puts "* RUBY PRIMER: ARRAYS"
    +end
    +
    +# ====================================================================================
    +# Enumerable ranges and .to_a
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Create an array with the numbers 1 to 10."
    +  one_to_ten = (1..10).to_a
    +  puts one_to_ten
    +end
    +
    +# ====================================================================================
    +# Finding elements
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Finding elements in an array using ~Array#find_all~."
    +  puts "Create a new array that only contains even numbers from the previous array."
    +
    +  one_to_ten = (1..10).to_a
    +  evens = one_to_ten.find_all do |number|
    +    number % 2 == 0
    +  end
    +
    +  puts evens
    +end
    +
    +# ====================================================================================
    +# Rejecting elements
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Removing elements in an array using ~Array#reject~."
    +  puts "Create a new array that rejects odd numbers."
    +
    +  one_to_ten = (1..10).to_a
    +  also_even = one_to_ten.reject do |number|
    +    number % 2 != 0
    +  end
    +
    +  puts also_even
    +end
    +
    +# ====================================================================================
    +# Array transform using the map function.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Creating new derived values from an array using ~Array#map~."
    +  puts "Create an array that doubles every number."
    +
    +  one_to_ten = (1..10).to_a
    +  doubled = one_to_ten.map do |number|
    +    number * 2
    +  end
    +
    +  puts doubled
    +end
    +
    +# ====================================================================================
    +# Combining array functions.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Combining ~Array#find_all~ along with ~Array#map~."
    +  puts "Create an array that selects only odd numbers and then multiply those by 10."
    +
    +  one_to_ten = (1..10).to_a
    +  odd_doubled = one_to_ten.find_all do |number|
    +    number % 2 != 0
    +  end.map do |odd_number|
    +    odd_number * 10
    +  end
    +
    +  puts odd_doubled
    +end
    +
    +# ====================================================================================
    +# Product function.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Create all combinations of array values using ~Array#product~."
    +  puts "All two-item pairs of numbers 1 to 10."
    +  one_to_ten = (1..10).to_a
    +  all_combinations = one_to_ten.product(one_to_ten)
    +  puts all_combinations
    +end
    +
    +# ====================================================================================
    +# Uniq and sort function.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Providing uniq values using ~Array#uniq~ and ~Array#sort~."
    +  puts "All uniq combinations of numbers regardless of order."
    +  puts "For example: [1, 2] is the same as [2, 1]."
    +  one_to_ten = (1..10).to_a
    +  uniq_combinations =
    +    one_to_ten.product(one_to_ten)
    +              .map do |unsorted_number|
    +                unsorted_number.sort
    +              end.uniq
    +  puts uniq_combinations
    +end
    +
    +# ====================================================================================
    +# Example of an advanced array transform.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Advanced chaining. Combining ~Array's ~map~, ~find_all~, ~sort~, and ~sort_by~."
    +  puts "All unique Pythagorean Triples between 1 and 100 sorted by area of the triangle."
    +
    +  one_to_hundred = (1..100).to_a
    +
    +  triples =
    +    one_to_hundred.product(one_to_hundred).map do |width, height|
    +                [width, height, Math.sqrt(width ** 2 + height ** 2)]
    +              end.find_all do |_, _, hypotenuse|
    +                hypotenuse.to_i == hypotenuse
    +              end.map do |triangle|
    +                triangle.map(&:to_i)
    +              end.uniq do |triangle|
    +                triangle.sort
    +              end.map do |width, height, hypotenuse|
    +                [width, height, hypotenuse, (width * height) / 2]
    +              end.sort_by do |_, _, _, area|
    +                area
    +              end
    +
    +  triples.each do |width, height, hypotenuse, _|
    +    puts "(#{width}, #{height}, #{hypotenuse})"
    +  end
    +end
    +
    +# ====================================================================================
    +# Example of an sorting.
    +# ====================================================================================
    +
    +repl do
    +  puts "** INFO: Implementing a custom sort function that operates on the ~Hash~ datatype."
    +
    +  things_to_sort = [
    +    { type: :background, order: 1 },
    +    { type: :foreground, order: 1 },
    +    { type: :foreground, order: 2 }
    +  ]
    +  puts "*** Original array."
    +  puts things_to_sort
    +
    +  puts "*** Simple sort using key."
    +  # For a simple sort, you can use sort_by
    +  results = things_to_sort.sort_by do |hash|
    +    hash[:order]
    +  end
    +
    +  puts results
    +
    +  puts "*** Custom sort."
    +  puts "**** Sorting process."
    +  # for a more complicated sort, you can provide a block that returns
    +  # -1, 0, 1 for a left and right operand
    +  results = things_to_sort.sort do |l, r|
    +    sort_result = 0
    +    puts "here is l: #{l}"
    +    puts "here is r: #{r || "nil"}"
    +    # if either value is nil/false return 0
    +    if !l || !r
    +      sort_result = 0
    +    # if the type of "left" is background and the
    +    # type of "right" is foreground, then return
    +    # -1 (which means "left" is less than "right"
    +    elsif l[:type] == :background && r[:type] == :foreground
    +      sort_result = -1
    +    # if the type of "left" is foreground and the
    +    # type of "right" is background, then return
    +    #  1 (which means "left" is greater than "right"
    +    elsif l[:type] == :foreground && r[:type] == :background
    +      sort_result = 1
    +    # if "left" and "right"'s type are the same, then
    +    # use the order as the tie breaker
    +    elsif l[:order] < r[:order]
    +      sort_result = -1
    +    elsif l[:order] > r[:order]
    +      sort_result = 1
    +    # returning 0 means both values are equal
    +    else
    +      sort_result = 0
    +    end
    +    sort_result
    +  end.to_a
    +
    +  puts "**** Sort result."
    +  puts results
    +end
    +
    +# ====================================================================================
    +# Api documention for Array that is worth commiting to memory because arrays are so
    +# awesome in Ruby: https://docs.ruby-lang.org/en/2.0.0/Array.html
    +# ====================================================================================
    +
    +
    +

    Learn Ruby Optional - Intermediate Ruby Primer - main.rb

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/main.rb
    +def tick args
       args.outputs.labels << [640, 380, "Open repl.rb in the text editor of your choice and follow the document.", 0, 1]
     end
     
     
    -

    00_learn_ruby_optional/00_intermediate_ruby_primer/app/repl.rb

    -
    +

    Learn Ruby Optional - Intermediate Ruby Primer - repl.rb

    +
    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/repl.rb
    +
     
    -

    01_rendering_basics/01_labels/app/main.rb

    -
    =begin
    +

    Rendering Basics - Labels - main.rb

    +
    # ./samples/01_rendering_basics/01_labels/app/main.rb
    +=begin
     
     APIs listing that haven't been encountered in a previous sample apps:
     
    @@ -2253,8 +2889,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    01_rendering_basics/02_lines/app/main.rb

    -
    =begin
    +

    Rendering Basics - Lines - main.rb

    +
    # ./samples/01_rendering_basics/02_lines/app/main.rb
    +=begin
     
     APIs listing that haven't been encountered in a previous sample apps:
     
    @@ -2310,8 +2947,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    01_rendering_basics/03_solids_borders/app/main.rb

    -
    =begin
    +

    Rendering Basics - Solids Borders - main.rb

    +
    # ./samples/01_rendering_basics/03_solids_borders/app/main.rb
    +=begin
     
     APIs listing that haven't been encountered in a previous sample apps:
     
    @@ -2379,8 +3017,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    01_rendering_basics/04_sprites/app/main.rb

    -
    =begin
    +

    Rendering Basics - Sprites - main.rb

    +
    # ./samples/01_rendering_basics/04_sprites/app/main.rb
    +=begin
     
     APIs listing that haven't been encountered in a previous sample apps:
     
    @@ -2425,8 +3064,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    01_rendering_basics/05_sounds/app/main.rb

    -
    =begin
    +

    Rendering Basics - Sounds - main.rb

    +
    # ./samples/01_rendering_basics/05_sounds/app/main.rb
    +=begin
     
      APIs Listing that haven't been encountered in previous sample apps:
     
    @@ -2617,8 +3257,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    02_input_basics/01_keyboard/app/main.rb

    -
    =begin
    +

    Input Basics - Keyboard - main.rb

    +
    # ./samples/02_input_basics/01_keyboard/app/main.rb
    +=begin
     
     APIs listing that haven't been encountered in a previous sample apps:
     
    @@ -2790,8 +3431,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    02_input_basics/02_mouse/app/main.rb

    -
    =begin
    +

    Input Basics - Mouse - main.rb

    +
    # ./samples/02_input_basics/02_mouse/app/main.rb
    +=begin
     
     APIs that haven't been encountered in a previous sample apps:
     
    @@ -2880,8 +3522,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    02_input_basics/03_mouse_point_to_rect/app/main.rb

    -
    =begin
    +

    Input Basics - Mouse Point To Rect - main.rb

    +
    # ./samples/02_input_basics/03_mouse_point_to_rect/app/main.rb
    +=begin
     
     APIs that haven't been encountered in a previous sample apps:
     
    @@ -2973,8 +3616,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    02_input_basics/04_mouse_rect_to_rect/app/main.rb

    -
    =begin
    +

    Input Basics - Mouse Rect To Rect - main.rb

    +
    # ./samples/02_input_basics/04_mouse_rect_to_rect/app/main.rb
    +=begin
     
     APIs that haven't been encountered in a previous sample apps:
     
    @@ -3073,8 +3717,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    02_input_basics/05_controller/app/main.rb

    -
    =begin
    +

    Input Basics - Controller - main.rb

    +
    # ./samples/02_input_basics/05_controller/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -3202,8 +3847,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    03_rendering_sprites/01_animation_using_seperate_pngs/app/main.rb

    -
    =begin
    +

    Rendering Sprites - Animation Using Separate Pngs - main.rb

    +
    # ./samples/03_rendering_sprites/01_animation_using_separate_pngs/app/main.rb
    +=begin
     
      Reminders:
     
    @@ -3336,8 +3982,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    03_rendering_sprites/02_animation_using_sprite_sheet/app/main.rb

    -
    def tick args
    +

    Rendering Sprites - Animation Using Sprite Sheet - main.rb

    +
    # ./samples/03_rendering_sprites/02_animation_using_sprite_sheet/app/main.rb
    +def tick args
       args.state.player.x ||= 100
       args.state.player.y ||= 100
       args.state.player.w ||= 64
    @@ -3437,8 +4084,9 @@ def running_sprite args
     end
     
     
    -

    03_rendering_sprites/03_animation_states/app/main.rb

    -
    class Game
    +

    Rendering Sprites - Animation States - main.rb

    +
    # ./samples/03_rendering_sprites/03_animation_states/app/main.rb
    +class Game
       attr_gtk
     
       def defaults
    @@ -3623,8 +4271,9 @@ end
     $gtk.reset
     
     
    -

    03_rendering_sprites/04_color_and_rotation/app/main.rb

    -
    =begin
    +

    Rendering Sprites - Color And Rotation - main.rb

    +
    # ./samples/03_rendering_sprites/04_color_and_rotation/app/main.rb
    +=begin
      APIs listing that haven't been encountered in previous sample apps:
     
      - merge: Returns a hash containing the contents of two original hashes.
    @@ -3852,8 +4501,9 @@ def source_rect state
     end
     
     
    -

    04_physics_and_collisions/01_simple/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Simple - main.rb

    +
    # ./samples/04_physics_and_collisions/01_simple/app/main.rb
    +=begin
     
      Reminders:
      - ARRAY#intersect_rect?: Returns true or false depending on if the two rectangles intersect.
    @@ -3963,8 +4613,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    04_physics_and_collisions/02_moving_objects/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Moving Objects - main.rb

    +
    # ./samples/04_physics_and_collisions/02_moving_objects/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -4266,8 +4917,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    04_physics_and_collisions/03_entities/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Entities - main.rb

    +
    # ./samples/04_physics_and_collisions/03_entities/app/main.rb
    +=begin
     
      Reminders:
     
    @@ -4420,8 +5072,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    04_physics_and_collisions/04_box_collision/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Box Collision - main.rb

    +
    # ./samples/04_physics_and_collisions/04_box_collision/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -4760,8 +5413,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    04_physics_and_collisions/04_box_collision_2/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Box Collision 2 - main.rb

    +
    # ./samples/04_physics_and_collisions/05_box_collision_2/app/main.rb
    +=begin
      APIs listing that haven't been encountered in previous sample apps:
     
      - times: Performs an action a specific number of times.
    @@ -4891,29 +5545,13 @@ class MetroidvaniaStarter
                             state.player_height,'sprites/player.png']
     
         # Outputs labels as primitives in top right of the screen
    -    outputs.primitives << [920, 700, 'Press 
    -    
    - - -s - - - - to access sprites.', 1, 0].label + outputs.primitives << [920, 700, 'Press \'s\' to access sprites.', 1, 0].label outputs.primitives << [920, 675, 'Click existing sprite to delete.', 1, 0].label outputs.primitives << [920, 640, '<- and -> to move.', 1, 0].label outputs.primitives << [920, 615, 'Press and hold space to jump.', 1, 0].label - outputs.primitives << [920, 580, 'Press - - - -e - - - - to export current map.', 1, 0].label + outputs.primitives << [920, 580, 'Press \'e\' to export current map.', 1, 0].label # if the map is saved and less than 120 frames have passed, the label is displayed if state.map_saved_at > 0 && state.map_saved_at.elapsed_time < 120 @@ -5249,8 +5887,9 @@ def tick args end -

    04_physics_and_collisions/04_jump_physics/app/main.rb

    -
    =begin
    +

    Physics And Collisions - Jump Physics - main.rb

    +
    # ./samples/04_physics_and_collisions/06_jump_physics/app/main.rb
    +=begin
     
      Reminders:
     
    @@ -5448,8 +6087,9 @@ def tick args
     end
     
     
    -

    05_mouse/03_mouse_click/app/main.rb

    -
    =begin
    +

    Mouse - Mouse Click - main.rb

    +
    # ./samples/05_mouse/01_mouse_click/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -5695,8 +6335,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    05_mouse/05_mouse_move/app/main.rb

    -
    =begin
    +

    Mouse - Mouse Move - main.rb

    +
    # ./samples/05_mouse/02_mouse_move/app/main.rb
    +=begin
     
      Reminders:
     
    @@ -5994,8 +6635,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    05_mouse/05_mouse_move_paint_app/app/main.rb

    -
    =begin
    +

    Mouse - Mouse Move Paint App - main.rb

    +
    # ./samples/05_mouse/03_mouse_move_paint_app/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -6237,8 +6879,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    05_mouse/06_coordinate_systems/app/main.rb

    -
    =begin
    +

    Mouse - Coordinate Systems - main.rb

    +
    # ./samples/05_mouse/04_coordinate_systems/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -6320,8 +6963,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    06_save_load/10_save_load_game/app/main.rb

    -
    =begin
    +

    Save Load - Save Load Game - main.rb

    +
    # ./samples/06_save_load/01_save_load_game/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -6712,8 +7356,9 @@ def tick args
     end
     
     
    -

    07_advanced_rendering/01_simple_render_targets/app/main.rb

    -
    def tick args
    +

    Advanced Rendering - Simple Render Targets - main.rb

    +
    # ./samples/07_advanced_rendering/01_simple_render_targets/app/main.rb
    +def tick args
       # args.outputs.render_targets are really really powerful.
       # They essentially allow you to create a sprite programmatically and cache the result.
     
    @@ -6767,8 +7412,9 @@ end
     $gtk.reset
     
     
    -

    07_advanced_rendering/02_render_targets_with_alphas/app/main.rb

    -
    # This sample is meant to show you how to do that dripping transition thing
    +

    Advanced Rendering - Render Targets With Alphas - main.rb

    +
    # ./samples/07_advanced_rendering/02_render_targets_with_alphas/app/main.rb
    +# This sample is meant to show you how to do that dripping transition thing
     #  at the start of the original Doom. Most of this file is here to animate
     #  a scene to wipe away; the actual wipe effect is in the last 20 lines or
     #  so.
    @@ -6865,8 +7511,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    07_advanced_rendering/03_render_target_viewports/app/main.rb

    -
    =begin
    +

    Advanced Rendering - Render Target Viewports - main.rb

    +
    # ./samples/07_advanced_rendering/03_render_target_viewports/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -7336,8 +7983,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    07_advanced_rendering/04_render_primitive_hierarchies/app/main.rb

    -
    =begin
    +

    Advanced Rendering - Render Primitive Hierarchies - main.rb

    +
    # ./samples/07_advanced_rendering/04_render_primitive_hierarchies/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -7511,8 +8159,9 @@ def collection_of_sprites args
     end
     
     
    -

    07_advanced_rendering/11_render_primitives_as_hash/app/main.rb

    -
    =begin
    +

    Advanced Rendering - Render Primitives As Hash - main.rb

    +
    # ./samples/07_advanced_rendering/05_render_primitives_as_hash/app/main.rb
    +=begin
     
      Reminders:
     
    @@ -7705,8 +8354,9 @@ def tick args
     end
     
     
    -

    08_lerping_easing_functions/01_easing_functions/app/main.rb

    -
    def tick args
    +

    Tweening Lerping Easing Functions - Easing Functions - main.rb

    +
    # ./samples/08_tweening_lerping_easing_functions/01_easing_functions/app/main.rb
    +def tick args
       # STOP! Watch the following presentation first!!!!
       # Math for Game Programmers: Fast and Funky 1D Nonlinear Transformations
       # https://www.youtube.com/watch?v=mr5xkf6zSzk
    @@ -7840,8 +8490,9 @@ module Easing
     end
     
     
    -

    08_lerping_easing_functions/02_cubic_bezier/app/main.rb

    -
    def tick args
    +

    Tweening Lerping Easing Functions - Cubic Bezier - main.rb

    +
    # ./samples/08_tweening_lerping_easing_functions/02_cubic_bezier/app/main.rb
    +def tick args
       args.outputs.background_color = [33, 33, 33]
       args.outputs.lines << bezier(100, 100,
                                    100, 620,
    @@ -7904,8 +8555,9 @@ def pow n, to
     end
     
     
    -

    08_lerping_easing_functions/03_easing_using_spline/app/main.rb

    -
    def tick args
    +

    Tweening Lerping Easing Functions - Easing Using Spline - main.rb

    +
    # ./samples/08_tweening_lerping_easing_functions/03_easing_using_spline/app/main.rb
    +def tick args
       args.state.duration = 10.seconds
       args.state.spline = [
         [0.0, 0.33, 0.66, 1.0],
    @@ -7925,8 +8577,9 @@ end
     end
     
     
    -

    08_lerping_easing_functions/04_parametric_enemy_movement/app/main.rb

    -
    def new_star args
    +

    Tweening Lerping Easing Functions - Parametric Enemy Movement - main.rb

    +
    # ./samples/08_tweening_lerping_easing_functions/04_parametric_enemy_movement/app/main.rb
    +def new_star args
       { x: 1280.randomize(:ratio),
         starting_y: 800,
         distance_to_travel: 900 + 100.randomize(:ratio),
    @@ -8141,8 +8794,9 @@ def tick args
     end
     
     
    -

    09_performance/01_sprites_as_hash/app/main.rb

    -
    # Sprites represented as Hashes using the queue ~args.outputs.sprites~
    +

    Performance - Sprites As Hash - main.rb

    +
    # ./samples/09_performance/01_sprites_as_hash/app/main.rb
    +# Sprites represented as Hashes using the queue ~args.outputs.sprites~
     # code up, but are the "slowest" to render.
     # The reason for this is the access of the key in the Hash and also
     # because the data args.outputs.sprites is cleared every tick.
    @@ -8207,8 +8861,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/02_sprites_as_entities/app/main.rb

    -
    # Sprites represented as Entities using the queue ~args.outputs.sprites~
    +

    Performance - Sprites As Entities - main.rb

    +
    # ./samples/09_performance/02_sprites_as_entities/app/main.rb
    +# Sprites represented as Entities using the queue ~args.outputs.sprites~
     # yields nicer access apis over Hashes, but require a bit more code upfront.
     # The hash sample has to use star[:s] to get the speed of the star, but
     # an entity can use .s instead.
    @@ -8274,8 +8929,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/03_sprites_as_strict_entities/app/main.rb

    -
    # Sprites represented as StrictEntities using the queue ~args.outputs.sprites~
    +

    Performance - Sprites As Strict Entities - main.rb

    +
    # ./samples/09_performance/03_sprites_as_strict_entities/app/main.rb
    +# Sprites represented as StrictEntities using the queue ~args.outputs.sprites~
     # yields apis access similar to Entities, but all properties that can be set on the
     # entity must be predefined with a default value. Strict entities do not support the
     # addition of new properties after the fact. They are more performant than OpenEntities
    @@ -8345,8 +9001,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/04_sprites_as_classes/app/main.rb

    -
    # Sprites represented as Classes using the queue ~args.outputs.sprites~.
    +

    Performance - Sprites As Classes - main.rb

    +
    # ./samples/09_performance/04_sprites_as_classes/app/main.rb
    +# Sprites represented as Classes using the queue ~args.outputs.sprites~.
     # gives you full control of property declaration and method invocation.
     # They are more performant than OpenEntities and StrictEntities, but more code upfront.
     class Star
    @@ -8398,8 +9055,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/05_static_sprites_as_classes/app/main.rb

    -
    # Sprites represented as Classes using the queue ~args.outputs.static_sprites~.
    +

    Performance - Static Sprites As Classes - main.rb

    +
    # ./samples/09_performance/05_static_sprites_as_classes/app/main.rb
    +# Sprites represented as Classes using the queue ~args.outputs.static_sprites~.
     # bypasses the queue behavior of ~args.outputs.sprites~. All instances are held
     # by reference. You get better performance, but you are mutating state of held objects
     # which is less functional/data oriented.
    @@ -8452,8 +9110,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/06_static_sprites_as_classes_with_custom_drawing/app/main.rb

    -
    # Sprites represented as Classes, with a draw_override method, and using the queue ~args.outputs.static_sprites~.
    +

    Performance - Static Sprites As Classes With Custom Drawing - main.rb

    +
    # ./samples/09_performance/06_static_sprites_as_classes_with_custom_drawing/app/main.rb
    +# Sprites represented as Classes, with a draw_override method, and using the queue ~args.outputs.static_sprites~.
     # is the fastest approach. This is comparable to what other game engines set as the default behavior.
     # There are tradeoffs for all this speed if the creation of a full blown class, and bypassing
     # functional/data-oriented practices.
    @@ -8527,8 +9186,9 @@ def reset_with count: count
     end
     
     
    -

    09_performance/07_collision_limits/app/main.rb

    -
    =begin
    +

    Performance - Collision Limits - main.rb

    +
    # ./samples/09_performance/07_collision_limits/app/main.rb
    +=begin
     
      Reminders:
      - find_all: Finds all elements of a collection that meet certain requirements.
    @@ -8585,8 +9245,9 @@ end
     $gtk.reset
     
     
    -

    10_advanced_debugging/01_trace_debugging/app/main.rb

    -
    class Game
    +

    Advanced Debugging - Trace Debugging - main.rb

    +
    # ./samples/10_advanced_debugging/01_trace_debugging/app/main.rb
    +class Game
       attr_gtk
     
       def method1 num
    @@ -8641,8 +9302,9 @@ def tick args
     end
     
     
    -

    10_advanced_debugging/02_trace_debugging_classes/app/main.rb

    -
    class Foobar
    +

    Advanced Debugging - Trace Debugging Classes - main.rb

    +
    # ./samples/10_advanced_debugging/02_trace_debugging_classes/app/main.rb
    +class Foobar
       def initialize
         trace! # Trace is added to the constructor.
       end
    @@ -8666,8 +9328,9 @@ def tick args
     end
     
     
    -

    10_advanced_debugging/03_unit_tests/exception_raising_tests.rb

    -
    begin :shared
    +

    Advanced Debugging - Unit Tests - exception_raising_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/exception_raising_tests.rb
    +begin :shared
       class ExceptionalClass
         def initialize exception_to_throw = nil
           raise exception_to_throw if exception_to_throw
    @@ -8690,13 +9353,15 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/gen_docs.rb

    -
    # sh ./amir-build-and-run.sh --eval samples/99_zz_gtk_unit_tests/gen_docs.rb --no-tick
    +

    Advanced Debugging - Unit Tests - gen_docs.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/gen_docs.rb
    +# sh ./amir-build-and-run.sh --eval samples/99_zz_gtk_unit_tests/gen_docs.rb --no-tick
     Kernel.export_docs!
     
     
    -

    10_advanced_debugging/03_unit_tests/geometry_tests.rb

    -
    begin :shared
    +

    Advanced Debugging - Unit Tests - geometry_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/geometry_tests.rb
    +begin :shared
       def primitive_representations x, y, w, h
         [
           [x, y, w, h],
    @@ -8814,8 +9479,9 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/http_tests.rb

    -
    def try_assert_or_schedule args, assert
    +

    Advanced Debugging - Unit Tests - http_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/http_tests.rb
    +def try_assert_or_schedule args, assert
       if $result[:complete]
         log_info "Request completed! Verifying."
         if $result[:http_response_code] != 200
    @@ -8841,8 +9507,9 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/object_to_primitive_tests.rb

    -
    class PlayerSpriteForTest
    +

    Advanced Debugging - Unit Tests - object_to_primitive_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/object_to_primitive_tests.rb
    +class PlayerSpriteForTest
     end
     
     def test_array_to_sprite args, assert
    @@ -8861,8 +9528,9 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/parsing_tests.rb

    -
    def test_parse_json args, assert
    +

    Advanced Debugging - Unit Tests - parsing_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/parsing_tests.rb
    +def test_parse_json args, assert
       result = args.gtk.parse_json '{ "name": "John Doe", "aliases": ["JD"] }'
       assert.equal! result, { "name"=>"John Doe", "aliases"=>["JD"] }, "Parsing JSON failed."
     end
    @@ -8893,8 +9561,9 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb

    -
    def test_serialize args, assert
    +

    Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb
    +def test_serialize args, assert
       GTK::Entity.__reset_id__!
       args.state.player_one = "test"
       result = args.gtk.serialize_state args.state
    @@ -8982,8 +9651,9 @@ end
     $tests.start
     
     
    -

    10_advanced_debugging/03_unit_tests/state_serialization_experimental_tests.rb

    -
    MAX_CODE_GEN_LENGTH = 50
    +

    Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/state_serialization_experimental_tests.rb
    +MAX_CODE_GEN_LENGTH = 50
     
     # NOTE: This is experimental/advanced stuff.
     def needs_partitioning? target
    @@ -9092,8 +9762,9 @@ $gtk.log_level = :off
     $gtk.tests.start
     
     
    -

    11_http/01_retrieve_images/app/main.rb

    -
    def tick args
    +

    Http - Retrieve Images - main.rb

    +
    # ./samples/11_http/01_retrieve_images/app/main.rb
    +def tick args
       args.outputs.background_color = [0, 0, 0]
     
       # Show a warning at the start.
    @@ -9148,8 +9819,20 @@ $gtk.tests.start
     end
     
     
    -

    99_genre_3d/3d_cube/app/main.rb

    -
    STARTX             = 0.0
    +

    12 C Extensions - Basics - main.rb

    +
    # ./samples/12_c_extensions/01_basics/app/main.rb
    +$gtk.ffi_misc.gtk_dlopen("./samples/12_c_extensions/01_basics/build.dir/ext.lib")
    +include FFI::CExt
    +
    +def tick args
    +  args.outputs.labels << [460, 600, "square(42) = #{square(42)}"]
    +end
    +
    +
    +
    +

    3d - 3d Cube - main.rb

    +
    # ./samples/99_genre_3d/3d_cube/app/main.rb
    +STARTX             = 0.0
     STARTY             = 0.0
     ENDY               = 20.0
     ENDX               = 20.0
    @@ -9201,8 +9884,9 @@ end
     $gtk.reset
     
     
    -

    99_genre_arcade/dueling_starships/app/main.rb

    -
    class DuelingSpaceships
    +

    Arcade - Dueling Starships - main.rb

    +
    # ./samples/99_genre_arcade/dueling_starships/app/main.rb
    +class DuelingSpaceships
       attr_accessor :state, :inputs, :outputs, :grid
     
       def tick
    @@ -9569,8 +10253,16 @@ def tick args
     end
     
     
    -

    99_genre_arcade/flappy_dragon/app/main.rb

    -
    class FlappyDragon
    +

    arcade/flappy dragon/credits.txt

    +
    # ./samples/99_genre_arcade/flappy_dragon/CREDITS.txt
    +code: Amir Rajan, https://twitter.com/amirrajan
    +graphics and audio: Nick Culbertson, https://twitter.com/MobyPixel
    +
    +
    +
    +

    arcade/flappy dragon/main.rb

    +
    # ./samples/99_genre_arcade/flappy_dragon/app/main.rb
    +class FlappyDragon
       attr_accessor :grid, :inputs, :state, :outputs
     
       def tick
    @@ -9932,8 +10624,9 @@ def tick args
     end
     
     
    -

    99_genre_arcade/pong/app/main.rb

    -
    def tick args
    +

    Arcade - Pong - main.rb

    +
    # ./samples/99_genre_arcade/pong/app/main.rb
    +def tick args
       defaults args
       render args
       calc args
    @@ -10094,8 +10787,9 @@ begin :assets
     end
     
     
    -

    99_genre_arcade/snakemoji/app/main.rb

    -
    # coding: utf-8
    +

    Arcade - Snakemoji - main.rb

    +
    # ./samples/99_genre_arcade/snakemoji/app/main.rb
    +# coding: utf-8
     ################################
     #  So I was working on a snake game while
     #  learning DragonRuby, and at some point I had a thought
    @@ -10262,8 +10956,9 @@ def defaults 🎮
     end
     
     
    -

    99_genre_arcade/solar_system/app/main.rb

    -
    # Focused tutorial video: https://s3.amazonaws.com/s3.dragonruby.org/dragonruby-nddnug-workshop.mp4
    +

    Arcade - Solar System - main.rb

    +
    # ./samples/99_genre_arcade/solar_system/app/main.rb
    +# Focused tutorial video: https://s3.amazonaws.com/s3.dragonruby.org/dragonruby-nddnug-workshop.mp4
     # Workshop/Presentation which provides motivation for creating a game engine: https://www.youtube.com/watch?v=S3CFce1arC8
     
     def defaults args
    @@ -10373,8 +11068,9 @@ def r
     end
     
     
    -

    99_genre_crafting/craft_game_starting_point/app/main.rb

    -
    # ==================================================
    +

    Crafting - Craft Game Starting Point - main.rb

    +
    # ./samples/99_genre_crafting/craft_game_starting_point/app/main.rb
    +# ==================================================
     # A NOTE TO JAM CRAFT PARTICIPANTS:
     # The comments and code in here are just as small piece of DragonRuby's capabilities.
     # Be sure to check out the rest of the sample apps. Start with README.txt and go from there!
    @@ -10799,8 +11495,71 @@ end
     $gtk.reset
     
     
    -

    99_genre_dev_tools/animation_creator_starting_point/app/main.rb

    -
    class OneBitLowrezPaint
    +

    Dev Tools - Add Buttons To Console - main.rb

    +
    # ./samples/99_genre_dev_tools/add_buttons_to_console/app/main.rb
    +# You can customize the buttons that show up in the Console.
    +class GTK::Console::Menu
    +  # STEP 1: Override the custom_buttons function.
    +  def custom_buttons
    +    [
    +      (button id: :yay,
    +              # row for button
    +              row: 3,
    +              # column for button
    +              col: 10,
    +              # text
    +              text: "I AM CUSTOM",
    +              # when clicked call the custom_button_clicked function
    +              method: :custom_button_clicked),
    +
    +      (button id: :yay,
    +              # row for button
    +              row: 3,
    +              # column for button
    +              col: 9,
    +              # text
    +              text: "CUSTOM ALSO",
    +              # when clicked call the custom_button_also_clicked function
    +              method: :custom_button_also_clicked)
    +    ]
    +  end
    +
    +  # STEP 2: Define the function that should be called.
    +  def custom_button_clicked
    +    log "* INFO: I AM CUSTOM was clicked!"
    +  end
    +
    +  def custom_button_also_clicked
    +    log "* INFO: Custom Button Clicked at #{Kernel.global_tick_count}!"
    +
    +    all_buttons_as_string = $gtk.console.menu.buttons.map do |b|
    +      <<-S.strip
    +** id: #{b[:id]}
    +:PROPERTIES:
    +:id:     :#{b[:id]}
    +:method: :#{b[:method]}
    +:text:   #{b[:text]}
    +:END:
    +S
    +    end.join("\n")
    +
    +    log <<-S
    +* INFO: Here are all the buttons:
    +#{all_buttons_as_string}
    +S
    +  end
    +end
    +
    +def tick args
    +  args.outputs.labels << [args.grid.center.x, args.grid.center.y,
    +                          "Open the DragonRuby Console to see the custom menu items.",
    +                          0, 1]
    +end
    +
    +
    +

    Dev Tools - Animation Creator Starting Point - main.rb

    +
    # ./samples/99_genre_dev_tools/animation_creator_starting_point/app/main.rb
    +class OneBitLowrezPaint
       attr_gtk
     
       def tick
    @@ -11249,8 +12008,9 @@ end
     # $gtk.reset
     
     
    -

    99_genre_dev_tools/tile_editor_starting_point/app/main.rb

    -
    =begin
    +

    Dev Tools - Tile Editor Starting Point - main.rb

    +
    # ./samples/99_genre_dev_tools/tile_editor_starting_point/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -11643,8 +12403,9 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    99_genre_lowrez/resolution_64x64/app/lowrez.rb

    -
    # Emulation of a 64x64 canvas. Don't change this file unless you know what you're doing :-)
    +

    Lowrez - Resolution 64x64 - lowrez.rb

    +
    # ./samples/99_genre_lowrez/resolution_64x64/app/lowrez.rb
    +# Emulation of a 64x64 canvas. Don't change this file unless you know what you're doing :-)
     # Head over to main.rb and study the code there.
     
     LOWREZ_SIZE            = 64
    @@ -11816,8 +12577,9 @@ module GTK
     end
     
     
    -

    99_genre_lowrez/resolution_64x64/app/main.rb

    -
    require 'app/lowrez.rb'
    +

    Lowrez - Resolution 64x64 - main.rb

    +
    # ./samples/99_genre_lowrez/resolution_64x64/app/main.rb
    +require 'app/lowrez.rb'
     
     def tick args
       # How to set the background color
    @@ -12432,3093 +13194,877 @@ end
     $gtk.reset
     
     
    -

    99_genre_narrative_rpg/choose_your_own_adventure/app/decision.rb

    -
    # Hey there! Welcome to Four Decisions. Here is how you
    -# create your decision tree. Remove =being and =end from the text to
    -# enable the game (just save the file). Change stuff and see what happens!
    -
    -def game
    -  {
    -    starting_decision: :stormy_night,
    -    decisions: {
    -      stormy_night: {
    -        description: 'It was a dark and stormy night. (storyline located in decision.rb)',
    -        option_one: {
    -          description: 'Go to sleep.',
    -          decision: :nap
    -        },
    -        option_two: {
    -          description: 'Watch a movie.',
    -          decision: :movie
    -        },
    -        option_three: {
    -          description: 'Go outside.',
    -          decision: :go_outside
    -        },
    -        option_four: {
    -          description: 'Get a snack.',
    -          decision: :get_a_snack
    -        }
    -      },
    -      nap: {
    -        description: 'You took a nap. The end.',
    -        option_one: {
    -          description: 'Start over.',
    -          decision: :stormy_night
    -        }
    -      }
    -    }
    -  }
    -end
    -
    -
    -

    99_genre_narrative_rpg/choose_your_own_adventure/app/main.rb

    -
    =begin
    +

    Platformer - Clepto Frog - main.rb

    +
    # ./samples/99_genre_platformer/clepto_frog/app/main.rb
    +MAP_FILE_PATH = 'app/map.txt'
     
    - Reminders:
    -
    - - Hashes: Collection of unique keys and their corresponding values. The values can be found
    -   using their keys.
    +require 'app/map.rb'
     
    -   In this sample app, the decisions needed for the game are stored in a hash. In fact, the
    -   decision.rb file contains hashes inside of other hashes!
    +class CleptoFrog
    +  attr_gtk
     
    -   Each option is a key in the first hash, but also contains a hash (description and
    -   decision being its keys) as its value.
    -   Go into the decision.rb file and take a look before diving into the code below.
    +  def render_ending
    +    state.game_over_at ||= state.tick_count
     
    - - args.outputs.labels: An array. The values generate a label.
    -   The parameters are [X, Y, TEXT, SIZE, ALIGNMENT, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    -   For more information about labels, go to mygame/documentation/02-labels.md.
    +    outputs.labels << [640, 700, "Clepto Frog", 4, 1]
     
    - - args.keyboard.key_down.KEY: Determines if a key is in the down state or pressed down.
    -   For more information about the keyboard, go to mygame/documentation/06-keyboard.md.
    +    if state.tick_count >= (state.game_over_at + 120)
    +      outputs.labels << [640, 620, "\"I... I.... don't believe it.\" - New Guy",
    +                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 120).ease(60)]
    +    end
     
    - - String interpolation: uses #{} syntax; everything between the #{ and the } is evaluated
    -   as Ruby code, and the placeholder is replaced with its corresponding value or result.
    +    if state.tick_count >= (state.game_over_at + 240)
    +      outputs.labels << [640, 580, "\"He actually stole all the mugs?\" - New Guy",
    +                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 240).ease(60)]
    +    end
     
    -=end
    +    if state.tick_count >= (state.game_over_at + 360)
    +      outputs.labels << [640, 540, "\"Kind of feel bad STARTING HIM WITH NOTHING again.\" - New Guy",
    +                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 360).ease(60)]
    +    end
     
    -# This sample app provides users with a story and multiple decisions that they can choose to make.
    -# Users can make a decision using their keyboard, and the story will move forward based on user choices.
    +    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
    +                        "sprites/square-green.png"]
     
    -# The decisions available to users are stored in the decision.rb file.
    -# We must have access to it for the game to function properly.
    -GAME_FILE = 'app/decision.rb' # found in app folder
    +    outputs.labels << [640, 300, "Current Time: #{"%.2f" % state.stuff_time}", 4, 1]
    +    outputs.labels << [640, 270, "Best Time: #{"%.2f" % state.stuff_best_time}", 4, 1]
     
    -require GAME_FILE # require used to load another file, import class/method definitions
    +    if state.tick_count >= (state.game_over_at + 550)
    +      restart_game
    +    end
    +  end
     
    -# Instructions are given using labels to users if they have not yet set up their story in the decision.rb file.
    -# Otherwise, the game is run.
    -def tick args
    -  if !args.state.loaded && !respond_to?(:game) # if game is not loaded and not responding to game symbol's method
    -    args.labels << [640, 370, 'Hey there! Welcome to Four Decisions.', 0, 1] # a welcome label is shown
    -    args.labels << [640, 340, 'Go to the file called decision.rb and tell me your story.', 0, 1]
    -  elsif respond_to?(:game) # otherwise, if responds to game
    -    args.state.loaded = true
    -    tick_game args # calls tick_game method, runs game
    +  def restart_game
    +    state.world = nil
    +    state.x = nil
    +    state.y = nil
    +    state.dx = nil
    +    state.dy = nil
    +    state.stuff_score = 0
    +    state.stuff_time = 0
    +    state.intro_tick_count = nil
    +    defaults
    +    state.game_start_at = state.tick_count
    +    state.scene = :game
    +    state.game_over_at = nil
       end
     
    -  if args.state.tick_count.mod_zero? 60 # update every 60 frames
    -    t = args.gtk.ffi_file.mtime GAME_FILE # mtime returns modification time for named file
    -    if t != args.state.mtime
    -      args.state.mtime = t
    -      require GAME_FILE # require used to load file
    -      args.state.game_definition = nil # game definition and decision are empty
    -      args.state.decision_id = nil
    +  def render_intro
    +    outputs.labels << [640, 700, "Clepto Frog", 4, 1]
    +    if state.tick_count >= 120
    +      outputs.labels << [640, 620, "\"Uh... your office has a pet frog?\" - New Guy",
    +                         4, 1, 0, 0, 0, 255 * 120.ease(60)]
         end
    -  end
    -end
     
    -# Runs methods needed for game to function properly
    -# Creates a rectangular border around the screen
    -def tick_game args
    -  defaults args
    -  args.borders << args.grid.rect
    -  render_decision args
    -  process_inputs args
    -end
    +    if state.tick_count >= 240
    +      outputs.labels << [640, 580, "\"Yep! His name is Clepto.\" - Jim",
    +                         4, 1, 0, 0, 0, 255 * 240.ease(60)]
    +    end
     
    -# Sets default values and uses decision.rb file to define game and decision_id
    -# variable using the starting decision
    -def defaults args
    -  args.state.game_definition ||= game
    -  args.state.decision_id ||= args.state.game_definition[:starting_decision]
    -end
    +    if state.tick_count >= 360
    +      outputs.labels << [640, 540, "\"Uh...\" - New Guy",
    +                         4, 1, 0, 0, 0, 255 * 360.ease(60)]
    +    end
     
    -# Outputs the possible decision descriptions the user can choose onto the screen
    -# as well as what key to press on their keyboard to make their decision
    -def render_decision args
    -  decision = current_decision args
    -  # text is either the value of decision's description key or warning that no description exists
    -  args.labels << [640, 360, decision[:description] || "No definition found for #{args.state.decision_id}. Please update decision.rb.", 0, 1] # uses string interpolation
    +    if state.tick_count >= 480
    +      outputs.labels << [640, 500, "\"He steals mugs while we're away...\" - Jim",
    +                         4, 1, 0, 0, 0, 255 * 480.ease(60)]
    +    end
     
    -  # All decisions are stored in a hash
    -  # The descriptions output onto the screen are the values for the description keys of the hash.
    -  if decision[:option_one]
    -    args.labels << [10, 360, decision[:option_one][:description], 0, 0] # option one's description label
    -    args.labels << [10, 335, "(Press 'left' on the keyboard to select this decision)", -5, 0] # label of what key to press to select the decision
    -  end
    +    if state.tick_count >= 600
    +      outputs.labels << [640, 460, "\"It's not a big deal, we take them back in the morning.\" - Jim",
    +                         4, 1, 0, 0, 0, 255 * 600.ease(60)]
    +    end
     
    -  if decision[:option_two]
    -    args.labels << [1270, 360, decision[:option_two][:description], 0, 2] # option two's description
    -    args.labels << [1270, 335, "(Press 'right' on the keyboard to select this decision)", -5, 2]
    -  end
    +    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
    +                        "sprites/square-green.png"]
     
    -  if decision[:option_three]
    -    args.labels << [640, 45, decision[:option_three][:description], 0, 1] # option three's description
    -    args.labels << [640, 20, "(Press 'down' on the keyboard to select this decision)", -5, 1]
    +    if state.tick_count == 800
    +      state.scene = :game
    +      state.game_start_at = state.tick_count
    +    end
       end
     
    -  if decision[:option_four]
    -    args.labels << [640, 700, decision[:option_four][:description], 0, 1] # option four's description
    -    args.labels << [640, 675, "(Press 'up' on the keyboard to select this decision)", -5, 1]
    +  def tick
    +    defaults
    +    if state.scene == :intro && state.tick_count <= 800
    +      render_intro
    +    elsif state.scene == :ending
    +      render_ending
    +    else
    +      render
    +    end
    +    calc
    +    process_inputs
       end
    -end
    -
    -# Uses keyboard input from the user to make a decision
    -# Assigns the decision as the value of the decision_id variable
    -def process_inputs args
    -  decision = current_decision args # calls current_decision method
     
    -  if args.keyboard.key_down.left! && decision[:option_one] # if left key pressed and option one exists
    -    args.state.decision_id = decision[:option_one][:decision] # value of option one's decision hash key is set to decision_id
    -  end
    -
    -  if args.keyboard.key_down.right! && decision[:option_two] # if right key pressed and option two exists
    -    args.state.decision_id = decision[:option_two][:decision] # value of option two's decision hash key is set to decision_id
    +  def defaults
    +    state.scene ||= :intro
    +    state.stuff_score     ||= 0
    +    state.stuff_time      ||= 0
    +    state.stuff_best_time ||= nil
    +    state.camera_x ||= 0
    +    state.camera_y ||= 0
    +    state.target_camera_scale ||= 1
    +    state.camera_scale ||= 1
    +    state.tongue_length          ||= 100
    +    state.dev_action             ||= :collision_mode
    +    state.action                 ||= :aiming
    +    state.tongue_angle           ||= 90
    +    state.tile_size                = 64
    +    state.gravity                  = -0.1
    +    state.air                      = -0.01
    +    state.player_width             = 60
    +    state.player_height            = 60
    +    state.collision_tolerance      = 0.0
    +    state.previous_tile_size     ||= state.tile_size
    +    state.x                      ||= 2400
    +    state.y                      ||= 200
    +    state.dy                     ||= 0
    +    state.dx                     ||= 0
    +    attempt_load_world_from_file
    +    state.world_lookup           ||= { }
    +    state.world_collision_rects  ||= []
    +    state.mode                   ||= :creating
    +    state.select_menu            ||= [0, 720, 1280, 720]
    +    state.sprite_quantity        ||= 20
    +    state.sprite_coords          ||= []
    +    state.banner_coords          ||= [640, 680 + 720]
    +    state.sprite_selected        ||= 1
    +    state.map_saved_at           ||= 0
    +    state.intro_tick_count       ||= state.tick_count
    +    if state.sprite_coords == []
    +      count = 1
    +      temp_x = 165
    +      temp_y = 500 + 720
    +      state.sprite_quantity.times do
    +        state.sprite_coords += [[temp_x, temp_y, count]]
    +        temp_x += 100
    +        count += 1
    +        if temp_x > 1280 - (165 + 50)
    +          temp_x = 165
    +          temp_y -= 75
    +        end
    +      end
    +    end
       end
     
    -  if args.keyboard.key_down.down! && decision[:option_three] # if down key pressed and option three exists
    -    args.state.decision_id = decision[:option_three][:decision] # value of option three's decision hash key is set to decision_id
    +  def start_of_tongue x = nil, y = nil
    +    x ||= state.x
    +    y ||= state.y
    +    [
    +      x + state.player_width.half,
    +      y + state.player_height.half
    +    ]
       end
     
    -  if args.keyboard.key_down.up! && decision[:option_four] # if up key pressed and option four exists
    -    args.state.decision_id = decision[:option_four][:decision] # value of option four's decision hash key is set to decision_id
    +  def stage_definition
    +    outputs.sprites << [vx(0), vy(0), vw(10000), vw(5875), 'sprites/level-map.png']
       end
    -end
     
    -# Uses decision_id's value to keep track of current decision being made
    -def current_decision args
    -  args.state.game_definition[:decisions][args.state.decision_id] || {} # either has value or is empty
    -end
    +  def render
    +    stage_definition
    +    start_of_tongue_render = [vx(start_of_tongue.x), vy(start_of_tongue.y)]
    +    end_of_tongue_render = [vx(end_of_tongue.x), vy(end_of_tongue.y)]
     
    -# Resets the game.
    -$gtk.reset
    +    if state.anchor_point
    +      anchor_point_render = [vx(state.anchor_point.x), vy(state.anchor_point.y)]
    +      outputs.sprites << { x: start_of_tongue_render.x,
    +                           y: start_of_tongue_render.y,
    +                           w: vw(2),
    +                           h: args.geometry.distance(start_of_tongue_render, anchor_point_render),
    +                           path:  'sprites/square-pink.png',
    +                           angle_anchor_y: 0,
    +                           angle: state.tongue_angle - 90 }
    +    else
    +      outputs.sprites << { x: vx(start_of_tongue.x),
    +                           y: vy(start_of_tongue.y),
    +                           w: vw(2),
    +                           h: vh(state.tongue_length),
    +                           path:  'sprites/square-pink.png',
    +                           angle_anchor_y: 0,
    +                           angle: state.tongue_angle - 90 }
    +    end
     
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/lowrez_simulator.rb

    -
    ###################################################################################
    -# YOU CAN PLAY AROUND WITH THE CODE BELOW, BUT USE CAUTION AS THIS IS WHAT EMULATES
    -# THE 64x64 CANVAS.
    -###################################################################################
    +    outputs.sprites << state.objects.map { |o| [vx(o.x), vy(o.y), vw(o.w), vh(o.h), o.path] }
     
    -TINY_RESOLUTION       = 64
    -TINY_SCALE            = 720.fdiv(TINY_RESOLUTION + 5)
    -CENTER_OFFSET         = 10
    -EMULATED_FONT_SIZE    = 20
    -EMULATED_FONT_X_ZERO  = 0
    -EMULATED_FONT_Y_ZERO  = 46
    +    if state.god_mode
    +      # SHOW HIDE COLLISIONS
    +      outputs.sprites << state.world.map do |x, y, w, h|
    +        x = vx(x)
    +        y = vy(y)
    +        if x > -80 && x < 1280 && y > -80 && y < 720
    +          {
    +            x: x,
    +            y: y,
    +            w: vw(w || state.tile_size),
    +            h: vh(h || state.tile_size),
    +            path: 'sprites/square-gray.png',
    +            a: 128
    +          }
    +        end
    +      end
    +    end
     
    -def tick args
    -  sprites = []
    -  labels = []
    -  borders = []
    -  solids = []
    -  mouse = emulate_lowrez_mouse args
    -  args.state.show_gridlines = false
    -  lowrez_tick args, sprites, labels, borders, solids, mouse
    -  render_gridlines_if_needed args
    -  render_mouse_crosshairs args, mouse
    -  emulate_lowrez_scene args, sprites, labels, borders, solids, mouse
    -end
    +    render_player
    +    outputs.sprites << [vx(2315), vy(45), vw(569), vh(402), 'sprites/square-blue.png', 0, 40]
     
    -def emulate_lowrez_mouse args
    -  args.state.new_entity_strict(:lowrez_mouse) do |m|
    -    m.x = args.mouse.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1
    -    m.y = args.mouse.y.idiv(TINY_SCALE)
    -    if args.mouse.click
    -      m.click = [
    -        args.mouse.click.point.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1,
    -        args.mouse.click.point.y.idiv(TINY_SCALE)
    -      ]
    -      m.down = m.click
    -    else
    -      m.click = nil
    -      m.down = nil
    -    end
    +    # Label in top left of the screen
    +    outputs.primitives << [20, 640, 180, 70, 255, 255, 255, 128].solid
    +    outputs.primitives << [30, 700, "Stuff: #{state.stuff_score} of #{$mugs.count}", 1].label
    +    outputs.primitives << [30, 670, "Time: #{"%.2f" % state.stuff_time}", 1].label
     
    -    if args.mouse.up
    -      m.up = [
    -        args.mouse.up.point.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1,
    -        args.mouse.up.point.y.idiv(TINY_SCALE)
    -      ]
    -    else
    -      m.up = nil
    -    end
    -  end
    -end
    +    if state.god_mode
    +      if state.map_saved_at > 0 && state.map_saved_at.elapsed_time < 120
    +        outputs.primitives << [920, 670, 'Map has been exported!', 1, 0, 50, 100, 50].label
    +      end
     
    -def render_mouse_crosshairs args, mouse
    -  return unless args.state.show_gridlines
    -  args.labels << [10, 25, "mouse: #{mouse.x} #{mouse.y}", 255, 255, 255]
    -end
     
    -def emulate_lowrez_scene args, sprites, labels, borders, solids, mouse
    -  args.render_target(:lowrez).solids  << [0, 0, 1280, 720]
    -  args.render_target(:lowrez).sprites << sprites
    -  args.render_target(:lowrez).borders << borders
    -  args.render_target(:lowrez).solids  << solids
    -  args.outputs.primitives << labels.map do |l|
    -    as_label = l.label
    -    l.text.each_char.each_with_index.map do |char, i|
    -      [CENTER_OFFSET + EMULATED_FONT_X_ZERO + (as_label.x * TINY_SCALE) + i * 5 * TINY_SCALE,
    -       EMULATED_FONT_Y_ZERO + (as_label.y * TINY_SCALE), char,
    -       EMULATED_FONT_SIZE, 0, as_label.r, as_label.g, as_label.b, as_label.a, 'fonts/dragonruby-gtk-4x4.ttf'].label
    +      # Creates sprite following mouse to help indicate which sprite you have selected
    +      outputs.primitives << [inputs.mouse.position.x, inputs.mouse.position.y,
    +                             state.tile_size, state.tile_size, 'sprites/square-indigo.png', 0, 100].sprite
         end
    +
    +    render_mini_map
    +    outputs.primitives << [0, 0, 1280, 720, 255, 255, 255, 255 * state.game_start_at.ease(60, :flip)].solid
       end
     
    -  args.sprites    << [CENTER_OFFSET, 0, 1280 * TINY_SCALE, 720 * TINY_SCALE, :lowrez]
    -end
    +  def render_mini_map
    +    x, y = 1170, 10
    +    outputs.primitives << [x, y, 100, 58, 0, 0, 0, 200].solid
    +    outputs.primitives << [x + args.state.x.fdiv(100) - 1, y + args.state.y.fdiv(100) - 1, 2, 2, 0, 255, 0].solid
    +    t_start = start_of_tongue
    +    t_end = end_of_tongue
    +    outputs.primitives << [
    +      x + t_start.x.fdiv(100), y + t_start.y.fdiv(100),
    +      x + t_end.x.fdiv(100), y + t_end.y.fdiv(100),
    +      255, 255, 255
    +    ].line
     
    -def render_gridlines_if_needed args
    -  if args.state.show_gridlines && args.static_lines.length == 0
    -    args.static_lines << 65.times.map do |i|
    -      [
    -        [CENTER_OFFSET + i * TINY_SCALE + 1,  0,
    -         CENTER_OFFSET + i * TINY_SCALE + 1,  720,                128, 128, 128],
    -        [CENTER_OFFSET + i * TINY_SCALE,      0,
    -         CENTER_OFFSET + i * TINY_SCALE,      720,                128, 128, 128],
    -        [CENTER_OFFSET,                       0 + i * TINY_SCALE,
    -         CENTER_OFFSET + 720,                 0 + i * TINY_SCALE, 128, 128, 128],
    -        [CENTER_OFFSET,                       1 + i * TINY_SCALE,
    -         CENTER_OFFSET + 720,                 1 + i * TINY_SCALE, 128, 128, 128]
    -      ]
    +    state.objects.each do |o|
    +      outputs.primitives << [x + o.x.fdiv(100) - 1, y + o.y.fdiv(100) - 1, 2, 2, 200, 200, 0].solid
         end
    -  elsif !args.state.show_gridlines
    -    args.static_lines.clear
       end
    -end
     
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/main.rb

    -
    require 'app/require.rb'
    +  def calc_camera percentage_override = nil
    +    percentage = percentage_override || (0.2 * state.camera_scale)
    +    target_scale = state.target_camera_scale
    +    distance_scale = target_scale - state.camera_scale
    +    state.camera_scale += distance_scale * percentage
     
    -def defaults args
    -  args.outputs.background_color = [0, 0, 0]
    -  args.state.last_story_line_text ||= ""
    -  args.state.scene_history ||= []
    -  args.state.storyline_history ||= []
    -  args.state.word_delay ||= 8
    -  if args.state.tick_count == 0
    -    args.gtk.stop_music
    -    args.outputs.sounds << 'sounds/static-loop.ogg'
    +    target_x = state.x * state.target_camera_scale
    +    target_y = state.y * state.target_camera_scale
    +
    +    distance_x = target_x - (state.camera_x + 640)
    +    distance_y = target_y - (state.camera_y + 360)
    +    state.camera_x += distance_x * percentage if distance_x.abs > 1
    +    state.camera_y += distance_y * percentage if distance_y.abs > 1
    +    state.camera_x = 0 if state.camera_x < 0
    +    state.camera_y = 0 if state.camera_y < 0
       end
     
    -  if args.state.storyline_history[-1] && args.state.storyline_queue_empty_at
    -    lines = args.state
    -                .storyline_history[-1]
    -                .gsub("-", "")
    -                .gsub("~", "")
    -                .wrapped_lines(55)
    +  def vx x
    +     (x * state.camera_scale) - state.camera_x
    +  end
     
    -    args.outputs.labels << multiple_lines(args, 690, 10 + lines.length * 25, lines, 0, 0)
    -  elsif !args.state.is_storyline_dialog_active
    -    args.outputs.labels << multiple_lines(args, 690, 55, "Use the arrow keys on your keyboard to move around. The GREEN boxes are important.".wrapped_lines(50))
    +  def vy y
    +    (y * state.camera_scale) - state.camera_y
       end
     
    -  return if args.state.current_scene
    -  set_scene(args, day_one_beginning(args))
    -end
    -
    -def inputs_move_player args
    -  if args.state.scene_changed_at.elapsed_time > 5
    -    if args.keyboard.down  || args.keyboard.s || args.keyboard.j
    -      args.state.player.y -= 0.25
    -    elsif args.keyboard.up || args.keyboard.w || args.keyboard.k
    -      args.state.player.y += 0.25
    -    end
    -
    -    if args.keyboard.left     || args.keyboard.a  || args.keyboard.h
    -      args.state.player.x -= 0.25
    -    elsif args.keyboard.right || args.keyboard.d  || args.keyboard.l
    -      args.state.player.x += 0.25
    -    end
    -
    -    args.state.player.y = 60 if args.state.player.y > 63
    -    args.state.player.y =  0 if args.state.player.y < -3
    -    args.state.player.x = 60 if args.state.player.x > 63
    -    args.state.player.x =  0 if args.state.player.x < -3
    +  def vw w
    +    w * state.camera_scale
       end
    -end
    -
    -def null_or_empty? ary
    -  return true unless ary
    -  return true if ary.length == 0
    -  return false
    -end
     
    -def calc_storyline_hotspot args
    -  hotspots = args.state.storylines.find_all do |hs|
    -    args.state.player.inside_rect?(hs.shift_rect(-2, 0))
    +  def vh h
    +    h * state.camera_scale
       end
     
    -  if !null_or_empty?(hotspots) && !args.state.inside_storyline_hotspot
    -    _, _, _, _, storyline = hotspots.first
    -    queue_storyline_text(args, storyline)
    -    args.state.inside_storyline_hotspot = true
    -  elsif null_or_empty?(hotspots)
    -    args.state.inside_storyline_hotspot = false
    +  def calc
    +    calc_camera
    +    calc_world_lookup
    +    calc_player
    +    calc_on_floor
    +    calc_score
       end
    -end
     
    -def calc_scenes args
    -  hotspots = args.state.scenes.find_all do |hs|
    -    args.state.player.inside_rect?(hs.shift_rect(-2, 0))
    +  def set_camera_scale v = nil
    +    return if v < 0.1
    +    state.target_camera_scale = v
       end
     
    -  if !null_or_empty?(hotspots) && !args.state.inside_scene_hotspot
    -    _, _, _, _, scene_method_or_hash = hotspots.first
    -    if scene_method_or_hash.is_a? Symbol
    -      set_scene(args, send(scene_method_or_hash, args))
    -      args.state.last_hotspot_scene = scene_method_or_hash
    -      args.state.scene_history << scene_method_or_hash
    -    else
    -      set_scene(args, scene_method_or_hash)
    +  def process_inputs_god_mode
    +    return unless state.god_mode
    +
    +    if inputs.keyboard.key_down.equal_sign || (inputs.keyboard.equal_sign && state.tick_count.mod_zero?(10))
    +      set_camera_scale state.camera_scale + 0.1
    +    elsif inputs.keyboard.key_down.hyphen || (inputs.keyboard.hyphen && state.tick_count.mod_zero?(10))
    +      set_camera_scale state.camera_scale - 0.1
    +    elsif inputs.keyboard.eight || inputs.keyboard.zero
    +      set_camera_scale 1
         end
    -    args.state.inside_scene_hotspot = true
    -  elsif null_or_empty?(hotspots)
    -    args.state.inside_scene_hotspot = false
    -  end
    -end
     
    -def null_or_whitespace? word
    -  return true if !word
    -  return true if word.strip.length == 0
    -  return false
    -end
    +    if input_up?
    +      state.y += 10
    +      state.dy = 0
    +    elsif input_down?
    +      state.y -= 10
    +      state.dy = 0
    +    end
     
    -def calc_storyline_presentation args
    -  return unless args.state.tick_count > args.state.next_storyline
    -  return unless args.state.scene_storyline_queue
    -  next_storyline = args.state.scene_storyline_queue.shift
    -  if null_or_whitespace? next_storyline
    -    args.state.storyline_queue_empty_at ||= args.state.tick_count
    -    args.state.is_storyline_dialog_active = false
    -    return
    +    if input_left?
    +      state.x -= 10
    +      state.dx = 0
    +    elsif input_right?
    +      state.x += 10
    +      state.dx = 0
    +    end
       end
    -  args.state.storyline_to_show = next_storyline
    -  args.state.is_storyline_dialog_active = true
    -  args.state.storyline_queue_empty_at = nil
    -  if next_storyline.end_with?(".") || next_storyline.end_with?("!") || next_storyline.end_with?("?") || next_storyline.end_with?("\"")
    -    args.state.next_storyline += 60
    -  elsif next_storyline.end_with?(",")
    -    args.state.next_storyline += 50
    -  elsif next_storyline.end_with?(":")
    -    args.state.next_storyline += 60
    -  else
    -    default_word_delay = 13 + args.state.word_delay - 8
    -    if next_storyline.gsub("-", "").gsub("~", "").length <= 4
    -      default_word_delay = 11 + args.state.word_delay - 8
    +
    +  def process_inputs
    +    if state.scene == :game
    +      process_inputs_player_movement
    +      process_inputs_god_mode
    +    elsif state.scene == :intro
    +      if args.inputs.keyboard.key_down.enter || args.inputs.keyboard.key_down.space
    +        if Kernel.tick_count < 600
    +          Kernel.tick_count = 600
    +        end
    +      end
         end
    -    number_of_syllabals = next_storyline.length - next_storyline.gsub("-", "").length
    -    args.state.next_storyline += default_word_delay + number_of_syllabals * (args.state.word_delay + 1)
       end
    -end
     
    -def inputs_reload_current_scene args
    -  return
    -  if args.inputs.keyboard.key_down.r!
    -    reload_current_scene
    +  def input_up?
    +    inputs.keyboard.w || inputs.keyboard.up || inputs.keyboard.k
       end
    -end
     
    -def inputs_dismiss_current_storyline args
    -  if args.inputs.keyboard.key_down.x!
    -    args.state.scene_storyline_queue.clear
    +  def input_up_released?
    +    inputs.keyboard.key_up.w ||
    +    inputs.keyboard.key_up.up ||
    +    inputs.keyboard.key_up.k
       end
    -end
     
    -def inputs_restart_game args
    -  if args.inputs.keyboard.exclamation_point
    -    args.gtk.reset_state
    +  def input_down?
    +    inputs.keyboard.s || inputs.keyboard.down || inputs.keyboard.j
       end
    -end
     
    -def inputs_change_word_delay args
    -  if args.inputs.keyboard.key_down.plus || args.inputs.keyboard.key_down.equal_sign
    -    args.state.word_delay -= 2
    -    if args.state.word_delay < 0
    -      args.state.word_delay = 0
    -      # queue_storyline_text args, "Text speed at MAXIMUM. Geez, how fast do you read?"
    -    else
    -      # queue_storyline_text args, "Text speed INCREASED."
    -    end
    +  def input_down_released?
    +    inputs.keyboard.key_up.s ||
    +    inputs.keyboard.key_up.down ||
    +    inputs.keyboard.key_up.j
       end
     
    -  if args.inputs.keyboard.key_down.hyphen || args.inputs.keyboard.key_down.underscore
    -    args.state.word_delay += 2
    -    # queue_storyline_text args, "Text speed DECREASED."
    +  def input_left?
    +    inputs.keyboard.a || inputs.keyboard.left || inputs.keyboard.h
       end
    -end
     
    -def multiple_lines args, x, y, texts, size = 0, minimum_alpha = nil
    -  texts.each_with_index.map do |t, i|
    -    [x, y - i * (25 + size * 2), t, size, 0, 255, 255, 255, adornments_alpha(args, 255, minimum_alpha)]
    +  def input_right?
    +    inputs.keyboard.d || inputs.keyboard.right || inputs.keyboard.l
       end
    -end
     
    -def lowrez_tick args, lowrez_sprites, lowrez_labels, lowrez_borders, lowrez_solids, lowrez_mouse
    -  # args.state.show_gridlines = true
    -  defaults args
    -  render_current_scene args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_solids << [0, 0, 64, 64, 0, 0, 0]
    -  calc_storyline_presentation args
    -  calc_scenes args
    -  calc_storyline_hotspot args
    -  inputs_move_player args
    -  inputs_print_mouse_rect args, lowrez_mouse
    -  inputs_reload_current_scene args
    -  inputs_dismiss_current_storyline args
    -  inputs_change_word_delay args
    -  inputs_restart_game args
    -  if !args.state.storyline_queue_empty_at
    -    args.outputs.labels << multiple_lines(args, 690, 80,
    -                                          ["Press \"X\" on the keyboard to dismiss dialog.",
    -                                           "Press \"+\" on the keyboard to INCREASE text speed.",
    -                                           "Press \"-\" on the keyboard to DECREASE text speed."], 0, 255)
    +  def set_object path, w, h
    +    state.object = path
    +    state.object_w = w
    +    state.object_h = h
       end
    -end
     
    -def inputs_print_mouse_rect args, lowrez_mouse
    -  if lowrez_mouse.click
    -    if args.state.previous_mouse_click
    -      dx = lowrez_mouse.click.x - args.state.previous_mouse_click.x
    -      dy = lowrez_mouse.click.y - args.state.previous_mouse_click.y
    -      x, y, w, h = args.state.previous_mouse_click.x, args.state.previous_mouse_click.y, dx, dy
    +  def collision_mode
    +    state.dev_action = :collision_mode
    +  end
     
    -      if dx < 0 && dx < 0
    -        x = x + w
    -        w = w.abs
    -        y = y + h
    -        h = h.abs
    -      end
    +  def process_inputs_player_movement
    +    if inputs.keyboard.key_down.g
    +      state.god_mode = !state.god_mode
    +      puts state.god_mode
    +    end
     
    -      w += 1
    -      h += 1
    +    if inputs.keyboard.key_down.u && state.dev_action == :collision_mode
    +      state.world = state.world[0..-2]
    +      state.world_lookup = {}
    +    end
     
    -      puts [x, y, w, h]
    -      args.state.previous_mouse_click = nil
    -    else
    -      args.state.previous_mouse_click = lowrez_mouse.click
    -      square_x, square_y = lowrez_mouse.click
    -      puts [square_x, square_y]
    -      8.map_with_index do |i|
    -        puts [square_x - i + 1, square_y - i + 1, i + 1, i + 1]
    -      end
    +    if inputs.keyboard.key_down.space && !state.anchor_point
    +      state.tongue_length = 0
    +      state.action = :shooting
    +      outputs.sounds << 'sounds/shooting.wav'
    +    elsif inputs.keyboard.key_down.space
    +      state.action = :aiming
    +      state.anchor_point  = nil
    +      state.tongue_length = 100
         end
     
    -  end
    -end
    +    if state.anchor_point
    +      if input_up?
    +        if state.tongue_length >= 105
    +          state.tongue_length -= 5
    +          state.dy += 0.8
    +        end
    +      elsif input_down?
    +        state.tongue_length += 5
    +        state.dy -= 0.8
    +      end
     
    -def try_centering! word
    -  word ||= ""
    -  just_word = word.gsub("-", "").gsub(",", "").gsub(".", "").gsub("'", "").gsub('""', "\"-\"")
    -  return word if just_word.strip.length == 0
    -  return word if just_word.include? "~"
    -  return "~#{word}" if just_word.length <= 2
    -  if just_word.length.mod_zero? 2
    -    center_index = just_word.length.idiv(2) - 1
    -  else
    -    center_index = (just_word.length - 1).idiv(2)
    -  end
    -  return "#{word[0..center_index - 1]}~#{word[center_index]}#{word[center_index + 1..-1]}"
    -end
    -
    -def queue_storyline args, scene
    -  queue_storyline_text args, scene[:storyline]
    -end
    -
    -def queue_storyline_text args, text
    -  args.state.last_story_line_text = text
    -  args.state.storyline_history << text if text
    -  words = (text || "").split(" ")
    -  words = words.map { |w| try_centering! w }
    -  args.state.scene_storyline_queue = words
    -  if args.state.scene_storyline_queue.length != 0
    -    args.state.scene_storyline_queue.unshift "~$--"
    -    args.state.storyline_to_show = "~."
    -  else
    -    args.state.storyline_to_show = ""
    +      if input_left? && state.dx > 1
    +        state.dx *= 0.98
    +      elsif input_left? && state.dx < -1
    +        state.dx *= 1.03
    +      elsif input_left? && !state.on_floor
    +        state.dx -= 3
    +      elsif input_right? && state.dx > 1
    +        state.dx *= 1.03
    +      elsif input_right? && state.dx < -1
    +        state.dx *= 0.98
    +      elsif input_right? && !state.on_floor
    +        state.dx += 3
    +      end
    +    else
    +      if input_left?
    +        state.tongue_angle += 1.5
    +        state.tongue_angle = state.tongue_angle
    +      elsif input_right?
    +        state.tongue_angle -= 1.5
    +        state.tongue_angle = state.tongue_angle
    +      end
    +    end
       end
    -  args.state.scene_storyline_queue << ""
    -  args.state.next_storyline = args.state.tick_count
    -end
     
    -def set_scene args, scene
    -  args.state.current_scene = scene
    -  args.state.background = scene[:background] ||  'sprites/todo.png'
    -  args.state.scene_fade = scene[:fade] || 0
    -  args.state.scenes = (scene[:scenes] || []).reject { |s| !s }
    -  args.state.scene_render_override = scene[:render_override]
    -  args.state.storylines = (scene[:storylines] || []).reject { |s| !s }
    -  args.state.scene_changed_at = args.state.tick_count
    -  if scene[:player]
    -    args.state.player = scene[:player]
    +  def add_floors
    +    # floors
    +    state.world += [
    +      [0,       0, 10000, 40],
    +      [0,    1670, 3250, 60],
    +      [6691, 1653, 3290, 60],
    +      [1521, 3792, 7370, 60],
    +      [0, 5137, 3290, 60]
    +    ]
       end
    -  args.state.inside_scene_hotspot = false
    -  args.state.inside_storyline_hotspot = false
    -  queue_storyline args, scene
    -end
     
    -def replay_storyline_rect
    -  [26, -1, 7, 4]
    -end
    +  def attempt_load_world_from_file
    +    return if state.world
    +    # exported_world = gtk.read_file(MAP_FILE_PATH)
    +    state.world = []
    +    state.objects = []
     
    -def labels_for_word word
    -  left_side_of_word = ""
    -  center_letter = ""
    -  right_side_of_word = ""
    +    if $collisions
    +      $collisions.map do |x, y, w, h|
    +        state.world << [x, y, w, h]
    +      end
     
    -  if word[0] == "~"
    -    left_side_of_word = ""
    -    center_letter = word[1]
    -    right_side_of_word = word[2..-1]
    -  elsif word.length > 0
    -    left_side_of_word, right_side_of_word = word.split("~")
    -    center_letter = right_side_of_word[0]
    -    right_side_of_word = right_side_of_word[1..-1]
    +      add_floors
    +    # elsif exported_world
    +    #   exported_world.each_line.map do |l|
    +    #     tokens = l.strip.split(',')
    +    #     x    = tokens[0].to_i
    +    #     y    = tokens[1].to_i
    +    #     type = tokens[2].to_i
    +    #     if type == 1
    +    #       state.world << [x, y, state.tile_size, state.tile_size]
    +    #     elsif type == 2
    +    #       w, h, path = tokens[3..-1]
    +    #       state.objects << [x, y, w.to_i, h.to_i, path]
    +    #     end
    +    #   end
    +
    +    #   add_floors
    +    end
    +
    +    if $mugs
    +      $mugs.map do |x, y, w, h, path|
    +        state.objects << [x, y, w, h, path]
    +      end
    +    end
       end
     
    -  right_side_of_word = right_side_of_word.gsub("-", "")
    +  def calc_world_lookup
    +    if state.tile_size != state.previous_tile_size
    +      state.previous_tile_size = state.tile_size
    +      state.world_lookup = {}
    +    end
     
    -  {
    -    left:   [29 - left_side_of_word.length * 4 - 1 * left_side_of_word.length, 2, left_side_of_word],
    -    center: [29, 2, center_letter, 255, 0, 0],
    -    right:  [34, 2, right_side_of_word]
    -  }
    -end
    +    return if state.world_lookup.keys.length > 0
    +    return unless state.world.length > 0
     
    -def render_scenes args, lowrez_sprites
    -  lowrez_sprites << args.state.scenes.flat_map do |hs|
    -    hotspot_square args, hs.x, hs.y, hs.w, hs.h
    -  end
    -end
    +    # Searches through the world and finds the cordinates that exist
    +    state.world_lookup = {}
    +    state.world.each do |x, y, w, h|
    +      state.world_lookup[[x, y, w || state.tile_size, h || state.tile_size]] = true
    +    end
    +
    +    # Assigns collision rects for every sprite drawn
    +    state.world_collision_rects =
    +      state.world_lookup
    +           .keys
    +           .map do |x, y, w, h|
    +             s = state.tile_size
    +             w ||= s
    +             h ||= s
    +             {
    +               args:       [x, y, w, h],
    +               left_right: [x,     y + 4, w,     h - 6],
    +               top:        [x + 4, y + 6, w - 8, h - 6],
    +               bottom:     [x + 1, y - 1, w - 2, h - 8],
    +             }
    +           end
     
    -def render_storylines args, lowrez_sprites
    -  lowrez_sprites << args.state.storylines.flat_map do |hs|
    -    hotspot_square args, hs.x, hs.y, hs.w, hs.h
       end
    -end
     
    -def adornments_alpha args, target_alpha = nil, minimum_alpha = nil
    -  return (minimum_alpha || 80) unless args.state.storyline_queue_empty_at
    -  target_alpha ||= 255
    -  target_alpha * args.state.storyline_queue_empty_at.ease(60)
    -end
    +  def calc_pendulum
    +    return if !state.anchor_point
    +    target_x = state.anchor_point.x - start_of_tongue.x
    +    target_y = state.anchor_point.y -
    +               state.tongue_length - 5 - 20 - state.player_height
     
    -def hotspot_square args, x, y, w, h
    -  if w >= 3 && h >= 3
    -    [
    -      [x + w.idiv(2) + 1, y, w.idiv(2), h, 'sprites/label-background.png', 0, adornments_alpha(args, 50), 23, 23, 23],
    -      [x, y, w.idiv(2), h, 'sprites/label-background.png', 0, adornments_alpha(args, 100), 223, 223, 223],
    -      [x + 1, y + 1, w - 2, h - 2, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 40, 140, 40],
    -    ]
    -  else
    -    [
    -      [x, y, w, h, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 0, 140, 0],
    -    ]
    -  end
    -end
    +    diff_y = state.y - target_y
     
    -def render_storyline_dialog args, lowrez_labels, lowrez_sprites
    -  return unless args.state.is_storyline_dialog_active
    -  return unless args.state.storyline_to_show
    -  labels = labels_for_word args.state.storyline_to_show
    -  if true # high rez version
    -    scale = 8.88
    -    offset = 45
    -    size = 25
    -    args.outputs.labels << [offset + labels[:left].x.-(1) * scale,
    -                            labels[:left].y * TINY_SCALE + 55,
    -                            labels[:left].text, size, 0, 0, 0, 0, 255,
    -                            'fonts/manaspc.ttf']
    -    center_text = labels[:center].text
    -    center_text = "|" if center_text == "$"
    -    args.outputs.labels << [offset + labels[:center].x * scale,
    -                            labels[:center].y * TINY_SCALE + 55,
    -                            center_text, size, 0, 255, 0, 0, 255,
    -                            'fonts/manaspc.ttf']
    -    args.outputs.labels << [offset + labels[:right].x * scale,
    -                            labels[:right].y * TINY_SCALE + 55,
    -                            labels[:right].text, size, 0, 0, 0, 0, 255,
    -                            'fonts/manaspc.ttf']
    -  else
    -    lowrez_labels << labels[:left]
    -    lowrez_labels << labels[:center]
    -    lowrez_labels << labels[:right]
    -  end
    -  args.state.is_storyline_dialog_active = true
    -  render_player args, lowrez_sprites
    -  lowrez_sprites <<  [0, 0, 64, 8, 'sprites/label-background.png']
    -end
    +    if target_x > 0
    +      state.dx += 0.6
    +    elsif target_x < 0
    +      state.dx -= 0.6
    +    end
     
    -def render_player args, lowrez_sprites
    -  lowrez_sprites << player_md_down(args, *args.state.player)
    -end
    +    if diff_y > 0
    +      state.dy -= 0.1
    +    elsif diff_y < 0
    +      state.dy += 0.1
    +    end
     
    -def render_adornments args, lowrez_sprites
    -  render_scenes args, lowrez_sprites
    -  render_storylines args, lowrez_sprites
    -  return if args.state.is_storyline_dialog_active
    -  lowrez_sprites << player_md_down(args, *args.state.player)
    -end
    +    state.dx *= 0.99
     
    -def global_alpha_percentage args, max_alpha = 255
    -  return 255 unless args.state.scene_changed_at
    -  return 255 unless args.state.scene_fade
    -  return 255 unless args.state.scene_fade > 0
    -  return max_alpha * args.state.scene_changed_at.ease(args.state.scene_fade)
    -end
    +    if state.dy.abs < 2
    +      state.dy *= 0.8
    +    else
    +      state.dy *= 0.90
    +    end
     
    -def render_current_scene args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [0, 0, 64, 64, args.state.background, 0, (global_alpha_percentage args)]
    -  if args.state.scene_render_override
    -    send args.state.scene_render_override, args, lowrez_sprites, lowrez_labels, lowrez_solids
    +    if state.tongue_length && state.y
    +      state.dy += state.tongue_angle.vector_y state.tongue_length.fdiv(1000)
    +    end
       end
    -  storyline_to_show = args.state.storyline_to_show || ""
    -  render_adornments args, lowrez_sprites
    -  render_storyline_dialog args, lowrez_labels, lowrez_sprites
     
    -  if args.state.background == 'sprites/tribute-game-over.png'
    -    lowrez_sprites << [0, 0, 64, 11, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 0, 0, 0]
    -    lowrez_labels << [9, 6, 'Return of', 255, 255, 255]
    -    lowrez_labels << [9, 1, ' Serenity', 255, 255, 255]
    -    if !args.state.ended
    -      args.gtk.stop_music
    -      args.outputs.sounds << 'sounds/music-loop.ogg'
    -      args.state.ended = true
    -    end
    +  def calc_tongue_angle
    +    return unless state.anchor_point
    +    state.tongue_angle = args.geometry.angle_from state.anchor_point, start_of_tongue
    +    state.tongue_length = args.geometry.distance(start_of_tongue, state.anchor_point)
    +    state.tongue_length = state.tongue_length.greater(100)
       end
    -end
     
    -def player_md_right args, x, y
    -  [x, y, 4, 11, 'sprites/player-right.png', 0, (global_alpha_percentage args)]
    -end
    +  def player_from_end_of_tongue
    +    p = state.tongue_angle.vector(state.tongue_length)
    +    derived_start = [state.anchor_point.x - p.x, state.anchor_point.y - p.y]
    +    derived_start.x -= state.player_width.half
    +    derived_start.y -= state.player_height.half
    +    derived_start
    +  end
     
    -def player_md_left args, x, y
    -  [x, y, 4, 11, 'sprites/player-left.png', 0, (global_alpha_percentage args)]
    -end
    +  def end_of_tongue
    +    p = state.tongue_angle.vector(state.tongue_length)
    +    [start_of_tongue.x + p.x, start_of_tongue.y + p.y]
    +  end
     
    -def player_md_up args, x, y
    -  [x, y, 4, 11, 'sprites/player-up.png', 0, (global_alpha_percentage args)]
    -end
    +  def calc_shooting
    +    return unless state.action == :shooting
    +    state.tongue_length += 30
    +    potential_anchor = end_of_tongue
    +    if potential_anchor.x <= 0
    +      state.anchor_point = potential_anchor
    +      state.action = :anchored
    +      outputs.sounds << 'sounds/attached.wav'
    +    elsif potential_anchor.x >= 10000
    +      state.anchor_point = potential_anchor
    +      state.action = :anchored
    +      outputs.sounds << 'sounds/attached.wav'
    +    elsif potential_anchor.y <= 0
    +      state.anchor_point = potential_anchor
    +      state.action = :anchored
    +      outputs.sounds << 'sounds/attached.wav'
    +    elsif potential_anchor.y >= 5875
    +      state.anchor_point = potential_anchor
    +      state.action = :anchored
    +      outputs.sounds << 'sounds/attached.wav'
    +    else
    +      anchor_rect = [potential_anchor.x - 5, potential_anchor.y - 5, 10, 10]
    +      collision = state.world_collision_rects.find_all do |v|
    +        [v[:args].x, v[:args].y, v[:args].w, v[:args].h].intersect_rect?(anchor_rect)
    +      end.first
    +      if collision
    +        state.anchor_point = potential_anchor
    +        state.action = :anchored
    +      outputs.sounds << 'sounds/attached.wav'
    +      end
    +    end
    +  end
     
    -def player_md_down args, x, y
    -  [x, y, 4, 11, 'sprites/player-down.png', 0, (global_alpha_percentage args)]
    -end
    +  def calc_player
    +    calc_shooting
    +    if !state.god_mode
    +      state.dy += state.gravity  # Since acceleration is the change in velocity, the change in y (dy) increases every frame
    +      state.dx += state.dx * state.air
    +    end
    +    calc_pendulum
    +    calc_box_collision
    +    calc_edge_collision
    +    if !state.god_mode
    +      state.y  += state.dy
    +      state.x  += state.dx
    +    end
    +    calc_tongue_angle
    +  end
     
    -def player_sm args, x, y
    -  [x, y, 3, 7, 'sprites/player-zoomed-out.png', 0, (global_alpha_percentage args)]
    -end
    +  def calc_box_collision
    +    return unless state.world_lookup.keys.length > 0
    +    collision_floor
    +    collision_left
    +    collision_right
    +    collision_ceiling
    +  end
     
    -def player_xs args, x, y
    -  [x, y, 1, 4, 'sprites/player-zoomed-out.png', 0, (global_alpha_percentage args)]
    -end
    +  def calc_edge_collision
    +    # Ensures that player doesn't fall below the map
    +    if next_y < 0 && state.dy < 0
    +      state.y = 0
    +      state.dy = state.dy.abs * 0.8
    +      state.collision_on_y = true
    +    # Ensures player doesn't go insanely high
    +    elsif next_y > 5875 - state.tile_size && state.dy > 0
    +      state.y = 5875 - state.tile_size
    +      state.dy = state.dy.abs * 0.8 * -1
    +      state.collision_on_y = true
    +    end
     
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/repl.rb

    -
    puts $gtk.args.state.current_scene
    +    # Ensures that player remains in the horizontal range its supposed to
    +    if state.x >= 10000 - state.tile_size && state.dx > 0
    +      state.x = 10000 - state.tile_size
    +      state.dx = state.dx.abs * 0.8 * -1
    +      state.collision_on_x = true
    +    elsif state.x <= 0 && state.dx < 0
    +      state.x = 0
    +      state.dx = state.dx.abs * 0.8
    +      state.collision_on_x = true
    +    end
    +  end
     
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/require.rb

    -
    require 'app/lowrez_simulator.rb'
    -require 'app/storyline_day_one.rb'
    -require 'app/storyline_blinking_light.rb'
    -require 'app/storyline_serenity_introduction.rb'
    -require 'app/storyline_speed_of_light.rb'
    -require 'app/storyline_serenity_alive.rb'
    -require 'app/storyline_serenity_bio.rb'
    -require 'app/storyline_anka.rb'
    -require 'app/storyline_final_message.rb'
    -require 'app/storyline_final_decision.rb'
    -require 'app/storyline.rb'
    +  def next_y
    +    state.y + state.dy
    +  end
     
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline.rb

    -
    def hotspot_top
    -  [4, 61, 56, 3]
    -end
    +  def next_x
    +    if state.dx < 0
    +      return (state.x + state.dx) - (state.tile_size - state.player_width)
    +    else
    +      return (state.x + state.dx) + (state.tile_size - state.player_width)
    +    end
    +  end
     
    -def hotspot_bottom
    -  [4, 0, 56, 3]
    -end
    +  def collision_floor
    +    return unless state.dy <= 0
     
    -def hotspot_top_right
    -  [62, 35, 3, 25]
    -end
    +    player_rect = [state.x, next_y, state.tile_size, state.tile_size]
     
    -def hotspot_bottom_right
    -  [62, 0, 3, 25]
    -end
    +    # Runs through all the sprites on the field and determines if the player hits the bottom of sprite (hence "-0.1" above)
    +    floor_collisions = state.world_collision_rects
    +                         .find_all { |r| r[:top].intersect_rect?(player_rect, state.collision_tolerance) }
    +                         .first
     
    -def storyline_history_include? args, text
    -  args.state.storyline_history.any? { |s| s.gsub("-", "").gsub(" ", "").include? text.gsub("-", "").gsub(" ", "") }
    -end
    +    return unless floor_collisions
    +    state.y = floor_collisions[:top].top
    +    state.dy = state.dy.abs * 0.8
    +  end
     
    -def blinking_light_side_of_home_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [48, 44, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [49, 45, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [50, 46, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -end
    +  def collision_left
    +    return unless state.dx < 0
    +    player_rect = [next_x, state.y, state.tile_size, state.tile_size]
     
    -def blinking_light_mountain_pass_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [18, 47, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [19, 48, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [20, 49, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -end
    +    # Runs through all the sprites on the field and determines if the player hits the left side of sprite (hence "-0.1" above)
    +    left_side_collisions = state.world_collision_rects
    +                             .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
    +                             .first
     
    -def blinking_light_path_to_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [0, 26, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [1, 27, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [2, 28, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -end
    +    return unless left_side_collisions
    +    state.x = left_side_collisions[:left_right].right
    +    state.dx = state.dy.abs * 0.8
    +    state.collision_on_x = true
    +  end
     
    -def blinking_light_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [23, 59, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [24, 60, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [25, 61, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -end
    +  def collision_right
    +    return unless state.dx > 0
     
    -def blinking_light_inside_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    -  lowrez_sprites << [30, 30, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [31, 31, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -  lowrez_sprites << [32, 32, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    -end
    +    player_rect = [next_x, state.y, state.tile_size, state.tile_size]
    +    # Runs through all the sprites on the field and determines if the player hits the right side of sprite (hence "-0.1" above)
    +    right_side_collisions = state.world_collision_rects
    +                              .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
    +                              .first
     
    -def decision_graph context_message, context_action, context_result_one, context_result_two, context_result_three = [], context_result_four = []
    -  result_one_scene, result_one_label, result_one_text = context_result_one
    -  result_two_scene, result_two_label, result_two_text = context_result_two
    -  result_three_scene, result_three_label, result_three_text = context_result_three
    -  result_four_scene, result_four_label, result_four_text = context_result_four
    +    return unless right_side_collisions
    +    state.x = right_side_collisions[:left_right].left - state.tile_size
    +    state.dx = state.dx.abs * 0.8 * -1
    +    state.collision_on_x = true
    +  end
     
    -  top_level_hash = {
    -    background: 'sprites/decision.png',
    -    fade: 60,
    -    player: [20, 36],
    -    storylines: [ ],
    -    scenes: [ ]
    -  }
    +  def collision_ceiling
    +    return unless state.dy > 0
     
    -  confirmation_result_one_hash = {
    -    background: 'sprites/decision.png',
    -    scenes: [ ],
    -    storylines: [ ]
    -  }
    +    player_rect = [state.x, next_y, state.player_width, state.player_height]
     
    -  confirmation_result_two_hash = {
    -    background: 'sprites/decision.png',
    -    scenes: [ ],
    -    storylines: [ ]
    -  }
    +    # Runs through all the sprites on the field and determines if the player hits the ceiling of sprite (hence "+0.1" above)
    +    ceil_collisions = state.world_collision_rects
    +                        .find_all { |r| r[:bottom].intersect_rect?(player_rect, state.collision_tolerance) }
    +                        .first
     
    -  confirmation_result_three_hash = {
    -    background: 'sprites/decision.png',
    -    scenes: [ ],
    -    storylines: [ ]
    -  }
    +    return unless ceil_collisions
    +    state.y = ceil_collisions[:bottom].y - state.tile_size
    +    state.dy = state.dy.abs * 0.8 * -1
    +    state.collision_on_y = true
    +  end
     
    -  confirmation_result_four_hash = {
    -    background: 'sprites/decision.png',
    -    scenes: [ ],
    -    storylines: [ ]
    -  }
    -
    -  top_level_hash[:storylines] << [ 5, 35, 4, 4, context_message]
    -  top_level_hash[:storylines] << [20, 35, 4, 4, context_action]
    +  def to_coord point
    +    # Integer divides (idiv) point.x to turn into grid
    +    # Then, you can just multiply each integer by state.tile_size
    +    # later and huzzah. Grid coordinates
    +    [point.x.idiv(state.tile_size), point.y.idiv(state.tile_size)]
    +  end
     
    -  confirmation_result_one_hash[:scenes]       << [20, 35, 4, 4, top_level_hash]
    -  confirmation_result_one_hash[:scenes]       << [60, 50, 4, 4, result_one_scene]
    -  confirmation_result_one_hash[:storylines]   << [40, 50, 4, 4, "#{result_one_label}: \"#{result_one_text}\""]
    -  confirmation_result_one_hash[:scenes]       << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    -  confirmation_result_one_hash[:scenes]       << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    -  confirmation_result_one_hash[:scenes]       << [40, 20, 4, 4, confirmation_result_two_hash]
    +  def export_map
    +    export_string = state.world.map do |x, y|
    +      "#{x},#{y},1"
    +    end
    +    export_string += state.objects.map do |x, y, w, h, path|
    +      "#{x},#{y},2,#{w},#{h},#{path}"
    +    end
    +    gtk.write_file(MAP_FILE_PATH, export_string.join("\n"))
    +    state.map_saved_at = state.tick_count
    +  end
     
    -  confirmation_result_two_hash[:scenes]       << [20, 35, 4, 4, top_level_hash]
    -  confirmation_result_two_hash[:scenes]       << [40, 50, 4, 4, confirmation_result_one_hash]
    -  confirmation_result_two_hash[:scenes]       << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    -  confirmation_result_two_hash[:scenes]       << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    -  confirmation_result_two_hash[:scenes]       << [60, 20, 4, 4, result_two_scene]
    -  confirmation_result_two_hash[:storylines]   << [40, 20, 4, 4, "#{result_two_label}: \"#{result_two_text}\""]
    +  def inputs_export_stage
    +  end
     
    -  confirmation_result_three_hash[:scenes]     << [20, 35, 4, 4, top_level_hash]
    -  confirmation_result_three_hash[:scenes]     << [40, 50, 4, 4, confirmation_result_one_hash]
    -  confirmation_result_three_hash[:scenes]     << [40, 40, 4, 4, confirmation_result_four_hash]
    -  confirmation_result_three_hash[:scenes]     << [60, 30, 4, 4, result_three_scene]
    -  confirmation_result_three_hash[:storylines] << [40, 30, 4, 4, "#{result_three_label}: \"#{result_three_text}\""]
    -  confirmation_result_three_hash[:scenes]     << [40, 20, 4, 4, confirmation_result_two_hash]
    +  def calc_score
    +    return unless state.scene == :game
    +    player = [state.x, state.y, state.player_width, state.player_height]
    +    collected = state.objects.find_all { |s| s.intersect_rect? player }
    +    state.stuff_score += collected.length
    +    if collected.length > 0
    +      outputs.sounds << 'sounds/collectable.wav'
    +    end
    +    state.objects = state.objects.reject { |s| collected.include? s }
    +    state.stuff_time += 0.01
    +    if state.objects.length == 0
    +      if !state.stuff_best_time || state.stuff_time < state.stuff_best_time
    +        state.stuff_best_time = state.stuff_time
    +      end
    +      state.game_over_at = nil
    +      state.scene = :ending
    +    end
    +  end
     
    -  confirmation_result_four_hash[:scenes]      << [20, 35, 4, 4, top_level_hash]
    -  confirmation_result_four_hash[:scenes]      << [40, 50, 4, 4, confirmation_result_one_hash]
    -  confirmation_result_four_hash[:scenes]      << [60, 40, 4, 4, result_four_scene]
    -  confirmation_result_four_hash[:storylines]  << [40, 40, 4, 4, "#{result_four_label}: \"#{result_four_text}\""]
    -  confirmation_result_four_hash[:scenes]      << [40, 30, 4, 4, confirmation_result_three_hash]
    -  confirmation_result_four_hash[:scenes]      << [40, 20, 4, 4, confirmation_result_two_hash]
    +  def calc_on_floor
    +    if state.action == :anchored
    +      state.on_floor = false
    +      state.on_floor_debounce = 30
    +    else
    +      state.on_floor_debounce ||= 30
     
    -  top_level_hash[:scenes]     << [40, 50, 4, 4, confirmation_result_one_hash]
    -  top_level_hash[:scenes]     << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    -  top_level_hash[:scenes]     << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    -  top_level_hash[:scenes]     << [40, 20, 4, 4, confirmation_result_two_hash]
    +      if state.dy.round != 0
    +        state.on_floor_debounce = 30
    +        state.on_floor = false
    +      else
    +        state.on_floor_debounce -= 1
    +      end
     
    -  top_level_hash
    -end
    +      if state.on_floor_debounce <= 0
    +        state.on_floor_debounce = 0
    +        state.on_floor = true
    +      end
    +    end
    +  end
     
    -def ship_control_hotspot offset_x, offset_y, a, b, c, d
    -  results = []
    -  results << [ 6 + offset_x, 0 + offset_y, 4, 4, a]  if a
    -  results << [ 1 + offset_x, 5 + offset_y, 4, 4, b]  if b
    -  results << [ 6 + offset_x, 5 + offset_y, 4, 4, c]  if c
    -  results << [ 11 + offset_x, 5 + offset_y, 4, 4, d] if d
    -  results
    -end
    +  def render_player
    +    path = "sprites/square-green.png"
    +    angle = 0
    +    # outputs.labels << [vx(state.x), vy(state.y) - 30, "dy: #{state.dy.round}"]
    +    if state.action == :idle
    +      # outputs.labels << [vx(state.x), vy(state.y), "IDLE"]
    +      path = "sprites/square-green.png"
    +    elsif state.action == :aiming && !state.on_floor
    +      # outputs.labels << [vx(state.x), vy(state.y), "AIMING AIR BORN"]
    +      angle = state.tongue_angle - 90
    +      path = "sprites/square-green.png"
    +    elsif state.action == :aiming # ON THE GROUND
    +      # outputs.labels << [vx(state.x), vy(state.y), "AIMING GROUND"]
    +      path = "sprites/square-green.png"
    +    elsif state.action == :shooting && !state.on_floor
    +      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING AIR BORN"]
    +      path = "sprites/square-green.png"
    +      angle = state.tongue_angle - 90
    +    elsif state.action == :shooting
    +      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING ON GROUND"]
    +      path = "sprites/square-green.png"
    +    elsif state.action == :anchored
    +      # outputs.labels << [vx(state.x), vy(state.y), "SWINGING"]
    +      angle = state.tongue_angle - 90
    +      path = "sprites/square-green.png"
    +    end
     
    -def reload_current_scene
    -  if $gtk.args.state.last_hotspot_scene
    -    set_scene $gtk.args, send($gtk.args.state.last_hotspot_scene, $gtk.args)
    -    tick $gtk.args
    -  elsif respond_to? :set_scene
    -    set_scene $gtk.args, (replied_to_serenity_alive_firmly $gtk.args)
    -    tick $gtk.args
    +    outputs.sprites << [vx(state.x),
    +                        vy(state.y),
    +                        vw(state.player_width),
    +                        vh(state.player_height),
    +                        path,
    +                        angle]
       end
    -  $gtk.console.close
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_anka.rb

    -
    def anka_inside_room args
    -  {
    -    background: 'sprites/inside-home.png',
    -    player: [34, 35],
    -    storylines: [
    -      [34, 34, 4, 4, "Ahhhh!!! Oh god, it was just- a nightmare."],
    -    ],
    -    scenes: [
    -      [32, -1, 8, 3, :anka_observatory]
    -    ]
    -  }
    -end
     
    -def anka_observatory args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [51, 12],
    -    storylines: [
    -      [50, 10, 4, 4,   "Breathe, Hiro. Just see what's there... everything--- will- be okay."]
    -    ],
    -    scenes: [
    -      [30, 18, 5, 12, :anka_inside_mainframe]
    -    ],
    -    render_override: :blinking_light_inside_observatory_render
    -  }
    +  def render_player_old
    +    # Player
    +    if state.action == :aiming
    +      path = 'sprites\frg\idle\frog_idle.png'
    +      if state.dx > 2
    +	  #directional right sprite was here but i needa redo it
    +        path = 'sprites\frg\anchor\frog-anchor-0.png'
    +      #directional left sprite was here but i needa redo it
    +	  elsif state.dx < -2
    +        path = 'sprites\frg\anchor\frog-anchor-0.png'
    +      end
    +      outputs.sprites << [vx(state.x),
    +                          vy(state.y),
    +                          vw(state.player_width),
    +                          vh(state.player_height),
    +                          path,
    +                          (state.tongue_angle - 90)]
    +    elsif state.action == :anchored || state.action == :shooting
    +      outputs.sprites << [vx(state.x),
    +                          vy(state.y),
    +                          vw(state.player_width),
    +                          vw(state.player_height),
    +                          'sprites/animations_povfrog/frog_bwah_up.png',
    +                          (state.tongue_angle - 90)]
    +    end
    +  end
     end
     
    -def anka_inside_mainframe args
    -  {
    -    player: [32, 4],
    -    background: 'sprites/mainframe.png',
    -    fade: 60,
    -    storylines: [
    -      [22, 45, 17, 4, (anka_last_reply args)],
    -      [45, 45,  4, 4, (anka_current_reply args)],
    -    ],
    -    scenes: [
    -      [*hotspot_top_right, :reply_to_anka]
    -    ]
    -  }
    -end
     
    -def reply_to_anka args
    -  decision_graph anka_current_reply(args),
    -                 "Matthew's-- wife is doing-- well. What's-- even-- better-- is that he's-- a dad, and he didn't-- even-- know it. Should- I- leave- out the part about-- the crew- being-- in hibernation-- for 20-- years? They- should- enter-- statis-- on a high- note... Right?",
    -                 [:replied_with_whole_truth, "Whole-- Truth--", anka_reply_whole_truth],
    -                 [:replied_with_half_truth, "Half-- Truth--", anka_reply_half_truth]
    -end
    +$game = CleptoFrog.new
     
    -def anka_last_reply args
    -  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    -    return "Buffer--: #{serenity_alive_firm_reply.quote}"
    -  else
    -    return "Buffer--: #{serenity_alive_sugarcoated_reply.quote}"
    +def tick args
    +  if args.state.scene == :game
    +    tick_instructions args, "SPACE to SHOOT and RELEASE tongue. LEFT, RIGHT to SWING and BUILD momentum. MINIMAP in bottom right corner.", 360
       end
    +  $game.args = args
    +  $game.tick
     end
     
    -def anka_reply_whole_truth
    -  "Matthew's wife is doing-- very-- well. In fact, she was pregnant. Matthew-- is a dad. He has a son. But, I need- all-- of-- you-- to brace-- yourselves. You've-- been in statis-- for 20 years. A lot has changed. Most of Earth's-- population--- didn't-- survive. Tell- Matthew-- that I'm-- sorry he didn't-- get to see- his- son grow- up."
    -end
    -
    -def anka_reply_half_truth
    -  "Matthew's--- wife- is doing-- very-- well. In fact, she was pregnant. Matthew is a dad! It's a boy! Tell- Matthew-- congrats-- for me. Hope-- to see- all of you- soon."
    -end
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.keyboard.directional_vector || args.inputs.keyboard.key_down.space
    +    args.state.key_event_occurred = true
    +  end
     
    -def replied_with_whole_truth args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [[60, 0, 4, 32, :replied_to_anka_back_home]],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: #{anka_reply_whole_truth.quote}"],
    -      [30, 10, 5, 4, "I- hope- I- did the right- thing- by laying-- it all- out- there."],
    -    ]
    -  }
    -end
    -
    -def replied_with_half_truth args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [[60, 0, 4, 32, :replied_to_anka_back_home]],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: #{anka_reply_half_truth.quote}"],
    -      [30, 10, 5, 4, "I- hope- I- did the right- thing- by not giving-- them- the whole- truth."],
    -    ]
    -  }
    -end
    -
    -def anka_current_reply args
    -  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    -    return "Hello. This is, Aanka. Sasha-- is still- trying-- to gather-- her wits about-- her, given- the gravity--- of your- last- reply. Thank- you- for being-- honest, and thank- you- for the help- with the ship- diagnostics. I was able-- to retrieve-- all of the navigation--- information---- after-- the battery--- swap. We- are ready-- to head back to Earth. Before-- we go- back- into-- statis, Matthew--- wanted-- to know- how his- wife- is doing. Please- reply-- as soon- as you can. He's-- not going-- to get- into-- the statis-- chamber-- until-- he knows- his wife is okay."
    -  else
    -    return "Hello. This is, Aanka. Thank- you for the help- with the ship's-- diagnostics. I was able-- to retrieve-- all of the navigation--- information--- after-- the battery-- swap. I- know-- that- you didn't-- tell- the whole truth- about-- how far we are from- Earth. Don't-- worry. I understand-- why you did it. We- are ready-- to head back to Earth. Before-- we go- back- into-- statis, Matthew--- wanted-- to know- how his- wife- is doing. Please- reply-- as soon- as you can. He's-- not going-- to get- into-- the statis-- chamber-- until-- he knows- his wife is okay."
    -  end
    -end
    -
    -def replied_to_anka_back_home args
    -  if args.state.scene_history.include? :replied_with_whole_truth
    -    return {
    -      fade: 60,
    -      background: 'sprites/inside-home.png',
    -      player: [34, 4],
    -      storylines: [
    -        [34, 4, 4, 4, "I- hope-- this pit in my stomach-- is gone-- by tomorrow---."],
    -      ],
    -      scenes: [
    -        [30, 38, 12, 13, :final_message_sad],
    -      ]
    -    }
    -  else
    -    return {
    -      fade: 60,
    -      background: 'sprites/inside-home.png',
    -      player: [34, 4],
    -      storylines: [
    -        [34, 4, 4, 4, "I- get the feeling-- I'm going-- to sleep real well tonight--."],
    -      ],
    -      scenes: [
    -        [30, 38, 12, 13, :final_message_happy],
    -      ]
    -    }
    -  end
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_blinking_light.rb

    -
    def the_blinking_light args
    -  {
    -    fade: 60,
    -    background: 'sprites/side-of-home.png',
    -    player: [16, 13],
    -    scenes: [
    -      [52, 24, 11, 5, :blinking_light_mountain_pass],
    -    ],
    -    render_override: :blinking_light_side_of_home_render
    -  }
    -end
    -
    -def blinking_light_mountain_pass args
    -  {
    -    background: 'sprites/mountain-pass-zoomed-out.png',
    -    player: [4, 4],
    -    scenes: [
    -      [18, 47, 5, 5, :blinking_light_path_to_observatory]
    -    ],
    -    render_override: :blinking_light_mountain_pass_render
    -  }
    -end
    -
    -def blinking_light_path_to_observatory args
    -  {
    -    background: 'sprites/path-to-observatory.png',
    -    player: [60, 4],
    -    scenes: [
    -      [0, 26, 5, 5, :blinking_light_observatory]
    -    ],
    -    render_override: :blinking_light_path_to_observatory_render
    -  }
    -end
    -
    -def blinking_light_observatory args
    -  {
    -    background: 'sprites/observatory.png',
    -    player: [60, 2],
    -    scenes: [
    -      [28, 39, 4, 10, :blinking_light_inside_observatory]
    -    ],
    -    render_override: :blinking_light_observatory_render
    -  }
    -end
    -
    -def blinking_light_inside_observatory args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    player: [60, 2],
    -    storylines: [
    -      [50, 2, 4, 8,   "That's weird. I thought- this- mainframe-- was broken--."]
    -    ],
    -    scenes: [
    -      [30, 18, 5, 12, :blinking_light_inside_mainframe]
    -    ],
    -    render_override: :blinking_light_inside_observatory_render
    -  }
    -end
    -
    -def blinking_light_inside_mainframe args
    -  {
    -    background: 'sprites/mainframe.png',
    -    fade: 60,
    -    player: [30, 4],
    -    scenes: [
    -      [62, 32, 4, 32, :reply_to_introduction]
    -    ],
    -    storylines: [
    -      [43, 43,  8, 8, "\"Mission-- control--, your- main- comm-- channels-- seem-- to be down. My apologies-- for- using-- this low- level-- exploit--. What's-- going-- on down there? We are ready-- for reentry--.\" Message--- Timestamp---: 4- hours-- 23--- minutes-- ago--."],
    -      [30, 30,  4, 4, "There's-- a low- level-- message-- here... NANI.T.F?"],
    -      [14, 10, 24, 4, "Oh interesting---. This transistor--- needed-- to be activated--- for the- mainframe-- to work."],
    -      [14, 20, 24, 4, "What the heck activated--- this thing- though?"]
    -    ]
    -  }
    +  args.outputs.debug << [0, y - 50, 1280, 60].solid
    +  args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
    +  args.outputs.debug << [640, y - 25, "(SPACE to dismiss instructions)" , -2, 1, 255, 255, 255].label
     end
     
     
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_day_one.rb

    -
    def day_one_beginning args
    -  {
    -    background: 'sprites/side-of-home.png',
    -    player: [16, 13],
    -    scenes: [
    -      [0, 0, 64, 2, :day_one_infront_of_home],
    -    ],
    -    storylines: [
    -      [35, 10, 6, 6,  "Man. Hard to believe- that today- is the 20th--- anniversary-- of The Impact."]
    -    ]
    -  }
    -end
    -
    -def day_one_infront_of_home args
    -  {
    -    background: 'sprites/front-of-home.png',
    -    player: [56, 23],
    -    scenes: [
    -      [43, 34, 10, 16, :day_one_home],
    -      [62, 0,  3, 40, :day_one_beginning],
    -      [0, 4, 3, 20, :day_one_ceremony]
    -    ],
    -    storylines: [
    -      [40, 20, 4, 4, "It looks like everyone- is already- at the rememberance-- ceremony."],
    -    ]
    -  }
    -end
    -
    -def day_one_home args
    -  {
    -    background: 'sprites/inside-home.png',
    -    player: [34, 3],
    -    scenes: [
    -      [28, 0, 12, 2, :day_one_infront_of_home]
    -    ],
    -    storylines: [
    -      [
    -        38, 4, 4, 4, "My mansion- in all its glory! Okay yea, it's just a shipping- container-. Apparently-, it's nothing- like the luxuries- of the 2040's. But it's- all we have- in- this day and age. And it'll suffice."
    -      ],
    -      [
    -        28, 7, 4, 7,
    -        "Ahhh. My reading- couch. It's so comfortable--."
    -      ],
    -      [
    -        38, 21, 4, 4,
    -        "I'm- lucky- to have a computer--. I'm- one of the few people- with- the skills to put this- thing to good use."
    -      ],
    -      [
    -        45, 37, 4, 8,
    -        "This corner- of my home- is always- warmer-. It's cause of the ref~lected-- light- from the solar-- panels--, just on the other- side- of this wall. It's hard- to believe- there was o~nce-- an unlimited- amount- of electricity--."
    -      ],
    -      [
    -        32, 40, 8, 10,
    -        "This isn't- a good time- to sleep. I- should probably- head to the ceremony-."
    -      ],
    -      [
    -        25, 21, 5, 12,
    -        "Fifteen-- years- of computer-- science-- notes, neatly-- organized. Compiler--- Theory--, Linear--- Algebra---, Game-- Development---... Every-- subject-- imaginable--."
    -      ]
    -    ]
    -  }
    -end
    -
    -def day_one_ceremony args
    -  {
    -    background: 'sprites/tribute.png',
    -    player: [57, 21],
    -    scenes: [
    -      [62, 0, 2, 40, :day_one_infront_of_home],
    -      [0, 24, 2, 40, :day_one_infront_of_library]
    -    ],
    -    storylines: [
    -      [53, 12, 3,  8,  "It's- been twenty- years since The Impact. Twenty- years, since Halley's-- Comet-- set Earth's- blue- sky on fire."],
    -      [45, 12, 3,  8,  "The space mission- sent to prevent- Earth's- total- destruction--, was a success. Only- 99.9%------ of the world's- population-- died-- that day. Hey, it's- better-- than 100%---- of humanity-- dying."],
    -      [20, 12, 23, 4, "The monument--- reads:---- Here- stands- the tribute-- to Space- Mission-- Serenity--- and- its- crew. You- have- given-- humanity--- a second-- chance."],
    -      [15, 12, 3,  8, "Rest- in- peace--- Matthew----, Sasha----, Aanka----"],
    -    ]
    -  }
    -end
    -
    -def day_one_infront_of_library args
    -  {
    -    background: 'sprites/outside-library.png',
    -    player: [57, 21],
    -    scenes: [
    -      [62, 0, 2, 40, :day_one_ceremony],
    -      [49, 39, 6, 9, :day_one_library]
    -    ],
    -    storylines: [
    -      [50, 20, 4, 8,  "Shipping- containers-- as far- as the eye- can see. It's- rather- beautiful-- if you ask me. Even- though-- this- view- represents-- all- that's-- left- of humanity-."]
    -    ]
    -  }
    -end
    -
    -def day_one_library args
    -  {
    -    background: 'sprites/library.png',
    -    player: [27, 4],
    -    scenes: [
    -      [0, 0, 64, 2, :end_day_one_infront_of_library]
    -    ],
    -    storylines: [
    -      [28, 22, 8, 4,  "I grew- up- in this library. I've- read every- book- here. My favorites-- were- of course-- anything- computer-- related."],
    -      [6, 32, 10, 6, "My favorite-- area--- of the library. The Science-- Section."]
    -    ]
    -  }
    -end
    -
    -def end_day_one_infront_of_library args
    -  {
    -    background: 'sprites/outside-library.png',
    -    player: [51, 33],
    -    scenes: [
    -      [49, 39, 6, 9, :day_one_library],
    -      [62, 0, 2, 40, :end_day_one_monument],
    -    ],
    -    storylines: [
    -      [50, 27, 4, 4, "It's getting late. Better get some sleep."]
    -    ]
    -  }
    -end
    -
    -def end_day_one_monument args
    -  {
    -    background: 'sprites/tribute.png',
    -    player: [2, 36],
    -    scenes: [
    -      [62, 0, 2, 40, :end_day_one_infront_of_home],
    -    ],
    -    storylines: [
    -      [50, 27, 4, 4, "It's getting late. Better get some sleep."],
    -    ]
    -  }
    -end
    -
    -def end_day_one_infront_of_home args
    -  {
    -    background: 'sprites/front-of-home.png',
    -    player: [1, 17],
    -    scenes: [
    -      [43, 34, 10, 16, :end_day_one_home],
    -    ],
    -    storylines: [
    -      [20, 10, 4, 4, "It's getting late. Better get some sleep."],
    -    ]
    -  }
    -end
    -
    -def end_day_one_home args
    -  {
    -    background: 'sprites/inside-home.png',
    -    player: [34, 3],
    -    scenes: [
    -      [32, 40, 8, 10, :end_day_one_dream],
    -    ],
    -    storylines: [
    -      [38, 4, 4, 4, "It's getting late. Better get some sleep."],
    -    ]
    -  }
    -end
    -
    -def end_day_one_dream args
    -  {
    -    background: 'sprites/dream.png',
    -    fade: 60,
    -    player: [4, 4],
    -    scenes: [
    -      [62, 0, 2, 64, :explaining_the_special_power]
    -    ],
    -    storylines: [
    -      [10, 10, 4, 4, "Why- does this- moment-- always- haunt- my dreams?"],
    -      [20, 10, 4, 4, "This kid- reads these computer--- science--- books- nonstop-. What's- wrong with him?"],
    -      [30, 10, 4, 4, "There- is nothing-- wrong- with him. This behavior-- should be encouraged---! In fact-, I think- he's- special---. Have- you seen- him use- a computer---? It's-- almost-- as if he can- speak-- to it."]
    -    ]
    -  }
    -end
    -
    -def explaining_the_special_power args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    player: [32, 30],
    -    scenes: [
    -      [
    -        38, 21, 4, 4, :explaining_the_special_power_inside_computer
    -      ],
    -    ]
    -  }
    -end
    -
    -def explaining_the_special_power_inside_computer args
    -  {
    -    background: 'sprites/pc.png',
    -    fade: 60,
    -    player: [34, 4],
    -    scenes: [
    -      [0, 62, 64, 3, :the_blinking_light]
    -    ],
    -    storylines: [
    -      [14, 20, 24, 4, "So... I have a special-- power--. I don't-- need a mouse-, keyboard--, or even-- a monitor--- to control-- a computer--."],
    -      [14, 25, 24, 4, "I only-- pretend-- to use peripherals---, so as not- to freak- anyone--- out."],
    -      [14, 30, 24, 4, "Inside-- this silicon--- Universe---, is the only-- place I- feel- at peace."],
    -      [14, 35, 24, 4, "It's-- the only-- place where I don't-- feel alone."]
    -    ]
    -  }
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_final_decision.rb

    -
    def final_decision_side_of_home args
    -  {
    -    fade: 120,
    -    background: 'sprites/side-of-home.png',
    -    player: [16, 13],
    -    scenes: [
    -      [52, 24, 11, 5, :final_decision_mountain_pass],
    -    ],
    -    render_override: :blinking_light_side_of_home_render,
    -    storylines: [
    -      [28, 13, 8, 4,  "Man. Hard to believe- that today- is the 21st--- anniversary-- of The Impact. Serenity--- will- be- home- soon."]
    -    ]
    -  }
    -end
    -
    -def final_decision_mountain_pass args
    -  {
    -    background: 'sprites/mountain-pass-zoomed-out.png',
    -    player: [4, 4],
    -    scenes: [
    -      [18, 47, 5, 5, :final_decision_path_to_observatory]
    -    ],
    -    render_override: :blinking_light_mountain_pass_render
    -  }
    -end
    -
    -def final_decision_path_to_observatory args
    -  {
    -    background: 'sprites/path-to-observatory.png',
    -    player: [60, 4],
    -    scenes: [
    -      [0, 26, 5, 5, :final_decision_observatory]
    -    ],
    -    render_override: :blinking_light_path_to_observatory_render
    -  }
    -end
    -
    -def final_decision_observatory args
    -  {
    -    background: 'sprites/observatory.png',
    -    player: [60, 2],
    -    scenes: [
    -      [28, 39, 4, 10, :final_decision_inside_observatory]
    -    ],
    -    render_override: :blinking_light_observatory_render
    -  }
    -end
    -
    -def final_decision_inside_observatory args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    player: [60, 2],
    -    storylines: [],
    -    scenes: [
    -      [30, 18, 5, 12, :final_decision_inside_mainframe]
    -    ],
    -    render_override: :blinking_light_inside_observatory_render
    -  }
    -end
    -
    -def final_decision_inside_mainframe args
    -  {
    -    player: [32, 4],
    -    background: 'sprites/mainframe.png',
    -    storylines: [],
    -    scenes: [
    -      [*hotspot_top, :final_decision_ship_status],
    -    ]
    -  }
    -end
    -
    -def final_decision_ship_status args
    -  {
    -    background: 'sprites/serenity.png',
    -    fade: 60,
    -    player: [30, 10],
    -    scenes: [
    -      [*hotspot_top_right, :final_decision]
    -    ],
    -    storylines: [
    -      [30,  8, 4, 4, "????"],
    -      *final_decision_ship_status_shared(args)
    -    ]
    -  }
    -end
    -
    -def final_decision args
    -  decision_graph  "Stasis-- Chambers--: UNDERPOWERED, Life- forms-- will be terminated---- unless-- equilibrium----- is reached.",
    -                  "I CAN'T DO THIS... But... If-- I-- don't--- bring-- the- chambers--- to- equilibrium-----, they all die...",
    -                  [:final_decision_game_over_noone, "Kill--- Everyone---", "DO--- NOTHING?"],
    -                  [:final_decision_game_over_matthew, "Kill--- Sasha---", "KILL--- SASHA?"],
    -                  [:final_decision_game_over_anka, "Kill--- Aanka---", "KILL--- AANKA?"],
    -                  [:final_decision_game_over_sasha, "Kill--- Matthew---", "KILL--- MATTHEW?"]
    -end
    -
    -def final_decision_game_over_noone args
    -  {
    -    background: 'sprites/tribute-game-over.png',
    -    player: [53, 14],
    -    fade: 600
    -  }
    -end
    -
    -def final_decision_game_over_matthew args
    -  {
    -    background: 'sprites/tribute-game-over.png',
    -    player: [53, 14],
    -    fade: 600
    -  }
    -end
    -
    -def final_decision_game_over_anka args
    -  {
    -    background: 'sprites/tribute-game-over.png',
    -    player: [53, 14],
    -    fade: 600
    -  }
    -end
    -
    -def final_decision_game_over_sasha args
    -  {
    -    background: 'sprites/tribute-game-over.png',
    -    player: [53, 14],
    -    fade: 600
    -  }
    -end
    -
    -def final_decision_ship_status_shared args
    -  [
    -    *ship_control_hotspot(24, 22,
    -                           "Stasis-- Chambers--: UNDERPOWERED, Life- forms-- will be terminated---- unless-- equilibrium----- is reached. WHAT?! NO!",
    -                           "Matthew's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION. WHAT?! NO!",
    -                           "Aanka's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION.  WHAT?! NO!",
    -                           "Sasha's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION. WHAT?! NO!"),
    -  ]
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_final_message.rb

    -
    def final_message_sad args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    player: [34, 35],
    -    storylines: [
    -      [34, 34, 4, 4, "Another-- sleepless-- night..."],
    -    ],
    -    scenes: [
    -      [32, -1, 8, 3, :final_message_observatory]
    -    ]
    -  }
    -end
    -
    -def final_message_happy args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    player: [34, 35],
    -    storylines: [
    -      [34, 34, 4, 4, "Oh man, I slept like rock!"],
    -    ],
    -    scenes: [
    -      [32, -1, 8, 3, :final_message_observatory]
    -    ]
    -  }
    -end
    -
    -def final_message_side_of_home args
    -  {
    -    fade: 60,
    -    background: 'sprites/side-of-home.png',
    -    player: [16, 13],
    -    scenes: [
    -      [52, 24, 11, 5, :final_message_mountain_pass],
    -    ],
    -    render_override: :blinking_light_side_of_home_render
    -  }
    -end
    -
    -def final_message_mountain_pass args
    -  {
    -    background: 'sprites/mountain-pass-zoomed-out.png',
    -    player: [4, 4],
    -    scenes: [
    -      [18, 47, 5, 5, :final_message_path_to_observatory],
    -    ],
    -    storylines: [
    -      [18, 13, 5, 5, "Hnnnnnnnggg. My legs-- are still sore- from yesterday."]
    -    ],
    -    render_override: :blinking_light_mountain_pass_render
    -  }
    -end
    -
    -def final_message_path_to_observatory args
    -  {
    -    background: 'sprites/path-to-observatory.png',
    -    player: [60, 4],
    -    scenes: [
    -      [0, 26, 5, 5, :final_message_observatory]
    -    ],
    -    storylines: [
    -      [22, 20, 10, 10, "This spot--, on the mountain, right here, it's-- perfect. This- is where- I'll-- yeet-- the person-- who is playing-- this- prank- on me."]
    -    ],
    -    render_override: :blinking_light_path_to_observatory_render
    -  }
    -end
    -
    -def final_message_observatory args
    -  if args.state.scene_history.include? :replied_with_whole_truth
    -    return {
    -      background: 'sprites/inside-observatory.png',
    -      fade: 60,
    -      player: [51, 12],
    -      storylines: [
    -        [50, 10, 4, 4, "Here-- we- go..."]
    -      ],
    -      scenes: [
    -        [30, 18, 5, 12, :final_message_inside_mainframe]
    -      ],
    -      render_override: :blinking_light_inside_observatory_render
    -    }
    -  else
    -    return {
    -      background: 'sprites/inside-observatory.png',
    -      fade: 60,
    -      player: [51, 12],
    -      storylines: [
    -        [50, 10, 4, 4, "I feel like I'm-- walking-- on sunshine!"]
    -      ],
    -      scenes: [
    -        [30, 18, 5, 12, :final_message_inside_mainframe]
    -      ],
    -      render_override: :blinking_light_inside_observatory_render
    -    }
    -  end
    -end
    -
    -def final_message_inside_mainframe args
    -  {
    -    player: [32, 4],
    -    background: 'sprites/mainframe.png',
    -    fade: 60,
    -    scenes: [[45, 45,  4, 4, :final_message_check_ship_status]]
    -  }
    -end
    -
    -def final_message_check_ship_status args
    -  {
    -    background: 'sprites/mainframe.png',
    -    storylines: [
    -      [45, 45, 4, 4, (final_message_current args)],
    -    ],
    -    scenes: [
    -      [*hotspot_top, :final_message_ship_status],
    -    ]
    -  }
    -end
    -
    -def final_message_ship_status args
    -  {
    -    background: 'sprites/serenity.png',
    -    fade: 60,
    -    player: [30, 10],
    -    scenes: [
    -      [30, 50, 4, 4, :final_message_ship_status_reviewed]
    -    ],
    -    storylines: [
    -      [30,  8, 4, 4, "Let me make- sure- everything--- looks good. It'll-- give me peace- of mind."],
    -      *final_message_ship_status_shared(args)
    -    ]
    -  }
    -end
    -
    -def final_message_ship_status_reviewed args
    -  {
    -    background: 'sprites/serenity.png',
    -    fade: 60,
    -    scenes: [
    -      [*hotspot_bottom, :final_message_summary]
    -    ],
    -    storylines: [
    -      [0, 62, 62, 3, "Whew. Everyone-- is in their- chambers. The engines-- are roaring-- and Serenity-- is coming-- home."],
    -    ]
    -  }
    -end
    -
    -def final_message_ship_status_shared args
    -  [
    -    *ship_control_hotspot( 0, 50,
    -                           "Stasis-- Chambers--: Online, All chambers-- are powered. Battery--- Allocation---: 3--- of-- 3--.",
    -                           "Matthew's--- Chamber--: OCCUPIED----",
    -                           "Aanka's--- Chamber--: OCCUPIED----",
    -                           "Sasha's--- Chamber--: OCCUPIED----"),
    -    *ship_control_hotspot(12, 35,
    -                          "Life- Support--: Not-- Needed---",
    -                          "O2--- Production---: OFF---",
    -                          "CO2--- Scrubbers---: OFF---",
    -                          "H2O--- Production---: OFF---"),
    -    *ship_control_hotspot(24, 20,
    -                          "Navigation: Offline---",
    -                          "Sensor: OFF---",
    -                          "Heads- Up- Display: DAMAGED---",
    -                          "Arithmetic--- Unit: DAMAGED----"),
    -    *ship_control_hotspot(36, 35,
    -                          "COMM: Underpowered----",
    -                          "Text: ON---",
    -                          "Audio: SEGFAULT---",
    -                          "Video: DAMAGED---"),
    -    *ship_control_hotspot(48, 50,
    -                          "Engine: Online, Coordinates--- Set- for Earth. Battery--- Allocation---: 3--- of-- 3---",
    -                          "Engine I: ON---",
    -                          "Engine II: ON---",
    -                          "Engine III: ON---")
    -  ]
    -end
    -
    -def final_message_last_reply args
    -  if args.state.scene_history.include? :replied_with_whole_truth
    -    return "Buffer--: #{anka_reply_whole_truth.quote}"
    -  else
    -    return "Buffer--: #{anka_reply_half_truth.quote}"
    -  end
    -end
    -
    -def final_message_current args
    -  if args.state.scene_history.include? :replied_with_whole_truth
    -    return "Hey... It's-- me Sasha. Aanka-- is trying-- her best to comfort-- Matthew. This- is the first- time- I've-- ever-- seen-- Matthew-- cry. We'll-- probably-- be in stasis-- by the time you get this message--. Thank- you- again-- for all your help. I look forward-- to meeting-- you in person."
    -  else
    -    return "Hey! It's-- me Sasha! LOL! Aanka-- and Matthew-- are dancing-- around-- like- goofballs--! They- are both- so adorable! Only-- this- tiny-- little-- genius-- can make-- a battle-- hardened-- general--- put- on a tiara-- and dance- around-- like a fairy-- princess-- XD------ Anyways, we are heading-- back into-- the chambers--. I hope our welcome-- home- parade-- has fireworks!"
    -  end
    -end
    -
    -def final_message_summary args
    -  if args.state.scene_history.include? :replied_with_whole_truth
    -    return {
    -      background: 'sprites/inside-observatory.png',
    -      fade: 60,
    -      player: [31, 11],
    -      scenes: [[60, 0, 4, 32, :final_decision_side_of_home]],
    -      storylines: [
    -        [30, 10, 5, 4, "I can't-- imagine-- what they are feeling-- right now. But at least- they- know everything---, and we can- concentrate-- on rebuilding--- this world-- right- off the bat. I can't-- wait to see the future-- they'll-- help- build."],
    -      ]
    -    }
    -  else
    -    return {
    -      background: 'sprites/inside-observatory.png',
    -      fade: 60,
    -      player: [31, 11],
    -      scenes: [[60, 0, 4, 32, :final_decision_side_of_home]],
    -      storylines: [
    -        [30, 10, 5, 4, "They all sounded-- so happy. I know- they'll-- be in for a tough- dose- of reality--- when they- arrive. But- at least- they'll-- be around-- all- of us. We'll-- help them- cope."],
    -      ]
    -    }
    -  end
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_serenity_alive.rb

    -
    def serenity_alive_side_of_home args
    -  {
    -    fade: 60,
    -    background: 'sprites/side-of-home.png',
    -    player: [16, 13],
    -    scenes: [
    -      [52, 24, 11, 5, :serenity_alive_mountain_pass],
    -    ],
    -    render_override: :blinking_light_side_of_home_render
    -  }
    -end
    -
    -def serenity_alive_mountain_pass args
    -  {
    -    background: 'sprites/mountain-pass-zoomed-out.png',
    -    player: [4, 4],
    -    scenes: [
    -      [18, 47, 5, 5, :serenity_alive_path_to_observatory],
    -    ],
    -    storylines: [
    -      [18, 13, 5, 5, "Hnnnnnnnggg. My legs-- are still sore- from yesterday."]
    -    ],
    -    render_override: :blinking_light_mountain_pass_render
    -  }
    -end
    -
    -def serenity_alive_path_to_observatory args
    -  {
    -    background: 'sprites/path-to-observatory.png',
    -    player: [60, 4],
    -    scenes: [
    -      [0, 26, 5, 5, :serenity_alive_observatory]
    -    ],
    -    storylines: [
    -      [22, 20, 10, 10, "This spot--, on the mountain, right here, it's-- perfect. This- is where- I'll-- yeet-- the person-- who is playing-- this- prank- on me."]
    -    ],
    -    render_override: :blinking_light_path_to_observatory_render
    -  }
    -end
    -
    -def serenity_alive_observatory args
    -  {
    -    background: 'sprites/observatory.png',
    -    player: [60, 2],
    -    scenes: [
    -      [28, 39, 4, 10, :serenity_alive_inside_observatory]
    -    ],
    -    render_override: :blinking_light_observatory_render
    -  }
    -end
    -
    -def serenity_alive_inside_observatory args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    player: [60, 2],
    -    storylines: [],
    -    scenes: [
    -      [30, 18, 5, 12, :serenity_alive_inside_mainframe]
    -    ],
    -    render_override: :blinking_light_inside_observatory_render
    -  }
    -end
    -
    -def serenity_alive_inside_mainframe args
    -  {
    -    background: 'sprites/mainframe.png',
    -    fade: 60,
    -    player: [30, 4],
    -    scenes: [
    -      [*hotspot_top, :serenity_alive_ship_status],
    -    ],
    -    storylines: [
    -      [22, 45, 17, 4, (serenity_alive_last_reply args)],
    -      [45, 45,  4, 4, (serenity_alive_current_message args)],
    -    ]
    -  }
    -end
    -
    -def serenity_alive_ship_status args
    -  {
    -    background: 'sprites/serenity.png',
    -    fade: 60,
    -    player: [30, 10],
    -    scenes: [
    -      [30, 50, 4, 4, :serenity_alive_ship_status_reviewed]
    -    ],
    -    storylines: [
    -      [30,  8, 4, 4, "Serenity? THE--- Mission-- Serenity?! How is that possible? They- are supposed-- to be dead."],
    -      [30, 10, 4, 4, "I... can't-- believe-- it. I- can access-- Serenity's-- computer? I- guess my \"superpower----\" isn't limited-- by proximity-- to- a machine--."],
    -      *serenity_alive_shared_ship_status(args)
    -    ]
    -  }
    -end
    -
    -def serenity_alive_ship_status_reviewed args
    -  {
    -    background: 'sprites/serenity.png',
    -    fade: 60,
    -    scenes: [
    -      [*hotspot_bottom, :serenity_alive_time_to_reply]
    -    ],
    -    storylines: [
    -      [0, 62, 62, 3, "Okay. Reviewing-- everything--, it looks- like- I- can- take- the batteries--- from the Stasis--- Chambers--- and- Engine--- to keep- the crew-- alive-- and-- their-- location--- pinpointed---."],
    -    ]
    -  }
    -end
    -
    -def serenity_alive_time_to_reply args
    -  decision_graph serenity_alive_current_message(args),
    -                  "Okay... time to deliver the bad news...",
    -                  [:replied_to_serenity_alive_firmly, "Firm-- Reply", serenity_alive_firm_reply],
    -                  [:replied_to_serenity_alive_kindly, "Sugar-- Coated---- Reply", serenity_alive_sugarcoated_reply]
    -end
    -
    -def serenity_alive_shared_ship_status args
    -  [
    -    *ship_control_hotspot( 0, 50,
    -                           "Stasis-- Chambers--: Online, All chambers-- are powered. Battery--- Allocation---: 3--- of-- 3--, Hmmm. They don't-- need this to be powered-- right- now. Everyone-- is awake.",
    -                           nil,
    -                           nil,
    -                           nil),
    -    *ship_control_hotspot(12, 35,
    -                          "Life- Support--: Offline, Unable--- to- Sustain-- Life. Battery--- Allocation---: 0--- of-- 3---, Okay. That is definitely---- not a good thing.",
    -                          nil,
    -                          nil,
    -                          nil),
    -    *ship_control_hotspot(24, 20,
    -                          "Navigation: Offline, Unable--- to- Calculate--- Location. Battery--- Allocation---: 0--- of-- 3---, Whelp. No wonder-- Sasha-- can't-- get- any-- readings. Their- Navigation--- is completely--- offline.",
    -                          nil,
    -                          nil,
    -                          nil),
    -    *ship_control_hotspot(36, 35,
    -                          "COMM: Underpowered----, Limited--- to- Text-- Based-- COMM. Battery--- Allocation---: 1--- of-- 3---, It's-- lucky- that- their- COMM---- system was able to survive-- twenty-- years--. Just- barely-- it seems.",
    -                          nil,
    -                          nil,
    -                          nil),
    -    *ship_control_hotspot(48, 50,
    -                          "Engine: Online, Full- Control-- Available. Battery--- Allocation---: 3--- of-- 3---, Hmmm. No point of having an engine-- online--, if you don't- know- where you're-- going.",
    -                          nil,
    -                          nil,
    -                          nil)
    -  ]
    -end
    -
    -def serenity_alive_firm_reply
    -  "Serenity, you are at a distance-- farther-- than- Neptune. All- of the ship's-- systems-- are failing. Please- move the batteries---- from- the Stasis-- Chambers-- over- to- Life-- Support--. I also-- need- you to move-- the batteries---- from- the Engines--- to your Navigation---- System."
    -end
    -
    -def serenity_alive_sugarcoated_reply
    -  "So... you- are- a teeny--- tiny--- bit--- farther-- from Earth- than you think. And you have a teeny--- tiny--- problem-- with your ship. Please-- move the batteries--- from the Stasis--- Chambers--- over to Life--- Support---. I also need you to move the batteries--- from the Engines--- to your- Navigation--- System. Don't-- worry-- Sasha. I'll-- get y'all-- home."
    -end
    -
    -def replied_to_serenity_alive_firmly args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [
    -      [*hotspot_bottom_right, :serenity_alive_path_from_observatory]
    -    ],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: #{serenity_alive_firm_reply.quote}"],
    -      *serenity_alive_reply_completed_shared_hotspots(args),
    -    ]
    -  }
    -end
    -
    -def replied_to_serenity_alive_kindly args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [
    -      [*hotspot_bottom_right, :serenity_alive_path_from_observatory]
    -    ],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: #{serenity_alive_sugarcoated_reply.quote}"],
    -      *serenity_alive_reply_completed_shared_hotspots(args),
    -    ]
    -  }
    -end
    -
    -def serenity_alive_path_from_observatory args
    -  {
    -    fade: 60,
    -    background: 'sprites/path-to-observatory.png',
    -    player: [4, 21],
    -    scenes: [
    -      [*hotspot_bottom_right, :serenity_bio_infront_of_home]
    -    ],
    -    storylines: [
    -      [22, 20, 10, 10, "I'm not sure what's-- worse. Waiting-- for Sasha's-- reply. Or jumping-- off- from- right- here."]
    -    ]
    -  }
    -end
    -
    -def serenity_alive_reply_completed_shared_hotspots args
    -  [
    -    [30, 10, 5, 4, "I guess it wasn't-- a joke- after-- all."],
    -    [40, 10, 5, 4, "I barely-- remember--- the- history----- of the crew."],
    -    [50, 10, 5, 4, "It probably--- wouldn't-- hurt- to- refresh-- my memory--."]
    -  ]
    -end
    -
    -def serenity_alive_last_reply args
    -  if args.state.scene_history.include? :replied_to_introduction_seriously
    -    return "Buffer--: \"Hello, Who- is sending-- this message--?\""
    -  else
    -    return "Buffer--: \"New- phone. Who dis?\""
    -  end
    -end
    -
    -def serenity_alive_current_message args
    -  if args.state.scene_history.include? :replied_to_introduction_seriously
    -    "This- is Sasha. The Serenity--- crew-- is out of hibernation---- and ready-- for Earth reentry--. But, it seems like we are having-- trouble-- with our Navigation---- systems. Please advise.".quote
    -  else
    -    "LOL! Thanks for the laugh. I needed that. This- is Sasha. The Serenity--- crew-- is out of hibernation---- and ready-- for Earth reentry--. But, it seems like we are having-- trouble-- with our Navigation---- systems. Can you help me out- babe?".quote
    -  end
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_serenity_bio.rb

    -
    def serenity_bio_infront_of_home args
    -  {
    -    fade: 60,
    -    background: 'sprites/front-of-home.png',
    -    player: [54, 23],
    -    scenes: [
    -      [44, 34, 8, 14, :serenity_bio_inside_home],
    -      [0, 3, 3, 22, :serenity_bio_library]
    -    ]
    -  }
    -end
    -
    -def serenity_bio_inside_home args
    -  {
    -    background: 'sprites/inside-home.png',
    -    player: [34, 4],
    -    storylines: [
    -      [34, 4, 4, 4, "I'm--- completely--- exhausted."],
    -    ],
    -    scenes: [
    -      [30, 38, 12, 13, :serenity_bio_restless_sleep],
    -      [32, 0, 8, 3, :serenity_bio_infront_of_home],
    -    ]
    -  }
    -end
    -
    -def serenity_bio_restless_sleep args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    storylines: [
    -      [32, 38, 10, 13, "I can't-- seem to sleep. I know nothing-- about the- crew-. Maybe- I- should- go read- up- on- them."],
    -    ],
    -    scenes: [
    -      [32, 0, 8, 3, :serenity_bio_infront_of_home],
    -    ]
    -  }
    -end
    -
    -def serenity_bio_library args
    -  {
    -    background: 'sprites/library.png',
    -    fade: 60,
    -    player: [30, 7],
    -    scenes: [
    -      [21, 35, 3, 18, :serenity_bio_book]
    -    ]
    -  }
    -end
    -
    -def serenity_bio_book args
    -  {
    -    background: 'sprites/book.png',
    -    fade: 60,
    -    player: [6, 52],
    -    storylines: [
    -      [ 4, 50, 56, 4, "The Title-- Reads: Never-- Forget-- Mission-- Serenity---"],
    -
    -      [ 4, 38,  8, 8, "Name: Matthew--- R. Sex: Male--- Age-- at-- Departure: 36-----"],
    -      [14, 38, 46, 8, "Tribute-- Text: Matthew graduated-- Magna-- Cum-- Laude-- from MIT--- with-- a- PHD---- in Aero-- Nautical--- Engineering. He was immensely--- competitive, and had an insatiable---- thirst- for aerial-- battle. From the age of twenty, he remained-- undefeated--- in the Israeli-- Air- Force- \"Blue Flag\" combat-- exercises. By the age of 29--- he had already-- risen through- the ranks, and became-- the Lieutenant--- General--- of Lufwaffe. Matthew-- volenteered-- to- pilot-- Mission-- Serenity. To- this day, his wife- and son- are pillars-- of strength- for us. Rest- in Peace- Matthew, we are sorry-- that- news of the pregancy-- never-- reached- you. Please forgive us."],
    -
    -      [4,  26,  8, 8, "Name: Aanka--- P. Sex: Female--- Age-- at-- Departure: 9-----"],
    -      [14, 26, 46, 8, "Tribute-- Text: Aanka--- gratuated--- Magna-- Cum- Laude-- from MIT, at- the- age- of eight, with a- PHD---- in Astro-- Physics. Her-- IQ--- was over 390, the highest-- ever- recorded--- IQ-- in- human-- history. She changed- the landscape-- of Physics-- with her efforts- in- unravelling--- the mysteries--- of- Dark- Matter--. Anka discovered-- the threat- of Halley's-- Comet-- collision--- with Earth. She spear headed-- the global-- effort-- for Misson-- Serenity. Her- multilingual--- address-- to- the world-- brought- us all hope."],
    -
    -      [4,  14,  8, 8, "Name: Sasha--- N. Sex: Female--- Age-- at-- Departure: 29-----"],
    -      [14, 14, 46, 8, "Tribute-- Text: Sasha gratuated-- Magna-- Cum- Laude-- from MIT--- with-- a- PHD---- in Computer---- Science----. She-- was-- brilliant--, strong- willed--, and-- a-- stunningly--- beautiful--- woman---. Sasha---- is- the- creator--- of the world's--- first- Ruby--- Quantum-- Machine---. After-- much- critical--- acclaim--, the Quantum-- Computer-- was placed in MIT's---- Museam-- next- to- Richard--- G. and Thomas--- K.'s---- Lisp-- Machine---. Her- engineering--- skills-- were-- paramount--- for Mission--- Serenity's--- success. Humanity-- misses-- you-- dearly,-- Sasha--. Life-- shines-- a dimmer-- light-- now- that- your- angelic- voice-- can never- be heard- again."],
    -    ],
    -    scenes: [
    -      [*hotspot_bottom, :serenity_bio_finally_to_bed]
    -    ]
    -  }
    -end
    -
    -def serenity_bio_finally_to_bed args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    player: [35, 3],
    -    storylines: [
    -      [34, 4, 4, 4, "Maybe-- I'll-- be able-- to sleep- now..."],
    -    ],
    -    scenes: [
    -      [32, 38, 10, 13, :bad_dream],
    -    ]
    -  }
    -end
    -
    -def bad_dream args
    -  {
    -    fade: 120,
    -    background: 'sprites/inside-home.png',
    -    player: [34, 35],
    -    storylines: [
    -      [34, 34, 4, 4, "Man. I did not- sleep- well- at all..."],
    -    ],
    -    scenes: [
    -      [32, -1, 8, 3, :bad_dream_observatory]
    -    ]
    -  }
    -end
    -
    -def bad_dream_observatory args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 120,
    -    player: [51, 12],
    -    storylines: [
    -      [50, 10, 4, 4,   "Breathe, Hiro. Just see what's there... everything--- will- be okay."]
    -    ],
    -    scenes: [
    -      [30, 18, 5, 12, :bad_dream_inside_mainframe]
    -    ],
    -    render_override: :blinking_light_inside_observatory_render
    -  }
    -end
    -
    -def bad_dream_inside_mainframe args
    -  {
    -    player: [32, 4],
    -    background: 'sprites/mainframe.png',
    -    fade: 120,
    -    storylines: [
    -      [22, 45, 17, 4, (bad_dream_last_reply args)],
    -    ],
    -    scenes: [
    -      [45, 45,  4, 4, :bad_dream_everyone_dead],
    -    ]
    -  }
    -end
    -
    -def bad_dream_everyone_dead args
    -  {
    -    background: 'sprites/mainframe.png',
    -    storylines: [
    -      [22, 45, 17, 4, (bad_dream_last_reply args)],
    -      [45, 45,  4, 4, "Hi-- Hiro. This is Sasha. By the time- you get this- message, chances-- are we will- already-- be- dead. The batteries--- got- damaged-- during-- removal. And- we don't-- have enough-- power-- for Life-- Support. The air-- is- already--- starting-- to taste- bad. It... would- have been- nice... to go- on a date--- with- you-- when-- I- got- back- to Earth. Anyways, good-- bye-- Hiro-- XOXOXO----"],
    -      [22,  5, 17, 4, "Meh. Whatever, I didn't-- want to save them anyways. What- a pain- in my ass."],
    -    ],
    -    scenes: [
    -      [*hotspot_bottom, :anka_inside_room]
    -    ]
    -  }
    -end
    -
    -def bad_dream_last_reply args
    -  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    -    return "Buffer--: #{serenity_alive_firm_reply.quote}"
    -  else
    -    return "Buffer--: #{serenity_alive_sugarcoated_reply.quote}"
    -  end
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_serenity_introduction.rb

    -
    # decision_graph "Message from Sasha",
    -#                "I should reply.",
    -#                [:replied_to_introduction_seriously,  "Reply Seriously", "Who is this?"],
    -# [:replied_to_introduction_humorously, "Reply Humorously", "New phone who dis?"]
    -def reply_to_introduction args
    -  decision_graph  "\"Mission-- control--, your- main- comm-- channels-- seem-- to be down. My apologies-- for- using-- this low- level-- exploit--. What's-- going-- on down there? We are ready-- for reentry--.\" Message--- Timestamp---: 4- hours-- 23--- minutes-- ago--.",
    -                  "Whoever-- pulled- off this exploit-- knows their stuff. I should reply--.",
    -                  [:replied_to_introduction_seriously,  "Serious Reply",  "Hello, Who- is sending-- this message--?"],
    -                  [:replied_to_introduction_humorously, "Humorous Reply", "New phone, who dis?"]
    -end
    -
    -def replied_to_introduction_seriously args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [
    -      *replied_to_introduction_shared_scenes(args)
    -    ],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: \"Hello, Who- is sending-- this message--?\""],
    -      *replied_to_introduction_shared_storylines(args)
    -    ]
    -  }
    -end
    -
    -def replied_to_introduction_humorously args
    -  {
    -    background: 'sprites/inside-observatory.png',
    -    fade: 60,
    -    player: [32, 21],
    -    scenes: [
    -      *replied_to_introduction_shared_scenes(args)
    -    ],
    -    storylines: [
    -      [30, 18, 5, 12, "Buffer-- has been set to: \"New- phone. Who dis?\""],
    -      *replied_to_introduction_shared_storylines(args)
    -    ]
    -  }
    -end
    -
    -def replied_to_introduction_shared_storylines args
    -  [
    -    [30, 10, 5, 4, "It's-- going-- to take a while-- for this reply-- to make it's-- way back."],
    -    [40, 10, 5, 4, "4- hours-- to send a message-- at light speed?! How far away-- is the sender--?"],
    -    [50, 10, 5, 4, "I know- I've-- read about-- light- speed- travel-- before--. Maybe-- the library--- still has that- poster."]
    -  ]
    -end
    -
    -def replied_to_introduction_shared_scenes args
    -  [[60, 0, 4, 32, :replied_to_introduction_observatory]]
    -end
    -
    -def replied_to_introduction_observatory args
    -  {
    -    background: 'sprites/observatory.png',
    -    player: [28, 39],
    -    scenes: [
    -      [60, 0, 4, 32, :replied_to_introduction_path_to_observatory]
    -    ]
    -  }
    -end
    -
    -def replied_to_introduction_path_to_observatory args
    -  {
    -    background: 'sprites/path-to-observatory.png',
    -    player: [0, 26],
    -    scenes: [
    -      [60, 0, 4, 20, :replied_to_introduction_mountain_pass]
    -    ],
    -  }
    -end
    -
    -def replied_to_introduction_mountain_pass args
    -  {
    -    background: 'sprites/mountain-pass-zoomed-out.png',
    -    player: [21, 48],
    -    scenes: [
    -      [0, 0, 15, 4, :replied_to_introduction_side_of_home]
    -    ],
    -    storylines: [
    -      [15, 28, 5, 3, "At least I'm-- getting-- my- exercise-- in- for- today--."]
    -    ]
    -  }
    -end
    -
    -def replied_to_introduction_side_of_home args
    -  {
    -    background: 'sprites/side-of-home.png',
    -    player: [58, 29],
    -    scenes: [
    -      [2, 0, 61, 2, :speed_of_light_front_of_home]
    -    ],
    -  }
    -end
    -
    -
    -

    99_genre_narrative_rpg/return_of_serenity/app/storyline_speed_of_light.rb

    -
    def speed_of_light_front_of_home args
    -  {
    -    background: 'sprites/front-of-home.png',
    -    player: [54, 23],
    -    scenes: [
    -      [44, 34, 8, 14, :speed_of_light_inside_home],
    -      [0, 3, 3, 22, :speed_of_light_outside_library]
    -    ]
    -  }
    -end
    -
    -def speed_of_light_inside_home args
    -  {
    -    background: 'sprites/inside-home.png',
    -    player: [35, 4],
    -    storylines: [
    -      [30, 38, 12, 13, "Can't- sleep right now. I have to- find- out- why- it took- over-- 4- hours-- to receive-- that message."]
    -    ],
    -    scenes: [
    -      [32, 0, 8, 3, :speed_of_light_front_of_home],
    -    ]
    -  }
    -end
    -
    -def speed_of_light_outside_library args
    -  {
    -    background: 'sprites/outside-library.png',
    -    player: [55, 19],
    -    scenes: [
    -      [49, 39, 6, 10, :speed_of_light_library],
    -      [61, 11, 3, 20, :speed_of_light_front_of_home]
    -    ]
    -  }
    -end
    -
    -def speed_of_light_library args
    -  {
    -    background: 'sprites/library.png',
    -    player: [30, 7],
    -    scenes: [
    -      [3, 50, 10, 3, :speed_of_light_celestial_bodies_diagram]
    -    ]
    -  }
    -end
    -
    -def speed_of_light_celestial_bodies_diagram args
    -  {
    -    background: 'sprites/planets.png',
    -    fade: 60,
    -    player: [30, 3],
    -    scenes: [
    -      [56 - 2, 10, 5, 5, :speed_of_light_distance_discovered]
    -    ],
    -    storylines: [
    -      [30, 2, 4, 4, "Here- it is! This is a diagram--- of the solar-- system--. It was printed-- over-- fifty-- years- ago. Geez-- that's-- old."],
    -
    -      [ 0 - 2, 10, 5, 5, "The label- reads: Sun. The length- of the Astronomical-------- Unit-- (AU), is the distance-- from the Sun- to the Earth. Which is about 150--- million--- kilometers----."],
    -      [ 7 - 2, 10, 5, 5, "The label- reads: Mercury. Distance from Sun: 0.39AU------------ or- 3----- light-- minutes--."],
    -      [14 - 2, 10, 5, 5, "The label- reads: Venus. Distance from Sun: 0.72AU------------ or- 6----- light-- minutes--."],
    -      [21 - 2, 10, 5, 5, "The label- reads: Earth. Distance from Sun: 1.00AU------------ or- 8----- light-- minutes--."],
    -      [28 - 2, 10, 5, 5, "The label- reads: Mars. Distance from Sun: 1.52AU------------ or- 12----- light-- minutes--."],
    -      [35 - 2, 10, 5, 5, "The label- reads: Jupiter. Distance from Sun: 5.20AU------------ or- 45----- light-- minutes--."],
    -      [42 - 2, 10, 5, 5, "The label- reads: Saturn. Distance from Sun: 9.53AU------------ or- 79----- light-- minutes--."],
    -      [49 - 2, 10, 5, 5, "The label- reads: Uranus. Distance from Sun: 19.81AU------------ or- 159----- light-- minutes--."],
    -      # [56 - 2, 15, 4, 4, "The label- reads: Neptune. Distance from Sun: 30.05AU------------ or- 4.1----- light-- hours--."],
    -      [63 - 2, 10, 5, 5, "The label- reads: Pluto. Wait. WTF? Pluto-- isn't-- a planet."],
    -    ]
    -  }
    -end
    -
    -def speed_of_light_distance_discovered args
    -  {
    -    background: 'sprites/planets.png',
    -    scenes: [
    -      [13, 0, 44, 3, :speed_of_light_end_of_day]
    -    ],
    -    storylines: [
    -      [ 0 - 2, 10, 5, 5, "The label- reads: Sun. The length- of the Astronomical-------- Unit-- (AU), is the distance-- from the Sun- to the Earth. Which is about 150--- million--- kilometers----."],
    -      [ 7 - 2, 10, 5, 5, "The label- reads: Mercury. Distance from Sun: 0.39AU------------ or- 3----- light-- minutes--."],
    -      [14 - 2, 10, 5, 5, "The label- reads: Venus. Distance from Sun: 0.72AU------------ or- 6----- light-- minutes--."],
    -      [21 - 2, 10, 5, 5, "The label- reads: Earth. Distance from Sun: 1.00AU------------ or- 8----- light-- minutes--."],
    -      [28 - 2, 10, 5, 5, "The label- reads: Mars. Distance from Sun: 1.52AU------------ or- 12----- light-- minutes--."],
    -      [35 - 2, 10, 5, 5, "The label- reads: Jupiter. Distance from Sun: 5.20AU------------ or- 45----- light-- minutes--."],
    -      [42 - 2, 10, 5, 5, "The label- reads: Saturn. Distance from Sun: 9.53AU------------ or- 79----- light-- minutes--."],
    -      [49 - 2, 10, 5, 5, "The label- reads: Uranus. Distance from Sun: 19.81AU------------ or- 159----- light-- minutes--."],
    -      [56 - 2, 10, 5, 5, "The label- reads: Neptune. Distance from Sun: 30.05AU------------ or- 4.1----- light-- hours--. What?! The message--- I received-- was from a source-- farther-- than-- Neptune?!"],
    -      [63 - 2, 10, 5, 5, "The label- reads: Pluto. Dista- Wait... Pluto-- isn't-- a planet. People-- thought- Pluto-- was a planet-- back- then?--"],
    -    ]
    -  }
    -end
    -
    -def speed_of_light_end_of_day args
    -  {
    -    fade: 60,
    -    background: 'sprites/inside-home.png',
    -    player: [35, 0],
    -    storylines: [
    -      [35, 10, 4, 4, "Wonder-- what the reply-- will be. Who- the hell is contacting--- me from beyond-- Neptune? This- has to be some- kind- of- joke."]
    -    ],
    -    scenes: [
    -      [31, 38, 10, 12, :serenity_alive_side_of_home]
    -    ]
    -  }
    -end
    -
    -
    -

    99_genre_platformer/clepto_frog/app/main.rb

    -
    MAP_FILE_PATH = 'app/map.txt'
    -
    -require 'app/map.rb'
    -
    -class CleptoFrog
    -  attr_gtk
    -
    -  def render_ending
    -    state.game_over_at ||= state.tick_count
    -
    -    outputs.labels << [640, 700, "Clepto Frog", 4, 1]
    -
    -    if state.tick_count >= (state.game_over_at + 120)
    -      outputs.labels << [640, 620, "\"I... I.... don't believe it.\" - New Guy",
    -                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 120).ease(60)]
    -    end
    -
    -    if state.tick_count >= (state.game_over_at + 240)
    -      outputs.labels << [640, 580, "\"He actually stole all the mugs?\" - New Guy",
    -                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 240).ease(60)]
    -    end
    -
    -    if state.tick_count >= (state.game_over_at + 360)
    -      outputs.labels << [640, 540, "\"Kind of feel bad STARTING HIM WITH NOTHING again.\" - New Guy",
    -                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 360).ease(60)]
    -    end
    -
    -    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
    -                        "sprites/square-green.png"]
    -
    -    outputs.labels << [640, 300, "Current Time: #{"%.2f" % state.stuff_time}", 4, 1]
    -    outputs.labels << [640, 270, "Best Time: #{"%.2f" % state.stuff_best_time}", 4, 1]
    -
    -    if state.tick_count >= (state.game_over_at + 550)
    -      restart_game
    -    end
    -  end
    -
    -  def restart_game
    -    state.world = nil
    -    state.x = nil
    -    state.y = nil
    -    state.dx = nil
    -    state.dy = nil
    -    state.stuff_score = 0
    -    state.stuff_time = 0
    -    state.intro_tick_count = nil
    -    defaults
    -    state.game_start_at = state.tick_count
    -    state.scene = :game
    -    state.game_over_at = nil
    -  end
    -
    -  def render_intro
    -    outputs.labels << [640, 700, "Clepto Frog", 4, 1]
    -    if state.tick_count >= 120
    -      outputs.labels << [640, 620, "\"Uh... your office has a pet frog?\" - New Guy",
    -                         4, 1, 0, 0, 0, 255 * 120.ease(60)]
    -    end
    -
    -    if state.tick_count >= 240
    -      outputs.labels << [640, 580, "\"Yep! His name is Clepto.\" - Jim",
    -                         4, 1, 0, 0, 0, 255 * 240.ease(60)]
    -    end
    -
    -    if state.tick_count >= 360
    -      outputs.labels << [640, 540, "\"Uh...\" - New Guy",
    -                         4, 1, 0, 0, 0, 255 * 360.ease(60)]
    -    end
    -
    -    if state.tick_count >= 480
    -      outputs.labels << [640, 500, "\"He steals mugs while we're away...\" - Jim",
    -                         4, 1, 0, 0, 0, 255 * 480.ease(60)]
    -    end
    -
    -    if state.tick_count >= 600
    -      outputs.labels << [640, 460, "\"It's not a big deal, we take them back in the morning.\" - Jim",
    -                         4, 1, 0, 0, 0, 255 * 600.ease(60)]
    -    end
    -
    -    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
    -                        "sprites/square-green.png"]
    -
    -    if state.tick_count == 800
    -      state.scene = :game
    -      state.game_start_at = state.tick_count
    -    end
    -  end
    -
    -  def tick
    -    defaults
    -    if state.scene == :intro && state.tick_count <= 800
    -      render_intro
    -    elsif state.scene == :ending
    -      render_ending
    -    else
    -      render
    -    end
    -    calc
    -    process_inputs
    -  end
    -
    -  def defaults
    -    state.scene ||= :intro
    -    state.stuff_score     ||= 0
    -    state.stuff_time      ||= 0
    -    state.stuff_best_time ||= nil
    -    state.camera_x ||= 0
    -    state.camera_y ||= 0
    -    state.target_camera_scale ||= 1
    -    state.camera_scale ||= 1
    -    state.tongue_length          ||= 100
    -    state.dev_action             ||= :collision_mode
    -    state.action                 ||= :aiming
    -    state.tongue_angle           ||= 90
    -    state.tile_size                = 64
    -    state.gravity                  = -0.1
    -    state.air                      = -0.01
    -    state.player_width             = 60
    -    state.player_height            = 60
    -    state.collision_tolerance      = 0.0
    -    state.previous_tile_size     ||= state.tile_size
    -    state.x                      ||= 2400
    -    state.y                      ||= 200
    -    state.dy                     ||= 0
    -    state.dx                     ||= 0
    -    attempt_load_world_from_file
    -    state.world_lookup           ||= { }
    -    state.world_collision_rects  ||= []
    -    state.mode                   ||= :creating
    -    state.select_menu            ||= [0, 720, 1280, 720]
    -    state.sprite_quantity        ||= 20
    -    state.sprite_coords          ||= []
    -    state.banner_coords          ||= [640, 680 + 720]
    -    state.sprite_selected        ||= 1
    -    state.map_saved_at           ||= 0
    -    state.intro_tick_count       ||= state.tick_count
    -    if state.sprite_coords == []
    -      count = 1
    -      temp_x = 165
    -      temp_y = 500 + 720
    -      state.sprite_quantity.times do
    -        state.sprite_coords += [[temp_x, temp_y, count]]
    -        temp_x += 100
    -        count += 1
    -        if temp_x > 1280 - (165 + 50)
    -          temp_x = 165
    -          temp_y -= 75
    -        end
    -      end
    -    end
    -  end
    -
    -  def start_of_tongue x = nil, y = nil
    -    x ||= state.x
    -    y ||= state.y
    -    [
    -      x + state.player_width.half,
    -      y + state.player_height.half
    -    ]
    -  end
    -
    -  def stage_definition
    -    outputs.sprites << [vx(0), vy(0), vw(10000), vw(5875), 'sprites/level-map.png']
    -  end
    -
    -  def render
    -    stage_definition
    -    start_of_tongue_render = [vx(start_of_tongue.x), vy(start_of_tongue.y)]
    -    end_of_tongue_render = [vx(end_of_tongue.x), vy(end_of_tongue.y)]
    -
    -    if state.anchor_point
    -      anchor_point_render = [vx(state.anchor_point.x), vy(state.anchor_point.y)]
    -      outputs.sprites << { x: start_of_tongue_render.x,
    -                           y: start_of_tongue_render.y,
    -                           w: vw(2),
    -                           h: args.geometry.distance(start_of_tongue_render, anchor_point_render),
    -                           path:  'sprites/square-pink.png',
    -                           angle_anchor_y: 0,
    -                           angle: state.tongue_angle - 90 }
    -    else
    -      outputs.sprites << { x: vx(start_of_tongue.x),
    -                           y: vy(start_of_tongue.y),
    -                           w: vw(2),
    -                           h: vh(state.tongue_length),
    -                           path:  'sprites/square-pink.png',
    -                           angle_anchor_y: 0,
    -                           angle: state.tongue_angle - 90 }
    -    end
    -
    -    outputs.sprites << state.objects.map { |o| [vx(o.x), vy(o.y), vw(o.w), vh(o.h), o.path] }
    -
    -    if state.god_mode
    -      # SHOW HIDE COLLISIONS
    -      outputs.sprites << state.world.map do |x, y, w, h|
    -        x = vx(x)
    -        y = vy(y)
    -        if x > -80 && x < 1280 && y > -80 && y < 720
    -          {
    -            x: x,
    -            y: y,
    -            w: vw(w || state.tile_size),
    -            h: vh(h || state.tile_size),
    -            path: 'sprites/square-gray.png',
    -            a: 128
    -          }
    -        end
    -      end
    -    end
    -
    -    render_player
    -    outputs.sprites << [vx(2315), vy(45), vw(569), vh(402), 'sprites/square-blue.png', 0, 40]
    -
    -    # Label in top left of the screen
    -    outputs.primitives << [20, 640, 180, 70, 255, 255, 255, 128].solid
    -    outputs.primitives << [30, 700, "Stuff: #{state.stuff_score} of #{$mugs.count}", 1].label
    -    outputs.primitives << [30, 670, "Time: #{"%.2f" % state.stuff_time}", 1].label
    -
    -    if state.god_mode
    -      if state.map_saved_at > 0 && state.map_saved_at.elapsed_time < 120
    -        outputs.primitives << [920, 670, 'Map has been exported!', 1, 0, 50, 100, 50].label
    -      end
    -
    -
    -      # Creates sprite following mouse to help indicate which sprite you have selected
    -      outputs.primitives << [inputs.mouse.position.x, inputs.mouse.position.y,
    -                             state.tile_size, state.tile_size, 'sprites/square-indigo.png', 0, 100].sprite
    -    end
    -
    -    render_mini_map
    -    outputs.primitives << [0, 0, 1280, 720, 255, 255, 255, 255 * state.game_start_at.ease(60, :flip)].solid
    -  end
    -
    -  def render_mini_map
    -    x, y = 1170, 10
    -    outputs.primitives << [x, y, 100, 58, 0, 0, 0, 200].solid
    -    outputs.primitives << [x + args.state.x.fdiv(100) - 1, y + args.state.y.fdiv(100) - 1, 2, 2, 0, 255, 0].solid
    -    t_start = start_of_tongue
    -    t_end = end_of_tongue
    -    outputs.primitives << [
    -      x + t_start.x.fdiv(100), y + t_start.y.fdiv(100),
    -      x + t_end.x.fdiv(100), y + t_end.y.fdiv(100),
    -      255, 255, 255
    -    ].line
    -
    -    state.objects.each do |o|
    -      outputs.primitives << [x + o.x.fdiv(100) - 1, y + o.y.fdiv(100) - 1, 2, 2, 200, 200, 0].solid
    -    end
    -  end
    -
    -  def calc_camera percentage_override = nil
    -    percentage = percentage_override || (0.2 * state.camera_scale)
    -    target_scale = state.target_camera_scale
    -    distance_scale = target_scale - state.camera_scale
    -    state.camera_scale += distance_scale * percentage
    -
    -    target_x = state.x * state.target_camera_scale
    -    target_y = state.y * state.target_camera_scale
    -
    -    distance_x = target_x - (state.camera_x + 640)
    -    distance_y = target_y - (state.camera_y + 360)
    -    state.camera_x += distance_x * percentage if distance_x.abs > 1
    -    state.camera_y += distance_y * percentage if distance_y.abs > 1
    -    state.camera_x = 0 if state.camera_x < 0
    -    state.camera_y = 0 if state.camera_y < 0
    -  end
    -
    -  def vx x
    -     (x * state.camera_scale) - state.camera_x
    -  end
    -
    -  def vy y
    -    (y * state.camera_scale) - state.camera_y
    -  end
    -
    -  def vw w
    -    w * state.camera_scale
    -  end
    -
    -  def vh h
    -    h * state.camera_scale
    -  end
    -
    -  def calc
    -    calc_camera
    -    calc_world_lookup
    -    calc_player
    -    calc_on_floor
    -    calc_score
    -  end
    -
    -  def set_camera_scale v = nil
    -    return if v < 0.1
    -    state.target_camera_scale = v
    -  end
    -
    -  def process_inputs_god_mode
    -    return unless state.god_mode
    -
    -    if inputs.keyboard.key_down.equal_sign || (inputs.keyboard.equal_sign && state.tick_count.mod_zero?(10))
    -      set_camera_scale state.camera_scale + 0.1
    -    elsif inputs.keyboard.key_down.hyphen || (inputs.keyboard.hyphen && state.tick_count.mod_zero?(10))
    -      set_camera_scale state.camera_scale - 0.1
    -    elsif inputs.keyboard.eight || inputs.keyboard.zero
    -      set_camera_scale 1
    -    end
    -
    -    if input_up?
    -      state.y += 10
    -      state.dy = 0
    -    elsif input_down?
    -      state.y -= 10
    -      state.dy = 0
    -    end
    -
    -    if input_left?
    -      state.x -= 10
    -      state.dx = 0
    -    elsif input_right?
    -      state.x += 10
    -      state.dx = 0
    -    end
    -  end
    -
    -  def process_inputs
    -    if state.scene == :game
    -      process_inputs_player_movement
    -      process_inputs_god_mode
    -    elsif state.scene == :intro
    -      if args.inputs.keyboard.key_down.enter || args.inputs.keyboard.key_down.space
    -        if Kernel.tick_count < 600
    -          Kernel.tick_count = 600
    -        end
    -      end
    -    end
    -  end
    -
    -  def input_up?
    -    inputs.keyboard.w || inputs.keyboard.up || inputs.keyboard.k
    -  end
    -
    -  def input_up_released?
    -    inputs.keyboard.key_up.w ||
    -    inputs.keyboard.key_up.up ||
    -    inputs.keyboard.key_up.k
    -  end
    -
    -  def input_down?
    -    inputs.keyboard.s || inputs.keyboard.down || inputs.keyboard.j
    -  end
    -
    -  def input_down_released?
    -    inputs.keyboard.key_up.s ||
    -    inputs.keyboard.key_up.down ||
    -    inputs.keyboard.key_up.j
    -  end
    -
    -  def input_left?
    -    inputs.keyboard.a || inputs.keyboard.left || inputs.keyboard.h
    -  end
    -
    -  def input_right?
    -    inputs.keyboard.d || inputs.keyboard.right || inputs.keyboard.l
    -  end
    -
    -  def set_object path, w, h
    -    state.object = path
    -    state.object_w = w
    -    state.object_h = h
    -  end
    -
    -  def collision_mode
    -    state.dev_action = :collision_mode
    -  end
    -
    -  def process_inputs_player_movement
    -    if inputs.keyboard.key_down.g
    -      state.god_mode = !state.god_mode
    -      puts state.god_mode
    -    end
    -
    -    if inputs.keyboard.key_down.u && state.dev_action == :collision_mode
    -      state.world = state.world[0..-2]
    -      state.world_lookup = {}
    -    end
    -
    -    if inputs.keyboard.key_down.space && !state.anchor_point
    -      state.tongue_length = 0
    -      state.action = :shooting
    -      outputs.sounds << 'sounds/shooting.wav'
    -    elsif inputs.keyboard.key_down.space
    -      state.action = :aiming
    -      state.anchor_point  = nil
    -      state.tongue_length = 100
    -    end
    -
    -    if state.anchor_point
    -      if input_up?
    -        if state.tongue_length >= 105
    -          state.tongue_length -= 5
    -          state.dy += 0.8
    -        end
    -      elsif input_down?
    -        state.tongue_length += 5
    -        state.dy -= 0.8
    -      end
    -
    -      if input_left? && state.dx > 1
    -        state.dx *= 0.98
    -      elsif input_left? && state.dx < -1
    -        state.dx *= 1.03
    -      elsif input_left? && !state.on_floor
    -        state.dx -= 3
    -      elsif input_right? && state.dx > 1
    -        state.dx *= 1.03
    -      elsif input_right? && state.dx < -1
    -        state.dx *= 0.98
    -      elsif input_right? && !state.on_floor
    -        state.dx += 3
    -      end
    -    else
    -      if input_left?
    -        state.tongue_angle += 1.5
    -        state.tongue_angle = state.tongue_angle
    -      elsif input_right?
    -        state.tongue_angle -= 1.5
    -        state.tongue_angle = state.tongue_angle
    -      end
    -    end
    -  end
    -
    -  def add_floors
    -    # floors
    -    state.world += [
    -      [0,       0, 10000, 40],
    -      [0,    1670, 3250, 60],
    -      [6691, 1653, 3290, 60],
    -      [1521, 3792, 7370, 60],
    -      [0, 5137, 3290, 60]
    -    ]
    -  end
    -
    -  def attempt_load_world_from_file
    -    return if state.world
    -    # exported_world = gtk.read_file(MAP_FILE_PATH)
    -    state.world = []
    -    state.objects = []
    -
    -    if $collisions
    -      $collisions.map do |x, y, w, h|
    -        state.world << [x, y, w, h]
    -      end
    -
    -      add_floors
    -    # elsif exported_world
    -    #   exported_world.each_line.map do |l|
    -    #     tokens = l.strip.split(',')
    -    #     x    = tokens[0].to_i
    -    #     y    = tokens[1].to_i
    -    #     type = tokens[2].to_i
    -    #     if type == 1
    -    #       state.world << [x, y, state.tile_size, state.tile_size]
    -    #     elsif type == 2
    -    #       w, h, path = tokens[3..-1]
    -    #       state.objects << [x, y, w.to_i, h.to_i, path]
    -    #     end
    -    #   end
    -
    -    #   add_floors
    -    end
    -
    -    if $mugs
    -      $mugs.map do |x, y, w, h, path|
    -        state.objects << [x, y, w, h, path]
    -      end
    -    end
    -  end
    -
    -  def calc_world_lookup
    -    if state.tile_size != state.previous_tile_size
    -      state.previous_tile_size = state.tile_size
    -      state.world_lookup = {}
    -    end
    -
    -    return if state.world_lookup.keys.length > 0
    -    return unless state.world.length > 0
    -
    -    # Searches through the world and finds the cordinates that exist
    -    state.world_lookup = {}
    -    state.world.each do |x, y, w, h|
    -      state.world_lookup[[x, y, w || state.tile_size, h || state.tile_size]] = true
    -    end
    -
    -    # Assigns collision rects for every sprite drawn
    -    state.world_collision_rects =
    -      state.world_lookup
    -           .keys
    -           .map do |x, y, w, h|
    -             s = state.tile_size
    -             w ||= s
    -             h ||= s
    -             {
    -               args:       [x, y, w, h],
    -               left_right: [x,     y + 4, w,     h - 6],
    -               top:        [x + 4, y + 6, w - 8, h - 6],
    -               bottom:     [x + 1, y - 1, w - 2, h - 8],
    -             }
    -           end
    -
    -  end
    -
    -  def calc_pendulum
    -    return if !state.anchor_point
    -    target_x = state.anchor_point.x - start_of_tongue.x
    -    target_y = state.anchor_point.y -
    -               state.tongue_length - 5 - 20 - state.player_height
    -
    -    diff_y = state.y - target_y
    -
    -    if target_x > 0
    -      state.dx += 0.6
    -    elsif target_x < 0
    -      state.dx -= 0.6
    -    end
    -
    -    if diff_y > 0
    -      state.dy -= 0.1
    -    elsif diff_y < 0
    -      state.dy += 0.1
    -    end
    -
    -    state.dx *= 0.99
    -
    -    if state.dy.abs < 2
    -      state.dy *= 0.8
    -    else
    -      state.dy *= 0.90
    -    end
    -
    -    if state.tongue_length && state.y
    -      state.dy += state.tongue_angle.vector_y state.tongue_length.fdiv(1000)
    -    end
    -  end
    -
    -  def calc_tongue_angle
    -    return unless state.anchor_point
    -    state.tongue_angle = args.geometry.angle_from state.anchor_point, start_of_tongue
    -    state.tongue_length = args.geometry.distance(start_of_tongue, state.anchor_point)
    -    state.tongue_length = state.tongue_length.greater(100)
    -  end
    -
    -  def player_from_end_of_tongue
    -    p = state.tongue_angle.vector(state.tongue_length)
    -    derived_start = [state.anchor_point.x - p.x, state.anchor_point.y - p.y]
    -    derived_start.x -= state.player_width.half
    -    derived_start.y -= state.player_height.half
    -    derived_start
    -  end
    -
    -  def end_of_tongue
    -    p = state.tongue_angle.vector(state.tongue_length)
    -    [start_of_tongue.x + p.x, start_of_tongue.y + p.y]
    -  end
    -
    -  def calc_shooting
    -    return unless state.action == :shooting
    -    state.tongue_length += 30
    -    potential_anchor = end_of_tongue
    -    if potential_anchor.x <= 0
    -      state.anchor_point = potential_anchor
    -      state.action = :anchored
    -      outputs.sounds << 'sounds/attached.wav'
    -    elsif potential_anchor.x >= 10000
    -      state.anchor_point = potential_anchor
    -      state.action = :anchored
    -      outputs.sounds << 'sounds/attached.wav'
    -    elsif potential_anchor.y <= 0
    -      state.anchor_point = potential_anchor
    -      state.action = :anchored
    -      outputs.sounds << 'sounds/attached.wav'
    -    elsif potential_anchor.y >= 5875
    -      state.anchor_point = potential_anchor
    -      state.action = :anchored
    -      outputs.sounds << 'sounds/attached.wav'
    -    else
    -      anchor_rect = [potential_anchor.x - 5, potential_anchor.y - 5, 10, 10]
    -      collision = state.world_collision_rects.find_all do |v|
    -        [v[:args].x, v[:args].y, v[:args].w, v[:args].h].intersect_rect?(anchor_rect)
    -      end.first
    -      if collision
    -        state.anchor_point = potential_anchor
    -        state.action = :anchored
    -      outputs.sounds << 'sounds/attached.wav'
    -      end
    -    end
    -  end
    -
    -  def calc_player
    -    calc_shooting
    -    if !state.god_mode
    -      state.dy += state.gravity  # Since acceleration is the change in velocity, the change in y (dy) increases every frame
    -      state.dx += state.dx * state.air
    -    end
    -    calc_pendulum
    -    calc_box_collision
    -    calc_edge_collision
    -    if !state.god_mode
    -      state.y  += state.dy
    -      state.x  += state.dx
    -    end
    -    calc_tongue_angle
    -  end
    -
    -  def calc_box_collision
    -    return unless state.world_lookup.keys.length > 0
    -    collision_floor
    -    collision_left
    -    collision_right
    -    collision_ceiling
    -  end
    -
    -  def calc_edge_collision
    -    # Ensures that player doesn't fall below the map
    -    if next_y < 0 && state.dy < 0
    -      state.y = 0
    -      state.dy = state.dy.abs * 0.8
    -      state.collision_on_y = true
    -    # Ensures player doesn't go insanely high
    -    elsif next_y > 5875 - state.tile_size && state.dy > 0
    -      state.y = 5875 - state.tile_size
    -      state.dy = state.dy.abs * 0.8 * -1
    -      state.collision_on_y = true
    -    end
    -
    -    # Ensures that player remains in the horizontal range its supposed to
    -    if state.x >= 10000 - state.tile_size && state.dx > 0
    -      state.x = 10000 - state.tile_size
    -      state.dx = state.dx.abs * 0.8 * -1
    -      state.collision_on_x = true
    -    elsif state.x <= 0 && state.dx < 0
    -      state.x = 0
    -      state.dx = state.dx.abs * 0.8
    -      state.collision_on_x = true
    -    end
    -  end
    -
    -  def next_y
    -    state.y + state.dy
    -  end
    -
    -  def next_x
    -    if state.dx < 0
    -      return (state.x + state.dx) - (state.tile_size - state.player_width)
    -    else
    -      return (state.x + state.dx) + (state.tile_size - state.player_width)
    -    end
    -  end
    -
    -  def collision_floor
    -    return unless state.dy <= 0
    -
    -    player_rect = [state.x, next_y, state.tile_size, state.tile_size]
    -
    -    # Runs through all the sprites on the field and determines if the player hits the bottom of sprite (hence "-0.1" above)
    -    floor_collisions = state.world_collision_rects
    -                         .find_all { |r| r[:top].intersect_rect?(player_rect, state.collision_tolerance) }
    -                         .first
    -
    -    return unless floor_collisions
    -    state.y = floor_collisions[:top].top
    -    state.dy = state.dy.abs * 0.8
    -  end
    -
    -  def collision_left
    -    return unless state.dx < 0
    -    player_rect = [next_x, state.y, state.tile_size, state.tile_size]
    -
    -    # Runs through all the sprites on the field and determines if the player hits the left side of sprite (hence "-0.1" above)
    -    left_side_collisions = state.world_collision_rects
    -                             .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
    -                             .first
    -
    -    return unless left_side_collisions
    -    state.x = left_side_collisions[:left_right].right
    -    state.dx = state.dy.abs * 0.8
    -    state.collision_on_x = true
    -  end
    -
    -  def collision_right
    -    return unless state.dx > 0
    -
    -    player_rect = [next_x, state.y, state.tile_size, state.tile_size]
    -    # Runs through all the sprites on the field and determines if the player hits the right side of sprite (hence "-0.1" above)
    -    right_side_collisions = state.world_collision_rects
    -                              .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
    -                              .first
    -
    -    return unless right_side_collisions
    -    state.x = right_side_collisions[:left_right].left - state.tile_size
    -    state.dx = state.dx.abs * 0.8 * -1
    -    state.collision_on_x = true
    -  end
    -
    -  def collision_ceiling
    -    return unless state.dy > 0
    -
    -    player_rect = [state.x, next_y, state.player_width, state.player_height]
    -
    -    # Runs through all the sprites on the field and determines if the player hits the ceiling of sprite (hence "+0.1" above)
    -    ceil_collisions = state.world_collision_rects
    -                        .find_all { |r| r[:bottom].intersect_rect?(player_rect, state.collision_tolerance) }
    -                        .first
    -
    -    return unless ceil_collisions
    -    state.y = ceil_collisions[:bottom].y - state.tile_size
    -    state.dy = state.dy.abs * 0.8 * -1
    -    state.collision_on_y = true
    -  end
    -
    -  def to_coord point
    -    # Integer divides (idiv) point.x to turn into grid
    -    # Then, you can just multiply each integer by state.tile_size
    -    # later and huzzah. Grid coordinates
    -    [point.x.idiv(state.tile_size), point.y.idiv(state.tile_size)]
    -  end
    -
    -  def export_map
    -    export_string = state.world.map do |x, y|
    -      "#{x},#{y},1"
    -    end
    -    export_string += state.objects.map do |x, y, w, h, path|
    -      "#{x},#{y},2,#{w},#{h},#{path}"
    -    end
    -    gtk.write_file(MAP_FILE_PATH, export_string.join("\n"))
    -    state.map_saved_at = state.tick_count
    -  end
    -
    -  def inputs_export_stage
    -  end
    -
    -  def calc_score
    -    return unless state.scene == :game
    -    player = [state.x, state.y, state.player_width, state.player_height]
    -    collected = state.objects.find_all { |s| s.intersect_rect? player }
    -    state.stuff_score += collected.length
    -    if collected.length > 0
    -      outputs.sounds << 'sounds/collectable.wav'
    -    end
    -    state.objects = state.objects.reject { |s| collected.include? s }
    -    state.stuff_time += 0.01
    -    if state.objects.length == 0
    -      if !state.stuff_best_time || state.stuff_time < state.stuff_best_time
    -        state.stuff_best_time = state.stuff_time
    -      end
    -      state.game_over_at = nil
    -      state.scene = :ending
    -    end
    -  end
    -
    -  def calc_on_floor
    -    if state.action == :anchored
    -      state.on_floor = false
    -      state.on_floor_debounce = 30
    -    else
    -      state.on_floor_debounce ||= 30
    -
    -      if state.dy.round != 0
    -        state.on_floor_debounce = 30
    -        state.on_floor = false
    -      else
    -        state.on_floor_debounce -= 1
    -      end
    -
    -      if state.on_floor_debounce <= 0
    -        state.on_floor_debounce = 0
    -        state.on_floor = true
    -      end
    -    end
    -  end
    -
    -  def render_player
    -    path = "sprites/square-green.png"
    -    angle = 0
    -    # outputs.labels << [vx(state.x), vy(state.y) - 30, "dy: #{state.dy.round}"]
    -    if state.action == :idle
    -      # outputs.labels << [vx(state.x), vy(state.y), "IDLE"]
    -      path = "sprites/square-green.png"
    -    elsif state.action == :aiming && !state.on_floor
    -      # outputs.labels << [vx(state.x), vy(state.y), "AIMING AIR BORN"]
    -      angle = state.tongue_angle - 90
    -      path = "sprites/square-green.png"
    -    elsif state.action == :aiming # ON THE GROUND
    -      # outputs.labels << [vx(state.x), vy(state.y), "AIMING GROUND"]
    -      path = "sprites/square-green.png"
    -    elsif state.action == :shooting && !state.on_floor
    -      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING AIR BORN"]
    -      path = "sprites/square-green.png"
    -      angle = state.tongue_angle - 90
    -    elsif state.action == :shooting
    -      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING ON GROUND"]
    -      path = "sprites/square-green.png"
    -    elsif state.action == :anchored
    -      # outputs.labels << [vx(state.x), vy(state.y), "SWINGING"]
    -      angle = state.tongue_angle - 90
    -      path = "sprites/square-green.png"
    -    end
    -
    -    outputs.sprites << [vx(state.x),
    -                        vy(state.y),
    -                        vw(state.player_width),
    -                        vh(state.player_height),
    -                        path,
    -                        angle]
    -  end
    -
    -  def render_player_old
    -    # Player
    -    if state.action == :aiming
    -      path = 'sprites\frg\idle\frog_idle.png'
    -      if state.dx > 2
    -	  #directional right sprite was here but i needa redo it
    -        path = 'sprites\frg\anchor\frog-anchor-0.png'
    -      #directional left sprite was here but i needa redo it
    -	  elsif state.dx < -2
    -        path = 'sprites\frg\anchor\frog-anchor-0.png'
    -      end
    -      outputs.sprites << [vx(state.x),
    -                          vy(state.y),
    -                          vw(state.player_width),
    -                          vh(state.player_height),
    -                          path,
    -                          (state.tongue_angle - 90)]
    -    elsif state.action == :anchored || state.action == :shooting
    -      outputs.sprites << [vx(state.x),
    -                          vy(state.y),
    -                          vw(state.player_width),
    -                          vw(state.player_height),
    -                          'sprites/animations_povfrog/frog_bwah_up.png',
    -                          (state.tongue_angle - 90)]
    -    end
    -  end
    -end
    -
    -
    -$game = CleptoFrog.new
    -
    -def tick args
    -  if args.state.scene == :game
    -    tick_instructions args, "SPACE to SHOOT and RELEASE tongue. LEFT, RIGHT to SWING and BUILD momentum. MINIMAP in bottom right corner.", 360
    -  end
    -  $game.args = args
    -  $game.tick
    -end
    -
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.keyboard.directional_vector || args.inputs.keyboard.key_down.space
    -    args.state.key_event_occurred = true
    -  end
    -
    -  args.outputs.debug << [0, y - 50, 1280, 60].solid
    -  args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
    -  args.outputs.debug << [640, y - 25, "(SPACE to dismiss instructions)" , -2, 1, 255, 255, 255].label
    -end
    -
    -
    -

    99_genre_platformer/clepto_frog/app/map.rb

    -
    $collisions = [
    +

    Platformer - Clepto Frog - map.rb

    +
    # ./samples/99_genre_platformer/clepto_frog/app/map.rb
    +$collisions = [
       [326, 463, 64, 64],
       [274, 462, 64, 64],
       [326, 413, 64, 64],
    @@ -16511,1353 +15057,5372 @@ end
       [39, 5217, 64, 64],
     ]
     
    -$mugs = [
    -  [85, 87, 39, 43, "sprites/square-orange.png"],
    -  [958, 1967, 39, 43, "sprites/square-orange.png"],
    -  [2537, 1734, 39, 43, "sprites/square-orange.png"],
    -  [3755, 2464, 39, 43, "sprites/square-orange.png"],
    -  [1548, 3273, 39, 43, "sprites/square-orange.png"],
    -  [2050, 220, 39, 43, "sprites/square-orange.png"],
    -  [854, 297, 39, 43, "sprites/square-orange.png"],
    -  [343, 526, 39, 43, "sprites/square-orange.png"],
    -  [3454, 772, 39, 43, "sprites/square-orange.png"],
    -  [5041, 298, 39, 43, "sprites/square-orange.png"],
    -  [6089, 300, 39, 43, "sprites/square-orange.png"],
    -  [6518, 295, 39, 43, "sprites/square-orange.png"],
    -  [7661, 47, 39, 43, "sprites/square-orange.png"],
    -  [9392, 1125, 39, 43, "sprites/square-orange.png"],
    -  [7298, 1152, 39, 43, "sprites/square-orange.png"],
    -  [5816, 1843, 39, 43, "sprites/square-orange.png"],
    -  [876, 3772, 39, 43, "sprites/square-orange.png"],
    -  [1029, 4667, 39, 43, "sprites/square-orange.png"],
    -  [823, 5324, 39, 43, "sprites/square-orange.png"],
    -  [3251, 5220, 39, 43, "sprites/square-orange.png"],
    -  [4747, 5282, 39, 43, "sprites/square-orange.png"],
    -  [9325, 5178, 39, 43, "sprites/square-orange.png"],
    -  [9635, 4298, 39, 43, "sprites/square-orange.png"],
    -  [7837, 4127, 39, 43, "sprites/square-orange.png"],
    -  [8651, 1971, 39, 43, "sprites/square-orange.png"],
    -  [6892, 2031, 39, 43, "sprites/square-orange.png"],
    -  [4626, 3882, 39, 43, "sprites/square-orange.png"],
    -  [4024, 4554, 39, 43, "sprites/square-orange.png"],
    -  [3925, 3337, 39, 43, "sprites/square-orange.png"],
    -  [5064, 1064, 39, 43, "sprites/square-orange.png"]
    -]
    +$mugs = [
    +  [85, 87, 39, 43, "sprites/square-orange.png"],
    +  [958, 1967, 39, 43, "sprites/square-orange.png"],
    +  [2537, 1734, 39, 43, "sprites/square-orange.png"],
    +  [3755, 2464, 39, 43, "sprites/square-orange.png"],
    +  [1548, 3273, 39, 43, "sprites/square-orange.png"],
    +  [2050, 220, 39, 43, "sprites/square-orange.png"],
    +  [854, 297, 39, 43, "sprites/square-orange.png"],
    +  [343, 526, 39, 43, "sprites/square-orange.png"],
    +  [3454, 772, 39, 43, "sprites/square-orange.png"],
    +  [5041, 298, 39, 43, "sprites/square-orange.png"],
    +  [6089, 300, 39, 43, "sprites/square-orange.png"],
    +  [6518, 295, 39, 43, "sprites/square-orange.png"],
    +  [7661, 47, 39, 43, "sprites/square-orange.png"],
    +  [9392, 1125, 39, 43, "sprites/square-orange.png"],
    +  [7298, 1152, 39, 43, "sprites/square-orange.png"],
    +  [5816, 1843, 39, 43, "sprites/square-orange.png"],
    +  [876, 3772, 39, 43, "sprites/square-orange.png"],
    +  [1029, 4667, 39, 43, "sprites/square-orange.png"],
    +  [823, 5324, 39, 43, "sprites/square-orange.png"],
    +  [3251, 5220, 39, 43, "sprites/square-orange.png"],
    +  [4747, 5282, 39, 43, "sprites/square-orange.png"],
    +  [9325, 5178, 39, 43, "sprites/square-orange.png"],
    +  [9635, 4298, 39, 43, "sprites/square-orange.png"],
    +  [7837, 4127, 39, 43, "sprites/square-orange.png"],
    +  [8651, 1971, 39, 43, "sprites/square-orange.png"],
    +  [6892, 2031, 39, 43, "sprites/square-orange.png"],
    +  [4626, 3882, 39, 43, "sprites/square-orange.png"],
    +  [4024, 4554, 39, 43, "sprites/square-orange.png"],
    +  [3925, 3337, 39, 43, "sprites/square-orange.png"],
    +  [5064, 1064, 39, 43, "sprites/square-orange.png"]
    +]
    +
    +
    +

    Platformer - Gorillas Basic - credits.txt

    +
    # ./samples/99_genre_platformer/gorillas_basic/CREDITS.txt
    +code: Amir Rajan, https://twitter.com/amirrajan
    +graphics: Nick Culbertson, https://twitter.com/MobyPixel
    +
    +
    +
    +

    Platformer - Gorillas Basic - main.rb

    +
    # ./samples/99_genre_platformer/gorillas_basic/app/main.rb
    +class YouSoBasicGorillas
    +  attr_accessor :outputs, :grid, :state, :inputs
    +
    +  def tick
    +    defaults
    +    render
    +    calc
    +    process_inputs
    +  end
    +
    +  def defaults
    +    outputs.background_color = [33, 32, 87]
    +    state.building_spacing       = 1
    +    state.building_room_spacing  = 15
    +    state.building_room_width    = 10
    +    state.building_room_height   = 15
    +    state.building_heights       = [4, 4, 6, 8, 15, 20, 18]
    +    state.building_room_sizes    = [5, 4, 6, 7]
    +    state.gravity                = 0.25
    +    state.first_strike         ||= :player_1
    +    state.buildings            ||= []
    +    state.holes                ||= []
    +    state.player_1_score       ||= 0
    +    state.player_2_score       ||= 0
    +    state.wind                 ||= 0
    +  end
    +
    +  def render
    +    render_stage
    +    render_value_insertion
    +    render_gorillas
    +    render_holes
    +    render_banana
    +    render_game_over
    +    render_score
    +    render_wind
    +  end
    +
    +  def render_score
    +    outputs.primitives << [0, 0, 1280, 31, fancy_white].solid
    +    outputs.primitives << [1, 1, 1279, 29].solid
    +    outputs.labels << [  10, 25, "Score: #{state.player_1_score}", 0, 0, fancy_white]
    +    outputs.labels << [1270, 25, "Score: #{state.player_2_score}", 0, 2, fancy_white]
    +  end
    +
    +  def render_wind
    +    outputs.primitives << [640, 12, state.wind * 500 + state.wind * 10 * rand, 4, 35, 136, 162].solid
    +    outputs.lines     <<  [640, 30, 640, 0, fancy_white]
    +  end
    +
    +  def render_game_over
    +    return unless state.over
    +    outputs.primitives << [grid.rect, 0, 0, 0, 200].solid
    +    outputs.primitives << [640, 370, "Game Over!!", 5, 1, fancy_white].label
    +    if state.winner == :player_1
    +      outputs.primitives << [640, 340, "Player 1 Wins!!", 5, 1, fancy_white].label
    +    else
    +      outputs.primitives << [640, 340, "Player 2 Wins!!", 5, 1, fancy_white].label
    +    end
    +  end
    +
    +  def render_stage
    +    return unless state.stage_generated
    +    return if state.stage_rendered
    +
    +    outputs.static_solids << [grid.rect, 33, 32, 87]
    +    outputs.static_solids << state.buildings.map(&:solids)
    +    state.stage_rendered = true
    +  end
    +
    +  def render_gorilla gorilla, id
    +    return unless gorilla
    +    if state.banana && state.banana.owner == gorilla
    +      animation_index  = state.banana.created_at.frame_index(3, 5, false)
    +    end
    +    if !animation_index
    +      outputs.sprites << [gorilla.solid, "sprites/#{id}-idle.png"]
    +    else
    +      outputs.sprites << [gorilla.solid, "sprites/#{id}-#{animation_index}.png"]
    +    end
    +  end
    +
    +  def render_gorillas
    +    render_gorilla state.player_1, :left
    +    render_gorilla state.player_2, :right
    +  end
    +
    +  def render_value_insertion
    +    return if state.banana
    +    return if state.over
    +
    +    if    state.current_turn == :player_1_angle
    +      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}_",    fancy_white]
    +    elsif state.current_turn == :player_1_velocity
    +      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}",     fancy_white]
    +      outputs.labels << [  10, 690, "Velocity: #{state.player_1_velocity}_", fancy_white]
    +    elsif state.current_turn == :player_2_angle
    +      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}_",    fancy_white]
    +    elsif state.current_turn == :player_2_velocity
    +      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}",     fancy_white]
    +      outputs.labels << [1120, 690, "Velocity: #{state.player_2_velocity}_", fancy_white]
    +    end
    +  end
    +
    +  def render_banana
    +    return unless state.banana
    +    rotation = state.tick_count.%(360) * 20
    +    rotation *= -1 if state.banana.dx > 0
    +    outputs.sprites << [state.banana.x, state.banana.y, 15, 15, 'sprites/banana.png', rotation]
    +  end
    +
    +  def render_holes
    +    outputs.sprites << state.holes.map do |s|
    +      animation_index = s.created_at.frame_index(7, 3, false)
    +      if animation_index
    +        [s.sprite, [s.sprite.rect, "sprites/explosion#{animation_index}.png" ]]
    +      else
    +        s.sprite
    +      end
    +    end
    +  end
    +
    +  def calc
    +    calc_generate_stage
    +    calc_current_turn
    +    calc_banana
    +  end
    +
    +  def calc_current_turn
    +    return if state.current_turn
    +
    +    state.current_turn = :player_1_angle
    +    state.current_turn = :player_2_angle if state.first_strike == :player_2
    +  end
    +
    +  def calc_generate_stage
    +    return if state.stage_generated
    +
    +    state.buildings << building_prefab(state.building_spacing + -20, *random_building_size)
    +    8.numbers.inject(state.buildings) do |buildings, i|
    +      buildings <<
    +        building_prefab(state.building_spacing +
    +                        state.buildings.last.right,
    +                        *random_building_size)
    +    end
    +
    +    building_two = state.buildings[1]
    +    state.player_1 = new_player(building_two.x + building_two.w.fdiv(2),
    +                               building_two.h)
    +
    +    building_nine = state.buildings[-3]
    +    state.player_2 = new_player(building_nine.x + building_nine.w.fdiv(2),
    +                               building_nine.h)
    +    state.stage_generated = true
    +    state.wind = 1.randomize(:ratio, :sign)
    +  end
    +
    +  def new_player x, y
    +    state.new_entity(:gorilla) do |p|
    +      p.x = x - 25
    +      p.y = y
    +      p.solid = [p.x, p.y, 50, 50]
    +    end
    +  end
    +
    +  def calc_banana
    +    return unless state.banana
    +
    +    state.banana.x  += state.banana.dx
    +    state.banana.dx += state.wind.fdiv(50)
    +    state.banana.y  += state.banana.dy
    +    state.banana.dy -= state.gravity
    +    banana_collision = [state.banana.x, state.banana.y, 10, 10]
    +
    +    if state.player_1 && banana_collision.intersect_rect?(state.player_1.solid)
    +      state.over = true
    +      if state.banana.owner == state.player_2
    +        state.winner = :player_2
    +      else
    +        state.winner = :player_1
    +      end
    +
    +      state.player_2_score += 1
    +    elsif state.player_2 && banana_collision.intersect_rect?(state.player_2.solid)
    +      state.over = true
    +      if state.banana.owner == state.player_2
    +        state.winner = :player_1
    +      else
    +        state.winner = :player_2
    +      end
    +      state.player_1_score += 1
    +    end
    +
    +    if state.over
    +      place_hole
    +      return
    +    end
    +
    +    return if state.holes.any? do |h|
    +      h.sprite.scale_rect(0.8, 0.5, 0.5).intersect_rect? [state.banana.x, state.banana.y, 10, 10]
    +    end
    +
    +    return unless state.banana.y < 0 || state.buildings.any? do |b|
    +      b.rect.intersect_rect? [state.banana.x, state.banana.y, 1, 1]
    +    end
    +
    +    place_hole
    +  end
    +
    +  def place_hole
    +    return unless state.banana
    +
    +    state.holes << state.new_entity(:banana) do |b|
    +      b.sprite = [state.banana.x - 20, state.banana.y - 20, 40, 40, 'sprites/hole.png']
    +    end
    +
    +    state.banana = nil
    +  end
    +
    +  def process_inputs_main
    +    return if state.banana
    +    return if state.over
    +
    +    if inputs.keyboard.key_down.enter
    +      input_execute_turn
    +    elsif inputs.keyboard.key_down.backspace
    +      state.as_hash[state.current_turn] ||= ""
    +      state.as_hash[state.current_turn]   = state.as_hash[state.current_turn][0..-2]
    +    elsif inputs.keyboard.key_down.char
    +      state.as_hash[state.current_turn] ||= ""
    +      state.as_hash[state.current_turn]  += inputs.keyboard.key_down.char
    +    end
    +  end
    +
    +  def process_inputs_game_over
    +    return unless state.over
    +    return unless inputs.keyboard.key_down.truthy_keys.any?
    +    state.over = false
    +    outputs.static_solids.clear
    +    state.buildings.clear
    +    state.holes.clear
    +    state.stage_generated = false
    +    state.stage_rendered = false
    +    if state.first_strike == :player_1
    +      state.first_strike = :player_2
    +    else
    +      state.first_strike = :player_1
    +    end
    +  end
    +
    +  def process_inputs
    +    process_inputs_main
    +    process_inputs_game_over
    +  end
    +
    +  def input_execute_turn
    +    return if state.banana
    +
    +    if state.current_turn == :player_1_angle && parse_or_clear!(:player_1_angle)
    +      state.current_turn = :player_1_velocity
    +    elsif state.current_turn == :player_1_velocity && parse_or_clear!(:player_1_velocity)
    +      state.current_turn = :player_2_angle
    +      state.banana =
    +        new_banana(state.player_1,
    +                   state.player_1.x + 25,
    +                   state.player_1.y + 60,
    +                   state.player_1_angle,
    +                   state.player_1_velocity)
    +    elsif state.current_turn == :player_2_angle && parse_or_clear!(:player_2_angle)
    +      state.current_turn = :player_2_velocity
    +    elsif state.current_turn == :player_2_velocity && parse_or_clear!(:player_2_velocity)
    +      state.current_turn = :player_1_angle
    +      state.banana =
    +        new_banana(state.player_2,
    +                   state.player_2.x + 25,
    +                   state.player_2.y + 60,
    +                   180 - state.player_2_angle,
    +                   state.player_2_velocity)
    +    end
    +
    +    if state.banana
    +      state.player_1_angle = nil
    +      state.player_1_velocity = nil
    +      state.player_2_angle = nil
    +      state.player_2_velocity = nil
    +    end
    +  end
    +
    +  def random_building_size
    +    [state.building_heights.sample, state.building_room_sizes.sample]
    +  end
    +
    +  def int? v
    +    v.to_i.to_s == v.to_s
    +  end
    +
    +  def random_building_color
    +    [[ 99,   0, 107],
    +     [ 35,  64, 124],
    +     [ 35, 136, 162],
    +     ].sample
    +  end
    +
    +  def random_window_color
    +    [[ 88,  62, 104],
    +     [253, 224, 187]].sample
    +  end
    +
    +  def windows_for_building starting_x, floors, rooms
    +    floors.-(1).combinations(rooms - 1).map do |floor, room|
    +      [starting_x +
    +       state.building_room_width.*(room) +
    +       state.building_room_spacing.*(room + 1),
    +       state.building_room_height.*(floor) +
    +       state.building_room_spacing.*(floor + 1),
    +       state.building_room_width,
    +       state.building_room_height,
    +       random_window_color]
    +    end
    +  end
    +
    +  def building_prefab starting_x, floors, rooms
    +    state.new_entity(:building) do |b|
    +      b.x      = starting_x
    +      b.y      = 0
    +      b.w      = state.building_room_width.*(rooms) +
    +                 state.building_room_spacing.*(rooms + 1)
    +      b.h      = state.building_room_height.*(floors) +
    +                 state.building_room_spacing.*(floors + 1)
    +      b.right  = b.x + b.w
    +      b.rect   = [b.x, b.y, b.w, b.h]
    +      b.solids = [[b.x - 1, b.y, b.w + 2, b.h + 1, fancy_white],
    +                  [b.x, b.y, b.w, b.h, random_building_color],
    +                  windows_for_building(b.x, floors, rooms)]
    +    end
    +  end
    +
    +  def parse_or_clear! game_prop
    +    if int? state.as_hash[game_prop]
    +      state.as_hash[game_prop] = state.as_hash[game_prop].to_i
    +      return true
    +    end
    +
    +    state.as_hash[game_prop] = nil
    +    return false
    +  end
    +
    +  def new_banana owner, x, y, angle, velocity
    +    state.new_entity(:banana) do |b|
    +      b.owner     = owner
    +      b.x         = x
    +      b.y         = y
    +      b.angle     = angle % 360
    +      b.velocity  = velocity / 5
    +      b.dx        = b.angle.vector_x(b.velocity)
    +      b.dy        = b.angle.vector_y(b.velocity)
    +    end
    +  end
    +
    +  def fancy_white
    +    [253, 252, 253]
    +  end
    +end
    +
    +$you_so_basic_gorillas = YouSoBasicGorillas.new
    +
    +def tick args
    +  $you_so_basic_gorillas.outputs = args.outputs
    +  $you_so_basic_gorillas.grid    = args.grid
    +  $you_so_basic_gorillas.state    = args.state
    +  $you_so_basic_gorillas.inputs  = args.inputs
    +  $you_so_basic_gorillas.tick
    +end
    +
    +
    +

    Platformer - Gorillas Basic - repl.rb

    +
    # ./samples/99_genre_platformer/gorillas_basic/app/repl.rb
    +begin
    +  if $gtk.args.state.current_turn == :player_1_angle
    +    $gtk.args.state.player_1_angle = "#{60 + 10.randomize(:ratio).to_i}"
    +    $you_so_basic_gorillas.input_execute_turn
    +    $gtk.args.state.player_1_velocity = "#{30 + 20.randomize(:ratio).to_i}"
    +    $you_so_basic_gorillas.input_execute_turn
    +  elsif $gtk.args.state.current_turn == :player_2_angle
    +    $gtk.args.state.player_2_angle = "#{60 + 10.randomize(:ratio).to_i}"
    +    $you_so_basic_gorillas.input_execute_turn
    +    $gtk.args.state.player_2_velocity = "#{30 + 20.randomize(:ratio).to_i}"
    +    $you_so_basic_gorillas.input_execute_turn
    +  else
    +    $you_so_basic_gorillas.input_execute_turn
    +  end
    +rescue Exception => e
    +  puts e
    +end
    +
    +
    +

    Platformer - Gorillas Basic - tests.rb

    +
    # ./samples/99_genre_platformer/gorillas_basic/app/tests.rb
    +$gtk.reset 100
    +$gtk.supress_framerate_warning = true
    +$gtk.require 'app/tests/building_generation_tests.rb'
    +$gtk.tests.start
    +
    +
    +

    Platformer - Gorillas Basic - Tests - building_generation_tests.rb

    +
    # ./samples/99_genre_platformer/gorillas_basic/app/tests/building_generation_tests.rb
    +def test_solids args, assert
    +  game = YouSoBasicGorillas.new
    +  game.outputs = args.outputs
    +  game.grid = args.grid
    +  game.state = args.state
    +  game.inputs = args.inputs
    +  game.tick
    +  assert.true! args.state.stage_generated, "stage wasn't generated but it should have been"
    +  game.tick
    +  assert.true! args.outputs.static_solids.length > 0, "stage wasn't rendered"
    +  number_of_building_components = (args.state.buildings.map { |b| 2 + b.solids[2].length }.inject do |sum, v| (sum || 0) + v end)
    +  the_only_background = 1
    +  static_solids = args.outputs.static_solids.length
    +  assert.true! static_solids == the_only_background.+(number_of_building_components), "not all parts of the buildings and background were rendered"
    +end
    +
    +
    +

    Platformer - The Little Probe - main.rb

    +
    # ./samples/99_genre_platformer/the_little_probe/app/main.rb
    +class FallingCircle
    +  attr_gtk
    +
    +  def tick
    +    fiddle
    +    defaults
    +    render
    +    input
    +    calc
    +  end
    +
    +  def fiddle
    +    state.gravity     = -0.02
    +    circle.radius     = 15
    +    circle.elasticity = 0.4
    +    camera.follow_speed = 0.4 * 0.4
    +  end
    +
    +  def render
    +    render_stage_editor
    +    render_debug
    +    render_game
    +  end
    +
    +  def defaults
    +    if state.tick_count == 0
    +      outputs.sounds << "sounds/bg.ogg"
    +    end
    +
    +    state.storyline ||= [
    +      { text: "<- -> to aim, hold space to charge",                            distance_gate: 0 },
    +      { text: "the little probe - by @amirrajan, made with DragonRuby Game Toolkit", distance_gate: 0 },
    +      { text: "mission control, this is sasha. landing on europa successful.", distance_gate: 0 },
    +      { text: "operation \"find earth 2.0\", initiated at 8-29-2036 14:00.",   distance_gate: 0 },
    +      { text: "jupiter's sure is beautiful...",   distance_gate: 4000 },
    +      { text: "hmm, it seems there's some kind of anomoly in the sky",   distance_gate: 7000 },
    +      { text: "dancing lights, i'll call them whisps.",   distance_gate: 8000 },
    +      { text: "#todo... look i ran out of time -_-",   distance_gate: 9000 },
    +      { text: "there's never enough time",   distance_gate: 9000 },
    +      { text: "the game jam was fun though ^_^",   distance_gate: 10000 },
    +    ]
    +
    +    load_level force: args.state.tick_count == 0
    +    state.line_mode            ||= :terrain
    +
    +    state.sound_index          ||= 1
    +    circle.potential_lift      ||= 0
    +    circle.angle               ||= 90
    +    circle.check_point_at      ||= -1000
    +    circle.game_over_at        ||= -1000
    +    circle.x                   ||= -485
    +    circle.y                   ||= 12226
    +    circle.check_point_x       ||= circle.x
    +    circle.check_point_y       ||= circle.y
    +    circle.dy                  ||= 0
    +    circle.dx                  ||= 0
    +    circle.previous_dy         ||= 0
    +    circle.previous_dx         ||= 0
    +    circle.angle               ||= 0
    +    circle.after_images        ||= []
    +    circle.terrains_to_monitor ||= {}
    +    circle.impact_history      ||= []
    +
    +    camera.x                   ||= 0
    +    camera.y                   ||= 0
    +    camera.target_x            ||= 0
    +    camera.target_y            ||= 0
    +    state.snaps                ||= { }
    +    state.snap_number            = 10
    +
    +    args.state.storyline_x ||= -1000
    +    args.state.storyline_y ||= -1000
    +  end
    +
    +  def render_game
    +    outputs.background_color = [0, 0, 0]
    +    outputs.sprites << [-circle.x + 1100,
    +                        -circle.y - 100,
    +                        2416 * 4,
    +                        3574 * 4,
    +                        'sprites/jupiter.png']
    +    outputs.sprites << [-circle.x,
    +                        -circle.y,
    +                        2416 * 4,
    +                        3574 * 4,
    +                        'sprites/level.png']
    +    outputs.sprites << state.whisp_queue
    +    render_aiming_retical
    +    render_circle
    +    render_notification
    +  end
    +
    +  def render_notification
    +    toast_length = 500
    +    if circle.game_over_at.elapsed_time < toast_length
    +      label_text = "..."
    +    elsif circle.check_point_at.elapsed_time > toast_length
    +      args.state.current_storyline = nil
    +      return
    +    end
    +    if circle.check_point_at &&
    +       circle.check_point_at.elapsed_time == 1 &&
    +       !args.state.current_storyline
    +       if args.state.storyline.length > 0 && args.state.distance_traveled > args.state.storyline[0][:distance_gate]
    +         args.state.current_storyline = args.state.storyline.shift[:text]
    +         args.state.distance_traveled ||= 0
    +         args.state.storyline_x = circle.x
    +         args.state.storyline_y = circle.y
    +       end
    +      return unless args.state.current_storyline
    +    end
    +    label_text = args.state.current_storyline
    +    return unless label_text
    +    x = circle.x + camera.x
    +    y = circle.y + camera.y - 40
    +    w = 900
    +    h = 30
    +    outputs.primitives << [x - w.idiv(2), y - h, w, h, 255, 255, 255, 255].solid
    +    outputs.primitives << [x - w.idiv(2), y - h, w, h, 0, 0, 0, 255].border
    +    outputs.labels << [x, y - 4, label_text, 1, 1, 0, 0, 0, 255]
    +  end
    +
    +  def render_aiming_retical
    +    outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.potential_lift * 10) - 5,
    +                        state.camera.y + circle.y + circle.angle.vector_y(circle.potential_lift * 10) - 5,
    +                        10, 10, 'sprites/circle-orange.png']
    +    outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5,
    +                        state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5,
    +                        10, 10, 'sprites/circle-orange.png', 0, 128]
    +    if rand > 0.9
    +      outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5,
    +                          state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5,
    +                          10, 10, 'sprites/circle-white.png', 0, 128]
    +    end
    +  end
    +
    +  def render_circle
    +    outputs.sprites << circle.after_images.map do |ai|
    +      ai.merge(x: ai.x + state.camera.x - circle.radius,
    +               y: ai.y + state.camera.y - circle.radius,
    +               w: circle.radius * 2,
    +               h: circle.radius * 2,
    +               path: 'sprites/circle-white.png')
    +    end
    +
    +    outputs.sprites << [(circle.x - circle.radius) + state.camera.x,
    +                        (circle.y - circle.radius) + state.camera.y,
    +                        circle.radius * 2,
    +                        circle.radius * 2,
    +                        'sprites/probe.png']
    +  end
    +
    +  def render_debug
    +    return unless state.debug_mode
    +
    +    outputs.labels << [10, 30, state.line_mode, 0, 0, 0, 0, 0]
    +    outputs.labels << [12, 32, state.line_mode, 0, 0, 255, 255, 255]
    +
    +    args.outputs.lines << trajectory(circle).line.to_hash.tap do |h|
    +      h[:x] += state.camera.x
    +      h[:y] += state.camera.y
    +      h[:x2] += state.camera.x
    +      h[:y2] += state.camera.y
    +    end
    +
    +    outputs.primitives << state.terrain.find_all do |t|
    +      circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360)
    +    end.map do |t|
    +      [
    +        t.line.associate(r: 0, g: 255, b: 0) do |h|
    +          h.x  += state.camera.x
    +          h.y  += state.camera.y
    +          h.x2 += state.camera.x
    +          h.y2 += state.camera.y
    +          if circle.rect.intersect_rect? t[:rect]
    +            h[:r] = 255
    +            h[:g] = 0
    +          end
    +          h
    +        end,
    +        t[:rect].border.associate(r: 255, g: 0, b: 0) do |h|
    +          h.x += state.camera.x
    +          h.y += state.camera.y
    +          h.b = 255 if line_near_rect? circle.rect, t
    +          h
    +        end
    +      ]
    +    end
    +
    +    outputs.primitives << state.lava.find_all do |t|
    +      circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360)
    +    end.map do |t|
    +      [
    +        t.line.associate(r: 0, g: 0, b: 255) do |h|
    +          h.x  += state.camera.x
    +          h.y  += state.camera.y
    +          h.x2 += state.camera.x
    +          h.y2 += state.camera.y
    +          if circle.rect.intersect_rect? t[:rect]
    +            h[:r] = 255
    +            h[:b] = 0
    +          end
    +          h
    +        end,
    +        t[:rect].border.associate(r: 255, g: 0, b: 0) do |h|
    +          h.x += state.camera.x
    +          h.y += state.camera.y
    +          h.b = 255 if line_near_rect? circle.rect, t
    +          h
    +        end
    +      ]
    +    end
    +
    +    if state.god_mode
    +      border = circle.rect.merge(x: circle.rect.x + state.camera.x,
    +                                 y: circle.rect.y + state.camera.y,
    +                                 g: 255)
    +    else
    +      border = circle.rect.merge(x: circle.rect.x + state.camera.x,
    +                                 y: circle.rect.y + state.camera.y,
    +                                 b: 255)
    +    end
    +
    +    outputs.borders << border
    +
    +    overlapping ||= {}
    +
    +    circle.impact_history.each do |h|
    +      label_mod = 300
    +      x = (h[:body][:x].-(150).idiv(label_mod)) * label_mod + camera.x
    +      y = (h[:body][:y].+(150).idiv(label_mod)) * label_mod + camera.y
    +      10.times do
    +        if overlapping[x] && overlapping[x][y]
    +          y -= 52
    +        else
    +          break
    +        end
    +      end
    +
    +      overlapping[x] ||= {}
    +      overlapping[x][y] ||= true
    +      outputs.primitives << [x, y - 25, 300, 50, 0, 0, 0, 128].solid
    +      outputs.labels << [x + 10, y + 24, "dy: %.2f" % h[:body][:new_dy], -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 10, y +  9, "dx: %.2f" % h[:body][:new_dx], -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 10, y -  5, " ?: #{h[:body][:new_reason]}", -2, 0, 255, 255, 255]
    +
    +      outputs.labels << [x + 100, y + 24, "angle: %.2f" % h[:impact][:angle], -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 100, y + 9, "m(l): %.2f" % h[:terrain][:slope], -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 100, y - 5, "m(c): %.2f" % h[:body][:slope], -2, 0, 255, 255, 255]
    +
    +      outputs.labels << [x + 200, y + 24, "ray: #{h[:impact][:ray]}", -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 200, y +  9, "nxt: #{h[:impact][:ray_next]}", -2, 0, 255, 255, 255]
    +      outputs.labels << [x + 200, y -  5, "typ: #{h[:impact][:type]}", -2, 0, 255, 255, 255]
    +    end
    +
    +    if circle.floor
    +      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y + 100, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0]
    +      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y + 101, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0, 255, 255, 255]
    +      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y +  85, "circle: #{circle.as_hash.slice(:x, :y).values}", -2, 0]
    +      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y +  86, "circle: #{circle.as_hash.slice(:x, :y).values}", -2, 0, 255, 255, 255]
    +      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y +  70, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0]
    +      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y +  71, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0, 255, 255, 255]
    +    end
    +  end
    +
    +  def render_stage_editor
    +    return unless state.god_mode
    +    return unless state.point_one
    +    args.lines << [state.point_one, inputs.mouse.point, 0, 255, 255]
    +  end
    +
    +  def trajectory body
    +    [body.x + body.dx,
    +     body.y + body.dy,
    +     body.x + body.dx * 1000,
    +     body.y + body.dy * 1000,
    +     0, 255, 255]
    +  end
    +
    +  def lengthen_line line, num
    +    line = normalize_line(line)
    +    slope = geometry.line_slope(line, replace_infinity: 10).abs
    +    if slope < 2
    +      [line.x - num, line.y, line.x2 + num, line.y2].line.to_hash
    +    else
    +      [line.x, line.y, line.x2, line.y2].line.to_hash
    +    end
    +  end
    +
    +  def normalize_line line
    +    if line.x > line.x2
    +      x  = line.x2
    +      y  = line.y2
    +      x2 = line.x
    +      y2 = line.y
    +    else
    +      x  = line.x
    +      y  = line.y
    +      x2 = line.x2
    +      y2 = line.y2
    +    end
    +    [x, y, x2, y2]
    +  end
    +
    +  def rect_for_line line
    +    if line.x > line.x2
    +      x  = line.x2
    +      y  = line.y2
    +      x2 = line.x
    +      y2 = line.y
    +    else
    +      x  = line.x
    +      y  = line.y
    +      x2 = line.x2
    +      y2 = line.y2
    +    end
    +
    +    w = x2 - x
    +    h = y2 - y
    +
    +    if h < 0
    +      y += h
    +      h = h.abs
    +    end
    +
    +    if w < circle.radius
    +      x -= circle.radius
    +      w = circle.radius * 2
    +    end
    +
    +    if h < circle.radius
    +      y -= circle.radius
    +      h = circle.radius * 2
    +    end
    +
    +    { x: x, y: y, w: w, h: h }
    +  end
    +
    +  def snap_to_grid x, y, snaps
    +    snap_number = 10
    +    x = x.to_i
    +    y = y.to_i
    +
    +    x_floor = x.idiv(snap_number) * snap_number
    +    x_mod   = x % snap_number
    +    x_ceil  = (x.idiv(snap_number) + 1) * snap_number
    +
    +    y_floor = y.idiv(snap_number) * snap_number
    +    y_mod   = y % snap_number
    +    y_ceil  = (y.idiv(snap_number) + 1) * snap_number
    +
    +    if snaps[x_floor]
    +      x_result = x_floor
    +    elsif snaps[x_ceil]
    +      x_result = x_ceil
    +    elsif x_mod < snap_number.idiv(2)
    +      x_result = x_floor
    +    else
    +      x_result = x_ceil
    +    end
    +
    +    snaps[x_result] ||= {}
    +
    +    if snaps[x_result][y_floor]
    +      y_result = y_floor
    +    elsif snaps[x_result][y_ceil]
    +      y_result = y_ceil
    +    elsif y_mod < snap_number.idiv(2)
    +      y_result = y_floor
    +    else
    +      y_result = y_ceil
    +    end
    +
    +    snaps[x_result][y_result] = true
    +    return [x_result, y_result]
    +
    +  end
    +
    +  def snap_line line
    +    x, y, x2, y2 = line
    +  end
    +
    +  def string_to_line s
    +    x, y, x2, y2 = s.split(',').map(&:to_f)
    +
    +    if x > x2
    +      x2, x = x, x2
    +      y2, y = y, y2
    +    end
    +
    +    x, y = snap_to_grid x, y, state.snaps
    +    x2, y2 = snap_to_grid x2, y2, state.snaps
    +    [x, y, x2, y2].line.to_hash
    +  end
    +
    +  def load_lines file
    +    data = gtk.read_file(file) || ""
    +    data.each_line
    +        .reject { |l| l.strip.length == 0 }
    +        .map { |l| string_to_line l }
    +        .map { |h| h.merge(rect: rect_for_line(h))  }
    +  end
    +
    +  def load_terrain
    +    load_lines 'data/level.txt'
    +  end
    +
    +  def load_lava
    +    load_lines 'data/level_lava.txt'
    +  end
    +
    +  def load_level force: false
    +    if force
    +      state.snaps = {}
    +      state.terrain = load_terrain
    +      state.lava = load_lava
    +    else
    +      state.terrain ||= load_terrain
    +      state.lava ||= load_lava
    +    end
    +  end
    +
    +  def save_lines lines, file
    +    s = lines.map do |l|
    +      "#{l.x1},#{l.y1},#{l.x2},#{l.y2}"
    +    end.join("\n")
    +    gtk.write_file(file, s)
    +  end
    +
    +  def save_level
    +    save_lines(state.terrain, 'level.txt')
    +    save_lines(state.lava, 'level_lava.txt')
    +    load_level force: true
    +  end
    +
    +  def line_near_rect? rect, terrain
    +    geometry.intersect_rect?(rect, terrain[:rect])
    +  end
    +
    +  def point_within_line? point, line
    +    return false if !point
    +    return false if !line
    +    return true
    +  end
    +
    +  def calc_impacts x, dx, y, dy, radius
    +    results = { }
    +    results[:x] = x
    +    results[:y] = y
    +    results[:dx] = x
    +    results[:dy] = y
    +    results[:point] = { x: x, y: y }
    +    results[:rect] = { x: x - radius, y: y - radius, w: radius * 2, h: radius * 2 }
    +    results[:trajectory] = trajectory(results)
    +    results[:impacts] = terrain.find_all { |t| line_near_rect? results[:rect], t }.map do |t|
    +      {
    +        terrain: t,
    +        point: geometry.line_intersect(results[:trajectory], t),
    +        type: :terrain
    +      }
    +    end.reject { |t| !point_within_line? t[:point], t[:terrain] }
    +
    +    results[:impacts] += lava.find_all { |t| line_near_rect? results[:rect], t }.map do |t|
    +      {
    +        terrain: t,
    +        point: geometry.line_intersect(results[:trajectory], t),
    +        type: :lava
    +      }
    +    end.reject { |t| !point_within_line? t[:point], t[:terrain] }
    +
    +    results
    +  end
    +
    +  def calc_potential_impacts
    +    impact_results = calc_impacts circle.x, circle.dx, circle.y, circle.dy, circle.radius
    +    circle.rect = impact_results[:rect]
    +    circle.trajectory = impact_results[:trajectory]
    +    circle.impacts = impact_results[:impacts]
    +  end
    +
    +  def calc_terrains_to_monitor
    +    circle.impact = nil
    +    circle.impacts.each do |i|
    +      circle.terrains_to_monitor[i[:terrain]] ||= {
    +        ray_start: geometry.ray_test(circle, i[:terrain]),
    +      }
    +
    +      circle.terrains_to_monitor[i[:terrain]][:ray_current] = geometry.ray_test(circle, i[:terrain])
    +      if circle.terrains_to_monitor[i[:terrain]][:ray_start] != circle.terrains_to_monitor[i[:terrain]][:ray_current]
    +        if circle.x.between?(i[:terrain].x, i[:terrain].x2) || circle.y.between?(i[:terrain].y, i[:terrain].y2)
    +          circle.impact = i
    +          circle.ray_current = circle.terrains_to_monitor[i[:terrain]][:ray_current]
    +        end
    +      end
    +    end
    +  end
    +
    +  def impact_result body, impact
    +    infinity_alias = 1000
    +    r = {
    +      body: {},
    +      terrain: {},
    +      impact: {}
    +    }
    +
    +    r[:body][:line] = body.trajectory.dup
    +    r[:body][:slope] = geometry.line_slope(body.trajectory, replace_infinity: infinity_alias)
    +    r[:body][:slope_sign] = r[:body][:slope].sign
    +    r[:body][:x] = body.x
    +    r[:body][:y] = body.y
    +    r[:body][:dy] = body.dy
    +    r[:body][:dx] = body.dx
    +
    +    r[:terrain][:line] = impact[:terrain].dup
    +    r[:terrain][:slope] = geometry.line_slope(impact[:terrain], replace_infinity: infinity_alias)
    +    r[:terrain][:slope_sign] = r[:terrain][:slope].sign
    +
    +    r[:impact][:angle] = geometry.angle_between_lines(body.trajectory, impact[:terrain], replace_infinity: infinity_alias)
    +    r[:impact][:point] = { x: impact[:point].x, y: impact[:point].y }
    +    r[:impact][:same_slope_sign] = r[:body][:slope_sign] == r[:terrain][:slope_sign]
    +    r[:impact][:ray] = body.ray_current
    +    r[:body][:new_on_floor] = body.on_floor
    +    r[:body][:new_floor] = r[:terrain][:line]
    +
    +    if r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs < 3
    +      play_sound
    +      r[:body][:new_dy] = r[:body][:dy] * circle.elasticity * -1
    +      r[:body][:new_dx] = r[:body][:dx] * circle.elasticity
    +      r[:impact][:type] = :horizontal
    +      r[:body][:new_reason] = "-"
    +    elsif r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs > 3
    +      play_sound
    +      r[:body][:new_dy] = r[:body][:dy] * 1.1
    +      r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity
    +      r[:impact][:type] = :vertical
    +      r[:body][:new_reason] = "|"
    +    else
    +      play_sound
    +      r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity
    +      r[:body][:new_dy] = r[:body][:dy] * -circle.elasticity
    +      r[:impact][:type] = :slanted
    +      r[:body][:new_reason] = "/"
    +    end
    +
    +    r[:impact][:energy] = r[:body][:new_dx].abs + r[:body][:new_dy].abs
    +
    +    if r[:impact][:energy] <= 0.3 && r[:terrain][:slope].abs < 4
    +      r[:body][:new_dx] = 0
    +      r[:body][:new_dy] = 0
    +      r[:impact][:energy] = 0
    +      r[:body][:new_on_floor] = true
    +      r[:body][:new_floor] = r[:terrain][:line]
    +      r[:body][:new_reason] = "0"
    +    end
    +
    +    r[:impact][:ray_next] = geometry.ray_test({ x: r[:body][:x] - (r[:body][:dx] * 1.1) + r[:body][:new_dx],
    +                                                y: r[:body][:y] - (r[:body][:dy] * 1.1) + r[:body][:new_dy] + state.gravity },
    +                                              r[:terrain][:line])
    +
    +    if r[:impact][:ray_next] == r[:impact][:ray]
    +      r[:body][:new_dx] *= -1
    +      r[:body][:new_dy] *= -1
    +      r[:body][:new_reason] = "clip"
    +    end
    +
    +    r
    +  end
    +
    +  def game_over!
    +    circle.x = circle.check_point_x
    +    circle.y = circle.check_point_y
    +    circle.dx = 0
    +    circle.dy = 0
    +    circle.game_over_at = state.tick_count
    +  end
    +
    +  def not_game_over!
    +    impact_history_entry = impact_result circle, circle.impact
    +    circle.impact_history << impact_history_entry
    +    circle.x -= circle.dx * 1.1
    +    circle.y -= circle.dy * 1.1
    +    circle.dx = impact_history_entry[:body][:new_dx]
    +    circle.dy = impact_history_entry[:body][:new_dy]
    +    circle.on_floor = impact_history_entry[:body][:new_on_floor]
    +
    +    if circle.on_floor
    +      circle.check_point_at = state.tick_count
    +      circle.check_point_x = circle.x
    +      circle.check_point_y = circle.y
    +    end
    +
    +    circle.previous_floor = circle.floor || {}
    +    circle.floor = impact_history_entry[:body][:new_floor] || {}
    +    circle.floor_point = impact_history_entry[:impact][:point]
    +    if circle.floor.slice(:x, :y, :x2, :y2) != circle.previous_floor.slice(:x, :y, :x2, :y2)
    +      new_relative_x = if circle.dx > 0
    +                         :right
    +                       elsif circle.dx < 0
    +                         :left
    +                       else
    +                         nil
    +                       end
    +
    +      new_relative_y = if circle.dy > 0
    +                         :above
    +                       elsif circle.dy < 0
    +                         :below
    +                       else
    +                         nil
    +                       end
    +
    +      circle.floor_relative_x = new_relative_x
    +      circle.floor_relative_y = new_relative_y
    +    end
    +
    +    circle.impact = nil
    +    circle.terrains_to_monitor.clear
    +  end
    +
    +  def calc_physics
    +    if args.state.god_mode
    +      calc_potential_impacts
    +      calc_terrains_to_monitor
    +      return
    +    end
    +
    +    if circle.y < -700
    +      game_over
    +      return
    +    end
    +
    +    return if state.game_over
    +    return if circle.on_floor
    +    circle.previous_dy = circle.dy
    +    circle.previous_dx = circle.dx
    +    circle.x  += circle.dx
    +    circle.y  += circle.dy
    +    args.state.distance_traveled ||= 0
    +    args.state.distance_traveled += circle.dx.abs + circle.dy.abs
    +    circle.dy += state.gravity
    +    calc_potential_impacts
    +    calc_terrains_to_monitor
    +    return unless circle.impact
    +    if circle.impact && circle.impact[:type] == :lava
    +      game_over!
    +    else
    +      not_game_over!
    +    end
    +  end
    +
    +  def input_god_mode
    +    state.debug_mode = !state.debug_mode if inputs.keyboard.key_down.forward_slash
    +
    +    # toggle god mode
    +    if inputs.keyboard.key_down.g
    +      state.god_mode = !state.god_mode
    +      state.potential_lift = 0
    +      circle.floor = nil
    +      circle.floor_point = nil
    +      circle.floor_relative_x = nil
    +      circle.floor_relative_y = nil
    +      circle.impact = nil
    +      circle.terrains_to_monitor.clear
    +      return
    +    end
    +
    +    return unless state.god_mode
    +
    +    circle.x = circle.x.to_i
    +    circle.y = circle.y.to_i
    +
    +    # move god circle
    +    if inputs.keyboard.left || inputs.keyboard.a
    +      circle.x -= 20
    +    elsif inputs.keyboard.right || inputs.keyboard.d || inputs.keyboard.f
    +      circle.x += 20
    +    end
    +
    +    if inputs.keyboard.up || inputs.keyboard.w
    +      circle.y += 20
    +    elsif inputs.keyboard.down || inputs.keyboard.s
    +      circle.y -= 20
    +    end
    +
    +    # delete terrain
    +    if inputs.keyboard.key_down.x
    +      calc_terrains_to_monitor
    +      state.terrain = state.terrain.reject do |t|
    +        t[:rect].intersect_rect? circle.rect
    +      end
    +
    +      state.lava = state.lava.reject do |t|
    +        t[:rect].intersect_rect? circle.rect
    +      end
    +
    +      calc_potential_impacts
    +      save_level
    +    end
    +
    +    # change terrain type
    +    if inputs.keyboard.key_down.l
    +      if state.line_mode == :terrain
    +        state.line_mode = :lava
    +      else
    +        state.line_mode = :terrain
    +      end
    +    end
    +
    +    if inputs.mouse.click && !state.point_one
    +      state.point_one = inputs.mouse.click.point
    +    elsif inputs.mouse.click && state.point_one
    +      l = [*state.point_one, *inputs.mouse.click.point]
    +      l = [l.x  - state.camera.x,
    +           l.y  - state.camera.y,
    +           l.x2 - state.camera.x,
    +           l.y2 - state.camera.y].line.to_hash
    +      l[:rect] = rect_for_line l
    +      if state.line_mode == :terrain
    +        state.terrain << l
    +      else
    +        state.lava << l
    +      end
    +      save_level
    +      next_x = inputs.mouse.click.point.x - 640
    +      next_y = inputs.mouse.click.point.y - 360
    +      circle.x += next_x
    +      circle.y += next_y
    +      state.point_one = nil
    +    elsif inputs.keyboard.one
    +      state.point_one = [circle.x + camera.x, circle.y+ camera.y]
    +    end
    +
    +    # cancel chain lines
    +    if inputs.keyboard.key_down.nine || inputs.keyboard.key_down.escape || inputs.keyboard.key_up.six || inputs.keyboard.key_up.one
    +      state.point_one = nil
    +    end
    +  end
    +
    +  def play_sound
    +    return if state.sound_debounce > 0
    +    state.sound_debounce = 5
    +    outputs.sounds << "sounds/03#{"%02d" % state.sound_index}.wav"
    +    state.sound_index += 1
    +    if state.sound_index > 21
    +      state.sound_index = 1
    +    end
    +  end
    +
    +  def input_game
    +    if inputs.keyboard.down || inputs.keyboard.space
    +      circle.potential_lift += 0.03
    +      circle.potential_lift = circle.potential_lift.lesser(10)
    +    elsif inputs.keyboard.key_up.down || inputs.keyboard.key_up.space
    +      play_sound
    +      circle.dy += circle.angle.vector_y circle.potential_lift
    +      circle.dx += circle.angle.vector_x circle.potential_lift
    +
    +      if circle.on_floor
    +        if circle.floor_relative_y == :above
    +          circle.y += circle.potential_lift.abs * 2
    +        elsif circle.floor_relative_y == :below
    +          circle.y -= circle.potential_lift.abs * 2
    +        end
    +      end
    +
    +      circle.on_floor = false
    +      circle.potential_lift = 0
    +      circle.terrains_to_monitor.clear
    +      circle.impact_history.clear
    +      circle.impact = nil
    +      calc_physics
    +    end
    +
    +    # aim probe
    +    if inputs.keyboard.right || inputs.keyboard.a
    +      circle.angle -= 2
    +    elsif inputs.keyboard.left || inputs.keyboard.d
    +      circle.angle += 2
    +    end
    +  end
    +
    +  def input
    +    input_god_mode
    +    input_game
    +  end
    +
    +  def calc_camera
    +    state.camera.target_x = 640 - circle.x
    +    state.camera.target_y = 360 - circle.y
    +    xdiff = state.camera.target_x - state.camera.x
    +    ydiff = state.camera.target_y - state.camera.y
    +    state.camera.x += xdiff * camera.follow_speed
    +    state.camera.y += ydiff * camera.follow_speed
    +  end
    +
    +  def calc
    +    state.sound_debounce ||= 0
    +    state.sound_debounce -= 1
    +    state.sound_debounce = 0 if state.sound_debounce < 0
    +    if state.god_mode
    +      circle.dy *= 0.1
    +      circle.dx *= 0.1
    +    end
    +    calc_camera
    +    state.whisp_queue ||= []
    +    if state.tick_count.mod_zero?(4)
    +      state.whisp_queue << {
    +        x: -300,
    +        y: 1400 * rand,
    +        speed: 2.randomize(:ratio) + 3,
    +        w: 20,
    +        h: 20, path: 'sprites/whisp.png',
    +        a: 0,
    +        created_at: state.tick_count,
    +        angle: 0,
    +        r: 100,
    +        g: 128 + 128 * rand,
    +        b: 128 + 128 * rand
    +      }
    +    end
    +
    +    state.whisp_queue.each do |w|
    +      w.x += w[:speed] * 2
    +      w.x -= circle.dx * 0.3
    +      w.y -= w[:speed]
    +      w.y -= circle.dy * 0.3
    +      w.angle += w[:speed]
    +      w.a = w[:created_at].ease(30) * 255
    +    end
    +
    +    state.whisp_queue = state.whisp_queue.reject { |w| w[:x] > 1280 }
    +
    +    if state.tick_count.mod_zero?(2) && (circle.dx != 0 || circle.dy != 0)
    +      circle.after_images << {
    +        x: circle.x,
    +        y: circle.y,
    +        w: circle.radius,
    +        h: circle.radius,
    +        a: 255,
    +        created_at: state.tick_count
    +      }
    +    end
    +
    +    circle.after_images.each do |ai|
    +      ai.a = ai[:created_at].ease(10, :flip) * 255
    +    end
    +
    +    circle.after_images = circle.after_images.reject { |ai| ai[:created_at].elapsed_time > 10 }
    +    calc_physics
    +  end
    +
    +  def circle
    +    state.circle
    +  end
    +
    +  def camera
    +    state.camera
    +  end
    +
    +  def terrain
    +    state.terrain
    +  end
    +
    +  def lava
    +    state.lava
    +  end
    +end
    +
    +# $gtk.reset
    +
    +def tick args
    +  args.outputs.background_color = [0, 0, 0]
    +  if args.inputs.keyboard.r
    +    args.gtk.reset
    +    return
    +  end
    +  # uncomment the line below to slow down the game so you
    +  # can see each tick as it passes
    +  # args.gtk.slowmo! 30
    +  $game ||= FallingCircle.new
    +  $game.args = args
    +  $game.tick
    +end
    +
    +def reset
    +  $game = nil
    +end
    +
    +
    +

    Platformer - The Little Probe - Data - level.txt

    +
    # ./samples/99_genre_platformer/the_little_probe/data/level.txt
    +640,8840,1180,8840
    +-60,10220,0,9960
    +-60,10220,0,10500
    +0,10500,0,10780
    +0,10780,40,10900
    +500,10920,760,10960
    +300,10560,820,10600
    +420,10320,700,10300
    +820,10600,1500,10600
    +1500,10600,1940,10600
    +1940,10600,2380,10580
    +2380,10580,2800,10620
    +2240,11080,2480,11020
    +2000,11120,2240,11080
    +1760,11180,2000,11120
    +1620,11180,1760,11180
    +1500,11220,1620,11180
    +1180,11280,1340,11220
    +1040,11240,1180,11280
    +840,11280,1040,11240
    +640,11280,840,11280
    +500,11220,640,11280
    +420,11140,500,11220
    +240,11100,420,11140
    +100,11120,240,11100
    +0,11180,100,11120
    +-160,11220,0,11180
    +-260,11240,-160,11220
    +1340,11220,1500,11220
    +960,13300,1280,13060
    +1280,13060,1540,12860
    +1540,12860,1820,12700
    +1820,12700,2080,12520
    +2080,12520,2240,12400
    +2240,12400,2240,12240
    +2240,12240,2400,12080
    +2400,12080,2560,11920
    +2560,11920,2640,11740
    +2640,11740,2740,11580
    +2740,11580,2800,11400
    +2800,11400,2800,11240
    +2740,11140,2800,11240
    +2700,11040,2740,11140
    +2700,11040,2740,10960
    +2740,10960,2740,10920
    +2700,10900,2740,10920
    +2380,10900,2700,10900
    +2040,10920,2380,10900
    +1720,10940,2040,10920
    +1380,11000,1720,10940
    +1180,10980,1380,11000
    +900,10980,1180,10980
    +760,10960,900,10980
    +240,10960,500,10920
    +40,10900,240,10960
    +0,9700,0,9960
    +-60,9500,0,9700
    +-60,9420,-60,9500
    +-60,9420,-60,9340
    +-60,9340,-60,9280
    +-60,9120,-60,9280
    +-60,8940,-60,9120
    +-60,8940,-60,8780
    +-60,8780,0,8700
    +0,8700,40,8680
    +40,8680,240,8700
    +240,8700,360,8780
    +360,8780,640,8840
    +1420,8400,1540,8480
    +1540,8480,1680,8500
    +1680,8500,1940,8460
    +1180,8840,1280,8880
    +1280,8880,1340,8860
    +1340,8860,1720,8860
    +1720,8860,1820,8920
    +1820,8920,1820,9140
    +1820,9140,1820,9280
    +1820,9460,1820,9280
    +1760,9480,1820,9460
    +1640,9480,1760,9480
    +1540,9500,1640,9480
    +1340,9500,1540,9500
    +1100,9500,1340,9500
    +1040,9540,1100,9500
    +960,9540,1040,9540
    +300,9420,360,9460
    +240,9440,300,9420
    +180,9600,240,9440
    +120,9660,180,9600
    +100,9820,120,9660
    +100,9820,120,9860
    +120,9860,140,9900
    +140,9900,140,10000
    +140,10440,180,10540
    +100,10080,140,10000
    +100,10080,140,10100
    +140,10100,140,10440
    +180,10540,300,10560
    +2140,9560,2140,9640
    +2140,9720,2140,9640
    +1880,9780,2140,9720
    +1720,9780,1880,9780
    +1620,9740,1720,9780
    +1500,9780,1620,9740
    +1380,9780,1500,9780
    +1340,9820,1380,9780
    +1200,9820,1340,9820
    +1100,9780,1200,9820
    +900,9780,1100,9780
    +820,9720,900,9780
    +540,9720,820,9720
    +360,9840,540,9720
    +360,9840,360,9960
    +360,9960,360,10080
    +360,10140,360,10080
    +360,10140,360,10240
    +360,10240,420,10320
    +700,10300,820,10280
    +820,10280,820,10280
    +820,10280,900,10320
    +900,10320,1040,10300
    +1040,10300,1200,10320
    +1200,10320,1380,10280
    +1380,10280,1500,10300
    +1500,10300,1760,10300
    +2800,10620,2840,10600
    +2840,10600,2900,10600
    +2900,10600,3000,10620
    +3000,10620,3080,10620
    +3080,10620,3140,10600
    +3140,10540,3140,10600
    +3140,10540,3140,10460
    +3140,10460,3140,10360
    +3140,10360,3140,10260
    +3140,10260,3140,10140
    +3140,10140,3140,10000
    +3140,10000,3140,9860
    +3140,9860,3160,9720
    +3160,9720,3160,9580
    +3160,9580,3160,9440
    +3160,9300,3160,9440
    +3160,9300,3160,9140
    +3160,9140,3160,8980
    +3160,8980,3160,8820
    +3160,8820,3160,8680
    +3160,8680,3160,8520
    +1760,10300,1880,10300
    +660,9500,960,9540
    +640,9460,660,9500
    +360,9460,640,9460
    +-480,10760,-440,10880
    +-480,11020,-440,10880
    +-480,11160,-260,11240
    +-480,11020,-480,11160
    +-600,11420,-380,11320
    +-380,11320,-200,11340
    +-200,11340,0,11340
    +0,11340,180,11340
    +960,13420,960,13300
    +960,13420,960,13520
    +960,13520,1000,13560
    +1000,13560,1040,13540
    +1040,13540,1200,13440
    +1200,13440,1380,13380
    +1380,13380,1620,13300
    +1620,13300,1820,13220
    +1820,13220,2000,13200
    +2000,13200,2240,13200
    +2240,13200,2440,13160
    +2440,13160,2640,13040
    +-480,10760,-440,10620
    +-440,10620,-360,10560
    +-380,10460,-360,10560
    +-380,10460,-360,10300
    +-380,10140,-360,10300
    +-380,10140,-380,10040
    +-380,9880,-380,10040
    +-380,9720,-380,9880
    +-380,9720,-380,9540
    +-380,9360,-380,9540
    +-380,9180,-380,9360
    +-380,9180,-380,9000
    +-380,8840,-380,9000
    +-380,8840,-380,8760
    +-380,8760,-380,8620
    +-380,8620,-380,8520
    +-380,8520,-360,8400
    +-360,8400,-100,8400
    +-100,8400,-60,8420
    +-60,8420,240,8440
    +240,8440,240,8380
    +240,8380,500,8440
    +500,8440,760,8460
    +760,8460,1000,8400
    +1000,8400,1180,8420
    +1180,8420,1420,8400
    +1940,8460,2140,8420
    +2140,8420,2200,8520
    +2200,8680,2200,8520
    +2140,8840,2200,8680
    +2140,8840,2140,9020
    +2140,9100,2140,9020
    +2140,9200,2140,9100
    +2140,9200,2200,9320
    +2200,9320,2200,9440
    +2140,9560,2200,9440
    +1880,10300,2200,10280
    +2200,10280,2480,10260
    +2480,10260,2700,10240
    +2700,10240,2840,10180
    +2840,10180,2900,10060
    +2900,9860,2900,10060
    +2900,9640,2900,9860
    +2900,9640,2900,9500
    +2900,9460,2900,9500
    +2740,9460,2900,9460
    +2700,9460,2740,9460
    +2700,9360,2700,9460
    +2700,9320,2700,9360
    +2600,9320,2700,9320
    +2600,9260,2600,9320
    +2600,9200,2600,9260
    +2480,9120,2600,9200
    +2440,9080,2480,9120
    +2380,9080,2440,9080
    +2320,9060,2380,9080
    +2320,8860,2320,9060
    +2320,8860,2380,8840
    +2380,8840,2480,8860
    +2480,8860,2600,8840
    +2600,8840,2740,8840
    +2740,8840,2840,8800
    +2840,8800,2900,8700
    +2900,8600,2900,8700
    +2900,8480,2900,8600
    +2900,8380,2900,8480
    +2900,8380,2900,8260
    +2900,8260,2900,8140
    +2900,8140,2900,8020
    +2900,8020,2900,7900
    +2900,7820,2900,7900
    +2900,7820,2900,7740
    +2900,7660,2900,7740
    +2900,7560,2900,7660
    +2900,7460,2900,7560
    +2900,7460,2900,7360
    +2900,7260,2900,7360
    +2840,7160,2900,7260
    +2800,7080,2840,7160
    +2700,7100,2800,7080
    +2560,7120,2700,7100
    +2400,7100,2560,7120
    +2320,7100,2400,7100
    +2140,7100,2320,7100
    +2040,7080,2140,7100
    +1940,7080,2040,7080
    +1820,7140,1940,7080
    +1680,7140,1820,7140
    +1540,7140,1680,7140
    +1420,7220,1540,7140
    +1280,7220,1380,7220
    +1140,7200,1280,7220
    +1000,7220,1140,7200
    +760,7280,900,7320
    +540,7220,760,7280
    +300,7180,540,7220
    +180,7120,180,7160
    +40,7140,180,7120
    +-60,7160,40,7140
    +-200,7120,-60,7160
    +180,7160,300,7180
    +-260,7060,-200,7120
    +-260,6980,-260,7060
    +-260,6880,-260,6980
    +-260,6880,-260,6820
    +-260,6820,-200,6760
    +-200,6760,-100,6740
    +-100,6740,-60,6740
    +-60,6740,40,6740
    +40,6740,300,6800
    +300,6800,420,6760
    +420,6760,500,6740
    +500,6740,540,6760
    +540,6760,540,6760
    +540,6760,640,6780
    +640,6660,640,6780
    +580,6580,640,6660
    +580,6440,580,6580
    +580,6440,640,6320
    +640,6320,640,6180
    +580,6080,640,6180
    +580,6080,640,5960
    +640,5960,640,5840
    +640,5840,640,5700
    +640,5700,660,5560
    +660,5560,660,5440
    +660,5440,660,5300
    +660,5140,660,5300
    +660,5140,660,5000
    +660,5000,660,4880
    +660,4880,820,4860
    +820,4860,1000,4840
    +1000,4840,1100,4860
    +1100,4860,1280,4860
    +1280,4860,1420,4840
    +1420,4840,1580,4860
    +1580,4860,1720,4820
    +1720,4820,1880,4860
    +1880,4860,2000,4840
    +2000,4840,2140,4840
    +2140,4840,2320,4860
    +2320,4860,2440,4880
    +2440,4880,2600,4880
    +2600,4880,2800,4880
    +2800,4880,2900,4880
    +2900,4880,2900,4820
    +2900,4740,2900,4820
    +2800,4700,2900,4740
    +2520,4680,2800,4700
    +2240,4660,2520,4680
    +1940,4620,2240,4660
    +1820,4580,1940,4620
    +1820,4500,1820,4580
    +1820,4500,1880,4420
    +1880,4420,2000,4420
    +2000,4420,2200,4420
    +2200,4420,2400,4440
    +2400,4440,2600,4440
    +2600,4440,2840,4440
    +2840,4440,2900,4400
    +2740,4260,2900,4280
    +2600,4240,2740,4260
    +2480,4280,2600,4240
    +2320,4240,2480,4280
    +2140,4220,2320,4240
    +1940,4220,2140,4220
    +1880,4160,1940,4220
    +1880,4160,1880,4080
    +1880,4080,2040,4040
    +2040,4040,2240,4060
    +2240,4060,2400,4040
    +2400,4040,2600,4060
    +2600,4060,2740,4020
    +2740,4020,2840,3940
    +2840,3780,2840,3940
    +2740,3660,2840,3780
    +2700,3680,2740,3660
    +2520,3700,2700,3680
    +2380,3700,2520,3700
    +2200,3720,2380,3700
    +2040,3720,2200,3720
    +1880,3700,2040,3720
    +1820,3680,1880,3700
    +1760,3600,1820,3680
    +1760,3600,1820,3480
    +1820,3480,1880,3440
    +1880,3440,1960,3460
    +1960,3460,2140,3460
    +2140,3460,2380,3460
    +2380,3460,2640,3440
    +2640,3440,2900,3380
    +2840,3280,2900,3380
    +2840,3280,2900,3200
    +2900,3200,2900,3140
    +2840,3020,2900,3140
    +2800,2960,2840,3020
    +2700,3000,2800,2960
    +2600,2980,2700,3000
    +2380,3000,2600,2980
    +2140,3000,2380,3000
    +1880,3000,2140,3000
    +1720,3040,1880,3000
    +1640,2960,1720,3040
    +1500,2940,1640,2960
    +1340,3000,1500,2940
    +1240,3000,1340,3000
    +1140,3020,1240,3000
    +1040,3000,1140,3020
    +960,2960,1040,3000
    +900,2960,960,2960
    +840,2840,900,2960
    +700,2820,840,2840
    +540,2820,700,2820
    +420,2820,540,2820
    +180,2800,420,2820
    +60,2780,180,2800
    +-60,2800,60,2780
    +-160,2760,-60,2800
    +-260,2740,-160,2760
    +-300,2640,-260,2740
    +-360,2560,-300,2640
    +-380,2460,-360,2560
    +-380,2460,-300,2380
    +-300,2300,-300,2380
    +-300,2300,-300,2220
    +-300,2100,-300,2220
    +-300,2100,-300,2040
    +-300,2040,-160,2040
    +-160,2040,-60,2040
    +-60,2040,60,2040
    +60,2040,180,2040
    +180,2040,360,2040
    +360,2040,540,2040
    +540,2040,700,2080
    +660,2160,700,2080
    +660,2160,700,2260
    +660,2380,700,2260
    +500,2340,660,2380
    +360,2340,500,2340
    +240,2340,360,2340
    +40,2320,240,2340
    +-60,2320,40,2320
    +-100,2380,-60,2320
    +-100,2380,-100,2460
    +-100,2460,-100,2540
    +-100,2540,0,2560
    +0,2560,140,2600
    +140,2600,300,2600
    +300,2600,460,2600
    +460,2600,640,2600
    +640,2600,760,2580
    +760,2580,820,2560
    +820,2560,820,2500
    +820,2500,820,2400
    +820,2400,840,2320
    +840,2320,840,2240
    +820,2120,840,2240
    +820,2020,820,2120
    +820,1900,820,2020
    +760,1840,820,1900
    +640,1840,760,1840
    +500,1840,640,1840
    +300,1860,420,1880
    +180,1840,300,1860
    +420,1880,500,1840
    +0,1840,180,1840
    +-60,1860,0,1840
    +-160,1840,-60,1860
    +-200,1800,-160,1840
    +-260,1760,-200,1800
    +-260,1680,-260,1760
    +-260,1620,-260,1680
    +-260,1540,-260,1620
    +-260,1540,-260,1460
    +-300,1420,-260,1460
    +-300,1420,-300,1340
    +-300,1340,-260,1260
    +-260,1260,-260,1160
    +-260,1060,-260,1160
    +-260,1060,-260,960
    +-260,880,-260,960
    +-260,880,-260,780
    +-260,780,-260,680
    +-300,580,-260,680
    +-300,580,-300,480
    +-300,480,-260,400
    +-300,320,-260,400
    +-300,320,-300,240
    +-300,240,-200,220
    +-200,220,-200,160
    +-200,160,-100,140
    +-100,140,0,120
    +0,120,60,120
    +60,120,180,120
    +180,120,300,120
    +300,120,420,140
    +420,140,580,180
    +580,180,760,180
    +760,180,900,180
    +960,180,1100,180
    +1100,180,1340,200
    +1340,200,1580,200
    +1580,200,1720,180
    +1720,180,2000,140
    +2000,140,2240,140
    +2240,140,2480,140
    +2520,140,2800,160
    +2800,160,3000,160
    +3000,160,3140,160
    +3140,260,3140,160
    +3140,260,3140,380
    +3080,500,3140,380
    +3080,620,3080,500
    +3080,620,3080,740
    +3080,740,3080,840
    +3080,960,3080,840
    +3080,1080,3080,960
    +3080,1080,3080,1200
    +3080,1200,3080,1340
    +3080,1340,3080,1460
    +3080,1580,3080,1460
    +3080,1700,3080,1580
    +3080,1700,3080,1760
    +3080,1760,3200,1760
    +3200,1760,3320,1760
    +3320,1760,3520,1760
    +3520,1760,3680,1740
    +3680,1740,3780,1700
    +3780,1700,3840,1620
    +3840,1620,3840,1520
    +3840,1520,3840,1420
    +3840,1320,3840,1420
    +3840,1120,3840,1320
    +3840,1120,3840,940
    +3840,940,3840,760
    +3780,600,3840,760
    +3780,600,3780,440
    +3780,320,3780,440
    +3780,320,3780,160
    +3780,60,3780,160
    +3780,60,4020,60
    +4020,60,4260,40
    +4260,40,4500,40
    +4500,40,4740,40
    +4740,40,4840,20
    +4840,20,4880,80
    +4880,80,5080,40
    +5080,40,5280,20
    +5280,20,5500,0
    +5500,0,5720,0
    +5720,0,5940,60
    +5940,60,6240,60
    +6240,60,6540,20
    +6540,20,6840,20
    +6840,20,7040,0
    +7040,0,7140,0
    +7140,0,7400,20
    +7400,20,7680,0
    +7680,0,7940,0
    +7940,0,8200,-20
    +8200,-20,8360,20
    +8360,20,8560,-40
    +8560,-40,8760,0
    +8760,0,8880,40
    +8880,120,8880,40
    +8840,220,8840,120
    +8620,240,8840,220
    +8420,260,8620,240
    +8200,280,8420,260
    +7940,280,8200,280
    +7760,240,7940,280
    +7560,220,7760,240
    +7360,280,7560,220
    +7140,260,7360,280
    +6940,240,7140,260
    +6720,220,6940,240
    +6480,220,6720,220
    +6360,300,6480,220
    +6240,300,6360,300
    +6200,500,6240,300
    +6200,500,6360,540
    +6360,540,6540,520
    +6540,520,6720,480
    +6720,480,6880,460
    +6880,460,7080,500
    +7080,500,7320,500
    +7320,500,7680,500
    +7680,620,7680,500
    +7520,640,7680,620
    +7360,640,7520,640
    +7200,640,7360,640
    +7040,660,7200,640
    +6880,720,7040,660
    +6720,700,6880,720
    +6540,700,6720,700
    +6420,760,6540,700
    +6280,740,6420,760
    +6240,760,6280,740
    +6200,920,6240,760
    +6200,920,6360,960
    +6360,960,6540,960
    +6540,960,6720,960
    +6720,960,6760,980
    +6760,980,6880,940
    +6880,940,7080,940
    +7080,940,7280,940
    +7280,940,7520,920
    +7520,920,7760,900
    +7760,900,7980,860
    +7980,860,8100,880
    +8100,880,8280,900
    +8280,900,8500,820
    +8500,820,8700,820
    +8700,820,8760,840
    +8760,960,8760,840
    +8700,1040,8760,960
    +8560,1060,8700,1040
    +8460,1080,8560,1060
    +8360,1040,8460,1080
    +8280,1080,8360,1040
    +8160,1120,8280,1080
    +8040,1120,8160,1120
    +7940,1100,8040,1120
    +7800,1120,7940,1100
    +7680,1120,7800,1120
    +7520,1100,7680,1120
    +7360,1100,7520,1100
    +7200,1120,7360,1100
    +7040,1180,7200,1120
    +6880,1160,7040,1180
    +6720,1160,6880,1160
    +6540,1160,6720,1160
    +6360,1160,6540,1160
    +6200,1160,6360,1160
    +6040,1220,6200,1160
    +6040,1220,6040,1400
    +6040,1400,6200,1440
    +6200,1440,6320,1440
    +6320,1440,6440,1440
    +6600,1440,6760,1440
    +6760,1440,6940,1420
    +6440,1440,6600,1440
    +6940,1420,7280,1400
    +7280,1400,7560,1400
    +7560,1400,7760,1400
    +7760,1400,7940,1360
    +7940,1360,8100,1380
    +8100,1380,8280,1340
    +8280,1340,8460,1320
    +8660,1300,8760,1360
    +8460,1320,8660,1300
    +8760,1360,8800,1500
    +8800,1660,8800,1500
    +8800,1660,8800,1820
    +8700,1840,8800,1820
    +8620,1860,8700,1840
    +8560,1800,8620,1860
    +8560,1800,8620,1680
    +8500,1640,8620,1680
    +8420,1680,8500,1640
    +8280,1680,8420,1680
    +8160,1680,8280,1680
    +7900,1680,8160,1680
    +7680,1680,7900,1680
    +7400,1660,7680,1680
    +7140,1680,7400,1660
    +6880,1640,7140,1680
    +6040,1820,6320,1780
    +5900,1840,6040,1820
    +6640,1700,6880,1640
    +6320,1780,6640,1700
    +5840,2040,5900,1840
    +5840,2040,5840,2220
    +5840,2220,5840,2320
    +5840,2460,5840,2320
    +5840,2560,5840,2460
    +5840,2560,5960,2620
    +5960,2620,6200,2620
    +6200,2620,6380,2600
    +6380,2600,6600,2580
    +6600,2580,6800,2600
    +6800,2600,7040,2580
    +7040,2580,7280,2580
    +7280,2580,7480,2560
    +7760,2540,7980,2520
    +7980,2520,8160,2500
    +7480,2560,7760,2540
    +8160,2500,8160,2420
    +8160,2420,8160,2320
    +8160,2180,8160,2320
    +7980,2160,8160,2180
    +7800,2180,7980,2160
    +7600,2200,7800,2180
    +7400,2200,7600,2200
    +6960,2200,7200,2200
    +7200,2200,7400,2200
    +6720,2200,6960,2200
    +6540,2180,6720,2200
    +6320,2200,6540,2180
    +6240,2160,6320,2200
    +6240,2160,6240,2040
    +6240,2040,6240,1940
    +6240,1940,6440,1940
    +6440,1940,6720,1940
    +6720,1940,6940,1920
    +7520,1920,7760,1920
    +6940,1920,7280,1920
    +7280,1920,7520,1920
    +7760,1920,8100,1900
    +8100,1900,8420,1900
    +8420,1900,8460,1940
    +8460,2120,8460,1940
    +8460,2280,8460,2120
    +8460,2280,8560,2420
    +8560,2420,8660,2380
    +8660,2380,8800,2340
    +8800,2340,8840,2400
    +8840,2520,8840,2400
    +8800,2620,8840,2520
    +8800,2740,8800,2620
    +8800,2860,8800,2740
    +8800,2940,8800,2860
    +8760,2980,8800,2940
    +8660,2980,8760,2980
    +8620,2960,8660,2980
    +8560,2880,8620,2960
    +8560,2880,8560,2780
    +8500,2740,8560,2780
    +8420,2760,8500,2740
    +8420,2840,8420,2760
    +8420,2840,8420,2940
    +8420,3040,8420,2940
    +8420,3160,8420,3040
    +8420,3280,8420,3380
    +8420,3280,8420,3160
    +8420,3380,8620,3460
    +8620,3460,8760,3460
    +8760,3460,8840,3400
    +8840,3400,8960,3400
    +8960,3400,9000,3500
    +9000,3700,9000,3500
    +9000,3900,9000,3700
    +9000,4080,9000,3900
    +9000,4280,9000,4080
    +9000,4500,9000,4280
    +9000,4620,9000,4500
    +9000,4780,9000,4620
    +9000,4780,9000,4960
    +9000,5120,9000,4960
    +9000,5120,9000,5300
    +8960,5460,9000,5300
    +8920,5620,8960,5460
    +8920,5620,8920,5800
    +8920,5800,8920,5960
    +8920,5960,8920,6120
    +8920,6120,8960,6300
    +8960,6300,8960,6480
    +8960,6660,8960,6480
    +8960,6860,8960,6660
    +8960,7040,8960,6860
    +8920,7420,8920,7220
    +8920,7420,8960,7620
    +8960,7620,8960,7800
    +8960,7800,8960,8000
    +8960,8000,8960,8180
    +8960,8180,8960,8380
    +8960,8580,8960,8380
    +8920,8800,8960,8580
    +8880,9000,8920,8800
    +8840,9180,8880,9000
    +8800,9220,8840,9180
    +8800,9220,8840,9340
    +8760,9380,8840,9340
    +8560,9340,8760,9380
    +8360,9360,8560,9340
    +8160,9360,8360,9360
    +8040,9340,8160,9360
    +7860,9360,8040,9340
    +7680,9360,7860,9360
    +7520,9360,7680,9360
    +7420,9260,7520,9360
    +7400,9080,7420,9260
    +7400,9080,7420,8860
    +7420,8860,7440,8720
    +7440,8720,7480,8660
    +7480,8660,7520,8540
    +7520,8540,7600,8460
    +7600,8460,7800,8480
    +7800,8480,8040,8480
    +8040,8480,8280,8480
    +8280,8480,8500,8460
    +8500,8460,8620,8440
    +8620,8440,8660,8340
    +8660,8340,8660,8220
    +8660,8220,8700,8080
    +8700,8080,8700,7920
    +8700,7920,8700,7760
    +8700,7760,8700,7620
    +8700,7480,8700,7620
    +8700,7480,8700,7320
    +8700,7160,8700,7320
    +8920,7220,8960,7040
    +8660,7040,8700,7160
    +8660,7040,8700,6880
    +8660,6700,8700,6880
    +8660,6700,8700,6580
    +8700,6460,8700,6580
    +8700,6460,8700,6320
    +8700,6160,8700,6320
    +8700,6160,8760,6020
    +8760,6020,8760,5860
    +8760,5860,8760,5700
    +8760,5700,8760,5540
    +8760,5540,8760,5360
    +8760,5360,8760,5180
    +8760,5000,8760,5180
    +8700,4820,8760,5000
    +8560,4740,8700,4820
    +8420,4700,8560,4740
    +8280,4700,8420,4700
    +8100,4700,8280,4700
    +7980,4700,8100,4700
    +7820,4740,7980,4700
    +7800,4920,7820,4740
    +7800,4920,7900,4960
    +7900,4960,8060,4980
    +8060,4980,8220,5000
    +8220,5000,8420,5040
    +8420,5040,8460,5120
    +8460,5180,8460,5120
    +8360,5200,8460,5180
    +8360,5280,8360,5200
    +8160,5300,8360,5280
    +8040,5260,8160,5300
    +7860,5220,8040,5260
    +7720,5160,7860,5220
    +7640,5120,7720,5160
    +7480,5120,7640,5120
    +7240,5120,7480,5120
    +7000,5120,7240,5120
    +6800,5160,7000,5120
    +6640,5220,6800,5160
    +6600,5360,6640,5220
    +6600,5460,6600,5360
    +6480,5520,6600,5460
    +6240,5540,6480,5520
    +5980,5540,6240,5540
    +5740,5540,5980,5540
    +5500,5520,5740,5540
    +5400,5520,5500,5520
    +5280,5540,5400,5520
    +5080,5540,5280,5540
    +4940,5540,5080,5540
    +4760,5540,4940,5540
    +4600,5540,4760,5540
    +4440,5560,4600,5540
    +4040,5580,4120,5520
    +4260,5540,4440,5560
    +4120,5520,4260,5540
    +4020,5720,4040,5580
    +4020,5840,4020,5720
    +4020,5840,4080,5940
    +4080,5940,4120,6040
    +4120,6040,4200,6080
    +4200,6080,4340,6080
    +4340,6080,4500,6060
    +4500,6060,4700,6060
    +4700,6060,4880,6060
    +4880,6060,5080,6060
    +5080,6060,5280,6080
    +5280,6080,5440,6100
    +5440,6100,5660,6100
    +5660,6100,5900,6080
    +5900,6080,6120,6080
    +6120,6080,6360,6080
    +6360,6080,6480,6100
    +6480,6100,6540,6060
    +6540,6060,6720,6060
    +6720,6060,6940,6060
    +6940,6060,7140,6060
    +7400,6060,7600,6060
    +7140,6060,7400,6060
    +7600,6060,7800,6060
    +7800,6060,7860,6080
    +7860,6080,8060,6080
    +8060,6080,8220,6080
    +8220,6080,8320,6140
    +8320,6140,8360,6300
    +8320,6460,8360,6300
    +8320,6620,8320,6460
    +8320,6800,8320,6620
    +8320,6960,8320,6800
    +8320,6960,8360,7120
    +8320,7280,8360,7120
    +8320,7440,8320,7280
    +8320,7600,8320,7440
    +8100,7580,8220,7600
    +8220,7600,8320,7600
    +7900,7560,8100,7580
    +7680,7560,7900,7560
    +7480,7580,7680,7560
    +7280,7580,7480,7580
    +7080,7580,7280,7580
    +7000,7600,7080,7580
    +6880,7600,7000,7600
    +6800,7580,6880,7600
    +6640,7580,6800,7580
    +6540,7580,6640,7580
    +6380,7600,6540,7580
    +6280,7620,6380,7600
    +6240,7700,6280,7620
    +6240,7700,6240,7800
    +6240,7840,6240,7800
    +6080,7840,6240,7840
    +5960,7820,6080,7840
    +5660,7840,5800,7840
    +5500,7800,5660,7840
    +5440,7700,5500,7800
    +5800,7840,5960,7820
    +5440,7540,5440,7700
    +5440,7440,5440,7540
    +5440,7320,5440,7440
    +5400,7320,5440,7320
    +5340,7400,5400,7320
    +5340,7400,5340,7500
    +5340,7600,5340,7500
    +5340,7600,5340,7720
    +5340,7720,5340,7860
    +5340,7860,5340,7960
    +5340,7960,5440,8020
    +5440,8020,5560,8020
    +5560,8020,5720,8040
    +5720,8040,5900,8060
    +5900,8060,6080,8060
    +6080,8060,6240,8060
    +6720,8040,6840,8060
    +6240,8060,6480,8040
    +6480,8040,6720,8040
    +6840,8060,6940,8060
    +6940,8060,7080,8120
    +7080,8120,7140,8180
    +7140,8460,7140,8320
    +7140,8620,7140,8460
    +7140,8620,7140,8740
    +7140,8860,7140,8740
    +7140,8960,7140,8860
    +7140,8960,7200,9080
    +7140,9200,7200,9080
    +7140,9200,7200,9320
    +7200,9320,7200,9460
    +7200,9760,7200,9900
    +7200,9620,7200,9460
    +7200,9620,7200,9760
    +7200,9900,7200,10060
    +7200,10220,7200,10060
    +7200,10360,7200,10220
    +7140,10400,7200,10360
    +6880,10400,7140,10400
    +6640,10360,6880,10400
    +6420,10360,6640,10360
    +6160,10380,6420,10360
    +5940,10340,6160,10380
    +5720,10320,5940,10340
    +5500,10340,5720,10320
    +5280,10300,5500,10340
    +5080,10300,5280,10300
    +4840,10280,5080,10300
    +4700,10280,4840,10280
    +4540,10280,4700,10280
    +4360,10280,4540,10280
    +4200,10300,4360,10280
    +4040,10380,4200,10300
    +4020,10500,4040,10380
    +3980,10640,4020,10500
    +3980,10640,3980,10760
    +3980,10760,4020,10920
    +4020,10920,4080,11000
    +4080,11000,4340,11020
    +4340,11020,4600,11060
    +4600,11060,4840,11040
    +4840,11040,4880,10960
    +4880,10740,4880,10960
    +4880,10740,4880,10600
    +4880,10600,5080,10560
    +5080,10560,5340,10620
    +5340,10620,5660,10620
    +5660,10620,6040,10600
    +6040,10600,6120,10620
    +6120,10620,6240,10720
    +6240,10720,6420,10740
    +6420,10740,6640,10760
    +6640,10760,6880,10780
    +7140,10780,7400,10780
    +6880,10780,7140,10780
    +7400,10780,7680,10780
    +7680,10780,8100,10760
    +8100,10760,8460,10740
    +8460,10740,8700,10760
    +8800,10840,8800,10980
    +8700,10760,8800,10840
    +8760,11200,8800,10980
    +8760,11200,8760,11380
    +8760,11380,8800,11560
    +8760,11680,8800,11560
    +8760,11760,8760,11680
    +8760,11760,8760,11920
    +8760,11920,8800,12080
    +8800,12200,8800,12080
    +8700,12240,8800,12200
    +8560,12220,8700,12240
    +8360,12220,8560,12220
    +8160,12240,8360,12220
    +7720,12220,7980,12220
    +7980,12220,8160,12240
    +7400,12200,7720,12220
    +7200,12180,7400,12200
    +7000,12160,7200,12180
    +6800,12160,7000,12160
    +6280,12140,6380,12180
    +6120,12180,6280,12140
    +6540,12180,6800,12160
    +6380,12180,6540,12180
    +5900,12200,6120,12180
    +5620,12180,5900,12200
    +5340,12120,5620,12180
    +5140,12100,5340,12120
    +4980,12120,5140,12100
    +4840,12120,4980,12120
    +4700,12200,4840,12120
    +4700,12380,4700,12200
    +4740,12480,4940,12520
    +4700,12380,4740,12480
    +4940,12520,5160,12560
    +5160,12560,5340,12600
    +5340,12600,5400,12600
    +5400,12600,5500,12600
    +5500,12600,5620,12600
    +5620,12600,5720,12560
    +5720,12560,5800,12440
    +5800,12440,5900,12380
    +5900,12380,6120,12420
    +6120,12420,6380,12440
    +6380,12440,6600,12460
    +6720,12460,6840,12520
    +6840,12520,6960,12520
    +6600,12460,6720,12460
    +6960,12520,7040,12500
    +7040,12500,7140,12440
    +7200,12440,7360,12500
    +7360,12500,7600,12560
    +7600,12560,7860,12600
    +7860,12600,8060,12500
    +8100,12500,8200,12340
    +8200,12340,8360,12360
    +8360,12360,8560,12400
    +8560,12400,8660,12420
    +8660,12420,8840,12400
    +8840,12400,9000,12360
    +9000,12360,9000,12360
    +2900,4400,2900,4280
    +900,7320,1000,7220
    +2640,13040,2900,12920
    +2900,12920,3160,12840
    +3480,12760,3780,12620
    +3780,12620,4020,12460
    +4300,12360,4440,12260
    +4020,12460,4300,12360
    +3160,12840,3480,12760
    +4440,12080,4440,12260
    +4440,12080,4440,11880
    +4440,11880,4440,11720
    +4440,11720,4600,11720
    +4600,11720,4760,11740
    +4760,11740,4980,11760
    +4980,11760,5160,11760
    +5160,11760,5340,11780
    +6000,11860,6120,11820
    +5340,11780,5620,11820
    +5620,11820,6000,11860
    +6120,11820,6360,11820
    +6360,11820,6640,11860
    +6940,11920,7240,11940
    +7240,11940,7520,11960
    +7520,11960,7860,11960
    +7860,11960,8100,11920
    +8100,11920,8420,11940
    +8420,11940,8460,11960
    +8460,11960,8500,11860
    +8460,11760,8500,11860
    +8320,11720,8460,11760
    +8160,11720,8320,11720
    +7940,11720,8160,11720
    +7720,11700,7940,11720
    +7520,11680,7720,11700
    +7320,11680,7520,11680
    +7200,11620,7320,11680
    +7200,11620,7200,11500
    +7200,11500,7280,11440
    +7280,11440,7420,11440
    +7420,11440,7600,11440
    +7600,11440,7980,11460
    +7980,11460,8160,11460
    +8160,11460,8360,11460
    +8360,11460,8460,11400
    +8420,11060,8500,11200
    +8280,11040,8420,11060
    +8100,11060,8280,11040
    +8460,11400,8500,11200
    +7800,11060,8100,11060
    +7520,11060,7800,11060
    +7240,11060,7520,11060
    +6940,11040,7240,11060
    +6640,11000,6940,11040
    +6420,10980,6640,11000
    +6360,11060,6420,10980
    +6360,11180,6360,11060
    +6200,11280,6360,11180
    +5960,11300,6200,11280
    +5720,11280,5960,11300
    +5500,11280,5720,11280
    +4940,11300,5200,11280
    +4660,11260,4940,11300
    +4440,11280,4660,11260
    +4260,11280,4440,11280
    +4220,11220,4260,11280
    +4080,11280,4220,11220
    +3980,11420,4080,11280
    +3980,11420,4040,11620
    +4040,11620,4040,11820
    +3980,11960,4040,11820
    +3840,12000,3980,11960
    +3720,11940,3840,12000
    +3680,11800,3720,11940
    +3680,11580,3680,11800
    +3680,11360,3680,11580
    +3680,11360,3680,11260
    +3680,11080,3680,11260
    +3680,11080,3680,10880
    +3680,10700,3680,10880
    +3680,10700,3680,10620
    +3680,10480,3680,10620
    +3680,10480,3680,10300
    +3680,10300,3680,10100
    +3680,10100,3680,9940
    +3680,9940,3720,9860
    +3720,9860,3920,9900
    +3920,9900,4220,9880
    +4980,9940,5340,9960
    +4220,9880,4540,9900
    +4540,9900,4980,9940
    +5340,9960,5620,9960
    +5620,9960,5900,9960
    +5900,9960,6160,10000
    +6160,10000,6480,10000
    +6480,10000,6720,10000
    +6720,10000,6880,9860
    +6880,9860,6880,9520
    +6880,9520,6940,9340
    +6940,9120,6940,9340
    +6940,9120,6940,8920
    +6940,8700,6940,8920
    +6880,8500,6940,8700
    +6880,8320,6880,8500
    +7140,8320,7140,8180
    +6760,8260,6880,8320
    +6540,8240,6760,8260
    +6420,8180,6540,8240
    +6280,8240,6420,8180
    +6160,8300,6280,8240
    +6120,8400,6160,8300
    +6080,8520,6120,8400
    +5840,8480,6080,8520
    +5620,8500,5840,8480
    +5500,8500,5620,8500
    +5340,8560,5500,8500
    +5160,8540,5340,8560
    +4620,8520,4880,8520
    +4360,8480,4620,8520
    +4880,8520,5160,8540
    +4140,8440,4360,8480
    +3920,8460,4140,8440
    +3720,8380,3920,8460
    +3680,8160,3720,8380
    +3680,8160,3720,7940
    +3720,7720,3720,7940
    +3680,7580,3720,7720
    +3680,7580,3720,7440
    +3720,7440,3720,7300
    +3720,7160,3720,7300
    +3720,7160,3720,7020
    +3720,7020,3780,6900
    +3780,6900,4080,6940
    +4080,6940,4340,6980
    +4340,6980,4600,6980
    +4600,6980,4880,6980
    +4880,6980,5160,6980
    +5160,6980,5400,7000
    +5400,7000,5560,7020
    +5560,7020,5660,7080
    +5660,7080,5660,7280
    +5660,7280,5660,7440
    +5660,7440,5740,7520
    +5740,7520,5740,7600
    +5740,7600,5900,7600
    +5900,7600,6040,7540
    +6040,7540,6040,7320
    +6040,7320,6120,7200
    +6120,7200,6120,7040
    +6120,7040,6240,7000
    +6240,7000,6480,7060
    +6480,7060,6800,7060
    +6800,7060,7080,7080
    +7080,7080,7320,7100
    +7940,7100,7980,6920
    +7860,6860,7980,6920
    +7640,6860,7860,6860
    +7400,6840,7640,6860
    +7320,7100,7560,7120
    +7560,7120,7760,7120
    +7760,7120,7940,7100
    +7200,6820,7400,6840
    +7040,6820,7200,6820
    +6600,6840,6840,6840
    +6380,6800,6600,6840
    +6120,6800,6380,6800
    +5900,6840,6120,6800
    +5620,6820,5900,6840
    +5400,6800,5620,6820
    +5140,6800,5400,6800
    +4880,6780,5140,6800
    +4600,6760,4880,6780
    +4340,6760,4600,6760
    +4080,6760,4340,6760
    +3840,6740,4080,6760
    +3680,6720,3840,6740
    +3680,6720,3680,6560
    +3680,6560,3720,6400
    +3720,6400,3720,6200
    +3720,6200,3780,6000
    +3720,5780,3780,6000
    +3720,5580,3720,5780
    +3720,5360,3720,5580
    +3720,5360,3840,5240
    +3840,5240,4200,5260
    +4200,5260,4600,5280
    +4600,5280,4880,5280
    +4880,5280,5140,5200
    +5140,5200,5220,5100
    +5220,5100,5280,4900
    +5280,4900,5340,4840
    +5340,4840,5720,4880
    +6120,4880,6480,4860
    +6880,4840,7200,4860
    +6480,4860,6880,4840
    +7200,4860,7320,4860
    +7320,4860,7360,4740
    +7360,4600,7440,4520
    +7360,4600,7360,4740
    +7440,4520,7640,4520
    +7640,4520,7800,4480
    +7800,4480,7800,4280
    +7800,4280,7800,4040
    +7800,4040,7800,3780
    +7800,3560,7800,3780
    +7800,3560,7860,3440
    +7860,3440,8060,3460
    +8060,3460,8160,3340
    +8160,3340,8160,3140
    +8160,3140,8160,2960
    +8000,2900,8160,2960
    +7860,2900,8000,2900
    +7640,2940,7860,2900
    +7400,2980,7640,2940
    +7100,2980,7400,2980
    +6840,3000,7100,2980
    +5620,2980,5840,2980
    +5840,2980,6500,3000
    +6500,3000,6840,3000
    +5560,2780,5620,2980
    +5560,2780,5560,2580
    +5560,2580,5560,2380
    +5560,2140,5560,2380
    +5560,2140,5560,1900
    +5560,1900,5620,1660
    +5620,1660,5660,1460
    +5660,1460,5660,1300
    +5500,1260,5660,1300
    +5340,1260,5500,1260
    +4600,1220,4840,1240
    +4440,1220,4600,1220
    +4440,1080,4440,1220
    +4440,1080,4600,1020
    +5080,1260,5340,1260
    +4840,1240,5080,1260
    +4600,1020,4940,1020
    +4940,1020,5220,1020
    +5220,1020,5560,960
    +5560,960,5660,860
    +5660,740,5660,860
    +5280,740,5660,740
    +4940,780,5280,740
    +4660,760,4940,780
    +4500,700,4660,760
    +4500,520,4500,700
    +4500,520,4700,460
    +4700,460,5080,440
    +5440,420,5740,420
    +5080,440,5440,420
    +5740,420,5840,360
    +5800,280,5840,360
    +5560,280,5800,280
    +4980,300,5280,320
    +4360,320,4660,300
    +4200,360,4360,320
    +5280,320,5560,280
    +4660,300,4980,300
    +4140,480,4200,360
    +4140,480,4140,640
    +4140,640,4200,780
    +4200,780,4200,980
    +4200,980,4220,1180
    +4220,1400,4220,1180
    +4220,1400,4260,1540
    +4260,1540,4500,1540
    +4500,1540,4700,1520
    +4700,1520,4980,1540
    +5280,1560,5400,1560
    +4980,1540,5280,1560
    +5400,1560,5400,1700
    +5400,1780,5400,1700
    +5340,1900,5400,1780
    +5340,2020,5340,1900
    +5340,2220,5340,2020
    +5340,2220,5340,2420
    +5340,2420,5340,2520
    +5080,2600,5220,2580
    +5220,2580,5340,2520
    +4900,2580,5080,2600
    +4700,2540,4900,2580
    +4500,2540,4700,2540
    +4220,2580,4340,2540
    +4200,2700,4220,2580
    +4340,2540,4500,2540
    +3980,2740,4200,2700
    +3840,2740,3980,2740
    +3780,2640,3840,2740
    +3780,2640,3780,2460
    +3780,2280,3780,2460
    +3620,2020,3780,2100
    +3780,2280,3780,2100
    +3360,2040,3620,2020
    +3080,2040,3360,2040
    +2840,2020,3080,2040
    +2740,1940,2840,2020
    +2740,1940,2800,1800
    +2800,1640,2800,1800
    +2800,1640,2800,1460
    +2800,1300,2800,1460
    +2700,1180,2800,1300
    +2480,1140,2700,1180
    +1580,1200,1720,1200
    +2240,1180,2480,1140
    +1960,1180,2240,1180
    +1720,1200,1960,1180
    +1500,1320,1580,1200
    +1500,1440,1500,1320
    +1500,1440,1760,1480
    +1760,1480,1940,1480
    +1940,1480,2140,1500
    +2140,1500,2320,1520
    +2400,1560,2400,1700
    +2280,1820,2380,1780
    +2320,1520,2400,1560
    +2380,1780,2400,1700
    +2080,1840,2280,1820
    +1720,1820,2080,1840
    +1420,1800,1720,1820
    +1280,1800,1420,1800
    +1240,1720,1280,1800
    +1240,1720,1240,1600
    +1240,1600,1280,1480
    +1280,1340,1280,1480
    +1180,1280,1280,1340
    +1000,1280,1180,1280
    +760,1280,1000,1280
    +360,1240,540,1260
    +180,1220,360,1240
    +540,1260,760,1280
    +180,1080,180,1220
    +180,1080,180,1000
    +180,1000,360,940
    +360,940,540,960
    +540,960,820,980
    +1100,980,1200,920
    +820,980,1100,980
    +6640,11860,6940,11920
    +5200,11280,5500,11280
    +4120,7330,4120,7230
    +4120,7230,4660,7250
    +4660,7250,4940,7250
    +4940,7250,5050,7340
    +5010,7400,5050,7340
    +4680,7380,5010,7400
    +4380,7370,4680,7380
    +4120,7330,4360,7370
    +4120,7670,4120,7760
    +4120,7670,4280,7650
    +4280,7650,4540,7660
    +4550,7660,4820,7680
    +4820,7680,4900,7730
    +4880,7800,4900,7730
    +4620,7820,4880,7800
    +4360,7790,4620,7820
    +4120,7760,4360,7790
    +6840,6840,7040,6820
    +5720,4880,6120,4880
    +1200,920,1340,810
    +1340,810,1520,790
    +1520,790,1770,800
    +2400,790,2600,750
    +2600,750,2640,520
    +2520,470,2640,520
    +2140,470,2520,470
    +1760,800,2090,800
    +2080,800,2400,790
    +1760,450,2140,470
    +1420,450,1760,450
    +1180,440,1420,450
    +900,480,1180,440
    +640,450,900,480
    +360,440,620,450
    +120,430,360,440
    +0,520,120,430
    +-20,780,0,520
    +-20,780,-20,1020
    +-20,1020,-20,1150
    +-20,1150,0,1300
    +0,1470,60,1530
    +0,1300,0,1470
    +60,1530,360,1530
    +360,1530,660,1520
    +660,1520,980,1520
    +980,1520,1040,1520
    +1040,1520,1070,1560
    +1070,1770,1070,1560
    +1070,1770,1100,2010
    +1070,2230,1100,2010
    +1070,2240,1180,2340
    +1180,2340,1580,2340
    +1580,2340,1940,2350
    +1940,2350,2440,2350
    +2440,2350,2560,2380
    +2560,2380,2600,2540
    +2810,2640,3140,2680
    +2600,2540,2810,2640
    +3140,2680,3230,2780
    +3230,2780,3260,2970
    +3230,3220,3260,2970
    +3200,3470,3230,3220
    +3200,3480,3210,3760
    +3210,3760,3210,4040
    +3200,4040,3230,4310
    +3210,4530,3230,4310
    +3210,4530,3230,4730
    +3230,4960,3230,4730
    +3230,4960,3260,5190
    +3170,5330,3260,5190
    +2920,5330,3170,5330
    +2660,5360,2920,5330
    +2420,5330,2660,5360
    +2200,5280,2400,5330
    +2020,5280,2200,5280
    +1840,5260,2020,5280
    +1660,5280,1840,5260
    +1500,5300,1660,5280
    +1360,5270,1500,5300
    +1200,5290,1340,5270
    +1070,5400,1200,5290
    +1040,5630,1070,5400
    +1000,5900,1040,5630
    +980,6170,1000,5900
    +980,6280,980,6170
    +980,6540,980,6280
    +980,6540,1040,6720
    +1040,6720,1360,6730
    +1360,6730,1760,6710
    +2110,6720,2420,6730
    +1760,6710,2110,6720
    +2420,6730,2640,6720
    +2640,6720,2970,6720
    +2970,6720,3160,6700
    +3160,6700,3240,6710
    +3240,6710,3260,6890
    +3260,7020,3260,6890
    +3230,7180,3260,7020
    +3230,7350,3230,7180
    +3210,7510,3230,7350
    +3210,7510,3210,7690
    +3210,7870,3210,7690
    +3210,7870,3210,7980
    +3200,8120,3210,7980
    +3200,8330,3200,8120
    +3160,8520,3200,8330
    +2460,11100,2480,11020
    +2200,11180,2460,11100
    +1260,11350,1600,11320
    +600,11430,930,11400
    +180,11340,620,11430
    +1600,11320,1910,11280
    +1910,11280,2200,11180
    +923.0029599285435,11398.99893503157,1264.002959928544,11351.99893503157
    +
    +

    Platformer - The Little Probe - Data - level_lava.txt

    +
    # ./samples/99_genre_platformer/the_little_probe/data/level_lava.txt
    +100,10740,500,10780
    +500,10780,960,10760
    +960,10760,1340,10760
    +1380,10760,1820,10780
    +1820,10780,2240,10780
    +2280,10780,2740,10740
    +2740,10740,3000,10780
    +3000,10780,3140,11020
    +-520,8820,-480,9160
    +-520,8480,-520,8820
    +-520,8480,-480,8180
    +-480,8180,-200,8120
    +-200,8120,100,8220
    +100,8220,420,8240
    +420,8240,760,8260
    +760,8260,1140,8280
    +1140,8280,1500,8200
    +1500,8200,1880,8240
    +1880,8240,2240,8260
    +2240,8260,2320,8480
    +2320,8480,2380,8680
    +2240,8860,2380,8680
    +2240,9080,2240,8860
    +2240,9080,2320,9260
    +2320,9260,2480,9440
    +2480,9440,2600,9640
    +2480,9840,2600,9640
    +2400,10020,2480,9840
    +2240,10080,2400,10020
    +1960,10080,2240,10080
    +1720,10080,1960,10080
    +1460,10080,1720,10080
    +1180,10080,1420,10080
    +900,10080,1180,10080
    +640,10080,900,10080
    +640,10080,640,9900
    +60,10520,100,10740
    +40,10240,60,10520
    +40,10240,40,9960
    +40,9960,40,9680
    +40,9680,40,9360
    +40,9360,60,9080
    +60,9080,100,8860
    +100,8860,460,9040
    +460,9040,760,9220
    +760,9220,1140,9220
    +1140,9220,1720,9200
    +-660,11580,-600,11420
    +-660,11800,-660,11580
    +-660,12000,-660,11800
    +-660,12000,-600,12220
    +-600,12220,-600,12440
    +-600,12440,-600,12640
    +-600,11240,-260,11280
    +-260,11280,100,11240
    +9000,12360,9020,12400
    +9020,12620,9020,12400
    +9020,12840,9020,12620
    +9020,13060,9020,12840
    +9020,13060,9020,13240
    +9020,13240,9020,13420
    +9020,13420,9020,13600
    +9020,13600,9020,13780
    +8880,13900,9020,13780
    +8560,13800,8880,13900
    +8220,13780,8560,13800
    +7860,13760,8220,13780
    +7640,13780,7860,13760
    +7360,13800,7640,13780
    +7100,13800,7360,13800
    +6540,13760,6800,13780
    +6800,13780,7100,13800
    +6280,13760,6540,13760
    +5760,13760,6280,13760
    +5220,13780,5760,13760
    +4700,13760,5220,13780
    +4200,13740,4700,13760
    +3680,13720,4200,13740
    +3140,13700,3680,13720
    +2600,13680,3140,13700
    +2040,13940,2600,13680
    +1640,13940,2040,13940
    +1200,13960,1640,13940
    +840,14000,1200,13960
    +300,13960,840,14000
    +-200,13900,300,13960
    +-600,12840,-600,12640
    +-600,13140,-600,12840
    +-600,13140,-600,13420
    +-600,13700,-600,13420
    +-600,13700,-600,13820
    +-600,13820,-200,13900
    +-600,11240,-560,11000
    +-560,11000,-480,10840
    +-520,10660,-480,10840
    +-520,10660,-520,10480
    +-520,10480,-520,10300
    +-520,10260,-480,10080
    +-480,9880,-440,10060
    +-520,9680,-480,9880
    +-520,9680,-480,9400
    +-480,9400,-480,9160
    +1820,9880,2140,9800
    +1540,9880,1820,9880
    +1200,9920,1500,9880
    +900,9880,1200,9920
    +640,9900,840,9880
    +2380,8760,2800,8760
    +2800,8760,2840,8660
    +2840,8660,2840,8420
    +2840,8160,2840,8420
    +2800,7900,2840,8160
    +2800,7900,2800,7720
    +2800,7540,2800,7720
    +2800,7540,2800,7360
    +2700,7220,2800,7360
    +2400,7220,2700,7220
    +2080,7240,2400,7220
    +1760,7320,2080,7240
    +1380,7360,1720,7320
    +1040,7400,1340,7360
    +640,7400,1000,7420
    +300,7380,640,7400
    +0,7300,240,7380
    +-300,7180,-60,7300
    +-380,6860,-360,7180
    +-380,6880,-360,6700
    +-360,6700,-260,6540
    +-260,6540,0,6520
    +0,6520,240,6640
    +240,6640,460,6640
    +460,6640,500,6480
    +500,6260,500,6480
    +460,6060,500,6260
    +460,5860,460,6060
    +460,5860,500,5640
    +500,5640,540,5440
    +540,5440,580,5220
    +580,5220,580,5000
    +580,4960,580,4740
    +580,4740,960,4700
    +960,4700,1140,4760
    +1140,4760,1420,4740
    +1420,4740,1720,4700
    +1720,4700,2000,4740
    +2000,4740,2380,4760
    +2380,4760,2700,4800
    +1720,4600,1760,4300
    +1760,4300,2200,4340
    +2200,4340,2560,4340
    +2560,4340,2740,4340
    +2160,12580,2440,12400
    +1820,12840,2160,12580
    +1500,13080,1820,12840
    +1140,13340,1500,13080
    +1140,13340,1580,13220
    +2110,13080,2520,13000
    +2520,13000,2900,12800
    +1580,13220,2110,13080
    +2900,12800,3200,12680
    +3200,12680,3440,12640
    +3440,12640,3720,12460
    +3720,12460,4040,12320
    +4040,12320,4360,12200
    +4360,11940,4380,12180
    +4360,11700,4360,11940
    +4360,11700,4540,11500
    +4540,11500,4880,11540
    +6000,11660,6280,11640
    +5440,11600,5720,11610
    +5720,11610,6000,11660
    +6280,11640,6760,11720
    +6760,11720,7060,11780
    +7060,11780,7360,11810
    +7360,11810,7640,11840
    +7640,11840,8000,11830
    +8000,11830,8320,11850
    +8320,11850,8390,11800
    +8330,11760,8390,11800
    +8160,11760,8330,11760
    +7910,11750,8160,11760
    +7660,11740,7900,11750
    +7400,11730,7660,11740
    +7160,11680,7400,11730
    +7080,11570,7160,11680
    +7080,11570,7100,11350
    +7100,11350,7440,11280
    +7440,11280,7940,11280
    +7960,11280,8360,11280
    +5840,11540,6650,11170
    +4880,11540,5440,11600
    +3410,11830,3420,11300
    +3410,11260,3520,10920
    +3520,10590,3520,10920
    +3520,10590,3540,10260
    +3520,9900,3540,10240
    +3520,9900,3640,9590
    +3640,9570,4120,9590
    +4140,9590,4600,9680
    +4620,9680,5030,9730
    +5120,9750,5520,9800
    +5620,9820,6080,9800
    +6130,9810,6580,9820
    +6640,9820,6800,9700
    +6780,9400,6800,9700
    +6780,9400,6840,9140
    +6820,8860,6840,9120
    +6780,8600,6820,8830
    +6720,8350,6780,8570
    +6480,8340,6720,8320
    +6260,8400,6480,8340
    +6050,8580,6240,8400
    +5760,8630,6040,8590
    +5520,8690,5740,8630
    +5120,8690,5450,8700
    +4570,8670,5080,8690
    +4020,8610,4540,8670
    +3540,8480,4020,8610
    +3520,8230,3520,8480
    +3520,7930,3520,8230
    +3520,7930,3540,7630
    +3480,7320,3540,7610
    +3480,7280,3500,7010
    +3500,6980,3680,6850
    +3680,6850,4220,6840
    +4230,6840,4760,6850
    +4780,6850,5310,6860
    +5310,6860,5720,6940
    +5720,6940,5880,7250
    +5880,7250,5900,7520
    +100,11240,440,11300
    +440,11300,760,11330
    +1480,11280,1840,11230
    +2200,11130,2360,11090
    +1840,11230,2200,11130
    +
    +

    Rpg Narrative - Choose Your Own Adventure - decision.rb

    +
    # ./samples/99_genre_rpg_narrative/choose_your_own_adventure/app/decision.rb
    +# Hey there! Welcome to Four Decisions. Here is how you
    +# create your decision tree. Remove =being and =end from the text to
    +# enable the game (just save the file). Change stuff and see what happens!
    +
    +def game
    +  {
    +    starting_decision: :stormy_night,
    +    decisions: {
    +      stormy_night: {
    +        description: 'It was a dark and stormy night. (storyline located in decision.rb)',
    +        option_one: {
    +          description: 'Go to sleep.',
    +          decision: :nap
    +        },
    +        option_two: {
    +          description: 'Watch a movie.',
    +          decision: :movie
    +        },
    +        option_three: {
    +          description: 'Go outside.',
    +          decision: :go_outside
    +        },
    +        option_four: {
    +          description: 'Get a snack.',
    +          decision: :get_a_snack
    +        }
    +      },
    +      nap: {
    +        description: 'You took a nap. The end.',
    +        option_one: {
    +          description: 'Start over.',
    +          decision: :stormy_night
    +        }
    +      }
    +    }
    +  }
    +end
    +
    +
    +

    Rpg Narrative - Choose Your Own Adventure - main.rb

    +
    # ./samples/99_genre_rpg_narrative/choose_your_own_adventure/app/main.rb
    +=begin
    +
    + Reminders:
    +
    + - Hashes: Collection of unique keys and their corresponding values. The values can be found
    +   using their keys.
    +
    +   In this sample app, the decisions needed for the game are stored in a hash. In fact, the
    +   decision.rb file contains hashes inside of other hashes!
    +
    +   Each option is a key in the first hash, but also contains a hash (description and
    +   decision being its keys) as its value.
    +   Go into the decision.rb file and take a look before diving into the code below.
    +
    + - args.outputs.labels: An array. The values generate a label.
    +   The parameters are [X, Y, TEXT, SIZE, ALIGNMENT, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    +   For more information about labels, go to mygame/documentation/02-labels.md.
    +
    + - args.keyboard.key_down.KEY: Determines if a key is in the down state or pressed down.
    +   For more information about the keyboard, go to mygame/documentation/06-keyboard.md.
    +
    + - String interpolation: uses #{} syntax; everything between the #{ and the } is evaluated
    +   as Ruby code, and the placeholder is replaced with its corresponding value or result.
    +
    +=end
    +
    +# This sample app provides users with a story and multiple decisions that they can choose to make.
    +# Users can make a decision using their keyboard, and the story will move forward based on user choices.
    +
    +# The decisions available to users are stored in the decision.rb file.
    +# We must have access to it for the game to function properly.
    +GAME_FILE = 'app/decision.rb' # found in app folder
    +
    +require GAME_FILE # require used to load another file, import class/method definitions
    +
    +# Instructions are given using labels to users if they have not yet set up their story in the decision.rb file.
    +# Otherwise, the game is run.
    +def tick args
    +  if !args.state.loaded && !respond_to?(:game) # if game is not loaded and not responding to game symbol's method
    +    args.labels << [640, 370, 'Hey there! Welcome to Four Decisions.', 0, 1] # a welcome label is shown
    +    args.labels << [640, 340, 'Go to the file called decision.rb and tell me your story.', 0, 1]
    +  elsif respond_to?(:game) # otherwise, if responds to game
    +    args.state.loaded = true
    +    tick_game args # calls tick_game method, runs game
    +  end
    +
    +  if args.state.tick_count.mod_zero? 60 # update every 60 frames
    +    t = args.gtk.ffi_file.mtime GAME_FILE # mtime returns modification time for named file
    +    if t != args.state.mtime
    +      args.state.mtime = t
    +      require GAME_FILE # require used to load file
    +      args.state.game_definition = nil # game definition and decision are empty
    +      args.state.decision_id = nil
    +    end
    +  end
    +end
    +
    +# Runs methods needed for game to function properly
    +# Creates a rectangular border around the screen
    +def tick_game args
    +  defaults args
    +  args.borders << args.grid.rect
    +  render_decision args
    +  process_inputs args
    +end
    +
    +# Sets default values and uses decision.rb file to define game and decision_id
    +# variable using the starting decision
    +def defaults args
    +  args.state.game_definition ||= game
    +  args.state.decision_id ||= args.state.game_definition[:starting_decision]
    +end
    +
    +# Outputs the possible decision descriptions the user can choose onto the screen
    +# as well as what key to press on their keyboard to make their decision
    +def render_decision args
    +  decision = current_decision args
    +  # text is either the value of decision's description key or warning that no description exists
    +  args.labels << [640, 360, decision[:description] || "No definition found for #{args.state.decision_id}. Please update decision.rb.", 0, 1] # uses string interpolation
    +
    +  # All decisions are stored in a hash
    +  # The descriptions output onto the screen are the values for the description keys of the hash.
    +  if decision[:option_one]
    +    args.labels << [10, 360, decision[:option_one][:description], 0, 0] # option one's description label
    +    args.labels << [10, 335, "(Press 'left' on the keyboard to select this decision)", -5, 0] # label of what key to press to select the decision
    +  end
    +
    +  if decision[:option_two]
    +    args.labels << [1270, 360, decision[:option_two][:description], 0, 2] # option two's description
    +    args.labels << [1270, 335, "(Press 'right' on the keyboard to select this decision)", -5, 2]
    +  end
    +
    +  if decision[:option_three]
    +    args.labels << [640, 45, decision[:option_three][:description], 0, 1] # option three's description
    +    args.labels << [640, 20, "(Press 'down' on the keyboard to select this decision)", -5, 1]
    +  end
    +
    +  if decision[:option_four]
    +    args.labels << [640, 700, decision[:option_four][:description], 0, 1] # option four's description
    +    args.labels << [640, 675, "(Press 'up' on the keyboard to select this decision)", -5, 1]
    +  end
    +end
    +
    +# Uses keyboard input from the user to make a decision
    +# Assigns the decision as the value of the decision_id variable
    +def process_inputs args
    +  decision = current_decision args # calls current_decision method
    +
    +  if args.keyboard.key_down.left! && decision[:option_one] # if left key pressed and option one exists
    +    args.state.decision_id = decision[:option_one][:decision] # value of option one's decision hash key is set to decision_id
    +  end
    +
    +  if args.keyboard.key_down.right! && decision[:option_two] # if right key pressed and option two exists
    +    args.state.decision_id = decision[:option_two][:decision] # value of option two's decision hash key is set to decision_id
    +  end
    +
    +  if args.keyboard.key_down.down! && decision[:option_three] # if down key pressed and option three exists
    +    args.state.decision_id = decision[:option_three][:decision] # value of option three's decision hash key is set to decision_id
    +  end
    +
    +  if args.keyboard.key_down.up! && decision[:option_four] # if up key pressed and option four exists
    +    args.state.decision_id = decision[:option_four][:decision] # value of option four's decision hash key is set to decision_id
    +  end
    +end
    +
    +# Uses decision_id's value to keep track of current decision being made
    +def current_decision args
    +  args.state.game_definition[:decisions][args.state.decision_id] || {} # either has value or is empty
    +end
    +
    +# Resets the game.
    +$gtk.reset
     
     
    -

    99_genre_platformer/gorillas_basic/app/main.rb

    -
    class YouSoBasicGorillas
    -  attr_accessor :outputs, :grid, :state, :inputs
    +

    Rpg Narrative - Return Of Serenity - lowrez_simulator.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/lowrez_simulator.rb
    +###################################################################################
    +# YOU CAN PLAY AROUND WITH THE CODE BELOW, BUT USE CAUTION AS THIS IS WHAT EMULATES
    +# THE 64x64 CANVAS.
    +###################################################################################
     
    -  def tick
    -    defaults
    -    render
    -    calc
    -    process_inputs
    +TINY_RESOLUTION       = 64
    +TINY_SCALE            = 720.fdiv(TINY_RESOLUTION + 5)
    +CENTER_OFFSET         = 10
    +EMULATED_FONT_SIZE    = 20
    +EMULATED_FONT_X_ZERO  = 0
    +EMULATED_FONT_Y_ZERO  = 46
    +
    +def tick args
    +  sprites = []
    +  labels = []
    +  borders = []
    +  solids = []
    +  mouse = emulate_lowrez_mouse args
    +  args.state.show_gridlines = false
    +  lowrez_tick args, sprites, labels, borders, solids, mouse
    +  render_gridlines_if_needed args
    +  render_mouse_crosshairs args, mouse
    +  emulate_lowrez_scene args, sprites, labels, borders, solids, mouse
    +end
    +
    +def emulate_lowrez_mouse args
    +  args.state.new_entity_strict(:lowrez_mouse) do |m|
    +    m.x = args.mouse.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1
    +    m.y = args.mouse.y.idiv(TINY_SCALE)
    +    if args.mouse.click
    +      m.click = [
    +        args.mouse.click.point.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1,
    +        args.mouse.click.point.y.idiv(TINY_SCALE)
    +      ]
    +      m.down = m.click
    +    else
    +      m.click = nil
    +      m.down = nil
    +    end
    +
    +    if args.mouse.up
    +      m.up = [
    +        args.mouse.up.point.x.idiv(TINY_SCALE) - CENTER_OFFSET.idiv(TINY_SCALE) - 1,
    +        args.mouse.up.point.y.idiv(TINY_SCALE)
    +      ]
    +    else
    +      m.up = nil
    +    end
       end
    +end
     
    -  def defaults
    -    outputs.background_color = [33, 32, 87]
    -    state.building_spacing       = 1
    -    state.building_room_spacing  = 15
    -    state.building_room_width    = 10
    -    state.building_room_height   = 15
    -    state.building_heights       = [4, 4, 6, 8, 15, 20, 18]
    -    state.building_room_sizes    = [5, 4, 6, 7]
    -    state.gravity                = 0.25
    -    state.first_strike         ||= :player_1
    -    state.buildings            ||= []
    -    state.holes                ||= []
    -    state.player_1_score       ||= 0
    -    state.player_2_score       ||= 0
    -    state.wind                 ||= 0
    +def render_mouse_crosshairs args, mouse
    +  return unless args.state.show_gridlines
    +  args.labels << [10, 25, "mouse: #{mouse.x} #{mouse.y}", 255, 255, 255]
    +end
    +
    +def emulate_lowrez_scene args, sprites, labels, borders, solids, mouse
    +  args.render_target(:lowrez).solids  << [0, 0, 1280, 720]
    +  args.render_target(:lowrez).sprites << sprites
    +  args.render_target(:lowrez).borders << borders
    +  args.render_target(:lowrez).solids  << solids
    +  args.outputs.primitives << labels.map do |l|
    +    as_label = l.label
    +    l.text.each_char.each_with_index.map do |char, i|
    +      [CENTER_OFFSET + EMULATED_FONT_X_ZERO + (as_label.x * TINY_SCALE) + i * 5 * TINY_SCALE,
    +       EMULATED_FONT_Y_ZERO + (as_label.y * TINY_SCALE), char,
    +       EMULATED_FONT_SIZE, 0, as_label.r, as_label.g, as_label.b, as_label.a, 'fonts/dragonruby-gtk-4x4.ttf'].label
    +    end
       end
     
    -  def render
    -    render_stage
    -    render_value_insertion
    -    render_gorillas
    -    render_holes
    -    render_banana
    -    render_game_over
    -    render_score
    -    render_wind
    +  args.sprites    << [CENTER_OFFSET, 0, 1280 * TINY_SCALE, 720 * TINY_SCALE, :lowrez]
    +end
    +
    +def render_gridlines_if_needed args
    +  if args.state.show_gridlines && args.static_lines.length == 0
    +    args.static_lines << 65.times.map do |i|
    +      [
    +        [CENTER_OFFSET + i * TINY_SCALE + 1,  0,
    +         CENTER_OFFSET + i * TINY_SCALE + 1,  720,                128, 128, 128],
    +        [CENTER_OFFSET + i * TINY_SCALE,      0,
    +         CENTER_OFFSET + i * TINY_SCALE,      720,                128, 128, 128],
    +        [CENTER_OFFSET,                       0 + i * TINY_SCALE,
    +         CENTER_OFFSET + 720,                 0 + i * TINY_SCALE, 128, 128, 128],
    +        [CENTER_OFFSET,                       1 + i * TINY_SCALE,
    +         CENTER_OFFSET + 720,                 1 + i * TINY_SCALE, 128, 128, 128]
    +      ]
    +    end
    +  elsif !args.state.show_gridlines
    +    args.static_lines.clear
    +  end
    +end
    +
    +
    +

    Rpg Narrative - Return Of Serenity - main.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/main.rb
    +require 'app/require.rb'
    +
    +def defaults args
    +  args.outputs.background_color = [0, 0, 0]
    +  args.state.last_story_line_text ||= ""
    +  args.state.scene_history ||= []
    +  args.state.storyline_history ||= []
    +  args.state.word_delay ||= 8
    +  if args.state.tick_count == 0
    +    args.gtk.stop_music
    +    args.outputs.sounds << 'sounds/static-loop.ogg'
    +  end
    +
    +  if args.state.last_story_line_text
    +    lines = args.state
    +                .last_story_line_text
    +                .gsub("-", "")
    +                .gsub("~", "")
    +                .wrapped_lines(50)
    +
    +    args.outputs.labels << lines.map_with_index { |l, i| [690, 200 - (i * 25), l, 1, 0, 255, 255, 255] }
    +  elsif args.state.storyline_history[-1]
    +    lines = args.state
    +                .storyline_history[-1]
    +                .gsub("-", "")
    +                .gsub("~", "")
    +                .wrapped_lines(50)
    +
    +    args.outputs.labels << lines.map_with_index { |l, i| [690, 200 - (i * 25), l, 1, 0, 255, 255, 255] }
    +  end
    +
    +  return if args.state.current_scene
    +  set_scene(args, day_one_beginning(args))
    +end
    +
    +def inputs_move_player args
    +  if args.state.scene_changed_at.elapsed_time > 5
    +    if args.keyboard.down  || args.keyboard.s || args.keyboard.j
    +      args.state.player.y -= 0.25
    +    elsif args.keyboard.up || args.keyboard.w || args.keyboard.k
    +      args.state.player.y += 0.25
    +    end
    +
    +    if args.keyboard.left     || args.keyboard.a  || args.keyboard.h
    +      args.state.player.x -= 0.25
    +    elsif args.keyboard.right || args.keyboard.d  || args.keyboard.l
    +      args.state.player.x += 0.25
    +    end
    +
    +    args.state.player.y = 60 if args.state.player.y > 63
    +    args.state.player.y =  0 if args.state.player.y < -3
    +    args.state.player.x = 60 if args.state.player.x > 63
    +    args.state.player.x =  0 if args.state.player.x < -3
    +  end
    +end
    +
    +def null_or_empty? ary
    +  return true unless ary
    +  return true if ary.length == 0
    +  return false
    +end
    +
    +def calc_storyline_hotspot args
    +  hotspots = args.state.storylines.find_all do |hs|
    +    args.state.player.inside_rect?(hs.shift_rect(-2, 0))
       end
     
    -  def render_score
    -    outputs.primitives << [0, 0, 1280, 31, fancy_white].solid
    -    outputs.primitives << [1, 1, 1279, 29].solid
    -    outputs.labels << [  10, 25, "Score: #{state.player_1_score}", 0, 0, fancy_white]
    -    outputs.labels << [1270, 25, "Score: #{state.player_2_score}", 0, 2, fancy_white]
    +  if !null_or_empty?(hotspots) && !args.state.inside_storyline_hotspot
    +    _, _, _, _, storyline = hotspots.first
    +    queue_storyline_text(args, storyline)
    +    args.state.inside_storyline_hotspot = true
    +  elsif null_or_empty?(hotspots)
    +    args.state.inside_storyline_hotspot = false
    +
    +    args.state.storyline_queue_empty_at ||= args.state.tick_count
    +    args.state.is_storyline_dialog_active = false
    +    args.state.scene_storyline_queue.clear
       end
    +end
     
    -  def render_wind
    -    outputs.primitives << [640, 12, state.wind * 500 + state.wind * 10 * rand, 4, 35, 136, 162].solid
    -    outputs.lines     <<  [640, 30, 640, 0, fancy_white]
    +def calc_scenes args
    +  hotspots = args.state.scenes.find_all do |hs|
    +    args.state.player.inside_rect?(hs.shift_rect(-2, 0))
       end
     
    -  def render_game_over
    -    return unless state.over
    -    outputs.primitives << [grid.rect, 0, 0, 0, 200].solid
    -    outputs.primitives << [640, 370, "Game Over!!", 5, 1, fancy_white].label
    -    if state.winner == :player_1
    -      outputs.primitives << [640, 340, "Player 1 Wins!!", 5, 1, fancy_white].label
    +  if !null_or_empty?(hotspots) && !args.state.inside_scene_hotspot
    +    _, _, _, _, scene_method_or_hash = hotspots.first
    +    if scene_method_or_hash.is_a? Symbol
    +      set_scene(args, send(scene_method_or_hash, args))
    +      args.state.last_hotspot_scene = scene_method_or_hash
    +      args.state.scene_history << scene_method_or_hash
         else
    -      outputs.primitives << [640, 340, "Player 2 Wins!!", 5, 1, fancy_white].label
    +      set_scene(args, scene_method_or_hash)
         end
    +    args.state.inside_scene_hotspot = true
    +  elsif null_or_empty?(hotspots)
    +    args.state.inside_scene_hotspot = false
       end
    +end
     
    -  def render_stage
    -    return unless state.stage_generated
    -    return if state.stage_rendered
    +def null_or_whitespace? word
    +  return true if !word
    +  return true if word.strip.length == 0
    +  return false
    +end
     
    -    outputs.static_solids << [grid.rect, 33, 32, 87]
    -    outputs.static_solids << state.buildings.map(&:solids)
    -    state.stage_rendered = true
    +def calc_storyline_presentation args
    +  return unless args.state.tick_count > args.state.next_storyline
    +  return unless args.state.scene_storyline_queue
    +  next_storyline = args.state.scene_storyline_queue.shift
    +  if null_or_whitespace? next_storyline
    +    args.state.storyline_queue_empty_at ||= args.state.tick_count
    +    args.state.is_storyline_dialog_active = false
    +    return
       end
    -
    -  def render_gorilla gorilla, id
    -    return unless gorilla
    -    if state.banana && state.banana.owner == gorilla
    -      animation_index  = state.banana.created_at.frame_index(3, 5, false)
    -    end
    -    if !animation_index
    -      outputs.sprites << [gorilla.solid, "sprites/#{id}-idle.png"]
    -    else
    -      outputs.sprites << [gorilla.solid, "sprites/#{id}-#{animation_index}.png"]
    +  args.state.storyline_to_show = next_storyline
    +  args.state.is_storyline_dialog_active = true
    +  args.state.storyline_queue_empty_at = nil
    +  if next_storyline.end_with?(".") || next_storyline.end_with?("!") || next_storyline.end_with?("?") || next_storyline.end_with?("\"")
    +    args.state.next_storyline += 60
    +  elsif next_storyline.end_with?(",")
    +    args.state.next_storyline += 50
    +  elsif next_storyline.end_with?(":")
    +    args.state.next_storyline += 60
    +  else
    +    default_word_delay = 13 + args.state.word_delay - 8
    +    if next_storyline.gsub("-", "").gsub("~", "").length <= 4
    +      default_word_delay = 11 + args.state.word_delay - 8
         end
    +    number_of_syllabals = next_storyline.length - next_storyline.gsub("-", "").length
    +    args.state.next_storyline += default_word_delay + number_of_syllabals * (args.state.word_delay + 1)
       end
    +end
     
    -  def render_gorillas
    -    render_gorilla state.player_1, :left
    -    render_gorilla state.player_2, :right
    +def inputs_reload_current_scene args
    +  return
    +  if args.inputs.keyboard.key_down.r!
    +    reload_current_scene
       end
    +end
     
    -  def render_value_insertion
    -    return if state.banana
    -    return if state.over
    -
    -    if    state.current_turn == :player_1_angle
    -      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}_",    fancy_white]
    -    elsif state.current_turn == :player_1_velocity
    -      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}",     fancy_white]
    -      outputs.labels << [  10, 690, "Velocity: #{state.player_1_velocity}_", fancy_white]
    -    elsif state.current_turn == :player_2_angle
    -      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}_",    fancy_white]
    -    elsif state.current_turn == :player_2_velocity
    -      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}",     fancy_white]
    -      outputs.labels << [1120, 690, "Velocity: #{state.player_2_velocity}_", fancy_white]
    -    end
    +def inputs_dismiss_current_storyline args
    +  if args.inputs.keyboard.key_down.x!
    +    args.state.scene_storyline_queue.clear
       end
    +end
     
    -  def render_banana
    -    return unless state.banana
    -    rotation = state.tick_count.%(360) * 20
    -    rotation *= -1 if state.banana.dx > 0
    -    outputs.sprites << [state.banana.x, state.banana.y, 15, 15, 'sprites/banana.png', rotation]
    +def inputs_restart_game args
    +  if args.inputs.keyboard.exclamation_point
    +    args.gtk.reset_state
       end
    +end
     
    -  def render_holes
    -    outputs.sprites << state.holes.map do |s|
    -      animation_index = s.created_at.frame_index(7, 3, false)
    -      if animation_index
    -        [s.sprite, [s.sprite.rect, "sprites/explosion#{animation_index}.png" ]]
    -      else
    -        s.sprite
    -      end
    +def inputs_change_word_delay args
    +  if args.inputs.keyboard.key_down.plus || args.inputs.keyboard.key_down.equal_sign
    +    args.state.word_delay -= 2
    +    if args.state.word_delay < 0
    +      args.state.word_delay = 0
    +      # queue_storyline_text args, "Text speed at MAXIMUM. Geez, how fast do you read?"
    +    else
    +      # queue_storyline_text args, "Text speed INCREASED."
         end
       end
     
    -  def calc
    -    calc_generate_stage
    -    calc_current_turn
    -    calc_banana
    +  if args.inputs.keyboard.key_down.hyphen || args.inputs.keyboard.key_down.underscore
    +    args.state.word_delay += 2
    +    # queue_storyline_text args, "Text speed DECREASED."
       end
    +end
     
    -  def calc_current_turn
    -    return if state.current_turn
    -
    -    state.current_turn = :player_1_angle
    -    state.current_turn = :player_2_angle if state.first_strike == :player_2
    +def multiple_lines args, x, y, texts, size = 0, minimum_alpha = nil
    +  texts.each_with_index.map do |t, i|
    +    [x, y - i * (25 + size * 2), t, size, 0, 255, 255, 255, adornments_alpha(args, 255, minimum_alpha)]
       end
    +end
     
    -  def calc_generate_stage
    -    return if state.stage_generated
    +def lowrez_tick args, lowrez_sprites, lowrez_labels, lowrez_borders, lowrez_solids, lowrez_mouse
    +  # args.state.show_gridlines = true
    +  defaults args
    +  render_current_scene args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  render_controller args, lowrez_borders
    +  lowrez_solids << [0, 0, 64, 64, 0, 0, 0]
    +  calc_storyline_presentation args
    +  calc_scenes args
    +  calc_storyline_hotspot args
    +  inputs_move_player args
    +  inputs_print_mouse_rect args, lowrez_mouse
    +  inputs_reload_current_scene args
    +  inputs_dismiss_current_storyline args
    +  inputs_change_word_delay args
    +  inputs_restart_game args
    +end
     
    -    state.buildings << building_prefab(state.building_spacing + -20, *random_building_size)
    -    8.numbers.inject(state.buildings) do |buildings, i|
    -      buildings <<
    -        building_prefab(state.building_spacing +
    -                        state.buildings.last.right,
    -                        *random_building_size)
    +def render_controller args, lowrez_borders
    +  args.state.up_button    = [85, 40, 15, 15, 255, 255, 255]
    +  args.state.down_button  = [85, 20, 15, 15, 255, 255, 255]
    +  args.state.left_button  = [65, 20, 15, 15, 255, 255, 255]
    +  args.state.right_button = [105, 20, 15, 15, 255, 255, 255]
    +  lowrez_borders << args.state.up_button
    +  lowrez_borders << args.state.down_button
    +  lowrez_borders << args.state.left_button
    +  lowrez_borders << args.state.right_button
    +end
    +
    +def inputs_print_mouse_rect args, lowrez_mouse
    +  if lowrez_mouse.up
    +    args.state.mouse_held = false
    +  elsif lowrez_mouse.click
    +    mouse_rect = [lowrez_mouse.x, lowrez_mouse.y, 1, 1]
    +    if args.state.up_button.intersect_rect? mouse_rect
    +      args.state.player.y += 1
         end
     
    -    building_two = state.buildings[1]
    -    state.player_1 = new_player(building_two.x + building_two.w.fdiv(2),
    -                               building_two.h)
    +    if args.state.down_button.intersect_rect? mouse_rect
    +      args.state.player.y -= 1
    +    end
     
    -    building_nine = state.buildings[-3]
    -    state.player_2 = new_player(building_nine.x + building_nine.w.fdiv(2),
    -                               building_nine.h)
    -    state.stage_generated = true
    -    state.wind = 1.randomize(:ratio, :sign)
    -  end
    +    if args.state.left_button.intersect_rect? mouse_rect
    +      args.state.player.x -= 1
    +    end
     
    -  def new_player x, y
    -    state.new_entity(:gorilla) do |p|
    -      p.x = x - 25
    -      p.y = y
    -      p.solid = [p.x, p.y, 50, 50]
    +    if args.state.right_button.intersect_rect? mouse_rect
    +      args.state.player.x += 1
    +    end
    +    args.state.mouse_held = true
    +  elsif args.state.mouse_held
    +    mouse_rect = [lowrez_mouse.x, lowrez_mouse.y, 1, 1]
    +    if args.state.up_button.intersect_rect? mouse_rect
    +      args.state.player.y += 0.25
         end
    -  end
     
    -  def calc_banana
    -    return unless state.banana
    +    if args.state.down_button.intersect_rect? mouse_rect
    +      args.state.player.y -= 0.25
    +    end
     
    -    state.banana.x  += state.banana.dx
    -    state.banana.dx += state.wind.fdiv(50)
    -    state.banana.y  += state.banana.dy
    -    state.banana.dy -= state.gravity
    -    banana_collision = [state.banana.x, state.banana.y, 10, 10]
    +    if args.state.left_button.intersect_rect? mouse_rect
    +      args.state.player.x -= 0.25
    +    end
     
    -    if state.player_1 && banana_collision.intersect_rect?(state.player_1.solid)
    -      state.over = true
    -      if state.banana.owner == state.player_2
    -        state.winner = :player_2
    -      else
    -        state.winner = :player_1
    -      end
    +    if args.state.right_button.intersect_rect? mouse_rect
    +      args.state.player.x += 0.25
    +    end
    +  end
     
    -      state.player_2_score += 1
    -    elsif state.player_2 && banana_collision.intersect_rect?(state.player_2.solid)
    -      state.over = true
    -      if state.banana.owner == state.player_2
    -        state.winner = :player_1
    -      else
    -        state.winner = :player_2
    +  if lowrez_mouse.click
    +    dx = lowrez_mouse.click.x - args.state.previous_mouse_click.x
    +    dy = lowrez_mouse.click.y - args.state.previous_mouse_click.y
    +    x, y, w, h = args.state.previous_mouse_click.x, args.state.previous_mouse_click.y, dx, dy
    +    puts "x #{lowrez_mouse.click.x}, y: #{lowrez_mouse.click.y}"
    +    if args.state.previous_mouse_click
    +
    +      if dx < 0 && dx < 0
    +        x = x + w
    +        w = w.abs
    +        y = y + h
    +        h = h.abs
           end
    -      state.player_1_score += 1
    -    end
     
    -    if state.over
    -      place_hole
    -      return
    +      w += 1
    +      h += 1
    +
    +      args.state.previous_mouse_click = nil
    +    else
    +      args.state.previous_mouse_click = lowrez_mouse.click
    +      square_x, square_y = lowrez_mouse.click
         end
    +  end
    +end
    +
    +def try_centering! word
    +  word ||= ""
    +  just_word = word.gsub("-", "").gsub(",", "").gsub(".", "").gsub("'", "").gsub('""', "\"-\"")
    +  return word if just_word.strip.length == 0
    +  return word if just_word.include? "~"
    +  return "~#{word}" if just_word.length <= 2
    +  if just_word.length.mod_zero? 2
    +    center_index = just_word.length.idiv(2) - 1
    +  else
    +    center_index = (just_word.length - 1).idiv(2)
    +  end
    +  return "#{word[0..center_index - 1]}~#{word[center_index]}#{word[center_index + 1..-1]}"
    +end
     
    -    return if state.holes.any? do |h|
    -      h.sprite.scale_rect(0.8, 0.5, 0.5).intersect_rect? [state.banana.x, state.banana.y, 10, 10]
    -    end
    +def queue_storyline args, scene
    +  queue_storyline_text args, scene[:storyline]
    +end
     
    -    return unless state.banana.y < 0 || state.buildings.any? do |b|
    -      b.rect.intersect_rect? [state.banana.x, state.banana.y, 1, 1]
    -    end
    +def queue_storyline_text args, text
    +  args.state.last_story_line_text = text
    +  args.state.storyline_history << text if text
    +  words = (text || "").split(" ")
    +  words = words.map { |w| try_centering! w }
    +  args.state.scene_storyline_queue = words
    +  if args.state.scene_storyline_queue.length != 0
    +    args.state.scene_storyline_queue.unshift "~$--"
    +    args.state.storyline_to_show = "~."
    +  else
    +    args.state.storyline_to_show = ""
    +  end
    +  args.state.scene_storyline_queue << ""
    +  args.state.next_storyline = args.state.tick_count
    +end
     
    -    place_hole
    +def set_scene args, scene
    +  args.state.current_scene = scene
    +  args.state.background = scene[:background] ||  'sprites/todo.png'
    +  args.state.scene_fade = scene[:fade] || 0
    +  args.state.scenes = (scene[:scenes] || []).reject { |s| !s }
    +  args.state.scene_render_override = scene[:render_override]
    +  args.state.storylines = (scene[:storylines] || []).reject { |s| !s }
    +  args.state.scene_changed_at = args.state.tick_count
    +  if scene[:player]
    +    args.state.player = scene[:player]
       end
    +  args.state.inside_scene_hotspot = false
    +  args.state.inside_storyline_hotspot = false
    +  queue_storyline args, scene
    +end
     
    -  def place_hole
    -    return unless state.banana
    +def replay_storyline_rect
    +  [26, -1, 7, 4]
    +end
     
    -    state.holes << state.new_entity(:banana) do |b|
    -      b.sprite = [state.banana.x - 20, state.banana.y - 20, 40, 40, 'sprites/hole.png']
    -    end
    +def labels_for_word word
    +  left_side_of_word = ""
    +  center_letter = ""
    +  right_side_of_word = ""
     
    -    state.banana = nil
    +  if word[0] == "~"
    +    left_side_of_word = ""
    +    center_letter = word[1]
    +    right_side_of_word = word[2..-1]
    +  elsif word.length > 0
    +    left_side_of_word, right_side_of_word = word.split("~")
    +    center_letter = right_side_of_word[0]
    +    right_side_of_word = right_side_of_word[1..-1]
       end
     
    -  def process_inputs_main
    -    return if state.banana
    -    return if state.over
    +  right_side_of_word = right_side_of_word.gsub("-", "")
     
    -    if inputs.keyboard.key_down.enter
    -      input_execute_turn
    -    elsif inputs.keyboard.key_down.backspace
    -      state.as_hash[state.current_turn] ||= ""
    -      state.as_hash[state.current_turn]   = state.as_hash[state.current_turn][0..-2]
    -    elsif inputs.keyboard.key_down.char
    -      state.as_hash[state.current_turn] ||= ""
    -      state.as_hash[state.current_turn]  += inputs.keyboard.key_down.char
    -    end
    -  end
    +  {
    +    left:   [29 - left_side_of_word.length * 4 - 1 * left_side_of_word.length, 2, left_side_of_word],
    +    center: [29, 2, center_letter, 255, 0, 0],
    +    right:  [34, 2, right_side_of_word]
    +  }
    +end
     
    -  def process_inputs_game_over
    -    return unless state.over
    -    return unless inputs.keyboard.key_down.truthy_keys.any?
    -    state.over = false
    -    outputs.static_solids.clear
    -    state.buildings.clear
    -    state.holes.clear
    -    state.stage_generated = false
    -    state.stage_rendered = false
    -    if state.first_strike == :player_1
    -      state.first_strike = :player_2
    -    else
    -      state.first_strike = :player_1
    -    end
    +def render_scenes args, lowrez_sprites
    +  lowrez_sprites << args.state.scenes.flat_map do |hs|
    +    hotspot_square args, hs.x, hs.y, hs.w, hs.h
       end
    +end
     
    -  def process_inputs
    -    process_inputs_main
    -    process_inputs_game_over
    +def render_storylines args, lowrez_sprites
    +  lowrez_sprites << args.state.storylines.flat_map do |hs|
    +    hotspot_square args, hs.x, hs.y, hs.w, hs.h
       end
    +end
     
    -  def input_execute_turn
    -    return if state.banana
    -
    -    if state.current_turn == :player_1_angle && parse_or_clear!(:player_1_angle)
    -      state.current_turn = :player_1_velocity
    -    elsif state.current_turn == :player_1_velocity && parse_or_clear!(:player_1_velocity)
    -      state.current_turn = :player_2_angle
    -      state.banana =
    -        new_banana(state.player_1,
    -                   state.player_1.x + 25,
    -                   state.player_1.y + 60,
    -                   state.player_1_angle,
    -                   state.player_1_velocity)
    -    elsif state.current_turn == :player_2_angle && parse_or_clear!(:player_2_angle)
    -      state.current_turn = :player_2_velocity
    -    elsif state.current_turn == :player_2_velocity && parse_or_clear!(:player_2_velocity)
    -      state.current_turn = :player_1_angle
    -      state.banana =
    -        new_banana(state.player_2,
    -                   state.player_2.x + 25,
    -                   state.player_2.y + 60,
    -                   180 - state.player_2_angle,
    -                   state.player_2_velocity)
    -    end
    +def adornments_alpha args, target_alpha = nil, minimum_alpha = nil
    +  return (minimum_alpha || 80) unless args.state.storyline_queue_empty_at
    +  target_alpha ||= 255
    +  target_alpha * args.state.storyline_queue_empty_at.ease(60)
    +end
     
    -    if state.banana
    -      state.player_1_angle = nil
    -      state.player_1_velocity = nil
    -      state.player_2_angle = nil
    -      state.player_2_velocity = nil
    -    end
    +def hotspot_square args, x, y, w, h
    +  if w >= 3 && h >= 3
    +    [
    +      [x + w.idiv(2) + 1, y, w.idiv(2), h, 'sprites/label-background.png', 0, adornments_alpha(args, 50), 23, 23, 23],
    +      [x, y, w.idiv(2), h, 'sprites/label-background.png', 0, adornments_alpha(args, 100), 223, 223, 223],
    +      [x + 1, y + 1, w - 2, h - 2, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 40, 140, 40],
    +    ]
    +  else
    +    [
    +      [x, y, w, h, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 0, 140, 0],
    +    ]
       end
    +end
     
    -  def random_building_size
    -    [state.building_heights.sample, state.building_room_sizes.sample]
    +def render_storyline_dialog args, lowrez_labels, lowrez_sprites
    +  return unless args.state.is_storyline_dialog_active
    +  return unless args.state.storyline_to_show
    +  labels = labels_for_word args.state.storyline_to_show
    +  if true # high rez version
    +    scale = 8.88
    +    offset = 45
    +    size = 25
    +    args.outputs.labels << [offset + labels[:left].x.-(1) * scale,
    +                            labels[:left].y * TINY_SCALE + 55,
    +                            labels[:left].text, size, 0, 0, 0, 0, 255,
    +                            'fonts/manaspc.ttf']
    +    center_text = labels[:center].text
    +    center_text = "|" if center_text == "$"
    +    args.outputs.labels << [offset + labels[:center].x * scale,
    +                            labels[:center].y * TINY_SCALE + 55,
    +                            center_text, size, 0, 255, 0, 0, 255,
    +                            'fonts/manaspc.ttf']
    +    args.outputs.labels << [offset + labels[:right].x * scale,
    +                            labels[:right].y * TINY_SCALE + 55,
    +                            labels[:right].text, size, 0, 0, 0, 0, 255,
    +                            'fonts/manaspc.ttf']
    +  else
    +    lowrez_labels << labels[:left]
    +    lowrez_labels << labels[:center]
    +    lowrez_labels << labels[:right]
       end
    +  args.state.is_storyline_dialog_active = true
    +  render_player args, lowrez_sprites
    +  lowrez_sprites <<  [0, 0, 64, 8, 'sprites/label-background.png']
    +end
     
    -  def int? v
    -    v.to_i.to_s == v.to_s
    -  end
    +def render_player args, lowrez_sprites
    +  lowrez_sprites << player_md_down(args, *args.state.player)
    +end
     
    -  def random_building_color
    -    [[ 99,   0, 107],
    -     [ 35,  64, 124],
    -     [ 35, 136, 162],
    -     ].sample
    -  end
    +def render_adornments args, lowrez_sprites
    +  render_scenes args, lowrez_sprites
    +  render_storylines args, lowrez_sprites
    +  return if args.state.is_storyline_dialog_active
    +  lowrez_sprites << player_md_down(args, *args.state.player)
    +end
     
    -  def random_window_color
    -    [[ 88,  62, 104],
    -     [253, 224, 187]].sample
    -  end
    +def global_alpha_percentage args, max_alpha = 255
    +  return 255 unless args.state.scene_changed_at
    +  return 255 unless args.state.scene_fade
    +  return 255 unless args.state.scene_fade > 0
    +  return max_alpha * args.state.scene_changed_at.ease(args.state.scene_fade)
    +end
     
    -  def windows_for_building starting_x, floors, rooms
    -    floors.-(1).combinations(rooms - 1).map do |floor, room|
    -      [starting_x +
    -       state.building_room_width.*(room) +
    -       state.building_room_spacing.*(room + 1),
    -       state.building_room_height.*(floor) +
    -       state.building_room_spacing.*(floor + 1),
    -       state.building_room_width,
    -       state.building_room_height,
    -       random_window_color]
    -    end
    +def render_current_scene args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [0, 0, 64, 64, args.state.background, 0, (global_alpha_percentage args)]
    +  if args.state.scene_render_override
    +    send args.state.scene_render_override, args, lowrez_sprites, lowrez_labels, lowrez_solids
       end
    +  storyline_to_show = args.state.storyline_to_show || ""
    +  render_adornments args, lowrez_sprites
    +  render_storyline_dialog args, lowrez_labels, lowrez_sprites
     
    -  def building_prefab starting_x, floors, rooms
    -    state.new_entity(:building) do |b|
    -      b.x      = starting_x
    -      b.y      = 0
    -      b.w      = state.building_room_width.*(rooms) +
    -                 state.building_room_spacing.*(rooms + 1)
    -      b.h      = state.building_room_height.*(floors) +
    -                 state.building_room_spacing.*(floors + 1)
    -      b.right  = b.x + b.w
    -      b.rect   = [b.x, b.y, b.w, b.h]
    -      b.solids = [[b.x - 1, b.y, b.w + 2, b.h + 1, fancy_white],
    -                  [b.x, b.y, b.w, b.h, random_building_color],
    -                  windows_for_building(b.x, floors, rooms)]
    +  if args.state.background == 'sprites/tribute-game-over.png'
    +    lowrez_sprites << [0, 0, 64, 11, 'sprites/label-background.png', 0, adornments_alpha(args, 200), 0, 0, 0]
    +    lowrez_labels << [9, 6, 'Return of', 255, 255, 255]
    +    lowrez_labels << [9, 1, ' Serenity', 255, 255, 255]
    +    if !args.state.ended
    +      args.gtk.stop_music
    +      args.outputs.sounds << 'sounds/music-loop.ogg'
    +      args.state.ended = true
         end
       end
    +end
     
    -  def parse_or_clear! game_prop
    -    if int? state.as_hash[game_prop]
    -      state.as_hash[game_prop] = state.as_hash[game_prop].to_i
    -      return true
    -    end
    +def player_md_right args, x, y
    +  [x, y, 4, 11, 'sprites/player-right.png', 0, (global_alpha_percentage args)]
    +end
     
    -    state.as_hash[game_prop] = nil
    -    return false
    -  end
    +def player_md_left args, x, y
    +  [x, y, 4, 11, 'sprites/player-left.png', 0, (global_alpha_percentage args)]
    +end
     
    -  def new_banana owner, x, y, angle, velocity
    -    state.new_entity(:banana) do |b|
    -      b.owner     = owner
    -      b.x         = x
    -      b.y         = y
    -      b.angle     = angle % 360
    -      b.velocity  = velocity / 5
    -      b.dx        = b.angle.vector_x(b.velocity)
    -      b.dy        = b.angle.vector_y(b.velocity)
    -    end
    -  end
    +def player_md_up args, x, y
    +  [x, y, 4, 11, 'sprites/player-up.png', 0, (global_alpha_percentage args)]
    +end
     
    -  def fancy_white
    -    [253, 252, 253]
    -  end
    +def player_md_down args, x, y
    +  [x, y, 4, 11, 'sprites/player-down.png', 0, (global_alpha_percentage args)]
     end
     
    -$you_so_basic_gorillas = YouSoBasicGorillas.new
    +def player_sm args, x, y
    +  [x, y, 3, 7, 'sprites/player-zoomed-out.png', 0, (global_alpha_percentage args)]
    +end
     
    -def tick args
    -  $you_so_basic_gorillas.outputs = args.outputs
    -  $you_so_basic_gorillas.grid    = args.grid
    -  $you_so_basic_gorillas.state    = args.state
    -  $you_so_basic_gorillas.inputs  = args.inputs
    -  $you_so_basic_gorillas.tick
    +def player_xs args, x, y
    +  [x, y, 1, 4, 'sprites/player-zoomed-out.png', 0, (global_alpha_percentage args)]
     end
     
     
    -

    99_genre_platformer/gorillas_basic/app/repl.rb

    -
    begin
    -  if $gtk.args.state.current_turn == :player_1_angle
    -    $gtk.args.state.player_1_angle = "#{60 + 10.randomize(:ratio).to_i}"
    -    $you_so_basic_gorillas.input_execute_turn
    -    $gtk.args.state.player_1_velocity = "#{30 + 20.randomize(:ratio).to_i}"
    -    $you_so_basic_gorillas.input_execute_turn
    -  elsif $gtk.args.state.current_turn == :player_2_angle
    -    $gtk.args.state.player_2_angle = "#{60 + 10.randomize(:ratio).to_i}"
    -    $you_so_basic_gorillas.input_execute_turn
    -    $gtk.args.state.player_2_velocity = "#{30 + 20.randomize(:ratio).to_i}"
    -    $you_so_basic_gorillas.input_execute_turn
    -  else
    -    $you_so_basic_gorillas.input_execute_turn
    -  end
    -rescue Exception => e
    -  puts e
    -end
    +

    Rpg Narrative - Return Of Serenity - repl.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/repl.rb
    +puts $gtk.args.state.current_scene
     
     
    -

    99_genre_platformer/gorillas_basic/app/tests.rb

    -
    $gtk.reset 100
    -$gtk.supress_framerate_warning = true
    -$gtk.require 'app/tests/building_generation_tests.rb'
    -$gtk.tests.start
    +

    Rpg Narrative - Return Of Serenity - require.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/require.rb
    +require 'app/lowrez_simulator.rb'
    +require 'app/storyline_day_one.rb'
    +require 'app/storyline_blinking_light.rb'
    +require 'app/storyline_serenity_introduction.rb'
    +require 'app/storyline_speed_of_light.rb'
    +require 'app/storyline_serenity_alive.rb'
    +require 'app/storyline_serenity_bio.rb'
    +require 'app/storyline_anka.rb'
    +require 'app/storyline_final_message.rb'
    +require 'app/storyline_final_decision.rb'
    +require 'app/storyline.rb'
     
     
    -

    99_genre_platformer/gorillas_basic/app/tests/building_generation_tests.rb

    -
    def test_solids args, assert
    -  game = YouSoBasicGorillas.new
    -  game.outputs = args.outputs
    -  game.grid = args.grid
    -  game.state = args.state
    -  game.inputs = args.inputs
    -  game.tick
    -  assert.true! args.state.stage_generated, "stage wasn't generated but it should have been"
    -  game.tick
    -  assert.true! args.outputs.static_solids.length > 0, "stage wasn't rendered"
    -  number_of_building_components = (args.state.buildings.map { |b| 2 + b.solids[2].length }.inject do |sum, v| (sum || 0) + v end)
    -  the_only_background = 1
    -  static_solids = args.outputs.static_solids.length
    -  assert.true! static_solids == the_only_background.+(number_of_building_components), "not all parts of the buildings and background were rendered"
    +

    Rpg Narrative - Return Of Serenity - storyline.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline.rb
    +def hotspot_top
    +  [4, 61, 56, 3]
     end
     
    -
    -

    99_genre_platformer/the_little_probe/app/main.rb

    -
    class FallingCircle
    -  attr_gtk
    +def hotspot_bottom
    +  [4, 0, 56, 3]
    +end
     
    -  def tick
    -    fiddle
    -    defaults
    -    render
    -    input
    -    calc
    -  end
    +def hotspot_top_right
    +  [62, 35, 3, 25]
    +end
     
    -  def fiddle
    -    state.gravity     = -0.02
    -    circle.radius     = 15
    -    circle.elasticity = 0.4
    -    camera.follow_speed = 0.4 * 0.4
    -  end
    +def hotspot_bottom_right
    +  [62, 0, 3, 25]
    +end
     
    -  def render
    -    render_stage_editor
    -    render_debug
    -    render_game
    -  end
    +def storyline_history_include? args, text
    +  args.state.storyline_history.any? { |s| s.gsub("-", "").gsub(" ", "").include? text.gsub("-", "").gsub(" ", "") }
    +end
     
    -  def defaults
    -    if state.tick_count == 0
    -      outputs.sounds << "sounds/bg.ogg"
    -    end
    +def blinking_light_side_of_home_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [48, 44, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [49, 45, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [50, 46, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +end
     
    -    state.storyline ||= [
    -      { text: "<- -> to aim, hold space to charge",                            distance_gate: 0 },
    -      { text: "the little probe - by @amirrajan, made with DragonRuby Game Toolkit", distance_gate: 0 },
    -      { text: "mission control, this is sasha. landing on europa successful.", distance_gate: 0 },
    -      { text: "operation \"find earth 2.0\", initiated at 8-29-2036 14:00.",   distance_gate: 0 },
    -      { text: "jupiter's sure is beautiful...",   distance_gate: 4000 },
    -      { text: "hmm, it seems there's some kind of anomoly in the sky",   distance_gate: 7000 },
    -      { text: "dancing lights, i'll call them whisps.",   distance_gate: 8000 },
    -      { text: "#todo... look i ran out of time -_-",   distance_gate: 9000 },
    -      { text: "there's never enough time",   distance_gate: 9000 },
    -      { text: "the game jam was fun though ^_^",   distance_gate: 10000 },
    -    ]
    +def blinking_light_mountain_pass_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [18, 47, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [19, 48, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [20, 49, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +end
     
    -    load_level force: args.state.tick_count == 0
    -    state.line_mode            ||= :terrain
    +def blinking_light_path_to_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [0, 26, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [1, 27, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [2, 28, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +end
     
    -    state.sound_index          ||= 1
    -    circle.potential_lift      ||= 0
    -    circle.angle               ||= 90
    -    circle.check_point_at      ||= -1000
    -    circle.game_over_at        ||= -1000
    -    circle.x                   ||= -485
    -    circle.y                   ||= 12226
    -    circle.check_point_x       ||= circle.x
    -    circle.check_point_y       ||= circle.y
    -    circle.dy                  ||= 0
    -    circle.dx                  ||= 0
    -    circle.previous_dy         ||= 0
    -    circle.previous_dx         ||= 0
    -    circle.angle               ||= 0
    -    circle.after_images        ||= []
    -    circle.terrains_to_monitor ||= {}
    -    circle.impact_history      ||= []
    +def blinking_light_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [23, 59, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [24, 60, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [25, 61, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +end
     
    -    camera.x                   ||= 0
    -    camera.y                   ||= 0
    -    camera.target_x            ||= 0
    -    camera.target_y            ||= 0
    -    state.snaps                ||= { }
    -    state.snap_number            = 10
    +def blinking_light_inside_observatory_render args, lowrez_sprites, lowrez_labels, lowrez_solids
    +  lowrez_sprites << [30, 30, 5, 5, 'sprites/square.png', 0,  50 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [31, 31, 3, 3, 'sprites/square.png', 0, 100 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +  lowrez_sprites << [32, 32, 1, 1, 'sprites/square.png', 0, 255 * (args.state.tick_count % 50).fdiv(50), 0, 255, 0]
    +end
     
    -    args.state.storyline_x ||= -1000
    -    args.state.storyline_y ||= -1000
    -  end
    +def decision_graph context_message, context_action, context_result_one, context_result_two, context_result_three = [], context_result_four = []
    +  result_one_scene, result_one_label, result_one_text = context_result_one
    +  result_two_scene, result_two_label, result_two_text = context_result_two
    +  result_three_scene, result_three_label, result_three_text = context_result_three
    +  result_four_scene, result_four_label, result_four_text = context_result_four
     
    -  def render_game
    -    outputs.background_color = [0, 0, 0]
    -    outputs.sprites << [-circle.x + 1100,
    -                        -circle.y - 100,
    -                        2416 * 4,
    -                        3574 * 4,
    -                        'sprites/jupiter.png']
    -    outputs.sprites << [-circle.x,
    -                        -circle.y,
    -                        2416 * 4,
    -                        3574 * 4,
    -                        'sprites/level.png']
    -    outputs.sprites << state.whisp_queue
    -    render_aiming_retical
    -    render_circle
    -    render_notification
    -  end
    +  top_level_hash = {
    +    background: 'sprites/decision.png',
    +    fade: 60,
    +    player: [20, 36],
    +    storylines: [ ],
    +    scenes: [ ]
    +  }
     
    -  def render_notification
    -    toast_length = 500
    -    if circle.game_over_at.elapsed_time < toast_length
    -      label_text = "..."
    -    elsif circle.check_point_at.elapsed_time > toast_length
    -      args.state.current_storyline = nil
    -      return
    -    end
    -    if circle.check_point_at &&
    -       circle.check_point_at.elapsed_time == 1 &&
    -       !args.state.current_storyline
    -       if args.state.storyline.length > 0 && args.state.distance_traveled > args.state.storyline[0][:distance_gate]
    -         args.state.current_storyline = args.state.storyline.shift[:text]
    -         args.state.distance_traveled ||= 0
    -         args.state.storyline_x = circle.x
    -         args.state.storyline_y = circle.y
    -       end
    -      return unless args.state.current_storyline
    -    end
    -    label_text = args.state.current_storyline
    -    return unless label_text
    -    x = circle.x + camera.x
    -    y = circle.y + camera.y - 40
    -    w = 900
    -    h = 30
    -    outputs.primitives << [x - w.idiv(2), y - h, w, h, 255, 255, 255, 255].solid
    -    outputs.primitives << [x - w.idiv(2), y - h, w, h, 0, 0, 0, 255].border
    -    outputs.labels << [x, y - 4, label_text, 1, 1, 0, 0, 0, 255]
    -  end
    +  confirmation_result_one_hash = {
    +    background: 'sprites/decision.png',
    +    scenes: [ ],
    +    storylines: [ ]
    +  }
    +
    +  confirmation_result_two_hash = {
    +    background: 'sprites/decision.png',
    +    scenes: [ ],
    +    storylines: [ ]
    +  }
     
    -  def render_aiming_retical
    -    outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.potential_lift * 10) - 5,
    -                        state.camera.y + circle.y + circle.angle.vector_y(circle.potential_lift * 10) - 5,
    -                        10, 10, 'sprites/circle-orange.png']
    -    outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5,
    -                        state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5,
    -                        10, 10, 'sprites/circle-orange.png', 0, 128]
    -    if rand > 0.9
    -      outputs.sprites << [state.camera.x + circle.x + circle.angle.vector_x(circle.radius * 3) - 5,
    -                          state.camera.y + circle.y + circle.angle.vector_y(circle.radius * 3) - 5,
    -                          10, 10, 'sprites/circle-white.png', 0, 128]
    -    end
    -  end
    +  confirmation_result_three_hash = {
    +    background: 'sprites/decision.png',
    +    scenes: [ ],
    +    storylines: [ ]
    +  }
     
    -  def render_circle
    -    outputs.sprites << circle.after_images.map do |ai|
    -      ai.merge(x: ai.x + state.camera.x - circle.radius,
    -               y: ai.y + state.camera.y - circle.radius,
    -               w: circle.radius * 2,
    -               h: circle.radius * 2,
    -               path: 'sprites/circle-white.png')
    -    end
    +  confirmation_result_four_hash = {
    +    background: 'sprites/decision.png',
    +    scenes: [ ],
    +    storylines: [ ]
    +  }
     
    -    outputs.sprites << [(circle.x - circle.radius) + state.camera.x,
    -                        (circle.y - circle.radius) + state.camera.y,
    -                        circle.radius * 2,
    -                        circle.radius * 2,
    -                        'sprites/probe.png']
    -  end
    +  top_level_hash[:storylines] << [ 5, 35, 4, 4, context_message]
    +  top_level_hash[:storylines] << [20, 35, 4, 4, context_action]
     
    -  def render_debug
    -    return unless state.debug_mode
    +  confirmation_result_one_hash[:scenes]       << [20, 35, 4, 4, top_level_hash]
    +  confirmation_result_one_hash[:scenes]       << [60, 50, 4, 4, result_one_scene]
    +  confirmation_result_one_hash[:storylines]   << [40, 50, 4, 4, "#{result_one_label}: \"#{result_one_text}\""]
    +  confirmation_result_one_hash[:scenes]       << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    +  confirmation_result_one_hash[:scenes]       << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    +  confirmation_result_one_hash[:scenes]       << [40, 20, 4, 4, confirmation_result_two_hash]
     
    -    outputs.labels << [10, 30, state.line_mode, 0, 0, 0, 0, 0]
    -    outputs.labels << [12, 32, state.line_mode, 0, 0, 255, 255, 255]
    +  confirmation_result_two_hash[:scenes]       << [20, 35, 4, 4, top_level_hash]
    +  confirmation_result_two_hash[:scenes]       << [40, 50, 4, 4, confirmation_result_one_hash]
    +  confirmation_result_two_hash[:scenes]       << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    +  confirmation_result_two_hash[:scenes]       << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    +  confirmation_result_two_hash[:scenes]       << [60, 20, 4, 4, result_two_scene]
    +  confirmation_result_two_hash[:storylines]   << [40, 20, 4, 4, "#{result_two_label}: \"#{result_two_text}\""]
     
    -    args.outputs.lines << trajectory(circle).line.to_hash.tap do |h|
    -      h[:x] += state.camera.x
    -      h[:y] += state.camera.y
    -      h[:x2] += state.camera.x
    -      h[:y2] += state.camera.y
    -    end
    +  confirmation_result_three_hash[:scenes]     << [20, 35, 4, 4, top_level_hash]
    +  confirmation_result_three_hash[:scenes]     << [40, 50, 4, 4, confirmation_result_one_hash]
    +  confirmation_result_three_hash[:scenes]     << [40, 40, 4, 4, confirmation_result_four_hash]
    +  confirmation_result_three_hash[:scenes]     << [60, 30, 4, 4, result_three_scene]
    +  confirmation_result_three_hash[:storylines] << [40, 30, 4, 4, "#{result_three_label}: \"#{result_three_text}\""]
    +  confirmation_result_three_hash[:scenes]     << [40, 20, 4, 4, confirmation_result_two_hash]
     
    -    outputs.primitives << state.terrain.find_all do |t|
    -      circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360)
    -    end.map do |t|
    -      [
    -        t.line.associate(r: 0, g: 255, b: 0) do |h|
    -          h.x  += state.camera.x
    -          h.y  += state.camera.y
    -          h.x2 += state.camera.x
    -          h.y2 += state.camera.y
    -          if circle.rect.intersect_rect? t[:rect]
    -            h[:r] = 255
    -            h[:g] = 0
    -          end
    -          h
    -        end,
    -        t[:rect].border.associate(r: 255, g: 0, b: 0) do |h|
    -          h.x += state.camera.x
    -          h.y += state.camera.y
    -          h.b = 255 if line_near_rect? circle.rect, t
    -          h
    -        end
    -      ]
    -    end
    +  confirmation_result_four_hash[:scenes]      << [20, 35, 4, 4, top_level_hash]
    +  confirmation_result_four_hash[:scenes]      << [40, 50, 4, 4, confirmation_result_one_hash]
    +  confirmation_result_four_hash[:scenes]      << [60, 40, 4, 4, result_four_scene]
    +  confirmation_result_four_hash[:storylines]  << [40, 40, 4, 4, "#{result_four_label}: \"#{result_four_text}\""]
    +  confirmation_result_four_hash[:scenes]      << [40, 30, 4, 4, confirmation_result_three_hash]
    +  confirmation_result_four_hash[:scenes]      << [40, 20, 4, 4, confirmation_result_two_hash]
     
    -    outputs.primitives << state.lava.find_all do |t|
    -      circle.x.between?(t.x - 640, t.x2 + 640) || circle.y.between?(t.y - 360, t.y2 + 360)
    -    end.map do |t|
    -      [
    -        t.line.associate(r: 0, g: 0, b: 255) do |h|
    -          h.x  += state.camera.x
    -          h.y  += state.camera.y
    -          h.x2 += state.camera.x
    -          h.y2 += state.camera.y
    -          if circle.rect.intersect_rect? t[:rect]
    -            h[:r] = 255
    -            h[:b] = 0
    -          end
    -          h
    -        end,
    -        t[:rect].border.associate(r: 255, g: 0, b: 0) do |h|
    -          h.x += state.camera.x
    -          h.y += state.camera.y
    -          h.b = 255 if line_near_rect? circle.rect, t
    -          h
    -        end
    -      ]
    -    end
    +  top_level_hash[:scenes]     << [40, 50, 4, 4, confirmation_result_one_hash]
    +  top_level_hash[:scenes]     << [40, 40, 4, 4, confirmation_result_four_hash] if result_four_scene
    +  top_level_hash[:scenes]     << [40, 30, 4, 4, confirmation_result_three_hash] if result_three_scene
    +  top_level_hash[:scenes]     << [40, 20, 4, 4, confirmation_result_two_hash]
     
    -    if state.god_mode
    -      border = circle.rect.merge(x: circle.rect.x + state.camera.x,
    -                                 y: circle.rect.y + state.camera.y,
    -                                 g: 255)
    -    else
    -      border = circle.rect.merge(x: circle.rect.x + state.camera.x,
    -                                 y: circle.rect.y + state.camera.y,
    -                                 b: 255)
    -    end
    +  top_level_hash
    +end
     
    -    outputs.borders << border
    +def ship_control_hotspot offset_x, offset_y, a, b, c, d
    +  results = []
    +  results << [ 6 + offset_x, 0 + offset_y, 4, 4, a]  if a
    +  results << [ 1 + offset_x, 5 + offset_y, 4, 4, b]  if b
    +  results << [ 6 + offset_x, 5 + offset_y, 4, 4, c]  if c
    +  results << [ 11 + offset_x, 5 + offset_y, 4, 4, d] if d
    +  results
    +end
     
    -    overlapping ||= {}
    +def reload_current_scene
    +  if $gtk.args.state.last_hotspot_scene
    +    set_scene $gtk.args, send($gtk.args.state.last_hotspot_scene, $gtk.args)
    +    tick $gtk.args
    +  elsif respond_to? :set_scene
    +    set_scene $gtk.args, (replied_to_serenity_alive_firmly $gtk.args)
    +    tick $gtk.args
    +  end
    +  $gtk.console.close
    +end
     
    -    circle.impact_history.each do |h|
    -      label_mod = 300
    -      x = (h[:body][:x].-(150).idiv(label_mod)) * label_mod + camera.x
    -      y = (h[:body][:y].+(150).idiv(label_mod)) * label_mod + camera.y
    -      10.times do
    -        if overlapping[x] && overlapping[x][y]
    -          y -= 52
    -        else
    -          break
    -        end
    -      end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_anka.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_anka.rb
    +def anka_inside_room args
    +  {
    +    background: 'sprites/inside-home.png',
    +    player: [34, 35],
    +    storylines: [
    +      [34, 34, 4, 4, "Ahhhh!!! Oh god, it was just- a nightmare."],
    +    ],
    +    scenes: [
    +      [32, -1, 8, 3, :anka_observatory]
    +    ]
    +  }
    +end
     
    -      overlapping[x] ||= {}
    -      overlapping[x][y] ||= true
    -      outputs.primitives << [x, y - 25, 300, 50, 0, 0, 0, 128].solid
    -      outputs.labels << [x + 10, y + 24, "dy: %.2f" % h[:body][:new_dy], -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 10, y +  9, "dx: %.2f" % h[:body][:new_dx], -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 10, y -  5, " ?: #{h[:body][:new_reason]}", -2, 0, 255, 255, 255]
    +def anka_observatory args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [51, 12],
    +    storylines: [
    +      [50, 10, 4, 4,   "Breathe, Hiro. Just see what's there... everything--- will- be okay."]
    +    ],
    +    scenes: [
    +      [30, 18, 5, 12, :anka_inside_mainframe]
    +    ],
    +    render_override: :blinking_light_inside_observatory_render
    +  }
    +end
     
    -      outputs.labels << [x + 100, y + 24, "angle: %.2f" % h[:impact][:angle], -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 100, y + 9, "m(l): %.2f" % h[:terrain][:slope], -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 100, y - 5, "m(c): %.2f" % h[:body][:slope], -2, 0, 255, 255, 255]
    +def anka_inside_mainframe args
    +  {
    +    player: [32, 4],
    +    background: 'sprites/mainframe.png',
    +    fade: 60,
    +    storylines: [
    +      [22, 45, 17, 4, (anka_last_reply args)],
    +      [45, 45,  4, 4, (anka_current_reply args)],
    +    ],
    +    scenes: [
    +      [*hotspot_top_right, :reply_to_anka]
    +    ]
    +  }
    +end
     
    -      outputs.labels << [x + 200, y + 24, "ray: #{h[:impact][:ray]}", -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 200, y +  9, "nxt: #{h[:impact][:ray_next]}", -2, 0, 255, 255, 255]
    -      outputs.labels << [x + 200, y -  5, "typ: #{h[:impact][:type]}", -2, 0, 255, 255, 255]
    -    end
    +def reply_to_anka args
    +  decision_graph anka_current_reply(args),
    +                 "Matthew's-- wife is doing-- well. What's-- even-- better-- is that he's-- a dad, and he didn't-- even-- know it. Should- I- leave- out the part about-- the crew- being-- in hibernation-- for 20-- years? They- should- enter-- statis-- on a high- note... Right?",
    +                 [:replied_with_whole_truth, "Whole-- Truth--", anka_reply_whole_truth],
    +                 [:replied_with_half_truth, "Half-- Truth--", anka_reply_half_truth]
    +end
    +
    +def anka_last_reply args
    +  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    +    return "Buffer--: #{serenity_alive_firm_reply.quote}"
    +  else
    +    return "Buffer--: #{serenity_alive_sugarcoated_reply.quote}"
    +  end
    +end
    +
    +def anka_reply_whole_truth
    +  "Matthew's wife is doing-- very-- well. In fact, she was pregnant. Matthew-- is a dad. He has a son. But, I need- all-- of-- you-- to brace-- yourselves. You've-- been in statis-- for 20 years. A lot has changed. Most of Earth's-- population--- didn't-- survive. Tell- Matthew-- that I'm-- sorry he didn't-- get to see- his- son grow- up."
    +end
    +
    +def anka_reply_half_truth
    +  "Matthew's--- wife- is doing-- very-- well. In fact, she was pregnant. Matthew is a dad! It's a boy! Tell- Matthew-- congrats-- for me. Hope-- to see- all of you- soon."
    +end
    +
    +def replied_with_whole_truth args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [[60, 0, 4, 32, :replied_to_anka_back_home]],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: #{anka_reply_whole_truth.quote}"],
    +      [30, 10, 5, 4, "I- hope- I- did the right- thing- by laying-- it all- out- there."],
    +    ]
    +  }
    +end
     
    -    if circle.floor
    -      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y + 100, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0]
    -      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y + 101, "point: #{circle.floor_point.slice(:x, :y).values}", -2, 0, 255, 255, 255]
    -      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y +  85, "circle: #{circle.hash.slice(:x, :y).values}", -2, 0]
    -      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y +  86, "circle: #{circle.hash.slice(:x, :y).values}", -2, 0, 255, 255, 255]
    -      outputs.labels << [circle.x + camera.x + 30, circle.y + camera.y +  70, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0]
    -      outputs.labels << [circle.x + camera.x + 31, circle.y + camera.y +  71, "rel: #{circle.floor_relative_x} #{circle.floor_relative_y}", -2, 0, 255, 255, 255]
    -    end
    -  end
    +def replied_with_half_truth args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [[60, 0, 4, 32, :replied_to_anka_back_home]],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: #{anka_reply_half_truth.quote}"],
    +      [30, 10, 5, 4, "I- hope- I- did the right- thing- by not giving-- them- the whole- truth."],
    +    ]
    +  }
    +end
     
    -  def render_stage_editor
    -    return unless state.god_mode
    -    return unless state.point_one
    -    args.lines << [state.point_one, inputs.mouse.point, 0, 255, 255]
    +def anka_current_reply args
    +  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    +    return "Hello. This is, Aanka. Sasha-- is still- trying-- to gather-- her wits about-- her, given- the gravity--- of your- last- reply. Thank- you- for being-- honest, and thank- you- for the help- with the ship- diagnostics. I was able-- to retrieve-- all of the navigation--- information---- after-- the battery--- swap. We- are ready-- to head back to Earth. Before-- we go- back- into-- statis, Matthew--- wanted-- to know- how his- wife- is doing. Please- reply-- as soon- as you can. He's-- not going-- to get- into-- the statis-- chamber-- until-- he knows- his wife is okay."
    +  else
    +    return "Hello. This is, Aanka. Thank- you for the help- with the ship's-- diagnostics. I was able-- to retrieve-- all of the navigation--- information--- after-- the battery-- swap. I- know-- that- you didn't-- tell- the whole truth- about-- how far we are from- Earth. Don't-- worry. I understand-- why you did it. We- are ready-- to head back to Earth. Before-- we go- back- into-- statis, Matthew--- wanted-- to know- how his- wife- is doing. Please- reply-- as soon- as you can. He's-- not going-- to get- into-- the statis-- chamber-- until-- he knows- his wife is okay."
       end
    +end
     
    -  def trajectory body
    -    [body.x + body.dx,
    -     body.y + body.dy,
    -     body.x + body.dx * 1000,
    -     body.y + body.dy * 1000,
    -     0, 255, 255]
    +def replied_to_anka_back_home args
    +  if args.state.scene_history.include? :replied_with_whole_truth
    +    return {
    +      fade: 60,
    +      background: 'sprites/inside-home.png',
    +      player: [34, 4],
    +      storylines: [
    +        [34, 4, 4, 4, "I- hope-- this pit in my stomach-- is gone-- by tomorrow---."],
    +      ],
    +      scenes: [
    +        [30, 38, 12, 13, :final_message_sad],
    +      ]
    +    }
    +  else
    +    return {
    +      fade: 60,
    +      background: 'sprites/inside-home.png',
    +      player: [34, 4],
    +      storylines: [
    +        [34, 4, 4, 4, "I- get the feeling-- I'm going-- to sleep real well tonight--."],
    +      ],
    +      scenes: [
    +        [30, 38, 12, 13, :final_message_happy],
    +      ]
    +    }
       end
    +end
     
    -  def lengthen_line line, num
    -    line = normalize_line(line)
    -    slope = geometry.line_slope(line, replace_infinity: 10).abs
    -    if slope < 2
    -      [line.x - num, line.y, line.x2 + num, line.y2].line.to_hash
    -    else
    -      [line.x, line.y, line.x2, line.y2].line.to_hash
    -    end
    -  end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_blinking_light.rb
    +def the_blinking_light args
    +  {
    +    fade: 60,
    +    background: 'sprites/side-of-home.png',
    +    player: [16, 13],
    +    scenes: [
    +      [52, 24, 11, 5, :blinking_light_mountain_pass],
    +    ],
    +    render_override: :blinking_light_side_of_home_render
    +  }
    +end
     
    -  def normalize_line line
    -    if line.x > line.x2
    -      x  = line.x2
    -      y  = line.y2
    -      x2 = line.x
    -      y2 = line.y
    -    else
    -      x  = line.x
    -      y  = line.y
    -      x2 = line.x2
    -      y2 = line.y2
    -    end
    -    [x, y, x2, y2]
    -  end
    +def blinking_light_mountain_pass args
    +  {
    +    background: 'sprites/mountain-pass-zoomed-out.png',
    +    player: [4, 4],
    +    scenes: [
    +      [18, 47, 5, 5, :blinking_light_path_to_observatory]
    +    ],
    +    render_override: :blinking_light_mountain_pass_render
    +  }
    +end
     
    -  def rect_for_line line
    -    if line.x > line.x2
    -      x  = line.x2
    -      y  = line.y2
    -      x2 = line.x
    -      y2 = line.y
    -    else
    -      x  = line.x
    -      y  = line.y
    -      x2 = line.x2
    -      y2 = line.y2
    -    end
    +def blinking_light_path_to_observatory args
    +  {
    +    background: 'sprites/path-to-observatory.png',
    +    player: [60, 4],
    +    scenes: [
    +      [0, 26, 5, 5, :blinking_light_observatory]
    +    ],
    +    render_override: :blinking_light_path_to_observatory_render
    +  }
    +end
     
    -    w = x2 - x
    -    h = y2 - y
    +def blinking_light_observatory args
    +  {
    +    background: 'sprites/observatory.png',
    +    player: [60, 2],
    +    scenes: [
    +      [28, 39, 4, 10, :blinking_light_inside_observatory]
    +    ],
    +    render_override: :blinking_light_observatory_render
    +  }
    +end
     
    -    if h < 0
    -      y += h
    -      h = h.abs
    -    end
    +def blinking_light_inside_observatory args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    player: [60, 2],
    +    storylines: [
    +      [50, 2, 4, 8,   "That's weird. I thought- this- mainframe-- was broken--."]
    +    ],
    +    scenes: [
    +      [30, 18, 5, 12, :blinking_light_inside_mainframe]
    +    ],
    +    render_override: :blinking_light_inside_observatory_render
    +  }
    +end
     
    -    if w < circle.radius
    -      x -= circle.radius
    -      w = circle.radius * 2
    -    end
    +def blinking_light_inside_mainframe args
    +  {
    +    background: 'sprites/mainframe.png',
    +    fade: 60,
    +    player: [30, 4],
    +    scenes: [
    +      [62, 32, 4, 32, :reply_to_introduction]
    +    ],
    +    storylines: [
    +      [43, 43,  8, 8, "\"Mission-- control--, your- main- comm-- channels-- seem-- to be down. My apologies-- for- using-- this low- level-- exploit--. What's-- going-- on down there? We are ready-- for reentry--.\" Message--- Timestamp---: 4- hours-- 23--- minutes-- ago--."],
    +      [30, 30,  4, 4, "There's-- a low- level-- message-- here... NANI.T.F?"],
    +      [14, 10, 24, 4, "Oh interesting---. This transistor--- needed-- to be activated--- for the- mainframe-- to work."],
    +      [14, 20, 24, 4, "What the heck activated--- this thing- though?"]
    +    ]
    +  }
    +end
     
    -    if h < circle.radius
    -      y -= circle.radius
    -      h = circle.radius * 2
    -    end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_day_one.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_day_one.rb
    +def day_one_beginning args
    +  {
    +    background: 'sprites/side-of-home.png',
    +    player: [16, 13],
    +    scenes: [
    +      [0, 0, 64, 2, :day_one_infront_of_home],
    +    ],
    +    storylines: [
    +      [35, 10, 6, 6,  "Man. Hard to believe- that today- is the 20th--- anniversary-- of The Impact."]
    +    ]
    +  }
    +end
     
    -    { x: x, y: y, w: w, h: h }
    -  end
    +def day_one_infront_of_home args
    +  {
    +    background: 'sprites/front-of-home.png',
    +    player: [56, 23],
    +    scenes: [
    +      [43, 34, 10, 16, :day_one_home],
    +      [62, 0,  3, 40, :day_one_beginning],
    +      [0, 4, 3, 20, :day_one_ceremony]
    +    ],
    +    storylines: [
    +      [40, 20, 4, 4, "It looks like everyone- is already- at the rememberance-- ceremony."],
    +    ]
    +  }
    +end
     
    -  def snap_to_grid x, y, snaps
    -    snap_number = 10
    -    x = x.to_i
    -    y = y.to_i
    +def day_one_home args
    +  {
    +    background: 'sprites/inside-home.png',
    +    player: [34, 3],
    +    scenes: [
    +      [28, 0, 12, 2, :day_one_infront_of_home]
    +    ],
    +    storylines: [
    +      [
    +        38, 4, 4, 4, "My mansion- in all its glory! Okay yea, it's just a shipping- container-. Apparently-, it's nothing- like the luxuries- of the 2040's. But it's- all we have- in- this day and age. And it'll suffice."
    +      ],
    +      [
    +        28, 7, 4, 7,
    +        "Ahhh. My reading- couch. It's so comfortable--."
    +      ],
    +      [
    +        38, 21, 4, 4,
    +        "I'm- lucky- to have a computer--. I'm- one of the few people- with- the skills to put this- thing to good use."
    +      ],
    +      [
    +        45, 37, 4, 8,
    +        "This corner- of my home- is always- warmer-. It's cause of the ref~lected-- light- from the solar-- panels--, just on the other- side- of this wall. It's hard- to believe- there was o~nce-- an unlimited- amount- of electricity--."
    +      ],
    +      [
    +        32, 40, 8, 10,
    +        "This isn't- a good time- to sleep. I- should probably- head to the ceremony-."
    +      ],
    +      [
    +        25, 21, 5, 12,
    +        "Fifteen-- years- of computer-- science-- notes, neatly-- organized. Compiler--- Theory--, Linear--- Algebra---, Game-- Development---... Every-- subject-- imaginable--."
    +      ]
    +    ]
    +  }
    +end
     
    -    x_floor = x.idiv(snap_number) * snap_number
    -    x_mod   = x % snap_number
    -    x_ceil  = (x.idiv(snap_number) + 1) * snap_number
    +def day_one_ceremony args
    +  {
    +    background: 'sprites/tribute.png',
    +    player: [57, 21],
    +    scenes: [
    +      [62, 0, 2, 40, :day_one_infront_of_home],
    +      [0, 24, 2, 40, :day_one_infront_of_library]
    +    ],
    +    storylines: [
    +      [53, 12, 3,  8,  "It's- been twenty- years since The Impact. Twenty- years, since Halley's-- Comet-- set Earth's- blue- sky on fire."],
    +      [45, 12, 3,  8,  "The space mission- sent to prevent- Earth's- total- destruction--, was a success. Only- 99.9%------ of the world's- population-- died-- that day. Hey, it's- better-- than 100%---- of humanity-- dying."],
    +      [20, 12, 23, 4, "The monument--- reads:---- Here- stands- the tribute-- to Space- Mission-- Serenity--- and- its- crew. You- have- given-- humanity--- a second-- chance."],
    +      [15, 12, 3,  8, "Rest- in- peace--- Matthew----, Sasha----, Aanka----"],
    +    ]
    +  }
    +end
     
    -    y_floor = y.idiv(snap_number) * snap_number
    -    y_mod   = y % snap_number
    -    y_ceil  = (y.idiv(snap_number) + 1) * snap_number
    +def day_one_infront_of_library args
    +  {
    +    background: 'sprites/outside-library.png',
    +    player: [57, 21],
    +    scenes: [
    +      [62, 0, 2, 40, :day_one_ceremony],
    +      [49, 39, 6, 9, :day_one_library]
    +    ],
    +    storylines: [
    +      [50, 20, 4, 8,  "Shipping- containers-- as far- as the eye- can see. It's- rather- beautiful-- if you ask me. Even- though-- this- view- represents-- all- that's-- left- of humanity-."]
    +    ]
    +  }
    +end
     
    -    if snaps[x_floor]
    -      x_result = x_floor
    -    elsif snaps[x_ceil]
    -      x_result = x_ceil
    -    elsif x_mod < snap_number.idiv(2)
    -      x_result = x_floor
    -    else
    -      x_result = x_ceil
    -    end
    +def day_one_library args
    +  {
    +    background: 'sprites/library.png',
    +    player: [27, 4],
    +    scenes: [
    +      [0, 0, 64, 2, :end_day_one_infront_of_library]
    +    ],
    +    storylines: [
    +      [28, 22, 8, 4,  "I grew- up- in this library. I've- read every- book- here. My favorites-- were- of course-- anything- computer-- related."],
    +      [6, 32, 10, 6, "My favorite-- area--- of the library. The Science-- Section."]
    +    ]
    +  }
    +end
     
    -    snaps[x_result] ||= {}
    +def end_day_one_infront_of_library args
    +  {
    +    background: 'sprites/outside-library.png',
    +    player: [51, 33],
    +    scenes: [
    +      [49, 39, 6, 9, :day_one_library],
    +      [62, 0, 2, 40, :end_day_one_monument],
    +    ],
    +    storylines: [
    +      [50, 27, 4, 4, "It's getting late. Better get some sleep."]
    +    ]
    +  }
    +end
     
    -    if snaps[x_result][y_floor]
    -      y_result = y_floor
    -    elsif snaps[x_result][y_ceil]
    -      y_result = y_ceil
    -    elsif y_mod < snap_number.idiv(2)
    -      y_result = y_floor
    -    else
    -      y_result = y_ceil
    -    end
    +def end_day_one_monument args
    +  {
    +    background: 'sprites/tribute.png',
    +    player: [2, 36],
    +    scenes: [
    +      [62, 0, 2, 40, :end_day_one_infront_of_home],
    +    ],
    +    storylines: [
    +      [50, 27, 4, 4, "It's getting late. Better get some sleep."],
    +    ]
    +  }
    +end
     
    -    snaps[x_result][y_result] = true
    -    return [x_result, y_result]
    +def end_day_one_infront_of_home args
    +  {
    +    background: 'sprites/front-of-home.png',
    +    player: [1, 17],
    +    scenes: [
    +      [43, 34, 10, 16, :end_day_one_home],
    +    ],
    +    storylines: [
    +      [20, 10, 4, 4, "It's getting late. Better get some sleep."],
    +    ]
    +  }
    +end
     
    -  end
    +def end_day_one_home args
    +  {
    +    background: 'sprites/inside-home.png',
    +    player: [34, 3],
    +    scenes: [
    +      [32, 40, 8, 10, :end_day_one_dream],
    +    ],
    +    storylines: [
    +      [38, 4, 4, 4, "It's getting late. Better get some sleep."],
    +    ]
    +  }
    +end
     
    -  def snap_line line
    -    x, y, x2, y2 = line
    -  end
    +def end_day_one_dream args
    +  {
    +    background: 'sprites/dream.png',
    +    fade: 60,
    +    player: [4, 4],
    +    scenes: [
    +      [62, 0, 2, 64, :explaining_the_special_power]
    +    ],
    +    storylines: [
    +      [10, 10, 4, 4, "Why- does this- moment-- always- haunt- my dreams?"],
    +      [20, 10, 4, 4, "This kid- reads these computer--- science--- books- nonstop-. What's- wrong with him?"],
    +      [30, 10, 4, 4, "There- is nothing-- wrong- with him. This behavior-- should be encouraged---! In fact-, I think- he's- special---. Have- you seen- him use- a computer---? It's-- almost-- as if he can- speak-- to it."]
    +    ]
    +  }
    +end
     
    -  def string_to_line s
    -    x, y, x2, y2 = s.split(',').map(&:to_f)
    +def explaining_the_special_power args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    player: [32, 30],
    +    scenes: [
    +      [
    +        38, 21, 4, 4, :explaining_the_special_power_inside_computer
    +      ],
    +    ]
    +  }
    +end
     
    -    if x > x2
    -      x2, x = x, x2
    -      y2, y = y, y2
    -    end
    +def explaining_the_special_power_inside_computer args
    +  {
    +    background: 'sprites/pc.png',
    +    fade: 60,
    +    player: [34, 4],
    +    scenes: [
    +      [0, 62, 64, 3, :the_blinking_light]
    +    ],
    +    storylines: [
    +      [14, 20, 24, 4, "So... I have a special-- power--. I don't-- need a mouse-, keyboard--, or even-- a monitor--- to control-- a computer--."],
    +      [14, 25, 24, 4, "I only-- pretend-- to use peripherals---, so as not- to freak- anyone--- out."],
    +      [14, 30, 24, 4, "Inside-- this silicon--- Universe---, is the only-- place I- feel- at peace."],
    +      [14, 35, 24, 4, "It's-- the only-- place where I don't-- feel alone."]
    +    ]
    +  }
    +end
     
    -    x, y = snap_to_grid x, y, state.snaps
    -    x2, y2 = snap_to_grid x2, y2, state.snaps
    -    [x, y, x2, y2].line.to_hash
    -  end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_final_decision.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_final_decision.rb
    +def final_decision_side_of_home args
    +  {
    +    fade: 120,
    +    background: 'sprites/side-of-home.png',
    +    player: [16, 13],
    +    scenes: [
    +      [52, 24, 11, 5, :final_decision_mountain_pass],
    +    ],
    +    render_override: :blinking_light_side_of_home_render,
    +    storylines: [
    +      [28, 13, 8, 4,  "Man. Hard to believe- that today- is the 21st--- anniversary-- of The Impact. Serenity--- will- be- home- soon."]
    +    ]
    +  }
    +end
     
    -  def load_lines file
    -    data = gtk.read_file(file) || ""
    -    data.each_line
    -        .reject { |l| l.strip.length == 0 }
    -        .map { |l| string_to_line l }
    -        .map { |h| h.merge(rect: rect_for_line(h))  }
    -  end
    +def final_decision_mountain_pass args
    +  {
    +    background: 'sprites/mountain-pass-zoomed-out.png',
    +    player: [4, 4],
    +    scenes: [
    +      [18, 47, 5, 5, :final_decision_path_to_observatory]
    +    ],
    +    render_override: :blinking_light_mountain_pass_render
    +  }
    +end
     
    -  def load_terrain
    -    load_lines 'level.txt'
    -  end
    +def final_decision_path_to_observatory args
    +  {
    +    background: 'sprites/path-to-observatory.png',
    +    player: [60, 4],
    +    scenes: [
    +      [0, 26, 5, 5, :final_decision_observatory]
    +    ],
    +    render_override: :blinking_light_path_to_observatory_render
    +  }
    +end
     
    -  def load_lava
    -    load_lines 'level_lava.txt'
    -  end
    +def final_decision_observatory args
    +  {
    +    background: 'sprites/observatory.png',
    +    player: [60, 2],
    +    scenes: [
    +      [28, 39, 4, 10, :final_decision_inside_observatory]
    +    ],
    +    render_override: :blinking_light_observatory_render
    +  }
    +end
     
    -  def load_level force: false
    -    if force
    -      state.snaps = {}
    -      state.terrain = load_terrain
    -      state.lava = load_lava
    -    else
    -      state.terrain ||= load_terrain
    -      state.lava ||= load_lava
    -    end
    -  end
    +def final_decision_inside_observatory args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    player: [60, 2],
    +    storylines: [],
    +    scenes: [
    +      [30, 18, 5, 12, :final_decision_inside_mainframe]
    +    ],
    +    render_override: :blinking_light_inside_observatory_render
    +  }
    +end
     
    -  def save_lines lines, file
    -    s = lines.map do |l|
    -      "#{l.x1},#{l.y1},#{l.x2},#{l.y2}"
    -    end.join("\n")
    -    gtk.write_file(file, s)
    -  end
    +def final_decision_inside_mainframe args
    +  {
    +    player: [32, 4],
    +    background: 'sprites/mainframe.png',
    +    storylines: [],
    +    scenes: [
    +      [*hotspot_top, :final_decision_ship_status],
    +    ]
    +  }
    +end
     
    -  def save_level
    -    save_lines(state.terrain, 'level.txt')
    -    save_lines(state.lava, 'level_lava.txt')
    -    load_level force: true
    -  end
    +def final_decision_ship_status args
    +  {
    +    background: 'sprites/serenity.png',
    +    fade: 60,
    +    player: [30, 10],
    +    scenes: [
    +      [*hotspot_top_right, :final_decision]
    +    ],
    +    storylines: [
    +      [30,  8, 4, 4, "????"],
    +      *final_decision_ship_status_shared(args)
    +    ]
    +  }
    +end
     
    -  def line_near_rect? rect, terrain
    -    geometry.intersect_rect?(rect, terrain[:rect])
    -  end
    +def final_decision args
    +  decision_graph  "Stasis-- Chambers--: UNDERPOWERED, Life- forms-- will be terminated---- unless-- equilibrium----- is reached.",
    +                  "I CAN'T DO THIS... But... If-- I-- don't--- bring-- the- chambers--- to- equilibrium-----, they all die...",
    +                  [:final_decision_game_over_noone, "Kill--- Everyone---", "DO--- NOTHING?"],
    +                  [:final_decision_game_over_matthew, "Kill--- Sasha---", "KILL--- SASHA?"],
    +                  [:final_decision_game_over_anka, "Kill--- Aanka---", "KILL--- AANKA?"],
    +                  [:final_decision_game_over_sasha, "Kill--- Matthew---", "KILL--- MATTHEW?"]
    +end
     
    -  def point_within_line? point, line
    -    return false if !point
    -    return false if !line
    -    return true
    -  end
    +def final_decision_game_over_noone args
    +  {
    +    background: 'sprites/tribute-game-over.png',
    +    player: [53, 14],
    +    fade: 600
    +  }
    +end
     
    -  def calc_impacts x, dx, y, dy, radius
    -    results = { }
    -    results[:x] = x
    -    results[:y] = y
    -    results[:dx] = x
    -    results[:dy] = y
    -    results[:point] = { x: x, y: y }
    -    results[:rect] = { x: x - radius, y: y - radius, w: radius * 2, h: radius * 2 }
    -    results[:trajectory] = trajectory(results)
    -    results[:impacts] = terrain.find_all { |t| line_near_rect? results[:rect], t }.map do |t|
    -      {
    -        terrain: t,
    -        point: geometry.line_intersect(results[:trajectory], t),
    -        type: :terrain
    -      }
    -    end.reject { |t| !point_within_line? t[:point], t[:terrain] }
    +def final_decision_game_over_matthew args
    +  {
    +    background: 'sprites/tribute-game-over.png',
    +    player: [53, 14],
    +    fade: 600
    +  }
    +end
     
    -    results[:impacts] += lava.find_all { |t| line_near_rect? results[:rect], t }.map do |t|
    -      {
    -        terrain: t,
    -        point: geometry.line_intersect(results[:trajectory], t),
    -        type: :lava
    -      }
    -    end.reject { |t| !point_within_line? t[:point], t[:terrain] }
    +def final_decision_game_over_anka args
    +  {
    +    background: 'sprites/tribute-game-over.png',
    +    player: [53, 14],
    +    fade: 600
    +  }
    +end
     
    -    results
    -  end
    +def final_decision_game_over_sasha args
    +  {
    +    background: 'sprites/tribute-game-over.png',
    +    player: [53, 14],
    +    fade: 600
    +  }
    +end
     
    -  def calc_potential_impacts
    -    impact_results = calc_impacts circle.x, circle.dx, circle.y, circle.dy, circle.radius
    -    circle.rect = impact_results[:rect]
    -    circle.trajectory = impact_results[:trajectory]
    -    circle.impacts = impact_results[:impacts]
    -  end
    +def final_decision_ship_status_shared args
    +  [
    +    *ship_control_hotspot(24, 22,
    +                           "Stasis-- Chambers--: UNDERPOWERED, Life- forms-- will be terminated---- unless-- equilibrium----- is reached. WHAT?! NO!",
    +                           "Matthew's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION. WHAT?! NO!",
    +                           "Aanka's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION.  WHAT?! NO!",
    +                           "Sasha's--- Chamber--: UNDER-- THREAT-- OF-- TERMINATION. WHAT?! NO!"),
    +  ]
    +end
     
    -  def calc_terrains_to_monitor
    -    circle.impact = nil
    -    circle.impacts.each do |i|
    -      circle.terrains_to_monitor[i[:terrain]] ||= {
    -        ray_start: geometry.ray_test(circle, i[:terrain]),
    -      }
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_final_message.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_final_message.rb
    +def final_message_sad args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    player: [34, 35],
    +    storylines: [
    +      [34, 34, 4, 4, "Another-- sleepless-- night..."],
    +    ],
    +    scenes: [
    +      [32, -1, 8, 3, :final_message_observatory]
    +    ]
    +  }
    +end
     
    -      circle.terrains_to_monitor[i[:terrain]][:ray_current] = geometry.ray_test(circle, i[:terrain])
    -      if circle.terrains_to_monitor[i[:terrain]][:ray_start] != circle.terrains_to_monitor[i[:terrain]][:ray_current]
    -        if circle.x.between?(i[:terrain].x, i[:terrain].x2) || circle.y.between?(i[:terrain].y, i[:terrain].y2)
    -          circle.impact = i
    -          circle.ray_current = circle.terrains_to_monitor[i[:terrain]][:ray_current]
    -        end
    -      end
    -    end
    -  end
    +def final_message_happy args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    player: [34, 35],
    +    storylines: [
    +      [34, 34, 4, 4, "Oh man, I slept like rock!"],
    +    ],
    +    scenes: [
    +      [32, -1, 8, 3, :final_message_observatory]
    +    ]
    +  }
    +end
     
    -  def impact_result body, impact
    -    infinity_alias = 1000
    -    r = {
    -      body: {},
    -      terrain: {},
    -      impact: {}
    -    }
    +def final_message_side_of_home args
    +  {
    +    fade: 60,
    +    background: 'sprites/side-of-home.png',
    +    player: [16, 13],
    +    scenes: [
    +      [52, 24, 11, 5, :final_message_mountain_pass],
    +    ],
    +    render_override: :blinking_light_side_of_home_render
    +  }
    +end
     
    -    r[:body][:line] = body.trajectory.dup
    -    r[:body][:slope] = geometry.line_slope(body.trajectory, replace_infinity: infinity_alias)
    -    r[:body][:slope_sign] = r[:body][:slope].sign
    -    r[:body][:x] = body.x
    -    r[:body][:y] = body.y
    -    r[:body][:dy] = body.dy
    -    r[:body][:dx] = body.dx
    +def final_message_mountain_pass args
    +  {
    +    background: 'sprites/mountain-pass-zoomed-out.png',
    +    player: [4, 4],
    +    scenes: [
    +      [18, 47, 5, 5, :final_message_path_to_observatory],
    +    ],
    +    storylines: [
    +      [18, 13, 5, 5, "Hnnnnnnnggg. My legs-- are still sore- from yesterday."]
    +    ],
    +    render_override: :blinking_light_mountain_pass_render
    +  }
    +end
     
    -    r[:terrain][:line] = impact[:terrain].dup
    -    r[:terrain][:slope] = geometry.line_slope(impact[:terrain], replace_infinity: infinity_alias)
    -    r[:terrain][:slope_sign] = r[:terrain][:slope].sign
    +def final_message_path_to_observatory args
    +  {
    +    background: 'sprites/path-to-observatory.png',
    +    player: [60, 4],
    +    scenes: [
    +      [0, 26, 5, 5, :final_message_observatory]
    +    ],
    +    storylines: [
    +      [22, 20, 10, 10, "This spot--, on the mountain, right here, it's-- perfect. This- is where- I'll-- yeet-- the person-- who is playing-- this- prank- on me."]
    +    ],
    +    render_override: :blinking_light_path_to_observatory_render
    +  }
    +end
     
    -    r[:impact][:angle] = geometry.angle_between_lines(body.trajectory, impact[:terrain], replace_infinity: infinity_alias)
    -    r[:impact][:point] = { x: impact[:point].x, y: impact[:point].y }
    -    r[:impact][:same_slope_sign] = r[:body][:slope_sign] == r[:terrain][:slope_sign]
    -    r[:impact][:ray] = body.ray_current
    -    r[:body][:new_on_floor] = body.on_floor
    -    r[:body][:new_floor] = r[:terrain][:line]
    +def final_message_observatory args
    +  if args.state.scene_history.include? :replied_with_whole_truth
    +    return {
    +      background: 'sprites/inside-observatory.png',
    +      fade: 60,
    +      player: [51, 12],
    +      storylines: [
    +        [50, 10, 4, 4, "Here-- we- go..."]
    +      ],
    +      scenes: [
    +        [30, 18, 5, 12, :final_message_inside_mainframe]
    +      ],
    +      render_override: :blinking_light_inside_observatory_render
    +    }
    +  else
    +    return {
    +      background: 'sprites/inside-observatory.png',
    +      fade: 60,
    +      player: [51, 12],
    +      storylines: [
    +        [50, 10, 4, 4, "I feel like I'm-- walking-- on sunshine!"]
    +      ],
    +      scenes: [
    +        [30, 18, 5, 12, :final_message_inside_mainframe]
    +      ],
    +      render_override: :blinking_light_inside_observatory_render
    +    }
    +  end
    +end
     
    -    if r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs < 3
    -      play_sound
    -      r[:body][:new_dy] = r[:body][:dy] * circle.elasticity * -1
    -      r[:body][:new_dx] = r[:body][:dx] * circle.elasticity
    -      r[:impact][:type] = :horizontal
    -      r[:body][:new_reason] = "-"
    -    elsif r[:impact][:angle].abs < 90 && r[:terrain][:slope].abs > 3
    -      play_sound
    -      r[:body][:new_dy] = r[:body][:dy] * 1.1
    -      r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity
    -      r[:impact][:type] = :vertical
    -      r[:body][:new_reason] = "|"
    -    else
    -      play_sound
    -      r[:body][:new_dx] = r[:body][:dx] * -circle.elasticity
    -      r[:body][:new_dy] = r[:body][:dy] * -circle.elasticity
    -      r[:impact][:type] = :slanted
    -      r[:body][:new_reason] = "/"
    -    end
    +def final_message_inside_mainframe args
    +  {
    +    player: [32, 4],
    +    background: 'sprites/mainframe.png',
    +    fade: 60,
    +    scenes: [[45, 45,  4, 4, :final_message_check_ship_status]]
    +  }
    +end
     
    -    r[:impact][:energy] = r[:body][:new_dx].abs + r[:body][:new_dy].abs
    +def final_message_check_ship_status args
    +  {
    +    background: 'sprites/mainframe.png',
    +    storylines: [
    +      [45, 45, 4, 4, (final_message_current args)],
    +    ],
    +    scenes: [
    +      [*hotspot_top, :final_message_ship_status],
    +    ]
    +  }
    +end
     
    -    if r[:impact][:energy] <= 0.3 && r[:terrain][:slope].abs < 4
    -      r[:body][:new_dx] = 0
    -      r[:body][:new_dy] = 0
    -      r[:impact][:energy] = 0
    -      r[:body][:new_on_floor] = true
    -      r[:body][:new_floor] = r[:terrain][:line]
    -      r[:body][:new_reason] = "0"
    -    end
    +def final_message_ship_status args
    +  {
    +    background: 'sprites/serenity.png',
    +    fade: 60,
    +    player: [30, 10],
    +    scenes: [
    +      [30, 50, 4, 4, :final_message_ship_status_reviewed]
    +    ],
    +    storylines: [
    +      [30,  8, 4, 4, "Let me make- sure- everything--- looks good. It'll-- give me peace- of mind."],
    +      *final_message_ship_status_shared(args)
    +    ]
    +  }
    +end
     
    -    r[:impact][:ray_next] = geometry.ray_test({ x: r[:body][:x] - (r[:body][:dx] * 1.1) + r[:body][:new_dx],
    -                                                y: r[:body][:y] - (r[:body][:dy] * 1.1) + r[:body][:new_dy] + state.gravity },
    -                                              r[:terrain][:line])
    +def final_message_ship_status_reviewed args
    +  {
    +    background: 'sprites/serenity.png',
    +    fade: 60,
    +    scenes: [
    +      [*hotspot_bottom, :final_message_summary]
    +    ],
    +    storylines: [
    +      [0, 62, 62, 3, "Whew. Everyone-- is in their- chambers. The engines-- are roaring-- and Serenity-- is coming-- home."],
    +    ]
    +  }
    +end
     
    -    if r[:impact][:ray_next] == r[:impact][:ray]
    -      r[:body][:new_dx] *= -1
    -      r[:body][:new_dy] *= -1
    -      r[:body][:new_reason] = "clip"
    -    end
    +def final_message_ship_status_shared args
    +  [
    +    *ship_control_hotspot( 0, 50,
    +                           "Stasis-- Chambers--: Online, All chambers-- are powered. Battery--- Allocation---: 3--- of-- 3--.",
    +                           "Matthew's--- Chamber--: OCCUPIED----",
    +                           "Aanka's--- Chamber--: OCCUPIED----",
    +                           "Sasha's--- Chamber--: OCCUPIED----"),
    +    *ship_control_hotspot(12, 35,
    +                          "Life- Support--: Not-- Needed---",
    +                          "O2--- Production---: OFF---",
    +                          "CO2--- Scrubbers---: OFF---",
    +                          "H2O--- Production---: OFF---"),
    +    *ship_control_hotspot(24, 20,
    +                          "Navigation: Offline---",
    +                          "Sensor: OFF---",
    +                          "Heads- Up- Display: DAMAGED---",
    +                          "Arithmetic--- Unit: DAMAGED----"),
    +    *ship_control_hotspot(36, 35,
    +                          "COMM: Underpowered----",
    +                          "Text: ON---",
    +                          "Audio: SEGFAULT---",
    +                          "Video: DAMAGED---"),
    +    *ship_control_hotspot(48, 50,
    +                          "Engine: Online, Coordinates--- Set- for Earth. Battery--- Allocation---: 3--- of-- 3---",
    +                          "Engine I: ON---",
    +                          "Engine II: ON---",
    +                          "Engine III: ON---")
    +  ]
    +end
     
    -    r
    +def final_message_last_reply args
    +  if args.state.scene_history.include? :replied_with_whole_truth
    +    return "Buffer--: #{anka_reply_whole_truth.quote}"
    +  else
    +    return "Buffer--: #{anka_reply_half_truth.quote}"
       end
    +end
     
    -  def game_over!
    -    circle.x = circle.check_point_x
    -    circle.y = circle.check_point_y
    -    circle.dx = 0
    -    circle.dy = 0
    -    circle.game_over_at = state.tick_count
    +def final_message_current args
    +  if args.state.scene_history.include? :replied_with_whole_truth
    +    return "Hey... It's-- me Sasha. Aanka-- is trying-- her best to comfort-- Matthew. This- is the first- time- I've-- ever-- seen-- Matthew-- cry. We'll-- probably-- be in stasis-- by the time you get this message--. Thank- you- again-- for all your help. I look forward-- to meeting-- you in person."
    +  else
    +    return "Hey! It's-- me Sasha! LOL! Aanka-- and Matthew-- are dancing-- around-- like- goofballs--! They- are both- so adorable! Only-- this- tiny-- little-- genius-- can make-- a battle-- hardened-- general--- put- on a tiara-- and dance- around-- like a fairy-- princess-- XD------ Anyways, we are heading-- back into-- the chambers--. I hope our welcome-- home- parade-- has fireworks!"
       end
    +end
     
    -  def not_game_over!
    -    impact_history_entry = impact_result circle, circle.impact
    -    circle.impact_history << impact_history_entry
    -    circle.x -= circle.dx * 1.1
    -    circle.y -= circle.dy * 1.1
    -    circle.dx = impact_history_entry[:body][:new_dx]
    -    circle.dy = impact_history_entry[:body][:new_dy]
    -    circle.on_floor = impact_history_entry[:body][:new_on_floor]
    +def final_message_summary args
    +  if args.state.scene_history.include? :replied_with_whole_truth
    +    return {
    +      background: 'sprites/inside-observatory.png',
    +      fade: 60,
    +      player: [31, 11],
    +      scenes: [[60, 0, 4, 32, :final_decision_side_of_home]],
    +      storylines: [
    +        [30, 10, 5, 4, "I can't-- imagine-- what they are feeling-- right now. But at least- they- know everything---, and we can- concentrate-- on rebuilding--- this world-- right- off the bat. I can't-- wait to see the future-- they'll-- help- build."],
    +      ]
    +    }
    +  else
    +    return {
    +      background: 'sprites/inside-observatory.png',
    +      fade: 60,
    +      player: [31, 11],
    +      scenes: [[60, 0, 4, 32, :final_decision_side_of_home]],
    +      storylines: [
    +        [30, 10, 5, 4, "They all sounded-- so happy. I know- they'll-- be in for a tough- dose- of reality--- when they- arrive. But- at least- they'll-- be around-- all- of us. We'll-- help them- cope."],
    +      ]
    +    }
    +  end
    +end
     
    -    if circle.on_floor
    -      circle.check_point_at = state.tick_count
    -      circle.check_point_x = circle.x
    -      circle.check_point_y = circle.y
    -    end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_alive.rb
    +def serenity_alive_side_of_home args
    +  {
    +    fade: 60,
    +    background: 'sprites/side-of-home.png',
    +    player: [16, 13],
    +    scenes: [
    +      [52, 24, 11, 5, :serenity_alive_mountain_pass],
    +    ],
    +    render_override: :blinking_light_side_of_home_render
    +  }
    +end
     
    -    circle.previous_floor = circle.floor || {}
    -    circle.floor = impact_history_entry[:body][:new_floor] || {}
    -    circle.floor_point = impact_history_entry[:impact][:point]
    -    if circle.floor.slice(:x, :y, :x2, :y2) != circle.previous_floor.slice(:x, :y, :x2, :y2)
    -      new_relative_x = if circle.dx > 0
    -                         :right
    -                       elsif circle.dx < 0
    -                         :left
    -                       else
    -                         nil
    -                       end
    +def serenity_alive_mountain_pass args
    +  {
    +    background: 'sprites/mountain-pass-zoomed-out.png',
    +    player: [4, 4],
    +    scenes: [
    +      [18, 47, 5, 5, :serenity_alive_path_to_observatory],
    +    ],
    +    storylines: [
    +      [18, 13, 5, 5, "Hnnnnnnnggg. My legs-- are still sore- from yesterday."]
    +    ],
    +    render_override: :blinking_light_mountain_pass_render
    +  }
    +end
     
    -      new_relative_y = if circle.dy > 0
    -                         :above
    -                       elsif circle.dy < 0
    -                         :below
    -                       else
    -                         nil
    -                       end
    +def serenity_alive_path_to_observatory args
    +  {
    +    background: 'sprites/path-to-observatory.png',
    +    player: [60, 4],
    +    scenes: [
    +      [0, 26, 5, 5, :serenity_alive_observatory]
    +    ],
    +    storylines: [
    +      [22, 20, 10, 10, "This spot--, on the mountain, right here, it's-- perfect. This- is where- I'll-- yeet-- the person-- who is playing-- this- prank- on me."]
    +    ],
    +    render_override: :blinking_light_path_to_observatory_render
    +  }
    +end
     
    -      circle.floor_relative_x = new_relative_x
    -      circle.floor_relative_y = new_relative_y
    -    end
    +def serenity_alive_observatory args
    +  {
    +    background: 'sprites/observatory.png',
    +    player: [60, 2],
    +    scenes: [
    +      [28, 39, 4, 10, :serenity_alive_inside_observatory]
    +    ],
    +    render_override: :blinking_light_observatory_render
    +  }
    +end
     
    -    circle.impact = nil
    -    circle.terrains_to_monitor.clear
    -  end
    +def serenity_alive_inside_observatory args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    player: [60, 2],
    +    storylines: [],
    +    scenes: [
    +      [30, 18, 5, 12, :serenity_alive_inside_mainframe]
    +    ],
    +    render_override: :blinking_light_inside_observatory_render
    +  }
    +end
    +
    +def serenity_alive_inside_mainframe args
    +  {
    +    background: 'sprites/mainframe.png',
    +    fade: 60,
    +    player: [30, 4],
    +    scenes: [
    +      [*hotspot_top, :serenity_alive_ship_status],
    +    ],
    +    storylines: [
    +      [22, 45, 17, 4, (serenity_alive_last_reply args)],
    +      [45, 45,  4, 4, (serenity_alive_current_message args)],
    +    ]
    +  }
    +end
    +
    +def serenity_alive_ship_status args
    +  {
    +    background: 'sprites/serenity.png',
    +    fade: 60,
    +    player: [30, 10],
    +    scenes: [
    +      [30, 50, 4, 4, :serenity_alive_ship_status_reviewed]
    +    ],
    +    storylines: [
    +      [30,  8, 4, 4, "Serenity? THE--- Mission-- Serenity?! How is that possible? They- are supposed-- to be dead."],
    +      [30, 10, 4, 4, "I... can't-- believe-- it. I- can access-- Serenity's-- computer? I- guess my \"superpower----\" isn't limited-- by proximity-- to- a machine--."],
    +      *serenity_alive_shared_ship_status(args)
    +    ]
    +  }
    +end
     
    -  def calc_physics
    -    if args.state.god_mode
    -      calc_potential_impacts
    -      calc_terrains_to_monitor
    -      return
    -    end
    +def serenity_alive_ship_status_reviewed args
    +  {
    +    background: 'sprites/serenity.png',
    +    fade: 60,
    +    scenes: [
    +      [*hotspot_bottom, :serenity_alive_time_to_reply]
    +    ],
    +    storylines: [
    +      [0, 62, 62, 3, "Okay. Reviewing-- everything--, it looks- like- I- can- take- the batteries--- from the Stasis--- Chambers--- and- Engine--- to keep- the crew-- alive-- and-- their-- location--- pinpointed---."],
    +    ]
    +  }
    +end
     
    -    if circle.y < -700
    -      game_over
    -      return
    -    end
    +def serenity_alive_time_to_reply args
    +  decision_graph serenity_alive_current_message(args),
    +                  "Okay... time to deliver the bad news...",
    +                  [:replied_to_serenity_alive_firmly, "Firm-- Reply", serenity_alive_firm_reply],
    +                  [:replied_to_serenity_alive_kindly, "Sugar-- Coated---- Reply", serenity_alive_sugarcoated_reply]
    +end
     
    -    return if state.game_over
    -    return if circle.on_floor
    -    circle.previous_dy = circle.dy
    -    circle.previous_dx = circle.dx
    -    circle.x  += circle.dx
    -    circle.y  += circle.dy
    -    args.state.distance_traveled ||= 0
    -    args.state.distance_traveled += circle.dx.abs + circle.dy.abs
    -    circle.dy += state.gravity
    -    calc_potential_impacts
    -    calc_terrains_to_monitor
    -    return unless circle.impact
    -    if circle.impact && circle.impact[:type] == :lava
    -      game_over!
    -    else
    -      not_game_over!
    -    end
    -  end
    +def serenity_alive_shared_ship_status args
    +  [
    +    *ship_control_hotspot( 0, 50,
    +                           "Stasis-- Chambers--: Online, All chambers-- are powered. Battery--- Allocation---: 3--- of-- 3--, Hmmm. They don't-- need this to be powered-- right- now. Everyone-- is awake.",
    +                           nil,
    +                           nil,
    +                           nil),
    +    *ship_control_hotspot(12, 35,
    +                          "Life- Support--: Offline, Unable--- to- Sustain-- Life. Battery--- Allocation---: 0--- of-- 3---, Okay. That is definitely---- not a good thing.",
    +                          nil,
    +                          nil,
    +                          nil),
    +    *ship_control_hotspot(24, 20,
    +                          "Navigation: Offline, Unable--- to- Calculate--- Location. Battery--- Allocation---: 0--- of-- 3---, Whelp. No wonder-- Sasha-- can't-- get- any-- readings. Their- Navigation--- is completely--- offline.",
    +                          nil,
    +                          nil,
    +                          nil),
    +    *ship_control_hotspot(36, 35,
    +                          "COMM: Underpowered----, Limited--- to- Text-- Based-- COMM. Battery--- Allocation---: 1--- of-- 3---, It's-- lucky- that- their- COMM---- system was able to survive-- twenty-- years--. Just- barely-- it seems.",
    +                          nil,
    +                          nil,
    +                          nil),
    +    *ship_control_hotspot(48, 50,
    +                          "Engine: Online, Full- Control-- Available. Battery--- Allocation---: 3--- of-- 3---, Hmmm. No point of having an engine-- online--, if you don't- know- where you're-- going.",
    +                          nil,
    +                          nil,
    +                          nil)
    +  ]
    +end
     
    -  def input_god_mode
    -    state.debug_mode = !state.debug_mode if inputs.keyboard.key_down.forward_slash
    +def serenity_alive_firm_reply
    +  "Serenity, you are at a distance-- farther-- than- Neptune. All- of the ship's-- systems-- are failing. Please- move the batteries---- from- the Stasis-- Chambers-- over- to- Life-- Support--. I also-- need- you to move-- the batteries---- from- the Engines--- to your Navigation---- System."
    +end
     
    -    # toggle god mode
    -    if inputs.keyboard.key_down.g
    -      state.god_mode = !state.god_mode
    -      state.potential_lift = 0
    -      circle.floor = nil
    -      circle.floor_point = nil
    -      circle.floor_relative_x = nil
    -      circle.floor_relative_y = nil
    -      circle.impact = nil
    -      circle.terrains_to_monitor.clear
    -      return
    -    end
    +def serenity_alive_sugarcoated_reply
    +  "So... you- are- a teeny--- tiny--- bit--- farther-- from Earth- than you think. And you have a teeny--- tiny--- problem-- with your ship. Please-- move the batteries--- from the Stasis--- Chambers--- over to Life--- Support---. I also need you to move the batteries--- from the Engines--- to your- Navigation--- System. Don't-- worry-- Sasha. I'll-- get y'all-- home."
    +end
     
    -    return unless state.god_mode
    +def replied_to_serenity_alive_firmly args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [
    +      [*hotspot_bottom_right, :serenity_alive_path_from_observatory]
    +    ],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: #{serenity_alive_firm_reply.quote}"],
    +      *serenity_alive_reply_completed_shared_hotspots(args),
    +    ]
    +  }
    +end
     
    -    circle.x = circle.x.to_i
    -    circle.y = circle.y.to_i
    +def replied_to_serenity_alive_kindly args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [
    +      [*hotspot_bottom_right, :serenity_alive_path_from_observatory]
    +    ],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: #{serenity_alive_sugarcoated_reply.quote}"],
    +      *serenity_alive_reply_completed_shared_hotspots(args),
    +    ]
    +  }
    +end
     
    -    # move god circle
    -    if inputs.keyboard.left || inputs.keyboard.a
    -      circle.x -= 20
    -    elsif inputs.keyboard.right || inputs.keyboard.d || inputs.keyboard.f
    -      circle.x += 20
    -    end
    +def serenity_alive_path_from_observatory args
    +  {
    +    fade: 60,
    +    background: 'sprites/path-to-observatory.png',
    +    player: [4, 21],
    +    scenes: [
    +      [*hotspot_bottom_right, :serenity_bio_infront_of_home]
    +    ],
    +    storylines: [
    +      [22, 20, 10, 10, "I'm not sure what's-- worse. Waiting-- for Sasha's-- reply. Or jumping-- off- from- right- here."]
    +    ]
    +  }
    +end
     
    -    if inputs.keyboard.up || inputs.keyboard.w
    -      circle.y += 20
    -    elsif inputs.keyboard.down || inputs.keyboard.s
    -      circle.y -= 20
    -    end
    +def serenity_alive_reply_completed_shared_hotspots args
    +  [
    +    [30, 10, 5, 4, "I guess it wasn't-- a joke- after-- all."],
    +    [40, 10, 5, 4, "I barely-- remember--- the- history----- of the crew."],
    +    [50, 10, 5, 4, "It probably--- wouldn't-- hurt- to- refresh-- my memory--."]
    +  ]
    +end
     
    -    # delete terrain
    -    if inputs.keyboard.key_down.x
    -      calc_terrains_to_monitor
    -      state.terrain = state.terrain.reject do |t|
    -        t[:rect].intersect_rect? circle.rect
    -      end
    +def serenity_alive_last_reply args
    +  if args.state.scene_history.include? :replied_to_introduction_seriously
    +    return "Buffer--: \"Hello, Who- is sending-- this message--?\""
    +  else
    +    return "Buffer--: \"New- phone. Who dis?\""
    +  end
    +end
     
    -      state.lava = state.lava.reject do |t|
    -        t[:rect].intersect_rect? circle.rect
    -      end
    +def serenity_alive_current_message args
    +  if args.state.scene_history.include? :replied_to_introduction_seriously
    +    "This- is Sasha. The Serenity--- crew-- is out of hibernation---- and ready-- for Earth reentry--. But, it seems like we are having-- trouble-- with our Navigation---- systems. Please advise.".quote
    +  else
    +    "LOL! Thanks for the laugh. I needed that. This- is Sasha. The Serenity--- crew-- is out of hibernation---- and ready-- for Earth reentry--. But, it seems like we are having-- trouble-- with our Navigation---- systems. Can you help me out- babe?".quote
    +  end
    +end
     
    -      calc_potential_impacts
    -      save_level
    -    end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_bio.rb
    +def serenity_bio_infront_of_home args
    +  {
    +    fade: 60,
    +    background: 'sprites/front-of-home.png',
    +    player: [54, 23],
    +    scenes: [
    +      [44, 34, 8, 14, :serenity_bio_inside_home],
    +      [0, 3, 3, 22, :serenity_bio_library]
    +    ]
    +  }
    +end
     
    -    # change terrain type
    -    if inputs.keyboard.key_down.l
    -      if state.line_mode == :terrain
    -        state.line_mode = :lava
    -      else
    -        state.line_mode = :terrain
    -      end
    -    end
    +def serenity_bio_inside_home args
    +  {
    +    background: 'sprites/inside-home.png',
    +    player: [34, 4],
    +    storylines: [
    +      [34, 4, 4, 4, "I'm--- completely--- exhausted."],
    +    ],
    +    scenes: [
    +      [30, 38, 12, 13, :serenity_bio_restless_sleep],
    +      [32, 0, 8, 3, :serenity_bio_infront_of_home],
    +    ]
    +  }
    +end
     
    -    if inputs.mouse.click && !state.point_one
    -      state.point_one = inputs.mouse.click.point
    -    elsif inputs.mouse.click && state.point_one
    -      l = [*state.point_one, *inputs.mouse.click.point]
    -      l = [l.x  - state.camera.x,
    -           l.y  - state.camera.y,
    -           l.x2 - state.camera.x,
    -           l.y2 - state.camera.y].line.to_hash
    -      l[:rect] = rect_for_line l
    -      if state.line_mode == :terrain
    -        state.terrain << l
    -      else
    -        state.lava << l
    -      end
    -      save_level
    -      next_x = inputs.mouse.click.point.x - 640
    -      next_y = inputs.mouse.click.point.y - 360
    -      circle.x += next_x
    -      circle.y += next_y
    -      state.point_one = nil
    -    elsif inputs.keyboard.one
    -      state.point_one = [circle.x + camera.x, circle.y+ camera.y]
    -    end
    +def serenity_bio_restless_sleep args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    storylines: [
    +      [32, 38, 10, 13, "I can't-- seem to sleep. I know nothing-- about the- crew-. Maybe- I- should- go read- up- on- them."],
    +    ],
    +    scenes: [
    +      [32, 0, 8, 3, :serenity_bio_infront_of_home],
    +    ]
    +  }
    +end
    +
    +def serenity_bio_library args
    +  {
    +    background: 'sprites/library.png',
    +    fade: 60,
    +    player: [30, 7],
    +    scenes: [
    +      [21, 35, 3, 18, :serenity_bio_book]
    +    ]
    +  }
    +end
     
    -    # cancel chain lines
    -    if inputs.keyboard.key_down.nine || inputs.keyboard.key_down.escape || inputs.keyboard.key_up.six || inputs.keyboard.key_up.one
    -      state.point_one = nil
    -    end
    -  end
    +def serenity_bio_book args
    +  {
    +    background: 'sprites/book.png',
    +    fade: 60,
    +    player: [6, 52],
    +    storylines: [
    +      [ 4, 50, 56, 4, "The Title-- Reads: Never-- Forget-- Mission-- Serenity---"],
     
    -  def play_sound
    -    return if state.sound_debounce > 0
    -    state.sound_debounce = 5
    -    outputs.sounds << "sounds/03#{"%02d" % state.sound_index}.wav"
    -    state.sound_index += 1
    -    if state.sound_index > 21
    -      state.sound_index = 1
    -    end
    -  end
    +      [ 4, 38,  8, 8, "Name: Matthew--- R. Sex: Male--- Age-- at-- Departure: 36-----"],
    +      [14, 38, 46, 8, "Tribute-- Text: Matthew graduated-- Magna-- Cum-- Laude-- from MIT--- with-- a- PHD---- in Aero-- Nautical--- Engineering. He was immensely--- competitive, and had an insatiable---- thirst- for aerial-- battle. From the age of twenty, he remained-- undefeated--- in the Israeli-- Air- Force- \"Blue Flag\" combat-- exercises. By the age of 29--- he had already-- risen through- the ranks, and became-- the Lieutenant--- General--- of Lufwaffe. Matthew-- volenteered-- to- pilot-- Mission-- Serenity. To- this day, his wife- and son- are pillars-- of strength- for us. Rest- in Peace- Matthew, we are sorry-- that- news of the pregancy-- never-- reached- you. Please forgive us."],
     
    -  def input_game
    -    if inputs.keyboard.down || inputs.keyboard.space
    -      circle.potential_lift += 0.03
    -      circle.potential_lift = circle.potential_lift.lesser(10)
    -    elsif inputs.keyboard.key_up.down || inputs.keyboard.key_up.space
    -      play_sound
    -      circle.dy += circle.angle.vector_y circle.potential_lift
    -      circle.dx += circle.angle.vector_x circle.potential_lift
    +      [4,  26,  8, 8, "Name: Aanka--- P. Sex: Female--- Age-- at-- Departure: 9-----"],
    +      [14, 26, 46, 8, "Tribute-- Text: Aanka--- gratuated--- Magna-- Cum- Laude-- from MIT, at- the- age- of eight, with a- PHD---- in Astro-- Physics. Her-- IQ--- was over 390, the highest-- ever- recorded--- IQ-- in- human-- history. She changed- the landscape-- of Physics-- with her efforts- in- unravelling--- the mysteries--- of- Dark- Matter--. Anka discovered-- the threat- of Halley's-- Comet-- collision--- with Earth. She spear headed-- the global-- effort-- for Misson-- Serenity. Her- multilingual--- address-- to- the world-- brought- us all hope."],
     
    -      if circle.on_floor
    -        if circle.floor_relative_y == :above
    -          circle.y += circle.potential_lift.abs * 2
    -        elsif circle.floor_relative_y == :below
    -          circle.y -= circle.potential_lift.abs * 2
    -        end
    -      end
    +      [4,  14,  8, 8, "Name: Sasha--- N. Sex: Female--- Age-- at-- Departure: 29-----"],
    +      [14, 14, 46, 8, "Tribute-- Text: Sasha gratuated-- Magna-- Cum- Laude-- from MIT--- with-- a- PHD---- in Computer---- Science----. She-- was-- brilliant--, strong- willed--, and-- a-- stunningly--- beautiful--- woman---. Sasha---- is- the- creator--- of the world's--- first- Ruby--- Quantum-- Machine---. After-- much- critical--- acclaim--, the Quantum-- Computer-- was placed in MIT's---- Museam-- next- to- Richard--- G. and Thomas--- K.'s---- Lisp-- Machine---. Her- engineering--- skills-- were-- paramount--- for Mission--- Serenity's--- success. Humanity-- misses-- you-- dearly,-- Sasha--. Life-- shines-- a dimmer-- light-- now- that- your- angelic- voice-- can never- be heard- again."],
    +    ],
    +    scenes: [
    +      [*hotspot_bottom, :serenity_bio_finally_to_bed]
    +    ]
    +  }
    +end
     
    -      circle.on_floor = false
    -      circle.potential_lift = 0
    -      circle.terrains_to_monitor.clear
    -      circle.impact_history.clear
    -      circle.impact = nil
    -      calc_physics
    -    end
    +def serenity_bio_finally_to_bed args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    player: [35, 3],
    +    storylines: [
    +      [34, 4, 4, 4, "Maybe-- I'll-- be able-- to sleep- now..."],
    +    ],
    +    scenes: [
    +      [32, 38, 10, 13, :bad_dream],
    +    ]
    +  }
    +end
     
    -    # aim probe
    -    if inputs.keyboard.right || inputs.keyboard.a
    -      circle.angle -= 2
    -    elsif inputs.keyboard.left || inputs.keyboard.d
    -      circle.angle += 2
    -    end
    -  end
    +def bad_dream args
    +  {
    +    fade: 120,
    +    background: 'sprites/inside-home.png',
    +    player: [34, 35],
    +    storylines: [
    +      [34, 34, 4, 4, "Man. I did not- sleep- well- at all..."],
    +    ],
    +    scenes: [
    +      [32, -1, 8, 3, :bad_dream_observatory]
    +    ]
    +  }
    +end
     
    -  def input
    -    input_god_mode
    -    input_game
    -  end
    +def bad_dream_observatory args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 120,
    +    player: [51, 12],
    +    storylines: [
    +      [50, 10, 4, 4,   "Breathe, Hiro. Just see what's there... everything--- will- be okay."]
    +    ],
    +    scenes: [
    +      [30, 18, 5, 12, :bad_dream_inside_mainframe]
    +    ],
    +    render_override: :blinking_light_inside_observatory_render
    +  }
    +end
     
    -  def calc_camera
    -    state.camera.target_x = 640 - circle.x
    -    state.camera.target_y = 360 - circle.y
    -    xdiff = state.camera.target_x - state.camera.x
    -    ydiff = state.camera.target_y - state.camera.y
    -    state.camera.x += xdiff * camera.follow_speed
    -    state.camera.y += ydiff * camera.follow_speed
    +def bad_dream_inside_mainframe args
    +  {
    +    player: [32, 4],
    +    background: 'sprites/mainframe.png',
    +    fade: 120,
    +    storylines: [
    +      [22, 45, 17, 4, (bad_dream_last_reply args)],
    +    ],
    +    scenes: [
    +      [45, 45,  4, 4, :bad_dream_everyone_dead],
    +    ]
    +  }
    +end
    +
    +def bad_dream_everyone_dead args
    +  {
    +    background: 'sprites/mainframe.png',
    +    storylines: [
    +      [22, 45, 17, 4, (bad_dream_last_reply args)],
    +      [45, 45,  4, 4, "Hi-- Hiro. This is Sasha. By the time- you get this- message, chances-- are we will- already-- be- dead. The batteries--- got- damaged-- during-- removal. And- we don't-- have enough-- power-- for Life-- Support. The air-- is- already--- starting-- to taste- bad. It... would- have been- nice... to go- on a date--- with- you-- when-- I- got- back- to Earth. Anyways, good-- bye-- Hiro-- XOXOXO----"],
    +      [22,  5, 17, 4, "Meh. Whatever, I didn't-- want to save them anyways. What- a pain- in my ass."],
    +    ],
    +    scenes: [
    +      [*hotspot_bottom, :anka_inside_room]
    +    ]
    +  }
    +end
    +
    +def bad_dream_last_reply args
    +  if args.state.scene_history.include? :replied_to_serenity_alive_firmly
    +    return "Buffer--: #{serenity_alive_firm_reply.quote}"
    +  else
    +    return "Buffer--: #{serenity_alive_sugarcoated_reply.quote}"
       end
    +end
     
    -  def calc
    -    state.sound_debounce ||= 0
    -    state.sound_debounce -= 1
    -    state.sound_debounce = 0 if state.sound_debounce < 0
    -    if state.god_mode
    -      circle.dy *= 0.1
    -      circle.dx *= 0.1
    -    end
    -    calc_camera
    -    state.whisp_queue ||= []
    -    if state.tick_count.mod_zero?(4)
    -      state.whisp_queue << {
    -        x: -300,
    -        y: 1400 * rand,
    -        speed: 2.randomize(:ratio) + 3,
    -        w: 20,
    -        h: 20, path: 'sprites/whisp.png',
    -        a: 0,
    -        created_at: state.tick_count,
    -        angle: 0,
    -        r: 100,
    -        g: 128 + 128 * rand,
    -        b: 128 + 128 * rand
    -      }
    -    end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_serenity_introduction.rb
    +# decision_graph "Message from Sasha",
    +#                "I should reply.",
    +#                [:replied_to_introduction_seriously,  "Reply Seriously", "Who is this?"],
    +# [:replied_to_introduction_humorously, "Reply Humorously", "New phone who dis?"]
    +def reply_to_introduction args
    +  decision_graph  "\"Mission-- control--, your- main- comm-- channels-- seem-- to be down. My apologies-- for- using-- this low- level-- exploit--. What's-- going-- on down there? We are ready-- for reentry--.\" Message--- Timestamp---: 4- hours-- 23--- minutes-- ago--.",
    +                  "Whoever-- pulled- off this exploit-- knows their stuff. I should reply--.",
    +                  [:replied_to_introduction_seriously,  "Serious Reply",  "Hello, Who- is sending-- this message--?"],
    +                  [:replied_to_introduction_humorously, "Humorous Reply", "New phone, who dis?"]
    +end
     
    -    state.whisp_queue.each do |w|
    -      w.x += w[:speed] * 2
    -      w.x -= circle.dx * 0.3
    -      w.y -= w[:speed]
    -      w.y -= circle.dy * 0.3
    -      w.angle += w[:speed]
    -      w.a = w[:created_at].ease(30) * 255
    -    end
    +def replied_to_introduction_seriously args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [
    +      *replied_to_introduction_shared_scenes(args)
    +    ],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: \"Hello, Who- is sending-- this message--?\""],
    +      *replied_to_introduction_shared_storylines(args)
    +    ]
    +  }
    +end
     
    -    state.whisp_queue = state.whisp_queue.reject { |w| w[:x] > 1280 }
    +def replied_to_introduction_humorously args
    +  {
    +    background: 'sprites/inside-observatory.png',
    +    fade: 60,
    +    player: [32, 21],
    +    scenes: [
    +      *replied_to_introduction_shared_scenes(args)
    +    ],
    +    storylines: [
    +      [30, 18, 5, 12, "Buffer-- has been set to: \"New- phone. Who dis?\""],
    +      *replied_to_introduction_shared_storylines(args)
    +    ]
    +  }
    +end
     
    -    if state.tick_count.mod_zero?(2) && (circle.dx != 0 || circle.dy != 0)
    -      circle.after_images << {
    -        x: circle.x,
    -        y: circle.y,
    -        w: circle.radius,
    -        h: circle.radius,
    -        a: 255,
    -        created_at: state.tick_count
    -      }
    -    end
    +def replied_to_introduction_shared_storylines args
    +  [
    +    [30, 10, 5, 4, "It's-- going-- to take a while-- for this reply-- to make it's-- way back."],
    +    [40, 10, 5, 4, "4- hours-- to send a message-- at light speed?! How far away-- is the sender--?"],
    +    [50, 10, 5, 4, "I know- I've-- read about-- light- speed- travel-- before--. Maybe-- the library--- still has that- poster."]
    +  ]
    +end
    +
    +def replied_to_introduction_shared_scenes args
    +  [[60, 0, 4, 32, :replied_to_introduction_observatory]]
    +end
    +
    +def replied_to_introduction_observatory args
    +  {
    +    background: 'sprites/observatory.png',
    +    player: [28, 39],
    +    scenes: [
    +      [60, 0, 4, 32, :replied_to_introduction_path_to_observatory]
    +    ]
    +  }
    +end
    +
    +def replied_to_introduction_path_to_observatory args
    +  {
    +    background: 'sprites/path-to-observatory.png',
    +    player: [0, 26],
    +    scenes: [
    +      [60, 0, 4, 20, :replied_to_introduction_mountain_pass]
    +    ],
    +  }
    +end
     
    -    circle.after_images.each do |ai|
    -      ai.a = ai[:created_at].ease(10, :flip) * 255
    -    end
    +def replied_to_introduction_mountain_pass args
    +  {
    +    background: 'sprites/mountain-pass-zoomed-out.png',
    +    player: [21, 48],
    +    scenes: [
    +      [0, 0, 15, 4, :replied_to_introduction_side_of_home]
    +    ],
    +    storylines: [
    +      [15, 28, 5, 3, "At least I'm-- getting-- my- exercise-- in- for- today--."]
    +    ]
    +  }
    +end
     
    -    circle.after_images = circle.after_images.reject { |ai| ai[:created_at].elapsed_time > 10 }
    -    calc_physics
    -  end
    +def replied_to_introduction_side_of_home args
    +  {
    +    background: 'sprites/side-of-home.png',
    +    player: [58, 29],
    +    scenes: [
    +      [2, 0, 61, 2, :speed_of_light_front_of_home]
    +    ],
    +  }
    +end
     
    -  def circle
    -    state.circle
    -  end
    +
    +

    Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb

    +
    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/storyline_speed_of_light.rb
    +def speed_of_light_front_of_home args
    +  {
    +    background: 'sprites/front-of-home.png',
    +    player: [54, 23],
    +    scenes: [
    +      [44, 34, 8, 14, :speed_of_light_inside_home],
    +      [0, 3, 3, 22, :speed_of_light_outside_library]
    +    ]
    +  }
    +end
     
    -  def camera
    -    state.camera
    -  end
    +def speed_of_light_inside_home args
    +  {
    +    background: 'sprites/inside-home.png',
    +    player: [35, 4],
    +    storylines: [
    +      [30, 38, 12, 13, "Can't- sleep right now. I have to- find- out- why- it took- over-- 4- hours-- to receive-- that message."]
    +    ],
    +    scenes: [
    +      [32, 0, 8, 3, :speed_of_light_front_of_home],
    +    ]
    +  }
    +end
     
    -  def terrain
    -    state.terrain
    -  end
    +def speed_of_light_outside_library args
    +  {
    +    background: 'sprites/outside-library.png',
    +    player: [55, 19],
    +    scenes: [
    +      [49, 39, 6, 10, :speed_of_light_library],
    +      [61, 11, 3, 20, :speed_of_light_front_of_home]
    +    ]
    +  }
    +end
     
    -  def lava
    -    state.lava
    -  end
    +def speed_of_light_library args
    +  {
    +    background: 'sprites/library.png',
    +    player: [30, 7],
    +    scenes: [
    +      [3, 50, 10, 3, :speed_of_light_celestial_bodies_diagram]
    +    ]
    +  }
     end
     
    -# $gtk.reset
    +def speed_of_light_celestial_bodies_diagram args
    +  {
    +    background: 'sprites/planets.png',
    +    fade: 60,
    +    player: [30, 3],
    +    scenes: [
    +      [56 - 2, 10, 5, 5, :speed_of_light_distance_discovered]
    +    ],
    +    storylines: [
    +      [30, 2, 4, 4, "Here- it is! This is a diagram--- of the solar-- system--. It was printed-- over-- fifty-- years- ago. Geez-- that's-- old."],
     
    -def tick args
    -  args.outputs.background_color = [0, 0, 0]
    -  if args.inputs.keyboard.r
    -    args.gtk.reset
    -    return
    -  end
    -  # uncomment the line below to slow down the game so you
    -  # can see each tick as it passes
    -  # args.gtk.slowmo! 30
    -  $game ||= FallingCircle.new
    -  $game.args = args
    -  $game.tick
    +      [ 0 - 2, 10, 5, 5, "The label- reads: Sun. The length- of the Astronomical-------- Unit-- (AU), is the distance-- from the Sun- to the Earth. Which is about 150--- million--- kilometers----."],
    +      [ 7 - 2, 10, 5, 5, "The label- reads: Mercury. Distance from Sun: 0.39AU------------ or- 3----- light-- minutes--."],
    +      [14 - 2, 10, 5, 5, "The label- reads: Venus. Distance from Sun: 0.72AU------------ or- 6----- light-- minutes--."],
    +      [21 - 2, 10, 5, 5, "The label- reads: Earth. Distance from Sun: 1.00AU------------ or- 8----- light-- minutes--."],
    +      [28 - 2, 10, 5, 5, "The label- reads: Mars. Distance from Sun: 1.52AU------------ or- 12----- light-- minutes--."],
    +      [35 - 2, 10, 5, 5, "The label- reads: Jupiter. Distance from Sun: 5.20AU------------ or- 45----- light-- minutes--."],
    +      [42 - 2, 10, 5, 5, "The label- reads: Saturn. Distance from Sun: 9.53AU------------ or- 79----- light-- minutes--."],
    +      [49 - 2, 10, 5, 5, "The label- reads: Uranus. Distance from Sun: 19.81AU------------ or- 159----- light-- minutes--."],
    +      # [56 - 2, 15, 4, 4, "The label- reads: Neptune. Distance from Sun: 30.05AU------------ or- 4.1----- light-- hours--."],
    +      [63 - 2, 10, 5, 5, "The label- reads: Pluto. Wait. WTF? Pluto-- isn't-- a planet."],
    +    ]
    +  }
     end
     
    -def reset
    -  $game = nil
    +def speed_of_light_distance_discovered args
    +  {
    +    background: 'sprites/planets.png',
    +    scenes: [
    +      [13, 0, 44, 3, :speed_of_light_end_of_day]
    +    ],
    +    storylines: [
    +      [ 0 - 2, 10, 5, 5, "The label- reads: Sun. The length- of the Astronomical-------- Unit-- (AU), is the distance-- from the Sun- to the Earth. Which is about 150--- million--- kilometers----."],
    +      [ 7 - 2, 10, 5, 5, "The label- reads: Mercury. Distance from Sun: 0.39AU------------ or- 3----- light-- minutes--."],
    +      [14 - 2, 10, 5, 5, "The label- reads: Venus. Distance from Sun: 0.72AU------------ or- 6----- light-- minutes--."],
    +      [21 - 2, 10, 5, 5, "The label- reads: Earth. Distance from Sun: 1.00AU------------ or- 8----- light-- minutes--."],
    +      [28 - 2, 10, 5, 5, "The label- reads: Mars. Distance from Sun: 1.52AU------------ or- 12----- light-- minutes--."],
    +      [35 - 2, 10, 5, 5, "The label- reads: Jupiter. Distance from Sun: 5.20AU------------ or- 45----- light-- minutes--."],
    +      [42 - 2, 10, 5, 5, "The label- reads: Saturn. Distance from Sun: 9.53AU------------ or- 79----- light-- minutes--."],
    +      [49 - 2, 10, 5, 5, "The label- reads: Uranus. Distance from Sun: 19.81AU------------ or- 159----- light-- minutes--."],
    +      [56 - 2, 10, 5, 5, "The label- reads: Neptune. Distance from Sun: 30.05AU------------ or- 4.1----- light-- hours--. What?! The message--- I received-- was from a source-- farther-- than-- Neptune?!"],
    +      [63 - 2, 10, 5, 5, "The label- reads: Pluto. Dista- Wait... Pluto-- isn't-- a planet. People-- thought- Pluto-- was a planet-- back- then?--"],
    +    ]
    +  }
    +end
    +
    +def speed_of_light_end_of_day args
    +  {
    +    fade: 60,
    +    background: 'sprites/inside-home.png',
    +    player: [35, 0],
    +    storylines: [
    +      [35, 10, 4, 4, "Wonder-- what the reply-- will be. Who- the hell is contacting--- me from beyond-- Neptune? This- has to be some- kind- of- joke."]
    +    ],
    +    scenes: [
    +      [31, 38, 10, 12, :serenity_alive_side_of_home]
    +    ]
    +  }
     end
     
     
    -

    99_genre_roguelike/roguelike_line_of_sight/app/constants.rb

    -
    SHOW_LEGEND = true
    +

    Rpg Roguelike - Roguelike Line Of Sight - constants.rb

    +
    # ./samples/99_genre_rpg_roguelike/roguelike_line_of_sight/app/constants.rb
    +SHOW_LEGEND = true
     SOURCE_TILE_SIZE = 16
     DESTINATION_TILE_SIZE = 16
     TILE_SHEET_SIZE = 256
    @@ -17867,8 +20432,9 @@ TILE_B = 0
     TILE_A = 255
     
     
    -

    99_genre_roguelike/roguelike_line_of_sight/app/legend.rb

    -
    def tick_legend args
    +

    Rpg Roguelike - Roguelike Line Of Sight - legend.rb

    +
    # ./samples/99_genre_rpg_roguelike/roguelike_line_of_sight/app/legend.rb
    +def tick_legend args
       return unless SHOW_LEGEND
     
       legend_padding = 16
    @@ -17935,8 +20501,9 @@ TILE_A = 255
     end
     
     
    -

    99_genre_roguelike/roguelike_line_of_sight/app/main.rb

    -
    require 'app/constants.rb'
    +

    Rpg Roguelike - Roguelike Line Of Sight - main.rb

    +
    # ./samples/99_genre_rpg_roguelike/roguelike_line_of_sight/app/main.rb
    +require 'app/constants.rb'
     require 'app/sprite_lookup.rb'
     require 'app/legend.rb'
     
    @@ -18035,8 +20602,9 @@ def tile_in_game x, y, tile_key
     end
     
     
    -

    99_genre_roguelike/roguelike_line_of_sight/app/sprite_lookup.rb

    -
    def sprite_lookup
    +

    Rpg Roguelike - Roguelike Line Of Sight - sprite_lookup.rb

    +
    # ./samples/99_genre_rpg_roguelike/roguelike_line_of_sight/app/sprite_lookup.rb
    +def sprite_lookup
       {
         0 => [3, 0],
         1 => [3, 1],
    @@ -18162,8 +20730,9 @@ end
     $gtk.args.state.reserved.sprite_lookup = sprite_lookup
     
     
    -

    99_genre_roguelike/roguelike_starting_point/app/main.rb

    -
    =begin
    +

    Rpg Roguelike - Roguelike Starting Point - main.rb

    +
    # ./samples/99_genre_rpg_roguelike/roguelike_starting_point/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -18604,8 +21173,9 @@ def tick args
     end
     
     
    -

    99_genre_tactical_rpg/hexagonal_grid/app/main.rb

    -
    class HexagonTileGame
    +

    Rpg Tactical - Hexagonal Grid - main.rb

    +
    # ./samples/99_genre_rpg_tactical/hexagonal_grid/app/main.rb
    +class HexagonTileGame
       attr_gtk
     
       def defaults
    @@ -18675,8 +21245,9 @@ end
     $gtk.reset
     
     
    -

    99_genre_tactical_rpg/isometric_grid/app/main.rb

    -
    class Isometric
    +

    Rpg Tactical - Isometric Grid - main.rb

    +
    # ./samples/99_genre_rpg_tactical/isometric_grid/app/main.rb
    +class Isometric
         attr_accessor :grid, :inputs, :state, :outputs
     
         def tick
    @@ -18812,25 +21383,9 @@ $gtk.reset
         def renderLabels
             #Labels
             outputs.labels << [50, 680, 'Click to delete!',             5, 0, 255, 255, 255, 255] if state.mode == :delete
    -        outputs.labels << [50, 640, 'Press 
    -    
    -  
    -
    -i
    -    
    -  
    -
    - for insert mode!', 5, 0, 255, 255, 255, 255] if state.mode == :delete
    +        outputs.labels << [50, 640, 'Press \'i\' for insert mode!', 5, 0, 255, 255, 255, 255] if state.mode == :delete
             outputs.labels << [50, 680, 'Click to insert!',             5, 0, 255, 255, 255, 255] if state.mode == :insert
    -        outputs.labels << [50, 640, 'Press 
    -    
    -  
    -
    -d
    -    
    -  
    -
    - for delete mode!', 5, 0, 255, 255, 255, 255] if state.mode == :insert
    +        outputs.labels << [50, 640, 'Press \'d\' for delete mode!', 5, 0, 255, 255, 255, 255] if state.mode == :insert
         end
     
         def calc
    @@ -18956,8 +21511,9 @@ def tick args
     end
     
     
    -

    99_genre_topdown_rpg/topdown_starting_point/app/main.rb

    -
    =begin
    +

    Rpg Topdown - Topdown Starting Point - main.rb

    +
    # ./samples/99_genre_rpg_topdown/topdown_starting_point/app/main.rb
    +=begin
     
      APIs listing that haven't been encountered in previous sample apps:
     
    @@ -19067,8 +21623,9 @@ def move_player args, *vector
     end
     
     
    -

    ./dragon/args.rb

    -
    # coding: utf-8
    +

    args.rb

    +
    # ./dragon/args.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # args.rb has been released under MIT (*only this file*).
    @@ -19266,8 +21823,9 @@ module GTK
     end
     
     
    -

    ./dragon/assert.rb

    -
    # coding: utf-8
    +

    assert.rb

    +
    # ./dragon/assert.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # assert.rb has been released under MIT (*only this file*).
    @@ -19397,8 +21955,9 @@ end
     end
     
     
    -

    ./dragon/attr_gtk.rb

    -
    # coding: utf-8
    +

    attr_gtk.rb

    +
    # ./dragon/attr_gtk.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # attr_gtk.rb has been released under MIT (*only this file*).
    @@ -19441,8 +22000,9 @@ module AttrGTK
     end
     
     
    -

    ./dragon/attr_sprite.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    attr_sprite.rb

    +
    # ./dragon/attr_sprite.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # attr_sprite.rb has been released under MIT (*only this file*).
     
    @@ -19500,8 +22060,9 @@ module AttrSprite
     end
     
     
    -

    ./dragon/console.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    console.rb

    +
    # ./dragon/console.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # console.rb has been released under MIT (*only this file*).
     
    @@ -19795,18 +22356,17 @@ S
                 @last_command_errored = false
               rescue Exception => e
                 string_e = "#{e}"
    +            puts "* EXCEPTION: #{e}"
    +            log  "* EXCEPTION: #{e}"
                 @last_command_errored = true
                 if (string_e.include? "wrong number of arguments")
                   method_name = (string_e.split ":")[0].gsub "'", ""
    -              results = Kernel.docs_search method_name
    -              if !results.include "* DOCS: No results found."
    +              results = (Kernel.docs_search method_name).strip
    +              if !results.include? "* DOCS: No results found."
                     puts results
                     log results
                   end
                 end
    -
    -            puts "#{e}"
    -            log "#{e}"
               end
             end
           end
    @@ -19818,6 +22378,10 @@ S
             (args.inputs.keyboard.key_up.b && args.inputs.keyboard.key_up.control)
         end
     
    +    def scroll_to_bottom
    +      @log_offset = 0
    +    end
    +
         def scroll_up_full
           @log_offset += lines_on_one_page
           @log_offset = @log.size if @log_offset > @log.size
    @@ -20013,63 +22577,6 @@ S
           render_log_offset args
         end
     
    -    def tick_help args
    -      tick_help_debounce args
    -      alpha_rate = 20
    -      @render_help_target_alpha  ||= 255
    -      @render_help_current_alpha ||= 255
    -      @render_help_target_alpha  += 4 if @render_help_current_alpha == @render_help_target_alpha
    -      @render_help_current_alpha = (@render_help_current_alpha.towards @render_help_target_alpha, 20)
    -
    -      @render_help_target_alpha  = @render_help_target_alpha.clamp(-255, 255)
    -      @render_help_current_alpha = @render_help_current_alpha.clamp(-255, 255)
    -
    -      [
    -        "* Prompt Commands:                   ",
    -        "You can type any of the following    ",
    -        "commands in the command prompt.      ",
    -        "** docs: Provides API docs.          ",
    -        "** $gtk: Accesses the global runtime.",
    -        "* Shortcut Keys:                     ",
    -        "** full page up:   ctrl + b          ",
    -        "** full page down: ctrl + f          ",
    -        "** half page up:   ctrl + u          ",
    -        "** half page down: ctrl + d          ",
    -        "** clear prompt:   ctrl + g          ",
    -        "** up arrow:       next command      ",
    -        "** down arrow:     prev command      ",
    -      ].each_with_index do |s, i|
    -        args.outputs.reserved << [args.grid.right - 10,
    -                                  top - 100 - line_height_px * i * 0.8,
    -                                  s, -3, 2, 180, 180, 180, (@render_help_current_alpha.clamp 0, 255)].label
    -      end
    -    end
    -
    -    def tick_help_debounce args
    -      hide_log_alpha = -255
    -      if hidden?
    -        @render_help_current_alpha = -255
    -      end
    -
    -      if prompt.last_input_str_changed
    -        @render_help_target_alpha = hide_log_alpha
    -      end
    -
    -      if args.inputs.mouse.moved
    -        @render_help_target_alpha = hide_log_alpha
    -      end
    -
    -      if args.inputs.mouse.wheel
    -        @render_help_target_alpha = hide_log_alpha
    -      end
    -
    -      if @render_help_last_log_invocation_count != @log_invocation_count
    -        @render_help_target_alpha = hide_log_alpha
    -      end
    -
    -      @render_help_last_log_invocation_count = @log_invocation_count
    -    end
    -
         def render_log_offset args
           return if @log_offset <= 0
           args.outputs.reserved << font_style.label(
    @@ -20126,7 +22633,6 @@ S
             process_inputs args
             return unless should_tick?
             calc args
    -        tick_help args
             prompt.tick
             menu.tick args
           rescue Exception => e
    @@ -20285,8 +22791,9 @@ S
     end
     
     
    -

    ./dragon/console_color.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    console_color.rb

    +
    # ./dragon/console_color.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # console_color.rb has been released under MIT (*only this file*).
     
    @@ -20318,8 +22825,9 @@ module GTK
     end
     
     
    -

    ./dragon/console_font_style.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    console_font_style.rb

    +
    # ./dragon/console_font_style.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # console_font_style.rb has been released under MIT (*only this file*).
     
    @@ -20361,14 +22869,17 @@ module GTK
     end
     
     
    -

    ./dragon/console_menu.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    console_menu.rb

    +
    # ./dragon/console_menu.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # console_menu.rb has been released under MIT (*only this file*).
     
     module GTK
       class Console
         class Menu
    +      attr_accessor :buttons
    +
           def initialize console
             @console = console
           end
    @@ -20402,28 +22913,63 @@ module GTK
             @console.hide
           end
     
    +      def hide_menu_clicked
    +        @menu_shown = :hidden
    +      end
    +
           def framerate_diagnostics_clicked
    +        @console.scroll_to_bottom
             $gtk.framerate_diagnostics
           end
     
    +      def itch_wizard_clicked
    +        @console.scroll_to_bottom
    +        $wizards.itch.start
    +      end
    +
    +      def docs_clicked
    +        @console.scroll_to_bottom
    +        log Kernel.docs_classes
    +      end
    +
    +      def scroll_end_clicked
    +        @console.scroll_to_bottom
    +      end
    +
    +      def custom_buttons
    +        []
    +      end
    +
           def tick args
             return unless @console.visible?
     
             @menu_shown ||= :hidden
     
    -        if @menu_shown == :hidden
    +        if $gtk.production
    +          @buttons = [
    +            (button id: :record,      row: 0, col:   9, text: "record gameplay",       method: :record_clicked),
    +            (button id: :replay,      row: 0, col:  10, text: "start replay",          method: :replay_clicked),
    +          ]
    +        elsif @menu_shown == :hidden
               @buttons = [
                 (button id: :show_menu,       row: 0, col: 10, text: "show menu", method: :show_menu_clicked),
               ]
             else
               @buttons = [
    -            (button id: :record,      row: 0, col:  4, text: "framerate diagnostics",   method: :framerate_diagnostics_clicked),
    -            (button id: :record,      row: 0, col:  5, text: "record",      method: :record_clicked),
    -            (button id: :replay,      row: 0, col:  6, text: "replay",      method: :replay_clicked),
    -            (button id: :reset,       row: 0, col:  7, text: "reset",       method: :reset_clicked),
    -            (button id: :scroll_up,   row: 0, col:  8, text: "scroll up",   method: :scroll_up_clicked),
    -            (button id: :scroll_down, row: 0, col:  9, text: "scroll down", method: :scroll_down_clicked),
    -            (button id: :close,       row: 0, col: 10, text: "close",       method: :close_clicked),
    +            (button id: :scroll_up,   row: 0, col:  6, text: "scroll up",             method: :scroll_up_clicked),
    +            (button id: :scroll_down, row: 0, col:  7, text: "scroll down",           method: :scroll_down_clicked),
    +            (button id: :scroll_down, row: 0, col:  8, text: "scroll end",            method: :scroll_end_clicked),
    +            (button id: :close,       row: 0, col:  9, text: "close console",         method: :close_clicked),
    +            (button id: :hide,        row: 0, col: 10, text: "hide menu",             method: :hide_menu_clicked),
    +
    +            (button id: :record,      row: 1, col:  7, text: "record gameplay",       method: :record_clicked),
    +            (button id: :replay,      row: 1, col:  8, text: "start replay",          method: :replay_clicked),
    +            (button id: :record,      row: 1, col:  9, text: "framerate diagnostics", method: :framerate_diagnostics_clicked),
    +            (button id: :reset,       row: 1, col: 10, text: "reset game",            method: :reset_clicked),
    +
    +            (button id: :reset,       row: 2, col: 10, text: "docs",                  method: :docs_clicked),
    +            (button id: :reset,       row: 2, col:  9, text: "itch wizard",           method: :itch_wizard_clicked),
    +            *custom_buttons
               ]
             end
     
    @@ -20456,10 +23002,11 @@ module GTK
             {
               id: id,
               rect: (rect_for_layout row, col),
    +          text: text,
               method: method
             }.let do |entity|
               primitives = []
    -          primitives << entity[:rect].merge(a: 80).solid
    +          primitives << entity[:rect].merge(a: 164).solid
               primitives << entity[:rect].merge(r: 255, g: 255, b: 255).border
               primitives << text.wrapped_lines(5)
                                 .map_with_index do |l, i|
    @@ -20484,8 +23031,9 @@ module GTK
     end
     
     
    -

    ./dragon/console_prompt.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    console_prompt.rb

    +
    # ./dragon/console_prompt.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # console_prompt.rb has been released under MIT (*only this file*).
     
    @@ -20657,8 +23205,9 @@ S
     end
     
     
    -

    ./dragon/controller.rb

    -
    # coding: utf-8
    +

    controller.rb

    +
    # ./dragon/controller.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # controller.rb has been released under MIT (*only this file*).
    @@ -20782,8 +23331,9 @@ end
     
     
     
    -

    ./dragon/controller/config.rb

    -
    # coding: utf-8
    +

    controller/config.rb

    +
    # ./dragon/controller/config.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # controller/config.rb has been released under MIT (*only this file*).
    @@ -21184,8 +23734,9 @@ module GTK
     end
     
     
    -

    ./dragon/controller/keys.rb

    -
    # coding: utf-8
    +

    controller/keys.rb

    +
    # ./dragon/controller/keys.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # controller/keys.rb has been released under MIT (*only this file*).
    @@ -21238,8 +23789,9 @@ module GTK
     end
     
     
    -

    ./dragon/directional_input_helper_methods.rb

    -
    # coding: utf-8
    +

    directional_input_helper_methods.rb

    +
    # ./dragon/directional_input_helper_methods.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # directional_input_helper_methods.rb has been released under MIT (*only this file*).
    @@ -21333,8 +23885,95 @@ S
     end
     
     
    -

    ./dragon/geometry.rb

    -
    # coding: utf-8
    +

    easing.rb

    +
    # ./dragon/easing.rb
    +# coding: utf-8
    +# Copyright 2019 DragonRuby LLC
    +# MIT License
    +# easing.rb has been released under MIT (*only this file*).
    +
    +module GTK
    +  module Easing
    +    def self.ease_extended start_tick, current_tick, end_tick, default_before, default_after, *definitions
    +      definitions.flatten!
    +      definitions = [:identity] if definitions.length == 0
    +      duration = end_tick - start_tick
    +      elapsed  = current_tick - start_tick
    +      y = elapsed.percentage_of(duration).cap_min_max(0, 1)
    +
    +      definitions.map do |definition|
    +        y = Easing.exec_definition(definition, start_tick, duration, y)
    +      end
    +
    +      y
    +    end
    +
    +    def self.ease_spline_extended start_tick, current_tick, end_tick, spline
    +      duration = end_tick - start_tick
    +      t = (current_tick - start_tick).fdiv duration
    +      time_allocation_per_curve = 1.fdiv(spline.length)
    +      curve_index, curve_t = t.fdiv(time_allocation_per_curve).let do |spline_t|
    +        [spline_t.to_i, spline_t - spline_t.to_i]
    +      end
    +      Geometry.cubic_bezier curve_t, *spline[curve_index]
    +    end
    +
    +    def self.initial_value *definitions
    +      definitions.flatten!
    +      return Easing.exec_definition (definitions.value(-1) || :identity), 0, 10, 0
    +    end
    +
    +    def self.final_value *definitions
    +      definitions.flatten!
    +      return Easing.exec_definition (definitions.value(-1) || :identity), 0, 10, 1.0
    +    end
    +
    +    def self.exec_definition definition, start_tick, duration, x
    +      if definition.is_a? Symbol
    +        return Easing.send(definition, x).cap_min_max(0, 1)
    +      elsif definition.is_a? Proc
    +        return definition.call(x, start_tick, duration).cap_min_max(0, 1)
    +      end
    +
    +      raise <<-S
    +* ERROR:
    +I don't know how to execute easing function with definition #{definition}.
    +
    +S
    +    end
    +
    +    def self.identity x
    +      x
    +    end
    +
    +    def self.flip x
    +      1 - x
    +    end
    +
    +    def self.quad x
    +      x * x
    +    end
    +
    +    def self.cube x
    +      x * x * x
    +    end
    +
    +    def self.quart x
    +      x * x * x * x * x
    +    end
    +
    +    def self.quint x
    +      x * x * x * x * x * x
    +    end
    +  end
    +end
    +
    +Easing = GTK::Easing
    +
    +
    +

    geometry.rb

    +
    # ./dragon/geometry.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # geometry.rb has been released under MIT (*only this file*).
    @@ -21705,8 +24344,9 @@ S
     end # module GTK
     
     
    -

    ./dragon/grid.rb

    -
    # coding: utf-8
    +

    grid.rb

    +
    # ./dragon/grid.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # grid.rb has been released under MIT (*only this file*).
    @@ -21897,8 +24537,9 @@ module GTK
     end
     
     
    -

    ./dragon/inputs.rb

    -
    # coding: utf-8
    +

    inputs.rb

    +
    # ./dragon/inputs.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # inputs.rb has been released under MIT (*only this file*).
    @@ -21971,7 +24612,7 @@ module GTK
               "2" => '@', "3" => '#', "4" => '$', "5" => '%',
               "6" => '^', "7" => '&', "8" => '*', "9" => '(',
               "0" => ')', ";" => ":", "=" => "+", "[" => "{",
    -          "]" => "}", '\'=> "|", '/' => "?", '.' => ">",
    +          "]" => "}", '\\'=> "|", '/' => "?", '.' => ">",
               ',' => "<", 'a' => 'A', 'b' => 'B', 'c' => 'C',
               'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G',
               'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K',
    @@ -22052,7 +24693,7 @@ module GTK
             "+"  => [:plus],
             "@"  => [:at],
             "/"  => [:forward_slash],
    -        "\" => [:back_slash],
    +        "\\" => [:back_slash],
             "*"  => [:asterisk],
             "<"  => [:less_than],
             ">"  => [:greater_than],
    @@ -22567,8 +25208,9 @@ module GTK
     end
     
     
    -

    ./dragon/log.rb

    -
    # coding: utf-8
    +

    log.rb

    +
    # ./dragon/log.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # log.rb has been released under MIT (*only this file*).
    @@ -22597,19 +25239,19 @@ module GTK
       class Log
         def self.write_to_log_and_puts *args
           return if $gtk.production
    -      $gtk.append_file 'logs/log.txt', args.join("\n") + "\n"
    +      $gtk.append_file_root 'logs/log.txt', args.join("\n") + "\n"
           args.each { |obj| $gtk.log obj, self }
         end
     
         def self.write_to_log_and_print *args
           return if $gtk.production
    -      $gtk.append_file 'logs/log.txt', args.join("\n")
    +      $gtk.append_file_root 'logs/log.txt', args.join("\n")
           Object.print(*args)
         end
     
         def self.puts_important *args
           return if $gtk.production
    -      $gtk.append_file 'logs/log.txt', args.join("\n")
    +      $gtk.append_file_root 'logs/log.txt', args.join("\n")
           $gtk.notify! "Important notification occurred."
           args.each { |obj| $gtk.log obj }
         end
    @@ -22834,8 +25476,9 @@ class Object
     end
     
     
    -

    ./dragon/numeric.rb

    -
    # coding: utf-8
    +

    numeric.rb

    +
    # ./dragon/numeric.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # numeric.rb has been released under MIT (*only this file*).
    @@ -23479,8 +26122,9 @@ class Integer
     end
     
     
    -

    ./dragon/runtime/framerate_diagnostics.rb

    -
    # Copyright 2019 DragonRuby LLC
    +

    runtime/framerate_diagnostics.rb

    +
    # ./dragon/runtime/framerate_diagnostics.rb
    +# Copyright 2019 DragonRuby LLC
     # MIT License
     # framerate_diagnostics.rb has been released under MIT (*only this file*).
     
    @@ -23647,8 +26291,9 @@ If this warning is getting annoying put the following in your tick method:
     end
     
     
    -

    ./dragon/string.rb

    -
    # coding: utf-8
    +

    string.rb

    +
    # ./dragon/string.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # string.rb has been released under MIT (*only this file*).
    @@ -23751,8 +26396,9 @@ S
     end
     
     
    -

    ./dragon/tests.rb

    -
    # coding: utf-8
    +

    tests.rb

    +
    # ./dragon/tests.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # tests.rb has been released under MIT (*only this file*).
    @@ -23893,8 +26539,9 @@ S
     end
     
     
    -

    ./dragon/trace.rb

    -
    # coding: utf-8
    +

    trace.rb

    +
    # ./dragon/trace.rb
    +# coding: utf-8
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # trace.rb has been released under MIT (*only this file*).
    @@ -23961,7 +26608,7 @@ module GTK
           @traced_classes.clear
           $trace_enabled = false
           if !$gtk.production
    -        $gtk.write_file 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of ~tick~ and this file will be populated with invocation information.\n"
    +        $gtk.write_file_root 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of ~tick~ and this file will be populated with invocation information.\n"
           end
         end
     
    @@ -23983,9 +26630,9 @@ module GTK
           if $trace_puts.length > 0
             text = $trace_puts.join("")
             if pad_with_newline
    -          $gtk.append_file 'logs/trace.txt', "\n" + text.strip
    +          $gtk.append_file_root 'logs/trace.txt', "\n" + text.strip
             else
    -          $gtk.append_file 'logs/trace.txt', text.strip
    +          $gtk.append_file_root 'logs/trace.txt', text.strip
             end
           end
           $trace_puts.clear
    @@ -24047,7 +26694,6 @@ module GTK
     end
     
     
    - -- cgit v1.2.3