comparison src/debrief.c @ 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:18d9d109289f
1 /* PC frontend for LOCKJAW, an implementation of the Soviet Mind Game
2
3 Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
4
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.
9
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.
14
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
18
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.
22
23 */
24 #include "ljplay.h"
25 #include "options.h"
26 #include <stdio.h>
27 #ifndef NO_DATETIME
28 #include <time.h>
29 #endif
30
31 #define USING_DEBRIEF_PAGE 1
32 #define MIN_RECTUM_ROWS 4
33 #define MIN_ZIGZAG_ROWS 2
34
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
44
45 #ifdef HAS_FOPEN
46 #include "ljpath.h" // for ljfopen
47 #endif
48
49 const char *const debriefBoolNames[2] = {
50 "Off", "On"
51 };
52
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;
60
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 }
77
78 static unsigned int countBlocksLeft(const LJField *p) {
79 unsigned int n = 0;
80
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 }
90
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;
100
101 for (size_t y = 0; y < p->ceiling; ++y) {
102
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];
107
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) {
111
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 {
118
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 }
126
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 }
133
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;
142
143 }
144 return p->ceiling;
145 }
146
147
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;
163
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 }
173
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 }
181
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) {
185
186 // If this column isn't empty, the whole field doesn't conform.
187 if (p->b[y][hole]) {
188 return 0;
189 }
190 }
191
192 for (unsigned int y = 0; y < p->ceiling; ++y) {
193
194 // At this point, the bottom y rows conform.
195 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
196
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 }
206
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 }
221
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 }
231
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 }
249
250 size_t buildOptionsReportPage(char *dst, const LJView *v) {
251 size_t pos = 0;
252
253
254 #if USING_DEBRIEF_PAGE
255 const LJField *p = v->field;
256
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 }
290
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]);
307
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
352
353 dst[pos] = 0;
354 return pos;
355 }
356
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
376
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 }
395
396
397 size_t buildDebriefPage(char *dst, const LJView *v) {
398 size_t pos = 0;
399
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];
405
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
415
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 }
425
426 /* Secret grades */
427 unsigned int nBlocksLeft = countBlocksLeft(p);
428 unsigned int nZigzagRows = calcZigzagGrade(p);
429 unsigned int nRectumRows = calcRectumGrade(p);
430
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
449
450 frames2msf(p->gameTime, p->nPieces, p->outGarbage, &gameMSF);
451 frames2msf(p->activeTime, p->nPieces, p->outGarbage, &activeMSF);
452
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));
474
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]);
514
515 pos += siprintf(dst + pos,
516 " score: %d\n",
517 p->score);
518
519 /*
520 tod stats not in lj
521
522 points/line, 40 lines score, silver squares, gold squares
523
524 */
525
526 #else
527 pos += siprintf(dst, "\n\nDebrief disabled. Score: %u\n", v->field->score);
528 #endif
529 return pos;
530 }
531
532 void debriefDrawPage(const char *page, size_t pageNumber);
533 LJBits debriefHandleKeys(void);
534 extern volatile char redrawWholeScreen;
535
536
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;
544
545 {
546 size_t pos;
547
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
565
566 LJBits lastKeys = ~0;
567 redrawWholeScreen = 1;
568 debriefHandleKeys(); // call once to clear key buffer
569
570 for (;;) {
571 LJBits sounds = 0;
572
573 if (redrawWholeScreen) {
574 redrawWholeScreen = 0;
575 debriefDrawPage(page[curPage], curPage);
576 }
577 LJBits keys = debriefHandleKeys();
578 LJBits newKeys = keys & ~lastKeys;
579
580 if (newKeys & VKEY_LEFT) {
581 if (curPage > 0) {
582 curPage -= 1;
583 redrawWholeScreen = 1;
584 sounds |= LJSND_HOLD;
585 }
586 }
587
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 }
595
596 if (newKeys & (VKEY_ROTR | VKEY_ROTL)) {
597 break;
598 } else {
599 lastKeys = keys;
600 }
601 playSoundEffects(v, sounds, 100);
602 }
603 }