← back to dqian__casino-cli

Function bodies 63 total

All specs Real LLM only Function bodies
parseKey function · typescript · L8-L59 (52 LOC)
src/keybindings.ts
export function parseKey(data: Buffer): KeyEvent {
  const raw = data.toString("utf-8");
  const bytes = [...data];

  // Ctrl+C
  if (bytes.length === 1 && bytes[0] === 3) {
    return { name: "c", ctrl: true, shift: false, raw };
  }

  // Enter
  if (bytes.length === 1 && (bytes[0] === 13 || bytes[0] === 10)) {
    return { name: "return", ctrl: false, shift: false, raw };
  }

  // Backspace
  if (bytes.length === 1 && (bytes[0] === 127 || bytes[0] === 8)) {
    return { name: "backspace", ctrl: false, shift: false, raw };
  }

  // Tab
  if (bytes.length === 1 && bytes[0] === 9) {
    return { name: "tab", ctrl: false, shift: false, raw };
  }

  // Escape sequences
  if (bytes[0] === 0x1b) {
    if (bytes.length === 1) {
      return { name: "escape", ctrl: false, shift: false, raw };
    }
    if (bytes[1] === 0x5b) {
      const rawStr = raw;
      switch (rawStr) {
        case "\x1b[A": return { name: "up", ctrl: false, shift: false, raw };
        case "\x1b[B": return { nam
renderScreen function · typescript · L11-L17 (7 LOC)
src/renderer.ts
export function renderScreen(state: AppState): void {
  if (state.screen === "roulette") {
    renderGameScreen(state);
    return;
  }
  renderMenuScreen(state);
}
renderMenuScreen function · typescript · L19-L142 (124 LOC)
src/renderer.ts
function renderMenuScreen(state: AppState): void {
  const { columns: width, rows: height } = process.stdout;
  const lines: string[] = [];

  // ASCII art title (raw, no ANSI)
  const titleRaw = [
    "  ██████╗ █████╗ ███████╗██╗███╗   ██╗ ██████╗ ",
    " ██╔════╝██╔══██╗██╔════╝██║████╗  ██║██╔═══██╗",
    " ██║     ███████║███████╗██║██╔██╗ ██║██║   ██║",
    " ██║     ██╔══██║╚════██║██║██║╚██╗██║██║   ██║",
    " ╚██████╗██║  ██║███████║██║██║ ╚████║╚██████╔╝",
    "  ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝╚═╝  ╚═══╝ ╚═════╝ ",
  ];
  const titleW = titleRaw[0]!.length;

  // Shimmer: ~0.25s sweep across ~50 chars every ~6s
  // menuAnimFrame ticks at 25ms intervals
  const shimmerCycle = 250; // frames per full cycle (~6.25s)
  const shimmerSweep = 10;  // frames for the sweep (~0.25s)
  const shimmerFrame = state.menuAnimFrame % shimmerCycle;
  const shimmerActive = shimmerFrame < shimmerSweep;
  const shimmerProgress = shimmerActive ? shimmerFrame / (shimmerSweep - 1) : -1;
  const shimme
renderGameScreen function · typescript · L144-L163 (20 LOC)
src/renderer.ts
function renderGameScreen(state: AppState): void {
  const { columns: width, rows: height } = process.stdout;
  const lines = renderRouletteScreen(state);

  // Render hotkey grid at bottom
  const hotkeyLines = renderHotkeyGrid(width, state.roulette.phase);
  const hotkeyHeight = hotkeyLines.length + 1; // +1 for separator

  // Fill space between content and hotkeys
  while (lines.length < height - hotkeyHeight) lines.push("");

  // Separator + hotkeys
  lines.push(`  ${t.gray}${"─".repeat(Math.max(0, width - 4))}${t.reset}`);
  lines.push(...hotkeyLines);

  // Trim to terminal height
  while (lines.length < height) lines.push("");

  writeLines(lines, height);
}
writeLines function · typescript · L165-L176 (12 LOC)
src/renderer.ts
function writeLines(lines: string[], totalRows: number): void {
  const { columns: width } = process.stdout;
  const out = [t.cursorHome];
  for (let i = 0; i < totalRows; i++) {
    const line = lines[i] ?? "";
    const visLen = t.stripAnsi(line).length;
    // Pad to full width to overwrite previous content (no eraseLine flicker)
    out.push(line + " ".repeat(Math.max(0, width - visLen)));
    if (i < totalRows - 1) out.push("\n");
  }
  process.stdout.write(out.join(""));
}
centerAnsiText function · typescript · L178-L182 (5 LOC)
src/renderer.ts
function centerAnsiText(text: string, width: number): string {
  const visLen = t.stripAnsi(text).length;
  const pad = Math.max(0, Math.floor((width - visLen) / 2));
  return " ".repeat(pad) + text;
}
numberColor function · typescript · L11-L14 (4 LOC)
src/roulette/board.ts
export function numberColor(n: number): "green" | "red" | "black" {
  if (n === 0) return "green";
  return RED_NUMBERS.has(n) ? "red" : "black";
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
numberAt function · typescript · L38-L40 (3 LOC)
src/roulette/board.ts
export function numberAt(tableRow: number, tableCol: number): number {
  return (tableRow - 1) * 3 + (tableCol + 1);
}
virtualToGridPos function · typescript · L63-L93 (31 LOC)
src/roulette/board.ts
export function virtualToGridPos(vr: number, vc: number): GridPosition {
  // vr=-1: zero-to-row-1 border
  if (vr === -1) {
    if (vc % 2 === 1) {
      // Odd vc: trio bet (0/1/2 or 0/2/3)
      const tableCol = Math.floor(vc / 2);
      return { kind: "trio", tableCol };
    }
    // Even vc: split between 0 and first row number
    const tableCol = Math.floor(vc / 2);
    return { kind: "hborder", tableRow: 0, tableCol };
  }

  // vc=-1: left edge (street / sixline)
  if (vc < 0) {
    const evenR = vr % 2 === 0;
    const tableRow = Math.floor(vr / 2) + 1;
    if (evenR) return { kind: "street", tableRow };
    return { kind: "sixline", tableRow };
  }

  const evenR = vr % 2 === 0;
  const evenC = vc % 2 === 0;
  const tableRow = Math.floor(vr / 2) + 1;
  const tableCol = Math.floor(vc / 2);

  if (evenR && evenC) return { kind: "cell", tableRow, tableCol };
  if (!evenR && evenC) return { kind: "hborder", tableRow, tableCol };
  if (evenR && !evenC) return { kind: "vborder", t
gridPosToBet function · typescript · L96-L132 (37 LOC)
src/roulette/board.ts
export function gridPosToBet(pos: GridPosition): BetType {
  switch (pos.kind) {
    case "cell":
      return { kind: "straight", number: numberAt(pos.tableRow, pos.tableCol) };
    case "hborder": {
      // tableRow=0 means zero-to-row-1 border (split with 0)
      const n1 = pos.tableRow === 0 ? 0 : numberAt(pos.tableRow, pos.tableCol);
      const n2 = numberAt(pos.tableRow + 1, pos.tableCol);
      const sorted = [Math.min(n1, n2), Math.max(n1, n2)] as [number, number];
      return { kind: "split", numbers: sorted };
    }
    case "vborder": {
      const n1 = numberAt(pos.tableRow, pos.tableCol);
      const n2 = numberAt(pos.tableRow, pos.tableCol + 1);
      const sorted = [Math.min(n1, n2), Math.max(n1, n2)] as [number, number];
      return { kind: "split", numbers: sorted };
    }
    case "trio": {
      const nums = [0, numberAt(1, pos.tableCol), numberAt(1, pos.tableCol + 1)]
        .sort((a, b) => a - b) as [number, number, number];
      return { kind: "trio", numbe
isWinner function · typescript · L136-L169 (34 LOC)
src/roulette/board.ts
export function isWinner(num: number, bet: BetType): boolean {
  switch (bet.kind) {
    case "straight": return num === bet.number;
    case "split": return bet.numbers.includes(num);
    case "street": {
      const nums = [numberAt(bet.row, 0), numberAt(bet.row, 1), numberAt(bet.row, 2)];
      return nums.includes(num);
    }
    case "trio": return bet.numbers.includes(num);
    case "corner": return bet.numbers.includes(num);
    case "sixline": {
      for (const r of bet.rows) {
        for (let c = 0; c < 3; c++) {
          if (numberAt(r, c) === num) return true;
        }
      }
      return false;
    }
    case "red": return num !== 0 && RED_NUMBERS.has(num);
    case "black": return num !== 0 && !RED_NUMBERS.has(num);
    case "odd": return num !== 0 && num % 2 === 1;
    case "even": return num !== 0 && num % 2 === 0;
    case "low": return num >= 1 && num <= 18;
    case "high": return num >= 19 && num <= 36;
    case "dozen":
      if (bet.which === 1) return num >= 
payout function · typescript · L171-L182 (12 LOC)
src/roulette/board.ts
export function payout(bet: BetType): number {
  switch (bet.kind) {
    case "straight": return 35;
    case "split": return 17;
    case "trio": return 11;
    case "street": return 11;
    case "corner": return 8;
    case "sixline": return 5;
    case "dozen": case "column": return 2;
    case "red": case "black": case "odd": case "even": case "low": case "high": return 1;
  }
}
sameBetType function · typescript · L185-L210 (26 LOC)
src/roulette/board.ts
export function sameBetType(a: BetType, b: BetType): boolean {
  if (a.kind !== b.kind) return false;
  switch (a.kind) {
    case "straight": return a.number === (b as typeof a).number;
    case "split": {
      const bb = b as typeof a;
      return a.numbers[0] === bb.numbers[0] && a.numbers[1] === bb.numbers[1];
    }
    case "trio": {
      const bb = b as typeof a;
      return a.numbers.every((n, i) => n === bb.numbers[i]);
    }
    case "street": return a.row === (b as typeof a).row;
    case "sixline": {
      const bb = b as typeof a;
      return a.rows[0] === bb.rows[0] && a.rows[1] === bb.rows[1];
    }
    case "corner": {
      const bb = b as typeof a;
      return a.numbers.every((n, i) => n === bb.numbers[i]);
    }
    case "dozen": return a.which === (b as typeof a).which;
    case "column": return a.which === (b as typeof a).which;
    default: return true; // red, black, odd, even, low, high
  }
}
betLabel function · typescript · L213-L233 (21 LOC)
src/roulette/board.ts
export function betLabel(bet: BetType): string {
  switch (bet.kind) {
    case "straight": return String(bet.number);
    case "split": return `${bet.numbers[0]}-${bet.numbers[1]}`;
    case "trio": return `Trio ${bet.numbers[0]}-${bet.numbers[1]}-${bet.numbers[2]}`;
    case "street": return `Street ${numberAt(bet.row, 0)}-${numberAt(bet.row, 2)}`;
    case "corner": return `${bet.numbers[0]}-${bet.numbers[3]}`;
    case "sixline": return `Line ${numberAt(bet.rows[0], 2)}-${numberAt(bet.rows[1], 0)}`;
    case "red": return "RED";
    case "black": return "BLK";
    case "odd": return "ODD";
    case "even": return "EVEN";
    case "low": return "1-18";
    case "high": return "19-36";
    case "dozen":
      if (bet.which === 1) return "1st 12";
      if (bet.which === 2) return "2nd 12";
      return "3rd 12";
    case "column": return `Col ${bet.which}`;
  }
}
placeBet function · typescript · L4-L25 (22 LOC)
src/roulette/game.ts
export function placeBet(state: AppState): void {
  const rs = state.roulette;
  const amount = rs.betAmount;
  if (amount > state.balance) {
    state.message = "Not enough balance!";
    return;
  }

  const betType = cursorToBet(rs);
  if (!betType) return;

  // Stack onto existing bet of same type
  const existing = rs.bets.find(b => sameBetType(b.type, betType));
  if (existing) {
    existing.amount += amount;
  } else {
    rs.bets.push({ type: betType, amount });
  }

  state.balance -= amount;
  state.message = "";
}
Repobility · MCP-ready · https://repobility.com
cursorToBet function · typescript · L27-L53 (27 LOC)
src/roulette/game.ts
function cursorToBet(rs: { cursorZone: string; cursorVR: number; cursorVC: number }): BetType | null {
  switch (rs.cursorZone) {
    case "zero":
      return { kind: "straight", number: 0 };
    case "grid": {
      const pos = virtualToGridPos(rs.cursorVR, rs.cursorVC);
      return gridPosToBet(pos);
    }
    case "column": {
      const which = (rs.cursorVC + 1) as 1 | 2 | 3;
      return { kind: "column", which };
    }
    case "dozen": {
      const which = (rs.cursorVC + 1) as 1 | 2 | 3;
      return { kind: "dozen", which };
    }
    case "outside": {
      if (rs.cursorVR === 0) {
        const types: BetType[] = [{ kind: "red" }, { kind: "black" }, { kind: "low" }];
        return types[rs.cursorVC] ?? null;
      }
      const types: BetType[] = [{ kind: "even" }, { kind: "odd" }, { kind: "high" }];
      return types[rs.cursorVC] ?? null;
    }
  }
  return null;
}
removeBet function · typescript · L55-L66 (12 LOC)
src/roulette/game.ts
export function removeBet(state: AppState): void {
  const rs = state.roulette;
  const betType = cursorToBet(rs);
  if (!betType) return;

  const idx = rs.bets.findIndex(b => sameBetType(b.type, betType));
  if (idx === -1) return;

  state.balance += rs.bets[idx]!.amount;
  rs.bets.splice(idx, 1);
  state.message = "";
}
clearBets function · typescript · L68-L73 (6 LOC)
src/roulette/game.ts
export function clearBets(state: AppState): void {
  const total = state.roulette.bets.reduce((sum, b) => sum + b.amount, 0);
  state.balance += total;
  state.roulette.bets = [];
  state.message = "Bets cleared";
}
totalBets function · typescript · L75-L77 (3 LOC)
src/roulette/game.ts
export function totalBets(state: AppState): number {
  return state.roulette.bets.reduce((sum, b) => sum + b.amount, 0);
}
spin function · typescript · L79-L112 (34 LOC)
src/roulette/game.ts
export function spin(state: AppState, render: () => void): void {
  if (state.roulette.bets.length === 0) {
    state.message = "Place a bet first!";
    return;
  }

  state.roulette.phase = "spinning";
  state.message = "";

  const target = Math.floor(Math.random() * 37);
  state.roulette.spinTarget = target;

  const targetIdx = WHEEL_ORDER.indexOf(target);
  const totalFrames = 80 + Math.floor(Math.random() * 50);
  const startIdx = Math.floor(Math.random() * WHEEL_ORDER.length);
  // Randomize the easing power (2.0–4.0) and number of rotations (2–5)
  const easePower = 2.0 + Math.random() * 2.0;
  const rotations = 2 + Math.floor(Math.random() * 4);
  // Random delay jitter factor (0.8–1.2)
  const jitterBase = 0.8 + Math.random() * 0.4;

  // Ball mode: drop ball into bounding box above wheel
  if (state.roulette.wheelMode === "ball") {
    state.roulette.ballY = 0;
    state.roulette.ballRow = 0;
    state.roulette.ballCol = (Math.random() - 0.5) * 20;
    state.roulette.ballVY
animateSpin function · typescript · L114-L180 (67 LOC)
src/roulette/game.ts
function animateSpin(
  state: AppState,
  render: () => void,
  startIdx: number,
  targetIdx: number,
  frame: number,
  totalFrames: number,
  easePower: number,
  rotations: number,
  jitterBase: number,
): void {
  // Check for skip (Enter during spin sets spinFrame high)
  if (state.roulette.spinFrame > totalFrames) frame = totalFrames + 1;

  // Ball mode: if ball has settled, determine winner from ball's position on wheel
  if (state.roulette.wheelMode === "ball" && !state.roulette.ballBouncing && frame > 30) {
    const winNum = ballLandingNumber(state.roulette);
    state.roulette.spinHighlight = winNum;
    state.roulette.spinHalfStep = false;
    finishSpin(state, render, winNum);
    return;
  }

  if (frame > totalFrames) {
    const finalNum = WHEEL_ORDER[targetIdx] ?? 0;
    state.roulette.spinHighlight = finalNum;
    state.roulette.spinHalfStep = false;

    if (state.roulette.wheelMode === "ball" && state.roulette.ballBouncing) {
      // Ball still bouncing — wait f
stepBallPhysics function · typescript · L182-L239 (58 LOC)
src/roulette/game.ts
function stepBallPhysics(rs: AppState["roulette"], tick: number): void {
  // Bounding box: y 0 (top) to FLOOR (bottom), x -HALF_W to +HALF_W
  const FLOOR = 5;
  const HALF_W = 17;
  const GRAVITY = 0.04;
  const BOUNCE = 0.5;
  const WALL_BOUNCE = 0.6;
  const FRICTION = 0.97;

  // Calm factor: 0 for first 2s, ramps to 1 over next 3s
  const elapsed = tick * 0.02; // 20ms per tick
  const calm = elapsed > 2 ? Math.min(1, (elapsed - 2) / 3) : 0;

  // Gravity pulls ball down
  rs.ballVY += GRAVITY;

  // Move
  rs.ballY += rs.ballVY;
  rs.ballCol += rs.ballVX;

  // Bounce off floor — spinning wheel kicks ball back up
  if (rs.ballY >= FLOOR) {
    rs.ballY = FLOOR;
    rs.ballVY = -Math.abs(rs.ballVY) * BOUNCE;
    // Wheel kick: spinning wheel imparts upward energy, fades as wheel slows
    rs.ballVY -= 0.35 * (1 - calm);
    // Random horizontal kick on bounce, fades with calming
    rs.ballVX += (Math.random() - 0.5) * (2.0 * (1 - calm * 0.8));
  }

  // Bounce off ceiling
  if (
startBallPhysics function · typescript · L241-L260 (20 LOC)
src/roulette/game.ts
function startBallPhysics(state: AppState, render: () => void): void {
  let tickCount = 0;
  const step = () => {
    const rs = state.roulette;
    if (!rs.ballBouncing) return;
    // Skip check (user pressed Enter)
    if (rs.spinFrame > 9000) {
      rs.ballRow = 6;
      rs.ballCol = 0;
      rs.ballBouncing = false;
      render();
      return;
    }
    tickCount++;
    stepBallPhysics(rs, tickCount);
    render();
    setTimeout(step, 20);
  };
  setTimeout(step, 20);
}
Same scanner, your repo: https://repobility.com — Repobility
ballLandingNumber function · typescript · L263-L270 (8 LOC)
src/roulette/game.ts
function ballLandingNumber(rs: AppState["roulette"]): number {
  const currentIdx = WHEEL_ORDER.indexOf(rs.spinHighlight);
  // Each wheel slot is 4 chars wide; ballCol is offset from center
  const slotOffset = Math.round(rs.ballCol / 4);
  const clamped = Math.max(-4, Math.min(4, slotOffset));
  const winIdx = (currentIdx + clamped + WHEEL_ORDER.length) % WHEEL_ORDER.length;
  return WHEEL_ORDER[winIdx] ?? 0;
}
finishSpin function · typescript · L272-L288 (17 LOC)
src/roulette/game.ts
function finishSpin(state: AppState, render: () => void, finalNum: number): void {
  state.roulette.result = finalNum;

  let winnings = 0;
  for (const bet of state.roulette.bets) {
    if (isWinner(finalNum, bet.type)) {
      winnings += bet.amount * (payout(bet.type) + 1);
    }
  }

  state.roulette.winAmount = winnings;
  state.balance += winnings;
  state.roulette.spinHistory.push(finalNum);
  if (state.roulette.spinHistory.length > 15) state.roulette.spinHistory.shift();
  state.roulette.phase = "result";
  render();
}
newRound function · typescript · L291-L318 (28 LOC)
src/roulette/game.ts
export function newRound(state: AppState): void {
  // Move cursor to the last spun number's position
  const lastResult = state.roulette.result;
  if (lastResult !== null && lastResult > 0) {
    const pos = tablePos(lastResult);
    if (pos) {
      state.roulette.cursorZone = "grid";
      state.roulette.cursorVR = (pos.tableRow - 1) * 2;
      state.roulette.cursorVC = pos.tableCol * 2;
    }
  } else if (lastResult === 0) {
    state.roulette.cursorZone = "zero";
    state.roulette.cursorVC = 0;
  }

  state.roulette.bets = [];
  state.roulette.phase = "betting";
  state.roulette.result = null;
  state.roulette.spinFrame = 0;
  state.roulette.winAmount = 0;
  state.roulette.ballRow = 0;
  state.roulette.ballCol = 0;
  state.roulette.ballY = 0;
  state.roulette.ballVY = 0;
  state.roulette.ballVX = 0;
  state.roulette.ballBouncing = false;
  state.message = "";
}
hlineFill function · typescript · L37-L40 (4 LOC)
src/roulette/renderer.ts
function hlineFill(n: number, color: string = t.gray): string {
  if (n <= 0) return "";
  return `${color}${HLINE.repeat(n)}${t.reset}`;
}
hlineFillHeavy function · typescript · L42-L45 (4 LOC)
src/roulette/renderer.ts
function hlineFillHeavy(n: number, color: string = t.gray): string {
  if (n <= 0) return "";
  return `${color}${HLINE_H.repeat(n)}${t.reset}`;
}
chipColor function · typescript · L47-L55 (9 LOC)
src/roulette/renderer.ts
function chipColor(amount: number): string {
  if (amount >= 500) return t.fg256(210); // bright coral/red
  if (amount >= 100) return t.fg256(216); // bright orange
  if (amount >= 50)  return t.fg256(228); // bright yellow
  if (amount >= 25)  return t.fg256(156); // bright green
  if (amount >= 10)  return t.fg256(123); // bright cyan
  if (amount >= 5)   return t.fg256(111); // bright blue
  return t.fg256(183); // lavender
}
dotFill function · typescript · L57-L60 (4 LOC)
src/roulette/renderer.ts
function dotFill(n: number, color: string = t.gray): string {
  if (n <= 0) return "";
  return `${color}${DOT.repeat(n)}${t.reset}`;
}
dotFillWithChip function · typescript · L62-L66 (5 LOC)
src/roulette/renderer.ts
function dotFillWithChip(n: number, dotColor: string, chipColorStr: string): string {
  if (n <= 0) return "";
  const mid = Math.floor(n / 2);
  return `${dotColor}${DOT.repeat(mid)}${t.reset}${chipColorStr}${CHIP}${t.reset}${dotColor}${DOT.repeat(n - mid - 1)}${t.reset}`;
}
Want this analysis on your repo? https://repobility.com/scan/
hlineFillWithChip function · typescript · L68-L72 (5 LOC)
src/roulette/renderer.ts
function hlineFillWithChip(n: number, lineColor: string, chipColorStr: string): string {
  if (n <= 0) return "";
  const mid = Math.floor(n / 2);
  return `${lineColor}${HLINE.repeat(mid)}${t.reset}${chipColorStr}${CHIP}${t.reset}${lineColor}${HLINE.repeat(n - mid - 1)}${t.reset}`;
}
renderHotkeyGrid function · typescript · L93-L115 (23 LOC)
src/roulette/renderer.ts
export function renderHotkeyGrid(width: number, phase: string): string[] {
  const keys = phase === "result" ? HOTKEYS_RESULT : phase === "spinning" ? [] : HOTKEYS;
  if (keys.length === 0) return [""];

  const maxKey = Math.max(...keys.map(h => h.key.length));
  const maxLabel = Math.max(...keys.map(h => h.label.length));
  const cellW = maxKey + 2 + maxLabel + 2;
  const cols = Math.max(1, Math.floor((width - 4) / cellW));
  const rows = Math.ceil(keys.length / cols);

  const lines: string[] = [];
  for (let r = 0; r < rows; r++) {
    let line = "  ";
    for (let c = 0; c < cols; c++) {
      const idx = r * cols + c;
      if (idx >= keys.length) break;
      const h = keys[idx]!;
      line += `${t.white}${t.bold}${h.key.padStart(maxKey)}${t.reset}  ${t.gray}${h.label.padEnd(maxLabel)}${t.reset}  `;
    }
    lines.push(line);
  }
  return lines;
}
renderRouletteScreen function · typescript · L117-L227 (111 LOC)
src/roulette/renderer.ts
export function renderRouletteScreen(state: AppState): string[] {
  const { columns: width } = process.stdout;
  const lines: string[] = [];
  const rs = state.roulette;

  // Header
  const balanceStr = `$${state.balance.toLocaleString()}`;
  const modeIcon = rs.wheelMode === "ball" ? "Ball" : "Arrow";
  const chipStr = `${modeIcon} | Chip: $${rs.betAmount}`;
  const header = `  ${t.bold}${t.yellow}ROULETTE${t.reset}  ${t.green}${balanceStr}${t.reset}`;
  const headerRight = `${t.gray}${chipStr}${t.reset}  `;
  const headerPad = Math.max(0, width - t.stripAnsi(header).length - t.stripAnsi(headerRight).length);
  lines.push(header + " ".repeat(headerPad) + headerRight);
  lines.push(`  ${t.gray}${"─".repeat(Math.max(0, width - 4))}${t.reset}`);

  // Wheel / status area — pointer char depends on ball mode
  const useBall = rs.wheelMode === "ball";
  let pointerChar = "▼";
  if (useBall) {
    if (rs.phase === "spinning") {
      pointerChar = rs.ballBouncing ? " " : "●";
    } else if 
getCursorBet function · typescript · L229-L251 (23 LOC)
src/roulette/renderer.ts
function getCursorBet(rs: AppState["roulette"]): BetType | null {
  switch (rs.cursorZone) {
    case "zero": return { kind: "straight", number: 0 };
    case "grid": return gridPosToBet(virtualToGridPos(rs.cursorVR, rs.cursorVC));
    case "column": {
      const which = (rs.cursorVC + 1) as 1 | 2 | 3;
      return { kind: "column", which };
    }
    case "dozen": {
      const which = (rs.cursorVC + 1) as 1 | 2 | 3;
      return { kind: "dozen", which };
    }
    case "outside": {
      if (rs.cursorVR === 0) {
        const types: BetType[] = [{ kind: "red" }, { kind: "black" }, { kind: "low" }];
        return types[rs.cursorVC] ?? null;
      }
      const types: BetType[] = [{ kind: "even" }, { kind: "odd" }, { kind: "high" }];
      return types[rs.cursorVC] ?? null;
    }
  }
  return null;
}
wheelBg function · typescript · L254-L275 (22 LOC)
src/roulette/renderer.ts
function wheelBg(color: "red" | "green" | "black", dist: number): string {
  if (color === "red") {
    // Bright red center → dark red edges
    if (dist === 0) return t.bg256(196);  // bright red
    if (dist === 1) return t.bg256(160);  // red
    if (dist === 2) return t.bg256(124);  // dark red
    if (dist === 3) return t.bg256(88);   // darker red
    return t.bg256(52);                    // darkest red
  }
  if (color === "green") {
    if (dist === 0) return t.bg256(34);
    if (dist === 1) return t.bg256(28);
    if (dist === 2) return t.bg256(22);
    return t.bg256(22);
  }
  // black numbers → gray shades
  if (dist === 0) return t.bg256(236);
  if (dist === 1) return t.bg256(235);
  if (dist === 2) return t.bg256(234);
  if (dist === 3) return t.bg256(233);
  return t.bg256(232);
}
renderWheel function · typescript · L277-L322 (46 LOC)
src/roulette/renderer.ts
function renderWheel(highlight: number, width: number, halfStep: boolean = false, pointerChar: string = "▼"): string[] {
  const idx = WHEEL_ORDER.indexOf(highlight);
  const windowSize = 9;
  const half = Math.floor(windowSize / 2);
  const slotW = 4; // each slot is " XX "

  // Pointer line (fixed position)
  const pointerPad = half * slotW + 1;
  const pointerLine = pointerChar.trim()
    ? centerAnsi(spc(pointerPad) + `${t.gray}${pointerChar}${t.reset}` + spc(pointerPad), width)
    : "";

  if (!halfStep) {
    let wheel = "";
    for (let i = -half; i <= half; i++) {
      const wheelIdx = (idx + i + WHEEL_ORDER.length) % WHEEL_ORDER.length;
      const num = WHEEL_ORDER[wheelIdx] ?? 0;
      const color = numberColor(num);
      const bg = wheelBg(color, Math.abs(i));
      wheel += `${bg}${t.white}${t.bold} ${String(num).padStart(2, " ")} ${t.reset}`;
    }
    return [pointerLine, centerAnsi(wheel, width)];
  }

  // Half-step: shift wheel left by 2 chars. Render windowSize+1
centerAnsi function · typescript · L324-L328 (5 LOC)
src/roulette/renderer.ts
function centerAnsi(text: string, width: number): string {
  const visLen = t.stripAnsi(text).length;
  const pad = Math.max(0, Math.floor((width - visLen) / 2));
  return " ".repeat(pad) + text;
}
renderHistEntry function · typescript · L330-L334 (5 LOC)
src/roulette/renderer.ts
function renderHistEntry(num: number): string {
  const color = numberColor(num);
  const bg = wheelBg(color, 1);
  return `${bg}${t.white}${t.bold} ${String(num).padStart(2)} ${t.reset}`;
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
renderBoard function · typescript · L338-L647 (310 LOC)
src/roulette/renderer.ts
function renderBoard(state: AppState, width: number): string[] {
  const rs = state.roulette;
  const rawLines: string[] = [];

  const gridInner = CELL_W * NUM_TABLE_COLS + (NUM_TABLE_COLS - 1); // 26
  // Dozen column starts at row 1, not zero. Zero row only spans number grid.
  // Board width for number rows: gutter + grid + dozen
  const numRowW = GUTTER_W + 1 + gridInner + 1 + DOZEN_W + 1;
  // Zero row width: gutter + grid only
  const zeroRowW = GUTTER_W + 1 + gridInner + 1;

  const hasHist = rs.spinHistory.length > 0;
  // Center the board itself; history column sits to the left (+2 for visual offset)
  const boardLeft = Math.max(HIST_W + 2, Math.floor((width - numRowW) / 2) + 2);
  const leftPad = boardLeft - HIST_W; // margin before history column
  const pad = " ".repeat(boardLeft); // includes room for history

  const d = `${t.gray}${DOT}${t.reset}`;
  const x = `${t.gray}${CROSS}${t.reset}`;
  const yd = `${t.yellow}${t.bold}${DOT}${t.reset}`;
  const yx = `${t.yellow}${
renderGutter function · typescript · L371-L391 (21 LOC)
src/roulette/renderer.ts
  function renderGutter(vr: number): string {
    const edgeVC = -1;
    const isCursor = rs.cursorZone === "grid" && rs.cursorVR === vr && rs.cursorVC === edgeVC && rs.phase === "betting";
    const pos = virtualToGridPos(vr, edgeVC);
    const bet = findBet(rs, gridPosToBet(pos));
    const isStreet = vr % 2 === 0;
    const marker = isStreet ? "St" : "6L";

    if (isCursor && bet) {
      const cc = chipColor(bet);
      return `${cc}${CHIP}${t.reset} ${t.white}${t.bold}${marker}${t.reset}`;
    }
    if (isCursor) {
      return `${t.yellow}${t.bold}${CHIP}${t.reset} ${t.white}${t.bold}${marker}${t.reset}`;
    }
    if (bet) {
      const cc = chipColor(bet);
      return `${cc}${CHIP}${t.reset} ${t.white}${t.bold}${marker}${t.reset}`;
    }
    return spc(GUTTER_W);
  }
outsideBorder3 function · typescript · L557-L565 (9 LOC)
src/roulette/renderer.ts
  function outsideBorder3(): string {
    let line = pad + spc(GUTTER_W);
    for (let c = 0; c < 3; c++) {
      line += c === 0 ? lj : xj;
      line += hlineFill(CELL_W);
    }
    line += tj;
    return line;
  }
findBet function · typescript · L653-L656 (4 LOC)
src/roulette/renderer.ts
function findBet(rs: AppState["roulette"], betType: BetType): number | null {
  const bet = rs.bets.find(b => sameBetType(b.type, betType));
  return bet ? bet.amount : null;
}
renderZeroCell function · typescript · L658-L679 (22 LOC)
src/roulette/renderer.ts
function renderZeroCell(w: number, isCursor: boolean, betAmount: number | null, isSpinHL: boolean = false): string {
  const d = `${t.gray}${VLINE}${t.reset}`;
  // Use ceil for left pad to shift "0" right by 1 to align with middle column
  const centerZero = (text: string, n: number) => {
    const left = Math.ceil((n - text.length) / 2);
    return spc(left) + text + spc(n - text.length - left);
  };
  if (isSpinHL) {
    return `${d}${wheelBg("green", 0)}${t.white}${t.bold}${centerZero("0", w)}${t.reset}`;
  }
  if (isCursor) {
    const cc = betAmount ? chipColor(betAmount) : `${t.yellow}${t.bold}`;
    const left = Math.ceil((w - 1) / 2);
    return `${d}${spc(left)}${t.yellow}${t.bold}0${t.reset} ${cc}${CHIP}${t.reset}${spc(w - left - 3)}`;
  }
  if (betAmount) {
    const cc = chipColor(betAmount);
    const left = Math.ceil((w - 1) / 2);
    return `${d}${spc(left)}${t.brightGreen}${t.bold}0${t.reset} ${cc}${CHIP}${t.reset}${spc(w - left - 3)}`;
  }
  return `${d}${t.green}${t.
renderNumberCell function · typescript · L681-L702 (22 LOC)
src/roulette/renderer.ts
function renderNumberCell(
  num: number, w: number, color: string,
  isCursor: boolean, isSpinHL: boolean, betAmount: number | null,
): string {
  const numStr = String(num).padStart(2, " ");
  const fgColor = color === "red" ? t.brightRed : t.brightWhite;
  const chipRight = (num - 1) % 3 !== 2; // right col → chip left, others → chip right

  if (isCursor) {
    const cc = betAmount ? chipColor(betAmount) : `${t.yellow}${t.bold}`;
    return labelWithChip(`${t.yellow}${t.bold}`, numStr, cc, w, chipRight);
  }
  if (isSpinHL) {
    const bg = wheelBg(color as "red" | "green" | "black", 0);
    return `${bg}${t.white}${t.bold}${centerText(numStr, w)}${t.reset}`;
  }
  if (betAmount) {
    const cc = chipColor(betAmount);
    return labelWithChip(`${fgColor}${t.bold}`, numStr, cc, w, chipRight);
  }
  return `${fgColor}${t.bold}${centerText(numStr, w)}${t.reset}`;
}
renderLabelCell function · typescript · L704-L718 (15 LOC)
src/roulette/renderer.ts
function renderLabelCell(
  label: string, w: number, isCursor: boolean, betAmount: number | null, labelColor?: string,
): string {
  if (isCursor) {
    const cc = betAmount ? chipColor(betAmount) : `${t.yellow}${t.bold}`;
    return labelWithChip(`${t.yellow}${t.bold}`, label, cc, w, true);
  }
  if (betAmount) {
    const cc = chipColor(betAmount);
    const lc = labelColor || t.brightWhite;
    return labelWithChip(`${lc}${t.bold}`, label, `${cc}`, w, true);
  }
  const lc = labelColor || t.gray;
  return `${lc}${t.bold}${centerText(label, w)}${t.reset}`;
}
renderDozenContent function · typescript · L721-L755 (35 LOC)
src/roulette/renderer.ts
function renderDozenContent(
  rs: AppState["roulette"], dozenIdx: number, dozenLine: number, w: number,
): string {
  const which = (dozenIdx + 1) as 1 | 2 | 3;
  const isCursor = rs.cursorZone === "dozen" && rs.cursorVC === dozenIdx && rs.phase === "betting";
  const bet = findBet(rs, { kind: "dozen", which });
  const labels = ["1st 12", "2nd 12", "3rd 12"];
  const label = labels[dozenIdx]!;

  // Line 3: label
  if (dozenLine === 3) {
    if (isCursor) return `${t.yellow}${t.bold}${centerText(label, w)}${t.reset}`;
    if (bet) return `${t.brightWhite}${t.bold}${centerText(label, w)}${t.reset}`;
    return `${t.gray}${t.bold}${centerText(label, w)}${t.reset}`;
  }
  // Line 4: chip
  if (dozenLine === 4) {
    if (isCursor && bet) {
      const cc = chipColor(bet);
      return centerWithAnsi(`${cc}${CHIP}`, CHIP, w);
    }
    if (isCursor) return centerWithAnsi(`${t.yellow}${t.bold}${CHIP}`, CHIP, w);
    if (bet) {
      const cc = chipColor(bet);
      return centerWithAnsi(`$
Repobility · MCP-ready · https://repobility.com
centerText function · typescript · L757-L762 (6 LOC)
src/roulette/renderer.ts
function centerText(text: string, w: number): string {
  if (text.length >= w) return text.slice(0, w);
  const left = Math.floor((w - text.length) / 2);
  const right = w - text.length - left;
  return spc(left) + text + spc(right);
}
centerWithAnsi function · typescript · L764-L770 (7 LOC)
src/roulette/renderer.ts
function centerWithAnsi(ansiText: string, rawText: string, w: number): string {
  const rawLen = rawText.length;
  if (rawLen >= w) return ansiText;
  const left = Math.floor((w - rawLen) / 2);
  const right = w - rawLen - left;
  return spc(left) + ansiText + spc(right);
}
labelWithChip function · typescript · L773-L782 (10 LOC)
src/roulette/renderer.ts
function labelWithChip(
  labelAnsi: string, label: string, chipAnsi: string, w: number, chipRight: boolean,
): string {
  const leftPad = Math.floor((w - label.length) / 2);
  const rightPad = w - label.length - leftPad;
  if (chipRight) {
    return `${spc(leftPad)}${labelAnsi}${label}${t.reset} ${chipAnsi}${CHIP}${t.reset}${spc(Math.max(0, rightPad - 2))}`;
  }
  return `${spc(Math.max(0, leftPad - 2))}${chipAnsi}${CHIP}${t.reset} ${labelAnsi}${label}${t.reset}${spc(rightPad)}`;
}
page 1 / 2next ›