view src/debrief.c @ 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children
line source
1 /* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
3 Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
5 This work is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 Original game concept and design by Alexey Pajitnov.
20 The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
21 or The Tetris Company LLC.
23 */
24 #include "ljplay.h"
25 #include "options.h"
26 #include <stdio.h>
27 #ifndef NO_DATETIME
28 #include <time.h>
29 #endif
31 #define USING_DEBRIEF_PAGE 1
32 #define MIN_RECTUM_ROWS 4
33 #define MIN_ZIGZAG_ROWS 2
35 // On newlib, use a variant of sprintf that doesn't handle floating
36 // point formats. On other C libraries, use standard sprintf.
37 #ifdef HAS_FPU
38 #define siprintf sprintf
39 #ifndef HAS_FOPEN
40 #define HAS_FOPEN
41 #endif
42 #define DEBRIEF_TO_STDOUT
43 #endif
45 #ifdef HAS_FOPEN
46 #include "ljpath.h" // for ljfopen
47 #endif
49 const char *const debriefBoolNames[2] = {
50 "Off", "On"
51 };
53 typedef struct TimeResultMSF {
54 unsigned int pieces_100m;
55 unsigned int garbage_100m;
56 unsigned int minutes;
57 unsigned char seconds;
58 unsigned char frames;
59 } TimeResultMSF;
61 void frames2msf(unsigned int gameTime,
62 unsigned int nPieces,
63 unsigned int outGarbage,
64 TimeResultMSF *out) {
65 unsigned int gameSeconds = gameTime / 60U;
66 unsigned int gameMinutes = gameSeconds / 60U;
67 out->pieces_100m = gameTime
68 ? 360000ULL * nPieces / gameTime
69 : 0;
70 out->garbage_100m = gameTime
71 ? 360000ULL * outGarbage / gameTime
72 : 0;
73 out->minutes = gameMinutes;
74 out->seconds = gameSeconds - gameMinutes * 60U;
75 out->frames = gameTime - gameSeconds * 60U;
76 }
78 static unsigned int countBlocksLeft(const LJField *p) {
79 unsigned int n = 0;
81 for (int y = 0; y < LJ_PF_HT; ++y) {
82 for (int x = 0; x < LJ_PF_WID; ++x) {
83 if (p->b[y][x]) {
84 ++n;
85 }
86 }
87 }
88 return n;
89 }
91 /**
92 * Calculates the number of rows starting at the bottom that
93 * conform to a zigzag pattern.
94 * @param p the playfield to test
95 * @return the number of rows successfully completed
96 */
97 unsigned int calcZigzagGrade(const LJField *p) {
98 unsigned int hole = p->leftWall;
99 int delta = 1;
101 for (size_t y = 0; y < p->ceiling; ++y) {
103 // invariant:
104 // at this point, y equals the number of rows known to conform
105 for (size_t x = p->leftWall; x < p->rightWall; ++x) {
106 unsigned int blk = p->b[y][x];
108 // A block should be in all cells of the row
109 // except for the hole cell, which should be empty.
110 if (x == hole) {
112 // if there's a block where a hole should be, this row
113 // doesn't conform, but all the rows below do conform
114 if (blk) {
115 return y;
116 }
117 } else {
119 // if there's a hole where a block should be, this row
120 // doesn't conform, but all the rows below do conform
121 if (!blk) {
122 return y;
123 }
124 }
125 }
127 // if this hole isn't covered up on the next row,
128 // this row doesn't conform either
129 // changed in 0.43 after a clarification from Kitaru
130 if (!p->b[y + 1][hole]) {
131 return y;
132 }
134 // by now we know that the row conforms,
135 // so move the hole for the next row
136 if (hole == p->rightWall - 1) {
137 delta = -1;
138 } else if (hole == p->leftWall) {
139 delta = 1;
140 }
141 hole += delta;
143 }
144 return p->ceiling;
145 }
148 /**
149 * Calculates the number of rows in a playfield that were prepared
150 * for an I tetromino.
151 * Rule for this pattern:
152 * 1. Only one hole on the bottom row.
153 * 2. This column must be unoccupied below the ceiling.
154 * 3. Conforming rows must be full except for the hole.
155 *
156 * Easy test procedure for this pattern:
157 * Level 4 garbage
158 * @return the number of rows that conform to constraint 3, or 0 if the
159 * field does not conform to constraint 1 or 2.
160 */
161 unsigned int calcRectumGrade(const LJField *p) {
162 unsigned int hole = LJ_PF_WID;
164 // search for the first hole on the bottom row
165 for (unsigned int x = p->leftWall;
166 x < p->rightWall;
167 ++x) {
168 if (!p->b[0][x]) {
169 hole = x;
170 break;
171 }
172 }
174 // If there is no hole in the bottom row, then 0 rows conform.
175 // This shouldn't happen in standard smg because the line would be
176 // cleared, but eventually LJ will support games other than SMG,
177 // and checking the array bounds is O(1), so why not?
178 if (hole >= p->rightWall) {
179 return 0;
180 }
182 // make sure that the row is clear through the whole visible
183 // portion of the playfield
184 for (unsigned int y = 0; y < p->ceiling; ++y) {
186 // If this column isn't empty, the whole field doesn't conform.
187 if (p->b[y][hole]) {
188 return 0;
189 }
190 }
192 for (unsigned int y = 0; y < p->ceiling; ++y) {
194 // At this point, the bottom y rows conform.
195 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
197 // Disregarding the hole column, if there's an empty cell
198 // in this row, then this row does not conform.
199 if (x != hole && !p->b[y][x]) {
200 return y;
201 }
202 }
203 }
204 return p->ceiling;
205 }
207 /**
208 * Converts a rank number (0-19) to a text rank.
209 * 0-9: 10-1 kyu; 10-99: 1-90 dan; 100+: grandmaster
210 * @return number of characters written
211 */
212 static size_t printSecretGrade(char *dst, int rank) {
213 if (rank >= 100) {
214 return siprintf(dst, "GM");
215 } else if (rank >= 10) {
216 return siprintf(dst, "S%d", rank - 9);
217 } else {
218 return siprintf(dst, "%d", 10 - rank);
219 }
220 }
222 static size_t printFracG(char *dst, unsigned int value) {
223 if (value == 0) {
224 return siprintf(dst, "Instant");
225 } else if (value == 1) {
226 return siprintf(dst, "1G");
227 } else {
228 return siprintf(dst, "1/%dG", value);
229 }
230 }
232 #include <string.h> // remove once fourcc transition is complete
233 static size_t printFourCC(char *dst, FourCC f) {
234 const char *data = ljGetFourCCName(f);
235 if (data) {
236 int pos = 0;
237 for(pos = 0; *data; ++pos) {
238 dst[pos] = *data++;
239 }
240 return pos;
241 } else {
242 int pos = 0;
243 for(pos = 0; f.c[pos] && pos < 4; ++pos) {
244 dst[pos] = f.c[pos];
245 }
246 return pos;
247 }
248 }
250 size_t buildOptionsReportPage(char *dst, const LJView *v) {
251 size_t pos = 0;
254 #if USING_DEBRIEF_PAGE
255 const LJField *p = v->field;
257 pos += siprintf(dst + pos,
258 "Options\n\n"
259 "Well: %dx%d%s, Enter: %s\n"
260 "Speed: ",
261 p->rightWall - p->leftWall,
262 p->ceiling,
263 v->hidePF ? " invisible" : "",
264 p->enterAbove ? "Above" : "Below");
265 pos += printFourCC(dst + pos,
266 optionsSpeedCurveNames[(size_t)p->speedState.curve]);
267 pos += siprintf(dst + pos,
268 ", ARE: %d ms\n"
269 "Randomizer: ",
270 v->field->areStyle * 50 / 3);
271 pos += printFourCC(dst + pos,
272 optionsRandNames[(size_t)p->randomizer]);
273 pos += siprintf(dst + pos, " of ");
274 pos += printFourCC(dst + pos,
275 optionsPieceSetNames[(size_t)p->pieceSet]);
276 pos += siprintf(dst + pos, "\nHold: ");
277 pos += printFourCC(dst + pos,
278 optionsHoldStyleNames[(size_t)p->holdStyle]);
279 pos += siprintf(dst + pos, ", Rotation: ");
280 pos += printFourCC(dst + pos,
281 optionsRotNames[(size_t)p->rotationSystem]);
282 if (p->maxUpwardKicks < 20) {
283 pos += siprintf(dst + pos,
284 " %d FK",
285 (int)p->maxUpwardKicks);
286 }
287 if (v->control->initialRotate) {
288 pos += siprintf(dst + pos, "+initial");
289 }
291 pos += siprintf(dst + pos,
292 "\nLock: ");
293 if (p->setLockDelay >= 128) {
294 pos += siprintf(dst + pos, "Never");
295 } else {
296 if (p->setLockDelay > 0) {
297 pos += siprintf(dst + pos,
298 "%d ms ",
299 p->setLockDelay * 50 / 3);
300 }
301 pos += printFourCC(dst + pos,
302 optionsLockdownNames[(size_t)p->lockReset]);
303 }
304 pos += siprintf(dst + pos,
305 ", Deep: %s\n",
306 debriefBoolNames[p->bottomBlocks]);
308 pos += siprintf(dst + pos,
309 "Line clear: %d ms ",
310 p->speed.lineDelay * 50 / 3);
311 pos += printFourCC(dst + pos,
312 optionsGravNames[(size_t)p->clearGravity]);
313 pos += siprintf(dst + pos,
314 ", Gluing: ");
315 pos += printFourCC(dst + pos,
316 optionsGluingNames[(size_t)p->gluing]);
317 pos += siprintf(dst + pos,
318 "\nDrop score: ");
319 pos += printFourCC(dst + pos,
320 optionsDropScoringNames[(size_t)p->dropScoreStyle]);
321 pos += siprintf(dst + pos,
322 ", T-spin: ");
323 pos += printFourCC(dst + pos,
324 optionsTspinNames[(size_t)p->tSpinAlgo]);
325 pos += siprintf(dst + pos,
326 "\nGarbage: ");
327 pos += printFourCC(dst + pos,
328 optionsGarbageNames[(size_t)p->garbageStyle]);
329 pos += siprintf(dst + pos,
330 ", DAS: %d ms ",
331 v->control->dasDelay * 50 / 3);
332 pos += printFracG(dst + pos, v->control->dasSpeed);
333 pos += siprintf(dst + pos,
334 "\nSoft drop: ");
335 pos += printFracG(dst + pos, v->control->softDropSpeed + 1);
336 dst[pos++] = ' ';
337 pos += printFourCC(dst + pos,
338 optionsZangiNames[v->control->softDropLock]);
339 pos += siprintf(dst + pos,
340 ", Hard drop: ");
341 pos += printFourCC(dst + pos,
342 optionsZangiNames[v->control->hardDropLock]);
343 pos += siprintf(dst + pos, "\nShadow: ");
344 pos += printFourCC(dst + pos,
345 optionsShadowNames[v->hideShadow]);
346 pos += siprintf(dst + pos,
347 ", Next: %d\n",
348 v->nextPieces);
349 #else
350 pos += siprintf(dst, "\n\nDebrief disabled.\n");
351 #endif
353 dst[pos] = 0;
354 return pos;
355 }
357 #ifdef DEBRIEF_SHORT
358 static const char *const naiveGravity[8] = {
359 "1L", "2L", "3L", "4L",
360 "T1", "T2", "T3"
361 };
362 static const char *const cascadeGravity[8] = {
363 "1L", "2L", "3L", "4L",
364 "5L", "6L", "7L", "8L+"
365 };
366 #else
367 static const char *const naiveGravity[8] = {
368 "single", "double", "triple", "home run",
369 "T single", "T double", "T triple"
370 };
371 static const char *const cascadeGravity[8] = {
372 "single", "double", "triple", "quad",
373 "5L", "6L", "7L", "8L+"
374 };
375 #endif
377 static size_t printLineCounts(char *dst, const LJField *p) {
378 size_t pos = 0;
379 const char *const *names = (p->clearGravity == LJGRAV_NAIVE)
380 ? naiveGravity : cascadeGravity;
381 dst[pos++] = '(';
382 for (int i = 0; i < 8; ++i) {
383 if (names[i]) {
384 pos += siprintf(dst + pos,
385 "%s%s: %u",
386 i ? "; " : "",
387 names[i],
388 p->nLineClears[i]);
389 }
390 }
391 dst[pos++] = ')';
392 dst[pos++] = '\n';
393 return pos;
394 }
397 size_t buildDebriefPage(char *dst, const LJView *v) {
398 size_t pos = 0;
400 #if USING_DEBRIEF_PAGE
401 #ifndef NO_DATETIME
402 const time_t finishTimeUNIX = time(NULL);
403 const struct tm *finishTime = localtime(&finishTimeUNIX);
404 char finishTimeStr[64];
406 /* I would have used
407 strftime(finishTimeStr, sizeof(finishTimeStr), "%Y-%m-%d at %H:%M", finishTime);
408 but it brings in the floating-point library on GBA/DS. */
409 siprintf(finishTimeStr,
410 "%04d-%02d-%02d at %02d:%02d",
411 finishTime->tm_year + 1900,
412 finishTime->tm_mon + 1, finishTime->tm_mday,
413 finishTime->tm_hour, finishTime->tm_min);
414 #endif
416 const LJField *p = v->field;
417 unsigned long int keys_100m = p->nPieces
418 ? 100U * v->control->presses / p->nPieces
419 : 666;
420 TimeResultMSF gameMSF, activeMSF;
421 const char *wordPieces = (p->nPieces != 1) ? "tetrominoes" : "tetromino";
422 if (p->pieceSet == LJRAND_234BLK) {
423 wordPieces = (p->nPieces != 1) ? "pieces" : "piece";
424 }
426 /* Secret grades */
427 unsigned int nBlocksLeft = countBlocksLeft(p);
428 unsigned int nZigzagRows = calcZigzagGrade(p);
429 unsigned int nRectumRows = calcRectumGrade(p);
431 pos += siprintf(dst + pos,
432 "Result:\n\n%s ",
433 v->control->countdown <= 0 ? "Cleared" : "Stopped");
434 pos += printFourCC(dst + pos, gimmickNames[p->gimmick]);
435 pos += siprintf(dst + pos,
436 " at level %d\n",
437 p->speedState.level);
438 #if !defined(NO_DATETIME) || defined(WITH_REPLAY)
439 #ifndef NO_DATETIME
440 pos += siprintf(dst + pos, "on %s", finishTimeStr);
441 #endif
442 #ifdef WITH_REPLAY
443 if (p->reloaded) {
444 pos += siprintf(dst + pos, " from saved state");
445 }
446 #endif
447 dst[pos++] = '\n';
448 #endif
450 frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF);
451 frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF);
453 pos += siprintf(dst + pos,
454 "Played %d %s in %u:%02u.%02u (%u.%02u/min)\n",
455 p->nPieces,
456 wordPieces,
457 gameMSF.minutes,
458 (unsigned int)gameMSF.seconds,
459 gameMSF.frames * 5U / 3U,
460 (unsigned int) (gameMSF.pieces_100m / 100),
461 (unsigned int) (gameMSF.pieces_100m % 100));
462 pos += siprintf(dst + pos,
463 "(active time only: %u:%02u.%02u, %u.%02u/min)\n",
464 activeMSF.minutes,
465 (unsigned int)activeMSF.seconds,
466 activeMSF.frames * 5U / 3U,
467 (unsigned int) (activeMSF.pieces_100m / 100),
468 (unsigned int) (activeMSF.pieces_100m % 100));
469 pos += siprintf(dst + pos,
470 "Pressed %u keys (%u.%02u/piece)\n\n",
471 v->control->presses,
472 (unsigned int) (keys_100m / 100),
473 (unsigned int) (keys_100m % 100));
475 pos += siprintf(dst + pos,
476 "Made %u lines",
477 p->lines);
478 if (p->gluing == LJGLUING_SQUARE) {
479 pos += siprintf(dst + pos,
480 " and %u pure + %u squares",
481 p->monosquares, p->multisquares);
482 }
483 dst[pos++] = '\n';
484 pos += printLineCounts(dst + pos, p);
485 pos += siprintf(dst + pos,
486 "Sent %u garbage (%u.%02u per minute)\n",
487 p->outGarbage,
488 (unsigned int) (gameMSF.garbage_100m / 100),
489 (unsigned int) (gameMSF.garbage_100m % 100));
490 if (nZigzagRows >= MIN_ZIGZAG_ROWS) {
491 pos += siprintf(dst + pos,
492 "Made %u rows of > for grade ",
493 nZigzagRows);
494 pos += printSecretGrade(dst + pos,
495 nZigzagRows >= 19 ? 100 : nZigzagRows);
496 } else if (nRectumRows >= MIN_RECTUM_ROWS) {
497 pos += siprintf(dst + pos,
498 "Made %u-row rectum for grade ",
499 nRectumRows);
500 pos += printSecretGrade(dst + pos,
501 nRectumRows >= 20 ? 100 : nRectumRows - 1);
502 } else if (nBlocksLeft >= 110 && nBlocksLeft <= 111) {
503 pos += siprintf(dst + pos,
504 "Secret grade: Eleventy%s",
505 (nBlocksLeft & 1) ? "-one" : "");
506 } else {
507 pos += siprintf(dst + pos,
508 "Left %d blocks behind",
509 nBlocksLeft);
510 }
511 dst[pos++] = '\n';
512 dst[pos++] = '\n';
513 pos += printFourCC(dst + pos, optionsScoringNames[p->scoreStyle]);
515 pos += siprintf(dst + pos,
516 " score: %d\n",
517 p->score);
519 /*
520 tod stats not in lj
522 points/line, 40 lines score, silver squares, gold squares
524 */
526 #else
527 pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score);
528 #endif
529 return pos;
530 }
532 void debriefDrawPage(const char *page, size_t pageNumber);
533 LJBits debriefHandleKeys(void);
534 extern volatile char redrawWholeScreen;
537 /**
538 * Reports the player's performance.
539 */
540 void debrief(LJView *v) {
541 char pageData[2000]; // current length is under 1000 chars
542 char *page[2] = {pageData, "Page coming soon!"};
543 int curPage = 0;
545 {
546 size_t pos;
548 pos = buildDebriefPage(page[0], v);
549 page[1] = page[0] + (++pos);
550 pos += buildOptionsReportPage(page[1], v);
551 }
552 #ifdef HAS_FOPEN
553 FILE *logFile = ljfopen("lj-scores.txt", "at");
554 if (logFile) {
555 fputs("\n\n\n", logFile);
556 fputs(page[0], logFile);
557 fputs(page[1], logFile);
558 #ifdef DEBRIEF_TO_STDOUT
559 fputs(page[0], stdout);
560 fputs(page[1], stdout);
561 #endif
562 fclose(logFile);
563 }
564 #endif
566 LJBits lastKeys = ~0;
567 redrawWholeScreen = 1;
568 debriefHandleKeys(); // call once to clear key buffer
570 for (;;) {
571 LJBits sounds = 0;
573 if (redrawWholeScreen) {
574 redrawWholeScreen = 0;
575 debriefDrawPage(page[curPage], curPage);
576 }
577 LJBits keys = debriefHandleKeys();
578 LJBits newKeys = keys & ~lastKeys;
580 if (newKeys & VKEY_LEFT) {
581 if (curPage > 0) {
582 curPage -= 1;
583 redrawWholeScreen = 1;
584 sounds |= LJSND_HOLD;
585 }
586 }
588 if (newKeys & VKEY_RIGHT) {
589 if (curPage + 1 < sizeof(page) / sizeof(page[0])) {
590 curPage += 1;
591 redrawWholeScreen = 1;
592 sounds |= LJSND_HOLD;
593 }
594 }
596 if (newKeys & (VKEY_ROTR | VKEY_ROTL)) {
597 break;
598 } else {
599 lastKeys = keys;
600 }
601 playSoundEffects(v, sounds, 100);
602 }
603 }