Background & Techniques
A young Delphi student (from a foreign country of course -
I think we have no such thing in the USA), recently wrote asking for help
in designing a board for a class assignment. She had carefully
hand placed 64 TPanels on the form and created 64 OnClick event exits,
but didn't quite know how to proceed from there. I didn't
either. I suggested to her that it would be better to
dynamically create an array of TPanels and use subscripting to access
them. That led me to wonder about the trade offs using 64
TPanel controls vs. my normal approach - a single
TStringGrid. Only one way to
find out - try them both. Since the panels were restricted to square
shapes, I also created an array of circular TShapes.
Reversi Rulesi
To make the boards comparison realistic we need to do something with
them. I chose the game of Reversi because the rules are
simple and it looked quite easy to implement. Each player starts
with two tiles of their color arranged diagonally on the center squares as
in the first board in the images above. I chose Red and Blue as the
piece colors and player names. In this version Red always plays
first. For each turn, player X must click on an empty
square that completes a line of one or more consecutive opponent
pieces with a player X piece on each end. The line can be
extend in any of 8 directions from the clicked square, (left, right, up
down, and the 4 diagonals). When clicked, all opponent's pieces in
each such line from the the clicked square are "reversed" to
become player X pieces. Play continues alternating turns until all
squares are filled. The player with the most pieces
wins. If a situation occurs where a player has no valid move,
he passes that turn an the opponent plays again.
While the program is mainly intended to illustrate game board building
techniques, they are all playable - in fact three games can be played simultaneously,
one on each board. Message labels above each board give turn
information and current score, A second message line reports cases
with no move available and game winner information.
Non-programmers are welcome to read on, but may
want to skip to the bottom of this page to
download executable version of the program.
Creating the boards
Global variables Offsetx, OffsetY and Panelsize define the location and
cell size for each board. Board2 and Board3 start at 9 and 18
panel size increments to the right of Board1. The FormActivate method
creates all of the components for the boards. Beginners frequently
have problems creating visual controls because the Parent property must be
specifically assigned. Parent is the windowed control that
will control the drawing and redrawing of the child controls. For
Board1, the TPanel board, the parent is "self", the main
form. For Board2, the TShapes, I created a Tpanel to frame them and
assigned it as the parent for each shape control. Board3 is single
TStringControl with 8 columns and 8 rows.
Click handling
TPanels can respond to clicks via an OnClick event exit, so that's the logical place to process user clicks on board
pieces. To build the exit, it's easiest to drop a TPanel on
the form temporarily, create an OnClick exit, delete the panel and
use the blank exit as the model for our click handling code. How to
keep track of which pieces are where on the board? For the
simplest case, we can use the Tag property to indicate the cell
status. Tag is a "spare" integer field
defined for every Delphi control. We'll use Tag
values of 0 for empty, 1 for a Red piece and 2 for a Blue
piece. If more complicated entries were required
(for example, if pieces had multiple properties like color and type
and value and power and smell or whatever, we could make our own
class descended from TPanel [TPiece=class(TPanel)] and
extend it with the additional properties we needed. But not in this
case. Local function ValidMove returns true if the
clicked empty cell adjoins at least one string one or more opponents
pieces between it and another piece of the clicker's color. If ValidMove returns true, procedure MakeMoves actually
changes the Tag values of the cells to indicate change all of the
adjoining strings of opponent's pieces to my pieces plus adding a piece of
my color on the the clicked cell. This change is applied in all 8
directions.
For the 2nd board, we'll use TShapes to make circular cells.
TShapes are descendents of TGraphicControl, a visual control type that has
less overhead than the standard windows TWinControl. TPanels
descend from TWinControls. Unlike TPanels, TShapes do
not have an OnClick event exit available. It is only a minor
inconvenience though to use the OnMouseUp event exit
instead. In this 2nd try at handling clicks, I
combined the ValidMove function with the MakeMoves
procedure. The MakeMoves function now returns the total score for the
pieces added and has a "Moveit" parameter that determines
whether or not the move is actually made.
Finally, the 3rd board is a TStringGrid. Now we have only
one control (and thus only one Tag value, but we can use the 64 cell values to indicate
who occupies each cell. An OnSelect event exit is called with
each
clicked cell with column and row passed as parameters. The same MakeMoves
logic used in Board2 is applied here. An OnDrawCell
exit checks the value to determine what piece (colored circle)
to draw each time the board need redrawing.
Comparing the types, String grids would make it easier to show a cell
and the piece on the cell as separate entities, for example if we wanted a
black and white checkerboard pattern for the board. Dragging
pieces or animating piece movement seems like it would be possible under
any of the boards - guess we'll need another version of the program
to check that out. In the meantime, perhaps someone will find this
useful.
.Running/Exploring the Program