#include #include #include #include #include #define WORD_LENGTH 5 /* represents a 5-bit letter */ typedef uint8_t let_t; /* represents a 5-bit letter + 3-bit position */ typedef uint8_t lpos_t; /* represents a set of possible letters (technically, 26-bits) */ typedef uint32_t let_set_t; /* represents 5 5-bit letters, a word */ typedef uint32_t word_t; #define let_from_char(c) (((c) - 'a') & 0x1F) #define let_to_char(c) ((c) + 'a') lpos_t cptolp(char c, uint8_t pos) { return (let_from_char(c) << 3) | pos; } let_set_t ls_add(let_set_t set, let_t letter) { return set | (1 << letter); } let_set_t ls_test(let_set_t set, let_t letter) { return (set & (1 << letter)) != 0; } uint8_t lp_pos(lpos_t lp) { return 0x07 & lp; } char lp_char(lpos_t lp) { return let_to_char(lp >> 3); } /* result is a 2-bit value: */ typedef enum result { RES_BLACK = 0x01, RES_YELLOW = 0x02, RES_GREEN = 0x03 } result_t; /* represents a wordle result: 5 2-bit result codes */ typedef uint16_t result_set_t; result_set_t rs_set(result_t res, size_t index, result_t value) { return res | ((value & 0x3) << (2 * index)); } result_t rs_get(result_t res, size_t index) { return (res >> (2 * index)) & 0x3; } char word_charat(word_t word, size_t index) { return (char) ((word >> (index * 5)) & 0x1F) + 'a'; } /* converts a 5-letter word to its compacted representation */ word_t strtoword(char *str) { word_t word = 0; for (size_t i = 0; i < WORD_LENGTH; ++i) { word |= (str[i] - 'a') << (5 * i); } return word; } void wordtostr(char *out, word_t word) { for (size_t i = 0; i < WORD_LENGTH; ++i) { out[i] = word_charat(word, i); } } result_set_t guess_match_werd(char *answer, char *guess) { bool guess_consumed[WORD_LENGTH] = {false}; bool answer_consumed[WORD_LENGTH] = {false}; result_set_t result_set = 0; for (size_t i = 0; i < WORD_LENGTH; ++i) { if (answer[i] == guess[i]) { answer_consumed[i] = true; guess_consumed[i] = true; result_set = rs_set(result_set, i, RES_GREEN); } } for (size_t i = 0; i < WORD_LENGTH; ++i) { if (guess_consumed[i]) { continue; } bool found = false; for (size_t j = 0; j < WORD_LENGTH; ++j) { if (answer_consumed[j]) { continue; } if (answer[j] == guess[i]) { answer_consumed[j] = true; found = true; break; } } if (found) { result_set = rs_set(result_set, i, RES_YELLOW); } else { result_set = rs_set(result_set, i, RES_BLACK); } } return result_set; } result_set_t guess_match(word_t answer_word, word_t guess_word) { char answer[6] = {0}, guess[6] = {0}; wordtostr(answer, answer_word); wordtostr(guess, guess_word); return guess_match_werd(answer, guess); } /* returns whether or not the game was won */ typedef struct wordle_game { word_t target; uint32_t turn_number, turn_limit; int (*guess_fn)(word_t *, void *); void *guess_fn_data; void (*display_fn)(result_set_t, void *); void *display_fn_data; } wordle_game_t; struct linebuf { char *buffer; size_t capacity; }; /* the guess function returns 0 on success and something else on failure */ int term_guess(word_t *word, void *data) { struct linebuf *linebuf = data; while (true) { fputs("guess> ", stdout); ssize_t bytes_read = getline(&(linebuf->buffer), &(linebuf->capacity), stdin); /* if the input is closed, there's no way to recover. fail out */ if (bytes_read == -1) { return -1; } char *buf_end = linebuf->buffer; strsep(&buf_end, "\n"); size_t length = strchr(linebuf->buffer, '\0') - linebuf->buffer; /* some basic error checking around length */ if (length == WORD_LENGTH) { *word = strtoword(linebuf->buffer); return 0; } else { if (length > WORD_LENGTH) { fprintf(stderr, "error: too long\n"); } else { fprintf(stderr, "error: too short\n"); } continue; } } } void term_display(result_set_t result_set, void *ignored) { (void) ignored; fputs("result ", stdout); for (size_t i = 0; i < WORD_LENGTH; ++i) { switch (rs_get(result_set, i)) { case RES_BLACK: fputc(' ', stdout); break; case RES_YELLOW: fputc('Y', stdout); break; case RES_GREEN: fputc('G', stdout); break; default: fputc('X', stdout); break; } } fputc('\n', stdout); } bool play_game(wordle_game_t *game) { for (game->turn_number = 1; game->turn_number <= game->turn_limit; ++game->turn_number) { word_t guess = 0; if (game->guess_fn(&guess, game->guess_fn_data) != 0) { printf("guesser failure\n"); break; } result_set_t result_set = guess_match(game->target, guess); game->display_fn(result_set, game->display_fn_data); if (guess == game->target) { return true; } } return false; } int main(int argc, char *argv[]) { struct linebuf lb = { .capacity = 6, .buffer = (char *) malloc(lb.capacity), }; wordle_game_t game = { .target = strtoword("total"), .turn_number = 0, .turn_limit = 6, .guess_fn = term_guess, .guess_fn_data = &lb, .display_fn = term_display, .display_fn_data = NULL, }; bool won = play_game(&game); if (won) { fprintf(stdout, "YOU WIN %d/%d\n", game.turn_number, game.turn_limit); } else { fprintf(stdout, "YOU LOSE\n"); } return 0; }