Skip to main content
added 1 character in body
Source Link
FMc
  • 12.8k
  • 2
  • 28
  • 40

Global variables are almost never needed. Don't organize programs around the need to modify global variables. Instead, organize your programs around functions that rely only on their local variables (or methods thethat follow similar principles). If some functions need access to the same information, pass that data back and forth using function arguments and return values.

Global variables are almost never needed. Don't organize programs around the need to modify global variables. Instead, organize your programs around functions that rely only on their local variables (or methods the follow similar principles). If some functions need access to the same information, pass that data back and forth using function arguments and return values.

Global variables are almost never needed. Don't organize programs around the need to modify global variables. Instead, organize your programs around functions that rely only on their local variables (or methods that follow similar principles). If some functions need access to the same information, pass that data back and forth using function arguments and return values.

added 89 characters in body
Source Link
FMc
  • 12.8k
  • 2
  • 28
  • 40

Try to separate logic/algorithm from interactivity and printing. Code that interacts with users is a hassle to test and debug. Ideally, you want most of your program to operate purely in the data realm: for functions that means receiving data and returning data; for methods it might mean receiving data and then returning data or modifying internal data. However, many programs require interactivity and related printing. Programs like this are often good fits for a design that used a main function to handle interactions and an immutable object that knows hold to hold the current state and then how to compute the next state. Let's start with a sketch of the interactive layer. This is where we perform all printing and collecting of user input. Notice especially that this part of the program contains very little algorithmic logic.

Inside the GameState class, we need to implement various read-only properties to get information derived from our core pieces of data (the word and the prior guesses):

With a design like this, the vast majority of your functionality could be tested and debugged purely by working with the data-oriented GameState class.

Try to separate logic/algorithm from interactivity and printing. Code that interacts with users is a hassle to test and debug. Ideally, you want most of your program to operate purely in the data realm: for functions that means receiving data and returning data; for methods it might mean receiving data and then returning data or modifying internal data. However, many programs require interactivity and related printing. Programs like this are often good fits for a design that used a main function to handle interactions and an immutable object that knows hold to hold the current state and then how to compute the next state. Let's start with a sketch of the interactive layer. This is where we perform all printing and collecting of user input.

Inside the GameState class, we need to implement various properties to get information derived from our core pieces of data (the word and the prior guesses):

Try to separate logic/algorithm from interactivity and printing. Code that interacts with users is a hassle to test and debug. Ideally, you want most of your program to operate purely in the data realm: for functions that means receiving data and returning data; for methods it might mean receiving data and then returning data or modifying internal data. However, many programs require interactivity and related printing. Programs like this are often good fits for a design that used a main function to handle interactions and an immutable object that knows hold to hold the current state and then how to compute the next state. Let's start with a sketch of the interactive layer. This is where we perform all printing and collecting of user input. Notice especially that this part of the program contains very little algorithmic logic.

Inside the GameState class, we need to implement various read-only properties to get information derived from our core pieces of data (the word and the prior guesses):

With a design like this, the vast majority of your functionality could be tested and debugged purely by working with the data-oriented GameState class.

Source Link
FMc
  • 12.8k
  • 2
  • 28
  • 40

If a function just calls another function, delete one of them. You don't need both main() and testing(), since the former does nothing other than call the latter.

Global variables are almost never needed. Don't organize programs around the need to modify global variables. Instead, organize your programs around functions that rely only on their local variables (or methods the follow similar principles). If some functions need access to the same information, pass that data back and forth using function arguments and return values.

Don't put logic in top-level code. At the top level of a program, you can perform imports or define constants, functions, or classes. Put all other code inside of functions or methods. If we apply all of the preceding ideas to your current code, we end up with the following structure:

def main():
    lives = 6
    chosen_word = random.choice(WORD_LIST)
    ...
    while lives > 0:
        guess = ask_input()
        blanks, lives = replace_blank(guess, chosen_word, blanks, lives)
        ...

def ask_input():
    ...

def replace_blank(guess, chosen_word, blanks, lives):
    ...
    return (blanks, lives)

Try to design functions so that they perform a single task. This reorganization of the code highlights some awkwardness in the implementation. The main() function initializes lives and blanks but then delegates responsibility for updating them to the replace_blank() function. Meanwhile, that function has various other responsibilities, like printing messages to the user and clearing the terminal.

Missing features. Many hangman games tell the user what letters have already been guessed and I've never seen one that penalizes a player for guessing an already-guessed letter. I mention this missing behavior because it points the way toward a less awkward code design. The first step is to select a data structure to hold prior guesses. A set is a natural fit. In fact, if we know the initial number of lives, the secret word, and the prior guesses, all other information about game state can be derived (remaining lives, whether the game is finished, how the secret word should be displayed, etc).

Try to separate logic/algorithm from interactivity and printing. Code that interacts with users is a hassle to test and debug. Ideally, you want most of your program to operate purely in the data realm: for functions that means receiving data and returning data; for methods it might mean receiving data and then returning data or modifying internal data. However, many programs require interactivity and related printing. Programs like this are often good fits for a design that used a main function to handle interactions and an immutable object that knows hold to hold the current state and then how to compute the next state. Let's start with a sketch of the interactive layer. This is where we perform all printing and collecting of user input.

import random
import os

WORD_LIST = [...]

HANGMANPICS = [...]

def main():
    # Setup.
    gs = GameState(random.choice(WORD_LIST))

    # Play until done.
    while not gs.is_finished:
        # Printing.
        os.system('clear')
        print(gs.graphic)
        print('Secret word:', gs.display_word)
        print('Prior guesses:', gs.display_guesses)
        if gs.message:
            print(gs.message)
        # Derive the next state based on user input.
        gs = gs.next_state(ask_input())

    # Wrap up.
    print(gs.outcome)

def ask_input():
    while True:
        guess = input("Guess a letter?  ").lower()
        if guess.isalpha() and len(guess) == 1:
            return guess

if __name__ == '__main__':
    main()

The rest of the code just requires implementing the needed data-retrieval behaviors in GameState. As noted, the initial state will hold an empty set for prior guesses. A message attribute will be used to store information needed by our interactive layer when printing game status.

from dataclasses import dataclass, field

@dataclass(frozen = True)
class GameState:
    word : str
    prior_guesses : set = field(default_factory = set)
    message : str = ''

    INITIAL_LIVES = len(HANGMANPICS) - 1
    BLANK = '_'

Inside the GameState class, we need to implement various properties to get information derived from our core pieces of data (the word and the prior guesses):

class GameState:

    @property
    def lives(self):
        return self.INITIAL_LIVES - sum(
            char not in self.word
            for char in self.prior_guesses
        )

    @property
    def display_word(self):
        return ''.join(
            char if char in self.prior_guesses else self.BLANK
            for char in self.word
        )

    @property
    def display_guesses(self):
        return ' '.join(sorted(self.prior_guesses))

    @property
    def is_finished(self):
        return self.lives <= 0 or self.word == self.display_word

    @property
    def graphic(self):
        return HANGMANPICS[self.INITIAL_LIVES - self.lives]

    @property
    def outcome(self):
        return (
            None if not self.is_finished else
            'You won' if self.display_word == self.word else
            'You lost'
        )

Finally, the class needs a method to generate a new GameState instance based on the current state and the user's guess:

class GameState:

    def next_state(self, guess):
        w = self.word
        pg = self.prior_guesses
        message = (
            f'Already guessed: {guess}' if guess in pg else
            f'Correct: {guess}' if guess in w else
            f'Not in word: {guess}. You lose a life'
        )
        return GameState(w, pg | {guess}, message)