8

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!

Gaffsey
  • 89
  • 1
  • 1
  • 5
  • You need something like this, depending on the platform you are running on https://docs.python.org/2/howto/curses.html. Also see http://stackoverflow.com/questions/25298923/python-get-arrow-keys-from-command-line – cdarke Sep 14 '16 at 11:03
  • Awesome, thank you! – Gaffsey Sep 20 '16 at 17:27

3 Answers3

9

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

Jona
  • 669
  • 10
  • 18
3

I would consider using simple-term-menu : https://github.com/IngoMeyer441/simple-term-menu

This project allows you to search through the menu using '/'

Bhakta Raghavan
  • 664
  • 6
  • 16
0

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)
FocusedWolf
  • 1,062
  • 16
  • 19