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_MAP(game, geometry, size, content):
91 map_geometry_class = globals()['MapGeometry' + geometry]
92 game.map_geometry = map_geometry_class(size)
93 game.map_content = content
94 if type(game.map_geometry) == MapGeometrySquare:
95 game.tui.movement_keys = {
96 game.tui.keys['square_move_up']: 'UP',
97 game.tui.keys['square_move_left']: 'LEFT',
98 game.tui.keys['square_move_down']: 'DOWN',
99 game.tui.keys['square_move_right']: 'RIGHT',
101 elif type(game.map_geometry) == MapGeometryHex:
102 game.tui.movement_keys = {
103 game.tui.keys['hex_move_upleft']: 'UPLEFT',
104 game.tui.keys['hex_move_upright']: 'UPRIGHT',
105 game.tui.keys['hex_move_right']: 'RIGHT',
106 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
107 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
108 game.tui.keys['hex_move_left']: 'LEFT',
110 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
112 def cmd_FOV(game, content):
114 cmd_FOV.argtypes = 'string'
116 def cmd_MAP_CONTROL(game, content):
117 game.map_control_content = content
118 cmd_MAP_CONTROL.argtypes = 'string'
120 def cmd_GAME_STATE_COMPLETE(game):
122 if game.tui.mode.name == 'post_login_wait':
123 game.tui.switch_mode('play')
124 if game.tui.mode.shows_info:
125 game.tui.query_info()
126 player = game.get_thing(game.player_id)
127 if player.position in game.portals:
128 game.tui.teleport_target_host = game.portals[player.position]
129 game.tui.switch_mode('teleport')
130 game.turn_complete = True
131 game.tui.do_refresh = True
132 cmd_GAME_STATE_COMPLETE.argtypes = ''
134 def cmd_PORTAL(game, position, msg):
135 game.portals[position] = msg
136 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
138 def cmd_PLAY_ERROR(game, msg):
140 game.tui.do_refresh = True
141 cmd_PLAY_ERROR.argtypes = 'string'
143 def cmd_GAME_ERROR(game, msg):
144 game.tui.log_msg('? game error: ' + msg)
145 game.tui.do_refresh = True
146 cmd_GAME_ERROR.argtypes = 'string'
148 def cmd_ARGUMENT_ERROR(game, msg):
149 game.tui.log_msg('? syntax error: ' + msg)
150 game.tui.do_refresh = True
151 cmd_ARGUMENT_ERROR.argtypes = 'string'
153 def cmd_ANNOTATION(game, position, msg):
154 game.info_db[position] = msg
155 if game.tui.mode.shows_info:
156 game.tui.do_refresh = True
157 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
159 def cmd_TASKS(game, tasks_comma_separated):
160 game.tasks = tasks_comma_separated.split(',')
161 cmd_TASKS.argtypes = 'string'
163 def cmd_THING_TYPE(game, thing_type, symbol_hint):
164 game.thing_types[thing_type] = symbol_hint
165 cmd_THING_TYPE.argtypes = 'string char'
169 cmd_PONG.argtypes = ''
171 class Game(GameBase):
172 turn_complete = False
176 def __init__(self, *args, **kwargs):
177 super().__init__(*args, **kwargs)
178 self.register_command(cmd_LOGIN_OK)
179 self.register_command(cmd_PONG)
180 self.register_command(cmd_CHAT)
181 self.register_command(cmd_PLAYER_ID)
182 self.register_command(cmd_TURN)
183 self.register_command(cmd_THING)
184 self.register_command(cmd_THING_TYPE)
185 self.register_command(cmd_THING_NAME)
186 self.register_command(cmd_MAP)
187 self.register_command(cmd_MAP_CONTROL)
188 self.register_command(cmd_PORTAL)
189 self.register_command(cmd_ANNOTATION)
190 self.register_command(cmd_GAME_STATE_COMPLETE)
191 self.register_command(cmd_ARGUMENT_ERROR)
192 self.register_command(cmd_GAME_ERROR)
193 self.register_command(cmd_PLAY_ERROR)
194 self.register_command(cmd_TASKS)
195 self.register_command(cmd_FOV)
196 self.map_content = ''
201 def get_string_options(self, string_option_type):
202 if string_option_type == 'map_geometry':
203 return ['Hex', 'Square']
204 elif string_option_type == 'thing_type':
205 return self.thing_types.keys()
208 def get_command(self, command_name):
209 from functools import partial
210 f = partial(self.commands[command_name], self)
211 f.argtypes = self.commands[command_name].argtypes
218 def __init__(self, name, help_intro, has_input_prompt=False,
219 shows_info=False, is_intro = False):
221 self.has_input_prompt = has_input_prompt
222 self.shows_info = shows_info
223 self.is_intro = is_intro
224 self.help_intro = help_intro
226 def __init__(self, host):
230 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
231 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)
232 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.')
233 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)
234 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)
235 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)
236 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
237 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
238 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
239 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)
240 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)
243 self.parser = Parser(self.game)
245 self.do_refresh = True
246 self.queue = queue.Queue()
247 self.login_name = None
248 self.map_mode = 'terrain'
249 self.password = 'foo'
250 self.switch_mode('waiting_for_server')
252 'switch_to_chat': 't',
253 'switch_to_play': 'p',
254 'switch_to_password': 'P',
255 'switch_to_annotate': 'M',
256 'switch_to_portal': 'T',
257 'switch_to_study': '?',
258 'switch_to_edit': 'm',
262 'toggle_map_mode': 'M',
263 'hex_move_upleft': 'w',
264 'hex_move_upright': 'e',
265 'hex_move_right': 'd',
266 'hex_move_downright': 'x',
267 'hex_move_downleft': 'y',
268 'hex_move_left': 'a',
269 'square_move_up': 'w',
270 'square_move_left': 'a',
271 'square_move_down': 's',
272 'square_move_right': 'd',
274 if os.path.isfile('config.json'):
275 with open('config.json', 'r') as f:
276 keys_conf = json.loads(f.read())
278 self.keys[k] = keys_conf[k]
279 self.show_help = False
280 self.disconnected = True
281 self.force_instant_connect = True
282 self.input_lines = []
284 curses.wrapper(self.loop)
291 def handle_recv(msg):
297 self.log_msg('@ attempting connect')
298 socket_client_class = PlomSocketClient
299 if self.host.startswith('ws://') or self.host.startswith('wss://'):
300 socket_client_class = WebSocketClient
302 self.socket = socket_client_class(handle_recv, self.host)
303 self.socket_thread = threading.Thread(target=self.socket.run)
304 self.socket_thread.start()
305 self.disconnected = False
306 self.game.thing_types = {}
307 self.socket.send('TASKS')
308 self.socket.send('THING_TYPES')
309 self.switch_mode('login')
310 except ConnectionRefusedError:
311 self.log_msg('@ server connect failure')
312 self.disconnected = True
313 self.switch_mode('waiting_for_server')
314 self.do_refresh = True
317 self.log_msg('@ attempting reconnect')
319 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
320 # conditions with ws4py, find out what exactly
321 self.switch_mode('waiting_for_server')
326 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
327 raise BrokenSocketConnection
328 self.socket.send(msg)
329 except (BrokenPipeError, BrokenSocketConnection):
330 self.log_msg('@ server disconnected :(')
331 self.disconnected = True
332 self.force_instant_connect = True
333 self.do_refresh = True
335 def log_msg(self, msg):
337 if len(self.log) > 100:
338 self.log = self.log[-100:]
340 def query_info(self):
341 self.send('GET_ANNOTATION ' + str(self.explorer))
343 def restore_input_values(self):
344 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
345 info = self.game.info_db[self.explorer]
348 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
349 self.input_ = self.game.portals[self.explorer]
350 elif self.mode.name == 'password':
351 self.input_ = self.password
353 def switch_mode(self, mode_name):
354 self.map_mode = 'terrain'
355 self.mode = getattr(self, 'mode_' + mode_name)
356 if self.mode.shows_info:
357 player = self.game.get_thing(self.game.player_id)
358 self.explorer = YX(player.position.y, player.position.x)
359 if self.mode.name == 'waiting_for_server':
360 self.log_msg('@ waiting for server …')
361 if self.mode.name == 'edit':
362 self.show_help = True
363 elif self.mode.name == 'login':
365 self.send('LOGIN ' + quote(self.login_name))
367 self.log_msg('@ enter username')
368 elif self.mode.name == 'teleport':
369 self.log_msg("@ May teleport to %s" % (self.teleport_target_host)),
370 self.log_msg("@ Enter 'YES!' to enthusiastically affirm.");
371 self.restore_input_values()
373 def loop(self, stdscr):
376 def safe_addstr(y, x, line):
377 if y < self.size.y - 1 or x + len(line) < self.size.x:
378 stdscr.addstr(y, x, line)
379 else: # workaround to <https://stackoverflow.com/q/7063128>
380 cut_i = self.size.x - x - 1
382 last_char = line[cut_i]
383 stdscr.addstr(y, self.size.x - 2, last_char)
384 stdscr.insstr(y, self.size.x - 2, ' ')
385 stdscr.addstr(y, x, cut)
387 def handle_input(msg):
388 command, args = self.parser.parse(msg)
391 def msg_into_lines_of_width(msg, width):
395 for i in range(len(msg)):
396 if x >= width or msg[i] == "\n":
406 def reset_screen_size():
407 self.size = YX(*stdscr.getmaxyx())
408 self.size = self.size - YX(self.size.y % 4, 0)
409 self.size = self.size - YX(0, self.size.x % 4)
410 self.window_width = int(self.size.x / 2)
412 def recalc_input_lines():
413 if not self.mode.has_input_prompt:
414 self.input_lines = []
416 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
419 def move_explorer(direction):
420 target = self.game.map_geometry.move(self.explorer, direction)
422 self.explorer = target
429 for line in self.log:
430 lines += msg_into_lines_of_width(line, self.window_width)
433 max_y = self.size.y - len(self.input_lines)
434 for i in range(len(lines)):
435 if (i >= max_y - height_header):
437 safe_addstr(max_y - i - 1, self.window_width, lines[i])
440 if not self.game.turn_complete:
442 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
443 info = 'outside field of view'
444 if self.game.fov[pos_i] == '.':
445 info = 'TERRAIN: %s\n' % self.game.map_content[pos_i]
446 for t in self.game.things:
447 if t.position == self.explorer:
448 info += 'THING: %s' % t.type_
449 if hasattr(t, 'name'):
450 info += ' (name: %s)' % t.name
452 if self.explorer in self.game.portals:
453 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
455 info += 'PORTAL: (none)\n'
456 if self.explorer in self.game.info_db:
457 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
459 info += 'ANNOTATION: waiting …'
460 lines = msg_into_lines_of_width(info, self.window_width)
462 for i in range(len(lines)):
463 y = height_header + i
464 if y >= self.size.y - len(self.input_lines):
466 safe_addstr(y, self.window_width, lines[i])
469 y = self.size.y - len(self.input_lines)
470 for i in range(len(self.input_lines)):
471 safe_addstr(y, self.window_width, self.input_lines[i])
475 if not self.game.turn_complete:
477 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
480 help = "hit [%s] for help" % self.keys['help']
481 if self.mode.has_input_prompt:
482 help = "enter /help for help"
483 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
486 if not self.game.turn_complete:
489 map_content = self.game.map_content
490 if self.map_mode == 'control':
491 map_content = self.game.map_control_content
492 for y in range(self.game.map_geometry.size.y):
493 start = self.game.map_geometry.size.x * y
494 end = start + self.game.map_geometry.size.x
495 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
496 if self.map_mode == 'terrain':
498 for t in self.game.things:
499 symbol = self.game.thing_types[t.type_]
500 if t.position in used_positions:
501 map_lines_split[t.position.y][t.position.x] = symbol + '+'
503 map_lines_split[t.position.y][t.position.x] = symbol + ' '
504 used_positions += [t.position]
505 if self.mode.shows_info:
506 map_lines_split[self.explorer.y][self.explorer.x] = '??'
508 if type(self.game.map_geometry) == MapGeometryHex:
510 for line in map_lines_split:
511 map_lines += [indent*' ' + ''.join(line)]
512 indent = 0 if indent else 1
514 for line in map_lines_split:
515 map_lines += [' '.join(line)]
516 window_center = YX(int(self.size.y / 2),
517 int(self.window_width / 2))
518 player = self.game.get_thing(self.game.player_id)
519 center = player.position
520 if self.mode.shows_info:
521 center = self.explorer
522 center = YX(center.y, center.x * 2)
523 offset = center - window_center
524 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
526 term_y = max(0, -offset.y)
527 term_x = max(0, -offset.x)
528 map_y = max(0, offset.y)
529 map_x = max(0, offset.x)
530 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
531 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
532 safe_addstr(term_y, term_x, to_draw)
537 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
538 self.mode.help_intro)
539 if self.mode == self.mode_play:
540 content += "Available actions:\n"
541 if 'MOVE' in self.game.tasks:
542 content += "[%s] – move player\n" % ','.join(self.movement_keys)
543 if 'PICK_UP' in self.game.tasks:
544 content += "[%s] – take thing under player\n" % self.keys['take_thing']
545 if 'DROP' in self.game.tasks:
546 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
547 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
548 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
549 content += 'Other modes available from here:\n'
550 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
551 content += '[%s] – study mode\n' % self.keys['switch_to_study']
552 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
553 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
554 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
555 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
556 elif self.mode == self.mode_study:
557 content += 'Available actions:\n'
558 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
559 content += '[%s] – toggle view between terrain, and password protection areas\n' % self.keys['toggle_map_mode']
560 content += '\n\nOther modes available from here:'
561 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
562 content += '[%s] – play mode\n' % self.keys['switch_to_play']
563 elif self.mode == self.mode_chat:
564 content += '/nick NAME – re-name yourself to NAME\n'
565 #content += '/msg USER TEXT – send TEXT to USER\n'
566 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
567 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
568 for i in range(self.size.y):
570 self.window_width * (not self.mode.has_input_prompt),
571 ' '*self.window_width)
573 for line in content.split('\n'):
574 lines += msg_into_lines_of_width(line, self.window_width)
575 for i in range(len(lines)):
579 self.window_width * (not self.mode.has_input_prompt),
584 if self.mode.has_input_prompt:
587 if self.mode.shows_info:
592 if not self.mode.is_intro:
598 curses.curs_set(False) # hide cursor
599 curses.use_default_colors();
602 self.explorer = YX(0, 0)
605 interval = datetime.timedelta(seconds=5)
606 last_ping = datetime.datetime.now() - interval
608 if self.disconnected and self.force_instant_connect:
609 self.force_instant_connect = False
611 now = datetime.datetime.now()
612 if now - last_ping > interval:
613 if self.disconnected:
620 self.do_refresh = False
623 msg = self.queue.get(block=False)
628 key = stdscr.getkey()
629 self.do_refresh = True
632 self.show_help = False
633 if key == 'KEY_RESIZE':
635 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
636 self.input_ = self.input_[:-1]
637 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
638 self.show_help = True
640 self.restore_input_values()
641 elif self.mode.has_input_prompt and key != '\n': # Return key
643 max_length = self.window_width * self.size.y - len(input_prompt) - 1
644 if len(self.input_) > max_length:
645 self.input_ = self.input_[:max_length]
646 elif key == self.keys['help'] and self.mode != self.mode_edit:
647 self.show_help = True
648 elif self.mode == self.mode_login and key == '\n':
649 self.login_name = self.input_
650 self.send('LOGIN ' + quote(self.input_))
652 elif self.mode == self.mode_password and key == '\n':
653 if self.input_ == '':
655 self.password = self.input_
657 self.switch_mode('play')
658 elif self.mode == self.mode_chat and key == '\n':
659 if self.input_ == '':
661 if self.input_[0] == '/': # FIXME fails on empty input
662 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
663 self.switch_mode('play')
664 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
665 self.switch_mode('study')
666 elif self.input_.startswith('/nick'):
667 tokens = self.input_.split(maxsplit=1)
669 self.send('NICK ' + quote(tokens[1]))
671 self.log_msg('? need login name')
672 #elif self.input_.startswith('/msg'):
673 # tokens = self.input_.split(maxsplit=2)
674 # if len(tokens) == 3:
675 # self.send('QUERY %s %s' % (quote(tokens[1]),
678 # self.log_msg('? need message target and message')
680 self.log_msg('? unknown command')
682 self.send('ALL ' + quote(self.input_))
684 elif self.mode == self.mode_annotate and key == '\n':
685 if self.input_ == '':
687 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
688 quote(self.password)))
690 self.switch_mode('play')
691 elif self.mode == self.mode_portal and key == '\n':
692 if self.input_ == '':
694 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
695 quote(self.password)))
697 self.switch_mode('play')
698 elif self.mode == self.mode_teleport and key == '\n':
699 if self.input_ == 'YES!':
700 self.host = self.teleport_target_host
703 self.log_msg('@ teleport aborted')
704 self.switch_mode('play')
706 elif self.mode == self.mode_study:
707 if key == self.keys['switch_to_chat']:
708 self.switch_mode('chat')
709 elif key == self.keys['switch_to_play']:
710 self.switch_mode('play')
711 elif key == self.keys['toggle_map_mode']:
712 if self.map_mode == 'terrain':
713 self.map_mode = 'control'
715 self.map_mode = 'terrain'
716 elif key in self.movement_keys:
717 move_explorer(self.movement_keys[key])
718 elif self.mode == self.mode_play:
719 if key == self.keys['switch_to_chat']:
720 self.switch_mode('chat')
721 elif key == self.keys['switch_to_study']:
722 self.switch_mode('study')
723 elif key == self.keys['switch_to_annotate']:
724 self.switch_mode('annotate')
725 elif key == self.keys['switch_to_portal']:
726 self.switch_mode('portal')
727 elif key == self.keys['switch_to_password']:
728 self.switch_mode('password')
729 if key == self.keys['switch_to_edit'] and\
730 'WRITE' in self.game.tasks:
731 self.switch_mode('edit')
732 elif key == self.keys['flatten'] and\
733 'FLATTEN_SURROUNDINGS' in self.game.tasks:
734 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
735 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
736 self.send('TASK:PICK_UP')
737 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
738 self.send('TASK:DROP')
739 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
740 self.send('TASK:MOVE ' + self.movement_keys[key])
741 elif self.mode == self.mode_edit:
742 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
743 self.switch_mode('play')
745 TUI('localhost:5000')