From 17d6f2a45d07932fb2ac354d4f8996c420eddc27 Mon Sep 17 00:00:00 2001 From: Amir Rajan Date: Sat, 7 Aug 2021 00:14:16 -0500 Subject: Docs synced. --- docs/docs.html | 8876 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 5581 insertions(+), 3295 deletions(-) (limited to 'docs/docs.html') diff --git a/docs/docs.html b/docs/docs.html index 39d0086..5ad5d6c 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -1,7 +1,8 @@ - + + DragonRuby Game Toolkit Documentation - +
@@ -10,16 +11,16 @@
  • DragonRuby Game Toolkit Live Docs
  • Hello World
  • Join the Discord and Subscribe to the News Letter
  • -
  • Watch Some Intro Videos
  • -
  • Getting Started Tutorial
  • -
  • Deploying To Itch.io
  • +
  • Intro Videos
  • +
  • Getting Started Tutorial
  • +
  • Deploying To Itch.io
  • Deploying To Mobile Devices
  • DragonRuby's Philosophy
  • -
  • Frequently Asked Questions, Comments, and Concerns
  • -
  • RECIPIES:
  • -
  • DOCS: GTK::Runtime
  • +
  • Frequently Asked Questions, Comments, and Concerns
  • +
  • RECIPIES:
  • +
  • DOCS: GTK::Runtime
  • SUMMARY: def tick args; end;
  • -
  • DOCS: GTK::Runtime#reset
  • +
  • DOCS: GTK::Runtime#reset
  • DOCS: GTK::Runtime#calcstringbox
  • DOCS: GTK::Runtime#write_file
  • DOCS: Array
  • @@ -29,28 +30,29 @@
  • DOCS: Array#reject_false
  • DOCS: Array#product
  • DOCS: Array#map_2d
  • -
  • DOCS: Array#include_any?
  • -
  • DOCS: Array#any_intersect_rect?
  • +
  • DOCS: Array#include_any?
  • +
  • DOCS: Array#any_intersect_rect?
  • DOCS: GTK::Args#audio
  • DOCS: GTK::Outputs
  • DOCS: GTK::Outputs#solids
  • DOCS: GTK::Outputs#borders
  • -
  • DOCS: GTK::Outputs#screenshots
  • +
  • DOCS: GTK::Outputs#sprites
  • +
  • DOCS: GTK::Outputs#screenshots
  • DOCS: GTK::Mouse
  • DOCS: GTK::MousePoint
  • DOCS: GTK::OpenEntity
  • DOCS: GTK::OpenEntity#as_hash
  • DOCS: Numeric#frame_index
  • DOCS: Numeric#elapsed_time
  • -
  • DOCS: Numeric#elapsed?
  • -
  • DOCS: Numeric#created?
  • +
  • DOCS: Numeric#elapsed?
  • +
  • DOCS: Numeric#created?
  • DOCS: Kernel
  • DOCS: Kernel::tick_count
  • DOCS: Kernel::global_tick_count
  • DOCS: Geometry
  • DOCS: GTK::Geometry#scale_rect
  • Source Code
  • -
    +

    DragonRuby Game Toolkit Live Docs

    @@ -61,9 +63,6 @@ To search docs you can type docs_search "SEARCH TERM" or if you wan

    docs_search { |entry| (entry.include? "Array") && (!entry.include? "Enumerable") }
     
    -

    - -

    Hello World

    Welcome to DragonRuby Game Toolkit. Take the steps below to get started. @@ -91,21 +90,26 @@ Reply with: I am a Dragon Rider.

    -

    Watch Some Intro Videos

    +

    Intro Videos

    -Each video is only 20 minutes and all of them will fit into a lunch break. So please watch them: +Here are some videos to help you get the lay of the land.

    +

    Quick Api Tour

    1. Beginner Introduction to DragonRuby Game Toolkit: https://youtu.be/ixw7TJhU08E
    2. +
    +

    If You Are Completely New to Ruby and Programming

    +
    1. Intermediate Introduction to Ruby Syntax: https://youtu.be/HG-XRZ5Ppgc
    2. Intermediate Introduction to Arrays in Ruby: https://youtu.be/N72sEYFRqfo
    3. +
    4. You may also want to try this free course provided at http://dragonruby.school.
    5. +
    +

    If You Have Game Dev Experience

    +
      +
    1. Building Tetris - Part 1: https://youtu.be/xZMwRSbC4rY
    2. +
    3. Building Tetris - Part 2: https://youtu.be/C3LLzDUDgz4
    4. +
    5. Low Res Game Jam Tutorial: https://youtu.be/pCI90ukKCME
    -

    -The second and third videos are not required if you are proficient with Ruby, but *definitely* watch the first one. -

    -

    -You may also want to try this free course provided at http://dragonruby.school. -

    Getting Started Tutorial

    This is a tutorial written by Ryan C Gordon (a Juggernaut in the industry who has contracted to Valve, Epic, Activision, and EA... check out his Wikipedia page: https://en.wikipedia.org/wiki/Ryan_C._Gordon). @@ -291,7 +295,7 @@ 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.

    -

    Deploying To Itch.io

    +

    Deploying To Itch.io

    Once you've built your game, you're all set to deploy! Good luck in your game dev journey and if you get stuck, come to the Discord channel!

    @@ -360,7 +364,7 @@ If you have a Pro subscription, you also have the capability to deploy to mobile To deploy to iOS, you need to have a Mac running MacOS Catalina, an iOS device, and an active/paid Developer Account with Apple. From the Console type: $wizards.ios.start and you will be guided through the deployment process.

    -To deploy to Android, you need to have an Android emulator/device, and a environment that is able to run Android SDK. dragonruby-publish will create an APK for you. From there, you can sign the APK and install it to your device. The signing and installation procedure varies from OS to OS. Here's an example of what the command might look like: +To deploy to Android, you need to have an Android emulator/device, and an environment that is able to run Android SDK. dragonruby-publish will create an APK for you. From there, you can sign the APK and install it to your device. The signing and installation procedure varies from OS to OS. Here's an example of what the command might look like:

    > adb logcat -e mygame # you'll want to run this in a separate terminal
     > keytool -genkey -v -keystore mygame.keystore -alias mygame -keyalg RSA -keysize 2048 -validity 10000
    @@ -369,7 +373,7 @@ To deploy to Android, you need to have an Android emulator/device, and a environ
     

    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. +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 principles.

    Challenge The Status Quo

    @@ -388,7 +392,7 @@ It's a hard pill to swallow, but forget blindly accepted best practices and try There is a programming idiom in software called "The Pit of Success". The term normalizes upfront pain as a necessity/requirement in the hopes that the investment will yield dividends "when you become successful" or "when the code becomes more complicated". This approach to development is strongly discouraged by us. It leads to over-architected and unnecessary code; creates barriers to rapid prototyping and shipping a game; and overwhelms beginners who are new to the engine or programming in general.

    -DragonRuby's philosophy is to provide multiple options across the "make it fast" vs "make it right" spectrum, with incremental/intuitive transitions between the options provided. A concrete example of this philosophy would be render primitives: the spectrum of options allows renderable constructs take the form of tuples/arrays (easy to pickup, simple, and fast to code/prototype with), hashes (a little more work, but gives you the ability to add additional properties), open and string entities (more work than hashes, but yields cleaner apis), and finally - if you really need full power/flexibility in rendering - classes (which take the most amount of code and programming knowledge to create). +DragonRuby's philosophy is to provide multiple options across the "make it fast" vs "make it right" spectrum, with incremental/intuitive transitions between the options provided. A concrete example of this philosophy would be render primitives: the spectrum of options allows renderable constructs that take the form of tuples/arrays (easy to pickup, simple, and fast to code/prototype with), hashes (a little more work, but gives you the ability to add additional properties), open and strict entities (more work than hashes, but yields cleaner apis), and finally - if you really need full power/flexibility in rendering - classes (which take the most amount of code and programming knowledge to create).

    Release Early and Often

    @@ -432,12 +436,12 @@ We are bombarded by marketing speak day in and day out. We don't do that here. T

    We want DragonRuby to *actually* help you build the game you want to build (as opposed to sell you something piece of demoware that doesn't work).

    -

    Frequently Asked Questions, Comments, and Concerns

    +

    Frequently Asked Questions, Comments, and Concerns

    Here are questions, comments, and concerns that frequently come up.

    Frequently Asked Questions

    -

    What is DragonRuby LLP?

    +

    What is DragonRuby LLP?

    DragonRuby LLP is a partnership of four devs who came together with the goal of bringing the aesthetics and joy of Ruby, everywhere possible.

    @@ -462,7 +466,7 @@ NOTE: We leave the "A DragonRuby LLP Product" off of this one because that just

    NOTE: Devs who use DragonRuby are "Dragon Riders/Riders of Dragons". That's a bad ass identifier huh?

    -

    What is DragonRuby?

    +

    What is DragonRuby?

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

    @@ -482,7 +486,7 @@ DragonRuby's goal is to be compliant with the ISO/IEC 30170:2012 standard. It's 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. +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 Stadia.

    What does Multilevel Cross-platform mean?

    @@ -513,7 +517,7 @@ These levels allow us to stay up to date with open source implementations of Rub

    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.

    -

    How is DragonRuby different than MRI?

    +

    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).

    @@ -535,7 +539,7 @@ end

    4. To ignore code in repl.rb, instead of commenting it out, prefix repl with the letter x and it'll be ignored. @@ -568,18 +572,18 @@ 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. Use ./dragonruby mygame --record to create a game play recording that you can use to find the exception (you can replay a recording by executing ./dragonruby mygame --replay last_replay.txt or through the DragonRuby Console using $gtk.recording.start_replay "last_replay.txt".
    6. 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.
    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. 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.

    Frequent Comments About Ruby as a Language Choice

    -

    But Ruby is dead.

    +

    But Ruby is dead.

    Let's check the official source for the answer to this question: isrubydead.com: https://isrubydead.com/.

    -On a more serious note, Ruby's _quantity_ levels aren't what they used to be. And that's totally fine. Every one chases the new and shiny. +On a more serious note, Ruby's _quantity_ levels aren't what they used to be. And that's totally fine. Everyone chases the new and shiny.

    What really matters is _quality/maturity_. Here is the latest (StackOverflow Survey sorted by highest paid developers)[https://insights.stackoverflow.com/survey/2019#top-paying-technologies]. @@ -587,11 +591,11 @@ What really matters is _quality/maturity_. Here is the latest (StackOverflow Sur

    Let's stop making this comment shall we?

    -

    But Ruby is slow.

    +

    But Ruby is slow.

    That doesn't make any sense. A language specification can't be slow... it's a language spec. Sure, an _implementation/runtime_ can be slow though, but then we'd have to talk about which runtime.

    -

    Dynamic languages are slow.

    +

    Dynamic languages are slow.

    They are certainly slower than statically compiled languages. With the processing power and compiler optimizations we have today, dynamic languages like Ruby are _fast enough_.

    @@ -602,7 +606,7 @@ Unless you are writing in some form of intermediate representation by hand, your NOTE: If you _are_ hand writing LLVM IR, we are always open to bringing on new partners with such a skill set. Email us ^_^.

    Frequent Concerns

    -

    DragonRuby is not open source. That's not right.

    +

    DragonRuby is not open source. That's not right.

    The current state of open source is unsustainable. Contributors work for free, most all open source repositories are severely under-staffed, and burnout from core members is rampant.

    @@ -618,7 +622,7 @@ 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.

    +

    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.

    @@ -640,21 +644,21 @@ You qualify for a free, unrestricted license to DragonRuby products if any of th

    Just contact Amir at amir.rajan@dragonruby.org with a short explanation of your current situation and he'll set you up. No questions asked.

    -

    But still, you should offer a free version. So I can try it out and see if I like it.

    +

    But still, you should offer a free version. So I can try it out and see if I like it.

    -You can try our [web-based sandbox environment](). But it won't do the runtime justice. Or just come to our [Slack]() or [Discord]() channel and ask questions. We'd be happy to have a one on one video chat with you and show off all the cool stuff we're doing. +You can try our web-based sandbox environment at http://fiddle.dragonruby.org. But it won't do the runtime justice. Or just come to our Discord Channel at http://discord.dragonruby.org and ask questions. We'd be happy to have a one on one video chat with you and show off all the cool stuff we're doing.

    Seriously just buy it. Get a refund if you don't like it. We make it stupid easy to do so.

    -

    I still think you should do a free version. Think of all people who would give it a shot.

    +

    I still think you should do a free version. Think of all people who would give it a shot.

    Free isn't a sustainable financial model. We don't want to spam your email. We don't want to collect usage data off of you either. We just want to provide quality toolchains to quality developers (as opposed to a large quantity of developers).

    The people that pay for DragonRuby and make an effort to understand it are the ones we want to build a community around, partner with, and collaborate with. So having that small monetary wall deters entitled individuals that don't value the same things we do.

    -

    What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.

    +

    What if I build something with DragonRuby, but DragonRuby LLP becomes insolvent.

    That won't happen if the development world stop asking for free stuff and non-trivially compensate open source developers. Look, we want to be able to work on the stuff we love, every day of our lives. And we'll go to great lengths to make that happen.

    @@ -743,11 +747,15 @@ You can represent a sprite as a Hash: flip_vertically: false, flip_horizontally: false, angle_anchor_x: 0.5, - angle_anchor_y: 1.0 + angle_anchor_y: 1.0, + blendmode_enum: 1 } end

    +The blendmode_enum value can be set to 0 (no blending), 1 (alpha blending), 2 (additive blending), 3 (modulo blending), 4 (multiply blending). +

    +

    You can represent a sprite as an object:

    # Create type with ALL sprite properties AND primitive_marker
    @@ -756,7 +764,7 @@ class Sprite
                     :source_x, :source_y, :source_w, :source_h,
                     :tile_x, :tile_y, :tile_w, :tile_h,
                     :flip_horizontally, :flip_vertically,
    -                :angle_anchor_x, :angle_anchor_y
    +                :angle_anchor_x, :angle_anchor_y, :blendmode_enum
     
       def primitive_marker
         :sprite
    @@ -832,16 +840,17 @@ You can add additional metadata about your game within a label, which requires y
     

    def tick args
       args.outputs.labels << {
    -    x:              200,
    -    y:              550,
    -    text:           "dragonruby",
    -    size_enum:      2,
    -    alignment_enum: 1,
    -    r:              155,
    -    g:              50,
    -    b:              50,
    -    a:              255,
    -    font:           "fonts/manaspc.ttf",
    +    x:                       200,
    +    y:                       550,
    +    text:                    "dragonruby",
    +    size_enum:               2,
    +    alignment_enum:          1,
    +    r:                       155,
    +    g:                       50,
    +    b:                       50,
    +    a:                       255,
    +    font:                    "fonts/manaspc.ttf",
    +    vertical_alignment_enum: 0, # 0 is bottom, 1 is middle, 2 is top
         # You can add any properties you like (this will be ignored/won't cause errors)
         game_data_one:  "Something",
         game_data_two: {
    @@ -871,6 +880,19 @@ You can get the render size of any string using args.gtk.calcstringbox
    +

    Rendering Labels With New Line Characters And Wrapping

    +

    +You can use a strategy like the following to create multiple labels from a String. +

    +
    def tick args
    +  long_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elitteger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim."
    +  max_character_length = 30
    +  long_strings_split = args.string.wrapped_lines long_string, max_character_length
    +  args.outputs.labels << long_strings_split.map_with_index do |s, i|
    +    { x: 10, y: 600 - (i * 20), text: s }
    +  end
    +end
    +

    How To Play A Sound

    Sounds that end .wav will play once: @@ -902,7 +924,7 @@ If you want to play a .ogg once as if it were a sound effect, you c end end

    -

    Using args.state To Store Your Game State

    +

    Using args.state To Store Your Game State

    args.state is a open data structure that allows you to define properties that are arbitrarily nested. You don't need to define any kind of class.

    @@ -975,7 +997,7 @@ All the properties below hang off of args and can be accessed in th args.PROPERTY end
    -

    args.state

    +

    args.state

    Store your game state inside of this state. Properties with arbitrary nesting is allowed and a backing Entity will be created on your behalf.

    @@ -984,35 +1006,35 @@ Store your game state inside of this state. Properties with arbitra args.state.player.y ||= 0 end
    -

    .*.entity_id

    +

    .*.entity_id

    Entities automatically receive an entity_id of type Fixnum.

    -

    .*.entity_type

    +

    .*.entity_type

    Entities can have an entity_type which is represented as a Symbol.

    -

    .*.created_at

    +

    .*.created_at

    Entities have created_at set to args.state.tick_count when they are created.

    -

    .*.created_at_elapsed

    +

    .*.created_at_elapsed

    Returns the elapsed number of ticks since creation.

    -

    .*.global_created_at

    +

    .*.global_created_at

    Entities have global_created_at set to Kernel.global_tick_count when they are created.

    -

    .*.global_created_at_elapsed

    +

    .*.global_created_at_elapsed

    Returns the elapsed number of global ticks since creation.

    -

    .*.as_hash

    +

    .*.as_hash

    Entity cast to a Hash so you can update values as if you were updating a Hash.

    -

    .new_entity

    +

    .new_entity

    Creates a new Entity with a type, and initial properties. An option block can be passed to change the newly created entity:

    @@ -1023,47 +1045,47 @@ Creates a new Entity with a type, and initial properties. An option end end -

    .new_entity_strict

    +

    .new_entity_strict

    Creates a new Strict Entity. While Entities created via args.state.new_entity can have new properties added later on, Entities created using args.state.new_entity must define all properties that are allowed during its initialization. Attempting to add new properties after initialization will result in an exception.

    -

    .tick_count

    +

    .tick_count

    Returns the current tick of the game. args.state.tick_count is 0 when the game is first started or if the game is reset via $gtk.reset.

    -

    args.inputs

    +

    args.inputs

    Access using input using args.inputs.

    -

    .up

    +

    .up

    Returns true if: the up arrow or w key is pressed or held on the keyboard; or if up is pressed or held on controller_one; or if the left_analog on controller_one is tilted upwards.

    -

    .down

    +

    .down

    Returns true if: the down arrow or s key is pressed or held on the keyboard; or if down is pressed or held on controller_one; or if the left_analog on controller_one is tilted downwards.

    -

    .left

    +

    .left

    Returns true if: the left arrow or a key is pressed or held on the keyboard; or if left is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the left.

    -

    .right

    +

    .right

    Returns true if: the right arrow or d key is pressed or held on the keyboard; or if right is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the right.

    -

    .left_right

    +

    .left_right

    Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.left and args.inputs.right.

    -

    .up_down

    +

    .up_down

    -Returns -1 (down), 0 (neutral), or +1 (right) depending on results of args.inputs.up and args.inputs.down. +Returns -1 (down), 0 (neutral), or +1 (up) depending on results of args.inputs.down and args.inputs.up.

    -

    .text OR .history

    +

    .text OR .history

    Returns a string that represents the last key that was pressed on the keyboard.

    -

    .mouse

    +

    .mouse

    Represents the user's

    @@ -1105,37 +1127,49 @@ Returns a bitmask for all buttons on the mouse: 1 for a button in t

    mouse.wheel

    -Represents the mouse wheel. Returns nil if no mouse wheel actions occurred. ***** .x Returns the negative or positive number if the mouse wheel has changed in the x axis. ***** .y Returns the negative or positive number if the mouse wheel has changed in the y axis. +Represents the mouse wheel. Returns nil if no mouse wheel actions occurred. +

    +

    +***** .x +

    +

    +Returns the negative or positive number if the mouse wheel has changed in the x axis. +

    +

    +***** .y +

    +

    +Returns the negative or positive number if the mouse wheel has changed in the y axis.

    .click OR .down, .previous_click, .up

    The properties args.inputs.mouse.(click|down|previous_click|up) each return nil if the mouse button event didn't occur. And return an Entity that has an x, y properties along with helper functions to determine collision: inside_rect?, inside_circle.

    -

    .controller_one, .controller_two

    +

    .controller_one, .controller_two

    Represents controllers connected to the usb ports.

    -

    ~.up

    +

    .up

    Returns true if up is pressed or held on the directional or left analog.

    -

    ~.down

    +

    .down

    Returns true if down is pressed or held on the directional or left analog.

    -

    ~.left

    +

    .left

    Returns true if left is pressed or held on the directional or left analog.

    -

    ~.right

    +

    .right

    Returns true if right is pressed or held on the directional or left analog.

    -

    ~.left_right

    +

    .left_right

    Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.controller_(one|two).left and args.inputs.controller_(one|two).right.

    -

    ~.up_down

    +

    .up_down

    Returns -1 (down), 0 (neutral), or +1 (up) depending on results of args.inputs.controller_(one|two).up and args.inputs.controller_(one|two).down.

    @@ -1155,19 +1189,19 @@ Returns a number between -1 and 1 which represents the

    Returns a number between -1 and 1 which represents the percentage the analog is moved vertically as a ratio of the maximum vertical movement.

    -

    .directional_up)

    +

    .directional_up

    Returns true if up is pressed or held on the directional.

    -

    .directional_down)

    +

    .directional_down

    Returns true if down is pressed or held on the directional.

    -

    .directional_left)

    +

    .directional_left

    Returns true if left is pressed or held on the directional.

    -

    .directional_right)

    +

    .directional_right

    Returns true if right is pressed or held on the directional.

    @@ -1191,7 +1225,7 @@ Returns true if the specific button is being held. args.input

    Returns true if the specific button was released. args.inputs.controller_(one|two).key_up.BUTTON will be true only on the frame the button was released.

    -

    .keyboard

    +

    .keyboard

    Represents the user's keyboard

    @@ -1333,139 +1367,148 @@ Returns a Hash with all keys on the keyboard in their respective st
  • :down_or_held
  • :up
  • -

    args.outputs

    +

    args.outputs

    args.outputs.PROPERTY is how you render to the screen.

    -

    .background_color

    +

    .background_color

    Set args.outputs.background_color to an Array with RGB values (eg. [255, 255, 255] for the color white).

    -

    .sounds

    +

    .sounds

    Send a file path to this collection to play a sound. The sound file must be under the mygame directory. Example: args.outputs.sounds << "sounds/jump.wav".

    -

    .solids

    +

    .solids

    Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is cleared at the end of every frame.

    -

    .static_solids

    +

    .static_solids

    Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is not cleared at the end of every frame. And objects can be mutated by reference.

    -

    .sprites, .static_sprites

    +

    .sprites, .static_sprites

    Send a Primitive to this collection to render a sprite to the screen.

    -

    .primitives, .static_primitives

    +

    .primitives, .static_primitives

    Send a Primitive of any type and it'll be rendered. The Primitive must have a primitive_marker that returns :solid, :sprite, :label, :line, :border.

    -

    .labels, .static_labels

    +

    .labels, .static_labels

    Send a Primitive to this collection to render text to the screen.

    -

    .lines, .static_lines

    +

    .lines, .static_lines

    Send a Primitive to this collection to render a line to the screen.

    -

    .borders, .static_borders

    +

    .borders, .static_borders

    Send a Primitive to this collection to render an unfilled rectangle to the screen.

    -

    .debug, .static_debug

    +

    .debug, .static_debug

    Send any Primitive to this collection which represents things you render to the screen for debugging purposes. Primitives in this collection will not be rendered in a production release of your game.

    -

    args.geometry

    +

    args.geometry

    This property contains geometric functions. Functions can be invoked via args.geometry.FUNCTION.

    -

    .inside_rect? rect_1, rect_2

    +

    +Here are some general notes with regards to the arguments these geometric functions accept. +

    +
      +
    1. Rectangles can be represented as an Array with four (or more) values [x, y, w, h], as a Hash { x:, y:, w:, h: } or an object that responds to x, y, w, and h.
    2. +
    3. Points can be represent as an Array with two (or more) values [x, y], as a Hash { x:, y:} or an object that responds to x, and y.
    4. +
    5. Lines can be represented as an Array with four (or more) values [x, y, x2, y2], as a Hash { x:, y:, x2:, y2: } or an object that responds to x, y, x2, and y2.
    6. +
    7. Angles are represented as degrees (not radians).
    8. +
    +

    .inside_rect? rect_1, rect_2

    Returns true if rect_1 is inside rect_2.

    -

    .intersect_rect? rect_2, rect_2

    +

    .intersect_rect? rect_2, rect_2

    Returns true if rect_1 intersects rect_2.

    -

    .scale_rect rect, x_percentage, y_percentage

    +

    .scale_rect rect, x_percentage, y_percentage

    Returns a new rectangle that is scaled by the percentages provided.

    -

    .angle_to start_point, end_point

    +

    .angle_to start_point, end_point

    Returns the angle in degrees between two points start_point to end_point.

    -

    .angle_from start_point, end_point

    +

    .angle_from start_point, end_point

    Returns the angle in degrees between two points start_point from end_point.

    -

    .point_inside_circle? point, circle_center_point, radius

    +

    .point_inside_circle? point, circle_center_point, radius

    Returns true if a point is inside a circle defined by its center and radius.

    -

    .center_inside_rect rect, other_rect

    +

    .center_inside_rect rect, other_rect

    Returns a new rectangle based of off rect that is centered inside of other_rect.

    -

    .center_inside_rect_x rect, other_rect

    +

    .center_inside_rect_x rect, other_rect

    Returns a new rectangle based of off rect that is centered horizontally inside of other_rect.

    -

    .center_inside_rect_y rect, other_rect

    +

    .center_inside_rect_y rect, other_rect

    Returns a new rectangle based of off rect that is centered vertically inside of other_rect.

    -

    .anchor_rect rect, anchor_x, anchor_y

    +

    .anchor_rect rect, anchor_x, anchor_y

    Returns a new rectangle based of off rect that has been repositioned based on the percentages passed into anchor_x, and anchor_y.

    -

    .shift_line line, x, y

    +

    .shift_line line, x, y

    Returns a line that is offset by x, and y.

    -

    .line_y_intercept line

    +

    .line_y_intercept line

    Given a line, the b value is determined for the point slope form equation: y = mx + b.

    -

    .angle_between_lines line_one, line_two, replace_infinity:

    +

    .angle_between_lines line_one, line_two, replace_infinity:

    Returns the angle between two lines as if they were infinitely long. A numeric value can be passed in for the last parameter which would represent lines that do not intersect.

    -

    .line_slope line, replace_infinity:

    +

    .line_slope line, replace_infinity:

    Given a line, the m value is determined for the point slope form equation: y = mx + b.

    -

    .line_rise_run

    +

    .line_rise_run

    Given a line, a Hash is returned that returns the slope as x and y properties with normalized values (the number is between -1 and 1).

    -

    .ray_test point, line

    +

    .ray_test point, line

    Given a point and a line, :on, :left, or :right which represents the location of the point relative to the line.

    -

    .line_rect line

    +

    .line_rect line

    Returns the bounding rectangle for a line.

    -

    .line_intersect line_one, line_two

    +

    .line_intersect line_one, line_two

    Returns a point that represents the intersection of the lines.

    -

    .distance point_one, point_two

    +

    .distance point_one, point_two

    Returns the distance between two points.

    -

    .cubic_bezier t, a, b, c, d

    +

    .cubic_bezier t, a, b, c, d

    Returns the cubic bezier function for tick_count t with anchors a, b, c, and d.

    -

    args.easing

    +

    args.easing

    A set of functions that allow you to determine the current progression of an easing function.

    -

    .ease start_tick, current_tick, duration, easing_functions

    +

    .ease start_tick, current_tick, duration, easing_functions

    Given a start, current, duration, and easing function names, ease returns a number between 0 and 1 that represents the progress of an easing function.

    @@ -1485,7 +1528,7 @@ This example will move a box at a linear speed from 0 to 1280. args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 } end
    -

    .ease_spline start_tick, current_tick, duration, spline

    +

    .ease_spline start_tick, current_tick, duration, spline

    Given a start, current, duration, and a multiple bezier values, this function returns a number between 0 and 1 that represents the progress of an easing function.

    @@ -1506,11 +1549,11 @@ This example will move a box at a linear speed from 0 to 1280 and then back to 0 args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 } end -

    args.string

    +

    args.string

    Useful string functions not included in Ruby core libraries.

    -

    .wrapped_lines string, max_character_length

    +

    .wrapped_lines string, max_character_length

    This function will return a collection of strings given an input string and max_character_length. The collection of strings returned will split the input string into strings of length <= max_character_length.

    @@ -1527,175 +1570,175 @@ teger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim." end end -

    args.grid

    +

    args.grid

    Returns the virtual grid for the game.

    -

    .name

    +

    .name

    Returns either :origin_bottom_left or :origin_center.

    -

    .bottom

    +

    .bottom

    Returns the y value that represents the bottom of the grid.

    -

    .top

    +

    .top

    Returns the y value that represents the top of the grid.

    -

    .left

    +

    .left

    Returns the x value that represents the left of the grid.

    -

    .right

    +

    .right

    Returns the x value that represents the right of the grid.

    -

    .rect

    +

    .rect

    Returns a rectangle Primitive that represents the grid.

    -

    .origin_bottom_left!

    +

    .origin_bottom_left!

    Change the grids coordinate system to 0, 0 at the bottom left corner.

    -

    .origin_center!

    +

    .origin_center!

    Change the grids coordinate system to 0, 0 at the center of the screen.

    -

    .w

    +

    .w

    Returns the grid's width (always 1280).

    -

    .h

    +

    .h

    Returns the grid's height (always 720).

    -

    args.gtk

    +

    args.gtk

    This represents the DragonRuby Game Toolkit's Runtime Environment and can be accessed via args.gtk.METHOD.

    -

    .argv

    +

    .argv

    Returns a String that represents the parameters passed into the ./dragonruby binary.

    -

    .platform

    +

    .platform

    Returns a String representing the operating system the game is running on.

    -

    .request_quit

    +

    .request_quit

    Request that the runtime quit the game.

    -

    .write_file path, contents

    +

    .write_file path, contents

    Writes/overwrites a file within the game directory + path.

    -

    .write_file_root

    +

    .write_file_root

    Writes/overwrites a file within the root dragonruby binary directory + path.

    -

    .append_file path, contents

    +

    .append_file path, contents

    Append content to a file located at the game directory + path.

    -

    .append_file_root path, contents

    +

    .append_file_root path, contents

    Append content to a file located at the root dragonruby binary directory + path.

    -

    .read_file path

    +

    .read_file path

    Reads a file from the sandboxed file system.

    -

    .parse_xml string, parse_xml_file path

    +

    .parse_xml string, parse_xml_file path

    Returns a Hash for a String that represents XML.

    -

    .parse_json string, parse_json_file path

    +

    .parse_json string, parse_json_file path

    Returns a Hash for a String that represents JSON.

    -

    .http_get url, extra_headers = {}

    +

    .http_get url, extra_headers = {}

    Creates an async task to perform an HTTP GET.

    -

    .http_post url, form_fields = {}, extra_headers = {}

    +

    .http_post url, form_fields = {}, extra_headers = {}

    Creates an async task to perform an HTTP POST.

    -

    .reset

    +

    .reset

    Resets the game by deleting all data in args.state and setting args.state.tick_count back to 0.

    -

    .stop_music

    +

    .stop_music

    Stops all background music.

    -

    .calcstringbox str, size_enum, font

    +

    .calcstringbox str, size_enum, font

    Returns a tuple with width and height of a string being rendered.

    -

    .slowmo! factor

    +

    .slowmo! factor

    Slows the game down by the factor provided.

    -

    .notify! string

    +

    .notify! string

    Renders a toast message at the bottom of the screen.

    -

    .system

    +

    .system

    Invokes a shell command and prints the result to the console.

    -

    .exec

    +

    .exec

    Invokes a shell command and returns a String that represents the result.

    -

    .save_state

    +

    .save_state

    Saves the game state to game_state.txt.

    -

    .load_state

    +

    .load_state

    Load args.state from game_state.txt.

    -

    .serialize_state file, state

    +

    .serialize_state file, state

    Saves entity state to a file. If only one parameter is provided a string is returned for state instead of writing to a file.

    -

    .deserialize_state file

    +

    .deserialize_state file

    Returns entity state from a file or serialization data represented as a String.

    -

    .reset_sprite path

    +

    .reset_sprite path

    Invalids the texture cache of a sprite.

    -

    .show_cursor

    +

    .show_cursor

    Shows the mouse cursor.

    -

    .hide_cursor

    +

    .hide_cursor

    Hides the mouse cursor.

    -

    .cursor_shown?

    +

    .cursor_shown?

    Returns true if the mouse cursor is shown.

    -

    .set_window_fullscreen enabled

    +

    .set_window_fullscreen enabled

    Sets the game to either fullscreen (enabled=true) or windowed (enabled=false).

    -

    .openurl url

    +

    .openurl url

    Opens a url using the Operating System's default browser.

    -

    .get_base_dir

    +

    .get_base_dir

    Returns the full path of the DragonRuby binary directory.

    -

    .get_game_dir

    +

    .get_game_dir

    Returns the full path of the game directory in its sandboxed environment.

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

    DOCS: 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:

    -

    DOCS: 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.

    @@ -2018,7 +2061,7 @@ end

    Rendering a solid using an Array with colors and alpha

    -The value for the color and alpha is an number between 0 and 255. The alpha property is optional and will be set to 255 if not specified. +The value for the color and alpha is a number between 0 and 255. The alpha property is optional and will be set to 255 if not specified.

    Creates a green solid rectangle with an opacity of 50%. @@ -2047,7 +2090,7 @@ end

    Rendering a solid using a Class

    -You can also create a class with solid/border properties and render it as a primitive. ALL properties must on the class. *Additionally*, a method called primitive_marker must be defined on the class. +You can also create a class with solid/border properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called primitive_marker must be defined on the class.

    Here is an example: @@ -2100,6 +2143,93 @@ You have to use args.outputs.borders: args.outputs.borders << [100, 100, 160, 90] end +

    DOCS: GTK::Outputs#sprites

    +

    +Add primitives to this collection to render a sprite to the screen. +

    +

    Rendering a sprite using an Array

    +

    +Creates a sprite of a white circle located at 100, 100. 160 pixels wide and 90 pixels tall. +

    +
    def tick args
    +  #                         X    Y   WIDTH   HEIGHT                      PATH
    +  args.outputs.sprites << [100, 100,   160,     90, "sprites/circle/white.png]
    +end
    +
    +

    Rendering a sprite using an Array with colors and alpha

    +

    +The value for the color and alpha is a number between 0 and 255. The alpha property is optional and will be set to 255 if not specified. +

    +

    +Creates a green circle sprite with an opacity of 50%. +

    +
    def tick args
    +  #                         X    Y  WIDTH  HEIGHT           PATH                ANGLE  ALPHA  RED  GREEN  BLUE
    +  args.outputs.sprites << [100, 100,  160,     90, "sprites/circle/white.png",     0,    128,   0,   255,    0]
    +end
    +
    +

    Rendering a sprite using a Hash

    +

    +If you want a more readable invocation. You can use the following hash to create a sprite. Any parameters that are not specified will be given a default value. The keys of the hash can be provided in any order. +

    +
    def tick args
    +  args.outputs.sprites << {
    +    x:                             0,
    +    y:                             0,
    +    w:                           100,
    +    h:                           100,
    +    path: "sprites/circle/white.png",
    +    angle:                         0,
    +    a:                           255,
    +    r:                             0,
    +    g:                           255,
    +    b:                             0
    +  }
    +end
    +
    +

    Rendering a solid using a Class

    +

    +You can also create a class with solid/border properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called primitive_marker must be defined on the class. +

    +

    +Here is an example: +

    +
    # Create type with ALL sprite properties AND primitive_marker
    +class Sprite
    +  attr_accessor :x, :y, :w, :h, :path, :angle, :angle_anchor_x, :angle_anchor_y,  :tile_x, :tile_y, :tile_w, :tile_h, :source_x, :source_y, :source_w, :source_h, :flip_horizontally, :flip_vertically, :a, :r, :g, :b
    +
    +  def primitive_marker
    +    :sprite
    +  end
    +end
    +
    +# Inherit from type
    +class Circle < Sprite
    +# constructor
    +  def initialize x, y, size, path
    +    self.x = x
    +    self.y = y
    +    self.w = size
    +    self.h = size
    +    self.path = path
    +  end
    +  def serlialize
    +    {x:self.x, y:self.y, w:self.w, h:self.h, path:self.path}
    +  end
    +
    +  def inspect
    +    serlialize.to_s
    +  end
    +
    +  def to_s
    +    serlialize.to_s
    +  end
    +end
    +def tick args
    +  # render circle sprite
    +  args.outputs.sprites  << Circle.new(10, 10, 32,"sprites/circle/white.png")
    +end
    +

    DOCS: GTK::Outputs#screenshots

    Add a hash to this collection to take a screenshot and save as png file. The keys of the hash can be provided in any order. @@ -2160,12 +2290,12 @@ The GTK::MousePoint has the following properties.

  • x: Integer representing the mouse's x.
  • y: Integer representing the mouse's y.
  • point: Array with the x and y values.
  • -
  • w: Width of the point that always returns 0 (included so that it can seemlessly work with GTK::Geometry functions).
  • -
  • h: Height of the point that always returns 0 (included so that it can seemlessly work with GTK::Geometry functions).
  • -
  • left: This value is the same as x (included so that it can seemlessly work with GTK::Geometry functions).
  • -
  • right: This value is the same as x (included so that it can seemlessly work with GTK::Geometry functions).
  • -
  • top: This value is the same as y (included so that it can seemlessly work with GTK::Geometry functions).
  • -
  • bottom: This value is the same as y (included so that it can seemlessly work with GTK::Geometry functions).
  • +
  • w: Width of the point that always returns 0 (included so that it can seamlessly work with GTK::Geometry functions).
  • +
  • h: Height of the point that always returns 0 (included so that it can seamlessly work with GTK::Geometry functions).
  • +
  • left: This value is the same as x (included so that it can seamlessly work with GTK::Geometry functions).
  • +
  • right: This value is the same as x (included so that it can seamlessly work with GTK::Geometry functions).
  • +
  • top: This value is the same as y (included so that it can seamlessly work with GTK::Geometry functions).
  • +
  • bottom: This value is the same as y (included so that it can seamlessly work with GTK::Geometry functions).
  • 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.
  • @@ -2322,7 +2452,7 @@ And here is an example where the override parameter is passed in: end end -

    DOCS: 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.

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

    DOCS: Numeric#created?

    +

    DOCS: Numeric#created?

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

    @@ -2455,7 +2585,7 @@ end Follows is a source code listing for all files that have been open sourced. This code can be found in the ./samples directory.

    Samples

    -

    Learn Ruby Optional - Beginner Ruby Primer - automation.rb

    +

    Learn Ruby Optional - Beginner Ruby Primer - automation.rb

    # ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/automation.rb
     # ==========================================================================
     #  _    _ ________     __  _      _____  _____ _______ ______ _   _ _ _ _ _
    @@ -2579,7 +2709,7 @@ $gtk.schedule_callback 400 do
     end
     
     
    -

    Learn Ruby Optional - Beginner Ruby Primer - main.rb

    +

    Learn Ruby Optional - Beginner Ruby Primer - main.rb

    # ./samples/00_learn_ruby_optional/00_beginner_ruby_primer/app/main.rb
     # ==========================================================================
     #  _    _ ________     __  _      _____  _____ _______ ______ _   _ _ _ _ _
    @@ -2900,7 +3030,7 @@ def outputs
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - printing.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - printing.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/01_printing.txt
     # ====================================================================================
     # Commenting Code
    @@ -2935,7 +3065,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - strings.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - strings.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/02_strings.txt
     # ====================================================================================
     #  Strings
    @@ -2954,7 +3084,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - numbers.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/03_numbers.txt
     # ====================================================================================
     #  Numerics
    @@ -2979,7 +3109,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - booleans.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/04_booleans.txt
     # ====================================================================================
     #  Booleans
    @@ -3015,7 +3145,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - conditionals.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/05_conditionals.txt
     # ====================================================================================
     #  Conditionals
    @@ -3133,7 +3263,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - looping.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - looping.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/06_looping.txt
     # ====================================================================================
     #  Looping
    @@ -3192,7 +3322,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - functions.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - functions.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/07_functions.txt
     # ====================================================================================
     # Functions
    @@ -3265,7 +3395,7 @@ repl do
     end
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt

    +

    Learn Ruby Optional - Intermediate Ruby Primer - arrays.txt

    # ./samples/00_learn_ruby_optional/00_intermediate_ruby_primer/app/08_arrays.txt
     # ====================================================================================
     # Arrays
    @@ -3479,14 +3609,14 @@ end
     # ====================================================================================
     
     
    -

    Learn Ruby Optional - Intermediate Ruby Primer - main.rb

    +

    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
     
     
    -

    Rendering Basics - Labels - main.rb

    +

    Rendering Basics - Labels - main.rb

    # ./samples/01_rendering_basics/01_labels/app/main.rb
     =begin
     
    @@ -3557,7 +3687,7 @@ def tick args
                                    g: 0,
                                    b: 200,
                                    a: 255,
    -                               font: "manaspc.ttf" }.label
    +                               font: "manaspc.ttf" }.label!
     
       # Primitives can hold anything, and can be given a label in the following forms
       args.outputs.primitives << [690 + 150, 330 - 80, "Custom font (.primitives Array)", 0, 1, 125, 0, 200, 255, "manaspc.ttf" ].label
    @@ -3571,7 +3701,7 @@ def tick args
                                    g: 0,
                                    b: 200,
                                    a: 255,
    -                               font: "manaspc.ttf" }.label
    +                               font: "manaspc.ttf" }.label!
     end
     
     def tick_instructions args, text, y = 715
    @@ -3589,7 +3719,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Rendering Basics - Lines - main.rb

    +

    Rendering Basics - Lines - main.rb

    # ./samples/01_rendering_basics/02_lines/app/main.rb
     =begin
     
    @@ -3647,7 +3777,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Rendering Basics - Solids Borders - main.rb

    +

    Rendering Basics - Solids Borders - main.rb

    # ./samples/01_rendering_basics/03_solids_borders/app/main.rb
     =begin
     
    @@ -3717,7 +3847,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Rendering Basics - Sprites - main.rb

    +

    Rendering Basics - Sprites - main.rb

    # ./samples/01_rendering_basics/04_sprites/app/main.rb
     =begin
     
    @@ -3764,7 +3894,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Rendering Basics - Sounds - main.rb

    +

    Rendering Basics - Sounds - main.rb

    # ./samples/01_rendering_basics/05_sounds/app/main.rb
     =begin
     
    @@ -3799,1011 +3929,360 @@ def tick args
     end
     
     
    -

    Rendering Basics - Audio Mixer - main.rb

    -
    # ./samples/01_rendering_basics/06_audio_mixer/app/main.rb
    -$gtk.reset
    +

    Input Basics - Keyboard - main.rb

    +
    # ./samples/02_input_basics/01_keyboard/app/main.rb
    +=begin
     
    -$boxsize = 30
    +APIs listing that haven't been encountered in a previous sample apps:
     
    -def render_sources args
    -  mouse_in_panel = (args.state.selected != 0) && args.inputs.mouse.position.inside_rect?([900, 450, 340, 250])
    -  mouse_new_down = (args.state.mouse_held == 1)
    +- args.inputs.keyboard.key_up.KEY: The value of the properties will be set
    +  to the frame  that the key_up event occurred (the frame correlates
    +  to args.state.tick_count). Otherwise the value will be nil. For a
    +  full listing of keys, take a look at mygame/documentation/06-keyboard.md.
    +- args.state.PROPERTY: The state property on args is a dynamic
    +  structure. You can define ANY property here with ANY type of
    +  arbitrary nesting. Properties defined on args.state will be retained
    +  across frames. If you attempt access a property that doesn't exist
    +  on args.state, it will simply return nil (no exception will be thrown).
     
    -  if (mouse_new_down && !mouse_in_panel)
    -    args.state.selected = 0  # will reset below if we hit something.
    -  end
    +=end
     
    -  args.audio.keys.each { |k|
    -    s = args.audio[k]
    +# Along with outputs, inputs are also an essential part of video game development
    +# DragonRuby can take input from keyboards, mouse, and controllers.
    +# This sample app will cover keyboard input.
     
    -    if (mouse_new_down) && !mouse_in_panel && args.inputs.mouse.position.inside_rect?([s[:screenx], s[:screeny], $boxsize, $boxsize])
    -      args.state.selected = k
    -      args.state.dragging_source = true
    -    end
    +# args.inputs.keyboard.key_up.a will check to see if the a key has been pressed
    +# This will work with the other keys as well
     
    -    isselected = (k == args.state.selected)
     
    -    if isselected && args.state.dragging_source
    -      # you can hang anything on the audio hashes you want, so we store the
    -      #  actual screen position so it doesn't scale weirdly vs your mouse.
    -      s[:screenx] = args.inputs.mouse.x - ($boxsize / 2)
    -      s[:screeny] = args.inputs.mouse.y - ($boxsize / 2)
    +def tick args
    +  tick_instructions args, "Sample app shows how keyboard events are registered and accessed.", 360
    +  # Notice how small_font accounts for all the remaining parameters
    +  args.outputs.labels << { x: 460, y: row_to_px(args, 0), text: "Current game time: #{args.state.tick_count}", size_enum: -1 }
    +  args.outputs.labels << { x: 460, y: row_to_px(args, 2), text: "Keyboard input: args.inputs.keyboard.key_up.h", size_enum: -1 }
    +  args.outputs.labels << { x: 460, y: row_to_px(args, 3), text: "Press \"h\" on the keyboard.", size_enum: -1 }
    +
    +  # Input on a specifc key can be found through args.inputs.keyboard.key_up followed by the key
    +  if args.inputs.keyboard.key_up.h
    +    args.state.h_pressed_at = args.state.tick_count
    +  end
     
    -      s[:screeny] = 50 if s[:screeny] < 50
    -      s[:screeny] = (719 - $boxsize) if s[:screeny] > (719 - $boxsize)
    -      s[:screenx] = 0 if s[:screenx] < 0
    -      s[:screenx] = (1279 - $boxsize) if s[:screenx] > (1279 - $boxsize)
    +  # This code simplifies to if args.state.h_pressed_at has not been initialized, set it to false
    +  args.state.h_pressed_at ||= false
     
    -      s[:x] = ((s[:screenx] / 1279.0) * 2.0) - 1.0  # scale to -1.0 - 1.0 range
    -      s[:y] = ((s[:screeny] / 719.0) * 2.0) - 1.0   # scale to -1.0 - 1.0 range
    -    end
    +  if args.state.h_pressed_at
    +    args.outputs.labels << { x: 460, y: row_to_px(args, 4), text: "\"h\" was pressed at time: #{args.state.h_pressed_at}", size_enum: -1 }
    +  else
    +    args.outputs.labels << { x: 460, y: row_to_px(args, 4), text: "\"h\" has never been pressed.", size_enum: -1 }
    +  end
     
    -    color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ]
    -    args.outputs.primitives << [s[:screenx], s[:screeny], $boxsize, $boxsize, *color].solid
    -  }
    +  tick_help_text args
     end
     
    -def render_panel args
    -  s = args.audio[args.state.selected]
    -  return if s.nil?
    -  mouse_down = (args.state.mouse_held > 0)
    -
    -  args.outputs.primitives << [900, 450, 340, 250, 127, 127, 200, 255].solid
    -  args.outputs.primitives << [1075, 690, "Source ##{args.state.selected}", 3, 1, 255, 255, 255].label
    -  args.outputs.primitives << [910, 660, 1230, 660, 255, 255, 255].line
    -  args.outputs.primitives << [910, 650, "screen:    (#{s[:screenx].to_i}, #{s[:screeny].to_i})", 0, 0, 255, 255, 255].label
    -  args.outputs.primitives << [910, 625, "position:  (#{s[:x].round(5).to_s[0..6]}, #{s[:y].round(5).to_s[0..6]})", 0, 0, 255, 255, 255].label
    +def row_to_px args, row_number, y_offset = 20
    +  # This takes a row_number and converts it to pixels DragonRuby understands.
    +  # Row 0 starts 5 units below the top of the grid
    +  # Each row afterward is 20 units lower
    +  args.grid.top - 5 - (y_offset * row_number)
    +end
     
    -  slider = [1022, 586, 200, 7]
    -  if mouse_down && args.inputs.mouse.position.inside_rect?(slider)
    -    s[:pitch] = ((args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)) * 2.0
    -  end
    -  slidercolor = (s[:pitch] / 2.0) * 255
    -  args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid
    -  args.outputs.primitives << [910, 600, "pitch: #{s[:pitch].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label
    +# Don't worry about understanding the code within this method just yet.
    +# This method shows you the help text within the game.
    +def tick_help_text args
    +  return unless args.state.h_pressed_at
     
    -  slider = [1022, 561, 200, 7]
    -  if mouse_down && args.inputs.mouse.position.inside_rect?(slider)
    -    s[:gain] = (args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)
    -  end
    -  slidercolor = s[:gain] * 255
    -  args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid
    -  args.outputs.primitives << [910, 575, "gain: #{s[:gain].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label
    +  args.state.key_value_history      ||= {}
    +  args.state.key_down_value_history ||= {}
    +  args.state.key_held_value_history ||= {}
    +  args.state.key_up_value_history   ||= {}
     
    -  checkbox = [1022, 533, 10, 12]
    -  if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox)
    -    s[:looping] = !s[:looping]
    +  if (args.inputs.keyboard.key_down.truthy_keys.length > 0 ||
    +      args.inputs.keyboard.key_held.truthy_keys.length > 0 ||
    +      args.inputs.keyboard.key_up.truthy_keys.length > 0)
    +    args.state.help_available = true
    +    args.state.no_activity_debounce = nil
    +  else
    +    args.state.no_activity_debounce ||= 5.seconds
    +    args.state.no_activity_debounce -= 1
    +    if args.state.no_activity_debounce <= 0
    +      args.state.help_available = false
    +      args.state.key_value_history        = {}
    +      args.state.key_down_value_history   = {}
    +      args.state.key_held_value_history   = {}
    +      args.state.key_up_value_history     = {}
    +    end
       end
    -  checkboxcolor = s[:looping] ? 255 : 0
    -  args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid
    -  args.outputs.primitives << [910, 550, "looping:", 0, 0, 255, 255, 255].label
     
    -  checkbox = [1022, 508, 10, 12]
    -  if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox)
    -    s[:paused] = !s[:paused]
    -  end
    -  checkboxcolor = s[:paused] ? 255 : 0
    -  args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid
    -  args.outputs.primitives << [910, 525, "paused:", 0, 0, 255, 255, 255].label
    +  args.outputs.labels << { x: 10, y: row_to_px(args, 6), text: "This is the api for the keys you've pressed:", size_enum: -1, r: 180 }
     
    -  button = [910, 460, 320, 20]
    -  if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(button)
    -    args.audio.delete(args.state.selected)
    -    args.state.selected = 0
    +  if !args.state.help_available
    +    args.outputs.labels << [10, row_to_px(args, 7),  "Press a key and I'll show code to access the key and what value will be returned if you used the code.", small_font]
    +    return
       end
    -  args.outputs.primitives << [*button, 255, 0, 0, 255].solid
    -  args.outputs.primitives << [button[0] + (button[2] / 2), button[1]+20, "DELETE SOURCE", 0, 1, 255, 255, 0].label
    -end
     
    -def spawn_new_sound args, num
    -  input = nil
    -  input = "sounds/#{num}.#{(num == 6) ? 'ogg' : 'wav'}"
    +  args.outputs.labels << { x: 10 , y: row_to_px(args, 7), text: "args.inputs.keyboard",          size_enum: -2 }
    +  args.outputs.labels << { x: 330, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_down", size_enum: -2 }
    +  args.outputs.labels << { x: 650, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_held", size_enum: -2 }
    +  args.outputs.labels << { x: 990, y: row_to_px(args, 7), text: "args.inputs.keyboard.key_up",   size_enum: -2 }
     
    -  # Spawn randomly in an area that won't be covered by UI.
    -  screenx = (rand * 600.0) + 200.0
    -  screeny = (rand * 400.0) + 100.0
    +  fill_history args, :key_value_history,      :down_or_held, nil
    +  fill_history args, :key_down_value_history, :down,        :key_down
    +  fill_history args, :key_held_value_history, :held,        :key_held
    +  fill_history args, :key_up_value_history,   :up,          :key_up
     
    -  args.state.next_sound_index += 1
    +  render_help_labels args, :key_value_history,      :down_or_held, nil,      10
    +  render_help_labels args, :key_down_value_history, :down,        :key_down, 330
    +  render_help_labels args, :key_held_value_history, :held,        :key_held, 650
    +  render_help_labels args, :key_up_value_history,   :up,          :key_up,   990
    +end
     
    -  # you can hang anything on the audio hashes you want, so we store the
    -  #  actual screen position in here for convenience.
    -  args.audio[args.state.next_sound_index] = {
    -    input: input,
    -    screenx: screenx,
    -    screeny: screeny,
    -    x: ((screenx / 1279.0) * 2.0) - 1.0,  # scale to -1.0 - 1.0 range
    -    y: ((screeny / 719.0) * 2.0) - 1.0,   # scale to -1.0 - 1.0 range
    -    z: 0.0,
    -    gain: 1.0,
    -    pitch: 1.0,
    -    looping: true,
    -    paused: false
    -  }
    +def fill_history args, history_key, state_key, keyboard_method
    +  fill_single_history args, history_key, state_key, keyboard_method, :raw_key
    +  fill_single_history args, history_key, state_key, keyboard_method, :char
    +  args.inputs.keyboard.keys[state_key].each do |key_name|
    +    fill_single_history args, history_key, state_key, keyboard_method, key_name
    +  end
    +end
     
    -  args.state.selected = args.state.next_sound_index
    +def fill_single_history args, history_key, state_key, keyboard_method, key_name
    +  current_value = args.inputs.keyboard.send(key_name)
    +  if keyboard_method
    +    current_value = args.inputs.keyboard.send(keyboard_method).send(key_name)
    +  end
    +  args.state.as_hash[history_key][key_name] ||= []
    +  args.state.as_hash[history_key][key_name] << current_value
    +  args.state.as_hash[history_key][key_name] = args.state.as_hash[history_key][key_name].reverse.uniq.take(3).reverse
     end
     
    -def render_launcher args
    -  total = 6
    -  x = (1280 - (total * $boxsize * 3)) / 2
    -  y = 10
    -  args.outputs.primitives << [0, 0, 1280, ((y*2) + $boxsize), 127, 127, 127, 255].solid
    -  for i in 1..total
    -    args.outputs.primitives << [x, y, $boxsize, $boxsize, 255, 255, 255, 255].solid
    -    args.outputs.primitives << [x+8, y+28, i.to_s, 3, 0, 0, 0, 255, 255].label
    -    if args.inputs.mouse.click && args.inputs.mouse.click.point.inside_rect?([x, y, $boxsize, $boxsize])
    -      spawn_new_sound args, i
    +def render_help_labels args, history_key, state_key, keyboard_method, x
    +  idx = 8
    +  args.outputs.labels << args.state
    +                           .as_hash[history_key]
    +                           .keys
    +                           .reverse
    +                           .map
    +                           .with_index do |k, i|
    +    v = args.state.as_hash[history_key][k]
    +    current_value = args.inputs.keyboard.send(k)
    +    if keyboard_method
    +      current_value = args.inputs.keyboard.send(keyboard_method).send(k)
         end
    -    x = x + ($boxsize * 3)
    +    idx += 2
    +    [
    +      { x: x, y: row_to_px(args, idx + 0, 16), text: "    .#{k} is #{current_value || "nil"}", size_enum: -2 },
    +      { x: x, y: row_to_px(args, idx + 1, 16), text: "       was #{v}", size_enum: -2 }
    +    ]
       end
     end
     
    -def render_ui args
    -  render_launcher args
    -  render_panel args
    +
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.mouse.click ||
    +     args.inputs.keyboard.directional_vector ||
    +     args.inputs.keyboard.key_down.enter ||
    +     args.inputs.keyboard.key_down.escape
    +    args.state.key_event_occurred = true
    +  end
    +
    +  args.outputs.debug << { x: 0,   y: y - 50, w: 1280, h: 60 }.solid!
    +  args.outputs.debug << { x: 640, y: y,      text: text,
    +                          size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
    +  args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)",
    +                          size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
     end
     
    +
    +

    Input Basics - Moving A Sprite - main.rb

    +
    # ./samples/02_input_basics/01_moving_a_sprite/app/main.rb
     def tick args
    -  args.state.mouse_held ||= 0
    -  args.state.dragging_source ||= false
    -  args.state.selected ||= 0
    -  args.state.next_sound_index ||= 0
    +  # create a player and set default values
    +  # for the player's x, y, w (width), and h (height)
    +  args.state.player.x ||= 100
    +  args.state.player.y ||= 100
    +  args.state.player.w ||=  50
    +  args.state.player.h ||=  50
     
    -  if args.inputs.mouse.up
    -    args.state.mouse_held = 0
    -    args.state.dragging_source = false
    -  elsif args.inputs.mouse.down || (args.state.mouse_held > 0)
    -    args.state.mouse_held += 1
    -  else
    +  # render the player to the screen
    +  args.outputs.sprites << { x: args.state.player.x,
    +                            y: args.state.player.y,
    +                            w: args.state.player.w,
    +                            h: args.state.player.h,
    +                            path: 'sprites/square/green.png' }
    +
    +  # move the player around using the keyboard
    +  if args.inputs.up
    +    args.state.player.y += 10
    +  elsif args.inputs.down
    +    args.state.player.y -= 10
       end
     
    -  args.outputs.background_color = [ 0, 0, 0, 255 ]
    -  render_sources args
    -  render_ui args
    +  if args.inputs.left
    +    args.state.player.x -= 10
    +  elsif args.inputs.right
    +    args.state.player.x += 10
    +  end
     end
     
    +$gtk.reset
    +
     
    -

    Rendering Basics - Sound Synthesis - main.rb

    -
    # ./samples/01_rendering_basics/07_sound_synthesis/app/main.rb
    -begin # region: top level tick methods
    -  def tick args
    -    defaults args
    -    render args
    -    input args
    -    process_audio_queue args
    -  end
    +

    Input Basics - Mouse - main.rb

    +
    # ./samples/02_input_basics/02_mouse/app/main.rb
    +=begin
     
    -  def defaults args
    -    args.state.sine_waves      ||= {}
    -    args.state.square_waves    ||= {}
    -    args.state.saw_tooth_waves ||= {}
    -    args.state.triangle_waves  ||= {}
    -    args.state.audio_queue     ||= []
    -    args.state.buttons         ||= [
    -      (frequency_buttons args),
    -      (sine_wave_note_buttons args),
    -      (bell_buttons args),
    -      (square_wave_note_buttons args),
    -      (saw_tooth_wave_note_buttons args),
    -      (triangle_wave_note_buttons args),
    -    ].flatten
    -  end
    +APIs that haven't been encountered in a previous sample apps:
     
    -  def render args
    -    args.outputs.borders << args.state.buttons.map { |b| b[:border] }
    -    args.outputs.labels  << args.state.buttons.map { |b| b[:label]  }
    -    args.outputs.labels  << args.layout
    -                              .rect(row: 0, col: 11.5)
    -                              .yield_self { |r| r.merge y: r.y + r.h }
    -                              .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.",
    -                                     alignment_enum: 1)
    -  end
    +- args.inputs.mouse.click: This property will be set if the mouse was clicked.
    +- args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
    +- args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
    +- args.inputs.mouse.click.point.created_at_elapsed: How many frames have passed
    +  since the click event.
     
    +Reminder:
     
    -  def input args
    -    args.state.buttons.each do |b|
    -      if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? b[:rect])
    -        parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", "
    -        args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}"
    -        send b[:method_to_call], args, b
    -      end
    -    end
    +- args.state.PROPERTY: The state property on args is a dynamic
    +  structure. You can define ANY property here with ANY type of
    +  arbitrary nesting. Properties defined on args.state will be retained
    +  across frames. If you attempt access a property that doesn't exist
    +  on args.state, it will simply return nil (no exception will be thrown).
     
    -    if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half }))
    -      args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan'
    -    end
    -  end
    +=end
     
    -  def process_audio_queue args
    -    to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count }
    -    args.state.audio_queue -= to_queue
    -    to_queue.each { |a| args.audio[a[:id]] = a }
    +# This code demonstrates DragonRuby mouse input
     
    -    args.audio.find_all { |k, v| v[:decay_rate] }
    -      .each     { |k, v| v[:gain] -= v[:decay_rate] }
    +# To see if the a mouse click occurred
    +# Use args.inputs.mouse.click
    +# Which returns a boolean
     
    -    sounds_to_stop = args.audio
    -                       .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] }
    -                       .map { |k, v| k }
    +# To see where a mouse click occurred
    +# Use args.inputs.mouse.click.point.x AND
    +# args.inputs.mouse.click.point.y
     
    -    sounds_to_stop.each { |k| args.audio.delete k }
    -  end
    -end
    +# To see which frame the click occurred
    +# Use args.inputs.mouse.click.created_at
     
    -begin # region: button definitions, ui layout, callback functions
    -  def button args, opts
    -    button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1))
    +# To see how many frames its been since the click occurred
    +# Use args.inputs.mouse.click.creat_at_elapsed
     
    -    button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0
    +# Saving the click in args.state can be quite useful
     
    -    label_offset_x = 5
    -    label_offset_y = 30
    +def tick args
    +  tick_instructions args, "Sample app shows how mouse events are registered and how to measure elapsed time."
    +  x = 460
     
    -    button_def[:label]  = button_def[:rect].merge text: opts[:text],
    -                                                  size_enum: -2.5,
    -                                                  x: button_def[:rect].x + label_offset_x,
    -                                                  y: button_def[:rect].y + label_offset_y
    +  args.outputs.labels << small_label(args, x, 11, "Mouse input: args.inputs.mouse")
     
    -    button_def
    +  if args.inputs.mouse.click
    +    args.state.last_mouse_click = args.inputs.mouse.click
       end
     
    -  def play_sine_wave args, sender
    -    queue_sine_wave args,
    -                    frequency: sender[:frequency],
    -                    duration: 1.seconds,
    -                    fade_out: true
    +  if args.state.last_mouse_click
    +    click = args.state.last_mouse_click
    +    args.outputs.labels << small_label(args, x, 12, "Mouse click happened at: #{click.created_at}")
    +    args.outputs.labels << small_label(args, x, 13, "Mouse clicked #{click.created_at_elapsed} ticks ago")
    +    args.outputs.labels << small_label(args, x, 14, "Mouse click location: #{click.point.x}, #{click.point.y}")
    +  else
    +    args.outputs.labels << small_label(args, x, 12, "Mouse click has not occurred yet.")
    +    args.outputs.labels << small_label(args, x, 13, "Please click mouse.")
       end
    +end
     
    -  def play_note args, sender
    -    method_to_call = :queue_sine_wave
    -    method_to_call = :queue_square_wave    if sender[:type] == :square
    -    method_to_call = :queue_saw_tooth_wave if sender[:type] == :saw_tooth
    -    method_to_call = :queue_triangle_wave  if sender[:type] == :triangle
    -    method_to_call = :queue_bell           if sender[:type] == :bell
    +def small_label args, x, row, message
    +  # This method effectively combines the row_to_px and small_font methods
    +  # It changes the given row value to a DragonRuby pixel value
    +  # and adds the customization parameters
    +  { x: x, y: row_to_px(args, row), text: message, alignment_enum: -2 }
    +end
     
    -    send method_to_call, args,
    -         frequency: (frequency_for note: sender[:note], octave: sender[:octave]),
    -         duration: 1.seconds,
    -         fade_out: true
    +def row_to_px args, row_number
    +  args.grid.top.shift_down(5).shift_down(20 * row_number)
    +end
    +
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.mouse.click ||
    +     args.inputs.keyboard.directional_vector ||
    +     args.inputs.keyboard.key_down.enter ||
    +     args.inputs.keyboard.key_down.escape
    +    args.state.key_event_occurred = true
       end
     
    -  def frequency_buttons args
    -    [
    -      (button args,
    -              row: 4.0, col: 0, text: "300hz",
    -              frequency: 300,
    -              method_to_call: :play_sine_wave),
    -      (button args,
    -              row: 5.0, col: 0, text: "400hz",
    -              frequency: 400,
    -              method_to_call: :play_sine_wave),
    -      (button args,
    -              row: 6.0, col: 0, text: "500hz",
    -              frequency: 500,
    -              method_to_call: :play_sine_wave),
    -    ]
    +  args.outputs.debug << { x: 0,   y: y - 50, w: 1280, h: 60 }.solid!
    +  args.outputs.debug << { x: 640, y: y, text: text, size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
    +  args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)", size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
    +end
    +
    +
    +

    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:
    +
    +- args.outputus.borders: An array. Values in this array will be rendered as
    +  unfilled rectangles on the screen.
    +- ARRAY#inside_rect?: An array with at least two values is considered a point. An array
    +  with at least four values is considered a rect. The inside_rect? function returns true
    +  or false depending on if the point is inside the rect.
    +
    +  ```
    +  # Point:  x: 100, y: 100
    +  # Rect:   x: 0, y: 0, w: 500, h: 500
    +  # Result: true
    +
    +  [100, 100].inside_rect? [0, 0, 500, 500]
    +  ```
    +
    +  ```
    +  # Point:  x: 100, y: 100
    +  # Rect:   x: 300, y: 300, w: 100, h: 100
    +  # Result: false
    +
    +  [100, 100].inside_rect? [300, 300, 100, 100]
    +  ```
    +
    +- args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
    +- args.inputs.mouse.click.point.created_at_elapsed: How many frames have passed
    +  since the click event.
    +
    +=end
    +
    +# To determine whether a point is in a rect
    +# Use point.inside_rect? rect
    +
    +# This is useful to determine if a click occurred in a rect
    +
    +def tick args
    +  tick_instructions args, "Sample app shows how to determing if a click happened inside a rectangle."
    +
    +  x = 460
    +
    +  args.outputs.labels << small_label(args, x, 15, "Click inside the blue box maybe ---->")
    +
    +  box = { x: 785, y: 370, w: 50, h: 50, r: 0, g: 0, b: 170 }
    +  args.outputs.borders << box
    +
    +  # Saves the most recent click into args.state
    +  # Unlike the other components of args,
    +  # args.state does not reset every tick.
    +  if args.inputs.mouse.click
    +    args.state.last_mouse_click = args.inputs.mouse.click
       end
     
    -  def sine_wave_note_buttons args
    -    [
    -      (button args,
    -              row: 1.5, col: 2, text: "Sine C4",
    -              note: :c, octave: 4, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 2.5, col: 2, text: "Sine D4",
    -              note: :d, octave: 4, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 3.5, col: 2, text: "Sine E4",
    -              note: :e, octave: 4, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 4.5, col: 2, text: "Sine F4",
    -              note: :f, octave: 4, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 5.5, col: 2, text: "Sine G4",
    -              note: :g, octave: 4, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 6.5, col: 2, text: "Sine A5",
    -              note: :a, octave: 5, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 7.5, col: 2, text: "Sine B5",
    -              note: :b, octave: 5, type: :sine, method_to_call: :play_note),
    -      (button args,
    -              row: 8.5, col: 2, text: "Sine C5",
    -              note: :c, octave: 5, type: :sine, method_to_call: :play_note),
    -    ]
    +  if args.state.last_mouse_click
    +    if args.state.last_mouse_click.point.inside_rect? box
    +      args.outputs.labels << small_label(args, x, 16, "Mouse click happened *inside* the box.")
    +    else
    +      args.outputs.labels << small_label(args, x, 16, "Mouse click happened *outside* the box.")
    +    end
    +  else
    +    args.outputs.labels << small_label(args, x, 16, "Mouse click has not occurred yet.")
       end
    -
    -  def square_wave_note_buttons args
    -    [
    -      (button args,
    -              row: 1.5, col: 6, text: "Square C4",
    -              note: :c, octave: 4, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 2.5, col: 6, text: "Square D4",
    -              note: :d, octave: 4, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 3.5, col: 6, text: "Square E4",
    -              note: :e, octave: 4, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 4.5, col: 6, text: "Square F4",
    -              note: :f, octave: 4, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 5.5, col: 6, text: "Square G4",
    -              note: :g, octave: 4, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 6.5, col: 6, text: "Square A5",
    -              note: :a, octave: 5, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 7.5, col: 6, text: "Square B5",
    -              note: :b, octave: 5, type: :square, method_to_call: :play_note),
    -      (button args,
    -              row: 8.5, col: 6, text: "Square C5",
    -              note: :c, octave: 5, type: :square, method_to_call: :play_note),
    -    ]
    -  end
    -  def saw_tooth_wave_note_buttons args
    -    [
    -      (button args,
    -              row: 1.5, col: 8, text: "Saw C4",
    -              note: :c, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 2.5, col: 8, text: "Saw D4",
    -              note: :d, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 3.5, col: 8, text: "Saw E4",
    -              note: :e, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 4.5, col: 8, text: "Saw F4",
    -              note: :f, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 5.5, col: 8, text: "Saw G4",
    -              note: :g, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 6.5, col: 8, text: "Saw A5",
    -              note: :a, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 7.5, col: 8, text: "Saw B5",
    -              note: :b, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    -      (button args,
    -              row: 8.5, col: 8, text: "Saw C5",
    -              note: :c, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    -    ]
    -  end
    -
    -  def triangle_wave_note_buttons args
    -    [
    -      (button args,
    -              row: 1.5, col: 10, text: "Triangle C4",
    -              note: :c, octave: 4, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 2.5, col: 10, text: "Triangle D4",
    -              note: :d, octave: 4, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 3.5, col: 10, text: "Triangle E4",
    -              note: :e, octave: 4, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 4.5, col: 10, text: "Triangle F4",
    -              note: :f, octave: 4, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 5.5, col: 10, text: "Triangle G4",
    -              note: :g, octave: 4, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 6.5, col: 10, text: "Triangle A5",
    -              note: :a, octave: 5, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 7.5, col: 10, text: "Triangle B5",
    -              note: :b, octave: 5, type: :triangle, method_to_call: :play_note),
    -      (button args,
    -              row: 8.5, col: 10, text: "Triangle C5",
    -              note: :c, octave: 5, type: :triangle, method_to_call: :play_note),
    -    ]
    -  end
    -
    -  def bell_buttons args
    -    [
    -      (button args,
    -              row: 1.5, col: 4, text: "Bell C4",
    -              note: :c, octave: 4, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 2.5, col: 4, text: "Bell D4",
    -              note: :d, octave: 4, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 3.5, col: 4, text: "Bell E4",
    -              note: :e, octave: 4, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 4.5, col: 4, text: "Bell F4",
    -              note: :f, octave: 4, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 5.5, col: 4, text: "Bell G4",
    -              note: :g, octave: 4, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 6.5, col: 4, text: "Bell A5",
    -              note: :a, octave: 5, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 7.5, col: 4, text: "Bell B5",
    -              note: :b, octave: 5, type: :bell, method_to_call: :play_note),
    -      (button args,
    -              row: 8.5, col: 4, text: "Bell C5",
    -              note: :c, octave: 5, type: :bell, method_to_call: :play_note),
    -    ]
    -  end
    -end
    -
    -begin # region: wave generation
    -  begin # sine wave
    -    def defaults_sine_wave_for
    -      { frequency: 440, sample_rate: 48000 }
    -    end
    -
    -    def sine_wave_for opts = {}
    -      opts = defaults_sine_wave_for.merge opts
    -      frequency   = opts[:frequency]
    -      sample_rate = opts[:sample_rate]
    -      period_size = (sample_rate.fdiv frequency).ceil
    -      period_size.map_with_index do |i|
    -        Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i)
    -      end.to_a
    -    end
    -
    -    def defaults_queue_sine_wave
    -      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    -    end
    -
    -    def queue_sine_wave args, opts = {}
    -      opts        = defaults_queue_sine_wave.merge opts
    -      frequency   = opts[:frequency]
    -      sample_rate = 48000
    -
    -      sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate
    -      args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate
    -
    -      proc = lambda do
    -        generate_audio_data args.state.sine_waves[frequency], sample_rate
    -      end
    -
    -      audio_state = new_audio_state args, opts
    -      audio_state[:input] = [1, sample_rate, proc]
    -      queue_audio args, audio_state: audio_state, wave: sine_wave
    -    end
    -  end
    -
    -  begin # region: square wave
    -    def defaults_square_wave_for
    -      { frequency: 440, sample_rate: 48000 }
    -    end
    -
    -    def square_wave_for opts = {}
    -      opts = defaults_square_wave_for.merge opts
    -      sine_wave = sine_wave_for opts
    -      sine_wave.map do |v|
    -        if v >= 0
    -          1.0
    -        else
    -          -1.0
    -        end
    -      end.to_a
    -    end
    -
    -    def defaults_queue_square_wave
    -      { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
    -    end
    -
    -    def queue_square_wave args, opts = {}
    -      opts        = defaults_queue_square_wave.merge opts
    -      frequency   = opts[:frequency]
    -      sample_rate = 48000
    -
    -      square_wave = square_wave_for frequency: frequency, sample_rate: sample_rate
    -      args.state.square_waves[frequency] ||= square_wave_for frequency: frequency, sample_rate: sample_rate
    -
    -      proc = lambda do
    -        generate_audio_data args.state.square_waves[frequency], sample_rate
    -      end
    -
    -      audio_state = new_audio_state args, opts
    -      audio_state[:input] = [1, sample_rate, proc]
    -      queue_audio args, audio_state: audio_state, wave: square_wave
    -    end
    -  end
    -
    -  begin # region: saw tooth wave
    -    def defaults_saw_tooth_wave_for
    -      { frequency: 440, sample_rate: 48000 }
    -    end
    -
    -    def saw_tooth_wave_for opts = {}
    -      opts = defaults_saw_tooth_wave_for.merge opts
    -      sine_wave = sine_wave_for opts
    -      period_size = sine_wave.length
    -      sine_wave.map_with_index do |v, i|
    -        (((i % period_size).fdiv period_size) * 2) - 1
    -      end
    -    end
    -
    -    def defaults_queue_saw_tooth_wave
    -      { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
    -    end
    -
    -    def queue_saw_tooth_wave args, opts = {}
    -      opts        = defaults_queue_saw_tooth_wave.merge opts
    -      frequency   = opts[:frequency]
    -      sample_rate = 48000
    -
    -      saw_tooth_wave = saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
    -      args.state.saw_tooth_waves[frequency] ||= saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
    -
    -      proc = lambda do
    -        generate_audio_data args.state.saw_tooth_waves[frequency], sample_rate
    -      end
    -
    -      audio_state = new_audio_state args, opts
    -      audio_state[:input] = [1, sample_rate, proc]
    -      queue_audio args, audio_state: audio_state, wave: saw_tooth_wave
    -    end
    -  end
    -
    -  begin # region: triangle wave
    -    def defaults_triangle_wave_for
    -      { frequency: 440, sample_rate: 48000 }
    -    end
    -
    -    def triangle_wave_for opts = {}
    -      opts = defaults_saw_tooth_wave_for.merge opts
    -      sine_wave = sine_wave_for opts
    -      period_size = sine_wave.length
    -      sine_wave.map_with_index do |v, i|
    -        ratio = (i.fdiv period_size)
    -        if ratio <= 0.5
    -          (ratio * 4) - 1
    -        else
    -          ratio -= 0.5
    -          1 - (ratio * 4)
    -        end
    -      end
    -    end
    -
    -    def defaults_queue_triangle_wave
    -      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    -    end
    -
    -    def queue_triangle_wave args, opts = {}
    -      opts        = defaults_queue_triangle_wave.merge opts
    -      frequency   = opts[:frequency]
    -      sample_rate = 48000
    -
    -      triangle_wave = triangle_wave_for frequency: frequency, sample_rate: sample_rate
    -      args.state.triangle_waves[frequency] ||= triangle_wave_for frequency: frequency, sample_rate: sample_rate
    -
    -      proc = lambda do
    -        generate_audio_data args.state.triangle_waves[frequency], sample_rate
    -      end
    -
    -      audio_state = new_audio_state args, opts
    -      audio_state[:input] = [1, sample_rate, proc]
    -      queue_audio args, audio_state: audio_state, wave: triangle_wave
    -    end
    -  end
    -
    -  begin # region: bell
    -    def defaults_queue_bell
    -      { frequency: 440, duration: 1.seconds, queue_in: 0 }
    -    end
    -
    -    def queue_bell args, opts = {}
    -      (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b }
    -    end
    -
    -    def bell_harmonics
    -      [
    -        { frequency_ratio: 0.5, duration_ratio: 1.00 },
    -        { frequency_ratio: 1.0, duration_ratio: 0.80 },
    -        { frequency_ratio: 2.0, duration_ratio: 0.60 },
    -        { frequency_ratio: 3.0, duration_ratio: 0.40 },
    -        { frequency_ratio: 4.2, duration_ratio: 0.25 },
    -        { frequency_ratio: 5.4, duration_ratio: 0.20 },
    -        { frequency_ratio: 6.8, duration_ratio: 0.15 }
    -      ]
    -    end
    -
    -    def defaults_bell_to_sine_waves
    -      { frequency: 440, duration: 1.seconds, queue_in: 0 }
    -    end
    -
    -    def bell_to_sine_waves opts = {}
    -      opts = defaults_bell_to_sine_waves.merge opts
    -      bell_harmonics.map do |b|
    -        {
    -          frequency: opts[:frequency] * b[:frequency_ratio],
    -          duration:  opts[:duration] * b[:duration_ratio],
    -          queue_in:  opts[:queue_in],
    -          gain:      (1.fdiv bell_harmonics.length),
    -          fade_out:  true
    -        }
    -      end
    -    end
    -  end
    -
    -  begin # audio entity construction
    -    def generate_audio_data sine_wave, sample_rate
    -      sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil
    -      copy_count  = (sample_size.fdiv sine_wave.length).ceil
    -      sine_wave * copy_count
    -    end
    -
    -    def defaults_new_audio_state
    -      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    -    end
    -
    -    def new_audio_state args, opts = {}
    -      opts        = defaults_new_audio_state.merge opts
    -      decay_rate  = 0
    -      decay_rate  = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out]
    -      frequency   = opts[:frequency]
    -      sample_rate = 48000
    -
    -      {
    -        id:               (new_id! args),
    -        frequency:        frequency,
    -        sample_rate:      48000,
    -        stop_at:          args.tick_count + opts[:queue_in] + opts[:duration],
    -        gain:             opts[:gain].to_f,
    -        queue_at:         args.state.tick_count + opts[:queue_in],
    -        decay_rate:       decay_rate,
    -        pitch:            1.0,
    -        looping:          true,
    -        paused:           false
    -      }
    -    end
    -
    -    def queue_audio args, opts = {}
    -      graph_wave args, opts[:wave], opts[:audio_state][:frequency]
    -      args.state.audio_queue << opts[:audio_state]
    -    end
    -
    -    def new_id! args
    -      args.state.audio_id ||= 0
    -      args.state.audio_id  += 1
    -    end
    -
    -    def graph_wave args, wave, frequency
    -      if args.state.tick_count != args.state.graphed_at
    -        args.outputs.static_lines.clear
    -        args.outputs.static_sprites.clear
    -      end
    -
    -      wave = wave
    -
    -      r, g, b = frequency.to_i % 85,
    -                frequency.to_i % 170,
    -                frequency.to_i % 255
    -
    -      starting_rect = args.layout.rect(row: 5, col: 13)
    -      x_scale    = 10
    -      y_scale    = 100
    -      max_points = 25
    -
    -      points = wave
    -      if wave.length > max_points
    -        resolution = wave.length.idiv max_points
    -        points = wave.find_all.with_index { |y, i| (i % resolution == 0) }
    -      end
    -
    -      args.outputs.static_lines << points.map_with_index do |y, x|
    -        next_y = points[x + 1]
    -
    -        if next_y
    -          {
    -            x:  starting_rect.x + (x * x_scale),
    -            y:  starting_rect.y + starting_rect.h.half + y_scale * y,
    -            x2: starting_rect.x + ((x + 1) * x_scale),
    -            y2: starting_rect.y + starting_rect.h.half + y_scale * next_y,
    -            r:  r,
    -            g:  g,
    -            b:  b
    -          }
    -        end
    -      end
    -
    -      args.outputs.static_sprites << points.map_with_index do |y, x|
    -        {
    -          x:  (starting_rect.x + (x * x_scale)) - 2,
    -          y:  (starting_rect.y + starting_rect.h.half + y_scale * y) - 2,
    -          w:  4,
    -          h:  4,
    -          path: 'sprites/square-white.png',
    -          r: r,
    -          g: g,
    -          b: b
    -        }
    -      end
    -
    -      args.state.graphed_at = args.state.tick_count
    -    end
    -  end
    -
    -  begin # region: musical note mapping
    -    def defaults_frequency_for
    -      { note: :a, octave: 5, sharp:  false, flat:   false }
    -    end
    -
    -    def frequency_for opts = {}
    -      opts = defaults_frequency_for.merge opts
    -      octave_offset_multiplier  = opts[:octave] - 5
    -      note = note_frequencies_octave_5[opts[:note]]
    -      if octave_offset_multiplier < 0
    -        note = note * 1 / (octave_offset_multiplier.abs + 1)
    -      elsif octave_offset_multiplier > 0
    -        note = note * (octave_offset_multiplier.abs + 1) / 1
    -      end
    -      note
    -    end
    -
    -    def note_frequencies_octave_5
    -      {
    -        a: 440.0,
    -        a_sharp: 466.16, b_flat: 466.16,
    -        b: 493.88,
    -        c: 523.25,
    -        c_sharp: 554.37, d_flat: 587.33,
    -        d: 587.33,
    -        d_sharp: 622.25, e_flat: 659.25,
    -        e: 659.25,
    -        f: 698.25,
    -        f_sharp: 739.99, g_flat: 739.99,
    -        g: 783.99,
    -        g_sharp: 830.61, a_flat: 830.61
    -      }
    -    end
    -  end
    -end
    -
    -$gtk.reset
    -
    -
    -

    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:
    -
    -- args.inputs.keyboard.key_up.KEY: The value of the properties will be set
    -  to the frame  that the key_up event occurred (the frame correlates
    -  to args.state.tick_count). Otherwise the value will be nil. For a
    -  full listing of keys, take a look at mygame/documentation/06-keyboard.md.
    -- args.state.PROPERTY: The state property on args is a dynamic
    -  structure. You can define ANY property here with ANY type of
    -  arbitrary nesting. Properties defined on args.state will be retained
    -  across frames. If you attempt access a property that doesn't exist
    -  on args.state, it will simply return nil (no exception will be thrown).
    -
    -=end
    -
    -# Along with outputs, inputs are also an essential part of video game development
    -# DragonRuby can take input from keyboards, mouse, and controllers.
    -# This sample app will cover keyboard input.
    -
    -# args.inputs.keyboard.key_up.a will check to see if the a key has been pressed
    -# This will work with the other keys as well
    -
    -
    -def tick args
    -  tick_instructions args, "Sample app shows how keyboard events are registered and accessed.", 360
    -  # Notice how small_font accounts for all the remaining parameters
    -  args.outputs.labels << [460, row_to_px(args, 0), "Current game time: #{args.state.tick_count}", small_font]
    -  args.outputs.labels << [460, row_to_px(args, 2), "Keyboard input: args.inputs.keyboard.key_up.h", small_font]
    -  args.outputs.labels << [460, row_to_px(args, 3), "Press \"h\" on the keyboard.", small_font]
    -
    -  # Input on a specifc key can be found through args.inputs.keyboard.key_up followed by the key
    -  if args.inputs.keyboard.key_up.h
    -    args.state.h_pressed_at = args.state.tick_count
    -  end
    -
    -  # This code simplifies to if args.state.h_pressed_at has not been initialized, set it to false
    -  args.state.h_pressed_at ||= false
    -
    -  if args.state.h_pressed_at
    -    args.outputs.labels << [460, row_to_px(args, 4), "\"h\" was pressed at time: #{args.state.h_pressed_at}", small_font]
    -  else
    -    args.outputs.labels << [460, row_to_px(args, 4), "\"h\" has never been pressed.", small_font]
    -  end
    -
    -  tick_help_text args
    -end
    -
    -def small_font
    -  # This method provides some values for the construction of labels
    -  # Specifically, Size, Alignment, & RGBA
    -  # This makes it so that custom parameters don't have to be repeatedly typed.
    -  # Additionally "small_font" provides programmers with more information than some numbers
    -  [-2, 0, 0, 0, 0, 255]
    -end
    -
    -def row_to_px args, row_number
    -  # This takes a row_number and converts it to pixels DragonRuby understands.
    -  # Row 0 starts 5 units below the top of the grid
    -  # Each row afterward is 20 units lower
    -  args.grid.top.shift_down(5).shift_down(20 * row_number)
    -end
    -
    -# Don't worry about understanding the code within this method just yet.
    -# This method shows you the help text within the game.
    -def tick_help_text args
    -  return unless args.state.h_pressed_at
    -
    -  args.state.key_value_history      ||= {}
    -  args.state.key_down_value_history ||= {}
    -  args.state.key_held_value_history ||= {}
    -  args.state.key_up_value_history   ||= {}
    -
    -  if (args.inputs.keyboard.key_down.truthy_keys.length > 0 ||
    -      args.inputs.keyboard.key_held.truthy_keys.length > 0 ||
    -      args.inputs.keyboard.key_up.truthy_keys.length > 0)
    -    args.state.help_available = true
    -    args.state.no_activity_debounce = nil
    -  else
    -    args.state.no_activity_debounce ||= 5.seconds
    -    args.state.no_activity_debounce -= 1
    -    if args.state.no_activity_debounce <= 0
    -      args.state.help_available = false
    -      args.state.key_value_history        = {}
    -      args.state.key_down_value_history   = {}
    -      args.state.key_held_value_history   = {}
    -      args.state.key_up_value_history     = {}
    -    end
    -  end
    -
    -  args.outputs.labels << [10, row_to_px(args, 6), "Advanced Help:", small_font]
    -
    -  if !args.state.help_available
    -    args.outputs.labels << [10, row_to_px(args, 7),  "Press a key and I'll show code to access the key and what value will be returned if you used the code.", small_font]
    -    return
    -  end
    -
    -  args.outputs.labels << [10 , row_to_px(args, 7), "args.inputs.keyboard", small_font]
    -  args.outputs.labels << [330, row_to_px(args, 7), "args.inputs.keyboard.key_down", small_font]
    -  args.outputs.labels << [650, row_to_px(args, 7), "args.inputs.keyboard.key_held", small_font]
    -  args.outputs.labels << [990, row_to_px(args, 7), "args.inputs.keyboard.key_up", small_font]
    -
    -  fill_history args, :key_value_history,      :down_or_held, nil
    -  fill_history args, :key_down_value_history, :down,        :key_down
    -  fill_history args, :key_held_value_history, :held,        :key_held
    -  fill_history args, :key_up_value_history,   :up,          :key_up
    -
    -  render_help_labels args, :key_value_history,      :down_or_held, nil,      10
    -  render_help_labels args, :key_down_value_history, :down,        :key_down, 330
    -  render_help_labels args, :key_held_value_history, :held,        :key_held, 650
    -  render_help_labels args, :key_up_value_history,   :up,          :key_up,   990
    -end
    -
    -def fill_history args, history_key, state_key, keyboard_method
    -  fill_single_history args, history_key, state_key, keyboard_method, :raw_key
    -  fill_single_history args, history_key, state_key, keyboard_method, :char
    -  args.inputs.keyboard.keys[state_key].each do |key_name|
    -    fill_single_history args, history_key, state_key, keyboard_method, key_name
    -  end
    -end
    -
    -def fill_single_history args, history_key, state_key, keyboard_method, key_name
    -  current_value = args.inputs.keyboard.send(key_name)
    -  if keyboard_method
    -    current_value = args.inputs.keyboard.send(keyboard_method).send(key_name)
    -  end
    -  args.state.as_hash[history_key][key_name] ||= []
    -  args.state.as_hash[history_key][key_name] << current_value
    -  args.state.as_hash[history_key][key_name] = args.state.as_hash[history_key][key_name].reverse.uniq.take(3).reverse
    -end
    -
    -def render_help_labels args, history_key, state_key, keyboard_method, x
    -  idx = 8
    -  args.outputs.labels << args.state
    -                           .as_hash[history_key]
    -                           .keys
    -                           .reverse
    -                           .map
    -                           .with_index do |k, i|
    -    v = args.state.as_hash[history_key][k]
    -    current_value = args.inputs.keyboard.send(k)
    -    if keyboard_method
    -      current_value = args.inputs.keyboard.send(keyboard_method).send(k)
    -    end
    -    idx += 2
    -    [
    -      [x, row_to_px(args, idx - 2),
    -       "    .#{k} is #{current_value || "nil"}",
    -       small_font],
    -      [x, row_to_px(args, idx - 1),
    -       "       was #{v}",
    -       small_font]
    -    ]
    -  end
    -end
    -
    -
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.mouse.click ||
    -     args.inputs.keyboard.directional_vector ||
    -     args.inputs.keyboard.key_down.enter ||
    -     args.inputs.keyboard.key_down.escape
    -    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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    -end
    -
    -
    -

    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:
    -
    -- args.inputs.mouse.click: This property will be set if the mouse was clicked.
    -- args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
    -- args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
    -- args.inputs.mouse.click.point.created_at_elapsed: How many frames have passed
    -  since the click event.
    -
    -Reminder:
    -
    -- args.state.PROPERTY: The state property on args is a dynamic
    -  structure. You can define ANY property here with ANY type of
    -  arbitrary nesting. Properties defined on args.state will be retained
    -  across frames. If you attempt access a property that doesn't exist
    -  on args.state, it will simply return nil (no exception will be thrown).
    -
    -=end
    -
    -# This code demonstrates DragonRuby mouse input
    -
    -# To see if the a mouse click occurred
    -# Use args.inputs.mouse.click
    -# Which returns a boolean
    -
    -# To see where a mouse click occurred
    -# Use args.inputs.mouse.click.point.x AND
    -# args.inputs.mouse.click.point.y
    -
    -# To see which frame the click occurred
    -# Use args.inputs.mouse.click.created_at
    -
    -# To see how many frames its been since the click occurred
    -# Use args.inputs.mouse.click.creat_at_elapsed
    -
    -# Saving the click in args.state can be quite useful
    -
    -def tick args
    -  tick_instructions args, "Sample app shows how mouse events are registered and how to measure elapsed time."
    -  x = 460
    -
    -  args.outputs.labels << small_label(args, x, 11, "Mouse input: args.inputs.mouse")
    -
    -  if args.inputs.mouse.click
    -    args.state.last_mouse_click = args.inputs.mouse.click
    -  end
    -
    -  if args.state.last_mouse_click
    -    click = args.state.last_mouse_click
    -    args.outputs.labels << small_label(args, x, 12, "Mouse click happened at: #{click.created_at}")
    -    args.outputs.labels << small_label(args, x, 13, "Mouse clicked #{click.created_at_elapsed} ticks ago")
    -    args.outputs.labels << small_label(args, x, 14, "Mouse click location: #{click.point.x}, #{click.point.y}")
    -  else
    -    args.outputs.labels << small_label(args, x, 12, "Mouse click has not occurred yet.")
    -    args.outputs.labels << small_label(args, x, 13, "Please click mouse.")
    -  end
    -end
    +end
     
     def small_label args, x, row, message
    -  # This method effectively combines the row_to_px and small_font methods
    -  # It changes the given row value to a DragonRuby pixel value
    -  # and adds the customization parameters
    -  [x, row_to_px(args, row), message, small_font]
    -end
    -
    -def small_font
    -  [-2, 0, 0, 0, 0, 255]
    +  { x: x, y: row_to_px(args, row), text: message, size_enum: -2 }
     end
     
     def row_to_px args, row_number
    @@ -4819,107 +4298,13 @@ def tick_instructions args, text, y = 715
         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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    +  args.outputs.debug << { x: 0,   y: y - 50, w: 1280, h: 60 }.solid!
    +  args.outputs.debug << { x: 640, y: y, text: text, size_enum: 1, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
    +  args.outputs.debug << { x: 640, y: y - 25, text: "(click to dismiss instructions)", size_enum: -2, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
     end
     
     
    -

    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:
    -
    -- args.outputus.borders: An array. Values in this array will be rendered as
    -  unfilled rectangles on the screen.
    -- ARRAY#inside_rect?: An array with at least two values is considered a point. An array
    -  with at least four values is considered a rect. The inside_rect? function returns true
    -  or false depending on if the point is inside the rect.
    -
    -  ```
    -  # Point:  x: 100, y: 100
    -  # Rect:   x: 0, y: 0, w: 500, h: 500
    -  # Result: true
    -
    -  [100, 100].inside_rect? [0, 0, 500, 500]
    -  ```
    -
    -  ```
    -  # Point:  x: 100, y: 100
    -  # Rect:   x: 300, y: 300, w: 100, h: 100
    -  # Result: false
    -
    -  [100, 100].inside_rect? [300, 300, 100, 100]
    -  ```
    -
    -- args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
    -- args.inputs.mouse.click.point.created_at_elapsed: How many frames have passed
    -  since the click event.
    -
    -=end
    -
    -# To determine whether a point is in a rect
    -# Use point.inside_rect? rect
    -
    -# This is useful to determine if a click occurred in a rect
    -
    -def tick args
    -  tick_instructions args, "Sample app shows how to determing if a click happened inside a rectangle."
    -
    -  x = 460
    -
    -  args.outputs.labels << small_label(args, x, 15, "Click inside the blue box maybe ---->")
    -
    -  box = [785, 370, 50, 50, 0, 0, 170]
    -  args.outputs.borders << box
    -
    -  # Saves the most recent click into args.state
    -  # Unlike the other components of args,
    -  # args.state does not reset every tick.
    -  if args.inputs.mouse.click
    -    args.state.last_mouse_click = args.inputs.mouse.click
    -  end
    -
    -  if args.state.last_mouse_click
    -    if args.state.last_mouse_click.point.inside_rect? box
    -      args.outputs.labels << small_label(args, x, 16, "Mouse click happened *inside* the box.")
    -    else
    -      args.outputs.labels << small_label(args, x, 16, "Mouse click happened *outside* the box.")
    -    end
    -  else
    -    args.outputs.labels << small_label(args, x, 16, "Mouse click has not occurred yet.")
    -  end
    -end
    -
    -def small_label args, x, row, message
    -  [x, row_to_px(args, row), message, small_font]
    -end
    -
    -def small_font
    -  [-2, 0, 0, 0, 0, 255]
    -end
    -
    -def row_to_px args, row_number
    -  args.grid.top.shift_down(5).shift_down(20 * row_number)
    -end
    -
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.mouse.click ||
    -     args.inputs.keyboard.directional_vector ||
    -     args.inputs.keyboard.key_down.enter ||
    -     args.inputs.keyboard.key_down.escape
    -    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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    -end
    -
    -
    -

    Input Basics - Mouse Rect To Rect - main.rb

    +

    Input Basics - Mouse Rect To Rect - main.rb

    # ./samples/02_input_basics/04_mouse_rect_to_rect/app/main.rb
     =begin
     
    @@ -4965,9 +4350,15 @@ def tick args
       # They are stored in game so that they do not get reset every tick
       if args.inputs.mouse.click
         if !args.state.box_collision_one
    -      args.state.box_collision_one = [args.inputs.mouse.click.point.x - 25, args.inputs.mouse.click.point.y - 25, 125, 125, 180, 0,   0, 180]
    +      args.state.box_collision_one = { x: args.inputs.mouse.click.point.x - 25,
    +                                       y: args.inputs.mouse.click.point.y - 25,
    +                                       w: 125, h: 125,
    +                                       r: 180, g: 0, b: 0, a: 180 }
         elsif !args.state.box_collision_two
    -      args.state.box_collision_two = [args.inputs.mouse.click.point.x - 25, args.inputs.mouse.click.point.y - 25, 125, 125,   0, 0, 180, 180]
    +      args.state.box_collision_two = { x: args.inputs.mouse.click.point.x - 25,
    +                                       y: args.inputs.mouse.click.point.y - 25,
    +                                       w: 125, h: 125,
    +                                       r: 0, g: 0, b: 180, a: 180 }
         else
           args.state.box_collision_one = nil
           args.state.box_collision_two = nil
    @@ -4994,15 +4385,11 @@ def tick args
     end
     
     def small_label args, x, row, message
    -  [x, row_to_px(args, row), message, small_font]
    -end
    -
    -def small_font
    -  [-2, 0, 0, 0, 0, 255]
    +  { x: x, y: row_to_px(args, row), text: message, size_enum: -2 }
     end
     
     def row_to_px args, row_number
    -  args.grid.top.shift_down(5).shift_down(20 * row_number)
    +  args.grid.top - 5 - (20 * row_number)
     end
     
     def tick_instructions args, text, y = 715
    @@ -5020,7 +4407,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Input Basics - Controller - main.rb

    +

    Input Basics - Controller - main.rb

    # ./samples/02_input_basics/05_controller/app/main.rb
     =begin
     
    @@ -5066,57 +4453,51 @@ class ControllerDemo
       def process_inputs
         state.buttons = []
     
    -    state.buttons << [100, 500, inputs.controller_one.key_held.l1, "L1"]
    -    state.buttons << [100, 600, inputs.controller_one.key_held.l2, "L2"]
    -
    -    state.buttons << [1100, 500, inputs.controller_one.key_held.r1, "R1"]
    -    state.buttons << [1100, 600, inputs.controller_one.key_held.r2, "R2"]
    -
    -    state.buttons << [540, 450, inputs.controller_one.key_held.select, "Select"]
    -    state.buttons << [660, 450, inputs.controller_one.key_held.start, "Start"]
    -
    -    state.buttons << [200, 300, inputs.controller_one.key_held.left, "Left"]
    -    state.buttons << [300, 400, inputs.controller_one.key_held.up, "Up"]
    -    state.buttons << [400, 300, inputs.controller_one.key_held.right, "Right"]
    -    state.buttons << [300, 200, inputs.controller_one.key_held.down, "Down"]
    -
    -    state.buttons << [800, 300, inputs.controller_one.key_held.x, "X"]
    -    state.buttons << [900, 400, inputs.controller_one.key_held.y, "Y"]
    -    state.buttons << [1000, 300, inputs.controller_one.key_held.a, "A"]
    -    state.buttons << [900, 200, inputs.controller_one.key_held.b, "B"]
    -
    -    state.buttons << [450 + inputs.controller_one.left_analog_x_perc * 100,
    -                      100 + inputs.controller_one.left_analog_y_perc * 100,
    -                      inputs.controller_one.key_held.l3,
    -                      "L3"]
    -
    -    state.buttons << [750 + inputs.controller_one.right_analog_x_perc * 100,
    -                      100 + inputs.controller_one.right_analog_y_perc * 100,
    -                      inputs.controller_one.key_held.r3,
    -                      "R3"]
    +    state.buttons << { x: 100,  y: 500, active: inputs.controller_one.key_held.l1, text: "L1"}
    +    state.buttons << { x: 100,  y: 600, active: inputs.controller_one.key_held.l2, text: "L2"}
    +    state.buttons << { x: 1100, y: 500, active: inputs.controller_one.key_held.r1, text: "R1"}
    +    state.buttons << { x: 1100, y: 600, active: inputs.controller_one.key_held.r2, text: "R2"}
    +    state.buttons << { x: 540,  y: 450, active: inputs.controller_one.key_held.select, text: "Select"}
    +    state.buttons << { x: 660,  y: 450, active: inputs.controller_one.key_held.start, text: "Start"}
    +    state.buttons << { x: 200,  y: 300, active: inputs.controller_one.key_held.left, text: "Left"}
    +    state.buttons << { x: 300,  y: 400, active: inputs.controller_one.key_held.up, text: "Up"}
    +    state.buttons << { x: 400,  y: 300, active: inputs.controller_one.key_held.right, text: "Right"}
    +    state.buttons << { x: 300,  y: 200, active: inputs.controller_one.key_held.down, text: "Down"}
    +    state.buttons << { x: 800,  y: 300, active: inputs.controller_one.key_held.x, text: "X"}
    +    state.buttons << { x: 900,  y: 400, active: inputs.controller_one.key_held.y, text: "Y"}
    +    state.buttons << { x: 1000, y: 300, active: inputs.controller_one.key_held.a, text: "A"}
    +    state.buttons << { x: 900,  y: 200, active: inputs.controller_one.key_held.b, text: "B"}
    +    state.buttons << { x: 450 + inputs.controller_one.left_analog_x_perc * 100,
    +                       y: 100 + inputs.controller_one.left_analog_y_perc * 100,
    +                       active: inputs.controller_one.key_held.l3,
    +                       text: "L3" }
    +    state.buttons << { x: 750 + inputs.controller_one.right_analog_x_perc * 100,
    +                       y: 100 + inputs.controller_one.right_analog_y_perc * 100,
    +                       active: inputs.controller_one.key_held.r3,
    +                       text: "R3" }
       end
     
       # Gives each button a square shape.
       # If the button is being pressed or held (which means it is considered active),
       # the square is filled in. Otherwise, the button simply has a border.
       def render
    -    state.buttons.each do |x, y, active, text|
    -      rect = [x, y, 75, 75]
    +    state.buttons.each do |b|
    +      rect = { x: b.x, y: b.y, w: 75, h: 75 }
     
    -      if active # if button is pressed
    +      if b.active # if button is pressed
             outputs.solids << rect # rect is output as solid (filled in)
           else
             outputs.borders << rect # otherwise, output as border
           end
     
           # Outputs the text of each button using labels.
    -      outputs.labels << [x, y + 95, text] # add 95 to place label above button
    +      outputs.labels << { x: b.x, y: b.y + 95, text: b.text } # add 95 to place label above button
         end
     
    -    outputs.labels << [10, 60, "Left Analog x: #{inputs.controller_one.left_analog_x_raw} (#{inputs.controller_one.left_analog_x_perc * 100}%)"]
    -    outputs.labels << [10, 30, "Left Analog y: #{inputs.controller_one.left_analog_y_raw} (#{inputs.controller_one.left_analog_y_perc * 100}%)"]
    -    outputs.labels << [900, 60, "Right Analog x: #{inputs.controller_one.right_analog_x_raw} (#{inputs.controller_one.right_analog_x_perc * 100}%)"]
    -    outputs.labels << [900, 30, "Right Analog y: #{inputs.controller_one.right_analog_y_raw} (#{inputs.controller_one.right_analog_y_perc * 100}%)"]
    +    outputs.labels << { x:  10, y: 60, text: "Left Analog x: #{inputs.controller_one.left_analog_x_raw} (#{inputs.controller_one.left_analog_x_perc * 100}%)" }
    +    outputs.labels << { x:  10, y: 30, text: "Left Analog y: #{inputs.controller_one.left_analog_y_raw} (#{inputs.controller_one.left_analog_y_perc * 100}%)" }
    +    outputs.labels << { x: 900, y: 60, text: "Right Analog x: #{inputs.controller_one.right_analog_x_raw} (#{inputs.controller_one.right_analog_x_perc * 100}%)" }
    +    outputs.labels << { x: 900, y: 30, text: "Right Analog y: #{inputs.controller_one.right_analog_y_raw} (#{inputs.controller_one.right_analog_y_perc * 100}%)" }
       end
     end
     
    @@ -5150,7 +4531,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Input Basics - Touch - main.rb

    +

    Input Basics - Touch - main.rb

    # ./samples/02_input_basics/06_touch/app/main.rb
     def tick args
       args.outputs.background_color = [ 0, 0, 0 ]
    @@ -5165,10 +4546,12 @@ def tick args
       #  the next new touch will be finger_one again, but until then, new touches
       #  don't fill in earlier slots.
       if !args.inputs.finger_one.nil?
    -    args.outputs.primitives << [640, 650, "Finger #1 is touching at (#{args.inputs.finger_one.x}, #{args.inputs.finger_one.y}).", 5, 1, 255, 255, 255].label
    +    args.outputs.primitives << { x: 640, y: 650, text: "Finger #1 is touching at (#{args.inputs.finger_one.x}, #{args.inputs.finger_one.y}).",
    +                                 size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
       end
       if !args.inputs.finger_two.nil?
    -    args.outputs.primitives << [640, 600, "Finger #2 is touching at (#{args.inputs.finger_two.x}, #{args.inputs.finger_two.y}).", 5, 1, 255, 255, 255].label
    +    args.outputs.primitives << { x: 640, y: 600, text: "Finger #2 is touching at (#{args.inputs.finger_two.x}, #{args.inputs.finger_two.y}).",
    +                                 size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }.label!
       end
     
       # Here's the more flexible interface: this will report as many simultaneous
    @@ -5189,14 +4572,13 @@ def tick args
         r = (color & 0xFF0000) >> 16
         g = (color & 0x00FF00) >> 8
         b = (color & 0x0000FF)
    -    args.outputs.primitives << [v.x - (size / 2), v.y + (size / 2), size, size, r, g, b, 255].solid
    -    args.outputs.primitives << [v.x, v.y + size, k.to_s, 0, 1, 0, 0, 0].label
    +    args.outputs.primitives << { x: v.x - (size / 2), y: v.y + (size / 2), w: size, h: size, r: r, g: g, b: b, a: 255 }.solid!
    +    args.outputs.primitives << { x: v.x, y: v.y + size, text: k.to_s, alignment_enum: 1 }.label!
       }
     end
     
    -
     
    -

    Rendering Sprites - Animation Using Separate Pngs - main.rb

    +

    Rendering Sprites - Animation Using Separate Pngs - main.rb

    # ./samples/03_rendering_sprites/01_animation_using_separate_pngs/app/main.rb
     =begin
     
    @@ -5231,6 +4613,8 @@ end
     # in this tick "entry point": `looping_animation`, and the
     # second method is `one_time_animation`.
     def tick args
    +  # uncomment the line below to see animation play out in slow motion
    +  # args.gtk.slowmo! 6
       looping_animation args
       one_time_animation args
     end
    @@ -5263,22 +4647,22 @@ def looping_animation args
                                                   does_sprite_loop
     
       # Now that we have `sprite_index, we can present the correct file.
    -  args.outputs.sprites << [100, 100, 100, 100, "sprites/dragon_fly_#{sprite_index}.png"]
    +  args.outputs.sprites << { x: 100, y: 100, w: 100, h: 100, path: "sprites/dragon_fly_#{sprite_index}.png" }
     
       # Try changing the numbers below to see how the animation changes:
    -  args.outputs.sprites << [100, 200, 100, 100, "sprites/dragon_fly_#{0.frame_index 6, 4, true}.png"]
    +  args.outputs.sprites << { x: 100, y: 200, w: 100, h: 100, path: "sprites/dragon_fly_#{0.frame_index 6, 4, true}.png" }
     end
     
     # This function shows how to animate a sprite that executes
     # only once when the "f" key is pressed.
     def one_time_animation args
       # This is just a label the shows instructions within the game.
    -  args.outputs.labels << [220, 350, "(press f to animate)"]
    +  args.outputs.labels <<  { x: 220, y: 350, text: "(press f to animate)" }
     
       # If "f" is pressed on the keyboard...
       if args.inputs.keyboard.key_down.f
         # Print the frame that "f" was pressed on.
    -    puts "Hello from main.rb! The \"f\" key was in the down state on frame: #{args.inputs.keyboard.key_down.f}"
    +    puts "Hello from main.rb! The \"f\" key was in the down state on frame: #{args.state.tick_count}"
     
         # And MOST IMPORTANTLY set the point it time to start the animation,
         # equal to "now" which is represented as args.state.tick_count.
    @@ -5311,7 +4695,7 @@ def one_time_animation args
       sprite_index ||= 0
     
       # Present the sprite.
    -  args.outputs.sprites << [100, 300, 100, 100, "sprites/dragon_fly_#{sprite_index}.png"]
    +  args.outputs.sprites << { x: 100, y: 300, w: 100, h: 100, path: "sprites/dragon_fly_#{sprite_index}.png" }
     
       tick_instructions args, "Sample app shows how to use Numeric#frame_index and string interpolation to animate a sprite over time."
     end
    @@ -5331,7 +4715,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Rendering Sprites - Animation Using Sprite Sheet - main.rb

    +

    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
    @@ -5433,7 +4817,7 @@ def running_sprite args
     end
     
     
    -

    Rendering Sprites - Animation States - main.rb

    +

    Rendering Sprites - Animation States - main.rb

    # ./samples/03_rendering_sprites/03_animation_states/app/main.rb
     class Game
       attr_gtk
    @@ -5620,7 +5004,7 @@ end
     $gtk.reset
     
     
    -

    Rendering Sprites - Color And Rotation - main.rb

    +

    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:
    @@ -5850,7 +5234,7 @@ def source_rect state
     end
     
     
    -

    Physics And Collisions - Simple - main.rb

    +

    Physics And Collisions - Simple - main.rb

    # ./samples/04_physics_and_collisions/01_simple/app/main.rb
     =begin
     
    @@ -5962,7 +5346,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Physics And Collisions - Moving Objects - main.rb

    +

    Physics And Collisions - Moving Objects - main.rb

    # ./samples/04_physics_and_collisions/02_moving_objects/app/main.rb
     =begin
     
    @@ -6266,7 +5650,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Physics And Collisions - Entities - main.rb

    +

    Physics And Collisions - Entities - main.rb

    # ./samples/04_physics_and_collisions/03_entities/app/main.rb
     =begin
     
    @@ -6421,7 +5805,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Physics And Collisions - Box Collision - main.rb

    +

    Physics And Collisions - Box Collision - main.rb

    # ./samples/04_physics_and_collisions/04_box_collision/app/main.rb
     =begin
     
    @@ -6762,7 +6146,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Physics And Collisions - Box Collision 2 - main.rb

    +

    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:
    @@ -7236,7 +6620,7 @@ def tick args
     end
     
     
    -

    Physics And Collisions - Box Collision 3 - main.rb

    +

    Physics And Collisions - Box Collision 3 - main.rb

    # ./samples/04_physics_and_collisions/06_box_collision_3/app/main.rb
     class Game
       attr_gtk
    @@ -7286,9 +6670,9 @@ class Game
         mouse_overlay = mouse_overlay.merge r: 255 if state.delete_mode
     
         if state.mouse_held
    -      outputs.primitives << mouse_overlay.border
    +      outputs.primitives << mouse_overlay.border!
         else
    -      outputs.primitives << mouse_overlay.solid
    +      outputs.primitives << mouse_overlay.solid!
         end
       end
     
    @@ -7495,7 +6879,7 @@ def tick args
     end
     
     
    -

    Physics And Collisions - Jump Physics - main.rb

    +

    Physics And Collisions - Jump Physics - main.rb

    # ./samples/04_physics_and_collisions/07_jump_physics/app/main.rb
     =begin
     
    @@ -7695,7 +7079,7 @@ def tick args
     end
     
     
    -

    Physics And Collisions - Bouncing On Collision - ball.rb

    +

    Physics And Collisions - Bouncing On Collision - ball.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/ball.rb
     GRAVITY = -0.08
     
    @@ -7786,7 +7170,7 @@ class Ball
       end
     
     
    -

    Physics And Collisions - Bouncing On Collision - block.rb

    +

    Physics And Collisions - Bouncing On Collision - block.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/block.rb
     DEGREES_TO_RADIANS = Math::PI / 180
     
    @@ -7949,7 +7333,7 @@ class Block
     end
     
     
    -

    Physics And Collisions - Bouncing On Collision - cannon.rb

    +

    Physics And Collisions - Bouncing On Collision - cannon.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/cannon.rb
     class Cannon
       def initialize args
    @@ -7973,7 +7357,7 @@ class Cannon
     end
     
     
    -

    Physics And Collisions - Bouncing On Collision - main.rb

    +

    Physics And Collisions - Bouncing On Collision - main.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/main.rb
     INFINITY= 10**10
     
    @@ -8094,7 +7478,7 @@ def tick args
     end
     
     
    -

    Physics And Collisions - Bouncing On Collision - peg.rb

    +

    Physics And Collisions - Bouncing On Collision - peg.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/peg.rb
     class Peg
       def initialize(x, y, block_size)
    @@ -8280,7 +7664,7 @@ class Peg
     end
     
     
    -

    Physics And Collisions - Bouncing On Collision - vector2d.rb

    +

    Physics And Collisions - Bouncing On Collision - vector2d.rb

    # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/vector2d.rb
     class Vector2d
         attr_accessor :x, :y
    @@ -8332,7 +7716,7 @@ class Vector2d
         end
       end
     
    -

    Physics And Collisions - Arbitrary Collision - ball.rb

    +

    Physics And Collisions - Arbitrary Collision - ball.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/ball.rb
     
     class Ball
    @@ -8502,7 +7886,7 @@ class Ball
       end
     
     
    -

    Physics And Collisions - Arbitrary Collision - blocks.rb

    +

    Physics And Collisions - Arbitrary Collision - blocks.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/blocks.rb
     MAX_COUNT=100
     
    @@ -9124,7 +8508,7 @@ class Line
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - linear_collider.rb

    +

    Physics And Collisions - Arbitrary Collision - linear_collider.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/linear_collider.rb
     
     COLLISIONWIDTH=8
    @@ -9308,7 +8692,7 @@ class LinearCollider
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - main.rb

    +

    Physics And Collisions - Arbitrary Collision - main.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/main.rb
     INFINITY= 10**10
     MAX_VELOCITY = 8.0
    @@ -9483,7 +8867,7 @@ def tick args
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - paddle.rb

    +

    Physics And Collisions - Arbitrary Collision - paddle.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/paddle.rb
     class Paddle
       attr_accessor :enabled
    @@ -9540,7 +8924,7 @@ class Paddle
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - rectangle.rb

    +

    Physics And Collisions - Arbitrary Collision - rectangle.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/rectangle.rb
     class Rectangle
       def initialize args
    @@ -9634,7 +9018,7 @@ class Rectangle
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - square_collider.rb

    +

    Physics And Collisions - Arbitrary Collision - square_collider.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/square_collider.rb
     
     class SquareCollider
    @@ -9667,7 +9051,7 @@ class SquareCollider
     end
     
     
    -

    Physics And Collisions - Arbitrary Collision - vector2d.rb

    +

    Physics And Collisions - Arbitrary Collision - vector2d.rb

    # ./samples/04_physics_and_collisions/09_arbitrary_collision/app/vector2d.rb
     class Vector2d
         attr_accessor :x, :y
    @@ -9719,779 +9103,1407 @@ class Vector2d
         end
       end
     
    -

    Physics And Collisions - Collision With Object Removal - ball.rb

    +

    Physics And Collisions - Collision With Object Removal - ball.rb

    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/ball.rb
     class Ball
       #TODO limit accessors?
       attr_accessor :xy, :width, :height, :velocity
     
     
    -  #@xy [Vector2d] x,y position
    -  #@velocity [Vector2d] velocity of ball
    -  def initialize
    -    @xy = Vector2d.new(WIDTH/2,500)
    -    @velocity = Vector2d.new(4,-4)
    -    @width =  20
    -    @height = 20
    +  #@xy [Vector2d] x,y position
    +  #@velocity [Vector2d] velocity of ball
    +  def initialize
    +    @xy = Vector2d.new(WIDTH/2,500)
    +    @velocity = Vector2d.new(4,-4)
    +    @width =  20
    +    @height = 20
    +  end
    +
    +  #move the ball according to its velocity
    +  def update args
    +    @xy.x+=@velocity.x
    +    @xy.y+=@velocity.y
    +  end
    +
    +  #render the ball to the screen
    +  def render args
    +    args.outputs.solids << [@xy.x,@xy.y,@width,@height,255,0,255];
    +    #args.outputs.labels << [20,HEIGHT-50,"velocity: " +@velocity.x.to_s+","+@velocity.y.to_s + "   magnitude:" + @velocity.mag.to_s]
    +  end
    +
    +  def rect
    +    [@xy.x,@xy.y,@width,@height]
    +  end
    +
    +end
    +
    +
    +

    Physics And Collisions - Collision With Object Removal - linear_collider.rb

    +
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/linear_collider.rb
    +#The LinearCollider (theoretically) produces collisions upon a line segment defined point.y two x,y cordinates
    +
    +class LinearCollider
    +
    +  #start [Array of length 2] start of the line segment as a x,y cordinate
    +  #last [Array of length 2] end of the line segment as a x,y cordinate
    +
    +  #inorder for the LinearCollider to be functional the line segment must be said to have a thickness
    +  #(as it is unlikly that a colliding object will land exactly on the linesegment)
    +
    +  #extension defines if the line's thickness extends negatively or positively
    +  #extension :pos     extends positively
    +  #extension :neg     extends negatively
    +
    +  #thickness [float] how thick the line should be (should always be atleast as large as the magnitude of the colliding object)
    +  def initialize (pointA, pointB, extension=:neg, thickness=10)
    +    @pointA = pointA
    +    @pointB = pointB
    +    @thickness = thickness
    +    @extension = extension
    +
    +    @pointAExtended={
    +      x: @pointA.x + @thickness*(@extension == :neg ? -1 : 1),
    +      y: @pointA.y + @thickness*(@extension == :neg ? -1 : 1)
    +    }
    +    @pointBExtended={
    +      x: @pointB.x + @thickness*(@extension == :neg ? -1 : 1),
    +      y: @pointB.y + @thickness*(@extension == :neg ? -1 : 1)
    +    }
    +
    +  end
    +
    +  def resetPoints(pointA,pointB)
    +    @pointA = pointA
    +    @pointB = pointB
    +
    +    @pointAExtended={
    +      x:@pointA.x + @thickness*(@extension == :neg ? -1 : 1),
    +      y:@pointA.y + @thickness*(@extension == :neg ? -1 : 1)
    +    }
    +    @pointBExtended={
    +      x:@pointB.x + @thickness*(@extension == :neg ? -1 : 1),
    +      y:@pointB.y + @thickness*(@extension == :neg ? -1 : 1)
    +    }
    +  end
    +
    +  #TODO: Ugly function
    +  def slope (pointA, pointB)
    +    return (pointB.x==pointA.x) ? INFINITY : (pointB.y+-pointA.y)/(pointB.x+-pointA.x)
    +  end
    +
    +  #TODO: Ugly function
    +  def intercept(pointA, pointB)
    +    if (slope(pointA, pointB) == INFINITY)
    +      -INFINITY
    +    elsif slope(pointA, pointB) == -1*INFINITY
    +      INFINITY
    +    else
    +      pointA.y+-1.0*(slope(pointA, pointB)*pointA.x)
    +    end
    +  end
    +
    +  def calcY(pointA, pointB, x)
    +    return slope(pointA, pointB)*x + intercept(pointA, pointB)
    +  end
    +
    +  #test if a collision has occurred
    +  def isCollision? (point)
    +    #INFINITY slop breaks down when trying to determin collision, ergo it requires a special test
    +    if slope(@pointA, @pointB) ==  INFINITY &&
    +      point.x >= [@pointA.x,@pointB.x].min+(@extension == :pos ? -@thickness : 0) &&
    +      point.x <= [@pointA.x,@pointB.x].max+(@extension == :neg ?  @thickness : 0) &&
    +      point.y >= [@pointA.y,@pointB.y].min && point.y <= [@pointA.y,@pointB.y].max
    +        return true
    +    end
    +
    +    isNegInLine   = @extension == :neg &&
    +                    point.y <= slope(@pointA, @pointB)*point.x+intercept(@pointA,@pointB) &&
    +                    point.y >= point.x*slope(@pointAExtended, @pointBExtended)+intercept(@pointAExtended,@pointBExtended)
    +    isPosInLine   = @extension == :pos &&
    +                    point.y >= slope(@pointA, @pointB)*point.x+intercept(@pointA,@pointB) &&
    +                    point.y <= point.x*slope(@pointAExtended, @pointBExtended)+intercept(@pointAExtended,@pointBExtended)
    +    isInBoxBounds = point.x >= [@pointA.x,@pointB.x].min &&
    +                    point.x <= [@pointA.x,@pointB.x].max &&
    +                    point.y >= [@pointA.y,@pointB.y].min+(@extension == :neg ? -@thickness : 0) &&
    +                    point.y <= [@pointA.y,@pointB.y].max+(@extension == :pos ? @thickness : 0)
    +
    +    return isInBoxBounds && (isNegInLine || isPosInLine)
    +
    +  end
    +
    +  def getRepelMagnitude (fbx, fby, vrx, vry, args)
    +    a = fbx ; b = vrx ; c = fby
    +    d = vry ; e = args.state.ball.velocity.mag
    +
    +    if b**2 + d**2 == 0
    +      puts "magnitude error"
    +    end
    +
    +    x1 = (-a*b+-c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 + d**2 - a**2 * d**2)**0.5)/(b**2 + d**2)
    +    x2 = -((a*b + c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 * d**2 - a**2 * d**2)**0.5)/(b**2 + d**2))
    +    return ((a+x1*b)**2 + (c+x1*d)**2 == e**2) ? x1 : x2
    +  end
    +
    +  def update args
    +    #each of the four points on the square ball - NOTE simple to extend to a circle
    +    points= [ {x: args.state.ball.xy.x,                          y: args.state.ball.xy.y},
    +              {x: args.state.ball.xy.x+args.state.ball.width,    y: args.state.ball.xy.y},
    +              {x: args.state.ball.xy.x,                          y: args.state.ball.xy.y+args.state.ball.height},
    +              {x: args.state.ball.xy.x+args.state.ball.width,    y: args.state.ball.xy.y + args.state.ball.height}
    +            ]
    +
    +    #for each point p in points
    +    for point in points
    +      #isCollision.md has more information on this section
    +      #TODO: section can certainly be simplifyed
    +      if isCollision?(point)
    +        u = Vector2d.new(1.0,((slope(@pointA, @pointB)==0) ? INFINITY : -1/slope(@pointA, @pointB))*1.0).normalize #normal perpendicular (to line segment) vector
    +
    +        #the vector with the repeling force can be u or -u depending of where the ball was coming from in relation to the line segment
    +        previousBallPosition=Vector2d.new(point.x-args.state.ball.velocity.x,point.y-args.state.ball.velocity.y)
    +        choiceA = (u.mult(1))
    +        choiceB =  (u.mult(-1))
    +        vectorRepel = nil
    +
    +        if (slope(@pointA, @pointB))!=INFINITY && u.y < 0
    +          choiceA, choiceB = choiceB, choiceA
    +        end
    +        vectorRepel = (previousBallPosition.y > calcY(@pointA, @pointB, previousBallPosition.x)) ? choiceA : choiceB
    +
    +        #vectorRepel = (previousBallPosition.y > slope(@pointA, @pointB)*previousBallPosition.x+intercept(@pointA,@pointB)) ? choiceA : choiceB)
    +        if (slope(@pointA, @pointB) == INFINITY) #slope INFINITY breaks down in the above test, ergo it requires a custom test
    +          vectorRepel = (previousBallPosition.x > @pointA.x) ? (u.mult(1)) : (u.mult(-1))
    +        end
    +        #puts ("     " + $t[0].to_s + "," + $t[1].to_s + "    " + $t[2].to_s + "," + $t[3].to_s + "     " + "   " + u.x.to_s + "," + u.y.to_s)
    +        #vectorRepel now has the repeling force
    +
    +        mag = args.state.ball.velocity.mag
    +        theta_ball=Math.atan2(args.state.ball.velocity.y,args.state.ball.velocity.x) #the angle of the ball's velocity
    +        theta_repel=Math.atan2(vectorRepel.y,vectorRepel.x) #the angle of the repeling force
    +        #puts ("theta:" + theta_ball.to_s + " " + theta_repel.to_s) #theta okay
    +
    +        fbx = mag * Math.cos(theta_ball) #the x component of the ball's velocity
    +        fby = mag * Math.sin(theta_ball) #the y component of the ball's velocity
    +
    +        repelMag = getRepelMagnitude(fbx, fby, vectorRepel.x, vectorRepel.y, args)
    +
    +        frx = repelMag* Math.cos(theta_repel) #the x component of the repel's velocity | magnitude is set to twice of fbx
    +        fry = repelMag* Math.sin(theta_repel) #the y component of the repel's velocity | magnitude is set to twice of fby
    +
    +        fsumx = fbx+frx #sum of x forces
    +        fsumy = fby+fry #sum of y forces
    +        fr = mag#fr is the resulting magnitude
    +        thetaNew = Math.atan2(fsumy, fsumx)  #thetaNew is the resulting angle
    +        xnew = fr*Math.cos(thetaNew) #resulting x velocity
    +        ynew = fr*Math.sin(thetaNew) #resulting y velocity
    +
    +        args.state.ball.velocity = Vector2d.new(xnew,ynew)
    +        #args.state.ball.xy.add(args.state.ball.velocity)
    +        break #no need to check the other points ?
    +      else
    +      end
    +    end
    +  end #end update
    +
    +end
    +
    +
    +

    Physics And Collisions - Collision With Object Removal - main.rb

    +
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/main.rb
    +# coding: utf-8
    +INFINITY= 10**10
    +WIDTH=1280
    +HEIGHT=720
    +
    +require 'app/vector2d.rb'
    +require 'app/paddle.rb'
    +require 'app/ball.rb'
    +require 'app/linear_collider.rb'
    +
    +#Method to init default values
    +def defaults args
    +  args.state.game_board ||= [(args.grid.w / 2 - args.grid.w / 4), 0, (args.grid.w / 2), args.grid.h]
    +  args.state.bricks ||= []
    +  args.state.num_bricks ||= 0
    +  args.state.game_over_at ||= 0
    +  args.state.paddle ||= Paddle.new
    +  args.state.ball   ||= Ball.new
    +  args.state.westWall  ||= LinearCollider.new({x: args.grid.w/4,      y: 0},          {x: args.grid.w/4,      y: args.grid.h}, :pos)
    +  args.state.eastWall  ||= LinearCollider.new({x: 3*args.grid.w*0.25, y: 0},          {x: 3*args.grid.w*0.25, y: args.grid.h})
    +  args.state.southWall ||= LinearCollider.new({x: 0,                  y: 0},          {x: args.grid.w,        y: 0})
    +  args.state.northWall ||= LinearCollider.new({x: 0,                  y:args.grid.h}, {x: args.grid.w,        y: args.grid.h}, :pos)
    +
    +  #args.state.testWall ||= LinearCollider.new({x:0 , y:0},{x:args.grid.w, y:args.grid.h})
    +end
    +
    +#Render loop
    +def render args
    +  render_instructions args
    +  render_board args
    +  render_bricks args
    +end
    +
    +begin :render_methods
    +  #Method to display the instructions of the game
    +  def render_instructions args
    +    args.outputs.labels << [225, args.grid.h - 30, "← and → to move the paddle left and right",  0, 1]
    +  end
    +
    +  def render_board args
    +    args.outputs.borders << args.state.game_board
    +  end
    +
    +  def render_bricks args
    +    args.outputs.solids << args.state.bricks.map(&:rect)
    +  end
    +end
    +
    +#Calls all methods necessary for performing calculations
    +def calc args
    +  add_new_bricks args
    +  reset_game args
    +  calc_collision args
    +  win_game args
    +
    +  args.state.westWall.update args
    +  args.state.eastWall.update args
    +  args.state.southWall.update args
    +  args.state.northWall.update args
    +  args.state.paddle.update args
    +  args.state.ball.update args
    +
    +  #args.state.testWall.update args
    +
    +  args.state.paddle.render args
    +  args.state.ball.render args
    +end
    +
    +begin :calc_methods
    +  def add_new_bricks args
    +    return if args.state.num_bricks > 40
    +
    +    #Width of the game board is 640px
    +    brick_width = (args.grid.w / 2) / 10
    +    brick_height = brick_width / 2
    +
    +    (4).map_with_index do |y|
    +      #Make a box that is 10 bricks wide and 4 bricks tall
    +      args.state.bricks += (10).map_with_index do |x|
    +        args.state.new_entity(:brick) do |b|
    +          b.x = x * brick_width + (args.grid.w / 2 - args.grid.w / 4)
    +          b.y = args.grid.h - ((y + 1) * brick_height)
    +          b.rect = [b.x + 1, b.y - 1, brick_width - 2, brick_height - 2, 235, 50 * y, 52]
    +
    +          #Add linear colliders to the brick
    +          b.collider_bottom = LinearCollider.new([(b.x-2), (b.y-5)], [(b.x+brick_width+1), (b.y-5)], :pos, brick_height)
    +          b.collider_right = LinearCollider.new([(b.x+brick_width+1), (b.y-5)], [(b.x+brick_width+1), (b.y+brick_height+1)], :pos)
    +          b.collider_left = LinearCollider.new([(b.x-2), (b.y-5)], [(b.x-2), (b.y+brick_height+1)], :neg)
    +          b.collider_top = LinearCollider.new([(b.x-2), (b.y+brick_height+1)], [(b.x+brick_width+1), (b.y+brick_height+1)], :neg)
    +
    +          # @xyCollision  = LinearCollider.new({x: @x,y: @y+@height}, {x: @x+@width, y: @y+@height})
    +          # @xyCollision2 = LinearCollider.new({x: @x,y: @y}, {x: @x+@width, y: @y}, :pos)
    +          # @xyCollision3 = LinearCollider.new({x: @x,y: @y}, {x: @x, y: @y+@height})
    +          # @xyCollision4 = LinearCollider.new({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height}, :pos)
    +
    +          b.broken = false
    +
    +          args.state.num_bricks += 1
    +        end
    +      end
    +    end
    +  end
    +
    +  def reset_game args
    +    if args.state.ball.xy.y < 20 && args.state.game_over_at.elapsed_time > 60
    +      #Freeze the ball
    +      args.state.ball.velocity.x = 0
    +      args.state.ball.velocity.y = 0
    +      #Freeze the paddle
    +      args.state.paddle.enabled = false
    +
    +      args.state.game_over_at = args.state.tick_count
    +    end
    +
    +    if args.state.game_over_at.elapsed_time < 60 && args.state.tick_count > 60 && args.state.bricks.count != 0
    +      #Display a "Game over" message
    +      args.outputs.labels << [100, 100, "GAME OVER", 10]
    +    end
    +
    +    #If 60 frames have passed since the game ended, restart the game
    +    if args.state.game_over_at != 0 && args.state.game_over_at.elapsed_time == 60
    +      # FIXME: only put value types in state
    +      args.state.ball = Ball.new
    +
    +      # FIXME: only put value types in state
    +      args.state.paddle = Paddle.new
    +
    +      args.state.bricks = []
    +      args.state.num_bricks = 0
    +    end
    +  end
    +
    +  def calc_collision args
    +    #Remove the brick if it is hit with the ball
    +    ball = args.state.ball
    +    ball_rect = [ball.xy.x, ball.xy.y, 20, 20]
    +
    +    #Loop through each brick to see if the ball is colliding with it
    +    args.state.bricks.each do |b|
    +      if b.rect.intersect_rect?(ball_rect)
    +        #Run the linear collider for the brick if there is a collision
    +        b[:collider_bottom].update args
    +        b[:collider_right].update args
    +        b[:collider_left].update args
    +        b[:collider_top].update args
    +
    +        b.broken = true
    +      end
    +    end
    +
    +    args.state.bricks = args.state.bricks.reject(&:broken)
    +  end
    +
    +  def win_game args
    +    if args.state.bricks.count == 0 && args.state.game_over_at.elapsed_time > 60
    +      #Freeze the ball
    +      args.state.ball.velocity.x = 0
    +      args.state.ball.velocity.y = 0
    +      #Freeze the paddle
    +      args.state.paddle.enabled = false
    +
    +      args.state.game_over_at = args.state.tick_count
    +    end
    +
    +    if args.state.game_over_at.elapsed_time < 60 && args.state.tick_count > 60 && args.state.bricks.count == 0
    +      #Display a "Game over" message
    +      args.outputs.labels << [100, 100, "CONGRATULATIONS!", 10]
    +    end
    +  end
    +
    +end
    +
    +def tick args
    +  defaults args
    +  render args
    +  calc args
    +
    +  #args.outputs.lines << [0, 0, args.grid.w, args.grid.h]
    +
    +  #$tc+=1
    +  #if $tc == 5
    +    #$train << [args.state.ball.xy.x, args.state.ball.xy.y]
    +    #$tc = 0
    +  #end
    +  #for t in $train
    +
    +    #args.outputs.solids << [t[0],t[1],5,5,255,0,0];
    +  #end
    +end
    +
    +
    +

    Physics And Collisions - Collision With Object Removal - paddle.rb

    +
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/paddle.rb
    +class Paddle
    +  attr_accessor :enabled
    +
    +  def initialize ()
    +    @x=WIDTH/2
    +    @y=100
    +    @width=100
    +    @height=20
    +    @speed=10
    +
    +    @xyCollision  = LinearCollider.new({x: @x,y: @y+@height+5}, {x: @x+@width, y: @y+@height+5})
    +    @xyCollision2 = LinearCollider.new({x: @x,y: @y}, {x: @x+@width, y: @y}, :pos)
    +    @xyCollision3 = LinearCollider.new({x: @x,y: @y}, {x: @x, y: @y+@height+5})
    +    @xyCollision4 = LinearCollider.new({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height+5}, :pos)
    +
    +    @enabled = true
    +  end
    +
    +  def update args
    +    @xyCollision.resetPoints({x: @x,y: @y+@height+5}, {x: @x+@width, y: @y+@height+5})
    +    @xyCollision2.resetPoints({x: @x,y: @y}, {x: @x+@width, y: @y})
    +    @xyCollision3.resetPoints({x: @x,y: @y}, {x: @x, y: @y+@height+5})
    +    @xyCollision4.resetPoints({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height+5})
    +
    +    @xyCollision.update  args
    +    @xyCollision2.update args
    +    @xyCollision3.update args
    +    @xyCollision4.update args
    +
    +    args.inputs.keyboard.key_held.left  ||= false
    +    args.inputs.keyboard.key_held.right  ||= false
    +
    +    if not (args.inputs.keyboard.key_held.left == args.inputs.keyboard.key_held.right)
    +      if args.inputs.keyboard.key_held.left && @enabled
    +        @x-=@speed
    +      elsif args.inputs.keyboard.key_held.right && @enabled
    +        @x+=@speed
    +      end
    +    end
    +
    +    xmin =WIDTH/4
    +    xmax = 3*(WIDTH/4)
    +    @x = (@x+@width > xmax) ? xmax-@width : (@x
    +

    Physics And Collisions - Collision With Object Removal - tests.rb

    +
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/tests.rb
    +# For advanced users:
    +# You can put some quick verification tests here, any method
    +# that starts with the `test_` will be run when you save this file.
    +
    +# Here is an example test and game
    +
    +# To run the test: ./dragonruby mygame --eval app/tests.rb --no-tick
    +
    +class MySuperHappyFunGame
    +  attr_gtk
    +
    +  def tick
    +    outputs.solids << [100, 100, 300, 300]
    +  end
    +end
    +
    +def test_universe args, assert
    +  game = MySuperHappyFunGame.new
    +  game.args = args
    +  game.tick
    +  assert.true!  args.outputs.solids.length == 1, "failure: a solid was not added after tick"
    +  assert.false! 1 == 2, "failure: some how, 1 equals 2, the world is ending"
    +  puts "test_universe completed successfully"
    +end
    +
    +puts "running tests"
    +$gtk.reset 100
    +$gtk.log_level = :off
    +$gtk.tests.start
    +
    +
    +

    Physics And Collisions - Collision With Object Removal - vector2d.rb

    +
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/vector2d.rb
    +
    +class Vector2d
    +  attr_accessor :x, :y
    +
    +  def initialize x=0, y=0
    +    @x=x
    +    @y=y
    +  end
    +
    +  #returns a vector multiplied by scalar x
    +  #x [float] scalar
    +  def mult x
    +    r = Vector2d.new(0,0)
    +    r.x=@x*x
    +    r.y=@y*x
    +    r
    +  end
    +
    +  # vect [Vector2d] vector to copy
    +  def copy vect
    +    Vector2d.new(@x, @y)
    +  end
    +
    +  #returns a new vector equivalent to this+vect
    +  #vect [Vector2d] vector to add to self
    +  def add vect
    +    Vector2d.new(@x+vect.x,@y+vect.y)
       end
     
    -  #move the ball according to its velocity
    -  def update args
    -    @xy.x+=@velocity.x
    -    @xy.y+=@velocity.y
    +  #returns a new vector equivalent to this-vect
    +  #vect [Vector2d] vector to subtract to self
    +  def sub vect
    +    Vector2d.new(@x-vect.c, @y-vect.y)
       end
     
    -  #render the ball to the screen
    -  def render args
    -    args.outputs.solids << [@xy.x,@xy.y,@width,@height,255,0,255];
    -    #args.outputs.labels << [20,HEIGHT-50,"velocity: " +@velocity.x.to_s+","+@velocity.y.to_s + "   magnitude:" + @velocity.mag.to_s]
    +  #return the magnitude of the vector
    +  def mag
    +    ((@x**2)+(@y**2))**0.5
       end
     
    -  def rect
    -    [@xy.x,@xy.y,@width,@height]
    +  #returns a new normalize version of the vector
    +  def normalize
    +    Vector2d.new(@x/mag, @y/mag)
       end
     
    +  #TODO delet?
    +  def distABS vect
    +    (((vect.x-@x)**2+(vect.y-@y)**2)**0.5).abs()
    +  end
     end
     
     
    -

    Physics And Collisions - Collision With Object Removal - linear_collider.rb

    -
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/linear_collider.rb
    -#The LinearCollider (theoretically) produces collisions upon a line segment defined point.y two x,y cordinates
    +

    Mouse - Mouse Click - main.rb

    +
    # ./samples/05_mouse/01_mouse_click/app/main.rb
    +=begin
     
    -class LinearCollider
    + APIs listing that haven't been encountered in previous sample apps:
     
    -  #start [Array of length 2] start of the line segment as a x,y cordinate
    -  #last [Array of length 2] end of the line segment as a x,y cordinate
    + - product: Returns an array of all combinations of elements from all arrays.
     
    -  #inorder for the LinearCollider to be functional the line segment must be said to have a thickness
    -  #(as it is unlikly that a colliding object will land exactly on the linesegment)
    +   For example, [1,2].product([1,2]) would return the following array...
    +   [[1,1], [1,2], [2,1], [2,2]]
    +   More than two arrays can be given to product and it will still work,
    +   such as [1,2].product([1,2],[3,4]). What would product return in this case?
     
    -  #extension defines if the line's thickness extends negatively or positively
    -  #extension :pos     extends positively
    -  #extension :neg     extends negatively
    +   Answer:
    +   [[1,1,3],[1,1,4],[1,2,3],[1,2,4],[2,1,3],[2,1,4],[2,2,3],[2,2,4]]
     
    -  #thickness [float] how thick the line should be (should always be atleast as large as the magnitude of the colliding object)
    -  def initialize (pointA, pointB, extension=:neg, thickness=10)
    -    @pointA = pointA
    -    @pointB = pointB
    -    @thickness = thickness
    -    @extension = extension
    + - num1.fdiv(num2): Returns the float division (will have a decimal) of the two given numbers.
    +   For example, 5.fdiv(2) = 2.5 and 5.fdiv(5) = 1.0
     
    -    @pointAExtended={
    -      x: @pointA.x + @thickness*(@extension == :neg ? -1 : 1),
    -      y: @pointA.y + @thickness*(@extension == :neg ? -1 : 1)
    -    }
    -    @pointBExtended={
    -      x: @pointB.x + @thickness*(@extension == :neg ? -1 : 1),
    -      y: @pointB.y + @thickness*(@extension == :neg ? -1 : 1)
    -    }
    + - yield: Allows you to call a method with a code block and yield to that block.
     
    -  end
    + Reminders:
     
    -  def resetPoints(pointA,pointB)
    -    @pointA = pointA
    -    @pointB = pointB
    + - ARRAY#inside_rect?: Returns true or false depending on if the point is inside the rect.
     
    -    @pointAExtended={
    -      x:@pointA.x + @thickness*(@extension == :neg ? -1 : 1),
    -      y:@pointA.y + @thickness*(@extension == :neg ? -1 : 1)
    -    }
    -    @pointBExtended={
    -      x:@pointB.x + @thickness*(@extension == :neg ? -1 : 1),
    -      y:@pointB.y + @thickness*(@extension == :neg ? -1 : 1)
    -    }
    -  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.
     
    -  #TODO: Ugly function
    -  def slope (pointA, pointB)
    -    return (pointB.x==pointA.x) ? INFINITY : (pointB.y+-pointA.y)/(pointB.x+-pointA.x)
    -  end
    + - args.inputs.mouse.click: This property will be set if the mouse was clicked.
     
    -  #TODO: Ugly function
    -  def intercept(pointA, pointB)
    -    if (slope(pointA, pointB) == INFINITY)
    -      -INFINITY
    -    elsif slope(pointA, pointB) == -1*INFINITY
    -      INFINITY
    -    else
    -      pointA.y+-1.0*(slope(pointA, pointB)*pointA.x)
    -    end
    -  end
    + - Ternary operator (?): Will evaluate a statement (just like an if statement)
    +   and perform an action if the result is true or another action if it is false.
     
    -  def calcY(pointA, pointB, x)
    -    return slope(pointA, pointB)*x + intercept(pointA, pointB)
    + - reject: Removes elements from a collection if they meet certain requirements.
    +
    + - args.outputs.borders: An array. The values generate a border.
    +   The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE]
    +   For more information about borders, go to mygame/documentation/03-solids-and-borders.md.
    +
    + - 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.
    +
    +=end
    +
    +# This sample app is a classic game of Tic Tac Toe.
    +
    +class TicTacToe
    +  attr_accessor :_, :state, :outputs, :inputs, :grid, :gtk
    +
    +  # Starts the game with player x's turn and creates an array (to_a) for space combinations.
    +  # Calls methods necessary for the game to run properly.
    +  def tick
    +    state.current_turn ||= :x
    +    state.space_combinations = [-1, 0, 1].product([-1, 0, 1]).to_a
    +    render_board
    +    input_board
       end
     
    -  #test if a collision has occurred
    -  def isCollision? (point)
    -    #INFINITY slop breaks down when trying to determin collision, ergo it requires a special test
    -    if slope(@pointA, @pointB) ==  INFINITY &&
    -      point.x >= [@pointA.x,@pointB.x].min+(@extension == :pos ? -@thickness : 0) &&
    -      point.x <= [@pointA.x,@pointB.x].max+(@extension == :neg ?  @thickness : 0) &&
    -      point.y >= [@pointA.y,@pointB.y].min && point.y <= [@pointA.y,@pointB.y].max
    -        return true
    +  # Uses borders to create grid squares for the game's board. Also outputs the game pieces using labels.
    +  def render_board
    +    square_size = 80
    +
    +    # Positions the game's board in the center of the screen.
    +    # Try removing what follows grid.w_half or grid.h_half and see how the position changes!
    +    board_left = grid.w_half - square_size * 1.5
    +    board_top  = grid.h_half - square_size * 1.5
    +
    +    # At first glance, the add(1) looks pretty trivial. But if you remove it,
    +    # you'll see that the positioning of the board would be skewed without it!
    +    # Or if you put 2 in the parenthesis, the pieces will be placed in the wrong squares
    +    # due to the change in board placement.
    +    outputs.borders << all_spaces do |x, y, space| # outputs borders for all board spaces
    +      space.border ||= [
    +        board_left + x.add(1) * square_size, # space.border is initialized using this definition
    +        board_top  + y.add(1) * square_size,
    +        square_size,
    +        square_size
    +      ]
         end
     
    -    isNegInLine   = @extension == :neg &&
    -                    point.y <= slope(@pointA, @pointB)*point.x+intercept(@pointA,@pointB) &&
    -                    point.y >= point.x*slope(@pointAExtended, @pointBExtended)+intercept(@pointAExtended,@pointBExtended)
    -    isPosInLine   = @extension == :pos &&
    -                    point.y >= slope(@pointA, @pointB)*point.x+intercept(@pointA,@pointB) &&
    -                    point.y <= point.x*slope(@pointAExtended, @pointBExtended)+intercept(@pointAExtended,@pointBExtended)
    -    isInBoxBounds = point.x >= [@pointA.x,@pointB.x].min &&
    -                    point.x <= [@pointA.x,@pointB.x].max &&
    -                    point.y >= [@pointA.y,@pointB.y].min+(@extension == :neg ? -@thickness : 0) &&
    -                    point.y <= [@pointA.y,@pointB.y].max+(@extension == :pos ? @thickness : 0)
    +    # Again, the calculations ensure that the piece is placed in the center of the grid square.
    +    # Remove the '- 20' and the piece will be placed at the top of the grid square instead of the center.
    +    outputs.labels << filled_spaces do |x, y, space| # put label in each filled space of board
    +          label board_left + x.add(1) * square_size + square_size.fdiv(2),
    +          board_top  + y.add(1) * square_size + square_size - 20,
    +          space.piece # text of label, either "x" or "o"
    +    end
     
    -    return isInBoxBounds && (isNegInLine || isPosInLine)
    +    # Uses a label to output whether x or o won, or if a draw occurred.
    +    # If the game is ongoing, a label shows whose turn it currently is.
    +    outputs.labels << if state.x_won
    +                        label grid.w_half, grid.top - 80, "x won" # the '-80' positions the label 80 pixels lower than top
    +                      elsif state.o_won
    +                        label grid.w_half, grid.top - 80, "o won" # grid.w_half positions the label in the center horizontally
    +                      elsif state.draw
    +                        label grid.w_half, grid.top - 80, "a draw"
    +                      else # if no one won and the game is ongoing
    +                        label grid.w_half, grid.top - 80, "turn: #{state.current_turn}"
    +                      end
    +  end
     
    +  # Calls the methods responsible for handling user input and determining the winner.
    +  # Does nothing unless the mouse is clicked.
    +  def input_board
    +    return unless inputs.mouse.click
    +    input_place_piece
    +    input_restart_game
    +    determine_winner
       end
     
    -  def getRepelMagnitude (fbx, fby, vrx, vry, args)
    -    a = fbx ; b = vrx ; c = fby
    -    d = vry ; e = args.state.ball.velocity.mag
    +  # Handles user input for placing pieces on the board.
    +  def input_place_piece
    +    return if state.game_over
     
    -    if b**2 + d**2 == 0
    -      puts "magnitude error"
    +    # Checks to find the space that the mouse was clicked inside of, and makes sure the space does not already
    +    # have a piece in it.
    +    __, __, space = all_spaces.find do |__, __, space|
    +      inputs.mouse.click.point.inside_rect?(space.border) && !space.piece
         end
     
    -    x1 = (-a*b+-c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 + d**2 - a**2 * d**2)**0.5)/(b**2 + d**2)
    -    x2 = -((a*b + c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 * d**2 - a**2 * d**2)**0.5)/(b**2 + d**2))
    -    return ((a+x1*b)**2 + (c+x1*d)**2 == e**2) ? x1 : x2
    +    # The piece that goes into the space belongs to the player whose turn it currently is.
    +    return unless space
    +    space.piece = state.current_turn
    +
    +    # This ternary operator statement allows us to change the current player's turn.
    +    # If it is currently x's turn, it becomes o's turn. If it is not x's turn, it become's x's turn.
    +    state.current_turn = state.current_turn == :x ? :o : :x
       end
     
    -  def update args
    -    #each of the four points on the square ball - NOTE simple to extend to a circle
    -    points= [ {x: args.state.ball.xy.x,                          y: args.state.ball.xy.y},
    -              {x: args.state.ball.xy.x+args.state.ball.width,    y: args.state.ball.xy.y},
    -              {x: args.state.ball.xy.x,                          y: args.state.ball.xy.y+args.state.ball.height},
    -              {x: args.state.ball.xy.x+args.state.ball.width,    y: args.state.ball.xy.y + args.state.ball.height}
    -            ]
    +  # Resets the game.
    +  def input_restart_game
    +    return unless state.game_over
    +    gtk.reset
    +  end
     
    -    #for each point p in points
    -    for point in points
    -      #isCollision.md has more information on this section
    -      #TODO: section can certainly be simplifyed
    -      if isCollision?(point)
    -        u = Vector2d.new(1.0,((slope(@pointA, @pointB)==0) ? INFINITY : -1/slope(@pointA, @pointB))*1.0).normalize #normal perpendicular (to line segment) vector
    +  # Checks if x or o won the game.
    +  # If neither player wins and all nine squares are filled, a draw happens.
    +  # Once a player is chosen as the winner or a draw happens, the game is over.
    +  def determine_winner
    +    state.x_won = won? :x # evaluates to either true or false (boolean values)
    +    state.o_won = won? :o
    +    state.draw = true if filled_spaces.length == 9 && !state.x_won && !state.o_won
    +    state.game_over = state.x_won || state.o_won || state.draw
    +  end
     
    -        #the vector with the repeling force can be u or -u depending of where the ball was coming from in relation to the line segment
    -        previousBallPosition=Vector2d.new(point.x-args.state.ball.velocity.x,point.y-args.state.ball.velocity.y)
    -        choiceA = (u.mult(1))
    -        choiceB =  (u.mult(-1))
    -        vectorRepel = nil
    +  # Determines if a player won by checking if there is a horizontal match or vertical match.
    +  # Horizontal_match and vertical_match have boolean values. If either is true, the game has been won.
    +  def won? piece
    +    # performs action on all space combinations
    +    won = [[-1, 0, 1]].product([-1, 0, 1]).map do |xs, y|
     
    -        if (slope(@pointA, @pointB))!=INFINITY && u.y < 0
    -          choiceA, choiceB = choiceB, choiceA
    -        end
    -        vectorRepel = (previousBallPosition.y > calcY(@pointA, @pointB, previousBallPosition.x)) ? choiceA : choiceB
    +      # Checks if the 3 grid spaces with the same y value (or same row) and
    +      # x values that are next to each other have pieces that belong to the same player.
    +      # Remember, the value of piece is equal to the current turn (which is the player).
    +      horizontal_match = state.spaces[xs[0]][y].piece == piece &&
    +                         state.spaces[xs[1]][y].piece == piece &&
    +                         state.spaces[xs[2]][y].piece == piece
     
    -        #vectorRepel = (previousBallPosition.y > slope(@pointA, @pointB)*previousBallPosition.x+intercept(@pointA,@pointB)) ? choiceA : choiceB)
    -        if (slope(@pointA, @pointB) == INFINITY) #slope INFINITY breaks down in the above test, ergo it requires a custom test
    -          vectorRepel = (previousBallPosition.x > @pointA.x) ? (u.mult(1)) : (u.mult(-1))
    -        end
    -        #puts ("     " + $t[0].to_s + "," + $t[1].to_s + "    " + $t[2].to_s + "," + $t[3].to_s + "     " + "   " + u.x.to_s + "," + u.y.to_s)
    -        #vectorRepel now has the repeling force
    +      # Checks if the 3 grid spaces with the same x value (or same column) and
    +      # y values that are next to each other have pieces that belong to the same player.
    +      # The && represents an "and" statement: if even one part of the statement is false,
    +      # the entire statement evaluates to false.
    +      vertical_match = state.spaces[y][xs[0]].piece == piece &&
    +                       state.spaces[y][xs[1]].piece == piece &&
    +                       state.spaces[y][xs[2]].piece == piece
     
    -        mag = args.state.ball.velocity.mag
    -        theta_ball=Math.atan2(args.state.ball.velocity.y,args.state.ball.velocity.x) #the angle of the ball's velocity
    -        theta_repel=Math.atan2(vectorRepel.y,vectorRepel.x) #the angle of the repeling force
    -        #puts ("theta:" + theta_ball.to_s + " " + theta_repel.to_s) #theta okay
    +      horizontal_match || vertical_match # if either is true, true is returned
    +    end
     
    -        fbx = mag * Math.cos(theta_ball) #the x component of the ball's velocity
    -        fby = mag * Math.sin(theta_ball) #the y component of the ball's velocity
    +    # Sees if there is a diagonal match, starting from the bottom left and ending at the top right.
    +    # Is added to won regardless of whether the statement is true or false.
    +    won << (state.spaces[-1][-1].piece == piece && # bottom left
    +            state.spaces[ 0][ 0].piece == piece && # center
    +            state.spaces[ 1][ 1].piece == piece)   # top right
     
    -        repelMag = getRepelMagnitude(fbx, fby, vectorRepel.x, vectorRepel.y, args)
    +    # Sees if there is a diagonal match, starting at the bottom right and ending at the top left
    +    # and is added to won.
    +    won << (state.spaces[ 1][-1].piece == piece && # bottom right
    +            state.spaces[ 0][ 0].piece == piece && # center
    +            state.spaces[-1][ 1].piece == piece)   # top left
     
    -        frx = repelMag* Math.cos(theta_repel) #the x component of the repel's velocity | magnitude is set to twice of fbx
    -        fry = repelMag* Math.sin(theta_repel) #the y component of the repel's velocity | magnitude is set to twice of fby
    +    # Any false statements (meaning false diagonal matches) are rejected from won
    +    won.reject_false.any?
    +  end
     
    -        fsumx = fbx+frx #sum of x forces
    -        fsumy = fby+fry #sum of y forces
    -        fr = mag#fr is the resulting magnitude
    -        thetaNew = Math.atan2(fsumy, fsumx)  #thetaNew is the resulting angle
    -        xnew = fr*Math.cos(thetaNew) #resulting x velocity
    -        ynew = fr*Math.sin(thetaNew) #resulting y velocity
    +  # Defines filled spaces on the board by rejecting all spaces that do not have game pieces in them.
    +  # The ! before a statement means "not". For example, we are rejecting any space combinations that do
    +  # NOT have pieces in them.
    +  def filled_spaces
    +    state.space_combinations
    +      .reject { |x, y| !state.spaces[x][y].piece } # reject spaces with no pieces in them
    +      .map do |x, y|
    +        if block_given?
    +          yield x, y, state.spaces[x][y]
    +        else
    +          [x, y, state.spaces[x][y]] # sets definition of space
    +        end
    +    end
    +  end
     
    -        args.state.ball.velocity = Vector2d.new(xnew,ynew)
    -        #args.state.ball.xy.add(args.state.ball.velocity)
    -        break #no need to check the other points ?
    -      else
    +  # Defines all spaces on the board.
    +  def all_spaces
    +    if !block_given?
    +      state.space_combinations.map do |x, y|
    +        [x, y, state.spaces[x][y]] # sets definition of space
    +      end
    +    else # if a block is given (block_given? is true)
    +      state.space_combinations.map do |x, y|
    +        yield x, y, state.spaces[x][y] # yield if a block is given
           end
         end
    -  end #end update
    +  end
     
    +  # Sets values for a label, such as the position, value, size, alignment, and color.
    +  def label x, y, value
    +    [x, y + 10, value, 20, 1, 0, 0, 0]
    +  end
     end
     
    -
    -

    Physics And Collisions - Collision With Object Removal - main.rb

    -
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/main.rb
    -# coding: utf-8
    -INFINITY= 10**10
    -WIDTH=1280
    -HEIGHT=720
    +$tic_tac_toe = TicTacToe.new
     
    -require 'app/vector2d.rb'
    -require 'app/paddle.rb'
    -require 'app/ball.rb'
    -require 'app/linear_collider.rb'
    +def tick args
    +  $tic_tac_toe._       = args
    +  $tic_tac_toe.state   = args.state
    +  $tic_tac_toe.outputs = args.outputs
    +  $tic_tac_toe.inputs  = args.inputs
    +  $tic_tac_toe.grid    = args.grid
    +  $tic_tac_toe.gtk     = args.gtk
    +  $tic_tac_toe.tick
    +  tick_instructions args, "Sample app shows how to work with mouse clicks."
    +end
     
    -#Method to init default values
    -def defaults args
    -  args.state.game_board ||= [(args.grid.w / 2 - args.grid.w / 4), 0, (args.grid.w / 2), args.grid.h]
    -  args.state.bricks ||= []
    -  args.state.num_bricks ||= 0
    -  args.state.game_over_at ||= 0
    -  args.state.paddle ||= Paddle.new
    -  args.state.ball   ||= Ball.new
    -  args.state.westWall  ||= LinearCollider.new({x: args.grid.w/4,      y: 0},          {x: args.grid.w/4,      y: args.grid.h}, :pos)
    -  args.state.eastWall  ||= LinearCollider.new({x: 3*args.grid.w*0.25, y: 0},          {x: 3*args.grid.w*0.25, y: args.grid.h})
    -  args.state.southWall ||= LinearCollider.new({x: 0,                  y: 0},          {x: args.grid.w,        y: 0})
    -  args.state.northWall ||= LinearCollider.new({x: 0,                  y:args.grid.h}, {x: args.grid.w,        y: args.grid.h}, :pos)
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.mouse.click ||
    +     args.inputs.keyboard.directional_vector ||
    +     args.inputs.keyboard.key_down.enter ||
    +     args.inputs.keyboard.key_down.escape
    +    args.state.key_event_occurred = true
    +  end
     
    -  #args.state.testWall ||= LinearCollider.new({x:0 , y:0},{x:args.grid.w, y:args.grid.h})
    +  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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
     end
     
    -#Render loop
    -def render args
    -  render_instructions args
    -  render_board args
    -  render_bricks args
    -end
    +
    +

    Mouse - Mouse Move - main.rb

    +
    # ./samples/05_mouse/02_mouse_move/app/main.rb
    +=begin
     
    -begin :render_methods
    -  #Method to display the instructions of the game
    -  def render_instructions args
    -    args.outputs.labels << [225, args.grid.h - 30, "← and → to move the paddle left and right",  0, 1]
    -  end
    + Reminders:
     
    -  def render_board args
    -    args.outputs.borders << args.state.game_board
    -  end
    + - num1.greater(num2): Returns the greater value.
    +   For example, if we have the command
    +   puts 4.greater(3)
    +   the number 4 would be printed to the console since it has a greater value than 3.
    +   Similar to lesser, which returns the lesser value.
     
    -  def render_bricks args
    -    args.outputs.solids << args.state.bricks.map(&:rect)
    -  end
    -end
    + - find_all: Finds all elements of a collection that meet certain requirements.
    +   For example, in this sample app, we're using find_all to find all zombies that have intersected
    +   or hit the player's sprite since these zombies have been killed.
     
    -#Calls all methods necessary for performing calculations
    -def calc args
    -  add_new_bricks args
    -  reset_game args
    -  calc_collision args
    -  win_game args
    + - args.inputs.keyboard.key_down.KEY: Determines if a key is being held or pressed.
    +   Stores the frame the "down" event occurred.
    +   For more information about the keyboard, go to mygame/documentation/06-keyboard.md.
     
    -  args.state.westWall.update args
    -  args.state.eastWall.update args
    -  args.state.southWall.update args
    -  args.state.northWall.update args
    -  args.state.paddle.update args
    -  args.state.ball.update args
    + - args.outputs.sprites: An array. The values generate a sprite.
    +   The parameters are [X, Y, WIDTH, HEIGHT, PATH, ANGLE, ALPHA, RED, GREEN, BLUE]
    +   For more information about sprites, go to mygame/documentation/05-sprites.md.
     
    -  #args.state.testWall.update args
    + - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
    +   When we want to create a new object, we can declare it as a new entity and then define
    +   its properties. (Remember, you can use state to define ANY property and it will
    +   be retained across frames.)
     
    -  args.state.paddle.render args
    -  args.state.ball.render args
    -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.
     
    -begin :calc_methods
    -  def add_new_bricks args
    -    return if args.state.num_bricks > 40
    + - map: Ruby method used to transform data; used in arrays, hashes, and collections.
    +   Can be used to perform an action on every element of a collection, such as multiplying
    +   each element by 2 or declaring every element as a new entity.
     
    -    #Width of the game board is 640px
    -    brick_width = (args.grid.w / 2) / 10
    -    brick_height = brick_width / 2
    + - sample: Chooses a random element from the array.
     
    -    (4).map_with_index do |y|
    -      #Make a box that is 10 bricks wide and 4 bricks tall
    -      args.state.bricks += (10).map_with_index do |x|
    -        args.state.new_entity(:brick) do |b|
    -          b.x = x * brick_width + (args.grid.w / 2 - args.grid.w / 4)
    -          b.y = args.grid.h - ((y + 1) * brick_height)
    -          b.rect = [b.x + 1, b.y - 1, brick_width - 2, brick_height - 2, 235, 50 * y, 52]
    + - reject: Removes elements that meet certain requirements.
    +   In this sample app, we're removing/rejecting zombies that reach the center of the screen. We're also
    +   rejecting zombies that were killed more than 30 frames ago.
     
    -          #Add linear colliders to the brick
    -          b.collider_bottom = LinearCollider.new([(b.x-2), (b.y-5)], [(b.x+brick_width+1), (b.y-5)], :pos, brick_height)
    -          b.collider_right = LinearCollider.new([(b.x+brick_width+1), (b.y-5)], [(b.x+brick_width+1), (b.y+brick_height+1)], :pos)
    -          b.collider_left = LinearCollider.new([(b.x-2), (b.y-5)], [(b.x-2), (b.y+brick_height+1)], :neg)
    -          b.collider_top = LinearCollider.new([(b.x-2), (b.y+brick_height+1)], [(b.x+brick_width+1), (b.y+brick_height+1)], :neg)
    +=end
     
    -          # @xyCollision  = LinearCollider.new({x: @x,y: @y+@height}, {x: @x+@width, y: @y+@height})
    -          # @xyCollision2 = LinearCollider.new({x: @x,y: @y}, {x: @x+@width, y: @y}, :pos)
    -          # @xyCollision3 = LinearCollider.new({x: @x,y: @y}, {x: @x, y: @y+@height})
    -          # @xyCollision4 = LinearCollider.new({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height}, :pos)
    +# This sample app allows users to move around the screen in order to kill zombies. Zombies appear from every direction so the goal
    +# is to kill the zombies as fast as possible!
    +
    +class ProtectThePuppiesFromTheZombies
    +  attr_accessor :grid, :inputs, :state, :outputs
    +
    +  # Calls the methods necessary for the game to run properly.
    +  def tick
    +    defaults
    +    render
    +    calc
    +    input
    +  end
    +
    +  # Sets default values for the zombies and for the player.
    +  # Initialization happens only in the first frame.
    +  def defaults
    +    state.flash_at               ||= 0
    +    state.zombie_min_spawn_rate  ||= 60
    +    state.zombie_spawn_countdown ||= random_spawn_countdown state.zombie_min_spawn_rate
    +    state.zombies                ||= []
    +    state.killed_zombies         ||= []
    +
    +    # Declares player as a new entity and sets its properties.
    +    # The player begins the game in the center of the screen, not moving in any direction.
    +    state.player ||= state.new_entity(:player, { x: 640,
    +                                               y: 360,
    +                                               attack_angle: 0,
    +                                               dx: 0,
    +                                               dy: 0 })
    +  end
     
    -          b.broken = false
    +  # Outputs a gray background.
    +  # Calls the methods needed to output the player, zombies, etc onto the screen.
    +  def render
    +    outputs.solids << [grid.rect, 100, 100, 100]
    +    render_zombies
    +    render_killed_zombies
    +    render_player
    +    render_flash
    +  end
     
    -          args.state.num_bricks += 1
    -        end
    -      end
    +  # Outputs the zombies on the screen and sets values for the sprites, such as the position, width, height, and animation.
    +  def render_zombies
    +    outputs.sprites << state.zombies.map do |z| # performs action on all zombies in the collection
    +      z.sprite = [z.x, z.y, 4 * 3, 8 * 3, animation_sprite(z)].sprite # sets definition for sprite, calls animation_sprite method
    +      z.sprite
         end
       end
     
    -  def reset_game args
    -    if args.state.ball.xy.y < 20 && args.state.game_over_at.elapsed_time > 60
    -      #Freeze the ball
    -      args.state.ball.velocity.x = 0
    -      args.state.ball.velocity.y = 0
    -      #Freeze the paddle
    -      args.state.paddle.enabled = false
    -
    -      args.state.game_over_at = args.state.tick_count
    -    end
    +  # Outputs sprites of killed zombies, and displays a slash image to show that a zombie has been killed.
    +  def render_killed_zombies
    +    outputs.sprites << state.killed_zombies.map do |z| # performs action on all killed zombies in collection
    +      z.sprite = [z.x,
    +                  z.y,
    +                  4 * 3,
    +                  8 * 3,
    +                  animation_sprite(z, z.death_at), # calls animation_sprite method
    +                  0, # angle
    +                  255 * z.death_at.ease(30, :flip)].sprite # transparency of a zombie changes when they die
    +                  # change the value of 30 and see what happens when a zombie is killed
     
    -    if args.state.game_over_at.elapsed_time < 60 && args.state.tick_count > 60 && args.state.bricks.count != 0
    -      #Display a "Game over" message
    -      args.outputs.labels << [100, 100, "GAME OVER", 10]
    +      # Sets values to output the slash over the zombie's sprite when a zombie is killed.
    +      # The slash is tilted 45 degrees from the angle of the player's attack.
    +      # Change the 3 inside scale_rect to 30 and the slash will be HUGE! Scale_rect positions
    +      # the slash over the killed zombie's sprite.
    +      [z.sprite, [z.sprite.rect, 'sprites/slash.png', 45 + state.player.attack_angle_on_click, z.sprite.a].scale_rect(3, 0.5, 0.5)]
         end
    +  end
     
    -    #If 60 frames have passed since the game ended, restart the game
    -    if args.state.game_over_at != 0 && args.state.game_over_at.elapsed_time == 60
    -      # FIXME: only put value types in state
    -      args.state.ball = Ball.new
    +  # Outputs the player sprite using the images in the sprites folder.
    +  def render_player
    +    state.player_sprite = [state.player.x,
    +                           state.player.y,
    +                          4 * 3,
    +                          8 * 3, "sprites/player-#{animation_index(state.player.created_at_elapsed)}.png"] # string interpolation
    +    outputs.sprites << state.player_sprite
     
    -      # FIXME: only put value types in state
    -      args.state.paddle = Paddle.new
    +    # Outputs a small red square that previews the angles that the player can attack in.
    +    # It can be moved in a perfect circle around the player to show possible movements.
    +    # Change the 60 in the parenthesis and see what happens to the movement of the red square.
    +    outputs.solids <<  [state.player.x + state.player.attack_angle.vector_x(60),
    +                        state.player.y + state.player.attack_angle.vector_y(60),
    +                        3, 3, 255, 0, 0]
    +  end
     
    -      args.state.bricks = []
    -      args.state.num_bricks = 0
    -    end
    +  # Renders flash as a solid. The screen turns white for 10 frames when a zombie is killed.
    +  def render_flash
    +    return if state.flash_at.elapsed_time > 10 # return if more than 10 frames have passed since flash.
    +    # Transparency gradually changes (or eases) during the 10 frames of flash.
    +    outputs.primitives << [grid.rect, 255, 255, 255, 255 * state.flash_at.ease(10, :flip)].solid
       end
     
    -  def calc_collision args
    -    #Remove the brick if it is hit with the ball
    -    ball = args.state.ball
    -    ball_rect = [ball.xy.x, ball.xy.y, 20, 20]
    +  # Calls all methods necessary for performing calculations.
    +  def calc
    +    calc_spawn_zombie
    +    calc_move_zombies
    +    calc_player
    +    calc_kill_zombie
    +  end
     
    -    #Loop through each brick to see if the ball is colliding with it
    -    args.state.bricks.each do |b|
    -      if b.rect.intersect_rect?(ball_rect)
    -        #Run the linear collider for the brick if there is a collision
    -        b[:collider_bottom].update args
    -        b[:collider_right].update args
    -        b[:collider_left].update args
    -        b[:collider_top].update args
    +  # Decreases the zombie spawn countdown by 1 if it has a value greater than 0.
    +  def calc_spawn_zombie
    +    if state.zombie_spawn_countdown > 0
    +      state.zombie_spawn_countdown -= 1
    +      return
    +    end
     
    -        b.broken = true
    +    # New zombies are created, positioned on the screen, and added to the zombies collection.
    +    state.zombies << state.new_entity(:zombie) do |z| # each zombie is declared a new entity
    +      if rand > 0.5
    +        z.x = grid.rect.w.randomize(:ratio) # random x position on screen (within grid scope)
    +        z.y = [-10, 730].sample # y position is set to either -10 or 730 (randomly chosen)
    +        # the possible values exceed the screen's scope so zombies appear to be coming from far away
    +      else
    +        z.x = [-10, 1290].sample # x position is set to either -10 or 1290 (randomly chosen)
    +        z.y = grid.rect.w.randomize(:ratio) # random y position on screen
           end
         end
     
    -    args.state.bricks = args.state.bricks.reject(&:broken)
    +    # Calls random_spawn_countdown method (determines how fast new zombies appear)
    +    state.zombie_spawn_countdown = random_spawn_countdown state.zombie_min_spawn_rate
    +    state.zombie_min_spawn_rate -= 1
    +    # set to either the current zombie_min_spawn_rate or 0, depending on which value is greater
    +    state.zombie_min_spawn_rate  = state.zombie_min_spawn_rate.greater(0)
       end
     
    -  def win_game args
    -    if args.state.bricks.count == 0 && args.state.game_over_at.elapsed_time > 60
    -      #Freeze the ball
    -      args.state.ball.velocity.x = 0
    -      args.state.ball.velocity.y = 0
    -      #Freeze the paddle
    -      args.state.paddle.enabled = false
    -
    -      args.state.game_over_at = args.state.tick_count
    -    end
    -
    -    if args.state.game_over_at.elapsed_time < 60 && args.state.tick_count > 60 && args.state.bricks.count == 0
    -      #Display a "Game over" message
    -      args.outputs.labels << [100, 100, "CONGRATULATIONS!", 10]
    +  # Moves all zombies towards the center of the screen.
    +  # All zombies that reach the center (640, 360) are rejected from the zombies collection and disappear.
    +  def calc_move_zombies
    +    state.zombies.each do |z| # for each zombie in the collection
    +      z.y = z.y.towards(360, 0.1) # move the zombie towards the center (640, 360) at a rate of 0.1
    +      z.x = z.x.towards(640, 0.1) # change 0.1 to 1.1 and see how much faster the zombies move to the center
         end
    +    state.zombies = state.zombies.reject { |z| z.y == 360 && z.x == 640 } # remove zombies that are in center
       end
     
    -end
    -
    -def tick args
    -  defaults args
    -  render args
    -  calc args
    +  # Calculates the position and movement of the player on the screen.
    +  def calc_player
    +    state.player.x += state.player.dx # changes x based on dx (change in x)
    +    state.player.y += state.player.dy # changes y based on dy (change in y)
     
    -  #args.outputs.lines << [0, 0, args.grid.w, args.grid.h]
    +    state.player.dx *= 0.9 # scales dx down
    +    state.player.dy *= 0.9 # scales dy down
     
    -  #$tc+=1
    -  #if $tc == 5
    -    #$train << [args.state.ball.xy.x, args.state.ball.xy.y]
    -    #$tc = 0
    -  #end
    -  #for t in $train
    +    # Compares player's x to 1280 to find lesser value, then compares result to 0 to find greater value.
    +    # This ensures that the player remains within the screen's scope.
    +    state.player.x = state.player.x.lesser(1280).greater(0)
    +    state.player.y = state.player.y.lesser(720).greater(0) # same with player's y
    +  end
     
    -    #args.outputs.solids << [t[0],t[1],5,5,255,0,0];
    -  #end
    -end
    +  # Finds all zombies that intersect with the player's sprite. These zombies are removed from the zombies collection
    +  # and added to the killed_zombies collection since any zombie that intersects with the player is killed.
    +  def calc_kill_zombie
     
    -
    -

    Physics And Collisions - Collision With Object Removal - paddle.rb

    -
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/paddle.rb
    -class Paddle
    -  attr_accessor :enabled
    +    # Find all zombies that intersect with the player. They are considered killed.
    +    killed_this_frame = state.zombies.find_all { |z| z.sprite.intersect_rect? state.player_sprite }
    +    state.zombies = state.zombies - killed_this_frame # remove newly killed zombies from zombies collection
    +    state.killed_zombies += killed_this_frame # add newly killed zombies to killed zombies
     
    -  def initialize ()
    -    @x=WIDTH/2
    -    @y=100
    -    @width=100
    -    @height=20
    -    @speed=10
    +    if killed_this_frame.length > 0 # if atleast one zombie was killed in the frame
    +      state.flash_at = state.tick_count # flash_at set to the frame when the zombie was killed
    +    # Don't forget, the rendered flash lasts for 10 frames after the zombie is killed (look at render_flash method)
    +    end
     
    -    @xyCollision  = LinearCollider.new({x: @x,y: @y+@height+5}, {x: @x+@width, y: @y+@height+5})
    -    @xyCollision2 = LinearCollider.new({x: @x,y: @y}, {x: @x+@width, y: @y}, :pos)
    -    @xyCollision3 = LinearCollider.new({x: @x,y: @y}, {x: @x, y: @y+@height+5})
    -    @xyCollision4 = LinearCollider.new({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height+5}, :pos)
    +    # Sets the tick_count (passage of time) as the value of the death_at variable for each killed zombie.
    +    # Death_at stores the frame a zombie was killed.
    +    killed_this_frame.each do |z|
    +      z.death_at = state.tick_count
    +    end
     
    -    @enabled = true
    +    # Zombies are rejected from the killed_zombies collection depending on when they were killed.
    +    # They are rejected if more than 30 frames have passed since their death.
    +    state.killed_zombies = state.killed_zombies.reject { |z| state.tick_count - z.death_at > 30 }
       end
     
    -  def update args
    -    @xyCollision.resetPoints({x: @x,y: @y+@height+5}, {x: @x+@width, y: @y+@height+5})
    -    @xyCollision2.resetPoints({x: @x,y: @y}, {x: @x+@width, y: @y})
    -    @xyCollision3.resetPoints({x: @x,y: @y}, {x: @x, y: @y+@height+5})
    -    @xyCollision4.resetPoints({x: @x+@width,y: @y}, {x: @x+@width, y: @y+@height+5})
    +  # Uses input from the user to move the player around the screen.
    +  def input
     
    -    @xyCollision.update  args
    -    @xyCollision2.update args
    -    @xyCollision3.update args
    -    @xyCollision4.update args
    +    # If the "a" key or left key is pressed, the x position of the player decreases.
    +    # Otherwise, if the "d" key or right key is pressed, the x position of the player increases.
    +    if inputs.keyboard.key_held.a || inputs.keyboard.key_held.left
    +      state.player.x -= 5
    +    elsif inputs.keyboard.key_held.d || inputs.keyboard.key_held.right
    +      state.player.x += 5
    +    end
     
    -    args.inputs.keyboard.key_held.left  ||= false
    -    args.inputs.keyboard.key_held.right  ||= false
    +    # If the "w" or up key is pressed, the y position of the player increases.
    +    # Otherwise, if the "s" or down key is pressed, the y position of the player decreases.
    +    if inputs.keyboard.key_held.w || inputs.keyboard.key_held.up
    +      state.player.y += 5
    +    elsif inputs.keyboard.key_held.s || inputs.keyboard.key_held.down
    +      state.player.y -= 5
    +    end
     
    -    if not (args.inputs.keyboard.key_held.left == args.inputs.keyboard.key_held.right)
    -      if args.inputs.keyboard.key_held.left && @enabled
    -        @x-=@speed
    -      elsif args.inputs.keyboard.key_held.right && @enabled
    -        @x+=@speed
    -      end
    +    # Sets the attack angle so the player can move and attack in the precise direction it wants to go.
    +    # If the mouse is moved, the attack angle is changed (based on the player's position and mouse position).
    +    # Attack angle also contributes to the position of red square.
    +    if inputs.mouse.moved
    +      state.player.attack_angle = inputs.mouse.position.angle_from [state.player.x, state.player.y]
         end
     
    -    xmin =WIDTH/4
    -    xmax = 3*(WIDTH/4)
    -    @x = (@x+@width > xmax) ? xmax-@width : (@x
    -

    Physics And Collisions - Collision With Object Removal - tests.rb

    -
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/tests.rb
    -# For advanced users:
    -# You can put some quick verification tests here, any method
    -# that starts with the `test_` will be run when you save this file.
    +

    Mouse - Mouse Move Paint App - main.rb

    +
    # ./samples/05_mouse/03_mouse_move_paint_app/app/main.rb
    +=begin
     
    -# Here is an example test and game
    + APIs listing that haven't been encountered in previous sample apps:
     
    -# To run the test: ./dragonruby mygame --eval app/tests.rb --no-tick
    + - Floor: Method that returns an integer number smaller than or equal to the original with no decimal.
     
    -class MySuperHappyFunGame
    -  attr_gtk
    +   For example, if we have a variable, a = 13.7, and we called floor on it, it would look like this...
    +   puts a.floor()
    +   which would print out 13.
    +   (There is also a ceil method, which returns an integer number greater than or equal to the original
    +   with no decimal. If we had called ceil on the variable a, the result would have been 14.)
     
    -  def tick
    -    outputs.solids << [100, 100, 300, 300]
    -  end
    -end
    + Reminders:
     
    -def test_universe args, assert
    -  game = MySuperHappyFunGame.new
    -  game.args = args
    -  game.tick
    -  assert.true!  args.outputs.solids.length == 1, "failure: a solid was not added after tick"
    -  assert.false! 1 == 2, "failure: some how, 1 equals 2, the world is ending"
    -  puts "test_universe completed successfully"
    -end
    + - Hashes: Collection of unique keys and their corresponding values. The value can be found
    +   using their keys.
     
    -puts "running tests"
    -$gtk.reset 100
    -$gtk.log_level = :off
    -$gtk.tests.start
    +   For example, if we have a "numbers" hash that stores numbers in English as the
    +   key and numbers in Spanish as the value, we'd have a hash that looks like this...
    +   numbers = { "one" => "uno", "two" => "dos", "three" => "tres" }
    +   and on it goes.
     
    -
    -

    Physics And Collisions - Collision With Object Removal - vector2d.rb

    -
    # ./samples/04_physics_and_collisions/10_collision_with_object_removal/app/vector2d.rb
    +   Now if we wanted to find the corresponding value of the "one" key, we could say
    +   puts numbers["one"]
    +   which would print "uno" to the console.
     
    -class Vector2d
    -  attr_accessor :x, :y
    + - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
    +   In this sample app, new_entity is used to create a new button that clears the grid.
    +   (Remember, you can use state to define ANY property and it will be retained across frames.)
     
    -  def initialize x=0, y=0
    -    @x=x
    -    @y=y
    -  end
    + - args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
     
    -  #returns a vector multiplied by scalar x
    -  #x [float] scalar
    -  def mult x
    -    r = Vector2d.new(0,0)
    -    r.x=@x*x
    -    r.y=@y*x
    -    r
    -  end
    + - args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
     
    -  # vect [Vector2d] vector to copy
    -  def copy vect
    -    Vector2d.new(@x, @y)
    -  end
    + - args.outputs.labels: An array. The values in the array generate a label.
    +   The parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    +   For more information about labels, go to mygame/documentation/02-labels.md.
     
    -  #returns a new vector equivalent to this+vect
    -  #vect [Vector2d] vector to add to self
    -  def add vect
    -    Vector2d.new(@x+vect.x,@y+vect.y)
    -  end
    + - ARRAY#inside_rect?: Returns true or false depending on if the point is inside the rect.
     
    -  #returns a new vector equivalent to this-vect
    -  #vect [Vector2d] vector to subtract to self
    -  def sub vect
    -    Vector2d.new(@x-vect.c, @y-vect.y)
    +=end
    +
    +# This sample app shows an empty grid that the user can paint on.
    +# To paint, the user must keep their mouse presssed and drag it around the grid.
    +# The "clear" button allows users to clear the grid so they can start over.
    +
    +class PaintApp
    +  attr_accessor :inputs, :state, :outputs, :grid, :args
    +
    +  # Runs methods necessary for the game to function properly.
    +  def tick
    +    print_title
    +    add_grid
    +    check_click
    +    draw_buttons
       end
     
    -  #return the magnitude of the vector
    -  def mag
    -    ((@x**2)+(@y**2))**0.5
    +  # Prints the title onto the screen by using a label.
    +  # Also separates the title from the grid with a line as a horizontal separator.
    +  def print_title
    +    args.outputs.labels << [ 640, 700, 'Paint!', 0, 1 ]
    +    outputs.lines << horizontal_separator(660, 0, 1280)
       end
     
    -  #returns a new normalize version of the vector
    -  def normalize
    -    Vector2d.new(@x/mag, @y/mag)
    +  # Sets the starting position, ending position, and color for the horizontal separator.
    +  # The starting and ending positions have the same y values.
    +  def horizontal_separator y, x, x2
    +    [x, y, x2, y, 150, 150, 150]
       end
     
    -  #TODO delet?
    -  def distABS vect
    -    (((vect.x-@x)**2+(vect.y-@y)**2)**0.5).abs()
    +  # Sets the starting position, ending position, and color for the vertical separator.
    +  # The starting and ending positions have the same x values.
    +  def vertical_separator x, y, y2
    +    [x, y, x, y2, 150, 150, 150]
       end
    -end
     
    -
    -

    Mouse - Mouse Click - main.rb

    -
    # ./samples/05_mouse/01_mouse_click/app/main.rb
    -=begin
    +  # Outputs a border and a grid containing empty squares onto the screen.
    +  def add_grid
     
    - APIs listing that haven't been encountered in previous sample apps:
    +    # Sets the x, y, height, and width of the grid.
    +    # There are 31 horizontal lines and 31 vertical lines in the grid.
    +    # Feel free to count them yourself before continuing!
    +    x, y, h, w = 640 - 500/2, 640 - 500, 500, 500 # calculations done so the grid appears in screen's center
    +    lines_h = 31
    +    lines_v = 31
     
    - - product: Returns an array of all combinations of elements from all arrays.
    +    # Sets values for the grid's border, grid lines, and filled squares.
    +    # The filled_squares variable is initially set to an empty array.
    +    state.grid_border ||= [ x, y, h, w ] # definition of grid's outer border
    +    state.grid_lines ||= draw_grid(x, y, h, w, lines_h, lines_v) # calls draw_grid method
    +    state.filled_squares ||= [] # there are no filled squares until the user fills them in
     
    -   For example, [1,2].product([1,2]) would return the following array...
    -   [[1,1], [1,2], [2,1], [2,2]]
    -   More than two arrays can be given to product and it will still work,
    -   such as [1,2].product([1,2],[3,4]). What would product return in this case?
    +    # Outputs the grid lines, border, and filled squares onto the screen.
    +    outputs.lines.concat state.grid_lines
    +    outputs.borders << state.grid_border
    +    outputs.solids << state.filled_squares
    +  end
     
    -   Answer:
    -   [[1,1,3],[1,1,4],[1,2,3],[1,2,4],[2,1,3],[2,1,4],[2,2,3],[2,2,4]]
    +  # Draws the grid by adding in vertical and horizontal separators.
    +  def draw_grid x, y, h, w, lines_h, lines_v
     
    - - num1.fdiv(num2): Returns the float division (will have a decimal) of the two given numbers.
    -   For example, 5.fdiv(2) = 2.5 and 5.fdiv(5) = 1.0
    +    # The grid starts off empty.
    +    grid = []
     
    - - yield: Allows you to call a method with a code block and yield to that block.
    +    # Calculates the placement and adds horizontal lines or separators into the grid.
    +    curr_y = y # start at the bottom of the box
    +    dist_y = h / (lines_h + 1) # finds distance to place horizontal lines evenly throughout 500 height of grid
    +    lines_h.times do
    +      curr_y += dist_y # increment curr_y by the distance between the horizontal lines
    +      grid << horizontal_separator(curr_y, x, x + w - 1) # add a separator into the grid
    +    end
     
    - Reminders:
    +    # Calculates the placement and adds vertical lines or separators into the grid.
    +    curr_x = x # now start at the left of the box
    +    dist_x = w / (lines_v + 1) # finds distance to place vertical lines evenly throughout 500 width of grid
    +    lines_v.times do
    +      curr_x += dist_x # increment curr_x by the distance between the vertical lines
    +      grid << vertical_separator(curr_x, y + 1, y  + h) # add separator
    +    end
     
    - - ARRAY#inside_rect?: Returns true or false depending on if the point is inside the rect.
    +    # paint_grid uses a hash to assign values to keys.
    +    state.paint_grid ||= {"x" => x, "y" => y, "h" => h, "w" => w, "lines_h" => lines_h,
    +                          "lines_v" => lines_v, "dist_x" => dist_x,
    +                          "dist_y" => dist_y }
     
    - - 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.
    +    return grid
    +  end
     
    - - args.inputs.mouse.click: This property will be set if the mouse was clicked.
    +  # Checks if the user is keeping the mouse pressed down and sets the mouse_hold variable accordingly using boolean values.
    +  # If the mouse is up, the user cannot drag the mouse.
    +  def check_click
    +    if inputs.mouse.down #is mouse up or down?
    +      state.mouse_held = true # mouse is being held down
    +    elsif inputs.mouse.up # if mouse is up
    +    state.mouse_held = false # mouse is not being held down or dragged
    +      state.mouse_dragging = false
    +    end
     
    - - Ternary operator (?): Will evaluate a statement (just like an if statement)
    -   and perform an action if the result is true or another action if it is false.
    +    if state.mouse_held &&    # mouse needs to be down
    +      !inputs.mouse.click &&     # must not be first click
    +      ((inputs.mouse.previous_click.point.x - inputs.mouse.position.x).abs > 15) # Need to move 15 pixels before "drag"
    +      state.mouse_dragging = true
    +    end
     
    - - reject: Removes elements from a collection if they meet certain requirements.
    +    # If the user clicks their mouse inside the grid, the search_lines method is called with a click input type.
    +    if ((inputs.mouse.click) && (inputs.mouse.click.point.inside_rect? state.grid_border))
    +      search_lines(inputs.mouse.click.point, :click)
     
    - - args.outputs.borders: An array. The values generate a border.
    -   The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE]
    -   For more information about borders, go to mygame/documentation/03-solids-and-borders.md.
    +    # If the user drags their mouse inside the grid, the search_lines method is called with a drag input type.
    +    elsif ((state.mouse_dragging) && (inputs.mouse.position.inside_rect? state.grid_border))
    +      search_lines(inputs.mouse.position, :drag)
    +    end
    +  end
     
    - - 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.
    +  # Sets the definition of a grid box and handles user input to fill in or clear grid boxes.
    +  def search_lines (point, input_type)
    +    point.x -= state.paint_grid["x"] # subtracts the value assigned to the "x" key in the paint_grid hash
    +    point.y -= state.paint_grid["y"] # subtracts the value assigned to the "y" key in the paint_grid hash
     
    -=end
    +    # Remove code following the .floor and see what happens when you try to fill in grid squares
    +    point.x = (point.x / state.paint_grid["dist_x"]).floor * state.paint_grid["dist_x"]
    +    point.y = (point.y / state.paint_grid["dist_y"]).floor * state.paint_grid["dist_y"]
     
    -# This sample app is a classic game of Tic Tac Toe.
    +    point.x += state.paint_grid["x"]
    +    point.y += state.paint_grid["y"]
     
    -class TicTacToe
    -  attr_accessor :_, :state, :outputs, :inputs, :grid, :gtk
    +    # Sets definition of a grid box, meaning its x, y, width, and height.
    +    # Floor is called on the point.x and point.y variables.
    +    # Ceil method is called on values of the distance hash keys, setting the width and height of a box.
    +    grid_box = [ point.x.floor, point.y.floor, state.paint_grid["dist_x"].ceil, state.paint_grid["dist_y"].ceil ]
     
    -  # Starts the game with player x's turn and creates an array (to_a) for space combinations.
    -  # Calls methods necessary for the game to run properly.
    -  def tick
    -    state.current_turn ||= :x
    -    state.space_combinations = [-1, 0, 1].product([-1, 0, 1]).to_a
    -    render_board
    -    input_board
    +    if input_type == :click # if user clicks their mouse
    +      if state.filled_squares.include? grid_box # if grid box is already filled in
    +        state.filled_squares.delete grid_box # box is cleared and removed from filled_squares
    +      else
    +        state.filled_squares << grid_box # otherwise, box is filled in and added to filled_squares
    +      end
    +    elsif input_type == :drag # if user drags mouse
    +      unless state.filled_squares.include? grid_box # unless the grid box dragged over is already filled in
    +        state.filled_squares << grid_box # the box is filled in and added to filled_squares
    +      end
    +    end
       end
     
    -  # Uses borders to create grid squares for the game's board. Also outputs the game pieces using labels.
    -  def render_board
    -    square_size = 80
    +  # Creates and outputs a "Clear" button on the screen using a label and a border.
    +  # If the button is clicked, the filled squares are cleared, making the filled_squares collection empty.
    +  def draw_buttons
    +    x, y, w, h = 390, 50, 240, 50
    +    state.clear_button        ||= state.new_entity(:button_with_fade)
     
    -    # Positions the game's board in the center of the screen.
    -    # Try removing what follows grid.w_half or grid.h_half and see how the position changes!
    -    board_left = grid.w_half - square_size * 1.5
    -    board_top  = grid.h_half - square_size * 1.5
    +    # The x and y positions are set to display the label in the center of the button.
    +    # Try changing the first two parameters to simply x, y and see what happens to the text placement!
    +    state.clear_button.label  ||= [x + w.half, y + h.half + 10, "Clear", 0, 1] # placed in center of border
    +    state.clear_button.border ||= [x, y, w, h]
     
    -    # At first glance, the add(1) looks pretty trivial. But if you remove it,
    -    # you'll see that the positioning of the board would be skewed without it!
    -    # Or if you put 2 in the parenthesis, the pieces will be placed in the wrong squares
    -    # due to the change in board placement.
    -    outputs.borders << all_spaces do |x, y, space| # outputs borders for all board spaces
    -      space.border ||= [
    -        board_left + x.add(1) * square_size, # space.border is initialized using this definition
    -        board_top  + y.add(1) * square_size,
    -        square_size,
    -        square_size
    -      ]
    +    # If the mouse is clicked inside the borders of the clear button,
    +    # the filled_squares collection is emptied and the squares are cleared.
    +    if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.clear_button.border)
    +      state.clear_button.clicked_at = inputs.mouse.click.created_at # time (frame) the click occurred
    +      state.filled_squares.clear
    +      inputs.mouse.previous_click = nil
         end
     
    -    # Again, the calculations ensure that the piece is placed in the center of the grid square.
    -    # Remove the '- 20' and the piece will be placed at the top of the grid square instead of the center.
    -    outputs.labels << filled_spaces do |x, y, space| # put label in each filled space of board
    -          label board_left + x.add(1) * square_size + square_size.fdiv(2),
    -          board_top  + y.add(1) * square_size + square_size - 20,
    -          space.piece # text of label, either "x" or "o"
    -    end
    +    outputs.labels << state.clear_button.label
    +    outputs.borders << state.clear_button.border
     
    -    # Uses a label to output whether x or o won, or if a draw occurred.
    -    # If the game is ongoing, a label shows whose turn it currently is.
    -    outputs.labels << if state.x_won
    -                        label grid.w_half, grid.top - 80, "x won" # the '-80' positions the label 80 pixels lower than top
    -                      elsif state.o_won
    -                        label grid.w_half, grid.top - 80, "o won" # grid.w_half positions the label in the center horizontally
    -                      elsif state.draw
    -                        label grid.w_half, grid.top - 80, "a draw"
    -                      else # if no one won and the game is ongoing
    -                        label grid.w_half, grid.top - 80, "turn: #{state.current_turn}"
    -                      end
    +    # When the clear button is clicked, the color of the button changes
    +    # and the transparency changes, as well. If you change the time from
    +    # 0.25.seconds to 1.25.seconds or more, the change will last longer.
    +    if state.clear_button.clicked_at
    +      outputs.solids << [x, y, w, h, 0, 180, 80, 255 * state.clear_button.clicked_at.ease(0.25.seconds, :flip)]
    +    end
       end
    +end
     
    -  # Calls the methods responsible for handling user input and determining the winner.
    -  # Does nothing unless the mouse is clicked.
    -  def input_board
    -    return unless inputs.mouse.click
    -    input_place_piece
    -    input_restart_game
    -    determine_winner
    +$paint_app = PaintApp.new
    +
    +def tick args
    +  $paint_app.inputs = args.inputs
    +  $paint_app.state = args.state
    +  $paint_app.grid = args.grid
    +  $paint_app.args = args
    +  $paint_app.outputs = args.outputs
    +  $paint_app.tick
    +  tick_instructions args, "How to create a simple paint app. CLICK and HOLD to draw."
    +end
    +
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.mouse.click ||
    +     args.inputs.keyboard.directional_vector ||
    +     args.inputs.keyboard.key_down.enter ||
    +     args.inputs.keyboard.key_down.escape
    +    args.state.key_event_occurred = true
       end
     
    -  # Handles user input for placing pieces on the board.
    -  def input_place_piece
    -    return if state.game_over
    +  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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    +end
     
    -    # Checks to find the space that the mouse was clicked inside of, and makes sure the space does not already
    -    # have a piece in it.
    -    __, __, space = all_spaces.find do |__, __, space|
    -      inputs.mouse.click.point.inside_rect?(space.border) && !space.piece
    -    end
    +
    +

    Mouse - Coordinate Systems - main.rb

    +
    # ./samples/05_mouse/04_coordinate_systems/app/main.rb
    +=begin
     
    -    # The piece that goes into the space belongs to the player whose turn it currently is.
    -    return unless space
    -    space.piece = state.current_turn
    + APIs listing that haven't been encountered in previous sample apps:
     
    -    # This ternary operator statement allows us to change the current player's turn.
    -    # If it is currently x's turn, it becomes o's turn. If it is not x's turn, it become's x's turn.
    -    state.current_turn = state.current_turn == :x ? :o : :x
    -  end
    + - args.inputs.mouse.click.position: Coordinates of the mouse's position on the screen.
    +   Unlike args.inputs.mouse.click.point, the mouse does not need to be pressed down for
    +   position to know the mouse's coordinates.
    +   For more information about the mouse, go to mygame/documentation/07-mouse.md.
     
    -  # Resets the game.
    -  def input_restart_game
    -    return unless state.game_over
    -    gtk.reset
    -  end
    + Reminders:
     
    -  # Checks if x or o won the game.
    -  # If neither player wins and all nine squares are filled, a draw happens.
    -  # Once a player is chosen as the winner or a draw happens, the game is over.
    -  def determine_winner
    -    state.x_won = won? :x # evaluates to either true or false (boolean values)
    -    state.o_won = won? :o
    -    state.draw = true if filled_spaces.length == 9 && !state.x_won && !state.o_won
    -    state.game_over = state.x_won || state.o_won || state.draw
    -  end
    + - args.inputs.mouse.click: This property will be set if the mouse was clicked.
     
    -  # Determines if a player won by checking if there is a horizontal match or vertical match.
    -  # Horizontal_match and vertical_match have boolean values. If either is true, the game has been won.
    -  def won? piece
    -    # performs action on all space combinations
    -    won = [[-1, 0, 1]].product([-1, 0, 1]).map do |xs, y|
    + - args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
     
    -      # Checks if the 3 grid spaces with the same y value (or same row) and
    -      # x values that are next to each other have pieces that belong to the same player.
    -      # Remember, the value of piece is equal to the current turn (which is the player).
    -      horizontal_match = state.spaces[xs[0]][y].piece == piece &&
    -                         state.spaces[xs[1]][y].piece == piece &&
    -                         state.spaces[xs[2]][y].piece == piece
    + - 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.
     
    -      # Checks if the 3 grid spaces with the same x value (or same column) and
    -      # y values that are next to each other have pieces that belong to the same player.
    -      # The && represents an "and" statement: if even one part of the statement is false,
    -      # the entire statement evaluates to false.
    -      vertical_match = state.spaces[y][xs[0]].piece == piece &&
    -                       state.spaces[y][xs[1]].piece == piece &&
    -                       state.spaces[y][xs[2]].piece == piece
    +   In this sample app, string interpolation is used to show the current position of the mouse
    +   in a label.
     
    -      horizontal_match || vertical_match # if either is true, true is returned
    -    end
    + - args.outputs.labels: An array that generates a label.
    +   The parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    +   For more information about labels, go to mygame/documentation/02-labels.md.
     
    -    # Sees if there is a diagonal match, starting from the bottom left and ending at the top right.
    -    # Is added to won regardless of whether the statement is true or false.
    -    won << (state.spaces[-1][-1].piece == piece && # bottom left
    -            state.spaces[ 0][ 0].piece == piece && # center
    -            state.spaces[ 1][ 1].piece == piece)   # top right
    + - args.outputs.solids: An array that generates a solid.
    +   The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE, ALPHA]
    +   For more information about solids, go to mygame/documentation/03-solids-and-borders.md.
     
    -    # Sees if there is a diagonal match, starting at the bottom right and ending at the top left
    -    # and is added to won.
    -    won << (state.spaces[ 1][-1].piece == piece && # bottom right
    -            state.spaces[ 0][ 0].piece == piece && # center
    -            state.spaces[-1][ 1].piece == piece)   # top left
    + - args.outputs.lines: An array that generates a line.
    +   The parameters are [X, Y, X2, Y2, RED, GREEN, BLUE, ALPHA]
    +   For more information about lines, go to mygame/documentation/04-lines.md.
     
    -    # Any false statements (meaning false diagonal matches) are rejected from won
    -    won.reject_false.any?
    -  end
    +=end
     
    -  # Defines filled spaces on the board by rejecting all spaces that do not have game pieces in them.
    -  # The ! before a statement means "not". For example, we are rejecting any space combinations that do
    -  # NOT have pieces in them.
    -  def filled_spaces
    -    state.space_combinations
    -      .reject { |x, y| !state.spaces[x][y].piece } # reject spaces with no pieces in them
    -      .map do |x, y|
    -        if block_given?
    -          yield x, y, state.spaces[x][y]
    -        else
    -          [x, y, state.spaces[x][y]] # sets definition of space
    -        end
    -    end
    -  end
    +# This sample app shows a coordinate system or grid. The user can move their mouse around the screen and the
    +# coordinates of their position on the screen will be displayed. Users can choose to view one quadrant or
    +# four quadrants by pressing the button.
     
    -  # Defines all spaces on the board.
    -  def all_spaces
    -    if !block_given?
    -      state.space_combinations.map do |x, y|
    -        [x, y, state.spaces[x][y]] # sets definition of space
    -      end
    -    else # if a block is given (block_given? is true)
    -      state.space_combinations.map do |x, y|
    -        yield x, y, state.spaces[x][y] # yield if a block is given
    +def tick args
    +
    +  # The addition and subtraction in the first two parameters of the label and solid
    +  # ensure that the outputs don't overlap each other. Try removing them and see what happens.
    +  pos = args.inputs.mouse.position # stores coordinates of mouse's position
    +  args.outputs.labels << [pos.x + 10, pos.y + 10, "#{pos}"] # outputs label of coordinates
    +  args.outputs.solids << [pos.x -  2, pos.y - 2, 5, 5] # outputs small blackk box placed where mouse is hovering
    +
    +  button = [0, 0, 370, 50] # sets definition of toggle button
    +  args.outputs.borders << button # outputs button as border (not filled in)
    +  args.outputs.labels << [10, 35, "click here toggle coordinate system"] # label of button
    +  args.outputs.lines << [    0, -720,    0, 720] # vertical line dividing quadrants
    +  args.outputs.lines << [-1280,    0, 1280,   0] # horizontal line dividing quadrants
    +
    +  if args.inputs.mouse.click # if the user clicks the mouse
    +    pos = args.inputs.mouse.click.point # pos's value is point where user clicked (coordinates)
    +    if pos.inside_rect? button # if the click occurred inside the button
    +      if args.grid.name == :bottom_left # if the grid shows bottom left as origin
    +        args.grid.origin_center! # origin will be shown in center
    +      else
    +        args.grid.origin_bottom_left! # otherwise, the view will change to show bottom left as origin
           end
         end
       end
     
    -  # Sets values for a label, such as the position, value, size, alignment, and color.
    -  def label x, y, value
    -    [x, y + 10, value, 20, 1, 0, 0, 0]
    -  end
    -end
    -
    -$tic_tac_toe = TicTacToe.new
    -
    -def tick args
    -  $tic_tac_toe._       = args
    -  $tic_tac_toe.state   = args.state
    -  $tic_tac_toe.outputs = args.outputs
    -  $tic_tac_toe.inputs  = args.inputs
    -  $tic_tac_toe.grid    = args.grid
    -  $tic_tac_toe.gtk     = args.gtk
    -  $tic_tac_toe.tick
    -  tick_instructions args, "Sample app shows how to work with mouse clicks."
    +  tick_instructions args, "Sample app shows the two supported coordinate systems in Game Toolkit."
     end
     
     def tick_instructions args, text, y = 715
    @@ -10509,1028 +10521,1394 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Mouse - Mouse Move - main.rb

    -
    # ./samples/05_mouse/02_mouse_move/app/main.rb
    +

    Save Load - Save Load Game - main.rb

    +
    # ./samples/06_save_load/01_save_load_game/app/main.rb
     =begin
     
    - Reminders:
    + APIs listing that haven't been encountered in previous sample apps:
     
    - - num1.greater(num2): Returns the greater value.
    -   For example, if we have the command
    -   puts 4.greater(3)
    -   the number 4 would be printed to the console since it has a greater value than 3.
    -   Similar to lesser, which returns the lesser value.
    + - Symbol (:): Ruby object with a name and an internal ID. Symbols are useful
    +   because with a given symbol name, you can refer to the same object throughout
    +   a Ruby program.
     
    - - find_all: Finds all elements of a collection that meet certain requirements.
    -   For example, in this sample app, we're using find_all to find all zombies that have intersected
    -   or hit the player's sprite since these zombies have been killed.
    +   In this sample app, we're using symbols for our buttons. We have buttons that
    +   light fires, save, load, etc. Each of these buttons has a distinct symbol like
    +   :light_fire, :save_game, :load_game, etc.
     
    - - args.inputs.keyboard.key_down.KEY: Determines if a key is being held or pressed.
    -   Stores the frame the "down" event occurred.
    -   For more information about the keyboard, go to mygame/documentation/06-keyboard.md.
    + - to_sym: Returns the symbol corresponding to the given string; creates the symbol
    +   if it does not already exist.
    +   For example,
    +   'car'.to_sym
    +   would return the symbol :car.
     
    - - args.outputs.sprites: An array. The values generate a sprite.
    -   The parameters are [X, Y, WIDTH, HEIGHT, PATH, ANGLE, ALPHA, RED, GREEN, BLUE]
    -   For more information about sprites, go to mygame/documentation/05-sprites.md.
    + - last: Returns the last element of an array.
     
    - - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
    -   When we want to create a new object, we can declare it as a new entity and then define
    -   its properties. (Remember, you can use state to define ANY property and it will
    -   be retained across frames.)
    + Reminders:
     
    - - 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.
    + - num1.lesser(num2): finds the lower value of the given options.
    +   For example, in the statement
    +   a = 4.lesser(3)
    +   3 has a lower value than 4, which means that the value of a would be set to 3,
    +   but if the statement had been
    +   a = 4.lesser(5)
    +   4 has a lower value than 5, which means that the value of a would be set to 4.
     
    - - map: Ruby method used to transform data; used in arrays, hashes, and collections.
    -   Can be used to perform an action on every element of a collection, such as multiplying
    -   each element by 2 or declaring every element as a new entity.
    + - num1.fdiv(num2): returns the float division (will have a decimal) of the two given numbers.
    +   For example, 5.fdiv(2) = 2.5 and 5.fdiv(5) = 1.0
     
    - - sample: Chooses a random element from the array.
    + - 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.
     
    - - reject: Removes elements that meet certain requirements.
    -   In this sample app, we're removing/rejecting zombies that reach the center of the screen. We're also
    -   rejecting zombies that were killed more than 30 frames ago.
    + - args.outputs.labels: An array. Values generate a label.
    +   Parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    +   For more information, go to mygame/documentation/02-labels.md.
    +
    + - ARRAY#inside_rect?: An array with at least two values is considered a point. An array
    +   with at least four values is considered a rect. The inside_rect? function returns true
    +   or false depending on if the point is inside the rect.
     
     =end
     
    -# This sample app allows users to move around the screen in order to kill zombies. Zombies appear from every direction so the goal
    -# is to kill the zombies as fast as possible!
    +# This code allows users to perform different tasks, such as saving and loading the game.
    +# Users also have options to reset the game and light a fire.
     
    -class ProtectThePuppiesFromTheZombies
    -  attr_accessor :grid, :inputs, :state, :outputs
    +class TextedBasedGame
     
    -  # Calls the methods necessary for the game to run properly.
    +  # Contains methods needed for game to run properly.
    +  # Increments tick count by 1 each time it runs (60 times in a single second)
       def tick
    -    defaults
    -    render
    -    calc
    -    input
    +    default
    +    show_intro
    +    state.engine_tick_count += 1
    +    tick_fire
       end
     
    -  # Sets default values for the zombies and for the player.
    -  # Initialization happens only in the first frame.
    -  def defaults
    -    state.flash_at               ||= 0
    -    state.zombie_min_spawn_rate  ||= 60
    -    state.zombie_spawn_countdown ||= random_spawn_countdown state.zombie_min_spawn_rate
    -    state.zombies                ||= []
    -    state.killed_zombies         ||= []
    +  # Sets default values.
    +  # The ||= ensures that a variable's value is only set to the value following the = sign
    +  # if the value has not already been set before. Intialization happens only in the first frame.
    +  def default
    +    state.engine_tick_count ||= 0
    +    state.active_module     ||= :room
    +    state.fire_progress     ||= 0
    +    state.fire_ready_in     ||= 10
    +    state.previous_fire     ||= :dead
    +    state.fire              ||= :dead
    +  end
     
    -    # Declares player as a new entity and sets its properties.
    -    # The player begins the game in the center of the screen, not moving in any direction.
    -    state.player ||= state.new_entity(:player, { x: 640,
    -                                               y: 360,
    -                                               attack_angle: 0,
    -                                               dx: 0,
    -                                               dy: 0 })
    +  def show_intro
    +    return unless state.engine_tick_count == 0 # return unless the game just started
    +    set_story_line "awake." # calls set_story_line method, sets to "awake"
       end
     
    -  # Outputs a gray background.
    -  # Calls the methods needed to output the player, zombies, etc onto the screen.
    -  def render
    -    outputs.solids << [grid.rect, 100, 100, 100]
    -    render_zombies
    -    render_killed_zombies
    -    render_player
    -    render_flash
    +  # Sets story line.
    +  def set_story_line story_line
    +    state.story_line    = story_line # story line set to value of parameter
    +    state.active_module = :alert # active module set to alert
       end
     
    -  # Outputs the zombies on the screen and sets values for the sprites, such as the position, width, height, and animation.
    -  def render_zombies
    -    outputs.sprites << state.zombies.map do |z| # performs action on all zombies in the collection
    -      z.sprite = [z.x, z.y, 4 * 3, 8 * 3, animation_sprite(z)].sprite # sets definition for sprite, calls animation_sprite method
    -      z.sprite
    +  # Clears story line.
    +  def clear_storyline
    +    state.active_module = :none # active module set to none
    +    state.story_line = nil # story line is cleared, set to nil (or empty)
    +  end
    +
    +  # Determines fire progress (how close the fire is to being ready to light).
    +  def tick_fire
    +    return if state.active_module == :alert # return if active module is alert
    +    state.fire_progress += 1 # increment fire progress
    +    # fire_ready_in is 10. The fire_progress is either the current value or 10, whichever has a lower value.
    +    state.fire_progress = state.fire_progress.lesser(state.fire_ready_in)
    +  end
    +
    +  # Sets the value of fire (whether it is dead or roaring), and the story line
    +  def light_fire
    +    return unless fire_ready? # returns unless the fire is ready to be lit
    +    state.fire = :roaring # fire is lit, set to roaring
    +    state.fire_progress = 0 # the fire progress returns to 0, since the fire has been lit
    +    if state.fire != state.previous_fire
    +      set_story_line "the fire is #{state.fire}." # the story line is set using string interpolation
    +      state.previous_fire = state.fire
         end
       end
     
    -  # Outputs sprites of killed zombies, and displays a slash image to show that a zombie has been killed.
    -  def render_killed_zombies
    -    outputs.sprites << state.killed_zombies.map do |z| # performs action on all killed zombies in collection
    -      z.sprite = [z.x,
    -                  z.y,
    -                  4 * 3,
    -                  8 * 3,
    -                  animation_sprite(z, z.death_at), # calls animation_sprite method
    -                  0, # angle
    -                  255 * z.death_at.ease(30, :flip)].sprite # transparency of a zombie changes when they die
    -                  # change the value of 30 and see what happens when a zombie is killed
    +  # Checks if the fire is ready to be lit. Returns a boolean value.
    +  def fire_ready?
    +    # If fire_progress (value between 0 and 10) is equal to fire_ready_in (value of 10),
    +    # the fire is ready to be lit.
    +    state.fire_progress == state.fire_ready_in
    +  end
    +
    +  # Divides the value of the fire_progress variable by 10 to determine how close the user is to
    +  # being able to light a fire.
    +  def light_fire_progress
    +    state.fire_progress.fdiv(10) # float division
    +  end
    +
    +  # Defines fire as the state.fire variable.
    +  def fire
    +    state.fire
    +  end
    +
    +  # Sets the title of the room.
    +  def room_title
    +    return "a room that is dark" if state.fire == :dead # room is dark if the fire is dead
    +    return "a room that is lit" # room is lit if the fire is not dead
    +  end
    +
    +  # Sets the active_module to room.
    +  def go_to_room
    +    state.active_module = :room
    +  end
    +
    +  # Defines active_module as the state.active_module variable.
    +  def active_module
    +    state.active_module
    +  end
    +
    +  # Defines story_line as the state.story_line variable.
    +  def story_line
    +    state.story_line
    +  end
    +
    +  # Update every 60 frames (or every second)
    +  def should_tick?
    +    state.tick_count.mod_zero?(60)
    +  end
    +
    +  # Sets the value of the game state provider.
    +  def initialize game_state_provider
    +    @game_state_provider = game_state_provider
    +  end
    +
    +  # Defines the game state.
    +  # Any variable prefixed with an @ symbol is an instance variable.
    +  def state
    +    @game_state_provider.state
    +  end
    +
    +  # Saves the state of the game in a text file called game_state.txt.
    +  def save
    +    $gtk.serialize_state('game_state.txt', state)
    +  end
    +
    +  # Loads the game state from the game_state.txt text file.
    +  # If the load is unsuccessful, the user is informed since the story line indicates the failure.
    +  def load
    +    parsed_state = $gtk.deserialize_state('game_state.txt')
    +    if !parsed_state
    +      set_story_line "no game to load. press save first."
    +    else
    +      $gtk.args.state = parsed_state
    +    end
    +  end
    +
    +  # Resets the game.
    +  def reset
    +    $gtk.reset
    +  end
    +end
    +
    +class TextedBasedGamePresenter
    +  attr_accessor :state, :outputs, :inputs
    +
    +  # Creates empty collection called highlights.
    +  # Calls methods necessary to run the game.
    +  def tick
    +    state.layout.highlights ||= []
    +    game.tick if game.should_tick?
    +    render
    +    process_input
    +  end
     
    -      # Sets values to output the slash over the zombie's sprite when a zombie is killed.
    -      # The slash is tilted 45 degrees from the angle of the player's attack.
    -      # Change the 3 inside scale_rect to 30 and the slash will be HUGE! Scale_rect positions
    -      # the slash over the killed zombie's sprite.
    -      [z.sprite, [z.sprite.rect, 'sprites/slash.png', 45 + state.player.attack_angle_on_click, z.sprite.a].scale_rect(3, 0.5, 0.5)]
    -    end
    +  # Outputs a label of the tick count (passage of time) and calls all render methods.
    +  def render
    +    outputs.labels << [10, 30, state.tick_count]
    +    render_alert
    +    render_room
    +    render_highlights
       end
     
    -  # Outputs the player sprite using the images in the sprites folder.
    -  def render_player
    -    state.player_sprite = [state.player.x,
    -                           state.player.y,
    -                          4 * 3,
    -                          8 * 3, "sprites/player-#{animation_index(state.player.created_at_elapsed)}.png"] # string interpolation
    -    outputs.sprites << state.player_sprite
    +  # Outputs a label onto the screen that shows the story line, and also outputs a "close" button.
    +  def render_alert
    +    return unless game.active_module == :alert
     
    -    # Outputs a small red square that previews the angles that the player can attack in.
    -    # It can be moved in a perfect circle around the player to show possible movements.
    -    # Change the 60 in the parenthesis and see what happens to the movement of the red square.
    -    outputs.solids <<  [state.player.x + state.player.attack_angle.vector_x(60),
    -                        state.player.y + state.player.attack_angle.vector_y(60),
    -                        3, 3, 255, 0, 0]
    +    outputs.labels << [640, 480, game.story_line, 5, 1]  # outputs story line label
    +    outputs.primitives << button(:alert_dismiss, 490, 380, "close")  # positions "close" button under story line
       end
     
    -  # Renders flash as a solid. The screen turns white for 10 frames when a zombie is killed.
    -  def render_flash
    -    return if state.flash_at.elapsed_time > 10 # return if more than 10 frames have passed since flash.
    -    # Transparency gradually changes (or eases) during the 10 frames of flash.
    -    outputs.primitives << [grid.rect, 255, 255, 255, 255 * state.flash_at.ease(10, :flip)].solid
    -  end
    +  def render_room
    +    return unless game.active_module == :room
    +    outputs.labels << [640, 700, game.room_title, 4, 1] # outputs room title label at top of screen
     
    -  # Calls all methods necessary for performing calculations.
    -  def calc
    -    calc_spawn_zombie
    -    calc_move_zombies
    -    calc_player
    -    calc_kill_zombie
    +    # The parameters for these outputs are (symbol, x, y, text, value/percentage) and each has a y value
    +    # that positions it 60 pixels lower than the previous output.
    +
    +    # outputs the light_fire_progress bar, uses light_fire_progress for its percentage (which changes bar's appearance)
    +    outputs.primitives << progress_bar(:light_fire, 490, 600, "light fire", game.light_fire_progress)
    +    outputs.primitives << button(       :save_game, 490, 540, "save") # outputs save button
    +    outputs.primitives << button(       :load_game, 490, 480, "load") # outputs load button
    +    outputs.primitives << button(      :reset_game, 490, 420, "reset") # outputs reset button
    +    outputs.labels << [640, 30, "the fire is #{game.fire}", 0, 1] # outputs fire label at bottom of screen
       end
     
    -  # Decreases the zombie spawn countdown by 1 if it has a value greater than 0.
    -  def calc_spawn_zombie
    -    if state.zombie_spawn_countdown > 0
    -      state.zombie_spawn_countdown -= 1
    -      return
    -    end
    +  # Outputs a collection of highlights using an array to set their values, and also rejects certain values from the collection.
    +  def render_highlights
    +    state.layout.highlights.each do |h| # for each highlight in the collection
    +        h.lifetime -= 1 # decrease the value of its lifetime
    +      end
     
    -    # New zombies are created, positioned on the screen, and added to the zombies collection.
    -    state.zombies << state.new_entity(:zombie) do |z| # each zombie is declared a new entity
    -      if rand > 0.5
    -        z.x = grid.rect.w.randomize(:ratio) # random x position on screen (within grid scope)
    -        z.y = [-10, 730].sample # y position is set to either -10 or 730 (randomly chosen)
    -        # the possible values exceed the screen's scope so zombies appear to be coming from far away
    -      else
    -        z.x = [-10, 1290].sample # x position is set to either -10 or 1290 (randomly chosen)
    -        z.y = grid.rect.w.randomize(:ratio) # random y position on screen
    +      outputs.solids << state.layout.highlights.map do |h| # outputs highlights collection
    +        [h.x, h.y, h.w, h.h, h.color, 255 * h.lifetime / h.max_lifetime] # sets definition for each highlight
    +        # transparency changes; divide lifetime by max_lifetime, multiply result by 255
           end
    -    end
     
    -    # Calls random_spawn_countdown method (determines how fast new zombies appear)
    -    state.zombie_spawn_countdown = random_spawn_countdown state.zombie_min_spawn_rate
    -    state.zombie_min_spawn_rate -= 1
    -    # set to either the current zombie_min_spawn_rate or 0, depending on which value is greater
    -    state.zombie_min_spawn_rate  = state.zombie_min_spawn_rate.greater(0)
    +      # reject highlights from collection that have no remaining lifetime
    +      state.layout.highlights = state.layout.highlights.reject { |h| h.lifetime <= 0 }
       end
     
    -  # Moves all zombies towards the center of the screen.
    -  # All zombies that reach the center (640, 360) are rejected from the zombies collection and disappear.
    -  def calc_move_zombies
    -    state.zombies.each do |z| # for each zombie in the collection
    -      z.y = z.y.towards(360, 0.1) # move the zombie towards the center (640, 360) at a rate of 0.1
    -      z.x = z.x.towards(640, 0.1) # change 0.1 to 1.1 and see how much faster the zombies move to the center
    -    end
    -    state.zombies = state.zombies.reject { |z| z.y == 360 && z.x == 640 } # remove zombies that are in center
    +  # Checks whether or not a button was clicked.
    +  # Returns a boolean value.
    +  def process_input
    +    button = button_clicked? # calls button_clicked? method
       end
     
    -  # Calculates the position and movement of the player on the screen.
    -  def calc_player
    -    state.player.x += state.player.dx # changes x based on dx (change in x)
    -    state.player.y += state.player.dy # changes y based on dy (change in y)
    +  # Returns a boolean value.
    +  # Finds the button that was clicked from the button list and determines what method to call.
    +  # Adds a highlight to the highlights collection.
    +  def button_clicked?
    +    return nil unless click_pos # return nil unless click_pos holds coordinates of mouse click
    +      button = @button_list.find do |k, v| # goes through button_list to find button clicked
    +        click_pos.inside_rect? v[:primitives].last.rect # was the mouse clicked inside the rect of button?
    +      end
    +      return unless button # return unless a button was clicked
    +      method_to_call = "#{button[0]}_clicked".to_sym # sets method_to_call to symbol (like :save_game or :load_game)
    +      if self.respond_to? method_to_call # returns true if self responds to the given method (method actually exists)
    +        border = button[1][:primitives].last # sets border definition using value of last key in button list hash
     
    -    state.player.dx *= 0.9 # scales dx down
    -    state.player.dy *= 0.9 # scales dy down
    +        # declares each highlight as a new entity, sets properties
    +        state.layout.highlights << state.new_entity(:highlight) do |h|
    +            h.x = border.x
    +            h.y = border.y
    +            h.w = border.w
    +            h.h = border.h
    +            h.max_lifetime = 10
    +            h.lifetime = h.max_lifetime
    +            h.color = [120, 120, 180] # sets color to shade of purple
    +          end
     
    -    # Compares player's x to 1280 to find lesser value, then compares result to 0 to find greater value.
    -    # This ensures that the player remains within the screen's scope.
    -    state.player.x = state.player.x.lesser(1280).greater(0)
    -    state.player.y = state.player.y.lesser(720).greater(0) # same with player's y
    -  end
    +          self.send method_to_call # invoke method identified by symbol
    +        else # otherwise, if self doesn't respond to given method
    +          border = button[1][:primitives].last # sets border definition using value of last key in hash
     
    -  # Finds all zombies that intersect with the player's sprite. These zombies are removed from the zombies collection
    -  # and added to the killed_zombies collection since any zombie that intersects with the player is killed.
    -  def calc_kill_zombie
    +          # declares each highlight as a new entity, sets properties
    +          state.layout.highlights << state.new_entity(:highlight) do |h|
    +            h.x = border.x
    +            h.y = border.y
    +            h.w = border.w
    +            h.h = border.h
    +            h.max_lifetime = 4 # different max_lifetime than the one set if respond_to? had been true
    +            h.lifetime = h.max_lifetime
    +            h.color = [120, 80, 80] # sets color to dark color
    +          end
     
    -    # Find all zombies that intersect with the player. They are considered killed.
    -    killed_this_frame = state.zombies.find_all { |z| z.sprite.intersect_rect? state.player_sprite }
    -    state.zombies = state.zombies - killed_this_frame # remove newly killed zombies from zombies collection
    -    state.killed_zombies += killed_this_frame # add newly killed zombies to killed zombies
    +          # instructions for users on how to add the missing method_to_call to the code
    +          puts "It looks like #{method_to_call} doesn't exists on TextedBasedGamePresenter. Please add this method:"
    +          puts "Just copy the code below and put it in the #{TextedBasedGamePresenter} class definition."
    +          puts ""
    +          puts "```"
    +          puts "class TextedBasedGamePresenter <--- find this class and put the method below in it"
    +          puts ""
    +          puts "  def #{method_to_call}"
    +          puts "    puts 'Yay that worked!'"
    +          puts "  end"
    +          puts ""
    +          puts "end <-- make sure to put the #{method_to_call} method in between the `class` word and the final `end` statement."
    +          puts "```"
    +          puts ""
    +      end
    +  end
     
    -    if killed_this_frame.length > 0 # if atleast one zombie was killed in the frame
    -      state.flash_at = state.tick_count # flash_at set to the frame when the zombie was killed
    -    # Don't forget, the rendered flash lasts for 10 frames after the zombie is killed (look at render_flash method)
    -    end
    +  # Returns the position of the mouse when it is clicked.
    +  def click_pos
    +    return nil unless inputs.mouse.click # returns nil unless the mouse was clicked
    +    return inputs.mouse.click.point # returns location of mouse click (coordinates)
    +  end
     
    -    # Sets the tick_count (passage of time) as the value of the death_at variable for each killed zombie.
    -    # Death_at stores the frame a zombie was killed.
    -    killed_this_frame.each do |z|
    -      z.death_at = state.tick_count
    -    end
    +  # Creates buttons for the button_list and sets their values using a hash (uses symbols as keys)
    +  def button id, x, y, text
    +    @button_list[id] ||= { # assigns values to hash keys
    +      id: id,
    +      text: text,
    +      primitives: [
    +        [x + 10, y + 30, text, 2, 0].label, # positions label inside border
    +        [x, y, 300, 50].border,             # sets definition of border
    +      ]
    +    }
     
    -    # Zombies are rejected from the killed_zombies collection depending on when they were killed.
    -    # They are rejected if more than 30 frames have passed since their death.
    -    state.killed_zombies = state.killed_zombies.reject { |z| state.tick_count - z.death_at > 30 }
    +    @button_list[id][:primitives] # returns label and border for buttons
       end
     
    -  # Uses input from the user to move the player around the screen.
    -  def input
    -
    -    # If the "a" key or left key is pressed, the x position of the player decreases.
    -    # Otherwise, if the "d" key or right key is pressed, the x position of the player increases.
    -    if inputs.keyboard.key_held.a || inputs.keyboard.key_held.left
    -      state.player.x -= 5
    -    elsif inputs.keyboard.key_held.d || inputs.keyboard.key_held.right
    -      state.player.x += 5
    -    end
    +  # Creates a progress bar (used for lighting the fire) and sets its values.
    +  def progress_bar id, x, y, text, percentage
    +    @button_list[id] = { # assigns values to hash keys
    +      id: id,
    +      text: text,
    +      primitives: [
    +        [x, y, 300, 50, 100, 100, 100].solid, # sets definition for solid (which fills the bar with gray)
    +        [x + 10, y + 30, text, 2, 0].label, # sets definition for label, positions inside border
    +        [x, y, 300, 50].border, # sets definition of border
    +      ]
    +    }
     
    -    # If the "w" or up key is pressed, the y position of the player increases.
    -    # Otherwise, if the "s" or down key is pressed, the y position of the player decreases.
    -    if inputs.keyboard.key_held.w || inputs.keyboard.key_held.up
    -      state.player.y += 5
    -    elsif inputs.keyboard.key_held.s || inputs.keyboard.key_held.down
    -      state.player.y -= 5
    -    end
    +    # Fills progress bar based on percentage. If the fire was ready to be lit (100%) and we multiplied by
    +    # 100, only 1/3 of the bar would only be filled in. 200 would cause only 2/3 to be filled in.
    +    @button_list[id][:primitives][0][2] = 300 * percentage
    +    @button_list[id][:primitives]
    +  end
     
    -    # Sets the attack angle so the player can move and attack in the precise direction it wants to go.
    -    # If the mouse is moved, the attack angle is changed (based on the player's position and mouse position).
    -    # Attack angle also contributes to the position of red square.
    -    if inputs.mouse.moved
    -      state.player.attack_angle = inputs.mouse.position.angle_from [state.player.x, state.player.y]
    -    end
    +  # Defines the game.
    +  def game
    +    @game
    +  end
     
    -    if inputs.mouse.click && state.player.dx < 0.5 && state.player.dy < 0.5
    -      state.player.attack_angle_on_click = inputs.mouse.position.angle_from [state.player.x, state.player.y]
    -      state.player.attack_angle = state.player.attack_angle_on_click # player's attack angle is set
    -      state.player.dx = state.player.attack_angle.vector_x(25) # change in player's position
    -      state.player.dy = state.player.attack_angle.vector_y(25)
    -    end
    +  # Initalizes the game and creates an empty list of buttons.
    +  def initialize
    +    @game = TextedBasedGame.new self
    +    @button_list ||= {}
       end
     
    -  # Sets the zombie spawn's countdown to a random number.
    -  # How fast zombies appear (change the 60 to 6 and too many zombies will appear at once!)
    -  def random_spawn_countdown minimum
    -    10.randomize(:ratio, :sign).to_i + 60
    +  # Clears the storyline and takes the user to the room.
    +  def alert_dismiss_clicked
    +    game.clear_storyline
    +    game.go_to_room
       end
     
    -  # Helps to iterate through the images in the sprites folder by setting the animation index.
    -  # 3 frames is how long to show an image, and 6 is how many images to flip through.
    -  def animation_index at
    -    at.idiv(3).mod(6)
    +  # Lights the fire when the user clicks the "light fire" option.
    +  def light_fire_clicked
    +    game.light_fire
       end
     
    -  # Animates the zombies by using the animation index to go through the images in the sprites folder.
    -  def animation_sprite zombie, at = nil
    -    at ||= zombie.created_at_elapsed # how long it is has been since a zombie was created
    -    index = animation_index at
    -    "sprites/zombie-#{index}.png" # string interpolation to iterate through images
    +  # Saves the game when the user clicks the "save" option.
    +  def save_game_clicked
    +    game.save
       end
    -end
     
    -$protect_the_puppies_from_the_zombies = ProtectThePuppiesFromTheZombies.new
    +  # Resets the game when the user clicks the "reset" option.
    +  def reset_game_clicked
    +    game.reset
    +  end
     
    -def tick args
    -  $protect_the_puppies_from_the_zombies.grid    = args.grid
    -  $protect_the_puppies_from_the_zombies.inputs  = args.inputs
    -  $protect_the_puppies_from_the_zombies.state    = args.state
    -  $protect_the_puppies_from_the_zombies.outputs = args.outputs
    -  $protect_the_puppies_from_the_zombies.tick
    -  tick_instructions args, "How to get the mouse position and translate it to an x, y position using .vector_x and .vector_y. CLICK to play."
    +  # Loads the game when the user clicks the "load" option.
    +  def load_game_clicked
    +    game.load
    +  end
     end
     
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.mouse.click ||
    -     args.inputs.keyboard.directional_vector ||
    -     args.inputs.keyboard.key_down.enter ||
    -     args.inputs.keyboard.key_down.escape
    -    args.state.key_event_occurred = true
    -  end
    +$text_based_rpg = TextedBasedGamePresenter.new
     
    -  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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    +def tick args
    +  $text_based_rpg.state = args.state
    +  $text_based_rpg.outputs = args.outputs
    +  $text_based_rpg.inputs = args.inputs
    +  $text_based_rpg.tick
     end
     
     
    -

    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:
    -
    - - Floor: Method that returns an integer number smaller than or equal to the original with no decimal.
    -
    -   For example, if we have a variable, a = 13.7, and we called floor on it, it would look like this...
    -   puts a.floor()
    -   which would print out 13.
    -   (There is also a ceil method, which returns an integer number greater than or equal to the original
    -   with no decimal. If we had called ceil on the variable a, the result would have been 14.)
    -
    - Reminders:
    -
    - - Hashes: Collection of unique keys and their corresponding values. The value can be found
    -   using their keys.
    -
    -   For example, if we have a "numbers" hash that stores numbers in English as the
    -   key and numbers in Spanish as the value, we'd have a hash that looks like this...
    -   numbers = { "one" => "uno", "two" => "dos", "three" => "tres" }
    -   and on it goes.
    -
    -   Now if we wanted to find the corresponding value of the "one" key, we could say
    -   puts numbers["one"]
    -   which would print "uno" to the console.
    +

    Advanced Audio - Audio Mixer - main.rb

    +
    # ./samples/07_advanced_audio/01_audio_mixer/app/main.rb
    +# these are the properties that you can sent on args.audio
    +def spawn_new_sound args, name, path
    +  # Spawn randomly in an area that won't be covered by UI.
    +  screenx = (rand * 600.0) + 200.0
    +  screeny = (rand * 400.0) + 100.0
     
    - - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
    -   In this sample app, new_entity is used to create a new button that clears the grid.
    -   (Remember, you can use state to define ANY property and it will be retained across frames.)
    +  id = new_sound_id! args
    +  # you can hang anything on the audio hashes you want, so we store the
    +  #  actual screen position in here for convenience.
    +  args.audio[id] = {
    +    name: name,
    +    input: path,
    +    screenx: screenx,
    +    screeny: screeny,
    +    x: ((screenx / 1279.0) * 2.0) - 1.0,  # scale to -1.0 - 1.0 range
    +    y: ((screeny / 719.0) * 2.0) - 1.0,   # scale to -1.0 - 1.0 range
    +    z: 0.0,
    +    gain: 1.0,
    +    pitch: 1.0,
    +    looping: true,
    +    paused: false
    +  }
     
    - - args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
    +  args.state.selected = id
    +end
     
    - - args.inputs.mouse.click.point.created_at: The frame the mouse click occurred in.
    +# these are values you can change on the ~args.audio~ data structure
    +def input_panel args
    +  return unless args.state.panel
    +  return if args.state.dragging
     
    - - args.outputs.labels: An array. The values in the array generate a label.
    -   The parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    -   For more information about labels, go to mygame/documentation/02-labels.md.
    +  audio_entry = args.audio[args.state.selected]
    +  results = args.state.panel
     
    - - ARRAY#inside_rect?: Returns true or false depending on if the point is inside the rect.
    +  if args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.pitch_slider_rect.rect)
    +    audio_entry.pitch = 2.0 * ((args.inputs.mouse.x - results.pitch_slider_rect.x).to_f / (results.pitch_slider_rect.w - 1.0))
    +  elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.playtime_slider_rect.rect)
    +    audio_entry.playtime = audio_entry.length_ * ((args.inputs.mouse.x - results.playtime_slider_rect.x).to_f / (results.playtime_slider_rect.w - 1.0))
    +  elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.gain_slider_rect.rect)
    +    audio_entry.gain = (args.inputs.mouse.x - results.gain_slider_rect.x).to_f / (results.gain_slider_rect.w - 1.0)
    +  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.looping_checkbox_rect.rect)
    +    audio_entry.looping = !audio_entry.looping
    +  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.paused_checkbox_rect.rect)
    +    audio_entry.paused = !audio_entry.paused
    +  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.delete_button_rect.rect)
    +    args.audio.delete args.state.selected
    +  end
    +end
     
    -=end
    +def render_sources args
    +  args.outputs.primitives << args.audio.keys.map do |k|
    +    s = args.audio[k]
     
    -# This sample app shows an empty grid that the user can paint on.
    -# To paint, the user must keep their mouse presssed and drag it around the grid.
    -# The "clear" button allows users to clear the grid so they can start over.
    +    isselected = (k == args.state.selected)
     
    -class PaintApp
    -  attr_accessor :inputs, :state, :outputs, :grid, :args
    +    color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ]
    +    [
    +      [s.screenx, s.screeny, args.state.boxsize, args.state.boxsize, *color].solid,
     
    -  # Runs methods necessary for the game to function properly.
    -  def tick
    -    print_title
    -    add_grid
    -    check_click
    -    draw_buttons
    +      {
    +        x: s.screenx + args.state.boxsize.half,
    +        y: s.screeny,
    +        text: s.name,
    +        r: 255,
    +        g: 255,
    +        b: 255,
    +        alignment_enum: 1
    +      }.label!
    +    ]
       end
    +end
     
    -  # Prints the title onto the screen by using a label.
    -  # Also separates the title from the grid with a line as a horizontal separator.
    -  def print_title
    -    args.outputs.labels << [ 640, 700, 'Paint!', 0, 1 ]
    -    outputs.lines << horizontal_separator(660, 0, 1280)
    -  end
    +def playtime_str t
    +  minutes = (t / 60.0).floor
    +  seconds = t - (minutes * 60.0).to_f
    +  return minutes.to_s + ':' + seconds.floor.to_s + ((seconds - seconds.floor).to_s + "000")[1..3]
    +end
     
    -  # Sets the starting position, ending position, and color for the horizontal separator.
    -  # The starting and ending positions have the same y values.
    -  def horizontal_separator y, x, x2
    -    [x, y, x2, y, 150, 150, 150]
    -  end
    +def label_with_drop_shadow x, y, text
    +  [
    +    { x: x + 1, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r:   0, g:   0, b:   0 }.label!,
    +    { x: x + 2, y: y + 0, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r:   0, g:   0, b:   0 }.label!,
    +    { x: x + 0, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r: 200, g: 200, b: 200 }.label!
    +  ]
    +end
     
    -  # Sets the starting position, ending position, and color for the vertical separator.
    -  # The starting and ending positions have the same x values.
    -  def vertical_separator x, y, y2
    -    [x, y, x, y2, 150, 150, 150]
    -  end
    +def check_box opts = {}
    +  checkbox_template = opts.args.layout.rect(w: 0.5, h: 0.5, col: 2)
    +  final_rect = checkbox_template.center_inside_rect_y(opts.args.layout.rect(row: opts.row, col: opts.col))
    +  color = { r:   0, g:   0, b:   0 }
    +  color = { r: 255, g: 255, b: 255 } if opts.checked
     
    -  # Outputs a border and a grid containing empty squares onto the screen.
    -  def add_grid
    +  {
    +    rect: final_rect,
    +    primitives: [
    +      (final_rect.to_solid color)
    +    ]
    +  }
    +end
     
    -    # Sets the x, y, height, and width of the grid.
    -    # There are 31 horizontal lines and 31 vertical lines in the grid.
    -    # Feel free to count them yourself before continuing!
    -    x, y, h, w = 640 - 500/2, 640 - 500, 500, 500 # calculations done so the grid appears in screen's center
    -    lines_h = 31
    -    lines_v = 31
    +def progress_bar opts = {}
    +  outer_rect  = opts.args.layout.rect(row: opts.row, col: opts.col, w: 5, h: 1)
    +  color = opts.percentage * 255
    +  baseline_progress_bar = opts.args
    +                              .layout
    +                              .rect(w: 5, h: 0.5)
     
    -    # Sets values for the grid's border, grid lines, and filled squares.
    -    # The filled_squares variable is initially set to an empty array.
    -    state.grid_border ||= [ x, y, h, w ] # definition of grid's outer border
    -    state.grid_lines ||= draw_grid(x, y, h, w, lines_h, lines_v) # calls draw_grid method
    -    state.filled_squares ||= [] # there are no filled squares until the user fills them in
    +  final_rect = baseline_progress_bar.center_inside_rect(outer_rect)
    +  center = final_rect.rect_center_point
     
    -    # Outputs the grid lines, border, and filled squares onto the screen.
    -    outputs.lines.concat state.grid_lines
    -    outputs.borders << state.grid_border
    -    outputs.solids << state.filled_squares
    -  end
    +  {
    +    rect: final_rect,
    +    primitives: [
    +      final_rect.merge(r: color, g: color, b: color, a: 128).solid!,
    +      label_with_drop_shadow(center.x, center.y, opts.text)
    +    ]
    +  }
    +end
     
    -  # Draws the grid by adding in vertical and horizontal separators.
    -  def draw_grid x, y, h, w, lines_h, lines_v
    +def panel_primitives args, audio_entry
    +  results = { primitives: [] }
     
    -    # The grid starts off empty.
    -    grid = []
    +  return results unless audio_entry
     
    -    # Calculates the placement and adds horizontal lines or separators into the grid.
    -    curr_y = y # start at the bottom of the box
    -    dist_y = h / (lines_h + 1) # finds distance to place horizontal lines evenly throughout 500 height of grid
    -    lines_h.times do
    -      curr_y += dist_y # increment curr_y by the distance between the horizontal lines
    -      grid << horizontal_separator(curr_y, x, x + w - 1) # add a separator into the grid
    -    end
    +  # this uses DRGTK's layout apis to layout the controls
    +  # imagine the screen is split into equal cells (24 cells across, 12 cells up and down)
    +  # args.layout.rect returns a hash which we merge values with to create primitives
    +  # using args.layout.rect removes the need for pixel pushing
     
    -    # Calculates the placement and adds vertical lines or separators into the grid.
    -    curr_x = x # now start at the left of the box
    -    dist_x = w / (lines_v + 1) # finds distance to place vertical lines evenly throughout 500 width of grid
    -    lines_v.times do
    -      curr_x += dist_x # increment curr_x by the distance between the vertical lines
    -      grid << vertical_separator(curr_x, y + 1, y  + h) # add separator
    -    end
    +  # args.outputs.debug << args.layout.debug_primitives(r: 255, g: 255, b: 255)
     
    -    # paint_grid uses a hash to assign values to keys.
    -    state.paint_grid ||= {"x" => x, "y" => y, "h" => h, "w" => w, "lines_h" => lines_h,
    -                          "lines_v" => lines_v, "dist_x" => dist_x,
    -                          "dist_y" => dist_y }
    +  white_color = { r: 255, g: 255, b: 255 }
    +  label_style = white_color.merge(vertical_alignment_enum: 1)
     
    -    return grid
    -  end
    +  # panel background
    +  results.primitives << args.layout.rect(row: 0, col: 0, w: 7, h: 6, include_col_gutter: true, include_row_gutter: true)
    +                                   .border!(r: 255, g: 255, b: 255)
     
    -  # Checks if the user is keeping the mouse pressed down and sets the mouse_hold variable accordingly using boolean values.
    -  # If the mouse is up, the user cannot drag the mouse.
    -  def check_click
    -    if inputs.mouse.down #is mouse up or down?
    -      state.mouse_held = true # mouse is being held down
    -    elsif inputs.mouse.up # if mouse is up
    -    state.mouse_held = false # mouse is not being held down or dragged
    -      state.mouse_dragging = false
    -    end
    +  # title
    +  results.primitives << args.layout.point(row: 0, col: 3.5, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text:           "Source #{args.state.selected} (#{args.audio[args.state.selected].name})",
    +                                          size_enum:      3,
    +                                          alignment_enum: 1)
     
    -    if state.mouse_held &&    # mouse needs to be down
    -      !inputs.mouse.click &&     # must not be first click
    -      ((inputs.mouse.previous_click.point.x - inputs.mouse.position.x).abs > 15) # Need to move 15 pixels before "drag"
    -      state.mouse_dragging = true
    -    end
    +  # seperator line
    +  results.primitives << args.layout.rect(row: 1, col: 0, w: 7, h: 0)
    +                                   .line!(white_color)
     
    -    # If the user clicks their mouse inside the grid, the search_lines method is called with a click input type.
    -    if ((inputs.mouse.click) && (inputs.mouse.click.point.inside_rect? state.grid_border))
    -      search_lines(inputs.mouse.click.point, :click)
    +  # screen location
    +  results.primitives << args.layout.point(row: 1.0, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "screen:")
     
    -    # If the user drags their mouse inside the grid, the search_lines method is called with a drag input type.
    -    elsif ((state.mouse_dragging) && (inputs.mouse.position.inside_rect? state.grid_border))
    -      search_lines(inputs.mouse.position, :drag)
    -    end
    -  end
    +  results.primitives << args.layout.point(row: 1.0, col: 2, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "(#{audio_entry.screenx.to_i}, #{audio_entry.screeny.to_i})")
     
    -  # Sets the definition of a grid box and handles user input to fill in or clear grid boxes.
    -  def search_lines (point, input_type)
    -    point.x -= state.paint_grid["x"] # subtracts the value assigned to the "x" key in the paint_grid hash
    -    point.y -= state.paint_grid["y"] # subtracts the value assigned to the "y" key in the paint_grid hash
    +  # position
    +  results.primitives << args.layout.point(row: 1.5, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "position:")
     
    -    # Remove code following the .floor and see what happens when you try to fill in grid squares
    -    point.x = (point.x / state.paint_grid["dist_x"]).floor * state.paint_grid["dist_x"]
    -    point.y = (point.y / state.paint_grid["dist_y"]).floor * state.paint_grid["dist_y"]
    +  results.primitives << args.layout.point(row: 1.5, col: 2, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "(#{audio_entry[:x].round(5).to_s[0..6]}, #{audio_entry[:y].round(5).to_s[0..6]})")
     
    -    point.x += state.paint_grid["x"]
    -    point.y += state.paint_grid["y"]
    +  results.primitives << args.layout.point(row: 2.0, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "pitch:")
     
    -    # Sets definition of a grid box, meaning its x, y, width, and height.
    -    # Floor is called on the point.x and point.y variables.
    -    # Ceil method is called on values of the distance hash keys, setting the width and height of a box.
    -    grid_box = [ point.x.floor, point.y.floor, state.paint_grid["dist_x"].ceil, state.paint_grid["dist_y"].ceil ]
    +  results.pitch_slider_rect = progress_bar(row: 2.0, col: 2,
    +                                           percentage: audio_entry.pitch / 2.0,
    +                                           text: "#{audio_entry.pitch.to_sf}",
    +                                           args: args)
     
    -    if input_type == :click # if user clicks their mouse
    -      if state.filled_squares.include? grid_box # if grid box is already filled in
    -        state.filled_squares.delete grid_box # box is cleared and removed from filled_squares
    -      else
    -        state.filled_squares << grid_box # otherwise, box is filled in and added to filled_squares
    -      end
    -    elsif input_type == :drag # if user drags mouse
    -      unless state.filled_squares.include? grid_box # unless the grid box dragged over is already filled in
    -        state.filled_squares << grid_box # the box is filled in and added to filled_squares
    -      end
    -    end
    -  end
    +  results.primitives << results.pitch_slider_rect.primitives
     
    -  # Creates and outputs a "Clear" button on the screen using a label and a border.
    -  # If the button is clicked, the filled squares are cleared, making the filled_squares collection empty.
    -  def draw_buttons
    -    x, y, w, h = 390, 50, 240, 50
    -    state.clear_button        ||= state.new_entity(:button_with_fade)
    +  results.primitives << args.layout.point(row: 2.5, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "playtime:")
     
    -    # The x and y positions are set to display the label in the center of the button.
    -    # Try changing the first two parameters to simply x, y and see what happens to the text placement!
    -    state.clear_button.label  ||= [x + w.half, y + h.half + 10, "Clear", 0, 1] # placed in center of border
    -    state.clear_button.border ||= [x, y, w, h]
    +  results.playtime_slider_rect = progress_bar(args: args,
    +                                              row:  2.5,
    +                                              col:  2,
    +                                              percentage: audio_entry.playtime / audio_entry.length_,
    +                                              text: "#{playtime_str(audio_entry.playtime)} / #{playtime_str(audio_entry.length_)}")
     
    -    # If the mouse is clicked inside the borders of the clear button,
    -    # the filled_squares collection is emptied and the squares are cleared.
    -    if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.clear_button.border)
    -      state.clear_button.clicked_at = inputs.mouse.click.created_at # time (frame) the click occurred
    -      state.filled_squares.clear
    -      inputs.mouse.previous_click = nil
    -    end
    +  results.primitives << results.playtime_slider_rect.primitives
     
    -    outputs.labels << state.clear_button.label
    -    outputs.borders << state.clear_button.border
    +  results.primitives << args.layout.point(row: 3.0, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "gain:")
     
    -    # When the clear button is clicked, the color of the button changes
    -    # and the transparency changes, as well. If you change the time from
    -    # 0.25.seconds to 1.25.seconds or more, the change will last longer.
    -    if state.clear_button.clicked_at
    -      outputs.solids << [x, y, w, h, 0, 180, 80, 255 * state.clear_button.clicked_at.ease(0.25.seconds, :flip)]
    -    end
    -  end
    -end
    +  results.gain_slider_rect = progress_bar(args: args,
    +                                          row:  3.0,
    +                                          col:  2,
    +                                          percentage: audio_entry.gain,
    +                                          text: "#{audio_entry.gain.to_sf}")
     
    -$paint_app = PaintApp.new
    +  results.primitives << results.gain_slider_rect.primitives
     
    -def tick args
    -  $paint_app.inputs = args.inputs
    -  $paint_app.state = args.state
    -  $paint_app.grid = args.grid
    -  $paint_app.args = args
    -  $paint_app.outputs = args.outputs
    -  $paint_app.tick
    -  tick_instructions args, "How to create a simple paint app. CLICK and HOLD to draw."
    -end
     
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.mouse.click ||
    -     args.inputs.keyboard.directional_vector ||
    -     args.inputs.keyboard.key_down.enter ||
    -     args.inputs.keyboard.key_down.escape
    -    args.state.key_event_occurred = true
    -  end
    +  results.primitives << args.layout.point(row: 3.5, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "looping:")
     
    -  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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    -end
    +  checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2)
     
    -
    -

    Mouse - Coordinate Systems - main.rb

    -
    # ./samples/05_mouse/04_coordinate_systems/app/main.rb
    -=begin
    +  results.looping_checkbox_rect = check_box(args: args, row: 3.5, col: 2, checked: audio_entry.looping)
    +  results.primitives << results.looping_checkbox_rect.primitives
     
    - APIs listing that haven't been encountered in previous sample apps:
    +  results.primitives << args.layout.point(row: 4.0, col: 0, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "paused:")
     
    - - args.inputs.mouse.click.position: Coordinates of the mouse's position on the screen.
    -   Unlike args.inputs.mouse.click.point, the mouse does not need to be pressed down for
    -   position to know the mouse's coordinates.
    -   For more information about the mouse, go to mygame/documentation/07-mouse.md.
    +  checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2)
     
    - Reminders:
    +  results.paused_checkbox_rect = check_box(args: args, row: 4.0, col: 2, checked: !audio_entry.paused)
    +  results.primitives << results.paused_checkbox_rect.primitives
     
    - - args.inputs.mouse.click: This property will be set if the mouse was clicked.
    +  results.delete_button_rect = { rect: args.layout.rect(row: 5, col: 0, w: 7, h: 1) }
     
    - - args.inputs.mouse.click.point.(x|y): The x and y location of the mouse.
    +  results.primitives << results.delete_button_rect.to_solid(r: 180)
     
    - - 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.
    +  results.primitives << args.layout.point(row: 5, col: 3.5, row_anchor: 0.5)
    +                                   .merge(label_style)
    +                                   .merge(text: "DELETE", alignment_enum: 1)
     
    -   In this sample app, string interpolation is used to show the current position of the mouse
    -   in a label.
    +  return results
    +end
     
    - - args.outputs.labels: An array that generates a label.
    -   The parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    -   For more information about labels, go to mygame/documentation/02-labels.md.
    +def render_panel args
    +  args.state.panel = nil
    +  audio_entry = args.audio[args.state.selected]
    +  return unless audio_entry
     
    - - args.outputs.solids: An array that generates a solid.
    -   The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE, ALPHA]
    -   For more information about solids, go to mygame/documentation/03-solids-and-borders.md.
    +  mouse_down = (args.state.mouse_held >= 0)
    +  args.state.panel = panel_primitives args, audio_entry
    +  args.outputs.primitives << args.state.panel.primitives
    +end
     
    - - args.outputs.lines: An array that generates a line.
    -   The parameters are [X, Y, X2, Y2, RED, GREEN, BLUE, ALPHA]
    -   For more information about lines, go to mygame/documentation/04-lines.md.
    +def new_sound_id! args
    +  args.state.sound_id ||= 0
    +  args.state.sound_id  += 1
    +  args.state.sound_id
    +end
     
    -=end
    +def render_launcher args
    +  args.outputs.primitives << args.state.spawn_sound_buttons.map(&:primitives)
    +end
     
    -# This sample app shows a coordinate system or grid. The user can move their mouse around the screen and the
    -# coordinates of their position on the screen will be displayed. Users can choose to view one quadrant or
    -# four quadrants by pressing the button.
    +def render_ui args
    +  render_launcher args
    +  render_panel args
    +end
     
     def tick args
    +  defaults args
    +  render args
    +  input args
    +end
     
    -  # The addition and subtraction in the first two parameters of the label and solid
    -  # ensure that the outputs don't overlap each other. Try removing them and see what happens.
    -  pos = args.inputs.mouse.position # stores coordinates of mouse's position
    -  args.outputs.labels << [pos.x + 10, pos.y + 10, "#{pos}"] # outputs label of coordinates
    -  args.outputs.solids << [pos.x -  2, pos.y - 2, 5, 5] # outputs small blackk box placed where mouse is hovering
    +def input args
    +  if !args.audio[args.state.selected]
    +    args.state.selected = nil
    +    args.state.dragging = nil
    +  end
     
    -  button = [0, 0, 370, 50] # sets definition of toggle button
    -  args.outputs.borders << button # outputs button as border (not filled in)
    -  args.outputs.labels << [10, 35, "click here toggle coordinate system"] # label of button
    -  args.outputs.lines << [    0, -720,    0, 720] # vertical line dividing quadrants
    -  args.outputs.lines << [-1280,    0, 1280,   0] # horizontal line dividing quadrants
    +  # spawn button and node interaction
    +  if args.inputs.mouse.click
    +    spawn_sound_button = args.state.spawn_sound_buttons.find { |b| args.inputs.mouse.inside_rect? b.rect }
     
    -  if args.inputs.mouse.click # if the user clicks the mouse
    -    pos = args.inputs.mouse.click.point # pos's value is point where user clicked (coordinates)
    -    if pos.inside_rect? button # if the click occurred inside the button
    -      if args.grid.name == :bottom_left # if the grid shows bottom left as origin
    -        args.grid.origin_center! # origin will be shown in center
    -      else
    -        args.grid.origin_bottom_left! # otherwise, the view will change to show bottom left as origin
    -      end
    +    audio_click_key, audio_click_value = args.audio.find do |k, v|
    +      args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize]
    +    end
    +
    +    if spawn_sound_button
    +      args.state.selected = nil
    +      spawn_new_sound args, spawn_sound_button.name, spawn_sound_button.path
    +    elsif audio_click_key
    +      args.state.selected = audio_click_key
         end
       end
     
    -  tick_instructions args, "Sample app shows the two supported coordinate systems in Game Toolkit."
    +  if args.state.mouse_state == :held && args.state.selected
    +    v = args.audio[args.state.selected]
    +    if args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize]
    +      args.state.dragging = args.state.selected
    +    end
    +
    +    if args.state.dragging
    +      s = args.audio[args.state.selected]
    +      # you can hang anything on the audio hashes you want, so we store the
    +      #  actual screen position so it doesn't scale weirdly vs your mouse.
    +      s.screenx = args.inputs.mouse.x - (args.state.boxsize / 2)
    +      s.screeny = args.inputs.mouse.y - (args.state.boxsize / 2)
    +
    +      s.screeny = 50 if s.screeny < 50
    +      s.screeny = (719 - args.state.boxsize) if s.screeny > (719 - args.state.boxsize)
    +      s.screenx = 0 if s.screenx < 0
    +      s.screenx = (1279 - args.state.boxsize) if s.screenx > (1279 - args.state.boxsize)
    +
    +      s.x = ((s.screenx / 1279.0) * 2.0) - 1.0  # scale to -1.0 - 1.0 range
    +      s.y = ((s.screeny / 719.0) * 2.0) - 1.0   # scale to -1.0 - 1.0 range
    +    end
    +  elsif args.state.mouse_state == :released
    +    args.state.dragging = nil
    +  end
    +
    +  input_panel args
     end
     
    -def tick_instructions args, text, y = 715
    -  return if args.state.key_event_occurred
    -  if args.inputs.mouse.click ||
    -     args.inputs.keyboard.directional_vector ||
    -     args.inputs.keyboard.key_down.enter ||
    -     args.inputs.keyboard.key_down.escape
    -    args.state.key_event_occurred = true
    +def defaults args
    +  args.state.mouse_state      ||= :released
    +  args.state.dragging_source  ||= false
    +  args.state.selected         ||= 0
    +  args.state.next_sound_index ||= 0
    +  args.state.boxsize          ||= 30
    +  args.state.sound_files      ||= [
    +    { name: :tada,   path: "sounds/tada.wav"   },
    +    { name: :splash, path: "sounds/splash.wav" },
    +    { name: :drum,   path: "sounds/drum.wav"   },
    +    { name: :spring, path: "sounds/spring.wav" },
    +    { name: :music,  path: "sounds/music.ogg"  }
    +  ]
    +
    +  # generate buttons based off the sound collection above
    +  args.state.spawn_sound_buttons ||= begin
    +    # create a group of buttons
    +    # column centered (using col_offset to calculate the column offset)
    +    # where each item is 2 columns apart
    +    rects = args.layout.rect_group row:   11,
    +                                   col_offset: {
    +                                     count: args.state.sound_files.length,
    +                                     w:     2
    +                                   },
    +                                   dcol:  2,
    +                                   w:     2,
    +                                   h:     1,
    +                                   group: args.state.sound_files
    +
    +    # now that you have the rects
    +    # construct the metadata for the buttons
    +    rects.map do |rect|
    +      {
    +        rect: rect,
    +        name: rect.name,
    +        path: rect.path,
    +        primitives: [
    +          rect.to_border(r: 255, g: 255, b: 255),
    +          rect.to_label(x: rect.center_x,
    +                        y: rect.center_y,
    +                        text: "#{rect.name}",
    +                        alignment_enum: 1,
    +                        vertical_alignment_enum: 1,
    +                        r: 255, g: 255, b: 255)
    +        ]
    +      }
    +    end
       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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    +  if args.inputs.mouse.up
    +    args.state.mouse_state = :released
    +    args.state.dragging_source = false
    +  elsif args.inputs.mouse.down
    +    args.state.mouse_state = :held
    +  end
    +
    +  args.outputs.background_color = [ 0, 0, 0, 255 ]
    +end
    +
    +def render args
    +  render_ui args
    +  render_sources args
     end
     
     
    -

    Save Load - Save Load Game - main.rb

    -
    # ./samples/06_save_load/01_save_load_game/app/main.rb
    -=begin
    +

    Advanced Audio - Audio Mixer - server_ip_address.txt

    +
    # ./samples/07_advanced_audio/01_audio_mixer/app/server_ip_address.txt
    +192.168.1.65
    +
    +

    Advanced Audio - Audio Mixer - Metadata - ios_metadata.txt

    +
    # ./samples/07_advanced_audio/01_audio_mixer/metadata/ios_metadata.txt
    +# ios_metadata.txt is used by the Pro version of DragonRuby Game Toolkit to create iOS apps.
    +# Information about the Pro version can be found at: http://dragonruby.org/toolkit/game#purchase
     
    - APIs listing that haven't been encountered in previous sample apps:
    +# teamid needs to be set to your assigned Team Id which can be found at https://developer.apple.com/account/#/membership/
    +teamid=
    +# appid needs to be set to your application identifier which can be found at https://developer.apple.com/account/resources/identifiers/list
    +appid=
    +# appname is the name you want to show up underneath the app icon on the device. Keep it under 10 characters.
    +appname=
     
    - - Symbol (:): Ruby object with a name and an internal ID. Symbols are useful
    -   because with a given symbol name, you can refer to the same object throughout
    -   a Ruby program.
    +
    +

    Advanced Audio - Sound Synthesis - main.rb

    +
    # ./samples/07_advanced_audio/02_sound_synthesis/app/main.rb
    +begin # region: top level tick methods
    +  def tick args
    +    defaults args
    +    render args
    +    input args
    +    process_audio_queue args
    +  end
     
    -   In this sample app, we're using symbols for our buttons. We have buttons that
    -   light fires, save, load, etc. Each of these buttons has a distinct symbol like
    -   :light_fire, :save_game, :load_game, etc.
    +  def defaults args
    +    args.state.sine_waves      ||= {}
    +    args.state.square_waves    ||= {}
    +    args.state.saw_tooth_waves ||= {}
    +    args.state.triangle_waves  ||= {}
    +    args.state.audio_queue     ||= []
    +    args.state.buttons         ||= [
    +      (frequency_buttons args),
    +      (sine_wave_note_buttons args),
    +      (bell_buttons args),
    +      (square_wave_note_buttons args),
    +      (saw_tooth_wave_note_buttons args),
    +      (triangle_wave_note_buttons args),
    +    ].flatten
    +  end
     
    - - to_sym: Returns the symbol corresponding to the given string; creates the symbol
    -   if it does not already exist.
    -   For example,
    -   'car'.to_sym
    -   would return the symbol :car.
    +  def render args
    +    args.outputs.borders << args.state.buttons.map { |b| b[:border] }
    +    args.outputs.labels  << args.state.buttons.map { |b| b[:label]  }
    +    args.outputs.labels  << args.layout
    +                              .rect(row: 0, col: 11.5)
    +                              .yield_self { |r| r.merge y: r.y + r.h }
    +                              .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.",
    +                                     alignment_enum: 1)
    +  end
     
    - - last: Returns the last element of an array.
     
    - Reminders:
    +  def input args
    +    args.state.buttons.each do |b|
    +      if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? b[:rect])
    +        parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", "
    +        args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}"
    +        send b[:method_to_call], args, b
    +      end
    +    end
     
    - - num1.lesser(num2): finds the lower value of the given options.
    -   For example, in the statement
    -   a = 4.lesser(3)
    -   3 has a lower value than 4, which means that the value of a would be set to 3,
    -   but if the statement had been
    -   a = 4.lesser(5)
    -   4 has a lower value than 5, which means that the value of a would be set to 4.
    +    if args.inputs.mouse.click && (args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half }))
    +      args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan'
    +    end
    +  end
     
    - - num1.fdiv(num2): returns the float division (will have a decimal) of the two given numbers.
    -   For example, 5.fdiv(2) = 2.5 and 5.fdiv(5) = 1.0
    +  def process_audio_queue args
    +    to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count }
    +    args.state.audio_queue -= to_queue
    +    to_queue.each { |a| args.audio[a[:id]] = a }
     
    - - 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.
    +    args.audio.find_all { |k, v| v[:decay_rate] }
    +      .each     { |k, v| v[:gain] -= v[:decay_rate] }
     
    - - args.outputs.labels: An array. Values generate a label.
    -   Parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
    -   For more information, go to mygame/documentation/02-labels.md.
    +    sounds_to_stop = args.audio
    +                       .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] }
    +                       .map { |k, v| k }
     
    - - ARRAY#inside_rect?: An array with at least two values is considered a point. An array
    -   with at least four values is considered a rect. The inside_rect? function returns true
    -   or false depending on if the point is inside the rect.
    +    sounds_to_stop.each { |k| args.audio.delete k }
    +  end
    +end
     
    -=end
    +begin # region: button definitions, ui layout, callback functions
    +  def button args, opts
    +    button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1))
     
    -# This code allows users to perform different tasks, such as saving and loading the game.
    -# Users also have options to reset the game and light a fire.
    +    button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0
     
    -class TextedBasedGame
    +    label_offset_x = 5
    +    label_offset_y = 30
     
    -  # Contains methods needed for game to run properly.
    -  # Increments tick count by 1 each time it runs (60 times in a single second)
    -  def tick
    -    default
    -    show_intro
    -    state.engine_tick_count += 1
    -    tick_fire
    +    button_def[:label]  = button_def[:rect].merge text: opts[:text],
    +                                                  size_enum: -2.5,
    +                                                  x: button_def[:rect].x + label_offset_x,
    +                                                  y: button_def[:rect].y + label_offset_y
    +
    +    button_def
       end
     
    -  # Sets default values.
    -  # The ||= ensures that a variable's value is only set to the value following the = sign
    -  # if the value has not already been set before. Intialization happens only in the first frame.
    -  def default
    -    state.engine_tick_count ||= 0
    -    state.active_module     ||= :room
    -    state.fire_progress     ||= 0
    -    state.fire_ready_in     ||= 10
    -    state.previous_fire     ||= :dead
    -    state.fire              ||= :dead
    +  def play_sine_wave args, sender
    +    queue_sine_wave args,
    +                    frequency: sender[:frequency],
    +                    duration: 1.seconds,
    +                    fade_out: true
       end
     
    -  def show_intro
    -    return unless state.engine_tick_count == 0 # return unless the game just started
    -    set_story_line "awake." # calls set_story_line method, sets to "awake"
    +  def play_note args, sender
    +    method_to_call = :queue_sine_wave
    +    method_to_call = :queue_square_wave    if sender[:type] == :square
    +    method_to_call = :queue_saw_tooth_wave if sender[:type] == :saw_tooth
    +    method_to_call = :queue_triangle_wave  if sender[:type] == :triangle
    +    method_to_call = :queue_bell           if sender[:type] == :bell
    +
    +    send method_to_call, args,
    +         frequency: (frequency_for note: sender[:note], octave: sender[:octave]),
    +         duration: 1.seconds,
    +         fade_out: true
       end
     
    -  # Sets story line.
    -  def set_story_line story_line
    -    state.story_line    = story_line # story line set to value of parameter
    -    state.active_module = :alert # active module set to alert
    +  def frequency_buttons args
    +    [
    +      (button args,
    +              row: 4.0, col: 0, text: "300hz",
    +              frequency: 300,
    +              method_to_call: :play_sine_wave),
    +      (button args,
    +              row: 5.0, col: 0, text: "400hz",
    +              frequency: 400,
    +              method_to_call: :play_sine_wave),
    +      (button args,
    +              row: 6.0, col: 0, text: "500hz",
    +              frequency: 500,
    +              method_to_call: :play_sine_wave),
    +    ]
       end
     
    -  # Clears story line.
    -  def clear_storyline
    -    state.active_module = :none # active module set to none
    -    state.story_line = nil # story line is cleared, set to nil (or empty)
    +  def sine_wave_note_buttons args
    +    [
    +      (button args,
    +              row: 1.5, col: 2, text: "Sine C4",
    +              note: :c, octave: 4, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 2.5, col: 2, text: "Sine D4",
    +              note: :d, octave: 4, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 3.5, col: 2, text: "Sine E4",
    +              note: :e, octave: 4, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 4.5, col: 2, text: "Sine F4",
    +              note: :f, octave: 4, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 5.5, col: 2, text: "Sine G4",
    +              note: :g, octave: 4, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 6.5, col: 2, text: "Sine A5",
    +              note: :a, octave: 5, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 7.5, col: 2, text: "Sine B5",
    +              note: :b, octave: 5, type: :sine, method_to_call: :play_note),
    +      (button args,
    +              row: 8.5, col: 2, text: "Sine C5",
    +              note: :c, octave: 5, type: :sine, method_to_call: :play_note),
    +    ]
       end
     
    -  # Determines fire progress (how close the fire is to being ready to light).
    -  def tick_fire
    -    return if state.active_module == :alert # return if active module is alert
    -    state.fire_progress += 1 # increment fire progress
    -    # fire_ready_in is 10. The fire_progress is either the current value or 10, whichever has a lower value.
    -    state.fire_progress = state.fire_progress.lesser(state.fire_ready_in)
    +  def square_wave_note_buttons args
    +    [
    +      (button args,
    +              row: 1.5, col: 6, text: "Square C4",
    +              note: :c, octave: 4, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 2.5, col: 6, text: "Square D4",
    +              note: :d, octave: 4, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 3.5, col: 6, text: "Square E4",
    +              note: :e, octave: 4, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 4.5, col: 6, text: "Square F4",
    +              note: :f, octave: 4, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 5.5, col: 6, text: "Square G4",
    +              note: :g, octave: 4, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 6.5, col: 6, text: "Square A5",
    +              note: :a, octave: 5, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 7.5, col: 6, text: "Square B5",
    +              note: :b, octave: 5, type: :square, method_to_call: :play_note),
    +      (button args,
    +              row: 8.5, col: 6, text: "Square C5",
    +              note: :c, octave: 5, type: :square, method_to_call: :play_note),
    +    ]
    +  end
    +  def saw_tooth_wave_note_buttons args
    +    [
    +      (button args,
    +              row: 1.5, col: 8, text: "Saw C4",
    +              note: :c, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 2.5, col: 8, text: "Saw D4",
    +              note: :d, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 3.5, col: 8, text: "Saw E4",
    +              note: :e, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 4.5, col: 8, text: "Saw F4",
    +              note: :f, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 5.5, col: 8, text: "Saw G4",
    +              note: :g, octave: 4, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 6.5, col: 8, text: "Saw A5",
    +              note: :a, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 7.5, col: 8, text: "Saw B5",
    +              note: :b, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    +      (button args,
    +              row: 8.5, col: 8, text: "Saw C5",
    +              note: :c, octave: 5, type: :saw_tooth, method_to_call: :play_note),
    +    ]
       end
     
    -  # Sets the value of fire (whether it is dead or roaring), and the story line
    -  def light_fire
    -    return unless fire_ready? # returns unless the fire is ready to be lit
    -    state.fire = :roaring # fire is lit, set to roaring
    -    state.fire_progress = 0 # the fire progress returns to 0, since the fire has been lit
    -    if state.fire != state.previous_fire
    -      set_story_line "the fire is #{state.fire}." # the story line is set using string interpolation
    -      state.previous_fire = state.fire
    -    end
    +  def triangle_wave_note_buttons args
    +    [
    +      (button args,
    +              row: 1.5, col: 10, text: "Triangle C4",
    +              note: :c, octave: 4, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 2.5, col: 10, text: "Triangle D4",
    +              note: :d, octave: 4, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 3.5, col: 10, text: "Triangle E4",
    +              note: :e, octave: 4, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 4.5, col: 10, text: "Triangle F4",
    +              note: :f, octave: 4, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 5.5, col: 10, text: "Triangle G4",
    +              note: :g, octave: 4, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 6.5, col: 10, text: "Triangle A5",
    +              note: :a, octave: 5, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 7.5, col: 10, text: "Triangle B5",
    +              note: :b, octave: 5, type: :triangle, method_to_call: :play_note),
    +      (button args,
    +              row: 8.5, col: 10, text: "Triangle C5",
    +              note: :c, octave: 5, type: :triangle, method_to_call: :play_note),
    +    ]
       end
     
    -  # Checks if the fire is ready to be lit. Returns a boolean value.
    -  def fire_ready?
    -    # If fire_progress (value between 0 and 10) is equal to fire_ready_in (value of 10),
    -    # the fire is ready to be lit.
    -    state.fire_progress == state.fire_ready_in
    +  def bell_buttons args
    +    [
    +      (button args,
    +              row: 1.5, col: 4, text: "Bell C4",
    +              note: :c, octave: 4, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 2.5, col: 4, text: "Bell D4",
    +              note: :d, octave: 4, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 3.5, col: 4, text: "Bell E4",
    +              note: :e, octave: 4, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 4.5, col: 4, text: "Bell F4",
    +              note: :f, octave: 4, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 5.5, col: 4, text: "Bell G4",
    +              note: :g, octave: 4, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 6.5, col: 4, text: "Bell A5",
    +              note: :a, octave: 5, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 7.5, col: 4, text: "Bell B5",
    +              note: :b, octave: 5, type: :bell, method_to_call: :play_note),
    +      (button args,
    +              row: 8.5, col: 4, text: "Bell C5",
    +              note: :c, octave: 5, type: :bell, method_to_call: :play_note),
    +    ]
    +  end
    +end
    +
    +begin # region: wave generation
    +  begin # sine wave
    +    def defaults_sine_wave_for
    +      { frequency: 440, sample_rate: 48000 }
    +    end
    +
    +    def sine_wave_for opts = {}
    +      opts = defaults_sine_wave_for.merge opts
    +      frequency   = opts[:frequency]
    +      sample_rate = opts[:sample_rate]
    +      period_size = (sample_rate.fdiv frequency).ceil
    +      period_size.map_with_index do |i|
    +        Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i)
    +      end.to_a
    +    end
    +
    +    def defaults_queue_sine_wave
    +      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    +    end
    +
    +    def queue_sine_wave args, opts = {}
    +      opts        = defaults_queue_sine_wave.merge opts
    +      frequency   = opts[:frequency]
    +      sample_rate = 48000
    +
    +      sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate
    +      args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate
    +
    +      proc = lambda do
    +        generate_audio_data args.state.sine_waves[frequency], sample_rate
    +      end
    +
    +      audio_state = new_audio_state args, opts
    +      audio_state[:input] = [1, sample_rate, proc]
    +      queue_audio args, audio_state: audio_state, wave: sine_wave
    +    end
       end
     
    -  # Divides the value of the fire_progress variable by 10 to determine how close the user is to
    -  # being able to light a fire.
    -  def light_fire_progress
    -    state.fire_progress.fdiv(10) # float division
    -  end
    +  begin # region: square wave
    +    def defaults_square_wave_for
    +      { frequency: 440, sample_rate: 48000 }
    +    end
    +
    +    def square_wave_for opts = {}
    +      opts = defaults_square_wave_for.merge opts
    +      sine_wave = sine_wave_for opts
    +      sine_wave.map do |v|
    +        if v >= 0
    +          1.0
    +        else
    +          -1.0
    +        end
    +      end.to_a
    +    end
    +
    +    def defaults_queue_square_wave
    +      { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
    +    end
     
    -  # Defines fire as the state.fire variable.
    -  def fire
    -    state.fire
    -  end
    +    def queue_square_wave args, opts = {}
    +      opts        = defaults_queue_square_wave.merge opts
    +      frequency   = opts[:frequency]
    +      sample_rate = 48000
     
    -  # Sets the title of the room.
    -  def room_title
    -    return "a room that is dark" if state.fire == :dead # room is dark if the fire is dead
    -    return "a room that is lit" # room is lit if the fire is not dead
    -  end
    +      square_wave = square_wave_for frequency: frequency, sample_rate: sample_rate
    +      args.state.square_waves[frequency] ||= square_wave_for frequency: frequency, sample_rate: sample_rate
     
    -  # Sets the active_module to room.
    -  def go_to_room
    -    state.active_module = :room
    -  end
    +      proc = lambda do
    +        generate_audio_data args.state.square_waves[frequency], sample_rate
    +      end
     
    -  # Defines active_module as the state.active_module variable.
    -  def active_module
    -    state.active_module
    +      audio_state = new_audio_state args, opts
    +      audio_state[:input] = [1, sample_rate, proc]
    +      queue_audio args, audio_state: audio_state, wave: square_wave
    +    end
       end
     
    -  # Defines story_line as the state.story_line variable.
    -  def story_line
    -    state.story_line
    -  end
    +  begin # region: saw tooth wave
    +    def defaults_saw_tooth_wave_for
    +      { frequency: 440, sample_rate: 48000 }
    +    end
     
    -  # Update every 60 frames (or every second)
    -  def should_tick?
    -    state.tick_count.mod_zero?(60)
    -  end
    +    def saw_tooth_wave_for opts = {}
    +      opts = defaults_saw_tooth_wave_for.merge opts
    +      sine_wave = sine_wave_for opts
    +      period_size = sine_wave.length
    +      sine_wave.map_with_index do |v, i|
    +        (((i % period_size).fdiv period_size) * 2) - 1
    +      end
    +    end
     
    -  # Sets the value of the game state provider.
    -  def initialize game_state_provider
    -    @game_state_provider = game_state_provider
    -  end
    +    def defaults_queue_saw_tooth_wave
    +      { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
    +    end
     
    -  # Defines the game state.
    -  # Any variable prefixed with an @ symbol is an instance variable.
    -  def state
    -    @game_state_provider.state
    -  end
    +    def queue_saw_tooth_wave args, opts = {}
    +      opts        = defaults_queue_saw_tooth_wave.merge opts
    +      frequency   = opts[:frequency]
    +      sample_rate = 48000
     
    -  # Saves the state of the game in a text file called game_state.txt.
    -  def save
    -    $gtk.serialize_state('game_state.txt', state)
    -  end
    +      saw_tooth_wave = saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
    +      args.state.saw_tooth_waves[frequency] ||= saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
     
    -  # Loads the game state from the game_state.txt text file.
    -  # If the load is unsuccessful, the user is informed since the story line indicates the failure.
    -  def load
    -    parsed_state = $gtk.deserialize_state('game_state.txt')
    -    if !parsed_state
    -      set_story_line "no game to load. press save first."
    -    else
    -      $gtk.args.state = parsed_state
    +      proc = lambda do
    +        generate_audio_data args.state.saw_tooth_waves[frequency], sample_rate
    +      end
    +
    +      audio_state = new_audio_state args, opts
    +      audio_state[:input] = [1, sample_rate, proc]
    +      queue_audio args, audio_state: audio_state, wave: saw_tooth_wave
         end
       end
     
    -  # Resets the game.
    -  def reset
    -    $gtk.reset
    -  end
    -end
    +  begin # region: triangle wave
    +    def defaults_triangle_wave_for
    +      { frequency: 440, sample_rate: 48000 }
    +    end
     
    -class TextedBasedGamePresenter
    -  attr_accessor :state, :outputs, :inputs
    +    def triangle_wave_for opts = {}
    +      opts = defaults_saw_tooth_wave_for.merge opts
    +      sine_wave = sine_wave_for opts
    +      period_size = sine_wave.length
    +      sine_wave.map_with_index do |v, i|
    +        ratio = (i.fdiv period_size)
    +        if ratio <= 0.5
    +          (ratio * 4) - 1
    +        else
    +          ratio -= 0.5
    +          1 - (ratio * 4)
    +        end
    +      end
    +    end
     
    -  # Creates empty collection called highlights.
    -  # Calls methods necessary to run the game.
    -  def tick
    -    state.layout.highlights ||= []
    -    game.tick if game.should_tick?
    -    render
    -    process_input
    -  end
    +    def defaults_queue_triangle_wave
    +      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    +    end
     
    -  # Outputs a label of the tick count (passage of time) and calls all render methods.
    -  def render
    -    outputs.labels << [10, 30, state.tick_count]
    -    render_alert
    -    render_room
    -    render_highlights
    -  end
    +    def queue_triangle_wave args, opts = {}
    +      opts        = defaults_queue_triangle_wave.merge opts
    +      frequency   = opts[:frequency]
    +      sample_rate = 48000
     
    -  # Outputs a label onto the screen that shows the story line, and also outputs a "close" button.
    -  def render_alert
    -    return unless game.active_module == :alert
    +      triangle_wave = triangle_wave_for frequency: frequency, sample_rate: sample_rate
    +      args.state.triangle_waves[frequency] ||= triangle_wave_for frequency: frequency, sample_rate: sample_rate
     
    -    outputs.labels << [640, 480, game.story_line, 5, 1]  # outputs story line label
    -    outputs.primitives << button(:alert_dismiss, 490, 380, "close")  # positions "close" button under story line
    +      proc = lambda do
    +        generate_audio_data args.state.triangle_waves[frequency], sample_rate
    +      end
    +
    +      audio_state = new_audio_state args, opts
    +      audio_state[:input] = [1, sample_rate, proc]
    +      queue_audio args, audio_state: audio_state, wave: triangle_wave
    +    end
       end
     
    -  def render_room
    -    return unless game.active_module == :room
    -    outputs.labels << [640, 700, game.room_title, 4, 1] # outputs room title label at top of screen
    +  begin # region: bell
    +    def defaults_queue_bell
    +      { frequency: 440, duration: 1.seconds, queue_in: 0 }
    +    end
     
    -    # The parameters for these outputs are (symbol, x, y, text, value/percentage) and each has a y value
    -    # that positions it 60 pixels lower than the previous output.
    +    def queue_bell args, opts = {}
    +      (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b }
    +    end
     
    -    # outputs the light_fire_progress bar, uses light_fire_progress for its percentage (which changes bar's appearance)
    -    outputs.primitives << progress_bar(:light_fire, 490, 600, "light fire", game.light_fire_progress)
    -    outputs.primitives << button(       :save_game, 490, 540, "save") # outputs save button
    -    outputs.primitives << button(       :load_game, 490, 480, "load") # outputs load button
    -    outputs.primitives << button(      :reset_game, 490, 420, "reset") # outputs reset button
    -    outputs.labels << [640, 30, "the fire is #{game.fire}", 0, 1] # outputs fire label at bottom of screen
    -  end
    +    def bell_harmonics
    +      [
    +        { frequency_ratio: 0.5, duration_ratio: 1.00 },
    +        { frequency_ratio: 1.0, duration_ratio: 0.80 },
    +        { frequency_ratio: 2.0, duration_ratio: 0.60 },
    +        { frequency_ratio: 3.0, duration_ratio: 0.40 },
    +        { frequency_ratio: 4.2, duration_ratio: 0.25 },
    +        { frequency_ratio: 5.4, duration_ratio: 0.20 },
    +        { frequency_ratio: 6.8, duration_ratio: 0.15 }
    +      ]
    +    end
     
    -  # Outputs a collection of highlights using an array to set their values, and also rejects certain values from the collection.
    -  def render_highlights
    -    state.layout.highlights.each do |h| # for each highlight in the collection
    -        h.lifetime -= 1 # decrease the value of its lifetime
    -      end
    +    def defaults_bell_to_sine_waves
    +      { frequency: 440, duration: 1.seconds, queue_in: 0 }
    +    end
     
    -      outputs.solids << state.layout.highlights.map do |h| # outputs highlights collection
    -        [h.x, h.y, h.w, h.h, h.color, 255 * h.lifetime / h.max_lifetime] # sets definition for each highlight
    -        # transparency changes; divide lifetime by max_lifetime, multiply result by 255
    +    def bell_to_sine_waves opts = {}
    +      opts = defaults_bell_to_sine_waves.merge opts
    +      bell_harmonics.map do |b|
    +        {
    +          frequency: opts[:frequency] * b[:frequency_ratio],
    +          duration:  opts[:duration] * b[:duration_ratio],
    +          queue_in:  opts[:queue_in],
    +          gain:      (1.fdiv bell_harmonics.length),
    +          fade_out:  true
    +        }
           end
    -
    -      # reject highlights from collection that have no remaining lifetime
    -      state.layout.highlights = state.layout.highlights.reject { |h| h.lifetime <= 0 }
    +    end
       end
     
    -  # Checks whether or not a button was clicked.
    -  # Returns a boolean value.
    -  def process_input
    -    button = button_clicked? # calls button_clicked? method
    -  end
    +  begin # audio entity construction
    +    def generate_audio_data sine_wave, sample_rate
    +      sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil
    +      copy_count  = (sample_size.fdiv sine_wave.length).ceil
    +      sine_wave * copy_count
    +    end
     
    -  # Returns a boolean value.
    -  # Finds the button that was clicked from the button list and determines what method to call.
    -  # Adds a highlight to the highlights collection.
    -  def button_clicked?
    -    return nil unless click_pos # return nil unless click_pos holds coordinates of mouse click
    -      button = @button_list.find do |k, v| # goes through button_list to find button clicked
    -        click_pos.inside_rect? v[:primitives].last.rect # was the mouse clicked inside the rect of button?
    -      end
    -      return unless button # return unless a button was clicked
    -      method_to_call = "#{button[0]}_clicked".to_sym # sets method_to_call to symbol (like :save_game or :load_game)
    -      if self.respond_to? method_to_call # returns true if self responds to the given method (method actually exists)
    -        border = button[1][:primitives].last # sets border definition using value of last key in button list hash
    +    def defaults_new_audio_state
    +      { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
    +    end
     
    -        # declares each highlight as a new entity, sets properties
    -        state.layout.highlights << state.new_entity(:highlight) do |h|
    -            h.x = border.x
    -            h.y = border.y
    -            h.w = border.w
    -            h.h = border.h
    -            h.max_lifetime = 10
    -            h.lifetime = h.max_lifetime
    -            h.color = [120, 120, 180] # sets color to shade of purple
    -          end
    +    def new_audio_state args, opts = {}
    +      opts        = defaults_new_audio_state.merge opts
    +      decay_rate  = 0
    +      decay_rate  = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out]
    +      frequency   = opts[:frequency]
    +      sample_rate = 48000
     
    -          self.send method_to_call # invoke method identified by symbol
    -        else # otherwise, if self doesn't respond to given method
    -          border = button[1][:primitives].last # sets border definition using value of last key in hash
    +      {
    +        id:               (new_id! args),
    +        frequency:        frequency,
    +        sample_rate:      48000,
    +        stop_at:          args.tick_count + opts[:queue_in] + opts[:duration],
    +        gain:             opts[:gain].to_f,
    +        queue_at:         args.state.tick_count + opts[:queue_in],
    +        decay_rate:       decay_rate,
    +        pitch:            1.0,
    +        looping:          true,
    +        paused:           false
    +      }
    +    end
     
    -          # declares each highlight as a new entity, sets properties
    -          state.layout.highlights << state.new_entity(:highlight) do |h|
    -            h.x = border.x
    -            h.y = border.y
    -            h.w = border.w
    -            h.h = border.h
    -            h.max_lifetime = 4 # different max_lifetime than the one set if respond_to? had been true
    -            h.lifetime = h.max_lifetime
    -            h.color = [120, 80, 80] # sets color to dark color
    -          end
    +    def queue_audio args, opts = {}
    +      graph_wave args, opts[:wave], opts[:audio_state][:frequency]
    +      args.state.audio_queue << opts[:audio_state]
    +    end
     
    -          # instructions for users on how to add the missing method_to_call to the code
    -          puts "It looks like #{method_to_call} doesn't exists on TextedBasedGamePresenter. Please add this method:"
    -          puts "Just copy the code below and put it in the #{TextedBasedGamePresenter} class definition."
    -          puts ""
    -          puts "```"
    -          puts "class TextedBasedGamePresenter <--- find this class and put the method below in it"
    -          puts ""
    -          puts "  def #{method_to_call}"
    -          puts "    puts 'Yay that worked!'"
    -          puts "  end"
    -          puts ""
    -          puts "end <-- make sure to put the #{method_to_call} method in between the `class` word and the final `end` statement."
    -          puts "```"
    -          puts ""
    -      end
    -  end
    +    def new_id! args
    +      args.state.audio_id ||= 0
    +      args.state.audio_id  += 1
    +    end
     
    -  # Returns the position of the mouse when it is clicked.
    -  def click_pos
    -    return nil unless inputs.mouse.click # returns nil unless the mouse was clicked
    -    return inputs.mouse.click.point # returns location of mouse click (coordinates)
    -  end
    +    def graph_wave args, wave, frequency
    +      if args.state.tick_count != args.state.graphed_at
    +        args.outputs.static_lines.clear
    +        args.outputs.static_sprites.clear
    +      end
     
    -  # Creates buttons for the button_list and sets their values using a hash (uses symbols as keys)
    -  def button id, x, y, text
    -    @button_list[id] ||= { # assigns values to hash keys
    -      id: id,
    -      text: text,
    -      primitives: [
    -        [x + 10, y + 30, text, 2, 0].label, # positions label inside border
    -        [x, y, 300, 50].border,             # sets definition of border
    -      ]
    -    }
    +      wave = wave
     
    -    @button_list[id][:primitives] # returns label and border for buttons
    -  end
    +      r, g, b = frequency.to_i % 85,
    +                frequency.to_i % 170,
    +                frequency.to_i % 255
     
    -  # Creates a progress bar (used for lighting the fire) and sets its values.
    -  def progress_bar id, x, y, text, percentage
    -    @button_list[id] = { # assigns values to hash keys
    -      id: id,
    -      text: text,
    -      primitives: [
    -        [x, y, 300, 50, 100, 100, 100].solid, # sets definition for solid (which fills the bar with gray)
    -        [x + 10, y + 30, text, 2, 0].label, # sets definition for label, positions inside border
    -        [x, y, 300, 50].border, # sets definition of border
    -      ]
    -    }
    +      starting_rect = args.layout.rect(row: 5, col: 13)
    +      x_scale    = 10
    +      y_scale    = 100
    +      max_points = 25
     
    -    # Fills progress bar based on percentage. If the fire was ready to be lit (100%) and we multiplied by
    -    # 100, only 1/3 of the bar would only be filled in. 200 would cause only 2/3 to be filled in.
    -    @button_list[id][:primitives][0][2] = 300 * percentage
    -    @button_list[id][:primitives]
    -  end
    +      points = wave
    +      if wave.length > max_points
    +        resolution = wave.length.idiv max_points
    +        points = wave.find_all.with_index { |y, i| (i % resolution == 0) }
    +      end
     
    -  # Defines the game.
    -  def game
    -    @game
    -  end
    +      args.outputs.static_lines << points.map_with_index do |y, x|
    +        next_y = points[x + 1]
     
    -  # Initalizes the game and creates an empty list of buttons.
    -  def initialize
    -    @game = TextedBasedGame.new self
    -    @button_list ||= {}
    -  end
    +        if next_y
    +          {
    +            x:  starting_rect.x + (x * x_scale),
    +            y:  starting_rect.y + starting_rect.h.half + y_scale * y,
    +            x2: starting_rect.x + ((x + 1) * x_scale),
    +            y2: starting_rect.y + starting_rect.h.half + y_scale * next_y,
    +            r:  r,
    +            g:  g,
    +            b:  b
    +          }
    +        end
    +      end
     
    -  # Clears the storyline and takes the user to the room.
    -  def alert_dismiss_clicked
    -    game.clear_storyline
    -    game.go_to_room
    -  end
    +      args.outputs.static_sprites << points.map_with_index do |y, x|
    +        {
    +          x:  (starting_rect.x + (x * x_scale)) - 2,
    +          y:  (starting_rect.y + starting_rect.h.half + y_scale * y) - 2,
    +          w:  4,
    +          h:  4,
    +          path: 'sprites/square-white.png',
    +          r: r,
    +          g: g,
    +          b: b
    +        }
    +      end
     
    -  # Lights the fire when the user clicks the "light fire" option.
    -  def light_fire_clicked
    -    game.light_fire
    +      args.state.graphed_at = args.state.tick_count
    +    end
       end
     
    -  # Saves the game when the user clicks the "save" option.
    -  def save_game_clicked
    -    game.save
    -  end
    +  begin # region: musical note mapping
    +    def defaults_frequency_for
    +      { note: :a, octave: 5, sharp:  false, flat:   false }
    +    end
     
    -  # Resets the game when the user clicks the "reset" option.
    -  def reset_game_clicked
    -    game.reset
    -  end
    +    def frequency_for opts = {}
    +      opts = defaults_frequency_for.merge opts
    +      octave_offset_multiplier  = opts[:octave] - 5
    +      note = note_frequencies_octave_5[opts[:note]]
    +      if octave_offset_multiplier < 0
    +        note = note * 1 / (octave_offset_multiplier.abs + 1)
    +      elsif octave_offset_multiplier > 0
    +        note = note * (octave_offset_multiplier.abs + 1) / 1
    +      end
    +      note
    +    end
     
    -  # Loads the game when the user clicks the "load" option.
    -  def load_game_clicked
    -    game.load
    +    def note_frequencies_octave_5
    +      {
    +        a: 440.0,
    +        a_sharp: 466.16, b_flat: 466.16,
    +        b: 493.88,
    +        c: 523.25,
    +        c_sharp: 554.37, d_flat: 587.33,
    +        d: 587.33,
    +        d_sharp: 622.25, e_flat: 659.25,
    +        e: 659.25,
    +        f: 698.25,
    +        f_sharp: 739.99, g_flat: 739.99,
    +        g: 783.99,
    +        g_sharp: 830.61, a_flat: 830.61
    +      }
    +    end
       end
     end
     
    -$text_based_rpg = TextedBasedGamePresenter.new
    -
    -def tick args
    -  $text_based_rpg.state = args.state
    -  $text_based_rpg.outputs = args.outputs
    -  $text_based_rpg.inputs = args.inputs
    -  $text_based_rpg.tick
    -end
    +$gtk.reset
     
     
    -

    Advanced Rendering - Simple Render Targets - main.rb

    +

    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.
    @@ -11586,7 +11964,7 @@ end
     $gtk.reset
     
     
    -

    Advanced Rendering - Render Targets With Tile Manipulation - main.rb

    +

    Advanced Rendering - Render Targets With Tile Manipulation - main.rb

    # ./samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/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
    @@ -11685,7 +12063,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Advanced Rendering - Render Target Viewports - main.rb

    +

    Advanced Rendering - Render Target Viewports - main.rb

    # ./samples/07_advanced_rendering/03_render_target_viewports/app/main.rb
     =begin
     
    @@ -12157,7 +12535,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Advanced Rendering - Render Primitive Hierarchies - main.rb

    +

    Advanced Rendering - Render Primitive Hierarchies - main.rb

    # ./samples/07_advanced_rendering/04_render_primitive_hierarchies/app/main.rb
     =begin
     
    @@ -12333,7 +12711,7 @@ def collection_of_sprites args
     end
     
     
    -

    Advanced Rendering - Render Primitives As Hash - main.rb

    +

    Advanced Rendering - Render Primitives As Hash - main.rb

    # ./samples/07_advanced_rendering/05_render_primitives_as_hash/app/main.rb
     =begin
     
    @@ -12473,7 +12851,7 @@ def tick args
         flip_horizontally: false,
         angle_anchor_x: 0.5,            # rotation center set to middle
         angle_anchor_y: 0.5
    -  }.sprite
    +  }.sprite!
     
       # Outputs label as primitive using a hash
       args.outputs.primitives << {
    @@ -12487,7 +12865,7 @@ def tick args
         b:         50,
         a:         255,                 # transparency
         font:      "fonts/manaspc.ttf"  # font style
    -  }.label
    +  }.label!
     
       # Outputs solid as primitive using a hash
       args.outputs.primitives << {
    @@ -12499,7 +12877,7 @@ def tick args
         g:  50,
         b:  50,
         a: 255                          # transparency
    -  }.solid
    +  }.solid!
     
       # Outputs border as primitive using a hash
       # Same parameters as solid
    @@ -12512,7 +12890,7 @@ def tick args
         g:  50,
         b:  50,
         a: 255                          # transparency
    -  }.border
    +  }.border!
     
       # Outputs line as primitive using a hash
       args.outputs.primitives << {
    @@ -12524,11 +12902,11 @@ def tick args
         g:   50,
         b:   50,
         a:  255                         # transparency
    -  }.line
    +  }.line!
     end
     
     
    -

    Advanced Rendering - Pixel Arrays - main.rb

    +

    Advanced Rendering - Pixel Arrays - main.rb

    # ./samples/07_advanced_rendering/06_pixel_arrays/app/main.rb
     $gtk.reset
     
    @@ -12573,8 +12951,105 @@ end
     
     
     
    -

    Advanced Rendering - Splitscreen Camera - main.rb

    -
    # ./samples/07_advanced_rendering/07_splitscreen_camera/app/main.rb
    +

    Advanced Rendering - Simple Camera - main.rb

    +
    # ./samples/07_advanced_rendering/07_simple_camera/app/main.rb
    +def tick args
    +  # variables you can play around with
    +  args.state.world.w      ||= 1280
    +  args.state.world.h      ||= 720
    +
    +  args.state.player.x     ||= 0
    +  args.state.player.y     ||= 0
    +  args.state.player.size  ||= 32
    +
    +  args.state.enemy.x      ||= 700
    +  args.state.enemy.y      ||= 700
    +  args.state.enemy.size   ||= 16
    +
    +  args.state.camera.x                ||= 640
    +  args.state.camera.y                ||= 300
    +  args.state.camera.scale            ||= 1.0
    +  args.state.camera.show_empty_space ||= :yes
    +
    +  # instructions
    +  args.outputs.primitives << { x: 0, y:  80.from_top, w: 360, h: 80, r: 0, g: 0, b: 0, a: 128 }.solid!
    +  args.outputs.primitives << { x: 10, y: 10.from_top, text: "arrow keys to move around", r: 255, g: 255, b: 255}.label!
    +  args.outputs.primitives << { x: 10, y: 30.from_top, text: "+/- to change zoom of camera", r: 255, g: 255, b: 255}.label!
    +  args.outputs.primitives << { x: 10, y: 50.from_top, text: "tab to change camera edge behavior", r: 255, g: 255, b: 255}.label!
    +
    +  # render scene
    +  args.outputs[:scene].w = args.state.world.w
    +  args.outputs[:scene].h = args.state.world.h
    +
    +  args.outputs[:scene].solids << { x: 0, y: 0, w: args.state.world.w, h: args.state.world.h, r: 20, g: 60, b: 80 }
    +  args.outputs[:scene].solids << { x: args.state.player.x, y: args.state.player.y,
    +                                   w: args.state.player.size, h: args.state.player.size, r: 80, g: 155, b: 80 }
    +  args.outputs[:scene].solids << { x: args.state.enemy.x, y: args.state.enemy.y,
    +                                   w: args.state.enemy.size, h: args.state.enemy.size, r: 155, g: 80, b: 80 }
    +
    +  # render camera
    +  scene_position = calc_scene_position args
    +  args.outputs.sprites << { x: scene_position.x,
    +                            y: scene_position.y,
    +                            w: scene_position.w,
    +                            h: scene_position.h,
    +                            path: :scene }
    +
    +  # move player
    +  if args.inputs.directional_angle
    +    args.state.player.x += args.inputs.directional_angle.vector_x * 5
    +    args.state.player.y += args.inputs.directional_angle.vector_y * 5
    +    args.state.player.x  = args.state.player.x.clamp(0, args.state.world.w - args.state.player.size)
    +    args.state.player.y  = args.state.player.y.clamp(0, args.state.world.h - args.state.player.size)
    +  end
    +
    +  # +/- to zoom in and out
    +  if args.inputs.keyboard.plus && args.state.tick_count.zmod?(3)
    +    args.state.camera.scale += 0.05
    +  elsif args.inputs.keyboard.hyphen && args.state.tick_count.zmod?(3)
    +    args.state.camera.scale -= 0.05
    +  elsif args.inputs.keyboard.key_down.tab
    +    if args.state.camera.show_empty_space == :yes
    +      args.state.camera.show_empty_space = :no
    +    else
    +      args.state.camera.show_empty_space = :yes
    +    end
    +  end
    +
    +  args.state.camera.scale = args.state.camera.scale.greater(0.1)
    +end
    +
    +def calc_scene_position args
    +  result = { x: args.state.camera.x - (args.state.player.x * args.state.camera.scale),
    +             y: args.state.camera.y - (args.state.player.y * args.state.camera.scale),
    +             w: args.state.world.w * args.state.camera.scale,
    +             h: args.state.world.h * args.state.camera.scale,
    +             scale: args.state.camera.scale }
    +
    +  return result if args.state.camera.show_empty_space == :yes
    +
    +  if result.w < args.grid.w
    +    result.merge!(x: (args.grid.w - result.w).half)
    +  elsif (args.state.player.x * result.scale) < args.grid.w.half
    +    result.merge!(x: 10)
    +  elsif (result.x + result.w) < args.grid.w
    +    result.merge!(x: - result.w + (args.grid.w - 10))
    +  end
    +
    +  if result.h < args.grid.h
    +    result.merge!(y: (args.grid.h - result.h).half)
    +  elsif (result.y) > 10
    +    result.merge!(y: 10)
    +  elsif (result.y + result.h) < args.grid.h
    +    result.merge!(y: - result.h + (args.grid.h - 10))
    +  end
    +
    +  result
    +end
    +
    +
    +

    Advanced Rendering - Splitscreen Camera - main.rb

    +
    # ./samples/07_advanced_rendering/08_splitscreen_camera/app/main.rb
     class CameraMovement
       attr_accessor :state, :inputs, :outputs, :grid
     
    @@ -12972,8 +13447,8 @@ def tick args
     end
     
     
    -

    Advanced Rendering - Z Targeting Camera - main.rb

    -
    # ./samples/07_advanced_rendering/08_z_targeting_camera/app/main.rb
    +

    Advanced Rendering - Z Targeting Camera - main.rb

    +
    # ./samples/07_advanced_rendering/09_z_targeting_camera/app/main.rb
     class Game
       attr_gtk
     
    @@ -13082,7 +13557,60 @@ end
     $gtk.reset
     
     
    -

    Tweening Lerping Easing Functions - Easing Functions - main.rb

    +

    Advanced Rendering - Blend Modes - main.rb

    +
    # ./samples/07_advanced_rendering/10_blend_modes/app/main.rb
    +$gtk.reset
    +
    +def draw_blendmode args, mode
    +  w = 160
    +  h = w
    +  args.state.x += (1280-w) / (args.state.blendmodes.length + 1)
    +  x = args.state.x
    +  y = (720 - h) / 2
    +  s = 'sprites/blue-feathered.png'
    +  args.outputs.sprites << { blendmode_enum: mode.value, x: x, y: y, w: w, h: h, path: s }
    +  args.outputs.labels << [x + (w/2), y, mode.name.to_s, 1, 1, 255, 255, 255]
    +end
    +
    +def tick args
    +
    +  # Different blend modes do different things, depending on what they
    +  # blend against (in this case, the pixels of the background color).
    +  args.state.bg_element ||= 1
    +  args.state.bg_color ||= 255
    +  args.state.bg_color_direction ||= 1
    +  bg_r = (args.state.bg_element == 1) ? args.state.bg_color : 0
    +  bg_g = (args.state.bg_element == 2) ? args.state.bg_color : 0
    +  bg_b = (args.state.bg_element == 3) ? args.state.bg_color : 0
    +  args.state.bg_color += args.state.bg_color_direction
    +  if (args.state.bg_color_direction > 0) && (args.state.bg_color >= 255)
    +    args.state.bg_color_direction = -1
    +    args.state.bg_color = 255
    +  elsif (args.state.bg_color_direction < 0) && (args.state.bg_color <= 0)
    +    args.state.bg_color_direction = 1
    +    args.state.bg_color = 0
    +    args.state.bg_element += 1
    +    if args.state.bg_element >= 4
    +      args.state.bg_element = 1
    +    end
    +  end
    +
    +  args.outputs.background_color = [ bg_r, bg_g, bg_b, 255 ]
    +
    +  args.state.blendmodes ||= [
    +    { name: :none,  value: 0 },
    +    { name: :blend, value: 1 },
    +    { name: :add,   value: 2 },
    +    { name: :mod,   value: 3 },
    +    { name: :mul,   value: 4 }
    +  ]
    +
    +  args.state.x = 0  # reset this, draw_blendmode will increment it.
    +  args.state.blendmodes.each { |blendmode| draw_blendmode args, blendmode }
    +end
    +
    +
    +

    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!!!!
    @@ -13218,7 +13746,7 @@ module Easing
     end
     
     
    -

    Tweening Lerping Easing Functions - Cubic Bezier - main.rb

    +

    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]
    @@ -13283,7 +13811,7 @@ def pow n, to
     end
     
     
    -

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

    +

    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
    @@ -13305,7 +13833,7 @@ def tick args
     end
     
     
    -

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

    +

    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),
    @@ -13522,7 +14050,7 @@ def tick args
     end
     
     
    -

    Performance - Sprites As Hash - main.rb

    +

    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~
    @@ -13565,7 +14093,11 @@ def tick args
     
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    -    puts "* INFO - Please specify the number of sprites to render."
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Sprites, Hashes"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
    @@ -13590,7 +14122,7 @@ def reset_with count: count
     end
     
     
    -

    Performance - Sprites As Entities - main.rb

    +

    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.
    @@ -13633,7 +14165,11 @@ def tick args
     
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    -    puts "* INFO - Please specify the number of sprites to render."
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Sprites, Open Entities"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
    @@ -13658,7 +14194,7 @@ def reset_with count: count
     end
     
     
    -

    Performance - Sprites As Strict Entities - main.rb

    +

    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
    @@ -13705,7 +14241,11 @@ def tick args
     
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    -    puts "* INFO - Please specify the number of sprites to render."
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Sprites, Strict Entities"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
    @@ -13730,7 +14270,7 @@ def reset_with count: count
     end
     
     
    -

    Performance - Sprites As Classes - main.rb

    +

    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.
    @@ -13760,6 +14300,11 @@ end
     def tick args
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Sprites, Classes"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
    @@ -13784,7 +14329,7 @@ def reset_with count: count
     end
     
     
    -

    Performance - Static Sprites As Classes - main.rb

    +

    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
    @@ -13815,19 +14360,24 @@ end
     def tick args
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Static Sprites, Classes"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
       # init
       if args.state.tick_count == 0
         args.state.stars = args.state.star_count.map { |i| Star.new args.grid }
    +    args.outputs.static_sprites << args.state.stars
       end
     
       # update
       args.state.stars.each(&:move)
     
       # render
    -  args.outputs.sprites << args.state.stars
       args.outputs.background_color = [0, 0, 0]
       args.outputs.primitives << args.gtk.current_framerate_primitives
     end
    @@ -13839,7 +14389,7 @@ def reset_with count: count
     end
     
     
    -

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

    +

    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.
    @@ -13883,10 +14433,21 @@ class Star
         # path,
         # angle,
         # alpha, red_saturation, green_saturation, blue_saturation
    +    # tile_x, tile_y, tile_w, tile_h,
         # flip_horizontally, flip_vertically,
    -    # tile_x, tile_y, tile_w, tile_h
         # angle_anchor_x, angle_anchor_y,
         # source_x, source_y, source_w, source_h
    +
    +    # The argument order for ffi_draw.draw_sprite_4 is:
    +    # x, y, w, h,
    +    # path,
    +    # angle,
    +    # alpha, red_saturation, green_saturation, blue_saturation
    +    # tile_x, tile_y, tile_w, tile_h,
    +    # flip_horizontally, flip_vertically,
    +    # angle_anchor_x, angle_anchor_y,
    +    # source_x, source_y, source_w, source_h,
    +    # blendmode_enum
       end
     end
     
    @@ -13894,6 +14455,11 @@ end
     def tick args
       # sets console command when sample app initially opens
       if Kernel.global_tick_count == 0
    +    puts ""
    +    puts ""
    +    puts "========================================================="
    +    puts "* INFO: Static Sprites, Classes, Draw Override"
    +    puts "* INFO: Please specify the number of sprites to render."
         args.gtk.console.set_command "reset_with count: 100"
       end
     
    @@ -13915,7 +14481,7 @@ def reset_with count: count
     end
     
     
    -

    Performance - Collision Limits - main.rb

    +

    Performance - Collision Limits - main.rb

    # ./samples/09_performance/07_collision_limits/app/main.rb
     =begin
     
    @@ -13974,7 +14540,7 @@ end
     $gtk.reset
     
     
    -

    Advanced Debugging - Trace Debugging - main.rb

    +

    Advanced Debugging - Trace Debugging - main.rb

    # ./samples/10_advanced_debugging/01_trace_debugging/app/main.rb
     class Game
       attr_gtk
    @@ -14031,7 +14597,7 @@ def tick args
     end
     
     
    -

    Advanced Debugging - Trace Debugging Classes - main.rb

    +

    Advanced Debugging - Trace Debugging Classes - main.rb

    # ./samples/10_advanced_debugging/02_trace_debugging_classes/app/main.rb
     class Foobar
       def initialize
    @@ -14057,7 +14623,54 @@ def tick args
     end
     
     
    -

    Advanced Debugging - Unit Tests - exception_raising_tests.rb

    +

    Advanced Debugging - Unit Tests - benchmark_api_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/benchmark_api_tests.rb
    +def test_benchmark_api args, assert
    +  result = args.gtk.benchmark iterations: 100,
    +                              only_one: -> () {
    +                                r = 0
    +                                (1..100).each do |i|
    +                                  r += 1
    +                                end
    +                              }
    +
    +  assert.equal! result.first_place.name, :only_one
    +
    +  result = args.gtk.benchmark iterations: 100,
    +                              iterations_100: -> () {
    +                                r = 0
    +                                (1..100).each do |i|
    +                                  r += 1
    +                                end
    +                              },
    +                              iterations_50: -> () {
    +                                r = 0
    +                                (1..50).each do |i|
    +                                  r += 1
    +                                end
    +                              }
    +
    +  assert.equal! result.first_place.name, :iterations_50
    +
    +  result = args.gtk.benchmark iterations: 1,
    +                              iterations_100: -> () {
    +                                r = 0
    +                                (1..100).each do |i|
    +                                  r += 1
    +                                end
    +                              },
    +                              iterations_50: -> () {
    +                                r = 0
    +                                (1..50).each do |i|
    +                                  r += 1
    +                                end
    +                              }
    +
    +  assert.equal! result.too_small_to_measure, true
    +end
    +
    +
    +

    Advanced Debugging - Unit Tests - exception_raising_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/exception_raising_tests.rb
     begin :shared
       class ExceptionalClass
    @@ -14080,7 +14693,7 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - fn_tests.rb

    +

    Advanced Debugging - Unit Tests - fn_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/fn_tests.rb
     def infinity
       1 / 0
    @@ -14264,13 +14877,13 @@ def test_array_hash args, assert
     end
     
     
    -

    Advanced Debugging - Unit Tests - gen_docs.rb

    +

    Advanced Debugging - Unit Tests - gen_docs.rb

    # ./samples/10_advanced_debugging/03_unit_tests/gen_docs.rb
     # ./dragonruby mygame --eval samples/99_zz_gtk_unit_tests/gen_docs.rb --no-tick
     Kernel.export_docs!
     
     
    -

    Advanced Debugging - Unit Tests - geometry_tests.rb

    +

    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
    @@ -14388,7 +15001,7 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - http_tests.rb

    +

    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]
    @@ -14414,7 +15027,7 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - nil_coercion_tests.rb

    +

    Advanced Debugging - Unit Tests - nil_coercion_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/nil_coercion_tests.rb
     # numbers
     def test_open_entity_add_number args, assert
    @@ -14510,7 +15123,7 @@ def test_open_entity_nil_bug args, assert
     end
     
     
    -

    Advanced Debugging - Unit Tests - object_to_primitive_tests.rb

    +

    Advanced Debugging - Unit Tests - object_to_primitive_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/object_to_primitive_tests.rb
     class PlayerSpriteForTest
     end
    @@ -14530,7 +15143,7 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - parsing_tests.rb

    +

    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"] }'
    @@ -14561,7 +15174,141 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - require_tests.rb

    +

    Advanced Debugging - Unit Tests - pretty_format_tests.rb

    +
    # ./samples/10_advanced_debugging/03_unit_tests/pretty_format_tests.rb
    +def H opts
    +  opts
    +end
    +
    +def A *opts
    +  opts
    +end
    +
    +def assert_format args, assert, hash, expected
    +  actual = args.fn.pretty_format hash
    +  assert.are_equal! actual, expected
    +end
    +
    +def test_pretty_print args, assert
    +  # =============================
    +  # hash with single value
    +  # =============================
    +  input = (H first_name: "John")
    +  expected = <<-S
    +{:first_name "John"}
    +S
    +  (assert_format args, assert, input, expected)
    +
    +  # =============================
    +  # hash with two values
    +  # =============================
    +  input = (H first_name: "John", last_name: "Smith")
    +  expected = <<-S
    +{:first_name "John"
    + :last_name "Smith"}
    +S
    +
    +  (assert_format args, assert, input, expected)
    +
    +  # =============================
    +  # hash with inner hash
    +  # =============================
    +  input = (H first_name: "John",
    +             last_name: "Smith",
    +             middle_initial: "I",
    +             so: (H first_name: "Pocahontas",
    +                    last_name: "Tsenacommacah"),
    +             friends: (A (H first_name: "Side", last_name: "Kick"),
    +                         (H first_name: "Tim", last_name: "Wizard")))
    +  expected = <<-S
    +{:first_name "John"
    + :last_name "Smith"
    + :middle_initial "I"
    + :so {:first_name "Pocahontas"
    +      :last_name "Tsenacommacah"}
    + :friends [{:first_name "Side"
    +            :last_name "Kick"}
    +           {:first_name "Tim"
    +            :last_name "Wizard"}]}
    +S
    +
    +  (assert_format args, assert, input, expected)
    +
    +  # =============================
    +  # array with one value
    +  # =============================
    +  input = (A 1)
    +  expected = <<-S
    +[1]
    +S
    +  (assert_format args, assert, input, expected)
    +
    +  # =============================
    +  # array with multiple values
    +  # =============================
    +  input = (A 1, 2, 3)
    +  expected = <<-S
    +[1
    + 2
    + 3]
    +S
    +  (assert_format args, assert, input, expected)
    +
    +  # =============================
    +  # array with multiple values hashes
    +  # =============================
    +  input = (A (H first_name: "Side", last_name: "Kick"),
    +             (H first_name: "Tim", last_name: "Wizard"))
    +  expected = <<-S
    +[{:first_name "Side"
    +  :last_name "Kick"}
    + {:first_name "Tim"
    +  :last_name "Wizard"}]
    +S
    +
    +  (assert_format args, assert, input, expected)
    +end
    +
    +def test_nested_nested args, assert
    +  # =============================
    +  # nested array in nested hash
    +  # =============================
    +  input = (H type: :root,
    +             text: "Root",
    +             children: (A (H level: 1,
    +                             text: "Level 1",
    +                             children: (A (H level: 2,
    +                                             text: "Level 2",
    +                                             children: [])))))
    +
    +  expected = <<-S
    +{:type :root
    + :text "Root"
    + :children [{:level 1
    +             :text "Level 1"
    +             :children [{:level 2
    +                         :text "Level 2"
    +                         :children []}]}]}
    +
    +S
    +
    +  (assert_format args, assert, input, expected)
    +end
    +
    +def test_scene args, assert
    +  script = <<-S
    +* Scene 1
    +** Narrator
    +They say happy endings don't exist.
    +** Narrator
    +They say true love is a lie.
    +S
    +  input = parse_org args, script
    +  puts (args.fn.pretty_format input)
    +end
    +
    +
    +

    Advanced Debugging - Unit Tests - require_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/require_tests.rb
     def write_src path, src
       $gtk.write_file path, src
    @@ -14603,7 +15350,7 @@ def test_require args, assert
     end
     
     
    -

    Advanced Debugging - Unit Tests - serialize_deserialize_tests.rb

    +

    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__!
    @@ -14722,7 +15469,7 @@ def test_by_reference_state_strict_entities args, assert
     end
     
     
    -

    Advanced Debugging - Unit Tests - state_serialization_experimental_tests.rb

    +

    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
     
    @@ -14832,7 +15579,7 @@ $gtk.reset 100
     $gtk.log_level = :off
     
     
    -

    Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb

    +

    Advanced Debugging - Unit Tests - suggest_autocompletion_tests.rb

    # ./samples/10_advanced_debugging/03_unit_tests/suggest_autocompletion_tests.rb
     def default_suggest_autocompletion args
       {
    @@ -14874,7 +15621,7 @@ S
     end
     
     
    -

    Http - Retrieve Images - main.rb

    +

    Http - Retrieve Images - main.rb

    # ./samples/11_http/01_retrieve_images/app/main.rb
     def tick args
       args.outputs.background_color = [0, 0, 0]
    @@ -14931,7 +15678,7 @@ def tick args
     end
     
     
    -

    Http - Web Server - main.rb

    +

    Http - Web Server - main.rb

    # ./samples/11_http/02_web_server/app/main.rb
     def tick args
       args.state.port ||= 3000
    @@ -14962,7 +15709,7 @@ def tick args
     end
     
     
    -

    C Extensions - Basics - main.rb

    +

    C Extensions - Basics - main.rb

    # ./samples/12_c_extensions/01_basics/app/main.rb
     $gtk.ffi_misc.gtk_dlopen("ext")
     include FFI::CExt
    @@ -14976,7 +15723,7 @@ end
     
     
     
    -

    C Extensions - Intermediate - main.rb

    +

    C Extensions - Intermediate - main.rb

    # ./samples/12_c_extensions/02_intermediate/app/main.rb
     $gtk.ffi_misc.gtk_dlopen("ext")
     include FFI::RE
    @@ -14999,7 +15746,7 @@ def tick args
     end
     
     
    -

    C Extensions - Native Pixel Arrays - main.rb

    +

    C Extensions - Native Pixel Arrays - main.rb

    # ./samples/12_c_extensions/03_native_pixel_arrays/app/main.rb
     $gtk.ffi_misc.gtk_dlopen("ext")
     include FFI::CExt
    @@ -15025,7 +15772,7 @@ end
     
     
     
    -

    Path Finding Algorithms - Breadth First Search - main.rb

    +

    Path Finding Algorithms - Breadth First Search - main.rb

    # ./samples/13_path_finding_algorithms/01_breadth_first_search/app/main.rb
     # A visual demonstration of a breadth first search
     # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
    @@ -15721,7 +16468,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Detailed Breadth First Search - main.rb

    +

    Path Finding Algorithms - Detailed Breadth First Search - main.rb

    # ./samples/13_path_finding_algorithms/02_detailed_breadth_first_search/app/main.rb
     # A visual demonstration of a breadth first search
     # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
    @@ -16371,7 +17118,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Breadcrumbs - main.rb

    +

    Path Finding Algorithms - Breadcrumbs - main.rb

    # ./samples/13_path_finding_algorithms/03_breadcrumbs/app/main.rb
     class Breadcrumbs
       attr_gtk
    @@ -16920,7 +17667,7 @@ end
      #  end
     
     
    -

    Path Finding Algorithms - Early Exit - main.rb

    +

    Path Finding Algorithms - Early Exit - main.rb

    # ./samples/13_path_finding_algorithms/04_early_exit/app/main.rb
     # Comparison of a breadth first search with and without early exit
     # Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
    @@ -17555,7 +18302,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Dijkstra - main.rb

    +

    Path Finding Algorithms - Dijkstra - main.rb

    # ./samples/13_path_finding_algorithms/05_dijkstra/app/main.rb
     # Demonstrates how Dijkstra's Algorithm allows movement costs to be considered
     
    @@ -18403,7 +19150,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Heuristic - main.rb

    +

    Path Finding Algorithms - Heuristic - main.rb

    # ./samples/13_path_finding_algorithms/06_heuristic/app/main.rb
     # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
     
    @@ -19387,7 +20134,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Heuristic With Walls - main.rb

    +

    Path Finding Algorithms - Heuristic With Walls - main.rb

    # ./samples/13_path_finding_algorithms/07_heuristic_with_walls/app/main.rb
     # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
     # The effectiveness of the Heuristic search algorithm is shown through this demonstration.
    @@ -20404,7 +21151,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - A Star - main.rb

    +

    Path Finding Algorithms - A Star - main.rb

    # ./samples/13_path_finding_algorithms/08_a_star/app/main.rb
     # This program is inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
     
    @@ -21437,7 +22184,7 @@ def reset
     end
     
     
    -

    Path Finding Algorithms - Tower Defense - main.rb

    +

    Path Finding Algorithms - Tower Defense - main.rb

    # ./samples/13_path_finding_algorithms/09_tower_defense/app/main.rb
     # An example of some major components in a tower defence game
     # The pathing of the tanks is determined by A* algorithm -- try editing the walls
    @@ -21742,7 +22489,7 @@ def a_star_color
     end
     
     
    -

    3d - 3d Cube - main.rb

    +

    3d - 3d Cube - main.rb

    # ./samples/99_genre_3d/01_3d_cube/app/main.rb
     STARTX             = 0.0
     STARTY             = 0.0
    @@ -21796,7 +22543,7 @@ end
     $gtk.reset
     
     
    -

    3d - Wireframe - main.rb

    +

    3d - Wireframe - main.rb

    # ./samples/99_genre_3d/02_wireframe/app/main.rb
     def tick args
       args.state.model   ||= Object3D.new('data/shuttle.off')
    @@ -21949,11 +22696,11 @@ class Vertex
       end
     end
     
    -

    3d - Wireframe - Data - what-is-this.txt

    +

    3d - Wireframe - Data - what-is-this.txt

    # ./samples/99_genre_3d/02_wireframe/data/what-is-this.txt
     https://en.wikipedia.org/wiki/OFF_(file_format)
     
    -

    Arcade - Bullet Hell - main.rb

    +

    Arcade - Bullet Hell - main.rb

    # ./samples/99_genre_arcade/bullet_hell/app/main.rb
     def tick args
       args.state.base_columns   ||= 10.times.map { |n| 50 * n + 1280 / 2 - 5 * 50 + 5 }
    @@ -22146,7 +22893,7 @@ def update_enemy_positions args
     end
     
     
    -

    Arcade - Dueling Starships - main.rb

    +

    Arcade - Dueling Starships - main.rb

    # ./samples/99_genre_arcade/dueling_starships/app/main.rb
     class DuelingSpaceships
       attr_accessor :state, :inputs, :outputs, :grid
    @@ -22515,14 +23262,14 @@ def tick args
     end
     
     
    -

    arcade/flappy dragon/credits.txt

    +

    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

    +

    arcade/flappy dragon/main.rb

    # ./samples/99_genre_arcade/flappy_dragon/app/main.rb
     class FlappyDragon
       attr_accessor :grid, :inputs, :state, :outputs
    @@ -22886,7 +23633,7 @@ def tick args
     end
     
     
    -

    Arcade - Pong - main.rb

    +

    Arcade - Pong - main.rb

    # ./samples/99_genre_arcade/pong/app/main.rb
     def tick args
       defaults args
    @@ -23049,7 +23796,7 @@ begin :assets
     end
     
     
    -

    Arcade - Snakemoji - main.rb

    +

    Arcade - Snakemoji - main.rb

    # ./samples/99_genre_arcade/snakemoji/app/main.rb
     # coding: utf-8
     ################################
    @@ -23218,7 +23965,7 @@ def defaults 🎮
     end
     
     
    -

    Arcade - Solar System - main.rb

    +

    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
    @@ -23330,7 +24077,7 @@ def r
     end
     
     
    -

    Arcade - Sound Golf - main.rb

    +

    Arcade - Sound Golf - main.rb

    # ./samples/99_genre_arcade/sound_golf/app/main.rb
     =begin
     
    @@ -23524,7 +24271,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Arcade - Twinstick - main.rb

    +

    Arcade - Twinstick - main.rb

    # ./samples/99_genre_arcade/twinstick/app/main.rb
     def tick args
       args.state.player         ||= {x: 600, y: 320, w: 80, h: 80, path: 'sprites/circle-white.png', vx: 0, vy: 0, health: 10, cooldown: 0, score: 0}
    @@ -23678,7 +24425,7 @@ def shoot_directional_vector args
       [dx, dy]
     end
     
    -

    Crafting - Craft Game Starting Point - 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:
    @@ -24105,7 +24852,439 @@ end
     $gtk.reset
     
     
    -

    Dev Tools - Add Buttons To Console - main.rb

    +

    Crafting - Farming Game Starting Point - main.rb

    +
    # ./samples/99_genre_crafting/farming_game_starting_point/app/main.rb
    +def tick args
    +  args.state.tile_size     = 80
    +  args.state.player_speed  = 4
    +  args.state.player      ||= tile(args, 7, 3, 0, 128, 180)
    +  generate_map args
    +  #press j to plant a green onion
    +  if args.inputs.keyboard.j
    +  #change this part you can change what you want to plant
    +   args.state.walls << tile(args, ((args.state.player.x+80)/args.state.tile_size), ((args.state.player.y)/args.state.tile_size), 255, 255, 255)
    +   args.state.plants << tile(args, ((args.state.player.x+80)/args.state.tile_size), ((args.state.player.y+80)/args.state.tile_size), 0, 160, 0)
    +  end
    +  # Adds walls, background, and player to args.outputs.solids so they appear on screen
    +  args.outputs.solids << [0,0,1280,720, 237,189,101]
    +  args.outputs.sprites << [0, 0, 1280, 720, 'sprites/background.png']
    +  args.outputs.solids << args.state.walls
    +  args.outputs.solids << args.state.player
    +  args.outputs.solids << args.state.plants
    +  args.outputs.labels << [320, 640, "press J to plant", 3, 1, 255, 0, 0, 200]
    +
    +  move_player args, -1,  0 if args.inputs.keyboard.left # x position decreases by 1 if left key is pressed
    +  move_player args,  1,  0 if args.inputs.keyboard.right # x position increases by 1 if right key is pressed
    +  move_player args,  0,  1 if args.inputs.keyboard.up # y position increases by 1 if up is pressed
    +  move_player args,  0, -1 if args.inputs.keyboard.down # y position decreases by 1 if down is pressed
    +end
    +
    +# Sets position, size, and color of the tile
    +def tile args, x, y, *color
    +  [x * args.state.tile_size, # sets definition for array using method parameters
    +   y * args.state.tile_size, # multiplying by tile_size sets x and y to correct position using pixel values
    +   args.state.tile_size,
    +   args.state.tile_size,
    +   *color]
    +end
    +
    +# Creates map by adding tiles to the wall, as well as a goal (that the player needs to reach)
    +def generate_map args
    +  return if args.state.area
    +
    +  # Creates the area of the map. There are 9 rows running horizontally across the screen
    +  # and 16 columns running vertically on the screen. Any spot with a "1" is not
    +  # open for the player to move into (and is green), and any spot with a "0" is available
    +  # for the player to move in.
    +  args.state.area = [
    +    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,],
    +    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
    +    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
    +  ].reverse # reverses the order of the area collection
    +
    +  # By reversing the order, the way that the area appears above is how it appears
    +  # on the screen in the game. If we did not reverse, the map would appear inverted.
    +
    +  #The wall starts off with no tiles.
    +  args.state.walls = []
    +  args.state.plants = []
    +
    +  # If v is 1, a green tile is added to args.state.walls.
    +  # If v is 2, a black tile is created as the goal.
    +  args.state.area.map_2d do |y, x, v|
    +    if    v == 1
    +      args.state.walls << tile(args, x, y, 255, 160, 156) # green tile
    +    end
    +  end
    +end
    +
    +# Allows the player to move their box around the screen
    +def move_player args, *vector
    +  box = args.state.player.shift_rect(vector) # box is able to move at an angle
    +
    +  # If the player's box hits a wall, it is not able to move further in that direction
    +  return if args.state.walls
    +                .any_intersect_rect?(box)
    +
    +  # Player's box is able to move at angles (not just the four general directions) fast
    +  args.state.player =
    +    args.state.player
    +        .shift_rect(vector.x * args.state.player_speed, # if we don't multiply by speed, then
    +                    vector.y * args.state.player_speed) # the box will move extremely slow
    +end
    +
    +
    +

    Crafting - Farming Game Starting Point - repl.rb

    +
    # ./samples/99_genre_crafting/farming_game_starting_point/app/repl.rb
    +# ===============================================================
    +# Welcome to repl.rb
    +# ===============================================================
    +# You can experiement with code within this file. Code in this
    +# file is only executed when you save (and only excecuted ONCE).
    +# ===============================================================
    +
    +# ===============================================================
    +# REMOVE the "x" from the word "xrepl" and save the file to RUN
    +# the code in between the do/end block delimiters.
    +# ===============================================================
    +
    +# ===============================================================
    +# ADD the "x" to the word "repl" (make it xrepl) and save the
    +# file to IGNORE the code in between the do/end block delimiters.
    +# ===============================================================
    +
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "The result of 1 + 2 is: #{1 + 2}"
    +end
    +
    +# ====================================================================================
    +# Ruby Crash Course:
    +# Strings, Numeric, Booleans, Conditionals, Looping, Enumerables, Arrays
    +# ====================================================================================
    +
    +# ====================================================================================
    +#  Strings
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  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
    +
    +# ====================================================================================
    +#  Numerics
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  a = 10
    +  puts "The value of a is: #{a}"
    +  puts "a + 1 is: #{a + 1}"
    +  puts "a / 3 is: #{a / 3}"
    +end
    +
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  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
    +
    +# ====================================================================================
    +#  Booleans
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  c = 30
    +  puts "The value of c is #{c}."
    +
    +  if c
    +    puts "This if statement ran because c is truthy."
    +  end
    +end
    +
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  d = false
    +  puts "The value of d is #{d}."
    +
    +  if !d
    +    puts "This if statement ran because d is falsey, using the not operator (!) makes d evaluate to true."
    +  end
    +
    +  e = nil
    +  puts "Nil is also considered falsey. The value of e is: #{e}."
    +
    +  if !e
    +    puts "This if statement ran because e is nil (a falsey value)."
    +  end
    +end
    +
    +# ====================================================================================
    +#  Conditionals
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  i_am_true  = true
    +  i_am_nil   = nil
    +  i_am_false = false
    +  i_am_hi    = "hi"
    +
    +  puts "======== if statement"
    +  i_am_one = 1
    +  if i_am_one
    +    puts "This was printed because i_am_one is truthy."
    +  end
    +
    +  puts "======== if/else statement"
    +  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
    +
    +  puts "======== if/elsif/else statement"
    +  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
    +
    +  puts "======== case statement "
    +  i_am_one = 1
    +  case i_am_one
    +  when 10
    +    puts "case equaled: 10"
    +  when 9
    +    puts "case equaled: 9"
    +  when 5
    +    puts "case equaled: 5"
    +  when 1
    +    puts "case equaled: 1"
    +  else
    +    puts "Value wasn't cased."
    +  end
    +
    +  puts "======== different types of comparisons"
    +  if 4 == 4
    +    puts "equal (4 == 4)"
    +  end
    +
    +  if 4 != 3
    +    puts "not equal (4 != 3)"
    +  end
    +
    +  if 3 < 4
    +    puts "less than (3 < 4)"
    +  end
    +
    +  if 4 > 3
    +    puts "greater than (4 > 3)"
    +  end
    +
    +  if ((4 > 3) || (3 < 4) || false)
    +    puts "or statement ((4 > 3) || (3 < 4) || false)"
    +  end
    +
    +  if ((4 > 3) && (3 < 4))
    +    puts "and statement ((4 > 3) && (3 < 4))"
    +  end
    +end
    +
    +# ====================================================================================
    +# Looping
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "======== times block"
    +  3.times do |i|
    +    puts i
    +  end
    +  puts "======== range block exclusive"
    +  (0...3).each do |i|
    +    puts i
    +  end
    +  puts "======== range block inclusive"
    +  (0..3).each do |i|
    +    puts i
    +  end
    +end
    +
    +# ====================================================================================
    +#  Enumerables
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "======== array each"
    +  colors = ["red", "blue", "yellow"]
    +  colors.each do |color|
    +    puts color
    +  end
    +
    +  puts '======== array each_with_index'
    +  colors = ["red", "blue", "yellow"]
    +  colors.each_with_index do |color, i|
    +    puts "#{color} at index #{i}"
    +  end
    +end
    +
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "======== single parameter function"
    +  def add_one_to n
    +    n + 5
    +  end
    +
    +  puts add_one_to(3)
    +
    +  puts "======== function with default value"
    +  def function_with_default_value v = 10
    +    v * 10
    +  end
    +
    +  puts "passing three: #{function_with_default_value(3)}"
    +  puts "passing nil: #{function_with_default_value}"
    +
    +  puts "======== Or Equal (||=) operator for nil values"
    +  def function_with_nil_default_with_local a = nil
    +    result   = a
    +    result ||= "or equal operator was exected and set a default value"
    +  end
    +
    +  puts "passing 'hi': #{function_with_nil_default_with_local 'hi'}"
    +  puts "passing nil: #{function_with_nil_default_with_local}"
    +end
    +
    +# ====================================================================================
    +#  Arrays
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "======== Create an array with the numbers 1 to 10."
    +  one_to_ten = (1..10).to_a
    +  puts one_to_ten
    +
    +  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
    +
    +  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
    +
    +  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
    +
    +  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
    +
    +  puts "======== All combination of numbers 1 to 10."
    +  one_to_ten = (1..10).to_a
    +  all_combinations = one_to_ten.product(one_to_ten)
    +  puts all_combinations
    +
    +  puts "======== All uniq combinations of numbers. 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
    +
    +# ====================================================================================
    +#  Advanced Arrays
    +# ====================================================================================
    +# Remove the x from xrepl to run the code. Add the x back to ignore to code.
    +xrepl do
    +  puts "======== All unique Pythagorean Triples between 1 and 40 sorted by area of the triangle."
    +
    +  one_to_hundred = (1..40).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, area|
    +    puts "(#{width}, #{height}, #{hypotenuse}) = #{area}"
    +  end
    +end
    +
    +
    +

    Crafting - Farming Game Starting Point - tests.rb

    +
    # ./samples/99_genre_crafting/farming_game_starting_point/app/tests.rb
    +# For advanced users:
    +# You can put some quick verification tests here, any method
    +# that starts with the `test_` will be run when you save this file.
    +
    +# Here is an example test and game
    +
    +# To run the test: ./dragonruby mygame --eval app/tests.rb --no-tick
    +
    +class MySuperHappyFunGame
    +  attr_gtk
    +
    +  def tick
    +    outputs.solids << [100, 100, 300, 300]
    +  end
    +end
    +
    +def test_universe args, assert
    +  game = MySuperHappyFunGame.new
    +  game.args = args
    +  game.tick
    +  assert.true!  args.outputs.solids.length == 1, "failure: a solid was not added after tick"
    +  assert.false! 1 == 2, "failure: some how, 1 equals 2, the world is ending"
    +  puts "test_universe completed successfully"
    +end
    +
    +puts "running tests"
    +$gtk.reset 100
    +$gtk.log_level = :off
    +$gtk.tests.start
    +
    +
    +

    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
    @@ -24167,7 +25346,7 @@ def tick args
     end
     
     
    -

    Dev Tools - Animation Creator Starting Point - main.rb

    +

    Dev Tools - Animation Creator Starting Point - main.rb

    # ./samples/99_genre_dev_tools/animation_creator_starting_point/app/main.rb
     class OneBitLowrezPaint
       attr_gtk
    @@ -24272,12 +25451,12 @@ class OneBitLowrezPaint
           label = { x: b.x + state.buttons_frame_selection.size.half,
                     y: b.y,
                     text: "#{i + 1}", r: 180, g: 180, b: 180,
    -                size_enum: -4, alignment_enum: 1 }.label
    +                size_enum: -4, alignment_enum: 1 }.label!
     
    -      selection_border = b.merge(r: 40, g: 40, b: 40).border
    +      selection_border = b.merge(r: 40, g: 40, b: 40).border!
     
           if i == state.animation_frames_selected_index
    -        selection_border = b.merge(r: 40, g: 230, b: 200).border
    +        selection_border = b.merge(r: 40, g: 230, b: 200).border!
           end
     
           [selection_border, label]
    @@ -24616,7 +25795,7 @@ end
     # $gtk.reset
     
     
    -

    Dev Tools - Tile Editor Starting Point - main.rb

    +

    Dev Tools - Tile Editor Starting Point - main.rb

    # ./samples/99_genre_dev_tools/tile_editor_starting_point/app/main.rb
     =begin
     
    @@ -25011,7 +26190,525 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Lowrez - Nokia 3310 - main.rb

    +

    Dungeon Crawl - Classics Jam - main.rb

    +
    # ./samples/99_genre_dungeon_crawl/classics_jam/app/main.rb
    +class Game
    +  attr_gtk
    +
    +  def tick
    +    defaults
    +    render
    +    input
    +    calc
    +  end
    +
    +  def defaults
    +    player.x              ||= 640
    +    player.y              ||= 360
    +    player.w              ||= 16
    +    player.h              ||= 16
    +    player.attacked_at    ||= -1
    +    player.angle          ||= 0
    +    player.future_player  ||= future_player_position 0, 0
    +    player.projectiles    ||= []
    +    player.damage         ||= 0
    +    state.level           ||= create_level level_one_template
    +  end
    +
    +  def render
    +    outputs.sprites << level.walls.map do |w|
    +      w.merge(path: 'sprites/square/gray.png')
    +    end
    +
    +    outputs.sprites << level.spawn_locations.map do |s|
    +      s.merge(path: 'sprites/square/blue.png')
    +    end
    +
    +    outputs.sprites << player.projectiles.map do |p|
    +      p.merge(path: 'sprites/square/blue.png')
    +    end
    +
    +    outputs.sprites << level.enemies.map do |e|
    +      e.merge(path: 'sprites/square/red.png')
    +    end
    +
    +    outputs.sprites << player.merge(path: 'sprites/circle/green.png', angle: player.angle)
    +
    +    outputs.labels << { x: 30, y: 30.from_top, text: "damage: #{player.damage || 0}" }
    +  end
    +
    +  def input
    +    player.angle = inputs.directional_angle || player.angle
    +    if inputs.controller_one.key_down.a || inputs.keyboard.key_down.space
    +      player.attacked_at = state.tick_count
    +    end
    +  end
    +
    +  def calc
    +    calc_player
    +    calc_projectiles
    +    calc_enemies
    +    calc_spawn_locations
    +  end
    +
    +  def calc_player
    +    if player.attacked_at == state.tick_count
    +      player.projectiles << { at: state.tick_count,
    +                              x: player.x,
    +                              y: player.y,
    +                              angle: player.angle,
    +                              w: 4,
    +                              h: 4 }.center_inside_rect(player)
    +    end
    +
    +    if player.attacked_at.elapsed_time > 5
    +      future_player = future_player_position inputs.left_right * 2, inputs.up_down * 2
    +      future_player_collision = future_collision player, future_player, level.walls
    +      player.x = future_player_collision.x if !future_player_collision.dx_collision
    +      player.y = future_player_collision.y if !future_player_collision.dy_collision
    +    end
    +  end
    +
    +  def calc_projectile_collisions entities
    +    entities.each do |e|
    +      e.damage ||= 0
    +      player.projectiles.each do |p|
    +        if !p.collided && (p.intersect_rect? e)
    +          p.collided = true
    +          e.damage  += 1
    +        end
    +      end
    +    end
    +  end
    +
    +  def calc_projectiles
    +    player.projectiles.map! do |p|
    +      dx, dy = p.angle.vector 10
    +      p.merge(x: p.x + dx, y: p.y + dy)
    +    end
    +
    +    calc_projectile_collisions level.walls + level.enemies + level.spawn_locations
    +    player.projectiles.reject! { |p| p.at.elapsed_time > 10000 }
    +    player.projectiles.reject! { |p| p.collided }
    +    level.enemies.reject! { |e| e.damage > e.hp }
    +    level.spawn_locations.reject! { |s| s.damage > s.hp }
    +  end
    +
    +  def calc_enemies
    +    level.enemies.map! do |e|
    +      dx =  0
    +      dx =  1 if e.x < player.x
    +      dx = -1 if e.x > player.x
    +      dy =  0
    +      dy =  1 if e.y < player.y
    +      dy = -1 if e.y > player.y
    +      future_e           = future_entity_position dx, dy, e
    +      future_e_collision = future_collision e, future_e, level.enemies + level.walls
    +      e.next_x = e.x
    +      e.next_y = e.y
    +      e.next_x = future_e_collision.x if !future_e_collision.dx_collision
    +      e.next_y = future_e_collision.y if !future_e_collision.dy_collision
    +      e
    +    end
    +
    +    level.enemies.map! do |e|
    +      e.x = e.next_x
    +      e.y = e.next_y
    +      e
    +    end
    +
    +    level.enemies.each do |e|
    +      player.damage += 1 if e.intersect_rect? player
    +    end
    +  end
    +
    +  def calc_spawn_locations
    +    level.spawn_locations.map! do |s|
    +      s.merge(countdown: s.countdown - 1)
    +    end
    +    level.spawn_locations
    +         .find_all { |s| s.countdown.neg? }
    +         .each do |s|
    +      s.countdown = s.rate
    +      s.merge(countdown: s.rate)
    +      new_enemy = create_enemy s
    +      if !(level.enemies.find { |e| e.intersect_rect? new_enemy })
    +        level.enemies << new_enemy
    +      end
    +    end
    +  end
    +
    +  def create_enemy spawn_location
    +    to_cell(spawn_location.ordinal_x, spawn_location.ordinal_y).merge hp: 2
    +  end
    +
    +  def create_level level_template
    +    {
    +      walls:           level_template.walls.map { |w| to_cell(w.ordinal_x, w.ordinal_y).merge(w) },
    +      enemies:         [],
    +      spawn_locations: level_template.spawn_locations.map { |s| to_cell(s.ordinal_x, s.ordinal_y).merge(s) }
    +    }
    +  end
    +
    +  def level_one_template
    +    {
    +      walls:           [{ ordinal_x: 25, ordinal_y: 20},
    +                        { ordinal_x: 25, ordinal_y: 21},
    +                        { ordinal_x: 25, ordinal_y: 22},
    +                        { ordinal_x: 25, ordinal_y: 23}],
    +      spawn_locations: [{ ordinal_x: 10, ordinal_y: 10, rate: 120, countdown: 0, hp: 5 }]
    +    }
    +  end
    +
    +  def player
    +    state.player ||= {}
    +  end
    +
    +  def level
    +    state.level  ||= {}
    +  end
    +
    +  def future_collision entity, future_entity, others
    +    dx_collision = others.find { |o| o != entity && (o.intersect_rect? future_entity.dx) }
    +    dy_collision = others.find { |o| o != entity && (o.intersect_rect? future_entity.dy) }
    +
    +    {
    +      dx_collision: dx_collision,
    +      x: future_entity.dx.x,
    +      dy_collision: dy_collision,
    +      y: future_entity.dy.y
    +    }
    +  end
    +
    +  def future_entity_position dx, dy, entity
    +    {
    +      dx:   entity.merge(x: entity.x + dx),
    +      dy:   entity.merge(y: entity.y + dy),
    +      both: entity.merge(x: entity.x + dx, y: entity.y + dy)
    +    }
    +  end
    +
    +  def future_player_position  dx, dy
    +    future_entity_position dx, dy, player
    +  end
    +
    +  def to_cell ordinal_x, ordinal_y
    +    { x: ordinal_x * 16, y: ordinal_y * 16, w: 16, h: 16 }
    +  end
    +end
    +
    +def tick args
    +  $game ||= Game.new
    +  $game.args = args
    +  $game.tick
    +end
    +
    +$gtk.reset
    +$game = nil
    +
    +
    +

    Fighting - Special Move Inputs - main.rb

    +
    # ./samples/99_genre_fighting/01_special_move_inputs/app/main.rb
    +def tick args
    +  #tick_instructions args, "Use LEFT and RIGHT arrow keys to move and SPACE to jump."
    +  defaults args
    +  render args
    +  input args
    +  calc args
    +end
    +
    +# sets default values and creates empty collections
    +# initialization only happens in the first frame
    +def defaults args
    +  fiddle args
    +
    +  args.state.tick_count = args.state.tick_count
    +  args.state.bridge_top = 128
    +  args.state.player.x  ||= 0                        # initializes player's properties
    +  args.state.player.y  ||= args.state.bridge_top
    +  args.state.player.w  ||= 64
    +  args.state.player.h  ||= 64
    +  args.state.player.dy ||= 0
    +  args.state.player.dx ||= 0
    +  args.state.player.r  ||= 0
    +  args.state.game_over_at ||= 0
    +  args.state.animation_time ||=0
    +
    +  args.state.timeleft ||=0
    +  args.state.timeright ||=0
    +  args.state.lastpush ||=0
    +
    +  args.state.inputlist ||=  ["j","k","l"]
    +end
    +
    +# sets enemy, player, hammer values
    +def fiddle args
    +  args.state.gravity                     = -0.5
    +  args.state.player_jump_power           = 10      # sets player values
    +  args.state.player_jump_power_duration  = 5
    +  args.state.player_max_run_speed        = 20
    +  args.state.player_speed_slowdown_rate  = 0.9
    +  args.state.player_acceleration         = 0.9
    +end
    +
    +# outputs objects onto the screen
    +def render args
    +  if (args.state.player.dx < 0.01) && (args.state.player.dx > -0.01)
    +    args.state.player.dx = 0
    +  end
    +
    +  #move list
    +  (args.layout.rect_group row: 0, col_from_right: 8, drow: 0.3,
    +                          merge: { vertical_alignment_enum: 0, size_enum: -2 },
    +                          group: [
    +                            { text: "move:             WASD" },
    +                            { text: "jump:             Space" },
    +                            { text: "attack forwards:  J (while on ground" },
    +                            { text: "attack upwards:   K (while on groud)" },
    +                            { text: "attack backwards: J (while on ground and holding A)" },
    +                            { text: "attack downwards: K (while in air)" },
    +                            { text: "dash attack:      J, K in quick succession." },
    +                            { text: "shield: hold      J, K at the same time." },
    +                            { text: "dash backwards:   A, A in quick succession." },
    +                          ]).into args.outputs.labels
    +
    +  # registered moves
    +  args.outputs.labels << { x: 0.to_layout_col,
    +                           y: 0.to_layout_row,
    +                           text: "input history",
    +                           size_enum: -2,
    +                           vertical_alignment_enum: 0 }
    +
    +  (args.state.inputlist.take(5)).map do |s|
    +    { text: s, size_enum: -2, vertical_alignment_enum: 0 }
    +  end.yield_self do |group|
    +    (args.layout.rect_group row: 0.3, col: 0, drow: 0.3, group: group).into args.outputs.labels
    +  end
    +
    +
    +  #sprites
    +  player = [args.state.player.x, args.state.player.y,
    +            args.state.player.w, args.state.player.h,
    +            "sprites/square/white.png",
    +            args.state.player.r]
    +
    +  playershield = [args.state.player.x - 20, args.state.player.y - 10,
    +                  args.state.player.w + 20, args.state.player.h + 20,
    +                  "sprites/square/blue.png",
    +                  args.state.player.r,
    +                  0]
    +
    +  playerjab = [args.state.player.x + 32, args.state.player.y,
    +               args.state.player.w, args.state.player.h,
    +               "sprites/isometric/indigo.png",
    +               args.state.player.r,
    +               0]
    +
    +  playerupper = [args.state.player.x, args.state.player.y + 32,
    +                 args.state.player.w, args.state.player.h,
    +                 "sprites/isometric/indigo.png",
    +                 args.state.player.r+90,
    +                 0]
    +
    +  if ((args.state.tick_count - args.state.lastpush) <= 15)
    +    if (args.state.inputlist[0] == "<<")
    +      player = [args.state.player.x, args.state.player.y,
    +                args.state.player.w, args.state.player.h,
    +                "sprites/square/yellow.png", args.state.player.r]
    +    end
    +
    +    if (args.state.inputlist[0] == "shield")
    +      player = [args.state.player.x, args.state.player.y,
    +                args.state.player.w, args.state.player.h,
    +                "sprites/square/indigo.png", args.state.player.r]
    +
    +      playershield = [args.state.player.x - 10, args.state.player.y - 10,
    +                      args.state.player.w + 20, args.state.player.h + 20,
    +                      "sprites/square/blue.png", args.state.player.r, 50]
    +    end
    +
    +    if (args.state.inputlist[0] == "back-attack")
    +      playerjab = [args.state.player.x - 20, args.state.player.y,
    +                   args.state.player.w - 10, args.state.player.h,
    +                   "sprites/isometric/indigo.png", args.state.player.r, 255]
    +    end
    +
    +    if (args.state.inputlist[0] == "forward-attack")
    +      playerjab = [args.state.player.x + 32, args.state.player.y,
    +                   args.state.player.w, args.state.player.h,
    +                   "sprites/isometric/indigo.png", args.state.player.r, 255]
    +    end
    +
    +    if (args.state.inputlist[0] == "up-attack")
    +      playerupper = [args.state.player.x, args.state.player.y + 32,
    +                     args.state.player.w, args.state.player.h,
    +                     "sprites/isometric/indigo.png", args.state.player.r + 90, 255]
    +    end
    +
    +    if (args.state.inputlist[0] == "dair")
    +      playerupper = [args.state.player.x, args.state.player.y - 32,
    +                     args.state.player.w, args.state.player.h,
    +                     "sprites/isometric/indigo.png", args.state.player.r + 90, 255]
    +    end
    +
    +    if (args.state.inputlist[0] == "dash-attack")
    +      playerupper = [args.state.player.x, args.state.player.y + 32,
    +                     args.state.player.w, args.state.player.h,
    +                     "sprites/isometric/violet.png", args.state.player.r + 90, 255]
    +
    +      playerjab = [args.state.player.x + 32, args.state.player.y,
    +                   args.state.player.w, args.state.player.h,
    +                   "sprites/isometric/violet.png", args.state.player.r, 255]
    +    end
    +  end
    +
    +  args.outputs.sprites << playerjab
    +  args.outputs.sprites << playerupper
    +  args.outputs.sprites << player
    +  args.outputs.sprites << playershield
    +
    +  args.outputs.solids << 20.map_with_index do |i| # uses 20 squares to form bridge
    +    [i * 64, args.state.bridge_top - 64, 64, 64]
    +  end
    +end
    +
    +# Performs calculations to move objects on the screen
    +def calc args
    +  # Since velocity is the change in position, the change in x increases by dx. Same with y and dy.
    +  args.state.player.x  += args.state.player.dx
    +  args.state.player.y  += args.state.player.dy
    +
    +  # Since acceleration is the change in velocity, the change in y (dy) increases every frame
    +  args.state.player.dy += args.state.gravity
    +
    +  # player's y position is either current y position or y position of top of
    +  # bridge, whichever has a greater value
    +  # ensures that the player never goes below the bridge
    +  args.state.player.y  = args.state.player.y.greater(args.state.bridge_top)
    +
    +  # player's x position is either the current x position or 0, whichever has a greater value
    +  # ensures that the player doesn't go too far left (out of the screen's scope)
    +  args.state.player.x  = args.state.player.x.greater(0)
    +
    +  # player is not falling if it is located on the top of the bridge
    +  args.state.player.falling = false if args.state.player.y == args.state.bridge_top
    +  #args.state.player.rect = [args.state.player.x, args.state.player.y, args.state.player.h, args.state.player.w] # sets definition for player
    +end
    +
    +# Resets the player by changing its properties back to the values they had at initialization
    +def reset_player args
    +  args.state.player.x = 0
    +  args.state.player.y = args.state.bridge_top
    +  args.state.player.dy = 0
    +  args.state.player.dx = 0
    +  args.state.enemy.hammers.clear # empties hammer collection
    +  args.state.enemy.hammer_queue.clear # empties hammer_queue
    +  args.state.game_over_at = args.state.tick_count # game_over_at set to current frame (or passage of time)
    +end
    +
    +# Processes input from the user to move the player
    +def input args
    +  if args.state.inputlist.length > 5
    +    args.state.inputlist.pop
    +  end
    +
    +  should_process_special_move = (args.inputs.keyboard.key_down.j)           ||
    +                                (args.inputs.keyboard.key_down.k)           ||
    +                                (args.inputs.keyboard.key_down.a)           ||
    +                                (args.inputs.keyboard.key_down.d)           ||
    +                                (args.inputs.controller_one.key_down.y)     ||
    +                                (args.inputs.controller_one.key_down.x)     ||
    +                                (args.inputs.controller_one.key_down.left)  ||
    +                                (args.inputs.controller_one.key_down.right)
    +
    +  if (should_process_special_move)
    +    if (args.inputs.keyboard.key_down.j && args.inputs.keyboard.key_down.k) ||
    +       (args.inputs.controller_one.key_down.x && args.inputs.controller_one.key_down.y)
    +      args.state.inputlist.unshift("shield")
    +    elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y) &&
    +          (args.state.inputlist[0] == "forward-attack") && ((args.state.tick_count - args.state.lastpush) <= 15)
    +      args.state.inputlist.unshift("dash-attack")
    +      args.state.player.dx = 20
    +    elsif (args.inputs.keyboard.key_down.j && args.inputs.keyboard.a) ||
    +          (args.inputs.controller_one.key_down.x && args.inputs.controller_one.key_down.left)
    +      args.state.inputlist.unshift("back-attack")
    +    elsif ( args.inputs.controller_one.key_down.x || args.inputs.keyboard.key_down.j)
    +      args.state.inputlist.unshift("forward-attack")
    +    elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y) &&
    +          (args.state.player.y > 128)
    +      args.state.inputlist.unshift("dair")
    +    elsif (args.inputs.keyboard.key_down.k || args.inputs.controller_one.key_down.y)
    +      args.state.inputlist.unshift("up-attack")
    +    elsif (args.inputs.controller_one.key_down.left || args.inputs.keyboard.key_down.a) &&
    +          (args.state.inputlist[0] == "<") &&
    +          ((args.state.tick_count - args.state.lastpush) <= 10)
    +      args.state.inputlist.unshift("<<")
    +      args.state.player.dx = -15
    +    elsif (args.inputs.controller_one.key_down.left || args.inputs.keyboard.key_down.a)
    +      args.state.inputlist.unshift("<")
    +      args.state.timeleft = args.state.tick_count
    +    elsif (args.inputs.controller_one.key_down.right || args.inputs.keyboard.key_down.d)
    +      args.state.inputlist.unshift(">")
    +    end
    +
    +    args.state.lastpush = args.state.tick_count
    +  end
    +
    +  if args.inputs.keyboard.space || args.inputs.controller_one.r2   # if the user presses the space bar
    +    args.state.player.jumped_at ||= args.state.tick_count # jumped_at is set to current frame
    +
    +    # if the time that has passed since the jump is less than the player's jump duration and
    +    # the player is not falling
    +    if args.state.player.jumped_at.elapsed_time < args.state.player_jump_power_duration && !args.state.player.falling
    +      args.state.player.dy = args.state.player_jump_power # change in y is set to power of player's jump
    +    end
    +  end
    +
    +  # if the space bar is in the "up" state (or not being pressed down)
    +  if args.inputs.keyboard.key_up.space || args.inputs.controller_one.key_up.r2
    +    args.state.player.jumped_at = nil # jumped_at is empty
    +    args.state.player.falling = true # the player is falling
    +  end
    +
    +  if args.inputs.left # if left key is pressed
    +    if args.state.player.dx < -5
    +      args.state.player.dx = args.state.player.dx
    +    else
    +      args.state.player.dx = -5
    +    end
    +
    +  elsif args.inputs.right # if right key is pressed
    +    if args.state.player.dx > 5
    +      args.state.player.dx = args.state.player.dx
    +    else
    +      args.state.player.dx = 5
    +    end
    +  else
    +    args.state.player.dx *= args.state.player_speed_slowdown_rate # dx is scaled down
    +  end
    +
    +  if ((args.state.player.dx).abs > 5) #&& ((args.state.tick_count - args.state.lastpush) <= 10)
    +    args.state.player.dx *= 0.95
    +  end
    +end
    +
    +def tick_instructions args, text, y = 715
    +  return if args.state.key_event_occurred
    +  if args.inputs.mouse.click ||
    +     args.inputs.keyboard.directional_vector ||
    +     args.inputs.keyboard.key_down.enter ||
    +     args.inputs.keyboard.key_down.space ||
    +     args.inputs.keyboard.key_down.escape
    +    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, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
    +end
    +
    +
    +

    Lowrez - Nokia 3310 - main.rb

    # ./samples/99_genre_lowrez/nokia_3310/app/main.rb
     require 'app/nokia.rb'
     
    @@ -25620,7 +27317,7 @@ def render_debug args
           text: text,
           size_enum: -1.5,
           r: 255, g: 255, b: 255
    -    }.label
    +    }.label!
       end
     
       args.outputs.debug << {
    @@ -25630,7 +27327,7 @@ def render_debug args
         size_enum: -0.5,
         alignment_enum: 1,
         r: 255, g: 255, b: 255
    -  }.label
    +  }.label!
     end
     
     def snake_demo args
    @@ -25640,7 +27337,7 @@ end
     $gtk.reset
     
     
    -

    Lowrez - Nokia 3310 - nokia.rb

    +

    Lowrez - Nokia 3310 - nokia.rb

    # ./samples/99_genre_lowrez/nokia_3310/app/nokia.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.
    @@ -25880,7 +27577,7 @@ module GTK
                 g: 240,
                 b: 216,
                 a: 100
    -          }.line
    +          }.line!
             end
     
             (NOKIA_WIDTH + 1).map_with_index do |i|
    @@ -25893,7 +27590,7 @@ module GTK
                 g: 240,
                 b: 216,
                 a: 100
    -          }.line
    +          }.line!
             end
     
             @args.state.overlay_rendered = true
    @@ -25903,7 +27600,7 @@ module GTK
     end
     
     
    -

    Lowrez - Resolution 64x64 - lowrez.rb

    +

    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.
    @@ -26077,7 +27774,7 @@ module GTK
     end
     
     
    -

    Lowrez - Resolution 64x64 - main.rb

    +

    Lowrez - Resolution 64x64 - main.rb

    # ./samples/99_genre_lowrez/resolution_64x64/app/main.rb
     require 'app/lowrez.rb'
     
    @@ -26641,7 +28338,7 @@ def render_debug args
             g: 128,
             b: 128,
             a: 80
    -      }.line
    +      }.line!
     
           args.outputs.static_debug << {
             x:  LOWREZ_X_OFFSET + (i * 10),
    @@ -26652,7 +28349,7 @@ def render_debug args
             g: 128,
             b: 128,
             a: 80
    -      }.line
    +      }.line!
         end
       end
     
    @@ -26679,7 +28376,7 @@ def render_debug args
           y: 720 - (i * 20),
           text: text,
           size_enum: -1.5
    -    }.label
    +    }.label!
       end
     
       args.outputs.debug << {
    @@ -26688,13 +28385,13 @@ def render_debug args
         text: "INFO: dev mode is currently enabled. Comment out the invocation of ~render_debug~ within the ~tick~ method to hide the debug layer.",
         size_enum: -0.5,
         alignment_enum: 1
    -  }.label
    +  }.label!
     end
     
     $gtk.reset
     
     
    -

    Platformer - Clepto Frog - main.rb

    +

    Platformer - Clepto Frog - main.rb

    # ./samples/99_genre_platformer/clepto_frog/app/main.rb
     MAP_FILE_PATH = 'app/map.txt'
     
    @@ -27562,7 +29259,7 @@ def tick_instructions args, text, y = 715
     end
     
     
    -

    Platformer - Clepto Frog - map.rb

    +

    Platformer - Clepto Frog - map.rb

    # ./samples/99_genre_platformer/clepto_frog/app/map.rb
     $collisions = [
       [326, 463, 64, 64],
    @@ -28591,14 +30288,14 @@ $mugs = [
     ]
     
     
    -

    Platformer - Gorillas Basic - credits.txt

    +

    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

    +

    Platformer - Gorillas Basic - main.rb

    # ./samples/99_genre_platformer/gorillas_basic/app/main.rb
     class YouSoBasicGorillas
       attr_accessor :outputs, :grid, :state, :inputs
    @@ -28975,7 +30672,7 @@ def tick args
     end
     
     
    -

    Platformer - Gorillas Basic - tests.rb

    +

    Platformer - Gorillas Basic - tests.rb

    # ./samples/99_genre_platformer/gorillas_basic/app/tests.rb
     $gtk.reset 100
     $gtk.supress_framerate_warning = true
    @@ -28983,7 +30680,7 @@ $gtk.require 'app/tests/building_generation_tests.rb'
     $gtk.tests.start
     
     
    -

    Platformer - Gorillas Basic - Tests - building_generation_tests.rb

    +

    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
    @@ -29002,7 +30699,7 @@ def test_solids args, assert
     end
     
     
    -

    Platformer - The Little Probe - main.rb

    +

    Platformer - The Little Probe - main.rb

    # ./samples/99_genre_platformer/the_little_probe/app/main.rb
     class FallingCircle
       attr_gtk
    @@ -29893,7 +31590,7 @@ def reset
     end
     
     
    -

    Platformer - The Little Probe - Data - level.txt

    +

    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
    @@ -31377,7 +33074,7 @@ end
     1910,11280,2200,11180
     923.0029599285435,11398.99893503157,1264.002959928544,11351.99893503157
     
    -

    Platformer - The Little Probe - Data - level_lava.txt

    +

    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
    @@ -31615,7 +33312,7 @@ end
     2200,11130,2360,11090
     1840,11230,2200,11130
     
    -

    Rpg Narrative - Choose Your Own Adventure - decision.rb

    +

    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
    @@ -31656,7 +33353,7 @@ def game
     end
     
     
    -

    Rpg Narrative - Choose Your Own Adventure - main.rb

    +

    Rpg Narrative - Choose Your Own Adventure - main.rb

    # ./samples/99_genre_rpg_narrative/choose_your_own_adventure/app/main.rb
     =begin
     
    @@ -31792,7 +33489,7 @@ end
     $gtk.reset
     
     
    -

    Rpg Narrative - Return Of Serenity - lowrez_simulator.rb

    +

    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
    @@ -31887,7 +33584,7 @@ def render_gridlines_if_needed args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - main.rb

    +

    Rpg Narrative - Return Of Serenity - main.rb

    # ./samples/99_genre_rpg_narrative/return_of_serenity/app/main.rb
     require 'app/require.rb'
     
    @@ -32364,7 +34061,7 @@ def player_xs args, x, y
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - require.rb

    +

    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'
    @@ -32379,7 +34076,7 @@ require 'app/storyline_final_decision.rb'
     require 'app/storyline.rb'
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline.rb

    +

    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]
    @@ -32529,7 +34226,7 @@ def reload_current_scene
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_anka.rb

    +

    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
       {
    @@ -32660,7 +34357,7 @@ def replied_to_anka_back_home args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_blinking_light.rb

    +

    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
       {
    @@ -32739,7 +34436,7 @@ def blinking_light_inside_mainframe args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_day_one.rb

    +

    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
       {
    @@ -32949,7 +34646,7 @@ def explaining_the_special_power_inside_computer args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_final_decision.rb

    +

    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
       {
    @@ -33089,7 +34786,7 @@ def final_decision_ship_status_shared args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_final_message.rb

    +

    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
       {
    @@ -33309,7 +35006,7 @@ def final_message_summary args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_serenity_alive.rb

    +

    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
       {
    @@ -33532,7 +35229,7 @@ def serenity_alive_current_message args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_serenity_bio.rb

    +

    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
       {
    @@ -33687,7 +35384,7 @@ def bad_dream_last_reply args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_serenity_introduction.rb

    +

    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.",
    @@ -33786,7 +35483,7 @@ def replied_to_introduction_side_of_home args
     end
     
     
    -

    Rpg Narrative - Return Of Serenity - storyline_speed_of_light.rb

    +

    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
       {
    @@ -33894,7 +35591,7 @@ def speed_of_light_end_of_day args
     end
     
     
    -

    Rpg Roguelike - Roguelike Starting Point - constants.rb

    +

    Rpg Roguelike - Roguelike Starting Point - constants.rb

    # ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/constants.rb
     SHOW_LEGEND = true
     SOURCE_TILE_SIZE = 16
    @@ -33906,7 +35603,7 @@ TILE_B = 0
     TILE_A = 255
     
     
    -

    Rpg Roguelike - Roguelike Starting Point - legend.rb

    +

    Rpg Roguelike - Roguelike Starting Point - legend.rb

    # ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/legend.rb
     def tick_legend args
       return unless SHOW_LEGEND
    @@ -33975,7 +35672,7 @@ def tick_legend args
     end
     
     
    -

    Rpg Roguelike - Roguelike Starting Point - main.rb

    +

    Rpg Roguelike - Roguelike Starting Point - main.rb

    # ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/main.rb
     require 'app/constants.rb'
     require 'app/sprite_lookup.rb'
    @@ -34076,7 +35773,7 @@ def tile_in_game x, y, tile_key
     end
     
     
    -

    Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb

    +

    Rpg Roguelike - Roguelike Starting Point - sprite_lookup.rb

    # ./samples/99_genre_rpg_roguelike/01_roguelike_starting_point/app/sprite_lookup.rb
     def sprite_lookup
       {
    @@ -34204,7 +35901,7 @@ end
     $gtk.args.state.reserved.sprite_lookup = sprite_lookup
     
     
    -

    Rpg Roguelike - Roguelike Line Of Sight - main.rb

    +

    Rpg Roguelike - Roguelike Line Of Sight - main.rb

    # ./samples/99_genre_rpg_roguelike/02_roguelike_line_of_sight/app/main.rb
     =begin
     
    @@ -34647,7 +36344,7 @@ def tick args
     end
     
     
    -

    Rpg Tactical - Hexagonal Grid - main.rb

    +

    Rpg Tactical - Hexagonal Grid - main.rb

    # ./samples/99_genre_rpg_tactical/hexagonal_grid/app/main.rb
     class HexagonTileGame
       attr_gtk
    @@ -34719,7 +36416,7 @@ end
     $gtk.reset
     
     
    -

    Rpg Tactical - Isometric Grid - main.rb

    +

    Rpg Tactical - Isometric Grid - main.rb

    # ./samples/99_genre_rpg_tactical/isometric_grid/app/main.rb
     class Isometric
         attr_accessor :grid, :inputs, :state, :outputs
    @@ -34985,7 +36682,7 @@ def tick args
     end
     
     
    -

    Rpg Topdown - Topdown Starting Point - main.rb

    +

    Rpg Topdown - Topdown Starting Point - main.rb

    # ./samples/99_genre_rpg_topdown/topdown_starting_point/app/main.rb
     =begin
     
    @@ -35101,7 +36798,7 @@ end
     

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

    -

    args.rb

    +

    args.rb

    # ./dragon/args.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -35115,58 +36812,21 @@ module GTK
       class Args
         include ArgsDeprecated
         include Serialize
    -
    -    # Contains information related to input devices and input events.
    -    #
    -    # @return [Inputs]
         attr_accessor :inputs
    -
    -    # Contains the means to interact with output devices such as the screen.
    -    #
    -    # @return [Outputs]
         attr_accessor :outputs
    -
    -    # Contains the means to interact with the audio mixer.
    -    #
    -    # @return [Hash]
         attr_accessor :audio
    -
    -    # Contains display size information to assist in positioning things on the screen.
    -    #
    -    # @return [Grid]
         attr_accessor :grid
    -
    -    # Provides access to game play recording facilities within Game Toolkit.
    -    #
    -    # @return [Recording]
         attr_accessor :recording
    -
    -    # Gives you access to geometry related functions.
    -    #
    -    # @return [Geometry]
         attr_accessor :geometry
    -
         attr_accessor :fn
    -
    -    # This is where you'll put state associated with your video game.
    -    #
    -    # @return [OpenEntity]
         attr_accessor :state
    -
    -    # Gives you access to the top level DragonRuby runtime.
    -    #
    -    # @return [Runtime]
    +    attr_accessor :temp_state
         attr_accessor :runtime
         alias_method :gtk, :runtime
    -
         attr_accessor :passes
    -
         attr_accessor :wizards
    -
         attr_accessor :layout
    -
         attr_accessor :easing
    -
         attr_accessor :string
     
         def initialize runtime, recording
    @@ -35175,6 +36835,7 @@ module GTK
           @audio = {}
           @passes = []
           @state = OpenEntity.new
    +      @temp_state = OpenEntity.new
           @state.tick_count = -1
           @runtime = runtime
           @recording = recording
    @@ -35204,11 +36865,12 @@ module GTK
     
         def serialize
           {
    -        state: state.as_hash,
    -        inputs: inputs.serialize,
    -        passes: passes.serialize,
    -        outputs: outputs.serialize,
    -        grid: grid.serialize
    +        state:      state.as_hash,
    +        temp_state: temp_state.as_hash,
    +        inputs:     inputs.serialize,
    +        passes:     passes.serialize,
    +        outputs:    outputs.serialize,
    +        grid:       grid.serialize
           }
         end
     
    @@ -35344,7 +37006,7 @@ module GTK
     end
     
     
    -

    assert.rb

    +

    assert.rb

    # ./dragon/assert.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -35372,10 +37034,10 @@ To add an assertion open up this class and write:
     
     class Assert
       def custom_assertion actual, expected, message = nil
    -    # this tell Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive).
    +    # this tells Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive).
         @assertion_performed = true
     
    -    # perform your custom logic here and rais an exception to denote a failure.
    +    # perform your custom logic here and raise an exception to denote a failure.
     
         raise "Some Error. #{message}."
       end
    @@ -35385,14 +37047,14 @@ end
         attr :assertion_performed
     
     =begin
    -Us this if you are throwing your own exceptions and you want to mark the tests as ran (so that it wont be marked as inconclusive).
    +Use this if you are throwing your own exceptions and you want to mark the tests as ran (so that it wont be marked as inconclusive).
     =end
         def ok!
           @assertion_performed = true
         end
     
     =begin
    -Assert if a value is a thruthy value. All assert method take an optional final parameter that is the message to display to the user.
    +Assert if a value is a truthy value. All assert methods take an optional final parameter that is the message to display to the user.
     
     @example
     
    @@ -35446,7 +37108,7 @@ end
           @assertion_performed = true
           if actual != expected
             actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip
    -        message = "actual:\n#{actual_string}\n\ndid not equal\n\nexpected:\n#{expected}.\n#{message}"
    +        message = "actual:\n#{actual_string}\n\ndid not equal\n\nexpected:\n#{expected}\n#{message}"
             raise message
           end
           nil
    @@ -35456,7 +37118,7 @@ end
           @assertion_performed = true
           if actual == expected
             actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip
    -        message = "actual:\n#{actual_string}\n\nequaled\n\nexpected:\n#{expected}.\n#{message}"
    +        message = "actual:\n#{actual_string}\n\nequaled\n\nexpected:\n#{expected}\n#{message}"
             raise message
           end
           nil
    @@ -35486,7 +37148,7 @@ end
     end
     
     
    -

    attr_gtk.rb

    +

    attr_gtk.rb

    # ./dragon/attr_gtk.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -35509,6 +37171,10 @@ module AttrGTK
         args.state
       end
     
    +  def temp_state
    +    args.temp_state
    +  end
    +
       def inputs
         args.inputs
       end
    @@ -35536,10 +37202,18 @@ module AttrGTK
       def layout
         args.layout
       end
    +
    +  def new_entity entity_type, init_hash = nil, &block
    +    args.state.new_entity entity_type, init_hash, &block
    +  end
    +
    +  def new_entity_strict entity_type, init_hash = nil, &block
    +    args.state.new_entity_strict entity_type, init_hash, &block
    +  end
     end
     
     
    -

    attr_sprite.rb

    +

    attr_sprite.rb

    # ./dragon/attr_sprite.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -35580,7 +37254,7 @@ module AttrSprite
       attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b, :tile_x,
                     :tile_y, :tile_w, :tile_h, :flip_horizontally,
                     :flip_vertically, :angle_anchor_x, :angle_anchor_y, :id,
    -                :source_x, :source_y, :source_w, :source_h
    +                :source_x, :source_y, :source_w, :source_h, :blendmode_enum
     
       def primitive_marker
         :sprite
    @@ -35600,7 +37274,7 @@ module AttrSprite
     end
     
     
    -

    console.rb

    +

    console.rb

    # ./dragon/console.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -35623,7 +37297,7 @@ module GTK
                       :font_style, :menu
     
         def initialize
    -      @font_style = FontStyle.new(font: 'font.ttf', size_enum: -1, line_height: 1.1)
    +      @font_style = FontStyle.new(font: 'font.ttf', size_enum: -1.5, line_height: 1.1)
           @menu = Menu.new self
           @disabled = false
           @log_offset = 0
    @@ -35643,6 +37317,8 @@ module GTK
           @text_color = Color.new [255, 255, 255]
           @error_color = Color.new [200, 50, 50]
           @header_color = Color.new [100, 200, 220]
    +      @code_color = Color.new [210, 168, 255]
    +      @comment_color    = Color.new [0, 200, 100]
           @animation_duration = 1.seconds
           @shown_at = -1
           load_history
    @@ -35917,6 +37593,8 @@ S
     
             if cmd == 'quit' || cmd == ':wq' || cmd == ':q!' || cmd == ':q' || cmd == ':wqa'
               $gtk.request_quit
    +        elsif cmd.start_with? ':'
    +          send ((cmd.gsub '-', '_').gsub ':', '')
             else
               puts "-> #{cmd}"
               begin
    @@ -35925,13 +37603,19 @@ S
                 if $results.nil?
                   puts "=> nil"
                 elsif $results == :console_silent_eval
    +              # do nothing since the console is silent
                 else
                   puts "=> #{$results}"
                 end
                 @last_command_errored = false
               rescue Exception => e
                 try_search_docs e
    -            puts  "* EXCEPTION: #{e}"
    +            # if an exception is thrown and the bactrace includes something helpful, then show it
    +            if (e.backtrace || []).first && (e.backtrace.first.include? "(eval)")
    +              puts  "* EXCEPTION: #{e}"
    +            else
    +              puts  "* EXCEPTION: #{e}\n#{e.__backtrace_to_org__}"
    +            end
               end
             end
           end
    @@ -36158,6 +37842,11 @@ S
           end
     
           render_log_offset args
    +
    +      args.outputs.reserved << { x: 10.from_right, y: @bottom + 10,
    +                                 text: "Press CTRL+g or ESCAPE to clear the prompt.",
    +                                 vertical_alignment_enum: 0,
    +                                 alignment_enum: 2, r: 80, g: 80, b: 80 }.label!
         end
     
         def render_log_offset args
    @@ -36180,7 +37869,7 @@ S
         end
     
         def include_subdued_markers? text
    -      include_any_words? text, subdued_markers
    +      (text.start_with? "* INFO: ") && (include_any_words? text, subdued_markers)
         end
     
         def include_any_words? text, words
    @@ -36326,8 +38015,32 @@ S
           (log_entry.start_with? "**** ")
         end
     
    +    def code? log_entry
    +      (just_symbol? log_entry) || (codeblock_marker? log_entry)
    +    end
    +
    +    def just_symbol? log_entry
    +      scrubbed = log_entry.gsub("*", "").strip
    +      (scrubbed.start_with? ":") && (!scrubbed.include? " ") && (!scrubbed.include? "=>")
    +    end
    +
    +    def code_comment? log_entry
    +      return true  if log_entry.strip.start_with?("# ")
    +      return false
    +    end
    +
    +    def codeblock_marker? log_entry
    +      return true if log_entry.strip.start_with?("#+begin_src")
    +      return true if log_entry.strip.start_with?("#+end_src")
    +      return false
    +    end
    +
         def color_for_log_entry(log_entry)
    -      if include_row_marker? log_entry
    +      if code? log_entry
    +        @code_color
    +      elsif code_comment? log_entry
    +        @comment_color
    +      elsif include_row_marker? log_entry
             @text_color
           elsif include_error_marker? log_entry
             @error_color
    @@ -36374,7 +38087,7 @@ S
     end
     
     
    -

    console_color.rb

    +

    console_color.rb

    # ./dragon/console_color.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -36400,6 +38113,10 @@ module GTK
             @color
           end
     
    +      def to_s
    +        "GTK::Console::Color #{to_h}"
    +      end
    +
           def to_h
             { r: @color[0], g: @color[1], b: @color[2], a: @color[3] }
           end
    @@ -36408,7 +38125,7 @@ module GTK
     end
     
     
    -

    console_font_style.rb

    +

    console_font_style.rb

    # ./dragon/console_font_style.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -36445,14 +38162,14 @@ module GTK
               size_enum: size_enum,
               alignment_enum: alignment_enum,
               **color.to_h,
    -        }.label
    +        }.label!
           end
         end
       end
     end
     
     
    -

    console_menu.rb

    +

    console_menu.rb

    # ./dragon/console_menu.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -36507,7 +38224,7 @@ module GTK
     
           def itch_wizard_clicked
             @console.scroll_to_bottom
    -        $wizards.itch.start
    +        $wizards.itch.restart
           end
     
           def docs_clicked
    @@ -36532,6 +38249,7 @@ module GTK
               @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),
    +            *custom_buttons
               ]
             elsif @menu_shown == :hidden
               @buttons = [
    @@ -36590,8 +38308,8 @@ module GTK
               method: method
             }.let do |entity|
               primitives = []
    -          primitives << entity[:rect].merge(a: 164).solid
    -          primitives << entity[:rect].merge(r: 255, g: 255, b: 255).border
    +          primitives << entity[:rect].solid!(a: 164)
    +          primitives << entity[:rect].border!(r: 255, g: 255, b: 255)
               primitives << text.wrapped_lines(5)
                                 .map_with_index do |l, i|
                                   [
    @@ -36615,7 +38333,7 @@ module GTK
     end
     
     
    -

    console_prompt.rb

    +

    console_prompt.rb

    # ./dragon/console_prompt.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -36793,17 +38511,17 @@ S
               "#{" " * (string_width.length - c.length) } #{c} |"
             end.join
     
    -        # remove seperators between empty values
    +        # remove separators between empty values
             formated_row = formated_row.gsub("  |  ", "     ")
     
             puts formated_row
           end
     
    -      def pretty_print_row_seperator string_width, cell_width, column_width, columns
    +      def pretty_print_row_separator string_width, cell_width, column_width, columns
             # this is a joint: +--------
             column_joint = "+#{"-" * cell_width}"
     
    -        # multiple joints create a row seperator: +----+----+
    +        # multiple joints create a row separator: +----+----+
             puts (column_joint * columns) + "+"
           end
     
    @@ -36812,12 +38530,12 @@ S
             args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 0.5,
                                                                y: y + 5,
                                                                x2: x + @cursor_position_px + 0.5,
    -                                                           y2: y + @font_style.letter_size.y + 5)
    +                                                           y2: y + @font_style.letter_size.y + 4)
     
             args.outputs.reserved << (@cursor_color.to_h.merge x: x + @cursor_position_px + 1,
                                                                y: y + 5,
                                                                x2: x + @cursor_position_px + 1,
    -                                                           y2: y + @font_style.letter_size.y + 5)
    +                                                           y2: y + @font_style.letter_size.y + 4)
     
             # debugging rectangle for string
             # args.outputs.reserved << (@cursor_color.to_h.merge x: x,
    @@ -36892,7 +38610,7 @@ S
     end
     
     
    -

    controller.rb

    +

    controller.rb

    # ./dragon/controller.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37018,7 +38736,7 @@ end
     
     
     
    -

    controller/config.rb

    +

    controller/config.rb

    # ./dragon/controller/config.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37421,7 +39139,7 @@ module GTK
     end
     
     
    -

    controller/keys.rb

    +

    controller/keys.rb

    # ./dragon/controller/keys.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37439,7 +39157,7 @@ module GTK
             :l1, :r1,
             :l2, :r2,
             :l3, :r3,
    -        :start, :select,
    +        :start, :select, :home,
             :directional_up, :directional_down, :directional_left, :directional_right
           ].freeze
     
    @@ -37447,6 +39165,22 @@ module GTK
             attr label
           end
     
    +      def back
    +        @select
    +      end
    +
    +      def back= value
    +        @select = value
    +      end
    +
    +      def guide
    +        @home
    +      end
    +
    +      def guide= value
    +        @home = value
    +      end
    +
           # Activate a key.
           #
           # @return [void]
    @@ -37476,7 +39210,7 @@ module GTK
     end
     
     
    -

    directional_input_helper_methods.rb

    +

    directional_input_helper_methods.rb

    # ./dragon/directional_input_helper_methods.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37493,7 +39227,7 @@ module GTK
     
           error_message = <<-S
     * ERROR
    -The GTK::DirectionalKeys module should only be included in objects that respond to the following api heirarchy:
    +The GTK::DirectionalKeys module should only be included in objects that respond to the following api hierarchy:
     
     - (#{ directional_methods.join("|") })
     - key_held.(#{ directional_methods.join("|") })
    @@ -37550,6 +39284,12 @@ S
           end
         end
     
    +    def directional_angle
    +      return nil unless directional_vector
    +
    +      Math.atan2(up_down, left_right).to_degrees
    +    end
    +
         def method_missing m, *args
           # combine the key with ctrl_
           if m.to_s.start_with?("ctrl_")
    @@ -37572,7 +39312,7 @@ S
     end
     
     
    -

    easing.rb

    +

    easing.rb

    # ./dragon/easing.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37672,7 +39412,7 @@ end
     Easing = GTK::Easing
     
     
    -

    entity.rb

    +

    entity.rb

    # ./dragon/entity.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37767,7 +39507,7 @@ S
     end
     
     
    -

    geometry.rb

    +

    geometry.rb

    # ./dragon/geometry.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -37776,6 +39516,22 @@ end
     
     module GTK
       module Geometry
    +    def self.rotate_point point, angle, around = nil
    +      s = Math.sin a.to_radians
    +      c = Math.cos a.to_radians
    +      px = point.x
    +      py = point.y
    +      cx = 0
    +      cy = 0
    +      if around
    +        cx = around.x
    +        cy = around.y
    +      end
    +
    +      point.merge(x: ((px - cx) * c - (py - cy) * s) + cx,
    +                  y: ((px - cx) * s + (py - cy) * c) + cy)
    +    end
    +
         # Returns f(t) for a cubic Bezier curve.
         def self.cubic_bezier t, a, b, c, d
           s  = 1 - t
    @@ -37857,7 +39613,7 @@ module GTK
         rescue Exception => e
           raise e, <<-S
     * ERROR:
    -center_inside_rect for self #{self} and other_rect #{other_rect}. Failed with exception #{e}.
    +center_inside_rect for self #{self} and other_rect #{other_rect}.\n#{e}.
     S
         end
     
    @@ -37874,7 +39630,7 @@ S
         rescue Exception => e
           raise e, <<-S
     * ERROR:
    -center_inside_rect_x for self #{self} and other_rect #{other_rect}. Failed with exception #{e}.
    +center_inside_rect_x for self #{self} and other_rect #{other_rect}.\n#{e}.
     S
         end
     
    @@ -37891,7 +39647,7 @@ S
         rescue Exception => e
           raise e, <<-S
     * ERROR:
    -center_inside_rect_y for self #{self} and other_rect #{other_rect}. Failed with exception #{e}.
    +center_inside_rect_y for self #{self} and other_rect #{other_rect}.\n#{e}.
     S
         end
     
    @@ -37900,7 +39656,7 @@ S
         end
     
     
    -    # Returns a primitive that is anchored/repositioned based off its retangle.
    +    # Returns a primitive that is anchored/repositioned based off its rectangle.
         # @gtk
         def anchor_rect anchor_x, anchor_y
           current_w = self.w
    @@ -38036,10 +39792,10 @@ S
     
         # @gtk
         def self.intersect_rect? rect_one, rect_two, tolerance = 0.1
    -      return false if rect_one.right - tolerance < rect_two.left + tolerance
    -      return false if rect_one.left + tolerance > rect_two.right - tolerance
    -      return false if rect_one.top - tolerance < rect_two.bottom + tolerance
    -      return false if rect_one.bottom + tolerance > rect_two.top - tolerance
    +      return false if ((rect_one.x + rect_one.w) - tolerance) < (rect_two.x + tolerance)
    +      return false if (rect_one.x + tolerance) > ((rect_two.x + rect_two.w) - tolerance)
    +      return false if ((rect_one.y + rect_one.h) - tolerance) < (rect_two.y + tolerance)
    +      return false if (rect_one.y + tolerance) > ((rect_two.y + rect_two.h) - tolerance)
           return true
         rescue Exception => e
           context_help_rect_one = (rect_one.__help_contract_implementation contract_intersect_rect?)[:not_implemented_methods]
    @@ -38067,6 +39823,7 @@ S
     - rect_one: #{rect_one}
     - rect_two: #{rect_two}
     #{context_help}
    +\n#{e}
     S
         end
     
    @@ -38077,14 +39834,14 @@ S
           y = y.shift_down(size * anchor_y)
           [x, y, size, size]
         rescue Exception => e
    -      raise e, ":to_square failed for size: #{size} x: #{x} y: #{y} anchor_x: #{anchor_x} anchor_y: #{anchor_y}."
    +      raise e, ":to_square failed for size: #{size} x: #{x} y: #{y} anchor_x: #{anchor_x} anchor_y: #{anchor_y}.\n#{e}"
         end
     
         # @gtk
         def self.distance point_one, point_two
           Math.sqrt((point_two.x - point_one.x)**2 + (point_two.y - point_one.y)**2)
         rescue Exception => e
    -      raise e, ":distance failed for point_one: #{point_one} point_two #{point_two}."
    +      raise e, ":distance failed for point_one: #{point_one} point_two #{point_two}.\n#{e}"
         end
     
         # @gtk
    @@ -38093,31 +39850,34 @@ S
           d_x = end_point.x - start_point.x
           Math::PI.+(Math.atan2(d_y, d_x)).to_degrees
         rescue Exception => e
    -      raise e, ":angle_from failed for start_point: #{start_point} end_point: #{end_point}."
    +      raise e, ":angle_from failed for start_point: #{start_point} end_point: #{end_point}.\n#{e}"
         end
     
         # @gtk
         def self.angle_to start_point, end_point
           angle_from end_point, start_point
         rescue Exception => e
    -      raise e, ":angle_to failed for start_point: #{start_point} end_point: #{end_point}."
    +      raise e, ":angle_to failed for start_point: #{start_point} end_point: #{end_point}.\n#{e}"
         end
     
         # @gtk
         def self.point_inside_circle? point, circle_center_point, radius
           (point.x - circle_center_point.x) ** 2 + (point.y - circle_center_point.y) ** 2 < radius ** 2
         rescue Exception => e
    -      raise e, ":point_inside_circle? failed for point: #{point} circle_center_point: #{circle_center_point} radius: #{radius}"
    +      raise e, ":point_inside_circle? failed for point: #{point} circle_center_point: #{circle_center_point} radius: #{radius}.\n#{e}"
         end
     
         # @gtk
         def self.inside_rect? inner_rect, outer_rect, tolerance = 0.0
    +      return nil if !inner_rect
    +      return nil if !outer_rect
    +
           inner_rect.x     + tolerance >= outer_rect.x     - tolerance &&
    -      inner_rect.right - tolerance <= outer_rect.right + tolerance &&
    +      (inner_rect.x + inner_rect.w) - tolerance <= (outer_rect.x + outer_rect.w) + tolerance &&
           inner_rect.y     + tolerance >= outer_rect.y     - tolerance &&
    -      inner_rect.top   - tolerance <= outer_rect.top   + tolerance
    +      (inner_rect.y + inner_rect.h) - tolerance <= (outer_rect.y + outer_rect.h) + tolerance
         rescue Exception => e
    -      raise e, ":inside_rect? failed for inner_rect: #{inner_rect} outer_rect: #{outer_rect}."
    +      raise e, ":inside_rect? failed for inner_rect: #{inner_rect} outer_rect: #{outer_rect}.\n#{e}"
         end
     
         # @gtk
    @@ -38152,7 +39912,7 @@ S
             return rect
           end
         rescue Exception => e
    -      raise e, ":scale_rect_extended failed for rect: #{rect} percentage_x: #{percentage_x} percentage_y: #{percentage_y} anchors_x: #{anchor_x} anchor_y: #{anchor_y}."
    +      raise e, ":scale_rect_extended failed for rect: #{rect} percentage_x: #{percentage_x} percentage_y: #{percentage_y} anchors_x: #{anchor_x} anchor_y: #{anchor_y}.\n#{e}"
         end
     
         # @gtk
    @@ -38166,13 +39926,27 @@ S
                                        anchor_x: anchor_x,
                                        anchor_y: anchor_y
         rescue Exception => e
    -      raise e, ":scale_rect failed for rect: #{rect} percentage: #{percentage} anchors [#{anchor_x} (x), #{anchor_y} (y)]."
    +      raise e, ":scale_rect failed for rect: #{rect} percentage: #{percentage} anchors [#{anchor_x} (x), #{anchor_y} (y)].\n#{e}"
    +    end
    +
    +    def self.rect_to_line rect
    +      l = rect.to_hash.line
    +      l.merge(x2: l.x + l.w - 1,
    +              y2: l.y + l.h)
    +    end
    +
    +    def self.rect_center_point rect
    +      { x: rect.x + rect.w.half, y: rect.y + rect.h.half }
    +    end
    +
    +    def rect_center_point
    +      Geometry.rect_center_point self
         end
       end # module Geometry
     end # module GTK
     
     
    -

    grid.rb

    +

    grid.rb

    # ./dragon/grid.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -38361,11 +40135,19 @@ module GTK
         def bottom_right
           [@right, @bottom].point
         end
    +
    +    def x
    +      0
    +    end
    +
    +    def y
    +      0
    +    end
       end
     end
     
     
    -

    inputs.rb

    +

    inputs.rb

    # ./dragon/inputs.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -38560,6 +40342,30 @@ module GTK
           }
         end
     
    +    def self.method_to_key_hash
    +      return @method_to_key_hash if @method_to_key_hash
    +      @method_to_key_hash = {}
    +      string_representation_overrides ||= {
    +        backspace: '\b'
    +      }
    +      char_to_method_hash.each do |k, v|
    +        v.each do |vi|
    +          t = { char_or_raw_key: k }
    +
    +          if k.is_a? Numeric
    +            t[:raw_key] = k
    +            t[:string_representation] = "raw_key == #{k}"
    +          else
    +            t[:char] = k
    +            t[:string_representation] = "\"#{k.strip}\""
    +          end
    +
    +          @method_to_key_hash[vi] = t
    +        end
    +      end
    +      @method_to_key_hash
    +    end
    +
         def self.char_to_method char, int = nil
           methods = char_to_method_hash[char] || char_to_method_hash[int]
           methods ? methods.dup : [char.to_sym || int]
    @@ -38674,24 +40480,32 @@ S
         end
     
         def method_missing m, *args
    -      begin
    -        define_singleton_method(m) do
    -          r = self.instance_variable_get("@#{m.without_ending_bang}".to_sym)
    -          clear_key m
    -          return r
    -        end
    +      if KeyboardKeys.method_to_key_hash[m.without_ending_bang]
    +        begin
    +          define_singleton_method(m) do
    +            r = self.instance_variable_get("@#{m.without_ending_bang}".to_sym)
    +            clear_key m
    +            return r
    +          end
     
    -        return self.send m
    -      rescue Exception => e
    -        log_important "#{e}"
    +          return self.send m
    +        rescue Exception => e
    +          log_important "#{e}"
    +        end
           end
     
    +      did_you_mean = KeyboardKeys.method_to_key_hash.find_all do |k, v|
    +        k.to_s[0..1] == m.to_s[0..1]
    +      end.map {|k, v| ":#{k} (#{v[:string_representation]})" }
    +      did_you_mean_string = ""
    +      did_you_mean_string = ". Did you mean #{did_you_mean.join ", "}?"
    +
           raise <<-S
     * ERROR:
    -There is no member on the keyboard called #{m}. Here is a to_s representation of what's available:
    -
    -#{KeyboardKeys.char_to_method_hash.map { |k, v| "[#{k} => #{v.join(",")}]" }.join("  ")}
    +#{KeyboardKeys.method_to_key_hash.map { |k, v| "** :#{k} #{v.string_representation}" }.join("\n")}
     
    +There is no key on the keyboard called :#{m}#{did_you_mean_string}.
    +Full list of available keys =:points_up:=.
     S
         end
     
    @@ -39075,6 +40889,10 @@ module GTK
             (controller_one && controller_one.directional_vector)
         end
     
    +    def directional_angle
    +      keyboard.directional_angle || (controller_one && controller_one.directional_angle)
    +    end
    +
         # Returns a signal indicating right (`1`), left (`-1`), or neither ('0').
         #
         # @return [Integer]
    @@ -39142,21 +40960,14 @@ module GTK
     end
     
     
    -

    ios_wizard.rb

    +

    ios_wizard.rb

    # ./dragon/ios_wizard.rb
    +# Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # ios_wizard.rb has been released under MIT (*only this file*).
     
    -class WizardException < Exception
    -  attr_accessor :console_primitives
    -
    -  def initialize *console_primitives
    -    @console_primitives = console_primitives
    -  end
    -end
    -
    -class IOSWizard
    +class IOSWizard < Wizard
       def initialize
         @doctor_executed_at = 0
       end
    @@ -39169,23 +40980,45 @@ class IOSWizard
         @steps ||= []
       end
     
    -  def steps_development_build
    +  def prerequisite_steps
         [
           :check_for_xcode,
           :check_for_brew,
           :check_for_certs,
    -      :check_for_device,
    -      :check_for_dev_profile,
    +    ]
    +  end
    +
    +  def app_metadata_retrieval_steps
    +    [
           :determine_team_identifier,
           :determine_app_name,
           :determine_app_id,
    -      :blow_away_temp,
    +    ]
    +  end
    +
    +  def steps_development_build
    +    [
    +      *prerequisite_steps,
    +
    +      :check_for_device,
    +      :check_for_dev_profile,
    +
    +      *app_metadata_retrieval_steps,
    +
    +      :clear_tmp_directory,
           :stage_app,
    +
           :development_write_info_plist,
    +
           :write_entitlements_plist,
           :compile_icons,
    -      :create_payload_directory,
    +      :clear_payload_directory,
    +
    +      :create_payload_directory_dev,
    +
    +      :create_payload,
           :code_sign_payload,
    +
           :create_ipa,
           :deploy
         ]
    @@ -39193,20 +41026,27 @@ class IOSWizard
     
       def steps_production_build
         [
    -      :check_for_xcode,
    -      :check_for_brew,
    -      :check_for_certs,
    +      *prerequisite_steps,
    +
           :check_for_distribution_profile,
    -      :determine_team_identifier,
    -      :determine_app_name,
    -      :determine_app_id,
    -      :blow_away_temp,
    +      :determine_app_version,
    +
    +      *app_metadata_retrieval_steps,
    +
    +      :clear_tmp_directory,
           :stage_app,
    +
           :production_write_info_plist,
    +
           :write_entitlements_plist,
           :compile_icons,
    -      :create_payload_directory,
    +      :clear_payload_directory,
    +
    +      :create_payload_directory_prod,
    +
    +      :create_payload,
           :code_sign_payload,
    +
           :create_ipa,
           :print_publish_help
         ]
    @@ -39242,6 +41082,8 @@ class IOSWizard
         @steps = steps_development_build
         @steps = steps_production_build if @production_build
         @certificate_name = nil
    +    @app_version = opts[:version]
    +    @app_version = "1.0" if @opts[:env] == :dev && !@app_version
         init_wizard_status
         log_info "Starting iOS Wizard so we can deploy to your device."
         @start_at = Kernel.global_tick_count
    @@ -39263,8 +41105,10 @@ class IOSWizard
           log "=" * $console.console_text_width
         else
           log_error e.to_s
    +      log e.__backtrace_to_org__
         end
     
    +    init_wizard_status
         $console.set_command "$wizards.ios.start env: :#{@opts[:env]}"
       end
     
    @@ -39371,13 +41215,66 @@ class IOSWizard
         return "profiles/development.mobileprovision"
       end
     
    +  def ios_metadata_template
    +    <<-S
    +# ios_metadata.txt is used by the Pro version of DragonRuby Game Toolkit to create iOS apps.
    +# Information about the Pro version can be found at: http://dragonruby.org/toolkit/game#purchase
    +
    +# teamid needs to be set to your assigned Team Id which can be found at https://developer.apple.com/account/#/membership/
    +teamid=
    +# appid needs to be set to your application identifier which can be found at https://developer.apple.com/account/resources/identifiers/list
    +appid=
    +# appname is the name you want to show up underneath the app icon on the device. Keep it under 10 characters.
    +appname=
    +S
    +  end
    +
    +  def ios_metadata
    +    contents = $gtk.read_file 'metadata/ios_metadata.txt'
    +
    +    if !contents
    +      $gtk.write_file 'metadata/ios_metadata.txt', ios_metadata_template
    +      contents = $gtk.read_file 'metadata/ios_metadata.txt'
    +    end
    +
    +    kvps = contents.each_line
    +                   .reject { |l| l.strip.length == 0 || (l.strip.start_with? "#") }
    +                   .map do |l|
    +                     key, value = l.split("=")
    +                     [key.strip.to_sym, value.strip]
    +                   end.flatten
    +    Hash[*kvps]
    +  end
    +
    +  def game_metadata
    +    contents = $gtk.read_file 'metadata/game_metadata.txt'
    +
    +    kvps = contents.each_line
    +                   .reject { |l| l.strip.length == 0 || (l.strip.start_with? "#") }
    +                   .map do |l|
    +                     key, value = l.split("=")
    +                     [key.strip.to_sym, value.strip]
    +                   end.flatten
    +    Hash[*kvps]
    +  end
    +
    +  def raise_ios_metadata_required
    +    raise WizardException.new(
    +            "* mygame/metadata/ios_metadata.txt needs to be filled out.",
    +            "You need to update metadata/ios_metadata.txt with a valid teamid, appname, and appid.",
    +            "Instructions for where the values should come from are within metadata/ios_metadata.txt."
    +          )
    +  end
    +
       def determine_team_identifier
    -    @team_name = (team_identifier_from_provisioning_profile @opts[:env])
    -    log_info "Team Identifer is: #{@team_name}"
    +    @team_id = (ios_metadata.teamid || "")
    +    raise_ios_metadata_required if @team_id.strip.length == 0
    +    log_info "Team Identifer is: #{@team_id}"
       end
     
       def determine_app_name
    -    @app_name = (provisioning_profile_xml @opts[:env])[:children].first[:children].first[:children][1][:children].first[:data]
    +    @app_name = (ios_metadata.appname || "")
    +    raise_ios_metadata_required if @app_name.strip.length == 0
         log_info "App name is: #{@app_name}."
       end
     
    @@ -39401,36 +41298,9 @@ class IOSWizard
         $gtk.parse_xml scrubbed
       end
     
    -  def app_id_from_provisioning_profile environment
    -    application_identifier_index = (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children][0][:children][0][:data]
    -    (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children].each.with_index do |node, i|
    -      if node[:children] && node[:children][0] && node[:children][0][:data] == "application-identifier"
    -        application_identifier_index = i
    -        break
    -      end
    -    end
    -
    -    app_id_with_team_identifier = (provisioning_profile_xml environment)[:children].first[:children].first[:children][13][:children][application_identifier_index + 1][:children].first[:data]
    -    team_identifer = team_identifier_from_provisioning_profile environment
    -    app_id_with_team_identifier.gsub "#{team_identifer}.", ""
    -  end
    -
    -  def team_identifier_from_provisioning_profile environment
    -    team_identifer_index = (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children][0][:children][0][:data]
    -
    -    (provisioning_profile_xml environment)[:children][0][:children][0][:children][13][:children].each.with_index do |node, i|
    -      if node[:children] && node[:children][0] && node[:children][0][:data] == "com.apple.developer.team-identifier"
    -        team_identifer_index = i
    -        break
    -      end
    -    end
    -
    -    (provisioning_profile_xml environment)[:children].first[:children].first[:children][13][:children][team_identifer_index + 1][:children].first[:data]
    -  end
    -
       def determine_app_id
    -    @app_id = app_id_from_provisioning_profile @opts[:env]
    -
    +    @app_id = ios_metadata.appid
    +    raise_ios_metadata_required if @app_id.strip.length == 0
         log_info "App Identifier is set to : #{@app_id}"
       end
     
    @@ -39449,7 +41319,7 @@ class IOSWizard
         end
       end
     
    -  def blow_away_temp
    +  def clear_tmp_directory
         sh "rm -rf #{tmp_directory}"
       end
     
    @@ -39590,7 +41460,8 @@ XML
     
         log_info "Creating Entitlements.plist"
     
    -    $gtk.write_file_root "tmp/ios/Entitlements.plist", entitlement_plist_string.gsub(":app_id", "#{@team_name}.#{@app_id}").strip
    +    $gtk.write_file_root "tmp/ios/Entitlements.plist", entitlement_plist_string.gsub(":app_id", "#{@team_id}.#{@app_id}").strip
    +    $gtk.write_file_root "tmp/ios/Entitlements.txt", entitlement_plist_string.gsub(":app_id", "#{@team_id}.#{@app_id}").strip
     
         sh "/usr/bin/plutil -convert binary1 \"#{tmp_directory}/Entitlements.plist\""
         sh "/usr/bin/plutil -convert xml1 \"#{tmp_directory}/Entitlements.plist\""
    @@ -39628,15 +41499,15 @@ XML
             CFBundleExecutable
             :app_name
             CFBundleInfoDictionaryVersion
    -        6.0
    +        :app_version
             CFBundlePackageType
             APPL
             CFBundleShortVersionString
    -        5.6
    +        :app_version
             CFBundleSignature
             ????
             CFBundleVersion
    -        5.6
    +        :app_version
             CFBundleIcons
             
                 CFBundlePrimaryIcon
    @@ -39785,13 +41656,13 @@ XML
             CFBundleIdentifier
             :app_id
             CFBundleInfoDictionaryVersion
    -        6.0
    +        :app_version
             CFBundleName
             :app_name
             CFBundlePackageType
             APPL
             CFBundleShortVersionString
    -        5.2
    +        :app_version
             CFBundleSignature
             ????
             CFBundleSupportedPlatforms
    @@ -39799,7 +41670,7 @@ XML
                     iPhoneOS
             
             CFBundleVersion
    -        5.2
    +        :app_version
             DTCompiler
             com.apple.compilers.llvm.clang.1_0
             DTPlatformBuild
    @@ -39885,6 +41756,7 @@ XML
         info_plist_string.gsub!(":app_id", @app_id)
     
         $gtk.write_file_root "tmp/ios/#{@app_name}.app/Info.plist", info_plist_string.strip
    +    $gtk.write_file_root "tmp/ios/Info.txt", info_plist_string.strip
     
         @info_plist_written = true
       end
    @@ -39938,13 +41810,13 @@ XML
             CFBundleIdentifier
             :app_id
             CFBundleInfoDictionaryVersion
    -        6.0
    +        :app_version
             CFBundleName
             :app_name
             CFBundlePackageType
             APPL
             CFBundleShortVersionString
    -        5.2
    +        :app_version
             CFBundleSignature
             ????
             CFBundleSupportedPlatforms
    @@ -39952,7 +41824,7 @@ XML
                     iPhoneOS
             
             CFBundleVersion
    -        5.2
    +        :app_version
             DTCompiler
             com.apple.compilers.llvm.clang.1_0
             DTPlatformBuild
    @@ -40036,8 +41908,10 @@ XML
     
         info_plist_string.gsub!(":app_name", @app_name)
         info_plist_string.gsub!(":app_id", @app_id)
    +    info_plist_string.gsub!(":app_version", @app_version)
     
         $gtk.write_file_root "tmp/ios/#{@app_name}.app/Info.plist", info_plist_string.strip
    +    $gtk.write_file_root "tmp/ios/Info.txt", info_plist_string.strip
     
         @info_plist_written = true
       end
    @@ -40059,28 +41933,53 @@ XML
         "#{relative_path}/#{$gtk.cli_arguments[:dragonruby]}"
       end
     
    -  def write_ip_address
    +  def embed_mobileprovision
    +    sh %Q[cp #{@provisioning_profile_path} "#{app_path}/embedded.mobileprovision"]
    +    sh %Q[/usr/bin/plutil -convert binary1 "#{app_path}/Info.plist"]
    +  end
    +
    +  def clear_payload_directory
    +    sh %Q[rm "#{@app_name}".ipa]
    +    sh %Q[rm -rf "#{app_path}/app"]
    +    sh %Q[rm -rf "#{app_path}/sounds"]
    +    sh %Q[rm -rf "#{app_path}/sprites"]
    +    sh %Q[rm -rf "#{app_path}/data"]
    +    sh %Q[rm -rf "#{app_path}/fonts"]
    +  end
    +
    +  def stage_app
    +    sh %Q[cp -r "#{root_folder}/app/" "#{app_path}/app/"]
    +    sh %Q[cp -r "#{root_folder}/sounds/" "#{app_path}/sounds/"]
    +    sh %Q[cp -r "#{root_folder}/sprites/" "#{app_path}/sprites/"]
    +    sh %Q[cp -r "#{root_folder}/data/" "#{app_path}/data/"]
    +    sh %Q[cp -r "#{root_folder}/fonts/" "#{app_path}/fonts/"]
    +  end
    +
    +  def create_payload
    +    sh %Q[mkdir -p #{tmp_directory}/ipa_root/Payload]
    +    sh %Q[cp -r "#{app_path}" "#{tmp_directory}/ipa_root/Payload"]
    +    sh %Q[chmod -R 755 "#{tmp_directory}/ipa_root/Payload"]
    +  end
    +
    +  def create_payload_directory_dev
    +    # write dev machine's ip address for hotloading
         $gtk.write_file "app/server_ip_address.txt", $gtk.ffi_misc.get_local_ip_address.strip
    +
    +    embed_mobileprovision
    +    clear_payload_directory
    +    stage_app
       end
     
    -  def create_payload_directory
    -    sh "cp #{@provisioning_profile_path} \"#{app_path}/embedded.mobileprovision\""
    -    sh "/usr/bin/plutil -convert binary1 \"#{app_path}/Info.plist\""
    -    write_ip_address
    -    sh "rm \"#{@app_name}\".ipa"
    -    sh "rm -rf \"#{app_path}/app\""
    -    sh "rm -rf \"#{app_path}/sounds\""
    -    sh "rm -rf \"#{app_path}/sprites\""
    -    sh "rm -rf \"#{app_path}/data\""
    -    sh "rm -rf \"#{app_path}/fonts\""
    -    sh "cp -r \"#{root_folder}/app/\" \"#{app_path}/app/\""
    -    sh "cp -r \"#{root_folder}/sounds/\" \"#{app_path}/sounds/\""
    -    sh "cp -r \"#{root_folder}/sprites/\" \"#{app_path}/sprites/\""
    -    sh "cp -r \"#{root_folder}/data/\" \"#{app_path}/data/\""
    -    sh "cp -r \"#{root_folder}/fonts/\" \"#{app_path}/fonts/\""
    -    sh "mkdir -p #{tmp_directory}/ipa_root/Payload"
    -    sh "cp -r \"#{app_path}\" \"#{tmp_directory}/ipa_root/Payload\""
    -    sh "chmod -R 755 \"#{tmp_directory}/ipa_root/Payload\""
    +  def create_payload_directory_prod
    +    # production builds does not hotload ip address
    +    sh %Q[rm "#{root_folder}/app/server_ip_address.txt"]
    +
    +    embed_mobileprovision
    +    stage_app
    +
    +    # production build marker
    +    sh %Q[mkdir -p "#{app_path}/metadata/"]
    +    sh %Q[touch metadata/DRAGONRUBY_PRODUCTION_BUILD]
       end
     
       def create_ipa
    @@ -40113,10 +42012,22 @@ SCRIPT
       end
     
       def print_publish_help
    -      log_info "Go to https://appstoreconnect.apple.com/apps and create an App if you haven't already done so."
    -      log_info "Go to https://appleid.apple.com and create a 'Application Specific Password'."
    -      log_info "To upload your app, Download Transporter from the App Store https://apps.apple.com/us/app/transporter/id1450874784?mt=12."
    -      log_info "Your app is located at ./tmp/ios/#{@app_name}.ipa"
    +    has_transporter = (sh "ls /Applications/Transporter.app").include? "Contents"
    +    if !has_transporter
    +      $gtk.openurl "https://apps.apple.com/us/app/transporter/id1450874784?mt=12"
    +      $console.set_command "$wizards.ios.start env: :#{@opts[:env]}, version: \"#{@opts[:version]}\""
    +      raise WizardException.new(
    +        "* To upload your app, Download Transporter from the App Store https://apps.apple.com/us/app/transporter/id1450874784?mt=12."
    +      )
    +    else
    +      sh "mkdir ./tmp/ios/intermediary_artifacts"
    +      sh "mv \"#{tmp_directory}/#{@app_name}.app\" #{tmp_directory}/intermediary_artifacts/"
    +      sh "mv \"#{tmp_directory}/do_zip.sh\" #{tmp_directory}/intermediary_artifacts"
    +      sh "mv \"#{tmp_directory}/Entitlements.plist\" #{tmp_directory}/intermediary_artifacts"
    +      sh "mv \"#{tmp_directory}/ipa_root\" #{tmp_directory}/intermediary_artifacts/"
    +      sh "open /Applications/Transporter.app"
    +      sh "open ./tmp/ios/"
    +    end
       end
     
       def compile_icons
    @@ -40137,47 +42048,43 @@ S
         sh "cp -r \"#{root_folder}/native/\" \"#{app_path}/native/\""
         sh "CODESIGN_ALLOCATE=\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\" /usr/bin/codesign -f -s \"#{@certificate_name}\" --entitlements #{tmp_directory}/Entitlements.plist \"#{tmp_directory}/#{@app_name}.app/native/ios-device/ext.dylib\""
       end
    +
    +  def set_version version
    +    @app_version = version
    +    start env: @opts[:env], version: version
    +  end
    +
    +  def app_version
    +    log_info "Attempting to retrieve App Version from metadata/ios_metadata.txt."
    +    ios_version_number = (ios_metadata.version || "").strip
    +    if ios_version_number.length == 0
    +      log_info "Not found. Attempting to retrieve App Version from metadata/game_metadata.txt."
    +      ios_version_number = (game_metadata.version || "").strip
    +    end
    +    ios_version_number
    +  end
    +
    +  def determine_app_version
    +    @app_version = app_version
    +    return if @app_version
    +  end
     end
     
     
    -

    itch_wizard.rb

    +

    itch_wizard.rb

    # ./dragon/itch_wizard.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # itch_wizard.rb has been released under MIT (*only this file*).
     
    -class ItchWizard
    +class ItchWizard < Wizard
       def steps
         [
           :check_metadata,
    -      :deploy
    +      :deploy,
         ]
       end
     
    -  def metadata_file_path
    -    "metadata/game_metadata.txt"
    -  end
    -
    -  def get_metadata
    -    metadata = $gtk.read_file metadata_file_path
    -
    -    if !metadata
    -      write_blank_metadata
    -      metadata = $gtk.read_file metadata_file_path
    -    end
    -
    -    dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a
    -
    -    {
    -      dev_id: dev_id.strip,
    -      dev_title: dev_title.strip,
    -      game_id: game_id.strip,
    -      game_title: game_title.strip,
    -      version: version.strip,
    -      icon: icon.strip
    -    }
    -  end
    -
       def write_blank_metadata
           $gtk.write_file metadata_file_path, <<-S.strip
     #devid=myname
    @@ -40195,7 +42102,7 @@ S
           write_blank_metadata
         end
     
    -    if metadata_text.each_line.to_a.length != 6
    +    if metadata_text.strip.each_line.to_a.length < 6
           write_blank_metadata
         end
     
    @@ -40207,70 +42114,67 @@ S
     
         if metadata[:dev_id].start_with?("#") || !@dev_id
           log "* PROMPT: Please provide your username for Itch."
    -      $console.set_command "$wizards.itch.set_dev_id \"your-itch-username\""
    +      $console.set_command "$wizards.itch.set_dev_id \"#{metadata[:dev_id]}\""
           return :need_dev_id
         end
     
         if metadata[:dev_title].start_with?("#") || !@dev_title
           log "* PROMPT: Please provide developer's/company's name that you want displayed."
    -      $console.set_command "$wizards.itch.set_dev_title \"Your Name\""
    +      $console.set_command "$wizards.itch.set_dev_title \"#{metadata[:dev_title]}\""
           return :need_dev_title
         end
     
         if metadata[:game_id].start_with?("#") || !@game_id
           log "* PROMPT: Please provide the id for you game. This is the id you specified when you set up a new game page on Itch."
    -      $console.set_command "$wizards.itch.set_game_id \"your-game-id\""
    +      $console.set_command "$wizards.itch.set_game_id \"#{metadata[:game_id]}\""
           return :need_game_id
         end
     
         if metadata[:game_title].start_with?("#") || !@game_title
           log "* PROMPT: Please provide the display name for your game. (This can include spaces)"
    -      $console.set_command "$wizards.itch.set_game_title \"Your Game\""
    +      $console.set_command "$wizards.itch.set_game_title \"#{metadata[:game_title]}\""
           return :need_game_title
         end
     
         if metadata[:version].start_with?("#") || !@version
           log "* PROMPT: Please provide the version for your game."
    -      $console.set_command "$wizards.itch.set_version \"1.0\""
    +      $console.set_command "$wizards.itch.set_version \"#{metadata[:version]}\""
           return :need_version
         end
     
         if metadata[:icon].start_with?("#") || !@icon
           log "* PROMPT: Please provide icon path for your game."
    -      $console.set_command "$wizards.itch.set_icon \"icon.png\""
    +      $console.set_command "$wizards.itch.set_icon \"#{metadata[:icon]}\""
           return :need_icon
         end
     
    +    puts "here!! success!!!"
    +
         return :success
       end
     
       def set_dev_id value
         @dev_id = value
    -    write_metadata
         start
       end
     
       def set_dev_title value
         @dev_title = value
    -    write_metadata
         start
       end
     
       def set_game_id value
         @game_id = value
    -    write_metadata
         start
       end
     
       def set_game_title value
         @game_title = value
    -    write_metadata
         start
       end
     
       def set_version value
         @version = value
    -    write_metadata
         start
       end
     
    @@ -40313,7 +42217,7 @@ S
         end
     
         if @icon
    -      text += "icon=metadata/#{@icon}\n"
    +      text += "icon=#{@icon}\n"
         else
           text += "#icon=metadata/icon.png\n"
         end
    @@ -40331,10 +42235,25 @@ S
     
       def deploy
         log_info "* Running dragonruby-publish: #{package_command}"
    -    results = $gtk.exec package_command
    +    $gtk.openurl "http://itch.io/dashboard" if $gtk.platform == "Mac OS X"
    +    if $gtk.platform? :mac
    +      $gtk.exec "rm -rf ./builds"
    +    end
    +    results = $gtk.exec "#{package_command} --only-package"
    +    puts File.expand_path("./builds")
    +
         log "#+begin_src"
         log results
         log "#+end_src"
    +
    +    if $gtk.platform? :mac
    +      $gtk.exec "open ./builds/"
    +    elsif $gtk.platform? :windows
    +      $gtk.exec "powershell \"ii .\""
    +    end
    +
    +    $gtk.openurl "https://itch.io/dashboard"
    +
         :success
       end
     
    @@ -40345,7 +42264,7 @@ S
         steps.each do |m|
           begin
             log_info "Running Itch Wizard Step: ~$wizards.itch.#{m}~"
    -        result = (send m) || :success if @wizard_status[m][:result] != :success
    +        result = (send m) || :success
             @wizard_status[m][:result] = result
             if result != :success
               log_info "Exiting wizard. :#{result}"
    @@ -40412,7 +42331,7 @@ S
     end
     
     
    -

    layout.rb

    +

    layout.rb

    # ./dragon/layout.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -40692,10 +42611,18 @@ module GTK
           device.grid_area.row_count
         end
     
    +    def row_max_index
    +      row_count - 1
    +    end
    +
         def col_count
           device.grid_area.col_count
         end
     
    +    def col_max_index
    +      col_count - 1
    +    end
    +
         def gutter_height
           device.grid_area.gutter
         end
    @@ -40718,18 +42645,124 @@ module GTK
     
         def rect_defaults
           {
    -        row: nil,
    -        col: nil,
    -        h:   1,
    -        w:   1,
    -        dx:  0,
    -        dy:  0,
    -        rect: :control_rect
    +        row:      nil,
    +        col:      nil,
    +        h:        1,
    +        w:        1,
    +        dx:       0,
    +        dx_ratio: 1,
    +        dy:       0,
    +        dy_ratio: 1,
    +        dh_ratio: 1,
    +        dw_ratio: 1,
    +        merge:    nil,
    +        rect:     :control_rect
           }
         end
     
    -    def rect opts
    +    def row n
    +      (rect row: n, col: 0, w: 0, h: 0).x
    +    end
    +
    +    def row_from_bottom n
    +      (rect row: row_count - n, col: 0, w: 0, h: 0).x
    +    end
    +
    +    def col n
    +      (rect row: 0, col: n, w: 0, h: 0).y
    +    end
    +
    +    def col_from_right n
    +      (rect row: 0, col: col_max_index - n, w: 0, h: 0).y
    +    end
    +
    +    def w n
    +      (rect row: 0, col: 0, w: n, h: 1).w
    +    end
    +
    +    def h n
    +      (rect row: 0, col: 0, w: 1, h: n).h
    +    end
    +
    +    def rect_group opts
    +      group = opts.group
    +      r     = opts.row || 0
    +      r     = row_max_index - opts.row_from_bottom if opts.row_from_bottom
    +      c     = opts.col || 0
    +      c     = col_max_index - opts.col_from_right  if opts.col_from_right
    +      drow  = opts.drow || 0
    +      dcol  = opts.dcol || 0
    +      w     = opts.w    || 0
    +      h     = opts.h    || 0
    +      merge = opts[:merge]
    +
    +      running_row = r
    +      running_col = c
    +
    +      running_col = calc_col_offset(opts.col_offset) if opts.col_offset
    +      running_row = calc_row_offset(opts.row_offset) if opts.row_offset
    +
    +      group.map do |i|
    +        group_layout_opts = i.layout || {}
    +        group_layout_opts = group_layout_opts.merge row: running_row,
    +                                                    col: running_col,
    +                                                    merge: merge,
    +                                                    w: w, h: h
    +        result = (rect group_layout_opts).merge i
    +
    +        if (i.is_a? Hash) && (i.primitive_marker == :label)
    +          if    i.alignment_enum == 1
    +            result.x += result.w.half
    +          elsif i.alignment_enum == 2
    +            result.x += result.w
    +          end
    +        end
    +
    +        running_row += drow
    +        running_col += dcol
    +        result
    +      end
    +    end
    +
    +    def calc_row_offset opts = {}
    +      count = opts[:count] || opts[:length] || 0
    +      h     = opts.h || 1
    +      (row_count - (count * h)) / 2.0
    +    end
    +
    +    def calc_col_offset opts = {}
    +      count = opts[:count] || opts[:length] || 0
    +      w     = opts.w || 1
    +      (col_count - (count * w)) / 2.0
    +    end
    +
    +    def point opts = {}
    +      opts.w = 1
    +      opts.h = 1
    +      opts.row ||= 0
    +      opts.col ||= 0
    +      r = rect opts
    +      r.x  += r.w * opts.col_anchor if opts.col_anchor
    +      r.y  += r.h * opts.row_anchor if opts.row_anchor
    +      r
    +    end
    +
    +    def rect *all_opts
    +      if all_opts.length == 1
    +        opts = all_opts.first
    +      else
    +        opts = {}
    +        all_opts.each do |o|
    +          opts.merge! o
    +        end
    +      end
    +
    +      opts.row = row_max_index - opts.row_from_bottom if opts.row_from_bottom
    +      opts.col = col_max_index - opts.col_from_right if opts.col_from_right
           opts = rect_defaults.merge opts
    +      opts.row ||= 0
    +      opts.col ||= 0
    +
           result = send opts[:rect]
           if opts[:row] && opts[:col] && opts[:w] && opts[:h]
             col = rect_col opts[:col], opts[:w]
    @@ -40737,7 +42770,9 @@ module GTK
             result = control_rect.merge x: col.x,
                                         y: row.y,
                                         w: col.w,
    -                                    h: row.h
    +                                    h: row.h,
    +                                    center_x: col.center_x,
    +                                    center_y: row.center_y
           elsif opts[:row] && !opts[:col]
             result = rect_row opts[:row], opts[:h]
           elsif !opts[:row] && opts[:col]
    @@ -40775,13 +42810,21 @@ module GTK
             result[:h] += device.grid_area.gutter * 2
           end
     
    -      result[:x] += opts[:dx] if opts[:dx]
    -      result[:y] += opts[:dy] if opts[:dy]
    -      result[:w] += opts[:dw] if opts[:dw]
    -      result[:h] += opts[:dh] if opts[:dh]
    +      result[:x] += opts[:dx]       if opts[:dx]
    +      result[:x] *= opts[:dx_ratio] if opts[:dx_ratio]
    +      result[:y] += opts[:dy]       if opts[:dy]
    +      result[:y] *= opts[:dy_ratio] if opts[:dy_ratio]
    +      result[:w] += opts[:dw]       if opts[:dw]
    +      result[:w] *= opts[:dw_ratio] if opts[:dw_ratio]
    +      result[:h] += opts[:dh]       if opts[:dh]
    +      result[:h] *= opts[:dh_ratio] if opts[:dh_ratio]
    +      result.merge! opts[:merge]    if opts[:merge]
           result[:row] = opts[:row]
           result[:col] = opts[:col]
     
    +      result[:h] = result[:h].clamp 0
    +      result[:w] = result[:w].clamp 0
    +
           if $gtk.args.grid.name == :center
             result[:x] -= 640
             result[:y] -= 360
    @@ -40816,7 +42859,7 @@ module GTK
     
           row_y = device.h - row_y - row_h
     
    -      result = control_rect.merge y: row_y, h: row_h
    +      result = control_rect.merge y: row_y, h: row_h, center_y: (row_y + row_h.half)
           @rect_cache[:row][index][h] = result
           @rect_cache[:row][index][h]
         end
    @@ -40839,7 +42882,7 @@ module GTK
           col_w = col_w.to_i
           col_w -= 1 if col_w.odd?
     
    -      result = control_rect.merge x: col_x, w: col_w
    +      result = control_rect.merge x: col_x, w: col_w, center_x: (col_x + col_w.half)
           @rect_cache[:col][index][w] = result
           @rect_cache[:col][index][w]
         end
    @@ -40890,6 +42933,26 @@ module GTK
           @device
         end
     
    +    def debug_primitives opts = {}
    +      @primitives ||= col_count.map_with_index do |col|
    +                        row_count.map_with_index do |row|
    +                          cell   = rect row: row, col: col
    +                          center = Geometry.rect_center_point cell
    +                          [
    +                            cell.merge(opts).border,
    +                            cell.merge(opts)
    +                                .label!(x: center.x,
    +                                        y: center.y,
    +                                        text: "#{row},#{col}",
    +                                        size_enum: -3,
    +                                        vertical_alignment_enum: 1,
    +                                        alignment_enum: 1)
    +                          ]
    +                        end
    +                      end
    +                      @primitives
    +    end
    +
         def serialize
           {
             device: @device.serialize,
    @@ -40903,11 +42966,18 @@ module GTK
         def to_s
           serialize.to_s
         end
    +
    +    def reset
    +      @primitives = nil
    +      @rect_cache ||= {}
    +      @rect_cache.clear
    +    end
    +
       end
     end
     
     
    -

    log.rb

    +

    log.rb

    # ./dragon/log.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -41040,6 +43110,11 @@ module GTK
           self.puts message
         end
     
    +    def self.reset
    +      @once = {}
    +      nil
    +    end
    +
         def self.puts_once *ids, message
           id = "#{ids}"
           @once ||= {}
    @@ -41175,7 +43250,52 @@ class Object
     end
     
     
    -

    numeric.rb

    +

    metadata.rb

    +
    # ./dragon/metadata.rb
    +# Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński
    +# Copyright 2021 DragonRuby LLC
    +# MIT License
    +# metadata.rb has been released under MIT (*only this file*).
    +
    +module Metadata
    +  def metadata_file_path
    +    "metadata/game_metadata.txt"
    +  end
    +
    +  def get_metadata
    +    metadata = $gtk.read_file metadata_file_path
    +
    +    if !metadata
    +      write_blank_metadata
    +      metadata = $gtk.read_file metadata_file_path
    +    end
    +
    +    dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a
    +
    +    {
    +      dev_id: dev_id.strip,
    +      dev_title: dev_title.strip,
    +      game_id: game_id.strip,
    +      game_title: game_title.strip,
    +      version: version.strip,
    +      icon: icon.strip
    +    }
    +  end
    +
    +  def write_blank_metadata
    +      $gtk.write_file metadata_file_path, <<-S.strip
    +#devid=myname
    +#devtitle=My Name
    +#gameid=mygame
    +#gametitle=My Game
    +#version=0.1
    +#icon=metadata/icon.png
    +S
    +  end
    +end
    +
    +
    +

    numeric.rb

    # ./dragon/numeric.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -41192,6 +43312,35 @@ class Numeric
       alias_method :lte, :<=
       alias_method :__original_eq_eq__, :== unless Numeric.instance_methods.include? :__original_eq_eq__
     
    +  def to_layout_row opts = {}
    +    $layout.rect(row: self,
    +                 col: opts.col || 0,
    +                 w:   opts.w || 0,
    +                 h:   opts.h || 0).y
    +  end
    +
    +  def to_layout_col opts = {}
    +    $layout.rect(row: 0,
    +                 col: self,
    +                 w:   opts.w || 0,
    +                 h:   opts.h || 0).x
    +  end
    +
    +  def to_layout_w
    +    $layout.rect(row: 0, col: 0, w: self, h: 1).w
    +  end
    +
    +  def to_layout_h
    +    $layout.rect(row: 0, col: 0, w: 1, h: self).h
    +  end
    +
    +  def to_layout_row_from_bottom opts = {}
    +    ($layout.row_max_index - self).to_layout_row opts
    +  end
    +
    +  def to_layout_col_from_right opts = {}
    +    ($layout.col_max_index - self).to_layout_col opts
    +  end
     
       # Converts a numeric value representing seconds into frames.
       #
    @@ -41207,10 +43356,26 @@ class Numeric
         self / 2.0
       end
     
    +  def third
    +    self / 3.0
    +  end
    +
    +  def quarter
    +    self / 4.0
    +  end
    +
       def to_byte
         clamp(0, 255).to_i
       end
     
    +  def clamp *opts
    +    min = (opts.at 0)
    +    max = (opts.at 1)
    +    return min if min && self < min
    +    return max if max && self > max
    +    return self
    +  end
    +
       def clamp_wrap min, max
         max, min = min, max if min > max
         return self if self >= min && self <= max
    @@ -41435,7 +43600,7 @@ S
         self * Math::PI.fdiv(180)
       end
     
    -  # Converts a number representing an angle in radians to degress.
    +  # Converts a number representing an angle in radians to degrees.
       #
       # @gtk
       def to_degrees
    @@ -41452,21 +43617,21 @@ S
         GTK::Geometry.to_square(self, x, y, anchor_x, anchor_y)
       end
     
    -  # Returns a normal vector for a number that represents an angle in degress.
    +  # Returns a normal vector for a number that represents an angle in degrees.
       #
       # @gtk
       def vector max_value = 1
         [vector_x(max_value), vector_y(max_value)]
       end
     
    -  # Returns the y component of a normal vector for a number that represents an angle in degress.
    +  # Returns the y component of a normal vector for a number that represents an angle in degrees.
       #
       # @gtk
       def vector_y max_value = 1
         max_value * Math.sin(self.to_radians)
       end
     
    -  # Returns the x component of a normal vector for a number that represents an angle in degress.
    +  # Returns the x component of a normal vector for a number that represents an angle in degrees.
       #
       # @gtk
       def vector_x max_value = 1
    @@ -41493,6 +43658,18 @@ S
         (self % n) == 0
       end
     
    +  def multiply n
    +    self * n
    +  end
    +
    +  def fmult n
    +    self * n.to_f
    +  end
    +
    +  def imult n
    +    (self * n).to_i
    +  end
    +
       def mult n
         self * n
       end
    @@ -41614,12 +43791,6 @@ S
         return gt other
       end
     
    -  def == other
    -    return true if __original_eq_eq__ other
    -    return __original_eq_eq__ other.entity_id if other.is_a? OpenEntity
    -    return false
    -  end
    -
       # @gtk
       def map
         unless block_given?
    @@ -41706,29 +43877,29 @@ S
       end
     
       def - other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self - other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :-, e
       end
     
       def + other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self + other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :+, e
       end
     
       def * other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self * other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :*, e
       end
     
       def / other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self / other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :/, e
       end
    @@ -41758,6 +43929,10 @@ S
       def self.clamp n, min, max
         n.clamp min, max
       end
    +
    +  def mid? l, r
    +    (between? l, r) || (between? r, l)
    +  end
     end
     
     class Fixnum
    @@ -41784,39 +43959,33 @@ class Fixnum
       end
     
       def + other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self + other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :+, e
       end
     
       def * other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self * other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :*, e
       end
     
       def / other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self / other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :/, e
       end
     
       def - other
    -    return nil unless other
    -    super
    +    return self unless other
    +    self - other
       rescue Exception => e
         __raise_arithmetic_exception__ other, :-, e
       end
     
    -  def == other
    -    return true if __original_eq_eq__ other
    -    return __original_eq_eq__ other.entity_id if other.is_a? GTK::OpenEntity
    -    return false
    -  end
    -
       # Returns `-1` if the number is less than `0`. `+1` if the number
       # is greater than `0`. Returns `0` if the number is equal to `0`.
       #
    @@ -41873,28 +44042,28 @@ class Float
       alias_method :__original_divide__,   :- unless Float.instance_methods.include? :__original_divide__
     
       def - other
    -    return nil unless other
    +    return self unless other
         super
       rescue Exception => e
         __raise_arithmetic_exception__ other, :-, e
       end
     
       def + other
    -    return nil unless other
    +    return self unless other
         super
       rescue Exception => e
         __raise_arithmetic_exception__ other, :+, e
       end
     
       def * other
    -    return nil unless other
    +    return self unless other
         super
       rescue Exception => e
         __raise_arithmetic_exception__ other, :*, e
       end
     
       def / other
    -    return nil unless other
    +    return self unless other
         super
       rescue Exception => e
         __raise_arithmetic_exception__ other, :/, e
    @@ -41941,10 +44110,14 @@ class Integer
       def nan?
         false
       end
    +
    +  def center other
    +    (self - other).abs.fdiv(2)
    +  end
     end
     
     
    -

    remote_hotload_client.rb

    +

    remote_hotload_client.rb

    # ./dragon/remote_hotload_client.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -41995,7 +44168,7 @@ module GTK
         def local_state
           @local_state ||= OpenEntity.new
           @local_state.hotload_client ||= @local_state.new_entity(:hotload_client,
    -                                                              notes: "This enitity is used by DragonRuby Game Toolkit to provide you hotloading on remote machines.",
    +                                                              notes: "This entity is used by DragonRuby Game Toolkit to provide you hotloading on remote machines.",
                                                                   changes: { },
                                                                   changes_queue: [],
                                                                   reloaded_files_times: [])
    @@ -42141,7 +44314,7 @@ module GTK
     end
     
     
    -

    runtime/autocomplete.rb

    +

    runtime/autocomplete.rb

    # ./dragon/runtime/autocomplete.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -42169,7 +44342,7 @@ module GTK
               sub_index       = index - previous_line[:sum]
               word            = (cursor_line[:line][0..sub_index - 1]).strip
               token           = (word.split " ")[-1]
    -          dots            = (token.split ".")
    +          dots            = (token.split ".").flat_map { |s| s.split "[" }.flat_map { |s| s.split "]" }.flat_map { |s| s.split "(" }.flat_map { |s| s.split ")" }
               dot             = dots[-1]
             end
     
    @@ -42190,6 +44363,10 @@ module GTK
             ignores ||= []
             ignores   = [ignores].flatten
             keys   = keys.map { |k| k.to_s }
    +        keys   = keys.reject { |k| k.include? '"' }
    +                     .reject { |k| k.start_with? "'" }
    +                     .reject { |k| k.include? "," }
    +                     .reject { |k| k.start_with? "#" }
             others = ["def", "end"] +
                      [ :entity_keys_by_ref,
                        :entity_name,
    @@ -42247,6 +44424,10 @@ module GTK
     
               return autocomplete_filter_methods lookup_result.call if lookup_result
     
    +          if dot[0].upcase == dot[0] && (Object.const_defined? dot.to_sym)
    +            return (Object.const_get dot.to_sym).autocomplete_methods
    +          end
    +
               start_collecting = false
               dots_after_state = dots.find_all do |s|
                 if s == "state"
    @@ -42262,10 +44443,16 @@ module GTK
                 target = target.as_hash[k.to_sym] if target.respond_to? :as_hash
               end
     
    -          return autocomplete_filter_methods target.as_hash.keys
    +          if target.respond_to? :as_hash
    +            return autocomplete_filter_methods target.as_hash.keys
    +          else
    +            return autocomplete_filter_methods target.autocomplete_methods
    +          end
             end
     
     
    +        text = text.each_line.reject { |l| l.strip.start_with? "#" }.join "\n"
    +        text = text.each_line.map { |l| l.split("#").first }.join "\n"
             text.gsub!("[", " ")
             text.gsub!("]", " ")
             text.gsub!("(", " ")
    @@ -42281,153 +44468,155 @@ module GTK
     end # end GTK
     
     
    -

    runtime/draw.rb

    -
    # ./dragon/runtime/draw.rb
    +

    runtime/benchmark.rb

    +
    # ./dragon/runtime/benchmark.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    -# draw.rb has been released under MIT (*only this file*).
    +# benchmark.rb has been released under MIT (*only this file*).
     
     module GTK
       class Runtime
    -    module Draw
    -      def primitives pass
    -        if $top_level.respond_to? :primitives_override
    -          return $top_level.tick_render @args, pass
    -        end
    -
    -        # Don't change this draw order unless you understand
    -        # the implications.
    -
    -        # pass.solids.each            { |s| draw_solid s }
    -        # while loops are faster than each with block
    +    module Benchmark
    +      def benchmark_single iterations, name, proc
    +        log <<-S
    +** Invoking :#{name}...
    +S
    +        time_start = Time.now
             idx = 0
    -        length = pass.solids.length
    -        while idx < pass.solids.length
    -          draw_solid (pass.solids.at idx) # accessing an array using .value instead of [] is faster
    +        r = nil
    +        while idx < iterations
    +          r = proc.call
               idx += 1
             end
     
    -        # pass.static_solids.each     { |s| draw_solid s }
    -        idx = 0
    -        length = pass.static_solids.length
    -        while idx < length
    -          draw_solid (pass.static_solids.at idx)
    -          idx += 1
    -        end
    +        result = (Time.now - time_start).round 3
     
    -        # pass.sprites.each           { |s| draw_sprite s }
    -        idx = 0
    -        length = pass.sprites.length
    -        while idx < length
    -          draw_sprite (pass.sprites.at idx)
    -          idx += 1
    -        end
    +        { name: name,
    +          time: result,
    +          time_ms: (result * 1000).to_i }
    +      end
     
    -        # pass.static_sprites.each    { |s| draw_sprite s }
    -        idx = 0
    -        length = pass.static_sprites.length
    -        while idx < length
    -          draw_sprite (pass.static_sprites.at idx)
    -          idx += 1
    -        end
    +      def benchmark opts = {}
    +        iterations = opts.iterations
     
    -        # pass.primitives.each        { |p| draw_primitive p }
    -        idx = 0
    -        length = pass.primitives.length
    -        while idx < length
    -          draw_primitive (pass.primitives.at idx)
    -          idx += 1
    -        end
    +        log <<-S
    +* BENCHMARK: Started
    +** Caller: #{(caller || []).first}
    +** Iterations: #{iterations}
    +S
    +        procs = opts.find_all { |k, v| v.respond_to? :call }
    +
    +        times = procs.map do |(name, proc)|
    +          benchmark_single iterations, name, proc
    +        end.sort_by { |r| r.time }
    +
    +        first_place = times.first
    +        second_place = times.second || first_place
    +
    +        times = times.map do |candidate|
    +          average_time = first_place.time
    +                           .add(candidate.time)
    +                           .abs
    +                           .fdiv(2)
    +
    +          difference_percentage = 0
    +          if average_time == 0
    +            difference_percentage = 0
    +          else
    +            difference_percentage = first_place.time
    +                                      .subtract(candidate.time)
    +                                      .abs
    +                                      .fdiv(average_time)
    +                                      .imult(100)
    +          end
     
    -        # pass.static_primitives.each { |p| draw_primitive p }
    -        idx = 0
    -        length = pass.static_primitives.length
    -        while idx < length
    -          draw_primitive (pass.static_primitives.at idx)
    -          idx += 1
    +          difference_time = ((first_place.time - candidate.time) * 1000).round
    +          candidate.merge(difference_percentage: difference_percentage,
    +                          difference_time: difference_time)
             end
     
    -        # pass.labels.each            { |l| draw_label l }
    -        idx = 0
    -        length = pass.labels.length
    -        while idx < length
    -          draw_label (pass.labels.at idx)
    -          idx += 1
    +        too_small_to_measure = false
    +        if (first_place.time + second_place.time) == 0
    +          too_small_to_measure = true
    +          difference_percentage = 0
    +          log <<-S
    +* BENCHMARK: Average time for experiments were too small. Increase the number of iterations.
    +S
    +        else
    +          difference_percentage = (((first_place.time - second_place.time).abs.fdiv((first_place.time + second_place.time).abs.fdiv(2))) * 100).round
             end
     
    -        # pass.static_labels.each     { |l| draw_label l }
    -        idx = 0
    -        length = pass.static_labels.length
    -        while idx < length
    -          draw_label (pass.static_labels.at idx)
    -          idx += 1
    -        end
    +        difference_time = first_place.time.-(second_place.time).*(1000).abs.round
     
    -        # pass.lines.each             { |l| draw_line l }
    -        idx = 0
    -        length = pass.lines.length
    -        while idx < length
    -          draw_line (pass.lines.at idx)
    -          idx += 1
    -        end
    +        r = {
    +          iterations: iterations,
    +          first_place: first_place,
    +          second_place: second_place,
    +          difference_time: difference_time,
    +          difference_percentage: difference_percentage,
    +          times: times,
    +          too_small_to_measure: too_small_to_measure
    +        }
     
    -        # pass.static_lines.each      { |l| draw_line l }
    -        idx = 0
    -        length = pass.static_lines.length
    -        while idx < pass.static_lines.length
    -          draw_line (pass.static_lines.at idx)
    -          idx += 1
    -        end
    +        log_message = []
    +        only_one_result = first_place.name == second_place.name
     
    -        # pass.borders.each           { |b| draw_border b }
    -        idx = 0
    -        length = pass.borders.length
    -        while idx < length
    -          draw_border (pass.borders.at idx)
    -          idx += 1
    +        if only_one_result
    +          log <<-S
    +* BENCHMARK: #{r.first_place.name} completed in #{r.first_place.time_ms}ms."
    +S
    +        else
    +          log <<-S
    +* BENCHMARK: #{r.message}
    +** Fastest: #{r.first_place.name.inspect}
    +** Second:  #{r.second_place.name.inspect}
    +** Margin:  #{r.difference_percentage}% (#{r.difference_time.abs}ms) #{r.first_place.time_ms}ms vs #{r.second_place.time_ms}ms.
    +** Times:
    +#{r.times.map { |t| "*** #{t.name}: #{t.time_ms}ms (#{t.difference_percentage}% #{t.difference_time.abs}ms)." }.join("\n")}
    +S
             end
     
    -        # pass.static_borders.each    { |b| draw_border b }
    -        idx = 0
    -        length = pass.static_borders.length
    -        while idx < length
    -          draw_border (pass.static_borders.at idx)
    -          idx += 1
    -        end
    +        r
    +      end
    +    end
    +  end
    +end
     
    -        if !$gtk.production
    -          # pass.debug.each        { |r| draw_primitive r }
    -          idx = 0
    -          length = pass.debug.length
    -          while idx < length
    -            draw_primitive (pass.debug.at idx)
    -            idx += 1
    -          end
    +
    +

    runtime/draw.rb

    +
    # ./dragon/runtime/draw.rb
    +# Copyright 2019 DragonRuby LLC
    +# MIT License
    +# draw.rb has been released under MIT (*only this file*).
     
    -          # pass.static_debug.each { |r| draw_primitive r }
    -          idx = 0
    -          length = pass.static_debug.length
    -          while idx < length
    -            draw_primitive (pass.static_debug.at idx)
    -            idx += 1
    -          end
    +module GTK
    +  class Runtime
    +    module Draw
    +      def primitives pass
    +        if $top_level.respond_to? :primitives_override
    +          return $top_level.tick_render @args, pass
             end
     
    -        # pass.reserved.each          { |r| draw_primitive r }
    -        idx = 0
    -        length = pass.reserved.length
    -        while idx < length
    -          draw_primitive (pass.reserved.at idx)
    -          idx += 1
    -        end
    +        fn.each_send pass.solids,            self, :draw_solid
    +        fn.each_send pass.static_solids,     self, :draw_solid
    +        fn.each_send pass.sprites,           self, :draw_sprite
    +        fn.each_send pass.static_sprites,    self, :draw_sprite
    +        fn.each_send pass.primitives,        self, :draw_primitive
    +        fn.each_send pass.static_primitives, self, :draw_primitive
    +        fn.each_send pass.labels,            self, :draw_label
    +        fn.each_send pass.static_labels,     self, :draw_label
    +        fn.each_send pass.lines,             self, :draw_line
    +        fn.each_send pass.static_lines,      self, :draw_line
    +        fn.each_send pass.borders,           self, :draw_border
    +        fn.each_send pass.static_borders,    self, :draw_border
     
    -        # pass.static_reserved.each   { |r| draw_primitive r }
    -        idx = 0
    -        length = pass.static_reserved.length
    -        while idx < length
    -          draw_primitive (pass.static_reserved.at idx)
    -          idx += 1
    +        if !$gtk.production
    +          fn.each_send pass.debug,           self, :draw_primitive
    +          fn.each_send pass.static_debug,    self, :draw_primitive
             end
    +
    +        fn.each_send pass.reserved,          self, :draw_primitive
    +        fn.each_send pass.static_reserved,   self, :draw_primitive
           rescue Exception => e
             pause!
             pretty_print_exception_and_export! e
    @@ -42438,7 +44627,9 @@ module GTK
             if s.respond_to? :draw_override
               s.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_solid s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a
    +          @ffi_draw.draw_solid_2 s.x, s.y, s.w, s.h,
    +                                 s.r, s.g, s.b, s.a,
    +                                 (s.blendmode_enum || 1)
             end
           rescue Exception => e
             raise_conversion_for_rendering_failed s, e, :solid
    @@ -42449,14 +44640,15 @@ module GTK
             if s.respond_to? :draw_override
               s.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_sprite_3 s.x, s.y, s.w, s.h,
    -                                  s.path.s_or_default,
    +          @ffi_draw.draw_sprite_4 s.x, s.y, s.w, s.h,
    +                                  (s.path || '').to_s,
                                       s.angle,
                                       s.a, s.r, s.g, s.b,
                                       s.tile_x, s.tile_y, s.tile_w, s.tile_h,
                                       !!s.flip_horizontally, !!s.flip_vertically,
                                       s.angle_anchor_x, s.angle_anchor_y,
    -                                  s.source_x, s.source_y, s.source_w, s.source_h
    +                                  s.source_x, s.source_y, s.source_w, s.source_h,
    +                                  (s.blendmode_enum || 1)
             end
           rescue Exception => e
             raise_conversion_for_rendering_failed s, e, :sprite
    @@ -42467,7 +44659,7 @@ module GTK
             if s.respond_to? :draw_override
               s.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_screenshot s.path.s_or_default,
    +          @ffi_draw.draw_screenshot (s.path || '').to_s,
                                         s.x, s.y, s.w, s.h,
                                         s.angle,
                                         s.a, s.r, s.g, s.b,
    @@ -42485,10 +44677,13 @@ module GTK
             if l.respond_to? :draw_override
               l.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_label l.x, l.y, l.text.s_or_default,
    -                               l.size_enum, l.alignment_enum,
    -                               l.r, l.g, l.b, l.a,
    -                               l.font.s_or_default(nil)
    +          @ffi_draw.draw_label_3 l.x, l.y,
    +                                 (l.text || '').to_s,
    +                                 l.size_enum, l.alignment_enum,
    +                                 l.r, l.g, l.b, l.a,
    +                                 l.font,
    +                                 (l.vertical_alignment_enum || 2),
    +                                 (l.blendmode_enum || 1)
             end
           rescue Exception => e
             raise_conversion_for_rendering_failed l, e, :label
    @@ -42499,7 +44694,21 @@ module GTK
             if l.respond_to? :draw_override
               l.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_line l.x, l.y, l.x2, l.y2, l.r, l.g, l.b, l.a
    +          if l.x2
    +            @ffi_draw.draw_line_2 l.x, l.y, l.x2, l.y2,
    +                                  l.r, l.g, l.b, l.a,
    +                                  (l.blendmode_enum || 1)
    +          else
    +            w = l.w || 0
    +            w = 1 if w == 0
    +            h = l.h || 0
    +            h = 1 if h == 0
    +            @ffi_draw.draw_line_2 l.x, l.y,
    +                                  l.x + w - 1,
    +                                  l.y + h - 1,
    +                                  l.r, l.g, l.b, l.a,
    +                                  (l.blendmode_enum || 1)
    +          end
             end
           rescue Exception => e
             raise_conversion_for_rendering_failed l, e, :line
    @@ -42510,7 +44719,9 @@ module GTK
             if s.respond_to? :draw_override
               s.draw_override @ffi_draw
             else
    -          @ffi_draw.draw_border s.x, s.y, s.w, s.h, s.r, s.g, s.b, s.a
    +          @ffi_draw.draw_border_2 s.x, s.y, s.w, s.h,
    +                                  s.r, s.g, s.b, s.a,
    +                                  (s.blendmode_enum || 1)
             end
           rescue Exception => e
             raise_conversion_for_rendering_failed s, e, :border
    @@ -42530,13 +44741,12 @@ module GTK
             pause!
             pretty_print_exception_and_export! e
           end
    -
         end
       end
     end
     
     
    -

    runtime/framerate.rb

    +

    runtime/framerate.rb

    # ./dragon/runtime/framerate.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -42623,7 +44833,7 @@ module GTK
     end # end module GTK
     
     
    -

    runtime/framerate_diagnostics.rb

    +

    runtime/framerate_diagnostics.rb

    # ./dragon/runtime/framerate_diagnostics.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
    @@ -42742,7 +44952,7 @@ If this warning is getting annoying put the following in your tick method:
     
           def framerate_diagnostics_primitives
             [
    -          { x: 0, y: 93.from_top, w: 500, h: 93, a: 128 }.solid,
    +          { x: 0, y: 93.from_top, w: 500, h: 93, a: 128 }.solid!,
               {
                 x: 5,
                 y: 5.from_top,
    @@ -42751,7 +44961,7 @@ If this warning is getting annoying put the following in your tick method:
                 g: 255,
                 b: 255,
                 size_enum: -2
    -          }.label,
    +          }.label!,
               {
                 x: 5,
                 y: 20.from_top,
    @@ -42760,7 +44970,7 @@ If this warning is getting annoying put the following in your tick method:
                 g: 255,
                 b: 255,
                 size_enum: -2
    -          }.label,
    +          }.label!,
               {
                 x: 5,
                 y: 35.from_top,
    @@ -42769,7 +44979,7 @@ If this warning is getting annoying put the following in your tick method:
                 g: 255,
                 b: 255,
                 size_enum: -2
    -          }.label,
    +          }.label!,
               {
                 x: 5,
                 y: 50.from_top,
    @@ -42778,7 +44988,7 @@ If this warning is getting annoying put the following in your tick method:
                 g: 255,
                 b: 255,
                 size_enum: -2
    -          }.label,
    +          }.label!,
               {
                 x: 5,
                 y: 65.from_top,
    @@ -42787,7 +44997,7 @@ If this warning is getting annoying put the following in your tick method:
                 g: 255,
                 b: 255,
                 size_enum: -2
    -          }.label,
    +          }.label!,
             ]
           end
     
    @@ -42796,7 +45006,7 @@ If this warning is getting annoying put the following in your tick method:
     end
     
     
    -

    runtime/hotload.rb

    +

    runtime/hotload.rb

    # ./dragon/runtime/hotload.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -42810,7 +45020,14 @@ module GTK
           def hotload_init
             @hotload_if_needed = 0
             @mailbox_if_needed = 0
    +
    +        # schema for file_mtimes
    +        # { FILE_PATH: { current: (Time as Fixnum),
    +        #                last:    (Time as Fixnum) },
    +        #   FILE_PATH: { current: (Time as Fixnum),
    +        #                last:    (Time as Fixnum) } }
             @file_mtimes = { }
    +
             @suppress_mailbox = true
             files_to_reload.each { |f| init_mtimes f }
             init_mtimes 'app/mailbox.rb'
    @@ -42848,6 +45065,7 @@ module GTK
                 'dragon/symbol.rb',
                 'dragon/numeric_deprecated.rb',
                 'dragon/numeric.rb',
    +            'dragon/hash_deprecated.rb',
                 'dragon/hash.rb',
                 'dragon/outputs_deprecated.rb',
                 'dragon/array_docs.rb',
    @@ -42880,6 +45098,7 @@ module GTK
                 'dragon/trace.rb',
                 'dragon/readme_docs.rb',
                 'dragon/hotload_client.rb',
    +            'dragon/wizards.rb',
                 'dragon/ios_wizard.rb',
                 'dragon/itch_wizard.rb',
               ] + core_files_to_reload + @required_files
    @@ -42898,10 +45117,8 @@ module GTK
           end
     
           def init_mtimes file
    -        current_key = "current_#{file}".to_sym
    -        last_key = "last_#{file}".to_sym
    -        @file_mtimes[current_key] ||= @ffi_file.mtime(file)
    -        @file_mtimes[last_key] ||= @ffi_file.mtime(file)
    +        @file_mtimes[file] ||= { current: @ffi_file.mtime(file),
    +                                 last: @ffi_file.mtime(file) }
           end
     
           def hotload_source_files
    @@ -42933,32 +45150,43 @@ module GTK
           end
     
           def hotload_if_needed
    +        return if Kernel.tick_count < 0
             hotload_source_files
             check_mailbox
           end
     
           def on_load_succeeded file
    -        @rcb_sender.files_reloaded << file
    -        @rcb_sender.reloaded_files << file
    +        self.files_reloaded << file
    +        self.reloaded_files << file
             Trace.untrace_classes!
           end
     
    +      def reset_all_mtimes
    +        @file_mtimes.each do |file, _|
    +          @file_mtimes[file].current = @ffi_file.mtime(file)
    +          @file_mtimes[file].last    = @file_mtimes[file].current
    +        end
    +
    +        files_to_reload.each do |file, _|
    +          @file_mtimes[file] ||= {}
    +          @file_mtimes[file].current = @ffi_file.mtime(file)
    +          @file_mtimes[file].last    = @file_mtimes[file].current
    +        end
    +      end
    +
           def reload_if_needed file, force = false
    -        current_key = "current_#{file}".to_sym
    -        last_key = "last_#{file}".to_sym
    -        @file_mtimes[current_key] ||= nil
    -        @file_mtimes[last_key] ||= nil
    -        @file_mtimes[current_key] = @ffi_file.mtime(file)
    -        return if !force && @file_mtimes[last_key] == @file_mtimes[current_key]
    +        @file_mtimes[file] ||= { current: @ffi_file.mtime(file), last: @ffi_file.mtime(file) }
    +        @file_mtimes[file].current = @ffi_file.mtime(file)
    +        return if !force && @file_mtimes[file].current == @file_mtimes[file].last
             on_load_succeeded file if reload_ruby_file file
    -        @file_mtimes[last_key] = @file_mtimes[current_key]
    +        @file_mtimes[file].last = @file_mtimes[file].current
           end
         end
       end
     end
     
     
    -

    string.rb

    +

    string.rb

    # ./dragon/string.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -42991,6 +45219,30 @@ S
         end
       end
     
    +  def char_byte
    +    return nil if self.length == 0
    +    c = self.each_char.first.bytes
    +    c = c.first if c.is_a? Enumerable
    +    c
    +  end
    +
    +  def insert_character_at index, char
    +    t = each_char.to_a
    +    t = (t.insert index, char)
    +    t.join
    +  end
    +
    +  def excluding_character_at index
    +    t = each_char.to_a
    +    t.delete_at index
    +    t.join
    +  end
    +
    +  def excluding_last_character
    +    return "" if self.length <= 1
    +    return excluding_character_at(self.length - 1)
    +  end
    +
       def end_with_bang?
         self[-1] == "!"
       end
    @@ -43070,7 +45322,7 @@ S
     end
     
     
    -

    tests.rb

    +

    tests.rb

    # ./dragon/tests.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -43206,14 +45458,14 @@ S
           log "#{self.failed.length} test(s) failed."
           self.failed.each do |h|
             log "**** Test name: :#{h[:m]}"
    -        log "#{h[:e].to_s.gsub("* ERROR:", "").strip}"
    +        log "#{h[:e].to_s.gsub("* ERROR:", "").strip}\n#{h[:e].__backtrace_to_org__}"
           end
         end
       end
     end
     
     
    -

    trace.rb

    +

    trace.rb

    # ./dragon/trace.rb
     # coding: utf-8
     # Copyright 2019 DragonRuby LLC
    @@ -43401,12 +45653,46 @@ module GTK
     end
     
     
    -

    wizards.rb

    +

    wizards.rb

    # ./dragon/wizards.rb
     # Copyright 2019 DragonRuby LLC
     # MIT License
     # wizards.rb has been released under MIT (*only this file*).
     
    +class Wizard
    +  def metadata_file_path
    +    "metadata/game_metadata.txt"
    +  end
    +
    +  def get_metadata
    +    metadata = $gtk.read_file metadata_file_path
    +
    +    if !metadata
    +      write_blank_metadata
    +      metadata = $gtk.read_file metadata_file_path
    +    end
    +
    +    dev_id, dev_title, game_id, game_title, version, icon = *metadata.each_line.to_a
    +
    +    {
    +      dev_id:     dev_id.strip.gsub("#", "").gsub("devid=", ""),
    +      dev_title:  dev_title.strip.gsub("#", "").gsub("devtitle=", ""),
    +      game_id:    game_id.strip.gsub("#", "").gsub("gameid=", ""),
    +      game_title: game_title.strip.gsub("#", "").gsub("gametitle=", ""),
    +      version:    version.strip.gsub("#", "").gsub("version=", ""),
    +      icon:       icon.strip.gsub("#", "").gsub("icon=", "")
    +    }
    +  end
    +end
    +
    +class WizardException < Exception
    +  attr_accessor :console_primitives
    +
    +  def initialize *console_primitives
    +    @console_primitives = console_primitives
    +  end
    +end
    +
     module GTK
       class Wizards
         attr_accessor :ios, :itch
    -- 
    cgit v1.2.3