Tier 0 — Absolute Beginner

Mad Libs Generator

Fill in the blanks and generate silly stories — template literals, regex replacement, and interactive readline.

🐍 Python 🟨 JavaScript

1. The Problem

Build a Mad Libs game: show a story template with blank categories (noun, verb, adjective...), prompt the player to fill each one in, then print the completed story with the words substituted in.

Why this project? Template strings, regex replacement, async readline for multi-step input, and arrays of objects as structured data — all in a playful context.

2. How to Think About This

Store each story as an object: a title, a template string with {NOUN}-style placeholders, and a blanks array listing each placeholder with a label and hint. Loop through blanks, ask for each word, then replace all placeholders in the template with the collected answers.

3. The Build

madlibs.mjs
import * as readline from "readline/promises";
import { stdin, stdout } from "process";

export const stories = [
  {
    title: "The Brave {ADJECTIVE1} Developer",
    template:
      "Once upon a time, a {ADJECTIVE1} developer named {NAME} decided to {VERB1} " +
      "a {NOUN1}. Armed with only a {NOUN2} and {NUMBER} cups of coffee, they " +
      "{VERB2} through the night. By morning, the {NOUN1} was {ADJECTIVE2} and " +
      "the whole world {VERB3} with joy. The end.",
    blanks: [
      { key: "ADJECTIVE1", label: "adjective",        hint: "e.g. brilliant" },
      { key: "NAME",       label: "a person's name",  hint: "e.g. Priya"     },
      { key: "VERB1",      label: "verb (to...)",      hint: "e.g. build"     },
      { key: "NOUN1",      label: "noun",              hint: "e.g. website"   },
      { key: "NOUN2",      label: "another noun",      hint: "e.g. keyboard"  },
      { key: "NUMBER",     label: "a number",          hint: "e.g. 47"        },
      { key: "VERB2",      label: "past-tense verb",   hint: "e.g. coded"     },
      { key: "ADJECTIVE2", label: "adjective",         hint: "e.g. magnificent"},
      { key: "VERB3",      label: "past-tense verb",   hint: "e.g. celebrated" },
    ],
  },
];

export function fillStory(story, answers) {
  let result = story.template;
  for (const [key, value] of Object.entries(answers)) {
    result = result.replaceAll(`{${key}}`, value);
  }
  return result;
}

const rl = readline.createInterface({ input: stdin, output: stdout });
const story = stories[Math.floor(Math.random() * stories.length)];
const answers = {};

console.log(`
📖 Mad Libs: "${story.title.replace(/\{[^}]+\}/g,"___")}"
`);
console.log("Fill in the blanks:
");

for (const blank of story.blanks) {
  let input = "";
  while (!input.trim()) {
    input = await rl.question(`  Enter a ${blank.label} (${blank.hint}): `);
  }
  answers[blank.key] = input.trim();
}

rl.close();
console.log(`
${"─".repeat(50)}`);
console.log(fillStory(story, answers));
console.log("─".repeat(50));

4. Test & Prove

import { fillStory, stories } from "./madlibs.mjs";
let p=0,f=0;
function test(d,fn){try{fn();console.log("  ✓",d);p++;}catch(e){console.log("  ✗",d,"—",e.message);f++;}}
const story = stories[0];
const answers = {};
story.blanks.forEach(b => { answers[b.key] = b.key.toLowerCase(); });
const result = fillStory(story, answers);
test("all placeholders replaced", () => { if(result.includes("{")) throw new Error("Placeholder left: "+result.match(/\{[^}]+\}/)?.[0]); });
test("answer words appear in result", () => { if(!result.includes("adjective1")) throw new Error(); });
test("original template unchanged",  () => { if(!story.template.includes("{ADJECTIVE1}")) throw new Error(); });
test("replaceAll replaces all occurrences", () => {
  const s = { template: "{X} and {X}", blanks:[{key:"X"}] };
  const r = fillStory(s, {X:"yes"});
  if(r !== "yes and yes") throw new Error(r);
});
console.log(`
${p} passed, ${f} failed`);

5. The Interface

node madlibs.mjs

📖 Mad Libs: "The Brave ___ Developer"

Fill in the blanks:
  Enter an adjective (e.g. brilliant): fearless
  Enter a person's name (e.g. Priya): Rohan
  ...

──────────────────────────────────────────────────
Once upon a time, a fearless developer named Rohan...
──────────────────────────────────────────────────

6. Run It

node madlibs.mjs
node madlibs.test.mjs

🎯 Try this next

  1. Add 3 more story templates — pick one randomly each run
  2. Load story templates from a JSON file so anyone can add stories
  3. Add a browser version — form inputs, result revealed on submit
  4. Highlight the filled-in words in the output using ANSI colour codes

What you practised

replaceAll() for substitution, arrays of objects as structured data, async readline for sequential prompts, input validation with a while loop, and keeping story data separate from game logic.

Reference: Strings · Arrays · Async & Promises

Try It Yourself