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,
21 class PRNGod(random.Random):
24 self.prngod_seed = seed
27 return self.prngod_seed
33 self.prngod_seed = ((self.prngod_seed * 1103515245) + 12345) % 2**32
34 return (self.prngod_seed >> 16) / (2**16 - 1)
44 def get_thing(self, id_, create_unfound=True):
45 for thing in self.things:
49 t = self.thing_type(self, id_)
54 def things_at_pos(self, pos):
65 def __init__(self, game_file_name, *args, **kwargs):
66 super().__init__(*args, **kwargs)
67 self.io = GameIO(game_file_name, self)
69 self.map_geometry = MapGeometryHex()
70 self.tasks = {'WAIT': Task_WAIT,
72 'PICKUP': Task_PICKUP,
75 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
76 'GET_GAMESTATE': cmd_GET_GAMESTATE,
78 'MAP_SIZE': cmd_MAP_SIZE,
80 'THING_TYPE': cmd_THING_TYPE,
81 'THING_POS': cmd_THING_POS,
82 'THING_HEALTH': cmd_THING_HEALTH,
83 'THING_INVENTORY': cmd_THING_INVENTORY,
84 'TERRAIN_LINE': cmd_TERRAIN_LINE,
85 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
86 'PLAYER_ID': cmd_PLAYER_ID,
88 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
90 self.thing_type = Thing
91 self.thing_types = {'human': ThingHuman,
92 'monster': ThingMonster,
95 self.player_is_alive = True
99 def get_string_options(self, string_option_type):
100 if string_option_type == 'direction':
101 return self.map_geometry.get_directions()
102 elif string_option_type == 'thingtype':
103 return list(self.thing_types.keys())
106 def send_gamestate(self, connection_id=None):
107 """Send out game state data relevant to clients."""
109 def send_thing(thing):
110 view_pos = self.map_geometry.pos_in_view(thing.position,
111 self.player.view_offset,
113 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
114 self.io.send('THING_POS %s %s' % (thing.id_, view_pos))
116 self.io.send('PLAYER_ID ' + str(self.player_id))
117 self.io.send('TURN ' + str(self.turn))
118 visible_map = self.player.get_visible_map()
119 self.io.send('VISIBLE_MAP %s %s' % (visible_map.size,
120 visible_map.start_indented))
121 for y, line in visible_map.lines():
122 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
123 visible_things = self.player.get_visible_things()
124 for thing in visible_things:
126 if hasattr(thing, 'health'):
127 self.io.send('THING_HEALTH %s %s' % (thing.id_,
129 if len(self.player.inventory) > 0:
130 self.io.send('PLAYER_INVENTORY %s' %
131 ','.join([str(i) for i in self.player.inventory]))
133 self.io.send('PLAYER_INVENTORY ,')
134 for id_ in self.player.inventory:
135 thing = self.get_thing(id_)
137 self.io.send('GAME_STATE_COMPLETE')
140 """Send turn finish signal, run game world, send new world data.
142 First sends 'TURN_FINISHED' message, then runs game world
143 until new player input is needed, then sends game state.
145 self.io.send('TURN_FINISHED ' + str(self.turn))
146 self.proceed_to_next_player_turn()
147 msg = str(self.player._last_task_result)
148 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
149 self.send_gamestate()
151 def get_command(self, command_name):
153 def partial_with_attrs(f, *args, **kwargs):
154 from functools import partial
155 p = partial(f, *args, **kwargs)
156 p.__dict__.update(f.__dict__)
159 def cmd_TASK_colon(task_name, game, *args):
160 if not game.player_is_alive:
161 raise GameError('You are dead.')
162 game.player.set_task(task_name, args)
165 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
166 t = game.get_thing(thing_id, False)
168 raise ArgError('No such Thing.')
169 task_class = game.tasks[task_name]
170 t.task = task_class(t, args)
173 def task_prefixed(command_name, task_prefix, task_command,
174 argtypes_prefix=None):
175 if command_name[:len(task_prefix)] == task_prefix:
176 task_name = command_name[len(task_prefix):]
177 if task_name in self.tasks:
178 f = partial_with_attrs(task_command, task_name, self)
179 task = self.tasks[task_name]
181 f.argtypes = argtypes_prefix + ' ' + task.argtypes
183 f.argtypes = task.argtypes
187 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
190 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
191 'int:nonneg int:nonneg ')
194 if command_name in self.commands:
195 f = partial_with_attrs(self.commands[command_name], self)
201 return self.get_thing(self.player_id)
203 def new_thing_id(self):
204 if len(self.things) == 0:
206 return self.things[-1].id_ + 1
208 def get_map(self, map_pos):
209 if not (map_pos in self.maps and
210 self.maps[map_pos].size == self.map_size):
211 self.maps[map_pos] = Map(self.map_size)
212 for pos in self.maps[map_pos]:
213 self.maps[map_pos][pos] = '.'
214 return self.maps[map_pos]
216 def proceed_to_next_player_turn(self):
217 """Run game world turns until player can decide their next step.
219 All things and processes inside the player's reality bubble
220 are worked through. Things are furthered in their tasks and,
221 if finished, decide new ones. The iteration order is: first
222 all things that come after the player in the world things
223 list, then (after incrementing the world turn) all that come
224 before the player; then the player's .proceed() is run.
226 Next, parts of the game world are put to sleep or woken up
227 based on how close they are to the player's position, or how
228 short ago the player visited them.
230 If the player's last task is finished at the end of the loop,
231 it breaks; otherwise it starts again.
236 for thing in self.things[player_i+1:]:
239 for map_pos in self.maps:
240 if self.maps[map_pos].awake:
241 for pos in self.maps[map_pos]:
242 if self.rand.random() > 0.999 and \
243 self.maps[map_pos][pos] == '.' and \
244 len(self.things_at_pos((map_pos, pos))) == 0:
245 self.add_thing_at('food', (map_pos, pos))
246 for thing in self.things[:player_i]:
248 self.player.proceed(is_AI=False)
250 def reality_bubble():
252 for map_pos in self.maps:
253 m = self.maps[map_pos]
254 if map_pos in self.player.close_maps:
256 # Newly inside chunks are regenerated from .stats.
258 for t_type in m.stats:
259 stat = m.stats[t_type]
260 to_create = stat['population'] // 100
261 to_create = stat['population'] // 100 +\
262 int(self.rand.randint(0, 99) < (stat['population'] % 100))
265 average_health = None
266 if stat['health'] > 0:
267 average_health = math.ceil(stat['health'] /
269 for i in range(to_create):
270 t = self.add_thing_at_random(map_pos, t_type)
272 t.health = average_health
274 # Inside chunks are set to max .awake and don't collect
279 # Outside chunks grow distant through .awake decremention.
280 # They collect .stats until they fall asleep – then any things
281 # inside are disappeared.
284 for t in self.things:
285 if t.position[0] == map_pos:
286 if not t.type_ in m.stats:
287 m.stats[t.type_] = {'population': 0,
289 m.stats[t.type_]['population'] += 1
290 if isinstance(t, ThingAnimate):
291 m.stats[t.type_]['health'] += t.health
294 del self.things[self.things.index(t)]
297 player_i = self.things.index(self.player)
300 if self.player.task is None or not self.player_is_alive:
303 def add_thing_at(self, type_, pos):
304 t = self.thing_types[type_](self)
309 def add_thing_at_random(self, big_yx, type_):
312 YX(self.rand.randint(0, self.map_size.y - 1),
313 self.rand.randint(0, self.map_size.x - 1)))
314 if self.maps[new_pos[0]][new_pos[1]] != '.':
316 if len(self.things_at_pos(new_pos)) > 0:
318 return self.add_thing_at(type_, new_pos)
320 def make_map_chunk(self, big_yx):
321 map_ = self.get_map(big_yx)
323 map_[pos] = self.rand.choice(('.', '.', '.', '~', 'x'))
324 self.add_thing_at_random(big_yx, 'monster')
325 self.add_thing_at_random(big_yx, 'monster')
326 self.add_thing_at_random(big_yx, 'monster')
327 self.add_thing_at_random(big_yx, 'monster')
328 self.add_thing_at_random(big_yx, 'monster')
329 self.add_thing_at_random(big_yx, 'monster')
330 self.add_thing_at_random(big_yx, 'monster')
331 self.add_thing_at_random(big_yx, 'monster')
332 self.add_thing_at_random(big_yx, 'food')
333 self.add_thing_at_random(big_yx, 'food')
334 self.add_thing_at_random(big_yx, 'food')
335 self.add_thing_at_random(big_yx, 'food')
337 def make_new_world(self, size, seed):
343 self.make_map_chunk(YX(0,0))
344 player = self.add_thing_at_random(YX(0,0), 'human')
345 player.surroundings # To help initializing reality bubble, see
346 # comment on ThingAnimate._position_set
347 self.player_id = player.id_