diff options
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | docs/docs.css | 2 | ||||
| -rw-r--r-- | dragon/args.rb | 17 | ||||
| -rw-r--r-- | dragon/assert.rb | 92 | ||||
| -rw-r--r-- | dragon/console.rb | 101 | ||||
| -rw-r--r-- | dragon/console_menu.rb | 103 | ||||
| -rw-r--r-- | dragon/console_prompt.rb | 15 | ||||
| -rw-r--r-- | dragon/grid.rb | 39 | ||||
| -rw-r--r-- | dragon/inputs.rb | 6 | ||||
| -rw-r--r-- | dragon/kernel_docs.rb | 2 | ||||
| -rw-r--r-- | dragon/log.rb | 34 | ||||
| -rw-r--r-- | dragon/numeric.rb | 48 | ||||
| -rw-r--r-- | dragon/numeric_docs.rb | 240 | ||||
| -rw-r--r-- | dragon/outputs_docs.rb | 8 | ||||
| -rw-r--r-- | dragon/readme_docs.rb | 17 | ||||
| -rw-r--r-- | dragon/string.rb | 7 | ||||
| -rw-r--r-- | dragon/tests.rb | 13 | ||||
| -rw-r--r-- | dragon/trace.rb | 3 | ||||
| -rw-r--r-- | samples/00_beginner_ruby_primer/app/main.rb | 4 |
19 files changed, 656 insertions, 101 deletions
@@ -1 +1,5 @@ -*DS_Store*
\ No newline at end of file +*DS_Store* +/docs/source.txt +/docs/docs.txt +/docs/docs.html +/docs/search_results.txt diff --git a/docs/docs.css b/docs/docs.css index 7be4b7c..25cc677 100644 --- a/docs/docs.css +++ b/docs/docs.css @@ -84,7 +84,7 @@ hr:before { pre { border: solid 1px silver; padding: 10px; - font-size: 16px; + font-size: 14px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; diff --git a/dragon/args.rb b/dragon/args.rb index 2c4c604..38a28b7 100644 --- a/dragon/args.rb +++ b/dragon/args.rb @@ -41,7 +41,7 @@ module GTK # @return [OpenEntity] attr_accessor :state - # Gives you access to the top level DragonRuby runtime. + # Gives you access to the top level DragonRuby runtime. # # @return [Runtime] attr_accessor :runtime @@ -49,6 +49,8 @@ module GTK attr_accessor :passes + attr_accessor :wizards + def initialize runtime, recording @inputs = Inputs.new @outputs = Outputs.new args: self @@ -57,12 +59,14 @@ module GTK @state.tick_count = -1 @runtime = runtime @recording = recording - @grid = Grid.new runtime.ffi_draw + @grid = Grid.new runtime @render_targets = {} @all_tests = [] @geometry = GTK::Geometry + @wizards = Wizards.new end + # The number of ticks since the start of the game. # # @return [Integer] @@ -101,15 +105,6 @@ module GTK end def render_target name - if @state.tick_count == 0 - log_important <<-S -* WARNING: -~render_target~ with name ~#{name}~ was created -on ~args.state.tick_count == 0~. You cannot create ~render_targets~ on the -first frame and need to wait until ~args.state.tick_count >= 1~. -S - end - name = name.to_s if !@render_targets[name] @render_targets[name] = Outputs.new(args: self, target: name, background_color_override: [255, 255, 255, 0]) diff --git a/dragon/assert.rb b/dragon/assert.rb index 0eb8c2a..0c26f29 100644 --- a/dragon/assert.rb +++ b/dragon/assert.rb @@ -4,13 +4,57 @@ # assert.rb has been released under MIT (*only this file*). module GTK +=begin +This is a tiny assertion api for the unit testing portion of Game Toolkit. + +@example + +1. Create a file called tests.rb under mygame. +2. Any method that begins with the word test_ will be considered a test. + +def test_this_works args, assert + assert.equal! 1, 1 +end + +3. To run a test, save the file while the game is running. + +@example + +To add an assertion open up this class and write: + +class Assert + def custom_assertion actual, expected, message = nil + # this tell Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive). + @assertion_performed = true + + # perform your custom logic here and rais an exception to denote a failure. + + raise "Some Error. #{message}." + end +end +=end class Assert 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). +=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. + +@example + +def test_does_this_work args, assert + some_result = Person.new + assert.true! some_result + # OR + assert.true! some_result, "Person was not created." +end +=end def true! value, message = nil @assertion_performed = true if !value @@ -20,6 +64,16 @@ module GTK nil end +=begin +Assert if a value is a falsey value. + +@example + +def test_does_this_work args, assert + some_result = nil + assert.false! some_result +end +=end def false! value, message = nil @assertion_performed = true if value @@ -29,16 +83,39 @@ module GTK nil end +=begin +Assert if two values are equal. + +@example + +def test_does_this_work args, assert + a = 1 + b = 1 + assert.equal! a, b +end +=end def equal! actual, expected, message = nil @assertion_performed = true if actual != expected actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip - message = "actual: #{actual_string} did not equal expected: #{expected}.\n#{message}" + message = "actual:\n#{actual_string} did not equal\nexpected:\n#{expected}.\n#{message}" raise message end nil end +=begin +Assert if a value is explicitly nil (not false). + +@example + +def test_does_this_work args, assert + a = nil + b = false + assert.nil! a # this will pass + assert.nil! b # this will throw an exception. +end +=end def nil! value, message = nil @assertion_performed = true if !value.nil? @@ -47,18 +124,5 @@ module GTK end nil end - - def raises! exception_class, message = nil - @assertion_performed = true - begin - yield - rescue exception_class - return # Success - rescue Exception => e - raise "Expected #{exception_class.name} to be raised but instead #{e.class.name} was raised\n#{message}" - else - raise "Expected #{exception_class.name} to be raised but nothing was raised\n#{message}" - end - end end end diff --git a/dragon/console.rb b/dragon/console.rb index acacfd2..c90755a 100644 --- a/dragon/console.rb +++ b/dragon/console.rb @@ -13,11 +13,12 @@ module GTK :last_command_errored, :last_command, :error_color, :shown_at, :header_color, :archived_log, :last_log_lines, :last_log_lines_count, :suppress_left_arrow_behavior, :command_set_at, - :toast_ids, - :font_style + :toast_ids, :bottom, + :font_style, :menu def initialize @font_style = FontStyle.new(font: 'font.ttf', size_enum: -1, line_height: 1.1) + @menu = Menu.new self @disabled = false @log_offset = 0 @visible = false @@ -26,6 +27,7 @@ module GTK @log = [ 'Console ready.' ] @max_log_lines = 1000 # I guess...? @max_history = 1000 # I guess...? + @log_invocation_count = 0 @command_history = [] @command_history_index = -1 @nonhistory_input = '' @@ -76,6 +78,7 @@ module GTK end def addsprite obj + @log_invocation_count += 1 obj[:id] ||= "id_#{obj[:path]}_#{Time.now.to_i}".to_sym if @last_line_log_index && @@ -103,6 +106,7 @@ module GTK def addtext obj @last_log_lines_count ||= 1 + @log_invocation_count += 1 str = obj.to_s @@ -450,7 +454,7 @@ S def write_primitive_and_return_offset(args, left, y, str, archived: false) if str.is_a?(Hash) padding = 10 - args.outputs.reserved << [left + 10, y - padding * 1.66, str[:w], str[:h], str[:path]].sprite + args.outputs.reserved << [left + 10, y + 5, str[:w], str[:h], str[:path]].sprite return str[:h] + padding else write_line args, left, y, str, archived: archived @@ -465,25 +469,25 @@ S args.outputs.reserved << font_style.label(x: left.shift_right(10), y: y, text: str, color: color) end + def should_tick? + return false if !@toggled_at + return false if slide_progress == 0 + return false if @disabled + return visible? + end + def render args return if !@toggled_at + return if slide_progress == 0 - if visible? - percent = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip) - else - percent = @toggled_at.global_ease(@animation_duration, :flip, :quint) - end - - return if percent == 0 - - bottom = top - (h * percent) - args.outputs.reserved << [left, bottom, w, h, *@background_color.mult_alpha(percent)].solid - args.outputs.reserved << [right.shift_left(110), bottom.shift_up(630), 100, 100, @logo, 0, (80.0 * percent).to_i].sprite + @bottom = top - (h * slide_progress) + args.outputs.reserved << [left, @bottom, w, h, *@background_color.mult_alpha(slide_progress)].solid + args.outputs.reserved << [right.shift_left(110), @bottom.shift_up(630), 100, 100, @logo, 0, (80.0 * slide_progress).to_i].sprite - y = bottom + 2 # just give us a little padding at the bottom. + y = @bottom + 2 # just give us a little padding at the bottom. prompt.render args, x: left.shift_right(10), y: y y += line_height_px * 1.5 - args.outputs.reserved << line(y: y, color: @text_color.mult_alpha(percent)) + args.outputs.reserved << line(y: y, color: @text_color.mult_alpha(slide_progress)) y += line_height_px.to_f / 2.0 ((@log.size - @log_offset) - 1).downto(0) do |idx| @@ -492,8 +496,8 @@ S break if y > top end - # past log seperator - args.outputs.reserved << line(y: y + line_height_px.half, color: @text_color.mult_alpha(0.25 * percent)) + # past log separator + args.outputs.reserved << line(y: y + line_height_px.half, color: @text_color.mult_alpha(0.25 * slide_progress)) y += line_height_px @@ -504,10 +508,19 @@ S end render_log_offset args - render_help args, top if percent == 1 end - def render_help args, top + def tick_help args + tick_help_debounce args + alpha_rate = 20 + @render_help_target_alpha ||= 255 + @render_help_current_alpha ||= 255 + @render_help_target_alpha += 4 if @render_help_current_alpha == @render_help_target_alpha + @render_help_current_alpha = (@render_help_current_alpha.towards @render_help_target_alpha, 20) + + @render_help_target_alpha = @render_help_target_alpha.clamp(-255, 255) + @render_help_current_alpha = @render_help_current_alpha.clamp(-255, 255) + [ "* Prompt Commands: ", "You can type any of the following ", @@ -525,8 +538,33 @@ S ].each_with_index do |s, i| args.outputs.reserved << [args.grid.right - 10, top - 100 - line_height_px * i * 0.8, - s, -3, 2, 180, 180, 180].label + s, -3, 2, 180, 180, 180, (@render_help_current_alpha.clamp 0, 255)].label + end + end + + def tick_help_debounce args + hide_log_alpha = -255 + if hidden? + @render_help_current_alpha = -255 + end + + if prompt.last_input_str_changed + @render_help_target_alpha = hide_log_alpha end + + if args.inputs.mouse.moved + @render_help_target_alpha = hide_log_alpha + end + + if args.inputs.mouse.wheel + @render_help_target_alpha = hide_log_alpha + end + + if @render_help_last_log_invocation_count != @log_invocation_count + @render_help_target_alpha = hide_log_alpha + end + + @render_help_last_log_invocation_count = @log_invocation_count end def render_log_offset args @@ -582,9 +620,18 @@ S begin return if @disabled render args - calc args process_inputs args + return unless should_tick? + calc args + tick_help args + prompt.tick + menu.tick args rescue Exception => e + begin + puts "#{e}" + puts "* FATAL: The GTK::Console console threw an unhandled exception and has been reset. You should report this exception (along with reproduction steps) to DragonRuby." + rescue + end @disabled = true $stdout.puts e $stdout.puts "* FATAL: The GTK::Console console threw an unhandled exception and has been reset. You should report this exception (along with reproduction steps) to DragonRuby." @@ -722,5 +769,15 @@ S @prompt.clear :console_silent_eval end + + def slide_progress + return 0 if !@toggled_at + if visible? + @slide_progress = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip) + else + @slide_progress = @toggled_at.global_ease(@animation_duration, :flip, :quint) + end + @slide_progress + end end end diff --git a/dragon/console_menu.rb b/dragon/console_menu.rb new file mode 100644 index 0000000..e8ce973 --- /dev/null +++ b/dragon/console_menu.rb @@ -0,0 +1,103 @@ +# Copyright 2019 DragonRuby LLC +# MIT License +# console_menu.rb has been released under MIT (*only this file*). + +module GTK + class Console + class Menu + def initialize console + @console = console + end + + def record_clicked + $recording.start 100 + end + + def replay_clicked + $replay.start 'replay.txt' + end + + def reset_clicked + $gtk.reset + end + + def scroll_up_clicked + @console.scroll_up_half + end + + def scroll_down_clicked + @console.scroll_down_half + end + + def close_clicked + @console.hide + end + + def tick args + return unless @console.visible? + + # defaults + @buttons = [ + (button id: :record, row: 0, col: 15, text: "record", method: :record_clicked), + (button id: :replay, row: 0, col: 16, text: "replay", method: :replay_clicked), + (button id: :reset, row: 0, col: 17, text: "reset", method: :reset_clicked), + (button id: :scroll_up, row: 0, col: 18, text: "scroll up", method: :scroll_up_clicked), + (button id: :scroll_down, row: 0, col: 19, text: "scroll down", method: :scroll_down_clicked), + (button id: :close, row: 0, col: 20, text: "close", method: :close_clicked), + ] + + # render + args.outputs.reserved << @buttons.map { |b| b[:primitives] } + + # inputs + if args.inputs.mouse.click + clicked = @buttons.find { |b| args.inputs.mouse.inside_rect? b[:rect] } + if clicked + send clicked[:method] + end + end + end + + def rect_for_layout row, col + col_width = 50 + row_height = 50 + col_margin = 5 + row_margin = 5 + x = (col_margin + (col * col_width) + (col * col_margin)) + y = (row_margin + (row * row_height) + (row * row_margin) + row_height).from_top + { x: x, y: y, w: row_height, h: col_width } + end + + def button args + id, row, col, text, method = args[:id], args[:row], args[:col], args[:text], args[:method] + + font_height = @console.font_style.line_height_px.half + { + id: id, + rect: (rect_for_layout row, col), + method: method + }.let do |entity| + primitives = [] + primitives << entity[:rect].merge(a: 80).solid + primitives << entity[:rect].merge(r: 255, g: 255, b: 255).border + primitives << text.wrapped_lines(5) + .map_with_index do |l, i| + [ + entity[:rect][:x] + entity[:rect][:w].half, + entity[:rect][:y] + entity[:rect][:h].half + font_height - (i * (font_height + 2)), + l, -3, 1, 255, 255, 255 + ] + end.labels + + entity.merge(primitives: primitives) + end + end + + def serialize + { + not_supported: "#{self}" + } + end + end + end +end diff --git a/dragon/console_prompt.rb b/dragon/console_prompt.rb index dd454f7..aea5df8 100644 --- a/dragon/console_prompt.rb +++ b/dragon/console_prompt.rb @@ -8,7 +8,7 @@ module GTK class Console class Prompt - attr_accessor :current_input_str, :font_style, :console_text_width + attr_accessor :current_input_str, :font_style, :console_text_width, :last_input_str, :last_input_str_changed def initialize(font_style:, text_color:, console_text_width:) @prompt = '-> ' @@ -24,6 +24,7 @@ module GTK def <<(str) @current_input_str << str + @current_input_changed_at = Kernel.global_tick_count reset_autocomplete end @@ -114,6 +115,18 @@ S args.outputs.reserved << font_style.label(x: x - 2, y: y + 3, text: (" " * (@prompt.length + current_input_str.length)) + "|", color: @cursor_color) end + def tick + if (@current_input_changed_at) && + (@current_input_changed_at < Kernel.global_tick_count) && + (@last_input_str != @current_input_str) + @last_input_str_changed = true + @last_input_str = "#{@current_input_str}" + @current_input_changed_at = nil + else + @last_input_str_changed = false + end + end + private def last_period_index diff --git a/dragon/grid.rb b/dragon/grid.rb index e5c21a3..c2a6a43 100644 --- a/dragon/grid.rb +++ b/dragon/grid.rb @@ -60,8 +60,9 @@ module GTK attr_accessor :left_margin, :bottom_margin - def initialize ffi_draw - @ffi_draw = ffi_draw + def initialize runtime + @runtime = runtime + @ffi_draw = runtime.ffi_draw origin_bottom_left! end @@ -109,18 +110,18 @@ module GTK return if @name == :bottom_left @name = :bottom_left @origin_x = 0.0 - @origin_y = $gtk.logical_height + @origin_y = @runtime.logical_height @left = 0.0 - @right = $gtk.logical_width - @top = $gtk.logical_height + @right = @runtime.logical_width + @top = @runtime.logical_height @bottom = 0.0 @left_margin = 0.0 @bottom_margin = 0.0 - @center_x = $gtk.logical_width.half - @center_y = $gtk.logical_height.half - @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect + @center_x = @runtime.logical_width.half + @center_y = @runtime.logical_height.half + @rect = [@left, @bottom, @runtime.logical_width, @runtime.logical_height].rect @center = [@center_x, @center_y].point - @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION + @ffi_draw.set_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION end # Sets the rendering coordinate system to have its origin in the center. @@ -130,24 +131,24 @@ module GTK def origin_center! return if @name == :center @name = :center - @origin_x = $gtk.logical_width.half - @origin_y = $gtk.logical_height.half - @left = -$gtk.logical_width.half - @right = $gtk.logical_width.half - @top = $gtk.logical_height.half - @bottom = -$gtk.logical_height.half + @origin_x = @runtime.logical_width.half + @origin_y = @runtime.logical_height.half + @left = [email protected]_width.half + @right = @runtime.logical_width.half + @top = @runtime.logical_height.half + @bottom = [email protected]_height.half @center_x = 0.0 @center_y = 0.0 - @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect + @rect = [@left, @bottom, @runtime.logical_width, @runtime.logical_height].rect @center = [@center_x, @center_y].point - @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION + @ffi_draw.set_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION end # The logical width used for rendering. # # @return [Float] def w - $gtk.logical_width + @runtime.logical_width end # Half the logical width used for rendering. @@ -161,7 +162,7 @@ module GTK # # @return [Float] def h - $gtk.logical_height + @runtime.logical_height end # Half the logical height used for rendering. diff --git a/dragon/inputs.rb b/dragon/inputs.rb index fc6d8ef..a4ef40f 100644 --- a/dragon/inputs.rb +++ b/dragon/inputs.rb @@ -295,7 +295,7 @@ S return self.send m rescue Exception => e - log_important "#{e}}" + log_important "#{e}" end raise <<-S @@ -498,6 +498,10 @@ module GTK [@x, @y].point end + def inside_rect? rect + point.inside_rect? rect + end + alias_method :position, :point def clear diff --git a/dragon/kernel_docs.rb b/dragon/kernel_docs.rb index 1f4977b..00eadf6 100644 --- a/dragon/kernel_docs.rb +++ b/dragon/kernel_docs.rb @@ -53,6 +53,8 @@ S final_string += k.docs_all end + final_string += "\n" + (($gtk.read_file "docs/source.txt") || "") + html_parse_result = (__docs_to_html__ final_string) $gtk.write_file 'docs/docs.txt', "#{final_string}" diff --git a/dragon/log.rb b/dragon/log.rb index 60246e6..b2ee0e1 100644 --- a/dragon/log.rb +++ b/dragon/log.rb @@ -81,6 +81,30 @@ module GTK " end + def self.puts_error *args + args ||= [] + title = args[0] + additional = args[1..-1] || [] + additional = "" if additional.length == 0 + if !title.multiline? && join_lines(additional).multiline? + message = headline "ERROR: #{title}" do + dynamic_block do + additional + end + end + elsif title.multiline? + message = headline "ERROR: " do + dynamic_block do + args + end + end + else + message = "* ERROR: #{title} #{additional}".strip + end + + self.puts message + end + def self.puts_info *args args ||= [] title = args[0] @@ -110,14 +134,14 @@ module GTK @once ||= {} return if @once[id] @once[id] = id + if !$gtk.cli_arguments[:replay] && !$gtk.cli_arguments[:record] + $gtk.notify!("Open the DragonRuby Console by pressing [`] [~] [²] [^] [º] or [§]. [Message ID: #{id}].") + end write_to_log_and_puts "" write_to_log_and_puts "#{message.strip}" write_to_log_and_puts "" write_to_log_and_puts "[Message ID: #{id}]" write_to_log_and_puts "" - return if $gtk.cli_arguments[:replay] - return if $gtk.cli_arguments[:record] - $gtk.notify!("One time notification occurred. [Message ID: #{id}] (Open console for more info.)") end def self.puts_once_info *ids, message @@ -222,6 +246,10 @@ class Object log_with_color XTERM_COLOR[:bright_white], *args end + def log_error *args + GTK::Log.puts_error(*args) + end + def log_info *args GTK::Log.puts_info(*args) end diff --git a/dragon/numeric.rb b/dragon/numeric.rb index c2c461f..6d8b84d 100644 --- a/dragon/numeric.rb +++ b/dragon/numeric.rb @@ -28,12 +28,6 @@ class Numeric clamp(0, 255).to_i end - # For a given number, the elapsed frames since that number is returned. - # `Kernel.tick_count` is used to determine how many frames have elapsed. - # An override numeric value can be passed in which will be used instead - # of `Kernel.tick_count`. - # - # @gtk def elapsed_time tick_count_override = nil (tick_count_override || Kernel.tick_count) - self end @@ -51,25 +45,42 @@ class Numeric # moment in time. # # @gtk - def elapsed? offset, tick_count_override = nil - (self + offset) < (tick_count_override || Kernel.tick_count) + def elapsed? offset = 0, tick_count_override = Kernel.tick_count + (self + offset) < tick_count_override end - # This is helpful for determining the index of frame-by-frame sprite animation. - # The numeric value `self` represents the moment the animation started. `frame_index` - # takes three additional parameters: how many frames exist in the sprite animation; - # how long to hold each animation for; and whether the animation should repeat. - # - # @gtk - def frame_index frame_count, hold_for, repeat, tick_count_override = nil + def frame_index *opts + frame_count_or_hash, hold_for, repeat, tick_count_override = opts + if frame_count_or_hash.is_a? Hash + frame_count = frame_count_or_hash[:count] + hold_for = frame_count_or_hash[:hold_for] + repeat = frame_count_or_hash[:repeat] + tick_count_override = frame_count_or_hash[:tick_count_override] + else + frame_count = frame_count_or_hash + end + + tick_count_override ||= Kernel.tick_count animation_frame_count = frame_count animation_frame_hold_time = hold_for animation_length = animation_frame_hold_time * animation_frame_count - if !repeat && self.+(animation_length) < (tick_count_override || Kernel.tick_count).-(1) + return nil if Kernel.tick_count < self + + if !repeat && (self + animation_length) < (tick_count_override - 1) return nil else return self.elapsed_time.-(1).idiv(animation_frame_hold_time) % animation_frame_count end + rescue Exception => e + raise <<-S +* ERROR: +#{opts} +#{e} +S + end + + def zero? + self == 0 end def zero @@ -479,6 +490,11 @@ S def serialize self end + + def from_top + return 720 - self unless $gtk + $gtk.args.grid.h - self + end end class Fixnum diff --git a/dragon/numeric_docs.rb b/dragon/numeric_docs.rb new file mode 100644 index 0000000..fb8a94d --- /dev/null +++ b/dragon/numeric_docs.rb @@ -0,0 +1,240 @@ +# coding: utf-8 +# Copyright 2019 DragonRuby LLC +# MIT License +# numeric_docs.rb has been released under MIT (*only this file*). + +module NumericDocs + def docs_method_sort_order + [ + :docs_frame_index, + :docs_elapsed_time, + :docs_elapsed?, + :docs_new? + ] + end + + def docs_frame_index + <<-S +* DOCS: ~Numeric#frame_index~ + +This function is helpful for determining the index of frame-by-frame + sprite animation. The numeric value ~self~ represents the moment the + animation started. + +~frame_index~ takes three additional parameters: + +- How many frames exist in the sprite animation. +- How long to hold each animation for. +- Whether the animation should repeat. + +~frame_index~ will return ~nil~ if the time for the animation is out +of bounds of the parameter specification. + +Example using variables: + +#+begin_src ruby + def tick args + start_looping_at = 0 + number_of_sprites = 6 + number_of_frames_to_show_each_sprite = 4 + does_sprite_loop = true + + sprite_index = + start_looping_at.frame_index number_of_sprites, + number_of_frames_to_show_each_sprite, + does_sprite_loop + + sprite_index ||= 0 + + args.outputs.sprites << [ + 640 - 50, + 360 - 50, + 100, + 100, + "sprites/dragon-\#{sprite_index}.png" + ] + end +#+end_src + +Example using named parameters: + +#+begin_src ruby + def tick args + start_looping_at = 0 + + sprite_index = + start_looping_at.frame_index count: 6, + hold_for: 4, + repeat: true, + tick_count_override: args.state.tick_count + + sprite_index ||= 0 + + args.outputs.sprites << [ + 640 - 50, + 360 - 50, + 100, + 100, + "sprites/dragon-\#{sprite_index}.png" + ] + end +#+end_src + +S + end + + def docs_new? + <<-S +* DOCS: ~Numeric#created?~ +Returns true if ~Numeric#elapsed_time == 0~. Essentially communicating that +number is equal to the current frame. + +Example usage: + +#+begin_src ruby + def tick args + args.state.box_queue ||= [] + + if args.state.box_queue.empty? + args.state.box_queue << { name: :red, + create_at: args.state.tick_count + 60 } + end + + boxes_to_spawn_this_frame = args.state + .box_queue + .find_all { |b| b[:create_at].new? } + + boxes_to_spawn_this_frame.each { |b| puts "box \#{b} was new? on \#{args.state.tick_count}." } + + args.state.box_queue -= boxes_to_spawn_this_frame + end +#+end_src +S + end + + def docs_elapsed? + <<-S +* DOCS: ~Numeric#elapsed?~ +Returns true if ~Numeric#elapsed_time~ is greater than the number. An optional parameter can be +passed into ~elapsed?~ which is added to the number before evaluating whether ~elapsed?~ is true. + +Example usage (no optional parameter): + +#+begin_src ruby + def tick args + args.state.box_queue ||= [] + + if args.state.box_queue.empty? + args.state.box_queue << { name: :red, + destroy_at: args.state.tick_count + 60 } + args.state.box_queue << { name: :green, + destroy_at: args.state.tick_count + 60 } + args.state.box_queue << { name: :blue, + destroy_at: args.state.tick_count + 120 } + end + + boxes_to_destroy = args.state + .box_queue + .find_all { |b| b[:destroy_at].elapsed? } + + if !boxes_to_destroy.empty? + puts "boxes to destroy count: \#{boxes_to_destroy.length}" + end + + boxes_to_destroy.each { |b| puts "box \#{b} was elapsed? on \#{args.state.tick_count}." } + + args.state.box_queue -= boxes_to_destroy + end +#+end_src + +Example usage (with optional parameter): + +#+begin_src ruby + def tick args + args.state.box_queue ||= [] + + if args.state.box_queue.empty? + args.state.box_queue << { name: :red, + create_at: args.state.tick_count + 120, + lifespan: 60 } + args.state.box_queue << { name: :green, + create_at: args.state.tick_count + 120, + lifespan: 60 } + args.state.box_queue << { name: :blue, + create_at: args.state.tick_count + 120, + lifespan: 120 } + end + + # lifespan is passed in as a parameter to ~elapsed?~ + boxes_to_destroy = args.state + .box_queue + .find_all { |b| b[:create_at].elapsed? b[:lifespan] } + + if !boxes_to_destroy.empty? + puts "boxes to destroy count: \#{boxes_to_destroy.length}" + end + + boxes_to_destroy.each { |b| puts "box \#{b} was elapsed? on \#{args.state.tick_count}." } + + args.state.box_queue -= boxes_to_destroy + end +#+end_src + +S + end + + def docs_elapsed_time + <<-S +* DOCS: ~Numeric#elapsed_time~ +For a given number, the elapsed frames since that number is returned. +`Kernel.tick_count` is used to determine how many frames have elapsed. +An optional numeric argument can be passed in which will be used instead +of `Kernel.tick_count`. + +Here is an example of how elapsed_time can be used. + +#+begin_src ruby + def tick args + args.state.last_click_at ||= 0 + + # record when a mouse click occurs + if args.inputs.mouse.click + args.state.last_click_at = args.state.tick_count + end + + # Use Numeric#elapsed_time to determine how long it's been + if args.state.last_click_at.elapsed_time > 120 + args.outputs.labels << [10, 710, "It has been over 2 seconds since the mouse was clicked."] + end + end +#+end_src + +And here is an example where the override parameter is passed in: + +#+begin_src ruby + def tick args + args.state.last_click_at ||= 0 + + # create a state variable that tracks time at half the speed of args.state.tick_count + args.state.simulation_tick = args.state.tick_count.idiv 2 + + # record when a mouse click occurs + if args.inputs.mouse.click + args.state.last_click_at = args.state.simulation_tick + end + + # Use Numeric#elapsed_time to determine how long it's been + if (args.state.last_click_at.elapsed_time args.state.simulation_tick) > 120 + args.outputs.labels << [10, 710, "It has been over 4 seconds since the mouse was clicked."] + end + end +#+end_src + +S + end +end + +class Numeric + extend Docs + extend NumericDocs +end diff --git a/dragon/outputs_docs.rb b/dragon/outputs_docs.rb index 1e8eecc..16c97ec 100644 --- a/dragon/outputs_docs.rb +++ b/dragon/outputs_docs.rb @@ -4,6 +4,14 @@ # outputs_docs.rb has been released under MIT (*only this file*). module OutputsDocs + def docs_method_sort_order + [ + :docs_class, + :docs_solids, + :docs_borders + ] + end + def docs_class <<-S * DOCS: ~GTK::Outputs~ diff --git a/dragon/readme_docs.rb b/dragon/readme_docs.rb index 870a5a1..39fc283 100644 --- a/dragon/readme_docs.rb +++ b/dragon/readme_docs.rb @@ -509,7 +509,22 @@ It's a hard pill to swallow, but forget blindly accepted best practices and try to figure out the underlying motivation for a specific approach to game development. Collaborate with us. -*** Release Often And Quickly +*** Continuity of Design + +There is a programming idiom in software called "the pit of +success". The term normalizes up front pain as a necessity in the +(hopes that the investment will yield dividends "when you become +successful"). This results in more "Enterprise TM" code upfront, and +makes it more difficult to get started when you are new to programming. + +DragonRuby's philosophy is to provide a spectrum across the "make it +fast" vs "make it right" spectrum and provide incremental, intuitive +transitions between points on that spectrum. This is captured in how +render primitives can be represented as tuples/arrays, hashes, open +structs/entities, and then finally classes (as opposed to forcing devs +to use classes upfront). + +*** Release Often And Soon The biggest mistake game devs make is spending too much time in isolation building their game. Release something, however small, and diff --git a/dragon/string.rb b/dragon/string.rb index 8d2d9ba..5fa273c 100644 --- a/dragon/string.rb +++ b/dragon/string.rb @@ -38,6 +38,7 @@ S self[0..-2] end + # @gtk def wrapped_lines length self.each_line.map do |l| l = l.rstrip @@ -50,20 +51,22 @@ S end.flatten end + # @gtk def wrap length wrapped_lines(length).join.rstrip end + # @gtk def multiline? include? "\n" end - def indent_lines amount + def indent_lines amount, char = " " self.each_line.each_with_index.map do |l, i| if i == 0 l else - " " * amount + l + char * amount + l end end.join end diff --git a/dragon/tests.rb b/dragon/tests.rb index 5cda2fe..7cbba09 100644 --- a/dragon/tests.rb +++ b/dragon/tests.rb @@ -16,7 +16,6 @@ module GTK def run_test m args = Args.new $gtk, nil assert = Assert.new - setup(args) if respond_to?(:setup) begin log_test_running m send(m, args, assert) @@ -32,7 +31,6 @@ module GTK mark_test_failed m, e end end - teardown if respond_to?(:teardown) end def test_methods_focused @@ -43,6 +41,7 @@ module GTK Object.methods.find_all { |m| m.start_with? "test_" } end + # @gtk def start log "* TEST: gtk.test.start has been invoked." if test_methods_focused.length != 0 @@ -73,10 +72,6 @@ module GTK def log_inconclusive m self.inconclusive << {m: m} log "Inconclusive." - log_once :assertion_ok_note, <<-S -NOTE FOR INCONCLUSIVE TESTS: No assertion was performed in the test. -Add assert.ok! at the end of the test if you are using your own assertions. -S end def log_passed m @@ -125,6 +120,12 @@ S log "#{self.passed.length} test(s) passed." self.passed.each { |h| log "**** :#{h[:m]}" } log "*** Inconclusive" + if self.inconclusive.length > 0 + log_once :assertion_ok_note, <<-S +NOTE FOR INCONCLUSIVE TESTS: No assertion was performed in the test. +Add assert.ok! at the end of the test if you are using your own assertions. +S + end log "#{self.inconclusive.length} test(s) inconclusive." self.inconclusive.each { |h| log "**** :#{h[:m]}" } log "*** Failed" diff --git a/dragon/trace.rb b/dragon/trace.rb index c3b4bbd..04067bf 100644 --- a/dragon/trace.rb +++ b/dragon/trace.rb @@ -65,7 +65,7 @@ module GTK @traced_classes.clear $trace_enabled = false if !$gtk.production - $gtk.write_file 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of `tick` and this file will be populated with invocation information.\n" + $gtk.write_file 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of ~tick~ and this file will be populated with invocation information.\n" end end @@ -95,6 +95,7 @@ module GTK $trace_puts.clear end + # @gtk def self.trace! instance = nil $trace_history ||= [] $trace_enabled = true diff --git a/samples/00_beginner_ruby_primer/app/main.rb b/samples/00_beginner_ruby_primer/app/main.rb index 33c6dbf..6822cf3 100644 --- a/samples/00_beginner_ruby_primer/app/main.rb +++ b/samples/00_beginner_ruby_primer/app/main.rb @@ -76,8 +76,8 @@ end def tick_reset_button return unless state.hello_dragonruby_confirmed - $gtk.reserved_primitives << state.reset_button.background - $gtk.reserved_primitives << state.reset_button.label + $gtk.args.outputs.reserved << state.reset_button.background + $gtk.args.outputs.reserved << state.reset_button.label if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.reset_button.background) restart_tutorial end |
