fork(2) download
  1. from __future__ import unicode_literals
  2. import sys
  3. from collections import OrderedDict, namedtuple
  4. try:
  5. from configparser import ConfigParser # Python 3
  6. except ImportError:
  7. from ConfigParser import ConfigParser # Python 2
  8.  
  9. try:
  10. from builtins import input as raw_input # Python 3
  11. except ImportError:
  12. from __builtin__ import raw_input # Python 2
  13.  
  14. Action = namedtuple("Action", "name do")
  15.  
  16.  
  17. class Menu:
  18. def __init__(self, title, actions, parent=None):
  19. self.title = title
  20. self.actions = actions
  21. self.parent = parent
  22.  
  23. @classmethod
  24. def from_config(cls, game, config, title, parent=None):
  25. if title not in game.menus: # create menu
  26. actions = OrderedDict()
  27. self = game.menus[title] = cls(title, actions, parent)
  28. for opt, value in config.items(title): # Python 3.2+:
  29. # config[title].items()
  30. if value in config.sections(): # submenu
  31. do = cls.from_config(game, config, value, self)
  32. else: # concrete choice
  33. def make_do(action, menu):
  34. return lambda: getattr(game, 'do_' + action)(menu)
  35. do = make_do(value, self)
  36. actions[opt] = Action(value, do)
  37. return game.menus[title]
  38.  
  39. def __call__(self):
  40. # show menu
  41. print(self.title + ":")
  42. for opt, action in self.actions.items():
  43. print("\t{}: {}".format(opt, action.name))
  44.  
  45. # take user input
  46. prompt = 'Choose [{}]: '.format(', '.join(self.actions))
  47. action_name = read_choice(choices=self.actions, prompt=prompt)
  48. if not sys.stdin.isatty():
  49. print(action_name) # echo the choice in non-interactive mode
  50.  
  51. # act on it
  52. return self.actions[action_name].do
  53.  
  54.  
  55. class Game:
  56. def __init__(self):
  57. self.menus = {}
  58. self.options = {}
  59.  
  60. def do_start(self, menu):
  61. print("start game with options: {}".format(self.options))
  62. # ... play game
  63. return menu # return to menu
  64.  
  65. def do_exit(self, menu):
  66. print("bye")
  67. return None # do nothing (the end)
  68.  
  69. def __getattr__(self, name): # called for non-existing game attributes
  70. if not name.startswith('do_'):
  71. raise AttributeError(name)
  72.  
  73. # game.do_something(menu) sets corresponding option to 'something'
  74. def set_option(menu):
  75. self.options[menu.title] = name[len('do_'):]
  76. return menu.parent # return to parent menu
  77. return set_option
  78.  
  79.  
  80. def read_choice(choices, prompt):
  81. c = None
  82. while c not in choices:
  83. c = raw_input(prompt)
  84. return c
  85.  
  86.  
  87. def main():
  88. # read menu provided in ini-format
  89. config = ConfigParser()
  90. config_lines = []
  91. for line in iter(sys.stdin.readline, ''): # consume config in
  92. # ini-format
  93. # line-by-line to allow
  94. # accepting user input
  95. # later from stdin
  96. # (avoid read-ahead)
  97. if line.rstrip() == '---':
  98. break # end of config (the rest is user input)
  99. config_lines.append(line)
  100.  
  101. # Python 3.3:
  102. # config.read_string(''.join(config_lines))
  103. # Python < 3.3:
  104. from io import StringIO
  105. config.readfp(StringIO(''.join(config_lines)))
  106.  
  107. # create game
  108. game = Game()
  109. menu = Menu.from_config(game, config, 'Main menu')
  110.  
  111. # run it
  112. while menu is not None: # use trampoline to avoid recursion limit
  113. menu = menu()
  114.  
  115. main()
Success #stdin #stdout 0.02s 5956KB
stdin
[Main menu]
1: start
2: Options
3: exit

[New Game]
1: start
0: Main menu

[Options]
1: Level
0: Main menu

[Level]
1: impossible
2: hard
0: Options
---
2
1
2
0
1
3
stdout
Main menu:
	1: start
	2: Options
	3: exit
Choose [1, 2, 3]: 2
Options:
	1: Level
	0: Main menu
Choose [1, 0]: 1
Level:
	1: impossible
	2: hard
	0: Options
Choose [1, 2, 0]: 2
Options:
	1: Level
	0: Main menu
Choose [1, 0]: 0
Main menu:
	1: start
	2: Options
	3: exit
Choose [1, 2, 3]: 1
start game with options: {'Level': 'hard'}
Main menu:
	1: start
	2: Options
	3: exit
Choose [1, 2, 3]: 3
bye