I'm working on an adventure game in Python using Pygame. My main problem is how I am going to define the boundaries of the room and make the main character walk aroud without hitting a boundary every time. Sadly, I have never studied algorithms so I have no clue on how to calculate a path. I know this question is quite general and hard to answer but a point in the right direction would be very appreciated. Thanks!
There are two easy ways of defining your boundaries which are appropriate for such a game.
The simpler method is to divide your area into a grid, and use a 2D array to keep track of which squares in the grid are passable. Usually, this array stores your map information too, so in each position, there is a number that indicates whether that square contains grass or wall or road or mountain etc. (and therefore what picture to display). To give you a rough picture:
######
#.# #
# ## #
# #
######
A more complex method which is necessary if you want a sort of "maze" look, with thin walls, is to use a 2D array that indicates whether there is a vertical wall in between grid squares, and also whether there is a horizontal wall between grid squares. A rough picture (it looks a stretched in ASCII but hopefully you'll get the point):
- - - -
| | |
- -
| |
- - - -
The next thing to decide is what directions your character may move in (up/down/left/right is easiest, but diagonals are not too much harder). Then the program basically has to "mentally" explore the area, starting from your current position, hoping to come across the destination.
A simple search that is easy to implement for up/down/left/right and will find you the shortest path, if there is one, is called Breadth-First search. Here is some pseudocode:
queue = new Queue #just a simple first-in-first-out
queue.push(startNode)
while not queue.empty():
exploreNode = queue.pop()
if isWalkable(exploreNode): #this doesn't work if you use
#"thin walls". The check must go
#where the pushes are instead
if isTarget(exploreNode):
#success!!!
else:
#push all neighbours
queue.push( exploreNode.up )
queue.push( exploreNode.down )
queue.push( exploreNode.left )
queue.push( exploreNode.right )
This algorithm is slow for large maps, but it will get you used to some graph-search and pathfinding concepts. Once you've verified that it works properly, you can try replacing it with A* or something similar, which should give the same results in less time!
A* and many other searching algorithms use a priority queue instead of a FIFO queue. This lets them consider "more likely" paths first, but get around to the roundabout paths if it turns out that the more direct paths are blocked.
I recommend you read up on the A* search algorithm as it is commonly used in games for pathing problems.
If this game is two dimensional (or 2.5) I suggest you use a tile system as checking for collisions will be easier. Theres lots of information online that can get you started with these.
Sadly, I have never studied algorithms so I have no clue on how to calculate a path.
Before you start writing games, you should educate yourself on those. This takes a little more effort at the beginning, but will save you much time later.
I am not familiar with pygame, but many applications commonly use bounding volumes to define the edge of some region. The idea is that as your character walks, you will check if the characters volume intersects with the volume of a wall. You can then either adjust the velocity or stop your character from moving. Use differing shapes to get a smooth wall so that your character doesn't get stuck on the pointy edges.
These concepts can be used for any application which requires quick edge and bounds detection.
Related
What is the best way to check collision of huge number of circles?
It's very easy to detect collision between two circles, but if we check every combination then it is O(n2) which definitely not an optimal solution.
We can assume that circle object has following properties:
Coordinates
Radius
Velocity
Direction
Velocity is constant, but direction can change.
I've come up with two solutions, but maybe there are some better solutions.
Solution 1
Divide whole space into overlapping squares and check for collision only with circles that are in the same square. Squares need to overlap so there won't be a problem when a circle moves from one square to another.
Solution 2
At the beginning distances between every pair of circles need to be calculated.
If the distance is small then these pair is stored in some list, and we need to check for collision in every update.
If the distance is big then we store after which update there can be a collision (it can be calculated because we know the distance and velocitites). It needs to be stored in some kind of priority queue. After previously calculated number of updates distance needs to be checked again and then we do the same procedure - put it on the list or again in the priority queue.
Answers to Mark Byers questions
Is it for a game?
It's for simulation, but it can be treated also as a game
Do you want to recalculate the new position every n milliseconds, and also check for collisions at this time?
Yes, time between update is constant.
Do you want to find the time at which the first/every collision occurs?
No, I want to find every collision and do 'something' when it occures.
How important is accuracy?
It depends on what do you mean by accuracy. I need to detect all collisions.
Is it a big problem if very small fast moving circles can pass through each other occasionally?
It can be assumed that speed is so small that it won't happen.
There are "spatial index" data-structures for storing your circles for quick comparison later; Quadtree, r-tree and kd-tree are examples.
Solution 1 seems to be a spatial index, and solution 2 would benefit from a spatial index every time you recalculate your pairs.
To complicate matters, your objects are moving - they have velocity.
It is normal to use spatial indexes for objects in games and simulations, but mostly for stationary objects, and typically objects that don't react to a collision by moving.
It is normal in games and such that you compute everything at set time intervals (discrete), so it might be that two objects pass through each other but you fail to notice because they moved so fast. Many games actually don't even evaluate collisions in strict chronological order. They have a spatial index for stationary objects e.g. walls, and lists for all the moving objects that they check exhaustively (although with relaxed discrete checks as I outlined).
Accurate continuous collision detection and where the objects react to collisions in simulations is usually much more demanding.
The pairs approach you outlined sounds promising. You might keep the pairs sorted by next collision, and reinsert them when they have collided in the appropriate new positions. You only have to sort the new generated collision list (O(n lg n)) for the two objects and then to merge two lists (the new collisions for each object, and the existing list of collisions; inserting the new collisions, removing those stale collisions that listed the two objects that collided) which is O(n).
Another solution to this is to adapt your spatial index to store the objects not strictly in one sector but in each that it has passed through since the last calculation, and do things discretely. This means storing fast moving objects in your spatial structure, and you'd need to optimise it for this case.
Remember that linked lists or lists of pointers are very bad for caching on modern processors. I'd advocate that you store copies of your circles - their important properties for collision detection at any rate - in an array (sequential memory) in each sector of any spatial index, or in the pairs you outlined above.
As Mark says in the comments, it could be quite simple to parallelise the calculations.
I assume you are doing simple hard-sphere molecular dynamic simulation, right? I came accros the same problem many times in Monte Carlo and molecular dynamic simulations. Both of your solutions are very often mentioned in literature about simulations. Personaly I prefer solution 1, but slightly modified.
Solution 1
Divide your space into rectangular cells that don't overlap. So when you check one circle for collision you look for all circles inside a cell that your first circle is, and look X cells in each direction around. I've tried many values of X and found that X=1 is the fastest solution. So you have to divide space into cells size in each direction equal to:
Divisor = SimulationBoxSize / MaximumCircleDiameter;
CellSize = SimulationBoxSize / Divisor;
Divisor should be bigger than 3, otherwise it will cause errors (if it is too small, you should enlarge your simulation box).
Then your algorithm will look like this:
Put all circles inside the box
Create cell structure and store indexes or pointers to circles inside a cell (on array or on a list)
Make a step in time (move everything) and update circles positions inside on cells
Look around every circle for collision. You should check one cell around in every direction
If there is a collision - do something
Go to 3.
If you will write it correctly then you would have something about O(N) complexity, because maximum number of circles inside 9 cells (in 2D) or 27 cells (in 3D) is constant for any total number of circles.
Solution 2
Ususaly this is done like this:
For each circle create a list of circles that are in distance R < R_max, calculate time after which we should update lists (something about T_update = R_max / V_max; where V_max is maximum current velocity)
Make a step in time
Check distance of each circle with circles on its list
If there is a collision - do something
If current time is bigger then T_update, go to 1.
Else go to 2.
This solution with lists is very often improved by adding another list with R_max_2 > R_max and with its own T_2 expiration time. In this solution this second list is used to update the first list. Of course after T_2 you have to update all lists which is O(N^2). Also be carefull with this T and T_2 times, because if collision can change velocity then those times would change. Also if you introduce some foreces to your system, then it will also cause velocity change.
Solution 1+2
You can use lists for collision detection and cells for updating lists. In one book it was written that this is the best solution, but I think that if you create small cells (like in my example) then solution 1 is better. But it is my opinion.
Other stuff
You can also do other things to improve speed of simulation:
When you calculate distance r = sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) + ...) you don't have to do square root operation. You can compare r^2 to some value - it's ok. Also you don't have to do all (x1-x2)*(x1-x2) operations (I mean, for all dimentions), because if x*x is bigger than some r_collision^2 then all other y*y and so on, summed up, would be bigger.
Molecular dynamics method is very easy to parallelise. You can do it with threads or even on GPU. You can calculate each distance in different thread. On GPU you can easly create thousends of threads almost costless.
For hard-spheres there is also effective algorithm that doesn't do steps in time, but instead it looks for nearest collision in time and jumps to this time and updates all positions. It can be good for not dense systems where collisions are not very probable.
one possible technique is to use the Delaunay triangulation on the center of your circles.
consider the center of each circle and apply the delaunay triangulation. this will tesselate your surface into triangles. this allows you to build a graph where each node stores the center of a triangle, and each edge connects to the center of a neighbour circle. the tesselation operated above will limit the number of neighbours to a reasonable value (6 neighbours on average)
now, when a circle moves, you have a limited set of circles to consider for collision. you then have to apply the tesselation again to the set of circles which are impacted by the move, but this operation involves only a very small subset of circles (the neighbours of the moving circle, and some neighbours of the neighbours)
the critical part is the first tesselation, which will take some time to perform, later tesselations are not a problem. and of course you need an efficient implementation of a graph in term of time and space...
Sub-divide your space up into regions and maintain a list of which circles are centred in each region.
Even if you use a very simple scheme, such as placing all the circles in a list, sorted by centre.x, then you can speed things up massively. To test a given circle, you only need to test it against the circles on either side of it in the list, going out until you reach one that has an x coordinate more than radius away.
You could make a 2D version of a "sphere tree" which is a special (and really easy to implement) case of the "spatial index" that Will suggested. The idea is to "combine" circles into a "containing" circle until you've got a single circle that "contains" the "huge number of circles".
Just to indicate the simplicity of computing a "containing circle" (top-of-my-head):
1) Add the center-locations of the two circles (as vectors) and scale by 1/2, thats the center of the containing circle
2) Subtract the center locations of the two circles (as vectors), add the radii and scale by 1/2, thats the radius of the containing circle
What answer is most efficient will depend somewhat on the density of circles. If the density is low, then placing placing a low-resolution grid over the map and marking those grid elements that contain a circle will likely be the most efficient. This will take approximately O(N*m*k) per update, where N is the total number of circles, m is the average number of circles per grid point, and k is the average number of grid points covered by one circle. If one circle moves more than one grid point per turn, then you have to modify m to include the number of grid points swept.
On the other hand, if the density is extremely high, you're best off trying a graph-walking approach. Let each circle contain all neighbors within a distance R (R > r_i for every circle radius r_i). Then, if you move, you query all the circles in the "forward" direction for neighbors they have and grab any that will be within D; then you forget all the ones in the backward direction that are now farther than D. Now a complete update will take O(N*n^2) where n is the average number of circles within a radius R. For something like a closely-spaced hexagonal lattice, this will give you much better results than the grid method above.
A suggestion - I am no game developer
Why not precalculate when the collisions are going to occur
as you specify
We can assume that circle object has following properties:
-Coordinates
-Radius
-Velocity
-Direction
Velocity is constant, but direction can change.
Then as the direction of one object changes, recalculate those pairs that are affected. This method is effective if directions do not change too frequently.
As Will mentioned in his answer, spacial partition trees are the common solution to this problem. Those algorithms sometimes take some tweaking to handle moving objects efficiently though. You'll want to use a loose bucket-fitting rule so that most steps of movement don't require an object to change buckets.
I've seen your "solution 1" used for this problem before and referred to as a "collision hash". It can work well if the space you're dealing with is small enough to be manageable and you expect your objects to be at least vaguely close to uniformly distributed. If your objects may be clustered, then it's obvious how that causes a problem. Using a hybrid approach of some type of a partition tree inside each hash-box can help with this and can convert a pure tree approach into something that's easier to scale concurrently.
Overlapping regions is one way to deal with objects that straddle the boundaries of tree buckets or hash boxes. A more common solution is to test any object that crosses the edge against all objects in the neighboring box, or to insert the object into both boxes (though that requires some extra handling to avoid breaking traversals).
If your code depends on a "tick" (and tests to determine if objects overlap at the tick), then:
when objects are moving "too fast" they skip over each other without colliding
when multiple objects collide in the same tick, the end result (e.g. how they bounce, how much damage they take, ...) depends on the order that you check for collisions and not the order that collisions would/should occur. In rare cases this can cause a game to lock up (e.g. 3 objects collide in the same tick; object1 and object2 are adjusted for their collision, then object2 and object3 are adjusted for their collision causing object2 to be colliding with object1 again, so the collision between object1 and object2 has to be redone but that causes object2 to be colliding with object3 again, so ...).
Note: In theory this second problem can be solved by "recursive tick sub-division" (if more than 2 objects collide, divide the length of the tick in half and retry until only 2 objects are colliding in that "sub-tick"). This can also cause games to lock up and/or crash (when 3 or more objects collide at the exact same instant you end up with a "recurse forever" scenario).
In addition; sometimes when game developers use "ticks" they also say "1 fixed length tick = 1 / variable frame rate", which is absurd because something that is supposed to be a fixed length can't depend on something variable (e.g. when the GPU is failing to achieve 60 frames per second the entire simulation goes in slow motion); and if they don't do this and have "variable length ticks" instead then both of the problems with "ticks" become significantly worse (especially at low frame rates) and the simulation becomes non-deterministic (which can be problematic for multi-player, and can result in different behavior when the player saves, loads or pauses the game).
The only correct way is to add a dimension (time), and give each object a line segment described as "starting coordinates and ending coordinates", plus a "trajectory after ending coordinates". When any object changes its trajectory (either because something unpredicted happened or because it reached its "ending coordinates") you'd find the "soonest" collision by doing a "distance between 2 lines < (object1.radius + object2.radius)" calculation for the object that changed and every other object; then modify the "ending coordinates" and "trajectory after ending coordinates" for both objects.
The outer "game loop" would be something like:
while(running) {
frame_time = estimate_when_frame_will_be_visible(); // Note: Likely to be many milliseconds after you start drawing the frame
while(soonest_object_end_time < frame_time) {
update_path_of_object_with_soonest_end_time();
}
for each object {
calculate_object_position_at_time(frame_time);
}
render();
}
Note that there are multiple ways to optimize this, including:
split the world into "zones" - e.g. so that if you know object1 would be passing through zones 1 and 2 then it can't collide with any other object that doesn't also pass through zone 1 or zone 2
keep objects in "end_time % bucket_size" buckets to minimize time taken to find "next soonest end time"
use multiple threads to do the "calculate_object_position_at_time(frame_time);" for each object in parallel
do all the "advance simulation state up to next frame time" work in parallel with "render()" (especially if most rendering is done by GPU, leaving CPU/s free).
For performance:
When collisions occur infrequently it can be significantly faster than "ticks" (you can do almost no work for relatively long periods of time); and when you have spare time (for whatever reason - e.g. including because the player paused the game) you can opportunistically calculate further into the future (effectively, "smoothing out" the overhead over time to avoid performance spikes).
When collisions occur frequently it will give you the correct results, but can be slower than a broken joke that gives you incorrect results under the same conditions.
It also makes it trivial to have an arbitrary relationship between "simulation time" and "real time" - things like fast forward and slow motion will not cause anything to break (even if the simulation is running as fast as hardware can handle or so slow that its hard to tell if anything is moving at all); and (in the absence of unpredictability) you can calculate ahead to an arbitrary time in the future, and (if you store old "object line segment" information instead of discarding it when it expires) you can skip to an arbitrary time in the past, and (if you only store old information at specific points in time to minimize storage costs) you can skip back to a time described by stored information and then calculate forward to an arbitrary time. These things combined also make it easy to do things like "instant slow motion replay".
Finally; it's also more convenient for multiplayer scenarios, where you don't want to waste a huge amount of bandwidth sending a "new location" for every object to every client at every tick.
Of course the downside is complexity - as soon as you want to deal with things like acceleration/deceleration (gravity, friction, lurching movement), smooth curves (elliptical orbits, splines) or different shaped objects (e.g. arbitrary meshes/polygons and not spheres/circles) the mathematics involved in calculating when the soonest collision will occur becomes significantly harder and more expensive; which is why game developers resort to the inferior "ticks" approach for simulations that are more complex than the case of N spheres or circles with linear motion.
So recently I've started experimenting with python in terms of game dev and I now want to create a maze game. I've implemented 4 different algorithms (Prim's, Kruskal's, Recursive Backtracker, Hunt and Kill to be exact) to generate a maze. Each of them return a maze as a 2D python array. What I found ineresting is the amount of crucial desicions a player should/may take in order to solve a maze in a different generated mazes.
Meaning that whether a player faces a crossroad or a junction
a player have to take a decision on their next step which might be crucial in terms of completion time and path lenght. This is severely simple to analyse by 'hand' when it comes to small mazes, but is there any possible code algorithm to count the amount of such crossroads and junctions in bigger mazes?
For any given point, say maze[x][y], you should be able to see if you're able to move to maze[x-1][y], maze[x+1][y], maze[x][y-1], and maze[x][y+1]. If you can move to three, you have a junction. If you can get to all four, you have a crossroad.
Just be certain that you're doing a safety check on x and y equaling 0 or len - 1 so you don't get an exception on an overflow, or a loopback from 0 to -1.
I'm developing a small game in python. I am using a 2D rectangular grid. I know that for pathfinding I can use A* and the likes, I know how this works, but the problem I have is a bit different.
Let's say we have a computer controlled human and some computer controlled zombies. When the human spots a zombie, it should get away from this as far as he can. At the moment, to test everything I just turn around 180° and run away, until I spot another zombie and repeat.
Obviously this is not very smart (and can cause problems if there is a zombie on both sides).
I was wondering if there was a smarter way to do this? Something like using Dijkstra to find a "safe zone" where I can run to? Alternatives are always welcome, I can't seem to figure it out.
You could suppose that the zombies can see everything within a particular range (radius or perhaps be more clever) and then have the human look for a spot that he thinks the zombies can't see. Pick the closest spot the zombie can't see and use the A* algorithm to find a path if one exists, else try a different one. Look out when there's nowhere to run. Alternatively you could weight all of the spots in your visibility region with a value based on how far away you would be from the zombies if you chose that spot.
Just off the top of my head, you'll probably be able to do some vector math and have the human run in the normal vector to the zombies.
I don't know how well this will work (or how it will scale to the number of zombies you have), but you could do something like:
For each zombie, compute the distance to the human and the direction it is from the human.
Create a vector for each zombie (or some subset of the close zombies), using the direction and the inverse of the distance, since the closer the zombie is the more important it is to run away.
Find the sum of all the vectors.
Make the human run in the normal vector to your result.
I'm not sure how resource intensive this would be, but it seems like the most logical way to prioritize where to run.
I'm making a game with Python->PyGame->Albow and ran into a problem with board generation. However I'll try to explain the problem in a language agnostic way. I believe it's not related to python.
I've split the game board generation into several parts.
Part one generates the board holes.
Holes are contained in a list/array. Each hole object has a mapping of angles relating to other holes which are surrounding it, each of those holes also links back to it. (Sort of like HTML DOM siblings, the difference being any angle is possible)
A hole is something like:
hole = {
empty: True,
links: {
90: <other hole>,
270: <another hole>,
etc...
}
}
Part two, calculate hole positions.
The code is something like this.
def calculate_position(hole):
for linked_hole in hole.links:
if linked_hole.position == None:
#calculate linked hole's position relative to this hole
linked_hole.position = [position relative to first hole]
calculate_position(linked_hole)
first_hole.position = (0, 0) #x, y
calculate_position( first_hole )
Part three, draw board.
Find the window height, expand the positions of holes (calculated in step two) to fit the window. Draw everything.
I believe that the problem is in step two I am calculating every hole relative to a previous hole. Rounding errors add up and the board goes squint shaped the further away from the starting hole the holes are and the bigger the board is. This only happens when making boards that aren't rectangular because otherwise there aren't rounding errors.
I am using simple trigonometry to calculate the relative positions of holes by converting the angle into radians and using built in sin/cos functions.
Any idea as to a solution or if I'm mistaken as to the problem is useful :)
PS: I will post the source code if it would help however feel it will clutter things up
Thanks for all the answers.
The people who said rounding probably wasn't going to be an issue were spot on. I had another look through the code with that in mind. I'm embarrassed to say I was generating the wrong angles in the first part of the board generation, the rendering part was correct.
I've marked Norman's answer as correct because it explains how to use a linear combination of vectors to solve the problem.
If hole positions are stored as integers, I don't doubt rounding error accumulates quickly enough to kill you. If hole positions are stored as floating point, and if you have an error of one unit in the last place (ULP) at each computation, I'm not quite sure how quickly error accumulates—but if error doubles at each step, then you have at most 53 links before even double-precision floating point would go wrong.
If you want to be rock-solid accurate, I would represent each position as a linear combination of vectors. You can represent each vector by its angle, and you have just a few angles, so you can represent the position of a hole as something like
Take six 30-degree steps and two 90-degree steps and four 180-degree steps
The numbers six, two, and four will be exact, and once you've computed all positions as vectors, you can then do the trig to convert to (x, y) coordinates all at one go. If you're worried about speed you can cache the arctangent of each angle and it will even be fast.
If this description is too terse, let me know.
The bit about accuracy becomes relatively important as soon as we realize these points are going to be converted to pixel coordinates, a.k.a. integers. Accumulate an error of 0.5 and bam! You're one pixel off.
So, either there is a huge problem with accuracy and rounding errors are climbing very very fast, or the source of the issue is elsewhere. I'm looking at this in step in particular:
expand the positions of holes (calculated in step two) to fit the window
Until I see a screenie, I'll assume "squint" means 'oval-kinda-sorta-thing'; sounds exactly what a bug in this step could produce.
I hate to be the one to suggest this, but, start in the the center. Also, you should look at your code and double check for an unfortunate conversion. The is, if a hole ends up at "138.2, 150.8", you need to keep the fractional parts until you have computed the next hole.
For a mental exercise I decided to try and solve the bubble breaker game found on many cell phones as well as an example here:Bubble Break Game
The random (N,M,C) board consists N rows x M columns with C colors
The goal is to get the highest score by picking the sequence of bubble groups that ultimately leads to the highest score
A bubble group is 2 or more bubbles of the same color that are adjacent to each other in either x or y direction. Diagonals do not count
When a group is picked, the bubbles disappear, any holes are filled with bubbles from above first, ie shift down, then any holes are filled by shifting right
A bubble group score = n * (n - 1) where n is the number of bubbles in the bubble group
The first algorithm is a simple exhaustive recursive algorithm which explores going through the board row by row and column by column picking bubble groups. Once the bubble group is picked, we create a new board and try to solve that board, recursively descending down
Some of the ideas I am using include normalized memoization. Once a board is solved we store the board and the best score in a memoization table.
I create a prototype in python which shows a (2,15,5) board takes 8859 boards to solve in about 3 seconds. A (3,15,5) board takes 12,384,726 boards in 50 minutes on a server. The solver rate is ~3k-4k boards/sec and gradually decreases as the memoization search takes longer. Memoization table grows to 5,692,482 boards, and hits 6,713,566 times.
What other approaches could yield high scores besides the exhaustive search?
I don't seen any obvious way to divide and conquer. But trending towards larger and larger bubbles groups seems to be one approach
Thanks to David Locke for posting the paper link which talks above a window solver which uses a constant-depth lookahead heuristic.
According to this paper, determining if you can empty the board (which is related to the problem you want to solve) is NP-Complete. That doesn't mean that you won't be able to find a good algorithm, it just means that you likely won't find an efficient one.
I'm thinking you could try a branch and bound search with the following idea:
Given a state of the game S, you branch on S by breaking it up in m sets Si where each Si is the state after taking a legal move of all m legal moves given the state S
You need two functions U(S) and L(S) that compute a lower and upper bound respectively of a given state S.
For the U(S) function I'm thinking calculate the score that you would get if you were able to freely shuffle K bubbles in the board (each move) and arrange the blocks in such a way that would result in the highest score, where K is a value you choose yourself. When your calculating U(S) for a given S it should go quicker if you choose higher K (the conditions are relaxed) so choosing the value of K will be a trade of for quickness of finding U(S) and quality (how tight an upper bound U(S) is.)
For the L(S) function calculate the score that you would get if you simply randomly kept click until you got to a state that could not be solved any further. You can do this several times taking the highest lower bound that you get.
Once you have these two functions you can apply standard Bound and Branch search. Note that the speed of your search is going to greatly depend on how tight your Upper Bound is and how tight your Lower Bound is.
To get a faster solution than exhaustive search, I think what you want is probably dynamic programming. In dynamic programming, you find some sort of "step" that takes you possibly closer to your solution, and keep track of the results of each step in a big matrix. Then, once you have filled in the matrix, you can find the best result, and then work backward to get a path through the matrix that leads to the best result. The matrix is effectively a form of memoization.
Dynamic programming is discussed in The Algorithm Design Manual but there is also plenty of discussion of it on the web. Here's a good intro: http://20bits.com/articles/introduction-to-dynamic-programming/
I'm not sure exactly what the "step" is for this problem. Perhaps you could make a scoring metric for a board that simply sums the points for each of the bubble groups, and then record this score as you try popping balloons? Good steps would tend to cause bubble groups to coalesce, improving the score, and bad steps would break up bubble groups, making the score worse.
You can translate this problem into problem of searching shortest path on graph. http://en.wikipedia.org/wiki/Shortest_path_problem
I would try whit A* and heuristics would include number of islands.
In my chess program I use some ideas which could probably adapted to this problem.
Move Ordering. First find all
possible moves, store them in a list,
and sort them according to some
heuristic. The "better" ones first,
the "bad" ones last. For example,
this could be a function of the size
of the group (prefer medium sized
groups), or the number of adjacent
colors, groups, etc.
Iterative Deepening. Instead of
running a pure depth-first search,
cut of the search after a certain
deep and use some heuristic to assess
the result. Now research the tree
with "better" moves first.
Pruning. Don't search moves which
seems "obviously" bad, according to
some, again, heuristic. This involves
the risk that you won't find the
optimal solution anymore, but
depending on your heuristics you will
very likely find it much earlier.
Hash Tables. No need to store every
board you come accross, just remember
a certain number and overwrite older
ones.
I'm almost finished writing my version of the "solver" in Java. It does both exhaustive search, which takes fricking ages for larger board sizes, and a directed search based on a "pool" of possible paths, which is pruned after every generation, and a fitness function used to prune the pool. I'm just trying to tune the fitness function now...
Update - this is now available at http://bubblesolver.sourceforge.net/
This isn't my area of expertise, but I would like to recommend a book to you. Get a copy of The Algorithm Design Manual by Steven Skiena. This has a whole list of different algorithms, and once you read through it you can use it as a reference. If nothing else it will help you consider your options.