Generating a tower defense maze (longest maze with limited walls) - near-optimal heuristic?
Asked Answered
L

8

50

In a tower defense game, you have an NxM grid with a start, a finish, and a number of walls.

Image1

Enemies take the shortest path from start to finish without passing through any walls (they aren't usually constrained to the grid, but for simplicity's sake let's say they are. In either case, they can't move through diagonal "holes")

Image2

The problem (for this question at least) is to place up to K additional walls to maximize the path the enemies have to take. For example, for K=14

Image3

My intuition tells me this problem is NP-hard if (as I'm hoping to do) we generalize this to include waypoints that must be visited before moving to the finish, and possibly also without waypoints.

But, are there any decent heuristics out there for near-optimal solutions?


[Edit] I have posted a related question here.

Leftwich answered 26/4, 2012 at 17:3 Comment(8)
This vaguely reminds me of using normalized cuts to smoothen segmented areas in images where each pixel is represented as a node in a graph. This is NP-complete, so what you're describing might be too. Anyway, in this case (i.e., image segmentation), approximations can be found based on spectral graph theoretic methods. Just my 2 cents.Polyneuritis
adding another wall at the bottom would make the map unsolvable, isn't that the maximum?Garganey
@KarolyHorvath: Sorry, I assumed most people would take it as a given that you are not allowed to block off the exit.Leftwich
Just a quick note. Your problem is potentially unsolvable if you allow the grid to be an NxM grid, because you could easily block off all paths from Start to Finish using sufficiently large K.Scammony
Are you interested in solutions using graph algorithms? I have used graph algorithms to find the shortest paths on traditional graph-based maps several times. (And, you know, if it's good enough for Google Maps, it should be good enough for you.)Scammony
@theJollySin: Please read my previous comment. And I'm not asking for the shortest path (a very simple problem to solve), I'm asking for the tower-placements that create the longest shortest path.Leftwich
@BlueRaja - If you want to be 100% sure your solution is correct, I believe you will need to find a lot of 'shortest paths'. Implicit in your problem statement is that the 'longest path' you seek is in fact the shortest path around the new walls. Your three-step analysis will include: (1) placing new walls intelligently near the old, (2) finding the shortest path around the new walls, and (3) comparing all the new wall arrangements. Though perhaps you could define some near-100% short-cut guidelines for wall-building that would usually work. I do not know if such rules will be easy to find.Scammony
Remember, whiteboarding-type programming questions are very on topic at Software Engineering.Archaean
P
6

I present a greedy approach and it's maybe close to the optimal (but I couldn't find approximation factor). Idea is simple, we should block the cells which are in critical places of the Maze. These places can help to measure the connectivity of maze. We can consider the vertex connectivity and we find minimum vertex cut which disconnects the start and final: (s,f). After that we remove some critical cells.

To turn it to the graph, take dual of maze. Find minimum (s,f) vertex cut on this graph. Then we examine each vertex in this cut. We remove a vertex its deletion increases the length of all s,f paths or if it is in the minimum length path from s to f. After eliminating a vertex, recursively repeat the above process for k time.

But there is an issue with this, this is when we remove a vertex which cuts any path from s to f. To prevent this we can weight cutting node as high as possible, means first compute minimum (s,f) cut, if cut result is just one node, make it weighted and set a high weight like n^3 to that vertex, now again compute the minimum s,f cut, single cutting vertex in previous calculation doesn't belong to new cut because of waiting.

But if there is just one path between s,f (after some iterations) we can't improve it. In this case we can use normal greedy algorithms like removing node from a one of a shortest path from s to f which doesn't belong to any cut. after that we can deal with minimum vertex cut.

The algorithm running time in each step is:

min-cut + path finding for all nodes in min-cut
O(min cut) + O(n^2)*O(number of nodes in min-cut)

And because number of nodes in min cut can not be greater than O(n^2) in very pessimistic situation the algorithm is O(kn^4), but normally it shouldn't take more than O(kn^3), because normally min-cut algorithm dominates path finding, also normally path finding doesn't takes O(n^2).

I guess the greedy choice is a good start point for simulated annealing type algorithms.


P.S: minimum vertex cut is similar to minimum edge cut, and similar approach like max-flow/min-cut can be applied on minimum vertex cut, just assume each vertex as two vertex, one Vi, one Vo, means input and outputs, also converting undirected graph to directed one is not hard.

Precambrian answered 1/5, 2012 at 22:18 Comment(3)
Hey Saeed. Sorry, I haven't had time to try this out yet. I agree that this will probably give a good starting point for simulated annealing, and would continue to be useful for the more complicated situations I'm actually interested in (multiple checkpoints between start and finish; teleports; etc). I will give this answer the bounty, unless something better comes along in the next hour. I'll let you know how it works out - thanks!Leftwich
Also you may be interested in the related question I just posted hereLeftwich
@BlueRaja-DannyPflughoeft, Nice question :), Seems it's better place, but Also CS.StackExchange is not bad for this.Precambrian
S
5

it can be easily shown (proof let as an exercise to the reader) that it is enough to search for the solution so that every one of the K blockades is put on the current minimum-length route. Note that if there are multiple minimal-length routes then all of them have to be considered. The reason is that if you don't put any of the remaining blockades on the current minimum-length route then it does not change; hence you can put the first available blockade on it immediately during search. This speeds up even a brute-force search.

But there are more optimizations. You can also always decide that you put the next blockade so that it becomes the FIRST blockade on the current minimum-length route, i.e. you work so that if you place the blockade on the 10th square on the route, then you mark the squares 1..9 as "permanently open" until you backtrack. This saves again an exponential number of squares to search for during backtracking search.

You can then apply heuristics to cut down the search space or to reorder it, e.g. first try those blockade placements that increase the length of the current minimum-length route the most. You can then run the backtracking algorithm for a limited amount of real-time and pick the best solution found thus far.

Stitching answered 7/5, 2012 at 6:37 Comment(3)
"You can always decide to put the next blockade so that it becomes the FIRST blockade on the current minimum-length route" - I don't see how that's true. It is possible all K towers need to be placed in the middle of the route (say there is an opening of size K, which would take a long time to walk around).Leftwich
I think it's badly worded. It means that you can organize the search so that whenever you put a blockade on a square of the current minimum route, you commit to not putting blockades on any of the earlier squares on the same route later during search. It can be easily shown that this does not eliminate any solutions from search.Stitching
I completely forgot this was here, and actually rediscovered your algorithm when trying to find a way to search for improvements for exisiting mazes (though this is not terribly useful for actually generating the mazes, as the search-space is WAYYY too large - even for a small maze, the most towers I can check for improvements in under an hour is 3). Thanks!Leftwich
P
4

I believe we can reduce the contained maximum manifold problem to boolean satisifiability and show NP-completeness through any dependency on this subproblem. Because of this, the algorithms spinning_plate provided are reasonable as heuristics, precomputing and machine learning is reasonable, and the trick becomes finding the best heuristic solution if we wish to blunder forward here.

Consider a board like the following:

..S........
#.#..#..###
...........
...........
..........F

This has many of the problems that cause greedy and gate-bound solutions to fail. If we look at that second row:

#.#..#..###

Our logic gates are, in 0-based 2D array ordered as [row][column]:

[1][4], [1][5], [1][6], [1][7], [1][8]

We can re-render this as an equation to satisfy the block:

if ([1][9] AND ([1][10] AND [1][11]) AND ([1][12] AND [1][13]):
    traversal_cost = INFINITY; longest = False # Infinity does not qualify

Excepting infinity as an unsatisfiable case, we backtrack and rerender this as:

if ([1][14] AND ([1][15] AND [1][16]) AND [1][17]:
    traversal_cost = 6; longest = True

And our hidden boolean relationship falls amongst all of these gates. You can also show that geometric proofs can't fractalize recursively, because we can always create a wall that's exactly N-1 width or height long, and this represents a critical part of the solution in all cases (therefore, divide and conquer won't help you).

Furthermore, because perturbations across different rows are significant:

..S........
#.#........
...#..#....
.......#..#
..........F

We can show that, without a complete set of computable geometric identities, the complete search space reduces itself to N-SAT.

By extension, we can also show that this is trivial to verify and non-polynomial to solve as the number of gates approaches infinity. Unsurprisingly, this is why tower defense games remain so fun for humans to play. Obviously, a more rigorous proof is desirable, but this is a skeletal start.

Do note that you can significantly reduce the n term in your n-choose-k relation. Because we can recursively show that each perturbation must lie on the critical path, and because the critical path is always computable in O(V+E) time (with a few optimizations to speed things up for each perturbation), you can significantly reduce your search space at a cost of a breadth-first search for each additional tower added to the board.


Because we may tolerably assume O(n^k) for a deterministic solution, a heuristical approach is reasonable. My advice thus falls somewhere between spinning_plate's answer and Soravux's, with an eye towards machine learning techniques applicable to the problem.

The 0th solution: Use a tolerable but suboptimal AI, in which spinning_plate provided two usable algorithms. Indeed, these approximate how many naive players approach the game, and this should be sufficient for simple play, albeit with a high degree of exploitability.

The 1st-order solution: Use a database. Given the problem formulation, you haven't quite demonstrated the need to compute the optimal solution on the fly. Therefore, if we relax the constraint of approaching a random board with no information, we can simply precompute the optimum for all K tolerable for each board. Obviously, this only works for a small number of boards: with V! potential board states for each configuration, we cannot tolerably precompute all optimums as V becomes very large.

The 2nd-order solution: Use a machine-learning step. Promote each step as you close a gap that results in a very high traversal cost, running until your algorithm converges or no more optimal solution can be found than greedy. A plethora of algorithms are applicable here, so I recommend chasing the classics and the literature for selecting the correct one that works within the constraints of your program.

The best heuristic may be a simple heat map generated by a locally state-aware, recursive depth-first traversal, sorting the results by most to least commonly traversed after the O(V^2) traversal. Proceeding through this output greedily identifies all bottlenecks, and doing so without making pathing impossible is entirely possible (checking this is O(V+E)).

Putting it all together, I'd try an intersection of these approaches, combining the heat map and critical path identities. I'd assume there's enough here to come up with a good, functional geometric proof that satisfies all of the constraints of the problem.

Photocopier answered 1/5, 2012 at 0:3 Comment(5)
Playing with this a bit more, I realized that it's n choose k, where the closure subproblem elevates it to NP-completeness. If you'll pardon the pun, this can be routed around by geometric identities and the observation that at least one of the perturbations must lie on the critical path. As this holds true recursively, ALL perturbations must lie on the critical path! Hm. I think I need to play with this more to see if I can offer a closed-form solution to the problem. For now, we can show that each perturbation must be in the set calculable in O(V+E) from a breadth-first search.Photocopier
I was thinking along those lines (pun) with my solution, although I offer no code of-course :)Boccioni
I don't think spinning-plate's heuristics will work well at all, for the reasons I mentioned in his answer. Could you expand on the heat-map idea some more? I'm afraid I don't understand the suggestion.Leftwich
@BlueRaja-DannyPflughoeft Certainly. The terse idea is to create a global table for each node in the graph, then perform a stack-bound depth-first traversal of nodes from Start to End, incrementing their respective elements in the global table each time you encounter them. Then, sort the table's elements in decreasing order of their number of encounters, greedily selecting off the front to determine simple and complex bottlenecks. This isn't an especially fast approach (O(V^2)), and it can be improved (see my terse proof about recursively finding elements on the critical path).Photocopier
The trick here is each traversal must also maintain its own state. A quick answer update is appropriate to ensure that's expressed clearly.Photocopier
A
3

At the risk of stating the obvious, here's one algorithm

1) Find the shortest path
2) Test blocking everything node on that path and see which one results in the longest path
3) Repeat K times

Naively, this will take O(K*(V+ E log E)^2) but you could with some little work improve 2 by only recalculating partial paths.

As you mention, simply trying to break the path is difficult because if most breaks simply add a length of 1 (or 2), its hard to find the choke points that lead to big gains.

If you take the minimum vertex cut between the start and the end, you will find the choke points for the entire graph. One possible algorithm is this

1) Find the shortest path 
2) Find the min-cut of the whole graph
3) Find the maximal contiguous node set that intersects one point on the path, block those.
4) Wash, rinse, repeat

3) is the big part and why this algorithm may perform badly, too. You could also try

  • the smallest node set that connects with other existing blocks.
  • finding all groupings of contiguous verticies in the vertex cut, testing each of them for the longest path a la the first algorithm

The last one is what might be most promising

If you find a min vertex cut on the whole graph, you're going to find the choke points for the whole graph.

Autoerotism answered 26/4, 2012 at 17:36 Comment(8)
#1 fails in the simple (and extremely common) case that you have a choke-point that's two-spaces wide. Closing off those two spaces would force the enemies to walk all the way around, but closing off just one space would have very little effect. Your second suggestion is interesting but I'm having trouble seeing how it could be effectively applied.Leftwich
@BlueRaja-DannyPflughoeft - Agreed. This is where the min cut part comes in. Im going to edit a bit of my answer to make it more clear, but I don't know without experimenting if any of these will workAutoerotism
If it's still unclear, please tell me what part is confusing so I can try (I'm just fleshing out an answer, mind you) to clarify. My intuition is that finding the groupings of continguous verticies in the max vertex cut is likely to yield a good resultsAutoerotism
I still don't follow your algorithm - if the "maximal contiguous node set that intersects one point on the path" is equal to the min-cut, then we can't cut it, as that would block the start from the finish. In the above example, this would happen after placing only one tower. What do we do then? Note that this issue is guaranteed to occur once we've blocked off all-but-one of the original min-cut.Leftwich
In the case where you identify a single cut point which cannot be removed, we know that node will never be cut and that there is also a path through it. Thus, you would have run the algo again as if the start point was the node adjacent to it.Autoerotism
In the end, you'll have blocked off most of the choke points. If you have extra barriers to place, then you do the zig-zag pattern afterwards, since it doesn't really matter if you it (in the example) in the top left corner or closer to the sinkAutoerotism
I implemented your Option #1 and found it to be near optimal in some cases. github.com/ChrisLundquist/Tower-Defense-Mazer/blob/master/… @BlueRaja-DannyPflughoeft the "two space choke" issue is a non-issue. You recalculate the path and block the second space if it would be the shortest path. You repeat until there is no path, then you undo the last step.Ephraim
@EnabrenTane: It is not a non-issue; it is in fact a proof that this algorithm is non-optimal (your suggestion is also non-optimal, since it fails for the three-block-wide case). I did implement this algorithm, though; but it is unfortunately very rarely near-optimal. For example, try it against the puzzles on pathery.com. Annealing turned out to do a much better job.Leftwich
H
1

Here is a thought. In your grid, group adjacent walls into islands and treat every island as a graph node. Distance between nodes is the minimal number of walls that is needed to connect them (to block the enemy).

In that case you can start maximizing the path length by blocking the most cheap arcs.

Heiney answered 26/4, 2012 at 17:58 Comment(0)
B
1

I have no idea if this would work, because you could make new islands using your points. but it could help work out where to put walls.

I suggest using a modified breadth first search with a K-length priority queue tracking the best K paths between each island.

i would, for every island of connected walls, pretend that it is a light. (a special light that can only send out horizontal and vertical rays of light)

Use ray-tracing to see which other islands the light can hit

say Island1 (i1) hits i2,i3,i4,i5 but doesn't hit i6,i7..

then you would have line(i1,i2), line(i1,i3), line(i1,i4) and line(i1,i5)

Mark the distance of all grid points to be infinity. Set the start point as 0.

Now use breadth first search from the start. Every grid point, mark the distance of that grid point to be the minimum distance of its neighbors.

But.. here is the catch..

every time you get to a grid-point that is on a line() between two islands, Instead of recording the distance as the minimum of its neighbors, you need to make it a priority queue of length K. And record the K shortest paths to that line() from any of the other line()s

This priority queque then stays the same until you get to the next line(), where it aggregates all priority ques going into that point.

Boccioni answered 1/5, 2012 at 1:45 Comment(3)
Hm. This almost sounds like Floyd-Warshall with priority queues instead of relaxation. Do note that the scanline solution can be shown to work if and only if bottlenecks can be recognized. Turning this 180 degrees, a heatmap of each node hit during blind traversal is a good heuristic. I think I like that idea.Photocopier
Thanks mate. I was thinking of Floyd-Warshall at the time. My idea was that Instead of the need to enumerate all possible paths, only enumerate the paths that cross different combinations of lines, and of those, only remember the K best.Boccioni
Nice. That approach definitely has merit. The trick is extending the priority queue for cases that result in making pathing impossible. If each element in K is subject to this, you require K more, and so forth. If not for that constraint, this would work like a charm. :)Photocopier
F
0

You haven't showed the need for this algorithm to be realtime, but I may be wrong about this premice. You could then precalculate the block positions.

If you can do this beforehand and then simply make the AI build the maze rock by rock as if it was a kind of tree, you could use genetic algorithms to ease up your need for heuristics. You would need to load any kind of genetic algorithm framework, start with a population of non-movable blocks (your map) and randomly-placed movable blocks (blocks that the AI would place). Then, you evolve the population by making crossovers and transmutations over movable blocks and then evaluate the individuals by giving more reward to the longest path calculated. You would then simply have to write a resource efficient path-calculator without the need of having heuristics in your code. In your last generation of your evolution, you would take the highest-ranking individual, which would be your solution, thus your desired block pattern for this map.

Genetic algorithms are proven to take you, under ideal situation, to a local maxima (or minima) in reasonable time, which may be impossible to reach with analytic solutions on a sufficiently large data set (ie. big enough map in your situation).

You haven't stated the language in which you are going to develop this algorithm, so I can't propose frameworks that may perfectly suit your needs.

Note that if your map is dynamic, meaning that the map may change over tower defense iterations, you may want to avoid this technique since it may be too intensive to re-evolve an entire new population every wave.

Fosse answered 28/4, 2012 at 14:16 Comment(2)
to effectively block a short road you might need 3-4-5 adjacent cells.. any one of them alone will hardly change the result at all.. because of that, I fear populations containing these elements have not much chance to survive and combine..Garganey
@Karoly: Right, for that reason annealing would probably work better. But I was hoping there's a more intelligent heuristic for this specific problem than the usual ol' "genetic/annealing global optimization," which can be applied to pretty much every problem, but usually only return half-decent results.Leftwich
S
0

I'm not at all an algorithms expert, but looking at the grid makes me wonder if Conway's game of life might somehow be useful for this. With a reasonable initial seed and well-chosen rules about birth and death of towers, you could try many seeds and subsequent generations thereof in a short period of time.

You already have a measure of fitness in the length of the creeps' path, so you could pick the best one accordingly. I don't know how well (if at all) it would approximate the best path, but it would be an interesting thing to use in a solution.

Sharlenesharline answered 3/5, 2012 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.