Guide

How to make a colour sorting puzzle

Water sort, ball sort, colour sort — whatever your favourite one is called, it's the same puzzle: tubes full of mixed colours, and you move them one at a time until each tube holds a single colour. Somebody asks for it in the community every few weeks, and the answer is better than you'd expect. The entire game is six lists of numbers. Everything you see on screen is just those six lists, drawn.

Needs the arrays update This guide uses List variables and the Array blocks. If you don't see Add to Array in the Add Block sheet under Actions, update Max2D first.

The idea, in one picture

Forget graphics for a minute. A tube is a list of numbers, where each number is a colour and the first item is the drop at the bottom:

TubeListMeans
t1[1, 3, 1, 4]red at the bottom, then green, red, yellow on top
t2[2, 2, 1, 3]blue, blue, red, green
t5[ ]an empty tube

A move is two operations on those lists: take the last number out of one, put it on the end of another. That's it — that's the game. Once you see it that way, the build is short.

What you're building

Six tubes: four packed with a shuffled mix of four colours, two left empty to give you room to work. Tap a tube and its top drop lifts out into your hand. Tap another tube and it drops in — if that tube is empty, or if its top drop is the same colour. Get every tube down to one colour and you've solved it.

Step 1: the board

Open the Variables panel and add these. The type you want is called List — note that the variable type says List while the blocks that work on it are called Array blocks. Same thing.

  1. Six List variablesnamed t1, t2, t3, t4, t5, t6 — one per tube.
  2. A Number variable: handthe colour you're currently carrying. 0 means empty-handed.
  3. A Number variable: originwhich tube you lifted it from, so you can put it back.

Make all the variables before you build any blocks — blocks can only point at variables that already exist.

Step 2: the tubes

Add six Sprite objects and name them tube1 through tube6, spaced across the screen. Give each one a Box Collider a little larger than the tube art — an object with no collider can't be tapped, and a generous box makes it comfortable on a phone.

The tube art itself does nothing. It's a picture. All the behaviour lives in the script you'll write in step 4.

Step 3: the drops

Inside each tube, place four small Sprite objects, one per slot, stacked from the bottom up. Give every single one its own namet1s0, t1s1, t1s2, t1s3, then t2s0 and so on. Twenty-four objects, twenty-four different names. This matters: blocks that address objects by name affect every object with that name, so two drops sharing a name will always show the same colour.

Each drop's only job is to look at its own square of the board and draw itself. Here's the whole script for t1s0 — the bottom drop of tube 1:

  1. On Playruns every frame, so the drop is always showing the current board.
  2. If (True/False)array_length('t1') > 0 — is the tube deep enough to have a drop in this slot? On false, go to a Set sprite with opacity 0 and stop. That's the empty slot.
  3. If (True/False)t1[0] == 1 → true: Set sprite, image red, opacity 1. False: fall through to the next test.
  4. If (True/False)t1[0] == 2 → blue. Then == 3 → green, and the last false branch is yellow.

The other twenty-three drops are the same script with the numbers changed: slot 2 of tube 1 tests array_length('t1') > 1 and reads t1[1]; the first drop of tube 3 tests array_length('t3') > 0 and reads t3[0]. Build one, then copy it and edit two numbers.

Why opacity and not Set Active Hiding a drop with Set Active false looks like the obvious move, and it will break the game. Set Active also stops the object's scripts — so a drop that switches itself off has no script left to notice the board changed, and can never come back. Set sprite with opacity 0 hides it while the script keeps running. Any object that has to hide and reappear under its own control needs opacity, not Set Active.
Why one If per colour You can't say "set the sprite to colour number 3" — the Set sprite block's image is a fixed picture you pick, not something you can calculate. So each drop asks four small questions instead. It's repetitive to build once and then it's done forever.

Step 4: one drop in your hand

This is the trick that makes the whole thing work. A tube can't reach into another tube — there's no way to say "the tube I tapped last time" and read its list. So instead of pouring from one tube into another, you lift one drop out into a variable, and then a second tap drops it in. Every tube only ever touches its own list. Nothing has to know about anything else.

Here's the script on tube1. It reads long written down and it's nine blocks in the editor.

  1. When Object Touchedthe tap that starts everything.
  2. If (True/False)hand == 0 — are your hands empty? True means lift (next step). False means you're carrying something, so jump to step 5.
  3. If (True/False)array_length('t1') > 0 — don't lift from an empty tube. On false, do nothing at all.
  4. Set variablehand → Expression solver → t1[array_length('t1') - 1]. That expression is "the last item", which is the drop on top.
  5. Remove from Arrayarray t1, index → Expression solver → array_length('t1') - 1. Copy the colour first, then remove it — the other way round and you're reading a drop that's already gone.
  6. Set variableorigin to 1. Tube 1 remembers it's the one holding the drop.

And the other half of the chain — what happens when you tap a tube while carrying a drop:

  1. If (True/False)origin == 1 — did this drop come from me? Then it's always allowed back. True jumps straight to the drop-in blocks.
  2. If (True/False)array_length('t1') < 4 — a full tube refuses.
  3. If (True/False)array_length('t1') == 0 || t1[array_length('t1') - 1] == hand — empty tube, or a matching colour on top. Anything else and nothing happens: the drop stays in your hand.
  4. Add to Arrayarray t1, value → Expression solver → hand.
  5. Set variablehand to 0, then origin to -1. Your hands are empty again.

Tubes 2 to 6 are this exact script with t1 swapped for t2… and origin == 1 swapped for origin == 2

The one that catches everyone: text vs numbers Add to Array has a plain value box and an expression box. If you type 1 into the plain box it stores the text "1" — and text "1" is never equal to the number 1, so your colour checks quietly stop matching. Use the Expression solver for values that are numbers, and everything compares properly.

Step 5: show what you're carrying

Add one more Sprite object above the tubes, named carry. Its script is the same four-question colour chain from step 3, but reading hand instead of a tube: hand > 0 decides whether it's visible at all, then hand == 1, hand == 2 and so on pick the picture. Now the player can always see what they're holding and where it came from.

Step 6: winning

Add a Text object for the banner, and give it a When block — not On Play. When fires once, the moment its condition turns true, instead of every frame forever. Its condition is "every tube is either empty, or full of one colour":

(array_length('t1') == 0 || (array_length('t1') == 4 && t1[0] == t1[1] && t1[1] == t1[2] && t1[2] == t1[3])) && … and the same again for t2 through t6, joined with &&.

It's a long line, but it's one line, and you only write it once. Hang a Set text block off it that says SOLVED — or a sound, or a particle burst, or a Load scene to the next level.

Step 7: restart

One Text object with a Box Collider, named btnReset, and a script of exactly two blocks: When Object TouchedBroadcast Message reset.

Then one invisible Empty object — call it manager — that listens. Give it a When Object Loads block and an On Message block for reset, both wired into the same chain: Clear Array on each tube, then Add to Array for each drop you want dealt, then Set variable to put hand back to 0 and origin to -1. One chain deals the board at the start of the game and every time somebody restarts.

That's also where new levels come from. A different set of Add to Array blocks is a different puzzle.

The blocks you used

BlockWhat it did here
Add to ArrayDropped a colour on top of a tube (and dealt the level)
Remove from ArrayLifted the top colour out of a tube
Clear ArrayEmptied every tube before re-dealing
array_length('t1')How many drops are in a tube — depth, fullness, and "is it empty"
t1[0]Read one slot. t1[array_length('t1') - 1] is the top drop
WhenFired the win banner once, instead of every frame
Broadcast Message / On MessageRestart button tells the manager to re-deal

When it doesn't behave

SymptomCause
Colours never match, every drop is refusedValues were stored as text. Use the Expression solver on Add to Array, not the plain value box
A drop vanishes and never comes backYou hid it with Set Active instead of Set sprite opacity 0 — its script is frozen too
Two drops always show the same colourThey share an object name. Every slot needs its own
Tapping a tube does nothingNo Box Collider on the tube, so there's nothing to tap
The top drop is at the bottomItem 0 of the list is the bottom of the tube. Place slot objects bottom-up to match
Lifting gives the wrong colourRemove from Array ran before Set variable. Copy the colour out first
The win banner fires over and overYou used On Play + If instead of a When block

Try it: Color Sort

The example project is the finished game — 36 objects, 6 tubes, 24 drops, and a level that's guaranteed solvable. Open the tube scripts to read the lift-and-drop chain, or open any drop to see the four-question colour test. Every script has a note next to it explaining what it does and why.

Change the level by editing the manager's Add to Array blocks. Add a fifth colour by adding a picture and one more If. Make it harder by taking away an empty tube.

Get the example project

Download ColorSort.zip (9 KB)

In Max2D: ProjectsImport Project → pick the zip. Needs a version of the app with the Array blocks — if Add to Array isn't in your Add Block sheet, update first.

Frequently asked questions

Can you make a water sort or ball sort puzzle in Max2D?

Yes — it's one of the tidiest projects you can build with arrays. Six List variables hold the board, one tap chain moves a colour, and everything else is presentation.

How do I store a stack of things?

A List variable. Add to Array puts a value on the end, Remove from Array takes one out by index, and array_length('name') counts them. The top of a stack is always name[array_length('name') - 1].

Why does my array value never match my number?

It was saved as text. The plain value box on Add to Array stores text; the Expression solver stores a number. Text "1" never equals the number 1.

How do I hide something without Set Active?

Set sprite with opacity 0 to hide, opacity 1 to show. Unlike Set Active it leaves the object's scripts running, so it can bring itself back.

Why one drop at a time instead of pouring the whole run?

Because no tube can read another tube's list — there's no way to point a block at a list you worked out at runtime. Lifting into a variable keeps every tube working only on itself, which is what makes the build this short.

Six lists and a tap. That's a puzzle game.

Build it tonight on your phone. Max2D is free on Google Play.

Free onGoogle Play