#include "stdlib.h" #include "math.h" #include "fxlib.h" #include "MonochromeLib.h" // Sprites char tile[14][26] = {{127,240,213, 88,170,168,213, 88,170,168,213, 88,170,168,213, 88,170,168,213, 88,170,168,213, 88,127,240}, {127,240,128, 8,128, 8,135, 8,136,136,128,136,129, 8,130, 8,132, 8,143,136,128, 8,128, 8,127,240}, {127,240,128, 8,128, 8,129, 8,131, 8,133, 8,137, 8,143,136,129, 8,129, 8,128, 8,128, 8,127,240}, {127,240,128, 8,128, 8,135, 8,136,136,136,136,135, 8,136,136,136,136,135, 8,128, 8,128, 8,127,240}, {127,240,128, 8,128, 8,144,200,177, 8,146, 8,147,200,146, 40,146, 40,185,200,128, 8,128, 8,127,240}, {127,240,128, 8,128, 8,128, 8,184,200,133, 40,152, 72,132,136,185,232,128, 8,128, 8,128, 8,127,240}, {127,240,128, 8,128, 8,128, 8,152,104,160,168,185, 40,165,232,152, 40,128, 8,128, 8,128, 8,127,240}, {127,240,253,248,249,248,253,248,253,248,248,248,255,248,231, 56,218,216,247, 56,238,216,195, 56,127,240}, {127,240,249,248,246,248,253,248,251,248,240,248,255,248,195, 56,222,248,194, 56,250,216,199, 56,127,240}, {127,240,240,248,247,248,240,248,254,248,241,248,255,248,247, 56,230,216,247,184,247,120,226, 24,127,240}, {127,240,247, 56,230,216,246,216,246,216,227, 56,255,248,231,152,219, 88,246,216,238, 24,195,216,127,240}, {127,240,231, 56,218,216,246,216,238,216,195, 56,255,248,243, 56,234,216,219, 56,194,216,251, 56,127,240}, {127,240,255,248,255,248,248,248,247,120,255,120,254,248,253,248,255,248,253,248,255,248,255,248,127,240}, {127,240,255,248,255,248,253,248,253,248,253,248,253,248,253,248,255,248,253,248,255,248,255,248,127,240}}; char scoreBG[] = {63,255,255,255,254,0,127,255,255,255,255,0,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,255,255,255,255,255,128,127,255,255,255,255,0,63,255,255,255,254,0}; // 41*19 #define false 0 #define true 1 typedef char bool; typedef struct Cell { int x; int y; } Cell; typedef struct Tile { int x; int y; int value; bool hasMerged; bool hasMoved; bool isNew; Cell previousPosition; Cell previousMerge; } Tile; typedef struct Grid { Tile array[4][4]; } Grid; typedef struct Traversal { int x[4]; int y[4]; } Traversal; typedef struct findFarthestPosition_return { Cell next; Cell farthest; } findFarthestPosition_return; #define SCREEN_ANIMATION_TIME 32 #define STORAGE_FILESIZE 31 #define STORAGE_SCORE_LENGTH 7 #define STORAGE_BESTSCORE_LOCATION 0 #define STORAGE_SCORE_LOCATION 7 #define STORAGE_GRID_LOCATION 14 // #define STORAGE_OVER_LOCATION 30 // #define STORAGE_WON_LOCATION 31 // #define STORAGE_KEEPPLAYING_LOCATION 32 FONTCHARACTER Storage_saveFile[]={'\\','\\','f','l','s','0','\\','2','0','4','8','.','s','a','v',0}; int Game_score = 0; bool Game_over = false; bool Game_won = false; bool Game_keepPlaying = true; bool Game_terminated = false; // Temp value int Storage_bestScore = 0; Grid Grid_grid; static int SysCallCode[] = {0xD201422B,0x60F20000,0x80010070}; static int (*SysCall)(int R4, int R5, int R6, int R7, int FNo ) = (void*)&SysCallCode; // Usual functions void debugMsg(unsigned char* message) { unsigned int key; SaveDisp(SAVEDISP_PAGE3); Bdisp_AllClr_VRAM(); PopUpWin(3); PrintXY(11, 13, message, 0); while(1) { GetKey(&key); if (key == KEY_CTRL_EXE) { break; } } RestoreDisp(SAVEDISP_PAGE3); } int RTC_getTicks() { return (*SysCall)(0, 0, 0, 0, 0x3B); } unsigned char* intToStr(unsigned char* c, int n) { // Code par Eiyeron, Licence Creative Commons BY-SA if(n==0) { c[0] = '0'; c[1] = 0; } else { int i, l=0; if(n<0) { c[0] = '-'; n = abs(n); l++; } for(i=n ; i ; i/=10) l++; c[l] = 0; for(i=n ; i ; i/=10) c[--l] = i%10+'0'; } return c; } int rand_int(int min, int max) { return min + (rand() % (int)(max - min + 1)); } // Grid Tile Grid_getEmptyTile() { Tile emptyTile; Cell emptyCell; emptyCell.x = -1; emptyCell.y = -1; emptyTile.x = -1; emptyTile.y = -1; emptyTile.value = 0; emptyTile.hasMerged = false; emptyTile.hasMoved = false; emptyTile.isNew = false; emptyTile.previousPosition = emptyCell; emptyTile.previousMerge = emptyCell; return emptyTile; } bool Grid_withinBounds(Cell position) { return (position.x >= 0 && position.x < 4 && position.y >= 0 && position.y < 4); } Tile Grid_cellContent(Cell cell) { // TODO Make cellContentEdit(Cell cell, Tile tile), because it's read-only this way Tile back; if (Grid_withinBounds(cell)) { return Grid_grid.array[cell.x][cell.y]; } else { back.value = -1; return back; } } bool Grid_cellOccupied(Cell cell) { return (Grid_cellContent(cell).value > 0); } bool Grid_cellAvailable(Cell cell) { return !Grid_cellOccupied(cell); } int Grid_insertTile(Tile tile) { Grid_grid.array[tile.x][tile.y] = tile; } int Grid_removeTile(Tile tile) { // TODO use with cellContentEdit Tile emptyTile; emptyTile = Grid_getEmptyTile(); emptyTile.x = tile.x; emptyTile.y = tile.y; emptyTile.value = 0; Grid_grid.array[tile.x][tile.y] = emptyTile; } int Grid_avaiableCellsAmount() { int avaiableCellsNumber = 0, x, y; Cell position; for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { position.x = x; position.y = y; if (Grid_cellAvailable(position)) { avaiableCellsNumber++; } } } return avaiableCellsNumber; } // Storage void Storage_saveAll(int bestScore, int score, Grid grid) { int i, handle; char Storage_fileBuffer[STORAGE_FILESIZE]; // Preparing for (i = 0; i < STORAGE_FILESIZE; i++) { // Empty Storage_fileBuffer Storage_fileBuffer[i] = 0; } for (i = STORAGE_SCORE_LENGTH-1; i >= 0 ; i--) { // Store scoress Storage_fileBuffer[i+STORAGE_BESTSCORE_LOCATION] = bestScore%10; Storage_fileBuffer[i+STORAGE_SCORE_LOCATION] = score%10; bestScore = bestScore/10; score = score/10; } // Writing Bfile_DeleteFile(Storage_saveFile); Bfile_CreateFile(Storage_saveFile, STORAGE_FILESIZE); handle = Bfile_OpenFile(Storage_saveFile, _OPENMODE_WRITE); Bfile_WriteFile(handle, Storage_fileBuffer, STORAGE_FILESIZE); Bfile_CloseFile(handle); } bool Storage_gameSaved() { return (Bfile_OpenFile(Storage_saveFile, _OPENMODE_READ) > 0); } void Storage_restore() { int handle, i; char Storage_fileBuffer[STORAGE_FILESIZE]; handle = Bfile_OpenFile(Storage_saveFile, _OPENMODE_READ); Bfile_ReadFile(handle, &Storage_fileBuffer, STORAGE_FILESIZE, 0); for (i = 0; i < 7; i++) { Storage_bestScore += pow(10, i)*((int) Storage_fileBuffer[6-i]); //Game_score += pow(10, i)*((int) Storage_fileBuffer[13-i]); } Bfile_CloseFile(handle); } // Screen (O HTML_Actuator) void Screen_welcome() { // TODO Show original game text + commands unsigned int key; PopUpWin(3); PrintXY(12, 16, "Welcome to 2048", 0); PrintXY(12, 30, "Press [EXE]", 0); ML_display_vram(); Storage_saveAll(0, 0, Grid_grid); while(1) { GetKey(&key); if (key == KEY_CTRL_EXE) { break; } } } void Screen_drawTile(int x, int y, int value) { ML_rectangle(x + 1, y + 1, x + 11, y + 11, 0, ML_TRANSPARENT, ML_WHITE); ML_bmp_or(tile[value], x, y, 13, 13); } void Screen_drawTileCase(Tile tile) { Screen_drawTile(5+tile.x*14, 5+tile.y*14, tile.value); } void Screen_drawTileMoving(Tile tile, float percentage) { // TODO Make smaller // Draw moved-tiles and under-merged-tile int x, y, OTx, OTy, NTx, NTy; NTx = 5+tile.x*14; NTy = 5+tile.y*14; OTx = 5+tile.previousPosition.x*14; OTy = 5+tile.previousPosition.y*14; x = OTx + (NTx - OTx) * percentage; y = OTy + (NTy - OTy) * percentage; // Draw over-merged-tile if (!tile.hasMerged) { Screen_drawTile(x, y, tile.value); } else { Screen_drawTile(x, y, tile.value-1); OTx = 5+tile.previousMerge.x*14; OTy = 5+tile.previousMerge.y*14; x = OTx + (NTx - OTx) * percentage; y = OTy + (NTy - OTy) * percentage; Screen_drawTile(x, y, tile.value-1); } } int Screen_drawFixedTiles(bool dontDrawPreviousPositionTiles) { int x, y; Tile tile; Cell position; for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { position.x = x; position.y = y; tile = Grid_cellContent(position); if (dontDrawPreviousPositionTiles && (tile.hasMoved || tile.hasMerged || tile.isNew)) { tile.value = 0; } Screen_drawTileCase(tile); } } } int Screen_drawMovingTiles(float percentage) { int x, y; Tile tile; Cell position; for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { position.x = x; position.y = y; tile = Grid_cellContent(position); if (tile.hasMoved || tile.hasMerged) { Screen_drawTileMoving(tile, percentage); } } } } void Screen_updateScore() { unsigned char chaineScore[6]; PrintXY(70, 14, intToStr(chaineScore, Game_score), 1); } void Screen_updateBestScore() { unsigned char chaineScore[6]; PrintXY(70, 35, intToStr(chaineScore, Storage_bestScore), 1); } void Screen_message(bool won) { if (won) { // PHD PrintXY(67, 54, "WON", 0); } else { PrintXY(67, 54, "LOSE", 0); } } void Screen_moveTiles() { int animationStartTime; float animationLength; Screen_drawFixedTiles(true); SaveDisp(SAVEDISP_PAGE1); animationStartTime = RTC_getTicks(); do { animationLength = RTC_getTicks() - animationStartTime; RestoreDisp(SAVEDISP_PAGE1); Screen_drawMovingTiles(animationLength/SCREEN_ANIMATION_TIME); ML_display_vram(); } while (animationLength <= SCREEN_ANIMATION_TIME); RestoreDisp(SAVEDISP_PAGE1); Screen_drawFixedTiles(false); } void Screen_actuateGUI() { Screen_updateScore(); Screen_updateBestScore(); if (Game_terminated) { if (Game_over) { Screen_message(false); } else if (Game_won) { Screen_message(true); } } } void Screen_actuate() { Screen_moveTiles(); Screen_actuateGUI(); ML_display_vram(); } int Screen_drawGame() { Bdisp_AllClr_DDVRAM(); // Title PrintXY(67, 54, "2048", 0); // Tiles Screen_drawFixedTiles(false); // Score ML_bmp_or(scoreBG, 68, 4, 41, 19); ML_bmp_or(scoreBG, 68, 25, 41, 19); PrintXY(70, 6, "SCORE", 1); PrintXY(70, 27, "BEST", 1); } // Game (O Game_manager) void Game_actuate() { if (Storage_bestScore < Game_score) { Storage_bestScore = Game_score; } Game_terminated = (Game_over || (Game_won && !Game_keepPlaying)); Screen_actuate(); Storage_saveAll(Storage_bestScore, Game_score, Grid_grid); } Traversal Game_buildTraversals(Cell vector) { Traversal traversal; int i; for (i = 0; i <= 3; i++) { traversal.x[i] = (vector.x == 1 ? 3-i : i ); traversal.y[i] = (vector.y == 1 ? 3-i : i ); } return traversal; } Cell Game_getVector(int direction) { Cell vector; switch(direction) { case 0: vector.x = 0; vector.y = -1; break; case 1: vector.x = 1; vector.y = 0; break; case 2: vector.x = 0; vector.y = 1; break; case 3: vector.x = -1; vector.y = 0; break; } return vector; } void Game_prepareTiles() { int x, y; Cell previousPosition; for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { previousPosition.x = x; previousPosition.y = y; Grid_grid.array[x][y].hasMerged = false; Grid_grid.array[x][y].hasMerged = false; Grid_grid.array[x][y].hasMoved = false; Grid_grid.array[x][y].isNew = false; Grid_grid.array[x][y].previousPosition = previousPosition; } } } findFarthestPosition_return Game_findFarthestPosition(Cell cell, Cell vector) { Cell previous; findFarthestPosition_return back; // Progress towards the vector direction until an obstacle is found do { previous = cell; cell.x = previous.x + vector.x; cell.y = previous.y + vector.y; } while (Grid_withinBounds(cell) && Grid_cellAvailable(cell)); back.farthest = previous; back.next = cell; // Used to check if a merge is required return back; } bool Game_moveTile(Tile tile, Cell cell) { Cell previousPosition; if (tile.x == cell.x && tile.y == cell.y) { return false; } else { Grid_removeTile(tile); previousPosition.x = tile.x; previousPosition.y = tile.y; // tile.previousPosition = previousPosition; tile.hasMoved = true; tile.x = cell.x; tile.y = cell.y; Grid_insertTile(tile); return true; } } bool Game_positionsEqual(Cell first, Tile second) { return (first.x == second.x && first.y == second.y); } Cell Grid_randomAvaiableCell() { int avaiableCellsNumber, choosenCellNumber, x, y; Cell position; avaiableCellsNumber = Grid_avaiableCellsAmount(); choosenCellNumber = rand_int(1, avaiableCellsNumber); avaiableCellsNumber = 0; // Used as counter here for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { position.x = x; position.y = y; if (Grid_cellAvailable(position)) { avaiableCellsNumber++; if (avaiableCellsNumber == choosenCellNumber) { return position; } } } } } // Game (O game_manager) void Game_addRandomTile() { Tile tile; Cell position; if (Grid_avaiableCellsAmount() > 0) { position = Grid_randomAvaiableCell(); tile = Grid_getEmptyTile(); tile.value = (rand_int(0, 10) < 9 ? 1 : 2); tile.x = position.x; tile.y = position.y; tile.isNew = true; Grid_insertTile(tile); } } void Game_move(int direction) { // 0: up, 1: right, 2: down, 3: left Cell vector, cell, farthest; Tile tile, merged, next; Traversal traversals; findFarthestPosition_return position; bool moved = false; int xI, yI; if (Game_terminated) { return; } // Test if verification is done correctly vector = Game_getVector(direction); traversals = Game_buildTraversals(vector); Game_prepareTiles(); for (xI = 0; xI <= 3; xI++) { for (yI = 0; yI <= 3; yI++) { cell.x = traversals.x[xI]; cell.y = traversals.y[yI]; tile = Grid_cellContent(cell); if (tile.value > 0) { position = Game_findFarthestPosition(cell, vector); next = Grid_cellContent(position.next); farthest = position.farthest; if (next.value == tile.value && !next.hasMerged) { // Merge merged = Grid_getEmptyTile(); merged.x = next.x; merged.y = next.y; merged.value = tile.value + 1; merged.hasMerged = true; merged.previousPosition = next.previousPosition; merged.previousMerge = cell; Grid_insertTile(merged); Grid_removeTile(tile); Game_score += pow(2, merged.value); if (merged.value == 11) { Game_won = true; } moved = true; } else { if (Game_moveTile(tile, farthest)) { moved = true; } } } } } if (moved) { Game_addRandomTile(); // if (!Game_moveAvaiable()) { // this.over = true; // } Game_actuate(); } } int Game_reset() { // Variables int x, y; // Reset variables Game_score = 0; Game_over = false; Game_won = false; Game_keepPlaying = false; for (x = 0; x <= 3; x++) { for (y = 0; y <= 3; y++) { Grid_grid.array[x][y] = Grid_getEmptyTile(); Grid_grid.array[x][y].x = x; Grid_grid.array[x][y].y = y; Grid_grid.array[x][y].value = 0; } } } int Game_newGame() { Game_reset(); Game_addRandomTile(); Game_addRandomTile(); Game_start(); } int Game_start() { Screen_drawGame(); Screen_actuateGUI(); Storage_saveAll(Storage_bestScore, Game_score, Grid_grid); } int Game_begin() { if (Storage_gameSaved()) { // Resume play // Game_reset(); Storage_restore(); // TODO Store over, won & keepPlay // Game_start(); Game_newGame(); // DEBUG Until Grid can be restored } else { // First time Screen_welcome(); Game_newGame(); } } int AddIn_main(int isAppli, unsigned short OptionNum) { // Variables unsigned int key; // Getting ready Bdisp_AllClr_DDVRAM(); srand(RTC_getTicks()); Game_begin(); // Main loop while (1) { GetKey(&key); switch (key) { case KEY_CTRL_UP: case KEY_CHAR_8: Game_move(0); break; case KEY_CTRL_RIGHT: case KEY_CHAR_6: Game_move(1); break; case KEY_CTRL_DOWN: case KEY_CHAR_2: Game_move(2); break; case KEY_CTRL_LEFT: case KEY_CHAR_4: Game_move(3); break; case KEY_CTRL_DEL: Game_newGame(); break; default: break; } } return 1; // 1 is OK here } // Exclusive code to any Add-in #pragma section _BR_Size unsigned long BR_Size; #pragma section #pragma section _TOP int InitializeSystem(int isAppli, unsigned short OptionNum) { return INIT_ADDIN_APPLICATION(isAppli, OptionNum); } #pragma section