wordle-cli

Golang implementation of wordle in CLI.
git clone http://git.hanabi.in/repos/wordle-cli.git
Log | Files | Refs | README | LICENSE

commit ed31a4bc69f4445f78d49fdc27dc2b2155f950f1
parent 6e6be1afe5775e90656161d597e232cd973ebf56
Author: Agastya Chandrakant <me@hanabi.in>
Date:   Fri, 18 Feb 2022 01:10:47 +0530

Refactor more code; also, reject repeated guesses.

Diffstat:
Msrc/main.go | 47++++-------------------------------------------
Dsrc/utils/fns.go | 142-------------------------------------------------------------------------------
Asrc/utils/gameplay-fns.go | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 199 insertions(+), 185 deletions(-)

diff --git a/src/main.go b/src/main.go @@ -1,50 +1,11 @@ package main import ( - "fmt" - - "git.hanabi.in/dev/wordle-cli/src/algos" - "git.hanabi.in/dev/wordle-cli/src/utils" -) - -const ( - chances = 6 - word_size = 5 + gameplay "git.hanabi.in/dev/wordle-cli/src/utils" ) func main() { - answer := utils.SelectAnswer() - guesses := startGuessing(answer) - fmt.Printf("Answer was: %s.\n", answer) - utils.PrintShare(guesses) -} - -func startGuessing(answer string) []string { - - var alphabet = utils.InitAlphabetTable() - var anslookup = algos.GenAnsLookup(answer) - var colouredChoices = []string{} - fmt.Printf("Guess a %d-letter word. You have %d tries.\n", word_size, chances) - - for cur_chance := 1; cur_chance <= chances; { - utils.GuessPrompt(cur_chance) - guess, err := utils.GetValidGuess(word_size) - if err != nil { - fmt.Printf("%v", err) - } else { - cur_chance++ - if guess != answer { - colour_string := algos.GetColours(answer, guess, anslookup, alphabet) - colouredChoices = append(colouredChoices, colour_string) - utils.PrintColouredGuess(colour_string, guess) - } else if guess == answer { - colour_string := "GGGGG" // If the answer was correct, GetColours is not called, hence hard-coding. - colouredChoices = append(colouredChoices, colour_string) - fmt.Println("Correct guess!") - break - } - } - utils.PrintColouredAlpha(alphabet) - } - return colouredChoices + answer := gameplay.SelectAnswer() + guesses := gameplay.StartGuessing(answer) + gameplay.GracefullyFinishGame(answer, guesses) } diff --git a/src/utils/fns.go b/src/utils/fns.go @@ -1,142 +0,0 @@ -package utils - -import ( - "fmt" - "math/rand" - "strings" - - "git.hanabi.in/dev/wordle-cli/src/algos" - "git.hanabi.in/dev/wordle-cli/src/colours" - "git.hanabi.in/dev/wordle-cli/src/data" -) - -// From a pre-defined sorted list of words, pick a random word which is the answer. -func SelectAnswer() string { - words := []string{} - for _, elem := range data.Words { - words = append(words, elem) - } - algos.Shuffle(words) - index := rand.Intn(len(words)) - return words[index] -} - -// Create a lookup table for alphabet, initialise them to grey colour. -func InitAlphabetTable() algos.Lookup { - var alphabet_table = make(algos.Lookup, 0) - letters := "abcdefghijklmnopqrstuvwxyz" - for _, elem := range letters { - letter_char := string(elem) - alphabet_table[letter_char] = colours.Code_grey - } - return alphabet_table -} - -// Check if the word is of the size of answer. Returns true if size mismatch. -func IsWrongGuessSize(guess string, word_size int) bool { - return len(guess) != word_size -} - -// Get user input for the guess, and process it (lower case) -func FetchGuess() string { - var guess string - fmt.Scanf("%s", &guess) - guess = strings.ToLower(guess) - return guess -} - -// Check if guessed word is not in the list of possible words. Returns true if not found. -func IsNotValidWord(guess string) bool { - idx := algos.BinarySearch(data.Words, guess) - return idx == -1 -} - -// returns either a valid guess, XOR an error. -func GetValidGuess(word_size int) (string, error) { - guess := FetchGuess() - if IsWrongGuessSize(guess, word_size) { - error_msg := fmt.Errorf("Word should be of length %d.\n", word_size) - return guess, error_msg - } - if IsNotValidWord(guess) { - error_msg := fmt.Errorf("Not a valid word.\n") - return guess, error_msg - } - return guess, nil -} - -// Print the guess prompt. -func GuessPrompt(chance int) { - msg := colours.Bold(fmt.Sprintf("Guess #%d?: ", chance)) - fmt.Print(msg) -} - -// Look at colour_string (Y,G,R) and print the characters of word in colour. -func PrintColouredGuess(colour_string, word string) { - for idx, word_elem := range word { - col_str_char := string(colour_string[idx]) - word_char := string(word_elem) - if col_str_char == "R" { - fmt.Print(colours.Red(word_char)) - } else if col_str_char == "Y" { - fmt.Print(colours.Yellow(word_char)) - } else if col_str_char == "G" { - fmt.Print(colours.Green(word_char)) - } - } - fmt.Println() -} - -// Print coloured alphabet for aiding which words to guess. -func PrintColouredAlpha(alphabet algos.Lookup) { - kbd_rows := []string{"qwertyuiop", "asdfghjkl", "zxcvbnm"} // keyboard layout printing. - for _, kbd_row := range kbd_rows { - for _, kbd_key := range kbd_row { - kbd_key_char := string(kbd_key) - char_col_code := alphabet[kbd_key_char] - if char_col_code == colours.Code_grey { - fmt.Print(colours.Grey(kbd_key_char)) - } else if char_col_code == colours.Code_red { - fmt.Print(colours.Red(kbd_key_char)) - } else if char_col_code == colours.Code_yellow { - fmt.Print(colours.Yellow(kbd_key_char)) - } else if char_col_code == colours.Code_green { - fmt.Print(colours.Green(kbd_key_char)) - } - fmt.Print(" ") - } - fmt.Println() - } - fmt.Println() -} - -// Print share emoji. -func PrintShare(guesses []string) { - if shouldPrintShareEmojis() { - fmt.Println() - for _, row := range guesses { - for _, elem_byte := range row { - elem := string(elem_byte) - if elem == "R" { - fmt.Print("🌑") - } else if elem == "Y" { - fmt.Print("🌕") - } else if elem == "G" { - fmt.Print("✅") - } - } - fmt.Println() - } - } -} - -// Prompt if the share emojies be printed. -func shouldPrintShareEmojis() bool { - var ans string - fmt.Print(colours.Bold("Share your results?[yN]: ")) - fmt.Scanf("%s", &ans) - if ans == "Y" || ans == "y" { - return true - } - return false -} diff --git a/src/utils/gameplay-fns.go b/src/utils/gameplay-fns.go @@ -0,0 +1,195 @@ +package gameplay + +import ( + "fmt" + "math/rand" + "strings" + + "git.hanabi.in/dev/wordle-cli/src/algos" + "git.hanabi.in/dev/wordle-cli/src/colours" + "git.hanabi.in/dev/wordle-cli/src/data" +) + +const ( + chances = 6 + word_size = 5 +) + +// From a pre-defined sorted list of words, pick a random word which is the answer. +func SelectAnswer() string { + words := []string{} + for _, elem := range data.Words { + words = append(words, elem) + } + algos.Shuffle(words) + index := rand.Intn(len(words)) + return words[index] +} + +// Create a lookup table for alphabet, initialise them to grey colour. +func initAlphabetTable() algos.Lookup { + var alphabet_table = make(algos.Lookup, 0) + letters := "abcdefghijklmnopqrstuvwxyz" + for _, elem := range letters { + letter_char := string(elem) + alphabet_table[letter_char] = colours.Code_grey + } + return alphabet_table +} + +// Check if the word is of the size of answer. Returns true if size mismatch. +func isWrongGuessSize(guess string) bool { + return len(guess) != word_size +} + +// Get user input for the guess, and process it (lower case) +func fetchGuess() string { + var guess string + fmt.Scanf("%s", &guess) + guess = strings.ToLower(guess) + return guess +} + +// Check if guessed word is not in the list of possible words. Returns true if not found. +func isNotValidWord(guess string) bool { + idx := algos.BinarySearch(data.Words, guess) + return idx == -1 +} + +// returns either a valid guess, XOR an error. +func getValidGuess(prev_guesses []string) (string, error) { + guess := fetchGuess() + var error_msg error = nil + if isWrongGuessSize(guess) { + error_msg = fmt.Errorf("Word should be of length %d.\n", word_size) + } else if isNotValidWord(guess) { + error_msg = fmt.Errorf("Not a valid word.\n") + } else if isOldGuess(guess, prev_guesses) { + error_msg = fmt.Errorf("You already guessed this word.\n") + } + return guess, error_msg +} + +// Check if the current guess was already guessed or not. +func isOldGuess(guess string, prev_guesses []string) bool { + for _, elem := range prev_guesses { + if elem == guess { + return true + } + } + return false +} + +// Print the guess prompt. +func guessPrompt(chance int) { + msg := colours.Bold(fmt.Sprintf("Guess #%d?: ", chance)) + fmt.Print(msg) +} + +// Look at colour_string (Y,G,R) and print the characters of word in colour. +func printColouredGuess(colour_string, word string) { + for idx, word_elem := range word { + col_str_char := string(colour_string[idx]) + word_char := string(word_elem) + if col_str_char == "R" { + fmt.Print(colours.Red(word_char)) + } else if col_str_char == "Y" { + fmt.Print(colours.Yellow(word_char)) + } else if col_str_char == "G" { + fmt.Print(colours.Green(word_char)) + } + } + fmt.Println() +} + +// Print coloured alphabet for aiding which words to guess. +func printColouredAlpha(alphabet algos.Lookup) { + kbd_rows := []string{"qwertyuiop", "asdfghjkl", "zxcvbnm"} // keyboard layout printing. + for _, kbd_row := range kbd_rows { + for _, kbd_key := range kbd_row { + kbd_key_char := string(kbd_key) + char_col_code := alphabet[kbd_key_char] + if char_col_code == colours.Code_grey { + fmt.Print(colours.Grey(kbd_key_char)) + } else if char_col_code == colours.Code_red { + fmt.Print(colours.Red(kbd_key_char)) + } else if char_col_code == colours.Code_yellow { + fmt.Print(colours.Yellow(kbd_key_char)) + } else if char_col_code == colours.Code_green { + fmt.Print(colours.Green(kbd_key_char)) + } + fmt.Print(" ") + } + fmt.Println() + } + fmt.Println() +} + +// Print share emoji. +func printShare(guesses []string) { + if shouldPrintShareEmojis() { + fmt.Println() + for _, row := range guesses { + for _, elem_byte := range row { + elem := string(elem_byte) + if elem == "R" { + fmt.Print("🌑") + } else if elem == "Y" { + fmt.Print("🌕") + } else if elem == "G" { + fmt.Print("✅") + } + } + fmt.Println() + } + } +} + +// Prompt if the share emojies be printed. +func shouldPrintShareEmojis() bool { + var ans string + fmt.Print(colours.Bold("Share your results?[yN]: ")) + fmt.Scanf("%s", &ans) + if ans == "Y" || ans == "y" { + return true + } + return false +} + +func StartGuessing(answer string) []string { + + var alphabet = initAlphabetTable() + var anslookup = algos.GenAnsLookup(answer) + var prev_guesses = []string{} + var colouredChoices = []string{} + fmt.Printf("Guess a %d-letter word. You have %d tries.\n", word_size, chances) + + for cur_chance := 1; cur_chance <= chances; { + guessPrompt(cur_chance) + guess, err := getValidGuess(prev_guesses) + if err != nil { + fmt.Printf("%v", err) + } else { + cur_chance++ + prev_guesses = append(prev_guesses, guess) + if guess != answer { + colour_string := algos.GetColours(answer, guess, anslookup, alphabet) + colouredChoices = append(colouredChoices, colour_string) + printColouredGuess(colour_string, guess) + } else if guess == answer { + colour_string := "GGGGG" // If the answer was correct, GetColours is not called, hence hard-coding. + colouredChoices = append(colouredChoices, colour_string) + fmt.Println("Correct guess!") + break + } + } + printColouredAlpha(alphabet) + } + return colouredChoices +} + +// Handle end of the game once correct answer is reached, or when all chances are over. +func GracefullyFinishGame(answer string, guesses []string) { + fmt.Printf("Answer was: %s.\n", answer) + printShare(guesses) +}