Back to Pursuit PIT
SOURCE / PINNED RELEASE

Made of little things.

Pursuit PIT

Release
565f4981bdc3…
Author-recorded commit
197afdbed44b…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7

Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game/towns.ts
// Towns along the interstate, with silly names. About half are real (and really are that
// silly); the rest are made up from parts. Everything comes from the road seed, so a town
// keeps its name for the whole run and in the replay.
import { hash01 } from './road.js';

const REAL = [
  'Boring', 'Peculiar', 'Booger Hole', 'Bird-in-Hand', 'Hot Coffee', 'Toad Suck', 'Monkeys Eyebrow',
  'Embarrass', 'Gnaw Bone', 'Accident', 'Ding Dong', 'Cool', 'Nothing', 'Why', 'Chicken',
  'Hooterville', 'Big Bottom', 'Possum Trot', 'Spot', 'Okay', 'Knockemstiff', 'Tightwad', 'Odd',
  'Gripe', 'Wahoo', 'Bumpass', 'Dull', 'Uncertain', 'Hazard', 'Goobertown', 'Bugtussle',
  'Smackover', 'Lizard Lick', 'Frankenstein', 'Whynot', 'Slapout', 'Zap', 'Ordinary', 'Toast', 'Gas',
];
const PREFIX = ['Lower', 'Upper', 'Little', 'Big', 'North', 'East', 'Soggy', 'Wobbly', 'Saggy', 'Old', 'New', 'Greater'];
const ROOT = ['Bottom', 'Knob', 'Puddle', 'Pickle', 'Muffin', 'Noodle', 'Butter', 'Beaver', 'Wiggle', 'Sausage',
  'Dumpling', 'Pudding', 'Waffle', 'Giggle', 'Squash', 'Biscuit', 'Turnip', 'Gravy', 'Wobble', 'Nugget'];
const END_JOINED = ['ton', 'ville', 'bury', 'bottom', 'by', 'wick'];
const END_WORD = ['Hollow', 'Junction', 'Creek', 'Falls', 'Flats', 'Springs', 'Gulch', 'Crossing', 'Corners', 'Heights'];
const POPS = ['69', '420', '3', '1 (MOSTLY DOGS)', '12', '7½', '88', 'UNKNOWN', '2 + A GOAT', '404', '5,280', '11'];
const TAGLINES = [
  "HOME OF THE WORLD'S LARGEST BALL OF TWINE",
  'OUR SAUSAGES ARE AWARD-WINNING',
  'WE HAVE A TRAFFIC LIGHT!',
  'TWINNED WITH ITSELF',
  'NOTHING HAPPENED HERE IN 1887',
  'BIRTHPLACE OF THE SPORK',
  "PROBABLY THE COUNTY'S FINEST PICKLES",
  'PLEASE DRIVE SLOWLY. PLEASE.',
  'HOME OF THE FIGHTING NOODLES',
  'ANNUAL MUFFIN FESTIVAL — JULY',
  'YES, THAT IS OUR REAL NAME',
  'ASK ABOUT OUR GOAT',
];
// The generator never puts these words side by side.
const BLOCK = ['beaverbottom', 'knobbottom', 'beaverknob', 'bottombottom', 'wobblywobble'];

export interface Town {
  index: number;
  name: string;
  pop: string;
  tagline: string;
}

const pickOf = <T>(list: T[], k: number, salt: number): T => list[Math.floor(hash01(k, salt) * list.length)];

function invent(k: number): string {
  for (let attempt = 0; ; attempt++) {
    const key = k * 31 + attempt;
    const root = pickOf(ROOT, key, 21);
    const prefix = hash01(key, 22) < 0.55 ? pickOf(PREFIX, key, 23) + ' ' : '';
    const name = hash01(key, 24) < 0.5
      ? prefix + root + pickOf(END_JOINED, key, 25)
      : prefix + root + ' ' + pickOf(END_WORD, key, 26);
    const squashed = name.toLowerCase().replace(/[^a-z]/g, '');
    if (!BLOCK.some((b) => squashed.includes(b)) || attempt > 8) return name;
  }
}

const cache = new Map<number, Town>();
let cachedFor = -1;

/** Town number j along the road (j >= 0). */
export function town(j: number, seed: number): Town {
  if (cachedFor !== seed) {
    cache.clear();
    cachedFor = seed;
  }
  let t = cache.get(j);
  if (!t) {
    // Real names are walked in a seeded stride, so none repeats until the list runs out.
    const stride = [7, 11, 13, 17, 19, 23][Math.floor(hash01(0, 30) * 6)];
    const offset = Math.floor(hash01(0, 31) * REAL.length);
    const real = hash01(j, 32) < 0.5;
    const name = real ? REAL[(offset + j * stride) % REAL.length] : invent(j);
    t = { index: j, name, pop: pickOf(POPS, j, 33), tagline: pickOf(TAGLINES, j, 34) };
    cache.set(j, t);
  }
  return t;
}

export const TOWN_NAMES_FOR_TESTS = { REAL, BLOCK };