183 lines
6.9 KiB
C
183 lines
6.9 KiB
C
// Space Taxi front-end harness: the immortal-cabbies table and the name
|
|
// entry screen, driven with scripted keys on the host (BLANK port).
|
|
//
|
|
// spacetaxi.c is INCLUDED so its static state and routines are visible;
|
|
// its main() is renamed away. jlInputGetChar, jlSaveWrite and jlSaveRead
|
|
// are replaced through the linker's --wrap with a key script and an
|
|
// in-memory save image (see the Makefile).
|
|
#define main staxiMain
|
|
#include "spacetaxi.c"
|
|
#undef main
|
|
|
|
#include <stdio.h>
|
|
|
|
#define SAVE_IMAGE_MAX 512u
|
|
|
|
bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len);
|
|
uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max);
|
|
int __wrap_jlInputGetChar(void);
|
|
|
|
static void check(bool ok, const char *what);
|
|
static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out);
|
|
static void setScore(uint8_t player, const uint8_t *digits);
|
|
|
|
static const char *gKeys = "";
|
|
static uint8_t gSaveImage[SAVE_IMAGE_MAX];
|
|
static uint32_t gSaveLen = 0u;
|
|
static int gFails = 0;
|
|
|
|
|
|
int __wrap_jlInputGetChar(void) {
|
|
if (*gKeys == 0) {
|
|
return -1;
|
|
}
|
|
return (unsigned char)*gKeys++;
|
|
}
|
|
|
|
|
|
uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max) {
|
|
(void)name;
|
|
if (gSaveLen == 0u || gSaveLen > max) {
|
|
return 0u;
|
|
}
|
|
memcpy(buf, gSaveImage, gSaveLen);
|
|
return gSaveLen;
|
|
}
|
|
|
|
|
|
bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len) {
|
|
(void)name;
|
|
if (len > sizeof(gSaveImage)) {
|
|
return false;
|
|
}
|
|
memcpy(gSaveImage, buf, len);
|
|
gSaveLen = len;
|
|
return true;
|
|
}
|
|
|
|
|
|
static void check(bool ok, const char *what) {
|
|
printf("%s %s\n", ok ? "ok " : "FAIL", what);
|
|
if (!ok) {
|
|
gFails++;
|
|
}
|
|
}
|
|
|
|
|
|
// `n` screen cells of one row as a C string.
|
|
static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out) {
|
|
uint8_t k;
|
|
|
|
for (k = 0u; k < n; k++) {
|
|
out[k] = (char)gSim.screen[ST_CELL(row, col + k)];
|
|
}
|
|
out[n] = 0;
|
|
}
|
|
|
|
|
|
// A player's score as the sim keeps it: seven screen-code digits with
|
|
// the point at ST_HS_POINT_AT.
|
|
static void setScore(uint8_t player, const uint8_t *digits) {
|
|
uint8_t k;
|
|
|
|
for (k = 0u; k < ST_NUMBER_CHARS; k++) {
|
|
gSim.scoreBackup[player][k] = (uint8_t)(ST_CHAR_DIGIT_BASE + digits[k]);
|
|
}
|
|
gSim.scoreBackup[player][ST_HS_POINT_AT] = ST_CHAR_POINT;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
static const uint8_t kDigits[ST_NUMBER_CHARS] = { 1u, 2u, 3u, 4u, 0u, 5u, 6u };
|
|
jlConfigT config;
|
|
jlSurfaceT *stage;
|
|
char buf[48];
|
|
uint8_t k;
|
|
|
|
config.codegenBytes = 160UL * 1024;
|
|
config.audioBytes = 32UL * 1024;
|
|
if (!jlInit(&config)) {
|
|
printf("jlInit failed\n");
|
|
return 1;
|
|
}
|
|
stage = jlStageGet();
|
|
memset(&gGame, 0, sizeof(gGame));
|
|
memset(&gSim, 0, sizeof(gSim));
|
|
stRenderInit(stage);
|
|
stAudioInit();
|
|
stSimNewGame(&gSim, 2u, false);
|
|
|
|
// ---- ranking ($4D92) ----
|
|
check(highScoreInsert((const uint8_t *)" 49.99") == ST_HS_ENTRIES, "49.99 is below the $50 floor");
|
|
check(highScoreInsert((const uint8_t *)" 50.00") == ST_HS_ENTRIES, "50.00 passes the floor but beats nobody");
|
|
check(highScoreInsert((const uint8_t *)" 500.00") == 8u, "500.00 lands in slot 8 (under 504.97, above 498.77)");
|
|
check(memcmp(gHighScores[9].score, " 498.77", 7) == 0 && memcmp(gHighScores[7].score, " 504.97", 7) == 0, "older entries shifted down, the last one dropped");
|
|
check(highScoreInsert((const uint8_t *)"3877.56") == 0u, "a tie goes above the older entry");
|
|
check(memcmp(gHighScores[1].score, "3877.56", 7) == 0 && memcmp(gHighScores[1].name, "MICHAEL PLATE ", 15) == 0, "the tied old entry is now second");
|
|
|
|
// ---- the entry screen for cabbie 2 with $1234.56 on shift 3 ($4D52 / $4E35) ----
|
|
gSim.player = 1u;
|
|
gGame.variation = 3u;
|
|
setScore(1u, kDigits);
|
|
gKeys = "zz"; // typed before the screen: flushed like $0277
|
|
check(nameEntryStart(), "1234.56 ranks");
|
|
check(gGame.state == ST_STATE_NAME_ENTRY && gGame.hsSlot == 3u, "slot 3 (under the two 3877.56 and 1809.51)");
|
|
check(gSim.borderColor == 2u && gSim.bgColor == 15u, "red border, grey field");
|
|
rowText(3u, 7u, 25u, buf);
|
|
check(strcmp(buf, "CONGRATULATIONS CABBIE 2,") == 0, buf);
|
|
rowText(0u, 15u, 8u, buf);
|
|
check(strcmp(buf, "$1234.56") == 0, buf);
|
|
rowText(9u, 9u, 21u, buf);
|
|
check(strcmp(buf, "ENTER YOUR NAME BELOW") == 0, buf);
|
|
check(*gKeys == 0, "earlier keystrokes discarded");
|
|
|
|
// ---- typing ($4ED8): s c o 9 t t <backspace> T ----
|
|
gKeys = "sco9tt\bT";
|
|
runNameEntry(1u);
|
|
rowText(ST_HS_NAME_ROW, ST_HS_NAME_COL, 5u, buf);
|
|
check(strcmp(buf, "SCOTT") == 0, buf);
|
|
check(gGame.hsLen == 5u, "five characters kept ('9' ignored, one deleted, one retyped)");
|
|
check(gGame.state == ST_STATE_NAME_ENTRY, "still entering");
|
|
for (k = 0u; k < 2u; k++) {
|
|
runNameEntry(1u);
|
|
}
|
|
check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == ' ', "first flip blanks the cursor ($4F4D)");
|
|
for (k = 0u; k < 3u; k++) {
|
|
runNameEntry(1u);
|
|
}
|
|
check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == '%', "second flip shows it");
|
|
gKeys = "\r";
|
|
runNameEntry(1u);
|
|
check(gGame.state != ST_STATE_NAME_ENTRY, "RETURN leaves the screen");
|
|
check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && gHighScores[3].shift == 3u, "name and shift stored");
|
|
check(gSaveLen == 240u && gSaveImage[3 * 24 + 7] == 0 && memcmp(&gSaveImage[3 * 24 + 8], "SCOTT ", 6) == 0 && gSaveImage[3 * 24 + 23] == 3u, "saved in the C64 image layout");
|
|
check(gSim.playersDone == 1u, "game-over bookkeeping continued");
|
|
|
|
// ---- reload from the image ($23E5) ----
|
|
memset(gHighScores, 0, sizeof(gHighScores));
|
|
highScoreLoad();
|
|
check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && memcmp(gHighScores[0].score, "3877.56", 7) == 0, "table restored from the save");
|
|
|
|
// ---- an empty name becomes "*" ----
|
|
gSim.player = 0u;
|
|
setScore(0u, kDigits);
|
|
check(nameEntryStart() && gGame.hsSlot == 3u, "second entry, a tie goes above");
|
|
gKeys = "\r";
|
|
runNameEntry(1u);
|
|
check(gHighScores[3].name[0] == '*' && memcmp(gHighScores[4].name, "SCOTT", 5) == 0, "an empty name is stored as *");
|
|
|
|
// ---- the table screen ($4C1F) ----
|
|
highScoreDraw();
|
|
rowText(1u, 9u, 20u, buf);
|
|
check(strcmp(buf, "THE IMMORTAL CABBIES") == 0, buf);
|
|
rowText(12u, 4u, 26u, buf);
|
|
check(strcmp(buf, "$1234.56 SCOTT ") == 0, buf);
|
|
check(gSim.screen[ST_CELL(12, 34)] == '3' && gSim.screen[ST_CELL(10, 15)] == '*', "shift digit in the SHIFT column, * name above");
|
|
rowText(24u, 3u, 34u, buf);
|
|
check(strcmp(buf, "PRESS FIRE TO RETURN TO TITLE PAGE") == 0, buf);
|
|
check(jlMusicIsPlaying(), "song 7 under the table");
|
|
|
|
printf("%s (%d failures)\n", gFails ? "staxiharness: FAIL" : "staxiharness: PASS", gFails);
|
|
jlShutdown();
|
|
return gFails ? 1 : 0;
|
|
}
|