
A bat and a ball are a game of catch. Breakout is what the ball breaks: rows of bricks across the top of the court, each of which goes when the ball touches it, and a score that goes up. This stage adds them, and with them the two things the course has been aiming at since S15 and S25: a grid of char as the game's memory of what is on the screen, and characters of your own design so that a brick looks like one.
#define BRICKROWS 4
#define BRICKCOLS 18
#define FIRSTBRICKROW 2
char bricks[BRICKROWS][BRICKCOLS];
int score, remaining;
Seventy-two bricks, each two characters wide, in four rows starting on screen row 2 and running from PLAYLEFT to PLAYRIGHT, the same columns the ball can reach. bricks[r][c] is 1 while the brick is there and 0 once it has gone. The screen shows the bricks, but the program cannot read the screen back; the array is what it consults. remaining counts the bricks still up, so that the game knows when the wall is cleared without scanning the grid.
draw_bricks sets every entry to 1 and prints every brick, once - 144 characters, six-tenths of a second, at the start. After that the only brick ever drawn is one being removed.
void hit_brick(p) ball *p;
{
int r, c;
r = p->y - FIRSTBRICKROW;
c = (p->x - PLAYLEFT) / 2;
if (r < 0 || r >= BRICKROWS || c < 0 || c >= BRICKCOLS)
return;
if (bricks[r][c] == 0)
return;
bricks[r][c] = 0;
curat(PLAYLEFT + 2 * c, FIRSTBRICKROW + r);
putchar(' ');
putchar(' ');
p->dy = -p->dy;
score += 10;
remaining--;
draw_score();
}
The ball's row minus the first brick row is the brick row; the ball's column, less PLAYLEFT, halved, is the brick column - integer division, so columns 2 and 3 both give brick 0, and 4 and 5 give brick 1. If that is off the grid, or the brick there has already gone, nothing happens. Otherwise: the brick comes out of the array, two spaces go where it was, the ball's vertical direction reverses, and the score and count change. hit_brick runs every tick after move_ball, and most ticks it returns at the first or second if.
That is the whole of collision on a grid: not "is the ball touching anything?" but "what is at the square the ball is on?" - one lookup, because the array is laid out like the screen.
#define BALLCHAR 200
#define BRICKCHAR 201
void define_shapes()
{
shapedef(BALLCHAR, 0x00, 0x30, 0x78, 0xfc, 0xfc, 0x78, 0x30, 0x00);
shapedef(BRICKCHAR, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x00);
}
Two characters from the range S25 said was safe, redefined once at the start, and drawn in the six pixels the 40-column screen shows of each (S25): a round ball, and a brick that is solid but for one column and one row of gap, so that a row of them reads as bricks and not as a bar. Everything else in the program still prints them as characters, with putchar, at four milliseconds each; the shape is the video chip's business.
The first version of this stage declared its counters as int score, left; - the bricks left - and the compiler stopped at that line: ERROR 21 ... duplicate declaration - storage class mismatch. left is the bat's member (S27), and on this compiler a structure member's name is taken for the whole program: not just by other structures (S18) but by globals too. remaining it is. When a plain global gets that message, look at your structures.
BREAK3.C with the bricks added, saved as BREAK4.C. The new and changed parts:
#define BALLCHAR 200
#define BRICKCHAR 201
#define BRICKROWS 4
#define BRICKCOLS 18
#define FIRSTBRICKROW 2
bat paddle;
ball b;
char bricks[BRICKROWS][BRICKCOLS];
int score, remaining;
void define_shapes()
{
shapedef(BALLCHAR, 0x00, 0x30, 0x78, 0xfc, 0xfc, 0x78, 0x30, 0x00);
shapedef(BRICKCHAR, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x00);
}
void draw_bricks()
{
int r, c;
for (r = 0; r < BRICKROWS; r++)
for (c = 0; c < BRICKCOLS; c++) {
bricks[r][c] = 1;
curat(PLAYLEFT + 2 * c, FIRSTBRICKROW + r);
putchar(BRICKCHAR);
putchar(BRICKCHAR);
}
remaining = BRICKROWS * BRICKCOLS;
}
void draw_score()
{
curat(0, 22);
printf("Score: %d", score);
}
void hit_brick(p) ball *p;
{
int r, c;
r = p->y - FIRSTBRICKROW;
c = (p->x - PLAYLEFT) / 2;
if (r < 0 || r >= BRICKROWS || c < 0 || c >= BRICKCOLS)
return;
if (bricks[r][c] == 0)
return;
bricks[r][c] = 0;
curat(PLAYLEFT + 2 * c, FIRSTBRICKROW + r);
putchar(' ');
putchar(' ');
p->dy = -p->dy;
score += 10;
remaining--;
draw_score();
}
and main:
main()
{
char line[40];
int c;
paddle.width = 5;
paddle.left = 18;
b.x = 20;
b.y = BATROW - 1;
b.dx = 1;
b.dy = -1;
b.oldx = b.x;
b.oldy = b.y;
score = 0;
define_shapes();
draw_court();
draw_bricks();
draw_bat(&paddle);
draw_score();
curoff();
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);
}
curat(b.oldx, b.oldy);
putchar(' ');
curon();
curat(12, 22);
if (remaining == 0)
printf("Cleared! Press ENTER");
else
printf("Lost. Press ENTER");
gets(line);
cls40();
}
The whole file is BREAK4.C in Appendix III.
Starting from
BREAK3.C, edited as above; HC BREAK4.C; BREAK4.
What you should see
Four rows of bricks across the top of the court, the bat, and a round ball that sets off from the bat and, when it reaches a brick, takes it out with a gap two characters wide, turns downward, and the score at the bottom left goes to 10. Every brick it touches goes the same way. Miss the ball and it is Lost. Press ENTER; clear every brick and it is Cleared!.

p->dy = -p->dy; in hit_brick to p->dx = -p->dx;. What does the ball do at a brick, and does it still clear them?FIRSTBRICKROW to 10. What happens to the game, and what happens to the score when the ball is between the bricks and the bat?0xff and take the last 0x00 to 0xff as well. What do the rows look like?30.1 Score more for the higher rows: 40 for the top row down to 10 for the bottom.
30.2 Make bricks in the top row take two hits: keep a 2 in the array for them, draw them with a different character, and turn them into ordinary bricks on the first hit.
30.3 Print the number of bricks remaining after the score, and make it right.
Worked solutions are in Appendix II.
| Symptom | Cause |
|---|---|
duplicate declaration - storage class mismatch at int score, left; | left is the bat's member. Rename the global. |
| A brick vanishes when the ball is a column to one side of it | The column arithmetic is off: (x - PLAYLEFT) / 2, with the bricks from column PLAYLEFT. |
| Bricks vanish but the ball goes straight through | The bounce, p->dy = -p->dy, is missing from hit_brick. |
| The ball takes out two bricks at once, or none where there is one | hit_brick is called before move_ball, or twice. |
| The bricks look like a solid bar | The brick shape has no gap; see define_shapes. |
| The score row is overwritten by the message | Both are on row 22. S31 sorts the row out. |
char bricks[ROWS][COLS], drawn once; hit_brick finds the brick from the ball's position by arithmetic, and on a hit clears the entry, prints two spaces, reverses dy, scores, and counts down. Two shapedefs make the ball and the brick. A member's name cannot be a global's.
S31, Losing The Ball - three lives, a launch from the bat, game over, and playing again.