R.T.Russell Home Page

Chapter 20 - Developing Real Programs




This is the final part of our tutorial. This section deals with how we write programs. It doesn't introduce any new BASIC commands but does try to put across one of the most popular methods of writing a program. Up to now, all our programs have been little 10 line wonders designed to demonstrate individual aspects of the language. There is a tendency amongst programmers to start small like this and build a bit at a time: add a couple of lines here, a subroutine there ... Eventually the whole thing becomes a tangled mess, hard to follow and harder to debug or expand.

Traditionally programming was taught by teaching people to break things down into logical, progressive steps. Sometimes this is so easy you don't consciously do it. For example in the section on user defined characters, we have to set the character for the alien before making him walk across the screen. It wouldn't be any use to do this the other way round. Designing a 'proper' program follows a similar line but on a much larger scale. So much so that it might be days before we actually get round to typing in any code. The general advice is to concentrate on the generalities first, the big picture, and then break these tasks into progressively smaller ones until they get so small the code just drops out of its own accord.

It's very easy to wave your hands in the air trying to make generalized gestures in the hope that everyone will see what you mean, like playing Charades. To avoid this, I intend to walk through the design of an actual program, explaining the steps as we go so you can apply them to your own creations. The process is often referred to as developing from the 'top down'. This is a common technique used by many programmers, not something I dreamed up after three nights of reading William Gibson novels. The reason many programmers use it is it's easily adapted to different languages and it gives good results, i.e. it works.

If we take user defined characters, we can see that these are very useful. What can be a pain is actually setting up the grid and doing the mental arithmetic to derive the necessary row totals. This looks like a good candidate for a program to me.

How do we start? There is a very loose language called pseudo-code whose exact definition is somewhat vague as everyone tends to develop their own. What it usually looks like is a mixture of standard English with the odd word of BASIC thrown in. What is represents is the logical sequence of events necessary to accomplish the task in hand.

We start by getting the big picture. By this I mean we break down the tasks our program has to deal with into large chunks each of which can be summed up in a single line of several words.

Something like this:
Declare global arrays and structures
Initialization
Draw main screen
Draw character
Draw cursor
REPEAT
  Get user action
  Process user action
UNTIL User chooses exit
Shutdown
END
That's our basic program design. As you see, no specifics, everything is delegated to sub tasks. The next step is to take each of these sub tasks and refine them in a similar fashion. We'll leave the first two lines until later because at this early stage we have no idea what variables we need or how to initialize them. We start with Draw main screen. For this we need a rough idea of what our user will be presented with when the program is running. Sketch this on paper first, use squared paper if you want so you get a better idea of size. Here's what we're aiming for:

+--------------------------------------------------+
|               Character Designer                 |
|               ******************                 |
|                                                  |
| Instructions                                     |
| Use arrow keys to move cursor                    |
| Press space to toggle selected cell              |
| Press X or ESC to exit                           |
|                                                  |
|                                                  |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|               [][][][][][][][] 000               |
|                                                  |
|               * * * * * * * *                    |
|                                                  |
|   BASIC code to produce this character:          |
|                                                  |
|  VDU 23,240,000,000,000,000,000,000,000,000      |
|                                                  |
|                                                  |
+--------------------------------------------------+

The top half of the screen is what I would term the main screen, it won't change much during the course of the program. The lower part is the grid, VDU codes and life-sized characters. This will change a lot, so the design must deal with this separately. The line of stars underneath the grid represents a line of the actual characters. One thing we can instantly see is that the VDU line is bigger than the 40 characters that MODE 6 gives up, so we'll need to select another mode to accommodate this. MODE 21 gives us 50 columns which should be fine. We now know enough to work out what Draw main screen should look like.

Draw main screen
 
Set background colour
CLS
PRINT Title
PRINT help instructions
 
END
CLS was used instead of 'clear the screen' because it's obvious this is the required command.

Not much to do there, the next one promises to be a little more challenging: Draw character. This can be seen to consist of three separate parts: the grid, the actual character and the VDU codes. Keeping it general we get the following.

Draw character
 
Draw grid
Draw actual character
Print VDU codes
 
END
It's pretty obvious there's more work to be done here, but this is a first pass so we'll leave the detail until later. During the design, things often change, so there is no point getting too involved at this stage.

Next up: Draw cursor. This will be used to move the cursor around the grid. To make the cursor appear to move, we need to first erase it from its old position and redraw it in the new one. We can use a different colour to highlight the cursor as it travels round the grid, so we need to know whether the cell it's on is set or not.

Draw cursor
 
Set to normal colour
Erase cursor at old position
Set to highlight colour
Highlight cell at current position
 
END
Now we dive into the REPEAT loop and find Get user action. This will be responsible for intercepting key presses from the keyboard, filtering out the unwanted and translating needed ones into a code for the rest of the program to use. From our sketched screen, we can deduce that the keys we are interested in are the arrow keys, the space bar and X or x. Using ESC to exit is a built-in feature, so we don't need to deal with it as such. The program can sit and wait for a key to be pressed as all other actions depend on this. Once pressed we can decide if the key is useful or not and translate it to a code if it is.
Get user action
 
REPEAT
  Wait for key press
  Translate key press into code if valid
UNTIL valid key found
 
END
Lastly, for the moment, we have Process action. This will take the action from the previous routine and do something with it. The options are quite straightforward so we can write it without further ado.
Process action
 
CASE Code OF
WHEN Up:    Move cursor up
            Draw cursor
WHEN Right: Move cursor right
            Draw cursor
WHEN Down:  Move cursor down
            Draw cursor
WHEN Left:  Move cursor left
            Draw cursor
WHEN Exit:  Set global Exit flag
ENDCASE
 
END
Okay, that's our first pass, let's bring it all together so we can see it in one place.
Main program
 
Declare global arrays and structures
Initialization
Draw main screen
Draw character
Draw cursor
REPEAT
  Get user action
  Process user action
UNTIL User chooses exit
Shutdown
END
 
Draw main screen
 
Set background colour
CLS
PRINT Title
PRINT help instructions
 
END
 
Draw character
 
Draw grid
Draw actual character
Print VDU codes
 
END
 
Draw cursor
 
Set to normal colour
Erase cursor at old position
Set to highlight colour
Highlight cell at current position
 
END
 
Get user action
 
REPEAT
  Wait for key press
  Translate key press into code if valid
UNTIL valid key found
 
END
 
Process action
 
CASE Code OF
  WHEN Up:    Move cursor up
              Draw cursor
  WHEN Right: Move cursor right
              Draw cursor
  WHEN Down:  Move cursor down
              Draw cursor
  WHEN Left:  Move cursor left
              Draw cursor
  WHEN Toggle:Toggle grid position
  WHEN Exit:  Set global Exit flag
ENDCASE
 
END
If you were at all intimidated by the thought of writing a reasonable size program like this, I hope you can now see how we are beginning to split it up into the bite-sized pieces you've been used to working with so far.

Several of the routines look like they could use more refinement. These are Draw character and Process action.

Draw character has three lines that need thinking about. The first says 'Draw grid'. Drawing the grid includes the row totals at the end of each row too. What we need to do is decide if drawing the grid and calculating the row totals will need sufficient code to merit its own routine or will it go into this one without overloading it? By now it's becoming obvious that we're going to need an array to hold the grid. To draw the grid, we'll use one screen position for each cell, so all we need to do is go through each row and column printing the correct character as we go. As we are already embroiled in the grid, it makes sense to work out the row totals here as well. For each row, we need to set the total to zero, then, when we find a grid position that's 'on',  add the value of that column to the total. If we have a value set to 128 and halve it for each column, we can work out the value for that column.

Draw grid and calculate row totals for each row
FOR each row
  Set column value to 128
  FOR each column
    Set row total to 0
    IF Grid at row column position = 0 THEN
      PRINT at position empty cell character
    ELSE
      PRINT at position filled cell character
      Set row total to row total + column value
    ENDIF
    Set column value to column value / 2
NEXT column
PRINT at end of row row total
NEXT row
Personally, I think that fits comfortably in the existing routine, no need for a new one. Let's dissect the next line: Draw actual character. We have already found the values for each row in the previous lump of code so this is now quite easy.
Call VDU 23 with chr 240 and row totals
PRINT in position character 240 eight times
Finally we need to print the totals out in a nice line so users can copy them into their programs.
PRINT in position "BASIC code to produce this character:"
PRINT in position "VDU 23, 240";
FOR each row
  PRINT ",";total for row;
NEXT row
So our entire routine looks like this:
Draw character
 
Draw grid and calculate row totals for each row
FOR each row
  Set column value to 128
  FOR each column
    Set row total to 0
    IF Grid at row column position = 0 THEN
      PRINT at position empty cell character
    ELSE
      PRINT at position filled cell character
      Set row total to row total + column value
    ENDIF
    Set column value to column value / 2
NEXT column
PRINT at end of row row total
NEXT row
Call VDU 23 with chr 240 and row totals
PRINT in position character 240 eight times
PRINT in position "BASIC code to produce this character:"
PRINT in position "VDU 23, 240";
FOR each row
  PRINT ",";total for row;
NEXT row
 
END
See how we still kept the original task descriptions? These usually end up as REMs in the completed program as they tell the reader what each section is trying to achieve.

Onto Process action. Two things here. The first one is a slight restructure. Each action involves redrawing the cursor, so maybe we should pull this out of each statement and move it to the end, saves space and typing. That's easy enough because we haven't written any code yet. The other is the use of move cursor. We have a choice again: make a separate routine or write code in each place. I chose the first option because then we can write a generic routine to handle all cursor movement. We can make a note of this and design the routine in a minute. Meanwhile, is there anything else? We haven't dealt with what happens when the space bar is pressed. In reality, this is not too much of a challenge:

WHEN Toggle:  Toggle grid position
              IF Grid at cursor position = 0 THEN
                Set Grid at cursor position to 1
              ELSE
                Set Grid at cursor position to 0
              ENDIF
All we need to do is update the grid, Draw character in the main program will sort the rest out. This is our revised Process action routine:
Process action
 
CASE Code OF
WHEN Up:    Move cursor up
WHEN Right: Move cursor right
WHEN Down:  Move cursor down
WHEN Left:  Move cursor left
WHEN Toggle:Toggle grid position
            IF Grid at cursor position = 0 THEN
              Set Grid at cursor position to 1
            ELSE
              Set Grid at cursor position to 0
            ENDIF
WHEN Exit:  Set global Exit flag
ENDCASE
Draw cursor
 
END
After all this, we can return to our cursor movement routine. All that is needed here is to take the direction the cursor wants to move in and check that it is a valid position i.e. not off the end of the grid. If the position checks out move the cursor there. Before moving the cursor, we need to remember the previous position so Draw cursor can erase it before highlighting the new position.
Move cursor
 
Save current position in old position
CASE Direction OF
  WHEN Up:    IF Cursor row > 1 THEN
                Decrease Cursor row by 1
  WHEN Right: IF Cursor column < 8 THEN
                Increase Cursor column by 1
  WHEN Down:  IF Cursor row < 8 THEN
                Increase Cursor row by 1
  WHEN Left:  IF Cursor column > 1 THEN
                Decrease Cursor column by 1
ENDCASE
 
END
Version 2 of our program design now looks like this:
Main program
 
Declare global arrays and structures
Initialization
Draw main screen
Draw character
Draw cursor
REPEAT
  Get user action
  Process user action
UNTIL User chooses exit
Shutdown
END
 
Draw main screen
 
Set background colour
CLS
PRINT Title
PRINT help instructions
 
END
 
Draw character
 
Draw grid and calculate row totals for each row
FOR each row
  Set column value to 128
  FOR each column
    Set row total to 0
    IF Grid at row column position = 0 THEN
      PRINT at position empty cell character
    ELSE
      PRINT at position filled cell character
      Set row total to row total + column value
    ENDIF
    Set column value to column value / 2
NEXT column
PRINT at end of row row total
NEXT row
 
Call VDU 23 with chr 240 and row totals
Call PRINT in position character 240 eight times
PRINT in position "BASIC code to produce this character:"
PRINT in position "VDU 23, 240";
FOR each row
  PRINT ",";total for row;
NEXT row
END
 
Draw cursor
 
Set to normal colour
Erase cursor at old position
Set to highlight colour
Highlight cell at current position
 
END
 
Get user action
 
REPEAT
  Wait for key press
  Translate key press into code if valid
UNTIL valid key found
 
END
 
Process action
 
CASE Code OF
WHEN Up:    Move cursor up
WHEN Right: Move cursor right
WHEN Down:  Move cursor down
WHEN Left:  Move cursor left
WHEN Toggle:Toggle grid position
            IF Grid at cursor position = 0 THEN
              Set Grid at cursor position to 1
            ELSE
              Set Grid at cursor position to 0
            ENDIF
WHEN Exit:  Set global Exit flag
ENDCASE
Draw cursor
END
 
Move cursor
 
Save current position in old position
CASE Direction OF
  WHEN Up:    IF Cursor row > 1 THEN
                Decrease Cursor row by 1
  WHEN Right: IF Cursor column < 8 THEN
                Increase Cursor column by 1
  WHEN Down:  IF Cursor row < 8 THEN
                Increase Cursor row by 1
  WHEN Left:  IF Cursor column > 1 THEN
                Decrease Cursor column by 1
ENDCASE
 
END
Isn't cut and paste wonderful?

Finding the variables

The design has reached a point where you can start to 'see' the underlying code i.e. we've refined it enough. It's now time to invite the friends and family round so we can play 'hunt the variable'. At this point, I usually get a red pen and scribble on the pseudo-code. This is a bit difficult to do in Notepad or Internet Explorer, so we'll do a blow by blow analysis of each routine. Fortunately this will not take as long as the previous section. For each routine, we need to know the name, type and scope of each variable we find.

Looking through the main program, there are two variables that spring out. One is the flag that is used to Exit, the other is the choice of action from Get user action routine. Both are integers and by virtue of the fact that they are in the main program, global. Exit will need to be set up to false when the program is started, so this is a job for the initialization routine.

Draw main screen doesn't have any variables, it's all print statements. Easy!

Draw character is a bit more involved. We will need two local integers for the FOR loops, call them Row% and Col%. Another integer is needed for the value of each bit, call this ColValue%, again, no-one else needs to know about this, so it's local. The main character data itself is crying out to be held in a two dimensional array. We'll call this Grid% and it is global as other routines need access to it. Another array is needed to hold the totals for the rows, RowTotal%. This will be a single dimension and be integers. Again, we'll make this global.

Draw cursor seems to have four integers associated with the current and previous position of the cursor. It would be nice to keep all these together, so let's put them in a structure. The structure needs to be global as we know that the Move cursor routine uses it as well. All are integers and the initial position will be set in the initialization routine.

Get user action has one integer variable to hold the key press, Key%, and one integer to hold the return code, Code%. Both are local.

Process action can have the value for the action code passed to it. The Exit flag is global as previously described.

Move cursor manipulates the cursor structure as described in draw cursor.

There are several other details that need to be considered. For example, the position of each PRINT statement needs to be decided. We can take a rough guess at this based on the screen layout, but be warned, you never get it right first time round!

One thing we have not considered is how to represent the grid. Each position can be depicted by one character position. If it's filled we can use a solid block. If it's empty a space would seem to be the obvious choice. However, if we use a space, how do we know where the cursor is? We need a character for an empty cell as well. One with a single bit outline will suffice. So we need two user defined characters:

XXXXXXXX  255           XXXXXXXX  255
XXXXXXXX  255           X......X  129
XXXXXXXX  255           X......X  129
XXXXXXXX  255           X......X  129
XXXXXXXX  255           X......X  129
XXXXXXXX  255           X......X  129
XXXXXXXX  255           X......X  129
XXXXXXXX  255           XXXXXXXX  255
Where 'X' = filled and '.' = empty. Characters 241 and 242 will do fine for these. Setting them up is a job for the initialization routine.

Using this information, we can now write the initialization routine.

Initialize
 
Set graphics mode
Setup user characters 240, 241 and 242
Setup cursor location
Turn off text cursor
 
END
... and lastly, it's complement, Shutdown, whose job it is to do any tidying up before the program stops. We don't want to clear the screen here as the user might want to make a note of his new creation after the program has finished. We'll just turn the text cursor back on and move it to the bottom of the screen so it's not in the way.
Shutdown
 
Re-enable text cursor
Send cursor to bottom of screen
 
END

The Program

At last we are in a position to write the code. As you have probably guessed by now, each task such as Draw character should be kept separate from the main body of the code. To achieve this, we use PROCs and FNs. I'm not going to give a line by line account of this as if you have followed all the above, there's nothing that should shock or astound you. Click the link for the complete program. I like to leave blank lines to make things easier to read. Also each task is separated by a line of stars and a little description, this makes it easier to find when scanning through the code. I know I said at the beginning that you should type all the code in by hand, please feel free to do this, but just in case you are itching to see what the finished creation looks like, here's a cut and paste friendly version. Click the link below then, if you're in Internet Explorer, click the Edit menu and Select All, Edit again and Copy. You can then paste the whole thing into the editor. If you have another browser, there will probably be a similar method lurking in there somewhere. Use Back on your browser to return here when you've looked at it.

Main program listing

Conclusion

And there is our program. Not too bad, was it? The problem with writing this down is it makes it appear like a completely linear process. It's not. Often the pseudo-code will go through a number of revisions before settling on the final version. Even then when allocating variables or writing code you will encounter situations that necessitate going back and rehashing something. Far from being a waste of time, it's the best way to develop. Why? Simply because the more you sit down and think about something, chewing over different methods of doing it, the more chance you stand of getting it right. If you had an important interview or appointment, you wouldn't trust to luck that you could find the way to your destination without planning a route. So how would you expect to write a decent program if you just roll up your sleeves and start typing? Should you do this, on having typed in your program it's all too easy to try and bodge round the bits that don't behave rather than retrace your steps and admit that you made a wrong decision early on. If you didn't plan, there are no steps to retrace and everywhere you turn is quicksand. If you did plan, chances are with a bit of experience you would have recognised the problems with your approach and thrown it out before it ever reached the coding stage. As I previously stated, none of the ideas here are new or mine. I have no vested interest in pushing this method except that I was taught it early in my career and have used it in PASCAL,C/C++, and VB/VBA. Oh, and BBC BASIC too. So have hundreds of other programmers. Use it. It works. (End of rant.)

Exercises

1) I'm sure you can see lots of improvements here, try adding two more commands, one to completely clear the grid and one to fill it. You could use C and F to do this. Notice how it is easy to code modifications like this because the structure makes everything easy to find.
2) It's nice to be able to reverse engineer characters too, given the row totals. Modify the project to allow the user to enter a total for the row he's currently on. Then redisplay the character.

Left CONTENTS

CHAPTER 21 Right


Best viewed with Any Browser Valid HTML 3.2!
© Peter Nairn 2006