joeylib2/tools/staxiharness/harness.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;
}