2020-04-28

The fictional D'ni numeral system that players must deduce in Riven is a base-25 positional system, with the twist that its 25 digits are constructed using an unrelated base-5 scheme in which the would-be fives place is orthogonally overlaid to form a single glyph:

0 1 2 3 4
0
5
10
15
20

The number 241, for example, is written as , i.e. 9 in the twenty-fives place and 16 in the ones place:

25¹ + 25⁰
25¹(5¹ + 5⁰) + 25⁰(5¹ + 5⁰)
25¹(5¹1 + 5⁰4) + 25⁰(5¹3 + 5⁰1)
241

Given the amount of symmetry in the notation I thought it would be fun to generate these procedurally. The following registers a custom html element that renders as an inline D'ni numeral:

const strokes = [
"M 16 20 h 4",
"M 18 2 V 38",
"M -0.5 2 C 12 10 12 30 -0.5 38",
"M 18 2 L 0 20 m 0 0 L 18 38",
"M 36 11 H 18 V 38",
];

define("dni-numeral", {
value: 0,
content: ({ value }) => {
const digits = [...iterDigits(value, 25)];
const width = 12 + 36 * digits.length;

return html`<svg viewBox=${`0 0 ${width} 40`} style="height: 1em;">
<g fill="none" stroke="currentColor" stroke-width="3">
${svg`<path d="M 0 2 H ${width} M 0 38 H ${width} M 6 2 V 38 ${
digits.map((_, i) => `M ${6 + 36 * (1 + i)} 2 V 38`).join(" ")
}
" />`
}


${digits.map((d) => iterDigits(d, 5)).map(([p1, p5], i) =>
svg`<g transform="translate(${width - 6 - 36 * (i + 1)})">
${(p1 !== 0 || !p5) && svg`<path d=${strokes[p1]} />`}
${p5 && svg`<path d=${strokes[p5]} transform="rotate(-90)" />`}
</g>
`
)}

</g>
</svg>
`
;
},
});

Using it is as simple as:

Preheat the oven to <dni-numeral value="350"></dni-numeral> degrees.

This opens the door to such fun things as a D'ni numeral clock—

—or an interactive converter: