DEV Community

Guillaume Sere
Guillaume Sere

Posted on

🧩 Building Mosaic Grid — A Rotation-Based Puzzle Game with Next.js

I recently built Mosaic Grid, a browser-based puzzle game focused on placement and rotation mechanics. In this post, I want to walk through the technical decisions, challenges, and architecture behind it.

https://mosaic-grid.vercel.app/

🎮 Concept

The game is played on a 6x6 grid, where players must:

  • Drag & drop pieces onto the board
  • Rotate them in 90° increments
  • Avoid overlaps
  • Match an expected solution

At its core, it’s a mix of spatial reasoning and constraint solving.

⚙️ Tech Stack

  • Next.js (App Router)
  • React
  • TypeScript
  • CSS Modules / Tailwind (depending on your setup)
  • Custom state management (no heavy external libs)

đź§  Core Architecture

1. Grid Representation

The board is represented as a 2D array:

type Cell = { 
occupied: boolean;
 pieceId?: number;
 };
 const grid: Cell[][] = Array(6).fill(null).map(() => 
Array(6).fill({ occupied: false }) 
);
Enter fullscreen mode Exit fullscreen mode

2. Piece Modeling

Each piece is defined as a matrix:

type Piece = {
  id: number;
  shape: number[][];
  rotation: number; // 0, 90, 180, 270
};

Example (2x2 block):

[
  [1, 1],
  [1, 1]
]
Enter fullscreen mode Exit fullscreen mode

3. Rotation Logic

Instead of storing multiple orientations, I compute rotations dynamically:

function rotateMatrix(matrix: number[][]): number[][] {
 return matrix[0].map((_, i) =>
 matrix.map(row => row[i]).reverse()
 ); 
}

Enter fullscreen mode Exit fullscreen mode

Each click applies a 90° rotation.

4. Drag & Drop System

I implemented a custom drag & drop system instead of relying on libraries.

Why?

  • More control over snapping behavior
  • Easier grid alignment
  • Better performance for small UI

Key ideas:

  • Track mouse position
  • Translate piece preview in real-time
  • Snap to nearest valid grid cell on drop

5. Placement Validation

Before placing a piece:

  • Check boundaries
  • Check collisions
  • Apply special rules (e.g. 2x2 pieces must align on even coordinates)
function canPlace(piece, x, y, grid) {
 for (let i = 0; i < piece.shape.length; i++) {
 for (let j = 0; j < piece.shape[i].length; j++) { 
if (piece.shape[i][j]) {
 if (!grid[y + i] || !grid[y + i][x + j]) return false;
 if (grid[y + i][x + j].occupied) return false;
 }
 }
 } 
return true;
 }
Enter fullscreen mode Exit fullscreen mode

6. Game State

State is split into:

  • grid
  • pieces (with position + rotation)
  • progress (correct vs incorrect placements)

I avoided global state libraries and used React state + lifting state up, which was sufficient for this scale.

🎯 UX Details

Some small but important features:

  • Double-click to rotate placed pieces
  • Click to rotate pieces in the palette
  • Visual feedback:
    1. 🟢 Correct placement
    2. đź”´ Incorrect placement
  • Automatic “snap” to valid positions

đź§© Challenges

1. Rotation + Collision Combined

Handling rotation while ensuring valid placement required recalculating bounding boxes dynamically.

2. Snapping System

Making the drag feel natural without jitter or misalignment took several iterations.

3. Edge Cases

  • Partial out-of-bounds placement
  • Rotated shapes exceeding grid limits
  • Special constraints for certain pieces

🚀 What’s Next

Possible improvements:

  • Procedural level generation
  • Mobile touch support improvements
  • Animations (Framer Motion)
  • Score / timer system
  • Multiplayer or daily challenges

đź’¬ Feedback

https://mosaic-grid.vercel.app/

If you’re into puzzle games or frontend architecture, I’d love your feedback!

What would you improve in this kind of system?
How would you handle state or performance differently?

webdev #nextjs #react #gamedev #javascript #indiedev

Top comments (0)