Disclaimer
To preface this, I am new to programming and even newer to python, so my knowledge of the mechanics of the interpreter is very limited.
Explanation I am currently writing a pythonic code that simulates a text-based game of cheating hangman, which means the program changes the word to evade the player from guessing the correct word using "word families." The game requires three files: hangman.py, play_hangman.py, and dictionary.txt. In hangman.py, I have created a hangman class that contains the self-instance of the class, several methods for producing the necessary objects, and a play method that uses these methods. Then, in play_hangman.py, it calls the hangman instance, and the play method, and puts the play method into a while loop that repeats as long as the player enters "yes" to keep playing.
The Problem I have called the methods into the play function for executing the game. However, it gives me an error saying:
- "12 positional arguments: 'word_length', 'num_guesses',
'remaining_words', 'remainingWords', 'letters_already_guessed',
'askForWordLength', 'printGameStats', 'askForPlayerGuess',
'wordStatus', 'printCountOfRemainingWords', 'retreiveRemainingWords',
and 'askForNumberOfGuesses' "
These are the twelve variables and methods I have called in the def play(): function. I have researched that I need to call an object of the class before the method, which I attempted to do, but does not work. I am not sure how to avoid the error.
HANGMAN.PY
import re
class Hangman:
# hangman self method
def hangman(self):
self.hangman = Hangman() # object of the Hangman class
def words(self):
with open('dictionary.txt') as file: # opens dictionary text file
file_lines = file.read().splitlines() # reads and splits each line
all_words = [] # empty list to contain all words
valid_words = [] # empty list to contain all valid words
for word in file_lines: # traverses all words in the file lines
if len(word) >= 3: # accepts word if it has at least 3 letters
all_words.append(word) # appends accepted word to list
# list of all invalid characters in python
CHARACTERS = ["~", "`", "!", "#", "#", "$", "%", "^", "&", "*", "(",
")", "-", "_", "=", "+", "[", "]", "{", "}", "|", "\","
"", "'", "?", "/", ">", ".", "<", ",", "", ";", ":"]
for i in CHARACTERS: # traverse list of invalids
for word in all_words:
if i not in word: # if invalid character is not in word
valid_words.append(word) # accept and append to list
return valid_words # return list of valid words
def askForWordLength(self, valid_words):
word_lengths = [] # empty list for possible word lengths
for word in valid_words: # traverse list of valid words
length = word.__len__() # record length of current word
if (length not in word_lengths):
word_lengths.append(length) # accept and append to list
word_lengths.sort()
# inform user of possible word lengths
print('The available word lengths are: ' + str(word_lengths[0]) + '-'
+ str(word_lengths[-1]))
print()
# have user choose from possible word lengths
while(1):
try:
length = int(input('Please enter the word length you want: '))
if (length in word_lengths):
return length
except ValueError:
print('Your input is invalid!. Please use a valid input!')
print()
def askForNumberOfGuesses(self):
while(1):
try:
num_guesses = int(input('Enter number of guesses you want: '))
if (num_guesses >= 3):
return num_guesses
except ValueError:
print('Your input is invalid!. Please use a valid input!')
print()
def wordStatus(self, length):
status = '-'
for i in range(0, length):
status += '-'
return
def remainingWords(self, lines, length):
words = []
for word in lines:
if (word.__len__() == length):
words.append(word)
return words
def printGameStats(self, letters_guessed, status, num_guesses):
print('Game Status: ' + str(status))
print()
print('Attempted Guesses' + str(letters_guessed))
print('Remaining Guesses' + str(num_guesses))
def askPlayerForGuess(self, letters_guessed):
letter = str(input('Guess a letter: ')).lower()
pattern = re.compile("^[a-z]{1}$")
invalid_guess = letter in letters_guessed or re.match(pattern, letter) == None
if (invalid_guess):
while (1):
print()
if (re.match(pattern, letter) == None):
print('Invalid guess. Please enter a correct character!')
if (letter in letters_guessed):
print('\nYou already guessed that letter' + letter)
letter = str(input('Please guess a letter: '))
valid_guess = letter not in letters_guessed and re.match(pattern, letter) != None
if (valid_guess):
return letter
return letter
def retrieveWordStatus(self, word_family, letters_already_guessed):
status = ''
for letter in word_family:
if (letter in letters_already_guessed):
status += letter
else:
status += '-'
return status
def retrieveRemainingWords(self, guess, num_guesses, remaining_words,
wordStatus, guesses_num, word_length,
createWordFamiliesDict,
findHighestCountWordFamily,
generateListOfWords):
word_families = createWordFamiliesDict(remaining_words, guess)
family_return = wordStatus(word_length)
avoid_guess = num_guesses == 0 and family_return in word_families
if (avoid_guess):
family_return = wordStatus(word_length)
else:
family_return = findHighestCountWordFamily(word_families)
words = generateListOfWords(remaining_words, guess, family_return)
return words
def createWordFamiliesDict(self, remainingWords, guess):
wordFamilies = dict()
for word in remainingWords:
status = ''
for letter in word:
if (letter == guess):
status += guess
else:
status += '-'
if (status not in wordFamilies):
wordFamilies[status] = 1
else:
wordFamilies[status] = wordFamilies[status] + 1
return wordFamilies
def generateListOfWords(self, remainingWords, guess, familyToReturn):
words = []
for word in remainingWords:
word_family = ''
for letter in word:
if (letter == guess):
word_family += guess
else:
word_family += '-'
if (word_family == familyToReturn):
words.append(word)
return words
def findHighestCountWordFamily(self, wordFamilies):
familyToReturn = ''
maxCount = 0
for word_family in wordFamilies:
if wordFamilies[word_family] > maxCount:
maxCount = wordFamilies[word_family]
familyToReturn = word_family
return familyToReturn
def printCountOfRemainingWords(self, remainingWords):
show_remain_words = str(input('Want to view the remaining words?: '))
if (show_remain_words == 'yes'):
print('Remaining words: ' + str(len(remainingWords)))
else:
print()
def play(self, askForWordLength, askForNumberOfGuesses, remainingWords,
words, wordStatus, printCountOfRemainingWords, printGameStats,
askPlayerForGuess, retrieveRemainingWords):
MODE = 1
openSession = 1
while (openSession == 1):
word_length = askForWordLength(words)
num_guesses = askForNumberOfGuesses()
wordStatus = wordStatus(word_length)
letters_already_guessed = []
print()
game_over = 0
while (game_over == 0):
if (MODE == 1):
printCountOfRemainingWords(remainingWords)
printGameStats(remainingWords, letters_already_guessed,
num_guesses, wordStatus)
guess = askPlayerForGuess(letters_already_guessed)
letters_already_guessed.append(guess)
num_guesses -= 1
remainingWords = retrieveRemainingWords(guess, remainingWords,
num_guesses, word_length)
wordStatus = wordStatus(remainingWords[0], letters_already_guessed)
print()
if (guess in wordStatus):
num_guesses += 1
if ('-' not in wordStatus):
game_over = 1
print('Congratulations! You won!')
print('Your word was: ' + wordStatus)
if (num_guesses == 0 and game_over == 0):
game_over = 1
print('Haha! You Lose')
print('Your word was: ' + remainingWords[0])
print('Thanks for playing Hangman!')
Self as First Parameter
When creating functions inside classes, the first parameter should be 'self', as you've done in the function askForNumberOfGuesses. This happens because, when calling functions from an object, Python passes the object to 'self' so the method's logic can access its data.
When calling functions such as "play" (in which you haven't used the self parameter in the declaration), the first parameter will be handled as the self, even though it has a different name. So, actually, your code expects the variable 'words' to be a Hangman object.
Static Functions
If the function needs to know information about the object, you need to add the 'self' as the first parameter.
However, if your method does not use any data declared inside the Hangman class (such as self.hangman that you've created inside init) you can just add a "#staticmethod" to the line before the function definition. This way, Python will know that you don't need self and that the first parameter is not the object.
Notice that, if you want to use a static method, you can use the class itself to call the method. You don't even need to create an object:
Hangman.static_function_name(function_parameters) # calls static function
hangman_object = Hangman()
hangman_object.non_static_function_name(function_parameters) # calls non-static function
hangman_object.static_function_name(function_parameters) # calls static function using object (there is no problem)
Design Considerations
Also, I believe you should change your init implementation and use 'self' to store data that you use in more than one function, instead of passing arguments to all the functions and returning them.
Also note that, in order to create a Hangman object, you need to use ():
self.hangman = Hangman # reference to the Hangman class
self.hangman = Hangman() # Object of the Hangman class
However, if you create a Hangman object inside init, it will call init again and this will go on "forever". So, you shouldn't create a Hangman object inside it's init method. I believe you should create the Hangman object in play_hangman.py file and call the play method from there.
In my opinion, init should create all the data you need as self.<var_name>, load should prepare words or whatever you need using self.<functions_you_create> and play should just start the game. play_hangman.py shouldn't need to know all these parameters.
hangman.py
class GameClass:
def __init__(self):
self.words = list()
# TODO: Implement adding other game data
def load(self):
# TODO: Implement logic
self.words.append('country')
pass
def play(self):
# TODO: Implement loop logic
print(self.words)
# while(True):
# break
pass
play_hangman.py
game = GameClass()
game.load()
game.play()
IDE
Use a good IDE so it will remember you to add self if you are not used yet.
None of those methods have self as their first argument.
And that is what is used to define class methods and give access to attributes.
So, adding that "self" keyword is the first step to make your code work.
I would recommend installing a linter, it automatically identifies this kind of error for you:
I'm new to Python. This is my 3rd project and I'm facing to some obstacles. Here I have a Caesar cipher project. It seems to do everything I needed it to do that accepts only capital letters, no special characters, no lower case letters, no spaces.
However, I have two issues:
It MUST only accept numbers that range from 1 to 26. Unfortunately, it's also accepting numbers that are even higher than 26.
Regardless of a key size it only shifts letters by 1 digit. Ideally, it must shift the letters according to entered key size:
this is where problem(s) occurring
It would be tremendous help if anyone could provide a solution or give suggestions to fix above issues. Thank you so much for your time and attention!
Here is my code:
MAX_NUMBER_KEY = 26
def getMode():
while True:
print('Please make your selection from the following "E" for encryption or "D" for decryption:')
mode = raw_input()
if mode in 'E D'.split():
return mode
else:
print('Error! Please try again and make sure to choose only "E" or "D"!')
def getText():
while True:
print('Enter your text that you would like to encrypt or decrypt:')
text = raw_input()
if text.isalpha() and text.isupper():
return text
else:
print('Error! No spaces, no special characters or numbers! Please only use letters!')
def getKey():
key = 0
while True:
print('Enter your number key for "K" (1-%s)' % (MAX_NUMBER_KEY))
key = int(raw_input().isdigit())
if (key >= 1 and key <= MAX_NUMBER_KEY):
return key
else:
print('Error! Please try again and choose only numbers between (1-26)!')
def getEncryptedMessage(mode, text, key):
if mode[0] == 'D':
key = -key
encrypted = ''
for each in text:
if each.isalpha():
num = ord(each)
num += key
if each.isupper():
if num > ord('Z'):
num -= 26
elif num < ord('A'):
num += 26
encrypted += chr(num)
else:
encrypted += each
return encrypted
mode = getMode()
message = getText()
key = getKey()
print('Your encrypted message is:')
print(getEncryptedMessage(mode, message, key))
In getKey(), raw_input().isdigit() returns a boolean, so by casting it to an int, you are going to be doing int(True) or int(False) which is 1 and 0 respectively.
I have an encryption code, which consists of 26 letters, and an option which allows the user to change it if they wish. After trying out my program several times, I came across an error, a logical one. The user can change the code, but also, they can enter the same character 26 times or at least 1 character more than once which could ruin my entire program. Is there any way to only allow the user to type each letter exactly once? Here's what I have so far:
import tkinter
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
encryption_code = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
letters += letters.lower()
encryption_code += encryption_code.lower()
window = tkinter.Tk()
encrypt_entry = tkinter.Entry(window)
encrypt_entry.pack()
def code_change(event):
global encrypt_entry
global encryption_code
encryptget = encrypt_entry.get()
if len(encryptget) == 26:
print("You have changed the encryption code")
encryption_code = encryptget
encryption_code += encryption_code.lower()
enc = dict(zip(letters, encryption_code))
dec = dict(zip(encryption_code, letters))
elif len(encryptget) < 26 or len(encryptget) > 26:
print("Please enter 26 characters")
encrypt_entry.delete(0, tkinter.END)
window.bind('<Return>', code_change)
EDIT
I have tried the following but now if I try typing the alphabet or encryption_code the elif statement does nothing.
import tkinter
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
encryption_code = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
letters += letters.lower()
encryption_code += encryption_code.lower()
window = tkinter.Tk()
encrypt_entry = tkinter.Entry(window)
encrypt_entry.pack()
def code_change(event):
global encrypt_entry
global encryption_code
encryptget = encrypt_entry.get()
if len(set(encryptget)) == 26 and encryptget != encryption_code and encryptget != letters:
print("You have changed the encryption code")
encryption_code = encryptget
encryption_code += encryption_code.lower()
enc = dict(zip(letters, encryption_code))
dec = dict(zip(encryption_code, letters))
elif len(set(encryptget)) != 26 and encryptget == encryption_code and encryptget == letters:
print("Please enter each character exactly once")
encrypt_entry.delete(0, tkinter.END)
window.bind('<Return>', code_change)
Tkinter has a feature specifically for this sort of validation. You're able to have it call a function for every insertion, and and this function can either accept or reject that insertion based on whatever criteria you want.
In your case the criteria is "no duplicate characters". An easy way to determine that is to convert the string to a set (by definition, a set has no duplicates), and then compare the length of the set to the length of the string.
To call this function each time the user presses a key, set the validate and validatecommand options of the entry widget. There's an extra step where you have to register the command, which tells tkinter which of several special arguments you want your command to receive.
The solution looks something like this:
# Define a command to be run whenever the user edits the entry
# N.B. d will be "1" for an insert, "0" for delete, and "-1"
# for anything else. P will be the value of the entry if this
# edit is allowed.
#
# Note: this function must always return either True or False
def encryption_validation(d, P):
if d == "1": # ie: an insert
unique_chars = set(P)
if len(P) > len(unique_chars):
return False
return True
# register the command with tkinter
vcmd = (window.register(encryption_validation), '%d', '%P')
# configure the entry widget to use this command
encrypt_entry.configure(validate="key", validatecommand=vcmd)
With the above, it will be impossible for the user to enter any character twice. Note that this solution also prevents the user from pasting a string with duplicate characters.
For a more exhaustive explanation of entry validation, see this answer on stackoverflow: https://stackoverflow.com/a/4140988/7432
Correct me if I'm mistaken, but it sounds like you just want to make sure that the string of characters the user inputs for the code don't contain any duplicates?
I don't personally know about the validation commands, but I think this could work to achieve your goal:
def IsCodeValid(encryption_code):
c = [0]*26
pos = 0
for i in range(len(encryption_code)):
pos = ord(encryption_code[i])-ord('A') #find placement relative to A in unicode
c[pos] = c[pos] + 1 #increment counter for that letter
j = 0
ValidCode = True
while j<26 and ValidCode:
if c[j]>1: #check to see if any letter occurred more than once
ValidCode = False
else:
j += 1
return ValidCode
Mind you, this also expects that all letters are entered capitalized. But you can fix that by normalizing the data before accepting it. Alternatively, you could complicate the logic to check both upper & lower case.
Edit: This is assuming that you don't want the code to run if the encryption_code is invalid, you could use this flag to request a new encryption_code from the user before running the rest of the program.
I'm building a simple 'quiz-program'. Code here:
import random
wordList1 = []
wordList2 = []
def wordAdd():
wordNew1 = str(input("Add a word to your wordlist: "))
wordNew2 = str(input("Add the translation to this word: "))
if wordNew1 != "exit":
wordList1.append(wordNew1)
wordAdd()
elif wordNew2 != "exit":
wordList2.append(wordNew2)
wordAdd()
else:
exercise()
def exercise():
q = random.choice(wordList1)
a = wordList2
if q[] == a[]:
print("Correct!")
else:
print("Wrong!")
wordAdd()
I'm trying to check the wordList1-number and compare it with the wordList2-number.
Now I didn't expect the def exercise to work but I can't find the solution to let it work...
I know about the dictionary-thing in Python but I would like to know wether such a array-construction is possible in Python.
Could someone help me with this?
Thanks in advance! Sytze
I played with your code a little. I'm not sure I perfectly understand your question, but I made it working the way I thought it needs to work. I added some comments to make it clear what I did.
I tried to stick with your basic concept (except the recursion), but I renamed a lot of things to make the code more readable.
import random
words = []
translations = []
def add_words():
# input word pairs until the user inputs "exit"
print('\nInput word and translation pairs. Type "exit" to finish.')
done = False
while not done:
word = raw_input("Add a word to your wordlist: ")
# only input translation, if the word wasn't exit
if word != "exit":
translation = raw_input("Add the translation to this word: ")
if word != "exit" and translation != "exit":
# append in pairs only
words.append(word)
translations.append(translation)
else:
done = True
def exercise():
# excercising until the user inputs "exit"
print("\nExcercising starts. ")
done = False
while not done:
# get a random index in the words
index = random.randrange(0, len(words))
# get the word and translation for the index
word = words[index]
translation = translations[index]
# ask the user
answer = raw_input('Enter the translation for "%s": ' % word)
if answer == "exit":
done = True
print("\nGoodbye!")
elif answer == translation:
print("Correct!")
else:
print("Wrong!")
add_words()
exercise()
I'm trying to replace the letters in key with the letters in alpha (and vice versa):
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = "XPMGTDHLYONZBWEARKJUFSCIQV"
I am trying to change a string to become encoded (and the other way around), so say "Hello" would become "LTZZE". Any idea how to do this? This is my current code:
usrInput = 0
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = "XPMGTDHLYONZBWEARKJUFSCIQV"
def menu():
print "SECRET DECODER MENU"
print ""
print "1) Quit"
print "2) Encode"
print "3) Decode"
usrInput = raw_input("What would you like to do?")
return usrInput
def encodeWord():
plain = plain.upper()
length = len(plain)
encode = plain.encode(alpha, key)
return encode
def decodeWord():
coded = coded.upper()
length = len(coded)
decode = coded.decode(key, alpha)
return decode
def main():
keepGoing = True
while keepGoing:
usrInput = menu()
if usrInput == "2":
plain = raw_input("Text you want to be encoded: ")
encodeWord()
print encode(plain)
elif usrInput == "3":
coded = raw_input("Code you need to be decyphered: ")
decodeWord()
print decode(coded)
elif usrInput == "1":
print "Thanks for doing super secret spy stuff with me. No one seems to want to anymore. Goodbye. ):"
keepGoing = False
else:
print "I don't know what to do! Ahhh!"
main()
Note this is a homework assignment for a computer science class. I created the assignment, and I'm aware it's on stack overflow. If you turn it in as your own work, I will know. You will earn a zero for the assignment and we will begin academic misconduct proceedings.
(If you're playing along at home, this is indeed a string manipulation assignment, and explicitly NOT to be considered a good cryptographic practice. We also are not allowing maketrans() for this assignment, because it's a string manipulation and function exercise for beginning programmers.)
If you're really desperate for help, come see me or one of the recitation leaders we're paying to help you.
Use str.maketrans and str.translate. If you use Python 2 this functions are in string (here (maketrans) and here (translate)).
Example (python 3):
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = "XPMGTDHLYONZBWEARKJUFSCIQV"
enc = str.maketrans(alpha, key)
usrInput = 'HELLO'
print(usrInput.translate(enc))
Example (python 2)
import string
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = "XPMGTDHLYONZBWEARKJUFSCIQV"
enc = string.maketrans(alpha, key)
inp = 'HELLO'
print string.translate(inp, enc)
Output:
LTZZE