4
\$\begingroup\$

I'm pretty new to python and have created a simple hangman. Since doing so I realise this appears to be a fairy common theme. It uses /usr/share/dict/words on the local system, resulting in some tricky words to guess. I guess I should look at maybe curl'ing a URL to get random words or an API. I've also not defined a main(), not sure if that's bad practice. Feedback appreciated.

#!/usr/local/bin/python3

import os
import random
import subprocess

def draw_man_and_show_word():
   global word
   showword=''.join(word)
   os.system('clear')
   for y in range(0,7):
      for x in range(0,7):
         print (chr(array[y][x]),end='')
      print()
   print () 
   print (showword)
   print () 

def add_limb_to_man():
   global size
   y=man[size][0]
   x=man[size][1]
   c=man[size][2]
   array[x][y]=c 
   size=size+1

def find_random_word():
   global theword
   lines=int(subprocess.getoutput("wc -l /usr/share/dict/words | awk \'{print $1}\'"))
   line=random.randint(1,lines)
   theword=subprocess.getoutput("head -"+str(line)+" /usr/share/dict/words |tail -1")
   theword=str.lower(theword) 

# 2D array of character (ASCII) values as a matrix for the hangman pic
array=[[124,45,45,45,45,32,32,32],[124,32,32,32,124,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32]]

# 2D array with the (single) character position to change (x,y,newvalue) for each additional limb
man=[[4,2,79],[4,3,43],[3,3,47],[5,3,92],[4,4,124],[3,5,47],[5,5,92]]

size=tries=0
limit=6
letters_tried=""

find_random_word()

# Array to represent word shown on screen (using array as string immutable)
word=['-' for x in range(0,len(theword))]

while ((tries <= limit)):
   draw_man_and_show_word()

   letter=""
   while (len(letter) != 1 or not letter.islower() or letters_tried.find(letter) >= 0):
      letter=input("Enter your choice of (single lowercase) letter:")

   letters_tried=letters_tried+letter

   pos=theword.find(letter)
   if (pos >= 0):
      tmpword=theword
      while (pos >= 0):
         word[pos]=letter
         tmpword=tmpword.replace(letter,'#',1)
         pos=tmpword.find(letter)

   else:
      add_limb_to_man()
      tries=tries+1

   if (''.join(word) == theword):
      draw_man_and_show_word()
      print()
      print("you got it!")
      exit()

draw_man_and_show_word()

print ("you lost. It was "+theword)
\$\endgroup\$
  • \$\begingroup\$ Edit: corrected the handling of counting lines in /usr/share/dict/words to allow it to not be mac specific, (slightly different wc output to fedora) and added correct handling of letters already tried using letters_tried variable \$\endgroup\$ – 23vc Sep 18 '18 at 11:56
1
\$\begingroup\$
  1. Avoid working in the global namespace

    This makes maintenance of code a pain. Instead create a main or hangman function

  2. Don't use global instead make these variables parameters of you function

    def draw_man_and_show_word():
       global word
    

    Could be

    def draw_man_and_show_word(word):
    
  3. Don't make lines to long, they become really hard to read

    array=[[124,45,45,45,45,32,32,32],[124,32,32,32,124,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32],[124,32,32,32,32,32,32,32]]
    

    See how you need to scroll all the way to the right, this would be much more easy on the eyes

    HANGMAN =  [
                   ['|', '-', '-', '-', '-', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', '|', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                   ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
               ]
    

    I also changed it to their character form which makes it even more easy to read

    And made the variable name ALL_CAPS since this is a constant

  4. Use unpacking // Optional namedtuple

    man=[[4,2,79],[4,3,43],[3,3,47],[5,3,92],[4,4,124],[3,5,47],[5,5,92]]
    y=man[size][0]
    x=man[size][1]
    c=man[size][2]
    array[x][y]=c 
    size=size+1
    

    First I cleaned it up a bit

    HANGMAN_STAGES = [
                         [4, 2, 'O'],
                         [4, 3, '+'],
                         [3, 3, '/'],
                         [5, 3, '\\'],
                         [4, 4, '|'],
                         [3, 5, '/'],
                         [5, 5, '\\']
                     ]
    

    And we can unpack the values from the list of lists with this

    x, y, value = HANGMAN_STAGES[size]

    Alternatively you can use the namedtuple module to store information more visually

    The it would become

    from collections import namedtuple
    
    Stage = namedtuple('Stage', 'x, y, value')
    HANGMAN_STAGES = [
                         Stage(4, 2, 'O'),
                         Stage(4, 3, '+'),
                         Stage(3, 3, '/'),
                         Stage(5, 3, '\\'),
                         Stage(4, 4, '|'),
                         Stage(3, 5, '/'),
                         Stage(5, 5, '\\')
                     ]
    

    And you can call them like this

    stage = HANGMAN_STAGES[size]
    HANGMAN[stage.y][stage.x] = stage.value
    
  5. Make the guessed_letters a set()

    For fast lookup O(0)

  6. join() instead of manually appending chars

    for y in range(0,7):
        for x in range(0,7):
            print (chr(array[y][x]),end='')
        print()
    

    Could be rewritten as print('\n'.join(''.join(row) for row in draw_list))

    If we have changed the HANGMAN to contain chr instead of the ordinal values

  7. Instead of the find and replace methods you could have used enumerate

    pos=theword.find(letter)
    if (pos >= 0):
        tmpword=theword
        while (pos >= 0):
            word[pos]=letter
            tmpword=tmpword.replace(letter,'#',1)
            pos=tmpword.find(letter)
    

    Can be rewritten in the form of

    for idx, char in enumerate(theword):
        if char == letter:
            guessed_word[idx] = letter
    

    But strings can't be assigned... so you should set the data type of guessed_word to a list

  8. Lastly top it off with a if __name__ == '__main__'

Putting it all together

import random

HANGMAN =  [
                ['|', '-', '-', '-', '-', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', '|', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
                ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
           ]

HANGMAN_STAGES = [
                    [4, 2, 'O'],
                    [4, 3, '+'],
                    [3, 3, '/'],
                    [5, 3, '\\'],
                    [4, 4, '|'],
                    [3, 5, '/'],
                    [5, 5, '\\']
                 ]
SPACE = '_'

def print_man_and_word(draw_list, guessed_word):
    print('\n'.join(''.join(row) for row in draw_list))
    print()
    print(''.join(guessed_word))

def get_random_word():
    # Your logic here (I don't have linux atm ;)
    word_list = ['cheese', 'pepper', 'sausage']
    return random.choice(word_list)

def get_letter(guessed_letters):
    letter = ''
    while len(letter) != 1 or not letter.islower() or letter in guessed_letters:
        letter = input("Enter your choice of (single lowercase) letter:")
    return letter

def hangman(word=None, draw_list=HANGMAN):
    if word is None:
        word = get_random_word()

    guessed_word = [SPACE] * len(word)
    guessed_letters = set()
    tries = 0

    while True:
        print_man_and_word(draw_list, guessed_word)
        letter = get_letter(guessed_letters)
        guessed_letters.add(letter)

        if letter in word:
            for idx, char in enumerate(word):
                if char == letter:
                    guessed_word[idx] = letter
        else:
            x, y, value = HANGMAN_STAGES[tries]
            draw_list[y][x] = value
            tries += 1

        if all(char != SPACE for char in guessed_word):
            print_man_and_word(draw_list, guessed_word)
            print('\nYou got it!')
            exit()

        elif tries == len(HANGMAN_STAGES):
            print_man_and_word(draw_list, guessed_word)
            print('You lost! it is {}'.format(word))
            exit()    

if __name__ == '__main__':
    hangman()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.