Make a Website

Ludo Game Source Code in HTML and JavaScript (Free, with Real Rules and a Computer Player)

A complete Ludo game in one index.html: 2 or 4 players, the real rules (a 6 to start, captures, safe stars, exact roll home, extra turns) and a computer player, tested move by move. Plus how to put it in an Android app, and what Flutter, Unity and online Ludo add.

A Ludo board game made with HTML and JavaScript

Ludo is one of the most played board games in the world, and building it is a great JavaScript project: a board, dice, turns, captures and a computer opponent. This guide gives you a complete Ludo game in one index.html file with the real rules, for 2 or 4 players, with an optional computer player. We tested it move by move with fixed dice.

The rules this game follows

RuleHow it works
StartEach player has 4 tokens in their base. A token leaves the base only with a 6, onto its colour's start square.
MoveTokens move clockwise by the number on the dice, once around the board (51 squares), then up their own coloured home column.
Extra turnRolling a 6, capturing a token, or bringing a token home gives another roll.
CaptureLanding on an opponent's token sends it back to its base.
Safe squaresStart squares and star squares are safe: tokens there cannot be captured.
HomeA token needs the exact number to reach home. The first player with all 4 tokens home wins.

House rules vary (some lose a turn after three 6s in a row, some let two tokens form a block); the code below is easy to change for those.

The full Ludo source code (HTML5 and JavaScript)

Save it as index.html and open it in any browser, on a computer or a phone. Click the dice, then the glowing token you want to move.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ludo</title>
<style>
  body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #f1f5f9; text-align: center; }
  #board { display: grid; grid-template-columns: repeat(15, 1fr); grid-template-rows: repeat(15, 1fr); width: min(94vw, 540px); aspect-ratio: 1;
           margin: 10px auto; border: 3px solid #1e293b; background: #fff; }
  .cell { border: 1px solid #e2e8f0; display: flex; align-items: center; justify-content: center; min-width: 0; min-height: 0;
          overflow: hidden; flex-wrap: wrap; gap: 1px; font-size: 12px; color: #94a3b8; }
  .red { background: #fecaca; } .green { background: #bbf7d0; } .yellow { background: #fef08a; } .blue { background: #bfdbfe; }
  .base.red { background: #ef4444; } .base.green { background: #22c55e; } .base.yellow { background: #eab308; } .base.blue { background: #3b82f6; }
  .centre { background: #1e293b; color: #fff; font-weight: 800; font-size: 14px; }
  .spot { background: #fff !important; border-radius: 50%; }
  .token { width: 40%; aspect-ratio: 1; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 1px 3px #0006; }
  .token.red { background: #dc2626; } .token.green { background: #16a34a; } .token.yellow { background: #ca8a04; } .token.blue { background: #2563eb; }
  .token.can { outline: 3px solid #0f172a; cursor: pointer; animation: pulse .8s infinite alternate; }
  @keyframes pulse { to { transform: scale(1.25); } }
  button, select { font: inherit; padding: 9px 16px; margin: 4px; }
  #dice { font-size: 28px; font-weight: 800; min-width: 60px; }
</style>
</head>
<body>
<h1>Ludo</h1>
<div>
  <select id="players"><option value="2">2 players</option><option value="4">4 players</option></select>
  <label><input type="checkbox" id="cpu" checked> Computer plays the others</label>
  <button id="new">New game</button>
</div>
<div id="board"></div>
<button id="dice">🎲</button>
<p id="msg"></p>

<script>
// The 52 squares of the track, clockwise, as [row, column] on a 15 x 15 board
const TRACK = [];
const run = (r, c, dr, dc, n) => { for (let i = 0; i < n; i++) TRACK.push([r + dr * i, c + dc * i]); };
run(6, 1, 0, 1, 5);  run(5, 6, -1, 0, 6); TRACK.push([0, 7]);
run(0, 8, 1, 0, 6);  run(6, 9, 0, 1, 6);  TRACK.push([7, 14]);
run(8, 14, 0, -1, 6); run(9, 8, 1, 0, 6); TRACK.push([14, 7]);
run(14, 6, -1, 0, 6); run(8, 5, 0, -1, 6); TRACK.push([7, 0]); TRACK.push([6, 0]);

const COLORS = ['red', 'green', 'yellow', 'blue'];
const START = [0, 13, 26, 39];               // each colour's first square on the track
const SAFE = [0, 8, 13, 21, 26, 34, 39, 47]; // start squares and star squares: no capture here
const HOME_COL = [[[7, 1], 0, 1], [[1, 7], 1, 0], [[7, 13], 0, -1], [[13, 7], -1, 0]];
const BASE = [[2, 2], [2, 11], [11, 11], [11, 2]];
const FINISH = 56;                           // 0-50 track, 51-55 home column, 56 home

let game;

// Run fn later, but only if the same game is still on (a new game cancels old timers)
function later(fn, ms) { const g = game; setTimeout(() => { if (g === game) fn(); }, ms); }

function newGame() {
  const n = +document.getElementById('players').value;
  const seats = n === 2 ? [0, 2] : [0, 1, 2, 3];
  const cpu = document.getElementById('cpu').checked;
  game = {
    players: seats.map((c, i) => ({ color: c, tokens: [-1, -1, -1, -1], cpu: cpu && i > 0 })),
    turn: 0, dice: 0, rolled: false, winner: null
  };
  document.getElementById('dice').textContent = '🎲';
  draw();
  say();
}

// Where a token stands on the board: [row, column], or null if it is home
function cellOf(color, pos, token) {
  if (pos === -1) { const [r, c] = BASE[color]; return [r + (token >> 1), c + (token & 1)]; }
  if (pos <= 50) return TRACK[(START[color] + pos) % 52];
  if (pos < FINISH) { const [[r, c], dr, dc] = HOME_COL[color]; const k = pos - 51; return [r + dr * k, c + dc * k]; }
  return null;
}

function canMove(pos, dice) {
  if (pos === -1) return dice === 6;
  return pos + dice <= FINISH;
}

function movable() {
  const p = game.players[game.turn];
  return p.tokens.map((pos, i) => (canMove(pos, game.dice) ? i : -1)).filter(i => i >= 0);
}

function roll() {
  if (game.winner || game.rolled) return;
  game.dice = 1 + Math.floor(Math.random() * 6);
  game.rolled = true;
  document.getElementById('dice').textContent = game.dice;
  const moves = movable();
  draw();
  if (!moves.length) { say('No move with ' + game.dice + '.'); later(nextTurn, 700); return; }
  if (game.players[game.turn].cpu) later(() => move(cpuChoice(moves)), 600);
  else say('Tap a glowing token.');
}

function move(i) {
  const p = game.players[game.turn];
  if (!game.rolled || !canMove(p.tokens[i], game.dice)) return;
  const from = p.tokens[i];
  p.tokens[i] = from === -1 ? 0 : from + game.dice;
  const pos = p.tokens[i];
  let bonus = game.dice === 6;

  // Capture: other colours on the same track square go back to base, unless it is a safe square
  if (pos <= 50) {
    const square = (START[p.color] + pos) % 52;
    if (!SAFE.includes(square)) {
      for (const o of game.players) {
        if (o === p) continue;
        o.tokens.forEach((op, k) => {
          if (op >= 0 && op <= 50 && (START[o.color] + op) % 52 === square) { o.tokens[k] = -1; bonus = true; }
        });
      }
    }
  }
  if (pos === FINISH) bonus = true;
  if (p.tokens.every(t => t === FINISH)) { game.winner = p; draw(); say(); return; }

  game.rolled = false;
  draw();
  if (bonus) { say('Roll again!'); if (p.cpu) later(roll, 600); }
  else nextTurn();
}

// The computer's choice: finish, capture, leave base, otherwise move the token that is furthest ahead
function cpuChoice(moves) {
  const p = game.players[game.turn];
  const score = i => {
    const from = p.tokens[i], to = from === -1 ? 0 : from + game.dice;
    if (to === FINISH) return 1000;
    if (to <= 50) {
      const sq = (START[p.color] + to) % 52;
      const hit = !SAFE.includes(sq) && game.players.some(o => o !== p && o.tokens.some(t => t >= 0 && t <= 50 && (START[o.color] + t) % 52 === sq));
      if (hit) return 900;
    }
    if (from === -1) return 800;
    return to;
  };
  return moves.reduce((a, b) => (score(b) > score(a) ? b : a));
}

function nextTurn() {
  game.rolled = false;
  game.turn = (game.turn + 1) % game.players.length;
  draw();
  say();
  if (game.players[game.turn].cpu) later(roll, 600);
}

function say(text) {
  const p = game.players[game.turn];
  const name = COLORS[p.color].toUpperCase();
  document.getElementById('msg').textContent = game.winner
    ? COLORS[game.winner.color].toUpperCase() + ' wins!'
    : name + (p.cpu ? ' (computer)' : '') + ': ' + (text || 'roll the dice.');
}

function draw() {
  const board = document.getElementById('board');
  board.innerHTML = '';
  const cells = [];
  for (let r = 0; r < 15; r++) for (let c = 0; c < 15; c++) {
    const d = document.createElement('div');
    d.className = 'cell';
    if (r < 6 && c < 6) d.classList.add('base', 'red');
    else if (r < 6 && c > 8) d.classList.add('base', 'green');
    else if (r > 8 && c > 8) d.classList.add('base', 'yellow');
    else if (r > 8 && c < 6) d.classList.add('base', 'blue');
    else if (r > 5 && r < 9 && c > 5 && c < 9) { d.classList.add('centre'); }
    board.appendChild(d);
    cells.push(d);
  }
  const at = (r, c) => cells[r * 15 + c];
  COLORS.forEach((col, i) => {
    at(...TRACK[START[i]]).classList.add(col);
    for (let k = 51; k < FINISH; k++) at(...cellOf(i, k)).classList.add(col);
  });
  SAFE.forEach(s => { if (!START.includes(s)) at(...TRACK[s]).textContent = 'β˜…'; });
  game.players.forEach(p => [0, 1, 2, 3].forEach(i => at(...cellOf(p.color, -1, i)).classList.add('spot')));

  const home = { 0: 0, 1: 0, 2: 0, 3: 0 };
  const canNow = game.rolled && !game.players[game.turn].cpu ? movable() : [];
  game.players.forEach((p, pi) => p.tokens.forEach((pos, i) => {
    if (pos === FINISH) { home[p.color]++; return; }
    const t = document.createElement('div');
    t.className = 'token ' + COLORS[p.color];
    if (pi === game.turn && canNow.includes(i)) { t.classList.add('can'); t.onclick = () => move(i); }
    at(...cellOf(p.color, pos, i)).appendChild(t);
  }));
  // Finished tokens: a count in each colour's side of the centre
  [[7, 6], [6, 7], [7, 8], [8, 7]].forEach(([r, c], col) => { if (game.players.some(p => p.color === col)) at(r, c).textContent = home[col]; });
}

document.getElementById('dice').onclick = () => { if (!game.players[game.turn].cpu) roll(); };
document.getElementById('new').onclick = newGame;
newGame();
</script>
</body>
</html>

How the code works

PartWhat it does
TRACKThe 52 squares of the shared track as row and column on a 15 × 15 grid, built from straight runs
START and SAFEWhere each colour enters the track, and the 8 squares where nobody can be captured
Token positionOne number per token: -1 in base, 0 to 50 on the track, 51 to 55 in the home column, 56 home. Every rule becomes simple arithmetic.
cellOf()Turns a colour and a position into the square to draw the token on
canMove()A 6 to leave the base, and never past home: the exact-roll rule
move()Moves the token, sends opponents on the same square home (unless it is safe), and gives an extra turn for a 6, a capture or a token home
cpuChoice()The computer brings a token home if it can, otherwise captures, otherwise leaves the base, otherwise moves its most advanced token
later()Delays the computer's moves so you can follow them, and cancels them if you start a new game

How to create your own Ludo game: next steps

  • Three 6s rule: count sixes in a row in roll() and end the turn on the third
  • Blocks: in move(), stop a token passing a square where an opponent has two tokens
  • Animation: move the token one square at a time with a short delay instead of jumping
  • Sound and a nicer dice: a roll sound and a dice drawn with dots
  • A smarter computer: avoid ending on squares an opponent can reach with one roll

Ludo game source code on GitHub

You will find many Ludo projects on GitHub, in JavaScript, Python, Java and C#. Most stop at a board and dice; the hard parts are the rules above and keeping turns right when extra rolls, captures and the computer all happen together. Whatever you start from, test those cases. Our version was checked with fixed dice for each rule. Put your own version on GitHub with a short README and a screenshot.

Ludo in Android Studio

The quickest way to put this game in an Android app is to load index.html from the app's assets folder in a WebView, as shown in our website to app guide. A native version in Kotlin or Java keeps the same logic (the track, the positions and the rules) and draws the board on a Canvas instead.

Ludo in Flutter and Unity

  • Flutter: there are open-source Flutter Ludo games on GitHub; the board becomes a grid of widgets and the rules stay the same. Good if you want one code base for Android and iPhone.
  • Unity: free and paid Ludo kits exist on the Unity Asset Store and GitHub, with 3D dice and animations. Unity adds polish but also a much bigger app and a steeper learning curve.

What apps like Ludo Master and Ludo King add

The popular Ludo apps are the same rules plus a lot of infrastructure: online play with friends or strangers, private rooms with codes, chat and voice during the game, coins and rewards, leaderboards, and a server that rolls the dice so nobody can cheat. That server part is what takes months: matchmaking, keeping two or four phones in sync, handling players who drop out, and stopping cheats.

Online Ludo with chat and video, ready to publish

Our chat apps include Ludo played live between people inside the chat, on your own servers:

To publish browser games like this one on your own games website, our HTML5 game portal script gives you game pages, categories, search and an admin panel, and our guide on making a game website like Poki explains how such sites earn.

Where to go next

Questions people ask

How do I create a Ludo game?
Draw a 15 by 15 board, store the 52-square track, give each token a position number (-1 in base, 0 to 50 on the track, 51 to 55 in the home column, 56 home), and code the rules: a 6 to start, captures, safe squares, exact roll home and extra turns. The full HTML and JavaScript is in this guide.
Where can I find Ludo game source code in JavaScript?
This guide has a complete, tested Ludo game in one index.html file with HTML5 and JavaScript, for 2 or 4 players with a computer player. GitHub also has many JavaScript Ludo projects.
What are the rules of Ludo?
Each player has 4 tokens and needs a 6 to bring one out. Tokens go once around the board and up their home column. Landing on an opponent sends it home, except on safe squares. A 6, a capture or a token home gives another roll, and you need the exact number to finish.
How do I make a Ludo game in Android Studio?
The fastest way is to load an HTML Ludo game from the app's assets in a WebView. A native version keeps the same track, positions and rules in Kotlin or Java and draws the board on a Canvas.
Is there a free Ludo game source code for Unity?
Yes, there are free and paid Ludo kits on the Unity Asset Store and on GitHub. Unity gives 3D dice and animations, but the app is bigger and harder to change than an HTML or Kotlin version.
Can I make a Ludo game in Flutter?
Yes. Open-source Flutter Ludo projects exist on GitHub. The board becomes a grid of widgets and the rules stay the same, with one code base for Android and iPhone.
How does the computer player choose its move?
In this code it brings a token home if it can, otherwise captures an opponent, otherwise brings a token out of the base, otherwise moves its most advanced token.
How do online Ludo games stop cheating?
The server rolls the dice and checks every move, so a changed app cannot choose its own numbers. Phones only show what the server decided.
Can I sell a Ludo game I make?
Yes, if the code and graphics are yours or licensed for it. Ludo is a traditional game, but names such as Ludo King are trademarks, so give your app its own name and design.
How do I add online multiplayer to Ludo?
You need a real-time server that creates rooms, rolls the dice, checks moves and sends them to all players, plus reconnecting for players who drop out. Our chat apps include Ludo played live inside the chat.

Written by Habib Baloch

I build Android apps, websites and Ubuntu servers, and write down exactly how I did it.

Ask me about this guide →