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, cmd_MAP_SIZE,
9 cmd_TERRAIN_LINE, cmd_PLAYER_ID,
10 cmd_TURN, cmd_SWITCH_PLAYER, cmd_SAVE)
11 from plomrogue.mapping import MapHex, YX
12 from plomrogue.parser import Parser
13 from plomrogue.io import GameIO
14 from plomrogue.misc import quote
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
70 self.map_size = YX(1,1)
75 return self.get_thing(self.player_id)
77 def new_thing_id(self):
78 if len(self.things) == 0:
80 return self.things[-1].id_ + 1
82 def ensure_map(self, map_pos):
83 if map_pos in self.maps and self.maps[map_pos].size == self.map_size:
85 self.maps[map_pos] = self.game.map_type(self.map_size)
87 def proceed_to_next_player_turn(self):
88 """Run game world turns until player can decide their next step.
90 Iterates through all non-player things, on each step
91 furthering them in their tasks (and letting them decide new
92 ones if they finish). The iteration order is: first all things
93 that come after the player in the world things list, then
94 (after incrementing the world turn) all that come before the
95 player; then the player's .proceed() is run, and if it does
96 not finish his task, the loop starts at the beginning. Once
97 the player's task is finished, or the player is dead, the loop
102 player_i = self.things.index(self.player)
103 for thing in self.things[player_i+1:]:
106 for pos in self.maps[YX(0,0)]:
107 if self.maps[YX(0,0)][pos] == '.' and \
108 len(self.things_at_pos((YX(0,0), pos))) == 0 and \
109 self.rand.random() > 0.999:
110 self.add_thing_at('food', (YX(0,0), pos))
111 for thing in self.things[:player_i]:
113 self.player.proceed(is_AI=False)
114 if self.player.task is None or not self.player_is_alive:
117 def add_thing_at(self, type_, pos):
118 t = self.game.thing_types[type_](self)
123 def make_new(self, yx, seed):
125 def add_thing_at_random(type_):
128 YX(self.rand.randint(0, yx.y - 1),
129 self.rand.randint(0, yx.x - 1)))
130 if self.maps[new_pos[0]][new_pos[1]] != '.':
132 if len(self.things_at_pos(new_pos)) > 0:
134 return self.add_thing_at(type_, new_pos)
141 self.ensure_map(YX(0,0))
142 self.ensure_map(YX(0,1))
143 self.ensure_map(YX(1,1))
144 self.ensure_map(YX(1,0))
145 self.ensure_map(YX(1,-1))
146 self.ensure_map(YX(0,-1))
147 self.ensure_map(YX(-1,-1))
148 self.ensure_map(YX(-1,0))
149 self.ensure_map(YX(-1,1))
150 for map_pos in self.maps:
151 map_ = self.maps[map_pos]
152 if YX(0,0) == map_pos:
154 map_[pos] = self.rand.choice(('.', '.', '.', '.', 'x'))
158 player = add_thing_at_random('human')
159 self.player_id = player.id_
160 add_thing_at_random('monster')
161 add_thing_at_random('monster')
162 add_thing_at_random('food')
163 add_thing_at_random('food')
164 add_thing_at_random('food')
165 add_thing_at_random('food')
172 def __init__(self, game_file_name):
173 self.io = GameIO(game_file_name, self)
174 self.map_type = MapHex
175 self.tasks = {'WAIT': Task_WAIT,
177 'PICKUP': Task_PICKUP,
180 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
181 'GET_GAMESTATE': cmd_GET_GAMESTATE,
183 'MAP_SIZE': cmd_MAP_SIZE,
185 'THING_TYPE': cmd_THING_TYPE,
186 'THING_POS': cmd_THING_POS,
187 'THING_HEALTH': cmd_THING_HEALTH,
188 'THING_INVENTORY': cmd_THING_INVENTORY,
189 'TERRAIN_LINE': cmd_TERRAIN_LINE,
190 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
191 'PLAYER_ID': cmd_PLAYER_ID,
193 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
195 self.world_type = World
196 self.world = self.world_type(self)
197 self.thing_type = Thing
198 self.thing_types = {'human': ThingHuman,
199 'monster': ThingMonster,
202 def get_string_options(self, string_option_type):
203 if string_option_type == 'direction':
204 return self.map_type().get_directions()
205 elif string_option_type == 'thingtype':
206 return list(self.thing_types.keys())
209 def send_gamestate(self, connection_id=None):
210 """Send out game state data relevant to clients."""
212 def send_thing(offset, thing):
213 offset_pos = (thing.position[1] - offset)
214 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
215 self.io.send('THING_POS %s %s' % (thing.id_, offset_pos))
217 self.io.send('TURN ' + str(self.world.turn))
218 visible_map = self.world.player.get_visible_map()
219 offset = self.world.player.get_surroundings_offset()
220 self.io.send('VISIBLE_MAP %s %s' % (offset, visible_map.size))
221 for y, line in visible_map.lines():
222 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
223 visible_things = self.world.player.get_visible_things()
224 for thing in visible_things:
225 send_thing(offset, thing)
226 if hasattr(thing, 'health'):
227 self.io.send('THING_HEALTH %s %s' % (thing.id_,
229 if len(self.world.player.inventory) > 0:
230 self.io.send('PLAYER_INVENTORY %s' %
231 ','.join([str(i) for i in self.world.player.inventory]))
233 self.io.send('PLAYER_INVENTORY ,')
234 for id_ in self.world.player.inventory:
235 thing = self.world.get_thing(id_)
236 send_thing(offset, thing)
237 self.io.send('GAME_STATE_COMPLETE')
240 """Send turn finish signal, run game world, send new world data.
242 First sends 'TURN_FINISHED' message, then runs game world
243 until new player input is needed, then sends game state.
245 self.io.send('TURN_FINISHED ' + str(self.world.turn))
246 self.world.proceed_to_next_player_turn()
247 msg = str(self.world.player._last_task_result)
248 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
249 self.send_gamestate()
251 def get_command(self, command_name):
253 def partial_with_attrs(f, *args, **kwargs):
254 from functools import partial
255 p = partial(f, *args, **kwargs)
256 p.__dict__.update(f.__dict__)
259 def cmd_TASK_colon(task_name, game, *args):
260 if not game.world.player_is_alive:
261 raise GameError('You are dead.')
262 game.world.player.set_task(task_name, args)
265 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
266 t = game.world.get_thing(thing_id, False)
268 raise ArgError('No such Thing.')
269 task_class = game.tasks[task_name]
270 t.task = task_class(t, args)
273 def task_prefixed(command_name, task_prefix, task_command,
274 argtypes_prefix=None):
275 if command_name[:len(task_prefix)] == task_prefix:
276 task_name = command_name[len(task_prefix):]
277 if task_name in self.tasks:
278 f = partial_with_attrs(task_command, task_name, self)
279 task = self.tasks[task_name]
281 f.argtypes = argtypes_prefix + ' ' + task.argtypes
283 f.argtypes = task.argtypes
287 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
290 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
291 'int:nonneg int:nonneg ')
294 if command_name in self.commands:
295 f = partial_with_attrs(self.commands[command_name], self)