From 131f3e9380ad5ac9261e3c4a578e9d71c5a84477 Mon Sep 17 00:00:00 2001 From: Joe Anderson Date: Fri, 19 Feb 2016 11:53:15 -0800 Subject: navigable help menus --- oo.py | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 149 insertions(+), 46 deletions(-) diff --git a/oo.py b/oo.py index 8037fb3..32b0624 100644 --- a/oo.py +++ b/oo.py @@ -420,7 +420,6 @@ class ooPlay: curses.init_pair(n, *color_pair(n)) # initialize flags - self.help_ind = 0 self.inverted = False self.toroidal = False self.extra_hard = False @@ -560,6 +559,31 @@ class ooPlay: self.display_subroutine(x, y) self.screen.refresh() + def get_input(self, options): + """Waits for input to match an option, returns matched option. + + options must be an iterable of strings and ints. + Each character in each string and each int are checked against. + If the input matches any character in a string, + then the character is returned instead of the ordinal. + """ + options = list(options) + chars = [] + ords = [] + for option in options: + if type(option) is str: + chars.extend(list(option)) + if type(option) is int: + ords.append(option) + breaks = list(map(ord, chars)) + ords + while True: + inp = self.screen.getch() + if inp in breaks: + break + if inp in map(ord, chars): + return chr(inp) + return inp + pause_length = 80 def sleep(self, delay=1): """Like most sleep functions, but can be escaped with spacebar. @@ -637,50 +661,127 @@ class ooPlay: if self.sleep(): return if pause: self.sleep(width) - def write_help(self): - """Write one of the help messages.""" - if self.help_ind == 0: - self.write("Help on controls.") - self.write("arrow or vi keys: move cursor") - self.write("space bar or return: rotates piece") - self.write("q: quit game") - self.write("n: new game") - self.write("f: fix rotation") - self.write("r: randomize rotations") - self.write("s: toggle show errors") - self.write("t: toggle toroidal mode") - self.write("i: toggle inverted mode") - self.write("x: toggle extra hard mode") - self.write("The next help is game explanation.") - self.write() - - if self.help_ind == 1: - self.write("Help on game.") - self.write("If game is not inverted," + - " the object is to have every line connect to another.") - self.write("If game is inverted," + - " the object is to have no two lines connected.") - self.write("If game is not toroidal," + - " the borders cannot have lines extending outwards.") - self.write("If game is toroidal," + - " the borders loop back" + - " and may connect to the opposite side.") - self.write("If game is extra hard," + - " then no completely (un)filled pieces are used.") - self.write("The next help is on controls.") - self.write() - - self.help_ind += 1 - self.help_ind %= 2 - - def success(self): - """Write and respond to the win screen.""" - self.write("You won!") - self.write("r n q", pause=False) + def write_menu(self, string, controls=[]): + """Writes string, waits for acceptable input, returns upper input. + + Inputs are allowed to be: + any upper case character in string, + any lower case version of those characters, or + any element of controls. + the spacebar (on which the text is redisplayed) + + Returns the matched input, made upper case if matched from string. + + A common setting for controls contains: + '\n' for exiting menu + curses.KEY_UP for backing up one menu + + The spacebar handling can be overwritten by adding ' ' to controls. + """ + options = [char + char.lower() for char in string if char.isupper()] + options.extend(controls) + options.append(' ') while True: - inp = self.screen.getch() - if inp in map(ord, 'QqNnRr'): break - return chr(inp) + self.write(string, pause=False) + inp = self.get_input(options) + if inp != ' ': + break + if inp in controls: + return inp + return inp.upper() + + def menu_system(self, string_tree, up_key=curses.KEY_UP, out_key='\n'): + """Runs a system of menus configured by string_tree. + + string_tree must be a tuple with: + a string to be passed to write_menu + a dictionary with a key per upper case letter in string, where: + the key is the upper case letter as a string + the value is a string_tree + + A string_tree that ends a path is a tuple with: + a string that has no upper case characters + an empty dictionary + + Each string tree is parsed in the same way, + where the string is written with write_menu, + and choosing an option results in opening a submenu + given by the corresponding dictionary item. + + Pressing up_key results in going up to the parent menu. + Pressing out_key results in exiting the menu (return). + """ + parents = [] + while True: + string, responses = string_tree + inp = self.write_menu(string, controls=[up_key, out_key]) + if inp in responses: + parents.append(string_tree) + string_tree = responses[inp] + elif inp == up_key and parents: + string_tree = parents.pop() + elif inp == out_key or not parents: + return + + def write_help(self): + """Write the help menu.""" + self.write("use spacebar to skip or repeat a message") + self.write("use the capital letter to make a selection") + self.write("use up to return to a previous menu") + self.write("use return to exit the help menu") + help_menu = \ + ("Controls or Gameplay?", + {"C": ("Move cursor | Rotations | Game | Toggles", + {"M": ("use arrow or vi keys (hjkl) to move the cursor", + {}), + "R": ("rotate Piece | Fix piece | Randomize", + {"P": ("use space bar or return to rotate a piece", + {}), + "F": ("use f to fix a piece's rotation", + {}), + "R": ("use r to randomize all pieces," + + " including fixed", + {})} + ), + "G": ("New | Quit", + {"N": ("use n to start a new game", + {}), + "Q": ("use q to quit the program", + {})} + ), + "T": ("Show errors | Inverted | Toroidal | eXtra hard", + {"S": ("use s to show/hide errors", + {}), + "I": ("use i to toggle inverted mode", + {}), + "T": ("use t to toggle toroidal on next new game", + {}), + "X": ("use x to toggle extra hard on next new game", + {})} + )} + ), + "G": ("Basic play | if Inverted | if Toroidal | if eXtra hard", + {"B": ("the object is to connect every line to another," + + " where no line should extend to a border," + + " and every possible piece may be in the puzzle", + {}), + "I": ("if inverted, the object changes to ensure" + + " every line is not connected to any other," + + " and border edges do not matter", + {}), + "T": ("if toroidal, the object changes so that" + + " the borders loop back, that is," + + " rightmost pieces may connect to leftmost pieces" + " and same with topmost and bottommost", + {}), + "X": ("if extra hard, the object is the same," + + " but when generating the puzzle," + + " no completely filled or unfilled pieces are used", + {})} + )} + ) + self.menu_system(help_menu) + self.write() def keyloop(self): """Wait for and parse keypress.""" @@ -699,8 +800,10 @@ class ooPlay: self.puzzle.rotate_cw(self.xpos, self.ypos) self.display_pos(self.xpos, self.ypos) if self.puzzle.is_solved(): - inp = self.success() - # if inp is changed by self.success, we catch it here + self.write("You won!") + menu = "Randomize | New game | Quit game" + inp = self.write_menu(menu) + # if inp is changed on a solved puzzle, we catch it here if inp in "Qq": self.write("quit") return -- cgit v1.2.3-54-g00ecf