43
Quarto Game, Custom Shapes, and Canvas Shape DSL Tutorial
At a Christmas party I attended a couple of weeks ago, I discovered a classic board game called Quarto. In fact, the host of the party who's worked for a major gaming company in the past asked me if I knew how to build it as a computer application. I discounted myself as a non-game-developer who only builds business applications, but then followed that by saying that if it is only a 2D game, it was simple to build. So, the challenge was on!!!
![](https://res.cloudinary.com/practicaldev/image/fetch/s--7UaCzU29--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/efeyysrlmyl6d428eleo.png)
Here is the Quarto classic black board version that I played with during Christmas:
I built Quarto as a computer game in 4-5 days using Glimmer DSL for SWT!
![](https://res.cloudinary.com/practicaldev/image/fetch/s--iyORDv3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-quarto.png)
Here is a video demo of Glimmer Quarto:
The key features in Glimmer DSL for SWT that helped me complete it very quickly are Custom Shape support (e.g. building cylinder and cube Custom Shapes and reusing to model Quarto piece Custom Shape) and the effortless Canvas Drag and Drop (e.g. designating one Custom Shape as a drag source and another as a drop target with on_drop listener). I also used affine Transforms to tilt the board by 45 degrees from a standard grid.
The top-level Quarto code is included below, followed by the code of the Quarto piece, cylinder, and cube Custom Shapes, followed by a link to the rest of the code, and then finally a quick tutorial for the Glimmer DSL for SWT Canvas Shape DSL.
Quarto
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#quarto
# Top-level Quarto board GUI code that visually maps to real GUI
shell(:shell_trim, (:double_buffered unless OS.mac?)) {
text 'Glimmer Quarto'
minimum_size BOARD_DIAMETER + AREA_MARGIN + PIECES_AREA_WIDTH + SHELL_MARGIN*2 + (OS.linux? ? 52 : (OS.windows? ? 16 : 0)), BOARD_DIAMETER + 24 + SHELL_MARGIN*2 + (OS.linux? ? 96 : (OS.windows? ? 32 : 0))
maximum_size BOARD_DIAMETER + AREA_MARGIN + PIECES_AREA_WIDTH + SHELL_MARGIN*2 + (OS.linux? ? 52 : (OS.windows? ? 16 : 0)), BOARD_DIAMETER + 24 + SHELL_MARGIN*2 + (OS.linux? ? 96 : (OS.windows? ? 32 : 0))
background COLOR_WOOD
quarto_menu_bar
@board = board(game: @game, location_x: SHELL_MARGIN, location_y: SHELL_MARGIN)
@available_pieces_area = available_pieces_area(game: @game, location_x: SHELL_MARGIN + BOARD_DIAMETER + AREA_MARGIN, location_y: SHELL_MARGIN)
@selected_piece_area = selected_piece_area(game: @game, location_x: SHELL_MARGIN + BOARD_DIAMETER + AREA_MARGIN, location_y: SHELL_MARGIN + AVAILABLE_PIECES_AREA_HEIGHT + AREA_MARGIN)
}
Piece
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#quarto
require_relative 'cylinder'
require_relative 'cube'
class Quarto
module View
class Piece
include Glimmer::UI::CustomShape
SIZE_SHORT = 28
SIZE_TALL = 48
BASIC_SHAPE_WIDTH = 48
BASIC_SHAPE_HEIGHT = 28
LINE_THICKNESS = 2
options :game, :model, :location_x, :location_y
before_body do
@background_color = model.light? ? COLOR_LIGHT_WOOD : COLOR_DARK_WOOD
@size = model.short? ? SIZE_SHORT : SIZE_TALL
@shape_location_x = 0
@shape_location_y = model.short? ? 20 : 0
end
body {
shape(location_x, location_y) {
if model.is_a?(Model::Piece::Cylinder)
cylinder(location_x: @shape_location_x, location_y: @shape_location_y, cylinder_height: @size, oval_width: BASIC_SHAPE_WIDTH, oval_height: BASIC_SHAPE_HEIGHT, pitted: model.pitted?, background_color: @background_color, line_thickness: LINE_THICKNESS)
else
cube(location_x: @shape_location_x, location_y: @shape_location_y, cube_height: @size, rectangle_width: BASIC_SHAPE_WIDTH, rectangle_height: BASIC_SHAPE_HEIGHT, pitted: model.pitted?, background_color: @background_color, line_thickness: LINE_THICKNESS)
end
}
}
end
end
end
Cylinder
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#quarto
class Quarto
module View
class Cylinder
include Glimmer::UI::CustomShape
DEFAULT_SIZE = 28
options :location_x, :location_y, :oval_width, :oval_height, :cylinder_height, :pitted, :background_color, :line_thickness
alias pitted? pitted
before_body do
self.location_x ||= 0
self.location_y ||= 0
self.oval_width ||= oval_height || cylinder_height || DEFAULT_SIZE
self.oval_height ||= oval_width || cylinder_height || DEFAULT_SIZE
self.cylinder_height ||= oval_width || oval_height || DEFAULT_SIZE
self.line_thickness ||= 1
end
body {
shape(location_x, location_y) {
oval(0, cylinder_height, oval_width, oval_height) {
background background_color
oval { # draws with foreground :black and has max size within parent by default
line_width line_thickness
}
}
rectangle(0, oval_height / 2.0, oval_width, cylinder_height) {
background background_color
}
polyline(0, oval_height / 2.0 + cylinder_height, 0, oval_height / 2.0, oval_width, oval_height / 2.0, oval_width, oval_height / 2.0 + cylinder_height) {
line_width line_thickness
}
oval(0, 0, oval_width, oval_height) {
background background_color
oval { # draws with foreground :black and has max size within parent by default
line_width line_thickness
}
}
if pitted?
oval(oval_width / 4.0, oval_height / 4.0, oval_width / 2.0, oval_height / 2.0) {
background :black
}
end
}
}
end
end
end
Cube
# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_SAMPLES.md#quarto
class Quarto
module View
class Cube
include Glimmer::UI::CustomShape
DEFAULT_SIZE = 28
options :location_x, :location_y, :rectangle_width, :rectangle_height, :cube_height, :pitted, :background_color, :line_thickness
alias pitted? pitted
before_body do
self.location_x ||= 0
self.location_y ||= 0
self.rectangle_width ||= rectangle_height || cube_height || DEFAULT_SIZE
self.rectangle_height ||= rectangle_width || cube_height || DEFAULT_SIZE
self.cube_height ||= rectangle_width || rectangle_height || DEFAULT_SIZE
self.line_thickness ||= 1
end
body {
shape(location_x, location_y) {
polygon(0, cube_height + rectangle_height / 2.0, rectangle_width / 2.0, cube_height, rectangle_width, cube_height + rectangle_height / 2.0, rectangle_width / 2.0, cube_height + rectangle_height) {
background background_color
}
polygon(0, cube_height + rectangle_height / 2.0, rectangle_width / 2.0, cube_height, rectangle_width, cube_height + rectangle_height / 2.0, rectangle_width / 2.0, cube_height + rectangle_height) {
line_width line_thickness
}
rectangle(0, rectangle_height / 2.0, rectangle_width, cube_height) {
background background_color
}
polyline(0, rectangle_height / 2.0 + cube_height, 0, rectangle_height / 2.0, rectangle_width, rectangle_height / 2.0, rectangle_width, rectangle_height / 2.0 + cube_height) {
line_width line_thickness
}
polygon(0, rectangle_height / 2.0, rectangle_width / 2.0, 0, rectangle_width, rectangle_height / 2.0, rectangle_width / 2.0, rectangle_height) {
background background_color
}
polygon(0, rectangle_height / 2.0, rectangle_width / 2.0, 0, rectangle_width, rectangle_height / 2.0, rectangle_width / 2.0, rectangle_height) {
line_width line_thickness
}
line(rectangle_width / 2.0, cube_height + rectangle_height, rectangle_width / 2.0, rectangle_height) {
line_width line_thickness
}
if pitted?
oval(rectangle_width / 4.0, rectangle_height / 4.0, rectangle_width / 2.0, rectangle_height / 2.0) {
background :black
}
end
}
}
end
end
end
Rest of the Quarto code:
Views:
Models:
Now, let us get into the Canvas Shape DSL tutorial.
Below are examples of using the Canvas Shape DSL in Glimmer DSL for SWT.
Example of line
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
line(30, 30, 170, 170) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--4paW4nnT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-line.png)
Example of rectangle
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
rectangle(30, 50, 140, 100) {
background :yellow
}
rectangle(30, 50, 140, 100) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--quPO8FGX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-rectangle.png)
Example of rectangle
with round corners having 60 degree angles by default (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
rectangle(30, 50, 140, 100, round: true) {
background :yellow
}
rectangle(30, 50, 140, 100, round: true) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--49ithkLR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-rectangle-round.png)
Example of rectangle
with round corners having different horizontal and vertical angles (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
rectangle(30, 50, 140, 100, 40, 80) {
background :yellow
}
rectangle(30, 50, 140, 100, 40, 80) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--mqhop1hV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-rectangle-round-angles.png)
Example of oval
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
oval(30, 50, 140, 100) {
background :yellow
}
oval(30, 50, 140, 100) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--Hlt0fyhn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-oval.png)
Example of arc
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
arc(30, 30, 140, 140, 0, 270) {
background :yellow
}
arc(30, 30, 140, 140, 0, 270) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--lIpj7c15--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-arc.png)
Example of polyline
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
polyline(30, 50, 50, 170, 70, 120, 90, 150, 110, 30, 130, 100, 150, 50, 170, 135) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--oHk2hZj0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-polyline.png)
Example of polygon
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
polygon(30, 90, 80, 20, 130, 40, 170, 90, 130, 140, 80, 170, 40, 160) {
background :yellow
}
polygon(30, 90, 80, 20, 130, 40, 170, 90, 130, 140, 80, 170, 40, 160) {
foreground :red
line_width 3
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--ZHPVfvrT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-polygon.png)
Example of text
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
text(" This is \n rendered text ", 30, 50) {
background :yellow
foreground :red
font height: 25, style: :italic
rectangle { # automatically scales to match text extent
foreground :red
line_width 3
}
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--UDwiDVvc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-text.png)
Example of image
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 512, 542
canvas {
background :white
image(File.expand_path('icons/scaffold_app.png', __dir__), 0, 5)
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--lhJIu88h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-image.png)
Example of image
pre-built with a smaller height (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
@image_object = image(File.expand_path('icons/scaffold_app.png', __dir__), height: 200)
shell {
text 'Canvas Shape DSL'
minimum_size 200, 230
canvas {
background :white
image(@image_object, 0, 5)
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--sI9oF1Qq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-image-shrunk.png)
Example of setting background_pattern
attribute to a horizontal gradient (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
oval(30, 30, 140, 140) {
background_pattern 0, 0, 200, 0, rgb(255, 255, 0), rgb(255, 0, 0)
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--K-i2j-Og--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-oval-background-pattern-gradient.png)
Example of setting foreground_pattern
attribute to a vertical gradient (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
oval(30, 30, 140, 140) {
foreground_pattern 0, 0, 0, 200, :blue, :green
line_width 10
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--tHwP2q6L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-oval-foreground-pattern-gradient.png)
Example of setting line_style
attribute to :dashdot
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
oval(30, 50, 140, 100) {
background :yellow
}
oval(30, 50, 140, 100) {
foreground :red
line_width 3
line_style :dashdot
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--XmbPoHUn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-oval-line-style-dashdot.png)
Example of setting line_width
attribute to 10
, line_join
attribute to :miter
(default) and line_cap
attribute to :flat
(default) (you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
polyline(30, 50, 50, 170, 70, 120, 90, 150, 110, 30, 130, 100, 150, 50, 170, 135) {
foreground :red
line_width 10
line_join :miter
line_cap :flat
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--a5iIJcvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-polyline-line-join-miter-line-cap-flat.png)
Example of setting line_width
attribute to 10
, line_join
attribute to :round
and line_cap
attribute to :round
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
polyline(30, 50, 50, 170, 70, 120, 90, 150, 110, 30, 130, 100, 150, 50, 170, 135) {
foreground :red
line_width 10
line_join :round
line_cap :round
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--gI-_zZCj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-polyline-line-join-round-line-cap-round.png)
Example of setting line_width
attribute to 10
, line_join
attribute to :bevel
and line_cap
attribute to :square
(you may copy/paste in girb
):
require 'glimmer-dsl-swt'
include Glimmer
shell {
text 'Canvas Shape DSL'
minimum_size 200, 220
canvas {
background :white
polyline(30, 50, 50, 170, 70, 120, 90, 150, 110, 30, 130, 100, 150, 50, 170, 135) {
foreground :red
line_width 10
line_join :bevel
line_cap :square
}
}
}.open
![](https://res.cloudinary.com/practicaldev/image/fetch/s--zCh83Rwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-swt/master/images/glimmer-canvas-shape-dsl-polyline-line-join-bevel-line-cap-square.png)
Happy Glimmering!
43