Алгоритм поиска решения головоломки

Я пытаюсь сделать игру, где игрок должен найти свой путь от начала и до конца на игровом поле. ![Настольная игра][1]

Как вы видите, эта игровая доска содержит кучу красных круговых препятствий. Для победы в игре игроку необходимо убрать минимальное количество препятствий. Итак, мой вопрос: как программно выяснить минимальное количество препятствий для удаления, чтобы освободить путь? Свободный путь будет считаться пространством между, кругами, не перекрывающимися и не трогательный.

Так что мне действительно нужно минимальное количество кругов для удаления, мне не нужен фактический путь. Существует ли простой способ сделать это?

и в дополнение к пониманию этой игровой доски, круги имеют одинаковый радиус, и он ограничен черными линиями.

также нет необходимости двигаться по прямой.

5 ответов


Это теория графов


alt text


для перевода графика что-то вроде этого может сработать.

сделайте стену (синие линии) между двумя кругами, если они перекрываются. Не забудьте добавить в верхней и нижней границе. Это создает несколько регионов. Это будут все узлы графа.

Далее мы должны найти ребра, стоимость перехода от одного узла к другому. Возьмем два соседних региона (разделяющих стену.) Затем грубой силой или каким-либо умным методом, который вы придумаете, определите стоимость переезда из одного региона в другой. Если вы удалите круг, это означает, что вы удалите все стены, которые идут к этому кругу. Если это делает два региона в один регион, стоимость края равна 1. Если вам нужно удалить два круга (все стены у них есть), чтобы объединить две области, стоимость составляет 2. Так далее.

некоторые края(зеленые) нарисованы. Мы должны идти от начального региона к конечному. Теперь у вас есть ежедневный взвешенный график.

Я думаю, что это может быть улучшенным много, но я оставляю это как упражнение =)

в этом случае минимум равен 3.

предупреждение, картина нарисована вручную, я забыл несколько стен, краев и областей. Только для иллюстрации. alt text


хорошо, поэтому я решил сделать визуализацию этого в pygame.

оказалось много труднее, чем я ожидал.

идея, как и в других предложениях, заключается в использовании Максимальный Расход. Узкое место в потоке от источника к приемнику, где поток наиболее плотный. Если мы разрежем график пополам на этом плотном горлышке бутылки (т. е. на min-cut), то есть минимальное количество кругов. Так происходит maxflow = мин-кат.

вот шаги, которые я взял:

  1. создать мир pygame были я могу случайным образом генерировать круги

  2. Make функция для разработки всех столкновений между кругами:

Это включало сортировку кругов по координате X. Теперь, чтобы найти все коллизии круга[0], я продолжаю двигаться вдоль массива, проверяя коллизии, пока не найду круг, значение x которого больше 2 * радиус больше значение x окружности[0], затем я могу открыть окружность[0] и повторить процесс..

  1. создать узлы источника и приемника, выясните, к каким кругам им нужно подключиться.

шаги 4-5 выполняются в разделе"findflow()создайте новый направленный график из моего неориентированного графика. Поскольку мне нужно было разработать поток через круги (т. е. узлы), а не ребра, мне нужно разделить каждый узел на два узла с ребром между ними.

допустим, у меня есть узел X, подключенный к узлу Y (YX) (в исходном графике).

Я меняю X на Xa и Xb, чтобы Xa подключался к Xb (Xa - >Xb) Также Г изменения (Ya - >Yb).

мне также нужно добавить (Yb - >Xa) и (Xb->Ya), чтобы представить исходное соединение между X и Y.

всем ребрам в неориентированном графе задается capacity=1 (например, вы можете пересечь круг только один раз)

  1. теперь я применяю networkx.ford_fulkerson() алгоритм на моем новом ориентированном графике. Это находит меня flowValue и flowGraph.

flowValue является целое число. Это мин-отрезок (или максимальная подача от источника к раковине). это ответ, который мы искали. Он представляет собой минимальное количество кругов, которые мы должны удалить.

Бонус-Задание:

Я думал.. зачем останавливаться здесь, имея количество кругов для удаления приятно, но я хочу знать точные круги, которые мне нужно удалить. Для этого мне нужно выяснить, где мин-отрезать на самом деле происходит на схема. Мне удалось выяснить, как это сделать, однако моя реализация имеет ошибку где-то, поэтому она иногда выбирает разрез немного неправильно и поэтому получает неправильные круги.

найти мин-вырезать мы будем использовать схема производится в Шаг 6. Идея заключается в том, что узким местом этого графика будет минимальное сокращение. если мы попробуем течь от источника к потоку, мы застрянем в горлышке бутылки, так как все края вокруг бутылочного горлышка будут на максимальной мощности. Поэтому мы просто используем DFS (глубина первый поиск), чтобы течь вниз, насколько мы можем. The DFS разрешено перемещаться только по краям, которые не имеют максимальной емкости в графе потока. (например, их поток равен 0, а не 1). Используя DFS из источника, я отмечал все узлы, которые я мог видеть, сохраняя их в себе.увиденный. Теперь после DFS для всех узлов в seen я проверяю, имеет ли узел максимальное ребро емкости к узлу, который не был замечен в DFS. Все такие узлы находятся на Мин-срезе.

вот изображение одной из симуляций I РАН:

simulation

и с удаленными кругами я заполнил его краской (возможно, вам придется немного увеличить, чтобы увидеть, что между кругами действительно есть зазор):

simulation_with_circles_removed

знания:

скорость в порядке даже в python, работает для 1000 кругов в порядке.

Это было сложнее, чем я думал, и у меня все еще есть ошибка в попытке использовать DFS для поиска исходных кругов. (Если кто-то может помочь найти ошибку, которая будет большой.)

код был элегантный сначала, хотя я продолжал добавлять хаки, чтобы изменить визуализацию и т. д.

рабочий код (кроме небольшой случайной ошибки в DFS):

__author__ = 'Robert'
import pygame
import networkx

class CirclesThing():
    def __init__(self,width,height,number_of_circles):
        self.removecircles = False #display removable circles as green.
        self.width = width
        self.height = height

        self.number_of_circles = number_of_circles
        self.radius = 40

        from random import randint
        self.circles = sorted(set((randint(self.radius,width-self.radius),randint(2*self.radius,height-2*self.radius)) for i in range(self.number_of_circles)))

        self.sink = (self.width/2, self.height-10)
        self.source = (self.width/2, 10)

        self.flowValue,self.flowGraph = self.find_flow()

        self.seen = set()
        self.seen.add(self.source)
        self.dfs(self.flowGraph,self.source)

        self.removable_circles = set()
        for node1 in self.flowGraph:
            if node1 not in self.seen or node1==self.source:
                continue
            for node2 in self.flowGraph[node1]:
                if self.flowGraph[node1][node2]==1:
                    if node2 not in self.seen:
                        self.removable_circles.add(node1[0])


    def find_flow(self):
        "finds the max flow from source to sink and returns the amount, along with the flow graph"
        G = networkx.Graph()
        for node1,node2 in self.get_connections_to_source_sink()+self.intersect_circles():
            G.add_edge(node1,node2,capacity=1)

        G2 = networkx.DiGraph()
        for node in G:
            if node not in (self.source,self.sink):
                G2.add_edge((node,'a'),(node,'b'),capacity=1) #each node is split into two new nodes. We add the edge between the two new nodes flowing from a to b.

        for edge in G.edges_iter():
            if self.source in edge or self.sink in edge:
                continue #add these edges later
            node1,node2 = edge
            G2.add_edge((node1,'b'),(node2,'a'),capacity=1) #if we flow through a circle (from node1a to node1b) we need to be able to flow from node1b to all node1's children
            G2.add_edge((node2,'b'),(node1,'a'),capactiy=1) #similarly for node2..

        for node in G[self.source]:
            G2.add_edge(self.source,(node,'a'))
        for node in G[self.sink]:
            G2.add_edge((node,'b'),self.sink)

        flowValue, flowGraph = networkx.ford_fulkerson(G2,self.source,self.sink)

        return flowValue, flowGraph


    def dfs(self,g,v):
        "depth first search from source of flowGraph. Don't explore any nodes that are at maximum capacity. (this means we can't explore past the min cut!)"
        for node in g[v]:
            if node not in self.seen:
                self.seen.add(node)
                if g[v][node]!=1 or v==self.source:
                    self.dfs(g,node)

    def display(self):
        self.draw_circles()
        self.draw_circles(circle_radius=5, circle_colour=(255,0,0))
        if not self.removecircles:
            lines = self.intersect_circles()
            self.draw_lines(lines)
        self.draw_source_sink()

    def draw_circles(self,circle_radius=None,circle_colour=(0,0,255),circles=None):
        if circle_radius is None:
            circle_radius = self.radius
        if circles is None:
            circles = self.circles

        circle_thickness = 2
        for pos in circles:
            cc = circle_colour if pos not in self.removable_circles else (100,200,0) #change colour of removable circles
            ct = circle_thickness if pos not in self.removable_circles else 4 #thicken removable circles
            if pos not in self.removable_circles or not self.removecircles:
                pygame.draw.circle(screen, cc, pos, circle_radius, ct)

    def intersect_circles(self):
        colliding_circles = []
        for i in range(len(self.circles)-1):
            for j in range(i+1,len(self.circles)):
                x1,y1 = self.circles[i]
                x2,y2 = self.circles[j]
                if x2-x1>2*self.radius+5: #add 5 to make a more obvious gap visually
                    break #can't collide anymore.
                if (x2-x1)**2 + (y2-y1)**2 <= (2*self.radius)**2+5:
                    colliding_circles.append(((x1,y1),(x2,y2)))
        return colliding_circles

    def draw_lines(self,lines,line_colour=(255, 0, 0)):
        for point_pair in lines:
            point1,point2 = point_pair
            try:
                tot = self.flowGraph[(point1,'b')][(point2,'a')] + self.flowGraph[(point2,'b')][(point1,'a')] #hack, does anything flow between the two circles?
            except KeyError:
                tot = 0
            thickness = 1 if tot==0 else 3
            lc = line_colour if tot==0 else (0,90,90)
            pygame.draw.line(screen, lc, point1, point2, thickness)

    def draw_source_sink(self):
        self.draw_circles(circles=(self.sink,self.source),circle_radius=15,circle_colour=(0,255,0))

        bottom_line = ((0,self.height-3*self.radius),(self.width,self.height-3*self.radius))
        top_line = ((0,3*self.radius),(self.width,3*self.radius))

        self.draw_lines([top_line, bottom_line],line_colour=(60,60,60))

        if not self.removecircles:
            self.draw_lines(self.get_connections_to_source_sink(),line_colour=(0,255,0))

    def get_connections_to_source_sink(self):
        connections = []
        for x,y in self.circles:
            if y<4*self.radius:
                connections.append((self.source,(x,y)))
            elif y>height-4*self.radius:
                connections.append((self.sink,(x,y)))
        return connections

    def get_caption(self):
        return "flow %s, circles removes %s" %(self.flowValue,len(self.removable_circles))



time_per_simulation = 5 #5 seconds
width, height = 1400, 600
background_colour = (255,255,255)
screen = pygame.display.set_mode((width, height))

screen.fill(background_colour)
from pygame.locals import USEREVENT
pygame.time.set_timer(USEREVENT+1,time_per_simulation*1000)

simulations = 0
simulations_max = 20

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == USEREVENT+1:
            if simulations % 2 ==0:
                world = CirclesThing(width,height,120) #new world
            else:
                world.removecircles = True #current world without green circles

            screen.fill(background_colour)
            world.display()
            pygame.display.set_caption(world.get_caption())
            pygame.display.flip()

            if simulations>=2*simulations_max:
                running = False
            simulations+=1

            if False:
                pygame.image.save(screen,'sim%s.bmp'%simulations)

один из вариантов-сначала удалить эти круги с наибольшим количеством перекрытий или касаний. После каждого удаления проверьте, является ли его решение, если не продолжить удаление.

var circle;
circle = findMostOverlapCircle();
while(circle != null) {
    circle.remove();
    circle = findMostOverlapCircle();
}