Mercurial > hg > index.fcgi > lj > lj046-2players
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 } |