Sunday, January 30, 2022

Glimmer DSL for GTK Tetris + Cairo Tutorial

Glimmer DSL for GTK (Ruby GNOME Desktop Development GUI Library) had a few releases in response to an issue requesting a Tetris sample. The project now includes brand new declarative support for Cairo graphics and an implementation of the Tetris game as a sample. Additionally, the majority of Cairo samples mentioned in this Cairo Tutorial blog post by Mohit Sindhwani ("Cairo with Ruby - Samples using RCairo") have been included too. 


Tutorial of using Cairo in Ruby with Glimmer DSL for GTK:


Below is a Glimmer DSL for GTK rewriting of Mohit's tutorial using the much simpler and more maintainable Glimmer DSL for GTK declarative Cairo syntax (instead of the default RCairo imperative syntax).

Glimmer DSL for GTK - Declarative Cairo Graphics Tutorial

Cairo is the engine behind drawing arbitrary 2D geometric shapes in GTK.

In Glimmer DSL for GTK, you can draw Cairo shapes declaratively in a way similar to how SVG works, but using one language; Ruby, thus being able to utilize Ruby logic (e.g. if statement or each loop) with it effortlessly when needed. Declarative syntax also yields less code that is simpler, not dependent on ordering of nested properties, and more understandable/maintainable.

Below is a quick tutorial consisting of samples inspired and ported from Mohit Sindhwani's blog post "Cairo with Ruby - Samples using RCairo".

Arc

samples/cairo/arc.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Hello, Drawing Area!'
  default_size 256, 256
  
  drawing_area {
    # Surface Paint
    paint 242.25, 242.25, 242.25
    
    # Set up the parameters
    xc = 128.0
    yc = 128.0
    radius = 100.0
    angle1 = 45.0  * (Math::PI/180.0) # angles are specified
    angle2 = 180.0  * (Math::PI/180.0) # in radians
    
    # The main arc
    arc(xc, yc, radius, angle1, angle2) {
      stroke 0, 0, 0
      line_width 10
    }
    
    # Draw helping lines
    
    # First, the circle at the centre
    arc(xc, yc, 10.0, 0, 2*Math::PI) {
      fill 255, 51, 51, 0.6
    }
    
    # Then, the lines reaching out
    path {
      arc xc, yc, radius, angle1, angle1
      line_to xc, yc
      arc xc, yc, radius, angle2, angle2
      line_to xc, yc
      
      stroke 255, 51, 51, 0.6
      line_width 6
    }
  }
}.show

Arc

Arc Negative

samples/cairo/arc_negative.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Arc Negative'
  default_size 256, 256
  
  drawing_area {
    # Surface Paint
    paint 255, 255, 255
    
    # Set up the parameters
    xc = 128.0
    yc = 128.0
    radius = 100.0
    angle1 = 45.0  * (Math::PI/180.0) # angles are specified
    angle2 = 180.0  * (Math::PI/180.0) # in radians
    
    # The main negative arc
    arc_negative(xc, yc, radius, angle1, angle2) {
      stroke 0, 0, 0
      line_width 10
    }
    
    # Draw helping lines
    
    # First, the circle at the centre
    arc(xc, yc, 10.0, 0, 2*Math::PI) {
      fill 255, 51, 51, 0.6
    }
    
    # Then, the lines reaching out
    path {
      arc(xc, yc, radius, angle1, angle1)
      line_to(xc, yc)
      arc(xc, yc, radius, angle2, angle2)
      line_to(xc, yc)
      
      stroke 255, 51, 51, 0.6
      line_width 6
    }
  }
}.show

Arc Negative

Clip

samples/cairo/clip.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Clip'
  default_size 256, 256
  
  drawing_area {
    # Surface Paint
    paint 255, 255, 255
    
    # Designate arc as the clipping area
    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {
      clip true
    }
    
    # Rectangle will get clipped by arc
    rectangle(0, 0, 256, 256) {
      fill 0, 0, 0
    }
    
    # Path will get clipped by arc
    path {
      move_to 0, 0
      line_to 256, 256
      move_to 256, 0
      line_to 0, 256
      
      stroke 0, 255, 0
      line_width 10
    }
  }
}.show

Clip

Clip Image

samples/cairo/clip_image.rb

require 'glimmer-dsl-gtk'
require 'net/http'

image_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))
image_file = File.join(Dir.home, 'breaking-blue-wave.png')
File.write(image_file, image_content)

include Glimmer

window {
  title 'Clip Image'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25

    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {
      clip true # designate arc as the clipping area
    }

    rectangle(0, 0, 256, 256) {
      # Source image is from:
      # - https://www.publicdomainpictures.net/en/view-image.php?image=7683&picture=breaking-blue-wave
      # Converted to PNG before using it
      image = Cairo::ImageSurface.from_png(image_file)
      w = image.width
      h = image.height
  
      scale 256.0/w, 256.0/h, exclude: :shape # applies scale to fill source image only
      fill image, 0, 0
    }
  }
}.show

Clip Image

Curve to

samples/cairo/curve_to.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Curve to'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    x=25.6
    y=128.0
    x1=102.4
    y1=230.4
    x2=153.6
    y2=25.6
    x3=230.4
    y3=128.0
    
    path {
      move_to x, y
      curve_to x1, y1, x2, y2, x3, y3
      
      line_width 10
      stroke 0, 0, 0
    }
    
    path {
      move_to x,y
      line_to x1,y1
      move_to x2,y2
      line_to x3,y3
      
      line_width 6
      stroke 255, 51, 51, 0.6
    }
  }
}.show

Curve to

Dashes

samples/cairo/dashes.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Dashes'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25

    dashes = [ 50.0, # ink
                       10.0,  # skip
                       10.0,  # ink
                       10.0   # skip
            ]
    offset = -50.0
    
    path {
      move_to 128.0, 25.6
      line_to 230.4, 230.4
      rel_line_to -102.4, 0.0
      curve_to 51.2, 230.4, 51.2, 128.0, 128.0, 128.0
      
      line_width 10
      dash dashes, offset
      stroke 0, 0, 0
    }
  }
}.show

Dashes

Fill and Stroke 2

(note: there is no Fill and Stroke 1; this was adopted from Mohit's blog post, which only mentioned Fill and Stroke 2)

samples/cairo/fill_and_stroke2.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Fill and Stroke 2'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    path {
      move_to 128.0, 25.6
      line_to 230.4, 230.4
      rel_line_to -102.4, 0.0
      curve_to 51.2, 230.4, 51.2, 128.0, 128.0, 128.0
      close_path

      fill 0, 0, 255
      stroke 0, 0, 0
      line_width 10
    }
    
    path {
      move_to 64.0, 25.6
      rel_line_to 51.2, 51.2
      rel_line_to -51.2, 51.2
      rel_line_to -51.2, -51.2
      close_path

      fill 0, 0, 255
      stroke 0, 0, 0
      line_width 10
    }
  }
}.show

Fill and Stroke 2

Fill Style

samples/cairo/fill_style.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Fill Style'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    path {
      rectangle 12, 12, 232, 70
      path { # sub-path
        arc 64, 64, 40, 0, 2*Math::PI
      }
      path { # sub-path
        arc_negative 192, 64, 40, 0, -2*Math::PI
      }
      
      fill_rule Cairo::FILL_RULE_EVEN_ODD
      line_width 6
      fill 0, 178.5, 0
      stroke 0, 0, 0
    }
    
    path {
      rectangle 12, 12, 232, 70
      path { # sub-path
        arc 64, 64, 40, 0, 2*Math::PI
      }
      path { # sub-path
        arc_negative 192, 64, 40, 0, -2*Math::PI
      }
      
      translate 0, 128
      fill_rule Cairo::FILL_RULE_WINDING
      line_width 6
      fill 0, 0, 229.5
      stroke 0, 0, 0
    }
  }
}.show

Fill Style

Gradient

samples/cairo/gradient.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Gradient'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
        
    # Create the Linear Pattern
    rectangle(0, 0, 256, 256) {
      pat = Cairo::LinearPattern.new(0.0, 0.0,  0.0, 256.0)
      pat.add_color_stop_rgba(1, 0, 0, 0, 1)
      pat.add_color_stop_rgba(0, 1, 1, 1, 1)
      
      fill pat
    }
    
    # Create the radial pattern
    arc(128.0, 128.0, 76.8, 0, 2 * Math::PI) {
      pat = Cairo::RadialPattern.new(115.2, 102.4, 25.6,
                                         102.4,  102.4, 128.0)
      pat.add_color_stop_rgba(0, 1, 1, 1, 1)
      pat.add_color_stop_rgba(1, 0, 0, 0, 1)
      
      fill pat
    }
  }
}.show

Gradient

Image

samples/cairo/image.rb

require 'glimmer-dsl-gtk'
require 'net/http'

image_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))
image_file = File.join(Dir.home, 'breaking-blue-wave.png')
File.write(image_file, image_content)

include Glimmer

window {
  title 'Image'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
        
    image = Cairo::ImageSurface.from_png(image_file)
    w = image.width
    h = image.height
    
    translate 128.0, 128.0
    rotate 45*Math::PI/180
    scale 256.0/w, 256.0/h
    translate -0.5*w, -0.5*h
    
    paint image, 0, 0
  }
}.show

Image

Image Gradient

samples/cairo/image_gradient.rb

require 'glimmer-dsl-gtk'
require 'net/http'

image_content = Net::HTTP.get(URI('https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-gtk/master/images/breaking-blue-wave.png'))
image_file = File.join(Dir.home, 'breaking-blue-wave.png')
File.write(image_file, image_content)

include Glimmer

window {
  title 'Image Gradient'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    image = Cairo::ImageSurface.from_png(image_file)
    w = image.width
    h = image.height
    
    # Load the image as a surface pattern
    pattern = Cairo::SurfacePattern.new(image)
    pattern.extend = Cairo::EXTEND_REPEAT
    
    # Set up the scale matrix
    pattern.matrix = Cairo::Matrix.scale(w/256.0 * 5.0, h/256.0 * 5.0)
    
    rectangle(0, 0, 256, 256) {
      translate 128.0, 128.0
      rotate Math::PI / 4
      scale 1/Math.sqrt(2), 1/Math.sqrt(2)
      translate -128.0, -128.0
        
      fill pattern
    }
  }
}.show

Image Gradient

Multi Segment Caps

samples/cairo/multi_segment_caps.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Multi Segment Caps'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    path {
      move_to 50.0, 75.0
      line_to 200.0, 75.0
      
      move_to 50.0, 125.0
      line_to 200.0, 125.0
      
      move_to 50.0, 175.0
      line_to 200.0, 175.0
      
      line_width 30
      line_cap Cairo::LINE_CAP_ROUND
      stroke 0, 0, 0
    }
  }
}.show

Multi Segment Caps

Rounded Rectangle

samples/cairo/rounded_rectangle.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Rounded Rectangle'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    path {
      rounded_rectangle(25.6, 25.6, 204.8, 204.8, 20)
    
      fill 127.5, 127.5, 255
      line_width 10.0
      stroke 127.5, 0, 0, 0.5
    }
  }
}.show

Rounded Rectangle

Set line cap

samples/cairo/set_line_cap.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Set line cap'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    # The main code
    path {
      move_to 64.0, 50.0
      line_to 64.0, 200.0
      
      line_cap Cairo::LINE_CAP_BUTT #  default
      line_width 30
      stroke 0, 0, 0
    }
    
    path {
      move_to 128.0, 50.0
      line_to 128.0, 200.0
      
      line_cap Cairo::LINE_CAP_ROUND
      line_width 30
      stroke 0, 0, 0
    }
    
    path {
      move_to 192.0, 50.0
      line_to 192.0, 200.0
      
      line_cap Cairo::LINE_CAP_SQUARE
      line_width 30
      stroke 0, 0, 0
    }
    
    #  draw helping lines */
    path {
      move_to 64.0, 50.0
      line_to 64.0, 200.0
      move_to 128.0, 50.0
      line_to 128.0, 200.0
      move_to 192.0, 50.0
      line_to 192.0, 200.0
      
      line_width 2.56
      stroke 255, 51, 51
    }
  }
}.show

Set line cap

Set line join

samples/cairo/set_line_join.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Set line join'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    # The main code
    path {
      move_to 76.8, 84.48
      rel_line_to 51.2, -51.2
      rel_line_to 51.2, 51.2
      
      line_join Cairo::LINE_JOIN_MITER # default
      line_width 40.96
      stroke 0, 0, 0
    }
    
    path {
      move_to 76.8, 161.28
      rel_line_to 51.2, -51.2
      rel_line_to 51.2, 51.2
      
      line_join Cairo::LINE_JOIN_BEVEL
      line_width 40.96
      stroke 0, 0, 0
    }
    
    path {
      move_to 76.8, 238.08
      rel_line_to 51.2, -51.2
      rel_line_to 51.2, 51.2
      
      line_join Cairo::LINE_JOIN_ROUND
      line_width 40.96
      stroke 0, 0, 0
    }
  }
}.show

Set line join

Text

samples/cairo/text.rb

require 'glimmer-dsl-gtk'

include Glimmer

window {
  title 'Text'
  default_size 256, 256
  
  drawing_area {
    paint 242.25, 242.25, 242.25
    
    font_family = OS.linux? ? 'Sans' : (OS.mac? ? 'Helvetica' : 'Arial')
    
    # The main code
    path {
      move_to 10.0, 135.0
      show_text 'Hello'
      
      font_face font_family, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD
      font_size 90.0
      line_width 2.56
      fill 0, 0, 0
      stroke 0, 0, 0
    }
    
    path {
      move_to 70.0, 165.0
      text_path 'void'
      
      font_face font_family, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD
      font_size 90.0
      line_width 2.56
      fill 127.5, 127.5, 255
      stroke 0, 0, 0
    }
    
    # draw helping lines
    path {
      arc 10.0, 135.0, 5.12, 0, 2*Math::PI
      close_path
      arc 70.0, 165.0, 5.12, 0, 2*Math::PI
      
      fill 255, 51, 51, 0.6
    }
  }
}.show

Text


Tetris Screenshot:


Here is a screenshot of Tetris, written with declarative Cairo code using Glimmer DSL for GTK.

Tetris Glimmer DSL for GTK Code:


Cairo usage is mostly the Glimmer-only `square` and `polygon` shape constructions (not available in standard RCairo).

# From: https://github.com/AndyObtiva/glimmer-dsl-gtk#tetris
require 'glimmer-dsl-gtk'
require_relative 'tetris/model/game'
class Tetris
include Glimmer
BLOCK_SIZE = 25
BEVEL_CONSTANT = 20
COLOR_GRAY = [192, 192, 192]
def initialize
@game = Model::Game.new
end
def launch
create_gui
register_observers
@game.start!
@main_window.show
end
def create_gui
@main_window = window {
title 'Glimmer Tetris'
default_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE # + 98
box(:vertical) {
tetris_menu_bar
box(:horizontal) {
@playfield_blocks = playfield(playfield_width: @game.playfield_width, playfield_height: @game.playfield_height, block_size: BLOCK_SIZE)
score_board
}
}
on(:key_press_event) do |widget, key_event|
case key_event.keyval
when 65364 # down arrow
@game.down!
when 32 # space
@game.down!(instant: true)
when 65362 # up arrow
case @game.up_arrow_action
when :instant_down
@game.down!(instant: true)
when :rotate_right
@game.rotate!(:right)
when :rotate_left
@game.rotate!(:left)
end
when 65361 # left arrow
@game.left!
when 65363 # right arrow
@game.right!
when 65506 # right shift
@game.rotate!(:right)
when 65505 # left shift
@game.rotate!(:left)
else
# Do Nothing
end
end
}
end
def register_observers
observe(@game, :game_over) do |game_over|
if game_over
show_game_over_dialog
else
start_moving_tetrominos_down
end
end
@game.playfield_height.times do |row|
@game.playfield_width.times do |column|
observe(@game.playfield[row][column], :color) do |new_color|
color = new_color
block = @playfield_blocks[row][column]
block[:background_square].fill = color
block[:top_bevel_edge].fill = [color[0] + 4*BEVEL_CONSTANT, color[1] + 4*BEVEL_CONSTANT, color[2] + 4*BEVEL_CONSTANT]
block[:right_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:bottom_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:left_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
block[:drawing_area].queue_draw
false
end
end
end
Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
observe(@game.preview_playfield[row][column], :color) do |new_color|
color = new_color
block = @preview_playfield_blocks[row][column]
block[:background_square].fill = color
block[:top_bevel_edge].fill = [color[0] + 4*BEVEL_CONSTANT, color[1] + 4*BEVEL_CONSTANT, color[2] + 4*BEVEL_CONSTANT]
block[:right_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:bottom_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:left_bevel_edge].fill = [color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT]
block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
block[:drawing_area].queue_draw
end
end
end
observe(@game, :score) do |new_score|
@score_label.text = new_score.to_s
end
observe(@game, :lines) do |new_lines|
@lines_label.text = new_lines.to_s
end
observe(@game, :level) do |new_level|
@level_label.text = new_level.to_s
end
end
def tetris_menu_bar
menu_bar {
menu_item(label: 'Game') { |mi|
m = menu {
check_menu_item(label: 'Pause') {
on(:activate) do
@game.paused = !@game.paused?
end
}
menu_item(label: 'Restart') {
on(:activate) do
@game.restart!
end
}
separator_menu_item
menu_item(label: 'Exit') {
on(:activate) do
@main_window.close
end
}
}
mi.submenu = m.gtk
}
menu_item(label: 'View') { |mi|
m = menu {
menu_item(label: 'Show High Scores') {
on(:activate) do
show_high_score_dialog
end
}
menu_item(label: 'Clear High Scores') {
on(:activate) do
@game.clear_high_scores!
end
}
}
mi.submenu = m.gtk
}
menu_item(label: 'Options') { |mi|
m = menu {
rmi = radio_menu_item(nil, 'Instant Down on Up') {
on(:activate) do
@game.instant_down_on_up!
end
}
default_rmi = radio_menu_item(rmi.group, 'Rotate Right on Up') {
on(:activate) do
@game.rotate_right_on_up!
end
}
default_rmi.activate
radio_menu_item(rmi.group, 'Rotate Left on Up') {
on(:activate) do
@game.rotate_left_on_up!
end
}
}
mi.submenu = m.gtk
}
menu_item(label: 'Help') { |mi|
m = menu {
menu_item(label: 'About') {
on(:activate) do
show_about_dialog
end
}
}
mi.submenu = m.gtk
}
}
end
def score_board
box(:vertical) {
label
@preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
label
label('Score')
@score_label = label
label
label('Lines')
@lines_label = label
label
label('Level')
@level_label = label
label
}
end
def playfield(playfield_width: , playfield_height: , block_size: , &extra_content)
blocks = []
box(:vertical) {
playfield_height.times.map do |row|
blocks << []
box(:horizontal) {
playfield_width.times.map do |column|
blocks.last << block(row: row, column: column, block_size: block_size)
end
}
end
extra_content&.call
}
blocks
end
def block(row: , column: , block_size: , &extra_content)
block = {}
bevel_pixel_size = 0.16 * block_size.to_f
color = Model::Block::COLOR_CLEAR
block[:drawing_area] = drawing_area {
size_request block_size, block_size
block[:background_square] = square(0, 0, block_size) {
fill *color
}
block[:top_bevel_edge] = polygon(0, 0, block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
fill color[0] + 4*BEVEL_CONSTANT, color[1] + 4*BEVEL_CONSTANT, color[2] + 4*BEVEL_CONSTANT
}
block[:right_bevel_edge] = polygon(block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size, block_size) {
fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT
}
block[:bottom_bevel_edge] = polygon(block_size, block_size, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size) {
fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT
}
block[:left_bevel_edge] = polygon(0, 0, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size) {
fill color[0] - BEVEL_CONSTANT, color[1] - BEVEL_CONSTANT, color[2] - BEVEL_CONSTANT
}
block[:border_square] = square(0, 0, block_size) {
stroke *COLOR_GRAY
}
extra_content&.call
}
block
end
def start_moving_tetrominos_down
unless @tetrominos_start_moving_down
@tetrominos_start_moving_down = true
GLib::Timeout.add(@game.delay*1000) do
@game.down! if !@game.game_over? && !@game.paused?
true
end
end
end
def show_game_over_dialog
message_dialog(@main_window) { |md|
title 'Game Over!'
text "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}"
on(:response) do
md.destroy
end
}.show
@game.restart!
false
end
def show_high_score_dialog
game_paused = !!@game.paused
@game.paused = true
if @game.high_scores.empty?
high_scores_string = "No games have been scored yet."
else
high_scores_string = @game.high_scores.map do |high_score|
"#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}"
end.join("\n")
end
message_dialog(@main_window) { |md|
title 'High Scores'
text high_scores_string
on(:response) do
md.destroy
end
}.show
@game.paused = game_paused
end
def show_about_dialog
message_dialog(@main_window) { |md|
title 'About'
text "Glimmer Tetris\n\nGlimmer DSL for GTK\n\nElaborate Sample\n\nCopyright (c) 2021-2022 Andy Maleh"
on(:response) do
md.destroy
end
}.show
end
end
Tetris.new.launch

Happy Glimmering!

No comments: