├── Action.cpp ├── Action.h ├── CONTRIBUTORS ├── Config.h ├── Field.cpp ├── Field.h ├── LICENSE ├── Makefile ├── Player.cpp ├── Player.h ├── README ├── Replay.cpp ├── Replay.h ├── Timer.cpp ├── Timer.h ├── common.cpp ├── common.h ├── main.cpp ├── scores.cpp ├── scores.h └── screenshot.png /Action.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Action.h" 4 | 5 | using namespace std; 6 | 7 | Action::Action() {} 8 | 9 | Action::Action(int px, int py, int pb, long pp) { 10 | x=px; 11 | y=py; 12 | button=pb; 13 | timeSinceStart=pp; 14 | } 15 | 16 | void Action::dump() { 17 | cout < 3 | #include 4 | #else 5 | #include 6 | #include 7 | #endif 8 | 9 | #include 10 | #include "Config.h" 11 | #include "common.h" 12 | #include "Field.h" 13 | #include "Timer.h" 14 | #include "Replay.h" 15 | #include "scores.h" 16 | 17 | using namespace std; 18 | 19 | extern void redisplay(); 20 | 21 | extern bool playReplay; 22 | extern int gameState; 23 | extern bool isFlagging; 24 | extern bool gamePaused; 25 | 26 | //extern unsigned short hitMineX, hitMineY; 27 | extern int squareSize; 28 | //extern char playerName[21]; 29 | extern char configDirectory[100]; 30 | 31 | 32 | Field::Field() { 33 | 34 | val3BV=0; 35 | 36 | } 37 | 38 | 39 | bool Field::replayFileNumberExists(long nr) { 40 | struct stat buffer; 41 | char rfname[100]; 42 | 43 | char tmp[110]; 44 | strcpy(tmp,configDirectory); 45 | sprintf(rfname,"%lu.replay",nr); 46 | 47 | strcat(tmp,rfname); 48 | 49 | if (stat(tmp, &buffer) != 0) { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | long Field::findLowestUnusedReplayNumber() { 57 | long lower=1; 58 | long upper=1; 59 | 60 | while (true) { 61 | if (!replayFileNumberExists(upper)) break; 62 | 63 | lower=upper; 64 | upper*=2; 65 | } 66 | 67 | long middle=0; 68 | 69 | // binary search 70 | while (lower0) { 126 | if (j>0) ffmProc(tmpField,i-1,j-1); 127 | ffmProc(tmpField,i-1,j); 128 | if (j0) ffmProc(tmpField,i,j-1); 133 | if (j0) ffmProc(tmpField,i+1,j-1); 137 | ffmProc(tmpField,i+1,j); 138 | if (j0) { 166 | if (j>0) tmpField[i-1][j-1]=1; 167 | tmpField[i-1][j]=1; 168 | if (j0) tmpField[i][j-1]=1; 172 | if (j0) tmpField[i+1][j-1]=1; 176 | tmpField[i+1][j]=1; 177 | if (jMAX_HEIGHT) 222 | height=MAX_HEIGHT; 223 | 224 | 225 | if (width<2) 226 | width=2; 227 | else if (width>MAX_WIDTH) 228 | width=MAX_WIDTH; 229 | 230 | if (mineCount>=height*width) 231 | mineCount=height*width-2; 232 | else if (mineCount<2) 233 | mineCount=2; 234 | 235 | } 236 | 237 | 238 | bool Field::isMine(int x, int y) { 239 | 240 | if (x<0 || x>=width) { 241 | cerr << "x=="<=height) { 245 | cerr << "y=="<scoreListLength; 386 | int oldFinalResultDisplay = ((Config*)glutGetWindowData())->player->field.oldFinalResultDisplay; 387 | 388 | Score *scores; 389 | 390 | int count=loadScores(fullpath,&scores); 391 | 392 | 393 | int *zero; 394 | zero=0; 395 | 396 | evalScore(newScore,scores, count, width, height, mineCount,oldFinalResultDisplay,scoreListLength); // XXX 397 | 398 | free(scores); 399 | 400 | // find the lowest unused replay file number 401 | 402 | long nr=1; 403 | 404 | nr=findLowestUnusedReplayNumber(); 405 | newScore.replayNumber=nr; 406 | appendScore(fullpath,newScore); 407 | 408 | char rfname[100]; 409 | 410 | char tmp[110]; 411 | strcpy(tmp,configDirectory); 412 | sprintf(rfname,"%lu.replay",nr); 413 | 414 | saveReplay(rfname,&newScore); 415 | 416 | saveReplay("last.replay",&newScore); 417 | } 418 | else { 419 | // cout << endl << "YOU HIT A MINE!" << endl; 420 | // cout << "You played " << (isFlagging?"":"non-") << "flagging."<player->replayHasScore) { 434 | 435 | showStatistics(won, config->player->score, playReplay); 436 | } 437 | } 438 | } 439 | 440 | void Field::revealAround(int squareX, int squareY) { 441 | 442 | // reveal squares around a square 443 | 444 | if (squareX>0) { 445 | if (squareY>0) revealSquare(squareX-1,squareY-1); 446 | revealSquare(squareX-1,squareY); 447 | if (squareY0) revealSquare(squareX,squareY-1); 452 | if (squareY0) revealSquare(squareX+1,squareY-1); 456 | revealSquare(squareX+1,squareY); 457 | if (squareY0) { 479 | if (squareY>0) adjacentMines+=mine[squareX-1][squareY-1]?1:0; 480 | adjacentMines+=mine[squareX-1][squareY]?1:0; 481 | if (squareY0) adjacentMines+=mine[squareX][squareY-1]?1:0; 486 | if (squareY0) adjacentMines+=mine[squareX+1][squareY-1]?1:0; 490 | adjacentMines+=mine[squareX+1][squareY]?1:0; 491 | if (squareY0) { 523 | if (squareY>0) flaggedAdjacentMines+=state[squareX-1][squareY-1]==10?1:0; 524 | flaggedAdjacentMines+=state[squareX-1][squareY]==10?1:0; 525 | if (squareY0) flaggedAdjacentMines+=state[squareX][squareY-1]==10?1:0; 530 | if (squareY0) flaggedAdjacentMines+=state[squareX+1][squareY-1]==10?1:0; 534 | flaggedAdjacentMines+=state[squareX+1][squareY]==10?1:0; 535 | if (squareY 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | #include "Timer.h" 9 | #include "Player.h" 10 | #include "Field.h" 11 | #include "Action.h" 12 | #include "scores.h" 13 | 14 | //extern char playerName[21]; 15 | extern int squareSize; 16 | extern int gameState; 17 | extern int originalWidth; 18 | extern int originalHeight; 19 | extern bool playReplay; 20 | extern bool gamePaused; 21 | 22 | int Player::loadReplay(const char *fname) { 23 | ifstream ifile; 24 | 25 | ifile.open(fname); 26 | if (!ifile.is_open()) { 27 | cerr<<"Error opening replay file '"<(ifile) ), (istreambuf_iterator() )) ; 31 | if (string::npos != content.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_.@- \x0d\x0a-:")) { 32 | cout << "Replay file contains invalid characters. Exiting."<> firstString; 61 | 62 | int fileVersion; 63 | 64 | if (firstString=="miny-replay-file-version:") { 65 | *ifile >> fileVersion; 66 | } 67 | else { 68 | fileVersion=-1; 69 | } 70 | 71 | 72 | replayHasScore=false; 73 | 74 | switch(fileVersion) { 75 | case -1: 76 | cout << "Unknown replay file format. Exiting." << endl; 77 | exit(1); 78 | case 1: { 79 | 80 | string tmpname; 81 | 82 | *ifile >> field.playerName; 83 | 84 | *ifile >> squareSize; 85 | 86 | *ifile >> field.width >> field.height; 87 | 88 | int mineCount=0; 89 | bool tmpmine; 90 | for (int j=0;j> tmpmine; 94 | if (tmpmine) { 95 | field.setMine(i,j); 96 | mineCount++; 97 | } 98 | } 99 | 100 | field.mineCount=mineCount; 101 | 102 | int count; 103 | *ifile>>count; 104 | Action *rp; 105 | 106 | for (int i=0;i> rp->timeSinceStart >> rp->x >> rp->y >> rp->button; 110 | 111 | if (i==0) 112 | rp->timeSinceStart=0; 113 | 114 | data.push_back(*rp); 115 | // cout << "rp" << endl; 116 | } 117 | 118 | break; 119 | } 120 | case 2: { 121 | 122 | string tmpname; 123 | 124 | replayHasScore=true; 125 | score.readFromFile(ifile); 126 | strcpy(field.playerName,score.name); 127 | squareSize=score.squareSize; 128 | field.width=score.width; 129 | field.height=score.height; 130 | 131 | 132 | int mineCount=0; 133 | bool tmpmine; 134 | for (int j=0;j> tmpmine; 138 | if (tmpmine) { 139 | field.setMine(i,j); 140 | mineCount++; 141 | } 142 | } 143 | 144 | field.mineCount=mineCount; 145 | 146 | int count; 147 | *ifile>>count; 148 | Action *rp; 149 | 150 | for (int i=0;i> rp->timeSinceStart >> rp->x >> rp->y >> rp->button; 154 | 155 | if (i==0) 156 | rp->timeSinceStart=0; 157 | 158 | data.push_back(*rp); 159 | } 160 | 161 | break; 162 | } 163 | 164 | 165 | default: 166 | cout << "Unknown replay file version. You are probably running an old version of the"<::iterator next; 176 | next=nextPlayed; 177 | next++; 178 | 179 | if (nextPlayed->timeSinceStart!=-1){ 180 | if (nextPlayed->timeSinceStart > field.timer.calculateElapsedTime()) { 181 | return true; 182 | } 183 | 184 | if ((*nextPlayed).button>=-1) { 185 | takeAction(nextPlayed->button, nextPlayed->x, nextPlayed->y); 186 | } 187 | else { 188 | takeAction((unsigned char)(-nextPlayed->button), nextPlayed->x, nextPlayed->y); 189 | } 190 | nextPlayed->timeSinceStart=-1; 191 | } 192 | 193 | if (next==data.end()) { 194 | if (playReplay) { 195 | cout<<"End of Replay."<=FIELD_X" and "y>=FIELD_Y", now 208 | // the top row and left column are 1 pixel narrower 209 | if (x>FIELD_X and xFIELD_Y and yoriginalWidth/2-12-DISPLAY_BORDER_WIDTH/2 and 215 | xBORDER_WIDTH and 217 | yFIELD_X and 224 | xFIELD_Y and 226 | y 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "Action.h" 11 | #include "Field.h" 12 | #include "scores.h" 13 | 14 | class Player { 15 | public: 16 | Field field; 17 | bool playbackOn; 18 | Score score; 19 | bool replayHasScore; 20 | int cursorX,cursorY; //player 21 | 22 | Player(); 23 | 24 | void handleInput(int, int, int); 25 | bool playStep(bool); //player 26 | int loadReplay(const char* fname); 27 | 28 | private: 29 | std::list data; 30 | std::list::iterator nextPlayed; 31 | 32 | void readFromFile(ifstream*); //player 33 | void takeAction(unsigned char, int, int); 34 | void takeAction(int, int, int); 35 | void refreshQueue(); 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Miny 2 | 3 | A minesweeper clone. 4 | 5 | 6 | FEATURES 7 | 8 | - Linux native 9 | - time, 3BV/s and IOE high scores, including percentiles and non-flagging 10 | versions 11 | - scores kept indefinitely for all played games 12 | - recording and playing replays 13 | - CSV export 14 | - timing in milliseconds 15 | - responsive on slow hardware 16 | - adjustable square size 17 | - to reveal squares around a numbered square with flagged adjacent mines you 18 | can click the square with any button 19 | - no "?" marks 20 | - pause 21 | 22 | 23 | TO DO 24 | 25 | - set window class to use with some window managers 26 | - prevent buffer overflows 27 | - estimate final statistics for a lost game, based on progress until losing 28 | 29 | 30 | COMPILING AND PLAYING 31 | 32 | To compile run 'make'. You'll need to have OpenGL libraries installed, on 33 | Ubuntu and Debian those should be covered by the package freeglut3-dev. 34 | 35 | To play run './miny [options]'. For best results run from terminal. 36 | 37 | While playing, you can see how many effective / ineffective clicks you have 38 | made. 39 | 40 | After every game the replay is saved in the .miny subdirectory of the user's 41 | home directory as 'last.replay'. Replays for won games are also saved as 42 | [number].replay. See below for how to play them. 43 | 44 | 45 | CURRENT GAME RESULTS TABLE 46 | 47 | After every won game there's a table showing how the current game ranks among 48 | all other results, and, if this game was non-flagging, among all other 49 | non-flagging results. That includes this game's place and percentile. 50 | 51 | Place is the position of this game among all other games (e.g. 213th). 52 | 53 | Percentile is a number between 0 and 100 saying how many percent of results 54 | were worse than this game. 100 means this is the best result of all, 50 55 | means this is the middle result etc. The higher this number is, the better. 56 | As of now this number is not exact but gives an approximate idea of how your 57 | game ranks. 58 | 59 | 60 | MOUSE AND KEYBOARD CONTROLS 61 | 62 | Apart from intuitive minesweeper mouse controls, these work: 63 | 64 | To start a new game press Space or click the yellow square or, after a game 65 | has ended, click anywhere outside the playfield. 66 | 67 | To pause/unpause while playing press P. 68 | 69 | To quit press Escape or Q. 70 | 71 | 72 | OPTIONS 73 | 74 | Options with a question mark take a value. 75 | 76 | Game setup: 77 | 78 | -n? player name. If this option is missing, system username is used. 79 | -d? difficulty - 0, 1, 2, 3 or 4 (1 - Beginner, 2 - Intermediate (default), 80 | 3 - Expert, 4 - Beginner Classic, 0 - all of the preceding four. 0 can 81 | be used when displaying scores) If -w, -h and -m are set, this option 82 | is ignored. 83 | -s? square size in pixels (3 to 100, default=35) 84 | -m? number of mines (min=2, max depends on field dimensions) 85 | -w? field width (2 to 100) 86 | -h? field height (2 to 100) 87 | 88 | 89 | Result display: 90 | 91 | -t list best times for the selected setup 92 | -3 list best 3BV/s for the selected setup 93 | -i list best IOEs for the selected setup 94 | -c list results as CSV, e.g. for further analysis in Excel/Calc. Scores are 95 | listed in the order they were achieved, i.e. ordered by date. The -l 96 | option is ignored in this case, all scores specified by other options 97 | are displayed. 98 | 99 | 100 | Result filter: 101 | 102 | -f? select results by flagging (0 - all (default), 1 - flagging only, 103 | 2 - non-flagging only) 104 | -g? select results by whether the game was won (0 - all, 1 - won only 105 | (default), 2 - lost only) (using this option may produce buggy results) 106 | -l? number of best results to display. (0 - all, default=20) 107 | 108 | 109 | Other: 110 | 111 | -p? play replay file. Value is by default either 'last' for the last played 112 | game or a number found in the Rep column of the high score table for 113 | other games. 114 | -C? config directory different to the default $HOME/.miny 115 | -o display the currently finished game result the more brief, old way 116 | 117 | 118 | NOTES 119 | 120 | Since version 0.5.0, the game stores scores in a new way, with more data 121 | about played games. There currently isn't and probably won't be a tool to 122 | convert old scores to the new system. If you don't want to keep them, you 123 | can delete *.hiscore (and whichever *.replay you don't want) files from your 124 | $HOME/.miny directory. 125 | 126 | 127 | EXAMPLES 128 | 129 | ./miny # start game on intermediate difficulty 130 | 131 | ./miny -d3 -njoe -s40 # start game on expert difficulty, player name joe, 132 | # square size 40 133 | 134 | ./miny -w10 -h10 -m10 # start game with the specified parameters 135 | 136 | ./miny -t # list best times for intermediate 137 | 138 | ./miny -d0 -3 # list best 3BV/s for beg, int, exp and beC 139 | combined 140 | 141 | ./miny -d3 -f2 -t # list best times for expert, non-flagging 142 | # only 143 | 144 | ./miny -d1 -3 # list best 3BV/s for beginner 145 | 146 | ./miny -w10 -h10 -m10 -t # list best times for games with the specified 147 | # parameters 148 | 149 | ./miny -d2 -c > scores.csv # export intermediate scores as CSV to scores.csv 150 | 151 | ./miny -p last # play replay for the last game played 152 | 153 | ./miny -p 4 # play the selected replay 154 | 155 | -------------------------------------------------------------------------------- /Replay.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | #include 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | #include "common.h" 9 | #include "Timer.h" 10 | #include "Replay.h" 11 | #include "Action.h" 12 | #include "Field.h" 13 | 14 | extern int gameState; 15 | //extern Timer timer; 16 | extern int squareSize; 17 | 18 | Replay::Replay() { 19 | recording=false; 20 | } 21 | 22 | void Replay::deleteData() { 23 | data.clear(); 24 | } 25 | 26 | void Replay::recordEvent(int x, int y, int button, long elapsedTime) { 27 | if (recording) { 28 | data.push_back(*(new Action(x,y,button,(gameState==GAME_INITIALIZED ? 0 : elapsedTime)))); 29 | // cout << "Elapsed time: " << elapsedTime << endl; 30 | } 31 | } 32 | 33 | void Replay::writeToFile(ofstream *file, void* fieldPtr, Score *score) { 34 | Field* field = (Field*)fieldPtr; 35 | 36 | *file << "miny-replay-file-version: 2" << endl; 37 | 38 | /* *file << field->playerName << endl << squareSize << endl; 39 | 40 | *file << field->width << " " << field->height << endl; 41 | */ 42 | 43 | score->writeToFile(file); 44 | 45 | for (int j=0;jheight;j++) { 46 | for (int i=0;iwidth;i++) 47 | *file << field->isMine(i,j) << " "; 48 | *file << endl; 49 | } 50 | 51 | *file << endl; 52 | *file << data.size() << endl; 53 | 54 | std::list::iterator iter; 55 | 56 | for (iter=data.begin(); iter!=data.end(); iter++) { 57 | *file << (*iter).timeSinceStart << " " << (*iter).x << " " << (*iter).y << " " << (*iter).button << endl; 58 | } 59 | } 60 | 61 | void Replay::dump() { 62 | cout << "Dumping replay data."<::iterator iter; 65 | 66 | for (iter=data.begin(); iter!=data.end(); iter++) { 67 | (*iter).dump(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Replay.h: -------------------------------------------------------------------------------- 1 | #ifndef REPLAY_H 2 | #define REPLAY_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "Action.h" 11 | #include "scores.h" 12 | 13 | 14 | using namespace std; 15 | 16 | class Replay { 17 | public: 18 | 19 | bool recording; 20 | Score score; 21 | 22 | std::list data; 23 | 24 | Replay(); 25 | 26 | void deleteData(); //recorder 27 | void recordEvent(int, int, int, long); //recorder 28 | void writeToFile(ofstream*, void*,Score*); //recorder 29 | void dump(); 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | #include "common.h" 3 | 4 | 5 | extern int gameState; 6 | extern bool gamePaused; 7 | 8 | 9 | 10 | long Timer::calculateTimeSinceStart() { 11 | 12 | if (gameState==GAME_INITIALIZED) 13 | return 0; 14 | else { 15 | 16 | long seconds, useconds; 17 | 18 | struct timeval now; 19 | 20 | gettimeofday(&now, NULL); 21 | 22 | seconds = now.tv_sec - timeStarted.tv_sec; 23 | useconds = now.tv_usec - timeStarted.tv_usec; 24 | 25 | return ((seconds) * 1000 + useconds/1000.0) + 0.5; 26 | } 27 | 28 | } 29 | 30 | 31 | long Timer::calculateTimePaused() { 32 | 33 | if (gameState==GAME_INITIALIZED) { 34 | return 0; 35 | } 36 | else if (gameState==GAME_PLAYING) { 37 | if (gamePaused) { 38 | return totalTimePaused+calculateTimeSinceStart()-pausedSince; 39 | } 40 | else { 41 | return totalTimePaused; 42 | } 43 | } 44 | else { 45 | return totalTimePaused; 46 | } 47 | 48 | 49 | } 50 | 51 | long Timer::calculateElapsedTime() { 52 | 53 | // calculates time from first click till now (when playing) or till game has ended, 54 | // minus the time when game was paused 55 | 56 | long seconds, useconds; 57 | long elapsedTime; 58 | 59 | 60 | 61 | if (gameState==GAME_PLAYING) { 62 | 63 | 64 | elapsedTime=calculateTimeSinceStart()-calculateTimePaused(); 65 | 66 | } 67 | else if (gameState==GAME_LOST or gameState==GAME_WON) { 68 | 69 | seconds = timeFinished.tv_sec - timeStarted.tv_sec; 70 | useconds = timeFinished.tv_usec - timeStarted.tv_usec; 71 | 72 | elapsedTime=((seconds) * 1000 + useconds/1000.0) + 0.5-totalTimePaused; 73 | 74 | } 75 | else 76 | elapsedTime=0; 77 | 78 | return elapsedTime; 79 | 80 | 81 | } 82 | 83 | void Timer::start() { 84 | 85 | gettimeofday(&timeStarted, NULL); 86 | } 87 | 88 | void Timer::stop() { 89 | gettimeofday(&timeFinished, NULL); 90 | } 91 | 92 | void Timer::reset() { 93 | totalTimePaused=0; 94 | } 95 | 96 | void Timer::pause() { 97 | pausedSince=calculateTimeSinceStart(); 98 | } 99 | 100 | void Timer::unpause() { 101 | totalTimePaused+=calculateTimeSinceStart()-pausedSince; 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | 4 | #include 5 | #include 6 | 7 | class Timer { 8 | public: 9 | long calculateElapsedTime(); 10 | 11 | void start(); 12 | 13 | void stop(); 14 | 15 | void reset(); 16 | 17 | void pause(); 18 | 19 | void unpause(); 20 | 21 | private: 22 | struct timeval timeStarted, timeFinished; 23 | 24 | long pausedSince; 25 | 26 | long totalTimePaused; 27 | 28 | long calculateTimePaused(); 29 | 30 | long calculateTimeSinceStart(); 31 | }; 32 | #endif 33 | -------------------------------------------------------------------------------- /common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | using namespace std; 4 | 5 | bool isValidName(char *n) { 6 | 7 | char *c=n; 8 | char *validChars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_.@-"; 9 | 10 | 11 | while(*c) { 12 | if (!strchr(validChars,*c)) { 13 | return false; 14 | } 15 | c++; 16 | } 17 | 18 | 19 | if (strlen(n)>MAX_NAME_LENGTH) { 20 | return false; 21 | } 22 | 23 | return true; 24 | 25 | 26 | } 27 | 28 | 29 | 30 | void ordinalNumberSuffix(char *suffix, int n) { 31 | 32 | int rem100=n%100; 33 | 34 | if (rem100==11 or rem100==12 or rem100==13) { 35 | strcpy(suffix,"th"); 36 | return; 37 | } 38 | 39 | switch(n%10) { 40 | case 1: strcpy(suffix,"st"); break; 41 | case 2: strcpy(suffix,"nd"); break; 42 | case 3: strcpy(suffix,"rd"); break; 43 | default: strcpy(suffix,"th"); break; 44 | } 45 | 46 | } 47 | 48 | 49 | unsigned int terminalWidth() { 50 | 51 | struct winsize w; 52 | ioctl(0, TIOCGWINSZ, &w); 53 | 54 | // printf ("lines %d\n", w.ws_row); 55 | // printf ("columns %d\n", w.ws_col); 56 | 57 | return w.ws_col; 58 | 59 | 60 | } 61 | 62 | 63 | bool outputLine(string l,int w) { 64 | 65 | // int tw=terminalWidth(); 66 | bool truncated=false; 67 | 68 | if (l.size()>w) { 69 | l.resize(w); 70 | truncated=true; 71 | } 72 | 73 | cout << l << endl; 74 | return truncated; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using namespace std; 13 | 14 | #define GAME_INITIALIZED -1 15 | #define GAME_PLAYING 0 16 | #define GAME_LOST 1 17 | #define GAME_WON 2 18 | 19 | #define MAX_WIDTH 100 20 | #define MAX_HEIGHT 100 21 | 22 | 23 | #define BORDER_WIDTH 10 24 | #define DISPLAY_BORDER_WIDTH 4 25 | #define FIELD_X 10 26 | #define FIELD_Y 48 27 | 28 | 29 | #define MAX_HS 20 30 | #define SCORE_FILE_VERSION 5 31 | 32 | #define MAX_NAME_LENGTH 20 33 | 34 | void ordinalNumberSuffix(char *, int); 35 | 36 | unsigned int terminalWidth(); 37 | 38 | bool outputLine(string,int); 39 | 40 | bool isValidName(char *); 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Miny 3 | * a minesweeper clone 4 | * (c) 2015-2019, 2021, 2023 spacecamper 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifdef __APPLE__ 24 | #include 25 | #include 26 | #else 27 | #include 28 | #include 29 | #endif 30 | 31 | #include "Config.h" 32 | #include "Replay.h" 33 | #include "Player.h" 34 | #include "common.h" 35 | 36 | #include "Timer.h" 37 | 38 | #include "scores.h" 39 | 40 | 41 | #define VERSION "0.6.0" 42 | 43 | 44 | 45 | 46 | // TODO prevent buffer overflows (strcpy and strcat) 47 | // TODO free allocated memory (after using scores from loadScores and filterScores is finished) 48 | 49 | using namespace std; 50 | 51 | 52 | int windowWidth, windowHeight, originalWidth, originalHeight; 53 | int squareSize; 54 | 55 | int gameState; // -1 - initialized, 0 - playing, 1 - lost, 2 - won 56 | char option_char; 57 | char configDirectory[100]; 58 | bool isFlagging; 59 | bool gamePaused; 60 | bool playReplay; 61 | bool boolDrawCursor; 62 | 63 | void redisplay() { 64 | glutPostRedisplay(); 65 | } 66 | 67 | 68 | bool directoryExists( const char* pzPath ) 69 | { 70 | if ( pzPath == NULL) return false; 71 | 72 | DIR *pDir; 73 | bool bExists = false; 74 | 75 | pDir = opendir (pzPath); 76 | 77 | if (pDir != NULL) 78 | { 79 | bExists = true; 80 | (void) closedir (pDir); 81 | } 82 | 83 | return bExists; 84 | } 85 | 86 | // ----------------------- GRAPHICS -------------------- // 87 | 88 | void drawRect(float x, float y, float w, float h) { 89 | glBegin(GL_TRIANGLES); 90 | 91 | glVertex2f(x,y); 92 | glVertex2f(x+w,y); 93 | glVertex2f(x,y+h); 94 | 95 | glVertex2f(x,y+h); 96 | glVertex2f(x+w,y+h); 97 | glVertex2f(x+w,y); 98 | 99 | glEnd(); 100 | } 101 | 102 | void drawDigitRect(int i, int x, int y, float zoom=1) { 103 | // Draws a segment of a number 104 | float u=2*zoom; 105 | 106 | switch(i) { 107 | case 0: drawRect(x,y,3*u,u); break; 108 | case 1: drawRect(x,y,u,3*u); break; 109 | case 2: drawRect(x+2*u,y,u,3*u); break; 110 | case 3: drawRect(x,y+2*u,3*u,u); break; 111 | case 4: drawRect(x,y+2*u,u,3*u); break; 112 | case 5: drawRect(x+2*u,y+2*u,u,3*u); break; 113 | case 6: drawRect(x,y+4*u,3*u,u); break; 114 | case 7: drawRect(x+u,y,u,3*u); break; 115 | case 8: drawRect(x+u,y+2*u,u,3*u); break; 116 | } 117 | } 118 | 119 | void drawDigit(int n, int x, int y, float zoom) { 120 | // draw a digit 10 px high, 6 px wide (if zoom==1) 121 | switch(n) { 122 | case 0: 123 | drawDigitRect(0,x,y,zoom); 124 | drawDigitRect(1,x,y,zoom); 125 | drawDigitRect(2,x,y,zoom); 126 | drawDigitRect(4,x,y,zoom); 127 | drawDigitRect(5,x,y,zoom); 128 | drawDigitRect(6,x,y,zoom); 129 | break; 130 | case 1: 131 | drawDigitRect(7,x,y,zoom); 132 | drawDigitRect(8,x,y,zoom); 133 | break; 134 | case 2: 135 | drawDigitRect(0,x,y,zoom); 136 | drawDigitRect(2,x,y,zoom); 137 | drawDigitRect(3,x,y,zoom); 138 | drawDigitRect(4,x,y,zoom); 139 | drawDigitRect(6,x,y,zoom); 140 | break; 141 | case 3: 142 | drawDigitRect(0,x,y,zoom); 143 | drawDigitRect(2,x,y,zoom); 144 | drawDigitRect(3,x,y,zoom); 145 | drawDigitRect(5,x,y,zoom); 146 | drawDigitRect(6,x,y,zoom); 147 | break; 148 | case 4: 149 | drawDigitRect(1,x,y,zoom); 150 | drawDigitRect(2,x,y,zoom); 151 | drawDigitRect(3,x,y,zoom); 152 | drawDigitRect(5,x,y,zoom); 153 | break; 154 | case 5: 155 | drawDigitRect(0,x,y,zoom); 156 | drawDigitRect(1,x,y,zoom); 157 | drawDigitRect(3,x,y,zoom); 158 | drawDigitRect(5,x,y,zoom); 159 | drawDigitRect(6,x,y,zoom); 160 | break; 161 | case 6: 162 | drawDigitRect(0,x,y,zoom); 163 | drawDigitRect(1,x,y,zoom); 164 | drawDigitRect(3,x,y,zoom); 165 | drawDigitRect(4,x,y,zoom); 166 | drawDigitRect(5,x,y,zoom); 167 | drawDigitRect(6,x,y,zoom); 168 | break; 169 | case 7: 170 | drawDigitRect(0,x,y,zoom); 171 | drawDigitRect(2,x,y,zoom); 172 | drawDigitRect(5,x,y,zoom); 173 | break; 174 | case 8: 175 | drawDigitRect(0,x,y,zoom); 176 | drawDigitRect(1,x,y,zoom); 177 | drawDigitRect(2,x,y,zoom); 178 | drawDigitRect(3,x,y,zoom); 179 | drawDigitRect(4,x,y,zoom); 180 | drawDigitRect(5,x,y,zoom); 181 | drawDigitRect(6,x,y,zoom); 182 | break; 183 | case 9: 184 | drawDigitRect(0,x,y,zoom); 185 | drawDigitRect(1,x,y,zoom); 186 | drawDigitRect(2,x,y,zoom); 187 | drawDigitRect(3,x,y,zoom); 188 | drawDigitRect(5,x,y,zoom); 189 | drawDigitRect(6,x,y,zoom); 190 | break; 191 | default: 192 | drawDigitRect(3,x,y,zoom); 193 | } 194 | } 195 | 196 | void drawFlag(int squareSize, int x, int y) { 197 | glColor3f(1,0,0); 198 | glBegin(GL_TRIANGLES); 199 | glVertex2f(x+.1*squareSize,y+.3*squareSize); 200 | glVertex2f(x+.6*squareSize,y+.1*squareSize); 201 | glVertex2f(x+.6*squareSize,y+.5*squareSize); 202 | glColor3f(0,0,0); 203 | glVertex2f(x+.6*squareSize,y+.6*squareSize); 204 | glVertex2f(x+.15*squareSize,y+.9*squareSize); 205 | glVertex2f(x+.9*squareSize,y+.9*squareSize); 206 | 207 | glEnd(); 208 | } 209 | 210 | void drawBackground(int fieldWidth, int fieldHeight) { 211 | // highlight boxes for in game statistics 212 | glColor3f(0,0,0); 213 | drawRect(BORDER_WIDTH, 214 | BORDER_WIDTH, 215 | 48+DISPLAY_BORDER_WIDTH, 216 | 24+DISPLAY_BORDER_WIDTH); 217 | glColor3f(0,0,0); 218 | drawRect(originalWidth-(BORDER_WIDTH+DISPLAY_BORDER_WIDTH+48), 219 | BORDER_WIDTH, 220 | 48+DISPLAY_BORDER_WIDTH, 221 | 24+DISPLAY_BORDER_WIDTH); 222 | // new game button 223 | glColor3f(1,1,0); 224 | drawRect(originalWidth/2-12-DISPLAY_BORDER_WIDTH/2, 225 | BORDER_WIDTH, 226 | 24+DISPLAY_BORDER_WIDTH, 227 | 24+DISPLAY_BORDER_WIDTH); 228 | 229 | // grid lines 230 | glColor3f(.3,.3,.3); 231 | glBegin(GL_LINES); 232 | for (int i=0;i=0 and field.state[x][y]<=8) { // revealed square 283 | glColor3f(.5,.5,.5); 284 | glBegin(GL_LINES); 285 | 286 | // top 287 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+y*squareSize); 288 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+y*squareSize); 289 | 290 | // left 291 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+y*squareSize); 292 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+(y+1)*squareSize-1); 293 | 294 | 295 | // bottom 296 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+(y+1)*squareSize-1); 297 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+(y+1)*squareSize-1); 298 | 299 | // right 300 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+y*squareSize); 301 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+(y+1)*squareSize-1); 302 | 303 | 304 | 305 | glEnd(); 306 | 307 | } 308 | else if (field.state[x][y]==9 or field.state[x][y]==10) { // unrevealed or flag 309 | 310 | glColor3f(.85,.85,.85); 311 | glBegin(GL_LINES); 312 | 313 | // top 314 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+y*squareSize); 315 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+y*squareSize); 316 | 317 | glVertex2f(FIELD_X+x*squareSize+1,FIELD_Y+y*squareSize+1); 318 | glVertex2f(FIELD_X+(x+1)*squareSize-2,FIELD_Y+y*squareSize+1); 319 | 320 | // left 321 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+y*squareSize); 322 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+(y+1)*squareSize-1); 323 | 324 | glVertex2f(FIELD_X+x*squareSize+1,FIELD_Y+y*squareSize+1); 325 | glVertex2f(FIELD_X+x*squareSize+1,FIELD_Y+(y+1)*squareSize-2); 326 | 327 | glEnd(); 328 | 329 | glColor3f(.55,.55,.55); 330 | glBegin(GL_LINES); 331 | 332 | // bottom 333 | glVertex2f(FIELD_X+x*squareSize,FIELD_Y+(y+1)*squareSize-1); 334 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+(y+1)*squareSize-1); 335 | 336 | glVertex2f(FIELD_X+x*squareSize+1,FIELD_Y+(y+1)*squareSize-2); 337 | glVertex2f(FIELD_X+(x+1)*squareSize-2,FIELD_Y+(y+1)*squareSize-2); 338 | 339 | // right 340 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+y*squareSize); 341 | glVertex2f(FIELD_X+(x+1)*squareSize-1,FIELD_Y+(y+1)*squareSize-1); 342 | 343 | glVertex2f(FIELD_X+(x+1)*squareSize-2,FIELD_Y+y*squareSize+1); 344 | glVertex2f(FIELD_X+(x+1)*squareSize-2,FIELD_Y+(y+1)*squareSize-2); 345 | 346 | 347 | glEnd(); 348 | } 349 | */ 350 | 351 | 352 | if (field.state[x][y]>=0 and field.state[x][y]<=8) { // revealed square 353 | 354 | 355 | 356 | switch(field.state[x][y]) { 357 | case 0: glColor3f(.5,.5,.5); break; 358 | case 1: glColor3f(0,0,1); break; 359 | case 2: glColor3f(0,1,0); break; 360 | case 3: glColor3f(1,0,0); break; 361 | case 4: glColor3f(0,0,.5); break; 362 | case 5: glColor3f(.5,0,0); break; 363 | case 6: glColor3f(0,1,1); break; 364 | case 7: glColor3f(0,0,0); break; 365 | case 8: glColor3f(.7,.7,.7); break; 366 | 367 | default: glColor3f(0,0,0); 368 | } 369 | if (field.state[x][y]!=0) { 370 | // number 371 | drawDigit(field.state[x][y],x1+.5*squareSize-3.0*zoom, 372 | y1+.5*squareSize-5.0*zoom,zoom); 373 | } 374 | //square background 375 | 376 | glColor3f(.5,.5,.5); 377 | 378 | drawRect(x1, y1, squareSize-1, squareSize-1); 379 | } 380 | if ((field.state[x][y]==9 or field.state[x][y]==11) 381 | and (gameState==GAME_LOST or gameState==GAME_WON) 382 | and field.isMine(x,y) 383 | ) { // unflagged mine after game 384 | 385 | drawMine(x, y, squareSize); 386 | 387 | } 388 | if(field.state[x][y]==11) { // Hit mine 389 | glColor3f(1,0,0); 390 | drawRect(x1, y1, squareSize-1, squareSize-1); 391 | } 392 | if (field.state[x][y]==10) { // flag 393 | // cross out flag where there is no mine 394 | if (gameState==GAME_LOST and !field.isMine(x,y)) { 395 | const short crossGap=.1*squareSize; 396 | 397 | glColor3f(0,0,0); 398 | 399 | glBegin(GL_LINES); 400 | 401 | glVertex2f(x1+crossGap,y1+crossGap); 402 | glVertex2f(x2-crossGap,y2-crossGap); 403 | 404 | glVertex2f(x2-crossGap,y1+crossGap); 405 | glVertex2f(x1+crossGap,y2-crossGap); 406 | 407 | glEnd(); 408 | } 409 | drawFlag(squareSize, x1, y1); 410 | } 411 | } 412 | } 413 | } 414 | 415 | void displayRemainingMines(int rem) { 416 | glColor3f(1,0,0); 417 | 418 | if (rem>999) rem=999; 419 | 420 | int dxy=BORDER_WIDTH+DISPLAY_BORDER_WIDTH; 421 | 422 | for (int i=0;i<3;i++) { 423 | const int digit=rem%10; 424 | rem/=10; 425 | drawDigit(digit,32-16*i+dxy,dxy,2); 426 | } 427 | } 428 | 429 | void displayElapsedTime(long etime) { 430 | glColor3f(1,0,0); 431 | 432 | if (etime>999) etime=999; 433 | 434 | const int dx=originalWidth-BORDER_WIDTH-16; 435 | const int dy=BORDER_WIDTH+DISPLAY_BORDER_WIDTH; 436 | 437 | for (int i=0;i<3;i++) { 438 | int digit=etime%10; 439 | etime/=10; 440 | drawDigit(digit,-16*i+dx,dy,2); 441 | } 442 | } 443 | 444 | void drawCursor(int x, int y) { 445 | glColor3f(1,1,0); 446 | glBegin(GL_TRIANGLES); 447 | 448 | glVertex2f(x,y); 449 | glVertex2f(x,y+20); 450 | glVertex2f(x+11,y+17); 451 | glEnd(); 452 | } 453 | 454 | void drawScene() { 455 | 456 | 457 | glClearColor(.7, .7, .7, 1); 458 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 459 | 460 | glOrtho(0.0, windowWidth, windowHeight, 0.0, -1.0, 10.0); 461 | glMatrixMode(GL_MODELVIEW); 462 | glLoadIdentity(); 463 | 464 | Config* config = (Config*)glutGetWindowData(); 465 | 466 | //cout<< config->player->field.timer.calculateElapsedTime() << endl; 467 | 468 | if(!config) { // if the field hasn't yet been configured 469 | return; 470 | } 471 | 472 | if (boolDrawCursor) { 473 | drawCursor(config->player->cursorX, config->player->cursorY); 474 | } 475 | 476 | displayRemainingMines(config->player->field.calculateRemainingMines()); 477 | 478 | displayElapsedTime(config->player->field.timer.calculateElapsedTime()/1000); 479 | 480 | if (gamePaused) { // hide field when game is paused 481 | 482 | glColor3f(.5,.5,.5); 483 | 484 | drawRect(FIELD_X + .5, FIELD_Y + .5, config->player->field.width*squareSize - 1, config->player->field.height*squareSize - 1); 485 | 486 | } 487 | else { 488 | drawField(config->player->field, squareSize); 489 | } 490 | 491 | drawBackground(config->player->field.width, config->player->field.height); 492 | 493 | glutSwapBuffers(); 494 | } 495 | 496 | // -------------------------- GLUT ----------------------- // 497 | 498 | void handleResize(int w, int h) { 499 | windowWidth=w; 500 | windowHeight=h; 501 | glViewport(0, 0, windowWidth, windowHeight); 502 | glMatrixMode(GL_PROJECTION); 503 | glLoadIdentity(); 504 | } 505 | 506 | void keyDown(unsigned char key, int x, int y) { 507 | 508 | Config* config = (Config*)glutGetWindowData(); 509 | 510 | config->player->handleInput(-((int)key), 0, 0); 511 | } 512 | 513 | void mouseClick(int button, int mState, int x, int y) { 514 | if (!gamePaused and mState==GLUT_DOWN) { 515 | Config* config = (Config*)glutGetWindowData(); 516 | config->player->handleInput(button, x, y); 517 | } 518 | } 519 | 520 | void mouseMove(int x, int y) { 521 | Config* const config = (Config*)glutGetWindowData(); 522 | config->player->handleInput(-1, x, y); 523 | } 524 | 525 | 526 | void update(int value) { 527 | glutPostRedisplay(); 528 | 529 | Player* player = ((Config*)glutGetWindowData())->player; 530 | 531 | if(!(player->playStep(false))){ 532 | playReplay=false; 533 | } 534 | 535 | // On my computer the first argument here being zero causes the game to register two events close to each other about every 16 ms instead of one event about every 8 ms. This might worsen the experience especially on monitors with higher refresh rate. 536 | glutTimerFunc(0, update, 0); 537 | } 538 | 539 | 540 | void initGraph(Config* config) { 541 | 542 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); 543 | windowWidth=FIELD_X+config->player->field.width*config->squareSize+BORDER_WIDTH; 544 | windowHeight=FIELD_Y+config->player->field.height*config->squareSize+BORDER_WIDTH; 545 | 546 | originalWidth=windowWidth; 547 | originalHeight=windowHeight; 548 | 549 | glutInitWindowSize(windowWidth, windowHeight); 550 | 551 | char title[100]; 552 | 553 | strcpy(title,"Miny v"); 554 | strcpy(title+6,VERSION); 555 | strcpy(title+6+strlen(VERSION),". Player: "); 556 | strcpy(title+16+strlen(VERSION),config->player->field.playerName); 557 | 558 | glutCreateWindow(title); 559 | 560 | glEnable(GL_DEPTH_TEST); 561 | 562 | glutSetWindowData((void*)config); 563 | glutDisplayFunc(drawScene); 564 | glutKeyboardFunc(keyDown); 565 | glutReshapeFunc(handleResize); 566 | 567 | if(!playReplay) { 568 | glutMouseFunc(mouseClick); 569 | glutPassiveMotionFunc(mouseMove); 570 | glutMotionFunc(mouseMove); 571 | } 572 | } 573 | 574 | void displayReplay(char replayFileName[100], Config* config) { 575 | if (config->player->loadReplay(replayFileName)) { 576 | exit(1); 577 | } 578 | config->squareSize=squareSize; 579 | 580 | config->player->field.init(); 581 | cout << "Playing replay..." << endl; 582 | initGraph(config); 583 | 584 | config->player->playStep(true); 585 | glutTimerFunc(0, update, 0); 586 | } 587 | 588 | void listScores(int listScoresType, int scoreListLength, int listFlagging, int listFinished, Config* config) { 589 | // TODO 'other' setups may produce too high 3BV/s etc and break layout 590 | 591 | char fullpath[110]; 592 | strcpy(fullpath,configDirectory); 593 | strcat(fullpath,"scores.dat"); 594 | 595 | Score *scores; 596 | int count=loadScores(fullpath,&scores); 597 | 598 | if (count==0) { // no scores in score file 599 | if (listScoresType!=4) 600 | cout<<"No high scores yet."<player->field.width==0 and config->player->field.height==0 and config->player->field.mineCount==0) { 641 | cout << "beginner, intermediate, expert, beginner classic"<player->field.width==9 and config->player->field.height==9 and config->player->field.mineCount==10) { 645 | cout << "beginner only"<player->field.width==16 and config->player->field.height==16 and config->player->field.mineCount==40) { 649 | cout << "intermediate only"<player->field.width==30 and config->player->field.height==16 and config->player->field.mineCount==99) { 653 | cout << "expert only"<player->field.width==8 and config->player->field.height==8 and config->player->field.mineCount==10) { 657 | cout << "beginner classic only"<player->field.width << "x" << config->player->field.height << ", " << config->player->field.mineCount << " mines" << endl; 665 | 666 | 667 | 668 | cout << setw(16)<player->field.playerName,"")) 676 | cout<<"all"<player->field.playerName<player->field.width, config->player->field.height, config->player->field.mineCount, squareSize,config->player->field.playerName); 698 | 699 | // cout<<"count="<player->field.checkValues(); 711 | 712 | initGraph(config); 713 | config->player->field.init(); 714 | 715 | glutTimerFunc(50, update, 0); 716 | } 717 | 718 | void configureSize(int difficulty, Field* field) { 719 | if (field->width!=0 and field->height!=0 and field->mineCount!=0) { // if these values were specified on the command line 720 | difficulty=-1; // prevent altering them in the switch 721 | } 722 | switch(difficulty) { 723 | case 0: 724 | field->height=0; 725 | field->width=0; 726 | field->mineCount=0; 727 | break; 728 | case 1: 729 | field->height=9; 730 | field->width=9; 731 | field->mineCount=10; 732 | break; 733 | case 2: 734 | field->height=16; 735 | field->width=16; 736 | field->mineCount=40; 737 | break; 738 | case 3: 739 | field->height=16; 740 | field->width=30; 741 | field->mineCount=99; 742 | break; 743 | case 4: 744 | field->height=8; 745 | field->width=8; 746 | field->mineCount=10; 747 | break; 748 | } 749 | } 750 | 751 | int main(int argc, char** argv) { 752 | srand (time(NULL)); 753 | 754 | glutInit(&argc, argv); 755 | 756 | Player player; 757 | 758 | player.field.height=0; 759 | player.field.width=0; 760 | player.field.mineCount=0; 761 | player.field.replay.recording=false; 762 | 763 | 764 | gameState=GAME_INITIALIZED; 765 | gamePaused=false; 766 | boolDrawCursor=false; 767 | 768 | char replayName[100]; 769 | char replayFileName[110]; 770 | int listScoresType=0; // 0 - none, 1 - time, 2 - 3bv/s, 3 - ioe, 4 - export as csv 771 | 772 | int difficulty=2; // 0-all of 1 to 4; 1-beg; 2-int; 3-exp; 4-beg classic 773 | int listFlagging=0; // 0-both, 1-flagging, 2-nf 774 | int listFinished=1; // 0-both, 1-finished, 2-unfinished 775 | int scoreListLength=MAX_HS; // how many scores to display 776 | 777 | bool defaultConfigDirectory=true; 778 | 779 | strcpy(configDirectory,getenv("HOME")); 780 | strcat(configDirectory,"/.miny/"); 781 | 782 | player.field.playerName[0]='\0'; 783 | 784 | strcpy(player.field.playerName,""); 785 | 786 | player.field.oldFinalResultDisplay=false; 787 | 788 | while ((option_char = getopt (argc, argv, "d:s:w:h:m:n:p:t3f:cg:il:C:o")) != -1) { 789 | switch (option_char) { 790 | case 'o': 791 | player.field.oldFinalResultDisplay=true; 792 | 793 | break; 794 | case 'd': 795 | difficulty=atoi(optarg); 796 | break; 797 | case 's': 798 | squareSize=atoi(optarg); 799 | break; 800 | case 'm': 801 | player.field.mineCount=atoi(optarg); 802 | break; 803 | case 'w': 804 | player.field.width=atoi(optarg); 805 | break; 806 | case 'h': 807 | player.field.height=atoi(optarg); 808 | break; 809 | case 'n': 810 | if (strlen(optarg)<20) 811 | strcpy(player.field.playerName,optarg); 812 | else 813 | strncpy(player.field.playerName,optarg,20); 814 | 815 | break; 816 | case 'p': 817 | strcpy(replayName,optarg); 818 | playReplay=true; 819 | boolDrawCursor=true; 820 | break; 821 | case 'l': 822 | scoreListLength=atoi(optarg); 823 | break; 824 | case '3': 825 | listScoresType=2; 826 | break; 827 | case 't': 828 | listScoresType=1; 829 | break; 830 | case 'i': 831 | listScoresType=3; 832 | break; 833 | 834 | case 'f': 835 | listFlagging=optarg[0]-'0'; 836 | break; 837 | case 'g': 838 | listFinished=optarg[0]-'0'; 839 | break; 840 | case 'c': 841 | listScoresType=4; 842 | break; 843 | case 'C': { 844 | int length=strlen(optarg); 845 | 846 | if (optarg[strlen(optarg)-1]!='/') 847 | length++; 848 | 849 | if (length>101) { 850 | cout<<"Config directory path must be shorter than 100 characters. Exiting."<20) { 935 | strncpy(player.field.playerName,getenv("USER"),20); 936 | player.field.playerName[21]='\0'; 937 | } 938 | else { 939 | strcpy(player.field.playerName,getenv("USER")); 940 | } 941 | else { 942 | strcpy(player.field.playerName,"unnamed"); 943 | } 944 | } 945 | 946 | /* if (config.squareSize==0) { 947 | config.squareSize=35; 948 | }*/ 949 | 950 | if (config.squareSize<3) { 951 | config.squareSize=3; 952 | } 953 | else if (config.squareSize>100) { 954 | config.squareSize=100; 955 | } 956 | 957 | squareSize=config.squareSize; 958 | 959 | beginGame(&config); 960 | } 961 | } 962 | 963 | if(listScoresType==0){ 964 | glutMainLoop(); 965 | } 966 | 967 | return 0; 968 | 969 | } 970 | -------------------------------------------------------------------------------- /scores.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "scores.h" 3 | 4 | Score::Score() { 5 | timeStamp=0; 6 | strcpy(name,"not-set-yet"); 7 | //strcpy(replayFile,"*"); 8 | replayNumber=0; 9 | width=0; 10 | height=0; 11 | mines=0; 12 | time=0; 13 | val3BV=0; 14 | flagging=false; 15 | effectiveClicks=0; 16 | ineffectiveClicks=0; 17 | squareSize=0; 18 | gameWon=false; 19 | 20 | 21 | } 22 | 23 | float Score::getIOE() { 24 | return (float)val3BV/(float)(effectiveClicks+ineffectiveClicks); 25 | } 26 | 27 | float Score::get3BVs() { 28 | //cout << "val3BV=" << val3BV <> timeStamp >> name >> replayNumber >> width >> height >> mines >> time >> val3BV >> flagging >> effectiveClicks >> ineffectiveClicks >> squareSize >> gameWon; 45 | 46 | // if (loadReplay) 47 | // cout << val3BV << endl; 48 | 49 | 50 | // cout << name << " "< (*(Score*)b).time ) return 1; 61 | 62 | return 0; 63 | 64 | } 65 | 66 | int compareBy3BVs(const void *a,const void *b) { 67 | 68 | float v1=(*(Score*)a).get3BVs(); 69 | float v2=(*(Score*)b).get3BVs(); 70 | 71 | /* v1=1000*(*(Score*)a).val3BV/(*(Score*)a).time; 72 | v2=1000*(*(Score*)b).val3BV/(*(Score*)b).time; 73 | */ 74 | if ( v1 < v2 ) return 1; 75 | if ( v1 > v2 ) return -1; 76 | 77 | return 0; 78 | 79 | } 80 | 81 | 82 | int compareByIOE(const void *a,const void *b) { 83 | 84 | float v1=1000*(*(Score*)a).getIOE(); 85 | float v2=1000*(*(Score*)b).getIOE(); 86 | 87 | if ( v1 < v2 ) return 1; 88 | if ( v1 > v2 ) return -1; 89 | 90 | return 0; 91 | 92 | } 93 | 94 | 95 | int filterScores(Score *scores, int count, Score **filteredScores, int fla, int fin, int w, int h, int m, int ss, char *pname) { 96 | 97 | *filteredScores=new Score[count]; // just allocate array of the same size for the filtered scores 98 | 99 | 100 | int counter=0; 101 | 102 | for (int i=0;imaxNameLen) 198 | maxNameLen=strlen(scores[i].name); 199 | 200 | int currentTimeLen=intLength(scores[i].time); 201 | if (currentTimeLen>maxTimeLen) 202 | maxTimeLen=currentTimeLen; 203 | 204 | int current3BVLen=intLength(scores[i].val3BV); 205 | if (current3BVLen>maxTimeLen) 206 | max3BVLen=current3BVLen; 207 | 208 | } 209 | 210 | maxTimeLen++; // for decimal point 211 | 212 | /* 213 | if (maxNameLen<4) maxNameLen=4; 214 | if (maxTimeLen<4) maxTimeLen=4; 215 | if (max3BVLen<3) max3BVLen=3; 216 | 217 | */ 218 | 219 | 220 | 221 | ostringstream headerLine; 222 | 223 | headerLine << setw(maxRankLen) << right << "" 224 | <<" "<tm_mday 265 | <<'-' 266 | <tm_mon+1 267 | <<'-' 268 | <tm_year+1900 269 | <<' ' 270 | <tm_hour 271 | <<':' 272 | <tm_min 273 | <<':' 274 | <tm_sec; 275 | 276 | dateString = stringStream.str(); 277 | } 278 | 279 | 280 | float val3BVs=scores[i].get3BVs();//(float)1000*scores[i].val3BV/scores[i].time; 281 | 282 | 283 | // name and time 284 | 285 | currentLine << setw(maxRankLen) << setfill(' ') << right << i+1 286 | << " " << setw(maxNameLen) << left << scores[i].name 287 | << " " << setw(maxTimeLen) << right << setprecision(3) << fixed 288 | << scores[i].time/1000.0; 289 | 290 | currentLine <tm_year+1900 365 | <<'-' 366 | <tm_mon+1 367 | <<'-' 368 | <tm_mday 369 | <<',' 370 | <tm_hour 371 | <<':' 372 | <tm_min 373 | <<':' 374 | <tm_sec; 375 | 376 | dateString = stringStream.str(); 377 | } 378 | 379 | 380 | 381 | float val3BVs=scores[i].get3BVs();//(float)1000*scores[i].val3BV/scores[i].time; 382 | 383 | // date and time of game 384 | 385 | cout << dateString << ','; 386 | 387 | 388 | // difficulty (in numbers and string) 389 | 390 | cout << scores[i].width << ',' << scores[i].height << ',' << scores[i].mines << ','; 391 | 392 | if (scores[i].width==8 and scores[i].height==8 and scores[i].mines==10) 393 | cout << "beC"; 394 | else if (scores[i].width==9 and scores[i].height==9 and scores[i].mines==10) 395 | cout << "beg"; 396 | else if (scores[i].width==16 and scores[i].height==16 and scores[i].mines==40) 397 | cout << "int"; 398 | else if (scores[i].width==30 and scores[i].height==16 and scores[i].mines==99) 399 | cout << "exp"; 400 | else 401 | cout << "oth"; 402 | 403 | cout << ','; 404 | 405 | 406 | 407 | // won game 408 | 409 | cout << (scores[i].gameWon?"1":"0") << ','; 410 | 411 | // time taken 412 | 413 | cout << scores[i].time << ','; 414 | 415 | 416 | // 3BV/s 417 | if (val3BVs==0) 418 | cout <<".,"; 419 | else 420 | cout<> version; 473 | 474 | Score tmps; 475 | int count=0; 476 | 477 | switch(version) { 478 | case SCORE_FILE_VERSION: 479 | { 480 | string content((istreambuf_iterator(inFile) ), (istreambuf_iterator() )) ; 481 | if (string::npos != content.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_.@- \x0d\x0a")) { 482 | cout << "Score file contains invalid characters. Exiting."<> version; 490 | while (!inFile.eof()) { 491 | tmps.readFromFile(&inFile); 492 | // inFile.read((char *) &tmphs, sizeof(Score)); 493 | // cout << "hs "<> version; 510 | 511 | *scores=new Score[count]; 512 | 513 | 514 | for (int i=0;i> version; 546 | if (version!=SCORE_FILE_VERSION) { 547 | cerr << "Error saving score: unsupported score file version."< 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | 18 | using namespace std; 19 | 20 | 21 | class Score { 22 | public: 23 | 24 | time_t timeStamp; 25 | char name[21]; 26 | unsigned int replayNumber; 27 | 28 | unsigned short width,height; 29 | unsigned short mines; 30 | unsigned int time; 31 | unsigned short val3BV; 32 | bool flagging; 33 | unsigned int effectiveClicks; 34 | unsigned int ineffectiveClicks; 35 | unsigned short squareSize; 36 | bool gameWon; 37 | 38 | Score(); 39 | 40 | 41 | float getIOE(); 42 | 43 | float get3BVs(); 44 | 45 | void readFromFile(ifstream *f); 46 | 47 | void writeToFile(ofstream *f); 48 | }; 49 | 50 | // XXX 51 | 52 | int compareByTime(const void *a,const void *b); 53 | 54 | int compareBy3BVs(const void *a,const void *b); 55 | 56 | int compareByIOE(const void *a,const void *b); 57 | 58 | int filterScores(Score *scores, int count,Score **filteredScores,int fla, int fin, int w, int h, int m, int ss, char *pname); 59 | 60 | void displayScores(Score *scores, int count,int limit,bool csv=false); 61 | 62 | int loadScores(char *fname, Score **scores); 63 | 64 | void appendScore(char *fname, Score score); 65 | 66 | bool evalScore2(ostringstream *scoreString, Score s, Score *scoresAll,int countAll,char *stringValueName,int (*compareFunc)(const void *,const void *),int scoreListLength,int *returnCountNF); 67 | 68 | void evalScore(Score s, Score *scores, int count, int w, int h, int m, bool oldView,int scoreListLength); 69 | 70 | 71 | 72 | 73 | //bool evalScore2(ostringstream *scoreString, Score s, Score *scoresAll,int countAll,char *stringValueName,int (*compareFunc)(const void *,const void *),int scoreListLength,int *returnCountNF ) ; 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacecamper/miny/663d8074b28fa670fc61fa5918a972a1639b0ad9/screenshot.png --------------------------------------------------------------------------------