npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wynham-games

v1.0.0-beta.12

Published

Create and manage interactive mini-games (Connect Four, Tic-Tac-Toe, Rock Paper Scissors, Hangman) inside your Discord bot, built on top of Discord.js.

Readme

game

An open-source Node.js library built on top of Discord.js that lets you create and manage interactive mini-games inside your Discord bot with a single function call.

It ships with player invitations, anti-spam cooldowns, bot opponents (with difficulty levels), full theming, and built-in translations.

Features

  • 🎮 5 ready-to-play games — Connect Four, Tic-Tac-Toe, Rock Paper Scissors, Hangman and Snake.
  • 🤖 Play against the bot — set player 2 to your client user and pick a difficulty (EasyImpossible).
  • 👥 Player-vs-player invites — the challenged user gets an Accept / Decline prompt before the game starts.
  • 🛡️ Anti-spam & cooldowns — built-in protection so the same users can't flood your bot with games.
  • 🌍 i18n out of the box — English, French, Spanish and Italian, selectable per manager or per game.
  • 🎨 Theming — customise embed colors, emojis, titles and input mode (buttons or select menu).

Requirements

  • Node.js
  • discord.js ^14.26.4 (peer dependency — install it in your bot project).

Installation

npm install wynham-games@beta discord.js

Quick start

The whole library revolves around a single class: GameManager. You instantiate it once with your Discord client, then call createGame(...) from a slash command interaction.

import { Client, GatewayIntentBits } from "discord.js";
import { GameManager, GameNameType } from "wynham-games";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// Create the manager once, ideally next to your client setup.
const games = new GameManager(client);

client.on("interactionCreate", async interaction => {
    if (!interaction.isChatInputCommand()) return;

    if (interaction.commandName === "connect4") {
        const opponent = interaction.options.getUser("opponent", true);

        await games.createGame(GameNameType.Connect4, interaction, {
            players: [interaction.user, opponent],
        });
    }
});

client.login(process.env.TOKEN);

That's it — createGame automatically defers the reply, shows the invite, waits for the opponent, and runs the game to completion.

Core concept: GameManager

Constructor

new GameManager(client: Client, options?: GameManagerOptions)

| Option | Type | Default | Description | | ----------------- | --------- | ------------------ | ------------------------------------------------------------------------------------ | | antiSpam.enable | boolean | true | Prevents the same pair of players from starting back-to-back games. | | antiSpam.time | number | 5000 | Cooldown duration in milliseconds. | | locale | Locale | Locale.EnglishUS | Default language for every game created by this manager. Can be overridden per game. |

import { Locale } from "discord.js";

const games = new GameManager(client, {
    antiSpam: { enable: true, time: 10_000 },
    locale: Locale.French,
});

createGame(gameName, interaction, options)

Validates the players, displays the invitation/difficulty menu when needed, and launches the game.

createGame(gameName: GameNameType, interaction: Interaction, options): Promise<void>
  • gameName — one of the GameNameType enum values.
  • interaction — a repliable slash-command interaction. It is deferred automatically if it hasn't been already.
  • options — the per-game options object (always contains the players tuple, see below).

The method is overloaded, so TypeScript infers the correct options type from the gameName you pass.

Bot opponents

Only RPS and Tic-Tac-Toe support a bot opponent (players[1].bot === true):

  • RPS runs immediately against the bot (the bot plays a random choice).
  • Tic-Tac-Toe first shows a difficulty-selection menu, then runs against the bot.

Connect Four and Hangman are player-vs-player only — passing a bot as the second player is rejected.

For human-vs-human, the second player is sent an Accept / Decline invite and the game only starts once they accept.

Snake is single-player: it takes a single player instead of a players tuple, and starts immediately with no invite.

Games & options

Every option object shares the players field and most share turnTimeout, locale, theme and lang. The table below lists what is specific to each game.

Shared options

| Option | Type | Default | Description | | ------------------ | -------------- | ---------------- | -------------------------------------------------------------- | | players | [User, User] | — | Required. The two participants. Player 2 may be the bot. | | turnTimeout | number | 30 | Max seconds a player has to act before the game ends (15–360). | | locale | Locale | manager's locale | Overrides the manager language for this game only. | | theme.colors | object | see below | Embed colors for play / win / draw states. | | lang.title | string | game name | Title shown on the game board. | | lang.inviteTitle | string | "… Challenge" | Title shown in the invite message. |

GameNameType.Connect4Connect4Options

| Option | Type | Default | Description | | ---------------------- | ----------- | --------- | --------------------------------------------- | | theme.emojis.player1 | string | 🔴 | Player 1 pieces. | | theme.emojis.player2 | string | 🟡 | Player 2 pieces. | | theme.emojis.empty | string | | Empty cells. | | theme.emojis.win | string | | Winning pieces. | | theme.inputType | InputType | Buttons | Column input mode: Buttons or SelectMenu. | | theme.buttonStyle | ButtonStyle | Primary | Style of the column input buttons: Primary, Secondary, Success, or Danger. |

import { GameNameType, InputType } from "wynham-games";
import { ButtonStyle } from "discord.js";

await games.createGame(GameNameType.Connect4, interaction, {
    players: [interaction.user, opponent],
    turnTimeout: 45,
    theme: {
        emojis: { player1: "🔵", player2: "🔴" },
        inputType: InputType.Buttons,
        buttonStyle: ButtonStyle.Success,
    },
});

GameNameType.TicTacToeTicTacToeOptions

| Option | Type | Default | Description | | ---------------------- | --------------- | ------- | ------------------------------------------------------ | | difficulty | BotDifficulty | Hard | Bot strength when player 2 is a bot — see table below. | | theme.emojis.player1 | string | | Player 1 marks. | | theme.emojis.player2 | string | | Player 2 marks. | | theme.emojis.empty | string | | Empty cells. |

The bot opponent behaves differently per difficulty:

| Difficulty | Behaviour | | ------------ | ---------------------------------------------------------- | | Easy | Always plays a random move. | | Normal | Plays the optimal move ~50% of the time, random otherwise. | | Hard | Plays the optimal move ~80% of the time. | | Impossible | Always optimal (minimax, unbeatable). |

GameNameType.RPSRPSOptions

| Option | Type | Default | Description | | ----------------------- | -------- | ------- | --------------- | | theme.emojis.rock | string | | Rock emoji. | | theme.emojis.paper | string | 🤚 | Paper emoji. | | theme.emojis.scissors | string | ✌️ | Scissors emoji. |

GameNameType.HangmanHangmanOptions

| Option | Type | Default | Description | | ----------- | ------- | ------- | -------------------------------- | | lifeCount | 19 | 6 | Number of wrong guesses allowed. |

The Hangman word list is taken from the active locale, so changing locale also changes the language of the words to guess.

GameNameType.SnakeSnakeOptions

Single-player. Snake does not use the shared players / lang.inviteTitle fields. It takes a single player and starts immediately — no invite is sent.

| Option | Type | Default | Description | | -------------------- | -------- | ------- | ------------------------------------------------------------------- | | player | User | — | Required. The player (must not be a bot). | | turnTimeout | number | 30 | Max seconds the player can stay idle between two moves (15–360). | | boardWidth | number | 10 | Board width in cells (clamped 5–12). | | boardHeight | number | 10 | Board height in cells (clamped 5–12). | | theme.colors.lose | number | 0x808080 | Color on game over / timeout (replaces the shared draw color). | | theme.emojis.head | string | 🟢 | Snake head. | | theme.emojis.body | string | 🟩 | Snake body. | | theme.emojis.food | string | 🍎 | Food. | | theme.emojis.empty | string | | Empty cells. | | lang.title | string | "Snake" | Title shown on the game board. |

The player moves the snake one cell per button press (⬅️ ⬆️ ⬇️ ➡️) and can quit with ⏹️. Eating the 🍎 grows the snake and increases the score; hitting a wall or the snake's own body ends the game.

import { GameNameType } from "wynham-games";

await games.createGame(GameNameType.Snake, interaction, {
    player: interaction.user,
    boardWidth: 10,
    boardHeight: 10,
});

Internationalisation

Built-in locales are keyed by the Discord.js Locale enum:

| Locale | Enum value | | ------------ | ------------------ | | English (US) | Locale.EnglishUS | | French | Locale.French | | Spanish (ES) | Locale.SpanishES | | Italian | Locale.Italian |

You can resolve a player's own language and fall back to English:

import { locales, Locale } from "wynham-games";

const locale = locales[interaction.locale] ?? locales[Locale.EnglishUS];

Or simply pass interaction.locale (or interaction.guildLocale) as the locale option when creating a game.

Enums reference

enum GameNameType {
    Connect4,
    RPS,
    TicTacToe,
    Hangman,
    Snake,
}
enum BotDifficulty {
    Easy,
    Normal,
    Hard,
    Impossible,
}
enum InputType {
    SelectMenu,
    Buttons,
}
enum Choice {
    Rock,
    Paper,
    Scissors,
}
enum InviteResult {
    Accepted,
    Declined,
    Timeout,
}

Building from source

npm run build      # tsc && tsc-alias
npm run format     # prettier --write .

License

MIT — Yanis Hamburger & Stefanoz.