/* pyrdle.c - Pure C90 implementation of the Wordle-like game */ /* Compile with: gcc -std=c90 -o pyrdle pyrdle.c -lncurses */ #include #include #include #include #include #include #define MAX_WORDS 10000 #define WORD_LENGTH 5 #define MAX_GUESSES 6 #define MAX_FILENAME 256 #define MAX_MESSAGE 256 /* Color pair constants */ #define COLOR_DEFAULT 1 #define COLOR_CORRECT 2 /* Green - correct position */ #define COLOR_PRESENT 3 /* Yellow - wrong position */ #define COLOR_ABSENT 4 /* Red - not in word */ #define COLOR_WWHITE 7 /* White text */ #define COLOR_UNUSED 6 /* Gray - unused letters */ /* Letter status constants */ #define STATUS_UNUSED 0 #define STATUS_ABSENT 1 #define STATUS_PRESENT 2 #define STATUS_CORRECT 3 /* Keyboard layout */ static const char* keyboard_rows[3] = { "QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM" }; /* Game state structure */ typedef struct { char words[MAX_WORDS][WORD_LENGTH + 1]; int word_count; char target_word[WORD_LENGTH + 1]; char guesses[MAX_GUESSES][WORD_LENGTH + 1]; int guess_colors[MAX_GUESSES][WORD_LENGTH]; int guess_count; char current_guess[WORD_LENGTH + 1]; int current_guess_length; int game_over; int won; int letter_status[26]; /* A-Z status */ } GameState; /* Function declarations */ int load_words(GameState* game, const char* filename); int is_valid_word(GameState* game, const char* word); void check_guess(GameState* game, const char* guess, int* colors); int make_guess(GameState* game, const char* guess); void draw_title(WINDOW* win, int y, const char* difficulty); void draw_board(WINDOW* win, GameState* game, int y); void draw_keyboard(WINDOW* win, GameState* game, int y); void draw_message(WINDOW* win, const char* message, int y, int color_pair); void draw_instructions(WINDOW* win, int y); void parse_arguments(int argc, char* argv[], char* filename, char* difficulty); void init_game(GameState* game); void to_upper(char* str); int main_game_loop(int argc, char* argv[]); /* Initialize game state */ void init_game(GameState* game) { int i, j; game->word_count = 0; game->target_word[0] = '\0'; game->guess_count = 0; game->current_guess[0] = '\0'; game->current_guess_length = 0; game->game_over = 0; game->won = 0; /* Initialize letter status to unused */ for (i = 0; i < 26; i++) { game->letter_status[i] = STATUS_UNUSED; } /* Clear guesses */ for (i = 0; i < MAX_GUESSES; i++) { game->guesses[i][0] = '\0'; for (j = 0; j < WORD_LENGTH; j++) { game->guess_colors[i][j] = COLOR_DEFAULT; } } } /* Convert string to uppercase */ void to_upper(char* str) { int i; for (i = 0; str[i]; i++) { str[i] = toupper(str[i]); } } /* Load words from file */ int load_words(GameState* game, const char* filename) { FILE* file; char filepath[MAX_FILENAME]; char line[32]; char word[WORD_LENGTH + 1]; int len; /* Construct file path */ sprintf(filepath, "wordlists/%s", filename); file = fopen(filepath, "r"); if (!file) { /* Try without wordlists/ prefix */ file = fopen(filename, "r"); if (!file) { return 0; } } game->word_count = 0; while (fgets(line, sizeof(line), file) && game->word_count < MAX_WORDS) { /* Remove newline and whitespace */ len = strlen(line); while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r' || line[len-1] == ' ')) { line[--len] = '\0'; } if (len == WORD_LENGTH) { strcpy(word, line); to_upper(word); strcpy(game->words[game->word_count], word); game->word_count++; } } fclose(file); if (game->word_count == 0) { return 0; } /* Select random target word */ srand((unsigned int)time(NULL)); strcpy(game->target_word, game->words[rand() % game->word_count]); return 1; } /* Check if word exists in word list */ int is_valid_word(GameState* game, const char* word) { int i; char upper_word[WORD_LENGTH + 1]; strcpy(upper_word, word); to_upper(upper_word); for (i = 0; i < game->word_count; i++) { if (strcmp(game->words[i], upper_word) == 0) { return 1; } } return 0; } /* Check guess against target word */ void check_guess(GameState* game, const char* guess, int* colors) { char target_copy[WORD_LENGTH + 1]; char guess_copy[WORD_LENGTH + 1]; int i, j; int target_used[WORD_LENGTH] = {0}; strcpy(target_copy, game->target_word); strcpy(guess_copy, guess); to_upper(guess_copy); /* Initialize all as absent */ for (i = 0; i < WORD_LENGTH; i++) { colors[i] = COLOR_ABSENT; } /* First pass: mark correct positions */ for (i = 0; i < WORD_LENGTH; i++) { if (guess_copy[i] == target_copy[i]) { colors[i] = COLOR_CORRECT; target_used[i] = 1; game->letter_status[guess_copy[i] - 'A'] = STATUS_CORRECT; } } /* Second pass: mark present but wrong position */ for (i = 0; i < WORD_LENGTH; i++) { if (colors[i] == COLOR_ABSENT) { for (j = 0; j < WORD_LENGTH; j++) { if (!target_used[j] && guess_copy[i] == target_copy[j]) { colors[i] = COLOR_PRESENT; target_used[j] = 1; if (game->letter_status[guess_copy[i] - 'A'] != STATUS_CORRECT) { game->letter_status[guess_copy[i] - 'A'] = STATUS_PRESENT; } break; } } /* If still absent, mark letter as not in word */ if (colors[i] == COLOR_ABSENT) { if (game->letter_status[guess_copy[i] - 'A'] == STATUS_UNUSED) { game->letter_status[guess_copy[i] - 'A'] = STATUS_ABSENT; } } } } } /* Process a guess */ int make_guess(GameState* game, const char* guess) { /* int i; */ char upper_guess[WORD_LENGTH + 1]; if (strlen(guess) != WORD_LENGTH) { return 0; } if (!is_valid_word(game, guess)) { return 0; } strcpy(upper_guess, guess); to_upper(upper_guess); strcpy(game->guesses[game->guess_count], upper_guess); check_guess(game, guess, game->guess_colors[game->guess_count]); game->guess_count++; if (strcmp(upper_guess, game->target_word) == 0) { game->game_over = 1; game->won = 1; } else if (game->guess_count >= MAX_GUESSES) { game->game_over = 1; game->won = 0; } return 1; } /* Draw game title */ void draw_title(WINDOW* win, int y, const char* difficulty) { char title[256]; int height, width, x; sprintf(title, "CORDLE - The C90 Wordle Game [%s]", difficulty); getmaxyx(win, height, width); x = (width - strlen(title)) / 2; wattron(win, A_BOLD); mvwaddstr(win, y, x, title); wattroff(win, A_BOLD); } /* Draw the game board */ void draw_board(WINDOW* win, GameState* game, int y) { int height, width, board_x; int row, col, x_pos, y_pos; char cell[4]; getmaxyx(win, height, width); board_x = (width - (WORD_LENGTH * 4 - 1)) / 2; for (row = 0; row < MAX_GUESSES; row++) { y_pos = y + row * 2; if (row < game->guess_count) { /* Draw completed guess */ for (col = 0; col < WORD_LENGTH; col++) { x_pos = board_x + col * 4; sprintf(cell, "[%c]", game->guesses[row][col]); wattron(win, COLOR_PAIR(game->guess_colors[row][col])); mvwaddstr(win, y_pos, x_pos, cell); wattroff(win, COLOR_PAIR(game->guess_colors[row][col])); } } else if (row == game->guess_count && !game->game_over) { /* Draw current guess */ for (col = 0; col < WORD_LENGTH; col++) { x_pos = board_x + col * 4; if (col < game->current_guess_length) { sprintf(cell, "[%c]", game->current_guess[col]); } else { strcpy(cell, "[ ]"); } wattron(win, COLOR_PAIR(COLOR_WWHITE)); mvwaddstr(win, y_pos, x_pos, cell); wattroff(win, COLOR_PAIR(COLOR_WWHITE)); } } else { /* Draw empty slots */ for (col = 0; col < WORD_LENGTH; col++) { x_pos = board_x + col * 4; mvwaddstr(win, y_pos, x_pos, "[ ]"); } } } } /* Draw visual keyboard */ void draw_keyboard(WINDOW* win, GameState* game, int y) { int height, width; int row_idx, x, y_pos, i; const char* row; char letter; int status, color; getmaxyx(win, height, width); for (row_idx = 0; row_idx < 3; row_idx++) { row = keyboard_rows[row_idx]; x = (width - strlen(row) * 2 + 1) / 2; y_pos = y + row_idx; /* Add indentation for keyboard layout */ if (row_idx == 1) x += 1; else if (row_idx == 2) x += 3; for (i = 0; row[i]; i++) { letter = row[i]; status = game->letter_status[letter - 'A']; switch (status) { case STATUS_CORRECT: color = COLOR_CORRECT; break; case STATUS_PRESENT: color = COLOR_PRESENT; break; case STATUS_ABSENT: color = COLOR_ABSENT; break; default: color = COLOR_UNUSED; break; } if (color == COLOR_UNUSED) { wattron(win, A_DIM); mvwaddch(win, y_pos, x, letter); wattroff(win, A_DIM); } else { wattron(win, COLOR_PAIR(color)); mvwaddch(win, y_pos, x, letter); wattroff(win, COLOR_PAIR(color)); } x += 2; } } } /* Draw centered message */ void draw_message(WINDOW* win, const char* message, int y, int color_pair) { int height, width, x; getmaxyx(win, height, width); x = (width - strlen(message)) / 2; wattron(win, COLOR_PAIR(color_pair)); mvwaddstr(win, y, x, message); wattroff(win, COLOR_PAIR(color_pair)); } /* Draw game instructions */ void draw_instructions(WINDOW* win, int y) { const char* instructions[] = { "Guess the 5-letter word in 6 tries!", "", "Colors: GREEN=Correct, YELLOW=Wrong position, RED=Not in word", "", "Type letters: A-Z", "Enter: Submit guess", "Backspace: Delete letter", "Ctrl+C or ESC: Quit game" }; int num_instructions = 8; int height, width, x, i; getmaxyx(win, height, width); for (i = 0; i < num_instructions; i++) { if (y + i < height - 1) { x = (width - strlen(instructions[i])) / 2; mvwaddstr(win, y + i, x, instructions[i]); } } } /* Parse command line arguments */ void parse_arguments(const int argc, char* argv[], char* filename, char* difficulty) { int i; /* Default values */ strcpy(filename, "pyrdle_words_easy.txt"); strcpy(difficulty, "EASY"); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--easy") == 0) { strcpy(filename, "pyrdle_words_easy.txt"); strcpy(difficulty, "EASY"); } else if (strcmp(argv[i], "--medium") == 0) { strcpy(filename, "pyrdle_words_medium.txt"); strcpy(difficulty, "MEDIUM"); } else if (strcmp(argv[i], "--hard") == 0) { strcpy(filename, "pyrdle_words_hard.txt"); strcpy(difficulty, "HARD"); } else if (strcmp(argv[i], "--techy") == 0) { strcpy(filename, "pyrdle_words_techy.txt"); strcpy(difficulty, "TECHNICAL"); } else if (strcmp(argv[i], "--literary") == 0) { strcpy(filename, "pyrdle_words_literary.txt"); strcpy(difficulty, "LITERARY"); } else if (strcmp(argv[i], "--cultural") == 0) { strcpy(filename, "pyrdle_words_cultural.txt"); strcpy(difficulty, "CULTURAL"); } else if (strcmp(argv[i], "--full") == 0) { strcpy(filename, "pyrdle_words_full.txt"); strcpy(difficulty, "FULL"); } else if (strcmp(argv[i], "--wordlist") == 0 && i + 1 < argc) { strcpy(filename, argv[i + 1]); strcpy(difficulty, "CUSTOM"); i++; /* Skip next argument */ } } } /* Main game loop */ int main_game_loop(int argc, char* argv[]) { WINDOW* stdscr; GameState game; char filename[MAX_FILENAME]; char difficulty[32]; char message[MAX_MESSAGE] = ""; int message_color = COLOR_DEFAULT; int height, width, y_pos; int key; char temp_guess[WORD_LENGTH + 1]; char win_message[MAX_MESSAGE]; /* Get command line arguments */ parse_arguments(argc, argv, filename, difficulty); /* Initialize ncurses */ stdscr = initscr(); curs_set(0); /* Hide cursor */ clear(); /* Initialize colors */ start_color(); init_pair(COLOR_DEFAULT, COLOR_WWHITE, COLOR_BLACK); init_pair(COLOR_CORRECT, COLOR_BLACK, COLOR_GREEN); init_pair(COLOR_PRESENT, COLOR_BLACK, COLOR_YELLOW); init_pair(COLOR_ABSENT, COLOR_WWHITE, COLOR_RED); init_pair(COLOR_WWHITE, COLOR_WWHITE, COLOR_BLACK); init_pair(COLOR_UNUSED, COLOR_WWHITE, COLOR_BLACK); /* Initialize game */ init_game(&game); if (!load_words(&game, filename)) { /* Try fallback */ if (strcmp(filename, "pyrdle_words.txt") != 0 && load_words(&game, "pyrdle_words.txt")) { strcpy(difficulty, "DEFAULT"); } else { mvaddstr(0, 0, "Error: Could not load word list"); mvaddstr(1, 0, "Press any key to exit..."); getch(); endwin(); return 1; } } /* Main game loop */ while (1) { clear(); getmaxyx(stdscr, height, width); y_pos = 1; /* Draw components */ draw_title(stdscr, y_pos, difficulty); y_pos += 2; draw_board(stdscr, &game, y_pos); y_pos += MAX_GUESSES * 2 + 1; draw_keyboard(stdscr, &game, y_pos); y_pos += 4; /* Draw message if exists */ if (strlen(message) > 0) { draw_message(stdscr, message, y_pos, message_color); y_pos += 2; } /* Draw instructions */ if (y_pos + 6 < height) { draw_instructions(stdscr, y_pos); } /* Handle game over */ if (game.game_over) { y_pos = (height > 20) ? height - 3 : y_pos; if (game.won) { sprintf(win_message, "Congratulations! You won in %d guesses!", game.guess_count); draw_message(stdscr, win_message, y_pos, COLOR_CORRECT); } else { sprintf(win_message, "Game Over! The word was: %s", game.target_word); draw_message(stdscr, win_message, y_pos, COLOR_ABSENT); } draw_message(stdscr, "Press 'N' for new game or 'Q' to quit", y_pos + 1, COLOR_DEFAULT); refresh(); key = getch(); if (key == 'n' || key == 'N') { /* Start new game */ init_game(&game); load_words(&game, filename); strcpy(message, ""); continue; } else if (key == 'q' || key == 'Q' || key == 27) { break; } continue; } refresh(); /* Get input */ key = getch(); /* Handle input */ if (key == 27) { /* ESC */ break; } else if (key == '\n' || key == '\r' || key == KEY_ENTER) { if (game.current_guess_length == WORD_LENGTH) { strcpy(temp_guess, game.current_guess); if (make_guess(&game, temp_guess)) { game.current_guess[0] = '\0'; game.current_guess_length = 0; strcpy(message, ""); } else { strcpy(message, "Not in word list!"); message_color = COLOR_ABSENT; } } else { sprintf(message, "Word must be %d letters!", WORD_LENGTH); message_color = COLOR_ABSENT; } } else if (key == KEY_BACKSPACE || key == 127 || key == '\b') { if (game.current_guess_length > 0) { game.current_guess_length--; game.current_guess[game.current_guess_length] = '\0'; strcpy(message, ""); } } else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) { if (game.current_guess_length < WORD_LENGTH) { game.current_guess[game.current_guess_length] = toupper(key); game.current_guess_length++; game.current_guess[game.current_guess_length] = '\0'; strcpy(message, ""); } } } endwin(); return 0; } /* Global variables for argc/argv (C90 limitation) */ static int __argc; static char** __argv; int main(int argc, char* argv[]) { int i; /* Store argc/argv globally for use in game loop */ __argc = argc; __argv = argv; /* Handle help */ for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { printf("pyrdle - C90 Wordle Game\n\n"); printf("Usage: %s [OPTIONS]\n\n", argv[0]); printf("Difficulty Levels:\n"); printf(" --easy Common everyday words (default)\n"); printf(" --medium Standard vocabulary\n"); printf(" --hard Challenging words\n\n"); printf("Word Categories:\n"); printf(" --techy Technical and scientific terms\n"); printf(" --literary Literary and archaic terms\n"); printf(" --cultural Cultural and international terms\n"); printf(" --full Complete dictionary\n\n"); printf("Custom:\n"); printf(" --wordlist FILE Use custom word list\n\n"); printf("Examples:\n"); printf(" %s --easy\n", argv[0]); printf(" %s --techy\n", argv[0]); printf(" %s --wordlist mywords.txt\n", argv[0]); return 0; } } return main_game_loop(argc, argv); }