def test_should_empty_index_after_removing_multiple_added_nodes_in_all_quad_nodes(): index = Index(INDEX_BBOX, max_items=1) index.insert(ITEM1, BBOX1) index.insert(ITEM2, INDEX_BBOX) index.remove(ITEM1, BBOX1) index.remove(ITEM2, INDEX_BBOX) assert len(index) == 0 assert index.intersect(INDEX_BBOX) == []
def test_should_empty_index_after_removing_multiple_added_nodes_in_multiple_horizontal_quad_nodes(): index = Index(INDEX_BBOX, max_items=1) bbox2 = (0, 0, INDEX_BBOX[2], 1) index.insert(ITEM1, BBOX1) index.insert(ITEM2, bbox2) index.remove(ITEM1, BBOX1) index.remove(ITEM2, bbox2) assert len(index) == 0 assert index.intersect(INDEX_BBOX) == []
class TreeStruct: bbox = (-10000, -10000, 10000, 10000) ## # Maintain link from points to rectangle objects? def __init__(self): self.index = Index(bbox=self.bbox, max_items=40, max_depth=15) def addRect(self, rect): if (bboxoutside(rect.bbox, self.bbox)): print('outside') pass # TODO self.index.insert(rect, rect.bbox) def removeRect(self, rect): self.index.remove(rect, rect.bbox) def query(self, rect): candidates = self.index.intersect(rect.bbox) # return candidates # TBD if we want to make the check both ways or are okay with overlaps only being detected on one end return [candidate for candidate in candidates if rect.overlaps(candidate) or candidate.overlaps(rect)]
def test_should_empty_index_after_removing_added_node(): index = Index(INDEX_BBOX) index.insert(ITEM1, BBOX1) index.remove(ITEM1, BBOX1) assert len(index) == 0 assert index.intersect(BBOX1) == []
class Model(EventDispatcher): def __init__(self): self.nx = 0 self.ny = 0 self.num_pieces = 0 self.num_intended_pieces = 0 self.image_path = None self.image_width = 0 self.image_height = 0 self.snap_distance = 0 self.pieces = None self.trays = None self.quadtree = None self.current_max_z_level = 0 self.timer = Timer() self.cheated = False self.start_time = datetime.now() def reset(self, image_path, image_width, image_height, num_intended_pieces, snap_distance_percent=0.5): self.image_path = image_path self.image_width = image_width self.image_height = image_height self.num_intended_pieces = num_intended_pieces self.nx, self.ny, self.num_pieces = create_jigsaw_dimensions( num_intended_pieces, image_width, image_height) self.snap_distance = snap_distance_percent * image_width / self.nx self.cheated = False self.pieces = make_jigsaw_cut(self.image_width, self.image_height, self.nx, self.ny) self.trays = Tray(num_pids=self.num_pieces) self.quadtree = QuadTree(bbox=(-100000, -100000, 100000, 100000)) for piece in tqdm(self.pieces.values(), desc="Building quad-tree"): self.quadtree.insert(piece, piece.bbox) self.current_max_z_level = self.num_pieces self.timer.reset() self.start_time = datetime.now() def to_dict(self): return { 'image_path': self.image_path, 'pieces': self.pieces, 'trays': self.trays, 'current_max_z_level': self.current_max_z_level, 'nx': self.nx, 'ny': self.ny, 'num_pieces': self.num_pieces, 'image_width': self.image_width, 'image_height': self.image_height, 'snap_distance': self.snap_distance, 'elapsed_seconds': self.elapsed_seconds, 'cheated': self.cheated, 'start_time': self.start_time } @classmethod def from_dict(cls, data): model = cls() model.image_path = data['image_path'] model.pieces = data['pieces'] model.trays = data['trays'] model.nx = data['nx'] model.ny = data['ny'] model.num_pieces = data['num_pieces'] model.image_width = data['image_width'] model.image_height = data['image_height'] model.snap_distance = data['snap_distance'] model.current_max_z_level = data['current_max_z_level'] model.timer = Timer(data['elapsed_seconds']) model.cheated = data['cheated'] model.start_time = data['start_time'] model.quadtree = QuadTree(bbox=(-100000, -100000, 100000, 100000)) for piece in tqdm(model.pieces.values(), desc="Building quad-tree"): model.quadtree.insert(piece, piece.bbox) return model def piece_at_coordinate(self, x, y): return self._top_piece_at_location(x, y) def piece_ids_in_rect(self, rect): def to_pid(piece): return piece.pid pieces_in_rect = self.quadtree.intersect(bbox=(rect.left, rect.bottom, rect.right, rect.top)) return list(self.trays.filter_visible(map(to_pid, pieces_in_rect))) def merge_random_pieces(self, n): self.cheated = True n = min(n, len(self.pieces) - 1) for _ in range(n): piece = random.choice(list(self.pieces.values())) neighbour = self.pieces[random.choice(list(piece.neighbours))] self.quadtree.remove(piece, piece.bbox) piece.x = neighbour.x piece.y = neighbour.y self.dispatch_event('on_snap_piece_to_position', piece.pid, piece.x, piece.y, piece.z) self._merge_pieces(piece, neighbour) self.quadtree.insert(piece, piece.bbox) def move_and_snap(self, pid, dx, dy): piece = self.pieces[pid] self.quadtree.remove(piece, piece.bbox) piece.x += dx piece.y += dy for neighbour_pid in piece.neighbours: if not self.trays.is_visible(neighbour_pid): continue neighbour = self.pieces[neighbour_pid] dist = Point.dist(Point(piece.x, piece.y), Point(neighbour.x, neighbour.y)) if dist < self.snap_distance: piece.x = neighbour.x piece.y = neighbour.y neighbour.z = piece.z self.dispatch_event('on_snap_piece_to_position', pid, piece.x, piece.y, piece.z) self._merge_pieces(piece, neighbour) self.quadtree.insert(piece, piece.bbox) def set_piece_position(self, piece, x, y): self.quadtree.remove(piece, piece.bbox) piece.x = x piece.y = y self.dispatch_event('on_snap_piece_to_position', piece.pid, piece.x, piece.y, piece.z) self.quadtree.insert(piece, piece.bbox) def move_pieces(self, pids, dx, dy): if len(pids) == 1: self.move_and_snap(pids[0], dx, dy) else: for pid in pids: piece = self.pieces[pid] self.quadtree.remove(piece, piece.bbox) piece.x += dx piece.y += dy self.quadtree.insert(piece, piece.bbox) def spread_out(self, pids): single_pieces = list( filter(lambda piece: len(piece.members) == 1, map(lambda pid: self.pieces[pid], pids))) if len(single_pieces) < 1: return n = math.ceil(math.sqrt(len(single_pieces))) left = min(map( lambda piece: piece.origin.x + piece.x, single_pieces, )) bottom = min(map(lambda piece: piece.origin.y + piece.y, single_pieces)) for i, piece in enumerate(single_pieces): self.set_piece_position( piece, left + 2 * piece.width * (i % n) - piece.origin.x, bottom + 2 * piece.height * (i // n) - piece.origin.y) def move_pieces_to_top(self, pids): new_z_levels = list( range(self.current_max_z_level, self.current_max_z_level + len(pids))) self.current_max_z_level += len(pids) sorted_pieces = sorted([self.pieces[pid] for pid in pids], key=lambda p: p.z) msg = [] for z, piece in zip(new_z_levels, sorted_pieces): piece.z = z msg.append((z, piece.pid)) self.dispatch_event('on_z_levels_changed', msg) def move_pieces_to_tray(self, tray, pids): self.trays.move_pids_to_tray(tray=tray, pids=pids) if self._tray_is_hidden(tray): self.dispatch_event('on_visibility_changed', tray, False, self.trays.hidden_pieces) def get_hidden_pieces(self): return self.trays.hidden_pieces def get_piece_data(self): data = [] for pid, piece in self.pieces.items(): piece_data = piece.data piece_data['tray'] = self.trays.pid_to_tray[pid] data.append(piece_data) return data def toggle_visibility(self, tray): self.trays.toggle_visibility(tray) self.dispatch_event('on_visibility_changed', tray, self._tray_is_visible(tray), self.trays.hidden_pieces) def toggle_pause(self, is_paused): if is_paused: self.timer.pause() else: self.timer.start() @property def elapsed_seconds(self): return self.timer.elapsed_seconds @property def percent_complete(self): total_moves = self.num_pieces - 1 moves_made = self.num_pieces - len(self.pieces) return 100 * moves_made / total_moves def _tray_is_visible(self, tray): return tray in self.trays.visible_trays def _tray_is_hidden(self, tray): return not self._tray_is_visible(tray) def _merge_pieces(self, p1, p2): # Merges p2 into p1 p1.merge(p2) self.pieces.pop(p2.pid) p2.neighbours.remove(p1.pid) for neighbour_pid in p2.neighbours: neighbour = self.pieces[neighbour_pid] neighbour.neighbours.remove(p2.pid) neighbour.neighbours.add(p1.pid) self.quadtree.remove(p2, p2.bbox) self.trays.merge_pids(p1.pid, p2.pid) self.dispatch_event('on_pieces_merged', p1.pid, p2.pid) self._check_game_over() def _pieces_at_location(self, x, y): for piece in self.quadtree.intersect(bbox=(x, y, x, y)): if self.trays.is_visible(piece.pid) and piece.contains( Point(x, y), self.nx): yield piece def _top_piece_at_location(self, x, y): return max(self._pieces_at_location(x, y), key=(lambda p: p.z), default=None) def _check_game_over(self): if len(self.pieces) == 1: self.timer.pause() self.dispatch_event('on_win', self.elapsed_seconds, self.num_pieces) save_statistics(image_path=self.image_path, num_pieces=self.num_pieces, num_intended_pieces=self.num_intended_pieces, image_width=self.image_width, image_height=self.image_height, snap_distance=self.snap_distance, start_time=self.start_time, piece_rotation=False, cheated=self.cheated, elapsed_seconds=self.elapsed_seconds) def __eq__(self, other): return ( self.current_max_z_level == other.current_max_z_level, self.pieces == other.pieces, self.nx == other.nx, self.ny == other.ny, self.num_pieces == other.num_pieces, self.image_height == other.image_height, self.image_width == other.image_width, self.trays == other.trays, self.snap_distance == other.snap_distance, self.quadtree == other.quadtree, )