How to make a menu in Python navigable with arrow keys - python

I'm making a text-based game with an option to select a class for their character. At present, the player enters their option, either typing in a number or the name of the class. It works well enough.
However, I would like to have the player navigate the menu with the arrow keys and select an option using the "enter" key. In order to make it clear which option they are about to select, I would also like to have the text of the selected option highlighted. If you've ever played an ASCII roguelike, you know what it looks like.
Here is the code that I currently have for classes:
def character():
print "What is your class?"
print "1. The sneaky thief."
print "2. The smarty wizard."
print "3. The proletariat."
charclass = raw_input("> ")
if charclass == "1" or "thief":
charclass = thief
print "You are a thief!"
elif charclass == "2" or "wizard":
charclass = wizard
print "You are a wizard!"
elif charclass == "3" or "prole":
charclass = prole
print "You are a prole!"
else:
print "I'm sorry, I didn't get that"
Thanks!

As it was already mentioned in a comment, you may use curses. Here is a small working menu to achieve what you want
import curses
classes = ["The sneaky thief", "The smarty wizard", "The proletariat"]
def character(stdscr):
attributes = {}
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
attributes['normal'] = curses.color_pair(1)
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE)
attributes['highlighted'] = curses.color_pair(2)
c = 0 # last character read
option = 0 # the current option that is marked
while c != 10: # Enter in ascii
stdscr.erase()
stdscr.addstr("What is your class?\n", curses.A_UNDERLINE)
for i in range(len(classes)):
if i == option:
attr = attributes['highlighted']
else:
attr = attributes['normal']
stdscr.addstr("{0}. ".format(i + 1))
stdscr.addstr(classes[i] + '\n', attr)
c = stdscr.getch()
if c == curses.KEY_UP and option > 0:
option -= 1
elif c == curses.KEY_DOWN and option < len(classes) - 1:
option += 1
stdscr.addstr("You chose {0}".format(classes[option]))
stdscr.getch()
curses.wrapper(character)
The last call to getch is just so you can see the result before the program terminates

I would consider using simple-term-menu : https://github.com/IngoMeyer441/simple-term-menu
This project allows you to search through the menu using '/'

Like Jona's answer but actually runs :P
Also have a longer version here that does vertical and horizontal menu: https://pastebin.com/raw/zv9EXdgH
#!/usr/bin/env python3
# SOURCE: https://docs.python.org/2/library/curses.html
# SOURCE: https://docs.python.org/3/howto/curses.html
# For Windows: pip install windows-curses
import curses
window = curses.initscr() # Initialize the library. Returns a WindowObject which represents the whole screen.
window.keypad(True) # Escape sequences generated by some keys (keypad, function keys) will be interpreted by curses.
curses.cbreak() # Keys are read one by one. Also safer than curses.raw() because you can still interrupt a running script with hotkeys.
curses.noecho() # Prevent getch() keys from being visible when pressed. Echoing of input characters is turned off.
# Initialize colors.
curses.start_color() # Must be called if the programmer wants to use colors.
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)
black = curses.color_pair(1)
white = curses.color_pair(2)
def display_menu(window):
selectedIndex = 0
while True:
window.clear()
window.addstr('Pick an option:\n', curses.A_UNDERLINE)
for i in range(len(MENU_OPTIONS)):
# Uncolored line number.
window.addstr('{}. '.format(i + 1))
# Colored menu option.
window.addstr(MENU_OPTIONS[i] + '\n', black if i == selectedIndex else white)
c = window.getch()
if c == curses.KEY_UP or c == curses.KEY_LEFT:
# Loop around backwards.
selectedIndex = (selectedIndex - 1 + len(MENU_OPTIONS)) % len(MENU_OPTIONS)
elif c == curses.KEY_DOWN or c == curses.KEY_RIGHT:
# Loop around forwards.
selectedIndex = (selectedIndex + 1) % len(MENU_OPTIONS)
# If curses.nonl() is called, Enter key = \r else \n.
elif c == curses.KEY_ENTER or chr(c) in '\r\n':
# If the last option, exit, is selected.
if selectedIndex == len(MENU_OPTIONS) - 1:
curses.endwin() # De-initialize the library, and return terminal to normal status. <-- Works without this on Windows, however in Linux you can't type in the terminal after exiting without this :P
break
window.addstr('\nYou choose {}\n'.format(MENU_OPTIONS[selectedIndex]))
window.getch()
else:
window.addstr("\nThe pressed key '{}' {} is not associated with a menu function.\n".format(chr(c), c))
window.getch()
MENU_OPTIONS = [
'Option 1',
'Option 2',
'Option 3',
'Exit',
]
if __name__ == '__main__':
display_menu(window)

Related

Time.sleep() not pausing the correct moment?

Hello I have created a quiz using python and tkinter. After each option is pressed I wanted the correct answer to turn green and the three incorrect to turn red then revert to the default for the next question.The problem here being that running the code will take the buttons to the default before the user can see the colours. To do this I tried to use time.sleep() in the function however no matter where I use it it just seems to pause on the button being pressed down and then goes onto the next question without seeing any colour change.
Here is the relevant piece of code
def entry(num):
global score
global x
global count
count +=1
if Qa[x] == 1:
option1.config(bg = "green")
option2.config(bg = "red")
option3.config(bg="red")
option4.config(bg="red")
elif Qa[x] == 2:
option1.config(bg="red")
option2.config(bg="green")
option3.config(bg="red")
option4.config(bg="red")
elif Qa[x] == 3:
option1.config(bg="red")
option2.config(bg="red")
option3.config(bg="green")
option4.config(bg="red")
elif Qa[x] == 4:
option1.config(bg="red")
option2.config(bg="red")
option3.config(bg="red")
option4.config(bg="green")
if num == Qa[x]:
score += 1
x +=1
if count <10:
my_label.config(text = Qs[x])
option1.config(text = (question_prompts[x])[1],bg = "SystemButtonFace",command = lambda: entry(1) )
option2.config(text=(question_prompts[x])[2],bg = "SystemButtonFace",command = lambda: entry(2) )
option3.config(text=(question_prompts[x])[3],bg = "SystemButtonFace",command = lambda: entry(3) )
option4.config(text=(question_prompts[x])[4],bg = "SystemButtonFace",command = lambda: entry(4) )
else:
End_score =Label(text = "Well done you scored" +" "+ str(score)+" " +"out of 11", font = 40)
End_score.place(relx=0.5,rely =0.5,anchor = CENTER)
print(x,score, count, Qa[x])
I haven't put the time.sleep() in here because I have tried it everywhere in this section an it gives the same result
I would really appreciate some help
The PROBLEM here is that the options will not actually change color until Tk can get back to its main loop. As long as you are running your function, the main loop cannot pull new events. You need to set the colors, then use root.after to schedule a callback at some point in the future where you reset to all green.

Cannot figure out how to finish hangman date signing

display a double 3 time method for tracking analysis with frameworks
we need to find the x for y
and describe why it happeneds that way
do not quote for 32 x 8. Most of the If the player guess a letter which exists in the word, the script writes it in all its correct positions. The player has 10 turns to guess the word. You can easily customize the game by changing the variables.live coding demo portion of the course and showing the process of coding a
#importing the time module
import time
#welcoming the user
name = raw_input("What is your name? ")
print "Hello, " + name, "Time to play hangman!"
print "
"
#wait for 1 second
time.sleep(1)
print "Start guessing..."
time.sleep(0.5)
#here we set the secret
word = "secret"
#creates an variable with an empty value
guesses = ''
#determine the number of turns
turns = 10
# Create a while loop
#check if the turns are more than zero
while turns > 0:
# make a counter that starts with zero
failed = 0
# for every character in secret_word
for char in word:
# see if the character is in the players guess
if char in guesses:
# print then out the character
print char,
else:
# if not found, print a dash
print "_",
# and increase the failed counter with one
failed += 1
# if failed is equal to zero
# print You Won
if failed == 0:
print "
You won"
# exit the script
break
print
# ask the user go guess a character
guess = raw_input("guess a character:")
# set the players guess to guesses
guesses += guess
# if the guess is not found in the secret word
if guess not in word:
# turns counter decreases with 1 (now 9)
turns -= 1
# print wrong
print "Wrong
"
# how many turns are left
print "You have", + turns, 'more guesses'
# if the turns are equal to zero
if turns == 0:
# print "You Lose"
print "You Lose
"
The mainloop is a function of the root window, so you should assign a name to it:
if __name__ == '__main__':
url = 'https://api.exchangerate-api.com/v4/latest/USD'
converter = MikesMagicalConverter(url)
app = App(converter) # Assing a name to root window
app.mainloop() # Run root window mainloop
When you create the button you should provide a reference to the master:
buttonExample = tk.Button(self, # Provide reference to master
text="Create new window",
command=lambda:self.currency_converter.createNewWindow(self)) # Pass reference
buttonExample.pack()
The command argument is a bit trickier; the class MikesMagicalConverter is not a widget and does not inherit from tkiner so you'll have to pass a reference to use as master to the Toplevel window.
You must provide a self parameter to the createNewWindow() method.
def createNewWindow(self, master):
newWindow = tk.Toplevel(master) # Using reference as master
labelExample = tk.Label(newWindow, text = "New Window")
buttonExample = tk.Button(newWindow, text = "New Window button")
labelExample.pack()
buttonExample.pack()
In general: many of your lines are way to long, makes the code hard to read.
With those changes the code will still generate error, but the button and Toplevel window pops up. I'm thinking you might rather separate the currency calculations from the GUI code.

Python Curses addstr() error when keys are not pressed

I'm recreating this game for terminal in python using the curses library. Whenever a key is not pressed, stdscr.addstr() returns an error.
This seems to be because the cursor is off screen (in the bottom-right corner) during those frames, but I can't figure out why it moves at all. The canvas that I'm printing is always the same size (It consists entirely of spaces which are replaced when Apple or Basket objects are rendered). I tried decreasing the canvas size, but the cursor still goes to that corner. I also tried setting curses.leaveok(True), and the cursor seemed to be following the basket, but my logging proves that it is still going to that corner.
The file that uses curses:
import random
import time
import curses
from apple_season.basket import Basket
from apple_season.apple import Apple
from apple_season.coords import Canvas
def main(stdscr):
curses.curs_set(1) # so i can see where the cursor is
dims = [curses.COLS - 1, curses.LINES - 1] # pylint: disable=no-member
stdscr.nodelay(True)
stdscr.leaveok(True)
key=""
stdscr.clear()
canvas = Canvas(*dims)
basket = Basket(canvas)
apples = []
i = 0
def finished_apples():
if len(apples) <= 100:
return False
else:
for apple in apples:
if not apple.has_fallen:
return False
return True
while not finished_apples():
if len(apples) <= 100: # don't make more if there are already 100
# decide whether or not to create new apple (1/100 chance per frame)
num = random.randint(0, 100)
if num == 25:
apples.append(Apple(canvas))
try:
key = stdscr.getkey()
stdscr.clear()
# pick up keyboard inputs
# quit option
if str(key) == "q":
break
# right arrow
elif str(key) == "KEY_RIGHT":
basket.move('right')
# left arrow
elif str(key) == "KEY_LEFT":
basket.move('left')
except Exception:
pass
# render objects - alters canvas to display them
for apple in apples:
if apple.has_fallen:
apple.render()
else:
if '.0' not in str(i / 2): # check if i is even (drop every other frame)
apple.fall()
apple.render()
basket.render()
try:
stdscr.addstr(canvas.display)
except Exception:
pass
stdscr.refresh()
i += 1
time.sleep(0.01)
if __name__ == "__main__":
curses.wrapper(main)
(The code above runs fine, but it doesn't do anything when stdscr.addstr(canvas.display) doesn't work, which is whenever there are no keys pressed)
What's interesting is that this doesn't happen when it's just the basket or just the apples.
To see all of the code: https://github.com/lol-cubes/Terminal-Apple-Season/tree/soErrorCode.
I put the stdscr.clear() in the try and except block, which made it so that that code was only executed when a key was pressed, therefore overflowing the terminal because I was attempting to display multiple frames at once.

Adding another sub-menu to existing code

The code below has a main menu and a sub menu. The first menu prompts on load and selecting an option will load another "page" with more options. I cannot figure out how to get to another sub section from here. Called sub2 for example.
Lets say when Option 1 is selected it will prompt you two choose two sub options option1_sub2_1 and option1_sub2_2.
Lets say when you select Option 2 it prompts for option2_sub2_1 option2_sub2_2
I have one set of options stored as:
menu_actions = {
'main_menu': main_menu,
'1': one,
'2': two,
'b': back,
'q': exit,
}
It is possible to have it setup somehow like this:
option1_sub2 = {
'1': one,
'2': two,
}
and
option2_sub2 = {
'1': one,
'2': two,
}
That way I can re-use the same numbers for the different sub2 items. Is this possible?
This is my first code project and I have not been able to make it past this part. Any help would be appreciated.
The code:
import sys, os
# Main definition - constants
menu_actions = {}
# =======================
# MENUS FUNCTIONS
# =======================
# Main menu (display list on screen)
def main_menu():
os.system('clear')
print "1. Option"
print "2. Option"
choice = raw_input(" >> ")
exec_menu(choice)
return
# Choose a menu
def exec_menu(choice):
os.system('clear')
ch = choice.lower()
if ch == '':
menu_actions['main_menu']()
else:
try:
menu_actions[ch]()
except KeyError:
print "Invalid selection, please try again.\n"
menu_actions['main_menu']()
return
# =======================
# SUB MENUS
# =======================
# Back to main menu
def back():
menu_actions['main_menu']()
# Exit program
def exit():
sys.exit()
# Option1
def one():
print "Option 1"
print (30 * '-')
print "Sub Option 1"
print "Sub Option 2"
print "[B]ack"
print "[Q]uit"
choice = raw_input(" >> ")
exec_menu(choice)
return
# FTP
def two():
print (30 * '-')
print "Sub Option 1"
print "Sub Option 2"
print "[B]ack"
print "[Q]uit"
choice = raw_input(" >> ")
exec_menu(choice)
return
# =======================
# MENUS DEFINITIONS
# =======================
# Menu definition
menu_actions = {
'main_menu': main_menu,
'1': one,
'2': two,
'b': back,
'q': exit,
}
# =======================
# MAIN PROGRAM
# =======================
# Main Program
if __name__ == "__main__":
# Launch main menu
main_menu()
start by abstracting it out
first create a method that will continually prompt for input until a given input result is achieved
def input_option(prompt,options):
while True:
result = raw_input(prompt)
if result in option: return result
print "Invalid Option Selected %r"%result
test this function with things like print input_option("Enter 1 or 2 or Three",["1","2","Three"])
now abstract out a function to display a menu and get input from the menu
def select_menu(options):
print "Select From the choices Below:"
options = []
for option_text,input_value in options:
print option_text
options.append(input_value)
return input_option(">>>",options)
you can test this like
options = [("1> option 1","1"),("2> option 2","2")]
print select_menu(options)
options2 = [("[B]ack","B"),("[Q]uit","Q")]
print select_menu(options2)
then all you have to do is tie it together by mapping actions to inputs
menu_otions = [("Show Menu [1]","1"),("Show Menu [2]","2"),("[Q]uit","Q")]
menu_actions = {"1":show_menu1,"2":show_menu2,"Q":sys.exit

Exploring a maze (using python 2.7)

I have done everything on my homework except a one step.
I did it in a different way, but it gave kind of right answer somehow.
Anyway, I have to explore a maz, so I went all the way through and got everything completely right, except part (Q COMMAND) of this task.
I need to use string method .upper()
2.2.6 The Top-Level Interface
interact() is the top-level function that denes the text-base user interface
as described in the introduction.
Note that when either the user quits or
when the finish square is found, the interact function should exit.
def interact():
mazefile = raw_input('Maze File: ')
maze = load_maze(mazefile)
position = (1, 1)
poshis = [position]
while True:
#print poshis, len(poshis)
print 'You are at position', position
command = raw_input('Command: ')
#print command
if command == '?':
print HELP
elif command == 'N' or command == 'E' or command == 'S'or command == 'W':
mov = move(maze, position, command)
if mov[0] == False: #invalid direction
print "You can't go in that direction"
elif mov[1] == True:#finished
print 'Congratulations - you made it!'
break
else: #can move, but not finished
position = mov[2]
poshis.append(position)
elif command == 'R': # reseting the maze to the first pos
position = (1, 1)
poshis = [position]
elif command == 'B': # back one move
if len(poshis) > 1:
poshis.pop()
position = poshis[-1]
elif command == 'L': # listing all possible leg dir
toggle = 0
result = ''
leg_list = get_legal_directions(maze, poshis[-1])
for Legal in leg_list:
if toggle:
result += ', '
result += Legal
else:
result += Legal
if not toggle:
toggle = 1
print result
elif command == 'Q': #quiting
m = raw_input('Are you sure you want to quit? [y] or n: ')
if m == 'y':
break
#print 'Are you sure you want to quit? [y] or n: '
#if raw_input == 'y':
# break
else: #invalid input
print 'Invalid Command:', command
Your question isn't particularly clear, but I'm guessing that the 'problem' is that if a user were to answer "Y" instead of "y" when asked if they are sure they want to quit, then the loop will continue.
If that is the problem, you should merely replace the line:
if m == 'y':
break
with:
if m.upper() == 'Y':
break
Because regardless of whether the user types "y" or "Y", the loop will still be broken out of.

Categories