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, yx):
46 class World(WorldBase):
48 def __init__(self, *args, **kwargs):
49 super().__init__(*args, **kwargs)
51 self.player_is_alive = True
55 return self.get_thing(self.player_id)
57 def new_thing_id(self):
58 if len(self.things) == 0:
60 return self.things[-1].id_ + 1
62 def new_map(self, yx):
63 self.map_ = self.game.map_type(yx)
65 def proceed_to_next_player_turn(self):
66 """Run game world turns until player can decide their next step.
68 Iterates through all non-player things, on each step
69 furthering them in their tasks (and letting them decide new
70 ones if they finish). The iteration order is: first all things
71 that come after the player in the world things list, then
72 (after incrementing the world turn) all that come before the
73 player; then the player's .proceed() is run, and if it does
74 not finish his task, the loop starts at the beginning. Once
75 the player's task is finished, or the player is dead, the loop
80 player_i = self.things.index(self.player)
81 for thing in self.things[player_i+1:]:
85 if self.map_[pos] == '.' and \
86 len(self.things_at_pos(pos)) == 0 and \
87 random.random() > 0.999:
88 self.add_thing_at('food', pos)
89 for thing in self.things[:player_i]:
91 self.player.proceed(is_AI=False)
92 if self.player.task is None or not self.player_is_alive:
95 def add_thing_at(self, type_, pos):
96 t = self.game.thing_types[type_](self)
101 def make_new(self, yx, seed):
103 def add_thing_at_random(type_):
105 new_pos = (random.randint(0, yx[0] -1),
106 random.randint(0, yx[1] - 1))
107 if self.map_[new_pos] != '.':
109 if len(self.things_at_pos(new_pos)) > 0:
111 return self.add_thing_at(type_, new_pos)
117 for pos in self.map_:
118 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
121 self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
123 player = add_thing_at_random('human')
124 self.player_id = player.id_
125 add_thing_at_random('monster')
126 add_thing_at_random('monster')
127 add_thing_at_random('food')
128 add_thing_at_random('food')
129 add_thing_at_random('food')
130 add_thing_at_random('food')
137 def __init__(self, game_file_name):
138 self.io = GameIO(game_file_name, self)
139 self.map_type = MapHex
140 self.tasks = {'WAIT': Task_WAIT,
142 'PICKUP': Task_PICKUP,
145 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
146 'GET_GAMESTATE': cmd_GET_GAMESTATE,
148 'THING_TYPE': cmd_THING_TYPE,
149 'THING_POS': cmd_THING_POS,
150 'THING_HEALTH': cmd_THING_HEALTH,
151 'THING_INVENTORY': cmd_THING_INVENTORY,
152 'TERRAIN_LINE': cmd_TERRAIN_LINE,
153 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
154 'PLAYER_ID': cmd_PLAYER_ID,
156 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
158 self.world_type = World
159 self.world = self.world_type(self)
160 self.thing_type = Thing
161 self.thing_types = {'human': ThingHuman,
162 'monster': ThingMonster,
165 def get_string_options(self, string_option_type):
166 if string_option_type == 'direction':
167 return self.world.map_.get_directions()
168 elif string_option_type == 'thingtype':
169 return list(self.thing_types.keys())
172 def send_gamestate(self, connection_id=None):
173 """Send out game state data relevant to clients."""
175 self.io.send('TURN ' + str(self.world.turn))
176 self.io.send('MAP ' + stringify_yx(self.world.map_.size))
177 visible_map = self.world.player.get_visible_map()
178 for y, line in visible_map.lines():
179 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
180 visible_things = self.world.player.get_visible_things()
181 for thing in visible_things:
182 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
183 self.io.send('THING_POS %s %s' % (thing.id_,
184 stringify_yx(thing.position)))
185 if hasattr(thing, 'health'):
186 self.io.send('THING_HEALTH %s %s' % (thing.id_,
188 if len(self.world.player.inventory) > 0:
189 self.io.send('PLAYER_INVENTORY %s' %
190 ','.join([str(i) for i in self.world.player.inventory]))
192 self.io.send('PLAYER_INVENTORY ,')
193 for id_ in self.world.player.inventory:
194 thing = self.world.get_thing(id_)
195 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
196 self.io.send('THING_POS %s %s' % (thing.id_,
197 stringify_yx(thing.position)))
198 self.io.send('GAME_STATE_COMPLETE')
201 """Send turn finish signal, run game world, send new world data.
203 First sends 'TURN_FINISHED' message, then runs game world
204 until new player input is needed, then sends game state.
206 self.io.send('TURN_FINISHED ' + str(self.world.turn))
207 self.world.proceed_to_next_player_turn()
208 msg = str(self.world.player._last_task_result)
209 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
210 self.send_gamestate()
212 def get_command(self, command_name):
214 def partial_with_attrs(f, *args, **kwargs):
215 from functools import partial
216 p = partial(f, *args, **kwargs)
217 p.__dict__.update(f.__dict__)
220 def cmd_TASK_colon(task_name, game, *args):
221 if not game.world.player_is_alive:
222 raise GameError('You are dead.')
223 game.world.player.set_task(task_name, args)
226 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
227 t = game.world.get_thing(thing_id, False)
229 raise ArgError('No such Thing.')
230 task_class = game.tasks[task_name]
231 t.task = task_class(t, args)
234 def task_prefixed(command_name, task_prefix, task_command,
235 argtypes_prefix=None):
236 if command_name[:len(task_prefix)] == task_prefix:
237 task_name = command_name[len(task_prefix):]
238 if task_name in self.tasks:
239 f = partial_with_attrs(task_command, task_name, self)
240 task = self.tasks[task_name]
242 f.argtypes = argtypes_prefix + ' ' + task.argtypes
244 f.argtypes = task.argtypes
248 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
251 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
252 'int:nonneg int:nonneg ')
255 if command_name in self.commands:
256 f = partial_with_attrs(self.commands[command_name], self)