Glimmer Tetris in One Day!
Tags: ruby, gamedev, programming, learning
Published: 2021-01-23 02:50:03

I present to you [Glimmer Tetris](https://github.com/AndyObtiva/glimmer-dsl-swt#tetris) in one day! Not bad, right!? It is included with the just released [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) v4.18.2.3.

![Glimmer Tetris](https://dev-to-uploads.s3.amazonaws.com/i/8s4axwxyc94gbz30nioz.png)

The most ridiculous thing about this is I am not a game developer of any sort. I've never programmed Tetris before. I probably haven't even played the game since the Gameboy original era more than 25 years ago. As a result, this absolutely demonstrates without the shadow of a doubt the superiority of [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt#glimmer-gui-dsl) as a desktop development GUI framework in Ruby. If I was able to make this game in one day, you might be able to make it in a few hours if you're a seasoned game developer. In any case, let's get into the code, shall we!

Top-level [Glimmer GUI](https://github.com/AndyObtiva/glimmer-dsl-swt) code:

```ruby
# 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
```

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](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-shells) (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](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-widgets) (`playfield` and `block`):

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

```ruby
# 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
```

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

- [Game](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/model/game.rb): 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](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/model/tetromino.rb): 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](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/tetris/model/block.rb): 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](https://github.com/AndyObtiva/glimmer-dsl-swt#observing-widgets) 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_body`block on the Tetris [custom shell](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-shells) 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](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-shells) 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](https://github.com/AndyObtiva/glimmer-dsl-swt#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](https://github.com/AndyObtiva/glimmer-dsl-swt#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](Original Post: https://github.com/AndyObtiva/glimmer-dsl-swt)! 

![Glimmer Tetris Game Over](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-tetris-game-over.png)

For another game created with [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt), check out [Tic Tac Toe!](https://github.com/AndyObtiva/glimmer-dsl-swt#tic-tac-toe-sample) (also super-concise and clean)

Originally Posted at the [Code Master Blog](http://andymaleh.blogspot.com/2021/01/glimmer-tetris-in-one-day.html
).