paulo@0: /* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game paulo@0: paulo@0: Copyright (C) 2006 Damian Yerrick paulo@0: paulo@0: This work is free software; you can redistribute it and/or modify paulo@0: it under the terms of the GNU General Public License as published by paulo@0: the Free Software Foundation; either version 2 of the License, or paulo@0: (at your option) any later version. paulo@0: paulo@0: This program is distributed in the hope that it will be useful, paulo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the paulo@0: GNU General Public License for more details. paulo@0: paulo@0: You should have received a copy of the GNU General Public License paulo@0: along with this program; if not, write to the Free Software paulo@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA paulo@0: paulo@0: Original game concept and design by Alexey Pajitnov. paulo@0: The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, paulo@0: or The Tetris Company LLC. paulo@0: paulo@0: */ paulo@0: #include "ljplay.h" paulo@0: #include "options.h" paulo@0: #include paulo@0: #ifndef NO_DATETIME paulo@0: #include paulo@0: #endif paulo@0: paulo@0: #define USING_DEBRIEF_PAGE 1 paulo@0: #define MIN_RECTUM_ROWS 4 paulo@0: #define MIN_ZIGZAG_ROWS 2 paulo@0: paulo@0: // On newlib, use a variant of sprintf that doesn't handle floating paulo@0: // point formats. On other C libraries, use standard sprintf. paulo@0: #ifdef HAS_FPU paulo@0: #define siprintf sprintf paulo@0: #ifndef HAS_FOPEN paulo@0: #define HAS_FOPEN paulo@0: #endif paulo@0: #define DEBRIEF_TO_STDOUT paulo@0: #endif paulo@0: paulo@0: #ifdef HAS_FOPEN paulo@0: #include "ljpath.h" // for ljfopen paulo@0: #endif paulo@0: paulo@0: const char *const debriefBoolNames[2] = { paulo@0: "Off", "On" paulo@0: }; paulo@0: paulo@0: typedef struct TimeResultMSF { paulo@0: unsigned int pieces_100m; paulo@0: unsigned int garbage_100m; paulo@0: unsigned int minutes; paulo@0: unsigned char seconds; paulo@0: unsigned char frames; paulo@0: } TimeResultMSF; paulo@0: paulo@0: void frames2msf(unsigned int gameTime, paulo@0: unsigned int nPieces, paulo@0: unsigned int outGarbage, paulo@0: TimeResultMSF *out) { paulo@0: unsigned int gameSeconds = gameTime / 60U; paulo@0: unsigned int gameMinutes = gameSeconds / 60U; paulo@0: out->pieces_100m = gameTime paulo@0: ? 360000ULL * nPieces / gameTime paulo@0: : 0; paulo@0: out->garbage_100m = gameTime paulo@0: ? 360000ULL * outGarbage / gameTime paulo@0: : 0; paulo@0: out->minutes = gameMinutes; paulo@0: out->seconds = gameSeconds - gameMinutes * 60U; paulo@0: out->frames = gameTime - gameSeconds * 60U; paulo@0: } paulo@0: paulo@0: static unsigned int countBlocksLeft(const LJField *p) { paulo@0: unsigned int n = 0; paulo@0: paulo@0: for (int y = 0; y < LJ_PF_HT; ++y) { paulo@0: for (int x = 0; x < LJ_PF_WID; ++x) { paulo@0: if (p->b[y][x]) { paulo@0: ++n; paulo@0: } paulo@0: } paulo@0: } paulo@0: return n; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Calculates the number of rows starting at the bottom that paulo@0: * conform to a zigzag pattern. paulo@0: * @param p the playfield to test paulo@0: * @return the number of rows successfully completed paulo@0: */ paulo@0: unsigned int calcZigzagGrade(const LJField *p) { paulo@0: unsigned int hole = p->leftWall; paulo@0: int delta = 1; paulo@0: paulo@0: for (size_t y = 0; y < p->ceiling; ++y) { paulo@0: paulo@0: // invariant: paulo@0: // at this point, y equals the number of rows known to conform paulo@0: for (size_t x = p->leftWall; x < p->rightWall; ++x) { paulo@0: unsigned int blk = p->b[y][x]; paulo@0: paulo@0: // A block should be in all cells of the row paulo@0: // except for the hole cell, which should be empty. paulo@0: if (x == hole) { paulo@0: paulo@0: // if there's a block where a hole should be, this row paulo@0: // doesn't conform, but all the rows below do conform paulo@0: if (blk) { paulo@0: return y; paulo@0: } paulo@0: } else { paulo@0: paulo@0: // if there's a hole where a block should be, this row paulo@0: // doesn't conform, but all the rows below do conform paulo@0: if (!blk) { paulo@0: return y; paulo@0: } paulo@0: } paulo@0: } paulo@0: paulo@0: // if this hole isn't covered up on the next row, paulo@0: // this row doesn't conform either paulo@0: // changed in 0.43 after a clarification from Kitaru paulo@0: if (!p->b[y + 1][hole]) { paulo@0: return y; paulo@0: } paulo@0: paulo@0: // by now we know that the row conforms, paulo@0: // so move the hole for the next row paulo@0: if (hole == p->rightWall - 1) { paulo@0: delta = -1; paulo@0: } else if (hole == p->leftWall) { paulo@0: delta = 1; paulo@0: } paulo@0: hole += delta; paulo@0: paulo@0: } paulo@0: return p->ceiling; paulo@0: } paulo@0: paulo@0: paulo@0: /** paulo@0: * Calculates the number of rows in a playfield that were prepared paulo@0: * for an I tetromino. paulo@0: * Rule for this pattern: paulo@0: * 1. Only one hole on the bottom row. paulo@0: * 2. This column must be unoccupied below the ceiling. paulo@0: * 3. Conforming rows must be full except for the hole. paulo@0: * paulo@0: * Easy test procedure for this pattern: paulo@0: * Level 4 garbage paulo@0: * @return the number of rows that conform to constraint 3, or 0 if the paulo@0: * field does not conform to constraint 1 or 2. paulo@0: */ paulo@0: unsigned int calcRectumGrade(const LJField *p) { paulo@0: unsigned int hole = LJ_PF_WID; paulo@0: paulo@0: // search for the first hole on the bottom row paulo@0: for (unsigned int x = p->leftWall; paulo@0: x < p->rightWall; paulo@0: ++x) { paulo@0: if (!p->b[0][x]) { paulo@0: hole = x; paulo@0: break; paulo@0: } paulo@0: } paulo@0: paulo@0: // If there is no hole in the bottom row, then 0 rows conform. paulo@0: // This shouldn't happen in standard smg because the line would be paulo@0: // cleared, but eventually LJ will support games other than SMG, paulo@0: // and checking the array bounds is O(1), so why not? paulo@0: if (hole >= p->rightWall) { paulo@0: return 0; paulo@0: } paulo@0: paulo@0: // make sure that the row is clear through the whole visible paulo@0: // portion of the playfield paulo@0: for (unsigned int y = 0; y < p->ceiling; ++y) { paulo@0: paulo@0: // If this column isn't empty, the whole field doesn't conform. paulo@0: if (p->b[y][hole]) { paulo@0: return 0; paulo@0: } paulo@0: } paulo@0: paulo@0: for (unsigned int y = 0; y < p->ceiling; ++y) { paulo@0: paulo@0: // At this point, the bottom y rows conform. paulo@0: for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { paulo@0: paulo@0: // Disregarding the hole column, if there's an empty cell paulo@0: // in this row, then this row does not conform. paulo@0: if (x != hole && !p->b[y][x]) { paulo@0: return y; paulo@0: } paulo@0: } paulo@0: } paulo@0: return p->ceiling; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Converts a rank number (0-19) to a text rank. paulo@0: * 0-9: 10-1 kyu; 10-99: 1-90 dan; 100+: grandmaster paulo@0: * @return number of characters written paulo@0: */ paulo@0: static size_t printSecretGrade(char *dst, int rank) { paulo@0: if (rank >= 100) { paulo@0: return siprintf(dst, "GM"); paulo@0: } else if (rank >= 10) { paulo@0: return siprintf(dst, "S%d", rank - 9); paulo@0: } else { paulo@0: return siprintf(dst, "%d", 10 - rank); paulo@0: } paulo@0: } paulo@0: paulo@0: static size_t printFracG(char *dst, unsigned int value) { paulo@0: if (value == 0) { paulo@0: return siprintf(dst, "Instant"); paulo@0: } else if (value == 1) { paulo@0: return siprintf(dst, "1G"); paulo@0: } else { paulo@0: return siprintf(dst, "1/%dG", value); paulo@0: } paulo@0: } paulo@0: paulo@0: #include // remove once fourcc transition is complete paulo@0: static size_t printFourCC(char *dst, FourCC f) { paulo@0: const char *data = ljGetFourCCName(f); paulo@0: if (data) { paulo@0: int pos = 0; paulo@0: for(pos = 0; *data; ++pos) { paulo@0: dst[pos] = *data++; paulo@0: } paulo@0: return pos; paulo@0: } else { paulo@0: int pos = 0; paulo@0: for(pos = 0; f.c[pos] && pos < 4; ++pos) { paulo@0: dst[pos] = f.c[pos]; paulo@0: } paulo@0: return pos; paulo@0: } paulo@0: } paulo@0: paulo@0: size_t buildOptionsReportPage(char *dst, const LJView *v) { paulo@0: size_t pos = 0; paulo@0: paulo@0: paulo@0: #if USING_DEBRIEF_PAGE paulo@0: const LJField *p = v->field; paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "Options\n\n" paulo@0: "Well: %dx%d%s, Enter: %s\n" paulo@0: "Speed: ", paulo@0: p->rightWall - p->leftWall, paulo@0: p->ceiling, paulo@0: v->hidePF ? " invisible" : "", paulo@0: p->enterAbove ? "Above" : "Below"); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsSpeedCurveNames[(size_t)p->speedState.curve]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", ARE: %d ms\n" paulo@0: "Randomizer: ", paulo@0: v->field->areStyle * 50 / 3); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsRandNames[(size_t)p->randomizer]); paulo@0: pos += siprintf(dst + pos, " of "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsPieceSetNames[(size_t)p->pieceSet]); paulo@0: pos += siprintf(dst + pos, "\nHold: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsHoldStyleNames[(size_t)p->holdStyle]); paulo@0: pos += siprintf(dst + pos, ", Rotation: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsRotNames[(size_t)p->rotationSystem]); paulo@0: if (p->maxUpwardKicks < 20) { paulo@0: pos += siprintf(dst + pos, paulo@0: " %d FK", paulo@0: (int)p->maxUpwardKicks); paulo@0: } paulo@0: if (v->control->initialRotate) { paulo@0: pos += siprintf(dst + pos, "+initial"); paulo@0: } paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "\nLock: "); paulo@0: if (p->setLockDelay >= 128) { paulo@0: pos += siprintf(dst + pos, "Never"); paulo@0: } else { paulo@0: if (p->setLockDelay > 0) { paulo@0: pos += siprintf(dst + pos, paulo@0: "%d ms ", paulo@0: p->setLockDelay * 50 / 3); paulo@0: } paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsLockdownNames[(size_t)p->lockReset]); paulo@0: } paulo@0: pos += siprintf(dst + pos, paulo@0: ", Deep: %s\n", paulo@0: debriefBoolNames[p->bottomBlocks]); paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "Line clear: %d ms ", paulo@0: p->speed.lineDelay * 50 / 3); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsGravNames[(size_t)p->clearGravity]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", Gluing: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsGluingNames[(size_t)p->gluing]); paulo@0: pos += siprintf(dst + pos, paulo@0: "\nDrop score: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsDropScoringNames[(size_t)p->dropScoreStyle]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", T-spin: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsTspinNames[(size_t)p->tSpinAlgo]); paulo@0: pos += siprintf(dst + pos, paulo@0: "\nGarbage: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsGarbageNames[(size_t)p->garbageStyle]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", DAS: %d ms ", paulo@0: v->control->dasDelay * 50 / 3); paulo@0: pos += printFracG(dst + pos, v->control->dasSpeed); paulo@0: pos += siprintf(dst + pos, paulo@0: "\nSoft drop: "); paulo@0: pos += printFracG(dst + pos, v->control->softDropSpeed + 1); paulo@0: dst[pos++] = ' '; paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsZangiNames[v->control->softDropLock]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", Hard drop: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsZangiNames[v->control->hardDropLock]); paulo@0: pos += siprintf(dst + pos, "\nShadow: "); paulo@0: pos += printFourCC(dst + pos, paulo@0: optionsShadowNames[v->hideShadow]); paulo@0: pos += siprintf(dst + pos, paulo@0: ", Next: %d\n", paulo@0: v->nextPieces); paulo@0: #else paulo@0: pos += siprintf(dst, "\n\nDebrief disabled.\n"); paulo@0: #endif paulo@0: paulo@0: dst[pos] = 0; paulo@0: return pos; paulo@0: } paulo@0: paulo@0: #ifdef DEBRIEF_SHORT paulo@0: static const char *const naiveGravity[8] = { paulo@0: "1L", "2L", "3L", "4L", paulo@0: "T1", "T2", "T3" paulo@0: }; paulo@0: static const char *const cascadeGravity[8] = { paulo@0: "1L", "2L", "3L", "4L", paulo@0: "5L", "6L", "7L", "8L+" paulo@0: }; paulo@0: #else paulo@0: static const char *const naiveGravity[8] = { paulo@0: "single", "double", "triple", "home run", paulo@0: "T single", "T double", "T triple" paulo@0: }; paulo@0: static const char *const cascadeGravity[8] = { paulo@0: "single", "double", "triple", "quad", paulo@0: "5L", "6L", "7L", "8L+" paulo@0: }; paulo@0: #endif paulo@0: paulo@0: static size_t printLineCounts(char *dst, const LJField *p) { paulo@0: size_t pos = 0; paulo@0: const char *const *names = (p->clearGravity == LJGRAV_NAIVE) paulo@0: ? naiveGravity : cascadeGravity; paulo@0: dst[pos++] = '('; paulo@0: for (int i = 0; i < 8; ++i) { paulo@0: if (names[i]) { paulo@0: pos += siprintf(dst + pos, paulo@0: "%s%s: %u", paulo@0: i ? "; " : "", paulo@0: names[i], paulo@0: p->nLineClears[i]); paulo@0: } paulo@0: } paulo@0: dst[pos++] = ')'; paulo@0: dst[pos++] = '\n'; paulo@0: return pos; paulo@0: } paulo@0: paulo@0: paulo@0: size_t buildDebriefPage(char *dst, const LJView *v) { paulo@0: size_t pos = 0; paulo@0: paulo@0: #if USING_DEBRIEF_PAGE paulo@0: #ifndef NO_DATETIME paulo@0: const time_t finishTimeUNIX = time(NULL); paulo@0: const struct tm *finishTime = localtime(&finishTimeUNIX); paulo@0: char finishTimeStr[64]; paulo@0: paulo@0: /* I would have used paulo@0: strftime(finishTimeStr, sizeof(finishTimeStr), "%Y-%m-%d at %H:%M", finishTime); paulo@0: but it brings in the floating-point library on GBA/DS. */ paulo@0: siprintf(finishTimeStr, paulo@0: "%04d-%02d-%02d at %02d:%02d", paulo@0: finishTime->tm_year + 1900, paulo@0: finishTime->tm_mon + 1, finishTime->tm_mday, paulo@0: finishTime->tm_hour, finishTime->tm_min); paulo@0: #endif paulo@0: paulo@0: const LJField *p = v->field; paulo@0: unsigned long int keys_100m = p->nPieces paulo@0: ? 100U * v->control->presses / p->nPieces paulo@0: : 666; paulo@0: TimeResultMSF gameMSF, activeMSF; paulo@0: const char *wordPieces = (p->nPieces != 1) ? "tetrominoes" : "tetromino"; paulo@0: if (p->pieceSet == LJRAND_234BLK) { paulo@0: wordPieces = (p->nPieces != 1) ? "pieces" : "piece"; paulo@0: } paulo@0: paulo@0: /* Secret grades */ paulo@0: unsigned int nBlocksLeft = countBlocksLeft(p); paulo@0: unsigned int nZigzagRows = calcZigzagGrade(p); paulo@0: unsigned int nRectumRows = calcRectumGrade(p); paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "Result:\n\n%s ", paulo@0: v->control->countdown <= 0 ? "Cleared" : "Stopped"); paulo@0: pos += printFourCC(dst + pos, gimmickNames[p->gimmick]); paulo@0: pos += siprintf(dst + pos, paulo@0: " at level %d\n", paulo@0: p->speedState.level); paulo@0: #if !defined(NO_DATETIME) || defined(WITH_REPLAY) paulo@0: #ifndef NO_DATETIME paulo@0: pos += siprintf(dst + pos, "on %s", finishTimeStr); paulo@0: #endif paulo@0: #ifdef WITH_REPLAY paulo@0: if (p->reloaded) { paulo@0: pos += siprintf(dst + pos, " from saved state"); paulo@0: } paulo@0: #endif paulo@0: dst[pos++] = '\n'; paulo@0: #endif paulo@0: paulo@0: frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF); paulo@0: frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF); paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "Played %d %s in %u:%02u.%02u (%u.%02u/min)\n", paulo@0: p->nPieces, paulo@0: wordPieces, paulo@0: gameMSF.minutes, paulo@0: (unsigned int)gameMSF.seconds, paulo@0: gameMSF.frames * 5U / 3U, paulo@0: (unsigned int) (gameMSF.pieces_100m / 100), paulo@0: (unsigned int) (gameMSF.pieces_100m % 100)); paulo@0: pos += siprintf(dst + pos, paulo@0: "(active time only: %u:%02u.%02u, %u.%02u/min)\n", paulo@0: activeMSF.minutes, paulo@0: (unsigned int)activeMSF.seconds, paulo@0: activeMSF.frames * 5U / 3U, paulo@0: (unsigned int) (activeMSF.pieces_100m / 100), paulo@0: (unsigned int) (activeMSF.pieces_100m % 100)); paulo@0: pos += siprintf(dst + pos, paulo@0: "Pressed %u keys (%u.%02u/piece)\n\n", paulo@0: v->control->presses, paulo@0: (unsigned int) (keys_100m / 100), paulo@0: (unsigned int) (keys_100m % 100)); paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: "Made %u lines", paulo@0: p->lines); paulo@0: if (p->gluing == LJGLUING_SQUARE) { paulo@0: pos += siprintf(dst + pos, paulo@0: " and %u pure + %u squares", paulo@0: p->monosquares, p->multisquares); paulo@0: } paulo@0: dst[pos++] = '\n'; paulo@0: pos += printLineCounts(dst + pos, p); paulo@0: pos += siprintf(dst + pos, paulo@0: "Sent %u garbage (%u.%02u per minute)\n", paulo@0: p->outGarbage, paulo@0: (unsigned int) (gameMSF.garbage_100m / 100), paulo@0: (unsigned int) (gameMSF.garbage_100m % 100)); paulo@0: if (nZigzagRows >= MIN_ZIGZAG_ROWS) { paulo@0: pos += siprintf(dst + pos, paulo@0: "Made %u rows of > for grade ", paulo@0: nZigzagRows); paulo@0: pos += printSecretGrade(dst + pos, paulo@0: nZigzagRows >= 19 ? 100 : nZigzagRows); paulo@0: } else if (nRectumRows >= MIN_RECTUM_ROWS) { paulo@0: pos += siprintf(dst + pos, paulo@0: "Made %u-row rectum for grade ", paulo@0: nRectumRows); paulo@0: pos += printSecretGrade(dst + pos, paulo@0: nRectumRows >= 20 ? 100 : nRectumRows - 1); paulo@0: } else if (nBlocksLeft >= 110 && nBlocksLeft <= 111) { paulo@0: pos += siprintf(dst + pos, paulo@0: "Secret grade: Eleventy%s", paulo@0: (nBlocksLeft & 1) ? "-one" : ""); paulo@0: } else { paulo@0: pos += siprintf(dst + pos, paulo@0: "Left %d blocks behind", paulo@0: nBlocksLeft); paulo@0: } paulo@0: dst[pos++] = '\n'; paulo@0: dst[pos++] = '\n'; paulo@0: pos += printFourCC(dst + pos, optionsScoringNames[p->scoreStyle]); paulo@0: paulo@0: pos += siprintf(dst + pos, paulo@0: " score: %d\n", paulo@0: p->score); paulo@0: paulo@0: /* paulo@0: tod stats not in lj paulo@0: paulo@0: points/line, 40 lines score, silver squares, gold squares paulo@0: paulo@0: */ paulo@0: paulo@0: #else paulo@0: pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score); paulo@0: #endif paulo@0: return pos; paulo@0: } paulo@0: paulo@0: void debriefDrawPage(const char *page, size_t pageNumber); paulo@0: LJBits debriefHandleKeys(void); paulo@0: extern volatile char redrawWholeScreen; paulo@0: paulo@0: paulo@0: /** paulo@0: * Reports the player's performance. paulo@0: */ paulo@0: void debrief(LJView *v) { paulo@0: char pageData[2000]; // current length is under 1000 chars paulo@0: char *page[2] = {pageData, "Page coming soon!"}; paulo@0: int curPage = 0; paulo@0: paulo@0: { paulo@0: size_t pos; paulo@0: paulo@0: pos = buildDebriefPage(page[0], v); paulo@0: page[1] = page[0] + (++pos); paulo@0: pos += buildOptionsReportPage(page[1], v); paulo@0: } paulo@0: #ifdef HAS_FOPEN paulo@0: FILE *logFile = ljfopen("lj-scores.txt", "at"); paulo@0: if (logFile) { paulo@0: fputs("\n\n\n", logFile); paulo@0: fputs(page[0], logFile); paulo@0: fputs(page[1], logFile); paulo@0: #ifdef DEBRIEF_TO_STDOUT paulo@0: fputs(page[0], stdout); paulo@0: fputs(page[1], stdout); paulo@0: #endif paulo@0: fclose(logFile); paulo@0: } paulo@0: #endif paulo@0: paulo@0: LJBits lastKeys = ~0; paulo@0: redrawWholeScreen = 1; paulo@0: debriefHandleKeys(); // call once to clear key buffer paulo@0: paulo@0: for (;;) { paulo@0: LJBits sounds = 0; paulo@0: paulo@0: if (redrawWholeScreen) { paulo@0: redrawWholeScreen = 0; paulo@0: debriefDrawPage(page[curPage], curPage); paulo@0: } paulo@0: LJBits keys = debriefHandleKeys(); paulo@0: LJBits newKeys = keys & ~lastKeys; paulo@0: paulo@0: if (newKeys & VKEY_LEFT) { paulo@0: if (curPage > 0) { paulo@0: curPage -= 1; paulo@0: redrawWholeScreen = 1; paulo@0: sounds |= LJSND_HOLD; paulo@0: } paulo@0: } paulo@0: paulo@0: if (newKeys & VKEY_RIGHT) { paulo@0: if (curPage + 1 < sizeof(page) / sizeof(page[0])) { paulo@0: curPage += 1; paulo@0: redrawWholeScreen = 1; paulo@0: sounds |= LJSND_HOLD; paulo@0: } paulo@0: } paulo@0: paulo@0: if (newKeys & (VKEY_ROTR | VKEY_ROTL)) { paulo@0: break; paulo@0: } else { paulo@0: lastKeys = keys; paulo@0: } paulo@0: playSoundEffects(v, sounds, 100); paulo@0: } paulo@0: }