refactored for modularity
This commit is contained in:
parent
cdd51fdf5c
commit
6590157cc5
@ -1,13 +1,31 @@
|
||||
cmake_minimum_required(VERSION 4.0)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(cordle C)
|
||||
|
||||
set(CMAKE_C_STANDARD 90)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# Find the curses library
|
||||
# Find ncurses library
|
||||
find_package(Curses REQUIRED)
|
||||
|
||||
add_executable(cordle cordle.c)
|
||||
# Include directories
|
||||
include_directories(include)
|
||||
include_directories(${CURSES_INCLUDE_DIR})
|
||||
|
||||
# Link against the curses library
|
||||
# Source files
|
||||
set(SOURCES
|
||||
src/main.c
|
||||
src/game.c
|
||||
src/words.c
|
||||
src/ui.c
|
||||
)
|
||||
|
||||
# Create executable
|
||||
add_executable(cordle ${SOURCES})
|
||||
|
||||
# Link ncurses
|
||||
target_link_libraries(cordle ${CURSES_LIBRARIES})
|
||||
target_include_directories(cordle PRIVATE ${CURSES_INCLUDE_DIRS})
|
||||
|
||||
# Set compiler flags for C90 compliance
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(cordle PRIVATE -Wall -Wextra -pedantic)
|
||||
endif()
|
||||
|
||||
39
include/game.h
Normal file
39
include/game.h
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
/* game.h - Game logic and state */
|
||||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
#define MAX_FILENAME 256
|
||||
#define MAX_MESSAGE 256
|
||||
|
||||
#define MAX_WORDS 10000
|
||||
#define WORD_LENGTH 5
|
||||
#define MAX_GUESSES 6
|
||||
|
||||
/* Letter status constants */
|
||||
#define STATUS_UNUSED 0
|
||||
#define STATUS_ABSENT 1
|
||||
#define STATUS_PRESENT 2
|
||||
#define STATUS_CORRECT 3
|
||||
|
||||
/* 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];
|
||||
} GameState;
|
||||
|
||||
/* Function declarations */
|
||||
void init_game(GameState* game);
|
||||
int make_guess(GameState* game, const char* guess);
|
||||
void check_guess(GameState* game, const char* guess, int* colors);
|
||||
|
||||
#endif /* GAME_H */
|
||||
22
include/ui.h
Normal file
22
include/ui.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* ui.h - User interface functions */
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#include <curses.h>
|
||||
#include "game.h"
|
||||
|
||||
/* Color pair constants */
|
||||
#define COLOR_DEFAULT 1
|
||||
#define COLOR_CORRECT 2
|
||||
#define COLOR_PRESENT 3
|
||||
#define COLOR_ABSENT 4
|
||||
#define COLOR_WWHITE 7
|
||||
#define COLOR_UNUSED 6
|
||||
|
||||
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);
|
||||
|
||||
#endif /* UI_H */
|
||||
11
include/words.h
Normal file
11
include/words.h
Normal file
@ -0,0 +1,11 @@
|
||||
/* words.h - Word list management */
|
||||
#ifndef WORDS_H
|
||||
#define WORDS_H
|
||||
|
||||
#include "game.h"
|
||||
|
||||
int load_words(GameState* game, const char* filename);
|
||||
int is_valid_word(GameState* game, const char* word);
|
||||
void to_upper(char* str);
|
||||
|
||||
#endif /* WORDS_H */
|
||||
115
src/game.c
Normal file
115
src/game.c
Normal file
@ -0,0 +1,115 @@
|
||||
//
|
||||
// Created by Gregory Gauthier on 06/10/2025.
|
||||
//
|
||||
/* game.c - Game logic implementation */
|
||||
#include "../include/game.h"
|
||||
#include "../include/ui.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Ensure uppercase */
|
||||
for (i = 0; i < WORD_LENGTH; i++) {
|
||||
guess_copy[i] = toupper(guess_copy[i]);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
char upper_guess[WORD_LENGTH + 1];
|
||||
int i;
|
||||
|
||||
if (strlen(guess) != WORD_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
strcpy(upper_guess, guess);
|
||||
for (i = 0; i < WORD_LENGTH; i++) {
|
||||
upper_guess[i] = toupper(upper_guess[i]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
237
src/main.c
Normal file
237
src/main.c
Normal file
@ -0,0 +1,237 @@
|
||||
//
|
||||
// Created by Gregory Gauthier on 06/10/2025.
|
||||
//
|
||||
|
||||
/* main.c - Entry point and main game loop */
|
||||
/* Compile with: gcc -std=c90 -o cordle src/*.c -Iinclude -lncurses */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <curses.h>
|
||||
#include "../include/game.h"
|
||||
#include "../include/words.h"
|
||||
#include "../include/ui.h"
|
||||
|
||||
/* Parse command line arguments */
|
||||
void parse_arguments(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 {
|
||||
/* Check if word is valid */
|
||||
if (!is_valid_word(&game, temp_guess)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* Main entry point */
|
||||
int main(int argc, char* argv[]) {
|
||||
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);
|
||||
}
|
||||
156
src/ui.c
Normal file
156
src/ui.c
Normal file
@ -0,0 +1,156 @@
|
||||
//
|
||||
// Created by Gregory Gauthier on 06/10/2025.
|
||||
//
|
||||
/* ui.c - User interface implementation */
|
||||
#include "../include/ui.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Keyboard layout */
|
||||
static const char* keyboard_rows[3] = {
|
||||
"QWERTYUIOP",
|
||||
"ASDFGHJKL",
|
||||
"ZXCVBNM"
|
||||
};
|
||||
|
||||
/* 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/words.c
Normal file
83
src/words.c
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// Created by Gregory Gauthier on 06/10/2025.
|
||||
//
|
||||
/* words.c - Word list management implementation */
|
||||
#include "../include/words.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user