diff --git a/README.md b/README.md index 70ac817..3868248 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ irb(main):002> mural.users.current_user ### Upload a file to a mural -To upload a `my.pdf` file that's located in the same directory as where you are +To upload a `my.pdf` file that’s located in the same directory as where you are running the script: ```rb @@ -161,9 +161,41 @@ an email notification. ![mind map example](./img/mind-map.png) -- Likewise, smart planners are collection of widgets, and the API doesn't +- Likewise, smart planners are collection of widgets, and the API doesn’t specify anything about them. ![smart planner example](./img/smart-planner.png) [register-app]: https://developers.mural.co/public/docs/register-your-app + +## Invaders module! + +How about some good ol’ nostalgia? Let’s bring in the +[space invaders](https://en.wikipedia.org/wiki/Space_Invaders) in a mural! + +![space invaders demo](./img/space-invaders-demo.png) + +To accomplish this, simply create a script with the following code: + +```rb +require 'mural' + +MURAL_ID = 'mural-1' # some mural you have write access to + +client = Mural::Client.from_env + +begin + board = Mural::Invader::Board.new(client, MURAL_ID) + + draw_invader_params = Mural::Invader::DrawInvaderParams.new.tap do |params| + params.origin = [0, 0] + params.pixel_width = 10 # default, and the minimum size for the square shape + params.variant = 0 + end + + board.draw_invader_grid(draw_invader_params) +rescue Mural::Error => e + puts "\n--- ☠️☠️☠️ MURAL ERROR ☠️☠️☠️ ---" + puts e.inspect +end +``` diff --git a/img/space-invaders-demo.png b/img/space-invaders-demo.png new file mode 100644 index 0000000..146fc2e Binary files /dev/null and b/img/space-invaders-demo.png differ diff --git a/lib/mural/invader/board.rb b/lib/mural/invader/board.rb new file mode 100644 index 0000000..5694e00 --- /dev/null +++ b/lib/mural/invader/board.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module Mural + module Invader + class Board + INVADER = { + 'crab' => 'Mural::Invader::Crab::SPRITE', + 'octopus' => 'Mural::Invader::Octopus::SPRITE', + 'squid' => 'Mural::Invader::Squid::SPRITE' + }.freeze + + ROWS = [ + ['squid', '#69ED36FF'], + ['crab', '#69ED36FF'], + ['crab', '#68F1F4FF'], + ['octopus', '#68F1F4FF'], + ['octopus', '#D937EEFF'] + ].freeze + COLS = 11 + + # the widest invader (octopus) is 12 "pixels" wide + MAX_INVADER_WIDTH = 12 + + # they are all the same height + MAX_INVADER_HEIGHT = 8 + + # the gitter scale with the size of the "pixel" + GUTTER_SIZE = 3 + + # how many shapes can we send in a single request + MAX_SHAPES_IN_PAYLOAD = 1_000 + + attr_reader :client, :mural_id + + def initialize(client, mural_id) + @client = client + @mural_id = mural_id + end + + def draw_invader_grid(draw_invader_params) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + draw_invader_params.to_h => { origin:, pixel_width:, variant: } + origin_x, origin_y = origin + + cell_width = (MAX_INVADER_WIDTH + GUTTER_SIZE) * pixel_width + cell_height = (MAX_INVADER_HEIGHT + GUTTER_SIZE) * pixel_width + + payload = ROWS.flat_map.with_index do |invader, row| + invader_kind, invader_color = invader + + (0...COLS).flat_map do |col| + cell_origin = [ + origin_x + (cell_width * col), + origin_y + (cell_height * row) + ] + + cell_invader_params = + Mural::Invader::DrawInvaderParams.new.tap do |params| + params.color = invader_color + params.origin = cell_origin + params.variant = variant + params.pixel_width = pixel_width + end + + invader_shape(invader_kind, cell_invader_params) + end + end + + payload.each_slice(MAX_SHAPES_IN_PAYLOAD) do |shapes| + client.mural_content.create_shapes(mural_id, shapes) + end + end + + def draw_invader(kind, draw_invader_params) + client + .mural_content + .create_shapes(mural_id, invader_shape(kind, draw_invader_params)) + end + + def invader_shape( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + kind, + draw_invader_params + ) + draw_invader_params.to_h => { color:, origin:, pixel_width:, variant: } + sprite = Object.const_get(INVADER[kind])[variant] + origin_x, origin_y = origin + + sprite.flat_map.with_index do |row_content, row| + row_content.map.with_index do |cell, col| + next unless cell == 1 + + Mural::Widget::CreateShapeParams.new.tap do |params| + params.x = origin_x + (col * pixel_width) + params.y = origin_y + (row * pixel_width) + params.width = pixel_width + params.height = pixel_width + params.shape = 'square' + + params.style = + Mural::Widget::CreateShapeParams::Style.new.tap do |style| + style.background_color = color + + # We want a transparent border, else the "pixel" is wider than + # the desired dimension + style.border_color = '#00000000' + end + end + end + end.compact + end + end + end +end diff --git a/lib/mural/invader/crab.rb b/lib/mural/invader/crab.rb new file mode 100644 index 0000000..17b5a68 --- /dev/null +++ b/lib/mural/invader/crab.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Mural + module Invader + class Crab + SPRITE = [ + [ + [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0] + ], + [ + [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0] + ] + ].freeze + end + end +end diff --git a/lib/mural/invader/draw_invader_params.rb b/lib/mural/invader/draw_invader_params.rb new file mode 100644 index 0000000..ec62bfd --- /dev/null +++ b/lib/mural/invader/draw_invader_params.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Mural + module Invader + class DrawInvaderParams + attr_accessor :pixel_width, :color, :origin, :variant + + def initialize + @pixel_width = 10 + @color = '#000000FF' + @origin = [0, 0] + @variant = 0 + end + + def to_h + { + pixel_width: pixel_width, + color: color, + origin: origin, + variant: variant + } + end + end + end +end diff --git a/lib/mural/invader/octopus.rb b/lib/mural/invader/octopus.rb new file mode 100644 index 0000000..e289e2b --- /dev/null +++ b/lib/mural/invader/octopus.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Mural + module Invader + class Octopus + SPRITE = [ + [ + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] + ], + [ + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0], + [0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0] + ] + ].freeze + end + end +end diff --git a/lib/mural/invader/squid.rb b/lib/mural/invader/squid.rb new file mode 100644 index 0000000..ff4dd74 --- /dev/null +++ b/lib/mural/invader/squid.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Mural + module Invader + class Squid + SPRITE = [ + [ + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 1, 0, 1, 0], + [1, 0, 0, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0, 1, 0] + ], + [ + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 1, 0, 0, 1, 0, 0], + [0, 1, 0, 1, 1, 0, 1, 0], + [1, 0, 1, 0, 0, 1, 0, 1] + ] + ].freeze + end + end +end