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