Skip to content

EnricoBilla/hangman

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

hangman

One-liner hangman in python

Why?

They challenged me, I had to

How?

I started with a basic idea, which you can see here:

word = "xylophone"
guess = ""
count = 5

for _ in range(count):
    a = input()
    if len(a) == 1:
        guess = guess + a
    else:
        print("Yass" if a == word else "Nope")
    print(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n")

word contains the word the player has to guess, count contains the number of errors the player can make and guess is the list of characters the player guessed.

This had two main problems:

  • you had to guess the specific word to win, guessing all characters of the word won't make you win
  • even correct characters are considered wrong when counting errors (but I found it later)

But, most importantly it wouldn't end right after you guessed the right word, but you have to use all your remaining guesses to end the game; so let's fix this now.

word = "xylophone"
guess = []
count = [False] + [True]*5

while count.pop():
    print("Yass" if (guess.append(input(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n")) or (guess[-1]==word and (count.sort(reverse=True) or True))) else "Retry")

Instead of having a for that iterates a fixed number of times, now I'm using a while loop that pops the last element of the list count, which is now a list of booleans, once all Trues are popped the remaining element is False so the while loop ends.

Now guess, instead of being a string, is a list of strings so that every input by the user is in a different item. This way I can check whether the last input was the correct word separately.

I also edited things in the loop, instead of having separate input() and print() I exploited the fact that input can also print to do both in one single line.

In this last snippet, I also used a short-circuit evaluation to do multiple things in one line, which is basically the idea behing the all program; you'll see later. After the player wrote his guess, the input is appended to guess through list.append(), which doesn't return anything (void functions in python return None, which is falsy).

That means that to evaluate the expression the program has also to evaluate the next condition after the or, which is guess[-1]==word and (count.sort(reverse=True) or True). Having an and as operator, both conditions here must be True, so if the last guess of the player isn't equal to word the program would skip the other expressions, having already got a False result. If the player guesses correctly, the count list would sort in descending order (False at the end); list.sort() does not return so I added a or True in order to print the You won string.

Now I wanted to fix the fact that in order to win the player had to guess the whole word and it wasn't enough to guess all the characters:

word = "xylophone"
guess = []
count = [False] + [True]*10

while count.pop():
    print("Congrats, it was {}".format(count.sort(reverse=True) or word) if (guess.append(input(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n")) or guess[-1]==word or set(guess).issuperset(word[1:-1])) else ("Retry" if count[-1] else "It was {} :(".format(word)))

Here, I just added the set(guess).issuperset(word[1:-1]) which checks whether all characters (excluding first and last) have been guessed by the player. The only other thing I did is to move the list.sort(...) thing inside the .format() of the winning string for readablity (yes, I said readability lol).

Time to get rid of the last problem, the wrong counting of errors:

word = "xylophone"
guess = []
count = [False] + [True]*10

while count[-1]:
    print("Congrats, it was {}".format(count.sort(reverse=True) or word) if (guess.append(input(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n")) or ((guess[-1] in word[1:-1] or guess[-1] in guess[:-1] or count.pop()) and False) or guess[-1]==word or set(guess).issuperset(word[1:-1])) else ("Retry" if count[-1] else "It was {} :(".format(word)))

Before, every input was considered an error, but only wrong characters should be considered error. To fix this instead of popping the list every cycle of the while loop I decided to call .pop() only if conditions are met later in the program.

So, I added to the boolean expression guess[-1] in word[1:-1] or guess[-1] in guess[:-1] or count.pop() all anded with a False, to not modify the whole meaning of that boolean expression. That line pops the count list only if the guessed characters is not in the word and if that same guess had not been guessed previously.

Well, now it's finished, right? No, I have been able to do all this in only 5 lines, 3 of which are only variables initialisation and 2 are the program itself. I can do better.

var = ["xylophone", [], [False] + [True]*10]

while var[2][-1]: print("Congrats, it was {}".format(var[2].sort(reverse=True) or var[0]) if (var[1].append(input(''.join(var[0][i] if (var[0][i] in var[1] or i==0 or i==len(var[0])-1) else "_" for i in range(len(var[0])))+"\n")) or ((var[1][-1] in var[0][1:-1] or var[1][-1] in var[1][:-1] or var[2].pop()) and False) or var[1][-1]==var[0] or set(var[1]).issuperset(var[0][1:-1])) else ("Retry" if var[2][-1] else "It was {} :(".format(var[0])))

Since inside the loop there is only one method called, it can be put on the same line of the while. Also, it's possible to use a single list to contain all the three variables; doing so I ended up having only two lines of code.

Now the last challenge, write the two lines as a single line. The first thing that came to my mind was to put the while inside a lambda and call it with variables as parameters. Easy no? No, obviously! In fact, in python while is a statement and lambdas cannot contain statement... I had to find another solution, and the only thing I came up to was converting the while into something else that could be repeated until a condition is met; I tried map(), filter() and other things. The only thing that could work is using an inline for.

So I stepped back, and edited the code to work with a different type of loop:

word = "xylophone"
guess = []
count = [False] + [True]*10

for _ in count:
    print("", end=(("Congrats, it was {}\n".format(count.sort(reverse=True) or word) if (guess.append(input(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n> ")) or (count.insert(0, False) and False) or ((guess[-1] in word[1:-1] or guess[-1] in guess[:-1] or count.pop()) and False) or guess[-1]==word or set(guess).issuperset(word[1:-1])) else ("You lose, it was {} :(\n".format(word) if not count[-1] else "")) if count[-1] else ""))

I decided to cycle every element of the count list, and every cycle I add a new element at the start of the list with the same basic trick in the boolean expression: count.insert(0, False) and False. Maybe and False is not necessary since list.insert() is void but I wanted to be sure :). Now I have an infinite loop, just gotta add a terminating condition, so I moved the condition from the old while to an if inside the print, if the condition is not met, just print nothing.

The point is that by default print() prints a new line at the end, but the behaviour can be set using the end parameter of the print() function, so I had to create a condition to decide whether I wanted a new line. Instead of doing that I moved everything to the end parameter, and leaving the first parameter of print() empty.

Now that I have a for with only a method inside of it I can put everything inside a lambda and call it directly with the parameters I want instead of creating a new variable:

(lambda word, guess, count: [print("", end=(("Congrats, it was {}\n".format(count.sort(reverse=True) or word) if (guess.append(input(''.join(word[i] if (word[i] in guess or i==0 or i==len(word)-1) else "_" for i in range(len(word)))+"\n> ")) or (count.insert(0, False) and False) or ((guess[-1] in word[1:-1] or guess[-1] in guess[:-1] or count.pop()) and False) or guess[-1]==word or set(guess).issuperset(word[1:-1])) else ("You lose, it was {} :(\n".format(word) if not count[-1] else "")) if count[-1] else "")) for _ in count])("xylophone", [], [False] + [True]*10)

And here is the final result, a one-liner hangman!

About

One-liner hangman in python

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages