543 lines
14 KiB
C
543 lines
14 KiB
C
#include "stdlib.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;
|
|
Cell previousPosition;
|
|
} 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;
|
|
|
|
int Game_score = 0;
|
|
bool Game_over = false;
|
|
bool Game_won = false;
|
|
bool Game_keepPlaying = true;
|
|
|
|
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
|
|
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
|
|
|
|
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
|
|
Cell emptyCell;
|
|
Tile emptyTile;
|
|
|
|
emptyTile.x = tile.x;
|
|
emptyTile.y = tile.y;
|
|
emptyTile.value = 0;
|
|
emptyTile.hasMerged = false;
|
|
emptyCell.x = -1;
|
|
emptyCell.y = -1;
|
|
emptyTile.previousPosition = emptyCell;
|
|
|
|
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;
|
|
}
|
|
|
|
void storage_setBestScore(int bestScore) {
|
|
Storage_bestScore = bestScore;
|
|
// Sauvegarder dans la mémoire
|
|
}
|
|
|
|
// Screen (O HTML_Actuator)
|
|
|
|
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);
|
|
//ML_display_vram(); // DEBUG
|
|
}
|
|
|
|
void Screen_drawTileCase(Tile tile) {
|
|
Screen_drawTile(5+tile.x*14, 5+tile.y*14, tile.value);
|
|
}
|
|
|
|
void Screen_drawTileMoving(Tile tile, float percentage) {
|
|
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;
|
|
Screen_drawTile(x, y, tile.value);
|
|
}
|
|
|
|
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.previousPosition.x >= 0 && tile.previousPosition.y >= 0) || (tile.previousPosition.x == -2 && tile.previousPosition.y == -2))) {
|
|
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.previousPosition.x >= 0 && tile.previousPosition.y >= 0) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool Game_isGameTerminated() { // Intentionally moved here
|
|
return (Game_over || (Game_won && !Game_keepPlaying));
|
|
}
|
|
|
|
void Screen_actuate() {
|
|
|
|
float i;
|
|
|
|
Screen_drawFixedTiles(true);
|
|
|
|
SaveDisp(SAVEDISP_PAGE1);
|
|
|
|
for (i = 0; i <= 1; i += 0.01) {
|
|
RestoreDisp(SAVEDISP_PAGE1);
|
|
Screen_drawMovingTiles(i);
|
|
ML_display_vram();
|
|
}
|
|
|
|
RestoreDisp(SAVEDISP_PAGE1);
|
|
Screen_drawFixedTiles(false);
|
|
|
|
Screen_updateScore();
|
|
Screen_updateBestScore();
|
|
|
|
if (Game_isGameTerminated()) {
|
|
if (Game_over) {
|
|
Screen_message(false);
|
|
} else if (Game_won) {
|
|
Screen_message(true);
|
|
}
|
|
}
|
|
|
|
ML_display_vram();
|
|
}
|
|
|
|
// Game (O Game_manager)
|
|
|
|
|
|
void Game_actuate() {
|
|
if (Storage_bestScore < Game_score) {
|
|
storage_setBestScore(Game_score);
|
|
}
|
|
Screen_actuate();
|
|
}
|
|
|
|
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() {
|
|
Cell previousPosition;
|
|
int x, y;
|
|
for (x = 0; x <= 3; x++) {
|
|
for (y = 0; y <= 3; y++) {
|
|
Grid_grid.array[x][y].hasMerged = false;
|
|
previousPosition.x = -1;
|
|
previousPosition.y = -1;
|
|
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.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, previousPosition;
|
|
|
|
if (Grid_avaiableCellsAmount() > 0) {
|
|
position = Grid_randomAvaiableCell();
|
|
tile.value = (rand_int(0, 10) < 9 ? 1 : 2);
|
|
tile.x = position.x;
|
|
tile.y = position.y;
|
|
previousPosition.x = -2;
|
|
previousPosition.y = -2;
|
|
tile.previousPosition = previousPosition;
|
|
tile.hasMerged = false;
|
|
Grid_insertTile(tile);
|
|
}
|
|
}
|
|
|
|
void Game_move(int direction) { // 0: up, 1: right, 2: down, 3: left
|
|
Cell vector, cell, farthest, next;
|
|
Tile tile, merged;
|
|
Traversal traversals;
|
|
findFarthestPosition_return position;
|
|
bool moved = false;
|
|
int xI, yI;
|
|
|
|
if (Game_isGameTerminated()) { return; }
|
|
|
|
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 = position.next;
|
|
farthest = position.farthest;
|
|
if (Grid_cellContent(next).value == tile.value && !tile.hasMerged && !Grid_cellContent(next).hasMerged) { // Merge
|
|
merged.x = next.x;
|
|
merged.y = next.y;
|
|
merged.value = tile.value + 1;
|
|
merged.hasMerged = true;
|
|
merged.previousPosition = cell;
|
|
|
|
Grid_insertTile(merged);
|
|
Grid_removeTile(tile);
|
|
|
|
Game_score += 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 initGame() {
|
|
// Variables
|
|
int x, y;
|
|
Cell emptyCell;
|
|
|
|
// Reset variables
|
|
srand(RTC_getTicks());
|
|
Game_score = 0;
|
|
Game_over = false;
|
|
Game_won = false;
|
|
Game_keepPlaying = true;
|
|
|
|
emptyCell.x = -1;
|
|
emptyCell.y = -1;
|
|
|
|
for (x = 0; x <= 3; x++) {
|
|
for (y = 0; y <= 3; y++) {
|
|
Grid_grid.array[x][y].x = x;
|
|
Grid_grid.array[x][y].y = y;
|
|
Grid_grid.array[x][y].value = 0;
|
|
Grid_grid.array[x][y].hasMerged = false;
|
|
Grid_grid.array[x][y].previousPosition = emptyCell;
|
|
}
|
|
}
|
|
|
|
ML_clear_screen();
|
|
|
|
// Draw Title
|
|
PrintXY(67, 54, "2048", 0);
|
|
|
|
Screen_drawFixedTiles(true);
|
|
|
|
// Draw 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_addRandomTile();
|
|
Game_addRandomTile();
|
|
|
|
Game_actuate();
|
|
|
|
}
|
|
|
|
int AddIn_main(int isAppli, unsigned short OptionNum) {
|
|
// Variables
|
|
unsigned int key;
|
|
|
|
initGame();
|
|
while (1) { // Main loop
|
|
GetKey(&key);
|
|
switch (key) {
|
|
case KEY_CTRL_UP:
|
|
Game_move(0);
|
|
break;
|
|
case KEY_CTRL_RIGHT:
|
|
Game_move(1);
|
|
break;
|
|
case KEY_CTRL_DOWN:
|
|
Game_move(2);
|
|
break;
|
|
case KEY_CTRL_LEFT:
|
|
Game_move(3);
|
|
break;
|
|
case KEY_CTRL_DEL:
|
|
initGame();
|
|
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
|