1 from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_PICKUP,
3 from plomrogue.errors import ArgError, GameError
4 from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE,
5 cmd_MAP, cmd_MAP, cmd_THING_TYPE,
6 cmd_THING_POS, cmd_THING_INVENTORY,
8 cmd_GET_PICKABLE_ITEMS,
9 cmd_TERRAIN_LINE, cmd_PLAYER_ID,
10 cmd_TURN, cmd_SWITCH_PLAYER, cmd_SAVE)
11 from plomrogue.mapping import MapHex
12 from plomrogue.parser import Parser
13 from plomrogue.io import GameIO
14 from plomrogue.misc import quote, stringify_yx
15 from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingFood
21 def __init__(self, game):
26 def get_thing(self, id_, create_unfound=True):
27 for thing in self.things:
31 t = self.game.thing_type(self, id_)
36 def things_at_pos(self, yx):
45 class World(WorldBase):
47 def __init__(self, *args, **kwargs):
48 super().__init__(*args, **kwargs)
50 self.player_is_alive = True
54 return self.get_thing(self.player_id)
56 def new_thing_id(self):
57 if len(self.things) == 0:
59 return self.things[-1].id_ + 1
61 def new_map(self, yx):
62 self.map_ = self.game.map_type(yx)
64 def proceed_to_next_player_turn(self):
65 """Run game world turns until player can decide their next step.
67 Iterates through all non-player things, on each step
68 furthering them in their tasks (and letting them decide new
69 ones if they finish). The iteration order is: first all things
70 that come after the player in the world things list, then
71 (after incrementing the world turn) all that come before the
72 player; then the player's .proceed() is run, and if it does
73 not finish his task, the loop starts at the beginning. Once
74 the player's task is finished, or the player is dead, the loop
79 player_i = self.things.index(self.player)
80 for thing in self.things[player_i+1:]:
83 for thing in self.things[:player_i]:
85 self.player.proceed(is_AI=False)
86 if self.player.task is None or not self.player_is_alive:
89 def make_new(self, yx, seed):
93 t = self.game.thing_types[type_](self)
95 new_pos = (random.randint(0, yx[0] -1),
96 random.randint(0, yx[1] - 1))
97 if self.map_[new_pos] != '.':
99 if len(self.things_at_pos(new_pos)) > 0:
110 for pos in self.map_:
111 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
114 self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
116 player = add_thing('human')
117 self.player_id = player.id_
130 def __init__(self, game_file_name):
131 self.io = GameIO(game_file_name, self)
132 self.map_type = MapHex
133 self.tasks = {'WAIT': Task_WAIT,
135 'PICKUP': Task_PICKUP,
138 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
139 'GET_GAMESTATE': cmd_GET_GAMESTATE,
141 'THING_TYPE': cmd_THING_TYPE,
142 'THING_POS': cmd_THING_POS,
143 'THING_HEALTH': cmd_THING_HEALTH,
144 'THING_INVENTORY': cmd_THING_INVENTORY,
145 'TERRAIN_LINE': cmd_TERRAIN_LINE,
146 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
147 'PLAYER_ID': cmd_PLAYER_ID,
149 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
151 self.world_type = World
152 self.world = self.world_type(self)
153 self.thing_type = Thing
154 self.thing_types = {'human': ThingHuman,
155 'monster': ThingMonster,
158 def get_string_options(self, string_option_type):
159 if string_option_type == 'direction':
160 return self.world.map_.get_directions()
161 elif string_option_type == 'thingtype':
162 return list(self.thing_types.keys())
165 def send_gamestate(self, connection_id=None):
166 """Send out game state data relevant to clients."""
168 self.io.send('TURN ' + str(self.world.turn))
169 self.io.send('MAP ' + stringify_yx(self.world.map_.size))
170 visible_map = self.world.player.get_visible_map()
171 for y, line in visible_map.lines():
172 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
173 visible_things = self.world.player.get_visible_things()
174 for thing in visible_things:
175 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
176 self.io.send('THING_POS %s %s' % (thing.id_,
177 stringify_yx(thing.position)))
178 if hasattr(thing, 'health'):
179 self.io.send('THING_HEALTH %s %s' % (thing.id_,
181 if len(self.world.player.inventory) > 0:
182 self.io.send('PLAYER_INVENTORY %s' %
183 ','.join([str(i) for i in self.world.player.inventory]))
185 self.io.send('PLAYER_INVENTORY ,')
186 for id_ in self.world.player.inventory:
187 thing = self.world.get_thing(id_)
188 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
189 self.io.send('THING_POS %s %s' % (thing.id_,
190 stringify_yx(thing.position)))
191 self.io.send('GAME_STATE_COMPLETE')
194 """Send turn finish signal, run game world, send new world data.
196 First sends 'TURN_FINISHED' message, then runs game world
197 until new player input is needed, then sends game state.
199 self.io.send('TURN_FINISHED ' + str(self.world.turn))
200 self.world.proceed_to_next_player_turn()
201 msg = str(self.world.player._last_task_result)
202 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
203 self.send_gamestate()
205 def get_command(self, command_name):
207 def partial_with_attrs(f, *args, **kwargs):
208 from functools import partial
209 p = partial(f, *args, **kwargs)
210 p.__dict__.update(f.__dict__)
213 def cmd_TASK_colon(task_name, game, *args):
214 if not game.world.player_is_alive:
215 raise GameError('You are dead.')
216 game.world.player.set_task(task_name, args)
219 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
220 t = game.world.get_thing(thing_id, False)
222 raise ArgError('No such Thing.')
223 task_class = game.tasks[task_name]
224 t.task = task_class(t, args)
227 def task_prefixed(command_name, task_prefix, task_command,
228 argtypes_prefix=None):
229 if command_name[:len(task_prefix)] == task_prefix:
230 task_name = command_name[len(task_prefix):]
231 if task_name in self.tasks:
232 f = partial_with_attrs(task_command, task_name, self)
233 task = self.tasks[task_name]
235 f.argtypes = argtypes_prefix + ' ' + task.argtypes
237 f.argtypes = task.argtypes
241 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
244 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
245 'int:nonneg int:nonneg ')
248 if command_name in self.commands:
249 f = partial_with_attrs(self.commands[command_name], self)