Skip to content

Type-Level Hangman in TypeScript

Published On
Read Time
1 min

You can actually implement Hangman in TypeScript at the type level. Your guessed letters are represented as a type, and the result is also a type. There’s no runtime code - just pure type manipulation.

A Hangman game implemented in TypeScript at the type level

It may not look exactly like classic Hangman, since TypeScript doesn’t support displaying multi-line template literal types. Instead, we use a single-line emoji visualization.

How many languages let you do this? TypeScript is impressive!

Implementation

And here’s the code. Open this in the TypeScript Playground and the ^? will show you the result type directly inline.

type Input = ""; // <-- Enter your guessed letters here

type X = Guess<Input>;
//   ^?

// ------------------------------------------------------------------------

type Guess<T extends string> = `${GameLetters<T>} => ${GameState<T>}`;

type GameLetters<TGuess extends string> = Letters<GameSolution, LettersOf<Uppercase<TGuess>>>;

type GameState<TGuess extends string> = Drawing<NumberOfWrongGuesses<LettersOf<GameSolution>, Uppercase<TGuess>>>;

type LettersOf<T extends string> = T extends `${infer TLetter}${infer TRest}` ? TLetter | LettersOf<TRest> : never;

type Letters<TWord extends string, TGuessedLetters extends string> = TWord extends `${infer TLetter}${infer TRest}`
  ? `${TLetter extends TGuessedLetters ? TLetter : "_"}${Letters<TRest, TGuessedLetters>}`
  : "";

type NumberOfWrongGuesses<
  TWordLetters extends string,
  TGuess extends string,
  TStrikes extends string[] = [],
> = TGuess extends `${infer TLetter}${infer TRest}`
  ? NumberOfWrongGuesses<
      TWordLetters,
      TRest,
      TLetter extends TWordLetters ? TStrikes : TLetter extends TStrikes[number] ? TStrikes : [...TStrikes, TLetter]
    >
  : TStrikes["length"];

type Drawing<TIndex extends number> = Drawings[TIndex] extends undefined ? LastOf<Drawings> : Drawings[TIndex];

type Drawings = ["🥳______", "🥳_____💣", "😐____💣", "☹️___💣", "😦__💣", "😵_💣", "😱💣", "☠️"];

type LastOf<T> = T extends [...infer _, infer TLast] ? TLast : never;

type GameSolution = Uppercase<"TypeScript">;