#ifdef _WIN32 //For 32 and 64 bits /* pdcurses include */ #include #else /* Linux ncurses include */ #include #endif #include "hangman.h" //includes time.h #include //atexit #include #include //tolower #include #include "prng.h" #include //usleep #include "figures.h" int main(int argc, char **argv) { const char *short_options = "w:hf:ct"; struct option long_options[] = { {"word", required_argument, NULL, 'w'}, {"help", no_argument, NULL, 'h'}, {"file", required_argument, NULL, 'f'}, {"credits", no_argument, NULL, 'c'}, {"troll", no_argument, NULL, 't'} }; int c, startscr = 1; /* Show startscreen by default */ char filename[255]; filename[0] = '\0'; /* Initialization */ initscr(); atexit(quitProgram); game_state *gs; gs = malloc(sizeof(game_state)); initCoordinates(gs); gs->allowedMoves = DEFAULTTRIES; gs->moves = 0; gs->guesses = 0; gs->trollEnabled = 0; keypad(stdscr, FALSE); nonl(); //No new line. Prevents the new line when hitting enter curs_set(0); while ( (c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1 ) { switch (c) { case 'w': sprintf(gs->guessWord, "%s", optarg); startscr = 0; break; case 'h': showHelp(gs); exit(0); break; case 'f': /* Set filename */ sprintf(filename, "%s", optarg); startscr = 0; break; case 'c': break; case 't': /* troll option */ gs->trollEnabled = 1; break; default: mvprintw(gs->centery, gs->centerx - 9, "Invalid arguments."); getch(); showHelp(gs); exit(0); break; } } InitializePRNG(time(NULL)); /* Initialize random number generator */ if (startscr) { showStartScreen(gs); } /* start game */ initGuessWord(gs, filename); initAlphabet(gs); drawAlphabet(gs, 0); drawFigure(gs, 0); startGame(gs); nl(); //enable newline again while (getch() != 10); clear(); free(gs); return 0; } void startGame(game_state *gs) { noecho(); /* Don't show the player input */ time(&gs->startTime); while (checkWin(gs)) { updateScreen(gs); playerInput(gs); } } void quitProgram(void) { endwin(); } void showStartScreen(game_state *gs) { mvprintw(gs->centery - 1, gs->centerx - 15, "Welcome to hangman in ncurses."); mvprintw(gs->centery, gs->centerx - 5, "Have fun!"); mvprintw(gs->centery + 1, gs->centerx - 17, "https://gitlab.com/STRUCTiX/hangman"); refresh(); getch(); clear(); } void updateScreen(game_state *gs) { mvprintw(0, 1, "Remaining wrong guesses: %i", (gs->allowedMoves - gs->moves)); mvprintw(gs->maxy, 1, "Press ctrl + c to exit."); drawGuessWord(gs); mvprintw(1, 1, "Wrong characters: %s", gs->wrongCharacters); refresh(); } void initCoordinates(game_state *gs) { getmaxyx(stdscr, gs->maxy, gs->maxx); gs->maxy -= 1; gs->maxx -= 1; gs->centery = gs->maxy / 2; gs->centerx = gs->maxx / 2; } void initGuessWord(game_state *gs, char *filename) { int i; mvprintw(0, 1, "Please enter your word: "); if (strlen(gs->guessWord) == 0) { /* Word can be set by arguments */ if (strlen(filename) == 0) { /* Manual input */ curs_set(1); getnstr(gs->guessWord, MAXWORDLENGTH - 1); /* Reads the guessWord with a limit */ curs_set(0); } else { /* Random line from file */ readRandomLine(filename, gs->guessWord); } } /* Make String all lowercase */ toLowerCase(gs->guessWord); gs->wordLength = strlen(gs->guessWord); for (i = 0; i < gs->wordLength; i++) { if (gs->guessWord[i] == ' ') { //Check for spaces if it's a sentence gs->currentWord[i] = ' '; } else { gs->currentWord[i] = '_'; } } clear(); //clear the screen } void initAlphabet(game_state *gs) { int hasNumber = 0; int i; for (i = 0; i < MAXWORDLENGTH; i++) { if (isdigit(gs->guessWord[i]) > 0) { hasNumber = 1; break; } } gs->alphabet = malloc(sizeof(char) * hasNumber ? ALPHABET : ALPHABET_NUM); strcpy(gs->alphabet, "abcdefghijklmnopqrstuvwxyz"); if (hasNumber) { strcat(gs->alphabet, "1234567890"); } } void drawAlphabet(game_state *gs, char usedchar) { int start = centerDiff(gs->centerx, gs->alphabet); int i; if (usedchar != 0) { /* 0 is used for initialization */ for (i = 0; i < (int)strlen(gs->alphabet); i++) { if (gs->alphabet[i] == usedchar) { gs->alphabet[i] = '_'; break; } } } mvprintw(1, start, "%s", gs->alphabet); } void toLowerCase(char *str) { int length = strlen(str); int i; for (i = 0; i < length; i++) { str[i] = tolower(str[i]); } } void drawGuessWord(game_state *gs) { int startpos = gs->centerx - gs->wordLength; int i, wordpos = 0; int switchspace = 0; int xcounter = startpos; /* counter of the x position on screen */ int ycounter = 0; /* counter of the y position on sceen (row) */ int tempstartpos = startpos; /* the calculated start position to draw the characters */ int rows = 1; /* number of rows */ if (gs->wordLength * 2 > gs->maxx - LINEBREAK) { /* The word will be longer then the max. linesize with offset. */ rows = 2; while (1) { if ((gs->wordLength * 2) / rows < gs->maxx - LINEBREAK) { /* splitted the word into equal sizes and found the right amount of rows. */ break; } rows++; } gs->wordRows = rows; //set the number of rows for clearing the screen later tempstartpos = xcounter = gs->centerx - (gs->wordLength / rows); } for (i = startpos; i < startpos + (gs->wordLength * 2); i++) { if (xcounter >= gs->centerx + (gs->wordLength / rows)) { /* end of the current row. Next line and revert the x to start */ ycounter++; xcounter = tempstartpos; } if (switchspace) { /* this will place a space */ mvprintw(gs->centery + ycounter, xcounter, " "); switchspace = 0; } else { mvprintw(gs->centery + ycounter, xcounter, "%c", gs->currentWord[wordpos++]); switchspace = 1; } xcounter++; } refresh(); } int playerInput(game_state *gs) { int inp; int i, found = 0; inp = getch(); if (inp != KEY_UP && inp != KEY_DOWN && inp != KEY_LEFT && inp != KEY_RIGHT) { for (i = 0; i < gs->wordLength; i++) { if (inp == gs->guessWord[i]) { found++; //break; } } if (found) { /* found a valid character */ if (fillCurrentWord(gs, inp)) { //gs->moves++; gs->guesses++; trollHitScreen(gs, found); } } else { /* no valid character found */ if (stackWrongCharacter(gs, inp)) { gs->moves++; gs->guesses++; } } } return 0; } int fillCurrentWord(game_state *gs, char validchar) { int i, alreadyUsed = 0; for (i = 0; i < gs->wordLength; i++) { if (gs->currentWord[i] == validchar) { alreadyUsed = 1; break; } } if (!alreadyUsed) { for (i = 0; i < gs->wordLength; i++) { if (gs->guessWord[i] == validchar) { gs->currentWord[i] = validchar; } } drawAlphabet(gs, validchar); return 1; } else { return 0; } } int stackWrongCharacter(game_state *gs, char wrongchar) { int i, alreadyUsed = 0; for (i = 0; i < gs->wordLength; i++) { if (gs->wrongCharacters[i] == wrongchar) { alreadyUsed = 1; } } if (!alreadyUsed) { gs->wrongCharacters[strlen(gs->wrongCharacters)] = wrongchar; drawAlphabet(gs, wrongchar); drawFigure(gs, 1); return 1; } else { return 0; } } int checkWin(game_state *gs) { if (strcmp(gs->guessWord, gs->currentWord) != 0 && gs->moves < gs->allowedMoves) { /* next move */ return 1; } else { /* game end: decide if game is won or lost */ time(&gs->endTime); printGameStats(gs); return 0; } } void printGameStats(game_state *gs) { //clear(); int i, z; double diff; diff = difftime(gs->endTime, gs->startTime); if (!gs->trollEnabled) { for (z = 0; z <= gs->wordRows; z++) { //<= takes the last row too for (i = 0; i < gs->maxx; i++) { mvprintw(gs->centery + z, i, " "); //mvprintw(gs->centery + 1, i, " "); } } } else { for (z = 0; z <= gs->wordRows; z++) { animateLineClear(gs, gs->centery + z, -1); //-1 to speed up the animation } } if (gs->moves >= gs->allowedMoves) { mvprintw(gs->centery, gs->centerx - 10, "Game lost. Solution:"); mvprintw(gs->centery + 1, gs->centerx - (gs->wordLength / 2), gs->guessWord); } else { char message[100]; float result = 0; sprintf(message, "Game won! Total guesses: %i", gs->guesses); mvprintw(gs->centery, gs->centerx - (strlen(message) / 2), message); if (gs->moves != 0) { result = (float)((gs->guesses - gs->moves) / gs->moves); } else { result = (float)((gs->guesses - gs->moves) / 1); } sprintf(message, "Wrong guesses: %i, right/wrong ratio: %.2f, time: %.2fsec", gs->moves, result, diff); mvprintw(gs->centery + 1, gs->centerx - (strlen(message) / 2), message); mvprintw(gs->centery + 2, gs->centerx - (gs->wordLength / 2), gs->guessWord); } refresh(); } void showHelp(game_state *gs) { char *wordstring = "-w or --word: Enter the word or sentence as an argument"; char *helpstring = "-h or --help: Show this page"; mvprintw(gs->centery, centerDiff(gs->centerx, wordstring), wordstring); mvprintw(gs->centery + 1, centerDiff(gs->centerx, helpstring), helpstring); getch(); } int centerDiff(int coordinate, char *str) { int len = strlen(str); return coordinate - (len / 2); /* Integer division */ } void readRandomLine(char *file, char *result) { FILE *fp = fopen(file, "r"); int count = 0, wordlength = 0; char line[MAXWORDLENGTH]; srand(time(NULL)); while (fgets(line, MAXWORDLENGTH, fp) != NULL) { count++; if ((rand() / (float)RAND_MAX) <= (1.0 / count)) { strcpy(result, line); } } /* remove \n at the end of the line */ wordlength = strlen(result); result[wordlength - 1] = '\0'; fclose(fp); } void trollHitScreen(game_state *gs, int hits) { if (gs->trollEnabled) { char *strings[] = {"Double Hit", "Triple Hit", "Multi Hit", \ "Ultra Hit", "Monster Hit", "Rampage", \ "Unstoppable", "Wicked sick", "Godlike"}; int selection[] = {2, 3, 4, 5, 6, 7, 8, 9, 10}; int i, found = 0; for (i = 0; i < 9; i++) { if (selection[i] == hits) { found = 1; break; } } if (hits > 10) { found = 1; i = 8; } if (found) { flash(); /* flash the screen */ mvprintw(5, centerDiff(gs->centerx, strings[i]), strings[i]); refresh(); animateLineClear(gs, 5, i); } } } void drawFigure(game_state *gs, int drawNext) { int row = 6; int length = 8; int i; static int state = 0; if (!state) { for (i = 0; i < row; i++) { mvprintw(i, gs->maxx - length - 1, "%s", base_figure[i]); } state++; } else { /* Draw the stages */ if (drawNext) { mvprintw(stages[state - 1] + 1, gs->maxx - 4, "%s", figure[state - 1]); state++; } } } void animateLineClear(game_state *gs, int line, int offsetMultiplier) { /* sleep and vanish */ int j = 0, k = gs->maxx, usec; usec = (ANIM_DURATION * 1000000 + (100000 * offsetMultiplier)) / gs->maxx; for (j = 0; j <= gs->centerx; j++, k--) { mvprintw(line, j, "!"); mvprintw(line, k, "!"); refresh(); usleep(usec); mvprintw(line, j, " "); mvprintw(line, k, " "); } }