def bounding_box_region( model, fx: Optional[int] = None, fy: Optional[int] = None, fz: Optional[int] = None) -> Tuple[Optional[Pos], Optional[Pos]]: def rangify(R, fv=None): if fv is None: fv = range(R) else: fv = [fv] return fv fx = rangify(model.R, fx) fy = rangify(model.R, fy) fz = rangify(model.R, fz) pos0 = pos1 = None for x in fx: for y in fy: for z in fz: if model[Pos(x, y, z)]: if pos0 is None: pos0 = pos1 = Pos(x, y, z) filled_cell_visited = True else: pos0 = Pos(min(pos0.x, x), min(pos0.y, y), min(pos0.z, z)) pos1 = Pos(max(pos1.x, x), max(pos1.y, y), max(pos1.z, z)) return (pos0, pos1)
def filling_func(x, y, z, zup, bbox): bbox_min, bbox_max = bbox lookahead = (0 if z == bbox_max.z else int(reassembly))\ if zup else (0 if z == bbox_min.z else -int(reassembly)) lookbehind = (0 if z == bbox_min.z else -int(reassembly))\ if zup else (0 if z == bbox_max.z else int(reassembly)) if reassembly and model_src[Pos(x, y - offset, z + lookahead)]: if lookahead != 0: commands.append(Void(Diff(0, -offset, lookahead))) if x != bbox_min.x: action = choose_action(model_src, model_tgt, Pos(x - 1, y - offset, z)) if action is not None: commands.append(action(Diff(-1, -offset, 0))) if not reassembly or lookbehind != 0: action = choose_action(model_src, model_tgt, Pos(x, y - offset, z + lookbehind), lookbehind != 0) if action is not None: commands.append(action(Diff(0, -offset, lookbehind))) if x != bbox_max.x: action = choose_action(model_src, model_tgt, Pos(x + 1, y - offset, z)) if action is not None: commands.append(action(Diff(1, -offset, 0)))
def filled_voxels(self) -> Set[Pos]: filled = set() for x in range(self.R): for y in range(self.R): for z in range(self.R): if self[Pos(x, y, z)]: filled.add(Pos(x, y, z)) return filled
def bounding_box(self) -> Optional[Region]: end = Pos(self.R - 1, self.R - 1, self.R - 1) box = None for pos in Region(Pos(0, 0, 0), end): if self[pos]: if not box: box = Region(pos, pos) else: box.expand(pos) return box
def merge_bounding_boxes(b0, b1) -> Tuple[Pos, Pos]: bmin0, bmax0 = b0 bmin1, bmax1 = b1 if bmin0 is None: return b1 if bmin1 is None: return b0 new_min = Pos(min(bmin0.x, bmin1.x), min(bmin0.y, bmin1.y), min(bmin0.z, bmin1.z)) new_max = Pos(max(bmax0.x, bmax1.x), max(bmax0.y, bmax1.y), max(bmax0.z, bmax1.z)) return new_min, new_max
def __init__(self, source: Model, target: Model): assert source.R == target.R self.R = source.R self._data = [ Cell.FULL if source._data[i] else Cell.EMPTY for i in range(self.R**3) ] self.target = target self.bots = [Bot(1, Pos(0, 0, 0), list(range(2, 41)))] self._set(Pos(0, 0, 0), Cell.BOT) self.trace = [] self.antigrav = False
def print_strip_below(model, i, lbound, z0, rbound, depth, last_layer): prog = single() for z in range(z0, z0 + depth): if model[Pos(lbound,i,z)]: prog += single(Fill(Diff(0,-1,0))) for x in range(lbound + 1, rbound): prog += single(SMove(Diff(1,0,0))) if model[Pos(x,i,z)]: prog += single(Fill(Diff(0,-1,0))) prog += move_x(-1 * (rbound - lbound - 1)) prog += single(SMove(Diff(0,0,1))) # TODO: optimise this part, make an L move prog += move_z(-1 * (depth + last_layer * z0)) return prog
def solve_gen(m: 'Model'): yield Cmd.Flip() pos = None for x in snake_fill_gen(m, Pos(0, 0, 0), Pos(m.R - 1, m.R - 1, m.R - 1)): if type(x) == Pos: pos = x else: yield x for x in return_home_gen(pos, Pos(0, 0, 0)): yield x yield Cmd.Flip() yield Cmd.Halt()
def single_bot_trace(model, model_height, plots): trace = [] trace.append(Flip()) empty = True current_pos = Pos(0, 0, 0) for plot in plots: plot_pos, plot_height = plot trace.extend(move_in_empty_space(plot_pos - current_pos)) current_pos = plot_pos plot_trace, current_pos = fill_plot(model, model_height, plot) trace.extend(plot_trace) trace.append(Flip()) trace.extend(move_in_empty_space(Pos(0, 0, 0) - current_pos)) trace.append(Halt()) return trace
def print_strip_below(model, i, lbound, rbound, last_layer): moves = [] for z in range(1, model.R - 1): if model[Pos(lbound, i, z)]: moves.append(Fill(Diff(0, -1, 0))) logging.debug('xyz = %d %d %d', lbound, i, z) for x in range(lbound + 1, rbound): logging.debug('xyz = %d %d %d', x, i, z) moves.append(SMove(Diff(1, 0, 0))) if model[Pos(x, i, z)]: moves.append(Fill(Diff(0, -1, 0))) moves.extend(move_x(-1 * (rbound - lbound - 1))) moves.append(SMove(Diff( 0, 0, 1))) # TODO: optimise this part, make an L move moves.extend(move_z(-1 * (model.R - 2 + last_layer))) return moves
def default_disassembly(model_src, model_tgt=None) -> List[Command]: finish, start = bounding_box(model_src) start = Pos(finish.x, start.y, finish.z) return apply_default_strategy(model_src, Model(model_src.R), (3, -1, 1), start, False)
def iterate_region(m, a: Pos, b: Pos): def rg(t1, t2): return range(min(t1, t2), max(t1, t2) + 1) for x in rg(a.x, b.x): for z in rg(a.z, b.z): for y in rg(a.y, b.y): yield (m[Pos(x, y, z)], (x, y, z))
def spawn_down(model, x, y, z): prog = single() if model[Pos(x,y-1,z)]: prog += single(Void(Diff(0,-1,0))) prog += +single(Fission(Diff(0,-1,0), 0)) return prog
def get_plot_height(model, plot_pos): x_z = [(plot_pos.x + dx, plot_pos.z + dz) \ for (dx, dz) in dx_dz_3x3(plot_pos.x, plot_pos.z, model.R)] for y in range(model.R - 2, -1, -1): for x, z in x_z: if model[Pos(x, y, z)]: return y + 1 return 0
def default_reassembly(model_src, model_tgt) -> List[Command]: finish, start = merge_bounding_boxes(bounding_box(model_src), bounding_box(model_tgt)) start = Pos(finish.x, start.y, finish.z) return apply_default_strategy(model_src, model_tgt, (3, -1, 1), start, True)
def drill_down(model, x, y, z, depth) -> GroupProgram: prog = single() while depth > 0: if model[Pos(x, y - 1, z)]: prog += single(Void(Diff(0, -1, 0))) step = 1 for i in range(2, depth + 1): if model[Pos(x, y - i, z)]: break else: step += 1 prog += move_y(-step) y -= step depth -= step return prog
def make_layer_pass(x, y, z, zup, layer_bounding_box, behaviour_func): bbox_min, bbox_max = layer_bounding_box while (x >= bbox_min.x and not xup) or (x <= bbox_max.x and xup): while (z >= bbox_min.z and not zup) or (z <= bbox_max.z and zup): behaviour_func(x, y, z, zup, layer_bounding_box) if (not zup and z == bbox_min.z) or (z == bbox_max.z and zup): break dz = z_speed if zup else -z_speed commands.append(SMove(Diff(0, 0, dz))) z += dz if (not xup and x == bbox_min.x) or (xup and x == bbox_max.x): break dx = x_speed if xup else -x_speed if not is_inside_region(Pos(x + dx, y - offset, z), bbox_min, bbox_max): bbox = bbox_max if not xup: bbox = bbox_min dx = bbox.x - x if reassembly: off = 1 if zup else -1 commands.append(SMove(Diff(0, 0, off))) if model_tgt[Pos(x, y, z)]: commands.append(Fill(Diff(0, 0, -off))) commands.append(SMove(Diff(dx, 0, 0))) x += dx if model_src[Pos(x, y, z)]: commands.append(Void(Diff(0, 0, -off))) commands.append(SMove(Diff(0, 0, -off))) else: commands.append(SMove(Diff(dx, 0, 0))) x += dx zup = not zup return x, y, z, zup
def create_plots(model): pos0, pos1 = bounding_box_footprint(model) plots = [] for x in range(pos0.x + 1, min(pos1.x + 2, model.R), 3): for z in range(pos0.z + 1, min(pos1.z + 2, model.R), 3): plot_pos = Pos(x, 0, z) plot_height = get_plot_height(model, plot_pos) if plot_height > 0: plots.append((plot_pos, plot_height)) return plots
def snake_fill_gen(m: 'Model', a: Pos, b: Pos): prevPos = Pos(a.x, b.y, a.z) nextPos = Pos(a.x, b.y, a.z) # The main idea is to skip path points where model has no voxels below it. # And then go to the next "job" using long lenear move. We always above our # buildings so we will never collide with anything. # for diff in append([Diff(0, 0, 0)], snake_path_gen(a, b)): for diff in snake_path_gen(a, b): # if diff.mlen() > 0: # yield SMove(diff) nextPos = nextPos + diff fillBelow = False for d in [Diff(0, -1, 0), Diff(0, -1, 1), Diff(0, -1, -1)]: if inside_region((nextPos + d), a, b) and m[nextPos + d]: fillBelow = True if fillBelow: dx = nextPos.x - prevPos.x dy = nextPos.y - prevPos.y dz = nextPos.z - prevPos.z # # # y first for x in split_linear_move(Diff(0, dy, 0)): yield x for x in split_linear_move(Diff(0, 0, dz)): yield x for x in split_linear_move(Diff(dx, 0, 0)): yield x for d in [Diff(0, -1, 0), Diff(0, -1, 1), Diff(0, -1, -1)]: if inside_region((nextPos + d), a, b) and m[nextPos + d]: yield Cmd.Void(d) m[nextPos + d] = False prevPos = nextPos yield prevPos
def partition_space(a, b, x_cnt, z_cnt): x_step = (b.x - a.x + 1) / x_cnt z_step = (b.z - a.z + 1) / z_cnt res = [] for z in range(z_cnt): row = [] z1 = round(a.z + z_step * z) if z != z_cnt - 1: z2 = round(a.z + z_step * (z + 1)) - 1 else: z2 = b.z for x in range(x_cnt): x1 = round(a.x + x_step * x) if x != x_cnt - 1: x2 = round(a.x + x_step * (x + 1)) - 1 else: x2 = b.x row.append((Pos(x1, 0, z1), Pos(x2, b.y + 1, z2))) res.append(row) return res
def grounded_voxels(self) -> Set[Pos]: visited = set() def visit(pos): if pos in visited: return if self[pos] == 0: return visited.add(pos) for p in pos.enum_adjacent(self.R): visit(p) for x in range(self.R): for z in range(self.R): visit(Pos(x, 0, z)) return visited
def main(): from production import data_files name = 'LA004_tgt.mdl' data = data_files.lightning_problem(name) m = Model.parse(data) print(f'Central vertical slice of {name}:') x = m.R // 2 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)) print('R =', m.R)
def set_cpp_state(): m = Model(5) m[Pos(2, 0, 2)] = True m[Pos(2, 1, 2)] = True m[Pos(2, 2, 2)] = True m[Pos(2, 3, 2)] = True s = State(5) s.matrix = m s.harmonics = LOW s.energy = 0 s.bots = [ Bot(bid=1, pos=Pos(0, 0, 1), seeds=[]), Bot(bid=2, pos=Pos(0, 0, 2), seeds=list(range(4, 40))), Bot(bid=40, pos=Pos(1, 0, 0), seeds=[]) ] return state_to_cpp(s)
def floor(model): for x in range(model.R): for z in range(model.R): yield Pos(x, 0, z)
def enum_voxels(self): for x in range(self.R): for y in range(self.R): for z in range(self.R): yield Pos(x, y, z)
def __setitem__(self, pos: Pos, value: bool): R = self.R assert pos.is_inside_matrix(R) self._data[pos.x * R * R + pos.y * R + pos.z] = value
def __getitem__(self, pos: Pos) -> bool: R = self.R assert pos.is_inside_matrix(R), pos return self._data[pos.x * R * R + pos.y * R + pos.z]
def projection_front(m): return [[any([m[Pos(x, y, z)] for z in range(m.R)]) for y in range(m.R)] for x in range(m.R)]
def bounding_box_footprint(model): pos0, pos1 = bounding_box_region(model) pos0 = Pos(pos0.x, 0, pos0.z) pos1 = Pos(pos1.x, 0, pos1.z) return (pos0, pos1)
def tick(self): newbots = [] commands = [] for bot in self.bots: command = bot.command commands.append(command) if isinstance(command, Halt): assert len(self.bots) == 1 assert self.bots[0].pos == Pos(0, 0, 0) self._set(Pos(0, 0, 0), Cell.EMPTY) elif isinstance(command, Wait): newbots.append(bot) elif isinstance(command, Flip): self.antigrav = not self.antigrav newbots.append(bot) elif isinstance(command, SMove): assert command.lld.is_long_linear() self._set(bot.pos, Cell.EMPTY) bot.pos += command.lld self._set(bot.pos, Cell.BOT) newbots.append(bot) elif isinstance(command, LMove): assert command.sld1.is_short_linear() assert command.sld2.is_short_linear() self._set(bot.pos, Cell.EMPTY) bot += command.sld1 assert bot.pos.is_inside_matrix(self.R) bot += command.sld1 self._set(bot.pos, Cell.BOT) newbots.append(bot) elif isinstance(command, Fission): newbot = Bot(bot.seeds[0], bot.pos + command.nd, bot.seeds[1:command.m + 1]) self._set(newbot.pos, Cell.BOT) bot.seeds = bot.seeds[command.m + 1:] newbots.append(bot) newbots.append(newbot) elif isinstance(command, Fill): assert command.nd.is_near() self._set(bot.pos + command.nd, Cell.FULL) newbots.append(bot) elif isinstance(command, Void): assert command.nd.is_near() self._set(bot.pos + command.nd, Cell.EMPTY) newbots.append(bot) elif isinstance(command, FusionP): assert self[(bot.pos + command.nd)] == Cell.BOT self._set(bot.pos + command.nd, Cell.EMPTY) otherbot = self.bot_at(bot.pos + command.nd) assert otherbot bot.seeds = sorted(bot.seeds + otherbot.seeds + [otherbot.bid]) newbots.append(bot) elif isinstance(command, FusionS): assert self[(bot.pos + command.nd)] == Cell.BOT elif isinstance(command, GFill): assert command.nd.is_near() assert command.fd.is_far() for pos in Region(bot.pos + command.nd, bot.pos + command.nd + command.fd): self._set(pos, Cell.FULL) newbots.append(bot) elif isinstance(command, GVoid): assert command.nd.is_near() assert command.fd.is_far() for pos in Region(bot.pos + command.nd, bot.pos + command.nd + command.fd): self._set(pos, Cell.EMPTY) newbots.append(bot) self.bots = newbots self.bots.sort(key=lambda bot: bot.bid) self.trace.append(commands) for bot in self.bots: bot.command = Wait()