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):
60 self.map_manager = map_manager
61 self.parser = Parser(self)
62 self.world = World(self)
66 """Prefix msg plus newline to self.log_text."""
67 self.log_text = msg + '\n' + self.log_text
69 def symbol_for_type(self, type_):
73 elif type_ == 'monster':
77 def cmd_LAST_PLAYER_TASK_RESULT(self, msg):
80 cmd_LAST_PLAYER_TASK_RESULT.argtypes = 'string'
82 def cmd_TURN_FINISHED(self, n):
83 """Do nothing. (This may be extended later.)"""
85 cmd_TURN_FINISHED.argtypes = 'int:nonneg'
87 def cmd_NEW_TURN(self, n):
88 """Set self.turn to n, empty self.things."""
90 self.world.things = []
91 cmd_NEW_TURN.argtypes = 'int:nonneg'
93 def cmd_VISIBLE_MAP_LINE(self, y, terrain_line):
94 self.world.map_.set_line(y, terrain_line)
95 cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string'
98 ASCII_printable = ' !"#$%&\'\(\)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWX'\
99 'YZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~'
102 def recv_loop(server_output):
103 for msg in plom_socket_io.recv(s):
104 while len(server_output) > 0:
106 server_output += [msg]
111 def __init__(self, tui, start, size):
114 self.win = curses.newwin(1, 1, self.start[0], self.start[1])
115 self.size_def = size # store for re-calling .size on SIGWINCH
121 return self.win.getmaxyx()
124 def size(self, size):
125 """Set window size. Size be y,x tuple. If y or x None, use legal max."""
126 n_lines, n_cols = size
128 n_lines = self.tui.stdscr.getmaxyx()[0] - self.start[0]
130 n_cols = self.tui.stdscr.getmaxyx()[1] - self.start[1]
131 self.win.resize(n_lines, n_cols)
134 return self.win.getmaxyx()[0] * self.win.getmaxyx()[1]
136 def safe_write(self, foo):
138 def to_chars_with_attrs(part):
139 attr = curses.A_NORMAL
141 if not type(part) == str:
142 part_string = part[0]
144 if len(part_string) > 0:
145 chars_with_attrs = []
146 for char in part_string:
147 chars_with_attrs += [(char, attr)]
148 return chars_with_attrs
149 elif len(part_string) == 1:
153 chars_with_attrs = []
154 if type(foo) == str or len(foo) == 2 and type(foo[1]) == int:
155 chars_with_attrs += to_chars_with_attrs(foo)
158 chars_with_attrs += to_chars_with_attrs(part)
160 if len(chars_with_attrs) < len(self):
161 for char_with_attr in chars_with_attrs:
162 self.win.addstr(char_with_attr[0], char_with_attr[1])
163 else: # workaround to <https://stackoverflow.com/q/7063128>
164 cut = chars_with_attrs[:len(self) - 1]
165 last_char_with_attr = chars_with_attrs[len(self) - 1]
166 self.win.addstr(self.size[0] - 1, self.size[1] - 2,
167 last_char_with_attr[0], last_char_with_attr[1])
168 self.win.insstr(self.size[0] - 1, self.size[1] - 2, ' ')
170 for char_with_attr in cut:
171 self.win.addstr(char_with_attr[0], char_with_attr[1])
173 def draw_and_refresh(self):
179 class EditWidget(Widget):
182 self.safe_write((''.join(self.tui.to_send), curses.color_pair(1)))
185 class LogWidget(Widget):
188 line_width = self.size[1]
189 log_lines = self.tui.game.log_text.split('\n')
191 for line in log_lines:
192 to_pad = line_width - (len(line) % line_width)
193 if to_pad == line_width:
195 to_join += [line + ' '*to_pad]
196 self.safe_write((''.join(to_join), curses.color_pair(3)))
199 class MapWidget(Widget):
203 if len(self.tui.game.world.map_.terrain) > 0:
204 terrain_as_list = list(self.tui.game.world.map_.terrain[:])
205 for t in self.tui.game.world.things:
206 pos_i = self.tui.game.world.map_.get_position_index(t.position)
207 terrain_as_list[pos_i] = self.tui.game.symbol_for_type(t.type_)
208 lines = self.tui.game.world.map_.list_terrain_to_lines(terrain_as_list)
209 line_width = self.size[1]
211 if line_width > len(line):
212 to_pad = line_width - (len(line) % line_width)
213 to_join += [line + '0' * to_pad]
215 to_join += [line[:line_width]]
216 if len(to_join) < self.size[0]:
217 to_pad = self.size[0] - len(to_join)
218 to_join += to_pad * ['0' * self.size[1]]
219 text = ''.join(to_join)
223 text_as_list += [(c, curses.color_pair(1))]
225 text_as_list += [(c, curses.color_pair(2))]
226 elif c in {'x', 'X', '#'}:
227 text_as_list += [(c, curses.color_pair(3))]
230 #self.safe_write(''.join(to_join))
231 self.safe_write(text_as_list)
234 class TurnWidget(Widget):
237 self.safe_write((str(self.tui.game.world.turn), curses.color_pair(2)))
242 def __init__(self, server_output):
243 self.server_output = server_output
245 self.parser = Parser(self.game)
246 curses.wrapper(self.loop)
248 def setup_screen(self, stdscr):
250 self.stdscr.refresh() # will be called by getkey else, clearing screen
251 self.stdscr.timeout(10)
252 self.stdscr.addstr(0, 0, 'SEND:')
253 self.stdscr.addstr(2, 0, 'TURN:')
255 def loop(self, stdscr):
256 self.setup_screen(stdscr)
257 curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_RED)
258 curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN)
259 curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_BLUE)
260 curses.curs_set(False) # hide cursor
262 edit_line = EditWidget(self, (0, 6), (1, 14))
263 turn_line = TurnWidget(self, (2, 6), (1, 14))
264 log_display = LogWidget(self, (4, 0), (None, 20))
265 map_view = MapWidget(self, (0, 21), (None, None))
266 map_view.update = True
267 widgets = [edit_line, turn_line, log_display, map_view]
275 key = self.stdscr.getkey()
277 if len(key) == 1 and key in ASCII_printable and \
278 len(self.to_send) < len(edit_line):
279 self.to_send += [key]
280 elif key == 'KEY_BACKSPACE':
281 self.to_send[:] = self.to_send[:-1]
283 plom_socket_io.send(s, ''.join(self.to_send))
285 elif key == 'KEY_RESIZE':
287 self.setup_screen(curses.initscr())
294 if len(self.server_output) > 0:
295 do_quit = self.handle_input(self.server_output[0])
298 self.server_output[:] = []
301 def handle_input(self, msg):
305 command = self.parser.parse(msg)
307 self.game.log('UNHANDLED INPUT: ' + msg)
310 except ArgError as e:
311 self.game.log('ARGUMENT ERROR: ' + msg + '\n' + str(e))
316 s = socket.create_connection(('127.0.0.1', 5000))
317 t = threading.Thread(target=recv_loop, args=(server_output,))