blog post
Gamifying My Portfolio: How I Built ‘The Grid’ (And Why You Should Play It)
By Luis Velazquez Bilbao • Apr 24, 2025
Table of Contents
Introduction

As a software engineer, I'm constantly looking for innovative ways to challenge myself technically while creating something fun and meaningful. The Grid is one such project—a browser-based mini-game that combines front-end and back-end engineering, game design, and even educational interactivity. But this post isn't about how fun the game is (although it is fun). It's a deep dive into how I built it: the tools I used, the architectural choices I made, and how I balanced performance, UX, and educational engagement in a unique tech-meets-gameplay project.

What is "The Grid"?

The Grid is a dynamic maze puzzle where the player navigates a series of interconnected rooms using a fog-of-war minimap and logical pathfinding (via BFS / Dijkstra algorithms). The twist? It's also a learning game.

In the Recruiter/Student mode, players must answer software engineering challenges—ranging from version control to algorithms—to progress, unlock rewards, or access other parts of my portfolio. Some rewards include classic mini arcade games like Snake, Pong and Space Invaders (built-in!), which also unlock secret keys.

About the Stack I used

The project leverages a modern, performant full-stack web stack:

Core Technologies

  • Nuxt 3: Full-stack framework providing SSR, routing, and development experience
  • TypeScript: Type safety for complex game logic and data structures
  • Tailwind CSS 4: Utility-first styling for responsive design
  • Vanilla JavaScript: Performance-critical game loops and DOM manipulations

Additional Tools & Libraries

  • Nuxt i18n: Multi-language support (English/Spanish)
  • JSON data files: Level configuration and challenge content
  • Web Audio API: Sound effects and background music
  • Canvas API: 2D rendering for minimap and visual effects
  • CSS3 Transforms: 3D visual perspectives and animations
How did I build it?

🧠 Game Mechanics & Architecture

The Grid follows a modular architecture separating concerns into distinct layers:

    
typescript
Copy
// Core game state management interface GameState { currentRoom: Room; playerPosition: Position; inventory: Item[]; discoveredRooms: Set; gameStatus: 'playing' | 'won' | 'lost'; } // Room structure from JSON data interface Room { id: string; connections: { [direction: string]: string }; items: Item[]; enemies: Enemy[]; challenges: Challenge[]; walls: WallConfiguration; }

Game Engine Core

The Game Engine Core is the central system that orchestrates everything in The Grid. Think of it as the "brain" that coordinates all the different parts of the game.

  • Game Loop : The continuous cycle that updates and renders the game
  • State Management : Keeping track of what's happening in the game
  • Input Handling : Responding to player actions (keyboard, mouse, touch)
  • Rendering : Drawing everything on screen
  • Asset Management : Loading and managing images, sounds, etc.
    
typescript
Copy
class GameEngine { private gameState: GameState; private renderer: Renderer; private inputHandler: InputHandler; constructor() { this.initializeGame(); this.startGameLoop(); } private gameLoop(): void { this.update(); this.render(); requestAnimationFrame(() => this.gameLoop()); } public movePlayer(direction: Direction): boolean { const currentRoom = this.gameState.currentRoom; const nextRoomId = currentRoom.connections[direction]; if (nextRoomId && this.canEnterRoom(nextRoomId)) { this.transitionToRoom(nextRoomId); this.updateMinimap(); return true; } return false; } }

Level Construction with JSON

All level data (rooms, doors, keys, enemies, etc.) is defined using structured JSON files. This enables dynamic, scalable level generation:

    
json
Copy
{ 'room': 3, 'wallType': 'brick3', 'gridRows': 9, 'gridCols': 5, 'buildings': [], 'enemies': [ { 'name': 'enemyKyle', 'options': { 'position': 23 } } ], 'doors': [ { 'type': 'default', 'needKey': '', 'positionInWall': 3, 'orientation': 'up', 'size': 1, 'isOpen': true, 'enterRoom': 1 } ], 'options': {} }

The engine reads this data and dynamically creates game rooms, entities, and links between them.

Pathfinding with BFS & Dijkstra

Each maze is generated using graph traversal algorithms like Breadth-First Search (BFS) and Dijkstra, depending on difficulty level and room complexity.

    
typescript
Copy
// Simplified BFS for maze traversal function bfs(startRoomId: string) { const queue = [startRoomId]; const visited = new Set(); while (queue.length) { const current = queue.shift(); visited.add(current); for (let neighbor of getConnectedRooms(current)) { if (!visited.has(neighbor)) { queue.push(neighbor); } } } }

Responsive 2D/3D Rooms

Rooms are rendered in both top-down 2D and angled 3D-like views using layered divs and CSS transforms:

    
component
Copy
<div class='room-container perspective-3d'> <div class='room-wall' style='transform: rotateX(30deg) rotateY(45deg)'> <!-- Room assets, player, items --> </div> </div>

This hybrid design makes the game visually interesting without relying on any external game engine.

Mini-map with Fog-of-War

The minimap only reveals rooms once the player visits them. Each room's discovery state is updated in real-time:

    
typescript
Copy
class MinimapRenderer { private discoveredRooms: Set = new Set(); private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; public revealRoom(roomId: string): void { this.discoveredRooms.add(roomId); this.redrawMinimap(); } private drawRoom(room: Room, position: Point): void { if (!this.discoveredRooms.has(room.id)) { this.ctx.fillStyle = '#1a1a1a'; // Fog of war } else { this.ctx.fillStyle = room.isCurrentRoom ? '#00ff00' : '#444'; } this.ctx.fillRect(position.x, position.y, 20, 20); this.drawConnections(room, position); } }

Educational Challenges with AI-Generated Questions

Challenges are stored in a JSON format and categorized by difficulty. They're dynamically inserted into the game based on player interactions:

    
json
Copy
{ 'difficulty': 'easy', 'category': 'version control', 'question': 'Which git command creates a new branch?', 'options': ['git commit', 'git merge', 'git branch'], 'answer': 'git branch', 'explanation': 'Use `git branch` to create and manage branches.' }

🕹️ Gameplay Features

  • 🎓 Recruiter/Student Mode : Educational quizzes earn rewards.
  • 🗝️ Item Collection : Keys, doors, and NPCs scattered in rooms.
  • 🕹️ Arcade Games : Snake, Pong & Space Invaders unlock hidden areas.
  • 🔄 Dynamic Levels : JSON-powered and easily customizable.
  • 🌐 Multilingual : i18n-powered EN/ES language toggle.

📸 Screenshots

Here are some snapshots from development showcasing various room views, challenges, and the arcade mini-games:

Software Engineering challenges that the player must answer to beat the enemies.

The player can see the blue key in the 2D view.

The player can see the arcade games (3D view).

The player can play Space Invaders in the arcade machine.

The player can switch between 2D and 3D views, change difficulty for challenges, language, and more.

What problems am I trying to solve?

The Portfolio Problem

Traditional developer portfolios suffer from several issues:

  • Static presentation : Limited engagement and interaction
  • Information overload : Recruiters often skim through numerous similar portfolios
  • Lack of skill demonstration : Showing code is different from demonstrating problem-solving in action
  • One-size-fits-all approach : No customization for different skill levels or interests

The Solution: Gamified Portfolio Experience

The Grid addresses these challenges by:

  • Interactive engagement : Players actively participate rather than passively consume
  • Progressive skill demonstration : Challenges scale with difficulty levels (easy, medium, hard)
  • Memorable experience : Gaming creates lasting impressions compared to static websites
  • Multi-dimensional showcase : Demonstrates frontend, backend, game development, and algorithmic thinking
What were some of my design decisions?

1. JSON-Driven Level Design

Decision: Use JSON files for level configuration instead of hardcoded levels.
Reasoning: Enables easy level editing, AI-generated content integration, and separation of game logic from content.

2. Modular Challenge System

Decision: Create a pluggable challenge system supporting multiple types (algorithmic, code review, arcade games).
Reasoning: Allows for diverse content types and easy expansion without core engine changes.

3. Progressive Difficulty Scaling

Decision: Implement difficulty levels that affect challenge complexity and game mechanics.
Reasoning: Accommodates different skill levels from junior developers to senior engineers.

4. Responsive 2D/3D Hybrid Rendering

Decision: Combine 2D minimap with 3D-perspective room views using CSS transforms.
Reasoning: Provides spatial awareness while maintaining performance on mobile devices.

What did I use to solve the problem?

Navigation & Pathfinding

  • BFS algorithm for shortest path calculations
  • Dijkstra's algorithm for weighted pathfinding with locked doors
  • Matrix-based room representation for efficient spatial calculations

Asset Management

    
typescript
Copy
class AssetLoader { private assets: Map = new Map(); public async preloadAssets(assetList: AssetManifest): Promise { const loadPromises = assetList.map(asset => this.loadAsset(asset)); await Promise.all(loadPromises); } private async loadAsset(asset: AssetInfo): Promise { return new Promise((resolve, reject) => { if (asset.type === 'image') { const img = new Image(); img.onload = () => { this.assets.set(asset.id, img); resolve(); }; img.src = asset.url; } // Similar logic for audio assets }); } }

State Management

    
typescript
Copy
// Reactive state management using Nuxt's composables export const useGameState = () => { const gameState = ref({ currentRoom: null, playerPosition: { x: 0, y: 0 }, inventory: [], discoveredRooms: new Set(), gameStatus: 'playing' }); const movePlayer = (direction: Direction) => { // Game logic here gameState.value = { ...gameState.value, /* updated state */ }; }; return { gameState: readonly(gameState), movePlayer }; };
What packages did I use to build the game?

Core Dependencies

    
json
Copy
{ 'dependencies': { '@nuxtjs/i18n': '^9.5.5', '@prisma/client': '^6.7.0', 'nuxt': '^3.16.2', 'three': '^0.175.0', 'vue': '^3.5.13', 'vue-router': '^4.5.0' }, 'devDependencies': { '@nuxtjs/tailwindcss': '^6.13.2', '@types/node': '^22.14.1' }

Custom Solutions

Rather than relying heavily on external libraries, I implemented core game systems from scratch:

  • Custom game engine : Built specifically for maze navigation
  • Vanilla Canvas API : For minimap rendering and visual effects
  • Native Web Audio API : For sound management and audio effects
  • CSS3 animations : For smooth transitions and visual feedback

This approach provided better performance control and reduced bundle size while demonstrating low-level web API knowledge.

What did I learn?

Technical Insights

  • Game State Management : Complex state synchronization between UI, game logic, and rendering systems
  • Performance Optimization : Balancing visual quality with smooth 60fps gameplay on mobile devices
  • Responsive Game Design : Adapting game interfaces for touch and keyboard inputs across device sizes
  • Asset Pipeline Management : Implementing efficient preloading strategies for seamless user experience

Design Insights

  • User Experience Flow : Balancing game difficulty to maintain engagement without frustration
  • Progressive Disclosure : Revealing game mechanics gradually to avoid overwhelming new players
  • Accessibility Considerations : Ensuring the game works for users with different abilities and device capabilities

Project Management

  • Scope Management : Iterative development approach to ship MVP while planning advanced features
  • Content Strategy : Balancing educational content with entertainment value
  • Testing Strategies : Cross-browser and cross-device testing for consistent experience

Technical Improvements

  • WebGL Rendering : Implement WebGL for more sophisticated 3D graphics and better performance
  • Service Worker Integration : Add offline capability and background asset updates
  • WebRTC Multiplayer : Enable collaborative maze-solving with other players
  • Advanced AI : Implement smarter enemy behavior using pathfinding algorithms

Feature Enhancements

  • Level Editor UX : More intuitive visual tools for level creation
  • Social Features : Leaderboards, achievement sharing, and challenge creation
  • Advanced Analytics : Heat maps of player movement and behavior analysis
  • Content Management : Database-driven content system for easier updates

Architecture Refinements

  • Micro-frontend Architecture : Separate game engine from portfolio content for reusability
  • Event-Driven System : Implement pub/sub pattern for better component decoupling
  • Testing Coverage : Comprehensive unit and integration testing for game logic
  • Performance Monitoring : Real-time performance metrics and optimization suggestions
Conclusions

Building The Grid has been an exercise in combining multiple disciplines: game development, web development, user experience design, and technical communication. The project successfully demonstrates several key competencies:

Technical Achievements

  • Full-stack development : Seamless integration of frontend and backend technologies
  • Game development : Custom engine implementation with smooth gameplay mechanics
  • Performance optimization : Responsive design running smoothly across devices
  • System architecture : Modular, maintainable codebase with clear separation of concerns

Professional Impact

The Grid serves as more than just a portfolio piece—it's a functional demonstration of problem-solving abilities, technical creativity, and attention to user experience. By gamifying the recruitment process, it creates memorable interactions that traditional portfolios cannot achieve.

Future Potential

The modular architecture and JSON-driven content system make The Grid extensible for various use cases: educational platforms, technical interviews, team building exercises, and developer onboarding programs. The project showcases the intersection of technical skill and creative thinking—qualities essential for senior software engineering roles in today's competitive market.

🎯 Goal & Final Thoughts

The Grid isn't just a portfolio piece. It's an interactive journey for recruiters and students alike to explore my technical abilities, creative thinking, and attention to UX/game design.

By blending serious software engineering with gamified learning and nostalgia-driven rewards, I aimed to make a strong, memorable first impression on anyone exploring my work.

Check out "The Grid " here:
https://libe.dev/grid/

Thank you for reading... Happy coding! 🚀

Luis Velazquez Bilbao
Software Engineer