| Crates.io | indian-rummy-core |
| lib.rs | indian-rummy-core |
| version | 0.1.0 |
| created_at | 2025-08-09 14:54:46.941668+00 |
| updated_at | 2025-08-09 14:54:46.941668+00 |
| description | A high-performance, thread-safe library for Indian Rummy game logic. |
| homepage | https://github.com/your-org/indian-rummy-core |
| repository | https://github.com/your-org/indian-rummy-core |
| max_upload_size | |
| id | 1788009 |
| size | 398,731 |
A high-performance Indian Rummy game logic library implemented in Rust with TypeScript bindings for Node.js applications.
Implementation Complete: Both Phase 1 (core card evaluation) and Phase 2 (full game logic) are now implemented as specified in
rummy.md.
npm install indian-rummy-core
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
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);
JsCardRepresents 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)
}
JsCompletedHandResultResult returned when finding a completed hand from a larger collection.
interface JsCompletedHandResult {
completedHand: JsCard[];
remainingCards: JsCard[];
}
score(hand: JsCard[], designatedJoker?: JsCard | null): numberCalculate the penalty score for a hand according to Indian Rummy rules.
isCompleteDeck(deck: JsCard[]): booleanCheck if a deck contains all 48 standard playing cards (12 ranks × 4 suits).
isCompletedHand(hand: JsCard[], designatedJoker?: JsCard | null): booleanCheck if a hand is completed according to Indian Rummy rules:
completedHandExists(cards: JsCard[], designatedJoker?: JsCard | null): JsCompletedHandResult | nullFind a completed hand from a collection of 13 or more cards.
Returns the optimal 13-card arrangement if possible, along with remaining cards.
JsPlayerRepresents a player in the game.
interface JsPlayer {
id: string;
name: string;
hand: JsCard[];
}
JsMoveTypeTypes 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
}
JsMoveRepresents 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;
}
JsMoveResultResult of processing a move.
interface JsMoveResult {
isValid: boolean;
isWin: boolean;
winner?: string;
scores: Record<string, number>;
errorMessage?: string;
}
JsGameStateCurrent state of the game.
interface JsGameState {
players: JsPlayer[];
designatedJoker: JsCard;
openPileTop?: JsCard;
nextTurnPlayer: string;
isComplete: boolean;
winner?: string;
finalScores: Record<string, number>;
}
JsIndianRummyGameMain 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;
}
JsSyndicateGameTournament 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;
}
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)
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);
}
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);
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`);
});
// 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 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);
}
}
// 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
}
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`);
// 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`);
});
// 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
);
// Serialize entire tournament
const syndicateJson = syndicate.serialize();
// Restore tournament
const restoredSyndicate = JsSyndicateGame.deserialize(syndicateJson);
console.log(`Restored syndicate with ${restoredSyndicate.getGameCount()} games`);
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);
}
This library is implemented in Rust for optimal performance:
MIT
The library includes comprehensive test coverage with 113+ tests:
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
See rummy.md for complete game specifications and tests/future-game-logic.test.ts for comprehensive test coverage.
Contributions are welcome! Please feel free to submit a Pull Request.