return ''.join(map_view_chars).split('\n')
map_lines = map_cells_to_lines(map_cells)
- self.y_cut(map_lines, center[0], size[0])
+ if len(map_lines) % 2 == 0:
+ map_lines = map_lines[1:]
+ else:
+ for i in range(len(map_lines)):
+ map_lines[i] = '0' + map_lines[i]
+ self.y_cut(map_lines, center[1][0], size[0])
map_width = self.size[1] * 2 + 1
- self.x_cut(map_lines, center[1] * 2, size[1], map_width)
+ self.x_cut(map_lines, center[1][1] * 2, size[1], map_width)
return map_lines
on any update, even before we actually receive map data.
"""
super().__init__(*args, **kwargs)
- self.map_ = ClientMap()
+ self.maps = {(0,0): ClientMap()}
self.player_inventory = []
self.player_id = 0
self.pickable_items = []
- def new_map(self, yx):
- self.map_ = ClientMap(yx)
+ def new_map(self, map_pos, size):
+ self.maps[map_pos] = ClientMap(size)
@property
def player(self):
def cmd_VISIBLE_MAP_LINE(game, y, terrain_line):
- game.world.map_.set_line(y, terrain_line)
+ game.world.maps[(0,0)].set_line(y, terrain_line)
cmd_VISIBLE_MAP_LINE.argtypes = 'int:nonneg string'
def get_text_lines(self):
lines = []
- pos_i = self.tui.game.world.map_.\
- get_position_index(self.tui.examiner_position)
- terrain = self.tui.game.world.map_.terrain[pos_i]
+ pos_i = self.tui.game.world.maps[(0,0)].\
+ get_position_index(self.tui.examiner_position[1])
+ terrain = self.tui.game.world.maps[(0,0)].terrain[pos_i]
lines = [terrain]
for t in self.tui.game.world.things_at_pos(self.tui.examiner_position):
lines += [t.type_]
def draw(self):
def annotated_terrain():
- terrain_as_list = list(self.tui.game.world.map_.terrain[:])
+ terrain_as_list = list(self.tui.game.world.maps[(0,0)].terrain[:])
for t in self.tui.game.world.things:
- pos_i = self.tui.game.world.map_.get_position_index(t.position)
+ if t.id_ in self.tui.game.world.player_inventory:
+ continue
+ pos_i = self.tui.game.world.maps[(0,0)].\
+ get_position_index(t.position[1])
symbol = self.tui.game.symbol_for_type(t.type_)
if terrain_as_list[pos_i][0] in {'f', '@', 'm'}:
old_symbol = terrain_as_list[pos_i][0]
else:
terrain_as_list[pos_i] = symbol
if self.tui.examiner_mode:
- pos_i = self.tui.game.world.map_.\
- get_position_index(self.tui.examiner_position)
+ pos_i = self.tui.game.world.maps[(0,0)].\
+ get_position_index(self.tui.examiner_position[1])
terrain_as_list[pos_i] = (terrain_as_list[pos_i][0], '?')
return terrain_as_list
chars_with_attrs += [c]
return chars_with_attrs
- if self.tui.game.world.map_.terrain == '':
+ if self.tui.game.world.maps[(0,0)].terrain == '':
lines = []
pad_y(lines)
self.safe_write(''.join(lines))
center = self.tui.game.world.player.position
if self.tui.examiner_mode:
center = self.tui.examiner_position
- lines = self.tui.game.world.map_.format_to_view(annotated_terrain,
- center, self.size)
+ lines = self.tui.game.world.maps[(0,0)].\
+ format_to_view(annotated_terrain, center, self.size)
pad_or_cut_x(lines)
pad_y(lines)
self.safe_write(lines_to_colored_chars(lines))
self.parser = Parser(self.game)
self.to_update = {}
self.item_pointer = 0
- self.examiner_position = (0, 0)
+ self.examiner_position = ((0,0), (0, 0))
self.examiner_mode = False
self.popup_text = 'Hi bob'
self.to_send = []
def move_examiner(direction):
start_pos = self.examiner_position
- new_examine_pos = self.game.world.map_.move(start_pos, direction)
+ new_examine_pos = self.game.world.maps[(0,0)].\
+ move(start_pos[0], direction)
if new_examine_pos:
- self.examiner_position = new_examine_pos
+ self.examiner_position[1] = new_examine_pos
self.to_update['map'] = True
def switch_to_pick_or_drop(target_widget):
def cmd_MAP(game, yx):
"""Create new map of size yx and only '?' cells."""
- game.world.new_map(yx)
+ game.world.new_map((0,0), yx)
cmd_MAP.argtypes = 'yx_tuple:pos'
def cmd_THING_TYPE(game, i, type_):
game.world.things[t_old_index] = t_new
cmd_THING_TYPE.argtypes = 'int:nonneg string:thingtype'
-def cmd_THING_POS(game, i, yx):
+def cmd_THING_POS(game, i, big_yx, small_yx):
t = game.world.get_thing(i)
- t.position = tuple(yx)
-cmd_THING_POS.argtypes = 'int:nonneg yx_tuple:nonneg'
+ t.position = (big_yx, small_yx)
+cmd_THING_POS.argtypes = 'int:nonneg yx_tuple yx_tuple'
def cmd_THING_INVENTORY(game, id_, ids):
t = game.world.get_thing(id_)
game.io.send('PICKABLE_ITEMS ,')
def cmd_TERRAIN_LINE(game, y, terrain_line):
- game.world.map_.set_line(y, terrain_line)
+ game.world.maps[(0,0)].set_line(y, terrain_line)
cmd_TERRAIN_LINE.argtypes = 'int:nonneg string'
def cmd_PLAYER_ID(game, id_):
save_file_name = game.io.game_file_name + '.save'
with open(save_file_name, 'w') as f:
write(f, 'TURN %s' % game.world.turn)
- write(f, 'MAP ' + stringify_yx(game.world.map_.size))
- for y, line in game.world.map_.lines():
+ write(f, 'MAP ' + stringify_yx(game.world.maps[(0,0)].size))
+ for y, line in game.world.maps[(0,0)].lines():
write(f, 'TERRAIN_LINE %5s %s' % (y, quote(line)))
for thing in game.world.things:
write(f, 'THING_TYPE %s %s' % (thing.id_, thing.type_))
- write(f, 'THING_POS %s %s' % (thing.id_,
- stringify_yx(thing.position)))
+ write(f, 'THING_POS %s %s %s' % (thing.id_,
+ stringify_yx(thing.position[0]),
+ stringify_yx(thing.position[1])))
if hasattr(thing, 'health'):
write(f, 'THING_HEALTH %s %s' % (thing.id_, thing.health))
if len(thing.inventory) > 0:
return t
return None
- def things_at_pos(self, yx):
+ def things_at_pos(self, pos):
things = []
for t in self.things:
- if t.position == yx:
+ if t.position == pos:
things += [t]
return things
super().__init__(*args, **kwargs)
self.player_id = 0
self.player_is_alive = True
+ self.maps = {}
@property
def player(self):
return 0
return self.things[-1].id_ + 1
- def new_map(self, yx):
- self.map_ = self.game.map_type(yx)
+ def new_map(self, map_pos, size):
+ self.maps[map_pos] = self.game.map_type(size)
def proceed_to_next_player_turn(self):
"""Run game world turns until player can decide their next step.
for thing in self.things[player_i+1:]:
thing.proceed()
self.turn += 1
- for pos in self.map_:
- if self.map_[pos] == '.' and \
- len(self.things_at_pos(pos)) == 0 and \
+ for pos in self.maps[(0,0)]:
+ if self.maps[(0,0)][pos] == '.' and \
+ len(self.things_at_pos(((0,0), pos))) == 0 and \
random.random() > 0.999:
- self.add_thing_at('food', pos)
+ self.add_thing_at('food', ((0,0), pos))
for thing in self.things[:player_i]:
thing.proceed()
self.player.proceed(is_AI=False)
def add_thing_at_random(type_):
while True:
- new_pos = (random.randint(0, yx[0] -1),
- random.randint(0, yx[1] - 1))
- if self.map_[new_pos] != '.':
+ new_pos = ((0,0),
+ (random.randint(0, yx[0] -1),
+ random.randint(0, yx[1] -1)))
+ if self.maps[new_pos[0]][new_pos[1]] != '.':
continue
if len(self.things_at_pos(new_pos)) > 0:
continue
self.things = []
random.seed(seed)
self.turn = 0
- self.new_map(yx)
- for pos in self.map_:
+ self.maps = {}
+ self.new_map((0,0), yx)
+ #self.new_map((0,1), yx)
+ #self.new_map((1,1), yx)
+ #self.new_map((1,0), yx)
+ #self.new_map((1,-1), yx)
+ #self.new_map((0,-1), yx)
+ #self.new_map((-1,-1), yx)
+ #self.new_map((-1,0), yx)
+ #self.new_map((-1,1), yx)
+ for pos in self.maps[(0,0)]:
if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
- self.map_[pos] = '#'
+ self.maps[(0,0)][pos] = '#'
continue
- self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
+ self.maps[(0,0)][pos] = random.choice(('.', '.', '.', '.', 'x'))
player = add_thing_at_random('human')
self.player_id = player.id_
def get_string_options(self, string_option_type):
if string_option_type == 'direction':
- return self.world.map_.get_directions()
+ return self.world.maps[(0,0)].get_directions()
elif string_option_type == 'thingtype':
return list(self.thing_types.keys())
return None
"""Send out game state data relevant to clients."""
self.io.send('TURN ' + str(self.world.turn))
- self.io.send('MAP ' + stringify_yx(self.world.map_.size))
+ self.io.send('MAP ' + stringify_yx(visible_map.size))
visible_map = self.world.player.get_visible_map()
for y, line in visible_map.lines():
self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
- visible_things = self.world.player.get_visible_things()
+ visible_things, offset = self.world.player.get_visible_things()
for thing in visible_things:
+ offset_pos = (thing.position[1][0] - offset[0],
+ thing.position[1][1] - offset[1])
self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
- self.io.send('THING_POS %s %s' % (thing.id_,
- stringify_yx(thing.position)))
+ self.io.send('THING_POS %s %s %s' % (thing.id_,
+ stringify_yx(thing.position[0]),
+ stringify_yx(offset_pos)))
if hasattr(thing, 'health'):
self.io.send('THING_HEALTH %s %s' % (thing.id_,
thing.health))
for id_ in self.world.player.inventory:
thing = self.world.get_thing(id_)
self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
- self.io.send('THING_POS %s %s' % (thing.id_,
- stringify_yx(thing.position)))
+ self.io.send('THING_POS %s %s %s' % (thing.id_,
+ stringify_yx(thing.position[0]),
+ stringify_yx(thing.position[1])))
self.io.send('GAME_STATE_COMPLETE')
def proceed(self):
def __init__(self, source_map, yx):
self.source_map = source_map
self.size = self.source_map.size
+ self.fov_radius = (self.size[0] / 2) - 0.5
self.terrain = '?' * self.size_i
self[yx] = '.'
self.shadow_cones = []
# would lose shade growth through hexes at shade borders.)
# TODO: Start circling only in earliest obstacle distance.
+ # TODO: get rid of circle_in_map logic
circle_in_map = True
distance = 1
yx = yx[:]
#print('DEBUG CIRCLE_OUT', yx)
while circle_in_map:
+ if distance > self.fov_radius:
+ break
circle_in_map = False
yx, _ = self.basic_circle_out_move(yx, 'RIGHT')
for dir_i in range(len(self.circle_out_directions)):
args = self.argsparse(argtypes, args_candidates)
return func, args
- def parse_yx_tuple(self, yx_string, range_):
- """Parse yx_string as yx_tuple:nonneg argtype, return result.
+ def parse_yx_tuple(self, yx_string, range_=None):
+ """Parse yx_string as yx_tuple, return result.
+
+ The range_ argument may be 'nonneg' (non-negative, including
+ 0) or 'pos' (positive, excluding 0).
- The range_ argument may be 'nonneg' (non-negative, including 0)
- or 'pos' (positive, excluding 0).
"""
def get_axis_position_from_argument(axis, token):
if len(token) < 3 or token[:2] != axis + ':' or \
- not token[2:].isdigit():
+ not (token[2:].isdigit() or token[2] == '-'):
raise ArgError('Non-int arg for ' + axis + ' position.')
n = int(token[2:])
if n < 1 and range_ == 'pos':
"""Parse into / return args_tokens as args defined by signature.
Expects signature to be a ' '-delimited sequence of any of the strings
- 'int:nonneg', 'yx_tuple:nonneg', 'yx_tuple:pos', 'string',
+ 'int:nonneg', 'yx_tuple', 'yx_tuple:nonneg', 'yx_tuple:pos', 'string',
'seq:int:nonneg', 'string:' + an option type string accepted by
self.game.get_string_options, defining the respective argument types.
"""
args += [self.parse_yx_tuple(arg, 'nonneg')]
elif tmpl == 'yx_tuple:pos':
args += [self.parse_yx_tuple(arg, 'pos')]
+ elif tmpl == 'yx_tuple':
+ args += [self.parse_yx_tuple(arg)]
elif tmpl == 'seq:int:nonneg':
if arg == ',':
args += [[]]
def test_unhandled(self):
p = Parser()
- self.assertEqual(p.parse(''), None)
- self.assertEqual(p.parse(' '), None)
- self.assertEqual(p.parse('x'), None)
+ self.assertEqual(p.parse(''), (None, ()))
+ self.assertEqual(p.parse(' '), (None, ()))
+ #self.assertEqual(p.parse('x'), (None, ()))
def test_argsparse(self):
from functools import partial
assertErr('', ['foo'])
assertErr('string', [])
assertErr('string string', ['foo'])
- self.assertEqual(p.argsparse('string', ('foo',)),
- (['foo'], {}))
+ self.assertEqual(p.argsparse('string', ('foo',)), ['foo'])
self.assertEqual(p.argsparse('string string', ('foo', 'bar')),
- (['foo', 'bar'], {}))
+ ['foo', 'bar'])
assertErr('int:nonneg', [''])
assertErr('int:nonneg', ['x'])
assertErr('int:nonneg', ['-1'])
assertErr('int:nonneg', ['0.1'])
- self.assertEqual(p.argsparse('int:nonneg', ('0',)),
- ([0], {}))
- assertErr('yx_tuple:nonneg', ['x'])
+ self.assertEqual(p.argsparse('int:nonneg', ('0',)), [0])
+ assertErr('yx_tuple', ['x'])
+ assertErr('yx_tuple', ['Y:1.1,X:1'])
+ self.assertEqual(p.argsparse('yx_tuple', ('Y:1,X:-2',)), [(1, -2)])
assertErr('yx_tuple:nonneg', ['Y:0,X:-1'])
assertErr('yx_tuple:nonneg', ['Y:-1,X:0'])
- assertErr('yx_tuple:nonneg', ['Y:1.1,X:1'])
assertErr('yx_tuple:nonneg', ['Y:1,X:1.1'])
self.assertEqual(p.argsparse('yx_tuple:nonneg', ('Y:1,X:2',)),
- ([(1, 2)], {}))
+ [(1, 2)])
assertErr('yx_tuple:pos', ['Y:0,X:1'])
assertErr('yx_tuple:pos', ['Y:1,X:0'])
assertErr('seq:int:nonneg', [''])
- assertErr('seq:int:nonneg', [','])
+ self.assertEqual(p.argsparse('seq:int:nonneg', [',']), [[]])
assertErr('seq:int:nonneg', ['a'])
assertErr('seq:int:nonneg', ['a,1'])
assertErr('seq:int:nonneg', [',1'])
assertErr('seq:int:nonneg', ['1,'])
self.assertEqual(p.argsparse('seq:int:nonneg', ('1,2,3',)),
- ([[1, 2, 3]], {}))
+ [[1, 2, 3]])
for arg in self.args:
if type(arg) == str:
stringed_args += [quote(arg)]
+ elif type(arg) == int:
+ stringed_args += [str(arg)]
else:
raise GameError('stringifying arg type not implemented')
return ' '.join(stringed_args)
argtypes = 'string:direction'
def check(self):
- test_pos = self.thing.world.map_.move(self.thing.position, self.args[0])
- if test_pos is None:
+ test_pos = ((0,0),
+ self.thing.world.maps[(0,0)].
+ move(self.thing.position[1], self.args[0]))
+ if test_pos == ((0,0), None):
raise GameError('would move outside map bounds')
- if self.thing.world.map_[test_pos] != '.':
+ if self.thing.world.maps[test_pos[0]][test_pos[1]] != '.':
raise GameError('%s would move into illegal terrain' % self.thing.id_)
for t in self.thing.world.things_at_pos(test_pos):
if t.blocking:
raise GameError('%s would move into other thing' % self.thing.id_)
def do(self):
- self.thing.position = self.thing.world.map_.move(self.thing.position,
- self.args[0])
+ self.thing.position = (0,0), self.thing.world.maps[(0,0)].\
+ move(self.thing.position[1], self.args[0])
for id_ in self.thing.inventory:
t = self.thing.world.get_thing(id_)
t.position = self.thing.position
class ThingBase:
type_ = '?'
- def __init__(self, world, id_=None, position=(0,0)):
+ def __init__(self, world, id_=None, position=((0,0), (0,0))):
self.world = world
self.position = position
if id_ is None:
super().__init__(*args, **kwargs)
self.set_task('WAIT')
self._last_task_result = None
- self._stencil = None
+ self._radius = 16
+ self.unset_surroundings()
- def move_on_dijkstra_map(self, targets):
- dijkstra_map = type(self.world.map_)(self.world.map_.size)
+ def move_on_dijkstra_map(self, own_pos, targets):
+ visible_map = self.get_visible_map()
+ dijkstra_map = self.world.game.map_type(visible_map.size)
n_max = 256
dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
for target in targets:
dijkstra_map[target] = 0
shrunk = True
- visible_map = self.get_visible_map()
while shrunk:
shrunk = False
for pos in dijkstra_map:
if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
dijkstra_map[pos] = dijkstra_map[yx] + 1
shrunk = True
- neighbors = dijkstra_map.get_neighbors(tuple(self.position))
+ neighbors = dijkstra_map.get_neighbors(own_pos)
n = n_max
target_direction = None
for direction in sorted(neighbors.keys()):
return target_direction
def hunt_player(self):
- visible_things = self.get_visible_things()
+ visible_things, offset = self.get_visible_things()
target = None
for t in visible_things:
if t.type_ == 'human':
- target = t.position
+ target = (t.position[1][0] - offset[0],
+ t.position[1][1] - offset[1])
break
if target is not None:
try:
- target_dir = self.move_on_dijkstra_map([target])
+ offset_self_pos = (self.position[1][0] - offset[0],
+ self.position[1][1] - offset[1])
+ target_dir = self.move_on_dijkstra_map(offset_self_pos,
+ [target])
if target_dir is not None:
self.set_task('MOVE', (target_dir,))
return True
if t.type_ == 'food':
self.set_task('PICKUP', (id_,))
return True
- visible_things = self.get_visible_things()
+ visible_things, offset = self.get_visible_things()
food_targets = []
for t in visible_things:
if t.type_ == 'food':
- food_targets += [t.position]
- target_dir = self.move_on_dijkstra_map(food_targets)
+ food_targets += [(t.position[1][0] - offset[0],
+ t.position[1][1] - offset[1])]
+ offset_self_pos = (self.position[1][0] - offset[0],
+ self.position[1][1] - offset[1])
+ target_dir = self.move_on_dijkstra_map(offset_self_pos,
+ food_targets)
if target_dir:
try:
self.set_task('MOVE', (target_dir,))
None. If is_AI, calls .decide_task to decide a self.task.
"""
- self._stencil = None
+ self.unset_surroundings()
self.health -= 1
if self.health <= 0:
if self is self.world.player:
except GameError:
self.set_task('WAIT')
+ def unset_surroundings(self):
+ self._stencil = None
+ self._surrounding_map = None
+ self._surroundings_offset = None
+
+ def must_fix_indentation(self):
+ return self._radius % 2 != self.position[1][0] % 2
+
+ def get_surroundings_offset(self):
+ if self._surroundings_offset is not None:
+ return self._surroundings_offset
+ add_line = self.must_fix_indentation()
+ offset_y = self.position[1][0] - self._radius - int(add_line)
+ offset_x = self.position[1][1] - self._radius
+ self._surroundings_offset = (offset_y, offset_x)
+ return self._surroundings_offset
+
+ def get_surrounding_map(self):
+ if self._surrounding_map is not None:
+ return self._surrounding_map
+ offset = self.get_surroundings_offset()
+ add_line = self.must_fix_indentation()
+ self._surrounding_map = self.world.game.\
+ map_type(size=(self._radius*2+1+int(add_line),
+ self._radius*2+1))
+ for pos in self._surrounding_map:
+ offset_pos = (pos[0] + offset[0], pos[1] + offset[1])
+ if offset_pos[0] >= 0 and \
+ offset_pos[0] < self.world.maps[(0,0)].size[0] and \
+ offset_pos[1] >= 0 and \
+ offset_pos[1] < self.world.maps[(0,0)].size[1]:
+ self._surrounding_map[pos] = self.world.maps[(0,0)][offset_pos]
+ return self._surrounding_map
+
def get_stencil(self):
if self._stencil is not None:
return self._stencil
- self._stencil = self.world.map_.get_fov_map(self.position)
+ m = self.get_surrounding_map()
+ offset = self.get_surroundings_offset()
+ fov_center = (self.position[1][0] - offset[0],
+ self.position[1][1] - offset[1])
+ self._stencil = m.get_fov_map(fov_center)
return self._stencil
def get_visible_map(self):
stencil = self.get_stencil()
- m = self.world.map_.new_from_shape(' ')
+ m = self.get_surrounding_map().new_from_shape(' ')
for pos in m:
if stencil[pos] == '.':
- m[pos] = self.world.map_[pos]
+ m[pos] = self._surrounding_map[pos]
return m
def get_visible_things(self):
stencil = self.get_stencil()
+ offset = self.get_surroundings_offset()
visible_things = []
for thing in self.world.things:
- if (not thing.in_inventory) and stencil[thing.position] == '.':
+ if abs(thing.position[1][0] - self.position[1][0]) > self._radius or\
+ abs(thing.position[1][1] - self.position[1][1]) > self._radius:
+ continue
+ offset_pos = (thing.position[1][0] - offset[0],
+ thing.position[1][1] - offset[1])
+ if (not thing.in_inventory) and stencil[offset_pos] == '.':
visible_things += [thing]
- return visible_things
+ return visible_things, offset
def get_pickable_items(self):
pickable_ids = []
- for t in [t for t in self.get_visible_things() if
+ visible_things, _ = self.get_visible_things()
+ for t in [t for t in visible_things if
isinstance(t, ThingItem) and
(t.position == self.position or
- t.position in
- self.world.map_.get_neighbors(self.position).values())]:
+ t.position[1] in
+ self.world.maps[(0,0)].get_neighbors(self.position[1]).values())]:
pickable_ids += [t.id_]
return pickable_ids