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.
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 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 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
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
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_body
block 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!
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.
Top comments (4)
Tetris Saga Part 2:
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 Saga Part 5:
Glimmer Tetris Icon via Image Shape DSL + Instant Down
Tetris Icon built with Glimmer DSL for SWT in Ruby:
Additional Options:
Tetris Saga Part 4:
Glimmer Tetris High Scores & More Menus
Tetris Saga Part 3:
Glimmer Tetris Gets Menu Items Including Pause & Restart