Commando Jump Game For The Micro:bit In C
Written by Mike James   
Thursday, 04 August 2016
Article Index
Commando Jump Game For The Micro:bit In C
Top Down Programming
Complete Listing

Starting The Program - Top Down

There is always a problem in getting started. A blank sheet of paper, or its digital equivalent a blank screen, is scary - for most people anyway. There is an old but still incredibly useful approach to programming, and many other things, called "top down programming". 

What you do is assume that you have already solved all of the problems in writing your program.

So, for example, in our game we can pretend that we have a function that starts the game, one that plays it and a function that ends it. 

That is:

startGame();
playGame();
endGame();

We have a start.

If you add some "stubs", i.e. functions that don't do anything, we can even run the program.

Again you may not be too impressed, but we have a program that we can compile run and test. You have a working program and you only just started. This is the joy of top down programming.

 

The idea is that we have a start and an overall structure for the program. We can now start to develop each of the stubs as a separate problem. 

Of course you now have to fill out the definitions of each of the functions to actually do something, but this isn't unreasonable. Another advantage is that you can fill in one of the stubs and still run and test the program while the other stubs remain stubs.

This is called stepwise refinement. 

Refining the startGame function

The easiest of the functions to refine is startGame. Let's add some code to show the player a short countdown to the start of the game and then display the wall which constitutes the playing area of the game:

void startGame() {
 countDown();
 drawPlayArea();
}

This may at first seem like procrastination of the worst sort, but as your arithmetic teacher should have told you:

"put off doing any working out as long as possible". 

This is equally true of top down stepwise refinement. We might well have to come back and add to the early functions we create so keep them simple at first.

Now we do need to write some code to create the functions - there does come a time when you can't split something down into yet more functions.

The countDown function is simply: 

void countDown() {
 int t;
 for (t = 5; t >= 0; t--) {
  uBit.display.scroll(t);
 }
 uBit.display.scroll("!");
}

This uses a for loop with a negative increment to count down from 5 to 0 and then shows an exclamation mark to indicate the start. Of course this is assuming that we have created the uBit object at the start of the program. So we need to add:

#include "MicroBit.h"
MicroBit uBit;

Programming is like this - you have to go back and make things right for things you just programmed. 

The drawPlayArea function is just as easy:

void drawPlayArea() {
 uBit.display.clear();
 int y;
 for (y = 1; y <= 5; y++) {
  uBit.display.image.setPixelValue(2, y, 255);
 }
}

This displays a "wall" of full on LED in the middle of the screen x=2 and y runs from 1 to 4. 

Notice that both of these functions are simple and this is another advantage of top down programming. It means you only ever have to deal with small functions that do simple things and not a huge program made up of lots of lines of code. 

However notice that we are using LEDs of intermediate brightness, i.e. 0 to 255, and this means we are assuming "greyscale" mode for the display. This has to be added to the startGame function:

void startGame() {
 countDown();
 uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE);
 drawPlayArea();
}

The playGame function

The playGame function is obviously going to the the most complex of the set. Again we try to simplify it by using other functions.

We need to put a time limit on how long the player has and we need to keep track of the commando's vertical position. We are also going to be counting the number of times the player presses button B. The commando, which is just a single LED and so appears as a "block", is going to move up one place for every ten presses.

The first problem we need to solve is how to keep track of the number of times the button has been pressed, The simplest thing to do is to use an event handler. This simply adds one to a global variable each time the user clicks the button. To set this up we need another function:

void setUpButton() {
uBit.buttonB.setEventConfiguration(
                MICROBIT_BUTTON_ALL_EVENTS);
uBit.messageBus.listen(MICROBIT_ID_BUTTON_B,
    MICROBIT_BUTTON_EVT_CLICK, buttonClickCount);
}

This sets ButtonB to respond to all events and calls buttonClickCount when the user clicks. This in turn simply adds one to clickCount:

void buttonClickCount(MicroBitEvent e) {
 clickCount++;
}

We also need to define clickCount and zero it:

int clickCount = 0;

 

The playGame function starts:

int playGame() {
int position = 4;
clickCount = 0;
int t1 = uBit.systemTime();

The variable t1 holds the time at which the player starts to play.  Notice that the playGame function returns an int where originally it didn't return anything. It turns out that for the endgame function to know what to do the playGame function has to return the final position of the man - so we have to change our first attempt at the function. 

Next we start a while loop that runs until the time is up. The logic is that we display the commando's position and read the number of times button A has been pressed. If the count gets to 10 then move the command up one. 

Given we have only five LEDs to switch on and off, the steps that the command takes are going to seem big. However the LEDs have 10 brightness levels. Why not make the commando move more smoothly by dimming one LED by one level and brightening the one above by a level for every press. This would make the commando seem to slide between LEDs with each button press.

This is more complicated than making a sudden jump after 10 presses, but the effect is worth it. The commando seems to move smoothly up the wall and it gives the player more feedback on what is happening.

To do this we need to implement the flashMan function:

void flashMan(int x, int y, int p, int vx, int vy) {
 uBit.display.image.setPixelValue(x, y, 0);
 uBit.display.image.setPixelValue(x + vx, y + vy, 0);
 uBit.sleep(100);
 uBit.display.image.setPixelValue(x, y, 25 * (10 - p));
 uBit.display.image.setPixelValue(x+vx, y+vy, 25 * p);
 uBit.sleep(100);
}

The flashMan function sets the LEDs at x,y and x+vx and y+vy off. It waits 100 milliseconds and then puts the same LEDs on, but the one at x,y is set to 25*(10-p) and the one at x+vx,y+vy is set to 25*p. You can see that p is the proportion that the brightness is split between the two LEDs.

For example suppose p is 0 then the first LED is set to 10*25 i.e. 250 and the second to 0. If p is 3 then the first is set to 7*25 and the second to 3*25. Finally is p is 10 the first is set to 0 and the second to 10*25. As p varies from 0 to 10 the position of the brightness moves from the first to the second LED.

What are vx and vy?

They simply set where the second LED is with respect to the first corresponding to the direction that the commando is heading in.  If vx=0 and vy=-1 the commando is going up, remember the LEDs are 0 at the top and 5 at the bottom. 

Using this function we can now write the while loop:

while (uBit.systemTime() - t1 < 20000) {
 flashMan(4, position, clickCount, 0, -1);
 if (clickCount > 10) {
  position = position - 1;
  clickCount = 0;
  if (position == 0)break;
 }
}

The condition in the while loop means that it stops after 20,000 milliseconds or 20 seconds. You will need to reduce this after you finish testing to something more challenging. Say 5000 or 5 seconds. 

The first thing we do is flash the command at the current position. Notice that vx and vy are set to move up and that the proportion of brightness is set by clickCount - the number of times the button had been pressed. 

Next we update press and if it is 10 or greater we need to move the commando up one complete LED. We also need to reset the button count ready for the next ten presses. Finally we check to see if the commando has reached the top of the wall. If so the break stops the while loop.

Finally the function returns the commandos position when the while loop ends - why? The simple reason is that other functions might, and do, need to know how the game ended and this is signaled by the commando's position - at the top of the wall (position==0) or not. 

The complete function is:

int playGame() {
 int position = 4;
 clickCount = 0;
 int t1 = uBit.systemTime();

 while (uBit.systemTime() - t1 < 20000) {
  flashMan(4, position, clickCount, 0, -1);
  if (clickCount > 10) {
   position = position - 1;
   clickCount = 0;
   if (position == 0)break;
  }
 }
 return position;
}

You can see that the logic is complicated but it isn't easy to see how it could be simplified.



Last Updated ( Friday, 19 August 2022 )