Wednesday, January 27, 2021

Glimmer Tetris High Scores & More Menus

Glimmer Tetris just got a High Score dialog (replacing the Game Over dialog) and got enhanced to save/load high scores to/from disk (tab delimited format). It uses the user directory (dumping a .glimmer-tetris/high_scores.txt file) to use the same security permissions as the current user. If writing fails for any reason, it fails gracefully, defaulting to memory storage of high scores. 

Otherwise, Glimmer Tetris got more menus:

  • View (to show/clear high scores)
  • Options (to enable/disable beeping) 
  • Help (to show about dialog)

 Seeing is believing!

Updated Model::Game logic for saving/loading high scores (relies on automatic observation support of arrays in Glimmer):

class Tetris
module Model
PastGame = Struct.new(:name, :score)
end
end
class Tetris
module Model
class Game
# more code precedes
def game_over!
add_high_score!
beep
self.game_over = true
end
def clear_high_scores!
high_scores.clear
end
def add_high_score!
self.added_high_score = true
high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score))
end
def save_high_scores!
high_score_file_content = @high_scores.map {|past_game| past_game.to_a.join("\t") }.join("\n")
FileUtils.mkdir_p(tetris_dir)
File.write(tetris_high_score_file, high_score_file_content)
rescue => e
# Fail safely by keeping high scores in memory if unable to access disk
Glimmer::Config.logger.error {"Failed to save high scores in: #{tetris_high_score_file}\n#{e.full_message}"}
end
def load_high_scores!
if File.exist?(tetris_high_score_file)
self.high_scores = File.read(tetris_high_score_file).split("\n").map {|line| PastGame.new(*line.split("\t")) }
end
rescue => e
# Fail safely by keeping high scores in memory if unable to access disk
Glimmer::Config.logger.error {"Failed to load high scores from: #{tetris_high_score_file}\n#{e.full_message}"}
end
def tetris_dir
@tetris_dir ||= File.join(Etc.getpwuid.dir, '.glimmer-tetris')
end
def tetris_high_score_file
File.join(tetris_dir, "high_scores.txt")
end
# more code succeeds
end
end
end
view raw gistfile1.txt hosted with ❤ by GitHub
 

 New high_score_dialog custom shell (replacing game_over_dialog): 

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/v4.18.3.2/samples/elaborate/tetris/view/high_score_dialog.rb
require_relative 'tetris_menu_bar'
class Tetris
module View
class HighScoreDialog
include Glimmer::UI::CustomShell
options :parent_shell, :game
after_body {
@game_over_observer = observe(game, :game_over) do |game_over|
close if !game_over
end
}
body {
dialog(parent_shell) {
row_layout {
type :vertical
center true
}
text 'Tetris'
tetris_menu_bar(game: game)
label(:center) {
text bind(game, :game_over) {|game_over| game_over ? 'Game Over!' : 'High Scores'}
font name: FONT_NAME, height: FONT_TITLE_HEIGHT, style: FONT_TITLE_STYLE
}
@high_score_table = table {
layout_data {
height 100
}
table_column {
text 'Name'
}
table_column {
text 'Score'
}
items bind(game, :high_scores), column_properties(:name, :score)
}
composite {
row_layout :horizontal
button {
text 'Clear'
on_widget_selected {
game.clear_high_scores!
}
}
@play_close_button = button {
text bind(game, :game_over) {|game_over| game_over ? 'Play Again?' : 'Close'}
focus true # initial focus
on_widget_selected {
close
game.restart! if game.game_over?
}
}
}
on_swt_show {
if game.game_over? && game.added_high_score?
game.added_high_score = false
@high_score_table.edit_table_item(
@high_score_table.items.first, # row item
0, # column
after_write: -> {
game.save_high_scores!
@play_close_button.set_focus
},
after_cancel: -> {
@play_close_button.set_focus
},
)
end
}
on_shell_closed {
@high_score_table.cancel_edit!
}
on_widget_disposed {
@game_over_observer.deregister
}
}
}
end
end
end
 

Updated tetris_menu_bar custom widget: 

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/v4.18.3.2/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
on_widget_selected {
game.restart!
}
}
menu_item(:separator)
menu_item {
text '&Exit'
accelerator :command, :x
on_widget_selected {
parent_proxy.close
}
}
} # end of menu
menu {
text '&View'
menu {
text '&High Scores'
menu_item {
text '&Show'
accelerator :command, :shift, :h
on_widget_selected {
parent_custom_shell&.show_high_score_dialog
}
}
menu_item {
text '&Clear'
accelerator :command, :shift, :c
on_widget_selected {
game.clear_high_scores!
}
}
}
} # end of menu
menu {
text '&Options'
menu_item(:check) {
text '&Beeping'
accelerator :command, :b
selection bind(game, :beeping)
}
} # end of menu
menu {
text '&Help'
menu_item {
text '&About'
accelerator :command, :shift, :a
on_widget_selected {
parent_custom_shell&.show_about_dialog
}
}
} # end of menu
}
}
def parent_custom_shell
# grab custom shell widget wrapping parent widget proxy (i.e. Tetris) and invoke method on it
the_parent_custom_shell = parent_proxy&.get_data('custom_shell')
the_parent_custom_shell if the_parent_custom_shell&.visible?
end
end
end
end
 

 All of this ships with Glimmer DSL for SWT v4.18.3.3 as part of the following changes:  

  • Tetris High Scores
  • Tetris Modify High Score Player Name
  • Tetris Show High Scores (Menu Item + Accelerator)
  • Tetris add a menu item with beep enablement option
  • Tetris Clear High Scores
  • Tetris Add left and right alt (option) buttons as alternative to shift for rotation. Use left ctrl as rotate left. Use a, s, d as left, down, right.
  • Fix issues relating to setting parenthood with custom widgets before building their body (instead of after)
  • Fix issues relating to not respecting arity of passed in table editing callbacks: before_write, after_write, and after_cancel

Happy Glimmering!

Check out last: Glimmer Tetris Icon via Image Shape DSL + Instant Down

This article is part of the Tetris Saga

No comments: