mirror of
https://github.com/jeremysrand/apple2048.git
synced 2025-04-18 03:37:42 +00:00
Add a bunch of animations and sounds
This commit is contained in:
parent
2e0a001c1c
commit
f2cfbc6a34
382
apple2048.c
382
apple2048.c
@ -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
35
game.c
@ -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
30
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user