Its Big Thing To See
2018-10-12 :: racket
The Racket GUI library provides an “editor toolkit” which can be used to implement programs that use an interactive graphical canvas where objects can be moved around with the mouse. This toolkit has good reference documentation, however this documentation can be somewhat overwhelming, and it is not always clear how to begin writing such interactive application, or how to achieve some basic functionality, so I wrote a tutorial on how to implement a chess board game in Racket using the pasteboard% and snip% classes which are part of the GUI library.
This tutorial presents a step-by-step guide on how to create a chess board application using only the Racket GUI toolkit. While it uses chess as an example application, it is meant to illustrate how to create new snip% objects and how to manage them inside pasteboard% objects, and rather than enumerating all the features of the pasteboard% class, it just shows the ones that are needed to solve the immediate problems of implementing the game. The tutorial will not implement a complete chess game, as in you will not be able to play a game of chess against the computer with this program — it will only deal with the graphical interface of the chess game.
The topics that I wanted to cover turned out to be too long for a single blog post, so the final program does not provide a payable game, I plan to write another blog post covering some more topics related to this.
The first thing we need in a chess game are the chess pieces, and to keep the application simple, we will use the Unicode characters for the chess pieces, so each piece will actually be displayed as text. There are 12 chess pieces in total listed below with their name, mnemonic letter and Unicode code point. The Unicode glyphs, as displayed below, are too small to be used in the game, but they can be rendered using a larger font to make them bigger.
Before we can write the snip% for the chess pieces, we need to define a “snip class”. This name is misleading, as we are not defining a class%, but an instance of the snip-class% object. This “snip class” is used when serializing snips from the pasteboard. We won’t use this functionality here, but we still need to define one. Since we don’t use serialization facilities the definition is very simple, and shown below. The class name, as passed to set-classname needs to be unique between all the snip classes used by the application:
The snip class object needs to be registered with the Racket editor gui, and this is done using the call below:
(send (get-the-snip-class-list) add chess-piece-snip-class)
We will use a single snip class for all chess pieces, since the only difference between them is how they are displayed. To have a minimal working snip, three things need to be present in the derived class:
Our chess-piece% snip class receives three arguments in the constructor: a glyph, which is a string representing the Unicode character for the piece, a font used to render the glyph and a size which is the size in pixels of the chess piece (since the piece is a square it will have the same width and height). The font object could already determine the size of the piece, but a separate size parameter allows us to define chess pieces that are larger than the glyph that they display (as an exercise, you can experiment with different variations of font and size in the make-chess-piece defined later).
The get-extent method is used by the pasteboard% to obtain the dimensions of the snip. The implementation is straightforward, as it just reports the size as both the width and height, but the actual method call is somewhat unusual: a device context, dc is passed in, together with the position of the snip on the canvas as the x and y coordinates, in return, the pasteboard% expects the width, height and some other parameters to be filled in by our object (the other parameters have to do with snips that are part of a text editor and represent text, they don’t concern us here, so they are set to 0). The method is somewhat unusual as it uses box-es for the output parameters, so we need to use box-set! to set the output value.
The draw method is used to paint the snip contents onto the canvas. It receives the device context, dc and the snip position on the canvas. The method also receives other parameters which would allow re-drawing only the parts of the snip that actually need updating, but our code always draws the entire snip contents. Our implementation simply draws the glyph in the middle of the snip area, but nothing else, so the actual snip appears transparent, with only the chess piece being drawn.
Creating chess-piece% instances is inconvenient, as we have to remember the Unicode glyph for each chess piece, as well as the font and size to use for them (and preferably to create all pieces of the same font and size). To simplify their, we can write a function, make-chess-piece which receives the piece mnemonic (K for king, Q for queen, etc) and creates the piece. Thus to create a white king piece, we can just call (make-chess-piece "K"):
"K" #u2654 "Q" #u2655 "R" #u2656 "B" #u2657 "N" #u2658 "P" #u2659
"k" #u265A "q" #u265B "r" #u265C "b" #u265D "n" #u265E "p" #u265F))
(define (make-chess-piece id)
(define glyph (hash-ref chess-piece-data id))
(define font (send the-font-list find-or-create-font 20 'default 'normal 'normal))
(new chess-piece% [glyph (string glyph)] [font font] [size 35]))
Finally, we can write some test code to check how our snips work: we’ll need a pasteboard% instance to hold the chess piece snips, and an editor-canvas% to display the contents of this pasteboard. As a test, we insert an instance of each chess piece type, so we can see how they look:
;; The pasteboard% that will hold and manage the chess pieces
(define board (new pasteboard%))
;; Toplevel window for our application
(define toplevel (new frame% [label "Chess Board"] [width (* 50 8)] [height (* 50 8)]))
;; The canvas which will display the pasteboard contents
(define canvas (new editor-canvas%
[style '(no-hscroll no-vscroll)]
(send toplevel show #t) ;; show the toplevel frame
;; Insert one of each of the chess pieces onto the board, so we can see them
;; and drag them around.
(for ([id (in-hash-keys chess-piece-data)])
(define piece (make-chess-piece id))
(send board insert piece (random (* 50 6)) (random (* 50 6))))
You can find the entire program in this GitHub Gist and if you run it you will get the result as shown below. With only about 65 lines of code we already have an application which can display chess pieces and move them around a board using the mouse. The board however does not look like a chess board at all, so we need to work on that next.
To display the chess board pattern on the background, we can create a derived pasteboard% class and implement the on-paint method. Since the chess board itself is static and the user does not interact with it, this approach is the simplest. The on-paint method is invoked twice during each repaint: once before the snips are painted and once after. The before? parameter tells us which invocation it is. The method also receives a device context, dc, plus some other parameters which determine the area that needs repainting — these parameters would help in speeding up complex redraw operations, but since our drawing needs are simple, we just redraw the entire board every time. Our implementation simply calls the draw-chess-board function (defined later) when the on-paint is invoked to draw the background:
The draw-chess-board function is shown below. It paints the checkered pattern of the board on the entire size of the device context, which is the canvas area. To assist the player identifying squares in chess notation, such as “d3”, it also paints the rank and file values on the left and bottom edges.
A few points about this code are worth mentioning:
To use the code, we need to instantiate our new chess-board% class for the board object, but the rest of the main program remains the same:
You can find the updated program in this GitHub Gist and if you run it you will get the result as shown below. We now have a chess board on which to move the pieces, but the pieces can still freely move on the board. We need to restrict the valid location of each chess piece to one of the squares and we will address this in the next section.
The pasteboard and snips use a coordinate system that is based on pixels, with the origin in the top left corner of the canvas, but a chess board is divided in only 64 squares forming an 8×8 grid. In addition to this, the chess game uses a “rank” and “file” coordinate system, where the rank represents the row on the board and it is numbered from 1 to 8, 1 being at the bottom, while the file are the columns of the board, labeled using letters from “a” to “h”.
Since each chess piece needs to know its location, we can store it directly into the chess-piece% object, as the location field with as setter and getter method. We allow the location to be #f which represents a piece that is not on the board. make-chess-piece will also have to be updated to allow passing an optional location to the created chess piece
The chess pieces are added to the chess board using the insert method, which allows placing a snip at any coordinate, or at (0, 0) if no coordinates are specified. To place a snip in its correct position, based on the location stored inside the snip, we can derive the pasteboard%’s after-insert method, which is invoked after each snip is inserted and call position-piece for the snip:
(define/augment (after-insert chess-piece . rest)
(position-piece this chess-piece))
;; rest of the chess-board% definition remains the same
position-piece takes a chess piece, finds the x, y coordinate for it on the chess board based on the location stored inside the snip and moves the piece to that location using the pasteboard%s move-to method. This function calculates the position based on the current canvas width and height, rather than pre-calculating the square positions, so it will work correctly regardless of the size of the board, or even if it is invoked when the size of the board changes:
(require embedded-gui) ; for snip-width and snip-height
(define (position-piece board piece)
(define-values (canvas-width canvas-height)
(let ((c (send board get-canvas)))
(send c get-size)))
(define-values (square-width square-height)
(values (/ canvas-width 8) (/ canvas-height 8)))
(define-values (rank file)
(location->rank-file (send piece get-location)))
(define-values (square-x square-y)
(values (* file square-width) (* rank square-height)))
(define piece-width (snip-width piece))
(define piece-height (snip-height piece))
(send board move-to piece
(+ square-x (/ (- square-width piece-width) 2))
(+ square-y (/ (- square-height piece-height) 2))))
The location->rank-file function, used by position-piece, converts a chess board location into the row and column of the corresponding square on the board. It is simpler for our program to use chess board locations, written as “d3” or “f5”, as they can be read as imputs from the user or from a file:
We can now write a function which loads a chess game onto the board. The game is encoded as a string with the piece mnemonic followed by its position. For example, “Ra1” means that the white rook is at square “a1”. The function just parses the string, creates the chess pieces and inserts them onto the board. Also, the first thing this function does is to clear the board of previous pieces, by calling the pasteboard%’s clear method:
If you run the program now, you will notice that, while the chess pieces are correctly positioned initially, when the board is resized, the pieces will stay in place, instead of moving with their respective squares. We can fix this by repositioning the pieces when the board is resized: the on-display-size method is q called when the canvas changes size, and inside it we can iterate over all the snips in the pasteboard and simply call position-piece for each one. Since position-piece looks at the location of a piece (“d3”, “f5”, etc) and always takes the current canvas size into consideration, it will move the piece at its correct location. The code that repositions the snip pieces is also wrapped in begin-edit-sequence/end-edit-sequence method calls on the pasteboard. These calls ensure that the pasteboard will not be refreshed while the snips are moved and it will only be refreshed at the end after all snips were moved to their new locations. It is a good idea to wrap any code that modifies more than one snip in a begin-edit-sequence/end-edit-sequence block, otherwise the pasteboard will refresh more than necessary and graphics performance will be poor.
You can find the updated program in this GitHub Gist and if you run it you will get the result as shown below. While the pieces are in place now, they can still be moved to arbitrary positions, so it is time to start implementing some game logic now, this however is left for another blog post.
Smart Contract Business Underpinning (Blockchain Report Excerpt)
Apple Inks $600 Million Deal To Acquire Assets and Talent From Dialog Semiconductor