summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2014-06-29 14:49:07 -0400
committerMichael Giacomelli <giac2000@hotmail.com>2014-08-12 00:01:24 +0200
commitb61553c2b18d37566b6561e413ef1b49de39c0a7 (patch)
tree69c93171d9cc7675b05f66925c7cbd42ff73c9d9 /apps
parent9a3400a4a667e32d1dd0b50364b083787ff63320 (diff)
downloadrockbox-b61553c2b18d37566b6561e413ef1b49de39c0a7.tar.gz
rockbox-b61553c2b18d37566b6561e413ef1b49de39c0a7.zip
Added 2048 game
Change-Id: I4012dca4f93ca0db386a454635534f648ba906e9 Reviewed-on: http://gerrit.rockbox.org/888 Reviewed-by: Michael Giacomelli <giac2000@hotmail.com> Tested: Michael Giacomelli <giac2000@hotmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/plugins/2048.c916
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES2
-rw-r--r--apps/plugins/bitmaps/native/SOURCES22
-rw-r--r--apps/plugins/bitmaps/native/_2048_background.103x103x24.bmpbin0 -> 32258 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_background.121x121x24.bmpbin0 -> 44166 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_background.168x168x24.bmpbin0 -> 84794 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_background.224x224x24.bmpbin0 -> 150650 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_background.56x56x24.bmpbin0 -> 9530 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmpbin0 -> 7898 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmpbin0 -> 26258 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmpbin0 -> 36626 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmpbin0 -> 70106 bytes
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmpbin0 -> 124538 bytes
14 files changed, 941 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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei
+ *
+ * Clone of 2048 by Gabriele Cirulli
+ *
+ * Thanks to [Saint], saratoga, and gevaerts for answering all my n00b
+ * questions :)
+ *
+ * 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.
+ *
+ ***************************************************************************/
+
+/* TODO
+ * Sounds!
+ * Better animations!
+ */
+
+/* includes */
+
+#include <plugin.h>
+
+#include "lib/display_text.h"
+
+#include "lib/helper.h"
+#include "lib/highscore.h"
+#include "lib/playback_control.h"
+#include "lib/pluginlib_actions.h"
+#include "lib/pluginlib_exit.h"
+
+#ifdef HAVE_LCD_BITMAP
+#include "pluginbitmaps/_2048_background.h"
+#include "pluginbitmaps/_2048_tiles.h"
+#endif
+
+/* defines */
+
+#define ANIM_SLEEPTIME (HZ/20)
+#define GRID_SIZE 4
+#define HISCORES_FILE PLUGIN_GAMES_DATA_DIR "/2048.score"
+#define NUM_SCORES 5
+#define NUM_STARTING_TILES 2
+#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/2048.save"
+#define WHAT_FONT FONT_UI
+#define SPACES (GRID_SIZE*GRID_SIZE)
+#define MIN_SPACE (BMPHEIGHT__2048_tiles*0.134) /* space between tiles */
+#define MAX_UNDOS 64
+#define VERT_SPACING 4
+#define WINNING_TILE 2048
+
+/* screen-specific configuration */
+
+#if LCD_WIDTH<LCD_HEIGHT
+/* tall screens */
+#define TITLE_X 0
+#define TITLE_Y 0
+#define BASE_Y (BMPHEIGHT__2048_tiles*1.5)
+#define BASE_X (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
+#define SCORE_X 0
+#define SCORE_Y (max_numeral_height)
+#define BEST_SCORE_X 0
+#define BEST_SCORE_Y (2*max_numeral_height)
+#else
+/* wide screens or square screens*/
+#define TITLE_X 0
+#define TITLE_Y 0
+#define BASE_X (LCD_WIDTH-(GRID_SIZE*BMPHEIGHT__2048_tiles)-(((GRID_SIZE+1)*MIN_SPACE)))
+#define BASE_Y (BMPHEIGHT__2048_tiles*.5-MIN_SPACE)
+#define SCORE_X 0
+#define SCORE_Y (max_numeral_height)
+#define BEST_SCORE_X 0
+#define BEST_SCORE_Y (2*max_numeral_height)
+#endif
+
+#define BACKGROUND_X (BASE_X-MIN_SPACE)
+#define BACKGROUND_Y (BASE_Y-MIN_SPACE)
+
+/* key mappings */
+
+#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
+
+/* colors */
+
+#define BACKGROUND (LCD_RGBPACK(0xfa, 0xf8, 0xef))
+#define BOARD_BACKGROUND (LCD_RGBPACK(0xbb, 0xad, 0xa0))
+#define TEXT_COLOR (LCD_RGBPACK(0x77, 0x6e, 0x65))
+
+static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
+
+/* game data */
+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;
+
+/* temporary data */
+static bool merged_grid[GRID_SIZE][GRID_SIZE];
+static int old_grid[GRID_SIZE][GRID_SIZE];
+static int max_numeral_height=-1;
+static bool loaded=false;
+
+/* first init_game will set this, when it is exceeded, it will be updated in the slide functions */
+static int best_score;
+
+static bool abnormal_exit=true;
+static struct highscore highscores[NUM_SCORES];
+
+/* returns a random int between min and max */
+static inline int rand_range(int min, int max)
+{
+ return rb->rand()%(max-min+1)+min;
+}
+
+/* prepares to exit */
+static void cleanup(void)
+{
+ backlight_use_settings();
+}
+
+/* returns 2 or 4 */
+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;
+}
+
+/* display the help text */
+static bool do_help(void)
+{
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_WHITE);
+ rb->lcd_set_background(LCD_BLACK);
+#endif
+ rb->lcd_setfont(FONT_UI);
+ 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);
+}
+
+/*** the logic for sliding ***/
+
+/* this is the helper function that does the actual tile moving */
+
+static inline void slide_internal(int startx, int starty,
+ int stopx, int stopy,
+ int dx, int dy,
+ int lookx, int looky)
+{
+ int best_score_before=best_score;
+ 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;
+ }
+ }
+ }
+ if(ctx->score>best_score_before)
+ best_score=ctx->score;
+}
+
+/* these functions move each tile 1 space in the direction specified via calls to slide_internal */
+
+/* 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 */
+}
+
+/* slightly modified version of base 2 log, returns 1 when given zero, and log2(n)+1 for anything else */
+
+static inline int ilog2(int n)
+{
+ if(n==0)
+ return 1;
+ int log=0;
+ while(n>1)
+ {
+ n>>=1;
+ ++log;
+ }
+ return log+1;
+}
+static void draw(void)
+{
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_background(BACKGROUND);
+#endif
+ rb->lcd_clear_display();
+
+ /* draw the background */
+
+ rb->lcd_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPWIDTH__2048_background);
+
+ /*
+ grey_gray_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPHEIGHT__2048_background);
+ */
+
+ /* draw the grid */
+
+ for(int y=0;y<GRID_SIZE;++y)
+ {
+ for(int x=0;x<GRID_SIZE;++x)
+ {
+ rb->lcd_bitmap_part(_2048_tiles, /* source */
+ BMPWIDTH__2048_tiles-BMPHEIGHT__2048_tiles*ilog2(ctx->grid[x][y]), 0, /* source upper left corner */
+ STRIDE(SCREEN_MAIN, BMPWIDTH__2048_tiles, BMPHEIGHT__2048_tiles), /* stride */
+ (BMPHEIGHT__2048_tiles+MIN_SPACE)*x+BASE_X, (BMPHEIGHT__2048_tiles+MIN_SPACE)*y+BASE_Y, /* dest upper-left corner */
+ BMPHEIGHT__2048_tiles, BMPHEIGHT__2048_tiles); /* size of the cut section */
+ }
+ }
+ /* draw the title */
+ char buf[32];
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(TEXT_COLOR);
+#endif
+ rb->snprintf(buf, 31, "%d", WINNING_TILE);
+
+ /* check if the title will go into the grid */
+ int w, h;
+ rb->lcd_setfont(FONT_UI);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ if(w+TITLE_X>=BACKGROUND_X && h+TITLE_Y>=BACKGROUND_Y)
+ {
+ /* if it goes into the grid, use the system font, which should be smaller */
+ rb->lcd_setfont(FONT_SYSFIXED);
+ }
+ rb->lcd_putsxy(TITLE_X, TITLE_Y, buf);
+
+ /* draw the score */
+ rb->snprintf(buf, 31, "Score: %d", ctx->score);
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_WHITE);
+ rb->lcd_set_background(BOARD_BACKGROUND);
+#endif
+ rb->lcd_setfont(FONT_UI);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ if(w+SCORE_X>=BACKGROUND_X && h+SCORE_Y>=BACKGROUND_Y)
+ {
+ /* score overflows */
+ /* first see if it fits with Score: and FONT_SYSFIXED */
+ rb->lcd_setfont(FONT_SYSFIXED);
+ rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
+ if(w+SCORE_X < BACKGROUND_X)
+ /* it fits, go and draw it */
+ goto draw_lbl;
+
+ /* now try with S: and FONT_UI */
+ rb->snprintf(buf, 31, "S: %d", ctx->score);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ rb->lcd_setfont(FONT_UI);
+ if(w+SCORE_X<BACKGROUND_X)
+ goto draw_lbl;
+
+ /* now try with S: and FONT_SYSFIXED */
+ rb->snprintf(buf, 31, "S: %d", ctx->score);
+ rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
+ rb->lcd_setfont(FONT_SYSFIXED);
+ if(w+SCORE_X<BACKGROUND_X)
+ goto draw_lbl;
+
+ /* then try without Score: and FONT_UI */
+ rb->snprintf(buf, 31, "%d", ctx->score);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ rb->lcd_setfont(FONT_UI);
+ if(w+SCORE_X<BACKGROUND_X)
+ goto draw_lbl;
+
+ /* as a last resort, don't use Score: and use the system font */
+ rb->snprintf(buf, 31, "%d", ctx->score);
+ rb->lcd_setfont(FONT_SYSFIXED);
+ }
+draw_lbl:
+ rb->lcd_putsxy(SCORE_X, SCORE_Y, buf);
+
+ /* draw the best score */
+
+ rb->snprintf(buf, 31, "Best: %d", best_score);
+#ifdef HAVE_LCD_COLOR
+ rb->lcd_set_foreground(LCD_WHITE);
+ rb->lcd_set_background(BOARD_BACKGROUND);
+#endif
+ rb->lcd_setfont(FONT_UI);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ if(w+BEST_SCORE_X>=BACKGROUND_X && h+BEST_SCORE_Y>=BACKGROUND_Y)
+ {
+ /* score overflows */
+ /* first see if it fits with Score: and FONT_SYSFIXED */
+ rb->lcd_setfont(FONT_SYSFIXED);
+ rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
+ if(w+BEST_SCORE_X < BACKGROUND_X)
+ /* it fits, go and draw it */
+ goto draw_best;
+
+ /* now try with S: and FONT_UI */
+ rb->snprintf(buf, 31, "B: %d", best_score);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ rb->lcd_setfont(FONT_UI);
+ if(w+BEST_SCORE_X<BACKGROUND_X)
+ goto draw_best;
+
+ /* now try with S: and FONT_SYSFIXED */
+ rb->snprintf(buf, 31, "B: %d", best_score);
+ rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED);
+ rb->lcd_setfont(FONT_SYSFIXED);
+ if(w+BEST_SCORE_X<BACKGROUND_X)
+ goto draw_best;
+
+ /* then try without Score: and FONT_UI */
+ rb->snprintf(buf, 31, "%d", best_score);
+ rb->font_getstringsize(buf, &w, &h, FONT_UI);
+ rb->lcd_setfont(FONT_UI);
+ if(w+BEST_SCORE_X<BACKGROUND_X)
+ goto draw_best;
+
+ /* as a last resort, don't use Score: and use the system font */
+ rb->snprintf(buf, 31, "%d", best_score);
+ rb->lcd_setfont(FONT_SYSFIXED);
+ }
+draw_best:
+ rb->lcd_putsxy(BEST_SCORE_X, BEST_SCORE_Y, buf);
+
+ rb->lcd_update();
+ /* revert the font back */
+ rb->lcd_setfont(WHAT_FONT);
+}
+
+/* place a 2 or 4 in a random empty space */
+static void place_random(void)
+{
+ int xpos[SPACES],ypos[SPACES];
+ int back=0;
+ /* get the indexes of empty spaces */
+ 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)
+ /* no empty spaces */
+ return;
+ int idx=rand_range(0,back-1);
+ ctx->grid[xpos[idx]][ypos[idx]]=rand_2_or_4();
+}
+
+/* copies old_grid to ctx->grid */
+static void restore_old_grid(void)
+{
+ memcpy(&ctx->grid, &old_grid, sizeof(int)*SPACES);
+}
+
+/* checks for a win or loss */
+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!");
+ const struct text_message prompt={(const char*[]){"Keep going?"}, 1};
+ enum yesno_res keepgoing=rb->gui_syncyesno_run(&prompt, NULL, NULL);
+ if(keepgoing==YESNO_NO)
+ return true;
+ else
+ return false;
+ }
+ }
+ }
+ 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;
+ }
+ /* no more legal moves */
+ ctx->score=oldscore;
+ draw(); /* Shame the player :) */
+ rb->splash(HZ*2, "Game Over!");
+ return true;
+ }
+ return false;
+}
+
+/* loads highscores from disk */
+/* creates an empty structure if the file does not exist */
+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);
+}
+
+/* initialize the data structures */
+static void init_game(bool newgame)
+{
+ best_score=highscores[0].score;
+ if(newgame)
+ {
+ /* initialize the game context */
+ memset(ctx->grid, 0, sizeof(int)*SPACES);
+ for(int i=0;i<NUM_STARTING_TILES;++i)
+ {
+ place_random();
+ }
+ ctx->score=0;
+ ctx->already_won=false;
+ }
+ /* using the menu resets the font */
+ /* set it again here */
+ rb->lcd_setfont(WHAT_FONT);
+ /* Now calculate font sizes */
+ /* Now get the height of the font */
+ rb->font_getstringsize("0123456789", NULL, &max_numeral_height,WHAT_FONT);
+ max_numeral_height+=VERT_SPACING;
+ backlight_ignore_timeout();
+ rb->lcd_clear_display();
+ draw();
+}
+
+/* save the current game state */
+static void save_game(void)
+{
+ rb->splash(0, "Saving...");
+ 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->close(fd);
+ rb->lcd_update();
+}
+
+/* loads a saved game, returns true on success */
+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;
+ rb->close(fd);
+ rb->remove(RESUME_FILE);
+ return (success==1);
+}
+
+/* update the highscores with ctx->score */
+static void hs_check_update(bool noshow)
+{
+ /* first, find the biggest tile to show as the level */
+ 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)
+ {
+ /* show the scores if there is a new high score */
+ if(hs_idx>=0)
+ {
+ rb->splashf(HZ*2,"New High Score: %d", ctx->score);
+ rb->lcd_clear_display();
+ highscore_show(hs_idx,highscores,NUM_SCORES,true);
+ }
+ }
+ highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
+}
+
+/* asks the user if they wish to quit */
+static bool confirm_quit(void)
+{
+ const struct text_message prompt={(const char*[]){"Are you sure?", "This will clear your current game."}, 2};
+ enum yesno_res response=rb->gui_syncyesno_run(&prompt, NULL, NULL);
+ if(response==YESNO_NO)
+ return false;
+ else
+ return true;
+}
+
+/* show the pause menu */
+static int do_2048_pause_menu(void)
+{
+ int sel=0;
+ MENUITEM_STRINGLIST(menu,"2048 Menu", 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:
+ {
+ if(!confirm_quit())
+ break;
+ else
+ {
+ hs_check_update(false);
+ 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: /* quit w/o saving */
+ {
+ if(!confirm_quit())
+ break;
+ else
+ {
+ return 2;
+ }
+ }
+ case 6:
+ return 3;
+ }
+ }
+ return 0;
+}
+
+static void exit_handler(void)
+{
+ cleanup();
+ if(abnormal_exit)
+ save_game();
+ return;
+}
+static bool check_hs;
+/* main game loop */
+static enum plugin_status do_game(bool newgame)
+{
+ init_game(newgame);
+ rb_atexit(&exit_handler);
+ int made_move=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;
+ 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 */
+ check_hs=true;
+ rb->remove(RESUME_FILE);
+ return PLUGIN_ERROR;
+ case 3: /* save and quit */
+ check_hs=false;
+ save_game();
+ return PLUGIN_ERROR;
+ }
+ break;
+ default:
+ {
+ exit_on_usb(button); /* handles poweroff and USB events */
+ break;
+ }
+ }
+ if(made_move)
+ {
+ /* Check if we actually moved, then add random */
+ if(memcmp(&old_grid, ctx->grid, sizeof(int)*SPACES))
+ {
+ place_random();
+ }
+ 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();
+ }
+}
+
+/* decide if this_item should be shown in the main menu */
+/* used to hide resume option when there is no save */
+static int mainmenu_cb(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;
+}
+
+/* show the main menu */
+static enum plugin_status do_2048_menu(void)
+{
+ int sel=0;
+ loaded=load_game();
+ MENUITEM_STRINGLIST(menu,"2048 Menu", mainmenu_cb, "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:
+ {
+ if(item==1 && loaded)
+ {
+ if(!confirm_quit())
+ break;
+ }
+ 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 */
+ if(check_hs)
+ hs_check_update(false);
+ 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:
+ if(confirm_quit())
+ 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);
+
+ /* now start the game menu */
+ enum plugin_status ret=do_2048_menu();
+ highscore_save(HISCORES_FILE,highscores,NUM_SCORES);
+ cleanup();
+ abnormal_exit=false;
+ 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..9b7436ac20 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -213,6 +213,8 @@ nim.c
#if LCD_DEPTH > 1 /* non-mono bitmap targets */
+/* 2048 runs on 1-bit LCDs, but it is unplayable */
+2048.c
matrix.c
#if (LCD_WIDTH > 138)
diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES
index 4b7f0e2670..d608062335 100644
--- a/apps/plugins/bitmaps/native/SOURCES
+++ b/apps/plugins/bitmaps/native/SOURCES
@@ -1,5 +1,27 @@
#ifdef HAVE_LCD_BITMAP
+/* 2048 */
+
+#define MIN(x,y) ((x<y)?x:y)
+#if MIN(LCD_WIDTH, LCD_HEIGHT)>=240
+_2048_tiles.48x48x24.bmp
+_2048_background.224x224x24.bmp
+#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=176
+_2048_tiles.36x36x24.bmp
+_2048_background.168x168x24.bmp
+#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=132 || MIN(LCD_WIDTH, LCD_HEIGHT)>=128
+_2048_tiles.26x26x24.bmp
+_2048_background.121x121x24.bmp
+#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=110
+_2048_tiles.22x22x24.bmp
+_2048_background.103x103x24.bmp
+#else
+/* default to smallest bitmaps */
+_2048_tiles.12x12x24.bmp
+_2048_background.56x56x24.bmp
+#endif
+#undef MIN
+
/* Brickmania */
#ifdef HAVE_LCD_COLOR
#if LCD_WIDTH >= 112
diff --git a/apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp b/apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp
new file mode 100644
index 0000000000..153126bc99
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp b/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp
new file mode 100644
index 0000000000..1e6e3270d0
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp b/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp
new file mode 100644
index 0000000000..c47b5d26d5
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp b/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp
new file mode 100644
index 0000000000..531d5ed541
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp b/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp
new file mode 100644
index 0000000000..c3008f8eae
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp
new file mode 100644
index 0000000000..e531c2b6e2
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp
new file mode 100644
index 0000000000..186ba8bc95
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp
new file mode 100644
index 0000000000..f7df5ba5a4
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp
new file mode 100644
index 0000000000..e27ada133d
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp
new file mode 100644
index 0000000000..09165203ed
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp
Binary files differ