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 get_map(self, map_pos, create_unfound=True):
83 if not (map_pos in self.maps and
84 self.maps[map_pos].size == self.map_size):
86 self.maps[map_pos] = self.game.map_type(self.map_size)
87 for pos in self.maps[map_pos]:
88 self.maps[map_pos][pos] = '~'
91 return self.maps[map_pos]
93 def proceed_to_next_player_turn(self):
94 """Run game world turns until player can decide their next step.
96 Iterates through all non-player things, on each step
97 furthering them in their tasks (and letting them decide new
98 ones if they finish). The iteration order is: first all things
99 that come after the player in the world things list, then
100 (after incrementing the world turn) all that come before the
101 player; then the player's .proceed() is run, and if it does
102 not finish his task, the loop starts at the beginning. Once
103 the player's task is finished, or the player is dead, the loop
108 player_i = self.things.index(self.player)
109 for thing in self.things[player_i+1:]:
112 for pos in self.maps[YX(0,0)]:
113 if self.maps[YX(0,0)][pos] == '.' and \
114 len(self.things_at_pos((YX(0,0), pos))) == 0 and \
115 self.rand.random() > 0.999:
116 self.add_thing_at('food', (YX(0,0), pos))
117 for thing in self.things[:player_i]:
119 self.player.proceed(is_AI=False)
120 if self.player.task is None or not self.player_is_alive:
123 def add_thing_at(self, type_, pos):
124 t = self.game.thing_types[type_](self)
129 def make_new(self, yx, seed):
131 def add_thing_at_random(type_):
134 YX(self.rand.randint(0, yx.y - 1),
135 self.rand.randint(0, yx.x - 1)))
136 if self.maps[new_pos[0]][new_pos[1]] != '.':
138 if len(self.things_at_pos(new_pos)) > 0:
140 return self.add_thing_at(type_, new_pos)
147 map_ = self.get_map(YX(0,0))
149 map_[pos] = self.rand.choice(('.', '.', '.', '.', 'x'))
150 player = add_thing_at_random('human')
151 self.player_id = player.id_
152 add_thing_at_random('monster')
153 add_thing_at_random('monster')
154 add_thing_at_random('food')
155 add_thing_at_random('food')
156 add_thing_at_random('food')
157 add_thing_at_random('food')
164 def __init__(self, game_file_name):
165 self.io = GameIO(game_file_name, self)
166 self.map_type = MapHex
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_type().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 = (thing.position[1] - offset)
206 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
207 self.io.send('THING_POS %s %s' % (thing.id_, offset_pos))
209 self.io.send('TURN ' + str(self.world.turn))
210 visible_map = self.world.player.get_visible_map()
211 offset = self.world.player.get_surroundings_offset()
212 self.io.send('VISIBLE_MAP %s %s' % (offset, visible_map.size))
213 for y, line in visible_map.lines():
214 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
215 visible_things = self.world.player.get_visible_things()
216 for thing in visible_things:
217 send_thing(offset, thing)
218 if hasattr(thing, 'health'):
219 self.io.send('THING_HEALTH %s %s' % (thing.id_,
221 if len(self.world.player.inventory) > 0:
222 self.io.send('PLAYER_INVENTORY %s' %
223 ','.join([str(i) for i in self.world.player.inventory]))
225 self.io.send('PLAYER_INVENTORY ,')
226 for id_ in self.world.player.inventory:
227 thing = self.world.get_thing(id_)
228 send_thing(offset, thing)
229 self.io.send('GAME_STATE_COMPLETE')
232 """Send turn finish signal, run game world, send new world data.
234 First sends 'TURN_FINISHED' message, then runs game world
235 until new player input is needed, then sends game state.
237 self.io.send('TURN_FINISHED ' + str(self.world.turn))
238 self.world.proceed_to_next_player_turn()
239 msg = str(self.world.player._last_task_result)
240 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
241 self.send_gamestate()
243 def get_command(self, command_name):
245 def partial_with_attrs(f, *args, **kwargs):
246 from functools import partial
247 p = partial(f, *args, **kwargs)
248 p.__dict__.update(f.__dict__)
251 def cmd_TASK_colon(task_name, game, *args):
252 if not game.world.player_is_alive:
253 raise GameError('You are dead.')
254 game.world.player.set_task(task_name, args)
257 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
258 t = game.world.get_thing(thing_id, False)
260 raise ArgError('No such Thing.')
261 task_class = game.tasks[task_name]
262 t.task = task_class(t, args)
265 def task_prefixed(command_name, task_prefix, task_command,
266 argtypes_prefix=None):
267 if command_name[:len(task_prefix)] == task_prefix:
268 task_name = command_name[len(task_prefix):]
269 if task_name in self.tasks:
270 f = partial_with_attrs(task_command, task_name, self)
271 task = self.tasks[task_name]
273 f.argtypes = argtypes_prefix + ' ' + task.argtypes
275 f.argtypes = task.argtypes
279 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
282 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
283 'int:nonneg int:nonneg ')
286 if command_name in self.commands:
287 f = partial_with_attrs(self.commands[command_name], self)