diff options
| author | _Tradam <[email protected]> | 2021-12-16 19:22:26 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-12-16 19:22:26 -0500 |
| commit | 5954b9beb4d4a3b4f248d72d1851195f030558a8 (patch) | |
| tree | fecd8aa840a25afdb502915b0fdb4d03b7ed339a /docs/docs.txt | |
| parent | 2f845281f133849256b57bb08fd3e9ae57600784 (diff) | |
| parent | eaa29e72939f5edf61735ccbb73c36ee89369f65 (diff) | |
| download | dragonruby-game-toolkit-contrib-5954b9beb4d4a3b4f248d72d1851195f030558a8.tar.gz dragonruby-game-toolkit-contrib-5954b9beb4d4a3b4f248d72d1851195f030558a8.zip | |
Diffstat (limited to 'docs/docs.txt')
| -rw-r--r-- | docs/docs.txt | 8590 |
1 files changed, 6204 insertions, 2386 deletions
diff --git a/docs/docs.txt b/docs/docs.txt index 0b543de..c0cc841 100644 --- a/docs/docs.txt +++ b/docs/docs.txt @@ -11,8 +11,6 @@ to get fancy you can provide a ~lambda~ to filter documentation: docs_search { |entry| (entry.include? "Array") && (!entry.include? "Enumerable") } #+end_src -[[docs_search.gif]] - * Hello World Welcome to DragonRuby Game Toolkit. Take the steps below to get started. @@ -37,20 +35,25 @@ Reply with: I am a Dragon Rider. #+end_quote -* Watch Some Intro Videos +* Intro Videos + +Here are some videos to help you get the lay of the land. -Each video is only 20 minutes and all of them will fit into a lunch -break. So please watch them: +** Quick Api Tour 1. Beginner Introduction to DragonRuby Game Toolkit: [[https://youtu.be/ixw7TJhU08E]] -2. Intermediate Introduction to Ruby Syntax: [[https://youtu.be/HG-XRZ5Ppgc]] -3. Intermediate Introduction to Arrays in Ruby: [[https://youtu.be/N72sEYFRqfo]] -The second and third videos are not required if you are proficient -with Ruby, but *definitely* watch the first one. +** 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. You may also want to try this free course provided at [[http://dragonruby.school]]. + +** If You Have Game Dev Experience -You may also want to try this free course provided at -[[http://dragonruby.school]]. +1. Building Tetris - Part 1: [[https://youtu.be/xZMwRSbC4rY]] +2. Building Tetris - Part 2: [[https://youtu.be/C3LLzDUDgz4]] +3. Low Res Game Jam Tutorial: [[https://youtu.be/pCI90ukKCME]] * Getting Started Tutorial @@ -392,7 +395,7 @@ 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~ +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 @@ -411,7 +414,7 @@ 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. +compensated with a strong commitment to the following principles. ** Challenge The Status Quo @@ -440,10 +443,10 @@ 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), +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 string entities (more work than hashes, but yields cleaner apis), +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). @@ -570,7 +573,7 @@ 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. +Switch, PS4, Xbox, and Stadia. **** What does Multilevel Cross-platform mean? @@ -648,7 +651,7 @@ file called ~repl.rb~ and put it in ~mygame/app/repl.rb~: - If you use the `repl` method, the code will be executed and the DragonRuby Console will automatically open so you can see the results (on Mac and Linux, the results will also be printed to the terminal). -- All ~puts~ statements will also be saved to ~logs/log.txt~. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can ~tail~ this file. +- All ~puts~ statements will also be saved to ~logs/puts.txt~. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can ~tail~ this file. 4. To ignore code in ~repl.rb~, instead of commenting it out, prefix ~repl~ with the letter ~x~ and it'll be ignored. @@ -684,7 +687,7 @@ challenges in creating something that is compatible. You can use DragonRuby's replay capabilities to troubleshoot: 1. DragonRuby is hot loaded which gives you a very fast feedback loop (if the game throws an exception, it's because of the code you just added). -2. 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"~. +2. 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"~. 3. 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~. 4. 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. 5. 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. @@ -697,7 +700,7 @@ 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. +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]. @@ -771,13 +774,11 @@ questions asked. *** 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. +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. @@ -897,11 +898,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 #+end_src +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~: #+begin_src ruby @@ -911,7 +916,7 @@ You can represent a sprite as an ~object~: :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 @@ -997,16 +1002,17 @@ You can add additional metadata about your game within a label, which requires y #+begin_src 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: { @@ -1039,6 +1045,21 @@ You can get the render size of any string using ~args.gtk.calcstringbox~. end #+end_src +** Rendering Labels With New Line Characters And Wrapping + +You can use a strategy like the following to create multiple labels from a String. + +#+begin_src ruby + 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 +#+end_src + ** How To Play A Sound Sounds that end ~.wav~ will play once: @@ -1195,7 +1216,7 @@ Returns ~true~ if: the ~right~ arrow or ~d~ key is pressed or held on the ~keybo *** ~.left_right~ Returns ~-1~ (left), ~0~ (neutral), or ~+1~ (right) depending on results of ~args.inputs.left~ and ~args.inputs.right~. *** ~.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~ Returns a string that represents the last key that was pressed on the keyboard. *** ~.mouse~ @@ -1229,17 +1250,17 @@ The properties ~args.inputs.mouse.(click|down|previous_click|up)~ each return ~n that has an ~x~, ~y~ properties along with helper functions to determine collision: ~inside_rect?~, ~inside_circle~. *** ~.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~. **** ~.(left_analog_x_raw|right_analog_x_raw)~ Returns the raw integer value for the analog's horizontal movement (~-32,000 to +32,000~). @@ -1249,13 +1270,13 @@ Returns the raw integer value for the analog's vertical movement (~-32,000 to +3 Returns a number between ~-1~ and ~1~ which represents the percentage the analog is moved horizontally as a ratio of the maximum horizontal movement. **** ~.left_analog_y_perc|right_analog_y_perc)~ 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. **** ~.(a|b|x|y|l1|r1|l2|r2|l3|r3|start|select)~ Returns ~true~ if the specific button is pressed or held. @@ -1411,6 +1432,14 @@ Send a Primitive to this collection to render an unfilled rectangle to the scree 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~ This property contains geometric functions. Functions can be invoked via ~args.geometry.FUNCTION~. + +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. ~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~. +3. ~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~. +4. ~Angles~ are represented as degrees (not radians). + *** ~.inside_rect? rect_1, rect_2~ Returns ~true~ if ~rect_1~ is inside ~rect_2~. *** ~.intersect_rect? rect_2, rect_2~ @@ -1625,6 +1654,34 @@ is currently in the file. Use ~GTK::Runtime#append_file~ to append to the file a end #+end_src +* DOCS: ~GTK::Runtime#benchmark~ +You can use this function to compare the relative performance of methods. + +#+begin_src ruby + def tick args + # press r to run benchmark + if args.inputs.keyboard.key_down.r + args.gtk.console.show + args.gtk.benchmark iterations: 1000, # number of iterations + # label for experiment + using_numeric_map: -> () { + # experiment body + v = 100.map do |i| + i * 100 + end + }, + # label for experiment + using_numeric_times: -> () { + # experiment body + v = [] + 100.times do |i| + v << i * 100 + end + } + end + end +#+end_src + * DOCS: ~Array~ The Array class has been extend to provide methods that @@ -1975,7 +2032,7 @@ wide and 90 pixels tall. ** 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 +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%. @@ -2011,7 +2068,7 @@ be provided in any order. ** 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~ +ALL properties must be on the class. *Additionally*, a method called ~primitive_marker~ must be defined on the class. Here is an example: @@ -2070,6 +2127,106 @@ You have to use ~args.outputs.borders~: #+end_src +* 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. + +#+begin_src + def tick args + # X Y WIDTH HEIGHT PATH + args.outputs.sprites << [100, 100, 160, 90, "sprites/circle/white.png] + end +#+end_src + +** 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%. + +#+begin_src + 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 +#+end_src + +** 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. + +#+begin_src + 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 +#+end_src + +** 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: + +#+begin_src + # 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 +#+end_src + + * DOCS: ~GTK::Outputs#screenshots~ Add a hash to this collection to take a screenshot and save as png file. @@ -2133,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. @@ -3580,7 +3737,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -3594,7 +3751,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 0, b: 200, a: 255, - font: "manaspc.ttf" }.label + font: "manaspc.ttf" }.label! end def tick_instructions args, text, y = 715 @@ -3831,771 +3988,6 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src -*** Rendering Basics - Audio Mixer - main.rb -#+begin_src ruby - # ./samples/01_rendering_basics/06_audio_mixer/app/main.rb - $gtk.reset - - $boxsize = 30 - - 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) - - if (mouse_new_down && !mouse_in_panel) - args.state.selected = 0 # will reset below if we hit something. - end - - args.audio.keys.each { |k| - s = args.audio[k] - - 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 - - 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) - - 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) - - 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 - - color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ] - args.outputs.primitives << [s[:screenx], s[:screeny], $boxsize, $boxsize, *color].solid - } - 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 - - 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 - - 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 - - checkbox = [1022, 533, 10, 12] - if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox) - s[:looping] = !s[:looping] - 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 - - 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 - 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'}" - - # 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.next_sound_index += 1 - - # 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 - } - - args.state.selected = args.state.next_sound_index - 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 - end - x = x + ($boxsize * 3) - end - end - - def render_ui args - render_launcher args - render_panel args - end - - def tick args - args.state.mouse_held ||= 0 - args.state.dragging_source ||= false - args.state.selected ||= 0 - args.state.next_sound_index ||= 0 - - 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 - end - - args.outputs.background_color = [ 0, 0, 0, 255 ] - render_sources args - render_ui args - end - -#+end_src - -*** Rendering Basics - Sound Synthesis - main.rb -#+begin_src ruby - # ./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 - - 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 - - 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 - - - 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 - - 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 - - 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 } - - args.audio.find_all { |k, v| v[:decay_rate] } - .each { |k, v| v[:gain] -= v[:decay_rate] } - - sounds_to_stop = args.audio - .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] } - .map { |k, v| k } - - sounds_to_stop.each { |k| args.audio.delete k } - 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)) - - button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0 - - label_offset_x = 5 - label_offset_y = 30 - - 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 - - def play_sine_wave args, sender - queue_sine_wave args, - frequency: sender[:frequency], - duration: 1.seconds, - fade_out: true - 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 - - send method_to_call, args, - frequency: (frequency_for note: sender[:note], octave: sender[:octave]), - duration: 1.seconds, - fade_out: 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), - ] - 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), - ] - 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 - -#+end_src - *** Input Basics - Keyboard - main.rb #+begin_src ruby # ./samples/02_input_basics/01_keyboard/app/main.rb @@ -4626,9 +4018,9 @@ Follows is a source code listing for all files that have been open sourced. This 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] + 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 @@ -4639,27 +4031,19 @@ Follows is a source code listing for all files that have been open sourced. This 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] + 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 << [460, row_to_px(args, 4), "\"h\" has never been pressed.", small_font] + args.outputs.labels << { x: 460, y: row_to_px(args, 4), text: "\"h\" has never been pressed.", size_enum: -1 } 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 + 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.shift_down(5).shift_down(20 * row_number) + args.grid.top - 5 - (y_offset * row_number) end # Don't worry about understanding the code within this method just yet. @@ -4689,17 +4073,17 @@ Follows is a source code listing for all files that have been open sourced. This end end - args.outputs.labels << [10, row_to_px(args, 6), "Advanced Help:", small_font] + 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 } 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] + 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 } fill_history args, :key_value_history, :down_or_held, nil fill_history args, :key_down_value_history, :down, :key_down @@ -4745,12 +4129,8 @@ Follows is a source code listing for all files that have been open sourced. This 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] + { 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 @@ -4765,13 +4145,51 @@ Follows is a source code listing for all files that have been open sourced. This 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 #+end_src +*** Input Basics - Moving A Sprite - main.rb +#+begin_src ruby + # ./samples/02_input_basics/01_moving_a_sprite/app/main.rb + def tick args + # 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 + + # 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 + + if args.inputs.left + args.state.player.x -= 10 + elsif args.inputs.right + args.state.player.x += 10 + end + end + + $gtk.reset + +#+end_src + *** Input Basics - Mouse - main.rb #+begin_src ruby # ./samples/02_input_basics/02_mouse/app/main.rb @@ -4809,7 +4227,7 @@ Follows is a source code listing for all files that have been open sourced. This # 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 + # Use args.inputs.mouse.click.created_at_elapsed # Saving the click in args.state can be quite useful @@ -4838,11 +4256,7 @@ Follows is a source code listing for all files that have been open sourced. This # 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, alignment_enum: -2 } end def row_to_px args, row_number @@ -4858,9 +4272,9 @@ Follows is a source code listing for all files that have been open sourced. This 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 #+end_src @@ -4912,7 +4326,7 @@ Follows is a source code listing for all files that have been open sourced. This args.outputs.labels << small_label(args, x, 15, "Click inside the blue box maybe ---->") - box = [785, 370, 50, 50, 0, 0, 170] + 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 @@ -4934,11 +4348,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -4954,9 +4364,9 @@ Follows is a source code listing for all files that have been open sourced. This 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 #+end_src @@ -5008,9 +4418,15 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -5037,15 +4453,11 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -5111,57 +4523,51 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -5212,10 +4618,12 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -5236,11 +4644,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 - #+end_src @@ -5280,6 +4687,8 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -5312,22 +4721,22 @@ Follows is a source code listing for all files that have been open sourced. This 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. @@ -5360,7 +4769,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -7353,9 +6762,9 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -7419,7 +6828,7 @@ Follows is a source code listing for all files that have been open sourced. This def calc_below return unless player.dy <= 0 - tiles_below = find_tiles { |t| t.rect.top <= player.y } + tiles_below = find_tiles { |t| t.rect.top <= player.prev_rect.y } collision = find_colliding_tile tiles_below, (player.rect.merge y: player.next_rect.y) return unless collision if collision.neighbors.b == :none && player.jumped_down_at.elapsed_time < 10 @@ -7450,7 +6859,7 @@ Follows is a source code listing for all files that have been open sourced. This def calc_above return unless player.dy > 0 - tiles_above = find_tiles { |t| t.rect.y >= player.y } + tiles_above = find_tiles { |t| t.rect.y >= player.prev_rect.y } collision = find_colliding_tile tiles_above, (player.rect.merge y: player.next_rect.y) return unless collision return if collision.neighbors.t == :none @@ -7459,17 +6868,17 @@ Follows is a source code listing for all files that have been open sourced. This end def calc_player_dx - player.y += player.dy - player.dy += state.gravity - player.dy += player.dy * state.drag ** 2 * -1 - end - - def calc_player_dy player.dx = player.dx.clamp(-5, 5) player.dx *= 0.9 player.x += player.dx end + def calc_player_dy + player.y += player.dy + player.dy += state.gravity + player.dy += player.dy * state.drag ** 2 * -1 + end + def reset_player player.x = 100 player.y = 720 @@ -7613,8 +7022,7 @@ Follows is a source code listing for all files that have been open sourced. This input end - # Sets default values - def defaults + def init_game s.platforms ||= [ # initializes platforms collection with two platforms using hashes new_platform(x: 0, y: 0, w: 700, h: 32, dx: 1, speed: 0, rect: nil), new_platform(x: 0, y: 300, w: 700, h: 32, dx: 1, speed: 0, rect: nil), # 300 pixels higher @@ -7637,6 +7045,11 @@ Follows is a source code listing for all files that have been open sourced. This s.camera ||= { y: -100 } # shows view on screen (as the player moves upward, the camera does too) end + # Sets default values + def defaults + init_game + end + # Outputs objects onto the screen def render outputs.solids << s.platforms.map do |p| # outputs platforms onto screen @@ -7724,7 +7137,9 @@ Follows is a source code listing for all files that have been open sourced. This rect: nil) end else + # game over s.as_hash.clear # otherwise clear the hash (no new platform is necessary) + init_game end end @@ -7863,15 +7278,15 @@ Follows is a source code listing for all files that have been open sourced. This # ./samples/04_physics_and_collisions/08_bouncing_on_collision/app/block.rb DEGREES_TO_RADIANS = Math::PI / 180 - class Block + class Block def initialize(x, y, block_size, rotation) @x = x - @y = y + @y = y @block_size = block_size @rotation = rotation #The repel velocity? - @velocity = {x: 2, y: 0} + @velocity = {x: 2, y: 0} horizontal_offset = (3 * block_size) * Math.cos(rotation * DEGREES_TO_RADIANS) vertical_offset = block_size * Math.sin(rotation * DEGREES_TO_RADIANS) @@ -7899,7 +7314,7 @@ Follows is a source code listing for all files that have been open sourced. This @imaginary_line = [ @x1, @y1, @x2, @y2 ] end - + end def draw args @@ -7943,7 +7358,7 @@ Follows is a source code listing for all files that have been open sourced. This collision = false if @rotation >= 0 - if (current_area < min_area && + if (current_area < min_area && current_area > 0 && args.state.ball.center.y > @y1 && args.state.ball.center.x < @x2) @@ -7951,7 +7366,7 @@ Follows is a source code listing for all files that have been open sourced. This collision = true end else - if (current_area < min_area && + if (current_area < min_area && current_area > 0 && args.state.ball.center.y > @y2 && args.state.ball.center.x > @x1) @@ -7959,7 +7374,7 @@ Follows is a source code listing for all files that have been open sourced. This collision = true end end - + return collision end @@ -7980,7 +7395,7 @@ Follows is a source code listing for all files that have been open sourced. This x:args.state.ball.center.x-args.state.ball.velocity.x, y:args.state.ball.center.y-args.state.ball.velocity.y } - + velocityMag = (args.state.ball.velocity.x**2 + args.state.ball.velocity.y**2)**0.5 # the current velocity magnitude of the ball theta_ball = Math.atan2(args.state.ball.velocity.y, args.state.ball.velocity.x) #the angle of the ball's velocity theta_repel = (180 * DEGREES_TO_RADIANS) - theta_ball + (@rotation * DEGREES_TO_RADIANS) @@ -8002,7 +7417,7 @@ Follows is a source code listing for all files that have been open sourced. This dampener = 0.3 ynew *= dampener * 0.5 - + #If the bounce is very low, that means the ball is rolling and we don't want to dampenen the X velocity if ynew > -0.1 xnew *= dampener @@ -8011,7 +7426,7 @@ Follows is a source code listing for all files that have been open sourced. This #Add the sine component of gravity back in (X component) gravity_x = 4 * Math.sin(@rotation * DEGREES_TO_RADIANS) xnew += gravity_x - + args.state.ball.velocity.x = -xnew args.state.ball.velocity.y = -ynew @@ -10425,12 +9840,23 @@ Follows is a source code listing for all files that have been open sourced. This # 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 + init_new_game render_board input_board end + def init_new_game + state.current_turn ||= :x + state.space_combinations ||= [-1, 0, 1].product([-1, 0, 1]).to_a + + state.spaces ||= {} + + state.space_combinations.each do |x, y| + state.spaces[x] ||= {} + state.spaces[x][y] ||= state.new_entity(:space) + 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 @@ -10506,6 +9932,7 @@ Follows is a source code listing for all files that have been open sourced. This def input_restart_game return unless state.game_over gtk.reset + init_new_game end # Checks if x or o won the game. @@ -10822,7 +10249,7 @@ Follows is a source code listing for all files that have been open sourced. This def calc_kill_zombie # 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 } + killed_this_frame = state.zombies.find_all { |z| z.sprite && (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 @@ -11650,6 +11077,1126 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Audio - Audio Mixer - main.rb +#+begin_src ruby + # ./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 + + 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.state.selected = id + end + + # 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 + + audio_entry = args.audio[args.state.selected] + results = args.state.panel + + 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 + + def render_sources args + args.outputs.primitives << args.audio.keys.map do |k| + s = args.audio[k] + + isselected = (k == args.state.selected) + + color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ] + [ + [s.screenx, s.screeny, args.state.boxsize, args.state.boxsize, *color].solid, + + { + 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 + + def playtime_str t + return "" unless 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 + + 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 + + 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 + + { + rect: final_rect, + primitives: [ + (final_rect.to_solid color) + ] + } + end + + 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) + + final_rect = baseline_progress_bar.center_inside_rect(outer_rect) + center = final_rect.rect_center_point + + { + 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 + + def panel_primitives args, audio_entry + results = { primitives: [] } + + return results unless audio_entry + + # 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 + + # args.outputs.debug << args.layout.debug_primitives(r: 255, g: 255, b: 255) + + white_color = { r: 255, g: 255, b: 255 } + label_style = white_color.merge(vertical_alignment_enum: 1) + + # 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) + + # 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) + + # seperator line + results.primitives << args.layout.rect(row: 1, col: 0, w: 7, h: 0) + .line!(white_color) + + # screen location + results.primitives << args.layout.point(row: 1.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "screen:") + + 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})") + + # position + results.primitives << args.layout.point(row: 1.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "position:") + + 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]})") + + results.primitives << args.layout.point(row: 2.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "pitch:") + + 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) + + results.primitives << results.pitch_slider_rect.primitives + + results.primitives << args.layout.point(row: 2.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "playtime:") + + results.playtime_slider_rect = progress_bar(args: args, + row: 2.5, + col: 2, + percentage: (audio_entry.playtime || 1) / (audio_entry.length_ || 1), + text: "#{playtime_str(audio_entry.playtime)} / #{playtime_str(audio_entry.length_)}") + + results.primitives << results.playtime_slider_rect.primitives + + results.primitives << args.layout.point(row: 3.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "gain:") + + results.gain_slider_rect = progress_bar(args: args, + row: 3.0, + col: 2, + percentage: audio_entry.gain, + text: "#{audio_entry.gain.to_sf}") + + results.primitives << results.gain_slider_rect.primitives + + + results.primitives << args.layout.point(row: 3.5, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "looping:") + + checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2) + + results.looping_checkbox_rect = check_box(args: args, row: 3.5, col: 2, checked: audio_entry.looping) + results.primitives << results.looping_checkbox_rect.primitives + + results.primitives << args.layout.point(row: 4.0, col: 0, row_anchor: 0.5) + .merge(label_style) + .merge(text: "paused:") + + checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2) + + results.paused_checkbox_rect = check_box(args: args, row: 4.0, col: 2, checked: !audio_entry.paused) + results.primitives << results.paused_checkbox_rect.primitives + + results.delete_button_rect = { rect: args.layout.rect(row: 5, col: 0, w: 7, h: 1) } + + results.primitives << results.delete_button_rect.to_solid(r: 180) + + results.primitives << args.layout.point(row: 5, col: 3.5, row_anchor: 0.5) + .merge(label_style) + .merge(text: "DELETE", alignment_enum: 1) + + return results + end + + def render_panel args + args.state.panel = nil + audio_entry = args.audio[args.state.selected] + return unless audio_entry + + mouse_down = (args.state.mouse_held >= 0) + args.state.panel = panel_primitives args, audio_entry + args.outputs.primitives << args.state.panel.primitives + end + + def new_sound_id! args + args.state.sound_id ||= 0 + args.state.sound_id += 1 + args.state.sound_id + end + + def render_launcher args + args.outputs.primitives << args.state.spawn_sound_buttons.map(&:primitives) + end + + def render_ui args + render_launcher args + render_panel args + end + + def tick args + defaults args + render args + input args + end + + def input args + if !args.audio[args.state.selected] + args.state.selected = nil + args.state.dragging = nil + end + + # 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 } + + 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 + + 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 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 + + 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 + +#+end_src + +*** Advanced Audio - Audio Mixer - server_ip_address.txt +#+begin_src ruby + # ./samples/07_advanced_audio/01_audio_mixer/app/server_ip_address.txt + 192.168.1.65 +#+end_src + +*** Advanced Audio - Sound Synthesis - main.rb +#+begin_src ruby + # ./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 + + 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 + + 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 + + + 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 + + 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 + + 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 } + + args.audio.find_all { |k, v| v[:decay_rate] } + .each { |k, v| v[:gain] -= v[:decay_rate] } + + sounds_to_stop = args.audio + .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] } + .map { |k, v| k } + + sounds_to_stop.each { |k| args.audio.delete k } + 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)) + + button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0 + + label_offset_x = 5 + label_offset_y = 30 + + 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 + + def play_sine_wave args, sender + queue_sine_wave args, + frequency: sender[:frequency], + duration: 1.seconds, + fade_out: true + 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 + + send method_to_call, args, + frequency: (frequency_for note: sender[:note], octave: sender[:octave]), + duration: 1.seconds, + fade_out: 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), + ] + 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), + ] + 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 + +#+end_src + +*** Advanced Rendering - Labels With Wrapped Text - main.rb +#+begin_src ruby + # ./samples/07_advanced_rendering/00_labels_with_wrapped_text/app/main.rb + def tick args + # defaults + args.state.scroll_location ||= 0 + args.state.textbox.messages ||= [] + args.state.textbox.scroll ||= 0 + + # render + args.outputs.background_color = [0, 0, 0, 255] + render_messages args + render_instructions args + + # inputs + if args.inputs.keyboard.key_down.one + queue_message args, "Hello there neighbour! my name is mark, how is your day today?" + end + + if args.inputs.keyboard.key_down.two + queue_message args, "I'm doing great sir, actually I'm having a picnic today" + end + + if args.inputs.keyboard.key_down.three + queue_message args, "Well that sounds wonderful!" + end + + if args.inputs.keyboard.key_down.home + args.state.scroll_location = 1 + end + + if args.inputs.keyboard.key_down.delete + clear_message_queue args + end + end + + def queue_message args, msg + args.state.textbox.messages.concat msg.wrapped_lines 50 + end + + def clear_message_queue args + args.state.textbox.messages = nil + args.state.textbox.scroll = 0 + end + + def render_messages args + args.outputs[:textbox].w = 400 + args.outputs[:textbox].h = 720 + + args.outputs.primitives << args.state.textbox.messages.each_with_index.map do |s, idx| + { + x: 0, + y: 20 * (args.state.textbox.messages.size - idx) + args.state.textbox.scroll * 20, + text: s, + size_enum: -3, + alignment_enum: 0, + r: 255, g:255, b: 255, a: 255 + } + end + + args.outputs[:textbox].labels << args.state.textbox.messages.each_with_index.map do |s, idx| + { + x: 0, + y: 20 * (args.state.textbox.messages.size - idx) + args.state.textbox.scroll * 20, + text: s, + size_enum: -3, + alignment_enum: 0, + r: 255, g:255, b: 255, a: 255 + } + end + + args.outputs[:textbox].borders << [0, 0, args.outputs[:textbox].w, 720] + + args.state.textbox.scroll += args.inputs.mouse.wheel.y unless args.inputs.mouse.wheel.nil? + + if args.state.scroll_location > 0 + args.state.textbox.scroll = 0 + args.state.scroll_location = 0 + end + + args.outputs.sprites << [900, 0, args.outputs[:textbox].w, 720, :textbox] + end + + def render_instructions args + args.outputs.labels << [30, + 30.from_top, + "press 1, 2, 3 to display messages, MOUSE WHEEL to scroll, HOME to go to top, BACKSPACE to delete.", + 0, 255, 255] + + args.outputs.primitives << [0, 55.from_top, 1280, 30, :pixel, 0, 255, 0, 0, 0].sprite + end + +#+end_src + +*** Advanced Rendering - Rotating Label - main.rb +#+begin_src ruby + # ./samples/07_advanced_rendering/00_rotating_label/app/main.rb + def tick args + # set the render target width and height to match the label + args.outputs[:scene].w = 220 + args.outputs[:scene].h = 30 + + + # make the background transparent + args.outputs[:scene].background_color = [255, 255, 255, 0] + + # set the blendmode of the label to 0 (no blending) + # center it inside of the scene + # set the vertical_alignment_enum to 1 (center) + args.outputs[:scene].labels << { x: 0, + y: 15, + text: "label in render target", + blendmode_enum: 0, + vertical_alignment_enum: 1 } + + # add a border to the render target + args.outputs[:scene].borders << { x: 0, + y: 0, + w: args.outputs[:scene].w, + h: args.outputs[:scene].h } + + # add the rendertarget to the main output as a sprite + args.outputs.sprites << { x: 640 - args.outputs[:scene].w.half, + y: 360 - args.outputs[:scene].h.half, + w: args.outputs[:scene].w, + h: args.outputs[:scene].h, + angle: args.state.tick_count, + path: :scene } + end + +#+end_src + *** Advanced Rendering - Simple Render Targets - main.rb #+begin_src ruby # ./samples/07_advanced_rendering/01_simple_render_targets/app/main.rb @@ -12602,7 +13149,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 << { @@ -12616,7 +13163,7 @@ Follows is a source code listing for all files that have been open sourced. This b: 50, a: 255, # transparency font: "fonts/manaspc.ttf" # font style - }.label + }.label! # Outputs solid as primitive using a hash args.outputs.primitives << { @@ -12628,7 +13175,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 50, b: 50, a: 255 # transparency - }.solid + }.solid! # Outputs border as primitive using a hash # Same parameters as solid @@ -12641,7 +13188,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 50, b: 50, a: 255 # transparency - }.border + }.border! # Outputs line as primitive using a hash args.outputs.primitives << { @@ -12653,7 +13200,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 50, b: 50, a: 255 # transparency - }.line + }.line! end #+end_src @@ -12705,9 +13252,108 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Rendering - Simple Camera - main.rb +#+begin_src ruby + # ./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 + +#+end_src + *** Advanced Rendering - Splitscreen Camera - main.rb #+begin_src ruby - # ./samples/07_advanced_rendering/07_splitscreen_camera/app/main.rb + # ./samples/07_advanced_rendering/08_splitscreen_camera/app/main.rb class CameraMovement attr_accessor :state, :inputs, :outputs, :grid @@ -12716,7 +13362,7 @@ Follows is a source code listing for all files that have been open sourced. This def serialize {state: state, inputs: inputs, outputs: outputs, grid: grid } end - + def inspect serialize.to_s end @@ -12786,12 +13432,12 @@ Follows is a source code listing for all files that have been open sourced. This default_player(0, 0, 64, 64, "sprites/player/player_#{state.player_cyan.orientation}_standing.png") end - + def new_player_magenta default_player(64, 0, 64, 64, "sprites/player/player_#{state.player_magenta.orientation}_standing.png") end - + def new_camera_magenta default_camera(0,0,720,720) end @@ -12803,7 +13449,8 @@ Follows is a source code listing for all files that have been open sourced. This def new_camera_center default_camera(0,0,1280,720) end - + + def new_room default_floor_tile(0,0,1024,1024,'sprites/rooms/camera_room.png') end @@ -12828,7 +13475,7 @@ Follows is a source code listing for all files that have been open sourced. This state.player_cyan.x += state.player_cyan.dx state.player_cyan.y += state.player_cyan.dy end - + def calc_player_magenta state.player_magenta.x += state.player_magenta.dx state.player_magenta.y += state.player_magenta.dy @@ -12843,7 +13490,7 @@ Follows is a source code listing for all files that have been open sourced. This state.camera_center.x += (targetX - state.camera_center.x) * 0.1 * timeScale state.camera_center.y += (targetY - state.camera_center.y) * 0.1 * timeScale end - + def calc_camera_magenta timeScale = 1 @@ -12880,7 +13527,7 @@ Follows is a source code listing for all files that have been open sourced. This def calc_trauma_decay state.trauma = state.trauma * 0.9 end - + def calc_random_float_range(min, max) rand * (max-min) + min end @@ -12908,7 +13555,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255} end - + def render_player_magenta outputs[:scene].sprites << {x: state.player_magenta.x, y: state.player_magenta.y, @@ -12954,16 +13601,16 @@ Follows is a source code listing for all files that have been open sourced. This render_camera_magenta_scene render_camera_cyan_scene - angle = Math.atan((state.player_magenta.y - state.player_cyan.y)/(state.player_magenta.x- state.player_cyan.x)) * 180/Math::PI + angle = Math.atan((state.player_magenta.y - state.player_cyan.y)/(state.player_magenta.x- state.player_cyan.x)) * 180/Math::PI output_split_camera angle - + end def render_camera_magenta_scene zoomFactor = 1 offsetX = 32 offsetY = 32 - + outputs[:scene_magenta].sprites << {x: (-state.camera_magenta.x*2), y: (-state.camera_magenta.y), w: outputs[:scene].width*2, @@ -12989,7 +13636,7 @@ Follows is a source code listing for all files that have been open sourced. This outputs.labels << [128,64,"#{quadrant}",8,2,255,0,255,255] if quadrant == 1 set_camera_attributes(w: 640, h: 720, m_x: 640, m_y: 0, c_x: 0, c_y: 0) - + elsif quadrant == 2 set_camera_attributes(w: 1280, h: 360, m_x: 0, m_y: 360, c_x: 0, c_y: 0) @@ -13007,7 +13654,7 @@ Follows is a source code listing for all files that have been open sourced. This state.camera_cyan.h = h + 64 outputs[:scene_cyan].width = (w) * 2 outputs[:scene_cyan].height = h - + state.camera_magenta.w = w + 64 state.camera_magenta.h = h + 64 outputs[:scene_magenta].width = (w) * 2 @@ -13019,7 +13666,7 @@ Follows is a source code listing for all files that have been open sourced. This path: :scene_magenta} outputs.sprites << {x: c_x, y: c_y, - w: w, + w: w, h: h, path: :scene_cyan} end @@ -13027,7 +13674,7 @@ Follows is a source code listing for all files that have been open sourced. This def add_trauma amount state.trauma = [state.trauma + amount, 1.0].min end - + def remove_trauma amount state.trauma = [state.trauma - amount, 0.0].max end @@ -13067,7 +13714,7 @@ Follows is a source code listing for all files that have been open sourced. This outputs.labels << [128,512,"#{state.player_cyan.x.round()}",8,2,0,255,255,255] outputs.labels << [128,480,"#{state.player_cyan.y.round()}",8,2,0,255,255,255] end - + def input_move_magenta if inputs.keyboard.key_held.w state.player_magenta.dy = 5 @@ -13101,14 +13748,14 @@ Follows is a source code listing for all files that have been open sourced. This $camera_movement.outputs = args.outputs $camera_movement.state = args.state $camera_movement.grid = args.grid - $camera_movement.tick + $camera_movement.tick end #+end_src *** Advanced Rendering - Z Targeting Camera - main.rb #+begin_src ruby - # ./samples/07_advanced_rendering/08_z_targeting_camera/app/main.rb + # ./samples/07_advanced_rendering/09_z_targeting_camera/app/main.rb class Game attr_gtk @@ -13218,6 +13865,114 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Rendering - Blend Modes - main.rb +#+begin_src ruby + # ./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 + +#+end_src + +*** Advanced Rendering - Render Target Noclear - main.rb +#+begin_src ruby + # ./samples/07_advanced_rendering/11_render_target_noclear/app/main.rb + def tick args + args.state.x ||= 500 + args.state.y ||= 350 + args.state.xinc ||= 7 + args.state.yinc ||= 7 + args.state.bgcolor ||= 1 + args.state.bginc ||= 1 + + # clear the render target on the first tick, and then never again. Draw + # another box to it every tick, accumulating over time. + clear_target = (args.state.tick_count == 0) || (args.inputs.keyboard.key_down.space) + args.render_target(:accumulation).background_color = [ 0, 0, 0, 0 ]; + args.render_target(:accumulation).clear_before_render = clear_target + args.render_target(:accumulation).solids << [args.state.x, args.state.y, 25, 25, 255, 0, 0, 255]; + args.state.x += args.state.xinc + args.state.y += args.state.yinc + args.state.bgcolor += args.state.bginc + + # animation upkeep...change where we draw the next box and what color the + # window background will be. + if args.state.xinc > 0 && args.state.x >= 1280 + args.state.xinc = -7 + elsif args.state.xinc < 0 && args.state.x < 0 + args.state.xinc = 7 + end + + if args.state.yinc > 0 && args.state.y >= 720 + args.state.yinc = -7 + elsif args.state.yinc < 0 && args.state.y < 0 + args.state.yinc = 7 + end + + if args.state.bginc > 0 && args.state.bgcolor >= 255 + args.state.bginc = -1 + elsif args.state.bginc < 0 && args.state.bgcolor <= 0 + args.state.bginc = 1 + end + + # clear the screen to a shade of blue and draw the render target, which + # is not clearing every frame, on top of it. Note that you can NOT opt to + # skip clearing the screen, only render targets. The screen clears every + # frame; double-buffering would prevent correct updates between frames. + args.outputs.background_color = [ 0, 0, args.state.bgcolor, 255 ] + args.outputs.sprites << [ 0, 0, 1280, 720, :accumulation ] + end + + $gtk.reset + +#+end_src + *** Tweening Lerping Easing Functions - Easing Functions - main.rb #+begin_src ruby # ./samples/08_tweening_lerping_easing_functions/01_easing_functions/app/main.rb @@ -13710,7 +14465,11 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -13780,7 +14539,99 @@ Follows is a source code listing for all files that have been open sourced. This # 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 + + # init + if args.state.tick_count == 0 + args.state.stars = args.state.star_count.map { |i| new_star args } + end + + # update + args.state.stars.each { |s| move_star args, s } + + # render + args.outputs.sprites << args.state.stars + args.outputs.background_color = [0, 0, 0] + args.outputs.primitives << args.gtk.current_framerate_primitives + end + + # resets game, and assigns star count given by user + def reset_with count: count + $gtk.reset + $gtk.args.state.star_count = count + end + +#+end_src + +*** Performance - Sprites As Struct - main.rb +#+begin_src ruby + # ./samples/09_performance/03_sprites_as_struct/app/main.rb + # create a Struct variant that allows for named parameters on construction. + class NamedStruct < Struct + def initialize **opts + super(*members.map { |k| opts[k] }) + end + end + + # create a Star NamedStruct + Star = NamedStruct.new(:x, :y, :w, :h, :path, :s, + :angle, :angle_anchor_x, :angle_anchor_y, + :r, :g, :b, :a, + :tile_x, :tile_y, + :tile_w, :tile_h, + :source_x, :source_y, + :source_w, :source_h, + :flip_horizontally, :flip_vertically, + :blendmode_enum) + + # Sprites represented as Structs. They require a little bit more code than Hashes, + # but are the a little faster to render too. + def random_x args + (args.grid.w.randomize :ratio) * -1 + end + + def random_y args + (args.grid.h.randomize :ratio) * -1 + end + + def random_speed + 1 + (4.randomize :ratio) + end + + def new_star args + Star.new x: (random_x args), + y: (random_y args), + w: 4, h: 4, + path: 'sprites/tiny-star.png', + s: random_speed + end + + def move_star args, star + star.x += star[:s] + star.y += star[:s] + if star.x > args.grid.w || star.y > args.grid.h + star.x = (random_x args) + star.y = (random_y args) + star[:s] = random_speed + end + end + + def tick args + args.state.star_count ||= 0 + + # sets console command when sample app initially opens + if Kernel.global_tick_count == 0 + puts "" + puts "" + puts "=========================================================" + puts "* INFO: Sprites, Structs" + puts "* INFO: Please specify the number of sprites to render." args.gtk.console.set_command "reset_with count: 100" end @@ -13808,7 +14659,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Performance - Sprites As Strict Entities - main.rb #+begin_src ruby - # ./samples/09_performance/03_sprites_as_strict_entities/app/main.rb + # ./samples/09_performance/04_sprites_as_strict_entities/app/main.rb # Sprites represented as StrictEntities using the queue ~args.outputs.sprites~ # yields apis access similar to Entities, but all properties that can be set on the # entity must be predefined with a default value. Strict entities do not support the @@ -13854,7 +14705,11 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -13882,7 +14737,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Performance - Sprites As Classes - main.rb #+begin_src ruby - # ./samples/09_performance/04_sprites_as_classes/app/main.rb + # ./samples/09_performance/05_sprites_as_classes/app/main.rb # Sprites represented as Classes using the queue ~args.outputs.sprites~. # gives you full control of property declaration and method invocation. # They are more performant than OpenEntities and StrictEntities, but more code upfront. @@ -13911,6 +14766,11 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -13938,7 +14798,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Performance - Static Sprites As Classes - main.rb #+begin_src ruby - # ./samples/09_performance/05_static_sprites_as_classes/app/main.rb + # ./samples/09_performance/06_static_sprites_as_classes/app/main.rb # Sprites represented as Classes using the queue ~args.outputs.static_sprites~. # bypasses the queue behavior of ~args.outputs.sprites~. All instances are held # by reference. You get better performance, but you are mutating state of held objects @@ -13968,19 +14828,24 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -13995,7 +14860,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Performance - Static Sprites As Classes With Custom Drawing - main.rb #+begin_src ruby - # ./samples/09_performance/06_static_sprites_as_classes_with_custom_drawing/app/main.rb + # ./samples/09_performance/07_static_sprites_as_classes_with_custom_drawing/app/main.rb # Sprites represented as Classes, with a draw_override method, and using the queue ~args.outputs.static_sprites~. # is the fastest approach. This is comparable to what other game engines set as the default behavior. # There are tradeoffs for all this speed if the creation of a full blown class, and bypassing @@ -14038,10 +14903,21 @@ Follows is a source code listing for all files that have been open sourced. This # 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 @@ -14049,6 +14925,11 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -14073,7 +14954,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Performance - Collision Limits - main.rb #+begin_src ruby - # ./samples/09_performance/07_collision_limits/app/main.rb + # ./samples/09_performance/08_collision_limits/app/main.rb =begin Reminders: @@ -14132,6 +15013,31 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Debugging - Logging - main.rb +#+begin_src ruby + # ./samples/10_advanced_debugging/00_logging/app/main.rb + def tick args + args.outputs.background_color = [255, 255, 255, 0] + if args.state.tick_count == 0 + args.gtk.log_spam "log level spam" + args.gtk.log_debug "log level debug" + args.gtk.log_info "log level info" + args.gtk.log_warn "log level warn" + args.gtk.log_error "log level error" + args.gtk.log_unfiltered "log level unfiltered" + puts "This is a puts call" + args.gtk.console.show + end + + if args.state.tick_count == 60 + puts "This is a puts call on tick 60" + elsif args.state.tick_count == 120 + puts "This is a puts call on tick 120" + end + end + +#+end_src + *** Advanced Debugging - Trace Debugging - main.rb #+begin_src ruby # ./samples/10_advanced_debugging/01_trace_debugging/app/main.rb @@ -14219,6 +15125,55 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Debugging - Unit Tests - benchmark_api_tests.rb +#+begin_src ruby + # ./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 + +#+end_src + *** Advanced Debugging - Unit Tests - exception_raising_tests.rb #+begin_src ruby # ./samples/10_advanced_debugging/03_unit_tests/exception_raising_tests.rb @@ -14739,6 +15694,142 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Advanced Debugging - Unit Tests - pretty_format_tests.rb +#+begin_src ruby + # ./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 + +#+end_src + *** Advanced Debugging - Unit Tests - require_tests.rb #+begin_src ruby # ./samples/10_advanced_debugging/03_unit_tests/require_tests.rb @@ -14790,12 +15881,12 @@ Follows is a source code listing for all files that have been open sourced. This GTK::Entity.__reset_id__! args.state.player_one = "test" result = args.gtk.serialize_state args.state - assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>4, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" GTK::Entity.__reset_id__! args.gtk.write_file 'state.txt', '' result = args.gtk.serialize_state 'state.txt', args.state - assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>4, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" end def test_deserialize args, assert @@ -14844,7 +15935,7 @@ Follows is a source code listing for all files that have been open sourced. This args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken", blood_type: nil) serialized_state = args.gtk.serialize_state args.state - assert.equal! serialized_state, '{:entity_id=>9, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}' + assert.equal! serialized_state, '{:entity_id=>7, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}' GTK::Entity.__reset_id__! deserialized_state = args.gtk.deserialize_state serialized_state @@ -14901,6 +15992,18 @@ Follows is a source code listing for all files that have been open sourced. This deserialized_state = args.gtk.deserialize_state serialized_state assert.equal! deserialized_state.strict_entity.one, deserialized_state.strict_entity.two end + + def test_serialization_excludes_thrash_count args, assert + GTK::Entity.__reset_id__! + args.state.player.name = "Ryu" + # force a nil pun + if args.state.player.age > 30 + end + assert.equal! args.state.player.as_hash[:__thrash_count__][:>], 1 + result = args.gtk.serialize_state args.state + assert.false! (result.include? "__thrash_count__"), + "The __thrash_count__ key exists in state when it shouldn't have." + end #+end_src @@ -15063,11 +16166,13 @@ Follows is a source code listing for all files that have been open sourced. This *** Http - Retrieve Images - main.rb #+begin_src ruby # ./samples/11_http/01_retrieve_images/app/main.rb + $gtk.register_cvar 'app.warn_seconds', "seconds to wait before starting", :uint, 11 + def tick args args.outputs.background_color = [0, 0, 0] # Show a warning at the start. - args.state.warning_debounce ||= 11 * 60 + args.state.warning_debounce ||= args.cvars['app.warn_seconds'].value * 60 if args.state.warning_debounce > 0 args.state.warning_debounce -= 1 args.outputs.labels << [640, 600, "This app shows random images from the Internet.", 10, 1, 255, 255, 255] @@ -15119,11 +16224,12 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src -*** Http - Web Server - main.rb +*** Http - In Game Web Server Http Get - main.rb #+begin_src ruby - # ./samples/11_http/02_web_server/app/main.rb + # ./samples/11_http/02_in_game_web_server_http_get/app/main.rb def tick args args.state.port ||= 3000 + args.state.reqnum ||= 0 # by default the embedded webserver runs on port 9001 (the port number is over 9000) and is disabled in a production build # to enable the http server in a production build, you need to manually start # the server up: @@ -15152,6 +16258,84 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Http - In Game Web Server Http Post - main.rb +#+begin_src ruby + # ./samples/11_http/03_in_game_web_server_http_post/app/main.rb + def tick args + # defaults + args.state.post_button = args.layout.rect(row: 0, col: 0, w: 5, h: 1).merge(text: "execute http_post") + args.state.post_body_button = args.layout.rect(row: 1, col: 0, w: 5, h: 1).merge(text: "execute http_post_body") + args.state.request_to_s ||= "" + args.state.request_body ||= "" + + # render + args.state.post_button.yield_self do |b| + args.outputs.borders << b + args.outputs.labels << b.merge(text: b.text, + y: b.y + 30, + x: b.x + 10) + end + + args.state.post_body_button.yield_self do |b| + args.outputs.borders << b + args.outputs.labels << b.merge(text: b.text, + y: b.y + 30, + x: b.x + 10) + end + + draw_label args, 0, 6, "Request:", args.state.request_to_s + draw_label args, 0, 14, "Request Body Unaltered:", args.state.request_body + + # input + if args.inputs.mouse.click + # ============= HTTP_POST ============= + if (args.inputs.mouse.inside_rect? args.state.post_button) + # ========= DATA TO SEND =========== + form_fields = { "userId" => "#{Time.now.to_i}" } + # ================================== + + args.gtk.http_post "http://localhost:9001/testing", + form_fields, + ["Content-Type: application/x-www-form-urlencoded"] + + args.gtk.notify! "http_post" + end + + # ============= HTTP_POST_BODY ============= + if (args.inputs.mouse.inside_rect? args.state.post_body_button) + # =========== DATA TO SEND ============== + json = "{ \"userId\": \"#{Time.now.to_i}\"}" + # ================================== + + args.gtk.http_post_body "http://localhost:9001/testing", + json, + ["Content-Type: application/json", "Content-Length: #{json.length}"] + + args.gtk.notify! "http_post_body" + end + end + + # calc + args.inputs.http_requests.each do |r| + puts "#{r}" + if r.uri == "/testing" + puts r + args.state.request_to_s = "#{r}" + args.state.request_body = r.raw_body + r.respond 200, "ok" + end + end + end + + def draw_label args, row, col, header, text + label_pos = args.layout.rect(row: row, col: col, w: 0, h: 0) + args.outputs.labels << "#{header}\n\n#{text}".wrapped_lines(80).map_with_index do |l, i| + { x: label_pos.x, y: label_pos.y - (i * 15), text: l, size_enum: -2 } + end + end + +#+end_src + *** C Extensions - Basics - main.rb #+begin_src ruby # ./samples/12_c_extensions/01_basics/app/main.rb @@ -16588,8 +17772,8 @@ Follows is a source code listing for all files that have been open sourced. This calc # Calc Path end - render - input + render + input end def defaults @@ -16666,7 +17850,7 @@ Follows is a source code listing for all files that have been open sourced. This [24, 9] => true, [25, 8] => true, [25, 9] => true, - } + } # Variables that are used by the breadth first search # Storing cells that the search has visited, prevents unnecessary steps @@ -16685,36 +17869,36 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.current_input ||= :none + state.current_input ||= :none end def calc # Setup the search to start from the star - search.frontier << grid.star + search.frontier << grid.star search.came_from[grid.star] = nil # Until there are no more cells to expand from - until search.frontier.empty? + until search.frontier.empty? # Takes the next frontier cell - new_frontier = search.frontier.shift + new_frontier = search.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless search.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) + unless search.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited in the first grid # Unless the target has been visited # Add the neighbor to the frontier and remember which cell it came from - search.frontier << neighbor + search.frontier << neighbor search.came_from[neighbor] = new_frontier end end end end - + # Draws everything onto the screen def render - render_background + render_background # render_heat_map render_walls # render_path @@ -16733,7 +17917,7 @@ Follows is a source code listing for all files that have been open sourced. This if current_cell && parent_cell outputs.lines << [(current_cell.x + 0.5) * grid.cell_size, (current_cell.y + 0.5) * grid.cell_size, (parent_cell.x + 0.5) * grid.cell_size, (parent_cell.y + 0.5) * grid.cell_size, purple] - + end render_trail(parent_cell) end @@ -16759,8 +17943,8 @@ Follows is a source code listing for all files that have been open sourced. This # Draws what the grid looks like with nothing on it def render_background - render_unvisited - render_grid_lines + render_unvisited + render_grid_lines end # Draws both grids @@ -16775,7 +17959,7 @@ Follows is a source code listing for all files that have been open sourced. This end for y in 0..grid.height - outputs.lines << horizontal_line(y) + outputs.lines << horizontal_line(y) end end @@ -16791,7 +17975,7 @@ Follows is a source code listing for all files that have been open sourced. This # Draws the walls on both grids def render_walls - grid.walls.each_key do |wall| + grid.walls.each_key do |wall| outputs.solids << [scale_up(wall), wall_color] end end @@ -16799,12 +17983,12 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the star on both grids def render_star outputs.sprites << [scale_up(grid.star), 'star.png'] - end + end # Renders the target on both grids def render_target outputs.sprites << [scale_up(grid.target), 'target.png'] - end + end # Labels the grids def render_labels @@ -16864,14 +18048,14 @@ Follows is a source code listing for all files that have been open sourced. This # The program has to remember that the user is dragging an object # even when the mouse is no longer over that object # So detecting input and processing input is separate - # detect_input - # process_input + # detect_input + # process_input if inputs.mouse.up state.current_input = :none elsif star_clicked? state.current_input = :star end - + if mouse_inside_grid? unless grid.target == cell_closest_to_mouse grid.target = cell_closest_to_mouse @@ -16889,33 +18073,33 @@ Follows is a source code listing for all files that have been open sourced. This # mouse left click is held def detect_input # When the mouse is up, nothing is being edited - if inputs.mouse.up - state.current_input = :none + if inputs.mouse.up + state.current_input = :none # When the star in the no second grid is clicked - elsif star_clicked? - state.current_input = :star + elsif star_clicked? + state.current_input = :star # When the target in the no second grid is clicked - elsif target_clicked? - state.current_input = :target + elsif target_clicked? + state.current_input = :target # When a wall in the first grid is clicked - elsif wall_clicked? - state.current_input = :remove_wall + elsif wall_clicked? + state.current_input = :remove_wall # When the first grid is clicked - elsif grid_clicked? + elsif grid_clicked? state.current_input = :add_wall end end # Processes click and drag based on what the user is currently dragging def process_input - if state.current_input == :star - input_star - elsif state.current_input == :target - input_target - elsif state.current_input == :remove_wall - input_remove_wall - elsif state.current_input == :add_wall - input_add_wall + if state.current_input == :star + input_star + elsif state.current_input == :target + input_target + elsif state.current_input == :remove_wall + input_remove_wall + elsif state.current_input == :add_wall + input_add_wall end end @@ -16923,10 +18107,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def input_star - old_star = grid.star.clone + old_star = grid.star.clone grid.star = cell_closest_to_mouse - unless old_star == grid.star - reset_search + unless old_star == grid.star + reset_search end end @@ -16934,10 +18118,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchs the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def input_target - old_target = grid.target.clone + old_target = grid.target.clone grid.target = cell_closest_to_mouse - unless old_target == grid.target - reset_search + unless old_target == grid.target + reset_search end end @@ -16946,32 +18130,32 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if mouse_inside_grid? + if mouse_inside_grid? if grid.walls.has_key?(cell_closest_to_mouse) - grid.walls.delete(cell_closest_to_mouse) - reset_search + grid.walls.delete(cell_closest_to_mouse) + reset_search end end end # Adds a wall in the first grid in the cell the mouse is over def input_add_wall - if mouse_inside_grid? + if mouse_inside_grid? unless grid.walls.has_key?(cell_closest_to_mouse) - grid.walls[cell_closest_to_mouse] = true - reset_search + grid.walls[cell_closest_to_mouse] = true + reset_search end end end - + # Whenever the user edits the grid, # The search has to be reset_searchd upto the current step # with the current grid as the initial state of the grid def reset_search # Reset_Searchs the search - search.frontier = [] - search.came_from = {} + search.frontier = [] + search.came_from = {} search.path = {} end @@ -16979,21 +18163,21 @@ Follows is a source code listing for all files that have been open sourced. This # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x, cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y] unless cell.x == 0 - neighbors << [cell.x, cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y] unless cell.x == grid.width - 1 + neighbors << [cell.x, cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y] unless cell.x == 0 + neighbors << [cell.x, cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y] unless cell.x == grid.width - 1 # Sorts the neighbors so the rendered path is a zigzag path # Cells in a diagonal direction are given priority # Comment this line to see the difference neighbors = neighbors.sort_by { |neighbor_x, neighbor_y| proximity_to_star(neighbor_x, neighbor_y) } - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -17018,13 +18202,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # Signal that the user is going to be moving the star from the first grid @@ -17052,13 +18236,13 @@ Follows is a source code listing for all files that have been open sourced. This # Light brown def unvisited_color - [221, 212, 213] + [221, 212, 213] # [255, 255, 255] end # Camo Green def wall_color - [134, 134, 120] + [134, 134, 120] end # Pastel White @@ -17096,7 +18280,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Breadth First Search tick is called - $breadcrumbs ||= Breadcrumbs.new(args) + $breadcrumbs ||= Breadcrumbs.new $breadcrumbs.args = args $breadcrumbs.tick end @@ -17153,8 +18337,8 @@ Follows is a source code listing for all files that have been open sourced. This # And calculate the path calc_path end - render - input + render + input end def defaults @@ -17170,14 +18354,14 @@ Follows is a source code listing for all files that have been open sourced. This # This step is roughly the grid's width * height # When anim_steps equals max_steps no more calculations will occur # and the slider will be at the end - state.max_steps ||= args.state.grid.width * args.state.grid.height + state.max_steps ||= args.state.grid.width * args.state.grid.height # The location of the star and walls of the grid # They can be modified to have a different initial grid # Walls are stored in a hash for quick look up when doing the search state.star ||= [2, 8] state.target ||= [10, 5] - state.walls ||= {} + state.walls ||= {} # Variables that are used by the breadth first search # Storing cells that the search has visited, prevents unnecessary steps @@ -17200,12 +18384,12 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.current_input ||= :none + state.current_input ||= :none end # Draws everything onto the screen def render - render_background + render_background render_heat_map render_walls render_path @@ -17218,8 +18402,8 @@ Follows is a source code listing for all files that have been open sourced. This # Draws what the grid looks like with nothing on it def render_background - render_unvisited - render_grid_lines + render_unvisited + render_grid_lines end # Draws both grids @@ -17236,7 +18420,7 @@ Follows is a source code listing for all files that have been open sourced. This end for y in 0..grid.height - outputs.lines << horizontal_line(y) + outputs.lines << horizontal_line(y) outputs.lines << early_exit_horizontal_line(y) end end @@ -17263,7 +18447,7 @@ Follows is a source code listing for all files that have been open sourced. This # Draws the walls on both grids def render_walls - state.walls.each_key do |wall| + state.walls.each_key do |wall| outputs.solids << [scale_up(wall), wall_color] outputs.solids << [early_exit_scale_up(wall), wall_color] end @@ -17273,13 +18457,13 @@ Follows is a source code listing for all files that have been open sourced. This def render_star outputs.sprites << [scale_up(state.star), 'star.png'] outputs.sprites << [early_exit_scale_up(state.star), 'star.png'] - end + end # Renders the target on both grids def render_target outputs.sprites << [scale_up(state.target), 'target.png'] outputs.sprites << [early_exit_scale_up(state.target), 'target.png'] - end + end # Labels the grids def render_labels @@ -17371,8 +18555,8 @@ Follows is a source code listing for all files that have been open sourced. This # The program has to remember that the user is dragging an object # even when the mouse is no longer over that object # So detecting input and processing input is separate - detect_input - process_input + detect_input + process_input end # Determines what the user is editing and stores the value @@ -17380,53 +18564,53 @@ Follows is a source code listing for all files that have been open sourced. This # mouse left click is held def detect_input # When the mouse is up, nothing is being edited - if inputs.mouse.up - state.current_input = :none + if inputs.mouse.up + state.current_input = :none # When the star in the no second grid is clicked - elsif star_clicked? - state.current_input = :star + elsif star_clicked? + state.current_input = :star # When the star in the second grid is clicked - elsif star2_clicked? - state.current_input = :star2 + elsif star2_clicked? + state.current_input = :star2 # When the target in the no second grid is clicked - elsif target_clicked? - state.current_input = :target + elsif target_clicked? + state.current_input = :target # When the target in the second grid is clicked - elsif target2_clicked? - state.current_input = :target2 + elsif target2_clicked? + state.current_input = :target2 # When a wall in the first grid is clicked - elsif wall_clicked? - state.current_input = :remove_wall + elsif wall_clicked? + state.current_input = :remove_wall # When a wall in the second grid is clicked - elsif wall2_clicked? + elsif wall2_clicked? state.current_input = :remove_wall2 # When the first grid is clicked - elsif grid_clicked? + elsif grid_clicked? state.current_input = :add_wall # When the second grid is clicked - elsif grid2_clicked? + elsif grid2_clicked? state.current_input = :add_wall2 end end # Processes click and drag based on what the user is currently dragging def process_input - if state.current_input == :star - input_star + if state.current_input == :star + input_star elsif state.current_input == :star2 - input_star2 - elsif state.current_input == :target - input_target - elsif state.current_input == :target2 - input_target2 - elsif state.current_input == :remove_wall - input_remove_wall + input_star2 + elsif state.current_input == :target + input_target + elsif state.current_input == :target2 + input_target2 + elsif state.current_input == :remove_wall + input_remove_wall elsif state.current_input == :remove_wall2 - input_remove_wall2 - elsif state.current_input == :add_wall - input_add_wall - elsif state.current_input == :add_wall2 - input_add_wall2 + input_remove_wall2 + elsif state.current_input == :add_wall + input_add_wall + elsif state.current_input == :add_wall2 + input_add_wall2 end end @@ -17434,10 +18618,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def input_star - old_star = state.star.clone + old_star = state.star.clone state.star = cell_closest_to_mouse - unless old_star == state.star - reset_search + unless old_star == state.star + reset_search end end @@ -17445,10 +18629,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def input_star2 - old_star = state.star.clone + old_star = state.star.clone state.star = cell_closest_to_mouse2 - unless old_star == state.star - reset_search + unless old_star == state.star + reset_search end end @@ -17456,10 +18640,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchs the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def input_target - old_target = state.target.clone + old_target = state.target.clone state.target = cell_closest_to_mouse - unless old_target == state.target - reset_search + unless old_target == state.target + reset_search end end @@ -17467,10 +18651,10 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchs the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def input_target2 - old_target = state.target.clone + old_target = state.target.clone state.target = cell_closest_to_mouse2 - unless old_target == state.target - reset_search + unless old_target == state.target + reset_search end end @@ -17479,10 +18663,10 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if mouse_inside_grid? + if mouse_inside_grid? if state.walls.has_key?(cell_closest_to_mouse) - state.walls.delete(cell_closest_to_mouse) - reset_search + state.walls.delete(cell_closest_to_mouse) + reset_search end end end @@ -17492,31 +18676,31 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if mouse_inside_grid2? + if mouse_inside_grid2? if state.walls.has_key?(cell_closest_to_mouse2) - state.walls.delete(cell_closest_to_mouse2) - reset_search + state.walls.delete(cell_closest_to_mouse2) + reset_search end end end # Adds a wall in the first grid in the cell the mouse is over def input_add_wall - if mouse_inside_grid? + if mouse_inside_grid? unless state.walls.has_key?(cell_closest_to_mouse) - state.walls[cell_closest_to_mouse] = true - reset_search + state.walls[cell_closest_to_mouse] = true + reset_search end end end - + # Adds a wall in the second grid in the cell the mouse is over def input_add_wall2 - if mouse_inside_grid2? + if mouse_inside_grid2? unless state.walls.has_key?(cell_closest_to_mouse2) - state.walls[cell_closest_to_mouse2] = true - reset_search + state.walls[cell_closest_to_mouse2] = true + reset_search end end end @@ -17526,10 +18710,10 @@ Follows is a source code listing for all files that have been open sourced. This # with the current grid as the initial state of the grid def reset_search # Reset_Searchs the search - state.frontier = [] - state.visited = {} - state.early_exit_visited = {} - state.came_from = {} + state.frontier = [] + state.visited = {} + state.early_exit_visited = {} + state.came_from = {} state.path = {} end @@ -17537,23 +18721,23 @@ Follows is a source code listing for all files that have been open sourced. This def step # The setup to the search # Runs once when there are no visited cells - if state.visited.empty? - state.visited[state.star] = true - state.early_exit_visited[state.star] = true - state.frontier << state.star + if state.visited.empty? + state.visited[state.star] = true + state.early_exit_visited[state.star] = true + state.frontier << state.star state.came_from[state.star] = nil end # A step in the search - unless state.frontier.empty? + unless state.frontier.empty? # Takes the next frontier cell - new_frontier = state.frontier.shift + new_frontier = state.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless state.visited.has_key?(neighbor) || state.walls.has_key?(neighbor) + unless state.visited.has_key?(neighbor) || state.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited in the first grid - state.visited[neighbor] = true + state.visited[neighbor] = true # Unless the target has been visited unless state.visited.has_key?(state.target) # Mark the neighbor as visited in the second grid as well @@ -17561,32 +18745,32 @@ Follows is a source code listing for all files that have been open sourced. This end # Add the neighbor to the frontier and remember which cell it came from - state.frontier << neighbor + state.frontier << neighbor state.came_from[neighbor] = new_frontier end end end end - + # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x, cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y] unless cell.x == 0 - neighbors << [cell.x, cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y] unless cell.x == grid.width - 1 + neighbors << [cell.x, cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y] unless cell.x == 0 + neighbors << [cell.x, cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y] unless cell.x == grid.width - 1 # Sorts the neighbors so the rendered path is a zigzag path # Cells in a diagonal direction are given priority # Comment this line to see the difference neighbors = neighbors.sort_by { |neighbor_x, neighbor_y| proximity_to_star(neighbor_x, neighbor_y) } - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -17611,13 +18795,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -17625,15 +18809,15 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the second grid helps with this def cell_closest_to_mouse2 # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= grid.width + 1 # Bound x and y to the first grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # Signal that the user is going to be moving the star from the first grid @@ -17712,12 +18896,12 @@ Follows is a source code listing for all files that have been open sourced. This # Light brown def unvisited_color - [221, 212, 213] + [221, 212, 213] end # Camo Green def wall_color - [134, 134, 120] + [134, 134, 120] end # Pastel White @@ -17747,7 +18931,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Breadth First Search tick is called - $early_exit_breadth_first_search ||= EarlyExitBreadthFirstSearch.new(args) + $early_exit_breadth_first_search ||= EarlyExitBreadthFirstSearch.new $early_exit_breadth_first_search.args = args $early_exit_breadth_first_search.tick end @@ -17783,8 +18967,8 @@ Follows is a source code listing for all files that have been open sourced. This # The next step in the search is calculated def tick defaults - render - input + render + input calc end @@ -17801,7 +18985,7 @@ Follows is a source code listing for all files that have been open sourced. This # Walls are stored in a hash for quick look up when doing the search state.star ||= [1, 5] state.target ||= [8, 4] - state.walls ||= {[1, 1] => true, [2, 1] => true, [3, 1] => true, [1, 2] => true, [2, 2] => true, [3, 2] => true} + state.walls ||= {[1, 1] => true, [2, 1] => true, [3, 1] => true, [1, 2] => true, [2, 2] => true, [3, 2] => true} state.hills ||= { [4, 1] => true, [5, 1] => true, @@ -17836,7 +19020,7 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.user_input ||= :none + state.user_input ||= :none # Values that are used for the breadth first search # Keeping track of what cells were visited prevents counting cells multiple times @@ -17860,7 +19044,7 @@ Follows is a source code listing for all files that have been open sourced. This # Draws everything onto the screen def render - render_background + render_background render_heat_maps @@ -17875,8 +19059,8 @@ Follows is a source code listing for all files that have been open sourced. This # Draws what the grid looks like with nothing on it def render_background - render_unvisited - render_grid_lines + render_unvisited + render_grid_lines render_labels end @@ -17895,7 +19079,7 @@ Follows is a source code listing for all files that have been open sourced. This end for y in 0..grid.height - outputs.lines << horizontal_line(y) + outputs.lines << horizontal_line(y) outputs.lines << shifted_horizontal_line(y) end end @@ -18008,16 +19192,16 @@ Follows is a source code listing for all files that have been open sourced. This def render_star outputs.sprites << [scale_up(state.star), 'star.png'] outputs.sprites << [move_and_scale_up(state.star), 'star.png'] - end + end # Renders the target on both grids def render_target outputs.sprites << [scale_up(state.target), 'target.png'] outputs.sprites << [move_and_scale_up(state.target), 'target.png'] - end + end def render_hills - state.hills.each_key do |hill| + state.hills.each_key do |hill| outputs.solids << [scale_up(hill), hill_color] outputs.solids << [move_and_scale_up(hill), hill_color] end @@ -18025,7 +19209,7 @@ Follows is a source code listing for all files that have been open sourced. This # Draws the walls on both grids def render_walls - state.walls.each_key do |wall| + state.walls.each_key do |wall| outputs.solids << [scale_up(wall), wall_color] outputs.solids << [move_and_scale_up(wall), wall_color] end @@ -18108,7 +19292,7 @@ Follows is a source code listing for all files that have been open sourced. This # If the mouse was clicked this tick if inputs.mouse.down # Determine what the user is editing and edit the state.user_input variable - determine_input + determine_input end # Process user input based on user_input variable and current mouse position @@ -18121,43 +19305,43 @@ Follows is a source code listing for all files that have been open sourced. This # mouse left click is held def determine_input # If the mouse is over the star in the first grid - if mouse_over_star? + if mouse_over_star? # The user is editing the star from the first grid - state.user_input = :star + state.user_input = :star # If the mouse is over the star in the second grid - elsif mouse_over_star2? + elsif mouse_over_star2? # The user is editing the star from the second grid - state.user_input = :star2 + state.user_input = :star2 # If the mouse is over the target in the first grid - elsif mouse_over_target? + elsif mouse_over_target? # The user is editing the target from the first grid - state.user_input = :target + state.user_input = :target # If the mouse is over the target in the second grid - elsif mouse_over_target2? + elsif mouse_over_target2? # The user is editing the target from the second grid - state.user_input = :target2 + state.user_input = :target2 # If the mouse is over a wall in the first grid - elsif mouse_over_wall? + elsif mouse_over_wall? # The user is removing a wall from the first grid - state.user_input = :remove_wall + state.user_input = :remove_wall # If the mouse is over a wall in the second grid - elsif mouse_over_wall2? + elsif mouse_over_wall2? # The user is removing a wall from the second grid state.user_input = :remove_wall2 # If the mouse is over a hill in the first grid - elsif mouse_over_hill? + elsif mouse_over_hill? # The user is adding a wall from the first grid - state.user_input = :add_wall + state.user_input = :add_wall # If the mouse is over a hill in the second grid - elsif mouse_over_hill2? + elsif mouse_over_hill2? # The user is adding a wall from the second grid state.user_input = :add_wall2 # If the mouse is over the first grid - elsif mouse_over_grid? + elsif mouse_over_grid? # The user is adding a hill from the first grid state.user_input = :add_hill # If the mouse is over the second grid - elsif mouse_over_grid2? + elsif mouse_over_grid2? # The user is adding a hill from the second grid state.user_input = :add_hill2 end @@ -18165,26 +19349,26 @@ Follows is a source code listing for all files that have been open sourced. This # Processes click and drag based on what the user is currently dragging def process_input - if state.user_input == :star - input_star + if state.user_input == :star + input_star elsif state.user_input == :star2 - input_star2 - elsif state.user_input == :target - input_target - elsif state.user_input == :target2 - input_target2 - elsif state.user_input == :remove_wall - input_remove_wall + input_star2 + elsif state.user_input == :target + input_target + elsif state.user_input == :target2 + input_target2 + elsif state.user_input == :remove_wall + input_remove_wall elsif state.user_input == :remove_wall2 - input_remove_wall2 - elsif state.user_input == :add_hill - input_add_hill - elsif state.user_input == :add_hill2 - input_add_hill2 - elsif state.user_input == :add_wall - input_add_wall - elsif state.user_input == :add_wall2 - input_add_wall2 + input_remove_wall2 + elsif state.user_input == :add_hill + input_add_hill + elsif state.user_input == :add_hill2 + input_add_hill2 + elsif state.user_input == :add_wall + input_add_wall + elsif state.user_input == :add_wall2 + input_add_wall2 end end @@ -18197,26 +19381,26 @@ Follows is a source code listing for all files that have been open sourced. This calc_dijkstra end end - + def calc_breadth_first # Sets up the Breadth First Search - breadth_first_search.visited[state.star] = true - breadth_first_search.frontier << state.star + breadth_first_search.visited[state.star] = true + breadth_first_search.frontier << state.star breadth_first_search.came_from[state.star] = nil until breadth_first_search.frontier.empty? return if breadth_first_search.visited.has_key?(state.target) # A step in the search # Takes the next frontier cell - new_frontier = breadth_first_search.frontier.shift + new_frontier = breadth_first_search.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do | neighbor | + adjacent_neighbors(new_frontier).each do | neighbor | # That have not been visited and are not walls - unless breadth_first_search.visited.has_key?(neighbor) || state.walls.has_key?(neighbor) + unless breadth_first_search.visited.has_key?(neighbor) || state.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited in the first grid - breadth_first_search.visited[neighbor] = true - breadth_first_search.frontier << neighbor + breadth_first_search.visited[neighbor] = true + breadth_first_search.frontier << neighbor # Remember which cell the neighbor came from breadth_first_search.came_from[neighbor] = new_frontier end @@ -18276,12 +19460,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def input_star - old_star = state.star.clone + old_star = state.star.clone unless cell_closest_to_mouse == state.target - state.star = cell_closest_to_mouse + state.star = cell_closest_to_mouse end - unless old_star == state.star - reset_search + unless old_star == state.star + reset_search end end @@ -18289,12 +19473,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def input_star2 - old_star = state.star.clone + old_star = state.star.clone unless cell_closest_to_mouse2 == state.target state.star = cell_closest_to_mouse2 end - unless old_star == state.star - reset_search + unless old_star == state.star + reset_search end end @@ -18302,12 +19486,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchs the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def input_target - old_target = state.target.clone + old_target = state.target.clone unless cell_closest_to_mouse == state.star state.target = cell_closest_to_mouse end - unless old_target == state.target - reset_search + unless old_target == state.target + reset_search end end @@ -18315,12 +19499,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchs the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def input_target2 - old_target = state.target.clone + old_target = state.target.clone unless cell_closest_to_mouse2 == state.star state.target = cell_closest_to_mouse2 end - unless old_target == state.target - reset_search + unless old_target == state.target + reset_search end end @@ -18329,11 +19513,11 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if mouse_over_grid? + if mouse_over_grid? if state.walls.has_key?(cell_closest_to_mouse) or state.hills.has_key?(cell_closest_to_mouse) - state.walls.delete(cell_closest_to_mouse) - state.hills.delete(cell_closest_to_mouse) - reset_search + state.walls.delete(cell_closest_to_mouse) + state.hills.delete(cell_closest_to_mouse) + reset_search end end end @@ -18343,21 +19527,21 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if mouse_over_grid2? + if mouse_over_grid2? if state.walls.has_key?(cell_closest_to_mouse2) or state.hills.has_key?(cell_closest_to_mouse2) - state.walls.delete(cell_closest_to_mouse2) - state.hills.delete(cell_closest_to_mouse2) - reset_search + state.walls.delete(cell_closest_to_mouse2) + state.hills.delete(cell_closest_to_mouse2) + reset_search end end end # Adds a hill in the first grid in the cell the mouse is over def input_add_hill - if mouse_over_grid? + if mouse_over_grid? unless state.hills.has_key?(cell_closest_to_mouse) - state.hills[cell_closest_to_mouse] = true - reset_search + state.hills[cell_closest_to_mouse] = true + reset_search end end end @@ -18365,32 +19549,32 @@ Follows is a source code listing for all files that have been open sourced. This # Adds a hill in the second grid in the cell the mouse is over def input_add_hill2 - if mouse_over_grid2? + if mouse_over_grid2? unless state.hills.has_key?(cell_closest_to_mouse2) - state.hills[cell_closest_to_mouse2] = true - reset_search + state.hills[cell_closest_to_mouse2] = true + reset_search end end end # Adds a wall in the first grid in the cell the mouse is over def input_add_wall - if mouse_over_grid? + if mouse_over_grid? unless state.walls.has_key?(cell_closest_to_mouse) - state.hills.delete(cell_closest_to_mouse) - state.walls[cell_closest_to_mouse] = true - reset_search + state.hills.delete(cell_closest_to_mouse) + state.walls[cell_closest_to_mouse] = true + reset_search end end end # Adds a wall in the second grid in the cell the mouse is over def input_add_wall2 - if mouse_over_grid2? + if mouse_over_grid2? unless state.walls.has_key?(cell_closest_to_mouse2) - state.hills.delete(cell_closest_to_mouse2) - state.walls[cell_closest_to_mouse2] = true - reset_search + state.hills.delete(cell_closest_to_mouse2) + state.walls[cell_closest_to_mouse2] = true + reset_search end end end @@ -18413,21 +19597,21 @@ Follows is a source code listing for all files that have been open sourced. This # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x , cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 - neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 + neighbors << [cell.x , cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 + neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 # Sorts the neighbors so the rendered path is a zigzag path # Cells in a diagonal direction are given priority # Comment this line to see the difference neighbors = neighbors.sort_by { |neighbor_x, neighbor_y| proximity_to_star(neighbor_x, neighbor_y) } - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -18452,13 +19636,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -18466,17 +19650,17 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the second grid helps with this def cell_closest_to_mouse2 # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= grid.width + 1 # Bound x and y to the first grid x = 0 if x < 0 y = 0 if y < 0 - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # Signal that the user is going to be moving the star from the first grid @@ -18549,12 +19733,12 @@ Follows is a source code listing for all files that have been open sourced. This # Light brown def unvisited_color - [221, 212, 213] + [221, 212, 213] end # Camo Green def wall_color - [134, 134, 120] + [134, 134, 120] end # Pastel White @@ -18597,7 +19781,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Dijkstra tick method is called - $movement_costs ||= Movement_Costs.new(args) + $movement_costs ||= Movement_Costs.new $movement_costs.args = args $movement_costs.tick end @@ -18624,13 +19808,13 @@ Follows is a source code listing for all files that have been open sourced. This def tick defaults - render - input + render + input # If animation is playing, and max steps have not been reached # Move the search a step forward if state.play && state.current_step < state.max_steps # Variable that tells the program what step to recalculate up to - state.current_step += 1 + state.current_step += 1 move_searches_one_step_forward end end @@ -18652,7 +19836,7 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.user_input ||= :none + state.user_input ||= :none # These variables allow the breadth first search to take place # Came_from is a hash with a key of a cell and a value of the cell that was expanded from to find the key. @@ -18677,7 +19861,7 @@ Follows is a source code listing for all files that have been open sourced. This # Unless the current step has a value unless state.current_step # Set the current step to 10 - state.current_step = 10 + state.current_step = 10 # And calculate the searches up to step 10 recalculate_searches end @@ -18687,7 +19871,7 @@ Follows is a source code listing for all files that have been open sourced. This # This step is roughly the grid's width * height # When anim_steps equals max_steps no more calculations will occur # and the slider will be at the end - state.max_steps = grid.width * grid.height + state.max_steps = grid.width * grid.height # Whether the animation should play or not # If true, every tick moves anim_steps forward one @@ -18780,7 +19964,7 @@ Follows is a source code listing for all files that have been open sourced. This # If the mouse was clicked this tick if inputs.mouse.down # Determine what the user is editing and appropriately edit the state.user_input variable - determine_input + determine_input end # Process user input based on user_input variable and current mouse position @@ -18793,35 +19977,35 @@ Follows is a source code listing for all files that have been open sourced. This if mouse_over_slider? state.user_input = :slider # If the mouse is over the star in the first grid - elsif bfs_mouse_over_star? + elsif bfs_mouse_over_star? # The user is editing the star from the first grid - state.user_input = :bfs_star + state.user_input = :bfs_star # If the mouse is over the star in the second grid - elsif heuristic_mouse_over_star? + elsif heuristic_mouse_over_star? # The user is editing the star from the second grid - state.user_input = :heuristic_star + state.user_input = :heuristic_star # If the mouse is over the target in the first grid - elsif bfs_mouse_over_target? + elsif bfs_mouse_over_target? # The user is editing the target from the first grid - state.user_input = :bfs_target + state.user_input = :bfs_target # If the mouse is over the target in the second grid - elsif heuristic_mouse_over_target? + elsif heuristic_mouse_over_target? # The user is editing the target from the second grid - state.user_input = :heuristic_target + state.user_input = :heuristic_target # If the mouse is over a wall in the first grid - elsif bfs_mouse_over_wall? + elsif bfs_mouse_over_wall? # The user is removing a wall from the first grid - state.user_input = :bfs_remove_wall + state.user_input = :bfs_remove_wall # If the mouse is over a wall in the second grid - elsif heuristic_mouse_over_wall? + elsif heuristic_mouse_over_wall? # The user is removing a wall from the second grid state.user_input = :heuristic_remove_wall # If the mouse is over the first grid - elsif bfs_mouse_over_grid? + elsif bfs_mouse_over_grid? # The user is adding a wall from the first grid state.user_input = :bfs_add_wall # If the mouse is over the second grid - elsif heuristic_mouse_over_grid? + elsif heuristic_mouse_over_grid? # The user is adding a wall from the second grid state.user_input = :heuristic_add_wall end @@ -18831,22 +20015,22 @@ Follows is a source code listing for all files that have been open sourced. This def process_input if state.user_input == :slider process_input_slider - elsif state.user_input == :bfs_star - process_input_bfs_star + elsif state.user_input == :bfs_star + process_input_bfs_star elsif state.user_input == :heuristic_star - process_input_heuristic_star - elsif state.user_input == :bfs_target - process_input_bfs_target - elsif state.user_input == :heuristic_target - process_input_heuristic_target - elsif state.user_input == :bfs_remove_wall - process_input_bfs_remove_wall + process_input_heuristic_star + elsif state.user_input == :bfs_target + process_input_bfs_target + elsif state.user_input == :heuristic_target + process_input_heuristic_target + elsif state.user_input == :bfs_remove_wall + process_input_bfs_remove_wall elsif state.user_input == :heuristic_remove_wall - process_input_heuristic_remove_wall - elsif state.user_input == :bfs_add_wall - process_input_bfs_add_wall - elsif state.user_input == :heuristic_add_wall - process_input_heuristic_add_wall + process_input_heuristic_remove_wall + elsif state.user_input == :bfs_add_wall + process_input_bfs_add_wall + elsif state.user_input == :heuristic_add_wall + process_input_heuristic_add_wall end end @@ -18923,7 +20107,7 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << bfs_horizontal_line(y) + outputs.lines << bfs_horizontal_line(y) end end @@ -18938,10 +20122,10 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << heuristic_horizontal_line(y) + outputs.lines << heuristic_horizontal_line(y) end end - + # Returns a vertical line for a column of the first grid def bfs_vertical_line column bfs_scale_up([column, 0, column, grid.height]) @@ -18976,7 +20160,7 @@ Follows is a source code listing for all files that have been open sourced. This def render_bfs_target outputs.sprites << [bfs_scale_up(grid.target), 'target.png'] end - + # Renders the target on the second grid def render_heuristic_target outputs.sprites << [heuristic_scale_up(grid.target), 'target.png'] @@ -18984,14 +20168,14 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the walls on the first grid def render_bfs_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [bfs_scale_up(wall), wall_color] end end # Renders the walls on the second grid def render_heuristic_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [heuristic_scale_up(wall), wall_color] end end @@ -19012,14 +20196,14 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the frontier cells on the first grid def render_bfs_frontier - bfs.frontier.each do | frontier_cell | + bfs.frontier.each do | frontier_cell | outputs.solids << [bfs_scale_up(frontier_cell), frontier_color, 200] end end # Renders the frontier cells on the second grid def render_heuristic_frontier - heuristic.frontier.each do | frontier_cell | + heuristic.frontier.each do | frontier_cell | outputs.solids << [heuristic_scale_up(frontier_cell), frontier_color, 200] end end @@ -19103,14 +20287,14 @@ Follows is a source code listing for all files that have been open sourced. This # Checks and handles input for the buttons # Called when the mouse is lifted def input_buttons - input_left_button - input_center_button - input_right_button + input_left_button + input_center_button + input_right_button end # Checks if the previous step button is clicked # If it is, it pauses the animation and moves the search one step backward - def input_left_button + def input_left_button if left_button_clicked? state.play = false state.current_step -= 1 @@ -19122,7 +20306,7 @@ Follows is a source code listing for all files that have been open sourced. This # Inverses whether the animation is playing or not when clicked def input_center_button if center_button_clicked? || inputs.keyboard.key_down.space - state.play = !state.play + state.play = !state.play end end @@ -19130,8 +20314,8 @@ Follows is a source code listing for all files that have been open sourced. This # If it is, it pauses the animation and moves the search one step forward def input_right_button if right_button_clicked? - state.play = false - state.current_step += 1 + state.play = false + state.current_step += 1 move_searches_one_step_forward end end @@ -19212,12 +20396,12 @@ Follows is a source code listing for all files that have been open sourced. This # on the slider # Changes the step of the search to be animated def process_input_slider - state.play = false + state.play = false mouse_x = inputs.mouse.point.x # Bounds the mouse_x to the closest x value on the slider line - mouse_x = slider.x if mouse_x < slider.x - mouse_x = slider.x + slider.w if mouse_x > slider.x + slider.w + mouse_x = slider.x if mouse_x < slider.x + mouse_x = slider.x + slider.w if mouse_x > slider.x + slider.w # Sets the current search step to the one represented by the mouse x value # The slider's circle moves due to the render_slider method using anim_steps @@ -19230,12 +20414,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_bfs_star - old_star = grid.star.clone + old_star = grid.star.clone unless bfs_cell_closest_to_mouse == grid.target - grid.star = bfs_cell_closest_to_mouse + grid.star = bfs_cell_closest_to_mouse end - unless old_star == grid.star - recalculate_searches + unless old_star == grid.star + recalculate_searches end end @@ -19243,12 +20427,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_heuristic_star - old_star = grid.star.clone + old_star = grid.star.clone unless heuristic_cell_closest_to_mouse == grid.target grid.star = heuristic_cell_closest_to_mouse end - unless old_star == grid.star - recalculate_searches + unless old_star == grid.star + recalculate_searches end end @@ -19256,12 +20440,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only recalculate_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_bfs_target - old_target = grid.target.clone + old_target = grid.target.clone unless bfs_cell_closest_to_mouse == grid.star grid.target = bfs_cell_closest_to_mouse end - unless old_target == grid.target - recalculate_searches + unless old_target == grid.target + recalculate_searches end end @@ -19269,12 +20453,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only recalculate_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_heuristic_target - old_target = grid.target.clone + old_target = grid.target.clone unless heuristic_cell_closest_to_mouse == grid.star grid.target = heuristic_cell_closest_to_mouse end - unless old_target == grid.target - recalculate_searches + unless old_target == grid.target + recalculate_searches end end @@ -19283,10 +20467,10 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if bfs_mouse_over_grid? + if bfs_mouse_over_grid? if grid.walls.has_key?(bfs_cell_closest_to_mouse) - grid.walls.delete(bfs_cell_closest_to_mouse) - recalculate_searches + grid.walls.delete(bfs_cell_closest_to_mouse) + recalculate_searches end end end @@ -19296,29 +20480,29 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if heuristic_mouse_over_grid? + if heuristic_mouse_over_grid? if grid.walls.has_key?(heuristic_cell_closest_to_mouse) - grid.walls.delete(heuristic_cell_closest_to_mouse) - recalculate_searches + grid.walls.delete(heuristic_cell_closest_to_mouse) + recalculate_searches end end end # Adds a wall in the first grid in the cell the mouse is over def process_input_bfs_add_wall - if bfs_mouse_over_grid? + if bfs_mouse_over_grid? unless grid.walls.has_key?(bfs_cell_closest_to_mouse) - grid.walls[bfs_cell_closest_to_mouse] = true - recalculate_searches + grid.walls[bfs_cell_closest_to_mouse] = true + recalculate_searches end end end # Adds a wall in the second grid in the cell the mouse is over def process_input_heuristic_add_wall - if heuristic_mouse_over_grid? + if heuristic_mouse_over_grid? unless grid.walls.has_key?(heuristic_cell_closest_to_mouse) - grid.walls[heuristic_cell_closest_to_mouse] = true - recalculate_searches + grid.walls[heuristic_cell_closest_to_mouse] = true + recalculate_searches end end end @@ -19328,13 +20512,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def bfs_cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -19342,17 +20526,17 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the second grid helps with this def heuristic_cell_closest_to_mouse # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= grid.width + 1 # Bound x and y to the first grid x = 0 if x < 0 y = 0 if y < 0 - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end def recalculate_searches @@ -19377,22 +20561,22 @@ Follows is a source code listing for all files that have been open sourced. This return if bfs.came_from.has_key?(grid.target) # Only runs at the beginning of the search as setup. - if bfs.came_from.empty? - bfs.frontier << grid.star - bfs.came_from[grid.star] = nil + if bfs.came_from.empty? + bfs.frontier << grid.star + bfs.came_from[grid.star] = nil end # A step in the search - unless bfs.frontier.empty? + unless bfs.frontier.empty? # Takes the next frontier cell - new_frontier = bfs.frontier.shift + new_frontier = bfs.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless bfs.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) + unless bfs.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - bfs.frontier << neighbor - bfs.came_from[neighbor] = new_frontier + bfs.frontier << neighbor + bfs.came_from[neighbor] = new_frontier end end end @@ -19447,12 +20631,12 @@ Follows is a source code listing for all files that have been open sourced. This # Get the next cell to explore from new_frontier = heuristic.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless heuristic.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) + unless heuristic.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - heuristic.frontier << neighbor - heuristic.came_from[neighbor] = new_frontier + heuristic.frontier << neighbor + heuristic.came_from[neighbor] = new_frontier end end end @@ -19496,16 +20680,16 @@ Follows is a source code listing for all files that have been open sourced. This # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x , cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 - neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 + neighbors << [cell.x , cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 + neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -19554,7 +20738,7 @@ Follows is a source code listing for all files that have been open sourced. This def wall_color [134, 134, 120] # Camo Green end - + def visited_color [204, 191, 179] # Dark Brown end @@ -19562,7 +20746,7 @@ Follows is a source code listing for all files that have been open sourced. This def frontier_color [103, 136, 204] # Blue end - + def path_color [231, 230, 228] # Pastel White end @@ -19583,7 +20767,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Breadth First Search tick is called - $heuristic_with_walls ||= Heuristic_With_Walls.new(args) + $heuristic_with_walls ||= Heuristic_With_Walls.new $heuristic_with_walls.args = args $heuristic_with_walls.tick end @@ -19610,13 +20794,13 @@ Follows is a source code listing for all files that have been open sourced. This def tick defaults - render - input + render + input # If animation is playing, and max steps have not been reached # Move the search a step forward if state.play && state.current_step < state.max_steps # Variable that tells the program what step to recalculate up to - state.current_step += 1 + state.current_step += 1 move_searches_one_step_forward end end @@ -19671,7 +20855,7 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.user_input ||= :none + state.user_input ||= :none # These variables allow the breadth first search to take place # Came_from is a hash with a key of a cell and a value of the cell that was expanded from to find the key. @@ -19696,7 +20880,7 @@ Follows is a source code listing for all files that have been open sourced. This # Unless the current step has a value unless state.current_step # Set the current step to 10 - state.current_step = 10 + state.current_step = 10 # And calculate the searches up to step 10 recalculate_searches end @@ -19706,7 +20890,7 @@ Follows is a source code listing for all files that have been open sourced. This # This step is roughly the grid's width * height # When anim_steps equals max_steps no more calculations will occur # and the slider will be at the end - state.max_steps = grid.width * grid.height + state.max_steps = grid.width * grid.height # Whether the animation should play or not # If true, every tick moves anim_steps forward one @@ -19799,7 +20983,7 @@ Follows is a source code listing for all files that have been open sourced. This # If the mouse was clicked this tick if inputs.mouse.down # Determine what the user is editing and appropriately edit the state.user_input variable - determine_input + determine_input end # Process user input based on user_input variable and current mouse position @@ -19812,35 +20996,35 @@ Follows is a source code listing for all files that have been open sourced. This if mouse_over_slider? state.user_input = :slider # If the mouse is over the star in the first grid - elsif bfs_mouse_over_star? + elsif bfs_mouse_over_star? # The user is editing the star from the first grid - state.user_input = :bfs_star + state.user_input = :bfs_star # If the mouse is over the star in the second grid - elsif heuristic_mouse_over_star? + elsif heuristic_mouse_over_star? # The user is editing the star from the second grid - state.user_input = :heuristic_star + state.user_input = :heuristic_star # If the mouse is over the target in the first grid - elsif bfs_mouse_over_target? + elsif bfs_mouse_over_target? # The user is editing the target from the first grid - state.user_input = :bfs_target + state.user_input = :bfs_target # If the mouse is over the target in the second grid - elsif heuristic_mouse_over_target? + elsif heuristic_mouse_over_target? # The user is editing the target from the second grid - state.user_input = :heuristic_target + state.user_input = :heuristic_target # If the mouse is over a wall in the first grid - elsif bfs_mouse_over_wall? + elsif bfs_mouse_over_wall? # The user is removing a wall from the first grid - state.user_input = :bfs_remove_wall + state.user_input = :bfs_remove_wall # If the mouse is over a wall in the second grid - elsif heuristic_mouse_over_wall? + elsif heuristic_mouse_over_wall? # The user is removing a wall from the second grid state.user_input = :heuristic_remove_wall # If the mouse is over the first grid - elsif bfs_mouse_over_grid? + elsif bfs_mouse_over_grid? # The user is adding a wall from the first grid state.user_input = :bfs_add_wall # If the mouse is over the second grid - elsif heuristic_mouse_over_grid? + elsif heuristic_mouse_over_grid? # The user is adding a wall from the second grid state.user_input = :heuristic_add_wall end @@ -19850,22 +21034,22 @@ Follows is a source code listing for all files that have been open sourced. This def process_input if state.user_input == :slider process_input_slider - elsif state.user_input == :bfs_star - process_input_bfs_star + elsif state.user_input == :bfs_star + process_input_bfs_star elsif state.user_input == :heuristic_star - process_input_heuristic_star - elsif state.user_input == :bfs_target - process_input_bfs_target - elsif state.user_input == :heuristic_target - process_input_heuristic_target - elsif state.user_input == :bfs_remove_wall - process_input_bfs_remove_wall + process_input_heuristic_star + elsif state.user_input == :bfs_target + process_input_bfs_target + elsif state.user_input == :heuristic_target + process_input_heuristic_target + elsif state.user_input == :bfs_remove_wall + process_input_bfs_remove_wall elsif state.user_input == :heuristic_remove_wall - process_input_heuristic_remove_wall - elsif state.user_input == :bfs_add_wall - process_input_bfs_add_wall - elsif state.user_input == :heuristic_add_wall - process_input_heuristic_add_wall + process_input_heuristic_remove_wall + elsif state.user_input == :bfs_add_wall + process_input_bfs_add_wall + elsif state.user_input == :heuristic_add_wall + process_input_heuristic_add_wall end end @@ -19942,7 +21126,7 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << bfs_horizontal_line(y) + outputs.lines << bfs_horizontal_line(y) end end @@ -19957,10 +21141,10 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << heuristic_horizontal_line(y) + outputs.lines << heuristic_horizontal_line(y) end end - + # Returns a vertical line for a column of the first grid def bfs_vertical_line column bfs_scale_up([column, 0, column, grid.height]) @@ -19995,7 +21179,7 @@ Follows is a source code listing for all files that have been open sourced. This def render_bfs_target outputs.sprites << [bfs_scale_up(grid.target), 'target.png'] end - + # Renders the target on the second grid def render_heuristic_target outputs.sprites << [heuristic_scale_up(grid.target), 'target.png'] @@ -20003,14 +21187,14 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the walls on the first grid def render_bfs_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [bfs_scale_up(wall), wall_color] end end # Renders the walls on the second grid def render_heuristic_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [heuristic_scale_up(wall), wall_color] end end @@ -20031,14 +21215,14 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the frontier cells on the first grid def render_bfs_frontier - bfs.frontier.each do | frontier_cell | + bfs.frontier.each do | frontier_cell | outputs.solids << [bfs_scale_up(frontier_cell), frontier_color, 200] end end # Renders the frontier cells on the second grid def render_heuristic_frontier - heuristic.frontier.each do | frontier_cell | + heuristic.frontier.each do | frontier_cell | outputs.solids << [heuristic_scale_up(frontier_cell), frontier_color, 200] end end @@ -20122,14 +21306,14 @@ Follows is a source code listing for all files that have been open sourced. This # Checks and handles input for the buttons # Called when the mouse is lifted def input_buttons - input_left_button - input_center_button - input_right_button + input_left_button + input_center_button + input_right_button end # Checks if the previous step button is clicked # If it is, it pauses the animation and moves the search one step backward - def input_left_button + def input_left_button if left_button_clicked? state.play = false state.current_step -= 1 @@ -20141,7 +21325,7 @@ Follows is a source code listing for all files that have been open sourced. This # Inverses whether the animation is playing or not when clicked def input_center_button if center_button_clicked? || inputs.keyboard.key_down.space - state.play = !state.play + state.play = !state.play end end @@ -20149,8 +21333,8 @@ Follows is a source code listing for all files that have been open sourced. This # If it is, it pauses the animation and moves the search one step forward def input_right_button if right_button_clicked? - state.play = false - state.current_step += 1 + state.play = false + state.current_step += 1 move_searches_one_step_forward end end @@ -20231,12 +21415,12 @@ Follows is a source code listing for all files that have been open sourced. This # on the slider # Changes the step of the search to be animated def process_input_slider - state.play = false + state.play = false mouse_x = inputs.mouse.point.x # Bounds the mouse_x to the closest x value on the slider line - mouse_x = slider.x if mouse_x < slider.x - mouse_x = slider.x + slider.w if mouse_x > slider.x + slider.w + mouse_x = slider.x if mouse_x < slider.x + mouse_x = slider.x + slider.w if mouse_x > slider.x + slider.w # Sets the current search step to the one represented by the mouse x value # The slider's circle moves due to the render_slider method using anim_steps @@ -20249,12 +21433,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_bfs_star - old_star = grid.star.clone + old_star = grid.star.clone unless bfs_cell_closest_to_mouse == grid.target - grid.star = bfs_cell_closest_to_mouse + grid.star = bfs_cell_closest_to_mouse end - unless old_star == grid.star - recalculate_searches + unless old_star == grid.star + recalculate_searches end end @@ -20262,12 +21446,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_heuristic_star - old_star = grid.star.clone + old_star = grid.star.clone unless heuristic_cell_closest_to_mouse == grid.target grid.star = heuristic_cell_closest_to_mouse end - unless old_star == grid.star - recalculate_searches + unless old_star == grid.star + recalculate_searches end end @@ -20275,12 +21459,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only recalculate_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_bfs_target - old_target = grid.target.clone + old_target = grid.target.clone unless bfs_cell_closest_to_mouse == grid.star grid.target = bfs_cell_closest_to_mouse end - unless old_target == grid.target - recalculate_searches + unless old_target == grid.target + recalculate_searches end end @@ -20288,12 +21472,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only recalculate_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_heuristic_target - old_target = grid.target.clone + old_target = grid.target.clone unless heuristic_cell_closest_to_mouse == grid.star grid.target = heuristic_cell_closest_to_mouse end - unless old_target == grid.target - recalculate_searches + unless old_target == grid.target + recalculate_searches end end @@ -20302,10 +21486,10 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if bfs_mouse_over_grid? + if bfs_mouse_over_grid? if grid.walls.has_key?(bfs_cell_closest_to_mouse) - grid.walls.delete(bfs_cell_closest_to_mouse) - recalculate_searches + grid.walls.delete(bfs_cell_closest_to_mouse) + recalculate_searches end end end @@ -20315,29 +21499,29 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if heuristic_mouse_over_grid? + if heuristic_mouse_over_grid? if grid.walls.has_key?(heuristic_cell_closest_to_mouse) - grid.walls.delete(heuristic_cell_closest_to_mouse) - recalculate_searches + grid.walls.delete(heuristic_cell_closest_to_mouse) + recalculate_searches end end end # Adds a wall in the first grid in the cell the mouse is over def process_input_bfs_add_wall - if bfs_mouse_over_grid? + if bfs_mouse_over_grid? unless grid.walls.has_key?(bfs_cell_closest_to_mouse) - grid.walls[bfs_cell_closest_to_mouse] = true - recalculate_searches + grid.walls[bfs_cell_closest_to_mouse] = true + recalculate_searches end end end # Adds a wall in the second grid in the cell the mouse is over def process_input_heuristic_add_wall - if heuristic_mouse_over_grid? + if heuristic_mouse_over_grid? unless grid.walls.has_key?(heuristic_cell_closest_to_mouse) - grid.walls[heuristic_cell_closest_to_mouse] = true - recalculate_searches + grid.walls[heuristic_cell_closest_to_mouse] = true + recalculate_searches end end end @@ -20347,13 +21531,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def bfs_cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -20361,17 +21545,17 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the second grid helps with this def heuristic_cell_closest_to_mouse # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= grid.width + 1 # Bound x and y to the first grid x = 0 if x < 0 y = 0 if y < 0 - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end def recalculate_searches @@ -20396,22 +21580,22 @@ Follows is a source code listing for all files that have been open sourced. This return if bfs.came_from.has_key?(grid.target) # Only runs at the beginning of the search as setup. - if bfs.came_from.empty? - bfs.frontier << grid.star - bfs.came_from[grid.star] = nil + if bfs.came_from.empty? + bfs.frontier << grid.star + bfs.came_from[grid.star] = nil end # A step in the search - unless bfs.frontier.empty? + unless bfs.frontier.empty? # Takes the next frontier cell - new_frontier = bfs.frontier.shift + new_frontier = bfs.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless bfs.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) + unless bfs.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - bfs.frontier << neighbor - bfs.came_from[neighbor] = new_frontier + bfs.frontier << neighbor + bfs.came_from[neighbor] = new_frontier end end end @@ -20466,12 +21650,12 @@ Follows is a source code listing for all files that have been open sourced. This # Get the next cell to explore from new_frontier = heuristic.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do |neighbor| + adjacent_neighbors(new_frontier).each do |neighbor| # That have not been visited and are not walls - unless heuristic.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) + unless heuristic.came_from.has_key?(neighbor) || grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - heuristic.frontier << neighbor - heuristic.came_from[neighbor] = new_frontier + heuristic.frontier << neighbor + heuristic.came_from[neighbor] = new_frontier end end end @@ -20515,16 +21699,16 @@ Follows is a source code listing for all files that have been open sourced. This # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x , cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 - neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 + neighbors << [cell.x , cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 + neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -20573,7 +21757,7 @@ Follows is a source code listing for all files that have been open sourced. This def wall_color [134, 134, 120] # Camo Green end - + def visited_color [204, 191, 179] # Dark Brown end @@ -20581,7 +21765,7 @@ Follows is a source code listing for all files that have been open sourced. This def frontier_color [103, 136, 204] # Blue end - + def path_color [231, 230, 228] # Pastel White end @@ -20602,7 +21786,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Breadth First Search tick is called - $heuristic ||= Heuristic.new(args) + $heuristic ||= Heuristic.new $heuristic.args = args $heuristic.tick end @@ -20630,8 +21814,8 @@ Follows is a source code listing for all files that have been open sourced. This def tick defaults - render - input + render + input if dijkstra.came_from.empty? calc_searches @@ -20684,7 +21868,7 @@ Follows is a source code listing for all files that have been open sourced. This # We store this value, because we want to remember the value even when # the user's cursor is no longer over what they're interacting with, but # they are still clicking down on the mouse. - state.user_input ||= :none + state.user_input ||= :none # These variables allow the breadth first search to take place # Came_from is a hash with a key of a cell and a value of the cell that was expanded from to find the key. @@ -20762,7 +21946,7 @@ Follows is a source code listing for all files that have been open sourced. This # If the mouse was clicked this tick if inputs.mouse.down # Determine what the user is editing and appropriately edit the state.user_input variable - determine_input + determine_input end # Process user input based on user_input variable and current mouse position @@ -20773,51 +21957,51 @@ Follows is a source code listing for all files that have been open sourced. This # This method is called when the mouse is clicked down def determine_input # If the mouse is over the star in the first grid - if dijkstra_mouse_over_star? + if dijkstra_mouse_over_star? # The user is editing the star from the first grid - state.user_input = :dijkstra_star + state.user_input = :dijkstra_star # If the mouse is over the star in the second grid - elsif greedy_mouse_over_star? + elsif greedy_mouse_over_star? # The user is editing the star from the second grid - state.user_input = :greedy_star + state.user_input = :greedy_star # If the mouse is over the star in the third grid - elsif a_star_mouse_over_star? + elsif a_star_mouse_over_star? # The user is editing the star from the third grid - state.user_input = :a_star_star + state.user_input = :a_star_star # If the mouse is over the target in the first grid - elsif dijkstra_mouse_over_target? + elsif dijkstra_mouse_over_target? # The user is editing the target from the first grid - state.user_input = :dijkstra_target + state.user_input = :dijkstra_target # If the mouse is over the target in the second grid - elsif greedy_mouse_over_target? + elsif greedy_mouse_over_target? # The user is editing the target from the second grid - state.user_input = :greedy_target + state.user_input = :greedy_target # If the mouse is over the target in the third grid - elsif a_star_mouse_over_target? + elsif a_star_mouse_over_target? # The user is editing the target from the third grid - state.user_input = :a_star_target + state.user_input = :a_star_target # If the mouse is over a wall in the first grid - elsif dijkstra_mouse_over_wall? + elsif dijkstra_mouse_over_wall? # The user is removing a wall from the first grid - state.user_input = :dijkstra_remove_wall + state.user_input = :dijkstra_remove_wall # If the mouse is over a wall in the second grid - elsif greedy_mouse_over_wall? + elsif greedy_mouse_over_wall? # The user is removing a wall from the second grid state.user_input = :greedy_remove_wall # If the mouse is over a wall in the third grid - elsif a_star_mouse_over_wall? + elsif a_star_mouse_over_wall? # The user is removing a wall from the third grid state.user_input = :a_star_remove_wall # If the mouse is over the first grid - elsif dijkstra_mouse_over_grid? + elsif dijkstra_mouse_over_grid? # The user is adding a wall from the first grid state.user_input = :dijkstra_add_wall # If the mouse is over the second grid - elsif greedy_mouse_over_grid? + elsif greedy_mouse_over_grid? # The user is adding a wall from the second grid state.user_input = :greedy_add_wall # If the mouse is over the third grid - elsif a_star_mouse_over_grid? + elsif a_star_mouse_over_grid? # The user is adding a wall from the third grid state.user_input = :a_star_add_wall end @@ -20825,30 +22009,30 @@ Follows is a source code listing for all files that have been open sourced. This # Processes click and drag based on what the user is currently dragging def process_input - if state.user_input == :dijkstra_star - process_input_dijkstra_star + if state.user_input == :dijkstra_star + process_input_dijkstra_star elsif state.user_input == :greedy_star - process_input_greedy_star + process_input_greedy_star elsif state.user_input == :a_star_star - process_input_a_star_star - elsif state.user_input == :dijkstra_target - process_input_dijkstra_target - elsif state.user_input == :greedy_target - process_input_greedy_target - elsif state.user_input == :a_star_target - process_input_a_star_target - elsif state.user_input == :dijkstra_remove_wall - process_input_dijkstra_remove_wall + process_input_a_star_star + elsif state.user_input == :dijkstra_target + process_input_dijkstra_target + elsif state.user_input == :greedy_target + process_input_greedy_target + elsif state.user_input == :a_star_target + process_input_a_star_target + elsif state.user_input == :dijkstra_remove_wall + process_input_dijkstra_remove_wall elsif state.user_input == :greedy_remove_wall - process_input_greedy_remove_wall + process_input_greedy_remove_wall elsif state.user_input == :a_star_remove_wall - process_input_a_star_remove_wall - elsif state.user_input == :dijkstra_add_wall - process_input_dijkstra_add_wall - elsif state.user_input == :greedy_add_wall - process_input_greedy_add_wall - elsif state.user_input == :a_star_add_wall - process_input_a_star_add_wall + process_input_a_star_remove_wall + elsif state.user_input == :dijkstra_add_wall + process_input_dijkstra_add_wall + elsif state.user_input == :greedy_add_wall + process_input_greedy_add_wall + elsif state.user_input == :a_star_add_wall + process_input_a_star_add_wall end end @@ -20863,7 +22047,7 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << dijkstra_horizontal_line(y) + outputs.lines << dijkstra_horizontal_line(y) end end @@ -20878,7 +22062,7 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << greedy_horizontal_line(y) + outputs.lines << greedy_horizontal_line(y) end end @@ -20893,10 +22077,10 @@ Follows is a source code listing for all files that have been open sourced. This # The horizontal grid lines for y in 0..grid.height - outputs.lines << a_star_horizontal_line(y) + outputs.lines << a_star_horizontal_line(y) end end - + # Returns a vertical line for a column of the first grid def dijkstra_vertical_line column dijkstra_scale_up([column, 0, column, grid.height]) @@ -20946,7 +22130,7 @@ Follows is a source code listing for all files that have been open sourced. This def render_dijkstra_target outputs.sprites << [dijkstra_scale_up(grid.target), 'target.png'] end - + # Renders the target on the second grid def render_greedy_target outputs.sprites << [greedy_scale_up(grid.target), 'target.png'] @@ -20959,21 +22143,21 @@ Follows is a source code listing for all files that have been open sourced. This # Renders the walls on the first grid def render_dijkstra_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [dijkstra_scale_up(wall), wall_color] end end # Renders the walls on the second grid def render_greedy_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [greedy_scale_up(wall), wall_color] end end # Renders the walls on the third grid def render_a_star_walls - grid.walls.each_key do | wall | + grid.walls.each_key do | wall | outputs.solids << [a_star_scale_up(wall), wall_color] end end @@ -21173,12 +22357,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_dijkstra_star - old_star = grid.star.clone + old_star = grid.star.clone unless dijkstra_cell_closest_to_mouse == grid.target - grid.star = dijkstra_cell_closest_to_mouse + grid.star = dijkstra_cell_closest_to_mouse end - unless old_star == grid.star - reset_searches + unless old_star == grid.star + reset_searches end end @@ -21186,12 +22370,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_greedy_star - old_star = grid.star.clone + old_star = grid.star.clone unless greedy_cell_closest_to_mouse == grid.target grid.star = greedy_cell_closest_to_mouse end - unless old_star == grid.star - reset_searches + unless old_star == grid.star + reset_searches end end @@ -21199,12 +22383,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only resets the search if the star changes position # Called whenever the user is editing the star (puts mouse down on star) def process_input_a_star_star - old_star = grid.star.clone + old_star = grid.star.clone unless a_star_cell_closest_to_mouse == grid.target grid.star = a_star_cell_closest_to_mouse end - unless old_star == grid.star - reset_searches + unless old_star == grid.star + reset_searches end end @@ -21212,12 +22396,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_dijkstra_target - old_target = grid.target.clone + old_target = grid.target.clone unless dijkstra_cell_closest_to_mouse == grid.star grid.target = dijkstra_cell_closest_to_mouse end - unless old_target == grid.target - reset_searches + unless old_target == grid.target + reset_searches end end @@ -21225,12 +22409,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_greedy_target - old_target = grid.target.clone + old_target = grid.target.clone unless greedy_cell_closest_to_mouse == grid.star grid.target = greedy_cell_closest_to_mouse end - unless old_target == grid.target - reset_searches + unless old_target == grid.target + reset_searches end end @@ -21238,12 +22422,12 @@ Follows is a source code listing for all files that have been open sourced. This # Only reset_searchess the search if the target changes position # Called whenever the user is editing the target (puts mouse down on target) def process_input_a_star_target - old_target = grid.target.clone + old_target = grid.target.clone unless a_star_cell_closest_to_mouse == grid.star grid.target = a_star_cell_closest_to_mouse end - unless old_target == grid.target - reset_searches + unless old_target == grid.target + reset_searches end end @@ -21252,10 +22436,10 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if dijkstra_mouse_over_grid? + if dijkstra_mouse_over_grid? if grid.walls.has_key?(dijkstra_cell_closest_to_mouse) - grid.walls.delete(dijkstra_cell_closest_to_mouse) - reset_searches + grid.walls.delete(dijkstra_cell_closest_to_mouse) + reset_searches end end end @@ -21265,10 +22449,10 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if greedy_mouse_over_grid? + if greedy_mouse_over_grid? if grid.walls.has_key?(greedy_cell_closest_to_mouse) - grid.walls.delete(greedy_cell_closest_to_mouse) - reset_searches + grid.walls.delete(greedy_cell_closest_to_mouse) + reset_searches end end end @@ -21278,40 +22462,40 @@ Follows is a source code listing for all files that have been open sourced. This # The mouse needs to be inside the grid, because we only want to remove walls # the cursor is directly over # Recalculations should only occur when a wall is actually deleted - if a_star_mouse_over_grid? + if a_star_mouse_over_grid? if grid.walls.has_key?(a_star_cell_closest_to_mouse) - grid.walls.delete(a_star_cell_closest_to_mouse) - reset_searches + grid.walls.delete(a_star_cell_closest_to_mouse) + reset_searches end end end # Adds a wall in the first grid in the cell the mouse is over def process_input_dijkstra_add_wall - if dijkstra_mouse_over_grid? + if dijkstra_mouse_over_grid? unless grid.walls.has_key?(dijkstra_cell_closest_to_mouse) - grid.walls[dijkstra_cell_closest_to_mouse] = true - reset_searches + grid.walls[dijkstra_cell_closest_to_mouse] = true + reset_searches end end end # Adds a wall in the second grid in the cell the mouse is over def process_input_greedy_add_wall - if greedy_mouse_over_grid? + if greedy_mouse_over_grid? unless grid.walls.has_key?(greedy_cell_closest_to_mouse) - grid.walls[greedy_cell_closest_to_mouse] = true - reset_searches + grid.walls[greedy_cell_closest_to_mouse] = true + reset_searches end end end # Adds a wall in the third grid in the cell the mouse is over def process_input_a_star_add_wall - if a_star_mouse_over_grid? + if a_star_mouse_over_grid? unless grid.walls.has_key?(a_star_cell_closest_to_mouse) - grid.walls[a_star_cell_closest_to_mouse] = true - reset_searches + grid.walls[a_star_cell_closest_to_mouse] = true + reset_searches end end end @@ -21321,13 +22505,13 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse helps with this def dijkstra_cell_closest_to_mouse # Closest cell to the mouse in the first grid - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Bound x and y to the grid - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -21335,17 +22519,17 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the second grid helps with this def greedy_cell_closest_to_mouse # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= grid.width + 1 # Bound x and y to the first grid x = 0 if x < 0 y = 0 if y < 0 - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end # When the user grabs the star and puts their cursor to the far right @@ -21353,17 +22537,17 @@ Follows is a source code listing for all files that have been open sourced. This # Finding the cell closest to the mouse in the third grid helps with this def a_star_cell_closest_to_mouse # Closest cell grid to the mouse in the second - x = (inputs.mouse.point.x / grid.cell_size).to_i - y = (inputs.mouse.point.y / grid.cell_size).to_i + x = (inputs.mouse.point.x / grid.cell_size).to_i + y = (inputs.mouse.point.y / grid.cell_size).to_i # Translate the cell to the first grid x -= (grid.width + 1) * 2 # Bound x and y to the first grid x = 0 if x < 0 y = 0 if y < 0 - x = grid.width - 1 if x > grid.width - 1 - y = grid.height - 1 if y > grid.height - 1 + x = grid.width - 1 if x > grid.width - 1 + y = grid.height - 1 if y > grid.height - 1 # Return closest cell - [x, y] + [x, y] end def reset_searches @@ -21391,21 +22575,21 @@ Follows is a source code listing for all files that have been open sourced. This def calc_dijkstra # Sets up the search to begin from the star - dijkstra.frontier << grid.star - dijkstra.came_from[grid.star] = nil - dijkstra.cost_so_far[grid.star] = 0 + dijkstra.frontier << grid.star + dijkstra.came_from[grid.star] = nil + dijkstra.cost_so_far[grid.star] = 0 # Until the target is found or there are no more cells to explore from until dijkstra.came_from.has_key?(grid.target) or dijkstra.frontier.empty? # Take the next frontier cell. The first element is the cell, the second is the priority. new_frontier = dijkstra.frontier.shift#[0] # For each of its neighbors - adjacent_neighbors(new_frontier).each do | neighbor | + adjacent_neighbors(new_frontier).each do | neighbor | # That have not been visited and are not walls - unless dijkstra.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) + unless dijkstra.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited dijkstra.frontier << neighbor - dijkstra.came_from[neighbor] = new_frontier + dijkstra.came_from[neighbor] = new_frontier dijkstra.cost_so_far[neighbor] = dijkstra.cost_so_far[new_frontier] + 1 end end @@ -21426,20 +22610,20 @@ Follows is a source code listing for all files that have been open sourced. This def calc_greedy # Sets up the search to begin from the star - greedy.frontier << grid.star - greedy.came_from[grid.star] = nil + greedy.frontier << grid.star + greedy.came_from[grid.star] = nil # Until the target is found or there are no more cells to explore from until greedy.came_from.has_key?(grid.target) or greedy.frontier.empty? # Take the next frontier cell - new_frontier = greedy.frontier.shift + new_frontier = greedy.frontier.shift # For each of its neighbors - adjacent_neighbors(new_frontier).each do | neighbor | + adjacent_neighbors(new_frontier).each do | neighbor | # That have not been visited and are not walls - unless greedy.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) + unless greedy.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - greedy.frontier << neighbor - greedy.came_from[neighbor] = new_frontier + greedy.frontier << neighbor + greedy.came_from[neighbor] = new_frontier end end # Sort the frontier so that cells that are in a zigzag pattern are prioritized over those in an line @@ -21469,12 +22653,12 @@ Follows is a source code listing for all files that have been open sourced. This current_frontier = a_star.frontier.shift # For each of that cells neighbors - adjacent_neighbors(current_frontier).each do | neighbor | + adjacent_neighbors(current_frontier).each do | neighbor | # That have not been visited and are not walls - unless a_star.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) + unless a_star.came_from.has_key?(neighbor) or grid.walls.has_key?(neighbor) # Add them to the frontier and mark them as visited - a_star.frontier << neighbor - a_star.came_from[neighbor] = current_frontier + a_star.frontier << neighbor + a_star.came_from[neighbor] = current_frontier a_star.cost_so_far[neighbor] = a_star.cost_so_far[current_frontier] + 1 end end @@ -21556,16 +22740,16 @@ Follows is a source code listing for all files that have been open sourced. This # Returns a list of adjacent cells # Used to determine what the next cells to be added to the frontier are def adjacent_neighbors(cell) - neighbors = [] + neighbors = [] # Gets all the valid neighbors into the array # From southern neighbor, clockwise - neighbors << [cell.x , cell.y - 1] unless cell.y == 0 - neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 - neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 - neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 + neighbors << [cell.x , cell.y - 1] unless cell.y == 0 + neighbors << [cell.x - 1, cell.y ] unless cell.x == 0 + neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1 + neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1 - neighbors + neighbors end # Finds the vertical and horizontal distance of a cell from the star @@ -21610,11 +22794,11 @@ Follows is a source code listing for all files that have been open sourced. This def wall_color [134, 134, 120] # Camo Green end - + def visited_color [204, 191, 179] # Dark Brown end - + def path_color [231, 230, 228] # Pastel White end @@ -21637,7 +22821,7 @@ Follows is a source code listing for all files that have been open sourced. This end # Every tick, new args are passed, and the Breadth First Search tick is called - $a_star_algorithm ||= A_Star_Algorithm.new(args) + $a_star_algorithm ||= A_Star_Algorithm.new $a_star_algorithm.args = args $a_star_algorithm.tick end @@ -22173,6 +23357,363 @@ Follows is a source code listing for all files that have been open sourced. This https://en.wikipedia.org/wiki/OFF_(file_format) #+end_src +*** 3d - Yaw Pitch Roll - main.rb +#+begin_src ruby + # ./samples/99_genre_3d/03_yaw_pitch_roll/app/main.rb + class Game + attr_gtk + + def tick + defaults + render + input + end + + def matrix_mul m, v + (hmap x: ((m.x.x * v.x) + (m.x.y * v.y) + (m.x.z * v.z) + (m.x.w * v.w)), + y: ((m.y.x * v.x) + (m.y.y * v.y) + (m.y.z * v.z) + (m.y.w * v.w)), + z: ((m.z.x * v.x) + (m.z.y * v.y) + (m.z.z * v.z) + (m.z.w * v.w)), + w: ((m.w.x * v.x) + (m.w.y * v.y) + (m.w.z * v.z) + (m.w.w * v.w))) + end + + def player_ship + [ + # engine back + { x: -1, y: -1, z: 1, w: 0 }, + { x: -1, y: 1, z: 1, w: 0 }, + + { x: -1, y: 1, z: 1, w: 0 }, + { x: 1, y: 1, z: 1, w: 0 }, + + { x: 1, y: 1, z: 1, w: 0 }, + { x: 1, y: -1, z: 1, w: 0 }, + + { x: 1, y: -1, z: 1, w: 0 }, + { x: -1, y: -1, z: 1, w: 0 }, + + # engine front + { x: -1, y: -1, z: -1, w: 0 }, + { x: -1, y: 1, z: -1, w: 0 }, + + { x: -1, y: 1, z: -1, w: 0 }, + { x: 1, y: 1, z: -1, w: 0 }, + + { x: 1, y: 1, z: -1, w: 0 }, + { x: 1, y: -1, z: -1, w: 0 }, + + { x: 1, y: -1, z: -1, w: 0 }, + { x: -1, y: -1, z: -1, w: 0 }, + + # engine left + { x: -1, z: -1, y: -1, w: 0 }, + { x: -1, z: -1, y: 1, w: 0 }, + + { x: -1, z: -1, y: 1, w: 0 }, + { x: -1, z: 1, y: 1, w: 0 }, + + { x: -1, z: 1, y: 1, w: 0 }, + { x: -1, z: 1, y: -1, w: 0 }, + + { x: -1, z: 1, y: -1, w: 0 }, + { x: -1, z: -1, y: -1, w: 0 }, + + # engine right + { x: 1, z: -1, y: -1, w: 0 }, + { x: 1, z: -1, y: 1, w: 0 }, + + { x: 1, z: -1, y: 1, w: 0 }, + { x: 1, z: 1, y: 1, w: 0 }, + + { x: 1, z: 1, y: 1, w: 0 }, + { x: 1, z: 1, y: -1, w: 0 }, + + { x: 1, z: 1, y: -1, w: 0 }, + { x: 1, z: -1, y: -1, w: 0 }, + + # top front of engine to front of ship + { x: 1, y: 1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + { x: 0, y: -1, z: 9, w: 0 }, + { x: -1, y: 1, z: 1, w: 0 }, + + # bottom front of engine + { x: 1, y: -1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + { x: -1, y: -1, z: 1, w: 0 }, + { x: 0, y: -1, z: 9, w: 0 }, + + # right wing + # front of wing + { x: 1, y: 0.10, z: 1, w: 0 }, + { x: 9, y: 0.10, z: -1, w: 0 }, + + { x: 9, y: 0.10, z: -1, w: 0 }, + { x: 10, y: 0.10, z: -2, w: 0 }, + + # back of wing + { x: 1, y: 0.10, z: -1, w: 0 }, + { x: 9, y: 0.10, z: -1, w: 0 }, + + { x: 10, y: 0.10, z: -2, w: 0 }, + { x: 8, y: 0.10, z: -1, w: 0 }, + + # front of wing + { x: 1, y: -0.10, z: 1, w: 0 }, + { x: 9, y: -0.10, z: -1, w: 0 }, + + { x: 9, y: -0.10, z: -1, w: 0 }, + { x: 10, y: -0.10, z: -2, w: 0 }, + + # back of wing + { x: 1, y: -0.10, z: -1, w: 0 }, + { x: 9, y: -0.10, z: -1, w: 0 }, + + { x: 10, y: -0.10, z: -2, w: 0 }, + { x: 8, y: -0.10, z: -1, w: 0 }, + + # left wing + # front of wing + { x: -1, y: 0.10, z: 1, w: 0 }, + { x: -9, y: 0.10, z: -1, w: 0 }, + + { x: -9, y: 0.10, z: -1, w: 0 }, + { x: -10, y: 0.10, z: -2, w: 0 }, + + # back of wing + { x: -1, y: 0.10, z: -1, w: 0 }, + { x: -9, y: 0.10, z: -1, w: 0 }, + + { x: -10, y: 0.10, z: -2, w: 0 }, + { x: -8, y: 0.10, z: -1, w: 0 }, + + # front of wing + { x: -1, y: -0.10, z: 1, w: 0 }, + { x: -9, y: -0.10, z: -1, w: 0 }, + + { x: -9, y: -0.10, z: -1, w: 0 }, + { x: -10, y: -0.10, z: -2, w: 0 }, + + # back of wing + { x: -1, y: -0.10, z: -1, w: 0 }, + { x: -9, y: -0.10, z: -1, w: 0 }, + + { x: -10, y: -0.10, z: -2, w: 0 }, + { x: -8, y: -0.10, z: -1, w: 0 }, + + # left fin + # top + { x: -1, y: 0.10, z: 1, w: 0 }, + { x: -1, y: 3, z: -3, w: 0 }, + + { x: -1, y: 0.10, z: -1, w: 0 }, + { x: -1, y: 3, z: -3, w: 0 }, + + { x: -1.1, y: 0.10, z: 1, w: 0 }, + { x: -1.1, y: 3, z: -3, w: 0 }, + + { x: -1.1, y: 0.10, z: -1, w: 0 }, + { x: -1.1, y: 3, z: -3, w: 0 }, + + # bottom + { x: -1, y: -0.10, z: 1, w: 0 }, + { x: -1, y: -2, z: -2, w: 0 }, + + { x: -1, y: -0.10, z: -1, w: 0 }, + { x: -1, y: -2, z: -2, w: 0 }, + + { x: -1.1, y: -0.10, z: 1, w: 0 }, + { x: -1.1, y: -2, z: -2, w: 0 }, + + { x: -1.1, y: -0.10, z: -1, w: 0 }, + { x: -1.1, y: -2, z: -2, w: 0 }, + + # right fin + { x: 1, y: 0.10, z: 1, w: 0 }, + { x: 1, y: 3, z: -3, w: 0 }, + + { x: 1, y: 0.10, z: -1, w: 0 }, + { x: 1, y: 3, z: -3, w: 0 }, + + { x: 1.1, y: 0.10, z: 1, w: 0 }, + { x: 1.1, y: 3, z: -3, w: 0 }, + + { x: 1.1, y: 0.10, z: -1, w: 0 }, + { x: 1.1, y: 3, z: -3, w: 0 }, + + # bottom + { x: 1, y: -0.10, z: 1, w: 0 }, + { x: 1, y: -2, z: -2, w: 0 }, + + { x: 1, y: -0.10, z: -1, w: 0 }, + { x: 1, y: -2, z: -2, w: 0 }, + + { x: 1.1, y: -0.10, z: 1, w: 0 }, + { x: 1.1, y: -2, z: -2, w: 0 }, + + { x: 1.1, y: -0.10, z: -1, w: 0 }, + { x: 1.1, y: -2, z: -2, w: 0 }, + ] + end + + def defaults + state.points ||= player_ship + state.shifted_points ||= state.points.map { |point| point } + + state.scale ||= 1 + state.angle_x ||= 0 + state.angle_y ||= 0 + state.angle_z ||= 0 + end + + def matrix_new x0, y0, z0, w0, x1, y1, z1, w1, x2, y2, z2, w2, x3, y3, z3, w3 + (hmap x: (hmap x: x0, y: y0, z: z0, w: w0), + y: (hmap x: x1, y: y1, z: z1, w: w1), + z: (hmap x: x2, y: y2, z: z2, w: w2), + w: (hmap x: x3, y: y3, z: z3, w: w3)) + end + + def angle_z_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new cos_t, -sin_t, 0, 0, + sin_t, cos_t, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1) + end + + def angle_y_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new cos_t, 0, sin_t, 0, + 0, 1, 0, 0, + -sin_t, 0, cos_t, 0, + 0, 0, 0, 1) + end + + def angle_x_matrix degrees + cos_t = Math.cos degrees.to_radians + sin_t = Math.sin degrees.to_radians + (matrix_new 1, 0, 0, 0, + 0, cos_t, -sin_t, 0, + 0, sin_t, cos_t, 0, + 0, 0, 0, 1) + end + + def scale_matrix factor + (matrix_new factor, 0, 0, 0, + 0, factor, 0, 0, + 0, 0, factor, 0, + 0, 0, 0, 1) + end + + def input + if (inputs.keyboard.shift && inputs.keyboard.p) + state.scale -= 0.1 + elsif inputs.keyboard.p + state.scale += 0.1 + end + + if inputs.mouse.wheel + state.scale += inputs.mouse.wheel.y + end + + state.scale = state.scale.clamp(0.1, 1000) + + if (inputs.keyboard.shift && inputs.keyboard.y) || inputs.keyboard.right + state.angle_y += 1 + elsif (inputs.keyboard.y) || inputs.keyboard.left + state.angle_y -= 1 + end + + if (inputs.keyboard.shift && inputs.keyboard.x) || inputs.keyboard.down + state.angle_x -= 1 + elsif (inputs.keyboard.x || inputs.keyboard.up) + state.angle_x += 1 + end + + if inputs.keyboard.shift && inputs.keyboard.z + state.angle_z += 1 + elsif inputs.keyboard.z + state.angle_z -= 1 + end + + if inputs.keyboard.zero + state.angle_x = 0 + state.angle_y = 0 + state.angle_z = 0 + end + + angle_x = state.angle_x + angle_y = state.angle_y + angle_z = state.angle_z + scale = state.scale + + s_matrix = scale_matrix state.scale + x_matrix = angle_z_matrix angle_z + y_matrix = angle_y_matrix angle_y + z_matrix = angle_x_matrix angle_x + + state.shifted_points = state.points.map do |point| + (matrix_mul s_matrix, + (matrix_mul z_matrix, + (matrix_mul x_matrix, + (matrix_mul y_matrix, point)))).merge(original: point) + end + end + + def thick_line line + [ + line.merge(y: line.y - 1, y2: line.y2 - 1, r: 0, g: 0, b: 0), + line.merge(x: line.x - 1, x2: line.x2 - 1, r: 0, g: 0, b: 0), + line.merge(x: line.x - 0, x2: line.x2 - 0, r: 0, g: 0, b: 0), + line.merge(y: line.y + 1, y2: line.y2 + 1, r: 0, g: 0, b: 0), + line.merge(x: line.x + 1, x2: line.x2 + 1, r: 0, g: 0, b: 0) + ] + end + + def render + outputs.lines << state.shifted_points.each_slice(2).map do |(p1, p2)| + perc = 0 + thick_line({ x: p1.x.*(10) + 640, y: p1.y.*(10) + 320, + x2: p2.x.*(10) + 640, y2: p2.y.*(10) + 320, + r: 255 * perc, + g: 255 * perc, + b: 255 * perc }) + end + + outputs.labels << [ 10, 700, "angle_x: #{state.angle_x.to_sf}", 0] + outputs.labels << [ 10, 670, "x, shift+x", 0] + + outputs.labels << [210, 700, "angle_y: #{state.angle_y.to_sf}", 0] + outputs.labels << [210, 670, "y, shift+y", 0] + + outputs.labels << [410, 700, "angle_z: #{state.angle_z.to_sf}", 0] + outputs.labels << [410, 670, "z, shift+z", 0] + + outputs.labels << [610, 700, "scale: #{state.scale.to_sf}", 0] + outputs.labels << [610, 670, "p, shift+p", 0] + end + end + + $game = Game.new + + def tick args + $game.args = args + $game.tick + end + + def set_angles x, y, z + $game.state.angle_x = x + $game.state.angle_y = y + $game.state.angle_z = z + end + + $gtk.reset + +#+end_src + *** Arcade - Bullet Hell - main.rb #+begin_src ruby # ./samples/99_genre_arcade/bullet_hell/app/main.rb @@ -22791,31 +24332,36 @@ Follows is a source code listing for all files that have been open sourced. This end def render_score - outputs.primitives << [10, 710, "HI SCORE: #{state.hi_score}", large_white_typeset].label - outputs.primitives << [10, 680, "SCORE: #{state.score}", large_white_typeset].label - outputs.primitives << [10, 650, "DIFFICULTY: #{state.difficulty.upcase}", large_white_typeset].label + outputs.primitives << { x: 10, y: 710, text: "HI SCORE: #{state.hi_score}", **large_white_typeset } + outputs.primitives << { x: 10, y: 680, text: "SCORE: #{state.score}", **large_white_typeset } + outputs.primitives << { x: 10, y: 650, text: "DIFFICULTY: #{state.difficulty.upcase}", **large_white_typeset } end def render_menu return unless state.scene == :menu render_overlay - outputs.labels << [640, 700, "Flappy Dragon", 50, 1, 255, 255, 255] - outputs.labels << [640, 500, "Instructions: Press Spacebar to flap. Don't die.", 4, 1, 255, 255, 255] - outputs.labels << [430, 430, "[Tab] Change difficulty", 4, 0, 255, 255, 255] - outputs.labels << [430, 400, "[Enter] Start at New Difficulty ", 4, 0, 255, 255, 255] - outputs.labels << [430, 370, "[Escape] Cancel/Resume ", 4, 0, 255, 255, 255] - outputs.labels << [640, 300, "(mouse, touch, and game controllers work, too!) ", 4, 1, 255, 255, 255] - outputs.labels << [640, 200, "Difficulty: #{state.new_difficulty.capitalize}", 4, 1, 255, 255, 255] + outputs.labels << { x: 640, y: 700, text: "Flappy Dragon", size_enum: 50, alignment_enum: 1, **white } + outputs.labels << { x: 640, y: 500, text: "Instructions: Press Spacebar to flap. Don't die.", size_enum: 4, alignment_enum: 1, **white } + outputs.labels << { x: 430, y: 430, text: "[Tab] Change difficulty", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 430, y: 400, text: "[Enter] Start at New Difficulty ", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 430, y: 370, text: "[Escape] Cancel/Resume ", size_enum: 4, alignment_enum: 0, **white } + outputs.labels << { x: 640, y: 300, text: "(mouse, touch, and game controllers work, too!) ", size_enum: 4, alignment_enum: 1, **white } + outputs.labels << { x: 640, y: 200, text: "Difficulty: #{state.new_difficulty.capitalize}", size_enum: 4, alignment_enum: 1, **white } - outputs.labels << [10, 100, "Code: @amirrajan", 255, 255, 255] - outputs.labels << [10, 80, "Art: @mobypixel", 255, 255, 255] - outputs.labels << [10, 60, "Music: @mobypixel", 255, 255, 255] - outputs.labels << [10, 40, "Engine: DragonRuby GTK", 255, 255, 255] + outputs.labels << { x: 10, y: 100, text: "Code: @amirrajan", **white } + outputs.labels << { x: 10, y: 80, text: "Art: @mobypixel", **white } + outputs.labels << { x: 10, y: 60, text: "Music: @mobypixel", **white } + outputs.labels << { x: 10, y: 40, text: "Engine: DragonRuby GTK", **white } end def render_overlay - outputs.primitives << [grid.rect.scale_rect(1.1, 0, 0), 0, 0, 0, 230].solid + overlay_rect = grid.rect.scale_rect(1.1, 0, 0) + outputs.primitives << { x: overlay_rect.x, + y: overlay_rect.y, + w: overlay_rect.w, + h: overlay_rect.h, + r: 0, g: 0, b: 0, a: 230 }.solid! end def render_game @@ -22828,14 +24374,14 @@ Follows is a source code listing for all files that have been open sourced. This def render_game_over return unless state.scene == :game - outputs.labels << [638, 358, score_text, 20, 1] - outputs.labels << [635, 360, score_text, 20, 1, 255, 255, 255] - outputs.labels << [638, 428, countdown_text, 20, 1] - outputs.labels << [635, 430, countdown_text, 20, 1, 255, 255, 255] + outputs.labels << { x: 638, y: 358, text: score_text, size_enum: 20, alignment_enum: 1 } + outputs.labels << { x: 635, y: 360, text: score_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 } + outputs.labels << { x: 638, y: 428, text: countdown_text, size_enum: 20, alignment_enum: 1 } + outputs.labels << { x: 635, y: 430, text: countdown_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 } end def render_background - outputs.sprites << [0, 0, 1280, 720, 'sprites/background.png'] + outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'sprites/background.png' } scroll_point_at = state.tick_count scroll_point_at = state.scene_at if state.scene == :menu @@ -22847,11 +24393,18 @@ Follows is a source code listing for all files that have been open sourced. This outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_front.png', 1.00, -80) end + def scrolling_background at, path, rate, y = 0 + [ + { x: 0 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path }, + { x: 1440 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path } + ] + end + def render_walls state.walls.each do |w| w.sprites = [ - [w.x, w.bottom_height - 720, 100, 720, 'sprites/wall.png', 180], - [w.x, w.top_y, 100, 720, 'sprites/wallbottom.png', 0] + { x: w.x, y: w.bottom_height - 720, w: 100, h: 720, path: 'sprites/wall.png', angle: 180 }, + { x: w.x, y: w.top_y, w: 100, h: 720, path: 'sprites/wallbottom.png', angle: 0 } ] end outputs.sprites << state.walls.map(&:sprites) @@ -22860,15 +24413,13 @@ Follows is a source code listing for all files that have been open sourced. This def render_dragon state.show_death = true if state.countdown == 3.seconds - render_debug_hitbox false - if state.show_death == false || !state.death_at animation_index = state.flapped_at.frame_index 6, 2, false if state.flapped_at sprite_name = "sprites/dragon_fly#{animation_index.or(0) + 1}.png" - state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2] + state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 } else sprite_name = "sprites/dragon_die.png" - state.dragon_sprite = [state.x, state.y, 100, 80, sprite_name, state.dy * 1.2] + state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 } sprite_changed_elapsed = state.death_at.elapsed_time - 1.seconds state.dragon_sprite.angle += (sprite_changed_elapsed ** 1.3) * state.death_fall_direction * -1 state.dragon_sprite.x += (sprite_changed_elapsed ** 1.2) * state.death_fall_direction @@ -22878,20 +24429,12 @@ Follows is a source code listing for all files that have been open sourced. This outputs.sprites << state.dragon_sprite end - def render_debug_hitbox show - return unless show - outputs.borders << [dragon_collision_box.rect, 255, 0, 0] if state.dragon_sprite - outputs.borders << state.walls.flat_map do |w| - w.sprites.map { |s| [s.rect, 255, 0, 0] } - end - end - def render_flash return unless state.flash_at - outputs.primitives << [grid.rect, - white, - 255 * state.flash_at.ease(20, :flip)].solid + outputs.primitives << { **grid.rect.to_hash, + **white, + a: 255 * state.flash_at.ease(20, :flip) }.solid! state.flash_at = 0 if state.flash_at.elapsed_time > 20 end @@ -23011,19 +24554,12 @@ Follows is a source code listing for all files that have been open sourced. This end end - def scrolling_background at, path, rate, y = 0 - [ - [ 0 - at.*(rate) % 1440, y, 1440, 720, path], - [1440 - at.*(rate) % 1440, y, 1440, 720, path] - ] - end - def white - [255, 255, 255] + { r: 255, g: 255, b: 255 } end def large_white_typeset - [5, 0, 255, 255, 255] + { size_enum: 5, alignment_enum: 0, r: 255, g: 255, b: 255 } end def at_beginning? @@ -23032,9 +24568,9 @@ Follows is a source code listing for all files that have been open sourced. This def dragon_collision_box state.dragon_sprite - .scale_rect(1.0 - collision_forgiveness, 0.5, 0.5) - .rect_shift_right(10) - .rect_shift_up(state.dy * 2) + .scale_rect(1.0 - collision_forgiveness, 0.5, 0.5) + .rect_shift_right(10) + .rect_shift_up(state.dy * 2) end def game_over? @@ -23043,7 +24579,7 @@ Follows is a source code listing for all files that have been open sourced. This state.walls .flat_map { |w| w.sprites } .any? do |s| - s.intersect_rect?(dragon_collision_box) + s && s.intersect_rect?(dragon_collision_box) end end @@ -24345,6 +25881,131 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Crafting - Farming Game Starting Point - main.rb +#+begin_src ruby + # ./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 + +#+end_src + +*** Crafting - Farming Game Starting Point - tests.rb +#+begin_src ruby + # ./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
+ +#+end_src + *** Dev Tools - Add Buttons To Console - main.rb #+begin_src ruby # ./samples/99_genre_dev_tools/add_buttons_to_console/app/main.rb @@ -24515,12 +26176,12 @@ Follows is a source code listing for all files that have been open sourced. This 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] @@ -25257,6 +26918,528 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Dungeon Crawl - Classics Jam - main.rb +#+begin_src ruby + # ./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 + +#+end_src + +*** Fighting - Special Move Inputs - main.rb +#+begin_src ruby + # ./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 + +#+end_src + *** Lowrez - Nokia 3310 - main.rb #+begin_src ruby # ./samples/99_genre_lowrez/nokia_3310/app/main.rb @@ -25867,7 +28050,7 @@ Follows is a source code listing for all files that have been open sourced. This text: text, size_enum: -1.5, r: 255, g: 255, b: 255 - }.label + }.label! end args.outputs.debug << { @@ -25877,7 +28060,7 @@ Follows is a source code listing for all files that have been open sourced. This size_enum: -0.5, alignment_enum: 1, r: 255, g: 255, b: 255 - }.label + }.label! end def snake_demo args @@ -26129,7 +28312,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 240, b: 216, a: 100 - }.line + }.line! end (NOKIA_WIDTH + 1).map_with_index do |i| @@ -26142,7 +28325,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 240, b: 216, a: 100 - }.line + }.line! end @args.state.overlay_rendered = true @@ -26894,7 +29077,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 128, b: 128, a: 80 - }.line + }.line! args.outputs.static_debug << { x: LOWREZ_X_OFFSET + (i * 10), @@ -26905,7 +29088,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 128, b: 128, a: 80 - }.line + }.line! end end @@ -26932,7 +29115,7 @@ Follows is a source code listing for all files that have been open sourced. This y: 720 - (i * 20), text: text, size_enum: -1.5 - }.label + }.label! end args.outputs.debug << { @@ -26941,7 +29124,378 @@ Follows is a source code listing for all files that have been open sourced. This 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 + +#+end_src + +*** Mario - Jumping - main.rb +#+begin_src ruby + # ./samples/99_genre_mario/01_jumping/app/main.rb + def tick args + defaults args + render args + input args + calc args + end + + def defaults args + args.state.player.x ||= args.grid.w.half + args.state.player.y ||= 0 + args.state.player.size ||= 100 + args.state.player.dy ||= 0 + args.state.player.action ||= :jumping + args.state.jump.power = 20 + args.state.jump.increase_frames = 10 + args.state.jump.increase_power = 1 + args.state.gravity = -1 + end + + def render args + args.outputs.sprites << { + x: args.state.player.x - + args.state.player.size.half, + y: args.state.player.y, + w: args.state.player.size, + h: args.state.player.size, + path: 'sprites/square/red.png' + } + end + + def input args + if args.inputs.keyboard.key_down.space + if args.state.player.action == :standing + args.state.player.action = :jumping + args.state.player.dy = args.state.jump.power + + # record when the action took place + current_frame = args.state.tick_count + args.state.player.action_at = current_frame + end + end + + # if the space bar is being held + if args.inputs.keyboard.key_held.space + # is the player jumping + is_jumping = args.state.player.action == :jumping + + # when was the jump performed + time_of_jump = args.state.player.action_at + + # how much time has passed since the jump + jump_elapsed_time = time_of_jump.elapsed_time + + # how much time is allowed for increasing power + time_allowed = args.state.jump.increase_frames + + # if the player is jumping + # and the elapsed time is less than + # the allowed time + if is_jumping && jump_elapsed_time < time_allowed + # increase the dy by the increase power + power_to_add = args.state.jump.increase_power + args.state.player.dy += power_to_add + end + end + end + + def calc args + if args.state.player.action == :jumping + args.state.player.y += args.state.player.dy + args.state.player.dy += args.state.gravity + end + + if args.state.player.y < 0 + args.state.player.y = 0 + args.state.player.action = :standing + end + end + +#+end_src + +*** Mario - Jumping And Collisions - main.rb +#+begin_src ruby + # ./samples/99_genre_mario/02_jumping_and_collisions/app/main.rb + class Game + attr_gtk + + def tick + defaults + render + input + calc + end + + def defaults + return if state.tick_count != 0 + + player.x = 64 + player.y = 800 + player.size = 50 + player.dx = 0 + player.dy = 0 + player.action = :falling + + player.max_speed = 20 + player.jump_power = 15 + player.jump_air_time = 15 + player.jump_increase_power = 1 + + state.gravity = -1 + state.drag = 0.001 + state.tile_size = 64 + state.tiles ||= [ + { ordinal_x: 0, ordinal_y: 0 }, + { ordinal_x: 1, ordinal_y: 0 }, + { ordinal_x: 2, ordinal_y: 0 }, + { ordinal_x: 3, ordinal_y: 0 }, + { ordinal_x: 4, ordinal_y: 0 }, + { ordinal_x: 5, ordinal_y: 0 }, + { ordinal_x: 6, ordinal_y: 0 }, + { ordinal_x: 7, ordinal_y: 0 }, + { ordinal_x: 8, ordinal_y: 0 }, + { ordinal_x: 9, ordinal_y: 0 }, + { ordinal_x: 10, ordinal_y: 0 }, + { ordinal_x: 11, ordinal_y: 0 }, + { ordinal_x: 12, ordinal_y: 0 }, + + { ordinal_x: 9, ordinal_y: 3 }, + { ordinal_x: 10, ordinal_y: 3 }, + { ordinal_x: 11, ordinal_y: 3 }, + ] + + tiles.each do |t| + t.rect = { x: t.ordinal_x * 64, + y: t.ordinal_y * 64, + w: 64, + h: 64 } + end + end + + def render + render_player + render_tiles + # render_grid + end + + def input + input_jump + input_move + end + + def calc + calc_player_rect + calc_left + calc_right + calc_below + calc_above + calc_player_dy + calc_player_dx + calc_game_over + end + + def render_player + outputs.sprites << { + x: player.x, + y: player.y, + w: player.size, + h: player.size, + path: 'sprites/square/red.png' + } + end + + def render_tiles + outputs.sprites << state.tiles.map do |t| + t.merge path: 'sprites/square/white.png', + x: t.ordinal_x * 64, + y: t.ordinal_y * 64, + w: 64, + h: 64 + end + end + + def render_grid + if state.tick_count == 0 + outputs[:grid].background_color = [0, 0, 0, 0] + outputs[:grid].borders << available_brick_locations + outputs[:grid].labels << available_brick_locations.map do |b| + [ + b.merge(text: "#{b.ordinal_x},#{b.ordinal_y}", + x: b.x + 2, + y: b.y + 2, + size_enum: -3, + vertical_alignment_enum: 0, + blendmode_enum: 0), + b.merge(text: "#{b.x},#{b.y}", + x: b.x + 2, + y: b.y + 2 + 20, + size_enum: -3, + vertical_alignment_enum: 0, + blendmode_enum: 0) + ] + end + end + + outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: :grid } + end + + def input_jump + if inputs.keyboard.key_down.space + player_jump + end + + if inputs.keyboard.key_held.space + player_jump_increase_air_time + end + end + + def input_move + if player.dx.abs < 20 + if inputs.keyboard.left + player.dx -= 2 + elsif inputs.keyboard.right + player.dx += 2 + end + end + end + + def calc_game_over + if player.y < -64 + player.x = 64 + player.y = 800 + player.dx = 0 + player.dy = 0 + end + end + + def calc_player_rect + player.rect = player_current_rect + player.next_rect = player_next_rect + player.prev_rect = player_prev_rect + end + + def calc_player_dx + player.dx = player_next_dx + player.x += player.dx + end + + def calc_player_dy + player.y += player.dy + player.dy = player_next_dy + end + + def calc_below + return unless player.dy < 0 + tiles_below = tiles_find { |t| t.rect.top <= player.prev_rect.y } + collision = tiles_find_colliding tiles_below, (player.rect.merge y: player.next_rect.y) + if collision + player.y = collision.rect.y + state.tile_size + player.dy = 0 + player.action = :standing + else + player.action = :falling + end + end + + def calc_left + return unless player.dx < 0 && player_next_dx < 0 + tiles_left = tiles_find { |t| t.rect.right <= player.prev_rect.left } + collision = tiles_find_colliding tiles_left, (player.rect.merge x: player.next_rect.x) + return unless collision + player.x = collision.rect.right + player.dx = 0 + end + + def calc_right + return unless player.dx > 0 && player_next_dx > 0 + tiles_right = tiles_find { |t| t.rect.left >= player.prev_rect.right } + collision = tiles_find_colliding tiles_right, (player.rect.merge x: player.next_rect.x) + return unless collision + player.x = collision.rect.left - player.rect.w + player.dx = 0 + end + + def calc_above + return unless player.dy > 0 + tiles_above = tiles_find { |t| t.rect.y >= player.prev_rect.y } + collision = tiles_find_colliding tiles_above, (player.rect.merge y: player.next_rect.y) + return unless collision + player.dy = 0 + player.y = collision.rect.bottom - player.rect.h + end + + def player_current_rect + { x: player.x, y: player.y, w: player.size, h: player.size } + end + + def available_brick_locations + (0..19).to_a + .product(0..11) + .map do |(ordinal_x, ordinal_y)| + { ordinal_x: ordinal_x, + ordinal_y: ordinal_y, + x: ordinal_x * 64, + y: ordinal_y * 64, + w: 64, + h: 64 } + end + end + + def player + state.player ||= args.state.new_entity :player + end + + def player_next_dy + player.dy + state.gravity + state.drag ** 2 * -1 + end + + def player_next_dx + player.dx * 0.8 + end + + def player_next_rect + player.rect.merge x: player.x + player_next_dx, + y: player.y + player_next_dy + end + + def player_prev_rect + player.rect.merge x: player.x - player.dx, + y: player.y - player.dy + end + + def player_jump + return if player.action != :standing + player.action = :jumping + player.dy = state.player.jump_power + current_frame = state.tick_count + player.action_at = current_frame + end + + def player_jump_increase_air_time + return if player.action != :jumping + return if player.action_at.elapsed_time >= player.jump_air_time + player.dy += player.jump_increase_power + end + + def tiles + state.tiles + end + + def tiles_find_colliding tiles, target + tiles.find { |t| t.rect.intersect_rect? target } + end + + def tiles_find &block + tiles.find_all(&block) + end + end + + def tick args + $game ||= Game.new + $game.args = args + $game.tick end $gtk.reset @@ -26951,7 +29505,7 @@ Follows is a source code listing for all files that have been open sourced. This *** Platformer - Clepto Frog - main.rb #+begin_src ruby # ./samples/99_genre_platformer/clepto_frog/app/main.rb - MAP_FILE_PATH = 'app/map.txt' + MAP_FILE_PATH = 'map.txt' require 'app/map.rb' @@ -27006,35 +29560,7 @@ Follows is a source code listing for all files that have been open sourced. This def render_intro outputs.labels << [640, 700, "Clepto Frog", 4, 1] - if state.tick_count >= 120 - outputs.labels << [640, 620, "\"Uh... your office has a pet frog?\" - New Guy", - 4, 1, 0, 0, 0, 255 * 120.ease(60)] - end - - if state.tick_count >= 240 - outputs.labels << [640, 580, "\"Yep! His name is Clepto.\" - Jim", - 4, 1, 0, 0, 0, 255 * 240.ease(60)] - end - - if state.tick_count >= 360 - outputs.labels << [640, 540, "\"Uh...\" - New Guy", - 4, 1, 0, 0, 0, 255 * 360.ease(60)] - end - - if state.tick_count >= 480 - outputs.labels << [640, 500, "\"He steals mugs while we're away...\" - Jim", - 4, 1, 0, 0, 0, 255 * 480.ease(60)] - end - - if state.tick_count >= 600 - outputs.labels << [640, 460, "\"It's not a big deal, we take them back in the morning.\" - Jim", - 4, 1, 0, 0, 0, 255 * 600.ease(60)] - end - - outputs.sprites << [640 - 50, 360 - 50, 100, 100, - "sprites/square-green.png"] - - if state.tick_count == 800 + if state.tick_count == 120 state.scene = :game state.game_start_at = state.tick_count end @@ -27042,7 +29568,7 @@ Follows is a source code listing for all files that have been open sourced. This def tick defaults - if state.scene == :intro && state.tick_count <= 800 + if state.scene == :intro && state.tick_count <= 120 render_intro elsif state.scene == :ending render_ending @@ -27145,15 +29671,15 @@ Follows is a source code listing for all files that have been open sourced. This if state.god_mode # SHOW HIDE COLLISIONS - outputs.sprites << state.world.map do |x, y, w, h| - x = vx(x) - y = vy(y) + outputs.sprites << state.world.map do |rect| + x = vx(rect.x) + y = vy(rect.y) if x > -80 && x < 1280 && y > -80 && y < 720 { x: x, y: y, - w: vw(w || state.tile_size), - h: vh(h || state.tile_size), + w: vw(rect.w || state.tile_size), + h: vh(rect.h || state.tile_size), path: 'sprites/square-gray.png', a: 128 } @@ -27176,8 +29702,10 @@ Follows is a source code listing for all files that have been open sourced. This # Creates sprite following mouse to help indicate which sprite you have selected - outputs.primitives << [inputs.mouse.position.x, inputs.mouse.position.y, - state.tile_size, state.tile_size, 'sprites/square-indigo.png', 0, 100].sprite + outputs.primitives << [inputs.mouse.position.x - 32 * state.camera_scale, + inputs.mouse.position.y - 32 * state.camera_scale, + state.tile_size * state.camera_scale, + state.tile_size * state.camera_scale, 'sprites/square-indigo.png', 0, 100].sprite end render_mini_map @@ -27258,6 +29786,29 @@ Follows is a source code listing for all files that have been open sourced. This set_camera_scale 1 end + if inputs.mouse.click + state.id_seed += 1 + id = state.id_seed + x = state.camera_x + (inputs.mouse.click.x.fdiv(state.camera_scale) - 32) + y = state.camera_y + (inputs.mouse.click.y.fdiv(state.camera_scale) - 32) + x = ((x + 2).idiv 4) * 4 + y = ((y + 2).idiv 4) * 4 + w = 64 + h = 64 + candidate_rect = { id: id, x: x, y: y, w: w, h: h } + scaled_candidate_rect = { x: x + 30, y: y + 30, w: w - 60, h: h - 60 } + to_remove = state.world.find { |r| r.intersect_rect? scaled_candidate_rect } + if to_remove && args.inputs.keyboard.x + state.world.reject! { |r| r.id == to_remove.id } + else + state.world << candidate_rect + end + export_map + state.world_lookup = {} + state.world_collision_rects = nil + calc_world_lookup + end + if input_up? state.y += 10 state.dy = 0 @@ -27279,12 +29830,6 @@ Follows is a source code listing for all files that have been open sourced. This if state.scene == :game process_inputs_player_movement process_inputs_god_mode - elsif state.scene == :intro - if args.inputs.keyboard.key_down.enter || args.inputs.keyboard.key_down.space - if Kernel.tick_count < 600 - Kernel.tick_count = 600 - end - end end end @@ -27382,17 +29927,6 @@ Follows is a source code listing for all files that have been open sourced. This end end - def add_floors - # floors - state.world += [ - [0, 0, 10000, 40], - [0, 1670, 3250, 60], - [6691, 1653, 3290, 60], - [1521, 3792, 7370, 60], - [0, 5137, 3290, 60] - ] - end - def attempt_load_world_from_file return if state.world # exported_world = gtk.read_file(MAP_FILE_PATH) @@ -27400,26 +29934,11 @@ Follows is a source code listing for all files that have been open sourced. This state.objects = [] if $collisions - $collisions.map do |x, y, w, h| - state.world << [x, y, w, h] + state.id_seed ||= 0 + $collisions.each do |x, y, w, h| + state.id_seed += 1 + state.world << { id: state.id_seed, x: x, y: y, w: w, h: h } end - - add_floors - # elsif exported_world - # exported_world.each_line.map do |l| - # tokens = l.strip.split(',') - # x = tokens[0].to_i - # y = tokens[1].to_i - # type = tokens[2].to_i - # if type == 1 - # state.world << [x, y, state.tile_size, state.tile_size] - # elsif type == 2 - # w, h, path = tokens[3..-1] - # state.objects << [x, y, w.to_i, h.to_i, path] - # end - # end - - # add_floors end if $mugs @@ -27440,23 +29959,24 @@ Follows is a source code listing for all files that have been open sourced. This # Searches through the world and finds the cordinates that exist state.world_lookup = {} - state.world.each do |x, y, w, h| - state.world_lookup[[x, y, w || state.tile_size, h || state.tile_size]] = true + state.world.each do |rect| + state.world_lookup[rect.id] = rect end # Assigns collision rects for every sprite drawn state.world_collision_rects = state.world_lookup .keys - .map do |x, y, w, h| + .map do |key| + rect = state.world_lookup[key] s = state.tile_size - w ||= s - h ||= s + rect.w ||= s + rect.h ||= s { - args: [x, y, w, h], - left_right: [x, y + 4, w, h - 6], - top: [x + 4, y + 6, w - 8, h - 6], - bottom: [x + 1, y - 1, w - 2, h - 8], + args: rect, + left_right: { x: rect.x, y: rect.y + 4, w: rect.w, h: rect.h - 6 }, + top: { x: rect.x + 4, y: rect.y + 6, w: rect.w - 8, h: rect.h - 6 }, + bottom: { x: rect.x + 1, y: rect.y - 1, w: rect.w - 2, h: rect.h - 8 }, } end @@ -27512,12 +30032,21 @@ Follows is a source code listing for all files that have been open sourced. This def end_of_tongue p = state.tongue_angle.vector(state.tongue_length) - [start_of_tongue.x + p.x, start_of_tongue.y + p.y] + { x: start_of_tongue.x + p.x, y: start_of_tongue.y + p.y } end def calc_shooting + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + calc_shooting_increment + end + + def calc_shooting_increment return unless state.action == :shooting - state.tongue_length += 30 + state.tongue_length += 5 potential_anchor = end_of_tongue if potential_anchor.x <= 0 state.anchor_point = potential_anchor @@ -27536,9 +30065,9 @@ Follows is a source code listing for all files that have been open sourced. This state.action = :anchored outputs.sounds << 'sounds/attached.wav' else - anchor_rect = [potential_anchor.x - 5, potential_anchor.y - 5, 10, 10] + anchor_rect = { x: potential_anchor.x - 5, y: potential_anchor.y - 5, w: 10, h: 10 } collision = state.world_collision_rects.find_all do |v| - [v[:args].x, v[:args].y, v[:args].w, v[:args].h].intersect_rect?(anchor_rect) + v[:args].intersect_rect?(anchor_rect) end.first if collision state.anchor_point = potential_anchor @@ -27634,7 +30163,7 @@ Follows is a source code listing for all files that have been open sourced. This .first return unless left_side_collisions - state.x = left_side_collisions[:left_right].right + state.x = left_side_collisions[:left_right].right + 1 state.dx = state.dy.abs * 0.8 state.collision_on_x = true end @@ -27649,7 +30178,7 @@ Follows is a source code listing for all files that have been open sourced. This .first return unless right_side_collisions - state.x = right_side_collisions[:left_right].left - state.tile_size + state.x = right_side_collisions[:left_right].left - state.tile_size - 1 state.dx = state.dx.abs * 0.8 * -1 state.collision_on_x = true end @@ -27665,7 +30194,7 @@ Follows is a source code listing for all files that have been open sourced. This .first return unless ceil_collisions - state.y = ceil_collisions[:bottom].y - state.tile_size + state.y = ceil_collisions[:bottom].y - state.tile_size - 1 state.dy = state.dy.abs * 0.8 * -1 state.collision_on_y = true end @@ -27678,13 +30207,17 @@ Follows is a source code listing for all files that have been open sourced. This end def export_map - export_string = state.world.map do |x, y| - "#{x},#{y},1" - end + export_string = "$collisions = [\n" + export_string += state.world.map do |rect| + "[#{rect.x},#{rect.y},#{rect.w},#{rect.h}]," + end.join "\n" + export_string += "\n]\n\n" + export_string += "$mugs = [\n" export_string += state.objects.map do |x, y, w, h, path| - "#{x},#{y},2,#{w},#{h},#{path}" - end - gtk.write_file(MAP_FILE_PATH, export_string.join("\n")) + "[#{x},#{y},#{w},#{h},'#{path}']," + end.join "\n" + export_string += "\n]\n\n" + gtk.write_file(MAP_FILE_PATH, export_string) state.map_saved_at = state.tick_count end @@ -28812,6 +31345,11 @@ Follows is a source code listing for all files that have been open sourced. This [4459, 3997, 64, 64], [76, 5215, 64, 64], [39, 5217, 64, 64], + [0, 0, 10000, 40], + [0, 1670, 3250, 60], + [6691, 1653, 3290, 60], + [1521, 3792, 7370, 60], + [0, 5137, 3290, 60] ] $mugs = [ @@ -29667,6 +32205,7 @@ Follows is a source code listing for all files that have been open sourced. This end def load_lines file + return unless state.snaps data = gtk.read_file(file) || "" data.each_line .reject { |l| l.strip.length == 0 } @@ -29725,10 +32264,10 @@ Follows is a source code listing for all files that have been open sourced. This results[:point] = { x: x, y: y } results[:rect] = { x: x - radius, y: y - radius, w: radius * 2, h: radius * 2 } results[:trajectory] = trajectory(results) - results[:impacts] = terrain.find_all { |t| line_near_rect? results[:rect], t }.map do |t| + results[:impacts] = terrain.find_all { |t| t && (line_near_rect? results[:rect], t) }.map do |t| { terrain: t, - point: geometry.line_intersect(results[:trajectory], t), + point: geometry.line_intersect(results[:trajectory], t, replace_infinity: 1000), type: :terrain } end.reject { |t| !point_within_line? t[:point], t[:terrain] } @@ -29736,10 +32275,10 @@ Follows is a source code listing for all files that have been open sourced. This results[:impacts] += lava.find_all { |t| line_near_rect? results[:rect], t }.map do |t| { terrain: t, - point: geometry.line_intersect(results[:trajectory], t), + point: geometry.line_intersect(results[:trajectory], t, replace_infinity: 1000), type: :lava } - end.reject { |t| !point_within_line? t[:point], t[:terrain] } + end.reject { |t| !t || (!point_within_line? t[:point], t[:terrain]) } results end @@ -29752,6 +32291,7 @@ Follows is a source code listing for all files that have been open sourced. This end def calc_terrains_to_monitor + return unless circle.impacts circle.impact = nil circle.impacts.each do |i| circle.terrains_to_monitor[i[:terrain]] ||= { @@ -35301,6 +37841,151 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Rpg Topdown - Topdown Casino - main.rb +#+begin_src ruby + # ./samples/99_genre_rpg_topdown/topdown_casino/app/main.rb + $gtk.reset + + def coinflip + rand < 0.5 + end + + class Game + attr_accessor :args + + def text_font + return nil #"rpg.ttf" + end + + def text_color + [ 255, 255, 255, 255 ] + end + + def set_gem_values + @args.state.gem0 = ((coinflip) ? 100 : 20) + @args.state.gem1 = ((coinflip) ? -10 : -50) + @args.state.gem2 = ((coinflip) ? -10 : -30) + if coinflip + tmp = @args.state.gem0 + @args.state.gem0 = @args.state.gem1 + @args.state.gem1 = tmp + end + if coinflip + tmp = @args.state.gem1 + @args.state.gem1 = @args.state.gem2 + @args.state.gem2 = tmp + end + if coinflip + tmp = @args.state.gem0 + @args.state.gem0 = @args.state.gem2 + @args.state.gem2 = tmp + end + end + + def initialize args + @args = args + @args.state.animticks = 0 + @args.state.score = 0 + @args.state.gem_chosen = false + @args.state.round_finished = false + @args.state.gem0_x = 197 + @args.state.gem0_y = 720-274 + @args.state.gem1_x = 623 + @args.state.gem1_y = 720-274 + @args.state.gem2_x = 1049 + @args.state.gem2_y = 720-274 + @args.state.hero_sprite = "sprites/herodown100.png" + @args.state.hero_x = 608 + @args.state.hero_y = 720-656 + set_gem_values + end + + def render_gem_value x, y, gem + if @args.state.gem_chosen + @args.outputs.labels << [ x, y + 96, gem.to_s, 1, 1, *text_color, text_font ] + end + end + + def render + gemsprite = ((@args.state.animticks % 400) < 200) ? 'sprites/gem200.png' : 'sprites/gem400.png' + @args.outputs.background_color = [ 0, 0, 0, 255 ] + @args.outputs.sprites << [608, 720-150, 64, 64, 'sprites/oldman.png'] + @args.outputs.sprites << [300, 720-150, 64, 64, 'sprites/fire.png'] + @args.outputs.sprites << [900, 720-150, 64, 64, 'sprites/fire.png'] + @args.outputs.sprites << [@args.state.gem0_x, @args.state.gem0_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.gem1_x, @args.state.gem1_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.gem2_x, @args.state.gem2_y, 32, 64, gemsprite] + @args.outputs.sprites << [@args.state.hero_x, @args.state.hero_y, 64, 64, @args.state.hero_sprite] + + @args.outputs.labels << [ 630, 720-30, "IT'S A SECRET TO EVERYONE.", 1, 1, *text_color, text_font ] + @args.outputs.labels << [ 50, 720-85, @args.state.score.to_s, 1, 1, *text_color, text_font ] + render_gem_value @args.state.gem0_x, @args.state.gem0_y, @args.state.gem0 + render_gem_value @args.state.gem1_x, @args.state.gem1_y, @args.state.gem1 + render_gem_value @args.state.gem2_x, @args.state.gem2_y, @args.state.gem2 + end + + def calc + @args.state.animticks += 16 + + return unless @args.state.gem_chosen + @args.state.round_finished_debounce ||= 60 * 3 + @args.state.round_finished_debounce -= 1 + return if @args.state.round_finished_debounce > 0 + + @args.state.gem_chosen = false + @args.state.hero.sprite[0] = 'sprites/herodown100.png' + @args.state.hero.sprite[1] = 608 + @args.state.hero.sprite[2] = 656 + @args.state.round_finished_debounce = nil + set_gem_values + end + + def walk xdir, ydir, anim + @args.state.hero_sprite = "sprites/#{anim}#{(((@args.state.animticks % 200) < 100) ? '100' : '200')}.png" + @args.state.hero_x += 5 * xdir + @args.state.hero_y += 5 * ydir + end + + def check_gem_touching gem_x, gem_y, gem + return if @args.state.gem_chosen + herorect = [ @args.state.hero_x, @args.state.hero_y, 64, 64 ] + return if !herorect.intersect_rect?([gem_x, gem_y, 32, 64]) + @args.state.gem_chosen = true + @args.state.score += gem + @args.outputs.sounds << ((gem < 0) ? 'sounds/lose.wav' : 'sounds/win.wav') + end + + def input + if @args.inputs.keyboard.key_held.left + walk(-1.0, 0.0, 'heroleft') + elsif @args.inputs.keyboard.key_held.right + walk(1.0, 0.0, 'heroright') + elsif @args.inputs.keyboard.key_held.up + walk(0.0, 1.0, 'heroup') + elsif @args.inputs.keyboard.key_held.down + walk(0.0, -1.0, 'herodown') + end + + check_gem_touching(@args.state.gem0_x, @args.state.gem0_y, @args.state.gem0) + check_gem_touching(@args.state.gem1_x, @args.state.gem1_y, @args.state.gem1) + check_gem_touching(@args.state.gem2_x, @args.state.gem2_y, @args.state.gem2) + end + + def tick + input + calc + render + end + end + + def tick args + args.state.game ||= Game.new args + args.state.game.args = args + args.state.game.tick + end + +#+end_src + *** Rpg Topdown - Topdown Starting Point - main.rb #+begin_src ruby # ./samples/99_genre_rpg_topdown/topdown_starting_point/app/main.rb @@ -35415,6 +38100,189 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** Teenytiny - Teenytiny Starting Point - main.rb +#+begin_src ruby + # ./samples/99_genre_teenytiny/teenytiny_starting_point/app/main.rb + # full documenation is at http://docs.dragonruby.org + # be sure to come to the discord if you hit any snags: http://discord.dragonruby.org + def tick args + # ==================================================== + # initialize default variables + # ==================================================== + + # ruby has an operator called ||= which means "only initialize this if it's nil" + args.state.count_down ||= 20 * 60 # set the count down to 20 seconds + # set the initial position of the target + args.state.target ||= { x: args.grid.w.half, + y: args.grid.h.half, + w: 20, + h: 20 } + + # set the initial position of the player + args.state.player ||= { x: 50, + y: 50, + w: 20, + h: 20 } + + # set the player movement speed + args.state.player_speed ||= 5 + + # set the score + args.state.score ||= 0 + args.state.teleports ||= 3 + + # set the instructions + args.state.instructions ||= "Get to the red goal! Use arrow keys to move. Spacebar to teleport (use them carefully)!" + + # ==================================================== + # render the game + # ==================================================== + args.outputs.labels << { x: args.grid.w.half, y: args.grid.h - 10, + text: args.state.instructions, + alignment_enum: 1 } + + # check if it's game over. if so, then render game over + # otherwise render the current time left + if game_over? args + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 40, + text: "game over! (press r to start over)", + alignment_enum: 1 } + else + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 40, + text: "time left: #{(args.state.count_down.idiv 60) + 1}", + alignment_enum: 1 } + end + + # render the score + args.outputs.labels << { x: args.grid.w.half, + y: args.grid.h - 70, + text: "score: #{args.state.score}", + alignment_enum: 1 } + + # render the player with teleport count + 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' } + + args.outputs.labels << { x: args.state.player.x + 10, + y: args.state.player.y + 40, + text: "teleports: #{args.state.teleports}", + alignment_enum: 1, size_enum: -2 } + + # render the target + args.outputs.sprites << { x: args.state.target.x, + y: args.state.target.y, + w: args.state.target.w, + h: args.state.target.h, + path: 'sprites/square-red.png' } + + # ==================================================== + # run simulation + # ==================================================== + + # count down calculation + args.state.count_down -= 1 + args.state.count_down = -1 if args.state.count_down < -1 + + # ==================================================== + # process player input + # ==================================================== + # if it isn't game over let them move + if !game_over? args + dir_y = 0 + dir_x = 0 + + # determine the change horizontally + if args.inputs.keyboard.up + dir_y += args.state.player_speed + elsif args.inputs.keyboard.down + dir_y -= args.state.player_speed + end + + # determine the change vertically + if args.inputs.keyboard.left + dir_x -= args.state.player_speed + elsif args.inputs.keyboard.right + dir_x += args.state.player_speed + end + + # determine if teleport can be used + if args.inputs.keyboard.key_down.space && args.state.teleports > 0 + args.state.teleports -= 1 + dir_x *= 20 + dir_y *= 20 + end + + # apply change to player + args.state.player.x += dir_x + args.state.player.y += dir_y + else + # if r is pressed, reset the game + if args.inputs.keyboard.key_down.r + $gtk.reset + return + end + end + + # ==================================================== + # determine score + # ==================================================== + + # calculate new score if the player is at goal + if !game_over? args + + # if the player is at the goal, then move the goal + if args.state.player.intersect_rect? args.state.target + # increment the goal + args.state.score += 1 + + # move the goal to a random location + args.state.target = { x: (rand args.grid.w), y: (rand args.grid.h), w: 20, h: 20 } + + # make sure the goal is inside the view area + if args.state.target.x < 0 + args.state.target.x += 20 + elsif args.state.target.x > 1280 + args.state.target.x -= 20 + end + + # make sure the goal is inside the view area + if args.state.target.y < 0 + args.state.target.y += 20 + elsif args.state.target.y > 720 + args.state.target.y -= 20 + end + end + end + end + + def game_over? args + args.state.count_down < 0 + end + + $gtk.reset + +#+end_src + +*** Teenytiny - Teenytiny Starting Point - license.txt +#+begin_src ruby + # ./samples/99_genre_teenytiny/teenytiny_starting_point/license.txt + Copyright 2019 DragonRuby LLC + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#+end_src + ** OSS 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 @@ -35432,66 +38300,32 @@ Follows is a source code listing for all files that have been open sourced. This class Args include ArgsDeprecated include Serialize - - # Contains information related to input devices and input events. - # - # @return [Inputs] + attr_accessor :cvars 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 @inputs = Inputs.new @outputs = Outputs.new args: self + @cvars = {} @audio = {} @passes = [] @state = OpenEntity.new + @temp_state = OpenEntity.new @state.tick_count = -1 @runtime = runtime @recording = recording @@ -35521,11 +38355,12 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -35657,6 +38492,23 @@ Follows is a source code listing for all files that have been open sourced. This def autocomplete_methods [:inputs, :outputs, :gtk, :state, :geometry, :audio, :grid, :layout, :fn] end + + def method_missing name, *args, &block + if (args.length <= 1) && (@state.as_hash.key? name) + raise <<-S + * ERROR - :#{name} method missing on ~#{self.class.name}~. + The method + :#{name} + with args + #{args} + doesn't exist on #{inspect}. + ** POSSIBLE SOLUTION - ~args.state.#{name}~ exists. + Did you forget ~.state~ before ~.#{name}~? + S + end + + super + end end end @@ -35691,10 +38543,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -35704,14 +38556,14 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -35765,7 +38617,7 @@ Follows is a source code listing for all files that have been open sourced. This @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 @@ -35775,7 +38627,7 @@ Follows is a source code listing for all files that have been open sourced. This @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 @@ -35830,6 +38682,10 @@ Follows is a source code listing for all files that have been open sourced. This args.state end + def temp_state + args.temp_state + end + def inputs args.inputs end @@ -35857,6 +38713,14 @@ Follows is a source code listing for all files that have been open sourced. This 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 #+end_src @@ -35864,6 +38728,7 @@ Follows is a source code listing for all files that have been open sourced. This *** attr_sprite.rb #+begin_src ruby # ./dragon/attr_sprite.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # attr_sprite.rb has been released under MIT (*only this file*). @@ -35903,7 +38768,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -35927,6 +38792,7 @@ Follows is a source code listing for all files that have been open sourced. This *** console.rb #+begin_src ruby # ./dragon/console.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console.rb has been released under MIT (*only this file*). @@ -35938,17 +38804,20 @@ Follows is a source code listing for all files that have been open sourced. This class Console include ConsoleDeprecated - attr_accessor :show_reason, :log, :logo, :background_color, - :text_color, :animation_duration, + attr_accessor :show_reason, :log, :logo, + :animation_duration, :max_log_lines, :max_history, :log, - :last_command_errored, :last_command, :error_color, :shown_at, - :header_color, :archived_log, :last_log_lines, :last_log_lines_count, + :last_command_errored, :last_command, :shown_at, + :archived_log, :last_log_lines, :last_log_lines_count, :suppress_left_arrow_behavior, :command_set_at, :toast_ids, :bottom, - :font_style, :menu + :font_style, :menu, + :background_color, :spam_color, :text_color, :warn_color, + :error_color, :header_color, :code_color, :comment_color, + :debug_color, :unfiltered_color 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 @@ -35963,13 +38832,22 @@ Follows is a source code listing for all files that have been open sourced. This @command_history_index = -1 @nonhistory_input = '' @logo = 'console-logo.png' - @history_fname = 'console_history.txt' + @history_fname = 'logs/console_history.txt' @background_color = Color.new [0, 0, 0, 224] - @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 + + # these are the colors for text at various log levels. + @spam_color = Color.new [160, 160, 160] + @debug_color = Color.new [0, 255, 0] + @text_color = Color.new [255, 255, 255] + @warn_color = Color.new [255, 255, 0] + @error_color = Color.new [200, 50, 50] + @unfiltered_color = Color.new [0, 255, 255] + load_history end @@ -36035,7 +38913,13 @@ Follows is a source code listing for all files that have been open sourced. This nil end - def add_text obj + def add_text obj, loglevel=-1 + # loglevel is one of the values of LogLevel in logging.h, or -1 to say "we don't care, colorize it with your special string parsing magic" + loglevel = -1 if loglevel < 0 + loglevel = 5 if loglevel > 5 # 5 == unfiltered (it's 0x7FFFFFFE in C, clamp it down) + loglevel = 2 if (loglevel == -1) && obj.start_with?('!c!') # oh well + colorstr = (loglevel != -1) ? "!c!#{loglevel}" : nil + @last_log_lines_count ||= 1 @log_invocation_count += 1 @@ -36044,12 +38928,18 @@ Follows is a source code listing for all files that have been open sourced. This log_lines = [] str.each_line do |s| - s.wrapped_lines(self.console_text_width).each do |l| - log_lines << l + if colorstr.nil? + s.wrapped_lines(self.console_text_width).each do |l| + log_lines << l + end + else + s.wrapped_lines(self.console_text_width).each do |l| + log_lines << "#{colorstr}#{l}" + end end end - if log_lines == @last_log_lines + if log_lines == @last_log_lines && log_lines.length != 0 @last_log_lines_count += 1 new_log_line_with_count = @last_log_lines.last + " (#{@last_log_lines_count})" if log_lines.length > 1 @@ -36242,6 +39132,8 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -36250,13 +39142,19 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -36316,10 +39214,12 @@ Follows is a source code listing for all files that have been open sourced. This def mouse_wheel_scroll args @inertia ||= 0 - if args.inputs.mouse.wheel && args.inputs.mouse.wheel.y > 0 - @inertia = 1 - elsif args.inputs.mouse.wheel && args.inputs.mouse.wheel.y < 0 - @inertia = -1 + if args.inputs.mouse.wheel + if args.inputs.mouse.wheel.y > 0 + @inertia = 1 + elsif args.inputs.mouse.wheel.y < 0 + @inertia = -1 + end end if args.inputs.mouse.click @@ -36328,13 +39228,11 @@ Follows is a source code listing for all files that have been open sourced. This return if @inertia == 0 - if @inertia != 0 - @inertia = (@inertia * 0.7) - if @inertia > 0 - @log_offset -= 1 - elsif @inertia < 0 - @log_offset += 1 - end + @inertia = (@inertia * 0.7) + if @inertia > 0 + @log_offset += 1 + elsif @inertia < 0 + @log_offset -= 1 end if @inertia.abs < 0.01 @@ -36352,6 +39250,7 @@ Follows is a source code listing for all files that have been open sourced. This if console_toggle_key_down? args args.inputs.text.clear toggle + args.inputs.keyboard.clear if !@visible end return unless visible? @@ -36363,7 +39262,16 @@ Follows is a source code listing for all files that have been open sourced. This @log_offset = 0 if @log_offset < 0 if args.inputs.keyboard.key_down.enter - eval_the_set_command + if slide_progress > 0.5 + # in the event of an exception, the console window pops up + # and is pre-filled with $gtk.reset. + # there is an annoying scenario where the exception could be thrown + # by pressing enter (while playing the game). if you press enter again + # quickly, then the game is reset which closes the console. + # so enter in the console is only evaluated if the slide_progress + # is atleast half way down the page. + eval_the_set_command + end elsif args.inputs.keyboard.key_down.v if args.inputs.keyboard.key_down.control || args.inputs.keyboard.key_down.meta prompt << $gtk.ffi_misc.getclipboard @@ -36440,7 +39348,7 @@ Follows is a source code listing for all files that have been open sourced. This def write_line(args, left, y, str, archived: false) color = color_for_log_entry(str) color = color.mult_alpha(0.5) if archived - + str = str[4..-1] if str.start_with?('!c!') # chop off loglevel color args.outputs.reserved << font_style.label(x: left.shift_right(10), y: y, text: str, color: color) end @@ -36483,6 +39391,11 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -36505,7 +39418,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -36651,8 +39564,34 @@ Follows is a source code listing for all files that have been open sourced. This (log_entry.start_with? "**** ") end - def color_for_log_entry(log_entry) - if include_row_marker? log_entry + 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_plain_text log_entry + log_entry = log_entry[4..-1] if log_entry.start_with? "!c!" + + 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 @@ -36667,6 +39606,29 @@ Follows is a source code listing for all files that have been open sourced. This end end + def color_for_log_entry(log_entry) + if log_entry.start_with?('!c!') # loglevel color specified. + return case log_entry[3..3].to_i + when 0 # spam + @spam_color + when 1 # debug + @debug_color + #when 2 # info (caught by the `else` block.) + # @text_color + when 3 # warn + @warn_color + when 4 # error + @error_color + when 5 # unfiltered + @unfiltered_color + else + color_for_plain_text log_entry + end + end + + return color_for_plain_text log_entry + end + def prompt @prompt ||= Prompt.new(font_style: font_style, text_color: @text_color, console_text_width: console_text_width) end @@ -36703,6 +39665,7 @@ Follows is a source code listing for all files that have been open sourced. This *** console_color.rb #+begin_src ruby # ./dragon/console_color.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_color.rb has been released under MIT (*only this file*). @@ -36727,6 +39690,10 @@ Follows is a source code listing for all files that have been open sourced. This @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 @@ -36739,6 +39706,7 @@ Follows is a source code listing for all files that have been open sourced. This *** console_font_style.rb #+begin_src ruby # ./dragon/console_font_style.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_font_style.rb has been released under MIT (*only this file*). @@ -36774,7 +39742,7 @@ Follows is a source code listing for all files that have been open sourced. This size_enum: size_enum, alignment_enum: alignment_enum, **color.to_h, - }.label + }.label! end end end @@ -36785,6 +39753,7 @@ Follows is a source code listing for all files that have been open sourced. This *** console_menu.rb #+begin_src ruby # ./dragon/console_menu.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_menu.rb has been released under MIT (*only this file*). @@ -36838,7 +39807,7 @@ Follows is a source code listing for all files that have been open sourced. This def itch_wizard_clicked @console.scroll_to_bottom - $wizards.itch.start + $wizards.itch.restart end def docs_clicked @@ -36863,6 +39832,7 @@ Follows is a source code listing for all files that have been open sourced. This @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 = [ @@ -36921,8 +39891,8 @@ Follows is a source code listing for all files that have been open sourced. This 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| [ @@ -36950,6 +39920,7 @@ Follows is a source code listing for all files that have been open sourced. This *** console_prompt.rb #+begin_src ruby # ./dragon/console_prompt.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # console_prompt.rb has been released under MIT (*only this file*). @@ -37023,14 +39994,14 @@ Follows is a source code listing for all files that have been open sourced. This return if @cursor_position.zero? new_pos = @cursor_position - 1 - (is_word_boundary? @current_input_str[new_pos]) ? + (is_word_boundary? @current_input_str[new_pos]) ? (new_pos -= 1 until !(is_word_boundary? @current_input_str[new_pos - 1]) || new_pos.zero?): (new_pos -= 1 until (is_word_boundary? @current_input_str[new_pos - 1]) || new_pos.zero?) @cursor_position = new_pos update_cursor_position_px end - + def move_cursor_right @cursor_position += 1 if @cursor_position < current_input_str.length update_cursor_position_px @@ -37043,7 +40014,7 @@ Follows is a source code listing for all files that have been open sourced. This (is_word_boundary? @current_input_str[new_pos]) ? (new_pos += 1 until !(is_word_boundary? @current_input_str[new_pos]) || (new_pos.equal? str_len)): (new_pos += 1 until (is_word_boundary? @current_input_str[new_pos]) || (new_pos.equal? str_len)) - + @cursor_position = new_pos update_cursor_position_px end @@ -37109,11 +40080,11 @@ Follows is a source code listing for all files that have been open sourced. This # partition the original list of items into a string to be printed items.each_slice(columns).each_with_index do |cells, i| - pretty_print_row_seperator string_width, cell_width, column_width, columns + pretty_print_row_separator string_width, cell_width, column_width, columns pretty_print_row cells, string_width, cell_width, column_width, columns end - pretty_print_row_seperator string_width, cell_width, column_width, columns + pretty_print_row_separator string_width, cell_width, column_width, columns end end @@ -37126,17 +40097,17 @@ Follows is a source code listing for all files that have been open sourced. This "#{" " * (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 @@ -37145,12 +40116,12 @@ Follows is a source code listing for all files that have been open sourced. This 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, @@ -37778,7 +40749,7 @@ Follows is a source code listing for all files that have been open sourced. This :l1, :r1, :l2, :r2, :l3, :r3, - :start, :select, + :start, :select, :home, :directional_up, :directional_down, :directional_left, :directional_right ].freeze @@ -37786,6 +40757,22 @@ Follows is a source code listing for all files that have been open sourced. This 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] @@ -37834,7 +40821,7 @@ Follows is a source code listing for all files that have been open sourced. This 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("|") }) @@ -37891,6 +40878,12 @@ Follows is a source code listing for all files that have been open sourced. This 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_") @@ -37928,8 +40921,8 @@ Follows is a source code listing for all files that have been open sourced. This ease_extended start_tick, current_tick, start_tick + duration, - (initial_value *definitions), - (final_value *definitions), + initial_value(*definitions), + final_value(*definitions), *definitions end @@ -38123,6 +41116,22 @@ Follows is a source code listing for all files that have been open sourced. This module GTK module Geometry + def self.rotate_point point, angle, around = nil + s = Math.sin angle.to_radians + c = Math.cos angle.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 @@ -38204,7 +41213,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38221,7 +41230,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38238,7 +41247,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38247,7 +41256,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38290,8 +41299,16 @@ Follows is a source code listing for all files that have been open sourced. This end # @gtk - def self.line_y_intercept line - line.y - line_slope(line) * line.x + def self.line_y_intercept line, replace_infinity: nil + line.y - line_slope(line, replace_infinity: replace_infinity) * line.x + rescue Exception => e + raise <<-S + * ERROR: ~Geometry::line_y_intercept~ + The following exception was thrown for line: #{line} + #{e} + + Consider passing in ~replace_infinity: VALUE~ to handle for vertical lines. + S end # @gtk @@ -38367,14 +41384,22 @@ Follows is a source code listing for all files that have been open sourced. This end # @gtk - def self.line_intersect line_one, line_two - m1 = line_slope(line_one) - m2 = line_slope(line_two) - b1 = line_y_intercept(line_one) - b2 = line_y_intercept(line_two) + def self.line_intersect line_one, line_two, replace_infinity: nil + m1 = line_slope(line_one, replace_infinity: replace_infinity) + m2 = line_slope(line_two, replace_infinity: replace_infinity) + b1 = line_y_intercept(line_one, replace_infinity: replace_infinity) + b2 = line_y_intercept(line_two, replace_infinity: replace_infinity) x = (b1 - b2) / (m2 - m1) y = (-b2.fdiv(m2) + b1.fdiv(m1)).fdiv(1.fdiv(m1) - 1.fdiv(m2)) [x, y] + rescue Exception => e + raise <<-S + * ERROR: ~Geometry::line_intersect~ + The following exception was thrown for line_one: #{line_one}, line_two: #{line_two} + #{e} + + Consider passing in ~replace_infinity: VALUE~ to handle for vertical lines. + S end def self.contract_intersect_rect? @@ -38383,10 +41408,10 @@ Follows is a source code listing for all files that have been open sourced. This # @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] @@ -38414,6 +41439,7 @@ Follows is a source code listing for all files that have been open sourced. This - rect_one: #{rect_one} - rect_two: #{rect_two} #{context_help} + \n#{e} S end @@ -38424,14 +41450,14 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38440,31 +41466,34 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38499,7 +41528,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38513,7 +41542,21 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -38710,6 +41753,14 @@ Follows is a source code listing for all files that have been open sourced. This def bottom_right [@right, @bottom].point end + + def x + 0 + end + + def y + 0 + end end end @@ -38911,6 +41962,30 @@ Follows is a source code listing for all files that have been open sourced. This } 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] @@ -39009,7 +42084,8 @@ Follows is a source code listing for all files that have been open sourced. This value = Kernel.tick_count if value collection.each do |m| - self.instance_variable_set("@#{m.to_s}".to_sym, value) + m_to_s = m.to_s + self.instance_variable_set("@#{m_to_s}".to_sym, value) if m_to_s.strip.length > 0 rescue Exception => e raise e, <<-S * ERROR: @@ -39025,24 +42101,32 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -39426,6 +42510,10 @@ Follows is a source code listing for all files that have been open sourced. This (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] @@ -39497,19 +42585,14 @@ Follows is a source code listing for all files that have been open sourced. This *** ios_wizard.rb #+begin_src ruby # ./dragon/ios_wizard.rb + # coding: utf-8 # 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 + # Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński - class IOSWizard + class IOSWizard < Wizard def initialize @doctor_executed_at = 0 end @@ -39522,23 +42605,46 @@ Follows is a source code listing for all files that have been open sourced. This @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, + :determine_devcert, + + :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 ] @@ -39546,20 +42652,28 @@ Follows is a source code listing for all files that have been open sourced. This 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, + :determine_prodcert, + + :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 ] @@ -39595,6 +42709,8 @@ Follows is a source code listing for all files that have been open sourced. This @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 @@ -39616,8 +42732,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -39724,13 +42842,70 @@ Follows is a source code listing for all files that have been open sourced. This 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= + # devcert is the certificate to use for development/deploying to your local device + devcert= + # prodcert is the certificate to use for distribution to the app store + prodcert= + 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, appid, devcert, and prodcert.", + "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 @@ -39754,37 +42929,22 @@ Follows is a source code listing for all files that have been open sourced. This $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}.", "" + def determine_app_id + @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 - 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] + def determine_devcert + @certificate_name = ios_metadata.devcert + raise_ios_metadata_required if @certificate_name.strip.length == 0 + log_info "Dev Certificate is set to: #{@certificate_name}" end - def determine_app_id - @app_id = app_id_from_provisioning_profile @opts[:env] - - log_info "App Identifier is set to : #{@app_id}" + def determine_prodcert + @certificate_name = ios_metadata.prodcert + raise_ios_metadata_required if @certificate_name.strip.length == 0 + log_info "Production (Distribution) Certificate is set to: #{@certificate_name}" end def set_app_name name @@ -39802,16 +42962,10 @@ Follows is a source code listing for all files that have been open sourced. This end end - def blow_away_temp + def clear_tmp_directory sh "rm -rf #{tmp_directory}" end - def stage_app - log_info "Staging." - sh "mkdir -p #{tmp_directory}" - sh "cp -R #{relative_path}/dragonruby-ios.app \"#{tmp_directory}/#{@app_name}.app\"" - end - def set_app_id id log_info = "App Id set to: #{id}" @app_id = id @@ -39842,34 +42996,13 @@ Follows is a source code listing for all files that have been open sourced. This def check_for_certs log_info "Attempting to find certificates on your computer." - if !cli_app_exist?(security_cli_app) - raise WizardException.new( - "* It doesn't look like you have #{security_cli_app}.", - "** 1. Open Disk Utility and run First Aid.", - { w: 700, h: 148, path: get_reserved_sprite("disk-utility.png") }, - ) - end - - if valid_certs.length == 0 - raise WizardException.new( - "* It doesn't look like you have any valid certs installed.", - "** 1. Open Xcode.", - "** 2. Log into your developer account. Xcode -> Preferences -> Accounts.", - { w: 700, h: 98, path: get_reserved_sprite("login-xcode.png") }, - "** 3. After loggin in, select Manage Certificates...", - { w: 700, h: 115, path: get_reserved_sprite("manage-certificates.png") }, - "** 4. Add a certificate for Apple Development.", - { w: 700, h: 217, path: get_reserved_sprite("add-cert.png") }, - ) - raise "You do not have any Apple development certs on this computer." - end - if @production_build - @certificate_name = valid_certs.find_all { |f| f[:name].include? "Distribution" }.first[:name] + @certificate_name = ios_metadata[:prodcert] else - @certificate_name = valid_certs.find_all { |f| f[:name].include? "Development" }.first[:name] + @certificate_name = ios_metadata[:devcert] end - log_info "I will be using Certificate: '#{@certificate_name}'." + + log_info "I will be using certificate: '#{@certificate_name}'." end def idevice_id_cli_app @@ -39884,24 +43017,6 @@ Follows is a source code listing for all files that have been open sourced. This "xcodebuild" end - def valid_certs - certs = sh("#{security_cli_app} -q find-identity -p codesigning -v").each_line.map do |l| - if l.include?(")") && !l.include?("Developer ID") && (l.include?("Development") || l.include?("Distribution")) - l.strip - else - nil - end - end.reject_nil.map do |l| - number, id, name = l.split(' ', 3) - name = name.gsub("\"", "") if name - { - number: 1, - id: id, - name: name - } - end - end - def connected_devices sh("idevice_id -l").strip.each_line.map do |l| l.strip @@ -39943,7 +43058,8 @@ Follows is a source code listing for all files that have been open sourced. This 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\"" @@ -39981,15 +43097,15 @@ Follows is a source code listing for all files that have been open sourced. This <key>CFBundleExecutable</key> <string>:app_name</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.6</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> - <string>5.6</string> + <string>:app_version</string> <key>CFBundleIcons</key> <dict> <key>CFBundlePrimaryIcon</key> @@ -40138,13 +43254,13 @@ Follows is a source code listing for all files that have been open sourced. This <key>CFBundleIdentifier</key> <string>:app_id</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundleName</key> <string>:app_name</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.2</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleSupportedPlatforms</key> @@ -40152,7 +43268,7 @@ Follows is a source code listing for all files that have been open sourced. This <string>iPhoneOS</string> </array> <key>CFBundleVersion</key> - <string>5.2</string> + <string>:app_version</string> <key>DTCompiler</key> <string>com.apple.compilers.llvm.clang.1_0</string> <key>DTPlatformBuild</key> @@ -40238,6 +43354,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40291,13 +43408,13 @@ Follows is a source code listing for all files that have been open sourced. This <key>CFBundleIdentifier</key> <string>:app_id</string> <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> + <string>:app_version</string> <key>CFBundleName</key> <string>:app_name</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>5.2</string> + <string>:app_version</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleSupportedPlatforms</key> @@ -40305,7 +43422,7 @@ Follows is a source code listing for all files that have been open sourced. This <string>iPhoneOS</string> </array> <key>CFBundleVersion</key> - <string>5.2</string> + <string>:app_version</string> <key>DTCompiler</key> <string>com.apple.compilers.llvm.clang.1_0</string> <key>DTPlatformBuild</key> @@ -40389,8 +43506,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40412,28 +43531,56 @@ Follows is a source code listing for all files that have been open sourced. This "#{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 + log_info "Staging." + sh "mkdir -p #{tmp_directory}" + sh "cp -R #{relative_path}/dragonruby-ios.app \"#{tmp_directory}/#{@app_name}.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 @@ -40466,10 +43613,22 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40490,6 +43649,26 @@ Follows is a source code listing for all files that have been open sourced. This 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 #+end_src @@ -40497,42 +43676,19 @@ Follows is a source code listing for all files that have been open sourced. This *** itch_wizard.rb #+begin_src ruby # ./dragon/itch_wizard.rb + # coding: utf-8 # 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 @@ -40550,7 +43706,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40562,70 +43718,67 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40668,7 +43821,7 @@ Follows is a source code listing for all files that have been open sourced. This end if @icon - text += "icon=metadata/#{@icon}\n" + text += "icon=#{@icon}\n" else text += "#icon=metadata/icon.png\n" end @@ -40686,10 +43839,25 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -40700,7 +43868,7 @@ Follows is a source code listing for all files that have been open sourced. This 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}" @@ -40771,6 +43939,7 @@ Follows is a source code listing for all files that have been open sourced. This *** layout.rb #+begin_src ruby # ./dragon/layout.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # layout.rb has been released under MIT (*only this file*). @@ -41049,10 +44218,18 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41075,18 +44252,124 @@ Follows is a source code listing for all files that have been open sourced. This 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] @@ -41094,7 +44377,9 @@ Follows is a source code listing for all files that have been open sourced. This 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] @@ -41132,13 +44417,21 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41173,7 +44466,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41196,7 +44489,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41247,6 +44540,26 @@ Follows is a source code listing for all files that have been open sourced. This @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, @@ -41260,6 +44573,13 @@ Follows is a source code listing for all files that have been open sourced. This def to_s serialize.to_s end + + def reset + @primitives = nil + @rect_cache ||= {} + @rect_cache.clear + end + end end @@ -41399,6 +44719,11 @@ Follows is a source code listing for all files that have been open sourced. This self.puts message end + def self.reset + @once = {} + nil + end + def self.puts_once *ids, message id = "#{ids}" @once ||= {} @@ -41535,6 +44860,55 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** metadata.rb +#+begin_src ruby + # ./dragon/metadata.rb + # coding: utf-8 + # Copyright 2021 DragonRuby LLC + # MIT License + # metadata.rb has been released under MIT (*only this file*). + + # Contributors outside of DragonRuby who also hold Copyright: Michał Dudziński + + 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 + +#+end_src + *** numeric.rb #+begin_src ruby # ./dragon/numeric.rb @@ -41553,6 +44927,35 @@ Follows is a source code listing for all files that have been open sourced. This 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. # @@ -41568,10 +44971,26 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41796,7 +45215,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41813,21 +45232,21 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -41854,6 +45273,18 @@ Follows is a source code listing for all files that have been open sourced. This (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 @@ -41955,32 +45386,6 @@ Follows is a source code listing for all files that have been open sourced. This (0..self).to_a end - def >= other - return false if !other - return gte other - end - - def > other - return false if !other - return gt other - end - - def <= other - return false if !other - return lte other - end - - def < other - return false if !other - 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? @@ -42066,34 +45471,6 @@ Follows is a source code listing for all files that have been open sourced. This S end - def - other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :-, e - end - - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - def serialize self end @@ -42119,6 +45496,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -42144,40 +45525,6 @@ Follows is a source code listing for all files that have been open sourced. This return !even? end - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - - def - other - return nil unless other - super - 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`. # @@ -42233,34 +45580,6 @@ Follows is a source code listing for all files that have been open sourced. This alias_method :__original_multiply__, :* unless Float.instance_methods.include? :__original_multiply__ alias_method :__original_divide__, :- unless Float.instance_methods.include? :__original_divide__ - def - other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :-, e - end - - def + other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :+, e - end - - def * other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :*, e - end - - def / other - return nil unless other - super - rescue Exception => e - __raise_arithmetic_exception__ other, :/, e - end - def serialize self end @@ -42302,6 +45621,276 @@ Follows is a source code listing for all files that have been open sourced. This def nan? false end + + def center other + (self - other).abs.fdiv(2) + end + end + +#+end_src + +*** recording.rb +#+begin_src ruby + # ./dragon/recording.rb + # coding: utf-8 + # Copyright 2019 DragonRuby LLC + # MIT License + # recording.rb has been released under MIT (*only this file*). + + module GTK + # FIXME: Gross + # @gtk + class Replay + # @gtk + def self.start file_name = nil + $recording.start_replay file_name + end + + # @gtk + def self.stop + $recording.stop_replay + end + end + + # @gtk + class Recording + def initialize runtime + @runtime = runtime + @tick_count = 0 + @global_input_order = 1 + end + + def tick + @tick_count += 1 + end + + def start_recording seed_number = nil + if !seed_number + log <<-S + * ERROR: + To start recording, you must provide an integer value to + seed random number generation. + S + $console.set_command "$recording.start SEED_NUMBER" + return + end + + if @is_recording + log <<-S + * ERROR: + You are already recording, first cancel (or stop) the current recording. + S + $console.set_command "$recording.cancel" + return + end + + if @is_replaying + log <<-S + * ERROR: + You are currently replaying a recording, first stop the replay. + S + return + end + + log_info <<-S + Recording has begun with RNG seed value set to #{seed_number}. + To stop recording use stop_recording(filename). + The recording will stop without saving a file if a filename is nil. + S + + $console.set_command "$recording.stop 'replay.txt'" + @runtime.__reset__ + @seed_number = seed_number + @runtime.set_rng seed_number + + @tick_count = 0 + @global_input_order = 1 + @is_recording = true + @input_history = [] + @runtime.notify! "Recording started. When completed, open the console to save it using $recording.stop FILE_NAME (or cancel).", 300 + end + + # @gtk + def start seed_number = nil + start_recording seed_number + end + + def is_replaying? + @is_replaying + end + + def is_recording? + @is_recording + end + + # @gtk + def stop file_name = nil + stop_recording file_name + end + + # @gtk + def cancel + stop_recording_core + @runtime.notify! "Recording cancelled." + end + + def stop_recording file_name = nil + if !file_name + log <<-S + * ERROR: + To please specify a file name when calling: + $recording.stop FILE_NAME + + If you do NOT want to save the recording, call: + $recording.cancel + S + $console.set_command "$recording.stop 'replay.txt'" + return + end + + if !@is_recording + log_info "You are not currently recording. Use start_recording(seed_number) to start recording." + $console.set_command "$recording.start" + return + end + + if file_name + text = "replay_version 2.0\n" + text << "stopped_at #{@tick_count}\n" + text << "seed #{@seed_number}\n" + text << "recorded_at #{Time.now.to_s}\n" + @input_history.each do |items| + text << "#{items}\n" + end + @runtime.write_file file_name, text + @runtime.write_file 'last_replay.txt', text + log_info "The recording has been saved successfully at #{file_name}. You can use start_replay(\"#{file_name}\") to replay the recording." + end + + $console.set_command "$replay.start '#{file_name}'" + stop_recording_core + @runtime.notify! "Recording saved to #{file_name}. To replay it: $replay.start \"#{file_name}\"." + log_info "You can run the replay later on startup using: ./dragonruby mygame --replay #{@replay_file_name}" + nil + end + + def stop_recording_core + @is_recording = false + @input_history = nil + @last_history = nil + @runtime.__reset__ + end + + def start_replay file_name = nil + if !file_name + log <<-S + * ERROR: + Please provide a file name to $recording.start. + S + $console.set_command "$replay.start 'replay.txt'" + return + end + + text = @runtime.read_file file_name + return false unless text + + if text.each_line.first.strip != "replay_version 2.0" + raise "The replay file #{file_name} is not compatible with this version of DragonRuby Game Toolkit. Please recreate the replay (sorry)." + end + + @replay_file_name = file_name + + $replay_data = { input_history: { } } + text.each_line do |l| + if l.strip.length == 0 + next + elsif l.start_with? 'replay_version' + next + elsif l.start_with? 'seed' + $replay_data[:seed] = l.split(' ').last.to_i + elsif l.start_with? 'stopped_at' + $replay_data[:stopped_at] = l.split(' ').last.to_i + elsif l.start_with? 'recorded_at' + $replay_data[:recorded_at] = l.split(' ')[1..-1].join(' ') + elsif l.start_with? '[' + name, value_1, value_2, value_count, id, tick_count = l.strip.gsub('[', '').gsub(']', '').split(',') + $replay_data[:input_history][tick_count.to_i] ||= [] + $replay_data[:input_history][tick_count.to_i] << { + id: id.to_i, + name: name.gsub(':', '').to_sym, + value_1: value_1.to_f, + value_2: value_2.to_f, + value_count: value_count.to_i + } + else + raise "Replay data seems corrupt. I don't know how to parse #{l}." + end + end + + $replay_data[:input_history].keys.each do |key| + $replay_data[:input_history][key] = $replay_data[:input_history][key].sort_by {|input| input[:id]} + end + + @runtime.__reset__ + @runtime.set_rng $replay_data[:seed] + @tick_count = 0 + @is_replaying = true + log_info "Replay has been started." + @runtime.notify! "Replay started [#{@replay_file_name}]." + end + + def stop_replay notification_message = "Replay has been stopped." + if !is_replaying? + log <<-S + * ERROR: + No replay is currently running. Call $replay.start FILE_NAME to start a replay. + S + + $console.set_command "$replay.start 'replay.txt'" + return + end + log_info notification_message + @is_replaying = false + $replay_data = nil + @tick_count = 0 + @global_input_order = 1 + $console.set_command_silent "$replay.start '#{@replay_file_name}'" + @runtime.__reset__ + @runtime.notify! notification_message + end + + def record_input_history name, value_1, value_2, value_count, clear_cache = false + return if @is_replaying + return unless @is_recording + @input_history << [name, value_1, value_2, value_count, @global_input_order, @tick_count] + @global_input_order += 1 + end + + def stage_replay_values + return unless @is_replaying + return unless $replay_data + + if $replay_data[:stopped_at] <= @tick_count + stop_replay "Replay completed [#{@replay_file_name}]. To rerun, bring up the Console and press enter." + return + end + + inputs_this_tick = $replay_data[:input_history][@tick_count] + + if @tick_count.zmod? 60 + log_info "Replay ends in #{($replay_data[:stopped_at] - @tick_count).idiv 60} second(s)." + end + + return unless inputs_this_tick + inputs_this_tick.each do |v| + args = [] + args << v[:value_1] if v[:value_count] >= 1 + args << v[:value_2] if v[:value_count] >= 2 + args << :replay + $gtk.send v[:name], *args + end + end + end end #+end_src @@ -42358,7 +45947,7 @@ Follows is a source code listing for all files that have been open sourced. This 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: []) @@ -42508,6 +46097,7 @@ Follows is a source code listing for all files that have been open sourced. This *** runtime/autocomplete.rb #+begin_src ruby # ./dragon/runtime/autocomplete.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # autocomplete.rb has been released under MIT (*only this file*). @@ -42534,7 +46124,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -42555,6 +46145,10 @@ Follows is a source code listing for all files that have been open sourced. This 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, @@ -42612,6 +46206,10 @@ Follows is a source code listing for all files that have been open sourced. This 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" @@ -42627,10 +46225,16 @@ Follows is a source code listing for all files that have been open sourced. This 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!("(", " ") @@ -42647,154 +46251,159 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src -*** runtime/draw.rb +*** runtime/benchmark.rb #+begin_src ruby - # ./dragon/runtime/draw.rb + # ./dragon/runtime/benchmark.rb + # coding: utf-8 # 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 - 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 - idx += 1 - end - - # pass.static_solids.each { |s| draw_solid s } + module Benchmark + def benchmark_single iterations, name, proc + log <<-S + ** Invoking :#{name}... + S idx = 0 - length = pass.static_solids.length - while idx < length - draw_solid (pass.static_solids.at idx) + r = nil + time_start = Time.now + while idx < iterations + r = proc.call 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 - - # 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 - 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.labels.each { |l| draw_label l } - idx = 0 - length = pass.labels.length - while idx < length - draw_label (pass.labels.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.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 + 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.lines.each { |l| draw_line l } - idx = 0 - length = pass.lines.length - while idx < length - draw_line (pass.lines.at idx) - idx += 1 - end + difference_time = first_place.time.-(second_place.time).*(1000).abs.round - # 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 + 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.borders.each { |b| draw_border b } - idx = 0 - length = pass.borders.length - while idx < length - draw_border (pass.borders.at idx) - idx += 1 - end + log_message = [] + only_one_result = first_place.name == second_place.name - # 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 + 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 - 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 + r + end + end + end + end + +#+end_src + +*** runtime/draw.rb +#+begin_src ruby + # ./dragon/runtime/draw.rb + # coding: utf-8 + # 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 + 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 + + if !self.production + fn.each_send pass.debug, self, :draw_primitive + fn.each_send pass.static_debug, self, :draw_primitive end - # 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 - 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 @@ -42805,7 +46414,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 + s = s.as_hash if s.is_a? OpenEntity + @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 @@ -42816,14 +46428,16 @@ Follows is a source code listing for all files that have been open sourced. This 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, + s = s.as_hash if s.is_a? OpenEntity + @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 @@ -42834,7 +46448,8 @@ Follows is a source code listing for all files that have been open sourced. This if s.respond_to? :draw_override s.draw_override @ffi_draw else - @ffi_draw.draw_screenshot s.path.s_or_default, + s = s.as_hash if s.is_a? OpenEntity + @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, @@ -42852,10 +46467,14 @@ Follows is a source code listing for all files that have been open sourced. This 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) + l = l.as_hash if l.is_a? OpenEntity + @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 @@ -42866,7 +46485,22 @@ Follows is a source code listing for all files that have been open sourced. This 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 + l = l.as_hash if l.is_a? OpenEntity + 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 @@ -42877,7 +46511,10 @@ Follows is a source code listing for all files that have been open sourced. This 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 + s = s.as_hash if s.is_a? OpenEntity + @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 @@ -42897,7 +46534,6 @@ Follows is a source code listing for all files that have been open sourced. This pause! pretty_print_exception_and_export! e end - end end end @@ -42907,6 +46543,7 @@ Follows is a source code listing for all files that have been open sourced. This *** runtime/framerate.rb #+begin_src ruby # ./dragon/runtime/framerate.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # framerate.rb has been released under MIT (*only this file*). @@ -42942,13 +46579,8 @@ Follows is a source code listing for all files that have been open sourced. This if @tick_speed_count > 60 * 2 if framerate_below_threshold? @last_framerate = current_framerate - if [email protected]? - if !@framerate_important_notification_happened - log_important framerate_warning_message - else - log framerate_warning_message - end - @framerate_important_notification_happened = true + if [email protected]? && [email protected]_replaying? + log framerate_warning_message end end @@ -42996,6 +46628,7 @@ Follows is a source code listing for all files that have been open sourced. This *** runtime/framerate_diagnostics.rb #+begin_src ruby # ./dragon/runtime/framerate_diagnostics.rb + # coding: utf-8 # Copyright 2019 DragonRuby LLC # MIT License # framerate_diagnostics.rb has been released under MIT (*only this file*). @@ -43113,7 +46746,7 @@ Follows is a source code listing for all files that have been open sourced. This 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, @@ -43122,7 +46755,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 20.from_top, @@ -43131,7 +46764,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 35.from_top, @@ -43140,7 +46773,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 50.from_top, @@ -43149,7 +46782,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255, size_enum: -2 - }.label, + }.label!, { x: 5, y: 65.from_top, @@ -43158,7 +46791,7 @@ Follows is a source code listing for all files that have been open sourced. This g: 255, b: 255, size_enum: -2 - }.label, + }.label!, ] end @@ -43183,7 +46816,14 @@ Follows is a source code listing for all files that have been open sourced. This 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' @@ -43221,6 +46861,7 @@ Follows is a source code listing for all files that have been open sourced. This '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', @@ -43253,6 +46894,7 @@ Follows is a source code listing for all files that have been open sourced. This '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 @@ -43271,10 +46913,8 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -43306,25 +46946,36 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -43366,6 +47017,30 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -43583,7 +47258,7 @@ Follows is a source code listing for all files that have been open sourced. This 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 @@ -43781,13 +47456,156 @@ Follows is a source code listing for all files that have been open sourced. This #+end_src +*** tweetcart.rb +#+begin_src ruby + # ./dragon/tweetcart.rb + # coding: utf-8 + # Copyright 2019 DragonRuby LLC + # MIT License + # tweetcart.rb has been released under MIT (*only this file*). + + def $top_level.TICK &block + $top_level.define_method(:tick) do |args| + args.outputs[:scene].w = 160 + args.outputs[:scene].h = 90 + args.outputs[:scene].background_color = [0, 0, 0, 0] + block.call args + args.outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: :scene } + end + + def $top_level.bg! *rgb + r,g,b = rgb + r ||= 255 + g ||= r + b ||= g + $args.outputs.background_color = [r, g, b] + end + + def $top_level.slds + $args.outputs[:scene].sprites + end + + def $top_level.slds! *os + if (os.first.is_a? Numeric) + sld!(*os) + else + os.each { |o| sld!(*o) } + end + end + + def $top_level.sld! *params + x, y, w, h, r, g, b, a = nil + if params.length == 2 + x, y = params + elsif params.length == 3 && (params.last.is_a? Array) + x = params[0] + y = params[1] + r, g, b, a = params[2] + r ||= 255 + g ||= r + b ||= g + a ||= 255 + elsif params.length == 4 + x, y, w, h = params + elsif params.length == 5 && (params.last.is_a? Array) + x = params[0] + y = params[1] + w = params[2] + h = params[3] + r,g,b,a = params[4] + r ||= 255 + g ||= r + b ||= g + a ||= 255 + elsif params.length >= 7 + x, y, w, h, r, g, b = params + else + raise "I don't know how to render #{params} with reasonable defaults." + end + + w ||= 1 + h ||= 1 + r ||= 255 + g ||= 255 + b ||= 255 + a ||= 255 + + slds << { x: x, y: y, + w: w, h: h, + r: r, g: g, b: b, a: a, + path: :pixel } + end + end + + =begin + wht = [255] * 3 + red = [255, 0, 0] + blu = [0, 130, 255] + purp = [150, 80, 255] + + TICK { + bg! 0 + + slds << [0, 0, 3, 3, 0, 255, 0, 255] + + sld! 10, 10 + sld! 20, 20, 3, 2 + sld! 30, 30, 2, 2, red + sld! 35, 35, blu + + slds! 40, 40 + + slds! [50, 50], + [60, 60, purp], + [70, 70, 10, 10, wht], + [80, 80, 4, 4, 255, 0, 255] + } + =end + +#+end_src + *** wizards.rb #+begin_src ruby # ./dragon/wizards.rb + # coding: utf-8 # 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 |
