DEV Community

loading...
Cover image for  Glimmer Tetris in One Day!

Glimmer Tetris in One Day!

andyobtiva profile image Andy Maleh ・5 min read

I present to you Glimmer Tetris in one day! Not bad, right!? It is included with the just released Glimmer DSL for SWT v4.18.2.3.

Glimmer Tetris

Top-level Glimmer GUI code:

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris.rb

# Tetris App View Custom Shell (represents `tetris` keyword)

require_relative 'tetris/model/game'

require_relative 'tetris/view/playfield'

class Tetris
  include Glimmer::UI::CustomShell

  BLOCK_SIZE = 25
  PLAYFIELD_WIDTH = 10
  PLAYFIELD_HEIGHT = 20

  before_body {
    Model::Game.configure_beeper do
      display.beep
    end

    Model::Game.start

    display {
      on_swt_keydown { |key_event|
        unless Model::Game.current_tetromino.stopped?
          case key_event.keyCode
          when swt(:arrow_down)
            Model::Game.current_tetromino.down
          when swt(:arrow_left)
            Model::Game.current_tetromino.left
          when swt(:arrow_right)
            Model::Game.current_tetromino.right
          when swt(:shift)
            if key_event.keyLocation == swt(:right) # right shift key
              Model::Game.current_tetromino.rotate(:right)
            elsif key_event.keyLocation == swt(:left) # left shift key
              Model::Game.current_tetromino.rotate(:left)
            end
          when 'd'.bytes.first, swt(:arrow_up)
            Model::Game.current_tetromino.rotate(:right)
          when 'a'.bytes.first
            Model::Game.current_tetromino.rotate(:left)
          end
        end
      }
    }
  }

  after_body {
    Thread.new {
      loop {
        sleep(0.9)
        sync_exec {
          unless @game_over
            Model::Game.current_tetromino.down
            if Model::Game.current_tetromino.stopped? && Model::Game.current_tetromino.row <= 0
              @game_over = true
              display.beep
              message_box(:icon_error) {
                text 'Tetris'
                message 'Game Over!'
              }.open
              Model::Game.restart
              @game_over = false
            end
            Model::Game.consider_adding_tetromino
          end
        }
      }
    }
  }

  body {
    shell(:no_resize) {
      text 'Glimmer Tetris'
      background :gray

      playfield(playfield_width: PLAYFIELD_WIDTH, playfield_height: PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
    }
  }
end

Tetris.launch
Enter fullscreen mode Exit fullscreen mode

Believe me when I tell you the code above is the cleanest Tetris implementation on Earth! In fact, the final part, which is the GUI body is only about 6 lines of code.

That is thanks in part to Glimmer DSL for SWT's ultra-modularity. After all, Tetris was declared as a Custom Shell (meaning it is a reusable app that can run standalone or as a window in another Glimmer app). Additionally, the GUI was broken up into multiple Custom Widgets (playfield and block):

  • Playfield: represents the play area of the GUI that has the Tetromino blocks falling in
  • Block: represents a single square block
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/view/playfield.rb

require_relative 'block'

class Tetris
  module View
    class Playfield
      include Glimmer::UI::CustomWidget

      options :playfield_width, :playfield_height, :block_size

      body {
        composite {
          grid_layout(playfield_width, true) {
            margin_width block_size
            margin_height block_size
            horizontal_spacing 0
            vertical_spacing 0
          }

          playfield_height.times { |row|
            playfield_width.times { |column|
              block(block_size: block_size, row: row, column: column)
            }
          }
        }
      }
    end
  end
end
view raw
glimmer-tetris-view-playfield.rb hosted with  by GitHub

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/view/block.rb

class Tetris
  module View
    class Block
      include Glimmer::UI::CustomWidget

      options :block_size, :row, :column

      body {
        composite {
          layout nil
          layout_data {
            width_hint block_size
            height_hint block_size
          }
          background bind(Model::Game.playfield[row][column], :color)
          rectangle(0, 0, block_size, block_size)
          rectangle(3, 3, block_size - 6, block_size - 6) {
            foreground :gray
          }
        }
      }
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Architecture is MVC with a bit of MVP (or MVVM with bidirectional data-binding), having the following models:

  • Game: embodies the rules of the game with the ability to start or restart a game (happens automatically upon launching the app or encountering game over)
  • Tetromino: represents the Tetris shapes with all their intricate operations such as moving down, right, and left, and rotation right (clockwise) and left (counterclockwise). It also houses the majority of the collision detection logic in collaboration with Game
  • Block: represents a single block that could form a tetromino shape or be a block in the playfield

Tetromino logic including collision detection obviously relied on some heavy math skills like calculus and linear algebra, so I had to dust up my Computer Science learning in Computer Graphics with matrix operations such as clockwise and counterclockwise rotations. Thankfully, Ruby supports matrix mathematical operations via the Matrix class.

Keyboard events are handled by a general display on_swt_keydown filter handler that ensures correct action by the model based on user key presses (e.g. left, right, down, right-shift for rotate right and left-shift for rotate left with some other alternatives for rotation like the letters 'a' and 'd'). It is declared in the before_bodyblock on the Tetris custom shell class, meaning configuration is done before building the GUI since the GUI relies on it.

Multi-threaded programming is used where necessary, mainly to kick start the main loop for moving the tetrominoes down the playfield about every second while allowing the user to speed up the movement with the down arrow key if desired. Keep in mind that any interaction with the GUI from a thread other than the main thread must happen through sync_exec. It is declared as an after_body block on the Tetris custom shell class, meaning it runs after building the body to ensure it is available for interaction, but before showing the GUI to the user, which happens last.

Otherwise, bidirectional data-binding handles everything relating to updating the GUI, so the code is squeaky concise and clean. The model logic just worries about the model of the game logic and is completely oblivious to the intricacies of the GUI.

The square blocks took advantage of the newly added Glimmer Canvas Shape DSL for border decorations.

Stay tuned for more changes coming to the Tetris elaborate sample like:

  • Scoring
  • Leveling
  • Preview Next Tetromino

Until then, Happy Glimmering!

Glimmer Tetris Game Over

For another game created with Glimmer DSL for SWT, check out Tic Tac Toe! (also super-concise and clean)

Originally Posted at the Code Master Blog.

Discussion (1)

pic
Editor guide
Collapse
andyobtiva profile image
Andy Maleh Author

The Glimmer Tetris game that took one day to develop just got improved with Scoring, Leveling, and Next Preview with one more day, thanks to Glimmer DSL for SWT's incredibly malleable architecture and extensive feature set.

Read more here:
andymaleh.blogspot.com/2021/01/gli...

Tetris Scoring