Skip to main content

Overview

Mokepon is a full-stack web game built with vanilla JavaScript on the client side and Node.js/Express on the server side. The architecture follows a clear separation between client and server, with a RESTful API handling communication.

Directory Structure

mokepon-game/
├── index.js                 # Express server entry point
├── package.json            # Project dependencies and scripts
├── Procfile               # Heroku deployment configuration
├── .env                   # Environment variables (local only)
└── public/                # Static client-side files
    ├── index.html         # Game UI structure
    ├── js/
    │   └── mokepon.js     # Client-side game logic
    ├── css/
    │   ├── reset.css      # CSS reset/normalize
    │   └── styles.css     # Game styling
    └── assets/
        ├── images/        # Mokepon sprites and backgrounds
        ├── icons/         # UI icons
        └── screenshots/   # Marketing/documentation images

Architecture

Client-Server Separation

The game uses a clear separation between frontend and backend: Client Side (/public)
  • Handles user interface and rendering
  • Manages game state and animations
  • Uses HTML Canvas for map rendering
  • Communicates with server via REST API
Server Side (index.js)
  • Express.js server managing game sessions
  • Handles player registration and tracking
  • Manages CPU opponents
  • Provides RESTful API endpoints
  • Implements security middleware
// Client determines server URL dynamically
const SERVER_URL = window.location.origin + "/";
This approach allows the game to work seamlessly in both development (localhost:3000) and production environments.

Key Files

Server: index.js

The main Express server file that handles all backend logic.

Core Classes

Player Class
class Player {
  constructor(id) {
    this.id = id;
    this.xPercent = undefined;  // Normalized X position (0-100%)
    this.yPercent = undefined;  // Normalized Y position (0-100%)
    this.mokepon = null;
    this.attacks = [];
    this.isCPU = false;
  }

  assignMokepon(mokepon) {
    this.mokepon = mokepon;
  }

  updateMokeponPosition(xPercent, yPercent) {
    this.xPercent = xPercent;
    this.yPercent = yPercent;
  }

  assignAttacks(attacks) {
    this.attacks = attacks;
  }
}
Player positions are stored as percentages (0-100%) rather than absolute pixels. This ensures the game works correctly across different screen sizes.
Mokepon Class
class Mokepon {
  constructor(name) {
    this.name = name;
    this.type = getMokeponType(name);
  }
}

function getMokeponType(name) {
  switch (name) {
    case "Hipodoge":
    case "Pydos":
      return "💧";  // Water type
    case "Capipepo":
    case "Tucapalma":
      return "🌱";  // Plant type
    case "Ratigueya":
    case "Langostelvis":
      return "🔥";  // Fire type
    default:
      return "💧";
  }
}

Combat System

The game uses a rock-paper-scissors style combat system:
const combatRules = {
  "💧": "🔥",  // Water beats Fire
  "🔥": "🌱",  // Fire beats Plant
  "🌱": "💧",  // Plant beats Water
};

Middleware Stack

app.use(express.static("public"));        // Serve static files
app.use(cors(corsOptions));               // Enable CORS
app.use(express.json());                   // Parse JSON bodies
app.use(compression());                    // Compress responses
app.use(helmet({ /* CSP config */ }));    // Security headers
The middleware order is important. Static file serving comes first, followed by CORS, then body parsing, and finally security headers.

Client: public/js/mokepon.js

The client-side game logic handling UI, Canvas rendering, and API communication.

Client-Side Mokepon Class

class Mokepon {
  static get DEFAULT_SIZE() {
    return 80;
  }

  static get BASE_SPEED() {
    return 7;
  }

  constructor(name, inputId, type, imageSrc, imageAlt, mapImage, attacks) {
    this.name = name;
    this.inputId = inputId;
    this.type = type;
    this.imageSrc = imageSrc;
    this.imageAlt = imageAlt;
    this.mapImage = new Image();
    this.mapImage.src = mapImage;
    this.width = Mokepon.DEFAULT_SIZE;
    this.height = Mokepon.DEFAULT_SIZE;
    this.x = 0;
    this.y = 0;
    this.speedX = 0;
    this.speedY = 0;
    this.attacks = attacks;
    this.isCPU = false;
  }

  renderPet() {
    canvas.drawImage(this.mapImage, this.x, this.y, this.width, this.height);
    
    // If this is a CPU Mokepon, add a label
    if (this.isCPU) {
      canvas.font = `${Math.max(12, this.width / 6)}px Arial`;
      canvas.fillStyle = "white";
      canvas.fillText("CPU", this.x + this.width / 2, this.y - 5);
    }
  }
}

UI: public/index.html

The HTML structure defines three main game sections:
  1. Character Selection - Choose your Mokepon
  2. Map Navigation - Move around and find opponents
  3. Battle Screen - Turn-based combat interface

Styling: public/css/

  • reset.css: Normalizes browser default styles
  • styles.css: Custom game styling with modern CSS features

Assets: public/assets/

  • images/: Mokepon character sprites, map backgrounds
  • icons/: UI icons and decorative elements
  • screenshots/: Game screenshots for documentation

API Endpoints

The server exposes the following RESTful API endpoints:
MethodEndpointDescription
GET/joinRegister a new player, returns player ID
POST/mokepon/:playerIdAssign a Mokepon to a player
GET/mokepon/:playerId/safePositionGet a safe spawn position
POST/mokepon/:playerId/positionUpdate player position, get enemies
POST/mokepon/:playerId/attacksSubmit player attack sequence
GET/mokepon/:playerId/attacksGet player/CPU attack list
GET/mokepon/:playerId/cpu-attackGet CPU’s next attack
POST/mokepon/:playerId/reset-attacksReset CPU attacks for new round
POST/mokepon/:playerId/check-advantageCheck type advantage
DELETE/player/:playerIdRemove player from game
GET/player/:playerIdRemove player (Beacon API)
The GET /player/:playerId endpoint exists to support the Beacon API, which can only make GET requests when the page unloads.

CPU Opponents

The server automatically generates 3 CPU opponents when the first player joins:
const CPU_COUNT = 3;

function generateCPUMokepons() {
  const mokeponNames = [
    "Hipodoge", "Capipepo", "Ratigueya",
    "Pydos", "Tucapalma", "Langostelvis"
  ];

  // Shuffle and select 3 random Mokepons
  const shuffled = [...mokeponNames].sort(() => 0.5 - Math.random());
  const selectedNames = shuffled.slice(0, CPU_COUNT);

  // Create CPU players and position them on the map
  for (let i = 0; i < CPU_COUNT; i++) {
    const cpuPlayer = new Player(`cpu-${i}-${Date.now()}`);
    cpuPlayer.isCPU = true;
    cpuPlayer.assignMokepon(new Mokepon(selectedNames[i]));
    // Position evenly across the map...
  }
}

Security Features

The server implements multiple security measures:
  1. Helmet: Sets secure HTTP headers including CSP
  2. CORS: Restricts origins in production
  3. Compression: Reduces response size
  4. Input Validation: Validates all user inputs
  5. Error Handling: Centralized error middleware
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
        // ... more directives
      },
    },
  })
);

Next Steps

  • Learn how to deploy the game
  • Review the setup guide for development environment
  • Explore the source code to understand implementation details

Build docs developers (and LLMs) love