SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
3.0 KB · 89 lines typescript
Raw Blame History
1import type { GameDefinition, GameSymbol, Grid } from "../types";2import type { Rng } from "../rng";34export interface ReelTables {5  /** Per reel: symbol ids and cumulative weights (base game). */6  base: { ids: string[]; weights: Float64Array }[];7  /** Per reel: free-spin tables. */8  free: { ids: string[]; weights: Float64Array }[];9  byId: Map<string, GameSymbol>;10  regularIds: string[];11}1213const tableCache = new WeakMap<GameDefinition, ReelTables>();1415function weightFor(sym: GameSymbol, reel: number, free: boolean): number {16  const w = free && sym.freeSpinWeight !== undefined ? sym.freeSpinWeight : sym.weight;17  if (Array.isArray(w)) return w[Math.min(reel, w.length - 1)] ?? 0;18  return w;19}2021/** Build (and cache) weighted draw tables for a definition, for up to 8 reels. */22export function reelTables(def: GameDefinition): ReelTables {23  const cached = tableCache.get(def);24  if (cached) return cached;25  const maxReels = Math.max(def.grid.reels, ...(def.dynamicGrid?.sequence.map((s) => s[0]) ?? [0]));26  const build = (free: boolean) =>27    Array.from({ length: maxReels }, (_, reel) => {28      const ids: string[] = [];29      const ws: number[] = [];30      for (const s of def.symbols) {31        const w = weightFor(s, reel, free);32        if (w > 0) {33          ids.push(s.id);34          ws.push(w);35        }36      }37      return { ids, weights: Float64Array.from(ws) };38    });39  const tables: ReelTables = {40    base: build(false),41    free: build(true),42    byId: new Map(def.symbols.map((s) => [s.id, s])),43    regularIds: def.symbols.filter((s) => s.kind === "regular").map((s) => s.id),44  };45  tableCache.set(def, tables);46  return tables;47}4849export function drawSymbol(tables: ReelTables, reel: number, free: boolean, rng: Rng): string {50  const t = (free ? tables.free : tables.base)[reel];51  return t.ids[rng.weighted(t.weights)];52}5354/** Draw a fresh grid of `reels × rows`. */55export function drawGrid(def: GameDefinition, rng: Rng, free: boolean, reels = def.grid.reels, rows = def.grid.rows): Grid {56  const tables = reelTables(def);57  const grid: Grid = [];58  for (let r = 0; r < reels; r++) {59    const col: string[] = new Array(rows);60    for (let y = 0; y < rows; y++) col[y] = drawSymbol(tables, r, free, rng);61    grid.push(col);62  }63  return grid;64}6566/** Apply stacked-wild expansion: with probability p a landed wild fills its reel. */67export function applyStackedWilds(def: GameDefinition, grid: Grid, rng: Rng): number[] {68  const w = def.wild;69  if (!w?.stacked) return [];70  const stacked: number[] = [];71  for (let r = 0; r < grid.length; r++) {72    if (grid[r].includes(w.id) && rng.chance(w.stacked)) {73      for (let y = 0; y < grid[r].length; y++) grid[r][y] = w.id;74      stacked.push(r);75    }76  }77  return stacked;78}7980export function cloneGrid(g: Grid): Grid {81  return g.map((c) => c.slice());82}8384export function countSymbol(grid: Grid, id: string): [number, number][] {85  const out: [number, number][] = [];86  for (let r = 0; r < grid.length; r++) for (let y = 0; y < grid[r].length; y++) if (grid[r][y] === id) out.push([r, y]);87  return out;88}89