A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1101 lines 31 kB view raw
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#include "pluginbitmaps/_2048_background.h" 45#include "pluginbitmaps/_2048_tiles.h" 46 47/* some constants */ 48 49static const int ANIM_SLEEPTIME = (HZ/20); 50static const int NUM_STARTING_TILES = 2; 51static const int VERT_SPACING = 4; 52static const int WHAT_FONT = FONT_UI; 53static const unsigned int WINNING_TILE = 2048; 54 55/* must use macros for these */ 56#define GRID_SIZE 4 57#define HISCORES_FILE PLUGIN_GAMES_DATA_DIR "/2048.score" 58#define MIN_SPACE (BMPHEIGHT__2048_tiles * 0.134) 59#define NUM_SCORES 5 60#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/2048.save" 61#define SPACES (GRID_SIZE * GRID_SIZE) 62 63/* screen-specific configuration */ 64 65#if (LCD_WIDTH < LCD_HEIGHT) /* tall screens */ 66# define TITLE_X 0 67# define TITLE_Y 0 68# define BASE_Y (BMPHEIGHT__2048_tiles*1.5) 69# define BASE_X (BMPHEIGHT__2048_tiles*.5-MIN_SPACE) 70# define SCORE_X 0 71# define SCORE_Y (max_numeral_height) 72# define BEST_SCORE_X 0 73# define BEST_SCORE_Y (2*max_numeral_height) 74#else /* wide or square screens */ 75# define TITLE_X 0 76# define TITLE_Y 0 77# define BASE_X (LCD_WIDTH-(GRID_SIZE*BMPHEIGHT__2048_tiles)-(((GRID_SIZE+1)*MIN_SPACE))) 78# define BASE_Y (BMPHEIGHT__2048_tiles*.5-MIN_SPACE) 79# define SCORE_X 0 80# define SCORE_Y (max_numeral_height) 81# define BEST_SCORE_X 0 82# define BEST_SCORE_Y (2*max_numeral_height) 83#endif /* LCD_WIDTH < LCD_HEIGHT */ 84 85#if LCD_DEPTH > 1 86/* where to draw the background bitmap */ 87static const int BACKGROUND_X = (BASE_X-MIN_SPACE); 88static const int BACKGROUND_Y = (BASE_Y-MIN_SPACE); 89#endif 90 91/* key mappings */ 92#define KEY_UP PLA_UP 93#define KEY_DOWN PLA_DOWN 94#define KEY_LEFT PLA_LEFT 95#define KEY_RIGHT PLA_RIGHT 96#define KEY_UNDO PLA_SELECT 97 98#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ 99 || (CONFIG_KEYPAD == IPOD_3G_PAD) \ 100 || (CONFIG_KEYPAD == IPOD_4G_PAD) 101#define KEY_EXIT PLA_SELECT_REPEAT 102#else 103#define KEY_EXIT PLA_CANCEL 104#endif 105 106/* notice how "color" is spelled :P */ 107#ifdef HAVE_LCD_COLOR 108 109/* colors */ 110 111static const unsigned BACKGROUND = LCD_RGBPACK(0xfa, 0xf8, 0xef); 112static const unsigned BOARD_BACKGROUND = LCD_RGBPACK(0xbb, 0xad, 0xa0); 113static const unsigned TEXT_COLOR = LCD_RGBPACK(0x77, 0x6e, 0x65); 114 115#endif 116 117/* PLA data */ 118static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; 119 120/*** game data structures ***/ 121 122struct game_ctx_t { 123 unsigned int grid[GRID_SIZE][GRID_SIZE]; /* 0 = empty */ 124 unsigned int score; 125 unsigned int cksum; /* sum of grid, XORed by score */ 126 bool already_won; /* has the player gotten 2048 yet? */ 127} game_ctx; 128 129static struct game_ctx_t *ctx = &game_ctx; 130 131/*** temporary data ***/ 132 133static bool merged_grid[GRID_SIZE][GRID_SIZE]; 134static int old_grid[GRID_SIZE][GRID_SIZE]; 135 136static int max_numeral_height = -1; 137 138#if LCD_DEPTH <= 1 139static int max_numeral_width; 140#endif 141 142static bool loaded = false; /* has a save been loaded? */ 143 144/* the high score */ 145static unsigned int best_score; 146 147static bool abnormal_exit = true; 148static struct highscore highscores[NUM_SCORES]; 149 150/***************************** UTILITY FUNCTIONS *****************************/ 151 152static inline int rand_range(int min, int max) 153{ 154 return rb->rand() % (max-min + 1) + min; 155} 156 157/* prepares for exit */ 158static void cleanup(void) 159{ 160 backlight_use_settings(); 161} 162 163/* returns 2 or 4 */ 164static inline int rand_2_or_4(void) 165{ 166 /* 1 in 10 chance of a four */ 167 if(rb->rand() % 10 == 0) 168 return 4; 169 else 170 return 2; 171} 172 173/* displays the help text */ 174static bool do_help(void) 175{ 176 177#ifdef HAVE_LCD_COLOR 178 rb->lcd_set_foreground(LCD_WHITE); 179 rb->lcd_set_background(LCD_BLACK); 180#endif 181 182 rb->lcd_setfont(FONT_UI); 183 184 static char* help_text[]= {"2048", "", "Aim", 185 "", "Join", "the", "numbers", "to", "get", "to", "the", "2048", "tile!", "", "", 186 "How", "to", "Play", "", 187 "", "Use", "the", "directional", "keys", "to", "move", "the", "tiles.", "When", 188 "two", "tiles", "with", "the", "same", "number", "touch,", "they", "merge", "into", "one!"}; 189 190 struct style_text style[] = { 191 {0, TEXT_CENTER | TEXT_UNDERLINE}, 192 {2, C_RED}, 193 {15, C_RED}, 194 {16, C_RED}, 195 {17, C_RED}, 196 LAST_STYLE_ITEM 197 }; 198 199 return display_text(ARRAYLEN(help_text), help_text, style, NULL, true); 200} 201 202/*** tile movement logic ***/ 203 204/* this function performs the tile movement */ 205static inline void slide_internal(int startx, int starty, 206 int stopx, int stopy, 207 int dx, int dy, 208 int lookx, int looky, 209 bool update_best) 210{ 211 unsigned int best_score_old = best_score; 212 213 /* loop over the rows or columns, moving the tiles in the specified direction */ 214 for(int y = starty; y != stopy; y += dy) 215 { 216 for(int x = startx; x != stopx; x += dx) 217 { 218 if(ctx->grid[x + lookx][y + looky] == ctx->grid[x][y] && 219 ctx->grid[x][y] && 220 !merged_grid[x + lookx][y + looky] && 221 !merged_grid[x][y]) /* merge these two tiles */ 222 { 223 /* Each merged tile cannot be merged again */ 224 merged_grid[x + lookx][y + looky] = true; 225 ctx->grid[x + lookx][y + looky] = 2 * ctx->grid[x][y]; 226 ctx->score += ctx->grid[x + lookx][y + looky]; 227 ctx->grid[x][y] = 0; 228 } 229 else if(ctx->grid[x + lookx][y + looky] == 0) /* Empty! */ 230 { 231 ctx->grid[x + lookx][y + looky] = ctx->grid[x][y]; 232 ctx->grid[x][y] = 0; 233 } 234 } 235 } 236 if(ctx->score > best_score_old && update_best) 237 best_score = ctx->score; 238} 239 240/* these functions move each tile 1 space in the direction specified via calls to slide_internal */ 241 242/* Up 243 0 244 1 ^ ^ ^ ^ 245 2 ^ ^ ^ ^ 246 3 ^ ^ ^ ^ 247 0 1 2 3 248*/ 249static void up(bool update_best) 250{ 251 slide_internal(0, 1, /* start values */ 252 GRID_SIZE, GRID_SIZE, /* stop values */ 253 1, 1, /* delta values */ 254 0, -1, /* lookahead values */ 255 update_best); 256} 257 258/* Down 259 0 v v v v 260 1 v v v v 261 2 v v v v 262 3 263 0 1 2 3 264*/ 265static void down(bool update_best) 266{ 267 slide_internal(0, GRID_SIZE-2, 268 GRID_SIZE, -1, 269 1, -1, 270 0, 1, 271 update_best); 272} 273 274/* Left 275 0 < < < 276 1 < < < 277 2 < < < 278 3 < < < 279 0 1 2 3 280*/ 281static void left(bool update_best) 282{ 283 slide_internal(1, 0, 284 GRID_SIZE, GRID_SIZE, 285 1, 1, 286 -1, 0, 287 update_best); 288} 289 290/* Right 291 0 > > > 292 1 > > > 293 2 > > > 294 3 > > > 295 0 1 2 3 296*/ 297static void right(bool update_best) 298{ 299 slide_internal(GRID_SIZE-2, 0, /* start */ 300 -1, GRID_SIZE, /* stop */ 301 -1, 1, /* delta */ 302 1, 0, /* lookahead */ 303 update_best); 304} 305 306/* copies old_grid to ctx->grid */ 307static inline void RESTORE_GRID(void) 308{ 309 memcpy(&ctx->grid, &old_grid, sizeof(ctx->grid)); 310} 311 312/* slightly modified base 2 logarithm, returns 1 when given zero, and log2(n) + 1 for anything else */ 313static inline int ilog2(int n) 314{ 315 if(n == 0) 316 return 1; 317 int log = 0; 318 while(n > 1) 319 { 320 n >>= 1; 321 ++log; 322 } 323 return log + 1; 324} 325 326/* low-depth displays resort to text drawing, see the #else case below */ 327 328#if LCD_DEPTH > 1 329 330/* draws game screen + updates LCD */ 331static void draw(void) 332{ 333#ifdef HAVE_LCD_COLOR 334 rb->lcd_set_background(BACKGROUND); 335#endif 336 337 rb->lcd_clear_display(); 338 339 /* draw the background */ 340 341 rb->lcd_bitmap(_2048_background, 342 BACKGROUND_X, BACKGROUND_Y, 343 BMPWIDTH__2048_background, BMPWIDTH__2048_background); 344 345 /* 346 grey_gray_bitmap(_2048_background, BACKGROUND_X, BACKGROUND_Y, BMPWIDTH__2048_background, BMPHEIGHT__2048_background); 347 */ 348 349 /* draw the grid */ 350 351 for(int y = 0; y < GRID_SIZE; ++y) 352 { 353 for(int x = 0; x < GRID_SIZE; ++x) 354 { 355 rb->lcd_bitmap_part(_2048_tiles, /* source */ 356 BMPWIDTH__2048_tiles - BMPHEIGHT__2048_tiles * ilog2(ctx->grid[x][y]), 0, /* source upper left corner */ 357 STRIDE(SCREEN_MAIN, BMPWIDTH__2048_tiles, BMPHEIGHT__2048_tiles), /* stride */ 358 (BMPHEIGHT__2048_tiles + MIN_SPACE) * x + BASE_X, (BMPHEIGHT__2048_tiles + MIN_SPACE) * y + BASE_Y, /* dest upper-left corner */ 359 BMPHEIGHT__2048_tiles, BMPHEIGHT__2048_tiles); /* size of the cut section */ 360 } 361 } 362 363 /* draw the title */ 364 char buf[32]; 365 366#ifdef HAVE_LCD_COLOR 367 rb->lcd_set_foreground(TEXT_COLOR); 368#endif 369 370 rb->snprintf(buf, sizeof(buf), "%d", WINNING_TILE); 371 372 /* check if the title will overlap the grid */ 373 int w, h; 374 rb->lcd_setfont(FONT_UI); 375 rb->font_getstringsize(buf, &w, &h, FONT_UI); 376 bool draw_title = true; 377 if(w + TITLE_X >= BACKGROUND_X && h + TITLE_Y >= BACKGROUND_Y) 378 { 379 /* if it goes into the grid, use the system font, which should be smaller */ 380 rb->lcd_setfont(FONT_SYSFIXED); 381 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 382 if(w + TITLE_X >= BACKGROUND_X && h + TITLE_Y >= BACKGROUND_Y) 383 { 384 /* title can't fit, don't draw it */ 385 draw_title = false; 386 h = 0; 387 } 388 } 389 390 if(draw_title) 391 rb->lcd_putsxy(TITLE_X, TITLE_Y, buf); 392 393 int score_y = TITLE_Y + h + VERT_SPACING; 394 395 /* draw the score */ 396 rb->snprintf(buf, sizeof(buf), "Score: %d", ctx->score); 397 398#ifdef HAVE_LCD_COLOR 399 rb->lcd_set_foreground(LCD_WHITE); 400 rb->lcd_set_background(BOARD_BACKGROUND); 401#endif 402 403 rb->lcd_setfont(FONT_UI); 404 rb->font_getstringsize(buf, &w, &h, FONT_UI); 405 406 /* try making the score fit */ 407 if(w + SCORE_X >= BACKGROUND_X && h + SCORE_Y >= BACKGROUND_Y) 408 { 409 /* score overflows */ 410 /* first see if it fits with Score: and FONT_SYSFIXED */ 411 rb->lcd_setfont(FONT_SYSFIXED); 412 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 413 if(w + SCORE_X < BACKGROUND_X) 414 /* it fits, go and draw it */ 415 goto draw_lbl; 416 417 /* now try with S: and FONT_UI */ 418 rb->snprintf(buf, sizeof(buf), "S: %d", ctx->score); 419 rb->font_getstringsize(buf, &w, &h, FONT_UI); 420 rb->lcd_setfont(FONT_UI); 421 if(w + SCORE_X < BACKGROUND_X) 422 goto draw_lbl; 423 424 /* now try with S: and FONT_SYSFIXED */ 425 rb->snprintf(buf, sizeof(buf), "S: %d", ctx->score); 426 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 427 rb->lcd_setfont(FONT_SYSFIXED); 428 if(w + SCORE_X < BACKGROUND_X) 429 goto draw_lbl; 430 431 /* then try without Score: and FONT_UI */ 432 rb->snprintf(buf, sizeof(buf), "%d", ctx->score); 433 rb->font_getstringsize(buf, &w, &h, FONT_UI); 434 rb->lcd_setfont(FONT_UI); 435 if(w + SCORE_X < BACKGROUND_X) 436 goto draw_lbl; 437 438 /* as a last resort, don't use Score: and use the system font */ 439 rb->snprintf(buf, sizeof(buf), "%d", ctx->score); 440 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 441 rb->lcd_setfont(FONT_SYSFIXED); 442 if(w + SCORE_X < BACKGROUND_X) 443 goto draw_lbl; 444 else 445 goto skip_draw_score; 446 } 447 448draw_lbl: 449 rb->lcd_putsxy(SCORE_X, score_y, buf); 450 score_y += h + VERT_SPACING; 451 452 /* draw the best score */ 453skip_draw_score: 454 rb->snprintf(buf, sizeof(buf), "Best: %d", best_score); 455 456#ifdef HAVE_LCD_COLOR 457 rb->lcd_set_foreground(LCD_WHITE); 458 rb->lcd_set_background(BOARD_BACKGROUND); 459#endif 460 461 rb->lcd_setfont(FONT_UI); 462 rb->font_getstringsize(buf, &w, &h, FONT_UI); 463 if(w + BEST_SCORE_X >= BACKGROUND_X && h + BEST_SCORE_Y >= BACKGROUND_Y) 464 { 465 /* score overflows */ 466 /* first see if it fits with Score: and FONT_SYSFIXED */ 467 rb->lcd_setfont(FONT_SYSFIXED); 468 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 469 if(w + BEST_SCORE_X < BACKGROUND_X) 470 /* it fits, go and draw it */ 471 goto draw_best; 472 473 /* now try with S: and FONT_UI */ 474 rb->snprintf(buf, sizeof(buf), "B: %d", best_score); 475 rb->font_getstringsize(buf, &w, &h, FONT_UI); 476 rb->lcd_setfont(FONT_UI); 477 if(w + BEST_SCORE_X < BACKGROUND_X) 478 goto draw_best; 479 480 /* now try with S: and FONT_SYSFIXED */ 481 rb->snprintf(buf, sizeof(buf), "B: %d", best_score); 482 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 483 rb->lcd_setfont(FONT_SYSFIXED); 484 if(w + BEST_SCORE_X < BACKGROUND_X) 485 goto draw_best; 486 487 /* then try without Score: and FONT_UI */ 488 rb->snprintf(buf, sizeof(buf), "%d", best_score); 489 rb->font_getstringsize(buf, &w, &h, FONT_UI); 490 rb->lcd_setfont(FONT_UI); 491 if(w + BEST_SCORE_X < BACKGROUND_X) 492 goto draw_best; 493 494 /* as a last resort, don't use Score: and use the system font */ 495 rb->snprintf(buf, sizeof(buf), "%d", best_score); 496 rb->font_getstringsize(buf, &w, &h, FONT_SYSFIXED); 497 rb->lcd_setfont(FONT_SYSFIXED); 498 if(w + BEST_SCORE_X < BACKGROUND_X) 499 goto draw_best; 500 else 501 goto skip_draw_best; 502 } 503draw_best: 504 rb->lcd_putsxy(BEST_SCORE_X, score_y, buf); 505 506skip_draw_best: 507 rb->lcd_update(); 508 509 /* revert the font */ 510 rb->lcd_setfont(WHAT_FONT); 511} 512 513#else /* LCD_DEPTH > 1 */ 514 515/* 1-bit display :( */ 516/* bitmaps are unreadable on these screens, so just resort to text-based drawing */ 517static void draw(void) 518{ 519 rb->lcd_clear_display(); 520 521 /* Draw the grid */ 522 /* find the biggest tile */ 523 unsigned int biggest_tile = 0; 524 for(int x = 0; x < GRID_SIZE; ++x) 525 { 526 for(int y = 0; y < GRID_SIZE; ++y) 527 if(ctx->grid[x][y] > biggest_tile) 528 biggest_tile = ctx->grid[x][y]; 529 } 530 531 char buf[32]; 532 533 rb->snprintf(buf, 32, "%d", biggest_tile); 534 535 int biggest_tile_width = rb->strlen(buf) * rb->font_get_width(rb->font_get(WHAT_FONT), '0') + MIN_SPACE; 536 537 for(int y = 0; y < GRID_SIZE; ++y) 538 { 539 for(int x = 0; x < GRID_SIZE; ++x) 540 { 541 if(ctx->grid[x][y]) 542 { 543 if(ctx->grid[x][y] > biggest_tile) 544 biggest_tile = ctx->grid[x][y]; 545 rb->snprintf(buf, 32, "%d", ctx->grid[x][y]); 546 rb->lcd_putsxy(biggest_tile_width * x, y * max_numeral_height + max_numeral_height, buf); 547 } 548 } 549 } 550 551 /* Now draw the score, and the game title */ 552 rb->snprintf(buf, 32, "Score: %d", ctx->score); 553 int buf_width, buf_height; 554 rb->font_getstringsize(buf, &buf_width, &buf_height, WHAT_FONT); 555 556 int score_leftmost = LCD_WIDTH - buf_width - 1; 557 /* Check if there is enough space to display "Score: ", otherwise, only display the score */ 558 if(score_leftmost >= 0) 559 rb->lcd_putsxy(score_leftmost, 0, buf); 560 else 561 rb->lcd_putsxy(score_leftmost, 0, buf + rb->strlen("Score: ")); 562 563 rb->snprintf(buf, 32, "%d", WINNING_TILE); 564 rb->font_getstringsize(buf, &buf_width, &buf_height, WHAT_FONT); 565 if(buf_width < score_leftmost) 566 rb->lcd_putsxy(0, 0, buf); 567 568 rb->lcd_update(); 569} 570 571#endif /* LCD_DEPTH > 1 */ 572 573/* place a 2 or 4 in a random empty space */ 574static void place_random(void) 575{ 576 int xpos[SPACES], ypos[SPACES]; 577 int back = 0; 578 /* get the indexes of empty spaces */ 579 for(int y = 0; y < GRID_SIZE; ++y) 580 for(int x = 0; x < GRID_SIZE; ++x) 581 { 582 if(!ctx->grid[x][y]) 583 { 584 xpos[back] = x; 585 ypos[back++] = y; 586 } 587 } 588 589 if(!back) 590 /* no empty spaces */ 591 return; 592 593 int idx = rand_range(0, back - 1); 594 ctx->grid[ xpos[idx] ][ ypos[idx] ] = rand_2_or_4(); 595} 596 597/* checks for a win or loss */ 598static bool check_gameover(void) 599{ 600 /* first, check for a loss */ 601 int oldscore = ctx->score; 602 bool have_legal_move = false; 603 604 memset(&merged_grid, 0, SPACES * sizeof(bool)); 605 up(false); 606 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid))) 607 { 608 RESTORE_GRID(); 609 ctx->score = oldscore; 610 have_legal_move = true; 611 } 612 RESTORE_GRID(); 613 614 memset(&merged_grid, 0, SPACES * sizeof(bool)); 615 down(false); 616 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid))) 617 { 618 RESTORE_GRID(); 619 ctx->score = oldscore; 620 have_legal_move = true; 621 } 622 RESTORE_GRID(); 623 624 memset(&merged_grid, 0, SPACES * sizeof(bool)); 625 left(false); 626 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid))) 627 { 628 RESTORE_GRID(); 629 ctx->score = oldscore; 630 have_legal_move = true; 631 } 632 RESTORE_GRID(); 633 634 memset(&merged_grid, 0, SPACES * sizeof(bool)); 635 right(false); 636 if(memcmp(&old_grid, &ctx->grid, sizeof(ctx->grid))) 637 { 638 RESTORE_GRID(); 639 ctx->score = oldscore; 640 have_legal_move = true; 641 } 642 ctx->score = oldscore; 643 if(!have_legal_move) 644 { 645 /* no more legal moves */ 646 draw(); /* Shame the player */ 647 rb->splash(HZ*2, "Game Over!"); 648 return true; 649 } 650 651 for(int y = 0;y < GRID_SIZE; ++y) 652 { 653 for(int x = 0; x < GRID_SIZE; ++x) 654 { 655 if(ctx->grid[x][y] == WINNING_TILE && !ctx->already_won) 656 { 657 /* Let the user see the tile in its full glory... */ 658 draw(); 659 ctx->already_won = true; 660 rb->splash(HZ*2,"You win!"); 661 } 662 } 663 } 664 return false; 665} 666 667/* loads highscores from disk */ 668/* creates an empty structure if the file does not exist */ 669static void load_hs(void) 670{ 671 if(rb->file_exists(HISCORES_FILE)) 672 highscore_load(HISCORES_FILE, highscores, NUM_SCORES); 673 else 674 memset(highscores, 0, sizeof(struct highscore) * NUM_SCORES); 675} 676 677/* initialize the data structures */ 678static void init_game(bool newgame) 679{ 680 best_score = highscores[0].score; 681 if(loaded && ctx->score > best_score) 682 best_score = ctx->score; 683 684 if(newgame) 685 { 686 /* initialize the game context */ 687 memset(ctx->grid, 0, sizeof(ctx->grid)); 688 for(int i = 0; i < NUM_STARTING_TILES; ++i) 689 { 690 place_random(); 691 } 692 ctx->score = 0; 693 ctx->already_won = false; 694 } 695 696 /* using the menu resets the font */ 697 /* set it again here */ 698 699 rb->lcd_setfont(WHAT_FONT); 700 701 /* Now calculate font sizes */ 702 /* Now get the height of the font */ 703 rb->font_getstringsize("0123456789", NULL, &max_numeral_height, WHAT_FONT); 704 max_numeral_height += VERT_SPACING; 705 706#if LCD_DEPTH <= 1 707 max_numeral_width = rb->font_get_width(rb->font_get(WHAT_FONT), '0'); 708#endif 709 710 backlight_ignore_timeout(); 711 712 draw(); 713} 714 715/* save the current game state */ 716static void save_game(void) 717{ 718 rb->splash(0, "Saving..."); 719 int fd = rb->open(RESUME_FILE, O_WRONLY|O_CREAT, 0666); 720 if(fd < 0) 721 { 722 return; 723 } 724 725 /* calculate checksum */ 726 ctx->cksum = 0; 727 728 for(int x = 0; x < GRID_SIZE; ++x) 729 for(int y = 0; y < GRID_SIZE; ++y) 730 ctx->cksum += ctx->grid[x][y]; 731 732 ctx->cksum ^= ctx->score; 733 734 rb->write(fd, ctx, sizeof(struct game_ctx_t)); 735 rb->close(fd); 736 rb->lcd_update(); 737} 738 739/* loads a saved game, returns true on success */ 740static bool load_game(void) 741{ 742 int success = 0; 743 int fd = rb->open(RESUME_FILE, O_RDONLY); 744 if(fd < 0) 745 { 746 rb->remove(RESUME_FILE); 747 return false; 748 } 749 750 int numread = rb->read(fd, ctx, sizeof(struct game_ctx_t)); 751 752 /* verify checksum */ 753 unsigned int calc = 0; 754 for(int x = 0; x < GRID_SIZE; ++x) 755 for(int y = 0; y < GRID_SIZE; ++y) 756 calc += ctx->grid[x][y]; 757 758 calc ^= ctx->score; 759 760 if(numread == sizeof(struct game_ctx_t) && calc == ctx->cksum) 761 ++success; 762 763 rb->close(fd); 764 rb->remove(RESUME_FILE); 765 766 return (success > 0); 767} 768 769/* update the highscores with ctx->score */ 770static void hs_check_update(bool noshow) 771{ 772 /* first, find the biggest tile to show as the level */ 773 unsigned int biggest = 0; 774 for(int x = 0; x < GRID_SIZE; ++x) 775 { 776 for(int y = 0; y < GRID_SIZE; ++y) 777 { 778 if(ctx->grid[x][y] > biggest) 779 biggest = ctx->grid[x][y]; 780 } 781 } 782 783 int hs_idx = highscore_update(ctx->score,biggest, "", highscores,NUM_SCORES); 784 if(!noshow) 785 { 786 /* show the scores if there is a new high score */ 787 if(hs_idx >= 0) 788 { 789 rb->splashf(HZ*2, "New High Score: %d", ctx->score); 790 rb->lcd_clear_display(); 791 highscore_show(hs_idx, highscores, NUM_SCORES, true); 792 } 793 } 794 highscore_save(HISCORES_FILE, highscores, NUM_SCORES); 795} 796 797/* asks the user if they wish to quit */ 798static bool confirm_quit(void) 799{ 800 const struct text_message prompt = { (const char*[]) {"Are you sure?", "This will clear your current game."}, 2}; 801 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); 802 if(response == YESNO_NO) 803 return false; 804 else 805 return true; 806} 807 808/* show the pause menu */ 809static int do_2048_pause_menu(void) 810{ 811 int sel = 0; 812 MENUITEM_STRINGLIST(menu,"2048 Menu", NULL, 813 "Resume Game", 814 "Start New Game", 815 "High Scores", 816 "Playback Control", 817 "Help", 818 "Quit without Saving", 819 "Quit"); 820 while(1) 821 { 822 switch(rb->do_menu(&menu, &sel, NULL, false)) 823 { 824 case 0: 825 draw(); 826 return 0; 827 case 1: 828 { 829 if(!confirm_quit()) 830 break; 831 else 832 { 833 hs_check_update(false); 834 return 1; 835 } 836 } 837 case 2: 838 highscore_show(-1, highscores, NUM_SCORES, true); 839 break; 840 case 3: 841 playback_control(NULL); 842 break; 843 case 4: 844 do_help(); 845 break; 846 case 5: /* quit w/o saving */ 847 { 848 if(!confirm_quit()) 849 break; 850 else 851 { 852 return 2; 853 } 854 } 855 case 6: 856 return 3; 857 default: 858 break; 859 } 860 } 861} 862 863static void exit_handler(void) 864{ 865 cleanup(); 866 if(abnormal_exit) 867 save_game(); 868#ifdef HAVE_ADJUSTABLE_CPU_FREQ 869 rb->cpu_boost(false); /* back to idle */ 870#endif 871 return; 872} 873 874static bool check_hs; 875 876/* main game loop */ 877static enum plugin_status do_game(bool newgame) 878{ 879 init_game(newgame); 880 rb_atexit(exit_handler); 881 int made_move = 0; 882 while(1) 883 { 884#ifdef HAVE_ADJUSTABLE_CPU_FREQ 885 rb->cpu_boost(false); /* Save battery when idling */ 886#endif 887 /* Wait for a button press */ 888 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); 889 made_move = 0; 890 891 memset(&merged_grid, 0, SPACES*sizeof(bool)); 892 memcpy(&old_grid, &ctx->grid, sizeof(int)*SPACES); 893 894 unsigned int grid_before_anim_step[GRID_SIZE][GRID_SIZE]; 895 896#ifdef HAVE_ADJUSTABLE_CPU_FREQ 897 rb->cpu_boost(true); /* doing work now... */ 898#endif 899 switch(button) 900 { 901 case KEY_UP: 902 for(int i = 0; i < GRID_SIZE - 1; ++i) 903 { 904 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)); 905 up(true); 906 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid))) 907 { 908 rb->sleep(ANIM_SLEEPTIME); 909 draw(); 910 } 911 } 912 made_move = 1; 913 break; 914 case KEY_DOWN: 915 for(int i = 0; i < GRID_SIZE - 1; ++i) 916 { 917 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)); 918 down(true); 919 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid))) 920 { 921 rb->sleep(ANIM_SLEEPTIME); 922 draw(); 923 } 924 } 925 made_move = 1; 926 break; 927 case KEY_LEFT: 928 for(int i = 0; i < GRID_SIZE - 1; ++i) 929 { 930 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)); 931 left(true); 932 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid))) 933 { 934 rb->sleep(ANIM_SLEEPTIME); 935 draw(); 936 } 937 } 938 made_move = 1; 939 break; 940 case KEY_RIGHT: 941 for(int i = 0; i < GRID_SIZE - 1; ++i) 942 { 943 memcpy(grid_before_anim_step, ctx->grid, sizeof(ctx->grid)); 944 right(true); 945 if(memcmp(grid_before_anim_step, ctx->grid, sizeof(ctx->grid))) 946 { 947 rb->sleep(ANIM_SLEEPTIME); 948 draw(); 949 } 950 } 951 made_move = 1; 952 break; 953 case KEY_EXIT: 954 switch(do_2048_pause_menu()) 955 { 956 case 0: /* resume */ 957 break; 958 case 1: /* new game */ 959 init_game(true); 960 made_move = 1; 961 continue; 962 case 2: /* quit without saving */ 963 check_hs = true; 964 rb->remove(RESUME_FILE); 965 return PLUGIN_ERROR; 966 case 3: /* save and quit */ 967 check_hs = false; 968 save_game(); 969 return PLUGIN_ERROR; 970 } 971 break; 972 default: 973 { 974 exit_on_usb(button); /* handle poweroff and USB events */ 975 break; 976 } 977 } 978 979 if(made_move) 980 { 981 /* Check if any tiles moved, then add random */ 982 if(memcmp(&old_grid, ctx->grid, sizeof(ctx->grid))) 983 { 984 place_random(); 985 } 986 memcpy(&old_grid, ctx->grid, sizeof(ctx->grid)); 987 if(check_gameover()) 988 return PLUGIN_OK; 989 draw(); 990 } 991#ifdef HAVE_ADJUSTABLE_CPU_FREQ 992 rb->cpu_boost(false); /* back to idle */ 993#endif 994 rb->yield(); 995 } 996} 997 998/* decide if this_item should be shown in the main menu */ 999/* used to hide resume option when there is no save */ 1000static int mainmenu_cb(int action, 1001 const struct menu_item_ex *this_item, 1002 struct gui_synclist *this_list) 1003{ 1004 (void)this_list; 1005 int idx = ((intptr_t)this_item); 1006 if(action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5)) 1007 return ACTION_EXIT_MENUITEM; 1008 return action; 1009} 1010 1011/* show the main menu */ 1012static enum plugin_status do_2048_menu(void) 1013{ 1014 int sel = 0; 1015 loaded = load_game(); 1016 MENUITEM_STRINGLIST(menu, 1017 "2048 Menu", 1018 mainmenu_cb, 1019 "Resume Game", 1020 "Start New Game", 1021 "High Scores", 1022 "Playback Control", 1023 "Help", 1024 "Quit without Saving", 1025 "Quit"); 1026 while(true) 1027 { 1028 switch(rb->do_menu(&menu, &sel, NULL, false)) 1029 { 1030 case 0: /* Start new game or resume a game */ 1031 case 1: 1032 { 1033 if(sel == 1 && loaded) 1034 { 1035 if(!confirm_quit()) 1036 break; 1037 } 1038 enum plugin_status ret = do_game(sel == 1); 1039 switch(ret) 1040 { 1041 case PLUGIN_OK: 1042 { 1043 loaded = false; 1044 rb->remove(RESUME_FILE); 1045 hs_check_update(false); 1046 break; 1047 } 1048 case PLUGIN_USB_CONNECTED: 1049 save_game(); 1050 /* Don't bother showing the high scores... */ 1051 return ret; 1052 case PLUGIN_ERROR: /* exit without menu */ 1053 if(check_hs) 1054 hs_check_update(false); 1055 return PLUGIN_OK; 1056 default: 1057 break; 1058 } 1059 break; 1060 } 1061 case 2: 1062 highscore_show(-1, highscores, NUM_SCORES, true); 1063 break; 1064 case 3: 1065 playback_control(NULL); 1066 break; 1067 case 4: 1068 do_help(); 1069 break; 1070 case 5: 1071 if(confirm_quit()) 1072 return PLUGIN_OK; 1073 break; 1074 case 6: 1075 if(loaded) 1076 save_game(); 1077 return PLUGIN_OK; 1078 default: 1079 break; 1080 } 1081 } 1082} 1083 1084/* plugin entry point */ 1085enum plugin_status plugin_start(const void* param) 1086{ 1087 (void)param; 1088 rb->srand(*rb->current_tick); 1089 load_hs(); 1090 rb->lcd_setfont(WHAT_FONT); 1091 1092 /* now start the game menu */ 1093 enum plugin_status ret = do_2048_menu(); 1094 1095 highscore_save(HISCORES_FILE, highscores, NUM_SCORES); 1096 cleanup(); 1097 1098 abnormal_exit = false; 1099 1100 return ret; 1101}