
A match-3 board can be mechanically correct and still feel slow, confusing, or unfair. The swap works. The fruit clears. The score moves. Yet the player cannot tell what just happened, the reward lands too late, or a hard level feels as if the game changed the rules.
That is the gap I had to close in Tropic Tumble, my tropical match-3 game. AI helped me work through the decision space and write a lot of the implementation. It could generate rules, compare options, and turn a chosen rule into Godot code quickly. I still had to decide what the player should understand, what should feel powerful, and what was fair enough to ship.
For me, game feel became five practical checks:
- Response: does the board answer the player’s action immediately?
- Clarity: can the player tell what activated, where it travelled, and what it changed?
- Payoff: does a stronger match create a meaningfully stronger result?
- Fairness: can the player understand why they won or lost?
- Pacing: are new rules introduced at a speed the player can absorb?
This article owns that mechanics work. The screens, art pipeline, and music now live in Designing a Mobile Game With AI: Screens, Art, and Music.
Five powers, five readable promises
I did not want five versions of a bomb. Each power needed a clear match shape and a result the player could learn from the board itself.





| Match shape | Power | Player promise |
|---|---|---|
| Exactly four fruit horizontally | Wind | Clear the full row |
| Exactly four fruit vertically | Water | Clear the full column |
| Exactly five fruit in a straight line | Solar | Select a fruit colour and clear every eligible tile of it |
| Five or more fruit in an L, T, or plus shape | Volcano | Blast a four-by-four area around the junction |
| Six or more fruit in a straight line | Earth | Send clearing pulses in eight directions |
The distinction matters. If four horizontal fruit sometimes create Water and sometimes create Wind, the player has to memorise an exception. If a six-tile line creates the same reward as a five-tile line, the extra effort has no payoff.
The game has one classifier that owns those decisions. This is a shortened excerpt from the real GDScript:
static func classify_group(group: Array, preferred_spawn: Vector2i) -> Dictionary:
var count := group.size()
var straight := _is_straight_line(group)
if straight and count >= 6:
return _make_plan(KIND_EARTH, pick_line_center_cell(group, preferred_spawn), true)
if straight and count == 5:
return _make_plan(KIND_SOLAR, pick_line_center_cell(group, preferred_spawn), true)
if straight and count == 4:
var kind := KIND_WIND if _is_horizontal_line(group) else KIND_WATER
return _make_plan(kind, pick_line_center_cell(group, preferred_spawn), true)
if count >= 5 and _is_l_t_or_plus_shape(group):
return _make_plan(KIND_VOLCANO, pick_volcano_bend_cell(group, preferred_spawn), true)
return {}
That order is deliberate. A six-tile straight match must be classified as Earth before the five-tile Solar rule can catch it. A non-straight group only reaches Volcano after the straight-line rules are ruled out. One canonical owner means the board, tutorials, missions, and tests do not each carry their own slightly different definition.
Correct geometry is only the first layer of feel
A rule tells the game which cells should change. Feel decides how the player experiences that change. I separate the two because tuning animation inside the rule makes both harder to reason about.
For a power activation, the complete response has several beats:
- Acknowledge the input. The selected power, swap, or tap needs an immediate visual and haptic answer.
- Show the identity. Wind should read as a horizontal force. Water should read vertically. The player needs that cue before the board is already empty.
- Clear in a readable order. A large result can happen quickly without every cell disappearing in the same unreadable flash.
- Settle the board once. Gravity and refill should feel like one consequence of the clear, not a second unrelated event.
- Land the reward. Score, objective progress, praise, and sound should agree with what the player just saw.
If those beats arrive out of order, the mechanic feels wrong even when the final board state is correct. A score can increase before the visible fruit clears. A sound can land after the effect is over. A refill can bounce twice and make the board look unsettled. None of those are classifier bugs, but all of them change the player’s trust in the move.
I also protect the difference between identity and clear geometry. Wind can show its swish before the clear, but the full row sweep belongs to the clear phase. Solar can show the chosen colour before it expands. That separation gives the player enough time to understand the cause without making every power slow.
The faster AI writes the first implementation, the more useful that breakdown becomes. I can ask it to change one layer without inviting it to rewrite the mechanic:
| Layer | Question | Typical owner |
|---|---|---|
| Classification | Which power did this match create? | Match classifier |
| Geometry | Which cells should this power affect? | Power or combo rules |
| Resolution | In what order do cells, obstacles, and chains resolve? | Board resolution |
| Presentation | What does the player see, hear, and feel? | Effects, audio, haptics |
| Evidence | What proves the layers still agree? | Rule, contract, and rendered tests |
That ownership is not about making the code sound sophisticated. It lets me tune the pause after a Water wave without risking which column Water clears.
The combo system is a matrix, not a list of surprises
Five powers create 15 unique pairs. I needed all 15 to work, but I also needed the family to make sense. Wind should keep speaking in rows. Water should keep speaking in columns. Solar should choose fruit. Volcano should add a dense local blast. Earth should add directional reach.
The real supported matrix looks like this:
| + | Wind | Water | Solar | Volcano | Earth |
|---|---|---|---|---|---|
| Wind | 3 centred rows | 3 rows + 3 columns | Rows containing the selected fruit | 6×6 burst + 3 rows | Earth star + 3 rows |
| Water | Same pair | 3 centred columns | Columns containing the selected fruit | 6×6 burst + 3 columns | Earth star + 3 columns |
| Solar | Same pair | Same pair | Full board | 4×4 bursts from selected-fruit cells, capped | Selected fruit + one-pulse stars |
| Volcano | Same pair | Same pair | Same pair | 6×6 burst | 6×6 burst + outer Earth pulses |
| Earth | Same pair | Same pair | Same pair | Same pair | Full board |
The matrix is not only documentation. It is a design test. Read down the Wind column and rows remain part of the result. Read across Water and columns remain part of it. The player can build an expectation instead of learning 15 unrelated tricks.
Two limits keep the spectacle useful:
- Every result is clipped to the real board. A combo at an edge does not target imaginary cells.
- Solar + Volcano caps its number of burst centres. A crowded board cannot create an unbounded amount of work and visual noise.
That is where AI helped me most. Once I had the identity of each power, it could enumerate the pairings and edge conditions quickly. I did not accept the first table it produced. I checked whether every result still sounded like both powers and whether a player could read the outcome.
Obstacles change the result without changing the rule
Powers become much harder to design once the board contains shells, syrup, nets, ice, and seaweed. A row clear cannot treat every occupied cell as the same kind of thing.
The power still owns its geometry. The obstacle family owns what one hit means. A shell may block a normal swap, syrup may behave as an overlay, and another obstacle may need more than one hit. If the Wind rule starts carrying separate hard-coded behaviour for every obstacle, it becomes the second owner of obstacle damage and eventually disagrees with a normal match or a booster.
So I ask two different questions:
- Did the power reach this cell? The power geometry answers.
- What should this cell do when reached? The occupant or obstacle rule answers.
That distinction protected a real Solar edge case. Solar chooses an eligible fruit colour from a frozen board snapshot. Fully blocked cells should not trick the selector into choosing a colour the player cannot work with. Once a colour is chosen, however, the clear still needs to reach matching fruit under relevant obstacle layers so those layers can take the correct hit. Excluding those cells from the clear would make Solar appear to skip parts of the board.
The useful test cases are not “activate every power once.” They are boundaries where two owners meet:
- power through an obstacle at the board edge;
- Solar selection when the most common visible colour has blocked cells;
- a chained power struck by another clear;
- a combo whose overlap must not double-apply one cell;
- a final obstacle removed by a power that should complete the level;
- presentation that waits for obstacle contact before showing the clear.
This is QA strengthening the mechanic without turning the article into the full test strategy. The question is always tied to the player promise: if the effect visibly reaches a tile, does the game respond in a way the player can understand?
Wind + Water, from rule to evidence
Wind + Water is a good example because the promise is simple and the implementation still needs precision.
The rule: use the upper or left power as the anchor, then clear the three rows and three columns centred on that anchor. Deduplicate the overlapping cells and clip the result at the board edges.
On an 8 by 8 board with the anchor at row 3, column 3, the expected result is a thick cross. Rows 2, 3, and 4 clear across the board. Columns 2, 3, and 4 clear from top to bottom. The nine cells in the middle belong to both bands, but each cell resolves once.
The resolver is small because the geometry rule already has one owner:
static func cells_wind_water_combo(
pos_a: Vector2i,
pos_b: Vector2i,
rows: int,
cols: int
) -> Array:
var anchor := combo_anchor(pos_a, pos_b)
return cells_cross3_at_anchor(anchor, rows, cols)
The contract test calls that resolver with a controlled board and compares the exact cell set with the canonical geometry:
_assert_combo(
MatchPowerupClassifier.KIND_WIND,
MatchPowerupClassifier.KIND_WATER,
PowerUpLogic.cells_wind_water_combo(a, b, rows, cols),
"Wind+Water"
)
That test proves the rule dispatcher and the geometry owner agree. It also checks that the debug callback receives the normalized pair key and the same anchor the presentation uses.
What does it not prove? It does not prove that the animation is readable, the sound lands at the right time, or the phone stutters during the effect. That final feel check stays human.
For the larger example I also want evidence at four different distances:
| Distance | Evidence | Failure it catches |
|---|---|---|
| Rule unit | Exact cross geometry at centre and edges | Wrong rows, columns, clipping, or deduplication |
| Contract | Resolver, normalized pair, and presentation anchor agree | Two subsystems interpret the same combo differently |
| Rendered capture | Real effect appears from the correct origin | Code is right but the visual starts elsewhere |
| Human play | Timing, readability, sound, haptic, and frame rate | Technically correct effect feels weak or confusing |
One test cannot replace the next. A screenshot cannot prove every target cell. A cell-set assertion cannot prove the player can follow the action. Layered evidence keeps each check focused.


A real level before and after the power play changes it. The evidence is not just that cells were returned. The board state, score, objectives, remaining moves, and presentation all have to agree.
Difficulty comes from the level, not the player’s wallet
I wanted a hard level to make you think, “I figured that out.” I did not want the game quietly changing difficulty because you failed twice, bought something, or looked likely to spend.
Tropic Tumble’s campaign level data is the authority. Difficulty comes from authored decisions:
- the board dimensions and opening arrangement;
- the goal and amount of work required;
- obstacle family, health, spread, corner placement, and access;
- the move limit or timer;
- which fruit and powers are available;
- the number of strong moves the layout makes possible.
Structural pressure is not the same as obstacle count. Four easy-to-reach obstacles in the centre can be less demanding than three spread into corners with only one adjacent route. A tight collect target and a score target also compete for the same moves. That combined workload is what the difficulty contract evaluates.
There is one narrow runtime safety valve. A level may explicitly author runtime_moves_assist, and the code caps it at one extra move. It is not a dynamic rescue system. It cannot make an incorrectly authored level valid.
const CAMPAIGN_RUNTIME_MOVES_ASSIST_MAX := 1
var assist := clampi(
int(level_data.get("runtime_moves_assist", 0)),
0,
CAMPAIGN_RUNTIME_MOVES_ASSIST_MAX
)
level_data["moves_limit"] += assist
The live adjustment must not read telemetry, failure history, purchase state, a backend profile, offer exposure, or monetization state. Those systems exist elsewhere in the product, but they do not get to change the puzzle under the player’s hand.
The honesty of a puzzle is in what the difficulty code is allowed to know about the player. Here, almost nothing.
Teach one new demand at a time
Powers, obstacles, hints, and tutorials all compete for the same limited attention. If I introduce a new fruit, a new obstacle, and a new power on one board, I cannot tell which rule confused the player.
The campaign therefore layers pressure:
- Let the basic match settle first. The early board has room to teach swapping and goals without an obstacle family fighting for attention.
- Introduce the power through its match shape. The tutorial points at the useful move, then lets the real result happen on the board.
- Wait for the board to settle before explaining. A blocking card should not cover the clear, gravity, refill, or score landing.
- Add obstacle families separately. Shells, syrup, nets, ice, and seaweed do not all follow the same damage rule, so each needs its own introduction.
- Keep hints as a nudge. A hint suggests a valid move after the player stalls. It does not solve the level continuously.
- Review the result at real speed. A tutorial can be logically correct and still feel slow if it pauses after every small action.
This is also why the visual-testing strategy stays in its own article. I use rule tests, contract tests, rendered captures, and real-device review, but the full strategy for the bugs AI could not see already owns that job.
The mechanic worksheet I use
Before a mechanic reaches code, I can force it through this worksheet. It stops a fun-sounding idea from arriving as an untestable paragraph.
| Field | What to write | Wind + Water example |
|---|---|---|
| Idea | The rough interaction | Combine one horizontal and one vertical line power |
| Player promise | What the player should understand | A thick cross clears through the board |
| Rule | Exact deterministic behaviour | Three centred rows plus three centred columns |
| Edge cases | Boundaries and conflicts | Corners, small boards, overlap, obstacles, chained powers |
| Code owner | One canonical implementation | PowerComboRules delegates to PowerUpLogic geometry |
| Automated evidence | What can be proven without taste | Exact cell set, pair normalization, anchor agreement |
| Human feel check | What a person must judge | Readability, timing, impact, sound, frame rate |
Copy the blank version:
Mechanic:
Idea:
Player promise:
Exact rule:
Edge cases:
Canonical code owner:
Automated evidence:
Human feel check:
Ship, tune, or hold:
The last line matters. Some mechanics are correct and still need tuning. Some are impressive and still do not belong in the game.
When I use AI against that worksheet, I give it the rule and the evidence boundary first. I can ask it to enumerate edge cases, find the existing code owner, implement the smallest change, and add a contract test. I do not ask it whether the power is fun. It has no hands on the phone and no responsibility for the release decision.
The human pass is not a vague “looks good to me” at the end. I play the same mechanic deliberately:
- at the centre and every board edge;
- with no obstacle, one obstacle family, and a mixed board;
- as a direct activation and inside a chain;
- at normal speed, not only frame by frame;
- with sound and haptics on, then off;
- on a real small phone where the effect has less visual room.
I note the first moment I lose track of cause and effect. That is usually where the presentation needs less noise, better order, or a stronger identity cue.
Build the rule, then play the promise
AI gave me speed across the mechanics work. It helped enumerate combinations, produce fixtures, move rules into canonical owners, and catch disagreements with contract tests. That made the system deeper than I could have built by treating every pairing as a one-off.
The game still needed my judgment at both ends. I had to make the original promise clear, then play the result on a real phone and decide whether the implementation kept it.
If you are building your own mechanic, start with the worksheet. Write the player promise in one sentence. Then make the rule, code owner, evidence, and feel check answer that sentence. A green contract proves the board followed the rule. Your hands and eyes decide whether the rule was worth following.
Tropic Tumble is live on iPhone and Android, and I am still tuning that line between correct, fair, and satisfying. The mechanics belong to the player once the board is in their hands.





Comments 0
Share your thoughts, ask questions, or add to the conversation.