Breakout, the game the course builds in HiSoft C, running on the Tatung Einstein: coloured brick rows, the bat and the ball on a black court
← Back to Courses
module
31

Losing The Ball

Introduction

Lose the ball in S30's game and it is over. Real Breakout gives you three balls, and between them a moment to breathe: the ball sits on the bat until you launch it. This stage adds that, and in doing so gives the game its final shape - a loop for the lives around the loop for the ticks - and takes over the bottom row properly, because the score, the lives and the messages all want it.

Two Loops

while (lives > 0 && remaining > 0) {
    place_ball(&b, &paddle);
    wait_launch();
    while (remaining > 0) {
        ... one tick ...
        move_ball(&b, &paddle);
        if (b.y >= BATROW)
            break;
        ...
    }
    if (b.y >= BATROW) {
        curat(b.oldx, b.oldy);
        putchar(' ');
        lives--;
        draw_score();
    }
}

The inner loop is S30's tick loop. The outer one runs once per ball: put the ball on the bat, wait for the launch, play until the ball is lost or the bricks are gone, and if it was lost, take it off the screen, take a life, and go round. The outer loop ends when there are no lives, or no bricks; main says which afterwards.

break leaves the inner loop only, which is what is wanted. Notice that b.y >= BATROW is tested twice: once to stop the tick loop, and once after it to tell a lost ball from a cleared wall, since both end the inner loop.

Placing And Launching

void place_ball(p, q) ball *p; bat *q;
{
    p->x = q->left + q->width / 2;
    p->y = BATROW - 1;
    p->oldx = p->x;
    p->oldy = p->y;
    p->dx = 1;
    p->dy = -1;
    curat(p->x, p->y);
    putchar(BALLCHAR);
}

void wait_launch()
{
    message("SPACE to launch");
    while (kbd() != LAUNCHKEY)
        ;
    draw_score();
}

The ball goes on the middle of the bat, wherever the bat is, pointing up and right, and is drawn there. Then the game waits for SPACE: a kbd() loop, because rawin() would take the key and this program's habit is to leave keys for the gets at the end. The player cannot move the bat while waiting, which is a simplification exercise 31.2 removes.

The Status Row

Row 22 now has three tenants: the score, the lives, and the messages. They take turns. draw_score prints the score and lives; message prints a message over them; draw_score again prints the score back over the message. For that to work, whichever is printed must be at least as long as whatever it covers, and neither may run to the edge of the screen - a character printed in the last column of the last row is asking MOS to scroll. So draw_score prints the score with %4d and a run of spaces, thirty-two characters every time, and message pads what it prints with spaces up to the same width:

void message(s) char *s;
{
    int i;
    curat(0, 22);
    printf("%s", s);
    for (i = strlen(s); i < 32; i++)
        putchar(' ');
}

Without the padding, the first run of this stage showed SPACE to launchs - the message and the tail of the score line it had not covered. Thirty-two characters is 130 milliseconds, which is nothing between balls and would be a great deal every tick; the score is redrawn only when it changes.

The Code

BREAK4.C with these changes, saved as BREAK5.C:

#define LIVES 3
#define LAUNCHKEY ' '
int score, remaining, lives;
void draw_score()
{
    curat(0, 22);
    printf("Score: %4d  Lives: %d           ", score, lives);
}

void message(s) char *s;
{
    int i;
    curat(0, 22);
    printf("%s", s);
    for (i = strlen(s); i < 32; i++)
        putchar(' ');
}

void place_ball(p, q) ball *p; bat *q;
{
    p->x = q->left + q->width / 2;
    p->y = BATROW - 1;
    p->oldx = p->x;
    p->oldy = p->y;
    p->dx = 1;
    p->dy = -1;
    curat(p->x, p->y);
    putchar(BALLCHAR);
}

void wait_launch()
{
    message("SPACE to launch");
    while (kbd() != LAUNCHKEY)
        ;
    draw_score();
}

and main:

main()
{
    char line[40];
    int c;
    paddle.width = 5;
    paddle.left = 18;
    score = 0;
    lives = LIVES;
    define_shapes();
    draw_court();
    draw_bricks();
    draw_bat(&paddle);
    draw_score();
    curoff();
    while (lives > 0 && remaining > 0) {
        place_ball(&b, &paddle);
        wait_launch();
        while (remaining > 0) {
            c = kbd();
            if (c == LEFTKEY)
                move_bat(&paddle, -1);
            if (c == RIGHTKEY)
                move_bat(&paddle, 1);
            move_ball(&b, &paddle);
            if (b.y >= BATROW)
                break;
            hit_brick(&b);
            draw_ball(&b);
            wait_frames(TICK);
        }
        if (b.y >= BATROW) {
            curat(b.oldx, b.oldy);
            putchar(' ');
            lives--;
            draw_score();
        }
    }
    curon();
    if (remaining == 0)
        message("Cleared!  Press ENTER");
    else
        message("Game over.  Press ENTER");
    gets(line);
    cls40();
}

The ball's starting position has gone from main; place_ball sets it. The whole file is BREAK5.C in Appendix III.

Starting from

BREAK4.C, edited as above; HC BREAK5.C; BREAK5.

What you should see

The court, bricks and bat, the ball sitting on the bat, and SPACE to launch on the bottom row. SPACE, and it is S30's game with Score: 0 Lives: 3 where the message was. Lose the ball and it reappears on the bat with Lives: 2 and the message; lose three and Game over. Press ENTER. ENTER, and the prompt.

Waiting to launch

Change One Thing

  • Change LIVES to 1. Which loop notices?
  • Take the for loop out of message. Launch, lose a ball, and read the bottom row.
  • Change wait_launch's while (kbd() != LAUNCHKEY) ; to rawin();. What happens at the prompt after the game?

Exercises

31.1 Print the lives as a row of ball characters instead of a number.

31.2 Let the bat move while the game waits for the launch, with the ball moving along with it.

31.3 Add a #define BONUS 1000 and award an extra life when the score passes it, once.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
Letters left over after the message on the bottom rowmessage or draw_score is shorter than what it covers. Pad to 32.
The screen scrolls when the score is printedThe row is 40 wide and something reached column 39. Keep to 32.
The ball launches at once, without SPACELAUNCHKEY is not ' ', or the wait loop is ==.
The lost ball stays on the screenThe rub-out after the inner loop is missing.
The game ends after one ballbreak is inside the outer loop's braces, or lives is not tested.
Keys at the prompt afterwardsA rawin() replaced by kbd() without the gets at the end.

Summary

An outer loop per ball around the inner loop per tick; break leaves the inner one; b.y >= BATROW tells lost from cleared. place_ball and wait_launch between balls; a status row that is always exactly thirty-two characters, whoever last wrote it.

Next

S32, Score And Speed - the game gets harder: a level counter, the ball speeding up as the wall comes down, and a fresh wall when it is cleared.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.