6 from plomrogue.game import GameBase
7 from plomrogue.parser import Parser
8 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
9 from plomrogue.things import ThingBase
10 from plomrogue.misc import quote
11 from plomrogue.errors import BrokenSocketConnection
13 from ws4py.client import WebSocketBaseClient
14 class WebSocketClient(WebSocketBaseClient):
16 def __init__(self, recv_handler, *args, **kwargs):
17 super().__init__(*args, **kwargs)
18 self.recv_handler = recv_handler
21 def received_message(self, message):
23 message = str(message)
24 self.recv_handler(message)
27 def plom_closed(self):
28 return self.client_terminated
30 from plomrogue.io_tcp import PlomSocket
31 class PlomSocketClient(PlomSocket):
33 def __init__(self, recv_handler, url):
35 self.recv_handler = recv_handler
36 host, port = url.split(':')
37 super().__init__(socket.create_connection((host, port)))
45 for msg in self.recv():
47 self.socket = ssl.wrap_socket(self.socket)
49 self.recv_handler(msg)
50 except BrokenSocketConnection:
51 pass # we assume socket will be known as dead by now
53 def cmd_TURN(game, n):
57 game.turn_complete = False
58 cmd_TURN.argtypes = 'int:nonneg'
60 def cmd_LOGIN_OK(game):
61 game.tui.switch_mode('post_login_wait')
62 game.tui.send('GET_GAMESTATE')
63 game.tui.log_msg('@ welcome')
64 cmd_LOGIN_OK.argtypes = ''
66 def cmd_CHAT(game, msg):
67 game.tui.log_msg('# ' + msg)
68 game.tui.do_refresh = True
69 cmd_CHAT.argtypes = 'string'
71 def cmd_PLAYER_ID(game, player_id):
72 game.player_id = player_id
73 cmd_PLAYER_ID.argtypes = 'int:nonneg'
75 def cmd_THING(game, yx, thing_type, thing_id):
76 t = game.get_thing(thing_id)
78 t = ThingBase(game, thing_id)
82 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
84 def cmd_THING_NAME(game, thing_id, name):
85 t = game.get_thing(thing_id)
88 cmd_THING_NAME.argtypes = 'int:nonneg string'
90 def cmd_THING_CHAR(game, thing_id, c):
91 t = game.get_thing(thing_id)
94 cmd_THING_CHAR.argtypes = 'int:nonneg char'
96 def cmd_MAP(game, geometry, size, content):
97 map_geometry_class = globals()['MapGeometry' + geometry]
98 game.map_geometry = map_geometry_class(size)
99 game.map_content = content
100 if type(game.map_geometry) == MapGeometrySquare:
101 game.tui.movement_keys = {
102 game.tui.keys['square_move_up']: 'UP',
103 game.tui.keys['square_move_left']: 'LEFT',
104 game.tui.keys['square_move_down']: 'DOWN',
105 game.tui.keys['square_move_right']: 'RIGHT',
107 elif type(game.map_geometry) == MapGeometryHex:
108 game.tui.movement_keys = {
109 game.tui.keys['hex_move_upleft']: 'UPLEFT',
110 game.tui.keys['hex_move_upright']: 'UPRIGHT',
111 game.tui.keys['hex_move_right']: 'RIGHT',
112 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
113 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
114 game.tui.keys['hex_move_left']: 'LEFT',
116 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
118 def cmd_FOV(game, content):
120 cmd_FOV.argtypes = 'string'
122 def cmd_MAP_CONTROL(game, content):
123 game.map_control_content = content
124 cmd_MAP_CONTROL.argtypes = 'string'
126 def cmd_GAME_STATE_COMPLETE(game):
128 if game.tui.mode.name == 'post_login_wait':
129 game.tui.switch_mode('play')
130 if game.tui.mode.shows_info:
131 game.tui.query_info()
132 player = game.get_thing(game.player_id)
133 if player.position in game.portals:
134 game.tui.teleport_target_host = game.portals[player.position]
135 game.tui.switch_mode('teleport')
136 game.turn_complete = True
137 game.tui.do_refresh = True
138 cmd_GAME_STATE_COMPLETE.argtypes = ''
140 def cmd_PORTAL(game, position, msg):
141 game.portals[position] = msg
142 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
144 def cmd_PLAY_ERROR(game, msg):
146 game.tui.do_refresh = True
147 cmd_PLAY_ERROR.argtypes = 'string'
149 def cmd_GAME_ERROR(game, msg):
150 game.tui.log_msg('? game error: ' + msg)
151 game.tui.do_refresh = True
152 cmd_GAME_ERROR.argtypes = 'string'
154 def cmd_ARGUMENT_ERROR(game, msg):
155 game.tui.log_msg('? syntax error: ' + msg)
156 game.tui.do_refresh = True
157 cmd_ARGUMENT_ERROR.argtypes = 'string'
159 def cmd_ANNOTATION(game, position, msg):
160 game.info_db[position] = msg
161 if game.tui.mode.shows_info:
162 game.tui.do_refresh = True
163 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
165 def cmd_TASKS(game, tasks_comma_separated):
166 game.tasks = tasks_comma_separated.split(',')
167 cmd_TASKS.argtypes = 'string'
169 def cmd_THING_TYPE(game, thing_type, symbol_hint):
170 game.thing_types[thing_type] = symbol_hint
171 cmd_THING_TYPE.argtypes = 'string char'
173 def cmd_TERRAIN(game, terrain_char, terrain_desc):
174 game.terrains[terrain_char] = terrain_desc
175 cmd_TERRAIN.argtypes = 'char string'
179 cmd_PONG.argtypes = ''
181 class Game(GameBase):
182 turn_complete = False
186 def __init__(self, *args, **kwargs):
187 super().__init__(*args, **kwargs)
188 self.register_command(cmd_LOGIN_OK)
189 self.register_command(cmd_PONG)
190 self.register_command(cmd_CHAT)
191 self.register_command(cmd_PLAYER_ID)
192 self.register_command(cmd_TURN)
193 self.register_command(cmd_THING)
194 self.register_command(cmd_THING_TYPE)
195 self.register_command(cmd_THING_NAME)
196 self.register_command(cmd_THING_CHAR)
197 self.register_command(cmd_TERRAIN)
198 self.register_command(cmd_MAP)
199 self.register_command(cmd_MAP_CONTROL)
200 self.register_command(cmd_PORTAL)
201 self.register_command(cmd_ANNOTATION)
202 self.register_command(cmd_GAME_STATE_COMPLETE)
203 self.register_command(cmd_ARGUMENT_ERROR)
204 self.register_command(cmd_GAME_ERROR)
205 self.register_command(cmd_PLAY_ERROR)
206 self.register_command(cmd_TASKS)
207 self.register_command(cmd_FOV)
208 self.map_content = ''
214 def get_string_options(self, string_option_type):
215 if string_option_type == 'map_geometry':
216 return ['Hex', 'Square']
217 elif string_option_type == 'thing_type':
218 return self.thing_types.keys()
221 def get_command(self, command_name):
222 from functools import partial
223 f = partial(self.commands[command_name], self)
224 f.argtypes = self.commands[command_name].argtypes
231 def __init__(self, name, help_intro, has_input_prompt=False,
232 shows_info=False, is_intro = False):
234 self.has_input_prompt = has_input_prompt
235 self.shows_info = shows_info
236 self.is_intro = is_intro
237 self.help_intro = help_intro
239 def __init__(self, host):
243 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
244 self.mode_study = self.Mode('study', 'This mode allows you to study the map and its tiles in detail. Move the question mark over a tile, and the right half of the screen will show detailed information on it.', shows_info=True)
245 self.mode_edit = self.Mode('edit', 'This mode allows you to change the map tile you currently stand on (if your map editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.')
246 self.mode_annotate = self.Mode('annotate', 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.', has_input_prompt=True, shows_info=True)
247 self.mode_portal = self.Mode('portal', 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your map editing password authorizes you so). Enter or edit a URL to imprint a teleportation target; enter emptiness to remove a pre-existing teleportation target. Hit Return to leave.', has_input_prompt=True, shows_info=True)
248 self.mode_chat = self.Mode('chat', 'This mode allows you to engage in chit-chat with other users. Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message. Lines that start with a "/" are used for commands like:', has_input_prompt=True)
249 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
250 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
251 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
252 self.mode_teleport = self.Mode('teleport', 'Follow the instructions to re-connect and log-in to another server, or enter anything else to abort.', has_input_prompt=True)
253 self.mode_password = self.Mode('password', 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.', has_input_prompt=True)
256 self.parser = Parser(self.game)
258 self.do_refresh = True
259 self.queue = queue.Queue()
260 self.login_name = None
261 self.map_mode = 'terrain'
262 self.password = 'foo'
263 self.switch_mode('waiting_for_server')
265 'switch_to_chat': 't',
266 'switch_to_play': 'p',
267 'switch_to_password': 'P',
268 'switch_to_annotate': 'M',
269 'switch_to_portal': 'T',
270 'switch_to_study': '?',
271 'switch_to_edit': 'm',
275 'toggle_map_mode': 'M',
276 'hex_move_upleft': 'w',
277 'hex_move_upright': 'e',
278 'hex_move_right': 'd',
279 'hex_move_downright': 'x',
280 'hex_move_downleft': 'y',
281 'hex_move_left': 'a',
282 'square_move_up': 'w',
283 'square_move_left': 'a',
284 'square_move_down': 's',
285 'square_move_right': 'd',
287 if os.path.isfile('config.json'):
288 with open('config.json', 'r') as f:
289 keys_conf = json.loads(f.read())
291 self.keys[k] = keys_conf[k]
292 self.show_help = False
293 self.disconnected = True
294 self.force_instant_connect = True
295 self.input_lines = []
297 curses.wrapper(self.loop)
304 def handle_recv(msg):
310 self.log_msg('@ attempting connect')
311 socket_client_class = PlomSocketClient
312 if self.host.startswith('ws://') or self.host.startswith('wss://'):
313 socket_client_class = WebSocketClient
315 self.socket = socket_client_class(handle_recv, self.host)
316 self.socket_thread = threading.Thread(target=self.socket.run)
317 self.socket_thread.start()
318 self.disconnected = False
319 self.game.thing_types = {}
320 self.game.terrains = {}
321 self.socket.send('TASKS')
322 self.socket.send('TERRAINS')
323 self.socket.send('THING_TYPES')
324 self.switch_mode('login')
325 except ConnectionRefusedError:
326 self.log_msg('@ server connect failure')
327 self.disconnected = True
328 self.switch_mode('waiting_for_server')
329 self.do_refresh = True
332 self.log_msg('@ attempting reconnect')
334 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
335 # conditions with ws4py, find out what exactly
336 self.switch_mode('waiting_for_server')
341 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
342 raise BrokenSocketConnection
343 self.socket.send(msg)
344 except (BrokenPipeError, BrokenSocketConnection):
345 self.log_msg('@ server disconnected :(')
346 self.disconnected = True
347 self.force_instant_connect = True
348 self.do_refresh = True
350 def log_msg(self, msg):
352 if len(self.log) > 100:
353 self.log = self.log[-100:]
355 def query_info(self):
356 self.send('GET_ANNOTATION ' + str(self.explorer))
358 def restore_input_values(self):
359 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
360 info = self.game.info_db[self.explorer]
363 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
364 self.input_ = self.game.portals[self.explorer]
365 elif self.mode.name == 'password':
366 self.input_ = self.password
368 def switch_mode(self, mode_name):
369 self.map_mode = 'terrain'
370 self.mode = getattr(self, 'mode_' + mode_name)
371 if self.mode.shows_info:
372 player = self.game.get_thing(self.game.player_id)
373 self.explorer = YX(player.position.y, player.position.x)
374 if self.mode.name == 'waiting_for_server':
375 self.log_msg('@ waiting for server …')
376 if self.mode.name == 'edit':
377 self.show_help = True
378 elif self.mode.name == 'login':
380 self.send('LOGIN ' + quote(self.login_name))
382 self.log_msg('@ enter username')
383 elif self.mode.name == 'teleport':
384 self.log_msg("@ May teleport to %s" % (self.teleport_target_host)),
385 self.log_msg("@ Enter 'YES!' to enthusiastically affirm.");
386 self.restore_input_values()
388 def loop(self, stdscr):
391 def safe_addstr(y, x, line):
392 if y < self.size.y - 1 or x + len(line) < self.size.x:
393 stdscr.addstr(y, x, line)
394 else: # workaround to <https://stackoverflow.com/q/7063128>
395 cut_i = self.size.x - x - 1
397 last_char = line[cut_i]
398 stdscr.addstr(y, self.size.x - 2, last_char)
399 stdscr.insstr(y, self.size.x - 2, ' ')
400 stdscr.addstr(y, x, cut)
402 def handle_input(msg):
403 command, args = self.parser.parse(msg)
406 def msg_into_lines_of_width(msg, width):
410 for i in range(len(msg)):
411 if x >= width or msg[i] == "\n":
421 def reset_screen_size():
422 self.size = YX(*stdscr.getmaxyx())
423 self.size = self.size - YX(self.size.y % 4, 0)
424 self.size = self.size - YX(0, self.size.x % 4)
425 self.window_width = int(self.size.x / 2)
427 def recalc_input_lines():
428 if not self.mode.has_input_prompt:
429 self.input_lines = []
431 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
434 def move_explorer(direction):
435 target = self.game.map_geometry.move(self.explorer, direction)
437 self.explorer = target
444 for line in self.log:
445 lines += msg_into_lines_of_width(line, self.window_width)
448 max_y = self.size.y - len(self.input_lines)
449 for i in range(len(lines)):
450 if (i >= max_y - height_header):
452 safe_addstr(max_y - i - 1, self.window_width, lines[i])
455 if not self.game.turn_complete:
457 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
458 info = 'outside field of view'
459 if self.game.fov[pos_i] == '.':
460 terrain_char = self.game.map_content[pos_i]
462 if terrain_char in self.game.terrains:
463 terrain_desc = self.game.terrains[terrain_char]
464 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
465 for t in self.game.things:
466 if t.position == self.explorer:
467 info += 'THING: %s / %s' % (t.type_,
468 self.game.thing_types[t.type_])
469 if hasattr(t, 'player_char'):
470 info += t.player_char
471 if hasattr(t, 'name'):
472 info += ' (%s)' % t.name
474 if self.explorer in self.game.portals:
475 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
477 info += 'PORTAL: (none)\n'
478 if self.explorer in self.game.info_db:
479 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
481 info += 'ANNOTATION: waiting …'
482 lines = msg_into_lines_of_width(info, self.window_width)
484 for i in range(len(lines)):
485 y = height_header + i
486 if y >= self.size.y - len(self.input_lines):
488 safe_addstr(y, self.window_width, lines[i])
491 y = self.size.y - len(self.input_lines)
492 for i in range(len(self.input_lines)):
493 safe_addstr(y, self.window_width, self.input_lines[i])
497 if not self.game.turn_complete:
499 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
502 help = "hit [%s] for help" % self.keys['help']
503 if self.mode.has_input_prompt:
504 help = "enter /help for help"
505 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
508 if not self.game.turn_complete:
511 map_content = self.game.map_content
512 if self.map_mode == 'control':
513 map_content = self.game.map_control_content
514 for y in range(self.game.map_geometry.size.y):
515 start = self.game.map_geometry.size.x * y
516 end = start + self.game.map_geometry.size.x
517 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
518 if self.map_mode == 'terrain':
520 for t in self.game.things:
521 symbol = self.game.thing_types[t.type_]
523 if hasattr(t, 'player_char'):
524 meta_char = t.player_char
525 if t.position in used_positions:
527 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
528 used_positions += [t.position]
529 if self.mode.shows_info:
530 map_lines_split[self.explorer.y][self.explorer.x] = '??'
532 if type(self.game.map_geometry) == MapGeometryHex:
534 for line in map_lines_split:
535 map_lines += [indent*' ' + ''.join(line)]
536 indent = 0 if indent else 1
538 for line in map_lines_split:
539 map_lines += [''.join(line)]
540 window_center = YX(int(self.size.y / 2),
541 int(self.window_width / 2))
542 player = self.game.get_thing(self.game.player_id)
543 center = player.position
544 if self.mode.shows_info:
545 center = self.explorer
546 center = YX(center.y, center.x * 2)
547 offset = center - window_center
548 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
550 term_y = max(0, -offset.y)
551 term_x = max(0, -offset.x)
552 map_y = max(0, offset.y)
553 map_x = max(0, offset.x)
554 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
555 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
556 safe_addstr(term_y, term_x, to_draw)
561 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
562 self.mode.help_intro)
563 if self.mode == self.mode_play:
564 content += "Available actions:\n"
565 if 'MOVE' in self.game.tasks:
566 content += "[%s] – move player\n" % ','.join(self.movement_keys)
567 if 'PICK_UP' in self.game.tasks:
568 content += "[%s] – take thing under player\n" % self.keys['take_thing']
569 if 'DROP' in self.game.tasks:
570 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
571 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
572 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
573 content += 'Other modes available from here:\n'
574 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
575 content += '[%s] – study mode\n' % self.keys['switch_to_study']
576 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
577 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
578 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
579 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
580 elif self.mode == self.mode_study:
581 content += 'Available actions:\n'
582 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
583 content += '[%s] – toggle view between terrain, and password protection areas\n' % self.keys['toggle_map_mode']
584 content += '\n\nOther modes available from here:'
585 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
586 content += '[%s] – play mode\n' % self.keys['switch_to_play']
587 elif self.mode == self.mode_chat:
588 content += '/nick NAME – re-name yourself to NAME\n'
589 #content += '/msg USER TEXT – send TEXT to USER\n'
590 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
591 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
592 for i in range(self.size.y):
594 self.window_width * (not self.mode.has_input_prompt),
595 ' '*self.window_width)
597 for line in content.split('\n'):
598 lines += msg_into_lines_of_width(line, self.window_width)
599 for i in range(len(lines)):
603 self.window_width * (not self.mode.has_input_prompt),
608 if self.mode.has_input_prompt:
611 if self.mode.shows_info:
616 if not self.mode.is_intro:
622 curses.curs_set(False) # hide cursor
623 curses.use_default_colors();
626 self.explorer = YX(0, 0)
629 interval = datetime.timedelta(seconds=5)
630 last_ping = datetime.datetime.now() - interval
632 if self.disconnected and self.force_instant_connect:
633 self.force_instant_connect = False
635 now = datetime.datetime.now()
636 if now - last_ping > interval:
637 if self.disconnected:
644 self.do_refresh = False
647 msg = self.queue.get(block=False)
652 key = stdscr.getkey()
653 self.do_refresh = True
656 self.show_help = False
657 if key == 'KEY_RESIZE':
659 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
660 self.input_ = self.input_[:-1]
661 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
662 self.show_help = True
664 self.restore_input_values()
665 elif self.mode.has_input_prompt and key != '\n': # Return key
667 max_length = self.window_width * self.size.y - len(input_prompt) - 1
668 if len(self.input_) > max_length:
669 self.input_ = self.input_[:max_length]
670 elif key == self.keys['help'] and self.mode != self.mode_edit:
671 self.show_help = True
672 elif self.mode == self.mode_login and key == '\n':
673 self.login_name = self.input_
674 self.send('LOGIN ' + quote(self.input_))
676 elif self.mode == self.mode_password and key == '\n':
677 if self.input_ == '':
679 self.password = self.input_
681 self.switch_mode('play')
682 elif self.mode == self.mode_chat and key == '\n':
683 if self.input_ == '':
685 if self.input_[0] == '/': # FIXME fails on empty input
686 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
687 self.switch_mode('play')
688 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
689 self.switch_mode('study')
690 elif self.input_.startswith('/nick'):
691 tokens = self.input_.split(maxsplit=1)
693 self.send('NICK ' + quote(tokens[1]))
695 self.log_msg('? need login name')
696 #elif self.input_.startswith('/msg'):
697 # tokens = self.input_.split(maxsplit=2)
698 # if len(tokens) == 3:
699 # self.send('QUERY %s %s' % (quote(tokens[1]),
702 # self.log_msg('? need message target and message')
704 self.log_msg('? unknown command')
706 self.send('ALL ' + quote(self.input_))
708 elif self.mode == self.mode_annotate and key == '\n':
709 if self.input_ == '':
711 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
712 quote(self.password)))
714 self.switch_mode('play')
715 elif self.mode == self.mode_portal and key == '\n':
716 if self.input_ == '':
718 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
719 quote(self.password)))
721 self.switch_mode('play')
722 elif self.mode == self.mode_teleport and key == '\n':
723 if self.input_ == 'YES!':
724 self.host = self.teleport_target_host
727 self.log_msg('@ teleport aborted')
728 self.switch_mode('play')
730 elif self.mode == self.mode_study:
731 if key == self.keys['switch_to_chat']:
732 self.switch_mode('chat')
733 elif key == self.keys['switch_to_play']:
734 self.switch_mode('play')
735 elif key == self.keys['toggle_map_mode']:
736 if self.map_mode == 'terrain':
737 self.map_mode = 'control'
739 self.map_mode = 'terrain'
740 elif key in self.movement_keys:
741 move_explorer(self.movement_keys[key])
742 elif self.mode == self.mode_play:
743 if key == self.keys['switch_to_chat']:
744 self.switch_mode('chat')
745 elif key == self.keys['switch_to_study']:
746 self.switch_mode('study')
747 elif key == self.keys['switch_to_annotate']:
748 self.switch_mode('annotate')
749 elif key == self.keys['switch_to_portal']:
750 self.switch_mode('portal')
751 elif key == self.keys['switch_to_password']:
752 self.switch_mode('password')
753 if key == self.keys['switch_to_edit'] and\
754 'WRITE' in self.game.tasks:
755 self.switch_mode('edit')
756 elif key == self.keys['flatten'] and\
757 'FLATTEN_SURROUNDINGS' in self.game.tasks:
758 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
759 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
760 self.send('TASK:PICK_UP')
761 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
762 self.send('TASK:DROP')
763 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
764 self.send('TASK:MOVE ' + self.movement_keys[key])
765 elif self.mode == self.mode_edit:
766 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
767 self.switch_mode('play')
769 TUI('localhost:5000')