diff options
Diffstat (limited to 'apps/plugins')
-rw-r--r-- | apps/plugins/2048.c | 916 | ||||
-rw-r--r-- | apps/plugins/CATEGORIES | 1 | ||||
-rw-r--r-- | apps/plugins/SOURCES | 2 | ||||
-rw-r--r-- | apps/plugins/bitmaps/native/SOURCES | 22 | ||||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp | bin | 0 -> 32258 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp | bin | 0 -> 44166 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp | bin | 0 -> 84794 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp | bin | 0 -> 150650 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp | bin | 0 -> 9530 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp | bin | 0 -> 7898 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp | bin | 0 -> 26258 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp | bin | 0 -> 36626 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp | bin | 0 -> 70106 bytes | |||
-rw-r--r-- | apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000000..153126bc99 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp b/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp Binary files differnew file mode 100644 index 0000000000..1e6e3270d0 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp b/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp Binary files differnew file mode 100644 index 0000000000..c47b5d26d5 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp b/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp Binary files differnew file mode 100644 index 0000000000..531d5ed541 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp b/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp Binary files differnew file mode 100644 index 0000000000..c3008f8eae --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp Binary files differnew file mode 100644 index 0000000000..e531c2b6e2 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp Binary files differnew file mode 100644 index 0000000000..186ba8bc95 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp Binary files differnew file mode 100644 index 0000000000..f7df5ba5a4 --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp Binary files differnew file mode 100644 index 0000000000..e27ada133d --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp diff --git a/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp b/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp Binary files differnew file mode 100644 index 0000000000..09165203ed --- /dev/null +++ b/apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp |