Skip to content

An implementation of, and strategy for beating the game Bubble Burst

Notifications You must be signed in to change notification settings


Repository files navigation

I've made a few notes on my implementation of the game as a guide for interested readers. As a note but not an excuse, this code has evolved over quite a long time and so there are parts that I haven't taken a lot of care with to refactor.

An exploration of the game itself can be found on my blog

Part 1 - Introduction

Part 2 - Heuristics

Thanks for reading!


To me the most interesting part is the use of System.Collections.Immutable.ImmutableList to represent the game grid. ImmutableList is a persistent data structure, which means that it's possible to share memory between different instances.

The API for a simple ImmutableList looks like this:

	var builder = ImmutableList.CreateBuilder<int>();

    builder.AddRange(new[] { 1, 2, 3, 4 });

    ImmutableList<int> foo = list.ToImmutable();
 To then mutate the list to add further items:

    builder = foo.ToBuilder();

    builder.AddRange(new[] { 5, 6, 7, 8 });

    ImmutableList<int> bar = builder.ToImmutable();

Under the covers, both immutable and newImmutable are now sharing memory (the values [1,2,3,4])! ImmutableList does this by implementing an AVL tree which makes mutations non-destructive and requires minimal data copying. Stephen Engelhardt has a great explanation of how they work.

But we need a 2D grid...

In order to implement a 2D grid, we need an ImmutableList<ImmutableList<BubbleBurst.Game.Bubble>>. Creating a builder for the outer list is easy - we just use the built in one) - but that doesn't allow us to mutate the inner collection, which are the lists actually storing the bubbles. To work around that, there's a wrapper around the outer list builder that provides an indexer for which the setter manages the mutation:

public class Builder
    private readonly ImmutableList<ImmutableList<Bubble>>.Builder _gridBuilder;

    internal Builder(ImmutableList<ImmutableList<Bubble>>.Builder grid)
        this._gridBuilder = grid;

    public ImmutableBubbleBurstGrid ToImmutable()
        return new ImmutableBubbleBurstGrid(_gridBuilder.ToImmutable());

    public Bubble this[int col, int row]
            var list = _gridBuilder[row].ToBuilder();
            list[col] = value;
            _gridBuilder[row] = list.ToImmutable();

This ensures we're still sharing memory and those builders are pretty much free to create and use.

I should add that using ImmutableList is largely unnecessary since we're greatly reducing the search space with the selection strategies. It's primary responsibility is to maintain my interest in this project and to learn about the pattern.


There's a couple of interesting algorithms in the game implementation itself, let's take a look at them!

Flood fill

A flood fill is used to put all the bubbles into groups. It works by iterating over each bubble of the grid, checking to see if it's part of a group already, and if not then recursively going north/south/east/west and adding any same-coloured bubbles to the current group.

It's also by far the most costly part of the program as it's a fairly naive implementation of a flood fill. As DotTrace shows, it's responsible for a good 74% of the program's runtime:

There's certainly room for improvement here, but I'm not sure it would have as drastic an impact on results as using good heuristics. Ultimately, we still need to intelligently select which grids to test - doing it say, 50% faster might not change the results that much.


The JumpTillTheresNoGaps function is one of two algorithms responsible for resolving the grid state after popping a bubble group. It works by beginning at the bottom of each column, and works its way up. As it detects gaps it increments an adjustment value which when encountering a bubble, moves the bubble down by that amount.


The PushColumnsRight function is fairly straightforward: for each column, check to see if the column is empty. If it is, then move from right to left and pull the bubbles to the right, using an adjustment similar to JumpTillTheresNoGaps.

Together they perform quite well, though we're doing many fewer of them than the FindBubbleGroup function:


An implementation of, and strategy for beating the game Bubble Burst






No releases published


No packages published