Add a bunch of animations and sounds

This commit is contained in:
Jeremy Rand 2014-07-24 00:31:37 -05:00
parent 2e0a001c1c
commit f2cfbc6a34
3 changed files with 418 additions and 29 deletions

View File

@ -8,8 +8,10 @@
#include <apple2enh.h>
#include <conio.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "game.h"
@ -17,6 +19,50 @@
#define TILE_WIDTH 10
#define TILE_HEIGHT 5
#define TILE_X_TO_SCREEN_X(x) (((x) - 1) * TILE_WIDTH)
#define TILE_Y_TO_SCREEN_Y(y) (((y) - 1) * TILE_HEIGHT)
typedef struct tTileAnim
{
uint8_t fromX;
uint8_t fromY;
uint8_t toX;
uint8_t toY;
char *tileString;
char *endTileString;
} tTileAnim;
static tTileAnim gTileAnims[NUM_TILES];
static tDir gAnimDir;
static uint8_t gNumAnims;
static char *gNewTileString = NULL;
static tPos gNewTilePos;
static bool gPlaySounds = true;
void shortDelay(uint16_t howMuch)
{
while (howMuch > 0) {
howMuch--;
}
}
void playSound(int8_t freq, int16_t duration)
{
while (duration > 0) {
if (gPlaySounds)
asm ("STA %w", 0xc030);
while (freq > 0) {
freq--;
}
duration--;
}
}
void printInstructions(void)
{
int seed = 0;
@ -40,11 +86,12 @@ void printInstructions(void)
"\n"
"PRESS ESCAPE OR Q TO QUIT AT ANY TIME.\n"
"PRESS R TO START A NEW GAME.\n"
"PRESS S TO TOGGLE SOUND.\n"
"\n"
"\n"
"\n"
"\n"
" PRESS ANY KEY TO START");
" PRESS ANY KEY TO START");
// The amount of time the user waits to read the in
while (!kbhit())
@ -57,36 +104,313 @@ void printInstructions(void)
}
void printBoard(void)
void printGrid(void)
{
tPos x;
tPos y;
for (x = 1; x <= BOARD_SIZE; x++) {
for (y = 1; y <= BOARD_SIZE; y++) {
textframexy((x - 1) * TILE_WIDTH,
(y - 1) * TILE_HEIGHT,
textframexy(TILE_X_TO_SCREEN_X(x),
TILE_Y_TO_SCREEN_Y(y),
TILE_WIDTH,
TILE_HEIGHT,
TEXTFRAME_WIDE);
cputsxy((x - 1) * TILE_WIDTH + 1,
(y - 1) * TILE_HEIGHT + 2,
}
}
}
void printValueAt(uint8_t screenX, uint8_t screenY, char *tileString)
{
screenX++;
screenY++;
cputsxy(screenX, screenY, " ");
screenY++;
cputsxy(screenX, screenY, tileString);
screenY++;
cputsxy(screenX, screenY, " ");
}
void printValues(void)
{
tPos x;
tPos y;
for (x = 1; x <= BOARD_SIZE; x++) {
for (y = 1; y <= BOARD_SIZE; y++) {
printValueAt(TILE_X_TO_SCREEN_X(x), TILE_Y_TO_SCREEN_Y(y),
tileStringForPos(x, y));
}
}
}
void printScore(void)
{
gotoxy(0,20);
printf("CURRENT SCORE: %ld\nTRY TO GET THE %ld TILE!", currentScore(),
nextTarget());
}
void resetAnimations(void)
{
memset(gTileAnims, 0, sizeof(gTileAnims));
gNewTileString = NULL;
gAnimDir = 0;
gNumAnims = 0;
}
void performAnimationsLeft(void)
{
bool animInProgress;
tPos pos;
tTileAnim *tileAnim;
tPos x;
tPos y;
do {
animInProgress = false;
for (pos = 0; pos < gNumAnims; pos++) {
tileAnim = &(gTileAnims[pos]);
if (tileAnim->tileString == NULL)
continue;
x = tileAnim->fromX;
y = tileAnim->fromY;
if ((x % TILE_WIDTH) != (TILE_WIDTH - 1)) {
x += TILE_WIDTH;
if (x < 40) {
cputcxy(x, y + 1, ' ');
cputcxy(x, y + 2, ' ');
cputcxy(x, y + 3, ' ');
}
x -= TILE_WIDTH;
}
x--;
textframexy(x, y, TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
if (x == tileAnim->toX) {
tileAnim->tileString = NULL;
printValueAt(x, y, tileAnim->endTileString);
} else {
tileAnim->fromX = x;
animInProgress = true;
printValueAt(x, y, tileAnim->tileString);
}
playSound(200, 2);
}
} while (animInProgress);
}
void performAnimationsRight(void)
{
bool animInProgress;
tPos pos;
tTileAnim *tileAnim;
tPos x;
tPos y;
do {
animInProgress = false;
for (pos = 0; pos < gNumAnims; pos++) {
tileAnim = &(gTileAnims[pos]);
if (tileAnim->tileString == NULL)
continue;
x = tileAnim->fromX;
y = tileAnim->fromY;
if ((x % TILE_WIDTH) != 0) {
cputcxy(x, y + 1, ' ');
cputcxy(x, y + 2, ' ');
cputcxy(x, y + 3, ' ');
}
x++;
textframexy(x, y, TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
if (x == tileAnim->toX) {
tileAnim->tileString = NULL;
printValueAt(x, y, tileAnim->endTileString);
} else {
tileAnim->fromX = x;
animInProgress = true;
printValueAt(x, y, tileAnim->tileString);
}
playSound(200, 2);
}
} while (animInProgress);
}
void performAnimationsUp(void)
{
bool animInProgress;
tPos pos;
tTileAnim *tileAnim;
tPos x;
tPos y;
do {
animInProgress = false;
for (pos = 0; pos < gNumAnims; pos++) {
tileAnim = &(gTileAnims[pos]);
if (tileAnim->tileString == NULL)
continue;
x = tileAnim->fromX;
y = tileAnim->fromY;
switch ((y % TILE_HEIGHT)) {
case 0:
default:
cputsxy(x, y + TILE_HEIGHT, " ");
if (y < TILE_Y_TO_SCREEN_Y(BOARD_SIZE))
textframexy(x, ((y / TILE_HEIGHT) + 1) * TILE_HEIGHT,
TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
break;
}
y--;
textframexy(x, y, TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
if (y == tileAnim->toY) {
tileAnim->tileString = NULL;
printValueAt(x, y, tileAnim->endTileString);
} else {
tileAnim->fromY = y;
animInProgress = true;
printValueAt(x, y, tileAnim->tileString);
}
playSound(200, 2);
}
} while (animInProgress);
}
void performAnimationsDown(void)
{
bool animInProgress;
tPos pos;
tTileAnim *tileAnim;
tPos x;
tPos y;
do {
animInProgress = false;
for (pos = 0; pos < gNumAnims; pos++) {
tileAnim = &(gTileAnims[pos]);
if (tileAnim->tileString == NULL)
continue;
x = tileAnim->fromX;
y = tileAnim->fromY;
switch ((y % TILE_HEIGHT)) {
case 0:
default:
cputsxy(x, y, " ");
textframexy(x, (y / TILE_HEIGHT) * TILE_HEIGHT,
TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
break;
}
y++;
textframexy(x, y, TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
if (y == tileAnim->toY) {
tileAnim->tileString = NULL;
printValueAt(x, y, tileAnim->endTileString);
} else {
tileAnim->fromY = y;
animInProgress = true;
printValueAt(x, y, tileAnim->tileString);
}
playSound(200, 2);
}
} while (animInProgress);
}
void performNewTileAnimation()
{
uint8_t screenX;
uint8_t screenY;
if (gNewTileString == NULL)
return;
screenX = TILE_X_TO_SCREEN_X(POS_TO_X(gNewTilePos));
screenY = TILE_Y_TO_SCREEN_Y(POS_TO_Y(gNewTilePos));
textframexy(screenX + 2, screenY + 1, TILE_WIDTH - 4, TILE_HEIGHT - 2,
TEXTFRAME_WIDE);
shortDelay(500);
textframexy(screenX + 1, screenY + 1, TILE_WIDTH - 2, TILE_HEIGHT - 1,
TEXTFRAME_WIDE);
shortDelay(500);
textframexy(screenX, screenY, TILE_WIDTH, TILE_HEIGHT, TEXTFRAME_WIDE);
printValueAt(screenX, screenY, gNewTileString);
}
void performAnimations(void)
{
switch (gAnimDir) {
case DIR_UP:
performAnimationsUp();
break;
case DIR_DOWN:
performAnimationsDown();
break;
case DIR_LEFT:
performAnimationsLeft();
break;
case DIR_RIGHT:
performAnimationsRight();
break;
}
performNewTileAnimation();
resetAnimations();
}
void printBoard(void)
{
performAnimations();
printGrid();
printValues();
printScore();
}
void gameWon(void)
{
gotoxy(0, 22);
printf("YOU HAVE WON THE GAME! PRESS ANY KEY...\n");
cgetc();
initGame();
clrscr();
newGame();
}
@ -95,7 +419,8 @@ void gameLost(void)
gotoxy(0, 22);
printf("SORRY, NO MORE MOVES. PRESS ANY KEY...");
cgetc();
initGame();
clrscr();
newGame();
}
@ -137,18 +462,55 @@ void handleNextEvent(void)
case 'r':
case 'R':
initGame();
clrscr();
newGame();
return;
case 's':
case 'S':
gPlaySounds = !gPlaySounds;
break;
}
}
}
void tileMoved(tPos from, tPos to, char *tileString)
{
tTileAnim *anim = &(gTileAnims[gNumAnims]);
anim->tileString = tileString;
anim->endTileString = tileStringForPos(POS_TO_X(to), POS_TO_Y(to));
anim->fromX = TILE_X_TO_SCREEN_X(POS_TO_X(from));
anim->fromY = TILE_Y_TO_SCREEN_Y(POS_TO_Y(from));
anim->toX = TILE_X_TO_SCREEN_X(POS_TO_X(to));
anim->toY = TILE_Y_TO_SCREEN_Y(POS_TO_Y(to));
gNumAnims++;
if (anim->fromX > anim->toX) {
gAnimDir = DIR_LEFT;
} else if (anim->fromX < anim->toX) {
gAnimDir = DIR_RIGHT;
} else if (anim->fromY > anim->toY) {
gAnimDir = DIR_UP;
} else if (anim->fromY < anim->toY) {
gAnimDir = DIR_DOWN;
}
}
void newTile(tPos at, char *tileString)
{
gNewTileString = tileString;
gNewTilePos = at;
}
int main(void)
{
printInstructions();
initGame();
initGameEngine(tileMoved, newTile);
newGame();
while (true) {
printBoard();

35
game.c
View File

@ -8,20 +8,12 @@
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include "game.h"
// A 4x4 board is actually represented as a 6x6 board internally. This gives
// us special tiles around the whole board which can have a value which
// prevents tiles from scrolling beyond the bounds of the board. Also, this
// is why the values of DIR_DOWN and DIR_UP in the game.h file is a bit
// strange.
#define NUM_TILES ((BOARD_SIZE + 2) * (BOARD_SIZE + 2))
#define POS_TO_X(pos) ((pos) % (BOARD_SIZE + 2))
#define POS_TO_Y(pos) ((pos) / (BOARD_SIZE + 2))
#define X_Y_TO_POS(x, y) (((y) * (BOARD_SIZE + 2)) + (x))
#define POS_IN_DIR(pos, dir) ((pos) + (dir))
// The maximum which the game supports in any one tile is 2^19 because that is
@ -79,7 +71,19 @@ static uint8_t gNumEmptyTiles;
void addRandomTile(void);
void initGame(void)
static tTileMoveCallback gTileMoveCallback = NULL;
static tNewTileCallback gNewTileCallback = NULL;
void initGameEngine(tTileMoveCallback tileMoveCallback,
tNewTileCallback newTileCallback)
{
gTileMoveCallback = tileMoveCallback;
gNewTileCallback = newTileCallback;
}
void newGame(void)
{
tPos pos;
tPos x;
@ -173,6 +177,7 @@ void slideInDirection(tDir dir)
} else {
gTileValues[destPos] = gTileValues[pos];
}
gTileMoveCallback(pos, destPos, gValueStrings[gTileValues[pos]]);
gTileValues[pos] = 0; // Empty the old position
}
@ -206,6 +211,7 @@ void addRandomTile(void)
else
gTileValues[pos] = 1; // This creates a 2
gNumEmptyTiles--;
gNewTileCallback(pos, gValueStrings[gTileValues[pos]]);
return;
}
randTile--;
@ -270,5 +276,10 @@ bool isGameLost(void)
char *tileStringForPos(tPos x, tPos y)
{
return gValueStrings[gTileValues[X_Y_TO_POS(x, y)]];
tTileValue value = gTileValues[X_Y_TO_POS(x, y)];
if (value < 0)
value *= -1;
return gValueStrings[value];
}

30
game.h
View File

@ -13,6 +13,17 @@
#define BOARD_SIZE 4
// A 4x4 board is actually represented as a 6x6 board internally. This gives
// us special tiles around the whole board which can have a value which
// prevents tiles from scrolling beyond the bounds of the board. Also, this
// is why the values of DIR_DOWN and DIR_UP in the game.h file is a bit
// strange.
#define NUM_TILES ((BOARD_SIZE + 2) * (BOARD_SIZE + 2))
#define POS_TO_X(pos) ((pos) % (BOARD_SIZE + 2))
#define POS_TO_Y(pos) ((pos) / (BOARD_SIZE + 2))
#define X_Y_TO_POS(x, y) (((y) * (BOARD_SIZE + 2)) + (x))
#define DIR_DOWN (BOARD_SIZE + 2)
#define DIR_UP (-(DIR_DOWN))
#define DIR_RIGHT 1
@ -23,19 +34,24 @@ typedef int8_t tDir;
typedef int8_t tPos;
typedef uint32_t tScore;
typedef void (*tTileMoveCallback)(tPos from, tPos to, char *tileString);
typedef void (*tNewTileCallback)(tPos at, char *tileString);
void initGame(void);
extern void initGameEngine(tTileMoveCallback tileMoveCallback,
tNewTileCallback newTileCallback);
void slideInDirection(tDir dir);
extern void newGame(void);
tScore currentScore(void);
extern void slideInDirection(tDir dir);
tScore nextTarget(void);
extern tScore currentScore(void);
bool isGameWon(void);
extern tScore nextTarget(void);
bool isGameLost(void);
extern bool isGameWon(void);
extern bool isGameLost(void);
// Positions are 1 based so the top-left corner is (1, 1) and the bottom-right
// corner is (BOARD_SIZE, BOARD_SIZE).
char *tileStringForPos(tPos x, tPos y);
extern char *tileStringForPos(tPos x, tPos y);