6

I'm working on writing a simple Hangman game in Python from what I know so far (I'm doing Learn Python the Hard Way) and so far I have this:

from sys import argv
import random

script_name, dict_file = argv

hang_list = open(dict_file).read().splitlines()
hang_list = filter(None, hang_list) 
word = random.choice(hang_list)

guesses = ''

def compare_words():
    global guesses
    new_word = ''
    for char in word:
        if char in guesses: 
            new_word += char
        else:
            new_word += "_"
    return new_word

def test_letter():
    global guesses
    letter = raw_input("Guess a letter: ")
    guesses += letter
    new_word = compare_words()
    print "\nCurrent guesses: %s" % guesses
    print "%s\n\n" % new_word
    if new_word == word:
        print "You won!"
    else:
        test_letter()


test_letter()

I've yet to implement the scoring system (piece of cake) but I have an issue with the layout. As you can tell, this will print "Current guesses: " and the new word each time; however, what I want is four lines that look like:

Guess a letter:
Guesses: abczy
__c__b_

And have those three lines keep updating. However, I am having trouble figuring out how to make the print replace stdout. I believe I need to use the \r escape character, yet I've tried placing that in various places but can't get it to work. So, how should I modify this to get it to replace? I would prefer not to just clear, as then it still makes things a bit messy; I want to just replace what's there. Thanks!

steelcowboy
  • 105
  • 6
  • possible duplicate of [Python Progress Bar](http://stackoverflow.com/questions/3160699/python-progress-bar) – tmdavison Jun 14 '15 at 23:22
  • Do you need to use stdout for some reason (including just to learn it) or can you just use a system call to `os.system("clear")` or `os.system("cls")`? For just three lines and one user input this is typically easier than stdout – LinkBerest Jun 14 '15 at 23:28
  • I believe you can't replace what is on the screen with Python. However, you can do `print '\n'*100` then reprint the whole thing with the modifications. [Related](http://stackoverflow.com/questions/5426546/in-python-how-to-change-text-after-its-printed) – jkd Jun 14 '15 at 23:29
  • Would I have more luck if I tried to implement this game using the curses interface? (this is on Linux btw) – steelcowboy Jun 15 '15 at 01:14

4 Answers4

2

It would be a bit tricky to make this work for all terminals, but if yours understands ANSI escape codes like mine does, this might work:

...
    if new_word == word:
        print "You won!"
    else:
        print '\033[F'*7
        print ' '*17 + '\b'*17 + '\033[F'
        test_letter()

This relies on the ANSI code F: move the cursor up one line; backspaces (\b) alone have no effect once the beginning of the line is reached. The first print takes you back up to the input line and the second deletes the character that was previously entered.

xnx
  • 24,509
  • 11
  • 70
  • 109
1

You can use the escape characters \033c and this will erase the code in a terminal window and put the cursor at the top left.

For example this code:

    import time

    print("text 1")
    time.sleep(1)
    print('\033c')
    time.sleep(1)
    print("text 2")

This code will print "text 1" wait one second, clear the console, wait one second and then print "text 2".

So you could use the code

def test_letter():
  print("\033c")
  global guesses
  letter = raw_input("Guess a letter: ")
  guesses += letter
  new_word = compare_words()
  print "\nCurrent guesses: %s" % guesses
  print "%s\n\n" % new_word
  if new_word == word:
     print "You won!"
  else:
    test_letter()

What this code will do is clear the console, ask the person to guess a number, display that four line piece of code that you wanted and then clear the console again.

I hope this helps!

Alex Hawking
  • 1,125
  • 5
  • 19
  • 35
1

If you want to replace the content of a specific line, from a specific position, you can use ANSI Escape Codes. To do this, make sure that you're using stdout.write() rather than print(). You can access this method by using the following import statement:

from sys import stdout

Then, in order to navigate the "cursor" (where text printed with this method will go), use the escape code \u001b[<L>;<C>H (or \u001b[<L>;<C>F where <L> and <C> represent the respective line number and character index of the desired position. For example, if you wanted to set the cursor to line 3; character 2, you would do the following.

stdout.write(u"\u001b[3;2H")

Note the u proceeding the double-quoted string. This is required in Python 2.x, since it contains special characters, but can be omitted in Python 3 and above.

Once you have set the cursor to be at the desired position, anything you write will replace the characters that currently reside there. This is important, because if the replacement string is shorter than the original, you may end up with trailing legacy characters. The simplest way to deal with this is to pad the printing string in spaces.

After doing this you should probably move the cursor back to the end of stdout, using the same method, and flush the output with stdout.flush().

Example

Let's say I had the following output on the terminal:

Name: Shakespeare
Score: 0

Some text...

I could change the score to 1 by running the following:

stdout.write(u"\u001b[2;8H")
stdout.write("1")
stdout.write(u"\u001b[5;0H")
stdout.flush()

Again, the u is optional in Python 3 and up.

Notes

This line-and-character-number method applies to all output currently being displayed in the terminal. This means that if you have anything left from another program or command, for example

$ python game.py

so it is best to clear the output at the start of your program, with something like print(u"\033c"), or os.system("clear"), otherwise you may end up writing to the wrong line.

Also, if your going to use stdout.write() anywhere else, remember to put \n at the end if you want to go to the next line.

James Jensen
  • 518
  • 6
  • 19
0

The \r character is a carriage return, which means it will return the cursor to the start of the current line. That's OK if you want to redraw the line the cursor is on, but no good if you want to redraw other lines.

To do what you want, you need to use a terminal library like curses on Linux or the console API on Windows. If you are just working on Linux and want a simpler way to access colours, cursor movement and input without echo, you could do worse than try out blessed (https://pypi.python.org/pypi/blessed/).

If you need a cross platform API to do this sort of thing, there is no pure Python way to handle it all yet, but I am working on one. The Screen class in https://github.com/peterbrittain/asciimatics cover all the features above in a cross-platform manner.

Peter Brittain
  • 13,489
  • 3
  • 41
  • 57