A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 Franklin Wei
11 *
12 * Clone of 2048 by Gabriele Cirulli
13 *
14 * Thanks to [Saint], saratoga, and gevaerts for answering all my n00b
15 * questions :)
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU General Public License
19 * as published by the Free Software Foundation; either version 2
20 * of the License, or (at your option) any later version.
21 *
22 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
23 * KIND, either express or implied.
24 *
25 ***************************************************************************/
26
27/* TODO
28 * Sounds!
29 * Better animations!
30 */
31
32/* includes */
33
34#include <plugin.h>
35
36#include "lib/display_text.h"
37
38#include "lib/helper.h"
39#include "lib/highscore.h"
40#include "lib/playback_control.h"
41#include "lib/pluginlib_actions.h"
42#include "lib/pluginlib_exit.h"
43
44#include "pluginbitmaps/_2048_background.h"
45#include "pluginbitmaps/_2048_tiles.h"
46
47/* some constants */
48
49static const int ANIM_SLEEPTIME = (HZ/20);
50static const int NUM_STARTING_TILES = 2;
51static const int VERT_SPACING = 4;
52static const int WHAT_FONT = FONT_UI;
53static const unsigned int WINNING_TILE = 2048;
54
55/* must use macros for these */
56#define GRID_SIZE 4
57#define HISCORES_FILE PLUGIN_GAMES_DATA_DIR "/2048.score"
58#define MIN_SPACE (BMPHEIGHT__2048_tiles * 0.134)
59#define NUM_SCORES 5
60#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/2048.save"
61#define SPACES (GRID_SIZE * GRID_SIZE)
62
63/* screen-specific configuration */
64
65#if (LCD_WIDTH < LCD_HEIGHT) /* tall screens */
66# define TITLE_X 0
67# define TITLE_Y 0
68# define BASE_Y (BMPHEIGHT__2048_tiles*1.5)
69# define BASE_X (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
70# define SCORE_X 0
71# define SCORE_Y (max_numeral_height)
72# define BEST_SCORE_X 0
73# define BEST_SCORE_Y (2*max_numeral_height)
74#else /* wide or square screens */
75# define TITLE_X 0
76# define TITLE_Y 0
77# define BASE_X (LCD_WIDTH-(GRID_SIZE*BMPHEIGHT__2048_tiles)-(((GRID_SIZE+1)*MIN_SPACE)))
78# define BASE_Y (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
79# define SCORE_X 0
80# define SCORE_Y (max_numeral_height)
81# define BEST_SCORE_X 0
82# define BEST_SCORE_Y (2*max_numeral_height)
83#endif /* LCD_WIDTH < LCD_HEIGHT */
84
85#if LCD_DEPTH > 1
86/* where to draw the background bitmap */
87static const int BACKGROUND_X = (BASE_X-MIN_SPACE);
88static const int BACKGROUND_Y = (BASE_Y-MIN_SPACE);
89#endif
90
91/* key mappings */
92#define KEY_UP PLA_UP
93#define KEY_DOWN PLA_DOWN
94#define KEY_LEFT PLA_LEFT
95#define KEY_RIGHT PLA_RIGHT
96#define KEY_UNDO PLA_SELECT
97
98#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
99 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
100 || (CONFIG_KEYPAD == IPOD_4G_PAD)
101#define KEY_EXIT PLA_SELECT_REPEAT
102#else
103#define KEY_EXIT PLA_CANCEL
104#endif
105
106/* notice how "color" is spelled :P */
107#ifdef HAVE_LCD_COLOR
108
109/* colors */
110
111static const unsigned BACKGROUND = LCD_RGBPACK(0xfa, 0xf8, 0xef);
112static const unsigned BOARD_BACKGROUND = LCD_RGBPACK(0xbb, 0xad, 0xa0);
113static const unsigned TEXT_COLOR = LCD_RGBPACK(0x77, 0x6e, 0x65);
114
115#endif
116
117/* PLA data */
118static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
119
120/*** game data structures ***/
121
122struct game_ctx_t {
123 unsigned int grid[GRID_SIZE][GRID_SIZE]; /* 0 = empty */
124 unsigned int score;
125 unsigned int cksum; /* sum of grid, XORed by score */
126 bool already_won; /* has the player gotten 2048 yet? */
127} game_ctx;
128
129static struct game_ctx_t *ctx = &game_ctx;
130
131/*** temporary data ***/
132
133static bool merged_grid[GRID_SIZE][GRID_SIZE];
134static int old_grid[GRID_SIZE][GRID_SIZE];
135
136static int max_numeral_height = -1;
137
138#if LCD_DEPTH <= 1
139static int max_numeral_width;
140#endif
141
142static bool loaded = false; /* has a save been loaded? */
143
144/* the high score */
145static unsigned int best_score;
146
147static bool abnormal_exit = true;
148static struct highscore highscores[NUM_SCORES];
149
150/***************************** UTILITY FUNCTIONS *****************************/
151
152static inline int rand_range(int min, int max)
153{
154 return rb->rand() % (max-min + 1) + min;
155}
156
157/* prepares for exit */
158static void cleanup(void)
159{
160 backlight_use_settings();
161}
162
163/* returns 2 or 4 */
164static inline int rand_2_or_4(void)
165{
166 /* 1 in 10 chance of a four */
167 if(rb->rand() % 10 == 0)
168 return 4;
169 else
170 return 2;
171}
172
173/* displays the help text */
174static bool do_help(void)
175{
176
177#ifdef HAVE_LCD_COLOR
178 rb->lcd_set_foreground(LCD_WHITE);
179 rb->lcd_set_background(LCD_BLACK);
180#endif
181
182 rb->lcd_setfont(FONT_UI);
183
184 static char* help_text[]= {"2048", "", "Aim",
185 "", "Join", "the", "numbers", "to", "get", "to", "the", "2048", "tile!", "", "",
186 "How", "to", "Play", "",
187 "", "Use", "the", "directional", "keys", "to", "move", "the", "tiles.", "When",
188 "two", "tiles", "with", "the", "same", "number", "touch,", "they", "merge", "into", "one!"};
189
190 struct style_text style[] = {
191 {0, TEXT_CENTER | TEXT_UNDERLINE},
192 {2, C_RED},
193 {15, C_RED},
194 {16, C_RED},
195 {17, C_RED},
196 LAST_STYLE_ITEM
197 };
198
199 return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
200}
201
202/*** tile movement logic ***/
203
204/* this function performs the tile movement */
205static inline void slide_internal(int startx, int starty,
206 int stopx, int stopy,
207 int dx, int dy,
208 int lookx, int looky,
209 bool update_best)
210{
211 unsigned int best_score_old = best_score;
212
213 /* loop over the rows or columns, moving the tiles in the specified direction */
214 for(int y = starty; y != stopy; y += dy)
215 {
216 for(int x = startx; x != stopx; x += dx)
217 {
218 if(ctx->grid[x + lookx][y + looky] == ctx->grid[x][y] &&
219 ctx->grid[x][y] &&
220 !merged_grid[x + lookx][y + looky] &&
221 !merged_grid[x][y]) /* merge these two tiles */
222 {
223 /* Each merged tile cannot be merged again */
224 merged_grid[x + lookx][y + looky] = true;
225 ctx->grid[x + lookx][y + looky] = 2 * ctx->grid[x][y];
226 ctx->score += ctx->grid[x + lookx][y + looky];
227 ctx->grid[x][y] = 0;
228 }
229 else if(ctx->grid[x + lookx][y + looky] == 0) /* Empty! */
230 {
231 ctx->grid[x + lookx][y + looky] = ctx->grid[x][y];
232 ctx->grid[x][y] = 0;
233 }
234 }
235 }
236 if(ctx->score > best_score_old && update_best)
237 best_score = ctx->score;
238}
239
240/* these functions move each tile 1 space in the direction specified via calls to slide_internal */
241
242/* Up
243 0
244 1 ^ ^ ^ ^
245 2 ^ ^ ^ ^
246 3 ^ ^ ^ ^
247 0 1 2 3
248*/
249static void up(bool update_best)
250{
251 slide_internal(0, 1, /* start values */
252 GRID_SIZE, GRID_SIZE, /* stop values */
253 1, 1, /* delta values */
254 0, -1, /* lookahead values */
255 update_best);
256}
257
258/* Down
259 0 v v v v
260 1 v v v v
261 2 v v v v
262 3
263 0 1 2 3
264*/
265static void down(bool update_best)
266{
267 slide_internal(0, GRID_SIZE-2,
268 GRID_SIZE, -1,
269 1, -1,
270 0, 1,
271 update_best);
272}
273
274/* Left
275 0 < < <
276 1 < < <
277 2 < < <
278 3 < < <
279 0 1 2 3
280*/
281static void left(bool update_best)
282{
283 slide_internal(1, 0,
284 GRID_SIZE, GRID_SIZE,
285 1, 1,
286 -1, 0,
287 update_best);
288}
289
290/* Right
291 0 > > >
292 1 > > >
293 2 > > >
294 3 > > >
295 0 1 2 3
296*/
297static void right(bool update_best)
298{
299 slide_internal(GRID_SIZE-2, 0, /* start */
300 -1, GRID_SIZE, /* stop */
301 -1, 1, /* delta */
302 1, 0, /* lookahead */
303 update_best);
304}
305
306/* copies old_grid to ctx->grid */
307static inline void RESTORE_GRID(void)
308{
309 memcpy(&ctx->grid, &old_grid, sizeof(ctx->grid));
310}
311
312/* slightly modified base 2 logarithm, returns 1 when given zero, and log2(n) + 1 for anything else */
313static inline int ilog2(int n)
314{
315 if(n == 0)
316 return 1;
317 int log = 0;
318 while(n > 1)
319 {
320 n >>= 1;
321 ++log;
322 }
323 return log + 1;
324}
325
326/* low-depth displays resort to text drawing, see the #else case below */
327
328#if LCD_DEPTH > 1
329
330/* draws game screen + updates LCD */
331static void draw(void)
332{
333#ifdef HAVE_LCD_COLOR
334 rb->lcd_set_background(BACKGROUND);
335#endif
336
337 rb->lcd_clear_display();
338
339 /* draw the background */
340
341 rb->lcd_bitmap(_2048_background,
342 BACKGROUND_X, BACKGROUND_Y,
343 BMPWIDTH__2048_background, BMPWIDTH__2048_background);
344
345 /*
346 grey_gray_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPHEIGHT__2048_background);
347 */
348
349 /* draw the grid */
350
351 for(int y = 0; y < GRID_SIZE; ++y)
352 {
353 for(int x = 0; x < GRID_SIZE; ++x)
354 {
355 rb->lcd_bitmap_part(_2048_tiles, /* source */
356 BMPWIDTH__2048_tiles - BMPHEIGHT__2048_tiles * ilog2(ctx->grid[x][y]), 0, /* source upper left corner */
357 STRIDE(SCREEN_MAIN, BMPWIDTH__2048_tiles, BMPHEIGHT__2048_tiles), /* stride */
358 (BMPHEIGHT__2048_tiles + MIN_SPACE) * x + BASE_X, (BMPHEIGHT__2048_tiles + MIN_SPACE) * y + BASE_Y, /* dest upper-left corner */
359 BMPHEIGHT__2048_tiles, BMPHEIGHT__2048_tiles); /* size of the cut section */
360 }
361 }
362
363 /* draw the title */
364 char buf[32];
365
366#ifdef HAVE_LCD_COLOR
367 rb->lcd_set_foreground(TEXT_COLOR);
368#endif
369
370 rb->snprintf(buf, sizeof(buf), "%d", WINNING_TILE);
371
372 /* check if the title will overlap the grid */
373 int w, h;
374 rb->lcd_setfont(FONT_UI);
375 rb->font_getstringsize(buf, &w, &h, FONT_UI);
376 bool draw_title = true;
377 if(w + TITLE_X >= BACKGROUND_X && h + TITLE_Y >= BACKGROUND_Y)
378 {
379 /* if it goes into the grid, use the system font, which should be smaller */
380 rb->lcd_setfont(FONT_SYSFIXED);
381 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
382 if(w + TITLE_X >= BACKGROUND_X && h + TITLE_Y >= BACKGROUND_Y)
383 {
384 /* title can't fit, don't draw it */
385 draw_title = false;
386 h = 0;
387 }
388 }
389
390 if(draw_title)
391 rb->lcd_putsxy(TITLE_X, TITLE_Y, buf);
392
393 int score_y = TITLE_Y + h + VERT_SPACING;
394
395 /* draw the score */
396 rb->snprintf(buf, sizeof(buf), "Score: %d", ctx->score);
397
398#ifdef HAVE_LCD_COLOR
399 rb->lcd_set_foreground(LCD_WHITE);
400 rb->lcd_set_background(BOARD_BACKGROUND);
401#endif
402
403 rb->lcd_setfont(FONT_UI);
404 rb->font_getstringsize(buf, &w, &h, FONT_UI);
405
406 /* try making the score fit */
407 if(w + SCORE_X >= BACKGROUND_X && h + SCORE_Y >= BACKGROUND_Y)
408 {
409 /* score overflows */
410 /* first see if it fits with Score: and FONT_SYSFIXED */
411 rb->lcd_setfont(FONT_SYSFIXED);
412 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
413 if(w + SCORE_X < BACKGROUND_X)
414 /* it fits, go and draw it */
415 goto draw_lbl;
416
417 /* now try with S: and FONT_UI */
418 rb->snprintf(buf, sizeof(buf), "S: %d", ctx->score);
419 rb->font_getstringsize(buf, &w, &h, FONT_UI);
420 rb->lcd_setfont(FONT_UI);
421 if(w + SCORE_X < BACKGROUND_X)
422 goto draw_lbl;
423
424 /* now try with S: and FONT_SYSFIXED */
425 rb->snprintf(buf, sizeof(buf), "S: %d", ctx->score);
426 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
427 rb->lcd_setfont(FONT_SYSFIXED);
428 if(w + SCORE_X < BACKGROUND_X)
429 goto draw_lbl;
430
431 /* then try without Score: and FONT_UI */
432 rb->snprintf(buf, sizeof(buf), "%d", ctx->score);
433 rb->font_getstringsize(buf, &w, &h, FONT_UI);
434 rb->lcd_setfont(FONT_UI);
435 if(w + SCORE_X < BACKGROUND_X)
436 goto draw_lbl;
437
438 /* as a last resort, don't use Score: and use the system font */
439 rb->snprintf(buf, sizeof(buf), "%d", ctx->score);
440 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
441 rb->lcd_setfont(FONT_SYSFIXED);
442 if(w + SCORE_X < BACKGROUND_X)
443 goto draw_lbl;
444 else
445 goto skip_draw_score;
446 }
447
448draw_lbl:
449 rb->lcd_putsxy(SCORE_X, score_y, buf);
450 score_y += h + VERT_SPACING;
451
452 /* draw the best score */
453skip_draw_score:
454 rb->snprintf(buf, sizeof(buf), "Best: %d", best_score);
455
456#ifdef HAVE_LCD_COLOR
457 rb->lcd_set_foreground(LCD_WHITE);
458 rb->lcd_set_background(BOARD_BACKGROUND);
459#endif
460
461 rb->lcd_setfont(FONT_UI);
462 rb->font_getstringsize(buf, &w, &h, FONT_UI);
463 if(w + BEST_SCORE_X >= BACKGROUND_X && h + BEST_SCORE_Y >= BACKGROUND_Y)
464 {
465 /* score overflows */
466 /* first see if it fits with Score: and FONT_SYSFIXED */
467 rb->lcd_setfont(FONT_SYSFIXED);
468 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
469 if(w + BEST_SCORE_X < BACKGROUND_X)
470 /* it fits, go and draw it */
471 goto draw_best;
472
473 /* now try with S: and FONT_UI */
474 rb->snprintf(buf, sizeof(buf), "B: %d", best_score);
475 rb->font_getstringsize(buf, &w, &h, FONT_UI);
476 rb->lcd_setfont(FONT_UI);
477 if(w + BEST_SCORE_X < BACKGROUND_X)
478 goto draw_best;
479
480 /* now try with S: and FONT_SYSFIXED */
481 rb->snprintf(buf, sizeof(buf), "B: %d", best_score);
482 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
483 rb->lcd_setfont(FONT_SYSFIXED);
484 if(w + BEST_SCORE_X < BACKGROUND_X)
485 goto draw_best;
486
487 /* then try without Score: and FONT_UI */
488 rb->snprintf(buf, sizeof(buf), "%d", best_score);
489 rb->font_getstringsize(buf, &w, &h, FONT_UI);
490 rb->lcd_setfont(FONT_UI);
491 if(w + BEST_SCORE_X < BACKGROUND_X)
492 goto draw_best;
493
494 /* as a last resort, don't use Score: and use the system font */
495 rb->snprintf(buf, sizeof(buf), "%d", best_score);
496 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
497 rb->lcd_setfont(FONT_SYSFIXED);
498 if(w + BEST_SCORE_X < BACKGROUND_X)
499 goto draw_best;
500 else
501 goto skip_draw_best;
502 }
503draw_best:
504 rb->lcd_putsxy(BEST_SCORE_X, score_y, buf);
505
506skip_draw_best:
507 rb->lcd_update();
508
509 /* revert the font */
510 rb->lcd_setfont(WHAT_FONT);
511}
512
513#else /* LCD_DEPTH > 1 */
514
515/* 1-bit display :( */
516/* bitmaps are unreadable on these screens, so just resort to text-based drawing */
517static void draw(void)
518{
519 rb->lcd_clear_display();
520
521 /* Draw the grid */
522 /* find the biggest tile */
523 unsigned int biggest_tile = 0;
524 for(int x = 0; x < GRID_SIZE; ++x)
525 {
526 for(int y = 0; y < GRID_SIZE; ++y)
527 if(ctx->grid[x][y] > biggest_tile)
528 biggest_tile = ctx->grid[x][y];
529 }
530
531 char buf[32];
532
533 rb->snprintf(buf, 32, "%d", biggest_tile);
534
535 int biggest_tile_width = rb->strlen(buf) * rb->font_get_width(rb->font_get(WHAT_FONT), '0') + MIN_SPACE;
536
537 for(int y = 0; y < GRID_SIZE; ++y)
538 {
539 for(int x = 0; x < GRID_SIZE; ++x)
540 {
541 if(ctx->grid[x][y])
542 {
543 if(ctx->grid[x][y] > biggest_tile)
544 biggest_tile = ctx->grid[x][y];
545 rb->snprintf(buf, 32, "%d", ctx->grid[x][y]);
546 rb->lcd_putsxy(biggest_tile_width * x, y * max_numeral_height + max_numeral_height, buf);
547 }
548 }
549 }
550
551 /* Now draw the score, and the game title */
552 rb->snprintf(buf, 32, "Score: %d", ctx->score);
553 int buf_width, buf_height;
554 rb->font_getstringsize(buf, &buf_width, &buf_height, WHAT_FONT);
555
556 int score_leftmost = LCD_WIDTH - buf_width - 1;
557 /* Check if there is enough space to display "Score: ", otherwise, only display the score */
558 if(score_leftmost >= 0)
559 rb->lcd_putsxy(score_leftmost, 0, buf);
560 else
561 rb->lcd_putsxy(score_leftmost, 0, buf + rb->strlen("Score: "));
562
563 rb->snprintf(buf, 32, "%d", WINNING_TILE);
564 rb->font_getstringsize(buf, &buf_width, &buf_height, WHAT_FONT);
565 if(buf_width < score_leftmost)
566 rb->lcd_putsxy(0, 0, buf);
567
568 rb->lcd_update();
569}
570
571#endif /* LCD_DEPTH > 1 */
572
573/* place a 2 or 4 in a random empty space */
574static void place_random(void)
575{
576 int xpos[SPACES], ypos[SPACES];
577 int back = 0;
578 /* get the indexes of empty spaces */
579 for(int y = 0; y < GRID_SIZE; ++y)
580 for(int x = 0; x < GRID_SIZE; ++x)
581 {
582 if(!ctx->grid[x][y])
583 {
584 xpos[back] = x;
585 ypos[back++] = y;
586 }
587 }
588
589 if(!back)
590 /* no empty spaces */
591 return;
592
593 int idx = rand_range(0, back - 1);
594 ctx->grid[ xpos[idx] ][ ypos[idx] ] = rand_2_or_4();
595}
596
597/* checks for a win or loss */
598static bool check_gameover(void)
599{
600 /* first, check for a loss */
601 int oldscore = ctx->score;
602 bool have_legal_move = false;
603
604 memset(&merged_grid, 0, SPACES * sizeof(bool));
605 up(false);
606 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid)))
607 {
608 RESTORE_GRID();
609 ctx->score = oldscore;
610 have_legal_move = true;
611 }
612 RESTORE_GRID();
613
614 memset(&merged_grid, 0, SPACES * sizeof(bool));
615 down(false);
616 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid)))
617 {
618 RESTORE_GRID();
619 ctx->score = oldscore;
620 have_legal_move = true;
621 }
622 RESTORE_GRID();
623
624 memset(&merged_grid, 0, SPACES * sizeof(bool));
625 left(false);
626 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid)))
627 {
628 RESTORE_GRID();
629 ctx->score = oldscore;
630 have_legal_move = true;
631 }
632 RESTORE_GRID();
633
634 memset(&merged_grid, 0, SPACES * sizeof(bool));
635 right(false);
636 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid)))
637 {
638 RESTORE_GRID();
639 ctx->score = oldscore;
640 have_legal_move = true;
641 }
642 ctx->score = oldscore;
643 if(!have_legal_move)
644 {
645 /* no more legal moves */
646 draw(); /* Shame the player */
647 rb->splash(HZ*2, "Game Over!");
648 return true;
649 }
650
651 for(int y = 0;y < GRID_SIZE; ++y)
652 {
653 for(int x = 0; x < GRID_SIZE; ++x)
654 {
655 if(ctx->grid[x][y] == WINNING_TILE && !ctx->already_won)
656 {
657 /* Let the user see the tile in its full glory... */
658 draw();
659 ctx->already_won = true;
660 rb->splash(HZ*2,"You win!");
661 }
662 }
663 }
664 return false;
665}
666
667/* loads highscores from disk */
668/* creates an empty structure if the file does not exist */
669static void load_hs(void)
670{
671 if(rb->file_exists(HISCORES_FILE))
672 highscore_load(HISCORES_FILE, highscores, NUM_SCORES);
673 else
674 memset(highscores, 0, sizeof(struct highscore) * NUM_SCORES);
675}
676
677/* initialize the data structures */
678static void init_game(bool newgame)
679{
680 best_score = highscores[0].score;
681 if(loaded && ctx->score > best_score)
682 best_score = ctx->score;
683
684 if(newgame)
685 {
686 /* initialize the game context */
687 memset(ctx->grid, 0, sizeof(ctx->grid));
688 for(int i = 0; i < NUM_STARTING_TILES; ++i)
689 {
690 place_random();
691 }
692 ctx->score = 0;
693 ctx->already_won = false;
694 }
695
696 /* using the menu resets the font */
697 /* set it again here */
698
699 rb->lcd_setfont(WHAT_FONT);
700
701 /* Now calculate font sizes */
702 /* Now get the height of the font */
703 rb->font_getstringsize("0123456789", NULL, &max_numeral_height, WHAT_FONT);
704 max_numeral_height += VERT_SPACING;
705
706#if LCD_DEPTH <= 1
707 max_numeral_width = rb->font_get_width(rb->font_get(WHAT_FONT), '0');
708#endif
709
710 backlight_ignore_timeout();
711
712 draw();
713}
714
715/* save the current game state */
716static void save_game(void)
717{
718 rb->splash(0, "Saving...");
719 int fd = rb->open(RESUME_FILE, O_WRONLY|O_CREAT, 0666);
720 if(fd < 0)
721 {
722 return;
723 }
724
725 /* calculate checksum */
726 ctx->cksum = 0;
727
728 for(int x = 0; x < GRID_SIZE; ++x)
729 for(int y = 0; y < GRID_SIZE; ++y)
730 ctx->cksum += ctx->grid[x][y];
731
732 ctx->cksum ^= ctx->score;
733
734 rb->write(fd, ctx, sizeof(struct game_ctx_t));
735 rb->close(fd);
736 rb->lcd_update();
737}
738
739/* loads a saved game, returns true on success */
740static bool load_game(void)
741{
742 int success = 0;
743 int fd = rb->open(RESUME_FILE, O_RDONLY);
744 if(fd < 0)
745 {
746 rb->remove(RESUME_FILE);
747 return false;
748 }
749
750 int numread = rb->read(fd, ctx, sizeof(struct game_ctx_t));
751
752 /* verify checksum */
753 unsigned int calc = 0;
754 for(int x = 0; x < GRID_SIZE; ++x)
755 for(int y = 0; y < GRID_SIZE; ++y)
756 calc += ctx->grid[x][y];
757
758 calc ^= ctx->score;
759
760 if(numread == sizeof(struct game_ctx_t) && calc == ctx->cksum)
761 ++success;
762
763 rb->close(fd);
764 rb->remove(RESUME_FILE);
765
766 return (success > 0);
767}
768
769/* update the highscores with ctx->score */
770static void hs_check_update(bool noshow)
771{
772 /* first, find the biggest tile to show as the level */
773 unsigned int biggest = 0;
774 for(int x = 0; x < GRID_SIZE; ++x)
775 {
776 for(int y = 0; y < GRID_SIZE; ++y)
777 {
778 if(ctx->grid[x][y] > biggest)
779 biggest = ctx->grid[x][y];
780 }
781 }
782
783 int hs_idx = highscore_update(ctx->score,biggest, "", highscores,NUM_SCORES);
784 if(!noshow)
785 {
786 /* show the scores if there is a new high score */
787 if(hs_idx >= 0)
788 {
789 rb->splashf(HZ*2, "New High Score: %d", ctx->score);
790 rb->lcd_clear_display();
791 highscore_show(hs_idx, highscores, NUM_SCORES, true);
792 }
793 }
794 highscore_save(HISCORES_FILE, highscores, NUM_SCORES);
795}
796
797/* asks the user if they wish to quit */
798static bool confirm_quit(void)
799{
800 const struct text_message prompt = { (const char*[]) {"Are you sure?", "This will clear your current game."}, 2};
801 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
802 if(response == YESNO_NO)
803 return false;
804 else
805 return true;
806}
807
808/* show the pause menu */
809static int do_2048_pause_menu(void)
810{
811 int sel = 0;
812 MENUITEM_STRINGLIST(menu,"2048 Menu", NULL,
813 "Resume Game",
814 "Start New Game",
815 "High Scores",
816 "Playback Control",
817 "Help",
818 "Quit without Saving",
819 "Quit");
820 while(1)
821 {
822 switch(rb->do_menu(&menu, &sel, NULL, false))
823 {
824 case 0:
825 draw();
826 return 0;
827 case 1:
828 {
829 if(!confirm_quit())
830 break;
831 else
832 {
833 hs_check_update(false);
834 return 1;
835 }
836 }
837 case 2:
838 highscore_show(-1, highscores, NUM_SCORES, true);
839 break;
840 case 3:
841 playback_control(NULL);
842 break;
843 case 4:
844 do_help();
845 break;
846 case 5: /* quit w/o saving */
847 {
848 if(!confirm_quit())
849 break;
850 else
851 {
852 return 2;
853 }
854 }
855 case 6:
856 return 3;
857 default:
858 break;
859 }
860 }
861}
862
863static void exit_handler(void)
864{
865 cleanup();
866 if(abnormal_exit)
867 save_game();
868#ifdef HAVE_ADJUSTABLE_CPU_FREQ
869 rb->cpu_boost(false); /* back to idle */
870#endif
871 return;
872}
873
874static bool check_hs;
875
876/* main game loop */
877static enum plugin_status do_game(bool newgame)
878{
879 init_game(newgame);
880 rb_atexit(exit_handler);
881 int made_move = 0;
882 while(1)
883 {
884#ifdef HAVE_ADJUSTABLE_CPU_FREQ
885 rb->cpu_boost(false); /* Save battery when idling */
886#endif
887 /* Wait for a button press */
888 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
889 made_move = 0;
890
891 memset(&merged_grid, 0, SPACES*sizeof(bool));
892 memcpy(&old_grid, &ctx->grid, sizeof(int)*SPACES);
893
894 unsigned int grid_before_anim_step[GRID_SIZE][GRID_SIZE];
895
896#ifdef HAVE_ADJUSTABLE_CPU_FREQ
897 rb->cpu_boost(true); /* doing work now... */
898#endif
899 switch(button)
900 {
901 case KEY_UP:
902 for(int i = 0; i < GRID_SIZE - 1; ++i)
903 {
904 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid));
905 up(true);
906 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)))
907 {
908 rb->sleep(ANIM_SLEEPTIME);
909 draw();
910 }
911 }
912 made_move = 1;
913 break;
914 case KEY_DOWN:
915 for(int i = 0; i < GRID_SIZE - 1; ++i)
916 {
917 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid));
918 down(true);
919 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)))
920 {
921 rb->sleep(ANIM_SLEEPTIME);
922 draw();
923 }
924 }
925 made_move = 1;
926 break;
927 case KEY_LEFT:
928 for(int i = 0; i < GRID_SIZE - 1; ++i)
929 {
930 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid));
931 left(true);
932 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)))
933 {
934 rb->sleep(ANIM_SLEEPTIME);
935 draw();
936 }
937 }
938 made_move = 1;
939 break;
940 case KEY_RIGHT:
941 for(int i = 0; i < GRID_SIZE - 1; ++i)
942 {
943 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid));
944 right(true);
945 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)))
946 {
947 rb->sleep(ANIM_SLEEPTIME);
948 draw();
949 }
950 }
951 made_move = 1;
952 break;
953 case KEY_EXIT:
954 switch(do_2048_pause_menu())
955 {
956 case 0: /* resume */
957 break;
958 case 1: /* new game */
959 init_game(true);
960 made_move = 1;
961 continue;
962 case 2: /* quit without saving */
963 check_hs = true;
964 rb->remove(RESUME_FILE);
965 return PLUGIN_ERROR;
966 case 3: /* save and quit */
967 check_hs = false;
968 save_game();
969 return PLUGIN_ERROR;
970 }
971 break;
972 default:
973 {
974 exit_on_usb(button); /* handle poweroff and USB events */
975 break;
976 }
977 }
978
979 if(made_move)
980 {
981 /* Check if any tiles moved, then add random */
982 if(memcmp(&old_grid, ctx->grid, sizeof(ctx->grid)))
983 {
984 place_random();
985 }
986 memcpy(&old_grid, ctx->grid, sizeof(ctx->grid));
987 if(check_gameover())
988 return PLUGIN_OK;
989 draw();
990 }
991#ifdef HAVE_ADJUSTABLE_CPU_FREQ
992 rb->cpu_boost(false); /* back to idle */
993#endif
994 rb->yield();
995 }
996}
997
998/* decide if this_item should be shown in the main menu */
999/* used to hide resume option when there is no save */
1000static int mainmenu_cb(int action,
1001 const struct menu_item_ex *this_item,
1002 struct gui_synclist *this_list)
1003{
1004 (void)this_list;
1005 int idx = ((intptr_t)this_item);
1006 if(action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5))
1007 return ACTION_EXIT_MENUITEM;
1008 return action;
1009}
1010
1011/* show the main menu */
1012static enum plugin_status do_2048_menu(void)
1013{
1014 int sel = 0;
1015 loaded = load_game();
1016 MENUITEM_STRINGLIST(menu,
1017 "2048 Menu",
1018 mainmenu_cb,
1019 "Resume Game",
1020 "Start New Game",
1021 "High Scores",
1022 "Playback Control",
1023 "Help",
1024 "Quit without Saving",
1025 "Quit");
1026 while(true)
1027 {
1028 switch(rb->do_menu(&menu, &sel, NULL, false))
1029 {
1030 case 0: /* Start new game or resume a game */
1031 case 1:
1032 {
1033 if(sel == 1 && loaded)
1034 {
1035 if(!confirm_quit())
1036 break;
1037 }
1038 enum plugin_status ret = do_game(sel == 1);
1039 switch(ret)
1040 {
1041 case PLUGIN_OK:
1042 {
1043 loaded = false;
1044 rb->remove(RESUME_FILE);
1045 hs_check_update(false);
1046 break;
1047 }
1048 case PLUGIN_USB_CONNECTED:
1049 save_game();
1050 /* Don't bother showing the high scores... */
1051 return ret;
1052 case PLUGIN_ERROR: /* exit without menu */
1053 if(check_hs)
1054 hs_check_update(false);
1055 return PLUGIN_OK;
1056 default:
1057 break;
1058 }
1059 break;
1060 }
1061 case 2:
1062 highscore_show(-1, highscores, NUM_SCORES, true);
1063 break;
1064 case 3:
1065 playback_control(NULL);
1066 break;
1067 case 4:
1068 do_help();
1069 break;
1070 case 5:
1071 if(confirm_quit())
1072 return PLUGIN_OK;
1073 break;
1074 case 6:
1075 if(loaded)
1076 save_game();
1077 return PLUGIN_OK;
1078 default:
1079 break;
1080 }
1081 }
1082}
1083
1084/* plugin entry point */
1085enum plugin_status plugin_start(const void* param)
1086{
1087 (void)param;
1088 rb->srand(*rb->current_tick);
1089 load_hs();
1090 rb->lcd_setfont(WHAT_FONT);
1091
1092 /* now start the game menu */
1093 enum plugin_status ret = do_2048_menu();
1094
1095 highscore_save(HISCORES_FILE, highscores, NUM_SCORES);
1096 cleanup();
1097
1098 abnormal_exit = false;
1099
1100 return ret;
1101}