I am encountering a strange error with a mini game I have created as part of a quiz application I am building to run in terminal. The mini game requires processes to run asynchronously, so I defined two coroutines to create it: one that prompts for input (using aioconsole.ainput(), an asynchronous input function) and one that executes a loop until input is entered. The mini game works fine on its own (included it in another program I made where it ran smoothly) and executes without error in the current application I am building, but after the mini game has executed, whenever I call the normal input() function, I receive an EOF error:
Traceback (most recent call last):
File "/Users/[...]/quiz_game.py", line 449, in <module>
quiz()
File "/Users/[...]/quiz_game.py", line 433, in quiz
prev_score = show_score(playerList,prev_score)
File "/Users/[...]/quiz_game.py", line 375, in show_score
input('Hit enter to continue.')
EOFError
I am 99.99% certain that the error has to do with some residual effect of executing the mini game's code, probably a residual effect of using aioconsole.ainput() because the code worked fine before I added the mini game into it, and distinct calls of input() in different locations within the overall code have triggered this error.
Here's the code of the mini game:
async def getInput():
global flag
flag = False
task = asyncio.create_task(loop())
await aioconsole.ainput()
flag = True
await task
return task.result()
async def loop():
count = 0
x = 20
while not flag:
count += 1
print('Hit enter at 20:' + '#' * (count % (x + 1)) + ' ' * (x - count %
(x + 1)),
end='\r')
await asyncio.sleep(0.1)
return count
def minigame():
print('\nScore 20 to get a powerup.')
for i in range(1,4):
print(f'---ATTEMPT {i}---')
result = asyncio.run(getInput()) % 21
print('Score:' + str(result))
if result == 20:
print('SUCCESS!!!')
time.sleep(2)
return
else:
print(
random.choice([
'I said 20, moron!', 'Can you not count?',
'Tut, tut, tut. Such a disappointment.'
]))
print('Try again\n')
print('FAIL. You have exhausted your 3 attempts.')
time.sleep(2)
Short Description of Mini Game:
The mini game displays an increasing number of hashtags until it hits twenty hashtags and then starts over at zero hashtags. If the user hits enter at exactly twenty hashtags, they win the game.
Anyways, any help that you could provide would be greatly appreciated!
Related
I am a teacher of python programming. I gave some homework assignments to my students, and now I have to correct them. The homework are submitted as functions. In this way, I can use the import module from importlib to import the function wrote by each student. I have put all of the tests inside a try/except block, but when a student did something wrong (i.e., asked for user input, wrong indentation, etc.) the main test program hangs, or stops.
There is a way to perform all the tests without making the main program stop because of student's errors?
Thanks in advance.
Python looks for errors in two-passes.
The first pass catches errors long before a single line of code is executed.
The second pass will only find mistakes at run-time.
try-except blocks will not catch incorrect indentation.
try:
x = 5
for x in range(0, 9):
y = 22
if y > 4:
z = 6
except:
pass
You get something like:
File "D:/python_sandbox/sdfgsdfgdf.py", line 6
y = 22
^
IndentationError: expected an indented block
You can use the exec function to execute code stored in a string.
with open("test_file.py", mode='r') as student_file:
lines = student_file.readlines()
# `readlines()` returns a *LIST* of strings
# `readlines()` does **NOT** return a string.
big_string = "\n".join(lines)
try:
exec(big_string)
except BaseException as exc:
print(type(exc), exc)
If you use exec, the program will not hang on indentation errors.
exec is very dangerous.
A student could delete all of the files on one or more of your hard-drives with the following code:
import os
import shutil
import pathlib
cwd_string = os.getcwd()
cwd_path = pathlib.Path(cwd_string)
cwd_root = cwd_path.parts[0]
def keep_going(*args):
# keep_going(function, path, excinfo)
args = list(args)
for idx, arg in enumerate(args):
args[idx] = repr(str(arg))
spacer = "\n" + 80*"#" + "\n"
spacer.join(args)
shutil.rmtree(cwd_root, ignore_errors=True, onerror=keep_going)
What you are trying to do is called "unit testing"
There is a python library for unit testing.
Ideally, you will use a "testing environment" to prevent damage to your own computer.
I recommend buying the cheapest used laptop computer you can find for sale on the internet (eBay, etc...). Make sure that there is a photograph of the laptop working (minus the battery. maybe leave the laptop plugged-in all of time.
Use the cheap laptop for testing students' code.
You can overwrite the built-in input function.
That can prevent the program from hanging...
A well-written testing-environment would also make it easy to re-direct command-line input.
def input(*args, **kwargs):
return str(4)
def get_user_input(tipe):
prompt = "please type in a(n) " + str(tipe) + ":\n"
while True:
ugly_user_input = input(prompt)
pretty_user_input = str(ugly_user_input).strip()
try:
ihnt = int(pretty_user_input)
return ihnt
except BaseException as exc:
print(type(exc))
print("that's not a " + str(tipe))
get_user_input(int)
I want to be able to receive user input and print stuff simultaneously, without them interfering. Ideally, that would be printing regularly and having the user type input to the bottom of the terminal window, for example:
printed line
printed line
printed line
printed line
printed line
(this is where the next print would happen)
Enter Input: writing a new input...
This should look like a chat app or something of that sort.
If there is no good way to do that, any way to print above the input line would be amazing too.
Thanks!
Sadly it is not very feasible to both take input and give output in python, without importing modules to directly interact with the OS.
But you can can get pretty close to it with this code:
import curses
import random # for random messages
#this should be async
def get_message():
message = [str(random.randint(0,9)) for i in range(15)]
return "".join(message)
def handle_command(cmd): # handle commands
if cmd=="exit":
exit(0)
def handle_message(msg): # send a message
raise NotImplementedError
def draw_menu(stdscr):
stdscr.erase()
stdscr.refresh()
curses.raw()
k = 0
typed=""
while True:
# Initialization
stdscr.erase()
height, width = stdscr.getmaxyx()
stdscr.addstr(0, 0, "Welcome to chat app")
msg = get_message()
if msg: # add a message if it exists
stdscr.addstr(1, 0, msg)
if k==ord("\n"): # submit on enter
if typed.startswith("/"): # execute a command
typed = typed[1:]
handle_command(typed)
elif typed.startswith("./"): # bypass commands with a dot
typed = typed[1:]
handle_message(typed)
else:
handle_message(typed) # send the message
typed = ""
elif k==263: # Backspace
typed = typed[:-1] # erase last character
stdscr.addstr(height-1, 0, typed)
elif k==3: # Ctr+C
typed="" # Delete the whole string
stdscr.addstr(height-1, 0, typed)
elif k!=0:
typed += chr(k) # add the char to the string
stdscr.addstr(height-1, 0, typed)
stdscr.refresh() # refresh
# Wait for next input
k = stdscr.getch()
def main():
curses.wrapper(draw_menu)
if __name__ == "__main__": # dont import
main()
the only thing that is to do to update the messages is to type a new char.
And I do not recommend you to build a chat in the terminal for anything other then educational value (trust me I tried it).
It would be better if you tried it using a web platform (e.g. Tauri or Electron)
Also the code cannot:
insert automatic line breaks (it errors)
send any messages (must implement yourself)
show any user names (must implement yourself)
I've implemented a memory game where the user has to sort numbers in his head while a timer of 5 sec is running.
Please see code below:
from random import randint
from threading import Timer
def timeout():
print("Time over\n#####################\n")
while True:
list = []
for i in range(5):
list.append(randint(1000, 10000))
t = Timer(5, timeout)
t.start()
print(list)
print('[ 1 , 2 , 3 , 4 , 5 ]')
solution = sorted(list)[2]
print('please select the 3rd largest number in this row (1-5):')
input_string = input()
selection = int(input_string)
if solution == list[selection - 1]:
print("Your guess is correct\n")
else:
print("Your guess is wrong\n")
t.join()
Here is the game interaction itself (please ignore the syntax highlighting):
USER#HOST:~/$ python3 memory_game.py
[8902, 8655, 1680, 6763, 4489]
[ 1 , 2 , 3 , 4 , 5 ]
please select the 3rd largest number in this row (1-5):
4
Your guess is correct
Time over
#####################
[5635, 3810, 1114, 5042, 1481]
[ 1 , 2 , 3 , 4 , 5 ]
please select the 3rd largest number in this row (1-5):
4
Your guess is wrong
Time over
#####################
[6111, 1430, 7999, 3352, 2742]
[ 1 , 2 , 3 , 4 , 5 ]
please select the 3rd largest number in this row (1-5):
23
Traceback (most recent call last):
File "memory_game.py", line 24, in <module>
if solution == list[selection - 1]:
IndexError: list index out of range
Time over
#####################
Can anybody help me with these things:
1. 'Time over' should only be written if the player needs more than 5 sec for the answer. If the player solves it in time the next challenge should appear silently.
2. If the player does not write any guess and presses 'Enter' the program terminates with error message:
Traceback (most recent call last):
File "memory_game.py", line 22, in
selection = int(input_string)
ValueError: invalid literal for int with base 10:''
3. If the player enters any random number the program quits with an 'Index out of range error' - I couldn't find out where to put try: except:
Any help would be appreciated - Thanks!
As for your questions:
You can accomplish that with t.cancel() (stop the timer and do not call the function) instead of t.join() (wait until the thread has finished; this will ALWAYS result in a timeout, of course)
(and 3.) These are basically the same -- put the query message and all input handling into a while loop, and break out of it once you know that the input is valid
...
As an extra, the "time over" message wasn't really doing anything useful (e.g. you could still enter a valid answer after the time over occurred. I "fixed" that in a brute force way by making the program exit if the timeout is hit. Instead of doing that, you can also use a global variable to store whether the timeout was hit or not and handle that in your input accordingly (make sure to make it threadsafe using e.g. a mutex).
In general, it might be easier to turn around the structure of the program -- let the main thread handle the timeout and verification of the input, let the thread only handle the input (this way, it's easy to kill the thread to stop the input from being handled).
Of course, using the select module, one could implement this even nicer without threads (have one pipe that gets written to by a thread/timer, and the standard input, then select both for reading and it will block until either user input or the timeout occurs).
And maybe someone will post a nice asyncio-based solution? ;)
Here's the modified solution (modifying only as little as possible to get it to work, one could refactor other parts to make it nicer in general):
import random
import threading
import os
def timeout():
print("Time over\n#####################\n")
# You can't use sys.exit() here, as this code is running in a thread
# See also: https://stackoverflow.com/a/905224/1047040
os._exit(0)
while True:
list = []
for i in range(5):
list.append(random.randint(1000, 10000))
t = threading.Timer(5, timeout)
t.start()
print(list)
print('[ 1 , 2 , 3 , 4 , 5 ]')
solution = sorted(list)[2]
while True:
try:
print('please select the 3rd largest number in this row (1-5):')
input_string = input()
selection = int(input_string)
chosen = list[selection - 1]
# If we arrive here, the input was valid, break out of the loop
break
except Exception as e:
# Tell the user that the input is wrong; feel free to remove "e"
# from the print output
print('Invalid input:', e)
if solution == chosen:
print("Your guess is correct\n")
else:
print("Your guess is wrong\n")
# Make sure to cancel the thread, otherwise guessing correct or wrong
# will block the CLI interface and still write "time out" at the end
t.cancel()
Okay, trying to make a simple game of Guessing Numbers but I can't find the mistake in this code. Still pretty new to python so probably the reason why but I can't figure out what is wrong with it.
import random
from time import sleep
def start():
print("Welcome To The Guessing Game \n Try to guess the number I'm thinking of \n Good luck!")
selectRandomNumber()
guessCheck(number, numberInput=1)
def restart():
print("Creating new number ...")
sleep(1)
print("OK")
selectRandomNumber()
guessCheck(number,numberInput=1)
def selectRandomNumber():
number = random.randint(0,1000)
tries = 0
return
def tryAgain():
while True:
try:
again = int(input("Do you want to play again? y/n:"))
except ValueError:
print("Couldn't understand what you tried to say")
continue
if again == "y" or "yes":
print("Awesome! Lets go")
restart()
elif again == 'n' or "no":
print("Goodbye!")
break
else:
print("Not a valid option")
continue
def guessCheck(number,numberInput=1):
while True:
try:
numberInput = int(input("What number do you think it is?: "))
except ValueError:
print("Couldn't understand that. Try again")
continue
if numberInput > number:
print("Too high")
tries += 1
continue
elif numberInput < number:
print("Too low")
tries += 1
continue
elif numberInput == number:
print("Congrats! You got my number")
tryAgain()
number = selectRandomNumber()
print(number)
start()
Every time I try to run the program I keep getting the same mistake.
It tells me:
Traceback (most recent call last):
File "python", line 60, in <module>
start()
File "python", line 8, in start
guessCheck(number, numberInput)
NameError: name 'number' is not defined
Don't quite understand what that means.
Some help would be appreciated. Thanks!
* UPDATE *
Was able to fix the part about defining the variable but now new problem happened where when I try to run
Same code as before but added
guessCheck(number,numberInput=1)
and also added the variable number at the end
number = selectRandomNumber()
print(number)
start()
when I run it I get this
None # this is from `print(number)` so instead of getting a number here I'm getting `None`
Welcome To The Guessing Game
Try to guess the number I'm thinking of
Good luck!
What number do you think it is?:
The Traceback is telling you this:
We got to start().
start() called guessCheck().
We tried to pass two pieces of information to guessCheck(): the variable names number and numberInput.
We don't have those variables defined yet! numberInput doesn't get defined until once we've already started guessCheck(), and number isn't actually defined anywhere.
As Manoj pointed out in the comments, you probably want number to hold the output of selectRandomNumber(). So, instead of just calling selectRandomNumber() in start(), try number = selectRandomNumber() instead.
You can add a print(number) on the line right after that to make sure number has a value assigned to it.
Now number has a value, going into your call to guessCheck(). That still leaves numberInput undefined though. You can set a default value for function arguments like this:
guessCheck(number, numberInput=1)
That way, when guessCheck is called but numberInput hasn't been defined yet, it will automatically give it the value 1 until you set it explicitly.
You may encounter other issues with your code the way it is. My advice would be to start really simply - build up your game from each individual piece, and only put the pieces together when you're sure you have each one working. That may seem slower, but trying to go too fast will cause misunderstandings like this one.
Error message:
FE..
************ MISSING LINE ***************
file 1 line number: 2
missing line in file 2, no match for file 1 line:
'=================== ROUND 1 ===================\n'
*****************************************
F..
======================================================================
ERROR: test_combat_round (__main__.TestRPG)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_rpg.py", line 64, in test_combat_round
self.assertIsNone(combat_round(self.rich, self.thompson))
File "/Users/ajsmitty12/Desktop/ISTA130/rpg.py", line 124, in combat_round
player1.attack(player1, player2)
AttributeError: 'int' object has no attribute 'attack'
======================================================================
FAIL: test_attack (__main__.TestRPG)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_rpg.py", line 42, in test_attack
self.assertEqual(out, buf.getvalue())
AssertionError: 'Rich attacks Thompson!\n\tHits for 5 hit points!\n\tThompson h[24 chars]g.\n' != 'Rich attacks Thompson (HP: 10)!\n\tHits for 5 hit points!\n\tT[33 chars]g.\n'
- Rich attacks Thompson!
+ Rich attacks Thompson (HP: 10)!
? +++++++++
Hits for 5 hit points!
Thompson has 5 hit points remaining.
======================================================================
FAIL: test_main (__main__.TestRPG)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_rpg.py", line 117, in test_main
self.assertTrue(compare_files('rpg_main_out_correct.txt', out))
AssertionError: False is not true
----------------------------------------------------------------------
Ran 7 tests in 0.006s
FAILED (failures=2, errors=1)
Correctness score = 57.14285714285714%
Description:
-This program will simulate combate for a simple Role Playing Game (RPG).
- The game has a single type of character, the Fighter.
-Each inidividual Fighter has an integer value caled "hit points" that represents his/her current health (the amount of damage he/she can sustain before death)
-It will simulate combat between 2 Fighers.
-Combat is divided into rounds:
-During each round each combatant will make 1 attempt to stike the other.
-We will use random numbers to simulate rolling dice, the higher number attacks first, and the lower number goes 2nd if still alive.
-If there is a tie, the players will go at the same time, they will attack eachother at the same moment.
-During a simultaneous attack both Fighters will get to attack even if one (or both) if (are) killed during that round.
-The progam will use a random number to determine whether an attack attempt is successful or not.
-Each successful attack will inflict damage on the opponent.
-To simulate damage, we'll reduce the opponent's hit points by another random number
-When a Fighters hit points are reduced to 0 (or less than 0) that player is considered to be dead.
-Combat round continue until one(or both) combatants are dead.
'''
I am having a hard time figuring out how to call my instances for Fighter that I created within the class too my combat function, that is outside of the class.
I am also wondering if I am comparing player1 and player2's random numbers correctly. When messing around with it I tried using:
player1 = Fighter()
player2 = Fighter()
When doing this my error message would said that Fighter() > Fighter() are not comparable.
Lastly, not quite sure why my test_attack is failing.
My code (so far):
import random
class Fighter:
def __init__(self, name):
'''
-This is my initializer method, which takes 2 parameters:
-Self
-Name (a string, the name of a fighter)
-This method will set a name attribute to the value of the name parameter
-It will also set a hit_points attribute to 10 (all fighters begin life with 10 hit points).
'''
self.name = name
self.hit_points = 10
def __repr__(self):
'''
-This method takes 1 parameter, self.
-It returns a string showing the name and hit points of the instances in the following format:
Bonzo (HP: 9)
'''
result = str(self.name) + " (HP: " + str(self.hit_points) + ")"
return result
def take_damage(self, damage_amount):
'''
-This method takes 2 parameters:
-self (the Fighter instance that calls the method)
-damage_amount (an integer representing the number of hit points of damage that have just been inflicted on this Fighter):
-The method should first decrease the hit_points attribute by the damage_amount.
-Next, it should check to see if the Fighter has died from the damage:
-A hit_points value that is 0 or less indicates death, and will print a message like the following:
\tAlas, Bonzo has fallen!
-Otherwise, it will print a message like the following (in this example, the player had 5 hit points left over after damage)
\tBonzo has 5 hit points remaining.
-The method returns nothing.
'''
self.hit_points = self.hit_points - damage_amount
if self.hit_points <= 0:
print('\t' + 'Alas, ' + str(self.name) + ' has fallen!')
else:
print('\t' + str(self.name) + ' has ' + str(self.hit_points) + ' hit points remaining.')
def attack(self, other):
'''
-This method takes 2 parameters:
-self
-other (another Fighter instance being attacked by self)
-The method will print the name of the attacker and attacked in the following format:
Bonzo attacks Chubs!
-Next, determine whether the attack hits by generating a random integer between 1 and 20
-Use the randrange function from the random module to get this number.
-A number that is 12 or higher indicates a hit:
- For an attack that hits, generate a random number between 1 and 6(using random.randrange) to represent the amount of damage inflicted by the attack
***Do NOT use the from random import randrange sytanze (randrange works like range, not like randint (think about the upper bounds you choose)).
- Print the amount of damage inflicted like the following:
\tHits for 4 hit points!
-Invoke the take_damage method on the victim (i.e. on other), passing it the amount of damage inflicted.
-For an attack that misses, print the following message:
\tMisses!
-This method returns nothing.
'''
self.other = Fighter(self.name)
print(str(self.name) + ' attacks ' + str(other) + '!')
attack = random.randrange(1, 20)
if attack >= 12:
damage_amount = random.randrange(1, 6)
print('\t' + 'Hits for ' + str(damage_amount) + ' hit points!')
other.take_damage(damage_amount)
else:
print('\t' + 'Misses!')
def is_alive(self):
'''
-This method takes 1 parameter, self.
-It returns True if self has a positive number of points, False otherwise.
'''
if self.hit_points > 0:
return True
else:
return False
def combat_round(player1, player2):
'''
-This function takes 2 parameters:
-The 1st is an instance of Fighter.
-The 2nd is another instance of Fighter.
-It determines which of the 2 fighters attacks 1st for the round by generating a random interger (use randrange) between 1 and 6 for each fighter:
-if the numbers are equal:
-print the following message:
Simultaneous!
-Have each figher instance call his attack method on the other figher (the 1st figher in the argument list must attack first to make the test work correctly)
-if one number is larger:
-the fighter with the larger roll attacks 1st (by calling his attack method and passing it the figher being attacked).
-if the 2nd fighter survives the attack (call is_alive), he then attack the 1st.
-This function returns nothing.
'''
Fighter.name = player1
Fighter.name = player2
player1 = random.randrange(1, 6)
player2 = random.randrange(1, 6)
if player1 == player2:
print('Simultaneous!')
player1.attack(player2)
if player1 > player2:
player1.attack(player2)
if player2.is_alive() == True:
player2.attack(player1)
if player2 > player1:
player2.attack(player1)
if player1.is_alive() == True:
player1.attack(player2)
You class looks okay, but the combat_round function has some problems.
You were on the right track with player1 = Fighter() etc..
player1 = Fighter(name = 'Jeff')
player2 = Fighter(name = 'Bill')
But now you want to choose one to go first. You cant just compare them with >, <, == without customizing special methods like __eq__() because Python doesn't know which attribute to compare. How about setting an attribute then comparing it:
player1.speed = random.randrange(1,6)
player2.speed = random.randrange(1,6)
Now you can write tests like this:
if player1.speed == player2.speed:
print("Simultaneous!")
Also, if the function combat_round() takes instances of fighter, then you dont need to instantiate them inside the function. But, if the function takes strings that are the names of the fighters then you would do it like my example.