6 from parser import ArgError, Parser
10 class MapSquare(game_common.Map):
12 def list_terrain_to_lines(self, terrain_as_list):
13 terrain = ''.join(terrain_as_list)
16 while start_cut < len(terrain):
17 limit = start_cut + self.size[1]
18 map_lines += [terrain[start_cut:limit]]
23 class MapHex(game_common.Map):
25 def list_terrain_to_lines(self, terrain_as_list):
26 new_terrain_list = [' ']
29 for c in terrain_as_list:
30 new_terrain_list += [c, ' ']
33 new_terrain_list += ['\n']
37 new_terrain_list += [' ']
38 return ''.join(new_terrain_list).split('\n')
41 map_manager = game_common.MapManager(globals())
44 class World(game_common.World):
46 def __init__(self, game, *args, **kwargs):
47 """Extend original with local classes and empty default map.
49 We need the empty default map because we draw the map widget
50 on any update, even before we actually receive map data.
52 super().__init__(*args, **kwargs)
54 self.map_ = self.game.map_manager.get_map_class('Hex')()
57 class Game(game_common.CommonCommandsMixin):
59 def __init__(self, tui):
61 self.map_manager = map_manager
62 self.parser = Parser(self)
63 self.world = World(self)
67 """Prefix msg plus newline to self.log_text."""
68 self.log_text = msg + '\n' + self.log_text
70 def symbol_for_type(self, type_):
74 elif type_ == 'monster':
78 def cmd_LAST_PLAYER_TASK_RESULT(self, msg):
81 self.tui.log.do_update = True
82 cmd_LAST_PLAYER_TASK_RESULT.argtypes = 'string'
84 def cmd_TURN_FINISHED(self, n):
85 """Do nothing. (This may be extended later.)"""
87 cmd_TURN_FINISHED.argtypes = 'int:nonneg'
89 def cmd_NEW_TURN(self, n):
90 """Set self.turn to n, empty self.things."""
92 self.tui.turn.do_update = True
93 self.world.things = []
94 cmd_NEW_TURN.argtypes = 'int:nonneg'
96 def cmd_VISIBLE_MAP_LINE(self, y, terrain_line):
97 self.world.map_.set_line(y, terrain_line)
98 cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string'
100 def cmd_VISIBLE_MAP_COMPLETE(self):
101 self.tui.map_.do_update = True
104 ASCII_printable = ' !"#$%&\'\(\)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWX'\
105 'YZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~'
108 def recv_loop(server_output):
109 for msg in plom_socket_io.recv(s):
110 while len(server_output) > 0:
112 server_output += [msg]
117 def __init__(self, tui, start, size):
120 self.win = curses.newwin(1, 1, self.start[0], self.start[1])
121 self.size_def = size # store for re-calling .size on SIGWINCH
123 self.do_update = True
127 return self.win.getmaxyx()
130 def size(self, size):
131 """Set window size. Size be y,x tuple. If y or x None, use legal max."""
132 n_lines, n_cols = size
134 n_lines = self.tui.stdscr.getmaxyx()[0] - self.start[0]
136 n_cols = self.tui.stdscr.getmaxyx()[1] - self.start[1]
137 self.win.resize(n_lines, n_cols)
140 return self.win.getmaxyx()[0] * self.win.getmaxyx()[1]
142 def safe_write(self, foo):
144 def to_chars_with_attrs(part):
145 attr = curses.A_NORMAL
147 if not type(part) == str:
148 part_string = part[0]
150 if len(part_string) > 0:
151 return [(char, attr) for char in part_string]
152 elif len(part_string) == 1:
156 chars_with_attrs = []
157 if type(foo) == str or len(foo) == 2 and type(foo[1]) == int:
158 chars_with_attrs += to_chars_with_attrs(foo)
161 chars_with_attrs += to_chars_with_attrs(part)
163 if len(chars_with_attrs) < len(self):
164 for char_with_attr in chars_with_attrs:
165 self.win.addstr(char_with_attr[0], char_with_attr[1])
166 else: # workaround to <https://stackoverflow.com/q/7063128>
167 cut = chars_with_attrs[:len(self) - 1]
168 last_char_with_attr = chars_with_attrs[len(self) - 1]
169 self.win.addstr(self.size[0] - 1, self.size[1] - 2,
170 last_char_with_attr[0], last_char_with_attr[1])
171 self.win.insstr(self.size[0] - 1, self.size[1] - 2, ' ')
173 for char_with_attr in cut:
174 self.win.addstr(char_with_attr[0], char_with_attr[1])
176 def draw_and_refresh(self):
182 class EditWidget(Widget):
185 self.safe_write((''.join(self.tui.to_send), curses.color_pair(1)))
188 class LogWidget(Widget):
191 line_width = self.size[1]
192 log_lines = self.tui.game.log_text.split('\n')
194 for line in log_lines:
195 to_pad = line_width - (len(line) % line_width)
196 if to_pad == line_width:
198 to_join += [line + ' '*to_pad]
199 self.safe_write((''.join(to_join), curses.color_pair(3)))
202 class MapWidget(Widget):
206 if len(self.tui.game.world.map_.terrain) > 0:
207 terrain_as_list = list(self.tui.game.world.map_.terrain[:])
208 for t in self.tui.game.world.things:
209 pos_i = self.tui.game.world.map_.get_position_index(t.position)
210 terrain_as_list[pos_i] = self.tui.game.symbol_for_type(t.type_)
211 lines = self.tui.game.world.map_.list_terrain_to_lines(terrain_as_list)
212 line_width = self.size[1]
214 if line_width > len(line):
215 to_pad = line_width - (len(line) % line_width)
216 to_join += [line + '0' * to_pad]
218 to_join += [line[:line_width]]
219 if len(to_join) < self.size[0]:
220 to_pad = self.size[0] - len(to_join)
221 to_join += to_pad * ['0' * self.size[1]]
222 text = ''.join(to_join)
226 text_as_list += [(c, curses.color_pair(1))]
228 text_as_list += [(c, curses.color_pair(2))]
229 elif c in {'x', 'X', '#'}:
230 text_as_list += [(c, curses.color_pair(3))]
233 self.safe_write(text_as_list)
236 class TurnWidget(Widget):
239 self.safe_write((str(self.tui.game.world.turn), curses.color_pair(2)))
244 def __init__(self, server_output):
245 self.server_output = server_output
246 self.game = Game(self)
247 self.parser = Parser(self.game)
248 self.do_update = True
249 curses.wrapper(self.loop)
251 def setup_screen(self, stdscr):
253 self.stdscr.refresh() # will be called by getkey else, clearing screen
254 self.stdscr.timeout(1)
255 self.stdscr.addstr(0, 0, 'SEND:')
256 self.stdscr.addstr(2, 0, 'TURN:')
258 def loop(self, stdscr):
259 self.setup_screen(stdscr)
260 curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_RED)
261 curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN)
262 curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_BLUE)
263 curses.curs_set(False) # hide cursor
265 self.edit = EditWidget(self, (0, 6), (1, 14))
266 self.turn = TurnWidget(self, (2, 6), (1, 14))
267 self.log = LogWidget(self, (4, 0), (None, 20))
268 self.map_ = MapWidget(self, (0, 21), (None, None))
269 widgets = (self.edit, self.turn, self.log, self.map_)
276 key = self.stdscr.getkey()
277 if len(key) == 1 and key in ASCII_printable and \
278 len(self.to_send) < len(self.edit):
279 self.to_send += [key]
280 self.edit.do_update = True
281 elif key == 'KEY_BACKSPACE':
282 self.to_send[:] = self.to_send[:-1]
283 self.edit.do_update = True
285 plom_socket_io.send(s, ''.join(self.to_send))
287 self.edit.do_update = True
288 elif key == 'KEY_RESIZE':
290 self.setup_screen(curses.initscr())
296 if len(self.server_output) > 0:
297 do_quit = self.handle_input(self.server_output[0])
300 self.server_output[:] = []
301 self.do_update = True
303 def handle_input(self, msg):
307 command = self.parser.parse(msg)
309 self.game.log('UNHANDLED INPUT: ' + msg)
310 self.log.do_update = True
313 except ArgError as e:
314 self.game.log('ARGUMENT ERROR: ' + msg + '\n' + str(e))
315 self.log.do_update = True
320 s = socket.create_connection(('127.0.0.1', 5000))
321 t = threading.Thread(target=recv_loop, args=(server_output,))