#include #include "gfx.h" #include "mines.h" typedef struct { // Node properties uint8_t num; // Node number, how many mines around gBool open; // Node shown or hidden gBool check; // Node needs to be checked or not, used for opening up empty nodes gBool flag; // Node is marked with flag by player uint16_t fieldNum; // Node number, used to randomize gamestart "animation" } nodeProps; static GEventMouse ev; static nodeProps minesField[MINES_FIELD_WIDTH][MINES_FIELD_HEIGHT]; // Mines field array static gBool minesGameOver = gFalse; static gBool minesGameWinner = gFalse; static int16_t minesEmptyNodes; // Empty node counter static int16_t minesFlags; // Flag counter static int16_t minesTime; // Time counter static GTimer minesTimeCounterTimer; static const char* minesGraph[] = {"1.bmp","2.bmp","3.bmp","4.bmp","5.bmp","6.bmp","7.bmp","8.bmp", "closed.bmp", "empty.bmp", "explode.bmp", "flag.bmp", "mine.bmp", "wrong.bmp"}; // 14 elements (0-13) static gdispImage minesImage; static uint8_t minesStatusIconWidth = 0; static uint8_t minesStatusIconHeight = 0; static gBool minesFirstGame = gTrue; // Just don't clear field for the first time, as we have black screen already... :/ static gBool minesSplashTxtVisible = gFalse; #if MINES_SHOW_SPLASH static GTimer minesSplashBlink; #endif static int uitoa(unsigned int value, char* buf, int max) { int n = 0; int i = 0; int tmp = 0; if (!buf) return -3; if (2 > max) return -4; i=1; tmp = value; if (0 > tmp) { tmp *= -1; i++; } for (;;) { tmp /= 10; if (0 >= tmp) break; i++; } if (i >= max) { buf[0] = '?'; buf[1] = 0x0; return 2; } n = i; tmp = value; if (0 > tmp) { tmp *= -1; } buf[i--] = 0x0; for (;;) { buf[i--] = (tmp % 10) + '0'; tmp /= 10; if (0 >= tmp) break; } if (-1 != i) { buf[i--] = '-'; } return n; } static void initRng(void) { srand(gfxSystemTicks()); } static uint32_t randomInt(uint32_t max) { return rand() % max; } static void printStats(void) { char pps_str[12]; gFont font = gdispOpenFont("fixed_5x8"); uitoa(MINES_MINE_COUNT, pps_str, sizeof(pps_str)); gdispFillString(minesStatusIconWidth+8, gdispGetHeight()-11, " ", font, GFX_BLACK, GFX_BLACK); gdispDrawString(minesStatusIconWidth+8, gdispGetHeight()-11, pps_str, font, GFX_WHITE); uitoa(minesFlags, pps_str, sizeof(pps_str)); gdispFillString(8+(minesStatusIconWidth*2)+gdispGetStringWidth("99999", font), gdispGetHeight()-11, " ", font, GFX_BLACK, GFX_BLACK); gdispDrawString(8+(minesStatusIconWidth*2)+gdispGetStringWidth("99999", font), gdispGetHeight()-11, pps_str, font, GFX_WHITE); gdispCloseFont(font); } static void minesUpdateTime(void) { char pps_str[12]; if (minesTime > 9999) minesTime = 9999; gFont font = gdispOpenFont("digital_7__mono_20"); uitoa(minesTime, pps_str, sizeof(pps_str)); gdispFillArea((MINES_FIELD_WIDTH*MINES_CELL_WIDTH)-gdispGetStringWidth("9999", font), gdispGetHeight()-15, gdispGetWidth(), 15, GFX_BLACK); gdispDrawString((MINES_FIELD_WIDTH*MINES_CELL_WIDTH)-gdispGetStringWidth(pps_str, font), gdispGetHeight()-15, pps_str, font, GFX_LIME); gdispCloseFont(font); } static void minesTimeCounter(void* arg) { (void)arg; minesTime++; minesUpdateTime(); } static gBool inRange(int16_t x, int16_t y) { if ((x >= 0) && (x < MINES_FIELD_WIDTH) && (y >= 0) && (y < MINES_FIELD_HEIGHT)) return gTrue; else return gFalse; } static void showOne(int16_t x, int16_t y) { minesField[x][y].open = gTrue; if (minesField[x][y].flag) { minesField[x][y].flag = gFalse; minesFlags--; } gdispFillArea((x*MINES_CELL_WIDTH)+1, (y*MINES_CELL_HEIGHT)+1, MINES_CELL_WIDTH-1, MINES_CELL_HEIGHT-1, GFX_BLACK); if ((minesField[x][y].num > 0) && (minesField[x][y].num < 9)) { gdispImageOpenFile(&minesImage, minesGraph[minesField[x][y].num-1]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); minesEmptyNodes--; } else if (minesField[x][y].num == 9) { minesGameOver = gTrue; minesGameWinner = gFalse; gdispImageOpenFile(&minesImage, minesGraph[10]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); // Dirty HACK to not draw mine icon on GameOver event :D minesField[x][y].num = 0; } else if (minesField[x][y].num == 0) { gdispImageOpenFile(&minesImage, minesGraph[9]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); minesField[x][y].check = gTrue; minesEmptyNodes--; } } static void openEmptyNodes(void) { int16_t x, y, i, j; gBool needToCheck = gTrue; while (needToCheck) { needToCheck = gFalse; for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { if (minesField[x][y].check) { for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if ((i != 0) || (j != 0)) { // We don't need to check middle node as it is the one we are checking right now! :D if (inRange(x+i,y+j)) { if (!minesField[x+i][y+j].open) showOne(x+i,y+j); if (minesField[x+i][y+j].check) needToCheck = gTrue; } } } } minesField[x][y].check = gFalse; } } } } } static DECLARE_THREAD_FUNCTION(thdMines, msg) { (void)msg; uint16_t x,y, delay; gBool delayed = gFalse; while (!minesGameOver) { if (minesEmptyNodes == 0) { minesGameOver = gTrue; minesGameWinner = gTrue; } initRng(); ginputGetMouseStatus(0, &ev); delayed = gFalse; if (ev.buttons & GINPUT_MOUSE_BTN_LEFT) { x = ev.x/MINES_CELL_WIDTH; y = ev.y/MINES_CELL_WIDTH; delay = 0; while (ev.buttons & GINPUT_MOUSE_BTN_LEFT) { // Wait until release ginputGetMouseStatus(0, &ev); gfxSleepMilliseconds(1); delay++; if (delay >= MINES_FLAG_DELAY) { delay = MINES_FLAG_DELAY; if (!delayed && inRange(x, y) && !minesField[x][y].open) { if (minesField[x][y].flag) { gdispImageOpenFile(&minesImage, minesGraph[8]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH-1, MINES_CELL_HEIGHT-1, 0, 0); gdispImageClose(&minesImage); minesField[x][y].flag = gFalse; minesFlags--; printStats(); } else { gdispImageOpenFile(&minesImage, minesGraph[11]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); minesField[x][y].flag = gTrue; minesFlags++; printStats(); } delayed = gTrue; } } } // Check time, if longer than MINES_FLAG_DELAY then add flag... if (delay < MINES_FLAG_DELAY) { if ((x < MINES_FIELD_WIDTH) && (y < MINES_FIELD_HEIGHT) && !minesField[x][y].open && !minesField[x][y].flag) { showOne(x, y); openEmptyNodes(); printStats(); } } } } THREAD_RETURN(0); } static void printGameOver(void) { if (minesGameOver) { gFont font = gdispOpenFont("DejaVuSans16"); if (minesGameWinner) { gdispDrawString((gdispGetWidth()-gdispGetStringWidth("You LIVE!", font))/2, gdispGetHeight()-15, "You LIVE!", font, GFX_WHITE); } else { gdispDrawString((gdispGetWidth()-gdispGetStringWidth("You DIED!", font))/2, gdispGetHeight()-15, "You DIED!", font, GFX_WHITE); } gdispCloseFont(font); } else { gdispFillArea(0, gdispGetHeight()-25, gdispGetWidth(), 25, GFX_BLACK); } } static void initField(void) { int16_t x, y, mines, i, j; minesFlags = 0; minesGameOver = gFalse; printGameOver(); gFont font = gdispOpenFont("fixed_5x8"); gdispImageOpenFile(&minesImage, "plainmine.bmp"); // Saving status icons width/height for later use minesStatusIconWidth = minesImage.width; minesStatusIconHeight = minesImage.height; gdispImageDraw(&minesImage, 4, gdispGetHeight()-minesImage.height, minesImage.width, minesImage.height, 0, 0); gdispImageClose(&minesImage); gdispImageOpenFile(&minesImage, "plainflag.bmp"); gdispImageDraw(&minesImage, 4+minesImage.width+gdispGetStringWidth("99999", font), gdispGetHeight()-minesImage.height, minesImage.width, minesImage.height, 0, 0); gdispImageClose(&minesImage); gdispCloseFont(font); printStats(); initRng(); // Clearing/resetting field here... i = 0; for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { minesField[x][y].num = 0; minesField[x][y].open = gFalse; minesField[x][y].check = gFalse; minesField[x][y].flag = gFalse; minesField[x][y].fieldNum = i; i++; } } // Randomizing closed field drawing... for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { // Getting random node and swapping it with current i = randomInt(MINES_FIELD_WIDTH); j = randomInt(MINES_FIELD_HEIGHT); mines = minesField[x][y].fieldNum; minesField[x][y].fieldNum = minesField[i][j].fieldNum; minesField[i][j].fieldNum = mines; } } // Clearing nodes randomly if (!minesFirstGame) { for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { i = minesField[x][y].fieldNum/MINES_FIELD_HEIGHT; j = minesField[x][y].fieldNum-(i*MINES_FIELD_HEIGHT); gdispFillArea((i*MINES_CELL_WIDTH)+1, (j*MINES_CELL_HEIGHT)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, GFX_BLACK); gfxSleepMilliseconds(2); } } } else { minesFirstGame = gFalse; } // Drawing closed nodes randomly gdispImageOpenFile(&minesImage, minesGraph[8]); for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { i = minesField[x][y].fieldNum/MINES_FIELD_HEIGHT; j = minesField[x][y].fieldNum-(i*MINES_FIELD_HEIGHT); gdispImageDraw(&minesImage, (i*MINES_CELL_HEIGHT)+1, (j*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gfxSleepMilliseconds(2); } } gdispImageClose(&minesImage); minesEmptyNodes = MINES_FIELD_WIDTH*MINES_FIELD_HEIGHT; // Placing mines in random nodes :D mines = 0; while (mines != MINES_MINE_COUNT) { x = randomInt(MINES_FIELD_WIDTH); y = randomInt(MINES_FIELD_HEIGHT); if (minesField[x][y].num != 9) { mines++; minesEmptyNodes--; minesField[x][y].num = 9; } } // Calculating numbers for nearby mine nodes for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { if (minesField[x][y].num != 9) { for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if ((i != 0) || (j != 0)) { // We don't need to check middle node as we already know it is not a mine! :D if (inRange(x+i,y+j) && (minesField[x+i][y+j].num == 9)) { minesField[x][y].num++; } } } } } } } minesTime = 0; minesUpdateTime(); gtimerStart(&minesTimeCounterTimer, minesTimeCounter, 0, gTrue, 1000); } void minesStart(void) { int16_t x, y; #if MINES_SHOW_SPLASH gtimerStop(&minesSplashBlink); gdispClear(GFX_BLACK); #endif initField(); gfxThreadCreate(0, 1024, gThreadpriorityNormal, thdMines, 0); while (!minesGameOver) { gfxSleepMilliseconds(100); } printGameOver(); gtimerStop(&minesTimeCounterTimer); if (!minesGameWinner) { // Print generated mines for player to see gFont font = gdispOpenFont("fixed_10x20"); for (x = 0; x < MINES_FIELD_WIDTH; x++) { for (y = 0; y < MINES_FIELD_HEIGHT; y++) { if (minesField[x][y].num == 9 && !minesField[x][y].flag) { gdispImageOpenFile(&minesImage, minesGraph[12]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); } if (minesField[x][y].flag && (minesField[x][y].num != 9)) { gdispImageOpenFile(&minesImage, minesGraph[13]); gdispImageDraw(&minesImage, (x*MINES_CELL_HEIGHT)+1, (y*MINES_CELL_WIDTH)+1, MINES_CELL_WIDTH, MINES_CELL_HEIGHT, 0, 0); gdispImageClose(&minesImage); } } } gdispCloseFont(font); } } #if MINES_SHOW_SPLASH static void minesSplashBlinker(void* arg) { (void)arg; minesSplashTxtVisible = !minesSplashTxtVisible; if (minesSplashTxtVisible) { gdispImageOpenFile(&minesImage, "splashtxt.bmp"); } else { gdispImageOpenFile(&minesImage, "splashclr.bmp"); } gdispImageDraw(&minesImage, (gdispGetWidth()/2)-150+93, (gdispGetHeight()/2)-100+161, 112, 10, 0, 0); gdispImageClose(&minesImage); } void minesShowSplash(void) { gdispImageOpenFile(&minesImage, "splash.bmp"); gdispImageDraw(&minesImage, (gdispGetWidth()/2)-150, (gdispGetHeight()/2)-100, 300, 200, 0, 0); gdispImageClose(&minesImage); gtimerStart(&minesSplashBlink, minesSplashBlinker, 0, gTrue, 400); } #endif void minesInit(void) { initRng(); gdispClear(GFX_BLACK); }