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,
8 cmd_GET_PICKABLE_ITEMS,
9 cmd_TERRAIN_LINE, cmd_PLAYER_ID,
10 cmd_TURN, cmd_SWITCH_PLAYER, cmd_SAVE)
11 from plomrogue.mapping import MapHex
12 from plomrogue.parser import Parser
13 from plomrogue.io import GameIO
14 from plomrogue.misc import quote, stringify_yx
15 from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingFood
22 def __init__(self, game):
27 def get_thing(self, id_, create_unfound=True):
28 for thing in self.things:
32 t = self.game.thing_type(self, id_)
37 def things_at_pos(self, pos):
46 class World(WorldBase):
48 def __init__(self, *args, **kwargs):
49 super().__init__(*args, **kwargs)
51 self.player_is_alive = True
56 return self.get_thing(self.player_id)
58 def new_thing_id(self):
59 if len(self.things) == 0:
61 return self.things[-1].id_ + 1
63 def new_map(self, map_pos, size):
64 self.maps[map_pos] = self.game.map_type(size)
66 def proceed_to_next_player_turn(self):
67 """Run game world turns until player can decide their next step.
69 Iterates through all non-player things, on each step
70 furthering them in their tasks (and letting them decide new
71 ones if they finish). The iteration order is: first all things
72 that come after the player in the world things list, then
73 (after incrementing the world turn) all that come before the
74 player; then the player's .proceed() is run, and if it does
75 not finish his task, the loop starts at the beginning. Once
76 the player's task is finished, or the player is dead, the loop
81 player_i = self.things.index(self.player)
82 for thing in self.things[player_i+1:]:
85 for pos in self.maps[(0,0)]:
86 if self.maps[(0,0)][pos] == '.' and \
87 len(self.things_at_pos(((0,0), pos))) == 0 and \
88 random.random() > 0.999:
89 self.add_thing_at('food', ((0,0), pos))
90 for thing in self.things[:player_i]:
92 self.player.proceed(is_AI=False)
93 if self.player.task is None or not self.player_is_alive:
96 def add_thing_at(self, type_, pos):
97 t = self.game.thing_types[type_](self)
102 def make_new(self, yx, seed):
104 def add_thing_at_random(type_):
107 (random.randint(0, yx[0] -1),
108 random.randint(0, yx[1] -1)))
109 if self.maps[new_pos[0]][new_pos[1]] != '.':
111 if len(self.things_at_pos(new_pos)) > 0:
113 return self.add_thing_at(type_, new_pos)
119 self.new_map((0,0), yx)
120 self.new_map((0,1), yx)
121 self.new_map((1,1), yx)
122 self.new_map((1,0), yx)
123 self.new_map((1,-1), yx)
124 self.new_map((0,-1), yx)
125 self.new_map((-1,-1), yx)
126 self.new_map((-1,0), yx)
127 self.new_map((-1,1), yx)
128 for map_pos in self.maps:
129 map_ = self.maps[map_pos]
132 map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
136 player = add_thing_at_random('human')
137 self.player_id = player.id_
138 add_thing_at_random('monster')
139 add_thing_at_random('monster')
140 add_thing_at_random('food')
141 add_thing_at_random('food')
142 add_thing_at_random('food')
143 add_thing_at_random('food')
150 def __init__(self, game_file_name):
151 self.io = GameIO(game_file_name, self)
152 self.map_type = MapHex
153 self.tasks = {'WAIT': Task_WAIT,
155 'PICKUP': Task_PICKUP,
158 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
159 'GET_GAMESTATE': cmd_GET_GAMESTATE,
161 'THING_TYPE': cmd_THING_TYPE,
162 'THING_POS': cmd_THING_POS,
163 'THING_HEALTH': cmd_THING_HEALTH,
164 'THING_INVENTORY': cmd_THING_INVENTORY,
165 'TERRAIN_LINE': cmd_TERRAIN_LINE,
166 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
167 'PLAYER_ID': cmd_PLAYER_ID,
169 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
171 self.world_type = World
172 self.world = self.world_type(self)
173 self.thing_type = Thing
174 self.thing_types = {'human': ThingHuman,
175 'monster': ThingMonster,
178 def get_string_options(self, string_option_type):
179 if string_option_type == 'direction':
180 return self.world.maps[(0,0)].get_directions()
181 elif string_option_type == 'thingtype':
182 return list(self.thing_types.keys())
185 def send_gamestate(self, connection_id=None):
186 """Send out game state data relevant to clients."""
188 self.io.send('TURN ' + str(self.world.turn))
189 visible_map = self.world.player.get_visible_map()
190 self.io.send('MAP ' + stringify_yx([0,0]) + ' ' + stringify_yx(visible_map.size))
191 for y, line in visible_map.lines():
192 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
193 visible_things, offset = self.world.player.get_visible_things()
194 for thing in visible_things:
195 offset_pos = (thing.position[1][0] - offset[0],
196 thing.position[1][1] - offset[1])
197 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
198 self.io.send('THING_POS %s %s %s' % (thing.id_,
199 stringify_yx(thing.position[0]),
200 stringify_yx(offset_pos)))
201 if hasattr(thing, 'health'):
202 self.io.send('THING_HEALTH %s %s' % (thing.id_,
204 if len(self.world.player.inventory) > 0:
205 self.io.send('PLAYER_INVENTORY %s' %
206 ','.join([str(i) for i in self.world.player.inventory]))
208 self.io.send('PLAYER_INVENTORY ,')
209 for id_ in self.world.player.inventory:
210 thing = self.world.get_thing(id_)
211 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
212 self.io.send('THING_POS %s %s %s' % (thing.id_,
213 stringify_yx(thing.position[0]),
214 stringify_yx(thing.position[1])))
215 self.io.send('GAME_STATE_COMPLETE')
218 """Send turn finish signal, run game world, send new world data.
220 First sends 'TURN_FINISHED' message, then runs game world
221 until new player input is needed, then sends game state.
223 self.io.send('TURN_FINISHED ' + str(self.world.turn))
224 self.world.proceed_to_next_player_turn()
225 msg = str(self.world.player._last_task_result)
226 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
227 self.send_gamestate()
229 def get_command(self, command_name):
231 def partial_with_attrs(f, *args, **kwargs):
232 from functools import partial
233 p = partial(f, *args, **kwargs)
234 p.__dict__.update(f.__dict__)
237 def cmd_TASK_colon(task_name, game, *args):
238 if not game.world.player_is_alive:
239 raise GameError('You are dead.')
240 game.world.player.set_task(task_name, args)
243 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
244 t = game.world.get_thing(thing_id, False)
246 raise ArgError('No such Thing.')
247 task_class = game.tasks[task_name]
248 t.task = task_class(t, args)
251 def task_prefixed(command_name, task_prefix, task_command,
252 argtypes_prefix=None):
253 if command_name[:len(task_prefix)] == task_prefix:
254 task_name = command_name[len(task_prefix):]
255 if task_name in self.tasks:
256 f = partial_with_attrs(task_command, task_name, self)
257 task = self.tasks[task_name]
259 f.argtypes = argtypes_prefix + ' ' + task.argtypes
261 f.argtypes = task.argtypes
265 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
268 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
269 'int:nonneg int:nonneg ')
272 if command_name in self.commands:
273 f = partial_with_attrs(self.commands[command_name], self)