summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Anderson <jandew+dev@gmail.com>2016-02-19 11:53:15 -0800
committerJoe Anderson <jandew+dev@gmail.com>2016-02-19 11:53:15 -0800
commit131f3e9380ad5ac9261e3c4a578e9d71c5a84477 (patch)
treeb660de1615116aa83b4095c2a34a974fe14c456b
parent57b7e327bae2f46553a534836520cf957b59ced8 (diff)
downloadoo-131f3e9380ad5ac9261e3c4a578e9d71c5a84477.tar.gz
oo-131f3e9380ad5ac9261e3c4a578e9d71c5a84477.zip
navigable help menus
-rw-r--r--oo.py195
1 files 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