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

indian-rummy-core

v0.1.1

Published

High-performance Indian Rummy game logic library implemented in Rust with TypeScript bindings for Node.js applications

Readme

Indian Rummy Core

A high-performance Indian Rummy game logic library implemented in Rust with TypeScript bindings for Node.js applications.

Features

  • High Performance: Native Rust implementation for optimal speed
  • TypeScript Support: Full type definitions included
  • Cross-Platform: Supports Windows, macOS, and Linux
  • Complete Game Logic: Full implementation including game state, player management, and tournaments
  • Joker Support: Handles both designated jokers and literal jokers
  • Node.js 22+: Built for modern Node.js environments

Implementation Complete: Both Phase 1 (core card evaluation) and Phase 2 (full game logic) are now implemented as specified in rummy.md.

Installation

npm install indian-rummy-core

Requirements

  • Node.js >= 22.0.0
  • Supported platforms: Windows (x64, arm64), macOS (x64, arm64), Linux (x64, arm64)

Quick Start

Phase 1: Card Evaluation

import { score, isCompletedHand, JsCard } from "indian-rummy-core";

// Define a hand
const hand: JsCard[] = [
  { rank: "A", suit: "S" },
  { rank: "2", suit: "S" },
  { rank: "3", suit: "S" }, // Life sequence
  { rank: "4", suit: "H" },
  { rank: "5", suit: "H" },
  { rank: "6", suit: "H" }, // Another sequence
  { rank: "7", suit: "S" },
  { rank: "7", suit: "H" },
  { rank: "7", suit: "D" }, // Triplet
  { rank: "K", suit: "S" },
  { rank: "K", suit: "H" },
  { rank: "K", suit: "D" },
  { rank: "K", suit: "C" }, // Triplet
];

// Check if hand is completed
const completed = isCompletedHand(hand);
console.log("Hand completed:", completed); // true

// Calculate penalty score
const penaltyScore = score(hand);
console.log("Penalty score:", penaltyScore); // 0 for completed hand

Phase 2: Complete Game

import { JsIndianRummyGame, JsMoveType } from "indian-rummy-core";

// Create a new game
const game = new JsIndianRummyGame(
  ['player1', 'player2'], 
  ['Alice', 'Bob'], 
  1 // number of decks
);

// Get game state
const state = game.getState();
console.log(`${state.nextTurnPlayer}'s turn`);

// Make a move
const move = {
  playerId: state.nextTurnPlayer,
  moveType: JsMoveType.OpenCard,
  cardReceived: game.getTopOpenCard()!,
  cardDiscarded: state.players[0].hand[0],
  didClaimWin: false
};

const result = game.processMove(move);
console.log("Move valid:", result.isValid);

API Reference

Core Types

JsCard

Represents a playing card with rank and suit.

interface JsCard {
  rank: string; // 'A' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'J' | 'Q' | 'K'
  suit: string; // 'S' (Spades) | 'C' (Clubs) | 'D' (Diamonds) | 'H' (Hearts) | 'J' (Joker)
}

JsCompletedHandResult

Result returned when finding a completed hand from a larger collection.

interface JsCompletedHandResult {
  completedHand: JsCard[];
  remainingCards: JsCard[];
}

Phase 1: Card Evaluation Functions

score(hand: JsCard[], designatedJoker?: JsCard | null): number

Calculate the penalty score for a hand according to Indian Rummy rules.

  • Returns the minimum possible penalty score
  • Jokers don't contribute to penalty scores
  • Completed hands return 0

isCompleteDeck(deck: JsCard[]): boolean

Check if a deck contains all 48 standard playing cards (12 ranks × 4 suits).

isCompletedHand(hand: JsCard[], designatedJoker?: JsCard | null): boolean

Check if a hand is completed according to Indian Rummy rules:

  • Must contain exactly 13 cards
  • All cards grouped into valid sets (sequences or triplets)
  • At least 2 sequences required
  • At least 1 "life" sequence (no jokers) required

completedHandExists(cards: JsCard[], designatedJoker?: JsCard | null): JsCompletedHandResult | null

Find a completed hand from a collection of 13 or more cards.

Returns the optimal 13-card arrangement if possible, along with remaining cards.

Phase 2: Game Management

JsPlayer

Represents a player in the game.

interface JsPlayer {
  id: string;
  name: string;
  hand: JsCard[];
}

JsMoveType

Types of moves a player can make.

enum JsMoveType {
  OpenCard = 'OpenCard',   // Take card from open pile
  CloseCard = 'CloseCard', // Take card from closed pile
  Fold = 'Fold'            // Fold the game
}

JsMove

Represents a move made by a player.

interface JsMove {
  playerId: string;
  moveType: JsMoveType;
  cardReceived?: JsCard;    // Required for OpenCard/CloseCard
  cardDiscarded?: JsCard;   // Required for OpenCard/CloseCard
  didClaimWin: boolean;
}

JsMoveResult

Result of processing a move.

interface JsMoveResult {
  isValid: boolean;
  isWin: boolean;
  winner?: string;
  scores: Record<string, number>;
  errorMessage?: string;
}

JsGameState

Current state of the game.

interface JsGameState {
  players: JsPlayer[];
  designatedJoker: JsCard;
  openPileTop?: JsCard;
  nextTurnPlayer: string;
  isComplete: boolean;
  winner?: string;
  finalScores: Record<string, number>;
}

Game Classes

JsIndianRummyGame

Main game class for managing a complete Indian Rummy game.

class JsIndianRummyGame {
  constructor(playerIds: string[], playerNames: string[], nDecks: number);
  
  // Game state
  getState(): JsGameState;
  isGameComplete(): boolean;
  
  // Move processing
  processMove(gameMove: JsMove): JsMoveResult;
  isValidMove(gameMove: JsMove): boolean;
  
  // Card access
  getTopOpenCard(): JsCard | null;
  getTopClosedCard(): JsCard | null;
  
  // Player access
  getPlayer(playerId: string): JsPlayer | null;
  
  // Serialization
  serialize(): string;
  static deserialize(json: string): JsIndianRummyGame;
}

JsSyndicateGame

Tournament management for multiple games.

class JsSyndicateGame {
  constructor(playerIds: string[]);
  
  // Game management
  addRummyGame(game: JsIndianRummyGame): void;
  getGameCount(): number;
  
  // Scoring
  getPlayerPoints(): Record<string, number>;
  getLeaderboard(): string[][]; // [playerName, points][]
  
  // Serialization
  serialize(): string;
  static deserialize(json: string): JsSyndicateGame;
}

Game Rules

Indian Rummy Basics

  • Objective: Form valid sets and sequences with 13 cards
  • Sets: Groups of 3-4 cards of the same rank with different suits
  • Sequences: Groups of 3+ consecutive cards of the same suit
  • Life: A sequence without any jokers (at least one required)

Jokers

  • Literal Jokers: Cards with suit 'J'
  • Designated Jokers: Any card can be designated as a wild card
  • Usage: Can substitute any card except in life sequences
  • Scoring: Jokers have 0 penalty value

Scoring

  • Numbered cards: Face value (1-9)
  • Face cards: 10 points each (J, Q, K)
  • Aces: 1 point
  • Jokers: 0 points

Examples

Phase 1: Card Evaluation

Basic Hand Validation

import { isCompletedHand, score, JsCard } from "indian-rummy-core";

const validHand: JsCard[] = [
  // Life sequence: A-2-3 of Spades
  { rank: "A", suit: "S" },
  { rank: "2", suit: "S" },
  { rank: "3", suit: "S" },
  // Sequence with joker: 4-5-Joker of Hearts
  { rank: "4", suit: "H" },
  { rank: "5", suit: "H" },
  { rank: "J", suit: "J" },
  // Triplet: 7s
  { rank: "7", suit: "S" },
  { rank: "7", suit: "H" },
  { rank: "7", suit: "D" },
  // Triplet: Kings
  { rank: "K", suit: "S" },
  { rank: "K", suit: "H" },
  { rank: "K", suit: "D" },
  { rank: "K", suit: "C" },
];

console.log(isCompletedHand(validHand)); // true
console.log(score(validHand)); // 0 (completed hand)

Finding Completed Hands

import { completedHandExists } from "indian-rummy-core";

const cards = [
  // 15 cards that include a possible completed hand
  { rank: "A", suit: "S" },
  { rank: "2", suit: "S" },
  { rank: "3", suit: "S" },
  { rank: "4", suit: "H" },
  { rank: "5", suit: "H" },
  { rank: "6", suit: "H" },
  { rank: "7", suit: "S" },
  { rank: "7", suit: "H" },
  { rank: "7", suit: "D" },
  { rank: "K", suit: "S" },
  { rank: "K", suit: "H" },
  { rank: "K", suit: "D" },
  { rank: "K", suit: "C" },
  { rank: "9", suit: "S" },
  { rank: "J", suit: "C" }, // Extra cards
];

const result = completedHandExists(cards);
if (result) {
  console.log("Found completed hand:", result.completedHand);
  console.log("Remaining cards:", result.remainingCards);
}

Working with Designated Jokers

import { score, isCompletedHand } from "indian-rummy-core";

const hand = [
  { rank: "A", suit: "S" },
  { rank: "2", suit: "S" }, // This will be our designated joker
  { rank: "3", suit: "S" },
  // ... rest of hand
];

const designatedJoker = { rank: "2", suit: "S" };

// Score with designated joker
const scoreWithJoker = score(hand, designatedJoker);
const isComplete = isCompletedHand(hand, designatedJoker);

Phase 2: Complete Game Management

Creating and Managing a Game

import { JsIndianRummyGame, JsMoveType } from "indian-rummy-core";

// Create a new game with 3 players
const playerIds = ['player1', 'player2', 'player3'];
const playerNames = ['Alice', 'Bob', 'Charlie'];
const game = new JsIndianRummyGame(playerIds, playerNames, 1);

// Get initial game state
const state = game.getState();
console.log(`Designated joker: ${state.designatedJoker.rank}${state.designatedJoker.suit}`);
console.log(`Next turn: ${state.nextTurnPlayer}`);
console.log(`Open pile top: ${game.getTopOpenCard()?.rank}${game.getTopOpenCard()?.suit}`);

// Check each player's hand
state.players.forEach(player => {
  console.log(`${player.name} has ${player.hand.length} cards`);
});

Processing Player Moves

// Get current player
const currentPlayer = state.players.find(p => p.id === state.nextTurnPlayer)!;

// Create a move to take from open pile
const move = {
  playerId: state.nextTurnPlayer,
  moveType: JsMoveType.OpenCard,
  cardReceived: game.getTopOpenCard()!,
  cardDiscarded: currentPlayer.hand[0], // Discard first card
  didClaimWin: false
};

// Validate and process the move
if (game.isValidMove(move)) {
  const result = game.processMove(move);
  
  if (result.isValid) {
    console.log('Move processed successfully');
    
    if (result.isWin) {
      console.log(`🎉 Winner: ${result.winner}`);
      console.log('Final scores:', result.scores);
    } else {
      console.log('Game continues...');
    }
  } else {
    console.log('Move failed:', result.errorMessage);
  }
}

Player Folding

// Player decides to fold
const foldMove = {
  playerId: 'player2',
  moveType: JsMoveType.Fold,
  didClaimWin: false
};

const foldResult = game.processMove(foldMove);
if (foldResult.isValid) {
  console.log('Player folded, game continues with remaining players');
  
  // Check if game ended due to folding
  if (game.isGameComplete()) {
    const finalState = game.getState();
    console.log('Game ended. Winner:', finalState.winner);
    console.log('Final scores:', finalState.finalScores);
  }
}

Win Declaration

// Player claims a win
const winMove = {
  playerId: state.nextTurnPlayer,
  moveType: JsMoveType.CloseCard,
  cardReceived: game.getTopClosedCard()!,
  cardDiscarded: currentPlayer.hand[1],
  didClaimWin: true // Claiming win!
};

const winResult = game.processMove(winMove);
if (winResult.isWin) {
  console.log(`🎉 Valid win by ${winResult.winner}!`);
  console.log('Final scores:', winResult.scores);
} else if (!winResult.isValid) {
  console.log('Invalid win claim:', winResult.errorMessage);
  // Player gets middle drop penalty for false win claim
}

Tournament Management

Creating and Managing Syndicates

import { JsSyndicateGame } from "indian-rummy-core";

// Create a syndicate for tournament play
const playerIds = ['player1', 'player2', 'player3'];
const syndicate = new JsSyndicateGame(playerIds);

// Add multiple games to the syndicate
for (let i = 0; i < 5; i++) {
  const game = new JsIndianRummyGame(
    playerIds, 
    ['Alice', 'Bob', 'Charlie'], 
    1
  );
  
  // Simulate game completion (in real usage, games would be played)
  syndicate.addRummyGame(game);
}

console.log(`Tournament has ${syndicate.getGameCount()} games`);

Tournament Scoring and Leaderboards

// Get cumulative points across all games
const totalPoints = syndicate.getPlayerPoints();
console.log('Total points:', totalPoints);

// Get leaderboard (sorted by points, ascending - lower is better)
const leaderboard = syndicate.getLeaderboard();
console.log('Tournament standings:');
leaderboard.forEach(([playerName, points], index) => {
  console.log(`${index + 1}. ${playerName}: ${points} points`);
});

Game Persistence

Saving and Loading Games

// Serialize game state
const gameJson = game.serialize();
console.log('Game saved to JSON');

// Save to file or database
// fs.writeFileSync('game.json', gameJson);

// Later, restore the game
const restoredGame = JsIndianRummyGame.deserialize(gameJson);
console.log('Game restored from JSON');

// Verify state is preserved
const originalState = game.getState();
const restoredState = restoredGame.getState();
console.log('States match:', 
  originalState.nextTurnPlayer === restoredState.nextTurnPlayer
);

Syndicate Persistence

// Serialize entire tournament
const syndicateJson = syndicate.serialize();

// Restore tournament
const restoredSyndicate = JsSyndicateGame.deserialize(syndicateJson);
console.log(`Restored syndicate with ${restoredSyndicate.getGameCount()} games`);

Error Handling

try {
  // Invalid game creation
  const invalidGame = new JsIndianRummyGame([], [], 0);
} catch (error) {
  console.log('Game creation failed:', error.message);
}

try {
  // Invalid move
  const invalidMove = {
    playerId: 'nonexistent',
    moveType: JsMoveType.OpenCard,
    cardReceived: { rank: 'A', suit: 'S' },
    cardDiscarded: { rank: '2', suit: 'C' },
    didClaimWin: false
  };
  
  const result = game.processMove(invalidMove);
  if (!result.isValid) {
    console.log('Move rejected:', result.errorMessage);
  }
} catch (error) {
  console.log('Move processing error:', error.message);
}

Performance

This library is implemented in Rust for optimal performance:

  • Fast scoring: Efficient algorithms for finding minimum penalty scores (~19ms average)
  • Quick validation: Deck validation in ~0.05ms, hand completion in ~9.4ms
  • Memory efficient: Minimal allocations and optimal data structures
  • Game processing: Move validation and processing in microseconds
  • Serialization: Fast JSON serialization for game persistence
  • Cross-platform: Native binaries for all major platforms

Benchmarks

  • Score calculation: ~19ms average for complex hands
  • Deck validation: ~0.05ms average for standard deck
  • Hand completion check: ~9.4ms average for complex hands
  • Completed hand search: ~0.03ms average for large collections
  • 1000 score calculations: <30 seconds total
  • Memory usage: No significant memory leaks during repeated operations

License

MIT

Testing

The library includes comprehensive test coverage with 113+ tests:

  • Phase 1 Tests: Core card evaluation functions (98 tests)
  • Phase 2 Tests: Complete game logic (15 tests)
  • Performance Tests: Benchmarks and stress testing
  • Error Handling: Edge cases and invalid input handling
  • Type Safety: TypeScript integration validation

Run tests with:

npm test                    # All tests
npm run test:game-logic     # Phase 2 game logic tests
npm run test:performance    # Performance benchmarks
npm run test:coverage       # Coverage report

Development Roadmap

Phase 1: Core Card Evaluation ✅ (Complete)

  • [x] Card and deck data structures
  • [x] Hand validation and scoring algorithms
  • [x] Set detection (sequences, triplets, life)
  • [x] Joker handling (literal and designated)
  • [x] TypeScript bindings and comprehensive test suite

Phase 2: Full Game Logic ✅ (Complete)

  • [x] Player management and game state
  • [x] Turn-based move processing
  • [x] Game flow (deal, draw, discard, fold, win)
  • [x] Syndicate games and tournament scoring
  • [x] Game state persistence and serialization
  • [x] Complete TypeScript API with full type safety

See rummy.md for complete game specifications and tests/future-game-logic.test.ts for comprehensive test coverage.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.