ft2_nibbles.c (24796B)
1 #include <stdint.h> 2 #include <stdio.h> 3 #include <math.h> // round() 4 #include "ft2_keyboard.h" 5 #include "ft2_config.h" 6 #include "ft2_video.h" 7 #include "ft2_gui.h" 8 #include "ft2_pattern_ed.h" 9 #include "ft2_bmp.h" 10 #include "ft2_tables.h" 11 #include "ft2_structs.h" 12 13 #define STAGES_BMP_WIDTH 530 14 #define NI_MAXLEVEL 30 15 16 static const char *NI_HelpText[] = 17 { 18 "Player 1 uses cursor keys to control movement.", 19 "Player 2 uses the following keys:", 20 "", 21 " (W=Up)", 22 " (A=Left) (S=Down) (D=Right)", 23 "", 24 "The \"Wrap\" option controls whether it's possible to walk through", 25 "the screen edges or not. Turn it on and use your brain to get", 26 "the maximum out of this feature.", 27 "The \"Surround\" option turns Nibbles into a completely different", 28 "game. Don't change this option during play! (you'll see why)", 29 "We wish you many hours of fun playing this game." 30 }; 31 #define NIBBLES_HELP_LINES (sizeof (NI_HelpText) / sizeof (char *)) 32 33 typedef struct 34 { 35 int16_t length; 36 uint8_t data[8]; 37 } nibblesBuffer_t; 38 39 typedef struct 40 { 41 uint8_t x, y; 42 } nibblesCoord_t; 43 44 static const char nibblesCheatCode1[] = "skip", nibblesCheatCode2[] = "triton"; 45 static char nibblesCheatBuffer[16]; 46 47 static const uint8_t NI_Speeds[4] = { 12, 8, 6, 4 }; 48 static bool NI_EternalLives; 49 static uint8_t NI_CheatIndex, NI_CurSpeed, NI_CurTick60Hz, NI_CurSpeed60Hz, NI_Screen[51][23], NI_Level; 50 static int16_t NI_P1Dir, NI_P2Dir, NI_P1Len, NI_P2Len, NI_Number, NI_NumberX, NI_NumberY, NI_P1NoClear, NI_P2NoClear; 51 static uint16_t NI_P1Lives, NI_P2Lives; 52 static int32_t NI_P1Score, NI_P2Score; 53 static nibblesCoord_t NI_P1[256], NI_P2[256]; 54 static nibblesBuffer_t nibblesBuffer[2]; 55 56 /* Non-FT2 feature: Check if either the Desktop or Buttons palette color 57 ** is so close to black that the player would have troubles seeing the walls 58 ** when playing Nibbles. 59 */ 60 // ------------------------------------------------------------------------ 61 // ------------------------------------------------------------------------ 62 static uint8_t rgb24ToLuminosity(uint32_t rgb24) 63 { 64 const uint8_t R = RGB32_R(rgb24); 65 const uint8_t G = RGB32_G(rgb24); 66 const uint8_t B = RGB32_B(rgb24); 67 68 // get highest channel value 69 uint8_t hi = 0; 70 if (hi < R) hi = R; 71 if (hi < G) hi = G; 72 if (hi < B) hi = B; 73 74 // get lowest channel value 75 uint8_t lo = 255; 76 if (lo > R) lo = R; 77 if (lo > G) lo = G; 78 if (lo > B) lo = B; 79 80 return (hi + lo) >> 1; // 0..255 81 } 82 83 static bool wallColorsAreCloseToBlack(void) 84 { 85 #define LUMINOSITY_THRESHOLD 4 86 87 const uint8_t wallColor1L = rgb24ToLuminosity(video.palette[PAL_DESKTOP]); 88 const uint8_t wallColor2L = rgb24ToLuminosity(video.palette[PAL_BUTTONS]); 89 90 /* Since the rest of the wall colors are based on lower and higher 91 ** contrast values from these two primary colors, we don't really 92 ** need to check them all. 93 */ 94 95 if (wallColor1L <= LUMINOSITY_THRESHOLD || wallColor2L <= LUMINOSITY_THRESHOLD) 96 return true; 97 98 return false; 99 } 100 // ------------------------------------------------------------------------ 101 // ------------------------------------------------------------------------ 102 103 static void drawNibblesFoodNumber(int32_t xOut, int32_t yOut, int32_t number) 104 { 105 if (number > 9) 106 return; 107 108 uint32_t *dstPtr = &video.frameBuffer[(yOut * SCREEN_W) + xOut]; 109 uint8_t *srcPtr = &bmp.font8[number * FONT8_CHAR_W]; 110 111 for (int32_t y = 0; y < FONT8_CHAR_H; y++, srcPtr += FONT8_WIDTH, dstPtr += SCREEN_W) 112 { 113 for (int32_t x = 0; x < FONT8_CHAR_W; x++) 114 { 115 if (srcPtr[x] != 0) 116 dstPtr[x] = video.palette[PAL_FORGRND]; 117 } 118 } 119 } 120 121 static void redrawNibblesScreen(void) 122 { 123 if (!editor.NI_Play) 124 return; 125 126 for (int16_t x = 0; x < 51; x++) 127 { 128 for (int16_t y = 0; y < 23; y++) 129 { 130 const int16_t xs = 152 + (x * 8); 131 const int16_t ys = 7 + (y * 7); 132 133 const uint8_t c = NI_Screen[x][y]; 134 if (c < 16) 135 { 136 if (config.NI_Grid) 137 { 138 fillRect(xs + 0, ys + 0, 8 - 0, 7 - 0, PAL_BUTTON2); 139 fillRect(xs + 1, ys + 1, 8 - 1, 7 - 1, c); 140 } 141 else 142 { 143 fillRect(xs, ys, 8, 7, c); 144 } 145 } 146 else 147 { 148 drawNibblesFoodNumber(xs+2, ys, NI_Number); 149 } 150 } 151 } 152 153 // fix wrongly rendered grid 154 if (config.NI_Grid) 155 { 156 vLine(560, 7, 161, PAL_BUTTON2); 157 hLine(152, 168, 409, PAL_BUTTON2); 158 } 159 else 160 { 161 // if we turned grid off, clear lines 162 vLine(560, 7, 161, PAL_BCKGRND); 163 hLine(152, 168, 409, PAL_BCKGRND); 164 } 165 } 166 167 static void nibblesAddBuffer(int16_t bufNum, uint8_t value) 168 { 169 nibblesBuffer_t *n = &nibblesBuffer[bufNum]; 170 if (n->length < 8) 171 { 172 n->data[n->length] = value; 173 n->length++; 174 } 175 } 176 177 static bool nibblesBufferFull(int16_t bufNum) 178 { 179 return (nibblesBuffer[bufNum].length > 0); 180 } 181 182 static int16_t nibblesGetBuffer(int16_t bufNum) 183 { 184 nibblesBuffer_t *n = &nibblesBuffer[bufNum]; 185 if (n->length > 0) 186 { 187 const int16_t dataOut = n->data[0]; 188 memmove(&n->data[0], &n->data[1], 7); 189 n->length--; 190 191 return dataOut; 192 } 193 194 return -1; 195 } 196 197 static void nibblesGetLevel(int16_t levelNum) 198 { 199 const int32_t readX = 1 + ((51+2) * (levelNum % 10)); 200 const int32_t readY = 1 + ((23+2) * (levelNum / 10)); 201 202 const uint8_t *stagePtr = &bmp.nibblesStages[(readY * STAGES_BMP_WIDTH) + readX]; 203 204 for (int32_t y = 0; y < 23; y++) 205 { 206 for (int32_t x = 0; x < 51; x++) 207 NI_Screen[x][y] = stagePtr[x]; 208 209 stagePtr += STAGES_BMP_WIDTH; 210 } 211 } 212 213 static void nibblesCreateLevel(int16_t levelNum) 214 { 215 if (levelNum >= NI_MAXLEVEL) 216 levelNum = NI_MAXLEVEL-1; 217 218 nibblesGetLevel(levelNum); 219 220 int32_t x1 = 0; 221 int32_t x2 = 0; 222 int32_t y1 = 0; 223 int32_t y2 = 0; 224 225 for (int32_t y = 0; y < 23; y++) 226 { 227 for (int32_t x = 0; x < 51; x++) 228 { 229 if (NI_Screen[x][y] == 1 || NI_Screen[x][y] == 3) 230 { 231 const uint8_t c = NI_Screen[x][y]; 232 233 if (c == 3) 234 { 235 x1 = x; 236 y1 = y; 237 } 238 239 if (c == 1) 240 { 241 x2 = x; 242 y2 = y; 243 } 244 245 NI_Screen[x][y] = 0; 246 } 247 } 248 } 249 250 const int32_t readX = (51 + 2) * (levelNum % 10); 251 const int32_t readY = (23 + 2) * (levelNum / 10); 252 253 NI_P1Dir = bmp.nibblesStages[(readY * 530) + (readX + 1)]; 254 NI_P2Dir = bmp.nibblesStages[(readY * 530) + (readX + 0)]; 255 256 NI_P1Len = 5; 257 NI_P2Len = 5; 258 NI_P1NoClear = 0; 259 NI_P2NoClear = 0; 260 NI_Number = 0; 261 nibblesBuffer[0].length = 0; 262 nibblesBuffer[1].length = 0; 263 264 for (int32_t i = 0; i < 256; i++) 265 { 266 NI_P1[i].x = (uint8_t)x1; 267 NI_P1[i].y = (uint8_t)y1; 268 NI_P2[i].x = (uint8_t)x2; 269 NI_P2[i].y = (uint8_t)y2; 270 } 271 } 272 273 static void nibbleWriteLevelSprite(int16_t xOut, int16_t yOut, int16_t levelNum) 274 { 275 const int32_t readX = (51 + 2) * (levelNum % 10); 276 const int32_t readY = (23 + 2) * (levelNum / 10); 277 278 const uint8_t *src = (const uint8_t *)&bmp.nibblesStages[(readY * 530) + readX]; 279 uint32_t *dst = &video.frameBuffer[(yOut * SCREEN_W) + xOut]; 280 281 for (int32_t y = 0; y < 23+2; y++) 282 { 283 for (int32_t x = 0; x < 51+2; x++) 284 dst[x] = video.palette[src[x]]; 285 286 src += 530; 287 dst += SCREEN_W; 288 } 289 290 // overwrite start position pixels 291 video.frameBuffer[(yOut * SCREEN_W) + (xOut + 0)] = video.palette[PAL_FORGRND]; 292 video.frameBuffer[(yOut * SCREEN_W) + (xOut + 1)] = video.palette[PAL_FORGRND]; 293 } 294 295 static void highScoreTextOutClipX(uint16_t x, uint16_t y, uint8_t paletteIndex, uint8_t shadowPaletteIndex, const char *textPtr, uint16_t clipX) 296 { 297 assert(textPtr != NULL); 298 299 uint16_t currX = x; 300 for (uint16_t i = 0; i < 22; i++) 301 { 302 const char ch = textPtr[i]; 303 if (ch == '\0') 304 break; 305 306 charOutClipX(currX + 1, y + 1, shadowPaletteIndex, ch, clipX); // shadow 307 charOutClipX(currX, y, paletteIndex, ch, clipX); // foreground 308 309 currX += charWidth(ch); 310 if (currX >= clipX) 311 break; 312 } 313 } 314 315 void nibblesHighScore(void) 316 { 317 if (editor.NI_Play) 318 { 319 okBox(0, "Nibbles message", "The highscore table is not available during play.", NULL); 320 return; 321 } 322 323 clearRect(152, 7, 409, 162); 324 325 bigTextOut(160, 10, PAL_FORGRND, "Fasttracker Nibbles Highscore"); 326 for (int16_t i = 0; i < 5; i++) 327 { 328 highScoreTextOutClipX(160, 42 + (26 * i), PAL_FORGRND, PAL_DSKTOP2, config.NI_HighScore[i].name, 160 + 70); 329 hexOutShadow(160 + 76, 42 + (26 * i), PAL_FORGRND, PAL_DSKTOP2, config.NI_HighScore[i].score, 8); 330 nibbleWriteLevelSprite(160 + 136, (42 - 9) + (26 * i), config.NI_HighScore[i].level); 331 332 highScoreTextOutClipX(360, 42 + (26 * i), PAL_FORGRND, PAL_DSKTOP2, config.NI_HighScore[i+5].name, 360 + 70); 333 hexOutShadow(360 + 76, 42 + (26 * i), PAL_FORGRND, PAL_DSKTOP2, config.NI_HighScore[i+5].score, 8); 334 nibbleWriteLevelSprite(360 + 136, (42 - 9) + (26 * i), config.NI_HighScore[i+5].level); 335 } 336 } 337 338 static void setNibbleDot(uint8_t x, uint8_t y, uint8_t c) 339 { 340 const uint16_t xs = 152 + (x * 8); 341 const uint16_t ys = 7 + (y * 7); 342 343 if (config.NI_Grid) 344 { 345 fillRect(xs + 0, ys + 0, 8 - 0, 7 - 0, PAL_BUTTON2); 346 fillRect(xs + 1, ys + 1, 8 - 1, 7 - 1, c); 347 } 348 else 349 { 350 fillRect(xs, ys, 8, 7, c); 351 } 352 353 NI_Screen[x][y] = c; 354 } 355 356 static void nibblesGenNewNumber(void) 357 { 358 while (true) 359 { 360 int16_t x = rand() % 51; 361 int16_t y = rand() % 23; 362 363 bool blockIsSuitable; 364 365 if (y < 22) 366 blockIsSuitable = NI_Screen[x][y] == 0 && NI_Screen[x][y+1] == 0; 367 else 368 blockIsSuitable = NI_Screen[x][y] == 0; // FT2 bugfix: prevent look-up overflow 369 370 if (blockIsSuitable) 371 { 372 NI_Number++; 373 NI_Screen[x][y] = (uint8_t)(16 + NI_Number); 374 NI_NumberX = x; 375 NI_NumberY = y; 376 377 const int16_t xs = 152 + (x * 8); 378 const int16_t ys = 7 + (y * 7); 379 380 if (config.NI_Grid) 381 { 382 fillRect(xs + 0, ys + 0, 8 - 0, 7 - 0, PAL_BUTTON2); 383 fillRect(xs + 1, ys + 1, 8 - 1, 7 - 1, PAL_BCKGRND); 384 } 385 else 386 { 387 fillRect(xs, ys, 8, 7, PAL_BCKGRND); 388 } 389 390 drawNibblesFoodNumber((x * 8) + 154, (y * 7) + 7, NI_Number); 391 392 break; 393 } 394 } 395 } 396 397 static void newNibblesGame(void) 398 { 399 nibblesCreateLevel(NI_Level); 400 redrawNibblesScreen(); 401 402 setNibbleDot(NI_P1[0].x, NI_P1[0].y, 6); 403 if (config.NI_NumPlayers == 1) 404 setNibbleDot(NI_P2[0].x, NI_P2[0].y, 7); 405 406 if (!config.NI_Surround) 407 nibblesGenNewNumber(); 408 } 409 410 static bool nibblesInvalid(int16_t x, int16_t y, int16_t d) 411 { 412 if (!config.NI_Wrap) 413 { 414 if ((x == 0 && d == 0) || (x == 50 && d == 2) || (y == 0 && d == 3) || (y == 22 && d == 1)) 415 return true; 416 } 417 418 assert(x >= 0 && x < 51 && y >= 0 && y < 23); 419 return (NI_Screen[x][y] >= 1 && NI_Screen[x][y] <= 15); 420 } 421 422 static void drawScoresLives(void) 423 { 424 // player 1 425 assert(NI_P1Lives < 100); 426 hexOutBg(89, 27, PAL_FORGRND, PAL_DESKTOP, NI_P1Score, 8); 427 textOutFixed(131, 39, PAL_FORGRND, PAL_DESKTOP, dec2StrTab[NI_P1Lives]); 428 429 // player 2 430 assert(NI_P2Lives < 100); 431 hexOutBg(89, 75, PAL_FORGRND, PAL_DESKTOP, NI_P2Score, 8); 432 textOutFixed(131, 87, PAL_FORGRND, PAL_DESKTOP, dec2StrTab[NI_P2Lives]); 433 } 434 435 static void nibblesDecLives(int16_t l1, int16_t l2) 436 { 437 char name[21+1]; 438 int16_t i, k; 439 highScoreType *h; 440 441 if (!NI_EternalLives) 442 { 443 NI_P1Lives -= l1; 444 NI_P2Lives -= l2; 445 } 446 447 drawScoresLives(); 448 449 if (l1+l2 == 2) 450 { 451 okBox(0, "Nibbles message", "Both players died!", NULL); 452 } 453 else 454 { 455 if (l2 == 0) 456 okBox(0, "Nibbles message", "Player 1 died!", NULL); 457 else 458 okBox(0, "Nibbles message", "Player 2 died!", NULL); 459 } 460 461 if (NI_P1Lives == 0 || NI_P2Lives == 0) 462 { 463 editor.NI_Play = false; 464 okBox(0, "Nibbles message", "GAME OVER", NULL); 465 466 // prevent highscore table from showing overflowing level graphics 467 if (NI_Level >= NI_MAXLEVEL) 468 NI_Level = NI_MAXLEVEL-1; 469 470 if (NI_P1Score > config.NI_HighScore[9].score) 471 { 472 strcpy(name, "Unknown"); 473 inputBox(0, "Player 1 - Enter your name:", name, sizeof (name) - 1); 474 475 i = 0; 476 while (NI_P1Score <= config.NI_HighScore[i].score) 477 i++; 478 479 for (k = 8; k >= i; k--) 480 memcpy(&config.NI_HighScore[k+1], &config.NI_HighScore[k], sizeof (highScoreType)); 481 482 if (i == 0) 483 okBox(0, "Nibbles message", "You've probably cheated!", NULL); 484 485 h = &config.NI_HighScore[i]; 486 487 k = (int16_t)strlen(name); 488 memset(h->name, 0, sizeof (h->name)); 489 memcpy(h->name, name, k); 490 h->nameLen = (uint8_t)k; 491 492 h->score = NI_P1Score; 493 h->level = NI_Level; 494 } 495 496 if (NI_P2Score > config.NI_HighScore[9].score) 497 { 498 strcpy(name, "Unknown"); 499 inputBox(0, "Player 2 - Enter your name:", name, sizeof (name) - 1); 500 501 i = 0; 502 while (NI_P2Score <= config.NI_HighScore[i].score) 503 i++; 504 505 for (k = 8; k >= i; k--) 506 memcpy(&config.NI_HighScore[k+1], &config.NI_HighScore[k], sizeof (highScoreType)); 507 508 if (i == 0) 509 okBox(0, "Nibbles message", "You've probably cheated!", NULL); 510 511 h = &config.NI_HighScore[i]; 512 k = (int16_t)strlen(name); 513 514 memset(h->name, 0, sizeof (h->name)); 515 memcpy(h->name, name, k); 516 h->nameLen = (uint8_t)k; 517 518 h->score = NI_P2Score; 519 h->level = NI_Level; 520 } 521 522 nibblesHighScore(); 523 } 524 else 525 { 526 editor.NI_Play = true; 527 newNibblesGame(); 528 } 529 } 530 531 static void nibblesEraseNumber(void) 532 { 533 if (!config.NI_Surround) 534 setNibbleDot((uint8_t)NI_NumberX, (uint8_t)NI_NumberY, 0); 535 } 536 537 static void nibblesNewLevel(void) 538 { 539 char text[24]; 540 541 sprintf(text, "Level %d finished!", NI_Level+1); 542 okBox(0, "Nibbles message", text, NULL); 543 544 // cast to int16_t to simulate a bug in FT2 545 NI_P1Score += 0x10000 + (int16_t)((12 - NI_CurSpeed) * 0x2000); 546 if (config.NI_NumPlayers == 1) 547 NI_P2Score += 0x10000; 548 549 NI_Level++; 550 551 if (NI_P1Lives < 99) 552 NI_P1Lives++; 553 554 if (config.NI_NumPlayers == 1) 555 { 556 if (NI_P2Lives < 99) 557 NI_P2Lives++; 558 } 559 560 NI_Number = 0; 561 nibblesCreateLevel(NI_Level); 562 redrawNibblesScreen(); 563 564 nibblesGenNewNumber(); 565 } 566 567 void moveNibblesPlayers(void) 568 { 569 if (ui.sysReqShown || --NI_CurTick60Hz != 0) 570 return; 571 572 if (nibblesBufferFull(0)) 573 { 574 switch (nibblesGetBuffer(0)) 575 { 576 case 0: if (NI_P1Dir != 2) NI_P1Dir = 0; break; 577 case 1: if (NI_P1Dir != 3) NI_P1Dir = 1; break; 578 case 2: if (NI_P1Dir != 0) NI_P1Dir = 2; break; 579 case 3: if (NI_P1Dir != 1) NI_P1Dir = 3; break; 580 default: break; 581 } 582 } 583 584 if (nibblesBufferFull(1)) 585 { 586 switch (nibblesGetBuffer(1)) 587 { 588 case 0: if (NI_P2Dir != 2) NI_P2Dir = 0; break; 589 case 1: if (NI_P2Dir != 3) NI_P2Dir = 1; break; 590 case 2: if (NI_P2Dir != 0) NI_P2Dir = 2; break; 591 case 3: if (NI_P2Dir != 1) NI_P2Dir = 3; break; 592 default: break; 593 } 594 } 595 596 memmove(&NI_P1[1], &NI_P1[0], 255 * sizeof (nibblesCoord_t)); 597 if (config.NI_NumPlayers == 1) 598 memmove(&NI_P2[1], &NI_P2[0], 255 * sizeof (nibblesCoord_t)); 599 600 switch (NI_P1Dir) 601 { 602 case 0: NI_P1[0].x++; break; 603 case 1: NI_P1[0].y--; break; 604 case 2: NI_P1[0].x--; break; 605 case 3: NI_P1[0].y++; break; 606 default: break; 607 } 608 609 if (config.NI_NumPlayers == 1) 610 { 611 switch (NI_P2Dir) 612 { 613 case 0: NI_P2[0].x++; break; 614 case 1: NI_P2[0].y--; break; 615 case 2: NI_P2[0].x--; break; 616 case 3: NI_P2[0].y++; break; 617 default: break; 618 } 619 } 620 621 if (NI_P1[0].x == 255) NI_P1[0].x = 50; 622 if (NI_P2[0].x == 255) NI_P2[0].x = 50; 623 if (NI_P1[0].y == 255) NI_P1[0].y = 22; 624 if (NI_P2[0].y == 255) NI_P2[0].y = 22; 625 626 NI_P1[0].x %= 51; 627 NI_P1[0].y %= 23; 628 NI_P2[0].x %= 51; 629 NI_P2[0].y %= 23; 630 631 if (config.NI_NumPlayers == 1) 632 { 633 if (nibblesInvalid(NI_P1[0].x, NI_P1[0].y, NI_P1Dir) && nibblesInvalid(NI_P2[0].x, NI_P2[0].y, NI_P2Dir)) 634 { 635 nibblesDecLives(1, 1); 636 goto NoMove; 637 } 638 else if (nibblesInvalid(NI_P1[0].x, NI_P1[0].y, NI_P1Dir)) 639 { 640 nibblesDecLives(1, 0); 641 goto NoMove; 642 } 643 else if (nibblesInvalid(NI_P2[0].x, NI_P2[0].y, NI_P2Dir)) 644 { 645 nibblesDecLives(0, 1); 646 goto NoMove; 647 } 648 else if (NI_P1[0].x == NI_P2[0].x && NI_P1[0].y == NI_P2[0].y) 649 { 650 nibblesDecLives(1, 1); 651 goto NoMove; 652 } 653 } 654 else 655 { 656 if (nibblesInvalid(NI_P1[0].x, NI_P1[0].y, NI_P1Dir)) 657 { 658 nibblesDecLives(1, 0); 659 goto NoMove; 660 } 661 } 662 663 int16_t j = 0; 664 int16_t i = NI_Screen[NI_P1[0].x][NI_P1[0].y]; 665 if (i >= 16) 666 { 667 NI_P1Score += (i & 15) * 999 * (NI_Level + 1); 668 nibblesEraseNumber(); j = 1; 669 NI_P1NoClear = NI_P1Len >> 1; 670 } 671 672 if (config.NI_NumPlayers == 1) 673 { 674 i = NI_Screen[NI_P2[0].x][NI_P2[0].y]; 675 if (i >= 16) 676 { 677 NI_P2Score += ((i & 15) * 999 * (NI_Level + 1)); 678 nibblesEraseNumber(); j = 1; 679 NI_P2NoClear = NI_P2Len >> 1; 680 } 681 } 682 683 NI_P1Score -= 17; 684 if (config.NI_NumPlayers == 1) 685 NI_P2Score -= 17; 686 687 if (NI_P1Score < 0) NI_P1Score = 0; 688 if (NI_P2Score < 0) NI_P2Score = 0; 689 690 if (!config.NI_Surround) 691 { 692 if (NI_P1NoClear > 0 && NI_P1Len < 255) 693 { 694 NI_P1NoClear--; 695 NI_P1Len++; 696 } 697 else 698 { 699 setNibbleDot(NI_P1[NI_P1Len].x, NI_P1[NI_P1Len].y, 0); 700 } 701 702 if (config.NI_NumPlayers == 1) 703 { 704 if (NI_P2NoClear > 0 && NI_P2Len < 255) 705 { 706 NI_P2NoClear--; 707 NI_P2Len++; 708 } 709 else 710 { 711 setNibbleDot(NI_P2[NI_P2Len].x, NI_P2[NI_P2Len].y, 0); 712 } 713 } 714 } 715 716 setNibbleDot(NI_P1[0].x, NI_P1[0].y, 6); 717 if (config.NI_NumPlayers == 1) 718 setNibbleDot(NI_P2[0].x, NI_P2[0].y, 5); 719 720 if (j == 1 && !config.NI_Surround) 721 { 722 if (NI_Number == 9) 723 { 724 nibblesNewLevel(); 725 NI_CurTick60Hz = NI_CurSpeed60Hz; 726 return; 727 } 728 729 nibblesGenNewNumber(); 730 } 731 732 NoMove: 733 NI_CurTick60Hz = NI_CurSpeed60Hz; 734 drawScoresLives(); 735 } 736 737 void showNibblesScreen(void) 738 { 739 if (ui.extendedPatternEditor) 740 exitPatternEditorExtended(); 741 742 hideTopScreen(); 743 ui.nibblesShown = true; 744 745 drawFramework(0, 0, 632, 3, FRAMEWORK_TYPE1); 746 drawFramework(0, 3, 148, 49, FRAMEWORK_TYPE1); 747 drawFramework(0, 52, 148, 49, FRAMEWORK_TYPE1); 748 drawFramework(0, 101, 148, 72, FRAMEWORK_TYPE1); 749 drawFramework(148, 3, 417, 170, FRAMEWORK_TYPE1); 750 drawFramework(150, 5, 413, 166, FRAMEWORK_TYPE2); 751 drawFramework(565, 3, 67, 170, FRAMEWORK_TYPE1); 752 753 bigTextOutShadow(4, 6, PAL_FORGRND, PAL_DSKTOP2, "Player 1"); 754 bigTextOutShadow(4, 55, PAL_FORGRND, PAL_DSKTOP2, "Player 2"); 755 756 textOutShadow(4, 27, PAL_FORGRND, PAL_DSKTOP2, "Score"); 757 textOutShadow(4, 75, PAL_FORGRND, PAL_DSKTOP2, "Score"); 758 textOutShadow(4, 39, PAL_FORGRND, PAL_DSKTOP2, "Lives"); 759 textOutShadow(4, 87, PAL_FORGRND, PAL_DSKTOP2, "Lives"); 760 textOutShadow(18, 106, PAL_FORGRND, PAL_DSKTOP2, "1 player"); 761 textOutShadow(18, 120, PAL_FORGRND, PAL_DSKTOP2, "2 players"); 762 textOutShadow(20, 135, PAL_FORGRND, PAL_DSKTOP2, "Surround"); 763 textOutShadow(20, 148, PAL_FORGRND, PAL_DSKTOP2, "Grid"); 764 textOutShadow(20, 161, PAL_FORGRND, PAL_DSKTOP2, "Wrap"); 765 textOutShadow(80, 105, PAL_FORGRND, PAL_DSKTOP2, "Difficulty:"); 766 textOutShadow(93, 118, PAL_FORGRND, PAL_DSKTOP2, "Novice"); 767 textOutShadow(93, 132, PAL_FORGRND, PAL_DSKTOP2, "Average"); 768 textOutShadow(93, 146, PAL_FORGRND, PAL_DSKTOP2, "Pro"); 769 textOutShadow(93, 160, PAL_FORGRND, PAL_DSKTOP2, "Triton"); 770 771 drawScoresLives(); 772 773 blitFast(569, 7, bmp.nibblesLogo, 59, 91); 774 775 showPushButton(PB_NIBBLES_PLAY); 776 showPushButton(PB_NIBBLES_HELP); 777 showPushButton(PB_NIBBLES_HIGHS); 778 showPushButton(PB_NIBBLES_EXIT); 779 780 checkBoxes[CB_NIBBLES_SURROUND].checked = config.NI_Surround ? true : false; 781 checkBoxes[CB_NIBBLES_GRID].checked = config.NI_Grid ? true : false; 782 checkBoxes[CB_NIBBLES_WRAP].checked = config.NI_Wrap ? true : false; 783 showCheckBox(CB_NIBBLES_SURROUND); 784 showCheckBox(CB_NIBBLES_GRID); 785 showCheckBox(CB_NIBBLES_WRAP); 786 787 uncheckRadioButtonGroup(RB_GROUP_NIBBLES_PLAYERS); 788 if (config.NI_NumPlayers == 0) 789 radioButtons[RB_NIBBLES_1PLAYER].state = RADIOBUTTON_CHECKED; 790 else 791 radioButtons[RB_NIBBLES_2PLAYERS].state = RADIOBUTTON_CHECKED; 792 showRadioButtonGroup(RB_GROUP_NIBBLES_PLAYERS); 793 794 uncheckRadioButtonGroup(RB_GROUP_NIBBLES_DIFFICULTY); 795 switch (config.NI_Speed) 796 { 797 default: 798 case 0: radioButtons[RB_NIBBLES_NOVICE].state = RADIOBUTTON_CHECKED; break; 799 case 1: radioButtons[RB_NIBBLES_AVERAGE].state = RADIOBUTTON_CHECKED; break; 800 case 2: radioButtons[RB_NIBBLES_PRO].state = RADIOBUTTON_CHECKED; break; 801 case 3: radioButtons[RB_NIBBLES_TRITON].state = RADIOBUTTON_CHECKED; break; 802 } 803 showRadioButtonGroup(RB_GROUP_NIBBLES_DIFFICULTY); 804 } 805 806 void hideNibblesScreen(void) 807 { 808 hidePushButton(PB_NIBBLES_PLAY); 809 hidePushButton(PB_NIBBLES_HELP); 810 hidePushButton(PB_NIBBLES_HIGHS); 811 hidePushButton(PB_NIBBLES_EXIT); 812 813 hideRadioButtonGroup(RB_GROUP_NIBBLES_PLAYERS); 814 hideRadioButtonGroup(RB_GROUP_NIBBLES_DIFFICULTY); 815 816 hideCheckBox(CB_NIBBLES_SURROUND); 817 hideCheckBox(CB_NIBBLES_GRID); 818 hideCheckBox(CB_NIBBLES_WRAP); 819 820 ui.nibblesShown = false; 821 } 822 823 void exitNibblesScreen(void) 824 { 825 hideNibblesScreen(); 826 showTopScreen(true); 827 } 828 829 // PUSH BUTTONS 830 831 void nibblesPlay(void) 832 { 833 if (editor.NI_Play) 834 { 835 if (okBox(2, "Nibbles request", "Restart current game of Nibbles?", NULL) != 1) 836 return; 837 } 838 839 if (config.NI_Surround && config.NI_NumPlayers == 0) 840 { 841 okBox(0, "Nibbles message", "Surround mode is not appropriate in one-player mode.", NULL); 842 return; 843 } 844 845 if (wallColorsAreCloseToBlack()) 846 okBox(0, "Nibbles warning", "The Desktop/Button colors are set to values that make the walls hard to see!", NULL); 847 848 assert(config.NI_Speed < 4); 849 NI_CurSpeed = NI_Speeds[config.NI_Speed]; 850 851 // adjust for 70Hz -> 60Hz countdown frames (this is not exact, but we don't want fractional numbers aka. frame skipping) 852 NI_CurSpeed60Hz = (uint8_t)SCALE_VBLANK_DELTA_REV(NI_CurSpeed); 853 NI_CurTick60Hz = (uint8_t)SCALE_VBLANK_DELTA_REV(NI_Speeds[2]); 854 855 editor.NI_Play = true; 856 NI_P1Score = 0; 857 NI_P2Score = 0; 858 NI_P1Lives = 5; 859 NI_P2Lives = 5; 860 NI_Level = 0; 861 862 newNibblesGame(); 863 } 864 865 void nibblesHelp(void) 866 { 867 if (editor.NI_Play) 868 { 869 okBox(0, "System message", "Help is not available during play.", NULL); 870 return; 871 } 872 873 clearRect(152, 7, 409, 162); 874 875 bigTextOut(160, 10, PAL_FORGRND, "Fasttracker Nibbles Help"); 876 for (uint16_t i = 0; i < NIBBLES_HELP_LINES; i++) 877 textOut(160, 36 + (11 * i), PAL_BUTTONS, NI_HelpText[i]); 878 } 879 880 void nibblesExit(void) 881 { 882 if (editor.NI_Play) 883 { 884 if (okBox(2, "System request", "Quit current game of Nibbles?", NULL) == 1) 885 { 886 editor.NI_Play = false; 887 exitNibblesScreen(); 888 } 889 890 return; 891 } 892 893 exitNibblesScreen(); 894 } 895 896 // RADIO BUTTONS 897 898 void nibblesSet1Player(void) 899 { 900 config.NI_NumPlayers = 0; 901 checkRadioButton(RB_NIBBLES_1PLAYER); 902 } 903 904 void nibblesSet2Players(void) 905 { 906 config.NI_NumPlayers = 1; 907 checkRadioButton(RB_NIBBLES_2PLAYERS); 908 } 909 910 void nibblesSetNovice(void) 911 { 912 config.NI_Speed = 0; 913 checkRadioButton(RB_NIBBLES_NOVICE); 914 } 915 916 void nibblesSetAverage(void) 917 { 918 config.NI_Speed = 1; 919 checkRadioButton(RB_NIBBLES_AVERAGE); 920 } 921 922 void nibblesSetPro(void) 923 { 924 config.NI_Speed = 2; 925 checkRadioButton(RB_NIBBLES_PRO); 926 } 927 928 void nibblesSetTriton(void) 929 { 930 config.NI_Speed = 3; 931 checkRadioButton(RB_NIBBLES_TRITON); 932 } 933 934 // CHECK BOXES 935 936 void nibblesToggleSurround(void) 937 { 938 config.NI_Surround ^= 1; 939 checkBoxes[CB_NIBBLES_SURROUND].checked = config.NI_Surround ? true : false; 940 showCheckBox(CB_NIBBLES_SURROUND); 941 } 942 943 void nibblesToggleGrid(void) 944 { 945 config.NI_Grid ^= 1; 946 checkBoxes[CB_NIBBLES_GRID].checked = config.NI_Grid ? true : false; 947 showCheckBox(CB_NIBBLES_GRID); 948 949 if (editor.NI_Play) 950 redrawNibblesScreen(); 951 } 952 953 void nibblesToggleWrap(void) 954 { 955 config.NI_Wrap ^= 1; 956 957 checkBoxes[CB_NIBBLES_WRAP].checked = config.NI_Wrap ? true : false; 958 showCheckBox(CB_NIBBLES_WRAP); 959 } 960 961 // GLOBAL FUNCTIONS 962 963 void nibblesKeyAdministrator(SDL_Scancode scancode) 964 { 965 if (scancode == SDL_SCANCODE_ESCAPE) 966 { 967 if (okBox(2, "System request", "Quit current game of Nibbles?", NULL) == 1) 968 { 969 editor.NI_Play = false; 970 exitNibblesScreen(); 971 } 972 973 return; 974 } 975 976 switch (scancode) 977 { 978 // player 1 979 case SDL_SCANCODE_RIGHT: nibblesAddBuffer(0, 0); break; 980 case SDL_SCANCODE_UP: nibblesAddBuffer(0, 1); break; 981 case SDL_SCANCODE_LEFT: nibblesAddBuffer(0, 2); break; 982 case SDL_SCANCODE_DOWN: nibblesAddBuffer(0, 3); break; 983 984 // player 2 985 case SDL_SCANCODE_D: nibblesAddBuffer(1, 0); break; 986 case SDL_SCANCODE_W: nibblesAddBuffer(1, 1); break; 987 case SDL_SCANCODE_A: nibblesAddBuffer(1, 2); break; 988 case SDL_SCANCODE_S: nibblesAddBuffer(1, 3); break; 989 990 default: break; 991 } 992 } 993 994 bool testNibblesCheatCodes(SDL_Keycode keycode) // not directly ported, but same cheatcodes 995 { 996 const char *codeStringPtr; 997 uint8_t codeStringLen; 998 999 // nibbles cheat codes can only be typed in while holding down left SHIFT+CTRL+ALT 1000 if (keyb.leftShiftPressed && keyb.leftCtrlPressed && keyb.leftAltPressed) 1001 { 1002 if (editor.NI_Play) 1003 { 1004 // during game: "S", "K", "I", "P" (skip to next level) 1005 codeStringPtr = nibblesCheatCode1; 1006 codeStringLen = sizeof (nibblesCheatCode1) - 1; 1007 } 1008 else 1009 { 1010 // not during game: "T", "R", "I", "T", "O", "N" (enable infinite lives) 1011 codeStringPtr = nibblesCheatCode2; 1012 codeStringLen = sizeof (nibblesCheatCode2) - 1; 1013 } 1014 1015 nibblesCheatBuffer[NI_CheatIndex] = (char)keycode; 1016 if (nibblesCheatBuffer[NI_CheatIndex] != codeStringPtr[NI_CheatIndex]) 1017 { 1018 NI_CheatIndex = 0; // start over again, one letter didn't match 1019 return true; 1020 } 1021 1022 if (++NI_CheatIndex == codeStringLen) // cheat code was successfully entered 1023 { 1024 NI_CheatIndex = 0; 1025 1026 if (editor.NI_Play) 1027 { 1028 nibblesNewLevel(); 1029 } 1030 else 1031 { 1032 NI_EternalLives ^= 1; 1033 if (NI_EternalLives) 1034 okBox(0, "Triton productions declares:", "Eternal lives activated!", NULL); 1035 else 1036 okBox(0, "Triton productions declares:", "Eternal lives deactivated!", NULL); 1037 } 1038 } 1039 1040 return true; // SHIFT+CTRL+ALT held down, don't test other keys 1041 } 1042 1043 return false; // SHIFT+CTRL+ALT not held down, test other keys 1044 } 1045 1046 void pbNibbles(void) 1047 { 1048 showNibblesScreen(); 1049 }