@betorigami/game-engine 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("@betorigami/guard"),l=require("fast-deep-equal");l=s(l,1);let u=require("big.js");u=s(u,1);const d=Array.from({length:52},(e,t)=>t),f=[`Spades`,`Hearts`,`Diamonds`,`Clubs`],p=[`Ace`,`Two`,`Three`,`Four`,`Five`,`Six`,`Seven`,`Eight`,`Nine`,`Ten`,`Jack`,`Queen`,`King`],m=[`Jack`,`Queen`,`King`],h={Hearts:`red`,Diamonds:`red`,Spades:`black`,Clubs:`black`};var g=class{value;constructor(e){let t=d[e];c.Guard.againstNullOrUndefined(t,`Invalid card value: ${e}`),this.value=t}get suit(){let e=Math.floor(this.value/13),t=f[e];return c.Guard.againstNullOrUndefined(t,`Invalid suit index: ${e}`),t}get rank(){let e=this.value%13,t=p[e];return c.Guard.againstNullOrUndefined(t,`Invalid rank index: ${e}`),t}toDto(){return{rank:this.rank,suit:this.suit,cardValue:this.value}}};let _=function(e){return e.UNSUPPORTED_EDGE=`UNSUPPORTED_EDGE`,e.INPUT_VALIDATION_ERROR=`INPUT_VALIDATION_ERROR`,e}({});var v=class e extends Error{static createUnsupportedEdgeError(t){return new e(`UNSUPPORTED_EDGE`,t)}static createValidationError(t){return new e(`INPUT_VALIDATION_ERROR`,t)}static isGameError(t){return t instanceof e}errorCode;constructor(e,t){super(t),this.errorCode=e}},y=class{gameId;edge;constructor(e,t){this.gameId=e,this.edge=t,this.validateEdge(t)}play(e,t,n=[]){let r=this.createGameStore(),i=this.generateRandomValues(e),a=i.toMultipleRandomValues();for(let e of n)r.send({...e.playerArgument,randomValuePool:i});let o=n[n.length-1];o&&c.Guard.against(!(0,l.default)(o.state,this.toExternalState(r.getSnapshot().context.state)),`The final state of the game does not match the expected state based on the previous actions`),r.send({...t,randomValuePool:i});let s=r.getSnapshot().context;return{playerArgument:s.playerArgument,state:this.toExternalState(s.state),config:s.config,additionalBetAmount:s.additionalBetAmount,proof:{proofHash:a.proofHash,proofString:a.proofString}}}validateEdge(e){let t=this.supportedEdgeConfigurations.includes(e);c.Guard.against(!t,()=>v.createUnsupportedEdgeError(`${this.gameId} does not support ${e}% edge. Supported edge configurations include: ${this.supportedEdgeConfigurations}`))}},b=class{multipliers;constructor(e){this.multipliers=e}getMultipliers(e){let t=this.multipliers.get(e);if(t===void 0)throw Error(`Multipliers for ${e} not found`);return t}getAllMultipliers(){return Array.from(this.multipliers.entries()).map(([e,t])=>({edge:e,multipliers:t}))}};let x=function(e){return e.DICE=`DICE`,e.MINES=`MINES`,e.KENO=`KENO`,e.LIMBO=`LIMBO`,e.ADVANCED_DICE=`ADVANCED_DICE`,e.BACCARAT=`BACCARAT`,e.DIAMONDS=`DIAMONDS`,e.PLINKO=`PLINKO`,e.ROULETTE=`ROULETTE`,e.WHEEL=`WHEEL`,e.BLACKJACK=`BLACKJACK`,e.BLITZ=`BLITZ`,e.DARTS=`DARTS`,e}({});const S=({outcomes:e,totalWeight:t})=>(c.Guard.against(t<=0,`An outcome distribution must have a positive total weight`),e.reduce((e,{multiplier:t,weight:n})=>e.plus(t.times(n)),(0,u.default)(0)).div(t)),C=({outcomes:e})=>(c.Guard.againstEmptyArray(e,`An outcome distribution must have at least one outcome`),e.reduce((e,{multiplier:t})=>t.gt(e)?t:e,e[0].multiplier)),w=({outcomes:e,totalWeight:t},n)=>{c.Guard.against(t<=0,`An outcome distribution must have a positive total weight`);let r=e.filter(e=>e.slot===n);return c.Guard.againstEmptyArray(r,`Slot ${n} is not part of the outcome distribution`),(0,u.default)(r.reduce((e,t)=>e+t.weight,0)).div(t)},T=e=>e.legs.reduce((e,t)=>e.add(t.amount),new u.default(`0`));var E=class e{source;index=0;constructor(e){this.source=e}static from(t,n){return c.Guard.against(n<=0,`Count must be positive, got ${n}`),c.Guard.against(t.values.length!==n,`Expected ${n} values, got ${t.values.length}`),new e(t)}get consumedValues(){return this.source.values.slice(0,this.index)}toMultipleRandomValues(){return this.source}takeOne(){let e=this.source.values[this.index];return c.Guard.againstUndefined(e,`RandomValuePool exhausted`),this.index+=1,e}take(e){c.Guard.against(this.index+e>this.source.values.length,`RandomValuePool exhausted: requested ${e} but only ${this.source.values.length-this.index} remain`);let t=this.source.values.slice(this.index,this.index+e);return this.index+=e,t}},D=class{gameId;constructor(e){this.gameId=e}getGameResult(e){let t=this.supportedEdgeConfigurations.includes(e.edge);return c.Guard.against(!t,()=>v.createUnsupportedEdgeError(`${this.gameId} does not support ${e.edge}% edge. Supported edge configurations include: ${this.supportedEdgeConfigurations}`)),this.determineGameResult(e)}};const O=(e,t)=>e.gt(t)?e:t,k=(e,t)=>e.lt(t)?e:t;var A=class{static withMultiplier(e,t){return new u.default(e).times(new u.default(100-t).div(100))}static withWinChance(e,t){return(0,u.default)(100-e).div(t)}};const j=(e,t)=>{if(t<0||e<0||t>e)throw Error(`Invalid input: ensure 0 ≤ r ≤ n and n ≥ 0.`);t>e-t&&(t=e-t);let n=1;for(let r=1;r<=t;r++)n=n*(e-r+1)/r;return n};exports.CARD_RANKS=p,exports.CARD_SUITS=f,exports.CARD_SUIT_COLORS=h,exports.CARD_VALUES=d,exports.Card=g,exports.FACE_CARD_RANKS=m,exports.GameError=v,exports.GameErrorCode=_,exports.MAX_CARD_VALUE=51,exports.MIN_CARD_VALUE=0,exports.MultiActionGame=y,exports.MultiplierCalculator=A,exports.MultiplierContainer=b,exports.OrigamiGame=x,exports.RandomValuePool=E,exports.SingleActionGame=D,exports.bigMax=O,exports.bigMin=k,exports.maxMultiplierOf=C,exports.nCr=j,exports.probabilityOf=w,exports.rtpOf=S,exports.sumPayout=T;
@@ -0,0 +1,235 @@
1
+ import { i as SingleRandomValue, n as Proof, r as RandomNumberGenerator, t as MultipleRandomValues } from "./random-l3djG58u.cjs";
2
+ import Big from "big.js";
3
+ //#region src/cards/types.d.ts
4
+ export declare const MIN_CARD_VALUE = 0;
5
+ export declare const MAX_CARD_VALUE = 51;
6
+ export declare const CARD_VALUES: number[];
7
+ export declare const CARD_SUITS: readonly ["Spades", "Hearts", "Diamonds", "Clubs"];
8
+ export type CardSuit = (typeof CARD_SUITS)[number];
9
+ export declare const CARD_RANKS: readonly ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"];
10
+ export type CardRank = (typeof CARD_RANKS)[number];
11
+ export declare const FACE_CARD_RANKS: readonly CardRank[];
12
+ export type CardValue = (typeof CARD_VALUES)[number];
13
+ export type CardDto = {
14
+ rank: CardRank;
15
+ suit: CardSuit;
16
+ cardValue: CardValue;
17
+ };
18
+ export declare const CARD_SUIT_COLORS: Record<CardSuit, 'red' | 'black'>;
19
+ //#endregion
20
+ //#region src/cards/card.d.ts
21
+ export declare class Card {
22
+ readonly value: CardValue;
23
+ constructor(rawValue: number);
24
+ get suit(): CardSuit;
25
+ get rank(): CardRank;
26
+ toDto(): CardDto;
27
+ }
28
+ //#endregion
29
+ //#region src/errors/game.error.d.ts
30
+ export declare enum GameErrorCode {
31
+ UNSUPPORTED_EDGE = "UNSUPPORTED_EDGE",
32
+ INPUT_VALIDATION_ERROR = "INPUT_VALIDATION_ERROR"
33
+ }
34
+ export declare class GameError extends Error {
35
+ static createUnsupportedEdgeError(message: string): GameError;
36
+ static createValidationError(message: string): GameError;
37
+ static isGameError(error: unknown): error is GameError;
38
+ errorCode: GameErrorCode;
39
+ constructor(errorCode: GameErrorCode, message?: string);
40
+ }
41
+ //#endregion
42
+ //#region src/origami-game.d.ts
43
+ export declare enum OrigamiGame {
44
+ DICE = "DICE",
45
+ MINES = "MINES",
46
+ KENO = "KENO",
47
+ LIMBO = "LIMBO",
48
+ ADVANCED_DICE = "ADVANCED_DICE",
49
+ BACCARAT = "BACCARAT",
50
+ DIAMONDS = "DIAMONDS",
51
+ PLINKO = "PLINKO",
52
+ ROULETTE = "ROULETTE",
53
+ WHEEL = "WHEEL",
54
+ BLACKJACK = "BLACKJACK",
55
+ BLITZ = "BLITZ",
56
+ DARTS = "DARTS"
57
+ }
58
+ //#endregion
59
+ //#region src/game.d.ts
60
+ export interface Game {
61
+ gameId: OrigamiGame;
62
+ supportedEdgeConfigurations: [number, ...number[]];
63
+ }
64
+ //#endregion
65
+ //#region src/payout.d.ts
66
+ /**
67
+ * A single exact payout amount from a game round.
68
+ *
69
+ * A round returns zero or more legs. Neither their order nor their count is
70
+ * significant: a leg is not tied to a particular bet, an amount may be zero,
71
+ * and the number of legs need not match the number of bets placed. The only
72
+ * supported operation is summing them.
73
+ *
74
+ * Amounts are gross — they include returned stake — and unrounded. Rounding to
75
+ * a currency's precision is the consuming service's decision, not this
76
+ * package's.
77
+ */
78
+ export type PayoutLeg = Readonly<{
79
+ amount: Big;
80
+ }>;
81
+ /**
82
+ * A round's payout, expressed as its constituent legs.
83
+ */
84
+ export type Payout = Readonly<{
85
+ legs: readonly PayoutLeg[];
86
+ }>;
87
+ /**
88
+ * Totals a round's payout.
89
+ *
90
+ * Summing the legs is lossless; reconstructing the total from a rounded
91
+ * payout multiplier is not, so this is the only supported way to arrive at
92
+ * what a round pays.
93
+ */
94
+ export declare const sumPayout: (payout: Payout) => Big;
95
+ //#endregion
96
+ //#region src/single-action-game.d.ts
97
+ export type EmptyGameOutput = {};
98
+ export interface GetGameResultRequest<TGameInputs> {
99
+ generator: RandomNumberGenerator;
100
+ edge: number;
101
+ gameInputs: TGameInputs;
102
+ }
103
+ export interface GameResult<TGameOutputs = EmptyGameOutput> {
104
+ isFinished: boolean;
105
+ payout: Payout;
106
+ payoutMultiplier: Big;
107
+ outputs: TGameOutputs & {
108
+ result: number[];
109
+ };
110
+ randomValues: SingleRandomValue | MultipleRandomValues;
111
+ }
112
+ export declare abstract class SingleActionGame<TGameInputs, TGameOutputs = EmptyGameOutput> implements Game {
113
+ readonly gameId: OrigamiGame;
114
+ protected constructor(gameId: OrigamiGame);
115
+ abstract get supportedEdgeConfigurations(): [number, ...number[]];
116
+ abstract getBetAmount(gameInputs: TGameInputs): Big;
117
+ getGameResult(request: GetGameResultRequest<TGameInputs>): GameResult<TGameOutputs>;
118
+ protected abstract determineGameResult(request: GetGameResultRequest<TGameInputs>): GameResult<TGameOutputs>;
119
+ }
120
+ //#endregion
121
+ //#region src/game-descriptor.d.ts
122
+ export interface GameDescriptor<TInputs, TOutputs = EmptyGameOutput> {
123
+ readonly id: OrigamiGame;
124
+ readonly create: () => SingleActionGame<TInputs, TOutputs>;
125
+ readonly suppliesOwnBetAmount: boolean;
126
+ }
127
+ //#endregion
128
+ //#region src/random-value-pool.d.ts
129
+ export declare class RandomValuePool {
130
+ private readonly source;
131
+ private index;
132
+ private constructor();
133
+ static from(result: MultipleRandomValues, count: number): RandomValuePool;
134
+ get consumedValues(): number[];
135
+ toMultipleRandomValues(): MultipleRandomValues;
136
+ takeOne(): number;
137
+ take(count: number): ReadonlyArray<number>;
138
+ }
139
+ //#endregion
140
+ //#region src/multi-action-game.d.ts
141
+ export type BaseInProgressState = Readonly<{
142
+ isFinished: false;
143
+ multiplier: Big;
144
+ }>;
145
+ export type BaseFinishedState = Readonly<{
146
+ isFinished: true;
147
+ payout: Payout;
148
+ multiplier: Big;
149
+ randomValues: number[];
150
+ }>;
151
+ export type BaseGameState = BaseInProgressState | BaseFinishedState;
152
+ export type GameContext<TAction, TState extends BaseGameState, TConfig> = {
153
+ readonly playerArgument: TAction;
154
+ readonly state: TState;
155
+ readonly config: TConfig;
156
+ readonly additionalBetAmount?: Big;
157
+ };
158
+ export type RandomValuePoolEvent = Readonly<{
159
+ randomValuePool: RandomValuePool;
160
+ }>;
161
+ export type UntypedGameContext = GameContext<{
162
+ readonly type: string;
163
+ }, BaseGameState, unknown>;
164
+ export type PlayResult = UntypedGameContext & {
165
+ readonly proof: Proof;
166
+ };
167
+ export interface GameStore<TInternalState extends BaseGameState, TAction, TConfig> {
168
+ send(event: {
169
+ readonly type: string;
170
+ } & RandomValuePoolEvent & Record<string, unknown>): void;
171
+ getSnapshot(): {
172
+ readonly context: GameContext<TAction, TInternalState, TConfig>;
173
+ };
174
+ }
175
+ export declare abstract class MultiActionGame<TInternalState extends BaseGameState, TExternalState extends BaseGameState, TAction extends {
176
+ readonly type: string;
177
+ }, TConfig> implements Game {
178
+ readonly gameId: OrigamiGame;
179
+ readonly edge: number;
180
+ protected constructor(gameId: OrigamiGame, edge: number);
181
+ abstract get supportedEdgeConfigurations(): [number, ...number[]];
182
+ abstract getValidNextActionTypes(state: TExternalState): ReadonlyArray<TAction['type']>;
183
+ protected abstract createGameStore(): GameStore<TInternalState, TAction, TConfig>;
184
+ protected abstract generateRandomValues(generator: RandomNumberGenerator): RandomValuePool;
185
+ protected abstract toExternalState(internal: TInternalState): TExternalState;
186
+ play(generator: RandomNumberGenerator, action: {
187
+ readonly type: string;
188
+ } & Record<string, unknown>, previousActions?: readonly UntypedGameContext[]): PlayResult;
189
+ private validateEdge;
190
+ }
191
+ //#endregion
192
+ //#region src/multiplier-container.d.ts
193
+ export declare class MultiplierContainer<TMultipliers> {
194
+ readonly multipliers: Map<number, TMultipliers>;
195
+ constructor(multipliers: Map<number, TMultipliers>);
196
+ getMultipliers(edge: number): TMultipliers;
197
+ getAllMultipliers(): {
198
+ edge: number;
199
+ multipliers: TMultipliers;
200
+ }[];
201
+ }
202
+ //#endregion
203
+ //#region src/outcomes.d.ts
204
+ export type Outcome<TSlot extends string = string> = Readonly<{
205
+ slot: TSlot;
206
+ multiplier: Big;
207
+ weight: number;
208
+ }>;
209
+ export type OutcomeDistribution<TSlot extends string = string> = Readonly<{
210
+ outcomes: readonly Outcome<TSlot>[];
211
+ totalWeight: number;
212
+ }>;
213
+ export interface DescribesOutcomes<TSelection, TSlot extends string = string> {
214
+ describeOutcomes(selection: TSelection, edge: number): OutcomeDistribution<TSlot>;
215
+ }
216
+ export declare const rtpOf: ({ outcomes, totalWeight }: OutcomeDistribution) => Big;
217
+ export declare const maxMultiplierOf: ({ outcomes }: OutcomeDistribution) => Big;
218
+ export declare const probabilityOf: <TSlot extends string>({ outcomes, totalWeight }: OutcomeDistribution<TSlot>, slot: TSlot) => Big;
219
+ //#endregion
220
+ //#region src/utils/big-max.d.ts
221
+ export declare const bigMax: (a: Big, b: Big) => Big;
222
+ //#endregion
223
+ //#region src/utils/big-min.d.ts
224
+ export declare const bigMin: (a: Big, b: Big) => Big;
225
+ //#endregion
226
+ //#region src/utils/multiplier-calculator.d.ts
227
+ export declare abstract class MultiplierCalculator {
228
+ static withMultiplier(multiplier: Big, edge: number): Big;
229
+ static withWinChance(edge: number, winChance: Big): Big;
230
+ }
231
+ //#endregion
232
+ //#region src/utils/ncr.d.ts
233
+ export declare const nCr: (n: number, r: number) => number;
234
+ //#endregion
235
+ export { MultipleRandomValues, Proof, RandomNumberGenerator, SingleRandomValue };
@@ -0,0 +1,235 @@
1
+ import { i as SingleRandomValue, n as Proof, r as RandomNumberGenerator, t as MultipleRandomValues } from "./random-l3djG58u.js";
2
+ import Big from "big.js";
3
+ //#region src/cards/types.d.ts
4
+ export declare const MIN_CARD_VALUE = 0;
5
+ export declare const MAX_CARD_VALUE = 51;
6
+ export declare const CARD_VALUES: number[];
7
+ export declare const CARD_SUITS: readonly ["Spades", "Hearts", "Diamonds", "Clubs"];
8
+ export type CardSuit = (typeof CARD_SUITS)[number];
9
+ export declare const CARD_RANKS: readonly ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"];
10
+ export type CardRank = (typeof CARD_RANKS)[number];
11
+ export declare const FACE_CARD_RANKS: readonly CardRank[];
12
+ export type CardValue = (typeof CARD_VALUES)[number];
13
+ export type CardDto = {
14
+ rank: CardRank;
15
+ suit: CardSuit;
16
+ cardValue: CardValue;
17
+ };
18
+ export declare const CARD_SUIT_COLORS: Record<CardSuit, 'red' | 'black'>;
19
+ //#endregion
20
+ //#region src/cards/card.d.ts
21
+ export declare class Card {
22
+ readonly value: CardValue;
23
+ constructor(rawValue: number);
24
+ get suit(): CardSuit;
25
+ get rank(): CardRank;
26
+ toDto(): CardDto;
27
+ }
28
+ //#endregion
29
+ //#region src/errors/game.error.d.ts
30
+ export declare enum GameErrorCode {
31
+ UNSUPPORTED_EDGE = "UNSUPPORTED_EDGE",
32
+ INPUT_VALIDATION_ERROR = "INPUT_VALIDATION_ERROR"
33
+ }
34
+ export declare class GameError extends Error {
35
+ static createUnsupportedEdgeError(message: string): GameError;
36
+ static createValidationError(message: string): GameError;
37
+ static isGameError(error: unknown): error is GameError;
38
+ errorCode: GameErrorCode;
39
+ constructor(errorCode: GameErrorCode, message?: string);
40
+ }
41
+ //#endregion
42
+ //#region src/origami-game.d.ts
43
+ export declare enum OrigamiGame {
44
+ DICE = "DICE",
45
+ MINES = "MINES",
46
+ KENO = "KENO",
47
+ LIMBO = "LIMBO",
48
+ ADVANCED_DICE = "ADVANCED_DICE",
49
+ BACCARAT = "BACCARAT",
50
+ DIAMONDS = "DIAMONDS",
51
+ PLINKO = "PLINKO",
52
+ ROULETTE = "ROULETTE",
53
+ WHEEL = "WHEEL",
54
+ BLACKJACK = "BLACKJACK",
55
+ BLITZ = "BLITZ",
56
+ DARTS = "DARTS"
57
+ }
58
+ //#endregion
59
+ //#region src/game.d.ts
60
+ export interface Game {
61
+ gameId: OrigamiGame;
62
+ supportedEdgeConfigurations: [number, ...number[]];
63
+ }
64
+ //#endregion
65
+ //#region src/payout.d.ts
66
+ /**
67
+ * A single exact payout amount from a game round.
68
+ *
69
+ * A round returns zero or more legs. Neither their order nor their count is
70
+ * significant: a leg is not tied to a particular bet, an amount may be zero,
71
+ * and the number of legs need not match the number of bets placed. The only
72
+ * supported operation is summing them.
73
+ *
74
+ * Amounts are gross — they include returned stake — and unrounded. Rounding to
75
+ * a currency's precision is the consuming service's decision, not this
76
+ * package's.
77
+ */
78
+ export type PayoutLeg = Readonly<{
79
+ amount: Big;
80
+ }>;
81
+ /**
82
+ * A round's payout, expressed as its constituent legs.
83
+ */
84
+ export type Payout = Readonly<{
85
+ legs: readonly PayoutLeg[];
86
+ }>;
87
+ /**
88
+ * Totals a round's payout.
89
+ *
90
+ * Summing the legs is lossless; reconstructing the total from a rounded
91
+ * payout multiplier is not, so this is the only supported way to arrive at
92
+ * what a round pays.
93
+ */
94
+ export declare const sumPayout: (payout: Payout) => Big;
95
+ //#endregion
96
+ //#region src/single-action-game.d.ts
97
+ export type EmptyGameOutput = {};
98
+ export interface GetGameResultRequest<TGameInputs> {
99
+ generator: RandomNumberGenerator;
100
+ edge: number;
101
+ gameInputs: TGameInputs;
102
+ }
103
+ export interface GameResult<TGameOutputs = EmptyGameOutput> {
104
+ isFinished: boolean;
105
+ payout: Payout;
106
+ payoutMultiplier: Big;
107
+ outputs: TGameOutputs & {
108
+ result: number[];
109
+ };
110
+ randomValues: SingleRandomValue | MultipleRandomValues;
111
+ }
112
+ export declare abstract class SingleActionGame<TGameInputs, TGameOutputs = EmptyGameOutput> implements Game {
113
+ readonly gameId: OrigamiGame;
114
+ protected constructor(gameId: OrigamiGame);
115
+ abstract get supportedEdgeConfigurations(): [number, ...number[]];
116
+ abstract getBetAmount(gameInputs: TGameInputs): Big;
117
+ getGameResult(request: GetGameResultRequest<TGameInputs>): GameResult<TGameOutputs>;
118
+ protected abstract determineGameResult(request: GetGameResultRequest<TGameInputs>): GameResult<TGameOutputs>;
119
+ }
120
+ //#endregion
121
+ //#region src/game-descriptor.d.ts
122
+ export interface GameDescriptor<TInputs, TOutputs = EmptyGameOutput> {
123
+ readonly id: OrigamiGame;
124
+ readonly create: () => SingleActionGame<TInputs, TOutputs>;
125
+ readonly suppliesOwnBetAmount: boolean;
126
+ }
127
+ //#endregion
128
+ //#region src/random-value-pool.d.ts
129
+ export declare class RandomValuePool {
130
+ private readonly source;
131
+ private index;
132
+ private constructor();
133
+ static from(result: MultipleRandomValues, count: number): RandomValuePool;
134
+ get consumedValues(): number[];
135
+ toMultipleRandomValues(): MultipleRandomValues;
136
+ takeOne(): number;
137
+ take(count: number): ReadonlyArray<number>;
138
+ }
139
+ //#endregion
140
+ //#region src/multi-action-game.d.ts
141
+ export type BaseInProgressState = Readonly<{
142
+ isFinished: false;
143
+ multiplier: Big;
144
+ }>;
145
+ export type BaseFinishedState = Readonly<{
146
+ isFinished: true;
147
+ payout: Payout;
148
+ multiplier: Big;
149
+ randomValues: number[];
150
+ }>;
151
+ export type BaseGameState = BaseInProgressState | BaseFinishedState;
152
+ export type GameContext<TAction, TState extends BaseGameState, TConfig> = {
153
+ readonly playerArgument: TAction;
154
+ readonly state: TState;
155
+ readonly config: TConfig;
156
+ readonly additionalBetAmount?: Big;
157
+ };
158
+ export type RandomValuePoolEvent = Readonly<{
159
+ randomValuePool: RandomValuePool;
160
+ }>;
161
+ export type UntypedGameContext = GameContext<{
162
+ readonly type: string;
163
+ }, BaseGameState, unknown>;
164
+ export type PlayResult = UntypedGameContext & {
165
+ readonly proof: Proof;
166
+ };
167
+ export interface GameStore<TInternalState extends BaseGameState, TAction, TConfig> {
168
+ send(event: {
169
+ readonly type: string;
170
+ } & RandomValuePoolEvent & Record<string, unknown>): void;
171
+ getSnapshot(): {
172
+ readonly context: GameContext<TAction, TInternalState, TConfig>;
173
+ };
174
+ }
175
+ export declare abstract class MultiActionGame<TInternalState extends BaseGameState, TExternalState extends BaseGameState, TAction extends {
176
+ readonly type: string;
177
+ }, TConfig> implements Game {
178
+ readonly gameId: OrigamiGame;
179
+ readonly edge: number;
180
+ protected constructor(gameId: OrigamiGame, edge: number);
181
+ abstract get supportedEdgeConfigurations(): [number, ...number[]];
182
+ abstract getValidNextActionTypes(state: TExternalState): ReadonlyArray<TAction['type']>;
183
+ protected abstract createGameStore(): GameStore<TInternalState, TAction, TConfig>;
184
+ protected abstract generateRandomValues(generator: RandomNumberGenerator): RandomValuePool;
185
+ protected abstract toExternalState(internal: TInternalState): TExternalState;
186
+ play(generator: RandomNumberGenerator, action: {
187
+ readonly type: string;
188
+ } & Record<string, unknown>, previousActions?: readonly UntypedGameContext[]): PlayResult;
189
+ private validateEdge;
190
+ }
191
+ //#endregion
192
+ //#region src/multiplier-container.d.ts
193
+ export declare class MultiplierContainer<TMultipliers> {
194
+ readonly multipliers: Map<number, TMultipliers>;
195
+ constructor(multipliers: Map<number, TMultipliers>);
196
+ getMultipliers(edge: number): TMultipliers;
197
+ getAllMultipliers(): {
198
+ edge: number;
199
+ multipliers: TMultipliers;
200
+ }[];
201
+ }
202
+ //#endregion
203
+ //#region src/outcomes.d.ts
204
+ export type Outcome<TSlot extends string = string> = Readonly<{
205
+ slot: TSlot;
206
+ multiplier: Big;
207
+ weight: number;
208
+ }>;
209
+ export type OutcomeDistribution<TSlot extends string = string> = Readonly<{
210
+ outcomes: readonly Outcome<TSlot>[];
211
+ totalWeight: number;
212
+ }>;
213
+ export interface DescribesOutcomes<TSelection, TSlot extends string = string> {
214
+ describeOutcomes(selection: TSelection, edge: number): OutcomeDistribution<TSlot>;
215
+ }
216
+ export declare const rtpOf: ({ outcomes, totalWeight }: OutcomeDistribution) => Big;
217
+ export declare const maxMultiplierOf: ({ outcomes }: OutcomeDistribution) => Big;
218
+ export declare const probabilityOf: <TSlot extends string>({ outcomes, totalWeight }: OutcomeDistribution<TSlot>, slot: TSlot) => Big;
219
+ //#endregion
220
+ //#region src/utils/big-max.d.ts
221
+ export declare const bigMax: (a: Big, b: Big) => Big;
222
+ //#endregion
223
+ //#region src/utils/big-min.d.ts
224
+ export declare const bigMin: (a: Big, b: Big) => Big;
225
+ //#endregion
226
+ //#region src/utils/multiplier-calculator.d.ts
227
+ export declare abstract class MultiplierCalculator {
228
+ static withMultiplier(multiplier: Big, edge: number): Big;
229
+ static withWinChance(edge: number, winChance: Big): Big;
230
+ }
231
+ //#endregion
232
+ //#region src/utils/ncr.d.ts
233
+ export declare const nCr: (n: number, r: number) => number;
234
+ //#endregion
235
+ export { MultipleRandomValues, Proof, RandomNumberGenerator, SingleRandomValue };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{Guard as e}from"@betorigami/guard";import t from"fast-deep-equal";import n from"big.js";const r=0,i=51,a=Array.from({length:52},(e,t)=>t),o=[`Spades`,`Hearts`,`Diamonds`,`Clubs`],s=[`Ace`,`Two`,`Three`,`Four`,`Five`,`Six`,`Seven`,`Eight`,`Nine`,`Ten`,`Jack`,`Queen`,`King`],c=[`Jack`,`Queen`,`King`],l={Hearts:`red`,Diamonds:`red`,Spades:`black`,Clubs:`black`};var u=class{value;constructor(t){let n=a[t];e.againstNullOrUndefined(n,`Invalid card value: ${t}`),this.value=n}get suit(){let t=Math.floor(this.value/13),n=o[t];return e.againstNullOrUndefined(n,`Invalid suit index: ${t}`),n}get rank(){let t=this.value%13,n=s[t];return e.againstNullOrUndefined(n,`Invalid rank index: ${t}`),n}toDto(){return{rank:this.rank,suit:this.suit,cardValue:this.value}}};let d=function(e){return e.UNSUPPORTED_EDGE=`UNSUPPORTED_EDGE`,e.INPUT_VALIDATION_ERROR=`INPUT_VALIDATION_ERROR`,e}({});var f=class e extends Error{static createUnsupportedEdgeError(t){return new e(`UNSUPPORTED_EDGE`,t)}static createValidationError(t){return new e(`INPUT_VALIDATION_ERROR`,t)}static isGameError(t){return t instanceof e}errorCode;constructor(e,t){super(t),this.errorCode=e}},p=class{gameId;edge;constructor(e,t){this.gameId=e,this.edge=t,this.validateEdge(t)}play(n,r,i=[]){let a=this.createGameStore(),o=this.generateRandomValues(n),s=o.toMultipleRandomValues();for(let e of i)a.send({...e.playerArgument,randomValuePool:o});let c=i[i.length-1];c&&e.against(!t(c.state,this.toExternalState(a.getSnapshot().context.state)),`The final state of the game does not match the expected state based on the previous actions`),a.send({...r,randomValuePool:o});let l=a.getSnapshot().context;return{playerArgument:l.playerArgument,state:this.toExternalState(l.state),config:l.config,additionalBetAmount:l.additionalBetAmount,proof:{proofHash:s.proofHash,proofString:s.proofString}}}validateEdge(t){let n=this.supportedEdgeConfigurations.includes(t);e.against(!n,()=>f.createUnsupportedEdgeError(`${this.gameId} does not support ${t}% edge. Supported edge configurations include: ${this.supportedEdgeConfigurations}`))}},m=class{multipliers;constructor(e){this.multipliers=e}getMultipliers(e){let t=this.multipliers.get(e);if(t===void 0)throw Error(`Multipliers for ${e} not found`);return t}getAllMultipliers(){return Array.from(this.multipliers.entries()).map(([e,t])=>({edge:e,multipliers:t}))}};let h=function(e){return e.DICE=`DICE`,e.MINES=`MINES`,e.KENO=`KENO`,e.LIMBO=`LIMBO`,e.ADVANCED_DICE=`ADVANCED_DICE`,e.BACCARAT=`BACCARAT`,e.DIAMONDS=`DIAMONDS`,e.PLINKO=`PLINKO`,e.ROULETTE=`ROULETTE`,e.WHEEL=`WHEEL`,e.BLACKJACK=`BLACKJACK`,e.BLITZ=`BLITZ`,e.DARTS=`DARTS`,e}({});const g=({outcomes:t,totalWeight:r})=>(e.against(r<=0,`An outcome distribution must have a positive total weight`),t.reduce((e,{multiplier:t,weight:n})=>e.plus(t.times(n)),n(0)).div(r)),_=({outcomes:t})=>(e.againstEmptyArray(t,`An outcome distribution must have at least one outcome`),t.reduce((e,{multiplier:t})=>t.gt(e)?t:e,t[0].multiplier)),v=({outcomes:t,totalWeight:r},i)=>{e.against(r<=0,`An outcome distribution must have a positive total weight`);let a=t.filter(e=>e.slot===i);return e.againstEmptyArray(a,`Slot ${i} is not part of the outcome distribution`),n(a.reduce((e,t)=>e+t.weight,0)).div(r)},y=e=>e.legs.reduce((e,t)=>e.add(t.amount),new n(`0`));var b=class t{source;index=0;constructor(e){this.source=e}static from(n,r){return e.against(r<=0,`Count must be positive, got ${r}`),e.against(n.values.length!==r,`Expected ${r} values, got ${n.values.length}`),new t(n)}get consumedValues(){return this.source.values.slice(0,this.index)}toMultipleRandomValues(){return this.source}takeOne(){let t=this.source.values[this.index];return e.againstUndefined(t,`RandomValuePool exhausted`),this.index+=1,t}take(t){e.against(this.index+t>this.source.values.length,`RandomValuePool exhausted: requested ${t} but only ${this.source.values.length-this.index} remain`);let n=this.source.values.slice(this.index,this.index+t);return this.index+=t,n}},x=class{gameId;constructor(e){this.gameId=e}getGameResult(t){let n=this.supportedEdgeConfigurations.includes(t.edge);return e.against(!n,()=>f.createUnsupportedEdgeError(`${this.gameId} does not support ${t.edge}% edge. Supported edge configurations include: ${this.supportedEdgeConfigurations}`)),this.determineGameResult(t)}};const S=(e,t)=>e.gt(t)?e:t,C=(e,t)=>e.lt(t)?e:t;var w=class{static withMultiplier(e,t){return new n(e).times(new n(100-t).div(100))}static withWinChance(e,t){return n(100-e).div(t)}};const T=(e,t)=>{if(t<0||e<0||t>e)throw Error(`Invalid input: ensure 0 ≤ r ≤ n and n ≥ 0.`);t>e-t&&(t=e-t);let n=1;for(let r=1;r<=t;r++)n=n*(e-r+1)/r;return n};export{s as CARD_RANKS,o as CARD_SUITS,l as CARD_SUIT_COLORS,a as CARD_VALUES,u as Card,c as FACE_CARD_RANKS,f as GameError,d as GameErrorCode,i as MAX_CARD_VALUE,r as MIN_CARD_VALUE,p as MultiActionGame,w as MultiplierCalculator,m as MultiplierContainer,h as OrigamiGame,b as RandomValuePool,x as SingleActionGame,S as bigMax,C as bigMin,_ as maxMultiplierOf,T as nCr,v as probabilityOf,g as rtpOf,y as sumPayout};
@@ -0,0 +1,36 @@
1
+ //#region src/random.d.ts
2
+ type Proof = {
3
+ proofHash: string;
4
+ proofString: string;
5
+ };
6
+ type SingleRandomValue = {
7
+ value: number;
8
+ values: number[];
9
+ count: 1;
10
+ min: number;
11
+ max: number;
12
+ } & Proof;
13
+ type MultipleRandomValues = {
14
+ values: number[];
15
+ count: number;
16
+ min: number;
17
+ max: number;
18
+ } & Proof;
19
+ interface RandomNumberGenerator {
20
+ getRandomInt({ min, max }: {
21
+ min: number;
22
+ max: number;
23
+ }): SingleRandomValue;
24
+ getUniqueRandomInts({ min, max, count }: {
25
+ min: number;
26
+ max: number;
27
+ count: number;
28
+ }): MultipleRandomValues;
29
+ getRandomIntsWithReplacement({ min, max, count }: {
30
+ min: number;
31
+ max: number;
32
+ count: number;
33
+ }): MultipleRandomValues;
34
+ }
35
+ //#endregion
36
+ export { SingleRandomValue as i, Proof as n, RandomNumberGenerator as r, MultipleRandomValues as t };
@@ -0,0 +1,36 @@
1
+ //#region src/random.d.ts
2
+ type Proof = {
3
+ proofHash: string;
4
+ proofString: string;
5
+ };
6
+ type SingleRandomValue = {
7
+ value: number;
8
+ values: number[];
9
+ count: 1;
10
+ min: number;
11
+ max: number;
12
+ } & Proof;
13
+ type MultipleRandomValues = {
14
+ values: number[];
15
+ count: number;
16
+ min: number;
17
+ max: number;
18
+ } & Proof;
19
+ interface RandomNumberGenerator {
20
+ getRandomInt({ min, max }: {
21
+ min: number;
22
+ max: number;
23
+ }): SingleRandomValue;
24
+ getUniqueRandomInts({ min, max, count }: {
25
+ min: number;
26
+ max: number;
27
+ count: number;
28
+ }): MultipleRandomValues;
29
+ getRandomIntsWithReplacement({ min, max, count }: {
30
+ min: number;
31
+ max: number;
32
+ count: number;
33
+ }): MultipleRandomValues;
34
+ }
35
+ //#endregion
36
+ export { SingleRandomValue as i, Proof as n, RandomNumberGenerator as r, MultipleRandomValues as t };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{getRandomInt({}){throw Error(`Not implemented`)}getRandomIntsWithReplacement({}){throw Error(`Not implemented`)}getUniqueRandomInts({}){throw Error(`Not implemented`)}},t=class extends e{value;constructor(e){super(),this.value=e}getRandomInt(e){return{...e,value:this.value,values:[this.value],count:1,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}},n=class extends e{values;constructor(e){super(),this.values=e}getRandomIntsWithReplacement(e){return{...e,values:this.values,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}getUniqueRandomInts(e){return{...e,values:this.values,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}};exports.MultipleRandomNumberGeneratorStub=n,exports.RandomNumberGeneratorStub=e,exports.SingleRandomNumberGeneratorStub=t;
@@ -0,0 +1,41 @@
1
+ import { i as SingleRandomValue, r as RandomNumberGenerator, t as MultipleRandomValues } from "../random-l3djG58u.cjs";
2
+ //#region src/testing/index.d.ts
3
+ export declare abstract class RandomNumberGeneratorStub implements RandomNumberGenerator {
4
+ getRandomInt({}: {
5
+ min: number;
6
+ max: number;
7
+ }): SingleRandomValue;
8
+ getRandomIntsWithReplacement({}: {
9
+ min: number;
10
+ max: number;
11
+ count: number;
12
+ }): MultipleRandomValues;
13
+ getUniqueRandomInts({}: {
14
+ min: number;
15
+ max: number;
16
+ count: number;
17
+ }): MultipleRandomValues;
18
+ }
19
+ export declare class SingleRandomNumberGeneratorStub extends RandomNumberGeneratorStub {
20
+ private readonly value;
21
+ constructor(value: number);
22
+ getRandomInt(args: {
23
+ min: number;
24
+ max: number;
25
+ }): SingleRandomValue;
26
+ }
27
+ export declare class MultipleRandomNumberGeneratorStub extends RandomNumberGeneratorStub {
28
+ private readonly values;
29
+ constructor(values: number[]);
30
+ getRandomIntsWithReplacement(args: {
31
+ min: number;
32
+ max: number;
33
+ count: number;
34
+ }): MultipleRandomValues;
35
+ getUniqueRandomInts(args: {
36
+ min: number;
37
+ max: number;
38
+ count: number;
39
+ }): MultipleRandomValues;
40
+ }
41
+ //#endregion
@@ -0,0 +1,41 @@
1
+ import { i as SingleRandomValue, r as RandomNumberGenerator, t as MultipleRandomValues } from "../random-l3djG58u.js";
2
+ //#region src/testing/index.d.ts
3
+ export declare abstract class RandomNumberGeneratorStub implements RandomNumberGenerator {
4
+ getRandomInt({}: {
5
+ min: number;
6
+ max: number;
7
+ }): SingleRandomValue;
8
+ getRandomIntsWithReplacement({}: {
9
+ min: number;
10
+ max: number;
11
+ count: number;
12
+ }): MultipleRandomValues;
13
+ getUniqueRandomInts({}: {
14
+ min: number;
15
+ max: number;
16
+ count: number;
17
+ }): MultipleRandomValues;
18
+ }
19
+ export declare class SingleRandomNumberGeneratorStub extends RandomNumberGeneratorStub {
20
+ private readonly value;
21
+ constructor(value: number);
22
+ getRandomInt(args: {
23
+ min: number;
24
+ max: number;
25
+ }): SingleRandomValue;
26
+ }
27
+ export declare class MultipleRandomNumberGeneratorStub extends RandomNumberGeneratorStub {
28
+ private readonly values;
29
+ constructor(values: number[]);
30
+ getRandomIntsWithReplacement(args: {
31
+ min: number;
32
+ max: number;
33
+ count: number;
34
+ }): MultipleRandomValues;
35
+ getUniqueRandomInts(args: {
36
+ min: number;
37
+ max: number;
38
+ count: number;
39
+ }): MultipleRandomValues;
40
+ }
41
+ //#endregion
@@ -0,0 +1 @@
1
+ var e=class{getRandomInt({}){throw Error(`Not implemented`)}getRandomIntsWithReplacement({}){throw Error(`Not implemented`)}getUniqueRandomInts({}){throw Error(`Not implemented`)}},t=class extends e{value;constructor(e){super(),this.value=e}getRandomInt(e){return{...e,value:this.value,values:[this.value],count:1,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}},n=class extends e{values;constructor(e){super(),this.values=e}getRandomIntsWithReplacement(e){return{...e,values:this.values,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}getUniqueRandomInts(e){return{...e,values:this.values,proofHash:`stub-proof-hash`,proofString:`stub-proof-string`}}};export{n as MultipleRandomNumberGeneratorStub,e as RandomNumberGeneratorStub,t as SingleRandomNumberGeneratorStub};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betorigami/game-engine",
3
- "version": "0.0.0",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",