~smlavine/ascii-breakout

a0f2a19acf19e757d72c47639c23c23fdfd01b14 — Sebastian LaVine 8 months ago 60944e7
Replace //-style comments with /* */-style
1 files changed, 227 insertions(+), 157 deletions(-)

M main.c
M main.c => main.c +227 -157
@@ 25,56 25,73 @@

#include "rogueutil.h"

// Store data about the ball, including location and velocity.
/*
 * Store data about the ball, including location and velocity.
 */
typedef struct {
	// Coordinates of the ball in board.
	/* Coordinates of the ball in board. */
	int x;
	int y;
	// How many frames the ball moves in each axis. For example, if xVelocity
	// was 1, then the ball would move on the x axis every frame. However, if
	// xVelocity was 3, then the ball would move on the x axis every 3 frames.

	/* How many frames the ball moves in each axis. For example, if
	 * xVelocity was 1, then the ball would move on the x axis every frame.
	 * However, if xVelocity was 3, then the ball would move on the x axis
	 * every 3 frames. */
	int xVelocity;
	int yVelocity;
	// How many tiles the ball moves on each axis, on a frame where it moves in
	// that axis.
	int xDirection; // negative is left, positive is right.
	int yDirection; // negative is up, positive is down.

	/* How many tiles the ball moves on each axis, on a frame where it
	 * moves in that axis. */
	int xDirection; /* negative is left, positive is right. */
	int yDirection; /* negative is up, positive is down. */
} Ball;

// Store data about the paddle, including location and direction.
/*
 * Store data about the paddle, including location and direction.
 */
typedef struct {
	// Coordinates (in board) of the left-most character in the paddle.
	/* Coordinates (in board) of the left-most character in the paddle. */
	int x;
	int y;
	// Length of the paddle.

	/* Length of the paddle. */
	int len;
	// Direction the paddle is moving - negative for left, positive for right.

	/* Direction the paddle is moving - negative for left, positive for
	 * right. */
	int direction;
	// The last direction the paddle was moving before it was frozen.

	/* The last direction the paddle was moving before it was frozen. */
	int lastDirection;
	// used to control speed of the paddle

	/* used to control speed of the paddle */
	int velocity;
} Paddle;

// Stores data about each tile (character space) on the board, namely, what it
// represents.
/*
 * Stores data about each tile (character space) on the board, namely, what it
 * represents.
 */
typedef enum {
	BALL = 'O',
	PADDLE = MAGENTA,
	RED_BLOCK = RED,
	BLUE_BLOCK = BLUE,
	GREEN_BLOCK = GREEN,
	EMPTY = 0, // A tile not occupied by the ball, paddle, or a block
	EMPTY = 0, /* A tile not occupied by the ball, paddle, or a block. */
} Tile;


// Dimensions of the play field.
// I used #define instead of const int for these so that I can declare board
// (see below) at file scope.
/*
 * Dimensions of the play field. I used #define instead of const int for these
 * so that I can declare board (see below) at file scope.
 */
#define WIDTH 60
#define HEIGHT 36

// Strings for the footer at the bottom of the game board.
/*
 * Strings for the footer at the bottom of the game board.
 */
const char *TITLE = "ASCII BREAKOUT";
const char *LIVES_FOOTER = "<3:";
const char *LEVEL_FOOTER = "Level:";


@@ 83,10 100,14 @@ const int INBETWEEN = 5;
const int FOOTER_XPOS = 4;
const int FOOTER_YPOS = HEIGHT + 2;

// The amount of lives the player starts out with at the beginning of the game.
/*
 * The amount of lives the player starts out with at the beginning of the game.
 */
const int STARTING_LIVES = 5;

// 2D array representing the play field. Randomly generated on each level.
/*
 * 2D array representing the play field. Randomly generated on each level.
 */
Tile board[WIDTH][HEIGHT];

void bar(int x, int y, int len, char c);


@@ 110,7 131,9 @@ void updateLives(int *lives);
void updateScore(unsigned int *score);
void updateTile(int x, int y);

// Draws a horizontal bar across the screen.
/*
 * Draws a horizontal bar across the screen.
 */
void
bar(int x, int y, int len, char c)
{


@@ 120,13 143,15 @@ bar(int x, int y, int len, char c)
	}
}

// Checks to see if the ball should move this frame. If it should, then
// moveBall will be called. Also handles collision and bouncing. Returns 0 if
// the ball reaches the bottom of the play field, otherwise returns 1.
/*
 * Checks to see if the ball should move this frame. If it should, then
 * moveBall will be called. Also handles collision and bouncing. Returns 0 if
 * the ball reaches the bottom of the play field, otherwise returns 1.
 */
int
checkBall(Ball *ball, int *blocksLeft, unsigned int *score, unsigned int frame)
{
	// The new coordinates of the ball, if it moves successfully.
	/* The new coordinates of the ball, if it moves successfully. */
	int nextX = (*ball).x,
		nextY = (*ball).y;



@@ 138,44 163,44 @@ checkBall(Ball *ball, int *blocksLeft, unsigned int *score, unsigned int frame)
		nextY += (*ball).yDirection;
	}

	// Don't do anything if the ball didn't change position.
	/* Don't do anything if the ball didn't change position. */
	if (nextX == (*ball).x && nextY == (*ball).y) {
		return 1;
	}

	// The ball has hit the bottom of the game field.
	/* The ball has hit the bottom of the game field. */
	if (nextY >= HEIGHT) {
		return 0;
	}

	// if the incoming tile is valid and empty, move there
	/* if the incoming tile is valid and empty, move there */
	if (nextX >= 0 && nextX < WIDTH && nextY >= 0
			&& board[nextX][nextY] == EMPTY) {
		moveBall(ball, nextX, nextY);
	// otherwise, bounce!
	// if stuck in a corner, invert both directions
	/* otherwise, bounce! */
	/* if stuck in a corner, invert both directions */
	} else if (nextY == 0 && (nextX == 0 || nextX == WIDTH - 1)) {
		(*ball).xDirection = -(*ball).xDirection;
		(*ball).yDirection = -(*ball).yDirection;
	// bounce off the side walls
	/* bounce off the side walls */
	} else if (nextX <= 0 || nextX >= WIDTH) { 
		(*ball).xDirection = -(*ball).xDirection;
	// bounce off the ceiling
	/* bounce off the ceiling */
	} else if (nextY <= 0) {
		(*ball).yDirection = -(*ball).yDirection;
	// bounce off paddle
	/* bounce off paddle */
	} else if (board[nextX][nextY] == PADDLE) {
		// If yDirection is not inverted here, then the ball will just roll
		// about on the paddle for a little bit, which is actually kindof fun.
		// Try it out if you're bored.
		/* If yDirection is not inverted here, then the ball will just
		 * roll about on the paddle for a little bit, which is actually
		 * kindof fun. Try it out if you're bored. */
		(*ball).yDirection = -(*ball).yDirection;
		// randomize bounce a bit
		/* randomize bounce a bit */
		if (rand() % 2 == 0)
			(*ball).xDirection = -(*ball).xDirection;
		// randomize velocity
		/* randomize velocity */
		(*ball).xVelocity = (rand() % 8) + 5;
		(*ball).yVelocity = (rand() % 8) + 5;
	// bounce off (and destroy) block
	/* bounce off (and destroy) block */
	} else { 
		destroyBlock(nextX, nextY, blocksLeft, score);
		if (rand() % 2 == 0)


@@ 184,52 209,59 @@ checkBall(Ball *ball, int *blocksLeft, unsigned int *score, unsigned int frame)
			(*ball).yDirection = -(*ball).yDirection;
	}

	// Indicates that the ball did not hit the bottom of the play field.
	/* Indicates that the ball did not hit the bottom of the play field. */
	return 1;
}

// Intercepts signals, particularly ^C SIGINT.
/*
 * Intercepts signals, particularly ^C SIGINT.
 */
void
cleanup(int sig)
{
	// Clean up the modifications made to the terminal settings before
	// quitting the program.
	/* Clean up the modifications made to the terminal settings before
	 * quitting the program. */
	setCursorVisibility(1);
	resetColor();
	locate(1, HEIGHT + 3);
}

// Destroys a block at board[x][y], and replaces it with EMPTY. Intended to
// be called when the ball bounces into a block.
/*
 * Destroys a block at board[x][y], and replaces it with EMPTY. Intended to be
 * called when the ball bounces into a block.
 */
void
destroyBlock(int x, int y, int *blocksLeft, unsigned int *score)
{
	// Blocks are generated in groups of two, which means that if one block
	// tile is hit, then one of its neighbors is also going to be destroyed.
	// Because of the way the board is generated, the first tile in a block is
	// always odd. We can use this fact to determine which tile of the block
	// the ball hit: the first or the second. If the x value is odd, then the
	// ball hit the first; if it is even, the the ball hit the second. This
	// is interpreted as an offset to the x value which, when added to the x
	// value, will give us the coordinate of the second tile in the block.
	/* Blocks are generated in groups of two, which means that if one block
	 * tile is hit, then one of its neighbors is also going to be
	 * destroyed. Because of the way the board is generated, the first tile
	 * in a block is always odd. We can use this fact to determine which
	 * tile of the block the ball hit: the first or the second. If the x
	 * value is odd, then the ball hit the first; if it is even, the the
	 * ball hit the second. This is interpreted as an offset to the x value
	 * which, when added to the x value, will give us the coordinate of the
	 * second tile in the block. */
	int offset = (x % 2 == 1) ? 1 : -1;
	board[x][y] = EMPTY;
	updateTile(x, y);
	board[x + offset][y] = EMPTY;
	updateTile(x + offset, y);
	// Remove a block from the total.
	/* Remove a block from the total. */
	(*blocksLeft)--;
	// Give the player points for destroying a block.
	/* Give the player points for destroying a block. */
	*score += 10;
	updateScore(score);
}

// Draws at (x, y) [on the terminal window] the proper value depending on the
// tile, including the proper color. Does not reset colors after usage.
/*
 * Draws at (x, y) [on the terminal window] the proper value depending on the
 * tile, including the proper color. Does not reset colors after usage.
 */
void
drawTile(int x, int y, Tile t)
{
	// alternates the character drawn for blocks
	/* alternates the character drawn for blocks */
	static int alternateBlockChar = 1;
	resetColor();
	locate(x, y);


@@ 246,7 278,8 @@ drawTile(int x, int y, Tile t)
	case GREEN_BLOCK:
		setBackgroundColor(t);
		setColor(BLACK);
		// This helps show the player that blocks are two characters wide.
		/* This helps show the player that blocks are two characters
		 * wide. */
		setChar(alternateBlockChar ? '(' : ')');
		alternateBlockChar = !alternateBlockChar;
		break;


@@ 257,24 290,27 @@ drawTile(int x, int y, Tile t)
	}
}

// Generates a starting game board. Returns the amount of blocks it generated
// in the level.
/*
 * Generates a starting game board. Returns the amount of blocks it generated
 * in the level.
 */
int
generateBoard(const int level, const int maxBlockY, Paddle paddle, Ball ball)
{
	// Initializes the board to be empty
	/* Initializes the board to be empty */
	memset(board, EMPTY, WIDTH * HEIGHT * sizeof(Tile));
	// Create the paddle.
	/* Create the paddle. */
	for (int i = 0; i < paddle.len; i++) {
		board[paddle.x + i][paddle.y] = PADDLE;
	}
	// Create the ball.
	/* Create the ball. */
	board[ball.x][ball.y] = BALL;

	int blocks = 0;
	// Fills in a section of the board with breakable blocks.
	/* Fills in a section of the board with breakable blocks. */
	for (int i = 3; i < WIDTH - 3; i += 2) {
		// maxBlockY is the lowest distance the blocks can be generated.
		/* maxBlockY is the lowest distance the blocks can be
		 * generated. */
		for (int j = 3; j < maxBlockY; j++) {
			blocks++;
			switch (rand() % 3) {


@@ 294,43 330,45 @@ generateBoard(const int level, const int maxBlockY, Paddle paddle, Ball ball)
			}
		}
	}
	// Formula for this should be (WIDTH - 6)(maxBlockY - 3) / 2, but I'm
	// counting it manually here just to be sure.
	/* Formula for this should be (WIDTH - 6)(maxBlockY - 3) / 2, but I'm
	 * counting it manually here just to be sure. */
	return blocks; 
}

// Draws initial graphics for the game. This includes a box around the playing
// field, the score, the paddle, the blocks, etc.
/*
 * Draws initial graphics for the game. This includes a box around the playing
 * field, the score, the paddle, the blocks, etc.
 */
void
initializeGraphics(int level, unsigned int score, int lives)
{
	cls();
	// Draws a box around the game field.
	/* Draws a box around the game field. */
	setColor(GREEN);
	bar(2, 1, WIDTH, '_'); // Top bar
	for (int y = 2; y < HEIGHT + 2; y++) { // Sides of the game field
	bar(2, 1, WIDTH, '_'); /* Top bar */
	for (int y = 2; y < HEIGHT + 2; y++) { /* Sides of the game field */
		locate(1, y);
		putchar('{');
		locate(WIDTH + 2, y);
		putchar('}');
	}
	printf("\n{");
	bar(2, HEIGHT + 2, WIDTH, '_'); // Bottom bar
	bar(2, HEIGHT + 2, WIDTH, '_'); /* Bottom bar */
	putchar('}');
	// Prints footer information.
	// title
	/* Prints footer information. */
	/* title */
	locate(FOOTER_XPOS, FOOTER_YPOS);
	setColor(CYAN);
	printf("%s", TITLE);
	// lives
	/* lives */
	updateLives(&lives);
	// level
	/* level */
	updateLevel(&level);
	// score
	/* score */
	updateScore(&score);
	// Draws the board tiles.
	// i and j refer to y and x so that blocks are drawn in rows, not columns.
	// This makes it easier to produce the two-character wide block effect.
	/* Draws the board tiles. i and j refer to y and x so that blocks are
	 * drawn in rows, not columns. This makes it easier to produce the
	 * two-character wide block effect. */
	for (int i = 0; i < HEIGHT; i++) {
		for (int j = 0; j < WIDTH; j++) {
			drawTile(j + 2, i + 2, board[j][i]);


@@ 339,21 377,27 @@ initializeGraphics(int level, unsigned int score, int lives)
	fflush(stdout);
}

// Returns the maximum of two values.
/*
 * Returns the maximum of two values.
 */
int
max(int a, int b)
{
	return a > b ? a : b;
}

// Returns the minimum of two values.
/*
 * Returns the minimum of two values.
 */
int
min(int a, int b)
{
	return a < b ? a : b;
}

// Moves the ball to board[x][y].
/*
 * Moves the ball to board[x][y].
 */
void
moveBall(Ball *ball, int x, int y)
{


@@ 365,14 409,18 @@ moveBall(Ball *ball, int x, int y)
	updateTile(x, y);
}

// Move the paddle according to its direction. 
/*
 * Move the paddle according to its direction.
 */
void
movePaddle(Paddle *paddle)
{
	// The x-coordinate (in board) of which tiles are going to be changed.
	/* The x-coordinate (in board) of which tiles are going to be changed.
	 */

	int newPaddleX, newEmptyX; 

	// if paddle is moving left
	/* if paddle is moving left */
	if ((*paddle).direction < 0 && (*paddle).x + (*paddle).direction >= 0) {
		for (int i = 0; i > (*paddle).direction; i--) {
			newPaddleX = (*paddle).x - 1;


@@ 383,7 431,7 @@ movePaddle(Paddle *paddle)
			updateTile(newEmptyX, (*paddle).y);
			(*paddle).x--;
		}
	// if paddle is moving right
	/* if paddle is moving right */
	} else if ((*paddle).direction > 0
			&& (*paddle).x + (*paddle).len + (*paddle).direction <= WIDTH) {
		for (int i = 0; i < (*paddle).direction; i++) {


@@ 399,19 447,20 @@ movePaddle(Paddle *paddle)
	fflush(stdout);
}

// Plays a level of the game. Returns the amount of lives remaining at the
// completion of the level, or 0 if the player runs out of lives.
/*
 * Plays a level of the game. Returns the amount of lives remaining at the
 * completion of the level, or 0 if the player runs out of lives.
 */
int
play(int level, unsigned int *score, int *lives)
{

	// The height of the blocks (how far down on the play field they generate)
	// increases as the levels progress, capping at five-sixths of the height
	// of the board.
	/* The height of the blocks (how far down on the play field they
	 * generate) increases as the levels progress, capping at five-sixths
	 * of the height of the board. */
	const int maxBlockY = (HEIGHT / 3) + min(level / 2, HEIGHT / 2);

	Paddle paddle;
	// The paddle gets shorter as the game goes on.
	/* The paddle gets shorter as the game goes on. */
	paddle.len = max(20 - (2 * (level / 3)), 10);
	paddle.x = (WIDTH - paddle.len) / 2;
	paddle.y = (11 * HEIGHT) / 12;


@@ 423,27 472,29 @@ play(int level, unsigned int *score, int *lives)
	ball.x = WIDTH / 2;
	ball.y = (maxBlockY + paddle.y) / 2;

	// Give the player some extra lives every once in a while, to be nice.
	/* Give the player some extra lives every once in a while, to be nice.
	 */
	if (level <= 1) {
		// But not on the first level, since the player starts out with some.
		/* But not on the first level, since the player starts out with
		 * some. */
		;
	} else if (level < 10) {
		*lives += 2;
	} else if (level < 20) {
		(*lives)++;
	// Gives out a life every two levels now.
	/* Gives out a life every two levels now. */
	} else if (level % 2 == 0 && level < 40) {
		(*lives)++;
	// And now every four, until level 60, then the handouts end.
	/* And now every four, until level 60, then the handouts end. */
	} else if (level % 4 == 0 && level < 60) {
		(*lives)++;
	}

	// Generates a new board for this level.
	/* Generates a new board for this level. */
	int blocksLeft = generateBoard(level, maxBlockY, paddle, ball);

	// A message is printed at the screen at the start of each level/life. It
	// is slightly different if you are not on level 1.
	/* A message is printed at the screen at the start of each level/life.
	 * It is slightly different if you are not on level 1. */
	char *message;
	if (level == 1) {
		message =


@@ 458,17 509,18 @@ play(int level, unsigned int *score, int *lives)
			"Press any key to continue";
	}

	// This is the life loop. In this loop, one life is played out. It can loop
	// many times within one call of play() (a level).
	/* This is the life loop. In this loop, one life is played out. It can
	 * loop many times within one call of play() (a level). */
	while (*lives > 0) {
		// Counts how many frames of gameplay have taken place so far.
		/* Counts how many frames of gameplay have taken place so far.
		 */
		unsigned int frame = 0;

		// when the game is paused, the ball freezes and gameplay-related input
		// is frozen.
		/* when the game is paused, the ball freezes and
		 * gameplay-related input is frozen. */
		int isPaused = 0;

		// The ball resets at the start of each life.
		/* The ball resets at the start of each life. */
		ball.x = WIDTH / 2;
		ball.y = (maxBlockY + paddle.y) / 2;
		ball.xVelocity = rand() % 10 + 6;


@@ 476,11 528,12 @@ play(int level, unsigned int *score, int *lives)
		ball.xDirection = rand() % 2 == 0 ? 1 : -1;
		ball.yDirection = -1;

		// The paddle recenters itself and resets at the start of each life.
		/* The paddle recenters itself and resets at the start of each
		 * life. */
		paddle.x = (WIDTH - paddle.len) / 2;
		paddle.direction = 0;
		paddle.lastDirection = 0;
		// Update paddle tile graphics.
		/* Update paddle tile graphics. */
		for (int i = 0; i < WIDTH; i++) {
			board[i][paddle.y] = EMPTY;
		}


@@ 488,50 541,51 @@ play(int level, unsigned int *score, int *lives)
			board[paddle.x + i][paddle.y] = PADDLE;
		}

		// Draws initial graphics for the board.
		/* Draws initial graphics for the board. */
		initializeGraphics(level, *score, *lives);

		showMessage(message, level, *lives);
		anykey(NULL);
		// Redraw initial graphics to make the message go away.
		/* Redraw initial graphics to make the message go away. */
		initializeGraphics(level, *score, *lives);

		// This is the main game loop. Input is interpreted, tiles move, etc.
		/* This is the main game loop. Input is interpreted, tiles
		 * move, etc. */
		for (;;) {
			// Controls the speed of the game; speed of the ball and the
			// paddle, mainly. Changing this value will also require changing
			// the various velocities of the ball and paddle for gameplay to
			// remain smooth.
			/* Controls the speed of the game; speed of the ball
			 * and the paddle, mainly. Changing this value will
			 * also require changing the various velocities of the
			 * ball and paddle for gameplay to remain smooth. */
			int sleepLength = 5;
			msleep(sleepLength);
			frame++;

			// There is no default case because I want the paddle to continue
			// to move even if there is no input.
			/* There is no default case because I want the paddle
			 * to continue to move even if there is no input. */
			switch (nb_getch()) {
			case 'p':
			case 'P':
				isPaused = !isPaused;
				break;
			case 'j': // move the paddle left
			case 'j': /* move the paddle left */
			case 'J':
				if (!isPaused) {
					paddle.direction = -1;
					paddle.lastDirection = 0;
				}
				break;
			case 'k': // move the paddle right
			case 'k': /* move the paddle right */
			case 'K':
				if (!isPaused) {
					paddle.direction = 1;
					paddle.lastDirection = 0;
				}
				break;
			case 'q': // quits the game.
			case 'q': /* quits the game. */
			case 'Q':
				return 0;
				break;
			case 'r': // redraw the screen. doesn't control the paddle.
			case 'r': /* redraw the screen. doesn't control the paddle. */
			case 'R':
				initializeGraphics(level, *score, *lives);
				break;


@@ 542,50 596,55 @@ play(int level, unsigned int *score, int *lives)
				movePaddle(&paddle);
			}

			// I move the cursor out of the way so that inputs that are not
			// caught by nb_getch) are not in the way of the play field.
			/* I move the cursor out of the way so that inputs that
			 * are not caught by nb_getch) are not in the way of
			 * the play field. */
			locate(WIDTH + 3, HEIGHT + 3);
			// Block the input characters from showing.
			/* Block the input characters from showing. */
			setString("  ");
			fflush(stdout);

			if (!isPaused && !checkBall(&ball, &blocksLeft, score, frame)) {
				(*lives)--;
				break; // breaks out of input loop
				break; /* breaks out of input loop */
			}

			// If there are no blocks remaining, then the player has won and
			// moves on to the next level.
			/* If there are no blocks remaining, then the player
			 * has won and moves on to the next level. */
			if (blocksLeft == 0) {
				return 1;
			}

		} // for (;;) (input loop)
		} /* for (;;) (input loop) */

		// if the player has ran out of lives, then the game is over.
		/* if the player has ran out of lives, then the game is over.
		 */
		if (*lives <= 0) {
			return 0;
		}
	} // life loop
	} /* life loop */

	return 1; // indicates that the player has not ran out of lives
	return 1; /* indicates that the player has not ran out of lives */
}

// Prints the message in the arguments, formatting it similar to printf,
// however only accepting/recognizing "%d", "%s", and "%c" as valid arguments.
// The lines will be properly centered on the screen.
/*
 * Prints the message in the arguments, formatting it similar to printf,
 * however only accepting/recognizing "%d", "%s", and "%c" as valid arguments.
 * The lines will be properly centered on the screen.
 */
void
showMessage(char *fmt, ...)
{
	// Copy the fmt string into an array so that strtok() can work with it.
	/* Copy the fmt string into an array so that strtok() can work with it.
	 */
	char fmtCopy[strlen(fmt) + 1];
	strcpy(fmtCopy, fmt);

	// This will hold the (formatted) text that will be printed on each line
	// before it is printed.
	/* This will hold the (formatted) text that will be printed on each
	 * line before it is printed. */
	char line[WIDTH + 1];

	// Variables to handle the format arguments
	/* Variables to handle the format arguments */
	int n;
	char *s;
	va_list ap;


@@ 596,18 655,21 @@ showMessage(char *fmt, ...)
			lineNumber++, token = strtok(NULL, "\n")) {
		memset(line, 0, WIDTH + 1);

		// Count how many printf arguments there are in the current line.
		/* Count how many printf arguments there are in the current
		 * line. */
		int argamt = 0;
		for (int i = 0; i < strlen(token) - 1; i++) {
			if (token[i] == '%' && token[i + 1] != '%') {
				argamt++;
			}
		}
		// while the value at *token is not '\0' (the end of the string)
		/* while the value at *token is not '\0' (the end of the
		 * string) */
		for (; *token; token++) {
			switch(*token) {
			case '%':
				// if a percentage is found, look for arguments.
				/* if a percentage is found, look for
				 * arguments. */
				switch (*++token) {
				case 'd':
					n = va_arg(ap, int);


@@ 631,18 693,20 @@ showMessage(char *fmt, ...)
				line[strlen(line)] = *token;
				break;
			}
		} // while (*token)
		} /* while (*token) */

		locate(WIDTH / 2 - strlen(line) / 2, HEIGHT / 2 + lineNumber);
		printf("%s", line);

	} // for(...)
	} /* for(...) */

	va_end(ap);
	fflush(stdout);
}

// Updates the level counter in the footer.
/*
 * Updates the level counter in the footer.
 */
void
updateLevel(int *level)
{


@@ 654,7 718,9 @@ updateLevel(int *level)
	printf("%02d\n", *level);
}

// Updates the lives counter in the footer.
/*
 * Updates the lives counter in the footer.
 */
void
updateLives(int *lives)
{


@@ 665,7 731,9 @@ updateLives(int *lives)
	printf("%02d\n", *lives);
}

// Updates the score counter in the footer.
/*
 * Updates the score counter in the footer.
 */
void updateScore(unsigned int *score)
{
	locate(FOOTER_XPOS + strlen(TITLE) + strlen(LIVES_FOOTER) +


@@ 676,8 744,10 @@ void updateScore(unsigned int *score)
	printf("%08u\n", *score);
}

// Redraws the tile at board[x][y] in the window. No bounds-checking is done
// here.
/*
 * Redraws the tile at board[x][y] in the window. No bounds-checking is done
 * here.
 */
void
updateTile(int x, int y)
{


@@ 703,8 773,8 @@ main(int argc, char *argv[])
		level++;
	}

	// When the program reaches this point, the player has ran out of lives,
	// and the game is over.
	/* When the program reaches this point, the player has ran out of
	 * lives, and the game is over. */
	showMessage("Game over!\nScore: %d\nLevel: %d\nPress any key to quit.",
			score, level);
	anykey(NULL);