1 from plomrogue.tasks import Task_WAIT, Task_MOVE
2 from plomrogue.errors import GameError, ArgError
3 from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE, cmd_MAP,
4 cmd_MAP, cmd_THING_TYPE, cmd_THING_POS,
5 cmd_TERRAIN_LINE, cmd_PLAYER_ID, cmd_TURN,
6 cmd_SWITCH_PLAYER, cmd_SAVE)
7 from plomrogue.mapping import MapHex
8 from plomrogue.parser import Parser
9 from plomrogue.io import GameIO
10 from plomrogue.misc import quote, stringify_yx
16 def __init__(self, world, id_, type_='?', position=[0,0]):
20 self.position = position
23 class Thing(ThingBase):
25 def __init__(self, *args, **kwargs):
26 super().__init__(*args, **kwargs)
28 self._last_task_result = None
31 def move_towards_target(self, target):
32 dijkstra_map = type(self.world.map_)(self.world.map_.size)
34 dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
35 dijkstra_map[target] = 0
37 visible_map = self.get_visible_map()
40 for pos in dijkstra_map:
41 if visible_map[pos] != '.':
43 neighbors = dijkstra_map.get_neighbors(tuple(pos))
44 for direction in neighbors:
45 yx = neighbors[direction]
46 if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
47 dijkstra_map[pos] = dijkstra_map[yx] + 1
49 #with open('log', 'a') as f:
50 # f.write('---------------------------------\n')
51 # for y, line in dijkstra_map.lines():
60 neighbors = dijkstra_map.get_neighbors(tuple(self.position))
62 #print('DEBUG', self.position, neighbors)
63 #dirs = dijkstra_map.get_directions()
64 #print('DEBUG dirs', dirs)
65 #print('DEBUG neighbors', neighbors)
67 #for pos in neighbors:
69 # debug_scores += [9000]
71 # debug_scores += [dijkstra_map[pos]]
72 #print('DEBUG debug_scores', debug_scores)
73 target_direction = None
74 for direction in neighbors:
75 yx = neighbors[direction]
77 n_new = dijkstra_map[yx]
80 target_direction = direction
81 #print('DEBUG result', direction)
83 self.set_task('MOVE', (target_direction,))
85 def decide_task(self):
86 # TODO: Check if monster can follow player too well (even when they should lose them)
87 visible_things = self.get_visible_things()
89 for t in visible_things:
90 if t.type_ == 'human':
93 if target is not None:
95 self.move_towards_target(target)
101 def set_task(self, task_name, args=()):
102 task_class = self.world.game.tasks[task_name]
103 self.task = task_class(self, args)
104 self.task.check() # will throw GameError if necessary
106 def proceed(self, is_AI=True):
107 """Further the thing in its tasks.
109 Decrements .task.todo; if it thus falls to <= 0, enacts method
110 whose name is 'task_' + self.task.name and sets .task =
111 None. If is_AI, calls .decide_task to decide a self.task.
113 Before doing anything, ensures an empty map visibility stencil
114 and checks that task is still possible, and aborts it
115 otherwise (for AI things, decides a new task).
121 except GameError as e:
123 self._last_task_result = e
128 self.set_task('WAIT')
131 if self.task.todo <= 0:
132 self._last_task_result = self.task.do()
134 if is_AI and self.task is None:
138 self.set_task('WAIT')
140 def get_stencil(self):
141 if self._stencil is not None:
143 self._stencil = self.world.map_.get_fov_map(self.position)
146 def get_visible_map(self):
147 stencil = self.get_stencil()
148 m = self.world.map_.new_from_shape(' ')
150 if stencil[pos] == '.':
151 m[pos] = self.world.map_[pos]
154 def get_visible_things(self):
155 stencil = self.get_stencil()
157 for thing in self.world.things:
158 if stencil[thing.position] == '.':
159 visible_things += [thing]
160 return visible_things
166 def __init__(self, game):
171 def get_thing(self, id_, create_unfound=True):
172 for thing in self.things:
176 t = self.game.thing_type(self, id_)
183 class World(WorldBase):
185 def __init__(self, *args, **kwargs):
186 super().__init__(*args, **kwargs)
189 def new_map(self, yx):
190 self.map_ = self.game.map_type(yx)
192 def proceed_to_next_player_turn(self):
193 """Run game world turns until player can decide their next step.
195 Iterates through all non-player things, on each step
196 furthering them in their tasks (and letting them decide new
197 ones if they finish). The iteration order is: first all things
198 that come after the player in the world things list, then
199 (after incrementing the world turn) all that come before the
200 player; then the player's .proceed() is run, and if it does
201 not finish his task, the loop starts at the beginning. Once
202 the player's task is finished, the loop breaks.
205 player = self.get_player()
206 player_i = self.things.index(player)
207 for thing in self.things[player_i+1:]:
210 for thing in self.things[:player_i]:
212 player.proceed(is_AI=False)
213 if player.task is None:
216 def get_player(self):
217 return self.get_thing(self.player_id)
219 def make_new(self, yx, seed):
224 for pos in self.map_:
225 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
228 self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
229 player = self.game.thing_type(self, 0)
230 player.type_ = 'human'
231 player.position = [random.randint(0, yx[0] -1),
232 random.randint(0, yx[1] - 1)]
233 npc = self.game.thing_type(self, 1)
234 npc.type_ = 'monster'
235 npc.position = [random.randint(0, yx[0] -1),
236 random.randint(0, yx[1] -1)]
237 self.things = [player, npc]
244 def __init__(self, game_file_name):
245 self.io = GameIO(game_file_name, self)
246 self.map_type = MapHex
247 self.tasks = {'WAIT': Task_WAIT, 'MOVE': Task_MOVE}
248 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
249 'GET_GAMESTATE': cmd_GET_GAMESTATE,
251 'THING_TYPE': cmd_THING_TYPE,
252 'THING_POS': cmd_THING_POS,
253 'TERRAIN_LINE': cmd_TERRAIN_LINE,
254 'PLAYER_ID': cmd_PLAYER_ID,
256 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
258 self.world_type = World
259 self.world = self.world_type(self)
260 self.thing_type = Thing
262 def get_string_options(self, string_option_type):
263 if string_option_type == 'direction':
264 return self.world.map_.get_directions()
267 def send_gamestate(self, connection_id=None):
268 """Send out game state data relevant to clients."""
270 self.io.send('TURN ' + str(self.world.turn))
271 self.io.send('MAP ' + stringify_yx(self.world.map_.size))
272 visible_map = self.world.get_player().get_visible_map()
273 for y, line in visible_map.lines():
274 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
275 visible_things = self.world.get_player().get_visible_things()
276 for thing in visible_things:
277 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
278 self.io.send('THING_POS %s %s' % (thing.id_,
279 stringify_yx(thing.position)))
280 player = self.world.get_player()
281 self.io.send('PLAYER_POS %s' % (stringify_yx(player.position)))
282 self.io.send('GAME_STATE_COMPLETE')
285 """Send turn finish signal, run game world, send new world data.
287 First sends 'TURN_FINISHED' message, then runs game world
288 until new player input is needed, then sends game state.
290 self.io.send('TURN_FINISHED ' + str(self.world.turn))
291 self.world.proceed_to_next_player_turn()
292 msg = str(self.world.get_player()._last_task_result)
293 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
294 self.send_gamestate()
296 def get_command(self, command_name):
298 def partial_with_attrs(f, *args, **kwargs):
299 from functools import partial
300 p = partial(f, *args, **kwargs)
301 p.__dict__.update(f.__dict__)
304 def cmd_TASK_colon(task_name, game, *args):
305 game.world.get_player().set_task(task_name, args)
308 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
309 t = game.world.get_thing(thing_id, False)
311 raise ArgError('No such Thing.')
312 task_class = game.tasks[task_name]
313 t.task = task_class(t, args)
316 def task_prefixed(command_name, task_prefix, task_command,
317 argtypes_prefix=None):
318 if command_name[:len(task_prefix)] == task_prefix:
319 task_name = command_name[len(task_prefix):]
320 if task_name in self.tasks:
321 f = partial_with_attrs(task_command, task_name, self)
322 task = self.tasks[task_name]
324 f.argtypes = argtypes_prefix + ' ' + task.argtypes
326 f.argtypes = task.argtypes
330 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
333 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
334 'int:nonneg int:nonneg ')
337 if command_name in self.commands:
338 f = partial_with_attrs(self.commands[command_name], self)