2025-12-17 21:24:41 +00:00
|
|
|
/*
|
2025-12-18 08:59:54 +00:00
|
|
|
* Created by Gregory Gauthier on 06/10/2025.
|
|
|
|
|
*/
|
2025-10-06 13:58:25 +00:00
|
|
|
|
|
|
|
|
/* main.c - Entry point and main game loop */
|
2025-12-17 09:28:00 +00:00
|
|
|
/* Compile with: gcc -std=c90 -o cordle src\/\*.c -Iinclude -lncurses */
|
2025-10-06 13:58:25 +00:00
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <ctype.h>
|
2026-01-26 14:29:55 +00:00
|
|
|
#include "../include/platform.h"
|
2025-10-06 13:58:25 +00:00
|
|
|
#include "../include/game.h"
|
|
|
|
|
#include "../include/words.h"
|
|
|
|
|
#include "../include/ui.h"
|
|
|
|
|
|
|
|
|
|
/* Parse command line arguments */
|
2025-12-17 09:28:00 +00:00
|
|
|
void parse_arguments(int argc, char *argv[], char *filename, char *difficulty) {
|
2025-10-06 13:58:25 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* Default values */
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_easy.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "EASY");
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
if (strcmp(argv[i], "--easy") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_easy.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "EASY");
|
|
|
|
|
} else if (strcmp(argv[i], "--medium") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_medium.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "MEDIUM");
|
|
|
|
|
} else if (strcmp(argv[i], "--hard") == 0) {
|
2025-12-17 10:08:03 +00:00
|
|
|
strcpy(filename, "cordle_words_hard.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "HARD");
|
|
|
|
|
} else if (strcmp(argv[i], "--techy") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_techy.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "TECHNICAL");
|
|
|
|
|
} else if (strcmp(argv[i], "--literary") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_literary.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "LITERARY");
|
|
|
|
|
} else if (strcmp(argv[i], "--cultural") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_cultural.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
strcpy(difficulty, "CULTURAL");
|
|
|
|
|
} else if (strcmp(argv[i], "--full") == 0) {
|
2025-12-17 09:59:33 +00:00
|
|
|
strcpy(filename, "cordle_words_full.txt");
|
2025-10-06 13:58:25 +00:00
|
|
|
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 */
|
2025-12-18 08:59:54 +00:00
|
|
|
int main_game_loop(int argc, char *argv[]) {
|
2025-12-17 09:28:00 +00:00
|
|
|
WINDOW *stdscr;
|
2025-10-06 13:58:25 +00:00
|
|
|
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();
|
2025-12-17 09:28:00 +00:00
|
|
|
curs_set(0); /* Hide cursor */
|
2026-01-26 14:04:05 +00:00
|
|
|
keypad(stdscr, TRUE); /* Enable special key detection */
|
2025-10-06 13:58:25 +00:00
|
|
|
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 */
|
2025-12-17 09:59:33 +00:00
|
|
|
if (strcmp(filename, "cordle_words.txt") != 0 &&
|
|
|
|
|
load_words(&game, "cordle_words.txt")) {
|
2025-10-06 13:58:25 +00:00
|
|
|
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!",
|
2025-12-17 09:28:00 +00:00
|
|
|
game.guess_count);
|
2025-10-06 13:58:25 +00:00
|
|
|
draw_message(stdscr, win_message, y_pos, COLOR_CORRECT);
|
|
|
|
|
} else {
|
|
|
|
|
sprintf(win_message, "Game Over! The word was: %s",
|
2025-12-17 09:28:00 +00:00
|
|
|
game.target_word);
|
2025-10-06 13:58:25 +00:00
|
|
|
draw_message(stdscr, win_message, y_pos, COLOR_ABSENT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
draw_message(stdscr, "Press 'N' for new game or 'Q' to quit",
|
2025-12-17 09:28:00 +00:00
|
|
|
y_pos + 1, COLOR_DEFAULT);
|
2025-10-06 13:58:25 +00:00
|
|
|
|
|
|
|
|
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 */
|
2025-12-17 09:28:00 +00:00
|
|
|
if (key == 27) {
|
|
|
|
|
/* ESC */
|
2025-10-06 13:58:25 +00:00
|
|
|
break;
|
2026-01-26 14:04:05 +00:00
|
|
|
} else if (key == KEY_UP || key == KEY_DOWN || key == KEY_LEFT || key == KEY_RIGHT) {
|
|
|
|
|
/* Ignore arrow keys to prevent crashes */
|
|
|
|
|
continue;
|
2025-10-06 13:58:25 +00:00
|
|
|
} 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 {
|
2025-12-18 09:13:20 +00:00
|
|
|
/* Validation failed in make_guess or is_valid_word */
|
|
|
|
|
strcpy(message, "Not in word list!");
|
|
|
|
|
message_color = COLOR_ABSENT;
|
2025-10-06 13:58:25 +00:00
|
|
|
}
|
|
|
|
|
} 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Main entry point */
|
2025-12-17 09:28:00 +00:00
|
|
|
int main(int argc, char *argv[]) {
|
2025-10-06 13:58:25 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* Handle help */
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
|
|
|
|
printf("cordle - 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);
|
2025-12-17 09:28:00 +00:00
|
|
|
}
|