game.turn = n
game.things = []
game.portals = {}
+ game.turn_complete = False
cmd_TURN.argtypes = 'int:nonneg'
def cmd_LOGIN_OK(game):
game.tui.switch_mode('post_login_wait')
- game.tui.socket.send('GET_GAMESTATE')
+ game.tui.send('GET_GAMESTATE')
game.tui.log_msg('@ welcome')
cmd_LOGIN_OK.argtypes = ''
cmd_THING_NAME.argtypes = 'int:nonneg string'
def cmd_MAP(game, size, content):
- game.map_size = size
+ game.map_geometry.size = size
game.map_content = content
cmd_MAP.argtypes = 'yx_tuple:pos string'
game.info_db = {}
if game.tui.mode.name == 'post_login_wait':
game.tui.switch_mode('play')
+ game.tui.help()
if game.tui.mode.shows_info:
game.tui.query_info()
+ player = game.get_thing(game.player_id, False)
+ if player.position in game.portals:
+ host, port = game.portals[player.position].split(':')
+ game.tui.teleport_target_host = host
+ game.tui.teleport_target_port = port
+ game.tui.switch_mode('teleport')
+ game.turn_complete = True
game.tui.do_refresh = True
cmd_GAME_STATE_COMPLETE.argtypes = ''
cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
def cmd_PLAY_ERROR(game, msg):
- game.tui.log_msg('imagine the screen flicker (TODO)')
+ game.tui.flash()
game.tui.do_refresh = True
cmd_PLAY_ERROR.argtypes = 'string'
+def cmd_GAME_ERROR(game, msg):
+ game.tui.log_msg('? game error: ' + msg)
+ game.tui.do_refresh = True
+cmd_GAME_ERROR.argtypes = 'string'
+
def cmd_ARGUMENT_ERROR(game, msg):
game.tui.log_msg('? syntax error: ' + msg)
game.tui.do_refresh = True
game.tui.do_refresh = True
cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
-def recv_loop(plom_socket, q):
- for msg in plom_socket.recv():
- q.put(msg)
-
class Game(GameBase):
commands = {'LOGIN_OK': cmd_LOGIN_OK,
'CHAT': cmd_CHAT,
'ANNOTATION': cmd_ANNOTATION,
'GAME_STATE_COMPLETE': cmd_GAME_STATE_COMPLETE,
'ARGUMENT_ERROR': cmd_ARGUMENT_ERROR,
+ 'GAME_ERROR': cmd_GAME_ERROR,
'PLAY_ERROR': cmd_PLAY_ERROR}
thing_type = ThingBase
+ turn_complete = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.map_size = YX(0, 0)
self.map_content = ''
self.player_id = -1
self.info_db = {}
class TUI:
- def __init__(self, socket, q, game):
- self.game = game
+ class Mode:
+
+ def __init__(self, name, has_input_prompt=False, shows_info=False,
+ is_intro = False):
+ self.name = name
+ self.has_input_prompt = has_input_prompt
+ self.shows_info = shows_info
+ self.is_intro = is_intro
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.mode_play = self.Mode('play')
+ self.mode_study = self.Mode('study', shows_info=True)
+ self.mode_edit = self.Mode('edit')
+ self.mode_annotate = self.Mode('annotate', has_input_prompt=True, shows_info=True)
+ self.mode_portal = self.Mode('portal', has_input_prompt=True, shows_info=True)
+ self.mode_chat = self.Mode('chat', has_input_prompt=True)
+ self.mode_waiting_for_server = self.Mode('waiting_for_server', is_intro=True)
+ self.mode_login = self.Mode('login', has_input_prompt=True, is_intro=True)
+ self.mode_post_login_wait = self.Mode('post_login_wait', is_intro=True)
+ self.mode_teleport = self.Mode('teleport', has_input_prompt=True)
+ self.game = Game()
self.game.tui = self
self.parser = Parser(self.game)
- self.queue = q
- self.socket = socket
self.log = []
self.do_refresh = True
+ self.queue = queue.Queue()
+ self.switch_mode('waiting_for_server')
curses.wrapper(self.loop)
+ def flash(self):
+ curses.flash()
+
+ def send(self, msg):
+ try:
+ self.socket.send(msg)
+ except BrokenPipeError:
+ self.log_msg('@ server disconnected :(')
+ self.do_refresh = True
+
def log_msg(self, msg):
self.log += [msg]
if len(self.log) > 100:
self.log = self.log[-100:]
def query_info(self):
- self.socket.send('GET_ANNOTATION ' + str(self.explorer))
+ self.send('GET_ANNOTATION ' + str(self.explorer))
def switch_mode(self, mode_name, keep_position = False):
self.mode = getattr(self, 'mode_' + mode_name)
if self.mode.shows_info and not keep_position:
player = self.game.get_thing(self.game.player_id, False)
self.explorer = YX(player.position.y, player.position.x)
- if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
+ if self.mode.name == 'waiting_for_server':
+ self.log_msg('@ waiting for server …')
+ elif self.mode.name == 'login':
+ self.log_msg('@ enter username')
+ elif self.mode.name == 'teleport':
+ self.log_msg("@ May teleport to %s:%s" % (self.teleport_target_host,
+ self.teleport_target_port));
+ self.log_msg("@ Enter 'YES!' to affirm.");
+ elif self.mode.name == 'annotate' and self.explorer in self.game.info_db:
info = self.game.info_db[self.explorer]
if info != '(none)':
self.input_ = info
elif self.mode.name == 'portal' and self.explorer in self.game.portals:
self.input_ = self.game.portals[self.explorer]
+ def help(self):
+ self.log_msg("HELP:");
+ self.log_msg("chat mode commands:");
+ self.log_msg(" :nick NAME - re-name yourself to NAME");
+ self.log_msg(" :msg USER TEXT - send TEXT to USER");
+ self.log_msg(" :help - show this help");
+ self.log_msg(" :p or :play - switch to play mode");
+ self.log_msg(" :? or :study - switch to study mode");
+ self.log_msg("commands common to study and play mode:");
+ self.log_msg(" w,a,s,d - move");
+ self.log_msg(" c - switch to chat mode");
+ self.log_msg("commands specific to play mode:");
+ self.log_msg(" e - write following ASCII character");
+ self.log_msg(" f - flatten surroundings");
+ self.log_msg(" ? - switch to study mode");
+ self.log_msg("commands specific to study mode:");
+ self.log_msg(" e - annotate terrain");
+ self.log_msg(" p - switch to play mode");
+
def loop(self, stdscr):
- class Mode:
+ def safe_addstr(y, x, line):
+ if y < self.size.y - 1 or x + len(line) < self.size.x:
+ stdscr.addstr(y, x, line)
+ else: # workaround to <https://stackoverflow.com/q/7063128>
+ cut_i = self.size.x - x - 1
+ cut = line[:cut_i]
+ last_char = line[cut_i]
+ stdscr.addstr(y, self.size.x - 2, last_char)
+ stdscr.insstr(y, self.size.x - 2, ' ')
+ stdscr.addstr(y, x, cut)
+
+ def connect():
+ import time
+
+ def recv_loop():
+ for msg in self.socket.recv():
+ if msg == 'BYE':
+ break
+ self.queue.put(msg)
- def __init__(self, name, has_input_prompt=False, shows_info=False,
- is_intro = False):
- self.name = name
- self.has_input_prompt = has_input_prompt
- self.shows_info = shows_info
- self.is_intro = is_intro
+ while True:
+ try:
+ s = socket.create_connection((self.host, self.port))
+ self.socket = PlomSocket(s)
+ self.socket_thread = threading.Thread(target=recv_loop)
+ self.socket_thread.start()
+ self.switch_mode('login')
+ return
+ except ConnectionRefusedError:
+ self.log_msg('@ server connect failure, trying again …')
+ draw_screen()
+ stdscr.refresh()
+ time.sleep(1)
+
+ def reconnect():
+ self.send('QUIT')
+ self.switch_mode('waiting_for_server')
+ connect()
def handle_input(msg):
command, args = self.parser.parse(msg)
lines = []
x = 0
for i in range(len(msg)):
- x += 1
if x >= width or msg[i] == "\n":
lines += [chunk]
chunk = ''
x = 0
if msg[i] != "\n":
chunk += msg[i]
+ x += 1
lines += [chunk]
return lines
def reset_screen_size():
self.size = YX(*stdscr.getmaxyx())
- self.size = self.size - YX(0, 1) # ugly TODO ncurses bug workaround, FIXME
self.size = self.size - YX(self.size.y % 2, 0)
self.size = self.size - YX(0, self.size.x % 4)
self.window_width = int(self.size.x / 2)
self.window_width)
def move_explorer(direction):
- # TODO movement constraints
- if direction == 'up':
- self.explorer += YX(-1, 0)
- elif direction == 'left':
- self.explorer += YX(0, -1)
- elif direction == 'down':
- self.explorer += YX(1, 0)
- elif direction == 'right':
- self.explorer += YX(0, 1)
- self.query_info()
+ target = self.game.map_geometry.move(self.explorer, direction)
+ if target:
+ self.explorer = target
+ self.query_info()
+ else:
+ self.flash()
def draw_history():
lines = []
for i in range(len(lines)):
if (i >= max_y - height_header):
break
- stdscr.addstr(max_y - i - 1, self.window_width, lines[i])
+ safe_addstr(max_y - i - 1, self.window_width, lines[i])
def draw_info():
+ if not self.game.turn_complete:
+ return
if self.explorer in self.game.portals:
info = 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
else:
y = height_header + i
if y >= self.size.y - len(self.input_lines):
break
- stdscr.addstr(y, self.window_width, lines[i])
+ safe_addstr(y, self.window_width, lines[i])
def draw_input():
y = self.size.y - len(self.input_lines)
for i in range(len(self.input_lines)):
- stdscr.addstr(y, self.window_width, self.input_lines[i])
+ safe_addstr(y, self.window_width, self.input_lines[i])
y += 1
def draw_turn():
- stdscr.addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
+ if not self.game.turn_complete:
+ return
+ safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
def draw_mode():
- stdscr.addstr(1, self.window_width, 'MODE: ' + self.mode.name)
+ safe_addstr(1, self.window_width, 'MODE: ' + self.mode.name)
def draw_map():
- player = self.game.get_thing(self.game.player_id, False)
- if not player:
- # catches race conditions where game.things still empty
+ if not self.game.turn_complete:
return
map_lines_split = []
- for y in range(self.game.map_size.y):
- start = self.game.map_size.x * y
- end = start + self.game.map_size.x
+ for y in range(self.game.map_geometry.size.y):
+ start = self.game.map_geometry.size.x * y
+ end = start + self.game.map_geometry.size.x
map_lines_split += [list(self.game.map_content[start:end])]
for t in self.game.things:
map_lines_split[t.position.y][t.position.x] = '@'
map_lines = []
for line in map_lines_split:
map_lines += [''.join(line)]
- map_center = YX(int(self.game.map_size.y / 2),
- int(self.game.map_size.x / 2))
+ map_center = YX(int(self.game.map_geometry.size.y / 2),
+ int(self.game.map_geometry.size.x / 2))
window_center = YX(int(self.size.y / 2),
int(self.window_width / 2))
- center = player
+ player = self.game.get_thing(self.game.player_id, False)
+ center = player.position
if self.mode.shows_info:
center = self.explorer
offset = center - window_center
term_x = max(0, -offset.x)
map_y = max(0, offset.y)
map_x = max(0, offset.x)
- while (term_y < self.size.y and map_y < self.game.map_size.y):
+ while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
- stdscr.addstr(term_y, term_x, to_draw)
+ safe_addstr(term_y, term_x, to_draw)
term_y += 1
map_y += 1
draw_turn()
draw_map()
- self.mode_play = Mode('play')
- self.mode_study = Mode('study', shows_info=True)
- self.mode_edit = Mode('edit')
- self.mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
- self.mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
- self.mode_chat = Mode('chat', has_input_prompt=True)
- self.mode_login = Mode('login', has_input_prompt=True, is_intro=True)
- self.mode_post_login_wait = Mode('post_login_wait', is_intro=True)
curses.curs_set(False) # hide cursor
stdscr.timeout(10)
reset_screen_size()
- self.mode = self.mode_login
self.explorer = YX(0, 0)
self.input_ = ''
input_prompt = '> '
+ connect()
while True:
if self.do_refresh:
draw_screen()
self.input_ = self.input_[:-1]
elif self.mode.has_input_prompt and key != '\n': # Return key
self.input_ += key
- # TODO find out why - 1 is necessary here
max_length = self.window_width * self.size.y - len(input_prompt) - 1
if len(self.input_) > max_length:
self.input_ = self.input_[:max_length]
elif self.mode == self.mode_login and key == '\n':
- self.socket.send('LOGIN ' + quote(self.input_))
+ self.send('LOGIN ' + quote(self.input_))
self.input_ = ""
elif self.mode == self.mode_chat and key == '\n':
- # TODO: query, nick, help, reconnect, unknown command
if self.input_[0] == ':':
if self.input_ in {':p', ':play'}:
self.switch_mode('play')
elif self.input_ in {':?', ':study'}:
self.switch_mode('study')
+ if self.input_ == ':help':
+ self.help()
+ if self.input_ == ':reconnect':
+ reconnect()
elif self.input_.startswith(':nick'):
tokens = self.input_.split(maxsplit=1)
if len(tokens) == 2:
- self.socket.send('LOGIN ' + quote(tokens[1]))
+ self.send('LOGIN ' + quote(tokens[1]))
else:
self.log_msg('? need login name')
elif self.input_.startswith(':msg'):
tokens = self.input_.split(maxsplit=2)
if len(tokens) == 3:
- self.socket.send('QUERY %s %s' % (quote(tokens[1]),
+ self.send('QUERY %s %s' % (quote(tokens[1]),
quote(tokens[2])))
else:
self.log_msg('? need message target and message')
else:
self.log_msg('? unknown command')
else:
- self.socket.send('ALL ' + quote(self.input_))
+ self.send('ALL ' + quote(self.input_))
self.input_ = ""
elif self.mode == self.mode_annotate and key == '\n':
- if (self.input_ == ''):
+ if self.input_ == '':
self.input_ = ' '
- self.socket.send('ANNOTATE %s %s' % (self.explorer, quote(self.input_)))
+ self.send('ANNOTATE %s %s' % (self.explorer, quote(self.input_)))
self.input_ = ""
self.switch_mode('study', keep_position=True)
elif self.mode == self.mode_portal and key == '\n':
- if (self.input_ == ''):
+ if self.input_ == '':
self.input_ = ' '
- self.socket.send('PORTAL %s %s' % (self.explorer, quote(self.input_)))
+ self.send('PORTAL %s %s' % (self.explorer, quote(self.input_)))
self.input_ = ""
self.switch_mode('study', keep_position=True)
+ elif self.mode == self.mode_teleport and key == '\n':
+ if self.input_ == 'YES!':
+ self.host = self.teleport_target_host
+ self.port = self.teleport_target_port
+ reconnect()
+ else:
+ self.log_msg('@ teleport aborted')
+ self.switch_mode('play')
+ self.input_ = ''
elif self.mode == self.mode_study:
if key == 'c':
self.switch_mode('chat')
elif key == 'P':
self.switch_mode('portal', keep_position=True)
elif key == 'w':
- move_explorer('up')
+ move_explorer('UP')
elif key == 'a':
- move_explorer('left')
+ move_explorer('LEFT')
elif key == 's':
- move_explorer('down')
+ move_explorer('DOWN')
elif key == 'd':
- move_explorer('right')
+ move_explorer('RIGHT')
elif self.mode == self.mode_play:
if key == 'c':
self.switch_mode('chat')
if key == 'e':
self.switch_mode('edit')
elif key == 'f':
- self.socket.send('TASK:FLATTEN_SURROUNDINGS')
+ self.send('TASK:FLATTEN_SURROUNDINGS')
elif key == 'w':
- self.socket.send('TASK:MOVE UP')
+ self.send('TASK:MOVE UP')
elif key == 'a':
- self.socket.send('TASK:MOVE LEFT')
+ self.send('TASK:MOVE LEFT')
elif key == 's':
- self.socket.send('TASK:MOVE DOWN')
+ self.send('TASK:MOVE DOWN')
elif key == 'd':
- self.socket.send('TASK:MOVE RIGHT')
+ self.send('TASK:MOVE RIGHT')
elif self.mode == self.mode_edit:
- self.socket.send('TASK:WRITE ' + key)
+ self.send('TASK:WRITE ' + key)
self.switch_mode('play')
-s = socket.create_connection(('127.0.0.1', 5000))
-plom_socket = PlomSocket(s)
-q = queue.Queue()
-t = threading.Thread(target=recv_loop, args=(plom_socket, q))
-t.start()
-TUI(plom_socket, q, Game())
+TUI('127.0.0.1', 5000)