🧩 PatternLock - Project Story

💡 Inspiration

The inspiration for PatternLock came from observing the massive success of daily puzzle games like Wordle and the New York Times' puzzle suite. We noticed a gap in the market: there wasn't a compelling daily sliding puzzle game that combined competitive elements with social sharing.

We asked ourselves: "What if we could create a puzzle game that's fair for everyone, encourages daily engagement, and sparks conversations in Reddit communities?"

The sliding puzzle (15-puzzle) is a classic that's been around since 1874, but it had never been properly adapted for the social media age. We saw an opportunity to:

  • Make it deterministic (everyone gets the same puzzle)
  • Add competitive elements (global leaderboard)
  • Integrate Reddit culture (themed emojis, shareable results)
  • Ensure mobile-first design (most Reddit users are on mobile)

🎓 What We Learned

1. Mathematical Challenges: Solvability Algorithm

The biggest technical challenge was ensuring every puzzle is solvable. Not all random arrangements of a 15-puzzle can be solved!

The Math Behind It:

A sliding puzzle configuration is solvable if and only if:

$$ \text{inversions} + \text{blank_row} \equiv 0 \pmod{2} $$

Where:

  • Inversions: Number of pairs $(i, j)$ where $i < j$ but $\text{tile}[i] > \text{tile}[j]$
  • Blank row: Row number of the empty tile (counting from bottom, 0-indexed)

Implementation:

function isSolvable(board: number[]): boolean {
  let inversions = 0;
  const flatBoard = board.filter(n => n !== 0);

  for (let i = 0; i < flatBoard.length; i++) {
    for (let j = i + 1; j < flatBoard.length; j++) {
      if (flatBoard[i] > flatBoard[j]) {
        inversions++;
      }
    }
  }

  const emptyRow = Math.floor(board.indexOf(0) / 4);
  const blankRowFromBottom = 3 - emptyRow;

  return (inversions + blankRowFromBottom) % 2 === 0;
}

This was fascinating to implement and taught us about permutation parity and combinatorial game theory.

2. Deterministic Random Generation

To ensure all players worldwide get the same puzzle, we implemented a Linear Congruential Generator (LCG):

$$ X_{n+1} = (a \cdot X_n + c) \bmod m $$

Where:

  • $a = 1664525$ (multiplier)
  • $c = 1013904223$ (increment)
  • $m = 2^{32}$ (modulus)
  • $X_0 = \text{seed}$ (UTC date as YYYYMMDD)

This taught us about pseudorandom number generation and the importance of reproducibility in competitive games.

3. Performance Optimization

We learned to optimize for mobile devices:

  • CSS Grid over JavaScript positioning (60fps animations)
  • CSS Variables for dynamic theming (no re-renders)
  • LocalStorage over backend calls (instant load)
  • Redis Sorted Sets for leaderboard ($O(\log n)$ insertions)

4. UX Design Principles

Through iteration, we learned:

  • Visual hierarchy matters: Target tiles should be grayscale (reference), not competing with the main puzzle
  • Always-visible is better than modal: Users shouldn't have to click repeatedly to see the target
  • Subtle feedback works: Color-coded performance (green/yellow/red) without being noisy
  • Mobile-first is essential: 48px+ touch targets, no hover states

🛠 How We Built It

Architecture

Frontend:

  • React 19 with TypeScript for type safety
  • Custom hooks for clean state management
  • CSS Grid for responsive layouts
  • Framer Motion (considered but removed for performance)

Backend:

  • Hono (fast, lightweight API framework)
  • Redis for leaderboard storage
  • Devvit Platform for Reddit integration

Key Technical Decisions:

  1. Client-side puzzle generation: No backend needed for puzzle logic
  2. Seeded randomness: Ensures fairness and reproducibility
  3. Redis Sorted Sets: Efficient leaderboard with $O(\log n)$ complexity
  4. CSS-only animations: Better performance than JavaScript

Development Process

Phase 1: Core Mechanics (Day 1-2)

  • Implemented sliding puzzle logic
  • Built solvability checker
  • Created deterministic shuffle algorithm
  • Added move validation

Phase 2: Daily System (Day 3-4)

  • Implemented seeded random generator
  • Created 5 target patterns
  • Added date-based seed generation
  • Built streak tracking

Phase 3: Polish & Features (Day 5-7)

  • Added 6 theme system
  • Implemented global leaderboard
  • Created always-visible mini target
  • Added emoji mode (Reddit-themed)
  • Refined mobile UX

Phase 4: Optimization (Day 8-9)

  • Performance tuning
  • Mobile testing
  • Accessibility improvements
  • Bug fixes

Code Structure

src/
├── client/
│   ├── components/     # React components
│   ├── hooks/          # Custom hooks
│   ├── utils/          # Helper functions
│   └── index.css       # Styling
├── server/
│   └── routes/         # API endpoints
└── shared/
    └── api.ts          # Type definitions

🚧 Challenges We Faced

Challenge 1: Solvability Guarantee

Problem: Random shuffles often created unsolvable puzzles.
Solution: Implemented the inversion counting algorithm. We shuffle randomly, then check solvability. If unsolvable, we swap two tiles to fix parity.
Learning: Sometimes you need to understand the math deeply before coding.

Challenge 2: Deterministic Across Devices

Problem: JavaScript's Math.random() isn't seeded, so different devices got different puzzles.
Solution: Implemented our own LCG with date-based seeding. Now everyone gets the same puzzle on the same day.
Learning: Don't trust built-in randomness for reproducibility.

Challenge 3: Mobile Performance

Problem: Initial version had janky animations on mobile devices.

Solution:

  • Switched from JavaScript animations to CSS transitions
  • Used transform instead of top/left (GPU-accelerated)
  • Reduced bundle size by removing heavy dependencies

Learning: Mobile performance requires different optimization strategies than desktop.

Challenge 4: Leaderboard Scalability

Problem: How to handle millions of players efficiently?

Solution: Redis Sorted Sets with composite scoring:

$$ \text{score} = \text{moves} \times 1000 + \text{time} $$

This allows sorting by moves first, then time, in a single data structure.

Learning: Choose the right data structure for your access patterns.

Challenge 5: Visual Hierarchy

Problem: Orange target tiles competed with the main puzzle for attention.
Solution: Made target tiles grayscale with reduced opacity. The main puzzle is now clearly the primary focus.
Learning: Sometimes less is more. Reference elements should look like references.

Challenge 6: Theme System Without Re-renders

Problem: Changing themes caused expensive re-renders.
Solution: Used CSS variables that update via document.documentElement.style.setProperty(). No React re-renders needed!
Learning: CSS variables are powerful for dynamic theming.

🎨 Design Philosophy

1. Reddit-First

  • Shareable results format (like Wordle)
  • Reddit-themed emojis (🤖⬆️⬇️💬)
  • Community-driven (leaderboard, stats)

2. Mobile-First

  • 48px+ touch targets
  • No hover states
  • Responsive grid
  • Fast load times

3. Competitive Yet Fair

  • Same puzzle for everyone
  • Transparent scoring
  • Global leaderboard
  • Performance tracking

4. Delightful UX

  • 6 gorgeous themes
  • Smooth animations
  • Subtle feedback
  • Always-visible target

📊 Technical Highlights

Algorithms Implemented

  1. Fisher-Yates Shuffle (with seeded random)
  2. Inversion Counting (solvability check)
  3. Linear Congruential Generator (deterministic random)
  4. Breadth-First Search (optimal move calculation)

Data Structures Used

  1. Arrays (board state)
  2. Sorted Sets (Redis leaderboard)
  3. Hash Maps (theme storage)
  4. LocalStorage (preferences)

Performance Metrics

  • Load Time: <1 second
  • FPS: 60 (smooth animations)
  • Bundle Size: Optimized
  • Leaderboard Query: $O(\log n)$

🏆 What Makes PatternLock Special

1. Fairness Through Math

Every player gets the same solvable puzzle. No RNG advantage.

2. Reddit Culture Integration

Emoji mode with Reddit-themed icons shows we understand the platform.

3. Mobile Excellence

Touch-optimized with 48px+ targets, perfect responsive layout.

4. Community-Driven

Global leaderboard and shareable results drive engagement.

5. Visual Polish

6 themes, smooth animations, subtle feedback, professional design.

🚀 Future Enhancements

Post-Hackathon Ideas

  1. Custom Puzzles: Let users create and share puzzles
  2. Tournaments: Weekly competitions with prizes
  3. Achievements: Unlock badges for milestones
  4. Social Features: Friend challenges, team competitions
  5. More Themes: Seasonal, community-created
  6. Accessibility: Screen reader support, colorblind modes

📝 Lessons Learned

  1. Math matters: Understanding the theory makes implementation easier
  2. Performance is UX: Smooth animations feel more polished
  3. Mobile-first wins: Most users are on mobile
  4. Less is more: Grayscale targets, subtle feedback
  5. Community drives engagement: Leaderboards and sharing are powerful

🎯 Conclusion

PatternLock combines classic puzzle mechanics with modern social features. By ensuring fairness through deterministic generation, optimizing for mobile, and integrating Reddit culture, we've created a daily game that's both competitive and fun.

The journey taught us about algorithms, performance optimization, UX design, and the importance of understanding your platform. We're proud of what we built and excited to see the Reddit community engage with it!


Built with: React, TypeScript, Hono, Redis, Devvit
Math: Permutation parity, LCG, graph theory
Design: Mobile-first, Reddit-themed, community-driven

Status: Ready to compete! 🏆

Built With

Share this project:

Updates