Function bodies 63 total
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 { namrenderScreen 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 shimmerenderGameScreen 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", tgridPosToBet 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", numbeisWinner 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.ballVYanimateSpin 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 fstepBallPhysics 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+1centerAnsi 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 ›