DEV Community

Cover image for Quarto Game, Custom Shapes, and Canvas Shape DSL Tutorial
Andy Maleh
Andy Maleh

Posted on • Edited on

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!!!

Quarto Board Game

Here is the classic Quarto black board version that the party attendees played with during Christmas:

Classic Quarto

I built Quarto as a computer game in 4-5 days using Glimmer DSL for SWT!

Quarto Computer Game

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) } 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Rest of the Quarto code:

Views:

https://github.com/AndyObtiva/glimmer-dsl-swt/tree/master/samples/elaborate/quarto/view

Models:

https://github.com/AndyObtiva/glimmer-dsl-swt/tree/master/samples/elaborate/quarto/model

Now, let us get into the Canvas Shape DSL tutorial.

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Line

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Rectangle

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Rectangle Round

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Rectangle Round Angles

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Oval

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Arc

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Polyline

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Polygon

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Text

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Image

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Image

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Oval Background Pattern Gradient

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Oval Foreground Pattern Gradient

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Oval

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Polyline Line Join Miter Line Cap Flat

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Polyline Line Join Round Line Cap Round

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 
Enter fullscreen mode Exit fullscreen mode

Canvas Shape DSL Polyline Line Join Miter Line Cap Flat

Happy Glimmering!

Top comments (0)