def test_matrix(): name = 'LA004_tgt.mdl' data = data_files.lightning_problem(name) m = Matrix.parse(data) assert m.R == 20 print(f'Central vertical slice of {name}') print('with a blip in the bottom left corner') print('and a hole in the middle') x = m.R // 2 m0 = Matrix(m) assert m0 == m assert m.num_full == 559 m[Pos(m.R // 2, 1, 1)] = True assert m.num_full == 560 assert m0 != m m[Pos(m.R // 2, 4, 8)] = False assert m.num_full == 559 for y in reversed(range(m.R)): s = [] for z in range(m.R): if m[Pos(x, y, z)]: s.append('*') else: s.append('.') print(' '.join(s)) assert m.count_inside_region(Pos(0, 0, 0), Pos(m.R - 1, m.R - 1, m.R - 1)) == 559
def test_pathfinding(): matrix = [ # z # ----> [ [1, 0, 0], # | [0, 1, 0], # | y [0, 0, 0] ], # v # x = 0 [[0, 0, 0], [0, 0, 0], [1, 1, 1]], # x = 1 [[0, 0, 1], [0, 1, 1], [0, 0, 0]], # x = 2 ] m = Matrix(3) for x, slice in enumerate(matrix): for y, row in enumerate(slice): for z, cell in enumerate(row): m[Pos(x, y, z)] = bool(cell) dst, path = cpp.path_to_nearest_of(m, Pos(0, 0, 1), [Pos(0, 0, 1)]) assert dst == Pos(0, 0, 1) assert path == [] dst, path = cpp.path_to_nearest_of(m, Pos(0, 0, 1), [Pos(0, 2, 1)]) assert dst == Pos(0, 2, 1) assert len(path) == 2 for cmd in path: print(cmd_from_cpp(cmd)) assert cpp.path_to_nearest_of(m, Pos(0, 0, 1), [Pos(100, 0, 0)]) is None
def test_safe_to_change(): matrix = [ # z # ----> [ [1, 0, 0], # | [1, 1, 0], # | y [0, 0, 0] ], # v # x = 0 [[0, 0, 0], [0, 0, 0], [0, 0, 0]], # x = 1 [[0, 0, 0], [0, 0, 0], [0, 0, 0]], # x = 2 ] m = Matrix(3) for x, slice in enumerate(matrix): for y, row in enumerate(slice): for z, cell in enumerate(row): m[Pos(x, y, z)] = bool(cell) m0 = Matrix(m) assert cpp.safe_to_change(m, Pos(0, 0, 1)) assert m == m0 assert cpp.safe_to_change(m, Pos(2, 0, 1)) assert m == m0 assert cpp.safe_to_change(m, Pos(0, 1, 1)) assert m == m0 assert not cpp.safe_to_change(m, Pos(0, 1, 0)) assert m == m0 assert cpp.safe_to_change(m, Pos(0, 2, 1)) assert m == m0 assert not cpp.safe_to_change(m, Pos(2, 2, 2)) assert m == m0
def process_command(state, bot, cmd) -> Tuple[Set[Pos], Callable[..., None]]: '''Checks preconditions and returns (volatile set, effect) pair.''' c = bot.pos if isinstance(cmd, Halt): if c != Pos(0, 0, 0): raise PreconditionError() if len(state.bots) != 1: raise PreconditionError() if state.harmonics != LOW: raise PreconditionError() def effect(): state.bots = [] return {c}, effect elif isinstance(cmd, Wait): def effect(): pass return {c}, effect elif isinstance(cmd, Flip): def effect(): if state.harmonics == HIGH: state.harmonics = LOW elif state.harmonics == LOW: state.harmonics = HIGH else: assert state.harmonics return {c}, effect elif isinstance(cmd, SMove): assert cmd.lld.is_long_linear() c1 = c + cmd.lld if not c1.is_inside_matrix(state.R): raise PreconditionError() vol = set(enum_region_cells(c, c1)) for p in vol: if state[p] != 0: raise PreconditionError() def effect(): bot.pos = c1 state.energy += 2 * cmd.lld.mlen() return vol, effect elif isinstance(cmd, LMove): assert cmd.sld1.is_short_linear() assert cmd.sld2.is_short_linear() c1 = c + cmd.sld1 c2 = c1 + cmd.sld2 if not c1.is_inside_matrix(state.R): raise PreconditionError() if not c2.is_inside_matrix(state.R): raise PreconditionError() vol = set(enum_region_cells(c, c1)) + set(enum_region_cells(c1, c2)) for p in vol: if state[p] != 0: raise PreconditionError() def effect(): bot.pos = c2 state.energy += 2 * (cmd.sld1.mlen() + 2 + cmd.sld2.mlen()) return vol, effect elif isinstance(cmd, Fission): assert cmd.nd.is_near() assert bot.seeds c1 = c + cmd.nd if not c1.is_inside_matrix(state.R): raise PreconditionError() if state[c1] != 0: raise PreconditionError() if len(bot.seeds) < m + 1: raise PreconditionError() bot.seeds.sort() def effect(): new_bot = Bot(bid=bot.seeds[0], pos=c1, seeds=bot.seeds[1:m + 1]) bot.seeds = bot.seeds[m + 1:] state.bots.append(new_bot) state.energy += 24 return {c, c1}, effect elif isinstance(cmd, Fill): assert cmd.nd.is_near() c1 = c + cmd.nd if not c1.is_inside_matrix(state.R): raise PreconditionError() def effect(): if state[c1] == 0: state[c1] = 1 state.energy += 12 elif state[c1] == 1: state.energy += 6 return {c, c1}, effect elif isinstance(cmd, FusionP): raise NotImplementedError() # TODO elif isinstance(cmd, FusionS): raise NotImplementedError() # TODO else: assert False, cmd
def __init__(self, R): self.R = R self.matrix = Model(R) self.harmonics = LOW self.energy = 0 self.bots = [Bot(1, Pos(0, 0, 0), list(range(39)))] # TODO
def enum_region_cells(c1: Pos, c2: Pos): for x in range(min(c1.x, c2.x), max(c1.x, c2.x) + 1): for y in range(min(c1.y, c2.y), max(c1.y, c2.y) + 1): for z in range(min(c1.z, c2.z), max(c1.z, c2.z) + 1): yield Pos(x, y, z)
def test_pack(): p = Pos(1, 2, 3) assert Pos.unpack(10, p.pack(10)) == p
def solve(self, name: str, src_model: Optional[bytes], tgt_model: Optional[bytes]) -> SolverResult: if src_model is not None: src_model = Matrix.parse(src_model) if tgt_model is not None: tgt_model = Matrix.parse(tgt_model) if src_model is None: src_model = Matrix(tgt_model.R) if tgt_model is None: tgt_model = Matrix(src_model.R) logger.info((src_model, tgt_model)) R = src_model.R cur_model = Matrix(src_model) trace = [] bot_pos = Pos(0, 0, 0) logger.info(f'R = {R}') while cur_model != tgt_model: changed = False for nd in cpp.enum_near_diffs(): p = bot_pos + nd if not p.is_inside(R): continue if cur_model[p] == tgt_model[p]: continue if not cpp.safe_to_change(cur_model, p): continue if cur_model[p]: trace.append(cpp.Void(nd)) cur_model[p] = False else: trace.append(cpp.Fill(nd)) cur_model[p] = True changed = True break if changed: continue p = cpp.path_to_nearest_safe_change_point(cur_model, bot_pos, cur_model, tgt_model) if p is None: return SolverResult(Pass(), extra=dict(msg='no reachable targets')) target, path = p for cmd in path: trace.append(cmd) bot_pos += cmd.move_offset() assert bot_pos == target # get back p = cpp.path_to_nearest_of(cur_model, bot_pos, [Pos(0, 0, 0)]) if p is None: return SolverResult(Fail(), extra=dict(msg="done but can't reach exit")) trace.extend(p[1]) trace.append(cpp.Halt()) trace = [cmd_from_cpp(cmd) for cmd in trace] trace = commands.compose_commands(trace) return SolverResult(trace)
def __iter__(self): for x in range(self.pos_min.x, self.pos_max.x + 1): for y in range(self.pos_min.y, self.pos_max.y + 1): for z in range(self.pos_min.z, self.pos_max.z + 1): yield Pos(x, y, z)
def __init__(self, pos1: Pos, pos2: Pos): self.pos_min = pos1.min(pos2) self.pos_max = pos1.max(pos2)