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>
916
apps/plugins/2048.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
2048,games
|
||||
alpine_cdc,apps
|
||||
alarmclock,apps
|
||||
autostart,apps
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
BIN
apps/plugins/bitmaps/native/_2048_background.103x103x24.bmp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp
Normal file
|
After Width: | Height: | Size: 122 KiB |