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
21 def __init__(self, game):
26 def get_thing(self, id_, create_unfound=True):
27 for thing in self.things:
31 t = self.game.thing_type(self, id_)
38 class World(WorldBase):
40 def __init__(self, *args, **kwargs):
41 super().__init__(*args, **kwargs)
43 self.player_is_alive = True
47 return self.get_thing(self.player_id)
49 def new_thing_id(self):
50 if len(self.things) == 0:
52 return self.things[-1].id_ + 1
54 def new_map(self, yx):
55 self.map_ = self.game.map_type(yx)
57 def proceed_to_next_player_turn(self):
58 """Run game world turns until player can decide their next step.
60 Iterates through all non-player things, on each step
61 furthering them in their tasks (and letting them decide new
62 ones if they finish). The iteration order is: first all things
63 that come after the player in the world things list, then
64 (after incrementing the world turn) all that come before the
65 player; then the player's .proceed() is run, and if it does
66 not finish his task, the loop starts at the beginning. Once
67 the player's task is finished, or the player is dead, the loop
72 player_i = self.things.index(self.player)
73 for thing in self.things[player_i+1:]:
76 for thing in self.things[:player_i]:
78 self.player.proceed(is_AI=False)
79 if self.player.task is None or not self.player_is_alive:
82 def make_new(self, yx, seed):
86 t = self.game.thing_types[type_](self)
87 t.position = (random.randint(0, yx[0] -1),
88 random.randint(0, yx[1] - 1))
97 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
100 self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
102 player = add_thing('human')
103 self.player_id = player.id_
116 def __init__(self, game_file_name):
117 self.io = GameIO(game_file_name, self)
118 self.map_type = MapHex
119 self.tasks = {'WAIT': Task_WAIT,
121 'PICKUP': Task_PICKUP,
124 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
125 'GET_GAMESTATE': cmd_GET_GAMESTATE,
127 'THING_TYPE': cmd_THING_TYPE,
128 'THING_POS': cmd_THING_POS,
129 'THING_HEALTH': cmd_THING_HEALTH,
130 'THING_INVENTORY': cmd_THING_INVENTORY,
131 'TERRAIN_LINE': cmd_TERRAIN_LINE,
132 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
133 'PLAYER_ID': cmd_PLAYER_ID,
135 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
137 self.world_type = World
138 self.world = self.world_type(self)
139 self.thing_type = Thing
140 self.thing_types = {'human': ThingHuman,
141 'monster': ThingMonster,
144 def get_string_options(self, string_option_type):
145 if string_option_type == 'direction':
146 return self.world.map_.get_directions()
147 elif string_option_type == 'thingtype':
148 return list(self.thing_types.keys())
151 def send_gamestate(self, connection_id=None):
152 """Send out game state data relevant to clients."""
154 self.io.send('TURN ' + str(self.world.turn))
155 self.io.send('MAP ' + stringify_yx(self.world.map_.size))
156 visible_map = self.world.player.get_visible_map()
157 for y, line in visible_map.lines():
158 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
159 visible_things = self.world.player.get_visible_things()
160 for thing in visible_things:
161 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
162 self.io.send('THING_POS %s %s' % (thing.id_,
163 stringify_yx(thing.position)))
164 if hasattr(thing, 'health'):
165 self.io.send('THING_HEALTH %s %s' % (thing.id_,
167 if len(self.world.player.inventory) > 0:
168 self.io.send('PLAYER_INVENTORY %s' %
169 ','.join([str(i) for i in self.world.player.inventory]))
171 self.io.send('PLAYER_INVENTORY ,')
172 for id_ in self.world.player.inventory:
173 thing = self.world.get_thing(id_)
174 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
175 self.io.send('THING_POS %s %s' % (thing.id_,
176 stringify_yx(thing.position)))
177 self.io.send('GAME_STATE_COMPLETE')
180 """Send turn finish signal, run game world, send new world data.
182 First sends 'TURN_FINISHED' message, then runs game world
183 until new player input is needed, then sends game state.
185 self.io.send('TURN_FINISHED ' + str(self.world.turn))
186 self.world.proceed_to_next_player_turn()
187 msg = str(self.world.player._last_task_result)
188 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
189 self.send_gamestate()
191 def get_command(self, command_name):
193 def partial_with_attrs(f, *args, **kwargs):
194 from functools import partial
195 p = partial(f, *args, **kwargs)
196 p.__dict__.update(f.__dict__)
199 def cmd_TASK_colon(task_name, game, *args):
200 if not game.world.player_is_alive:
201 raise GameError('You are dead.')
202 game.world.player.set_task(task_name, args)
205 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
206 t = game.world.get_thing(thing_id, False)
208 raise ArgError('No such Thing.')
209 task_class = game.tasks[task_name]
210 t.task = task_class(t, args)
213 def task_prefixed(command_name, task_prefix, task_command,
214 argtypes_prefix=None):
215 if command_name[:len(task_prefix)] == task_prefix:
216 task_name = command_name[len(task_prefix):]
217 if task_name in self.tasks:
218 f = partial_with_attrs(task_command, task_name, self)
219 task = self.tasks[task_name]
221 f.argtypes = argtypes_prefix + ' ' + task.argtypes
223 f.argtypes = task.argtypes
227 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
230 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
231 'int:nonneg int:nonneg ')
234 if command_name in self.commands:
235 f = partial_with_attrs(self.commands[command_name], self)