Understanding City Block Game Logic
I spent about three weeks debugging a nonogram-style solver last year before I figured out that most of the problems came from the edge cases, not the core algorithm. The City Block Game — sometimes called a logic block puzzle — is essentially a constraint satisfaction problem wrapped in a casual game interface. You place rectangular pieces on a grid, and the pieces can't overlap. That's the basic version. Some variants add color matching or directional constraints, which changes everything about how you'd approach solving it. The grid itself is usually 10x10 or 12x12, though I've seen mobile versions go as large as 15x15 with no time limit. The pieces are polyominoes, which is just the fancy word for shapes made of connected squares. Tetris blocks are polyominoes too, but City Block Game pieces tend to be more varied — L-shapes, T-shapes, straight lines, plus the weird irregular chunks that make you pause for twenty seconds trying to figure out where they fit.
Where People Get Stuck With City Block Game
The first time I ran into a real problem was with a level that had a 3x3 empty space surrounded by walls, and the only piece that could fit was a 3x1 horizontal bar — except the piece I had left was an L-shape. The level design forced you to think about the last piece before you placed the first one, which is backwards from how most players approach it. I solved it by working from the inside out instead of filling the grid left to right. That changed my whole strategy for every level after. Here's what most beginners miss: the corners and edges are actually the easy parts. The center of the grid is where pieces get trapped. I learned this the hard way on a level that required placing a 2x3 vertical rectangle in the middle, but I'd already filled the surrounding cells with smaller pieces that blocked access. The solver I built flagged this as an "impossible state" and I had to implement a backtracking undo system. Took me two days to get it right.
How the Solving Algorithm Works
A brute force approach tries every piece in every position and rotation until something fits. For a 10x10 grid with ten pieces, that's roughly 100 positions per piece times however many orientations each piece has. A 2x3 rectangle has two orientations — horizontal and vertical — while an L-shape has four if rotations are allowed. The search space explodes fast. The optimized version uses constraint propagation. You maintain a set of possible positions for each remaining piece, and when you place a piece, you remove those cells from every other piece's possible positions. If any piece ends up with zero valid positions, you backtrack immediately instead of continuing down a dead branch. This cuts the typical solve time from several seconds down to under 100 milliseconds on a 10x10 grid. Here's the practical implementation:
Get the Full Details

First, represent the grid as a 2D array. Zero means empty, and positive integers represent which piece ID occupies that cell. Each piece has a shape definition — an array of coordinate offsets from a anchor point. For placement, you check every cell on the grid, and for each cell, you check if the piece shape fits without overlapping existing pieces or going out of bounds. The key optimization is sorting pieces by their number of valid placements, fewest first. This is called most-constrained-variable heuristic, and it means you're placing the hardest-to-fit pieces first while you still have options. I've seen solvers that place pieces in arbitrary order take ten times longer on the same level.
What Happens When the Grid Gets Bigger
A 15x15 grid with twelve pieces is where the naive backtracking starts to struggle. The constraint propagation helps, but you hit a wall around depth 8 or 9 in the search tree. At that point, you need either a smarter piece selection strategy or a limit on backtracking depth with random restarts. I ended up using a hybrid approach — constraint propagation for the first six pieces, then switching to a randomized greedy placement for the rest, accepting that it might not find the optimal solution but would return something valid within a second. This is the same tradeoff you see in actual City Block Game apps. The hint system doesn't solve the entire puzzle — it just suggests one piece placement that won't lead to a dead end. The hint algorithm runs the constrained solver for a few moves ahead and checks if any placement keeps all remaining pieces solvable.
Common Pitfalls in Implementation
The first bug I always hit is rotating piece shapes incorrectly. A piece defined as [[0,0], [1,0], [2,0], [2,1]] rotated 90 degrees clockwise becomes [[0,0], [0,-1], [0,-2], [1,-2]], and then you have to re-anchor it so the coordinates are non-negative again. Skip that re-normalization step and your piece will try to place at negative grid positions, which either crashes or silently fails depending on how you handle out-of-bounds checks. The second issue is duplicate piece detection. Some levels give you two identical pieces — same shape, different color. If your solver treats them as the same object, you'll place one and think the other is gone too. I fixed this by giving each piece a unique ID even when the shape data matches, and the placement check uses the ID, not the shape. A less obvious problem shows up with "floating" pieces — pieces that technically fit but are completely surrounded by other pieces with no path to the edge. The standard placement check doesn't care about connectivity, so it would place the piece and then later realize you can't reach it with your current tool or movement system. If your game has any kind of piece dragging or movement mechanic, you need to validate that the target cell is reachable before allowing the placement.

Practical Tips for Playing
Start from the edges and work inward. The corner cells have only one or two possible orientations for any given piece, so placing pieces there first eliminates possibilities for the rest of the grid. I mentally mark off cells as I place pieces rather than trying to hold the whole board in my head — it sounds stupid but it prevents the kind of errors where you think a cell is empty when you already filled it two turns ago. Keep your largest pieces available for last. A 1x5 straight piece can only go horizontally or vertically across five consecutive empty cells. If you fill those cells with smaller pieces first, that long piece becomes useless. Save it until you've identified where the long gaps are. This is counter-intuitive because your instinct is to get the big piece out of the way early, but that's exactly when it becomes most valuable. When you're truly stuck, look for the cell with the fewest possible piece fits. If only one piece can cover a particular cell, place that piece there — even if it seems like a bad spot for the piece otherwise. This is the same logic a solver uses, and applying it manually catches about half of the situations where I'd normally ask for a hint.
The One Exception That Breaks Everything
Sometimes a level is designed so that two medium-sized pieces compete for the same narrow corridor, and neither can fit without the other moving first. This creates a dependency chain that manual play can't resolve — you'd need to place one, then remove it, then place the other, then come back. The official City Block Game apps handle this by either making the level unwinnable in strict placement mode (they expect you to use the rearrange tool) or by building in a "swap" mechanic that lets you exchange two placed pieces' positions. If you're building your own version, consider adding a rearrange function that lets you swap any two pieces currently on the board. It's a small feature that solves a whole class of puzzles that would otherwise require backtracking through your entire placement history. I added this to my implementation after level 47 and it made the later levels significantly more fun instead of frustrating. The piece generation algorithm also matters more than you'd think. Random polyomino generation tends to produce too many simple shapes early on and then throws impossible combinations at you later. A better approach is to generate the grid fill first — partition the grid into connected regions of the right sizes — and then derive the piece shapes from that partition. This guarantees a solution exists by construction, and you can then scramble it for the player without worrying about unsolvable states.
Performance Numbers
On a standard 10x10 grid, a constraint-propagation solver finds solutions in 20 to 80 milliseconds on modern hardware. A 12x12 grid with the same approach takes 100 to 400 milliseconds. The 15x15 grid is where you need to decide between perfect solutions (which can take several seconds with backtracking) and good-enough solutions (which the randomized greedy approach delivers in under 200 milliseconds). Most commercial apps target the 12x12 size because it's the sweet spot between complexity and performance on mobile devices. If you're implementing this in JavaScript for a browser game, expect 2x to 3x slower performance compared to a compiled language. A 10x10 solve that takes 50ms in Python might take 150ms in Chrome, which is still fine for interactive use but noticeable if you're showing a solving animation in real time.
