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 pos in self.maps[(0,0)]:
129 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
130 self.maps[(0,0)][pos] = '#'
132 self.maps[(0,0)][pos] = random.choice(('.', '.', '.', '.', 'x'))
134 player = add_thing_at_random('human')
135 self.player_id = player.id_
136 add_thing_at_random('monster')
137 add_thing_at_random('monster')
138 add_thing_at_random('food')
139 add_thing_at_random('food')
140 add_thing_at_random('food')
141 add_thing_at_random('food')
148 def __init__(self, game_file_name):
149 self.io = GameIO(game_file_name, self)
150 self.map_type = MapHex
151 self.tasks = {'WAIT': Task_WAIT,
153 'PICKUP': Task_PICKUP,
156 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
157 'GET_GAMESTATE': cmd_GET_GAMESTATE,
159 'THING_TYPE': cmd_THING_TYPE,
160 'THING_POS': cmd_THING_POS,
161 'THING_HEALTH': cmd_THING_HEALTH,
162 'THING_INVENTORY': cmd_THING_INVENTORY,
163 'TERRAIN_LINE': cmd_TERRAIN_LINE,
164 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
165 'PLAYER_ID': cmd_PLAYER_ID,
167 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
169 self.world_type = World
170 self.world = self.world_type(self)
171 self.thing_type = Thing
172 self.thing_types = {'human': ThingHuman,
173 'monster': ThingMonster,
176 def get_string_options(self, string_option_type):
177 if string_option_type == 'direction':
178 return self.world.maps[(0,0)].get_directions()
179 elif string_option_type == 'thingtype':
180 return list(self.thing_types.keys())
183 def send_gamestate(self, connection_id=None):
184 """Send out game state data relevant to clients."""
186 self.io.send('TURN ' + str(self.world.turn))
187 self.io.send('MAP ' + stringify_yx(visible_map.size))
188 visible_map = self.world.player.get_visible_map()
189 for y, line in visible_map.lines():
190 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
191 visible_things, offset = self.world.player.get_visible_things()
192 for thing in visible_things:
193 offset_pos = (thing.position[1][0] - offset[0],
194 thing.position[1][1] - offset[1])
195 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
196 self.io.send('THING_POS %s %s %s' % (thing.id_,
197 stringify_yx(thing.position[0]),
198 stringify_yx(offset_pos)))
199 if hasattr(thing, 'health'):
200 self.io.send('THING_HEALTH %s %s' % (thing.id_,
202 if len(self.world.player.inventory) > 0:
203 self.io.send('PLAYER_INVENTORY %s' %
204 ','.join([str(i) for i in self.world.player.inventory]))
206 self.io.send('PLAYER_INVENTORY ,')
207 for id_ in self.world.player.inventory:
208 thing = self.world.get_thing(id_)
209 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
210 self.io.send('THING_POS %s %s %s' % (thing.id_,
211 stringify_yx(thing.position[0]),
212 stringify_yx(thing.position[1])))
213 self.io.send('GAME_STATE_COMPLETE')
216 """Send turn finish signal, run game world, send new world data.
218 First sends 'TURN_FINISHED' message, then runs game world
219 until new player input is needed, then sends game state.
221 self.io.send('TURN_FINISHED ' + str(self.world.turn))
222 self.world.proceed_to_next_player_turn()
223 msg = str(self.world.player._last_task_result)
224 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
225 self.send_gamestate()
227 def get_command(self, command_name):
229 def partial_with_attrs(f, *args, **kwargs):
230 from functools import partial
231 p = partial(f, *args, **kwargs)
232 p.__dict__.update(f.__dict__)
235 def cmd_TASK_colon(task_name, game, *args):
236 if not game.world.player_is_alive:
237 raise GameError('You are dead.')
238 game.world.player.set_task(task_name, args)
241 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
242 t = game.world.get_thing(thing_id, False)
244 raise ArgError('No such Thing.')
245 task_class = game.tasks[task_name]
246 t.task = task_class(t, args)
249 def task_prefixed(command_name, task_prefix, task_command,
250 argtypes_prefix=None):
251 if command_name[:len(task_prefix)] == task_prefix:
252 task_name = command_name[len(task_prefix):]
253 if task_name in self.tasks:
254 f = partial_with_attrs(task_command, task_name, self)
255 task = self.tasks[task_name]
257 f.argtypes = argtypes_prefix + ' ' + task.argtypes
259 f.argtypes = task.argtypes
263 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
266 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
267 'int:nonneg int:nonneg ')
270 if command_name in self.commands:
271 f = partial_with_attrs(self.commands[command_name], self)