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,
7 cmd_THING_HEALTH, cmd_SEED,
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
20 class PRNGod(random.Random):
23 self.prngod_seed = seed
26 return self.prngod_seed
32 self.prngod_seed = ((self.prngod_seed * 1103515245) + 12345) % 2**32
33 return (self.prngod_seed >> 16) / (2**16 - 1)
39 def __init__(self, game):
44 def get_thing(self, id_, create_unfound=True):
45 for thing in self.things:
49 t = self.game.thing_type(self, id_)
54 def things_at_pos(self, pos):
63 class World(WorldBase):
65 def __init__(self, *args, **kwargs):
66 super().__init__(*args, **kwargs)
68 self.player_is_alive = True
74 return self.get_thing(self.player_id)
76 def new_thing_id(self):
77 if len(self.things) == 0:
79 return self.things[-1].id_ + 1
81 def new_map(self, map_pos, size):
82 self.maps[map_pos] = self.game.map_type(size)
84 def proceed_to_next_player_turn(self):
85 """Run game world turns until player can decide their next step.
87 Iterates through all non-player things, on each step
88 furthering them in their tasks (and letting them decide new
89 ones if they finish). The iteration order is: first all things
90 that come after the player in the world things list, then
91 (after incrementing the world turn) all that come before the
92 player; then the player's .proceed() is run, and if it does
93 not finish his task, the loop starts at the beginning. Once
94 the player's task is finished, or the player is dead, the loop
99 player_i = self.things.index(self.player)
100 for thing in self.things[player_i+1:]:
103 for pos in self.maps[(0,0)]:
104 if self.maps[(0,0)][pos] == '.' and \
105 len(self.things_at_pos(((0,0), pos))) == 0 and \
106 self.rand.random() > 0.999:
107 self.add_thing_at('food', ((0,0), pos))
108 for thing in self.things[:player_i]:
110 self.player.proceed(is_AI=False)
111 if self.player.task is None or not self.player_is_alive:
114 def add_thing_at(self, type_, pos):
115 t = self.game.thing_types[type_](self)
120 def make_new(self, yx, seed):
122 def add_thing_at_random(type_):
125 (self.rand.randint(0, yx[0] -1),
126 self.rand.randint(0, yx[1] -1)))
127 if self.maps[new_pos[0]][new_pos[1]] != '.':
129 if len(self.things_at_pos(new_pos)) > 0:
131 return self.add_thing_at(type_, new_pos)
137 self.new_map((0,0), yx)
138 self.new_map((0,1), yx)
139 self.new_map((1,1), yx)
140 self.new_map((1,0), yx)
141 self.new_map((1,-1), yx)
142 self.new_map((0,-1), yx)
143 self.new_map((-1,-1), yx)
144 self.new_map((-1,0), yx)
145 self.new_map((-1,1), yx)
146 for map_pos in self.maps:
147 map_ = self.maps[map_pos]
150 map_[pos] = self.rand.choice(('.', '.', '.', '.', 'x'))
154 player = add_thing_at_random('human')
155 self.player_id = player.id_
156 add_thing_at_random('monster')
157 add_thing_at_random('monster')
158 add_thing_at_random('food')
159 add_thing_at_random('food')
160 add_thing_at_random('food')
161 add_thing_at_random('food')
168 def __init__(self, game_file_name):
169 self.io = GameIO(game_file_name, self)
170 self.map_type = MapHex
171 self.tasks = {'WAIT': Task_WAIT,
173 'PICKUP': Task_PICKUP,
176 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
177 'GET_GAMESTATE': cmd_GET_GAMESTATE,
180 'THING_TYPE': cmd_THING_TYPE,
181 'THING_POS': cmd_THING_POS,
182 'THING_HEALTH': cmd_THING_HEALTH,
183 'THING_INVENTORY': cmd_THING_INVENTORY,
184 'TERRAIN_LINE': cmd_TERRAIN_LINE,
185 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
186 'PLAYER_ID': cmd_PLAYER_ID,
188 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
190 self.world_type = World
191 self.world = self.world_type(self)
192 self.thing_type = Thing
193 self.thing_types = {'human': ThingHuman,
194 'monster': ThingMonster,
197 def get_string_options(self, string_option_type):
198 if string_option_type == 'direction':
199 return self.world.maps[(0,0)].get_directions()
200 elif string_option_type == 'thingtype':
201 return list(self.thing_types.keys())
204 def send_gamestate(self, connection_id=None):
205 """Send out game state data relevant to clients."""
207 def send_thing(offset, thing):
208 offset_pos = (thing.position[1][0] - offset[0],
209 thing.position[1][1] - offset[1])
210 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
211 self.io.send('THING_POS %s %s' % (thing.id_,
212 stringify_yx(offset_pos)))
214 self.io.send('TURN ' + str(self.world.turn))
215 visible_map = self.world.player.get_visible_map()
216 offset = self.world.player.get_surroundings_offset()
217 self.io.send('VISIBLE_MAP ' + stringify_yx(offset) + ' ' + stringify_yx(visible_map.size))
218 for y, line in visible_map.lines():
219 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
220 visible_things = self.world.player.get_visible_things()
221 for thing in visible_things:
222 send_thing(offset, thing)
223 if hasattr(thing, 'health'):
224 self.io.send('THING_HEALTH %s %s' % (thing.id_,
226 if len(self.world.player.inventory) > 0:
227 self.io.send('PLAYER_INVENTORY %s' %
228 ','.join([str(i) for i in self.world.player.inventory]))
230 self.io.send('PLAYER_INVENTORY ,')
231 for id_ in self.world.player.inventory:
232 thing = self.world.get_thing(id_)
233 send_thing(offset, thing)
234 self.io.send('GAME_STATE_COMPLETE')
237 """Send turn finish signal, run game world, send new world data.
239 First sends 'TURN_FINISHED' message, then runs game world
240 until new player input is needed, then sends game state.
242 self.io.send('TURN_FINISHED ' + str(self.world.turn))
243 self.world.proceed_to_next_player_turn()
244 msg = str(self.world.player._last_task_result)
245 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
246 self.send_gamestate()
248 def get_command(self, command_name):
250 def partial_with_attrs(f, *args, **kwargs):
251 from functools import partial
252 p = partial(f, *args, **kwargs)
253 p.__dict__.update(f.__dict__)
256 def cmd_TASK_colon(task_name, game, *args):
257 if not game.world.player_is_alive:
258 raise GameError('You are dead.')
259 game.world.player.set_task(task_name, args)
262 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
263 t = game.world.get_thing(thing_id, False)
265 raise ArgError('No such Thing.')
266 task_class = game.tasks[task_name]
267 t.task = task_class(t, args)
270 def task_prefixed(command_name, task_prefix, task_command,
271 argtypes_prefix=None):
272 if command_name[:len(task_prefix)] == task_prefix:
273 task_name = command_name[len(task_prefix):]
274 if task_name in self.tasks:
275 f = partial_with_attrs(task_command, task_name, self)
276 task = self.tasks[task_name]
278 f.argtypes = argtypes_prefix + ' ' + task.argtypes
280 f.argtypes = task.argtypes
284 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
287 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
288 'int:nonneg int:nonneg ')
291 if command_name in self.commands:
292 f = partial_with_attrs(self.commands[command_name], self)