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.
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:
| Tube | List | Means |
|---|---|---|
| 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.
- Six List variablesnamed
t1,t2,t3,t4,t5,t6— one per tube. - A Number variable: handthe colour you're currently carrying.
0means empty-handed. - 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 name — t1s0, 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:
- On Playruns every frame, so the drop is always showing the current board.
- 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. - If (True/False)
t1[0] == 1→ true: Set sprite, image red, opacity 1. False: fall through to the next test. - 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.
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.
- When Object Touchedthe tap that starts everything.
- 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. - If (True/False)
array_length('t1') > 0— don't lift from an empty tube. On false, do nothing at all. - Set variable
hand→ Expression solver →t1[array_length('t1') - 1]. That expression is "the last item", which is the drop on top. - 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. - Set variable
originto1. 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:
- 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. - If (True/False)
array_length('t1') < 4— a full tube refuses. - 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. - Add to Arrayarray
t1, value → Expression solver →hand. - Set variable
handto0, thenoriginto-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…
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 Touched → Broadcast 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
| Block | What it did here |
|---|---|
| Add to Array | Dropped a colour on top of a tube (and dealt the level) |
| Remove from Array | Lifted the top colour out of a tube |
| Clear Array | Emptied 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 |
| When | Fired the win banner once, instead of every frame |
| Broadcast Message / On Message | Restart button tells the manager to re-deal |
When it doesn't behave
| Symptom | Cause |
|---|---|
| Colours never match, every drop is refused | Values were stored as text. Use the Expression solver on Add to Array, not the plain value box |
| A drop vanishes and never comes back | You hid it with Set Active instead of Set sprite opacity 0 — its script is frozen too |
| Two drops always show the same colour | They share an object name. Every slot needs its own |
| Tapping a tube does nothing | No Box Collider on the tube, so there's nothing to tap |
| The top drop is at the bottom | Item 0 of the list is the bottom of the tube. Place slot objects bottom-up to match |
| Lifting gives the wrong colour | Remove from Array ran before Set variable. Copy the colour out first |
| The win banner fires over and over | You 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.
In Max2D: Projects → Import 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.