Wednesday, January 27, 2021

Glimmer Tetris Gets Menu Items Including Pause & Restart

Glimmer Tetris gets menu bar items, including Start, Pause, Restart, and Exit.

 

Additionally, the Glimmer Tetris code got refactored quite a bit:

  • Model::Game class is no longer a singleton, thus can serve multiple Tetris Custom Shell instances independently. 
  • The multi-threaded Tetromino down movement loop is now synchronized with a Mutex to allow the game to just stop, ending the thread, and restart when the user wants instead of keeping the thread running perpetually. The Mutex ensures the newly started thread doesn't clash with the older one yet waits for it to end before starting the Tetromino down movement loop again.
  • Tetris Custom Shell now cleans resources (deregisters observers) when closed in case it was started as part of another app (like the Glimmer Meta-Sample)
  • Game Over Dialog has been turned into its own Custom Shell, thus reusable with the game_over_dialog keyword
  • The new Tetris Menu Bar has been made its own Custom Widget as shown in the code below. One thing important to note here is that every shell (and dialog by extension) gets its own menu bar in SWT, so to make the Game Over dialog keep the same Tetris menu actions of Start/Pause/Stop/Exit, you simply reuse the Tetris Menu Bar Custom Widget with one keyword: `tetris_menu_bar`
  • Some Model logic that was leaked into the Tetris Custom Shell View got extracted and moved into Model::Game
  • Improved code according to the Law of Demeter by not calling Model::Tetromino logic directly from the Tetris Custom Shell View, yet creating higher level intermediate methods on Model::Game and calling them instead
  • Adding Ruby-idiomatic bang to the end of mutation methods on the Model::Game and Model::Tetromino Models

Tetris Menu Bar Custom Widget

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/v4.18.3.1/samples/elaborate/tetris/view/tetris_menu_bar.rb
class Tetris
module View
class TetrisMenuBar
include Glimmer::UI::CustomWidget
options :game
body {
menu_bar {
menu {
text '&Game'
menu_item {
text '&Start'
enabled bind(game, :game_over)
accelerator :command, :s
on_widget_selected {
game.start!
}
}
menu_item(:check) {
text '&Pause'
accelerator :command, :p
enabled bind(game, :game_over, on_read: :!)
selection bind(game, :paused)
}
menu_item {
text '&Restart'
accelerator :command, :r
enabled bind(game, :game_over, on_read: :!)
on_widget_selected {
game.restart!
}
}
menu_item(:separator)
menu_item {
text '&Exit'
accelerator :command, :x
on_widget_selected {
parent_proxy.close
}
}
}
}
}
end
end
end

Tetris App Custom Shell

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/v4.18.3.1/samples/elaborate/tetris.rb
# Tetris App View Custom Shell (represents `tetris` keyword)
require_relative 'tetris/model/game'
require_relative 'tetris/view/playfield'
require_relative 'tetris/view/score_lane'
require_relative 'tetris/view/game_over_dialog'
require_relative 'tetris/view/tetris_menu_bar'
class Tetris
include Glimmer::UI::CustomShell
BLOCK_SIZE = 25
option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
attr_reader :game
before_body {
@mutex = Mutex.new
@game = Model::Game.new(playfield_width, playfield_height)
@game.configure_beeper do
display.beep
end
Display.app_name = 'Glimmer Tetris'
display {
@keyboard_listener = on_swt_keydown { |key_event|
case key_event.keyCode
when swt(:arrow_down)
game.down!
when swt(:arrow_left)
game.left!
when swt(:arrow_right)
game.right!
when swt(:shift)
if key_event.keyLocation == swt(:right) # right shift key
game.rotate!(:right)
elsif key_event.keyLocation == swt(:left) # left shift key
game.rotate!(:left)
end
when 'd'.bytes.first, swt(:arrow_up)
game.rotate!(:right)
when 'a'.bytes.first
game.rotate!(:left)
end
}
}
}
after_body {
@game_over_observer = observe(@game, :game_over) do |game_over|
if game_over
@game_over_dialog = game_over_dialog(parent_shell: body_root, game: @game) if @game_over_dialog.nil? || @game_over_dialog.disposed?
@game_over_dialog.show
else
start_moving_tetrominos_down
end
end
@game.start!
}
body {
shell(:no_resize) {
grid_layout {
num_columns 2
make_columns_equal_width false
margin_width 0
margin_height 0
horizontal_spacing 0
}
text 'Glimmer Tetris'
minimum_size 475, 500
background :gray
tetris_menu_bar(game: game)
playfield(game_playfield: game.playfield, playfield_width: playfield_width, playfield_height: playfield_height, block_size: BLOCK_SIZE)
score_lane(game: game, block_size: BLOCK_SIZE) {
layout_data(:fill, :fill, true, true)
}
on_widget_disposed {
deregister_observers
}
}
}
def start_moving_tetrominos_down
Thread.new do
@mutex.synchronize do
loop do
time = Time.now
sleep @game.delay
break if @game.game_over? || body_root.disposed?
sync_exec {
@game.down! unless @game.paused?
}
end
end
end
end
def deregister_observers
@game_over_observer&.deregister
@game_over_observer = nil
@keyboard_listener&.deregister
@keyboard_listener = nil
end
end
Tetris.launch
view raw gistfile1.txt hosted with ❤ by GitHub

Game Over Dialog Custom Shell

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/v4.18.3.1/samples/elaborate/tetris/view/game_over_dialog.rb
require_relative 'tetris_menu_bar'
class Tetris
module View
class GameOverDialog
include Glimmer::UI::CustomShell
options :parent_shell, :game
after_body {
observe(game, :game_over) do |game_over|
hide if !game_over
end
}
body {
dialog(parent_shell) {
row_layout {
type :vertical
center true
}
text 'Tetris'
tetris_menu_bar(game: game)
label(:center) {
text 'Game Over!'
font name: 'Menlo', height: 30, style: :bold
}
label # filler
button {
text 'Play Again?'
on_widget_selected {
hide
game.restart!
}
}
on_shell_activated {
display.beep
}
}
}
end
end
end

These changes shipped with Glimmer DSL for SWT v4.18.3.1 as part of the following:

  • Provide an auto_sync_exec all data-binding config option to automatically sync_exec GUI calls from other threads instead of requiring users to use sync_exec on model attribute-change logic. Default value to false.
  • Have CustomShell::launch method take options to pass to custom shell keyword invocation
  • Update Glimmer Meta-Sample to load entire gem into user directory (since some new samples rely on images)
  • Update Glimmer Meta-Sample to display errors in a `dialog` instead of a `message_box` to allow scrolling
  • Removed newly added CustomShell::shutdown as unnecessary (could just do CustomShell::launchd_custom_shell.close)
  • Supporting deregistering Display listeners just like standard listeners via deregister
  • Enhance performance of excluded keyword check
  • Remove CustomWidget support for multiple before_body/after_body blocks instead of one each since it is not needed.
  • Add new :fill_screen style for `shell` to start app filling the screen size (not full screen mode though)
  • Tetris Menu Bar with Game Menu -> Start, Pause, Restart, and Exit
  • Tetris refactor mutation methods to end with bangs
  • Tetris Stop game if user does not play again in the end (instead of closing it)
  • End Tetris Thread loop gracefully if game over is encountered
  • Tetris use more observers instead of callbacks to Game
  • Turn Tetris::Model::Game class from a singleton class to a standard class supporting instances
  • Fix issue of `tetris` keyword not found when run from meta-sample app

Otherwise, Glimmer DSL for SWT v4.18.3.0 included a major feature: Canvas Transform DSL:

4.18.3.0

  •  Canvas Transform DSL (DSL declared Transform objects are auto-disposed after getting used by their parent shape)
  •  Canvas support a top-level Transform DSL fluent interface for methods that use Transform arguments manually (e.g. tr1 = transform.rotate(90).translate(0, -100))
  •  Hello, Canvas Transform! Sample

# From: https://github.com/AndyObtiva/glimmer-dsl-swt#canvas-transform-dsl
include Glimmer
shell {
text 'Canvas Transform Example'
minimum_size 330, 352
canvas { |canvas_proxy|
background :white
text('glimmer', 0, 0) {
foreground :red
transform {
translate 220, 100
scale 2.5, 2.5
rotate 90
}
}
text('glimmer', 0, 0) {
foreground :dark_green
transform {
translate 0, 0
shear 2, 3
scale 2, 2
}
}
text('glimmer', 0, 0) {
foreground :blue
transform {
translate 0, 220
scale 3, 3
}
}
}
}.open
 

 

 



Happy Glimmering!

You may check out the Glimmer Tetris High Score Dialog next!

This article is part of the Tetris Saga

No comments: