예제 #1
0
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) == []
예제 #2
0
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) == []
예제 #3
0
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)]
예제 #4
0
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) == []
예제 #5
0
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,
        )