Mercurial > hg > index.fcgi > lj > lj046
diff src/debrief.c @ 0:c84446dfb3f5
initial add
author | paulo@localhost |
---|---|
date | Fri, 13 Mar 2009 00:39:12 -0700 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/debrief.c Fri Mar 13 00:39:12 2009 -0700 1.3 @@ -0,0 +1,603 @@ 1.4 +/* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game 1.5 + 1.6 +Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net> 1.7 + 1.8 +This work is free software; you can redistribute it and/or modify 1.9 +it under the terms of the GNU General Public License as published by 1.10 +the Free Software Foundation; either version 2 of the License, or 1.11 +(at your option) any later version. 1.12 + 1.13 +This program is distributed in the hope that it will be useful, 1.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of 1.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1.16 +GNU General Public License for more details. 1.17 + 1.18 +You should have received a copy of the GNU General Public License 1.19 +along with this program; if not, write to the Free Software 1.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 1.21 + 1.22 +Original game concept and design by Alexey Pajitnov. 1.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, 1.24 +or The Tetris Company LLC. 1.25 + 1.26 +*/ 1.27 +#include "ljplay.h" 1.28 +#include "options.h" 1.29 +#include <stdio.h> 1.30 +#ifndef NO_DATETIME 1.31 +#include <time.h> 1.32 +#endif 1.33 + 1.34 +#define USING_DEBRIEF_PAGE 1 1.35 +#define MIN_RECTUM_ROWS 4 1.36 +#define MIN_ZIGZAG_ROWS 2 1.37 + 1.38 +// On newlib, use a variant of sprintf that doesn't handle floating 1.39 +// point formats. On other C libraries, use standard sprintf. 1.40 +#ifdef HAS_FPU 1.41 +#define siprintf sprintf 1.42 +#ifndef HAS_FOPEN 1.43 +#define HAS_FOPEN 1.44 +#endif 1.45 +#define DEBRIEF_TO_STDOUT 1.46 +#endif 1.47 + 1.48 +#ifdef HAS_FOPEN 1.49 +#include "ljpath.h" // for ljfopen 1.50 +#endif 1.51 + 1.52 +const char *const debriefBoolNames[2] = { 1.53 + "Off", "On" 1.54 +}; 1.55 + 1.56 +typedef struct TimeResultMSF { 1.57 + unsigned int pieces_100m; 1.58 + unsigned int garbage_100m; 1.59 + unsigned int minutes; 1.60 + unsigned char seconds; 1.61 + unsigned char frames; 1.62 +} TimeResultMSF; 1.63 + 1.64 +void frames2msf(unsigned int gameTime, 1.65 + unsigned int nPieces, 1.66 + unsigned int outGarbage, 1.67 + TimeResultMSF *out) { 1.68 + unsigned int gameSeconds = gameTime / 60U; 1.69 + unsigned int gameMinutes = gameSeconds / 60U; 1.70 + out->pieces_100m = gameTime 1.71 + ? 360000ULL * nPieces / gameTime 1.72 + : 0; 1.73 + out->garbage_100m = gameTime 1.74 + ? 360000ULL * outGarbage / gameTime 1.75 + : 0; 1.76 + out->minutes = gameMinutes; 1.77 + out->seconds = gameSeconds - gameMinutes * 60U; 1.78 + out->frames = gameTime - gameSeconds * 60U; 1.79 +} 1.80 + 1.81 +static unsigned int countBlocksLeft(const LJField *p) { 1.82 + unsigned int n = 0; 1.83 + 1.84 + for (int y = 0; y < LJ_PF_HT; ++y) { 1.85 + for (int x = 0; x < LJ_PF_WID; ++x) { 1.86 + if (p->b[y][x]) { 1.87 + ++n; 1.88 + } 1.89 + } 1.90 + } 1.91 + return n; 1.92 +} 1.93 + 1.94 +/** 1.95 + * Calculates the number of rows starting at the bottom that 1.96 + * conform to a zigzag pattern. 1.97 + * @param p the playfield to test 1.98 + * @return the number of rows successfully completed 1.99 + */ 1.100 +unsigned int calcZigzagGrade(const LJField *p) { 1.101 + unsigned int hole = p->leftWall; 1.102 + int delta = 1; 1.103 + 1.104 + for (size_t y = 0; y < p->ceiling; ++y) { 1.105 + 1.106 + // invariant: 1.107 + // at this point, y equals the number of rows known to conform 1.108 + for (size_t x = p->leftWall; x < p->rightWall; ++x) { 1.109 + unsigned int blk = p->b[y][x]; 1.110 + 1.111 + // A block should be in all cells of the row 1.112 + // except for the hole cell, which should be empty. 1.113 + if (x == hole) { 1.114 + 1.115 + // if there's a block where a hole should be, this row 1.116 + // doesn't conform, but all the rows below do conform 1.117 + if (blk) { 1.118 + return y; 1.119 + } 1.120 + } else { 1.121 + 1.122 + // if there's a hole where a block should be, this row 1.123 + // doesn't conform, but all the rows below do conform 1.124 + if (!blk) { 1.125 + return y; 1.126 + } 1.127 + } 1.128 + } 1.129 + 1.130 + // if this hole isn't covered up on the next row, 1.131 + // this row doesn't conform either 1.132 + // changed in 0.43 after a clarification from Kitaru 1.133 + if (!p->b[y + 1][hole]) { 1.134 + return y; 1.135 + } 1.136 + 1.137 + // by now we know that the row conforms, 1.138 + // so move the hole for the next row 1.139 + if (hole == p->rightWall - 1) { 1.140 + delta = -1; 1.141 + } else if (hole == p->leftWall) { 1.142 + delta = 1; 1.143 + } 1.144 + hole += delta; 1.145 + 1.146 + } 1.147 + return p->ceiling; 1.148 +} 1.149 + 1.150 + 1.151 +/** 1.152 + * Calculates the number of rows in a playfield that were prepared 1.153 + * for an I tetromino. 1.154 + * Rule for this pattern: 1.155 + * 1. Only one hole on the bottom row. 1.156 + * 2. This column must be unoccupied below the ceiling. 1.157 + * 3. Conforming rows must be full except for the hole. 1.158 + * 1.159 + * Easy test procedure for this pattern: 1.160 + * Level 4 garbage 1.161 + * @return the number of rows that conform to constraint 3, or 0 if the 1.162 + * field does not conform to constraint 1 or 2. 1.163 + */ 1.164 +unsigned int calcRectumGrade(const LJField *p) { 1.165 + unsigned int hole = LJ_PF_WID; 1.166 + 1.167 + // search for the first hole on the bottom row 1.168 + for (unsigned int x = p->leftWall; 1.169 + x < p->rightWall; 1.170 + ++x) { 1.171 + if (!p->b[0][x]) { 1.172 + hole = x; 1.173 + break; 1.174 + } 1.175 + } 1.176 + 1.177 + // If there is no hole in the bottom row, then 0 rows conform. 1.178 + // This shouldn't happen in standard smg because the line would be 1.179 + // cleared, but eventually LJ will support games other than SMG, 1.180 + // and checking the array bounds is O(1), so why not? 1.181 + if (hole >= p->rightWall) { 1.182 + return 0; 1.183 + } 1.184 + 1.185 + // make sure that the row is clear through the whole visible 1.186 + // portion of the playfield 1.187 + for (unsigned int y = 0; y < p->ceiling; ++y) { 1.188 + 1.189 + // If this column isn't empty, the whole field doesn't conform. 1.190 + if (p->b[y][hole]) { 1.191 + return 0; 1.192 + } 1.193 + } 1.194 + 1.195 + for (unsigned int y = 0; y < p->ceiling; ++y) { 1.196 + 1.197 + // At this point, the bottom y rows conform. 1.198 + for (unsigned int x = p->leftWall; x < p->rightWall; ++x) { 1.199 + 1.200 + // Disregarding the hole column, if there's an empty cell 1.201 + // in this row, then this row does not conform. 1.202 + if (x != hole && !p->b[y][x]) { 1.203 + return y; 1.204 + } 1.205 + } 1.206 + } 1.207 + return p->ceiling; 1.208 +} 1.209 + 1.210 +/** 1.211 + * Converts a rank number (0-19) to a text rank. 1.212 + * 0-9: 10-1 kyu; 10-99: 1-90 dan; 100+: grandmaster 1.213 + * @return number of characters written 1.214 + */ 1.215 +static size_t printSecretGrade(char *dst, int rank) { 1.216 + if (rank >= 100) { 1.217 + return siprintf(dst, "GM"); 1.218 + } else if (rank >= 10) { 1.219 + return siprintf(dst, "S%d", rank - 9); 1.220 + } else { 1.221 + return siprintf(dst, "%d", 10 - rank); 1.222 + } 1.223 +} 1.224 + 1.225 +static size_t printFracG(char *dst, unsigned int value) { 1.226 + if (value == 0) { 1.227 + return siprintf(dst, "Instant"); 1.228 + } else if (value == 1) { 1.229 + return siprintf(dst, "1G"); 1.230 + } else { 1.231 + return siprintf(dst, "1/%dG", value); 1.232 + } 1.233 +} 1.234 + 1.235 +#include <string.h> // remove once fourcc transition is complete 1.236 +static size_t printFourCC(char *dst, FourCC f) { 1.237 + const char *data = ljGetFourCCName(f); 1.238 + if (data) { 1.239 + int pos = 0; 1.240 + for(pos = 0; *data; ++pos) { 1.241 + dst[pos] = *data++; 1.242 + } 1.243 + return pos; 1.244 + } else { 1.245 + int pos = 0; 1.246 + for(pos = 0; f.c[pos] && pos < 4; ++pos) { 1.247 + dst[pos] = f.c[pos]; 1.248 + } 1.249 + return pos; 1.250 + } 1.251 +} 1.252 + 1.253 +size_t buildOptionsReportPage(char *dst, const LJView *v) { 1.254 + size_t pos = 0; 1.255 + 1.256 + 1.257 +#if USING_DEBRIEF_PAGE 1.258 + const LJField *p = v->field; 1.259 + 1.260 + pos += siprintf(dst + pos, 1.261 + "Options\n\n" 1.262 + "Well: %dx%d%s, Enter: %s\n" 1.263 + "Speed: ", 1.264 + p->rightWall - p->leftWall, 1.265 + p->ceiling, 1.266 + v->hidePF ? " invisible" : "", 1.267 + p->enterAbove ? "Above" : "Below"); 1.268 + pos += printFourCC(dst + pos, 1.269 + optionsSpeedCurveNames[(size_t)p->speedState.curve]); 1.270 + pos += siprintf(dst + pos, 1.271 + ", ARE: %d ms\n" 1.272 + "Randomizer: ", 1.273 + v->field->areStyle * 50 / 3); 1.274 + pos += printFourCC(dst + pos, 1.275 + optionsRandNames[(size_t)p->randomizer]); 1.276 + pos += siprintf(dst + pos, " of "); 1.277 + pos += printFourCC(dst + pos, 1.278 + optionsPieceSetNames[(size_t)p->pieceSet]); 1.279 + pos += siprintf(dst + pos, "\nHold: "); 1.280 + pos += printFourCC(dst + pos, 1.281 + optionsHoldStyleNames[(size_t)p->holdStyle]); 1.282 + pos += siprintf(dst + pos, ", Rotation: "); 1.283 + pos += printFourCC(dst + pos, 1.284 + optionsRotNames[(size_t)p->rotationSystem]); 1.285 + if (p->maxUpwardKicks < 20) { 1.286 + pos += siprintf(dst + pos, 1.287 + " %d FK", 1.288 + (int)p->maxUpwardKicks); 1.289 + } 1.290 + if (v->control->initialRotate) { 1.291 + pos += siprintf(dst + pos, "+initial"); 1.292 + } 1.293 + 1.294 + pos += siprintf(dst + pos, 1.295 + "\nLock: "); 1.296 + if (p->setLockDelay >= 128) { 1.297 + pos += siprintf(dst + pos, "Never"); 1.298 + } else { 1.299 + if (p->setLockDelay > 0) { 1.300 + pos += siprintf(dst + pos, 1.301 + "%d ms ", 1.302 + p->setLockDelay * 50 / 3); 1.303 + } 1.304 + pos += printFourCC(dst + pos, 1.305 + optionsLockdownNames[(size_t)p->lockReset]); 1.306 + } 1.307 + pos += siprintf(dst + pos, 1.308 + ", Deep: %s\n", 1.309 + debriefBoolNames[p->bottomBlocks]); 1.310 + 1.311 + pos += siprintf(dst + pos, 1.312 + "Line clear: %d ms ", 1.313 + p->speed.lineDelay * 50 / 3); 1.314 + pos += printFourCC(dst + pos, 1.315 + optionsGravNames[(size_t)p->clearGravity]); 1.316 + pos += siprintf(dst + pos, 1.317 + ", Gluing: "); 1.318 + pos += printFourCC(dst + pos, 1.319 + optionsGluingNames[(size_t)p->gluing]); 1.320 + pos += siprintf(dst + pos, 1.321 + "\nDrop score: "); 1.322 + pos += printFourCC(dst + pos, 1.323 + optionsDropScoringNames[(size_t)p->dropScoreStyle]); 1.324 + pos += siprintf(dst + pos, 1.325 + ", T-spin: "); 1.326 + pos += printFourCC(dst + pos, 1.327 + optionsTspinNames[(size_t)p->tSpinAlgo]); 1.328 + pos += siprintf(dst + pos, 1.329 + "\nGarbage: "); 1.330 + pos += printFourCC(dst + pos, 1.331 + optionsGarbageNames[(size_t)p->garbageStyle]); 1.332 + pos += siprintf(dst + pos, 1.333 + ", DAS: %d ms ", 1.334 + v->control->dasDelay * 50 / 3); 1.335 + pos += printFracG(dst + pos, v->control->dasSpeed); 1.336 + pos += siprintf(dst + pos, 1.337 + "\nSoft drop: "); 1.338 + pos += printFracG(dst + pos, v->control->softDropSpeed + 1); 1.339 + dst[pos++] = ' '; 1.340 + pos += printFourCC(dst + pos, 1.341 + optionsZangiNames[v->control->softDropLock]); 1.342 + pos += siprintf(dst + pos, 1.343 + ", Hard drop: "); 1.344 + pos += printFourCC(dst + pos, 1.345 + optionsZangiNames[v->control->hardDropLock]); 1.346 + pos += siprintf(dst + pos, "\nShadow: "); 1.347 + pos += printFourCC(dst + pos, 1.348 + optionsShadowNames[v->hideShadow]); 1.349 + pos += siprintf(dst + pos, 1.350 + ", Next: %d\n", 1.351 + v->nextPieces); 1.352 +#else 1.353 + pos += siprintf(dst, "\n\nDebrief disabled.\n"); 1.354 +#endif 1.355 + 1.356 + dst[pos] = 0; 1.357 + return pos; 1.358 +} 1.359 + 1.360 +#ifdef DEBRIEF_SHORT 1.361 +static const char *const naiveGravity[8] = { 1.362 + "1L", "2L", "3L", "4L", 1.363 + "T1", "T2", "T3" 1.364 +}; 1.365 +static const char *const cascadeGravity[8] = { 1.366 + "1L", "2L", "3L", "4L", 1.367 + "5L", "6L", "7L", "8L+" 1.368 +}; 1.369 +#else 1.370 +static const char *const naiveGravity[8] = { 1.371 + "single", "double", "triple", "home run", 1.372 + "T single", "T double", "T triple" 1.373 +}; 1.374 +static const char *const cascadeGravity[8] = { 1.375 + "single", "double", "triple", "quad", 1.376 + "5L", "6L", "7L", "8L+" 1.377 +}; 1.378 +#endif 1.379 + 1.380 +static size_t printLineCounts(char *dst, const LJField *p) { 1.381 + size_t pos = 0; 1.382 + const char *const *names = (p->clearGravity == LJGRAV_NAIVE) 1.383 + ? naiveGravity : cascadeGravity; 1.384 + dst[pos++] = '('; 1.385 + for (int i = 0; i < 8; ++i) { 1.386 + if (names[i]) { 1.387 + pos += siprintf(dst + pos, 1.388 + "%s%s: %u", 1.389 + i ? "; " : "", 1.390 + names[i], 1.391 + p->nLineClears[i]); 1.392 + } 1.393 + } 1.394 + dst[pos++] = ')'; 1.395 + dst[pos++] = '\n'; 1.396 + return pos; 1.397 +} 1.398 + 1.399 + 1.400 +size_t buildDebriefPage(char *dst, const LJView *v) { 1.401 + size_t pos = 0; 1.402 + 1.403 +#if USING_DEBRIEF_PAGE 1.404 +#ifndef NO_DATETIME 1.405 + const time_t finishTimeUNIX = time(NULL); 1.406 + const struct tm *finishTime = localtime(&finishTimeUNIX); 1.407 + char finishTimeStr[64]; 1.408 + 1.409 + /* I would have used 1.410 + strftime(finishTimeStr, sizeof(finishTimeStr), "%Y-%m-%d at %H:%M", finishTime); 1.411 + but it brings in the floating-point library on GBA/DS. */ 1.412 + siprintf(finishTimeStr, 1.413 + "%04d-%02d-%02d at %02d:%02d", 1.414 + finishTime->tm_year + 1900, 1.415 + finishTime->tm_mon + 1, finishTime->tm_mday, 1.416 + finishTime->tm_hour, finishTime->tm_min); 1.417 +#endif 1.418 + 1.419 + const LJField *p = v->field; 1.420 + unsigned long int keys_100m = p->nPieces 1.421 + ? 100U * v->control->presses / p->nPieces 1.422 + : 666; 1.423 + TimeResultMSF gameMSF, activeMSF; 1.424 + const char *wordPieces = (p->nPieces != 1) ? "tetrominoes" : "tetromino"; 1.425 + if (p->pieceSet == LJRAND_234BLK) { 1.426 + wordPieces = (p->nPieces != 1) ? "pieces" : "piece"; 1.427 + } 1.428 + 1.429 + /* Secret grades */ 1.430 + unsigned int nBlocksLeft = countBlocksLeft(p); 1.431 + unsigned int nZigzagRows = calcZigzagGrade(p); 1.432 + unsigned int nRectumRows = calcRectumGrade(p); 1.433 + 1.434 + pos += siprintf(dst + pos, 1.435 + "Result:\n\n%s ", 1.436 + v->control->countdown <= 0 ? "Cleared" : "Stopped"); 1.437 + pos += printFourCC(dst + pos, gimmickNames[p->gimmick]); 1.438 + pos += siprintf(dst + pos, 1.439 + " at level %d\n", 1.440 + p->speedState.level); 1.441 +#if !defined(NO_DATETIME) || defined(WITH_REPLAY) 1.442 +#ifndef NO_DATETIME 1.443 + pos += siprintf(dst + pos, "on %s", finishTimeStr); 1.444 +#endif 1.445 +#ifdef WITH_REPLAY 1.446 + if (p->reloaded) { 1.447 + pos += siprintf(dst + pos, " from saved state"); 1.448 + } 1.449 +#endif 1.450 + dst[pos++] = '\n'; 1.451 +#endif 1.452 + 1.453 + frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF); 1.454 + frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF); 1.455 + 1.456 + pos += siprintf(dst + pos, 1.457 + "Played %d %s in %u:%02u.%02u (%u.%02u/min)\n", 1.458 + p->nPieces, 1.459 + wordPieces, 1.460 + gameMSF.minutes, 1.461 + (unsigned int)gameMSF.seconds, 1.462 + gameMSF.frames * 5U / 3U, 1.463 + (unsigned int) (gameMSF.pieces_100m / 100), 1.464 + (unsigned int) (gameMSF.pieces_100m % 100)); 1.465 + pos += siprintf(dst + pos, 1.466 + "(active time only: %u:%02u.%02u, %u.%02u/min)\n", 1.467 + activeMSF.minutes, 1.468 + (unsigned int)activeMSF.seconds, 1.469 + activeMSF.frames * 5U / 3U, 1.470 + (unsigned int) (activeMSF.pieces_100m / 100), 1.471 + (unsigned int) (activeMSF.pieces_100m % 100)); 1.472 + pos += siprintf(dst + pos, 1.473 + "Pressed %u keys (%u.%02u/piece)\n\n", 1.474 + v->control->presses, 1.475 + (unsigned int) (keys_100m / 100), 1.476 + (unsigned int) (keys_100m % 100)); 1.477 + 1.478 + pos += siprintf(dst + pos, 1.479 + "Made %u lines", 1.480 + p->lines); 1.481 + if (p->gluing == LJGLUING_SQUARE) { 1.482 + pos += siprintf(dst + pos, 1.483 + " and %u pure + %u squares", 1.484 + p->monosquares, p->multisquares); 1.485 + } 1.486 + dst[pos++] = '\n'; 1.487 + pos += printLineCounts(dst + pos, p); 1.488 + pos += siprintf(dst + pos, 1.489 + "Sent %u garbage (%u.%02u per minute)\n", 1.490 + p->outGarbage, 1.491 + (unsigned int) (gameMSF.garbage_100m / 100), 1.492 + (unsigned int) (gameMSF.garbage_100m % 100)); 1.493 + if (nZigzagRows >= MIN_ZIGZAG_ROWS) { 1.494 + pos += siprintf(dst + pos, 1.495 + "Made %u rows of > for grade ", 1.496 + nZigzagRows); 1.497 + pos += printSecretGrade(dst + pos, 1.498 + nZigzagRows >= 19 ? 100 : nZigzagRows); 1.499 + } else if (nRectumRows >= MIN_RECTUM_ROWS) { 1.500 + pos += siprintf(dst + pos, 1.501 + "Made %u-row rectum for grade ", 1.502 + nRectumRows); 1.503 + pos += printSecretGrade(dst + pos, 1.504 + nRectumRows >= 20 ? 100 : nRectumRows - 1); 1.505 + } else if (nBlocksLeft >= 110 && nBlocksLeft <= 111) { 1.506 + pos += siprintf(dst + pos, 1.507 + "Secret grade: Eleventy%s", 1.508 + (nBlocksLeft & 1) ? "-one" : ""); 1.509 + } else { 1.510 + pos += siprintf(dst + pos, 1.511 + "Left %d blocks behind", 1.512 + nBlocksLeft); 1.513 + } 1.514 + dst[pos++] = '\n'; 1.515 + dst[pos++] = '\n'; 1.516 + pos += printFourCC(dst + pos, optionsScoringNames[p->scoreStyle]); 1.517 + 1.518 + pos += siprintf(dst + pos, 1.519 + " score: %d\n", 1.520 + p->score); 1.521 + 1.522 + /* 1.523 + tod stats not in lj 1.524 + 1.525 + points/line, 40 lines score, silver squares, gold squares 1.526 + 1.527 + */ 1.528 + 1.529 +#else 1.530 + pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score); 1.531 +#endif 1.532 + return pos; 1.533 +} 1.534 + 1.535 +void debriefDrawPage(const char *page, size_t pageNumber); 1.536 +LJBits debriefHandleKeys(void); 1.537 +extern volatile char redrawWholeScreen; 1.538 + 1.539 + 1.540 +/** 1.541 + * Reports the player's performance. 1.542 + */ 1.543 +void debrief(LJView *v) { 1.544 + char pageData[2000]; // current length is under 1000 chars 1.545 + char *page[2] = {pageData, "Page coming soon!"}; 1.546 + int curPage = 0; 1.547 + 1.548 + { 1.549 + size_t pos; 1.550 + 1.551 + pos = buildDebriefPage(page[0], v); 1.552 + page[1] = page[0] + (++pos); 1.553 + pos += buildOptionsReportPage(page[1], v); 1.554 + } 1.555 +#ifdef HAS_FOPEN 1.556 + FILE *logFile = ljfopen("lj-scores.txt", "at"); 1.557 + if (logFile) { 1.558 + fputs("\n\n\n", logFile); 1.559 + fputs(page[0], logFile); 1.560 + fputs(page[1], logFile); 1.561 +#ifdef DEBRIEF_TO_STDOUT 1.562 + fputs(page[0], stdout); 1.563 + fputs(page[1], stdout); 1.564 +#endif 1.565 + fclose(logFile); 1.566 + } 1.567 +#endif 1.568 + 1.569 + LJBits lastKeys = ~0; 1.570 + redrawWholeScreen = 1; 1.571 + debriefHandleKeys(); // call once to clear key buffer 1.572 + 1.573 + for (;;) { 1.574 + LJBits sounds = 0; 1.575 + 1.576 + if (redrawWholeScreen) { 1.577 + redrawWholeScreen = 0; 1.578 + debriefDrawPage(page[curPage], curPage); 1.579 + } 1.580 + LJBits keys = debriefHandleKeys(); 1.581 + LJBits newKeys = keys & ~lastKeys; 1.582 + 1.583 + if (newKeys & VKEY_LEFT) { 1.584 + if (curPage > 0) { 1.585 + curPage -= 1; 1.586 + redrawWholeScreen = 1; 1.587 + sounds |= LJSND_HOLD; 1.588 + } 1.589 + } 1.590 + 1.591 + if (newKeys & VKEY_RIGHT) { 1.592 + if (curPage + 1 < sizeof(page) / sizeof(page[0])) { 1.593 + curPage += 1; 1.594 + redrawWholeScreen = 1; 1.595 + sounds |= LJSND_HOLD; 1.596 + } 1.597 + } 1.598 + 1.599 + if (newKeys & (VKEY_ROTR | VKEY_ROTL)) { 1.600 + break; 1.601 + } else { 1.602 + lastKeys = keys; 1.603 + } 1.604 + playSoundEffects(v, sounds, 100); 1.605 + } 1.606 +}