summaryrefslogtreecommitdiff
path: root/apps/plugins/2048.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/2048.c')
-rw-r--r--apps/plugins/2048.c916
1 files changed, 916 insertions, 0 deletions
diff --git a/apps/plugins/2048.c b/apps/plugins/2048.c
new file mode 100644
index 0000000000..9a74b0ff38
--- /dev/null
+++ b/apps/plugins/2048.c
@@ -0,0 +1,916 @@
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#ifdef HAVE_LCD_BITMAP
45#include "pluginbitmaps/_2048_background.h"
46#include "pluginbitmaps/_2048_tiles.h"
47#endif
48
49/* defines */
50
51#define ANIM_SLEEPTIME (HZ/20)
52#define GRID_SIZE 4
53#define HISCORES_FILE PLUGIN_GAMES_DATA_DIR "/2048.score"
54#define NUM_SCORES 5
55#define NUM_STARTING_TILES 2
56#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/2048.save"
57#define WHAT_FONT FONT_UI
58#define SPACES (GRID_SIZE*GRID_SIZE)
59#define MIN_SPACE (BMPHEIGHT__2048_tiles*0.134) /* space between tiles */
60#define MAX_UNDOS 64
61#define VERT_SPACING 4
62#define WINNING_TILE 2048
63
64/* screen-specific configuration */
65
66#if LCD_WIDTH<LCD_HEIGHT
67/* tall screens */
68#define TITLE_X 0
69#define TITLE_Y 0
70#define BASE_Y (BMPHEIGHT__2048_tiles*1.5)
71#define BASE_X (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
72#define SCORE_X 0
73#define SCORE_Y (max_numeral_height)
74#define BEST_SCORE_X 0
75#define BEST_SCORE_Y (2*max_numeral_height)
76#else
77/* wide screens or square screens*/
78#define TITLE_X 0
79#define TITLE_Y 0
80#define BASE_X (LCD_WIDTH-(GRID_SIZE*BMPHEIGHT__2048_tiles)-(((GRID_SIZE+1)*MIN_SPACE)))
81#define BASE_Y (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
82#define SCORE_X 0
83#define SCORE_Y (max_numeral_height)
84#define BEST_SCORE_X 0
85#define BEST_SCORE_Y (2*max_numeral_height)
86#endif
87
88#define BACKGROUND_X (BASE_X-MIN_SPACE)
89#define BACKGROUND_Y (BASE_Y-MIN_SPACE)
90
91/* key mappings */
92
93#define KEY_UP PLA_UP
94#define KEY_DOWN PLA_DOWN
95#define KEY_LEFT PLA_LEFT
96#define KEY_RIGHT PLA_RIGHT
97#define KEY_EXIT PLA_CANCEL
98#define KEY_UNDO PLA_SELECT
99
100/* colors */
101
102#define BACKGROUND (LCD_RGBPACK(0xfa, 0xf8, 0xef))
103#define BOARD_BACKGROUND (LCD_RGBPACK(0xbb, 0xad, 0xa0))
104#define TEXT_COLOR (LCD_RGBPACK(0x77, 0x6e, 0x65))
105
106static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
107
108/* game data */
109struct game_ctx_t {
110 int grid[GRID_SIZE][GRID_SIZE];
111 int score;
112 int cksum; /* sum of grid, XORed by score */
113 bool already_won;
114};
115
116static struct game_ctx_t ctx_data;
117/* use a pointer to make save/load easier */
118static struct game_ctx_t *ctx=&ctx_data;
119
120/* temporary data */
121static bool merged_grid[GRID_SIZE][GRID_SIZE];
122static int old_grid[GRID_SIZE][GRID_SIZE];
123static int max_numeral_height=-1;
124static bool loaded=false;
125
126/* first init_game will set this, when it is exceeded, it will be updated in the slide functions */
127static int best_score;
128
129static bool abnormal_exit=true;
130static struct highscore highscores[NUM_SCORES];
131
132/* returns a random int between min and max */
133static inline int rand_range(int min, int max)
134{
135 return rb->rand()%(max-min+1)+min;
136}
137
138/* prepares to exit */
139static void cleanup(void)
140{
141 backlight_use_settings();
142}
143
144/* returns 2 or 4 */
145static inline int rand_2_or_4(void)
146{
147 /* 1 in 10 chance of a four */
148 if(rb->rand()%10==0)
149 return 4;
150 else
151 return 2;
152}
153
154/* display the help text */
155static bool do_help(void)
156{
157#ifdef HAVE_LCD_COLOR
158 rb->lcd_set_foreground(LCD_WHITE);
159 rb->lcd_set_background(LCD_BLACK);
160#endif
161 rb->lcd_setfont(FONT_UI);
162 char* help_text[]= {"2048", "", "Aim",
163 "", "Join", "the", "numbers", "to", "get", "to", "the", "2048", "tile!", "", "",
164 "How", "to", "Play", "",
165 "", "Use", "the", "directional", "keys", "to", "move", "the", "tiles.", "When",
166 "two", "tiles", "with", "the", "same", "number", "touch,", "they", "merge", "into", "one!"};
167 struct style_text style[]={
168 {0, TEXT_CENTER|TEXT_UNDERLINE},
169 {2, C_RED},
170 {15, C_RED}, {16, C_RED}, {17,C_RED},
171 LAST_STYLE_ITEM
172 };
173 return display_text(ARRAYLEN(help_text), help_text, style,NULL,true);
174}
175
176/*** the logic for sliding ***/
177
178/* this is the helper function that does the actual tile moving */
179
180static inline void slide_internal(int startx, int starty,
181 int stopx, int stopy,
182 int dx, int dy,
183 int lookx, int looky)
184{
185 int best_score_before=best_score;
186 for(int y=starty;y!=stopy;y+=dy)
187 {
188 for(int x=startx;x!=stopx;x+=dx)
189 {
190 if(ctx->grid[x+lookx][y+looky]==ctx->grid[x][y] && ctx->grid[x][y] && !merged_grid[x+lookx][y+looky] && !merged_grid[x][y]) /* Slide into */
191 {
192 /* Each merged tile cannot be merged again */
193 merged_grid[x+lookx][y+looky]=true;
194 ctx->grid[x+lookx][y+looky]=2*ctx->grid[x][y];
195 ctx->score+=ctx->grid[x+lookx][y+looky];
196 ctx->grid[x][y]=0;
197 }
198 else if(ctx->grid[x+lookx][y+looky]==0) /* Empty! */
199 {
200 ctx->grid[x+lookx][y+looky]=ctx->grid[x][y];
201 ctx->grid[x][y]=0;
202 }
203 }
204 }
205 if(ctx->score>best_score_before)
206 best_score=ctx->score;
207}
208
209/* these functions move each tile 1 space in the direction specified via calls to slide_internal */
210
211/* Up
212 0
213 1 ^ ^ ^ ^
214 2 ^ ^ ^ ^
215 3 ^ ^ ^ ^
216 0 1 2 3
217*/
218static void up(void)
219{
220 slide_internal(0, 1, /* start values */
221 GRID_SIZE, GRID_SIZE, /* stop values */
222 1, 1, /* delta values */
223 0, -1); /* lookahead values */
224}
225/* Down
226 0 v v v v
227 1 v v v v
228 2 v v v v
229 3
230 0 1 2 3
231*/
232static void down(void)
233{
234 slide_internal(0, GRID_SIZE-2,
235 GRID_SIZE, -1,
236 1, -1,
237 0, 1);
238}
239/* Left
240 0 < < <
241 1 < < <
242 2 < < <
243 3 < < <
244 0 1 2 3
245*/
246static void left(void)
247{
248 slide_internal(1, 0,
249 GRID_SIZE, GRID_SIZE,
250 1, 1,
251 -1, 0);
252}
253/* Right
254 0 > > >
255 1 > > >
256 2 > > >
257 3 > > >
258 0 1 2 3
259*/
260static void right(void)
261{
262 slide_internal(GRID_SIZE-2, 0, /* start */
263 -1, GRID_SIZE, /* stop */
264 -1, 1, /* delta */
265 1, 0); /* lookahead */
266}
267
268/* slightly modified version of base 2 log, returns 1 when given zero, and log2(n)+1 for anything else */
269
270static inline int ilog2(int n)
271{
272 if(n==0)
273 return 1;
274 int log=0;
275 while(n>1)
276 {
277 n>>=1;
278 ++log;
279 }
280 return log+1;
281}
282static void draw(void)
283{
284#ifdef HAVE_LCD_COLOR
285 rb->lcd_set_background(BACKGROUND);
286#endif
287 rb->lcd_clear_display();
288
289 /* draw the background */
290
291 rb->lcd_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPWIDTH__2048_background);
292
293 /*
294 grey_gray_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPHEIGHT__2048_background);
295 */
296
297 /* draw the grid */
298
299 for(int y=0;y<GRID_SIZE;++y)
300 {
301 for(int x=0;x<GRID_SIZE;++x)
302 {
303 rb->lcd_bitmap_part(_2048_tiles, /* source */
304 BMPWIDTH__2048_tiles-BMPHEIGHT__2048_tiles*ilog2(ctx->grid[x][y]), 0, /* source upper left corner */
305 STRIDE(SCREEN_MAIN, BMPWIDTH__2048_tiles, BMPHEIGHT__2048_tiles), /* stride */
306 (BMPHEIGHT__2048_tiles+MIN_SPACE)*x+BASE_X, (BMPHEIGHT__2048_tiles+MIN_SPACE)*y+BASE_Y, /* dest upper-left corner */
307 BMPHEIGHT__2048_tiles, BMPHEIGHT__2048_tiles); /* size of the cut section */
308 }
309 }
310 /* draw the title */
311 char buf[32];
312#ifdef HAVE_LCD_COLOR
313 rb->lcd_set_foreground(TEXT_COLOR);
314#endif
315 rb->snprintf(buf, 31, "%d", WINNING_TILE);
316
317 /* check if the title will go into the grid */
318 int w, h;
319 rb->lcd_setfont(FONT_UI);
320 rb->font_getstringsize(buf, &w, &h, FONT_UI);
321 if(w+TITLE_X>=BACKGROUND_X && h+TITLE_Y>=BACKGROUND_Y)
322 {
323 /* if it goes into the grid, use the system font, which should be smaller */
324 rb->lcd_setfont(FONT_SYSFIXED);
325 }
326 rb->lcd_putsxy(TITLE_X, TITLE_Y, buf);
327
328 /* draw the score */
329 rb->snprintf(buf, 31, "Score: %d", ctx->score);
330#ifdef HAVE_LCD_COLOR
331 rb->lcd_set_foreground(LCD_WHITE);
332 rb->lcd_set_background(BOARD_BACKGROUND);
333#endif
334 rb->lcd_setfont(FONT_UI);
335 rb->font_getstringsize(buf, &w, &h, FONT_UI);
336 if(w+SCORE_X>=BACKGROUND_X && h+SCORE_Y>=BACKGROUND_Y)
337 {
338 /* score overflows */
339 /* first see if it fits with Score: and FONT_SYSFIXED */
340 rb->lcd_setfont(FONT_SYSFIXED);
341 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
342 if(w+SCORE_X < BACKGROUND_X)
343 /* it fits, go and draw it */
344 goto draw_lbl;
345
346 /* now try with S: and FONT_UI */
347 rb->snprintf(buf, 31, "S: %d", ctx->score);
348 rb->font_getstringsize(buf, &w, &h, FONT_UI);
349 rb->lcd_setfont(FONT_UI);
350 if(w+SCORE_X<BACKGROUND_X)
351 goto draw_lbl;
352
353 /* now try with S: and FONT_SYSFIXED */
354 rb->snprintf(buf, 31, "S: %d", ctx->score);
355 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
356 rb->lcd_setfont(FONT_SYSFIXED);
357 if(w+SCORE_X<BACKGROUND_X)
358 goto draw_lbl;
359
360 /* then try without Score: and FONT_UI */
361 rb->snprintf(buf, 31, "%d", ctx->score);
362 rb->font_getstringsize(buf, &w, &h, FONT_UI);
363 rb->lcd_setfont(FONT_UI);
364 if(w+SCORE_X<BACKGROUND_X)
365 goto draw_lbl;
366
367 /* as a last resort, don't use Score: and use the system font */
368 rb->snprintf(buf, 31, "%d", ctx->score);
369 rb->lcd_setfont(FONT_SYSFIXED);
370 }
371draw_lbl:
372 rb->lcd_putsxy(SCORE_X, SCORE_Y, buf);
373
374 /* draw the best score */
375
376 rb->snprintf(buf, 31, "Best: %d", best_score);
377#ifdef HAVE_LCD_COLOR
378 rb->lcd_set_foreground(LCD_WHITE);
379 rb->lcd_set_background(BOARD_BACKGROUND);
380#endif
381 rb->lcd_setfont(FONT_UI);
382 rb->font_getstringsize(buf, &w, &h, FONT_UI);
383 if(w+BEST_SCORE_X>=BACKGROUND_X && h+BEST_SCORE_Y>=BACKGROUND_Y)
384 {
385 /* score overflows */
386 /* first see if it fits with Score: and FONT_SYSFIXED */
387 rb->lcd_setfont(FONT_SYSFIXED);
388 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
389 if(w+BEST_SCORE_X < BACKGROUND_X)
390 /* it fits, go and draw it */
391 goto draw_best;
392
393 /* now try with S: and FONT_UI */
394 rb->snprintf(buf, 31, "B: %d", best_score);
395 rb->font_getstringsize(buf, &w, &h, FONT_UI);
396 rb->lcd_setfont(FONT_UI);
397 if(w+BEST_SCORE_X<BACKGROUND_X)
398 goto draw_best;
399
400 /* now try with S: and FONT_SYSFIXED */
401 rb->snprintf(buf, 31, "B: %d", best_score);
402 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
403 rb->lcd_setfont(FONT_SYSFIXED);
404 if(w+BEST_SCORE_X<BACKGROUND_X)
405 goto draw_best;
406
407 /* then try without Score: and FONT_UI */
408 rb->snprintf(buf, 31, "%d", best_score);
409 rb->font_getstringsize(buf, &w, &h, FONT_UI);
410 rb->lcd_setfont(FONT_UI);
411 if(w+BEST_SCORE_X<BACKGROUND_X)
412 goto draw_best;
413
414 /* as a last resort, don't use Score: and use the system font */
415 rb->snprintf(buf, 31, "%d", best_score);
416 rb->lcd_setfont(FONT_SYSFIXED);
417 }
418draw_best:
419 rb->lcd_putsxy(BEST_SCORE_X, BEST_SCORE_Y, buf);
420
421 rb->lcd_update();
422 /* revert the font back */
423 rb->lcd_setfont(WHAT_FONT);
424}
425
426/* place a 2 or 4 in a random empty space */
427static void place_random(void)
428{
429 int xpos[SPACES],ypos[SPACES];
430 int back=0;
431 /* get the indexes of empty spaces */
432 for(int y=0;y<GRID_SIZE;++y)
433 for(int x=0;x<GRID_SIZE;++x)
434 {
435 if(!ctx->grid[x][y])
436 {
437 xpos[back]=x;
438 ypos[back]=y;
439 ++back;
440 }
441 }
442 if(!back)
443 /* no empty spaces */
444 return;
445 int idx=rand_range(0,back-1);
446 ctx->grid[xpos[idx]][ypos[idx]]=rand_2_or_4();
447}
448
449/* copies old_grid to ctx->grid */
450static void restore_old_grid(void)
451{
452 memcpy(&ctx->grid, &old_grid, sizeof(int)*SPACES);
453}
454
455/* checks for a win or loss */
456static bool check_gameover(void)
457{
458 int numempty=0;
459 for(int y=0;y<GRID_SIZE;++y)
460 {
461 for(int x=0;x<GRID_SIZE;++x)
462 {
463 if(ctx->grid[x][y]==0)
464 ++numempty;
465 if(ctx->grid[x][y]==WINNING_TILE && !ctx->already_won)
466 {
467 /* Let the user see the tile in its full glory... */
468 draw();
469 ctx->already_won=true;
470 rb->splash(HZ*2,"You win!");
471 const struct text_message prompt={(const char*[]){"Keep going?"}, 1};
472 enum yesno_res keepgoing=rb->gui_syncyesno_run(&prompt, NULL, NULL);
473 if(keepgoing==YESNO_NO)
474 return true;
475 else
476 return false;
477 }
478 }
479 }
480 if(!numempty)
481 {
482 /* No empty spaces, check for valid moves */
483 /* Then, get the current score */
484 int oldscore=ctx->score;
485 memset(&merged_grid,0,SPACES*sizeof(bool));
486 up();
487 if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
488 {
489 restore_old_grid();
490 ctx->score=oldscore;
491 return false;
492 }
493 restore_old_grid();
494 memset(&merged_grid,0,SPACES*sizeof(bool));
495 down();
496 if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
497 {
498 restore_old_grid();
499 ctx->score=oldscore;
500 return false;
501 }
502 restore_old_grid();
503 memset(&merged_grid,0,SPACES*sizeof(bool));
504 left();
505 if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
506 {
507 restore_old_grid();
508 ctx->score=oldscore;
509 return false;
510 }
511 restore_old_grid();
512 memset(&merged_grid,0,SPACES*sizeof(bool));
513 right();
514 if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
515 {
516 restore_old_grid();
517 ctx->score=oldscore;
518 return false;
519 }
520 /* no more legal moves */
521 ctx->score=oldscore;
522 draw(); /* Shame the player :) */
523 rb->splash(HZ*2, "Game Over!");
524 return true;
525 }
526 return false;
527}
528
529/* loads highscores from disk */
530/* creates an empty structure if the file does not exist */
531static void load_hs(void)
532{
533 if(rb->file_exists(HISCORES_FILE))
534 highscore_load(HISCORES_FILE, highscores, NUM_SCORES);
535 else
536 memset(highscores, 0, sizeof(struct highscore)*NUM_SCORES);
537}
538
539/* initialize the data structures */
540static void init_game(bool newgame)
541{
542 best_score=highscores[0].score;
543 if(newgame)
544 {
545 /* initialize the game context */
546 memset(ctx->grid, 0, sizeof(int)*SPACES);
547 for(int i=0;i<NUM_STARTING_TILES;++i)
548 {
549 place_random();
550 }
551 ctx->score=0;
552 ctx->already_won=false;
553 }
554 /* using the menu resets the font */
555 /* set it again here */
556 rb->lcd_setfont(WHAT_FONT);
557 /* Now calculate font sizes */
558 /* Now get the height of the font */
559 rb->font_getstringsize("0123456789", NULL, &max_numeral_height,WHAT_FONT);
560 max_numeral_height+=VERT_SPACING;
561 backlight_ignore_timeout();
562 rb->lcd_clear_display();
563 draw();
564}
565
566/* save the current game state */
567static void save_game(void)
568{
569 rb->splash(0, "Saving...");
570 int fd=rb->open(RESUME_FILE,O_WRONLY|O_CREAT, 0666);
571 if(fd<0)
572 {
573 return;
574 }
575 ctx->cksum=0;
576 for(int x=0;x<GRID_SIZE;++x)
577 for(int y=0;y<GRID_SIZE;++y)
578 ctx->cksum+=ctx->grid[x][y];
579 ctx->cksum^=ctx->score;
580 rb->write(fd, ctx,sizeof(struct game_ctx_t));
581 rb->close(fd);
582 rb->lcd_update();
583}
584
585/* loads a saved game, returns true on success */
586static bool load_game(void)
587{
588 int success=0;
589 int fd=rb->open(RESUME_FILE, O_RDONLY);
590 if(fd<0)
591 {
592 rb->remove(RESUME_FILE);
593 return false;
594 }
595 int numread=rb->read(fd, ctx, sizeof(struct game_ctx_t));
596 int calc=0;
597 for(int x=0;x<GRID_SIZE;++x)
598 for(int y=0;y<GRID_SIZE;++y)
599 calc+=ctx->grid[x][y];
600 calc^=ctx->score;
601 if(numread==sizeof(struct game_ctx_t) && calc==ctx->cksum)
602 ++success;
603 rb->close(fd);
604 rb->remove(RESUME_FILE);
605 return (success==1);
606}
607
608/* update the highscores with ctx->score */
609static void hs_check_update(bool noshow)
610{
611 /* first, find the biggest tile to show as the level */
612 int biggest=0;
613 for(int x=0;x<GRID_SIZE;++x)
614 {
615 for(int y=0;y<GRID_SIZE;++y)
616 {
617 if(ctx->grid[x][y]>biggest)
618 biggest=ctx->grid[x][y];
619 }
620 }
621 int hs_idx=highscore_update(ctx->score,biggest, "", highscores,NUM_SCORES);
622 if(!noshow)
623 {
624 /* show the scores if there is a new high score */
625 if(hs_idx>=0)
626 {
627 rb->splashf(HZ*2,"New High Score: %d", ctx->score);
628 rb->lcd_clear_display();
629 highscore_show(hs_idx,highscores,NUM_SCORES,true);
630 }
631 }
632 highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
633}
634
635/* asks the user if they wish to quit */
636static bool confirm_quit(void)
637{
638 const struct text_message prompt={(const char*[]){"Are you sure?", "This will clear your current game."}, 2};
639 enum yesno_res response=rb->gui_syncyesno_run(&prompt, NULL, NULL);
640 if(response==YESNO_NO)
641 return false;
642 else
643 return true;
644}
645
646/* show the pause menu */
647static int do_2048_pause_menu(void)
648{
649 int sel=0;
650 MENUITEM_STRINGLIST(menu,"2048 Menu", NULL,
651 "Resume Game",
652 "Start New Game",
653 "High Scores",
654 "Playback Control",
655 "Help",
656 "Quit without Saving",
657 "Quit");
658 bool quit=false;
659 while(!quit)
660 {
661 switch(rb->do_menu(&menu, &sel, NULL, false))
662 {
663 case 0:
664 draw();
665 return 0;
666 case 1:
667 {
668 if(!confirm_quit())
669 break;
670 else
671 {
672 hs_check_update(false);
673 return 1;
674 }
675 }
676 case 2:
677 highscore_show(-1,highscores, NUM_SCORES, true);
678 break;
679 case 3:
680 playback_control(NULL);
681 break;
682 case 4:
683 do_help();
684 break;
685 case 5: /* quit w/o saving */
686 {
687 if(!confirm_quit())
688 break;
689 else
690 {
691 return 2;
692 }
693 }
694 case 6:
695 return 3;
696 }
697 }
698 return 0;
699}
700
701static void exit_handler(void)
702{
703 cleanup();
704 if(abnormal_exit)
705 save_game();
706 return;
707}
708static bool check_hs;
709/* main game loop */
710static enum plugin_status do_game(bool newgame)
711{
712 init_game(newgame);
713 rb_atexit(&exit_handler);
714 int made_move=0;
715 while(1)
716 {
717#ifdef HAVE_ADJUSTABLE_CPU_FREQ
718 rb->cpu_boost(false); /* Save battery when idling */
719#endif
720 /* Wait for a button press */
721 int button=pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
722 made_move=0;
723 memset(&merged_grid,0,SPACES*sizeof(bool));
724 memcpy(&old_grid, &ctx->grid, sizeof(int)*SPACES);
725 int grid_before_anim_step[GRID_SIZE][GRID_SIZE];
726#ifdef HAVE_ADJUSTABLE_CPU_FREQ
727 rb->cpu_boost(true); /* doing work now... */
728#endif
729 switch(button)
730 {
731 case KEY_UP:
732 for(int i=0;i<GRID_SIZE-1;++i)
733 {
734 memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
735 up();
736 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
737 {
738 rb->sleep(ANIM_SLEEPTIME);
739 draw();
740 }
741 }
742 made_move=1;
743 break;
744 case KEY_DOWN:
745 for(int i=0;i<GRID_SIZE-1;++i)
746 {
747 memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
748 down();
749 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
750 {
751 rb->sleep(ANIM_SLEEPTIME);
752 draw();
753 }
754 }
755 made_move=1;
756 break;
757 case KEY_LEFT:
758 for(int i=0;i<GRID_SIZE-1;++i)
759 {
760 memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
761 left();
762 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
763 {
764 rb->sleep(ANIM_SLEEPTIME);
765 draw();
766 }
767 }
768 made_move=1;
769 break;
770 case KEY_RIGHT:
771 for(int i=0;i<GRID_SIZE-1;++i)
772 {
773 memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
774 right();
775 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
776 {
777 rb->sleep(ANIM_SLEEPTIME);
778 draw();
779 }
780 }
781 made_move=1;
782 break;
783 case KEY_EXIT:
784 switch(do_2048_pause_menu())
785 {
786 case 0: /* resume */
787 break;
788 case 1: /* new game */
789 init_game(true);
790 made_move=1;
791 continue;
792 break;
793 case 2: /* quit without saving */
794 check_hs=true;
795 rb->remove(RESUME_FILE);
796 return PLUGIN_ERROR;
797 case 3: /* save and quit */
798 check_hs=false;
799 save_game();
800 return PLUGIN_ERROR;
801 }
802 break;
803 default:
804 {
805 exit_on_usb(button); /* handles poweroff and USB events */
806 break;
807 }
808 }
809 if(made_move)
810 {
811 /* Check if we actually moved, then add random */
812 if(memcmp(&old_grid, ctx->grid, sizeof(int)*SPACES))
813 {
814 place_random();
815 }
816 memcpy(&old_grid, ctx->grid, sizeof(int)*SPACES);
817 if(check_gameover())
818 return PLUGIN_OK;
819 draw();
820 }
821#ifdef HAVE_ADJUSTABLE_CPU_FREQ
822 rb->cpu_boost(false); /* back to idle */
823#endif
824 rb->yield();
825 }
826}
827
828/* decide if this_item should be shown in the main menu */
829/* used to hide resume option when there is no save */
830static int mainmenu_cb(int action, const struct menu_item_ex *this_item)
831{
832 int idx=((intptr_t)this_item);
833 if(action==ACTION_REQUEST_MENUITEM && !loaded && (idx==0 || idx==5))
834 return ACTION_EXIT_MENUITEM;
835 return action;
836}
837
838/* show the main menu */
839static enum plugin_status do_2048_menu(void)
840{
841 int sel=0;
842 loaded=load_game();
843 MENUITEM_STRINGLIST(menu,"2048 Menu", mainmenu_cb, "Resume Game", "Start New Game","High Scores","Playback Control", "Help", "Quit without Saving", "Quit");
844 bool quit=false;
845 while(!quit)
846 {
847 int item;
848 switch(item=rb->do_menu(&menu,&sel,NULL,false))
849 {
850 case 0: /* Start new game or resume a game */
851 case 1:
852 {
853 if(item==1 && loaded)
854 {
855 if(!confirm_quit())
856 break;
857 }
858 enum plugin_status ret=do_game(item==1);
859 switch(ret)
860 {
861 case PLUGIN_OK:
862 {
863 loaded=false;
864 rb->remove(RESUME_FILE);
865 hs_check_update(false);
866 break;
867 }
868 case PLUGIN_USB_CONNECTED:
869 save_game();
870 /* Don't bother showing the high scores... */
871 return ret;
872 case PLUGIN_ERROR: /* exit without menu */
873 if(check_hs)
874 hs_check_update(false);
875 return PLUGIN_OK;
876 default:
877 break;
878 }
879 break;
880 }
881 case 2:
882 highscore_show(-1,highscores, NUM_SCORES, true);
883 break;
884 case 3:
885 playback_control(NULL);
886 break;
887 case 4:
888 do_help();
889 break;
890 case 5:
891 if(confirm_quit())
892 return PLUGIN_OK;
893 case 6:
894 if(loaded)
895 save_game();
896 return PLUGIN_OK;
897 default:
898 break;
899 }
900 }
901 return PLUGIN_OK;
902}
903enum plugin_status plugin_start(const void* param)
904{
905 (void)param;
906 rb->srand(*rb->current_tick);
907 load_hs();
908 rb->lcd_setfont(WHAT_FONT);
909
910 /* now start the game menu */
911 enum plugin_status ret=do_2048_menu();
912 highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
913 cleanup();
914 abnormal_exit=false;
915 return ret;
916}