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 MapGeometryHex, Map, 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
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 get_map(self, map_pos, create_unfound=True):
82 if not (map_pos in self.maps and
83 self.maps[map_pos].size == self.game.map_size):
85 self.maps[map_pos] = Map(self.game.map_size)
86 for pos in self.maps[map_pos]:
87 self.maps[map_pos][pos] = '.'
90 return self.maps[map_pos]
92 def proceed_to_next_player_turn(self):
93 """Run game world turns until player can decide their next step.
95 Iterates through all non-player things, on each step
96 furthering them in their tasks (and letting them decide new
97 ones if they finish). The iteration order is: first all things
98 that come after the player in the world things list, then
99 (after incrementing the world turn) all that come before the
100 player; then the player's .proceed() is run, and if it does
101 not finish his task, the loop starts at the beginning. Once
102 the player's task is finished, or the player is dead, the loop
107 player_i = self.things.index(self.player)
108 for thing in self.things[player_i+1:]:
111 for pos in self.maps[YX(0,0)]:
112 if self.maps[YX(0,0)][pos] == '.' and \
113 len(self.things_at_pos((YX(0,0), pos))) == 0 and \
114 self.rand.random() > 0.999:
115 self.add_thing_at('food', (YX(0,0), pos))
116 for thing in self.things[:player_i]:
118 self.player.proceed(is_AI=False)
119 if self.player.task is None or not self.player_is_alive:
122 def add_thing_at(self, type_, pos):
123 t = self.game.thing_types[type_](self)
128 def make_new(self, yx, seed):
130 def add_thing_at_random(type_):
133 YX(self.rand.randint(0, yx.y - 1),
134 self.rand.randint(0, yx.x - 1)))
135 if self.maps[new_pos[0]][new_pos[1]] != '.':
137 if len(self.things_at_pos(new_pos)) > 0:
139 return self.add_thing_at(type_, new_pos)
145 self.game.map_size = yx
146 map_ = self.get_map(YX(0,0))
148 map_[pos] = self.rand.choice(('.', '.', '.', '~', 'x'))
149 player = add_thing_at_random('human')
150 self.player_id = player.id_
151 add_thing_at_random('monster')
152 add_thing_at_random('monster')
153 add_thing_at_random('food')
154 add_thing_at_random('food')
155 add_thing_at_random('food')
156 add_thing_at_random('food')
163 def __init__(self, game_file_name):
164 self.io = GameIO(game_file_name, self)
166 self.map_geometry = MapGeometryHex()
167 self.tasks = {'WAIT': Task_WAIT,
169 'PICKUP': Task_PICKUP,
172 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
173 'GET_GAMESTATE': cmd_GET_GAMESTATE,
175 'MAP_SIZE': cmd_MAP_SIZE,
177 'THING_TYPE': cmd_THING_TYPE,
178 'THING_POS': cmd_THING_POS,
179 'THING_HEALTH': cmd_THING_HEALTH,
180 'THING_INVENTORY': cmd_THING_INVENTORY,
181 'TERRAIN_LINE': cmd_TERRAIN_LINE,
182 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
183 'PLAYER_ID': cmd_PLAYER_ID,
185 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
187 self.world_type = World
188 self.world = self.world_type(self)
189 self.thing_type = Thing
190 self.thing_types = {'human': ThingHuman,
191 'monster': ThingMonster,
194 def get_string_options(self, string_option_type):
195 if string_option_type == 'direction':
196 return self.map_geometry.get_directions()
197 elif string_option_type == 'thingtype':
198 return list(self.thing_types.keys())
201 def send_gamestate(self, connection_id=None):
202 """Send out game state data relevant to clients."""
204 def send_thing(offset, thing):
205 offset_pos = self.map_geometry.pos_in_projection(thing.position,
208 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
209 self.io.send('THING_POS %s %s' % (thing.id_, offset_pos))
211 self.io.send('TURN ' + str(self.world.turn))
212 visible_map = self.world.player.get_visible_map()
213 offset = self.world.player.get_surroundings_offset()
214 self.io.send('VISIBLE_MAP %s %s' % (offset, visible_map.size))
215 for y, line in visible_map.lines():
216 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
217 visible_things = self.world.player.get_visible_things()
218 for thing in visible_things:
219 send_thing(offset, thing)
220 if hasattr(thing, 'health'):
221 self.io.send('THING_HEALTH %s %s' % (thing.id_,
223 if len(self.world.player.inventory) > 0:
224 self.io.send('PLAYER_INVENTORY %s' %
225 ','.join([str(i) for i in self.world.player.inventory]))
227 self.io.send('PLAYER_INVENTORY ,')
228 for id_ in self.world.player.inventory:
229 thing = self.world.get_thing(id_)
230 send_thing(offset, thing)
231 self.io.send('GAME_STATE_COMPLETE')
234 """Send turn finish signal, run game world, send new world data.
236 First sends 'TURN_FINISHED' message, then runs game world
237 until new player input is needed, then sends game state.
239 self.io.send('TURN_FINISHED ' + str(self.world.turn))
240 self.world.proceed_to_next_player_turn()
241 msg = str(self.world.player._last_task_result)
242 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
243 self.send_gamestate()
245 def get_command(self, command_name):
247 def partial_with_attrs(f, *args, **kwargs):
248 from functools import partial
249 p = partial(f, *args, **kwargs)
250 p.__dict__.update(f.__dict__)
253 def cmd_TASK_colon(task_name, game, *args):
254 if not game.world.player_is_alive:
255 raise GameError('You are dead.')
256 game.world.player.set_task(task_name, args)
259 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
260 t = game.world.get_thing(thing_id, False)
262 raise ArgError('No such Thing.')
263 task_class = game.tasks[task_name]
264 t.task = task_class(t, args)
267 def task_prefixed(command_name, task_prefix, task_command,
268 argtypes_prefix=None):
269 if command_name[:len(task_prefix)] == task_prefix:
270 task_name = command_name[len(task_prefix):]
271 if task_name in self.tasks:
272 f = partial_with_attrs(task_command, task_name, self)
273 task = self.tasks[task_name]
275 f.argtypes = argtypes_prefix + ' ' + task.argtypes
277 f.argtypes = task.argtypes
281 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
284 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
285 'int:nonneg int:nonneg ')
288 if command_name in self.commands:
289 f = partial_with_attrs(self.commands[command_name], self)