Building a Console Word Game with Python
Tags: python, wordgame, console, beginners
Published: 2021-05-04 17:04:22

A while back, during the time I started learning to code with Python, I wrote a program for a game that is quite similar to [Hangman](https://en.wikipedia.org/wiki/Hangman_(game)). A particular number of guesses is given and then the user tries to guess the letters of the secret word. The user loses one guess for each incorrect try and the game ends either when the user has guessed all the letters of the word completely or when they run out of guesses. 

###Getting into it
There are probably a million ways to go about writing this game but since this is my article, we will be doing it my way😁. 

We want our game to look like what we have below:
```
You've chosen a/an 9-letter word!
Guess a letter: e
Good guess!
Guesses left:  9
Available letters:  a b c d f g h i j k l m n o p q r s t u v w x y z
---E--E--
Guess a letter: l
Good guess!
Guesses left:  9
Available letters:  a b c d f g h i j k m n o p q r s t u v w x y z
---E-LE--
Guess a letter: l
You have already guessed this letter!
Guess a letter:
...........................................
```
So now let's think about the structure. 
* We need a secret word which the user tries to guess. We could easily do this by picking a random word from a list of words.
* The user has 9 guesses or tries which only decrease when a user guesses wrongly.
* When a user enters a guess, we have to check if the letter is in our secret word. If the letter has been guessed already, we let the user know, and as the kind developers that we are, we don't reduce their guesses😉.
* After each guess, the user is told how many guesses they have left and the letters that haven't been tried yet. They're also shown the word with the letters that have been guessed correctly and the rest of the letters are replaced with dashes i.e. if the word is BANANA and only 'A' has been guessed correctly, `-A-A-A` is shown.
* We should also always check if the user has run out of guesses so that the guesses don't start counting in negatives😅.
* The game ends either when the user has been able to successfully guess the secret word, or when they run out of guesses.
* Some edge cases to consider are; when a user enters an invalid guess, e.g. a number, or when they enter more than one character at once. 

Having considered all these, the code for our game will be split into five separate functions/methods.
* `play_game`
* `check_guess`
* `can_be_formed_from`
* `display_info`
* `format_word`

###play_game
This is the entry point of our game. This is where the user is prompted for their guess. The necessary checks are then carried out. To ensure that our game is bug-less. 
```python
import random as r

guesses = 9
letters = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')


def format_word(guessed_letters, word):
    pass


def display_info(guessed_letters, word):
    pass


def check_guess(guess, word, guessed_letters):
    pass


def can_be_formed_from(letters, word):
    pass


def play_game():
    word_list = ['programmer', 'python', 'defenestrate']
    secret_word = r.choice(word_list)
    word_length = len(secret_word)
    guessed = []
    print(f'You have chosen a/an {word_length}-letter word.')
    while guesses > 0:
        guess = input('Enter a letter: ')
        guess = guess.upper()
        check_guess(guess, secret_word, guessed)
        if can_be_formed_from(guessed, secret_word):
            return print('YOU WON!')
    print('You ran out of guesses.\nYOU LOST!')
```
As we can see from above, a random word is chosen from a list of words(you can extend your list or fetch a bunch of words online), and then we do the user the courtesy of telling them how long it is. Then as long as the number of guesses is greater than zero, we keep prompting the user for a guess. The `check_guess` function(which we'll get to in a moment) is then called to validate our guess and add it to `guessed` which contains all the letters that have been tried. The `can_be_formed_from` function is also called to check if the `secret_word` can be formed from all the guessed letters. If it can, the user wins🎉. On the other hand, if the user runs out of guesses the game is ended. It would mean the user has lost, sadly😔. But as Nelson Mandela once said:
>*_You never lose, you either win or learn._*

###check_guess
This function takes in the guessed letters, the
secret word and the new guess. It checks if the
guess is valid and prints the appropriate message if it's not. The `display_info` function is then called.
```python
def check_guess(guess, word, guessed_letters):
    global guesses
    if guess in letters and len(guess) == 1 and guess not in guessed_letters:
        guessed_letters.append(guess)
        if guess in word:
            print('Good guess!')
        else:
            print('Bad guess!')
            guesses -= 1
    else:
        if len(guess) != 1:
            print('You must enter one letter, no more, no less!')
        elif guess.upper() not in letters:
            print('Your guess has to be a letter!')
        elif guess in guessed_letters:
            print('This letter has been tried already!')
    display_info(guessed_letters, word)
```
A seen above, the following checks are performed:
* If the guess is valid i.e. a letter of the English alphabet(This game can be tweaked to work with any language).
* If the guess contains exactly one letter.
* If the guess is present in our secret word.
* If the letter entered has been tried already.


###can_be_formed_from
As the name implies, this function accepts two strings and returns `True` if the second string can be formed from the letters of the first else `False`.
```python
def can_be_formed_from(letters, word):
    for letter in word:
        if letter not in letters:
            return False
    return True
```

###display_info
This function takes in the guessed letters and the secret word and generates the required data i.e. the number of guesses left, the letters that haven't been
tried yet etc. from the provided data.
```python
def display_info(guessed_letters, word):
    available_letters = ' '.join([
        letter for letter in letters if letter not in guessed_letters
    ])
    print(format_word(guessed_letters, word))
    print(f'Guesses left: {guesses}.')
    print(f'Available letters: {available_letters}.')
```

###format_word
This function helps to format our secret word with the letters that have been guessed correctly. For example, if the secret word is PYTHON and only 'n', 'o' and 'p' have been guessed correctly, P---ON is displayed.
```python
def format_word(guessed_letters, word):
    formatted_string = ''
    for letter in word:
        if letter in guessed_letters:
            formatted_string += letter
        else:
            formatted_string += '-'
    return formatted_string
```

Putting all this together, we have:

_word_game.py_
```python
import random as r

guesses = 9
letters = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')


def format_word(guessed_letters, word):
    formatted_string = ''
    for letter in word:
        if letter in guessed_letters:
            formatted_string += letter
        else:
            formatted_string += '-'
    return formatted_string


def display_info(guessed_letters, word):
    available_letters = ' '.join([
        letter for letter in letters if letter not in guessed_letters
    ])
    print(format_word(guessed_letters, word))
    print(f'Guesses left: {guesses}.')
    print(f'Available letters: {available_letters}.')


def check_guess(guess, word, guessed_letters):
    global guesses
    if guess in letters and len(guess) == 1 and guess not in guessed_letters:
        guessed_letters.append(guess)
        if guess in word:
            print('Good guess!')
        else:
            print('Bad guess!')
            guesses -= 1
    else:
        if len(guess) != 1:
            print('You must enter one letter, no more, no less!')
        elif guess.upper() not in letters:
            print('Your guess has to be a letter!')
        elif guess in guessed_letters:
            print('This letter has been tried already!')
    display_info(guessed_letters, word)


def can_be_formed_from(letters, word):
    for letter in word:
        if letter not in letters:
            return False
    return True


def play_game():
    word_list = ['programmer', 'python', 'defenestrate']
    secret_word = r.choice(word_list).upper()
    word_length = len(secret_word)
    guessed = []
    print(f'You have chosen a/an {word_length}-letter word.')
    while guesses > 0:
        guess = input('Enter a letter: ')
        guess = guess.upper()
        check_guess(guess, secret_word, guessed)
        if can_be_formed_from(guessed, secret_word):
            return print('YOU WON!')
    print('You ran out of guesses.\nYOU LOST!')


if __name__ == '__main__':
    play_game()

```

###Conclusion
By separating our code into small functions, it made building our game feel like a walk in the park. I hope this read was helpful to you😊. The full source code can be found [*here*](https://gist.github.com/olumidayy/e67ffe633e55e0910ebe5093d2d335b9). Also, the OOP approach for our game can be found [*here*](https://gist.github.com/olumidayy/05d9c42b6b434e102ff101edd882a7ca). Feel free to ask questions here or reach on Twitter [@OlumideNwosu](https://twitter.com/olumidenwosu).