From f2cfbc6a347f56e49e16f5ed783a34ddb76b012f Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Thu, 24 Jul 2014 00:31:37 -0500 Subject: [PATCH] Add a bunch of animations and sounds --- apple2048.c | 382 ++++++++++++++++++++++++++++++++++++++++++++++++++-- game.c | 35 +++-- game.h | 30 ++++- 3 files changed, 418 insertions(+), 29 deletions(-) diff --git a/apple2048.c b/apple2048.c index d9a5991..3cd1bfc 100644 --- a/apple2048.c +++ b/apple2048.c @@ -8,8 +8,10 @@ #include #include +#include #include #include +#include #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(); diff --git a/game.c b/game.c index 6bf26cc..5564150 100644 --- a/game.c +++ b/game.c @@ -8,20 +8,12 @@ #include +#include #include + #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]; } diff --git a/game.h b/game.h index dd79a82..3cde001 100644 --- a/game.h +++ b/game.h @@ -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);