summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2014-06-29 14:49:07 -0400
committerFranklin Wei <frankhwei536@gmail.com>2014-06-29 14:49:07 -0400
commite560aae05de51a39d92f977f098585c2a878598d (patch)
tree6e326152aa9ec7507ac66523938ae431f98a03b1
parent5877f7b5c077df92983553d455318b26096e8bf0 (diff)
downloadrockbox-e560aae05de51a39d92f977f098585c2a878598d.tar.gz
rockbox-e560aae05de51a39d92f977f098585c2a878598d.zip
Added 2048 game
Change-Id: I4012dca4f93ca0db386a454635534f648ba906e9
-rw-r--r--apps/plugins/2048.c702
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
3 files changed, 704 insertions, 0 deletions
diff --git a/apps/plugins/2048.c b/apps/plugins/2048.c
new file mode 100644
index 0000000000..128a2a8b74
--- /dev/null
+++ b/apps/plugins/2048.c
@@ -0,0 +1,702 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei
+ *
+ * Clone of 2048 by Gabriel Cirulli
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include <plugin.h>
+#include "lib/helper.h"
+#include "lib/playback_control.h"
+#include "lib/highscore.h"
+#include "lib/display_text.h"
+#include "lib/pluginlib_actions.h"
+
+#define WINNING_TILE 2048
+#define MAX_STARTING_TILES 2
+#define HISCORES_FILE PLUGIN_GAMES_DATA_DIR "/2048.scores"
+#define NUM_SCORES 5
+#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/2048.resume"
+#define BACKUP_FILE PLUGIN_GAMES_DATA_DIR "/2048.backup" /* where to save when battery dies */
+#define WHAT_FONT FONT_SYSFIXED
+#define ANIM_SLEEPTIME 5
+#define GRID_SIZE 4
+#define SPACES (GRID_SIZE*GRID_SIZE)
+#define MIN_SPACE 4 /* minimum space between tiles */
+#define MAX_UNDOS 64
+#define KEY_UP PLA_UP
+#define KEY_DOWN PLA_DOWN
+#define KEY_LEFT PLA_LEFT
+#define KEY_RIGHT PLA_RIGHT
+#define KEY_EXIT PLA_CANCEL
+#define KEY_UNDO PLA_SELECT
+static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
+
+/* What is needed to save/load a game */
+struct undo_data_node {
+ /* Simplified version of game_ctx_t */
+ int grid[GRID_SIZE][GRID_SIZE];
+ int score;
+};
+struct undo_data_t {
+ struct undo_data_node undo_stack[MAX_UNDOS];
+ int top;
+};
+struct game_ctx_t {
+ int grid[GRID_SIZE][GRID_SIZE];
+ int score;
+ int cksum; /* sum of grid, XORed by score */
+ bool already_won;
+};
+static struct game_ctx_t ctx_data;
+/* use a pointer to make save/load easier */
+static struct game_ctx_t *ctx=&ctx_data;
+static struct undo_data_t undo_data;
+
+static struct undo_data_t *undo=&undo_data;
+/* temporary data */
+static bool merged_grid[GRID_SIZE][GRID_SIZE];
+static int old_grid[GRID_SIZE][GRID_SIZE];
+static int max_numeral_width=-1, max_numeral_height=-1;
+static bool loaded=false;
+extern void exit(int);
+/* TODO
+ * Draw tiles, not just numbers
+ * Add nice colors
+ * Sandbox mode (play after win)
+ */
+static struct highscore highscores[NUM_SCORES];
+static inline int rand_range(int min, int max)
+{
+ return rb->rand()%(max-min+1)+min;
+}
+static void cleanup(void)
+{
+ backlight_use_settings();
+}
+static inline int rand_2_or_4(void)
+{
+ /* 1 in 10 chance of a four */
+ if(rb->rand()%10==0)
+ return 4;
+ else
+ return 2;
+}
+static bool do_help(void)
+{
+ char* help_text[]= {"2048", "", "Aim",
+ "", "Join", "the", "numbers", "to", "get", "to", "the", "2048", "tile!", "", "",
+ "How", "to", "Play", "", ""
+ "", "Use", "the", "directional", "keys", "to", "move", "the", "tiles.", "When", "two", "tiles", "with", "the", "same", "number", "touch,", "they", "merge", "into", "one!"};
+ struct style_text style[]={
+ {0, TEXT_CENTER|TEXT_UNDERLINE},
+ {2, C_RED},
+ {15, C_RED}, {16, C_RED}, {17,C_RED},
+ LAST_STYLE_ITEM
+ };
+ return display_text(ARRAYLEN(help_text), help_text, style,NULL,true);
+}
+static inline void slide_internal(int startx, int starty, int stopx, int stopy, int dx, int dy, int lookx, int looky)
+{
+ for(int y=starty;y!=stopy;y+=dy)
+ {
+ for(int x=startx;x!=stopx;x+=dx)
+ {
+ 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 */
+ {
+ /* Each merged tile cannot be merged again */
+ merged_grid[x+lookx][y+looky]=true;
+ ctx->grid[x+lookx][y+looky]=2*ctx->grid[x][y];
+ ctx->score+=ctx->grid[x+lookx][y+looky];
+ ctx->grid[x][y]=0;
+ }
+ else if(ctx->grid[x+lookx][y+looky]==0) /* Empty! */
+ {
+ ctx->grid[x+lookx][y+looky]=ctx->grid[x][y];
+ ctx->grid[x][y]=0;
+ }
+ }
+ }
+}
+/* Up
+ 0
+ 1 ^ ^ ^ ^
+ 2 ^ ^ ^ ^
+ 3 ^ ^ ^ ^
+ 0 1 2 3
+*/
+static void up(void)
+{
+ slide_internal(0, 1, /* start values */
+ GRID_SIZE, GRID_SIZE, /* stop values */
+ 1, 1, /* delta values */
+ 0, -1); /* lookahead values */
+}
+/* Down
+ 0 v v v v
+ 1 v v v v
+ 2 v v v v
+ 3
+ 0 1 2 3
+*/
+static void down(void)
+{
+ slide_internal(0, GRID_SIZE-2,
+ GRID_SIZE, -1,
+ 1, -1,
+ 0, 1);
+}
+/* Left
+ 0 < < <
+ 1 < < <
+ 2 < < <
+ 3 < < <
+ 0 1 2 3
+*/
+static void left(void)
+{
+ slide_internal(1, 0,
+ GRID_SIZE, GRID_SIZE,
+ 1, 1,
+ -1, 0);
+}
+/* Right
+ 0 > > >
+ 1 > > >
+ 2 > > >
+ 3 > > >
+ 0 1 2 3
+*/
+static void right(void)
+{
+ slide_internal(GRID_SIZE-2, 0, /* start */
+ -1, GRID_SIZE, /* stop */
+ -1, 1, /* delta */
+ 1, 0); /* lookahead */
+}
+static int biggest_tile=0;
+static void draw(void)
+{
+ rb->lcd_clear_display();
+ /* Draw the grid */
+ /* find the biggest tile */
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ for(int y=0;y<GRID_SIZE;++y)
+ if(ctx->grid[x][y]>biggest_tile)
+ biggest_tile=ctx->grid[x][y];
+ }
+ char str[32];
+ rb->snprintf(str, 31,"%d", biggest_tile);
+ int biggest_tile_width=rb->strlen(str)*max_numeral_width+MIN_SPACE;
+ for(int y=0;y<GRID_SIZE;++y)
+ {
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ if(ctx->grid[x][y])
+ {
+ if(ctx->grid[x][y]>biggest_tile)
+ biggest_tile=ctx->grid[x][y];
+ rb->snprintf(str,31,"%d", ctx->grid[x][y]);
+ rb->lcd_putsxy(biggest_tile_width*x,y*max_numeral_height+max_numeral_height,str);
+ }
+ }
+ }
+ /* Now draw the score, and the game title */
+ rb->snprintf(str, 31, "Score: %d", ctx->score);
+ int str_width, str_height;
+ rb->font_getstringsize(str, &str_width, &str_height, WHAT_FONT);
+ int score_leftmost=LCD_WIDTH-str_width-1;
+ /* Check if there is enough space to display "Score: ", otherwise, only display the score */
+ if(score_leftmost>=0)
+ rb->lcd_putsxy(score_leftmost,0,str);
+ else
+ rb->lcd_putsxy(score_leftmost,0,str+rb->strlen("Score: "));
+ /* Reuse the same string for the title */
+
+ rb->snprintf(str, 31, "%d", WINNING_TILE);
+ rb->font_getstringsize(str, &str_width, &str_height, WHAT_FONT);
+ if(str_width<score_leftmost)
+ rb->lcd_putsxy(0,0,str);
+ rb->lcd_update();
+}
+static void place_random(void)
+{
+ int xpos[SPACES],ypos[SPACES];
+ int back=0;
+ for(int y=0;y<GRID_SIZE;++y)
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ if(!ctx->grid[x][y])
+ {
+ xpos[back]=x;
+ ypos[back]=y;
+ ++back;
+ }
+ }
+ if(!back)
+ return;
+ int idx=rand_range(0,back-1);
+ ctx->grid[xpos[idx]][ypos[idx]]=rand_2_or_4();
+}
+static void restore_old_grid(void)
+{
+ memcpy(&ctx->grid, &old_grid, sizeof(int)*SPACES);
+}
+static bool check_gameover(void)
+{
+ int numempty=0;
+ for(int y=0;y<GRID_SIZE;++y)
+ {
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ if(ctx->grid[x][y]==0)
+ ++numempty;
+ if(ctx->grid[x][y]==WINNING_TILE && !ctx->already_won)
+ {
+ /* Let the user see the tile in its full glory... */
+ draw();
+ ctx->already_won=true;
+ rb->splash(HZ*2,"You win!");
+ return true;
+ }
+ }
+ }
+ if(!numempty)
+ {
+ /* No empty spaces, check for valid moves */
+ /* Then, get the current score */
+ int oldscore=ctx->score;
+ memset(&merged_grid,0,SPACES*sizeof(bool));
+ up();
+ if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
+ {
+ restore_old_grid();
+ ctx->score=oldscore;
+ return false;
+ }
+ restore_old_grid();
+ memset(&merged_grid,0,SPACES*sizeof(bool));
+ down();
+ if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
+ {
+ restore_old_grid();
+ ctx->score=oldscore;
+ return false;
+ }
+ restore_old_grid();
+ memset(&merged_grid,0,SPACES*sizeof(bool));
+ left();
+ if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
+ {
+ restore_old_grid();
+ ctx->score=oldscore;
+ return false;
+ }
+ restore_old_grid();
+ memset(&merged_grid,0,SPACES*sizeof(bool));
+ right();
+ if(memcmp(&old_grid, &ctx->grid, sizeof(int)*SPACES))
+ {
+ restore_old_grid();
+ ctx->score=oldscore;
+ return false;
+ }
+ ctx->score=oldscore;
+ draw(); /* Shame the player :) */
+ rb->splash(HZ*2, "Game Over! :(");
+ return true;
+ }
+ return false;
+}
+static void load_hs(void)
+{
+ if(rb->file_exists(HISCORES_FILE))
+ highscore_load(HISCORES_FILE, highscores, NUM_SCORES);
+ else
+ memset(highscores, 0, sizeof(struct highscore)*NUM_SCORES);
+}
+static void undo_push(void)
+{
+ if(undo->top==MAX_UNDOS)
+ {
+ memmove(undo->undo_stack, &(undo->undo_stack[1]), (MAX_UNDOS-1)*sizeof(struct undo_data_node));
+ --undo->top;
+ }
+ undo->undo_stack[undo->top].score=ctx->score;
+ memcpy(undo->undo_stack[undo->top].grid, ctx->grid, sizeof(int)*SPACES);
+ ++undo->top;
+}
+static void pop_undo(void)
+{
+ if(undo->top>0)
+ {
+ --undo->top;
+ memcpy(ctx->grid, undo->undo_stack[undo->top].grid, sizeof(int)*SPACES);
+ ctx->score=undo->undo_stack[undo->top].score;
+ }
+}
+static void init_game(bool newgame)
+{
+ if(newgame)
+ {
+ /* initialize the game context */
+ memset(ctx->grid, 0, sizeof(int)*SPACES);
+ for(int i=0;i<MAX_STARTING_TILES;++i)
+ {
+ place_random();
+ }
+ ctx->score=0;
+ ctx->already_won=false;
+ memset(undo, 0, sizeof(struct undo_data_t));
+ }
+ /* Now calculate font sizes, etc */
+ max_numeral_width=-1;
+ max_numeral_height=-1;
+ /* Find the width of the widest character so the drawing can be nicer */
+ for(char c='0';c<='9';++c)
+ {
+ int width=rb->font_get_width(rb->font_get(WHAT_FONT), c);
+ if(width>max_numeral_width)
+ max_numeral_width=width;
+ }
+ /* Now get the height of the font */
+ rb->font_getstringsize("0123456789", NULL, &max_numeral_height,WHAT_FONT);
+ backlight_ignore_timeout();
+ rb->lcd_clear_display();
+ draw();
+}
+static void save_backup(void)
+{
+ int fd=rb->open(BACKUP_FILE,O_WRONLY|O_CREAT, 0666);
+ if(fd<0)
+ {
+ rb->splash(2*HZ, "Saving failed.");
+ return;
+ }
+ ctx->cksum=0;
+ for(int x=0;x<GRID_SIZE;++x)
+ for(int y=0;y<GRID_SIZE;++y)
+ ctx->cksum+=ctx->grid[x][y];
+ ctx->cksum^=ctx->score;
+ rb->write(fd, ctx,sizeof(struct game_ctx_t));
+ rb->close(fd);
+}
+static void save_game(void)
+{
+ int fd=rb->open(RESUME_FILE,O_WRONLY|O_CREAT, 0666);
+ if(fd<0)
+ {
+ return;
+ }
+ ctx->cksum=0;
+ for(int x=0;x<GRID_SIZE;++x)
+ for(int y=0;y<GRID_SIZE;++y)
+ ctx->cksum+=ctx->grid[x][y];
+ ctx->cksum^=ctx->score;
+ rb->write(fd, ctx,sizeof(struct game_ctx_t));
+ rb->write(fd, undo, sizeof(struct undo_data_t));
+ rb->close(fd);
+}
+static bool load_game(void)
+{
+ int success=0;
+ int fd=rb->open(RESUME_FILE, O_RDONLY);
+ if(fd<0)
+ {
+ rb->remove(RESUME_FILE);
+ return false;
+ }
+ int numread=rb->read(fd, ctx, sizeof(struct game_ctx_t));
+ int calc=0;
+ for(int x=0;x<GRID_SIZE;++x)
+ for(int y=0;y<GRID_SIZE;++y)
+ calc+=ctx->grid[x][y];
+ calc^=ctx->score;
+ if(numread==sizeof(struct game_ctx_t) && calc==ctx->cksum)
+ ++success;
+ numread=rb->read(fd, undo, sizeof(struct undo_data_t));
+ if(numread==sizeof(struct undo_data_t))
+ ++success;
+ rb->close(fd);
+ rb->remove(RESUME_FILE);
+ return (success==2);
+}
+static int do_2048_pause_menu(void)
+{
+ int sel=0;
+ MENUITEM_STRINGLIST(menu,"Paused", NULL,
+ "Resume Game",
+ "Start New Game",
+ "High Scores",
+ "Playback Control",
+ "Help",
+ "Quit without Saving",
+ "Quit");
+ bool quit=false;
+ while(!quit)
+ {
+ switch(rb->do_menu(&menu, &sel, NULL, false))
+ {
+ case 0:
+ draw();
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ highscore_show(-1,highscores, NUM_SCORES, true);
+ break;
+ case 3:
+ playback_control(NULL);
+ break;
+ case 4:
+ do_help();
+ break;
+ case 5:
+ return 2;
+ case 6:
+ return 3;
+ }
+ }
+ return 0;
+}
+static enum plugin_status do_game(bool newgame)
+{
+ init_game(newgame);
+ int made_move=0, move_was_undo=0;
+ while(1)
+ {
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+ rb->cpu_boost(false); /* Save battery when idling */
+#endif
+ /* Wait for a button press */
+ int button=pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
+ made_move=0;
+ move_was_undo=0;
+ memset(&merged_grid,0,SPACES*sizeof(bool));
+ memcpy(&old_grid, &ctx->grid, sizeof(int)*SPACES);
+ int grid_before_anim_step[GRID_SIZE][GRID_SIZE];
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+ rb->cpu_boost(true); /* doing work now... */
+#endif
+ switch(button)
+ {
+ case KEY_UP:
+ for(int i=0;i<GRID_SIZE-1;++i)
+ {
+ memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
+ up();
+ if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
+ {
+ rb->sleep(ANIM_SLEEPTIME);
+ draw();
+ }
+ }
+ made_move=1;
+ break;
+ case KEY_DOWN:
+ for(int i=0;i<GRID_SIZE-1;++i)
+ {
+ memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
+ down();
+ if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
+ {
+ rb->sleep(ANIM_SLEEPTIME);
+ draw();
+ }
+ }
+ made_move=1;
+ break;
+ case KEY_LEFT:
+ for(int i=0;i<GRID_SIZE-1;++i)
+ {
+ memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
+ left();
+ if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
+ {
+ rb->sleep(ANIM_SLEEPTIME);
+ draw();
+ }
+ }
+ made_move=1;
+ break;
+ case KEY_RIGHT:
+ for(int i=0;i<GRID_SIZE-1;++i)
+ {
+ memcpy(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES);
+ right();
+ if(memcmp(grid_before_anim_step, ctx->grid, sizeof(int)*SPACES))
+ {
+ rb->sleep(ANIM_SLEEPTIME);
+ draw();
+ }
+ }
+ made_move=1;
+ break;
+ case KEY_EXIT:
+ switch(do_2048_pause_menu())
+ {
+ case 0: /* resume */
+ break;
+ case 1: /* new game */
+ init_game(true);
+ made_move=1;
+ continue;
+ break;
+ case 2: /* quit without saving */
+ rb->remove(RESUME_FILE);
+ return PLUGIN_ERROR;
+ case 3: /* save and quit */
+ save_game();
+ return (enum plugin_status)0x80;
+ }
+ break;
+#ifdef ENABLE_UNDO
+ case KEY_UNDO: /* undo */
+ pop_undo();
+ made_move=1;
+ move_was_undo=1;
+ break;
+#endif
+ default:
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
+ {
+ return PLUGIN_USB_CONNECTED;
+ }
+ break;
+ }
+ if(made_move)
+ {
+ if(!rb->battery_level_safe())
+ save_backup();
+ /* Check if we actually moved, then add random */
+ if(memcmp(&old_grid, ctx->grid, sizeof(int)*SPACES) && !move_was_undo)
+ {
+ place_random();
+ undo_push();
+ }
+ memcpy(&old_grid, ctx->grid, sizeof(int)*SPACES);
+ if(check_gameover())
+ return PLUGIN_OK;
+ draw();
+ }
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+ rb->cpu_boost(false); /* back to idle */
+#endif
+ rb->yield();
+ }
+}
+static void hs_check_update(bool noshow)
+{
+ /* first, find the biggest tile */
+ int biggest=0;
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ for(int y=0;y<GRID_SIZE;++y)
+ {
+ if(ctx->grid[x][y]>biggest)
+ biggest=ctx->grid[x][y];
+ }
+ }
+ int hs_idx=highscore_update(ctx->score,biggest, "", highscores,NUM_SCORES);
+ if(!noshow)
+ {
+ if(hs_idx>=0)
+ rb->splash(HZ*2,"High Score!");
+ rb->lcd_clear_display();
+ highscore_show(hs_idx,highscores,NUM_SCORES,true);
+ }
+ highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
+}
+static int mainmenu_shouldshow(int action, const struct menu_item_ex *this_item)
+{
+ int idx=((intptr_t)this_item);
+ if(action==ACTION_REQUEST_MENUITEM && !loaded && (idx==0 || idx==5))
+ return ACTION_EXIT_MENUITEM;
+ return action;
+}
+static enum plugin_status do_2048_menu(void)
+{
+ int sel=0;
+ loaded=load_game();
+ MENUITEM_STRINGLIST(menu,"2048 Menu", mainmenu_shouldshow, "Resume Game", "Start New Game","High Scores","Playback Control", "Help", "Quit without Saving", "Quit");
+ bool quit=false;
+ while(!quit)
+ {
+ int item;
+ switch(item=rb->do_menu(&menu,&sel,NULL,false))
+ {
+ case 0: /* Start new game or resume a game */
+ case 1:
+ {
+ enum plugin_status ret=do_game(item==1);
+ switch(ret)
+ {
+ case PLUGIN_OK:
+ {
+ loaded=false;
+ rb->remove(RESUME_FILE);
+ hs_check_update(false);
+ break;
+ }
+ case PLUGIN_USB_CONNECTED:
+ save_game();
+ /* Don't bother showing the high scores... */
+ return ret;
+ case PLUGIN_ERROR: /* exit without menu */
+ hs_check_update(false);
+ return PLUGIN_OK;
+ case (enum plugin_status)0x80: /* exit wo/ hs */
+ return PLUGIN_OK;
+ default:
+ break;
+ }
+ break;
+ }
+ case 2:
+ highscore_show(-1,highscores, NUM_SCORES, true);
+ break;
+ case 3:
+ playback_control(NULL);
+ break;
+ case 4:
+ do_help();
+ break;
+ case 5:
+ return PLUGIN_OK;
+ case 6:
+ if(loaded)
+ save_game();
+ return PLUGIN_OK;
+ default:
+ break;
+ }
+ }
+ return PLUGIN_OK;
+}
+enum plugin_status plugin_start(const void* param)
+{
+ (void)param;
+ rb->srand(*(rb->current_tick));
+ load_hs();
+ rb->lcd_setfont(WHAT_FONT);
+ enum plugin_status ret=do_2048_menu();
+ highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
+ cleanup();
+ return ret;
+}
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 8a849aafc2..626dca0d32 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -1,3 +1,4 @@
+2048,games
alpine_cdc,apps
alarmclock,apps
autostart,apps
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index c56cd5eb46..03d2fe2c35 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -1,4 +1,5 @@
/* plugins common to all models */
+2048.c
#if !defined(SIMULATOR) && (CONFIG_BATTERY_MEASURE != 0)
battery_bench.c
#endif