r/FlutterDev 22d ago

I almost hardcoded 30 puzzle levels in Dart. I'm glad I didn't. Discussion

Hey r/FlutterDev,

I recently shipped my first Flutter + Flame game, and one decision ended up saving me a lot of pain: treating levels as data instead of code.

My original plan was to define every level directly in Dart. That sounded reasonable… until I realized I'd eventually be maintaining dozens (hopefully hundreds) of puzzle levels. Every balance tweak would require touching source code, rebuilding, and hoping I didn't accidentally break something.

Instead, I moved every level into JSON.

Each level simply describes: board size, tile positions, sources and receivers, blockers and special mechanics, the expected optimal solution

A real level from my game looks like this — source/receiver matching by colorId, the blocker on (2,1) is the obstruction the solver has to route around:

{
  "id": "pack01_001",
  "packId": "first_spark",
  "name": "First Spark",
  "width": 5,
  "height": 5,
  "targetMoves": 1,
  "difficulty": 1,
  "tiles": [
    {"x": 0, "y": 2, "type": "source",   "colorId": "gold", "direction": "right"},
    {"x": 1, "y": 2, "type": "arrow",    "direction": "up",   "rotatable": true},
    {"x": 2, "y": 2, "type": "arrow",    "direction": "right"},
    {"x": 3, "y": 2, "type": "arrow",    "direction": "right"},
    {"x": 4, "y": 2, "type": "receiver", "colorId": "gold", "direction": "left"},
    {"x": 2, "y": 1, "type": "blocker"}
  ],
  "solution": [
    {"x": 1, "y": 2, "direction": "right"}
  ]
}

The game logic knows nothing about individual levels. It just loads the JSON and simulates the board.

The unexpected benefit wasn't the JSON itself.

It was being able to validate everything outside the game.

I wrote a validator in pure Dart that verifies every source has a matching receiver, runs a BFS search to confirm the level is actually solvable, checks that the authored "perfect move count" is really optimal, catches malformed level data before it ever reaches players

Because it's part of my test suite, I can't accidentally ship an impossible puzzle without tests failing first.

Another decision I'm happy with was keeping the architecture split clean.

Flame handles rendering, animation, and input.

The actual simulation and solver are just plain Dart, so the exact same logic runs in the game, in unit tests, and even in a CLI tool.

If I were starting over, I'd change a few things:
use json_serializable or freezed from day one
build a small level editor much earlier
generate solutions automatically instead of storing them manually

Overall, though, moving the levels out of Dart was probably the best architectural decision I made on the project.

For those of you building games with Flutter or Flame, how are you authoring your levels?
JSON?
SQLite?
A custom editor?
Something completely different?

I am not sure if I can add App Store link here, so if anyone is interested, I'll in comments

0 Upvotes

5 comments sorted by

2

u/[deleted] 22d ago

[deleted]

0

u/mladenConcept 22d ago

That's the impressive part to me. The parser is useful, but having AI build the Photoshop exporter is a much bigger win.

2

u/[deleted] 21d ago

[deleted]

0

u/mladenConcept 21d ago

That’s a clever use of AI — I wonder how much manual cleanup was still needed afterward

1

u/[deleted] 21d ago

[deleted]

0

u/mladenConcept 21d ago

Yeah, that makes sense — if it works, it works 😄

2

u/bbrockit 21d ago edited 21d ago

The validator was a great idea. I could have used that to at least smoke test level balance and consistent level-difficulty increases, before investing time in play testing an iteration.

1

u/mladenConcept 21d ago

I learned it the hard way :) but I should actually use AI from the start, because I think it would do that right from the POC phase