1 from plomrogue.tasks import Task_WAIT, Task_MOVE, Task_PICKUP, Task_DROP
2 from plomrogue.errors import ArgError, GameError
3 from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE,
4 cmd_MAP, cmd_MAP, cmd_THING_TYPE,
5 cmd_THING_POS, cmd_THING_INVENTORY,
7 cmd_GET_PICKABLE_ITEMS,
8 cmd_TERRAIN_LINE, cmd_PLAYER_ID,
9 cmd_TURN, cmd_SWITCH_PLAYER, cmd_SAVE)
10 from plomrogue.mapping import MapHex
11 from plomrogue.parser import Parser
12 from plomrogue.io import GameIO
13 from plomrogue.misc import quote, stringify_yx
14 from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingItem
20 def __init__(self, game):
25 def get_thing(self, id_, create_unfound=True):
26 for thing in self.things:
30 t = self.game.thing_type(self, id_)
37 class World(WorldBase):
39 def __init__(self, *args, **kwargs):
40 super().__init__(*args, **kwargs)
42 self.player_is_alive = True
46 return self.get_thing(self.player_id)
48 def new_thing_id(self):
49 if len(self.things) == 0:
51 return self.things[-1].id_ + 1
53 def new_map(self, yx):
54 self.map_ = self.game.map_type(yx)
56 def proceed_to_next_player_turn(self):
57 """Run game world turns until player can decide their next step.
59 Iterates through all non-player things, on each step
60 furthering them in their tasks (and letting them decide new
61 ones if they finish). The iteration order is: first all things
62 that come after the player in the world things list, then
63 (after incrementing the world turn) all that come before the
64 player; then the player's .proceed() is run, and if it does
65 not finish his task, the loop starts at the beginning. Once
66 the player's task is finished, or the player is dead, the loop
71 player_i = self.things.index(self.player)
72 for thing in self.things[player_i+1:]:
75 for thing in self.things[:player_i]:
77 self.player.proceed(is_AI=False)
78 if self.player.task is None or not self.player_is_alive:
81 def make_new(self, yx, seed):
85 t = self.game.thing_types[type_](self)
86 t.position = (random.randint(0, yx[0] -1),
87 random.randint(0, yx[1] - 1))
96 if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
99 self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
101 player = add_thing('human')
102 self.player_id = player.id_
113 def __init__(self, game_file_name):
114 self.io = GameIO(game_file_name, self)
115 self.map_type = MapHex
116 self.tasks = {'WAIT': Task_WAIT,
118 'PICKUP': Task_PICKUP,
120 self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
121 'GET_GAMESTATE': cmd_GET_GAMESTATE,
123 'THING_TYPE': cmd_THING_TYPE,
124 'THING_POS': cmd_THING_POS,
125 'THING_HEALTH': cmd_THING_HEALTH,
126 'THING_INVENTORY': cmd_THING_INVENTORY,
127 'TERRAIN_LINE': cmd_TERRAIN_LINE,
128 'GET_PICKABLE_ITEMS': cmd_GET_PICKABLE_ITEMS,
129 'PLAYER_ID': cmd_PLAYER_ID,
131 'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
133 self.world_type = World
134 self.world = self.world_type(self)
135 self.thing_type = Thing
136 self.thing_types = {'human': ThingHuman,
137 'monster': ThingMonster,
140 def get_string_options(self, string_option_type):
141 if string_option_type == 'direction':
142 return self.world.map_.get_directions()
143 elif string_option_type == 'thingtype':
144 return list(self.thing_types.keys())
147 def send_gamestate(self, connection_id=None):
148 """Send out game state data relevant to clients."""
150 self.io.send('TURN ' + str(self.world.turn))
151 self.io.send('MAP ' + stringify_yx(self.world.map_.size))
152 visible_map = self.world.player.get_visible_map()
153 for y, line in visible_map.lines():
154 self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
155 visible_things = self.world.player.get_visible_things()
156 for thing in visible_things:
157 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
158 self.io.send('THING_POS %s %s' % (thing.id_,
159 stringify_yx(thing.position)))
160 if hasattr(thing, 'health'):
161 self.io.send('THING_HEALTH %s %s' % (thing.id_,
163 if len(self.world.player.inventory) > 0:
164 self.io.send('PLAYER_INVENTORY %s' %
165 ','.join([str(i) for i in self.world.player.inventory]))
167 self.io.send('PLAYER_INVENTORY ,')
168 for id_ in self.world.player.inventory:
169 thing = self.world.get_thing(id_)
170 self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
171 self.io.send('THING_POS %s %s' % (thing.id_,
172 stringify_yx(thing.position)))
173 self.io.send('GAME_STATE_COMPLETE')
176 """Send turn finish signal, run game world, send new world data.
178 First sends 'TURN_FINISHED' message, then runs game world
179 until new player input is needed, then sends game state.
181 self.io.send('TURN_FINISHED ' + str(self.world.turn))
182 self.world.proceed_to_next_player_turn()
183 msg = str(self.world.player._last_task_result)
184 self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
185 self.send_gamestate()
187 def get_command(self, command_name):
189 def partial_with_attrs(f, *args, **kwargs):
190 from functools import partial
191 p = partial(f, *args, **kwargs)
192 p.__dict__.update(f.__dict__)
195 def cmd_TASK_colon(task_name, game, *args):
196 if not game.world.player_is_alive:
197 raise GameError('You are dead.')
198 game.world.player.set_task(task_name, args)
201 def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
202 t = game.world.get_thing(thing_id, False)
204 raise ArgError('No such Thing.')
205 task_class = game.tasks[task_name]
206 t.task = task_class(t, args)
209 def task_prefixed(command_name, task_prefix, task_command,
210 argtypes_prefix=None):
211 if command_name[:len(task_prefix)] == task_prefix:
212 task_name = command_name[len(task_prefix):]
213 if task_name in self.tasks:
214 f = partial_with_attrs(task_command, task_name, self)
215 task = self.tasks[task_name]
217 f.argtypes = argtypes_prefix + ' ' + task.argtypes
219 f.argtypes = task.argtypes
223 command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
226 command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
227 'int:nonneg int:nonneg ')
230 if command_name in self.commands:
231 f = partial_with_attrs(self.commands[command_name], self)