@jackpotkit/core
v1.0.0
Published
Pure TypeScript game mechanics for JackpotKit.
Maintainers
Readme
@jackpotkit/core
Platform-independent TypeScript primitives for building deterministic, testable game mechanics.
npm install @jackpotkit/coreimport {
SeededRandomSource,
createGameEvent,
nextRandomValue,
resolveResult,
type GameResult,
type ResultProvider,
} from '@jackpotkit/core';
const random = new SeededRandomSource('campaign-preview');
const sample = nextRandomValue(random); // always in [0, 1)
type PlayRequest = { campaignId: string };
type PlayData = { rewardId: string };
const provider: ResultProvider<PlayRequest, GameResult<PlayData>> = async (request) => {
const response = await fetch(`/api/campaigns/${request.campaignId}/play`, {
method: 'POST',
});
if (!response.ok) throw new Error('Play request failed');
return response.json() as Promise<GameResult<PlayData>>;
};
const result = await resolveResult(provider, { campaignId: 'summer-2026' });
const event = createGameEvent('result-resolved', result);Included primitives
- Immutable result, reward, lifecycle, and typed event contracts.
MathRandomSourcefor ordinary client-side variation.SeededRandomSourcefor reproducible previews, tests, and debugging.- Injectable
RandomSourcecontracts with range validation. - Sync or async
ResultProviderfunctions with normalized failures. - Typed configuration, result, provider, and lifecycle errors.
- Composable validation results and assertion helpers.
SeededRandomSource uses a stable FNV-1a string hash and Mulberry32 generator. Its sequence is part of the public compatibility contract. It is deterministic, not cryptographically secure.
For rewards with real value, the backend must validate eligibility, choose and persist the outcome, and return that result. JackpotKit does not provide networking, settlement, fulfilment, or a security boundary.
Shared primitives are available from @jackpotkit/core; implemented games also have the intentional @jackpotkit/core/spin-wheel, @jackpotkit/core/scratch-card, @jackpotkit/core/slot-machine, @jackpotkit/core/bingo, @jackpotkit/core/dice, @jackpotkit/core/coin-flip, and @jackpotkit/core/lucky-box subpaths. Other internal paths are unsupported.
Spin Wheel
import { SeededRandomSource } from '@jackpotkit/core';
import { createSpinWheel } from '@jackpotkit/core/spin-wheel';
const wheel = createSpinWheel({
segments: [
{ id: 'common', label: '10 points', weight: 9 },
{ id: 'rare', label: 'Bonus badge', weight: 1 },
],
randomSource: new SeededRandomSource('preview'),
});
wheel.spin();
wheel.spinTo('rare');
await wheel.spinWith(serverResultProvider, request);
wheel.reset();Weights affect selection probability only; every prebuilt visual slice remains equal. Controlled and server results are validated against configured segment IDs before animation.
Scratch Card
import { createScratchCard, createScratchProgressTracker } from '@jackpotkit/core/scratch-card';
const card = createScratchCard({
threshold: 0.65,
result: { prize: { id: 'points', amount: 250 } },
});
const tracker = createScratchProgressTracker({
width: 320,
height: 180,
brushRadius: 20,
});
card.start();
card.scratch(tracker.scratchLine({ x: 20, y: 40 }, { x: 280, y: 40 }));
card.reveal();
card.reset();Coverage uses a deterministic grid and is independent of Skia, React Native, or frame rate. startWith() accepts a consumer-supplied result provider; scratching never chooses or changes the prize.
Slot Machine
import { SeededRandomSource } from '@jackpotkit/core';
import { createSlotMachine } from '@jackpotkit/core/slot-machine';
const machine = createSlotMachine({
symbols: [
{ id: 'cherry', weight: 5 },
{ id: 'star', weight: 1 },
],
reelCount: 3,
rowCount: 3,
randomSource: new SeededRandomSource('preview'),
paylines: [
[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
],
});
machine.spin();
machine.spinTo(controlledSelection);
await machine.spinWith(serverResultProvider, request);
machine.reset();The grid is reel-major, and each payline contains one row index per reel. Built-in evaluation reports matching symbol IDs; optional consumer evaluation can add application-specific, non-monetary result metadata.
Bingo
import { SeededRandomSource } from '@jackpotkit/core';
import { createBingo } from '@jackpotkit/core/bingo';
const bingo = createBingo({ randomSource: new SeededRandomSource('preview') });
bingo.call(27);
bingo.mark(27);
bingo.check();
bingo.reset();Classic cards use a 5 × 5, 1–75 layout with column ranges and a center free space. The engine also accepts externally supplied cards, configurable sizes and ranges, random remaining-number draws, mark/unmark, and row, column, diagonal, four-corners, full-board, or custom coordinate patterns. All exposed cards, patterns, results, and state snapshots are immutable.
Dice, Coin Flip, and Lucky Box
import { createCoinFlip } from '@jackpotkit/core/coin-flip';
import { createDice } from '@jackpotkit/core/dice';
import { createLuckyBox } from '@jackpotkit/core/lucky-box';
createDice({ count: 2, sides: 6 }).rollTo({ values: [2, 6] });
createCoinFlip().flipTo({ faceId: 'tails' });
const boxes = createLuckyBox({ boxes: [{ id: 'one' }, { id: 'two' }] });
boxes.select('one');
boxes.revealTo({ boxId: 'two' });All three engines support injected randomness, exact controlled results, synchronous or asynchronous result providers, reset, immutable results, and late-provider invalidation. Lucky Box intentionally separates the selected and winning box.
