AndyObtiva/glimmer

[Small idea / question] Customizing Glimmer Tetris?

rubyFeedback opened this issue · 1 comments

Heya andy,

This is mostly a small-ish question.

Is it possible to "customize" tetris? Specifically colours and
perhaps gradient for use of the individual blocks.

I assume some customization is possible e. g. you use:

block_size: BLOCK_SIZE

So I suppose one can tweak that. But is the same possible
for colours/fill? Can there be a black border around individual
tetris elements? like a 1px solid black border?

What about gradients, e. g. if we have the large I-shaped
tetris block, can this be styled individually too via gradient?

Since SWT probably allows most flexibility it may suffice if
the code could mention this perhaps? If it is too complicated
to support don't worry about it. I did not see colors at
https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/v4.18.3.1/samples/elaborate/tetris.rb
so I assume it is stored elsewhere.

I am sorry. This issue escaped me because I was busy with a work deadline when I received it and I forgot to respond to it afterwards.

The answer is definitely yes, but as a senior software developer, you should have figured out the answer for yourself. It is not something that is a big deal, and there are enough samples in Glimmer DSL for SWT to answer your own question.

In Glimmer DSL for SWT, there is a Tetris::View::Block component that represents every tetromino square you see in Tetris's view, which is at:
https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/view/block.rb

Notice how it has a base_color property data-bound underneath bevel:

require_relative 'bevel'

class Tetris
  module View
    class Block
      include Glimmer::UI::CustomWidget
  
      options :game_playfield, :block_size, :row, :column
  
      body {
        canvas { |canvas_proxy|
          bevel(size: block_size) {
            base_color <= [game_playfield[row][column], :color]
          }
        }
      }
    end
  end
end

Specifically, this is the line responsible for giving blocks their color through unidirectional data-binding to the color of a row/column on the game playfield:

base_color <= [game_playfield[row][column], :color]

Now, the game playfield is made up of blocks, which get their colors from tetromino objects:
https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/model/tetromino.rb

The only thing that matters to you is the color map at the top:

class Tetris
  module Model
    class Tetromino
      ORIENTATIONS = [:north, :east, :south, :west]
      
      LETTER_COLORS = {
        I: :cyan,
        J: :blue,
        L: :dark_yellow,
        O: :yellow,
        S: :green,
        T: :magenta,
        Z: :red,
      }
...

You can replace the static built-in colors (like :cyan) with rgb arrays as follows:

class Tetris
  module Model
    class Tetromino
      ORIENTATIONS = [:north, :east, :south, :west]
      
      LETTER_COLORS = {
        I: [120, 140, 200],
        J: [0, 0, 255],
        L: [0, 128, 128],
        O: [0, 255, 255],
        S: [0, 255, 0],
        T: [255, 0, 255],
        Z: [255, 0, 0],
      }

Now, you get a differently colored Tetris (note that this screenshot also includes the second change mentioned below about adding a 1-pixel black border):

Screen Shot 2023-03-19 at 11 27 16 PM

If you want to add a block border around the blocks, let's go back to Tetris::View::Block, and we'll notice it is filling its canvas with bevel components. These are at:
https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/view/bevel.rb

The original bevel body is:

      body {
        rectangle(x, y, size, size) {
          background <= [self, :base_color]
          
          polygon(0, 0, size, 0, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
            background <= [self, :base_color, on_read: ->(color_value) {
              unless color_value.nil?
                color = color(color_value)
                rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
              end
            }]
          }
          polygon(size, 0, size - bevel_pixel_size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size, size) {
            background <= [self, :base_color, on_read: ->(color_value) {
              unless color_value.nil?
                color = color(color_value)
                rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
              end
            }]
          }
          polygon(size, size, 0, size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size) {
            background <= [self, :base_color, on_read: ->(color_value) {
              unless color_value.nil?
                color = color(color_value)
                rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
              end
            }]
          }
          polygon(0, 0, 0, size, bevel_pixel_size, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
            background <= [self, :base_color, on_read: ->(color_value) {
              unless color_value.nil?
                color = color(color_value)
                rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
              end
            }]
          }
          rectangle(0, 0, size, size) {
            foreground <= [self, :base_color, on_read: ->(color_value) {
              # use gray instead of white for the border
              color_value == Model::Block::COLOR_CLEAR ? :gray : color_value
            }]
          }
        }
      }

In Glimmer DSL for SWT, you can take advantage of its relative positioning feature (not supported by other Glimmer toolkits yet) and actually nest everything inside a new rectangle that has a foreground color (meaning border color) of black:

      body {
        rectangle(x, y, size, size) {
          foreground :black
          
          rectangle(x + 1, y + 1, size - 2, size - 2) {
            background <= [self, :base_color]
            
            polygon(0, 0, size, 0, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
              background <= [self, :base_color, on_read: ->(color_value) {
                unless color_value.nil?
                  color = color(color_value)
                  rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
                end
              }]
            }
            polygon(size, 0, size - bevel_pixel_size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size, size) {
              background <= [self, :base_color, on_read: ->(color_value) {
                unless color_value.nil?
                  color = color(color_value)
                  rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
                end
              }]
            }
            polygon(size, size, 0, size, bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size, size - bevel_pixel_size) {
              background <= [self, :base_color, on_read: ->(color_value) {
                unless color_value.nil?
                  color = color(color_value)
                  rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
                end
              }]
            }
            polygon(0, 0, 0, size, bevel_pixel_size, size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
              background <= [self, :base_color, on_read: ->(color_value) {
                unless color_value.nil?
                  color = color(color_value)
                  rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
                end
              }]
            }
            rectangle(0, 0, size, size) {
              foreground <= [self, :base_color, on_read: ->(color_value) {
                # use gray instead of white for the border
                color_value == Model::Block::COLOR_CLEAR ? :gray : color_value
              }]
            }
          }
        }
      }

Notice how we also shrunk the size of the second (inner) rectangle to compensate for the 1-pixel border:

          rectangle(x + 1, y + 1, size - 2, size - 2) {
            ...
          }

Now, Tetris has a block border around all the blocks.

tetris-with-block-border-around-blocks

If you want gradiants in Glimmer DSL for SWT, you simply copy the examples in hello_canvas.rb by using background_pattern instead of background:
https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/hello/hello_canvas.rb

Unfortunately, backgorund_pattern does not support data-binding out of the box (I added it to the TODO.md list) because it receives a compound value, not a single value, so you'd have to do manual data-binding using the observe keyword.

Here is an example of re-writing the bevel component to have a gradiant (data-bound in after_body) instead of a bevel look (obviously in a real app, you'd have to rename the component to gradiant or something similar):

class Tetris
  module View
    class Bevel
      include Glimmer::UI::CustomShape
      
      options :base_color, :size, :bevel_pixel_size
      option :x, default: 0
      option :y, default: 0
      
      before_body do
        self.bevel_pixel_size = 0.16*size.to_f if bevel_pixel_size.nil?
      end
      
      after_body do
        observe(self, :base_color) do |new_base_color|
          new_base_color = color(new_base_color)
          sum_of_colors = new_base_color.red + new_base_color.green + new_base_color.blue
          gradiant_difference = sum_of_colors > 384 ? 150 : -150
          new_base_color2_red = [[new_base_color.red + gradiant_difference, 255].min, 0].max
          new_base_color2_green = [[new_base_color.green + gradiant_difference, 255].min, 0].max
          new_base_color2_blue = [[new_base_color.blue + gradiant_difference, 255].min, 0].max
          new_base_color2 = rgb(new_base_color2_red, new_base_color2_green, new_base_color2_blue)
          body_root.content {
            background_pattern 0, 0, size, size, new_base_color, new_base_color2
          }
        end
      end
      
      body {
        rectangle(x, y, size, size)
      }
    end
  end
end

Screen Shot 2023-03-20 at 12 12 23 AM

This should address all your questions, so I am closing this.