A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Franklin Wei and committed by
Michael Giacomelli
b61553c2 9a3400a4

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

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_background.121x121x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_background.168x168x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_background.224x224x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_background.56x56x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_tiles.12x12x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_tiles.22x22x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_tiles.26x26x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_tiles.36x36x24.bmp

This is a binary file and will not be displayed.

apps/plugins/bitmaps/native/_2048_tiles.48x48x24.bmp

This is a binary file and will not be displayed.

+1
docs/CREDITS
··· 637 637 Sebastian Leonhardt 638 638 Avi Eisenberg 639 639 Richard Burke 640 + Franklin Wei 640 641 641 642 The libmad team 642 643 The wavpack team
+16
manual/plugins/2048.tex
··· 1 + \subsection{2048} 2 + %\screenshot{plugins/images/ss-2048}{2048}{fig:2048} 3 + 4 + 2048 is a simple, addictive puzzle game played by moving tiles in around on a 4x4 grid. Tiles slide as far as possible in the direction chosen by the player each turn until they are stopped by either another tile or the edge of the grid. If two tiles of the same number collide while moving, they merge into a tile with the total value of the two tiles that collided. The resulting tile cannot merge with another the same move. After each move, a tile with the value of 2 or 4 is created in an empty spot on the grid. 5 + 6 + The game is won when a tile with a value of 2048 is created, and the player loses when there are no more possible moves. 7 + 8 + \begin{btnmap} 9 + \PluginUp, \PluginDown, \PluginLeft, \PluginRight 10 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCUp, \PluginRCDown, \PluginRCLeft, \PluginRCRight} 11 + & Slide tiles\\ 12 + 13 + \PluginCancel 14 + \opt{HAVEREMOTEKEYMAP}{& \PluginRCCancel} 15 + & Go to menu\\ 16 + \end{btnmap}
+2
manual/plugins/main.tex
··· 23 23 \opt{archosrecorder,archosfmrecorder,iriverh100,iaudiom5,lcd_color} 24 24 {and Rockboy in \reference{ref:Rockboy}}.} 25 25 26 + \opt{lcd_bitmap}{\input{plugins/2048.tex}} 27 + 26 28 \opt{lcd_bitmap}{\input{plugins/blackjack.tex}} 27 29 28 30 \opt{lcd_bitmap}{\input{plugins/brickmania.tex}}