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 text = 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 self.safe_write(''.join(to_join))
222 class TurnWidget(Widget):
225 self.safe_write((str(self.tui.game.world.turn), curses.color_pair(2)))
230 def __init__(self, server_output):
231 self.server_output = server_output
233 self.parser = Parser(self.game)
234 curses.wrapper(self.loop)
236 def setup_screen(self, stdscr):
238 self.stdscr.refresh() # will be called by getkey else, clearing screen
239 self.stdscr.timeout(10)
240 self.stdscr.addstr(0, 0, 'SEND:')
241 self.stdscr.addstr(2, 0, 'TURN:')
243 def loop(self, stdscr):
244 self.setup_screen(stdscr)
245 curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_RED)
246 curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_GREEN)
247 curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_BLUE)
248 curses.curs_set(False) # hide cursor
250 edit_line = EditWidget(self, (0, 6), (1, 14))
251 turn_line = TurnWidget(self, (2, 6), (1, 14))
252 log_display = LogWidget(self, (4, 0), (None, 20))
253 map_view = MapWidget(self, (0, 21), (None, None))
254 map_view.update = True
255 widgets = [edit_line, turn_line, log_display, map_view]
263 key = self.stdscr.getkey()
265 if len(key) == 1 and key in ASCII_printable and \
266 len(self.to_send) < len(edit_line):
267 self.to_send += [key]
268 elif key == 'KEY_BACKSPACE':
269 self.to_send[:] = self.to_send[:-1]
271 plom_socket_io.send(s, ''.join(self.to_send))
273 elif key == 'KEY_RESIZE':
275 self.setup_screen(curses.initscr())
282 if len(self.server_output) > 0:
283 do_quit = self.handle_input(self.server_output[0])
286 self.server_output[:] = []
289 def handle_input(self, msg):
293 command = self.parser.parse(msg)
295 self.game.log('UNHANDLED INPUT: ' + msg)
298 except ArgError as e:
299 self.game.log('ARGUMENT ERROR: ' + msg + '\n' + str(e))
304 s = socket.create_connection(('127.0.0.1', 5000))
305 t = threading.Thread(target=recv_loop, args=(server_output,))