MASTERCLASS EXTRA
WILF’S WORKSHOP PROGRAMMER’S WORLD
This Series
Part One
Part Two
Part Three
Part Four
Creating a teachable program
Building an interface that meets the needs
Basic analysis of the game
Building code that can learn
How the program can adapt to experience
Wilf Hey
Building an interface
wilf.hey@futurenet.co.uk Wilf Hey has been grappling with programming for several decades now, and is always up for a game of noughts and crosses
Wilf Hey considers how to start writing a program that will have the ‘intelligence’ to learn to play a good game of noughts and crosses
PROJECT TIME
ver the next few months, we’ll look at how a program (or program suite) can improve its own performance by adapting its behaviour. Esentially, this kind of adaptation we recognise in humans and animals as ‘intelligence’, so perhaps we can be forgiven for adopting the same term and applying it to a program that will learn to do its task better over time. We don’t have the hubris to say that we can build a ‘thinking’ program, but surely learning is in itself an admirable and unusual property of a piece of code.
SKILL LEVEL
O
If we wanted to better the behaviour of a child, we would first set a goal. Ideally, we would teach that child how to learn anything and then give it free reign. Even if we could devise a computer program to do this, we have neither the time nor the resources to do so. We must content ourselves with crafting a program that will perfect its behaviour over time during a small task – like playing a simple game of noughts and crosses. 1 Counting and consequences In a recent Workshop project, we built a QBasic program that played a good game of counting: it was good because we knew how to analyse the game, and built that knowledge directly into the program. It played as well as we had programmed it, and no better. To a non-programmer, it may seem intelligent because it played the game well, no matter what rules were set by its opponent. This was just a blind, because the program worked out the consequences of the rules before it made a
3 00 HRS
0
10
MINS
7
INTERMEDIATE A strong knowledge of programming is useful
YOU’LL NEED THIS
! The traditional ‘board’ for Naughts & Crosses is a roughly-drawn matrix of four ledger lines making up nine cells.
Any programming language, but QBasic is used here for illustration purposes
http://forum.pcplus.co.uk
single move. What will be different about our new learning program? We’ll only build into it some basic facts about the game. It will have to find out for itself the consequences of both bad and good moves, and will select moves according to its own experience. So what is noughts and crosses? What does it take to win? You probably learned the game when you were much younger, and it’s likely that you’ve developed what you think is a good strategy. The object is to put your symbol – a nought or a cross – in three cells in a row on the matrix. The row may be orthogonal or diagonal. Two opposing players take turns putting their symbol in any empty cell from among the nine. By convention, the first player to take a turn adopts the ‘Cross’ as symbol. If opponents play well, the game is
Triggering memories of trig’ The simple way to put a slant on a shape You probably remember that the trigonometric functions depend on the special relationship between the three sides of a right-angled triangle: the square of the length of the longest side is equal to the sum of the squares of the shorter two sides. Think of an equilateral triangle, split right down the middle. The longest side is one of the original sides, and the shortest is exactly half of one of the original. If it measures one metre, the long side measures two metres. The relationships between the sides means that the other short side must have a length of square root three. We’ve prepared a small program for the SuperDisc called ‘TRIG01’. It’s coded
in QBasic, but included is a straight executable version named ‘TRIG01.exe’. It produces a display showing the fundamental trigonometric functions in the ‘1-2-root 3’ triangle. These functions, sine, cosine and tangent (SIN, COS and TAN) are the ratios of one side of the triangle to another. Each of these ratios remains the same for a particular angle size, no matter how large the triangle. Suppose you want to draw a line at a 10 degree slant to the horizon. Using the SIN and COS functions, you can calculate the coordinates of a point. Joining one end of the original horizontal line to that point produces a line at the desired slant. Because these two functions are available in almost any computer
language, you have the tools to work out how to draw that slanted line. In QBasic, SIN and COS deal with angles measured in radians, so we have to multiply the number of degrees by a special constant to convert it to radians, and use that to get SIN and COS. ■
The right-angled triangle with sides 1, 2 and root 3 provides a simple way to remind yourself of the use of trig’ functions.
PCPlus 228 | April 2005
165
PROGRAMMER’S WORLD WILF’S XXXXXXXXXXXXXX WORKSHOP
MASTERCLASS EXTRA
So what is Wilf’s Workshop? The art and theory of modern programming, whichever languages you use. Learn techniques that will benefit any project, and gain a deeper understanding of advanced coding disciplines.
PASSING SHOT In a recent Workshop project, we looked at how the modulo function can aid us turning switches on and off in sequence. Here, we have a similar mechanism: in the test program NandX02 (on the SuperDisc), we test the effect of drawing the ‘X’ symbol in each cell in turn using the same code for each cell. How do we make the program progress from one cell to the next, and then cycle back to the start? The modulo function, of course! Note the use of DO / LOOP (instead of FOR / NEXT). It’s more flexible and allows for the use of EXIT DO to break out of the sequence when interrupted.
PAINT’s ‘Stretch and Skew’ function can be used to show the effectiveness of putting a slant into lines that make up the matrix.
Because the cell is a rhombus instead of a square, more attention must be paid when designing ‘X’ and ‘O’ characters.
bound to be a draw (no winner), but there are enough variations that even an expert player may occasionally make a mistake and their opponent will win.
the board will be displayed, and how characters can be placed into cells. The matrix is a three-by-three set of squares, but a precise display of nine cells like that would seem somewhat mechanical. It would be better if we can simulate a more human display. Our board should look a little rough, made up of two parallel horizontal lines and two parallel vertical lines. This makes only one cell with eight surrounding open areas; they would look more like cells if they were enclosed on all sides. And when we say vertical and horizontal lines, we should more accurately say ‘nearly vertical’ and ‘nearly horizontal’. If we drew the matrix with computer-like precision, we would lose the look and feel of a genuine human game of noughts and crosses. Try, with an easy drawing tool such as PAINT, a few matrix drawings with slanted lines. Draw perfect horizontal and vertical lines and ‘stretch and skew’ them a bit to see what looks natural. We found that a horizontal slant of five degrees and a vertical slant of 10 degrees produces an agreeable, human-looking matrix. Putting that little natural looking tilt to
2 Knowing the rules At the very least, a noughts and crosses playing program must know the rules: when to take its turn, how to take claim of a cell, and enough about the configuration of symbols on the matrix that it can identify when it has won or lost, or when no further turn is possible. The normal way children play the game is with a pencil and paper. The player who adopts ‘X’ draws the matrix and selects one of the cells for the first turn. Then the other player chooses a cell for the ‘O’ symbol. Computer programs are ill-equipped to draw the matrix and mark a cell, so it must be able to simulate the board on-screen. Since the opposing player (a human) is poor at firing up pixels on the screen, our ‘intelligent’ program must be able to determine its opponent’s desired cell and place the appropriate symbol in that cell on their human opponent’s behalf. It seems that our first order of business is to develop the interface – how
How to place a symbol (‘X’ or ‘0’) into a cell
By putting the slant on the board we use, we’ve introduced a complication: the boundaries of each cell are themselves slanted lines – a diamond shape. We should aim to make a small subroutine to draw each symbol relative to the coordinates of a corner of a cell. Then we should be able to place an ‘X’ into any cell on demand.
1
166
PCPlus 228 | April 2005
A drawing subroutine must use relative addressing and depends on being passed information about the cell’s location. In the test program NandX02, a subroutine called SelectCell will determine the coordinates of its top-left corner, and then subroutine LETTERX will draw the symbol ‘X’ using the coordinates of the cell’s top-left corner.
2
Developing the subroutine LETTERX isn’t an easy task, and involves some interesting debugging and testing. For example, LINE –STEP commands must be used because they use only relative coordinate addressing. Drawing the ‘X’ requires careful planning, as the shape is traced and then coloured in with a PAINT STEP command.
3
MASTERCLASS EXTRA
WILF’S WORKSHOP PROGRAMMER’S WORLD
Rounding to a multiple of six The long lines displaying the noughts and crosses matrix get rounded up In creating the noughts and crosses matrix, we rounded up calculations so that pixels were a multiple of six. This is easy: just add five and subtract the ‘modulo six’ result. For example, 225 is rounded by first adding five to make 230. This number, modulo six, is two, which is subtracted to leave 228. This
number is the smallest multiple of six not less than 225. Why do we insist on the coordinates all being multiples of six? The matrix has to be divided into three cells along each side, and we’ll want to locate the centre of each cell. To get whole pixels in each subdivision, each side of the
the ledger lines within program code involves a slight complication. Trigonometry must be used, and if you’re like most of the population, that subject will be pretty dim in your memory. You can see how to produce the slant by running the program NandX01, either the BAS form with the QBasic interpreter, or directly with the EXE form. It goes stepby-step through the method for drawing the matrix and then displays the coordinates of points so that you can draw the matrix directly and instantly (fundamentally, four LINE statements!), as in the next test program, NandX02, also on the SuperDisc. 3 Dimensional awareness NandX02 doesn’t bother to go through the whole process of drawing a square matrix and then ‘correcting’ it by slanting it. It draws the slanted version directly. Nor does it go through calculations to determine the coordinates for each cell. Instead, it’s aware of the dimensions of each (identical) cell, and keeps account of only one point: the north-west corner of each of the nine cells. However, even with that piece of knowledge, it doesn’t go through gross calculations based on the actual on-screen display. Instead, we inspected the nine values it had to know and derived a simple formula so that, given a number between one and nine, it can compute the X and Y coordinates of that corner. This was planned in two steps. In the first, we constructed the formula to turn the single number one to nine into two numbers: row, and column, both in the range one to three. From this, taking the displayed results of NandX01, we noted the effect on the X coordinate by moving one column to the right, and by moving one row down. If the board was orthogonal (made up of boring horizontal and vertical lines), we would only need one factor. In an orthogonal matrix, moving one column to the right doesn’t alter the Y ordinate, and moving one row down doesn’t alter the X ordinate. With the slants we’ve imposed, changing a
matrix must be divisible by three, and should be even. If you wanted to draw a chess board (eight cells by eight), you would be wise to add 15 to the side’s length and subtract modulo 16. In the calculations for slanting lines, rounding up will slightly change the angle of slope, but by only a small amount. ■
cell modifies both coordinates, but the calculation is still quite simple. When this is perfected, we have all the ingredients for a successful interface; we can draw the matrix complete with a nifty slant that makes it look natural, and we have a way to identify each cell when required. We’ve developed a subroutine that will draw a nice ‘X’ in any cell you choose. It does rather more than it needs to, but once we’re satisfied with the look of the character, we can simplify the code. However, the PC is certainly fast enough to make the loss of time negligible. Depending on our mood, perhaps we won’t change the LETTERX routine – it’s not really broken, so why ‘fix’ it? There’s still work to be done, as there’s no LETTERO subroutine yet (though we’ve taken the first step by making a subroutine that puts a blob in a chosen cell). Perhaps you will complete this work by creating the necessary instructions to draw a letter ‘O’. You must remember: use only drawing commands that have relative addressing – in particular, relative to the location of the north-west corner of a cell. There is an important thing to note here, so we’ll save you the headache of running into the problem yourself. The PAINT command is useful to fill in an outline you make up with LINE statements, but it has its limitations. You should set a border and be absolutely sure that the painting process will eventually run into this border colour. If there’s any doubt, you risk the possibility of painting the whole screen all at once, as the colour ‘leaks’ through a hole in the border. A good way to keep things under control is to reserve a colour – perhaps ‘bright magenta’ – for use as a border, never to be used for anything else. Then, when you want to wipe clean a cell, you draw bright magenta lines around its edge, then you can PAINT anywhere within the cell. Here’s the trick: you PAINT bright magenta, stopping at the bright magenta border. Now you can draw LINEs and PAINT to your heart’s content knowing that you have totally erased the cell and nothing further. PCP
When the coordinates of the end of long lines is a multiple of six, it’s easy to divide a slanted line into six parts.
GOING FURTHER Perhaps you’re under the impression that the way to win in noughts and crosses is to make the first move in the centre cell. This is false. The expert player can guarantee at least a tie no matter which cell is the first choice. In fact, when you’re playing against a novice player, taking the centre cell with your first ‘X’ isn’t the best strategy at all. Taking any one of the four corner cells is better by a factor of four to one! Why is this? Try thinking through the best first moves of the second player when opposing an expert.
NEXT MONTH We’ll look closely at possible strategies: what knowledge must be in-built, and what can be left for the program to find out for itself?
Ideally you should be able to create a subroutine to draw a symbol at ANY location.
We found temporary ‘debug’ routines helpful, displaying results on-the-fly. These subroutines will be deleted when the program is ready for running. Debugging routines all begin with ‘d’ and appear at the end of the main program code section. In order to print more visible characters, they must juggle the cursor, saving and resetting it each time.
4
Our tactic was to draw a full size ‘X’, going right up to the corners of the cell. This is relatively easy to draw, but is much too large. We then drew two slanted (near-horizontal) lines – this, in effect, chopped short each of the four arms of the ‘X’. Then the chopped-off remains are covered over via a simple PAINT command.
5
This is fast, but not direct. Among the debug subroutines is ‘dFullScan’, which displays the coordinates of the edges of the symbol. Armed with this information, we have the luxury of being able to re-code LETTERX so that our ‘chopping off arms’ strategy isn’t needed. Perhaps you can design a routine to create LETTERO in a similar way?
6
PCPlus 228 | April 2005
167