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)
43 def get_thing(self, id_, create_unfound=True):
44 for thing in self.things:
48 t = self.thing_type(self, id_)
53 def things_at_pos(self, pos):
64 def __init__(self, game_file_name, *args, **kwargs):
65 super().__init__(*args, **kwargs)
66 self.io = GameIO(game_file_name, self)
68 self.map_geometry = MapGeometryHex()
69 self.tasks = {'WAIT': Task_WAIT,
71 'PICKUP': Task_PICKUP,
74 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
75 'GET_GAMESTATE': cmd_GET_GAMESTATE,
77 'MAP_SIZE': cmd_MAP_SIZE,
79 'THING_TYPE': cmd_THING_TYPE,
80 'THING_POS': cmd_THING_POS,
81 'THING_HEALTH': cmd_THING_HEALTH,
82 'THING_INVENTORY': cmd_THING_INVENTORY,
83 'TERRAIN_LINE': cmd_TERRAIN_LINE,
84 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
85 'PLAYER_ID': cmd_PLAYER_ID,
87 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
89 self.thing_type = Thing
90 self.thing_types = {'human': ThingHuman,
91 'monster': ThingMonster,
94 self.player_is_alive = True
98 def get_string_options(self, string_option_type):
99 if string_option_type == 'direction':
100 return self.map_geometry.get_directions()
101 elif string_option_type == 'thingtype':
102 return list(self.thing_types.keys())
105 def send_gamestate(self, connection_id=None):
106 """Send out game state data relevant to clients."""
108 def send_thing(thing):
109 view_pos = self.map_geometry.pos_in_view(thing.position,
110 self.player.view_offset,
112 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
113 self.io.send('THING_POS %s %s' % (thing.id_, view_pos))
115 self.io.send('TURN ' + str(self.turn))
116 visible_map = self.player.get_visible_map()
117 self.io.send('VISIBLE_MAP %s %s' % (visible_map.size,
118 visible_map.start_indented))
119 for y, line in visible_map.lines():
120 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
121 visible_things = self.player.get_visible_things()
122 for thing in visible_things:
124 if hasattr(thing, 'health'):
125 self.io.send('THING_HEALTH %s %s' % (thing.id_,
127 if len(self.player.inventory) > 0:
128 self.io.send('PLAYER_INVENTORY %s' %
129 ','.join([str(i) for i in self.player.inventory]))
131 self.io.send('PLAYER_INVENTORY ,')
132 for id_ in self.player.inventory:
133 thing = self.get_thing(id_)
135 self.io.send('GAME_STATE_COMPLETE')
138 """Send turn finish signal, run game world, send new world data.
140 First sends 'TURN_FINISHED' message, then runs game world
141 until new player input is needed, then sends game state.
143 self.io.send('TURN_FINISHED ' + str(self.turn))
144 self.proceed_to_next_player_turn()
145 msg = str(self.player._last_task_result)
146 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
147 self.send_gamestate()
149 def get_command(self, command_name):
151 def partial_with_attrs(f, *args, **kwargs):
152 from functools import partial
153 p = partial(f, *args, **kwargs)
154 p.__dict__.update(f.__dict__)
157 def cmd_TASK_colon(task_name, game, *args):
158 if not game.player_is_alive:
159 raise GameError('You are dead.')
160 game.player.set_task(task_name, args)
163 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
164 t = game.get_thing(thing_id, False)
166 raise ArgError('No such Thing.')
167 task_class = game.tasks[task_name]
168 t.task = task_class(t, args)
171 def task_prefixed(command_name, task_prefix, task_command,
172 argtypes_prefix=None):
173 if command_name[:len(task_prefix)] == task_prefix:
174 task_name = command_name[len(task_prefix):]
175 if task_name in self.tasks:
176 f = partial_with_attrs(task_command, task_name, self)
177 task = self.tasks[task_name]
179 f.argtypes = argtypes_prefix + ' ' + task.argtypes
181 f.argtypes = task.argtypes
185 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
188 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
189 'int:nonneg int:nonneg ')
192 if command_name in self.commands:
193 f = partial_with_attrs(self.commands[command_name], self)
199 return self.get_thing(self.player_id)
201 def new_thing_id(self):
202 if len(self.things) == 0:
204 return self.things[-1].id_ + 1
206 def get_map(self, map_pos, create_unfound=True):
207 if not (map_pos in self.maps and
208 self.maps[map_pos].size == self.map_size):
210 self.maps[map_pos] = Map(self.map_size)
211 for pos in self.maps[map_pos]:
212 self.maps[map_pos][pos] = '.'
215 return self.maps[map_pos]
217 def proceed_to_next_player_turn(self):
218 """Run game world turns until player can decide their next step.
220 Iterates through all non-player things, on each step
221 furthering them in their tasks (and letting them decide new
222 ones if they finish). The iteration order is: first all things
223 that come after the player in the world things list, then
224 (after incrementing the world turn) all that come before the
225 player; then the player's .proceed() is run, and if it does
226 not finish his task, the loop starts at the beginning. Once
227 the player's task is finished, or the player is dead, the loop
232 player_i = self.things.index(self.player)
233 for thing in self.things[player_i+1:]:
236 for pos in self.maps[YX(0,0)]:
237 if self.maps[YX(0,0)][pos] == '.' and \
238 len(self.things_at_pos((YX(0,0), pos))) == 0 and \
239 self.rand.random() > 0.999:
240 self.add_thing_at('food', (YX(0,0), pos))
241 for thing in self.things[:player_i]:
243 self.player.proceed(is_AI=False)
244 if self.player.task is None or not self.player_is_alive:
247 def add_thing_at(self, type_, pos):
248 t = self.thing_types[type_](self)
253 def make_new_world(self, yx, seed):
255 def add_thing_at_random(type_):
258 YX(self.rand.randint(0, yx.y - 1),
259 self.rand.randint(0, yx.x - 1)))
260 if self.maps[new_pos[0]][new_pos[1]] != '.':
262 if len(self.things_at_pos(new_pos)) > 0:
264 return self.add_thing_at(type_, new_pos)
271 map_ = self.get_map(YX(0,0))
273 map_[pos] = self.rand.choice(('.', '.', '.', '~', 'x'))
274 player = add_thing_at_random('human')
275 self.player_id = player.id_
276 add_thing_at_random('monster')
277 add_thing_at_random('monster')
278 add_thing_at_random('food')
279 add_thing_at_random('food')
280 add_thing_at_random('food')
281 add_thing_at_random('food')