6 from plomrogue.io_tcp import PlomSocket
7 from plomrogue.game import GameBase
8 from plomrogue.parser import Parser
9 from plomrogue.mapping import YX
10 from plomrogue.things import ThingBase
11 from plomrogue.misc import quote
13 # TODO: fix screen refreshes on intermediary map results
15 def cmd_TURN(game, n):
19 cmd_TURN.argtypes = 'int:nonneg'
21 def cmd_LOGIN_OK(game):
22 game.tui.switch_mode('post_login_wait')
23 game.tui.socket.send('GET_GAMESTATE')
24 game.tui.log_msg('@ welcome')
25 cmd_LOGIN_OK.argtypes = ''
27 def cmd_CHAT(game, msg):
28 game.tui.log_msg('# ' + msg)
29 game.tui.do_refresh = True
30 cmd_CHAT.argtypes = 'string'
32 def cmd_PLAYER_ID(game, player_id):
33 game.player_id = player_id
34 cmd_PLAYER_ID.argtypes = 'int:nonneg'
36 def cmd_THING_POS(game, thing_id, position):
37 t = game.get_thing(thing_id, True)
39 cmd_THING_POS.argtypes = 'int:nonneg yx_tuple:nonneg'
41 def cmd_THING_NAME(game, thing_id, name):
42 t = game.get_thing(thing_id, True)
44 cmd_THING_NAME.argtypes = 'int:nonneg string'
46 def cmd_MAP(game, size, content):
48 game.map_content = content
49 cmd_MAP.argtypes = 'yx_tuple:pos string'
51 def cmd_GAME_STATE_COMPLETE(game):
53 if game.tui.mode.name == 'post_login_wait':
54 game.tui.switch_mode('play')
55 if game.tui.mode.shows_info:
57 game.tui.do_refresh = True
58 cmd_GAME_STATE_COMPLETE.argtypes = ''
60 def cmd_PORTAL(game, position, msg):
61 game.portals[position] = msg
62 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
64 def cmd_PLAY_ERROR(game, msg):
65 game.tui.log_msg('imagine the screen flicker (TODO)')
66 game.tui.do_refresh = True
67 cmd_PLAY_ERROR.argtypes = 'string'
69 def cmd_ARGUMENT_ERROR(game, msg):
70 game.tui.log_msg('? syntax error: ' + msg)
71 game.tui.do_refresh = True
72 cmd_ARGUMENT_ERROR.argtypes = 'string'
74 def cmd_ANNOTATION(game, position, msg):
75 game.info_db[position] = msg
76 if game.tui.mode.shows_info:
77 game.tui.do_refresh = True
78 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
80 def recv_loop(plom_socket, q):
81 for msg in plom_socket.recv():
85 commands = {'LOGIN_OK': cmd_LOGIN_OK,
87 'PLAYER_ID': cmd_PLAYER_ID,
89 'THING_POS': cmd_THING_POS,
90 'THING_NAME': cmd_THING_NAME,
93 'ANNOTATION': cmd_ANNOTATION,
94 'GAME_STATE_COMPLETE': cmd_GAME_STATE_COMPLETE,
95 'ARGUMENT_ERROR': cmd_ARGUMENT_ERROR,
96 'PLAY_ERROR': cmd_PLAY_ERROR}
97 thing_type = ThingBase
99 def __init__(self, *args, **kwargs):
100 super().__init__(*args, **kwargs)
101 self.map_size = YX(0, 0)
102 self.map_content = ''
107 def get_command(self, command_name):
108 from functools import partial
109 f = partial(self.commands[command_name], self)
110 f.argtypes = self.commands[command_name].argtypes
115 def __init__(self, socket, q, game):
118 self.parser = Parser(self.game)
122 self.do_refresh = True
123 curses.wrapper(self.loop)
125 def log_msg(self, msg):
127 if len(self.log) > 100:
128 self.log = self.log[-100:]
130 def query_info(self):
131 self.socket.send('GET_ANNOTATION ' + str(self.explorer))
133 def switch_mode(self, mode_name, keep_position = False):
134 self.mode = getattr(self, 'mode_' + mode_name)
135 if self.mode.shows_info and not keep_position:
136 player = self.game.get_thing(self.game.player_id, False)
137 self.explorer = YX(player.position.y, player.position.x)
138 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
139 info = self.game.info_db[self.explorer]
142 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
143 self.input_ = self.game.portals[self.explorer]
145 def loop(self, stdscr):
149 def __init__(self, name, has_input_prompt=False, shows_info=False,
152 self.has_input_prompt = has_input_prompt
153 self.shows_info = shows_info
154 self.is_intro = is_intro
156 def handle_input(msg):
157 command, args = self.parser.parse(msg)
160 def msg_into_lines_of_width(msg, width):
164 for i in range(len(msg)):
166 if x >= width or msg[i] == "\n":
175 def reset_screen_size():
176 self.size = YX(*stdscr.getmaxyx())
177 self.size = self.size - YX(0, 1) # ugly TODO ncurses bug workaround, FIXME
178 self.size = self.size - YX(self.size.y % 2, 0)
179 self.size = self.size - YX(0, self.size.x % 4)
180 self.window_width = int(self.size.x / 2)
182 def recalc_input_lines():
183 if not self.mode.has_input_prompt:
184 self.input_lines = []
186 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
189 def move_explorer(direction):
190 # TODO movement constraints
191 if direction == 'up':
192 self.explorer += YX(-1, 0)
193 elif direction == 'left':
194 self.explorer += YX(0, -1)
195 elif direction == 'down':
196 self.explorer += YX(1, 0)
197 elif direction == 'right':
198 self.explorer += YX(0, 1)
203 for line in self.log:
204 lines += msg_into_lines_of_width(line, self.window_width)
207 max_y = self.size.y - len(self.input_lines)
208 for i in range(len(lines)):
209 if (i >= max_y - height_header):
211 stdscr.addstr(max_y - i - 1, self.window_width, lines[i])
214 if self.explorer in self.game.portals:
215 info = 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
217 info = 'PORTAL: (none)\n'
218 if self.explorer in self.game.info_db:
219 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
221 info += 'ANNOTATION: waiting …'
222 lines = msg_into_lines_of_width(info, self.window_width)
224 for i in range(len(lines)):
225 y = height_header + i
226 if y >= self.size.y - len(self.input_lines):
228 stdscr.addstr(y, self.window_width, lines[i])
231 y = self.size.y - len(self.input_lines)
232 for i in range(len(self.input_lines)):
233 stdscr.addstr(y, self.window_width, self.input_lines[i])
237 stdscr.addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
240 stdscr.addstr(1, self.window_width, 'MODE: ' + self.mode.name)
244 for y in range(self.game.map_size.y):
245 start = self.game.map_size.x * y
246 end = start + self.game.map_size.x
247 map_lines_split += [list(self.game.map_content[start:end])]
248 for t in self.game.things:
249 map_lines_split[t.position.y][t.position.x] = '@'
250 if self.mode.shows_info:
251 map_lines_split[self.explorer.y][self.explorer.x] = '?'
253 for line in map_lines_split:
254 map_lines += [''.join(line)]
255 map_center = YX(int(self.game.map_size.y / 2),
256 int(self.game.map_size.x / 2))
257 window_center = YX(int(self.size.y / 2),
258 int(self.window_width / 2))
260 if self.mode.shows_info:
261 center = self.explorer
263 player = self.game.get_thing(self.game.player_id, False)
265 center = player.position
266 offset = center - window_center
267 term_y = max(0, -offset.y)
268 term_x = max(0, -offset.x)
269 map_y = max(0, offset.y)
270 map_x = max(0, offset.x)
271 while (term_y < self.size.y and map_y < self.game.map_size.y):
272 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
273 stdscr.addstr(term_y, term_x, to_draw)
280 if self.mode.has_input_prompt:
282 if self.mode.shows_info:
287 if not self.mode.is_intro:
291 self.mode_play = Mode('play')
292 self.mode_study = Mode('study', shows_info=True)
293 self.mode_edit = Mode('edit')
294 self.mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
295 self.mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
296 self.mode_chat = Mode('chat', has_input_prompt=True)
297 self.mode_login = Mode('login', has_input_prompt=True, is_intro=True)
298 self.mode_post_login_wait = Mode('post_login_wait', is_intro=True)
299 curses.curs_set(False) # hide cursor
302 self.mode = self.mode_login
303 self.explorer = YX(0, 0)
309 self.do_refresh = False
312 msg = self.queue.get(block=False)
317 key = stdscr.getkey()
318 self.do_refresh = True
321 if key == 'KEY_RESIZE':
323 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
324 self.input_ = self.input_[:-1]
325 elif self.mode.has_input_prompt and key != '\n': # Return key
327 # TODO find out why - 1 is necessary here
328 max_length = self.window_width * self.size.y - len(input_prompt) - 1
329 if len(self.input_) > max_length:
330 self.input_ = self.input_[:max_length]
331 elif self.mode == self.mode_login and key == '\n':
332 self.socket.send('LOGIN ' + quote(self.input_))
334 elif self.mode == self.mode_chat and key == '\n':
335 # TODO: query, nick, help, reconnect, unknown command
336 if self.input_[0] == ':':
337 if self.input_ in {':p', ':play'}:
338 self.switch_mode('play')
339 elif self.input_ in {':?', ':study'}:
340 self.switch_mode('study')
341 elif self.input_.startswith(':nick'):
342 tokens = self.input_.split(maxsplit=1)
344 self.socket.send('LOGIN ' + quote(tokens[1]))
346 self.log_msg('? need login name')
347 elif self.input_.startswith(':msg'):
348 tokens = self.input_.split(maxsplit=2)
350 self.socket.send('QUERY %s %s' % (quote(tokens[1]),
353 self.log_msg('? need message target and message')
355 self.log_msg('? unknown command')
357 self.socket.send('ALL ' + quote(self.input_))
359 elif self.mode == self.mode_annotate and key == '\n':
360 if (self.input_ == ''):
362 self.socket.send('ANNOTATE %s %s' % (self.explorer, quote(self.input_)))
364 self.switch_mode('study', keep_position=True)
365 elif self.mode == self.mode_portal and key == '\n':
366 if (self.input_ == ''):
368 self.socket.send('PORTAL %s %s' % (self.explorer, quote(self.input_)))
370 self.switch_mode('study', keep_position=True)
371 elif self.mode == self.mode_study:
373 self.switch_mode('chat')
375 self.switch_mode('play')
377 self.switch_mode('annotate', keep_position=True)
379 self.switch_mode('portal', keep_position=True)
383 move_explorer('left')
385 move_explorer('down')
387 move_explorer('right')
388 elif self.mode == self.mode_play:
390 self.switch_mode('chat')
392 self.switch_mode('study')
394 self.switch_mode('edit')
396 self.socket.send('TASK:FLATTEN_SURROUNDINGS')
398 self.socket.send('TASK:MOVE UP')
400 self.socket.send('TASK:MOVE LEFT')
402 self.socket.send('TASK:MOVE DOWN')
404 self.socket.send('TASK:MOVE RIGHT')
405 elif self.mode == self.mode_edit:
406 self.socket.send('TASK:WRITE ' + key)
407 self.switch_mode('play')
409 s = socket.create_connection(('127.0.0.1', 5000))
410 plom_socket = PlomSocket(s)
412 t = threading.Thread(target=recv_loop, args=(plom_socket, q))
414 TUI(plom_socket, q, Game())