import React, { useEffect, useRef, useState } from 'react';
/**
- Live Casino Prototype with Extended Bet Types
-
- Added: Red/Black, Dozen, Column bets with updated pay table.
-
- Still client-side demo only; server-side authority required in production. */
export default function LiveCasinoPrototype() { const videoRef = useRef(null); const canvasRef = useRef(null); const [balance, setBalance] = useState(1000); const [currentBets, setCurrentBets] = useState([]); const [selectedChip, setSelectedChip] = useState(10); const [isSpinning, setIsSpinning] = useState(false); const [lastResult, setLastResult] = useState(null); const [messages, setMessages] = useState([]);
const redNumbers = [1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36]; const blackNumbers = [2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35];
useEffect(() => { async function attachDemo() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); if (videoRef.current) videoRef.current.srcObject = stream; } catch (e) { console.warn('No camera available — video placeholder will remain blank.'); } } attachDemo(); drawWheel(0); return () => { if (videoRef.current && videoRef.current.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach(t => t.stop()); } }; }, []);
function placeBet(type, extra = null) { if (balance < selectedChip) return alert('Insufficient balance'); const bet = { id: Date.now() + Math.random(), type, amount: selectedChip, ...extra }; setCurrentBets(b => [...b, bet]); setBalance(b => b - selectedChip); }
function clearBets() { const refund = currentBets.reduce((s, b) => s + b.amount, 0); setBalance(b => b + refund); setCurrentBets([]); }
function spinWheel() { if (isSpinning) return; setIsSpinning(true); setLastResult(null); const outcome = Math.floor(Math.random() * 37); animateSpin(outcome, () => { resolveBets(outcome); setIsSpinning(false); setLastResult(outcome); }); }
function resolveBets(outcome) {
let payout = 0;
currentBets.forEach(b => {
if (b.type === 'number' && b.number === outcome) {
payout += b.amount * 36;
} else if (b.type === 'even' && outcome !== 0 && outcome % 2 === 0) {
payout += b.amount * 2;
} else if (b.type === 'odd' && outcome % 2 === 1) {
payout += b.amount * 2;
} else if (b.type === 'red' && redNumbers.includes(outcome)) {
payout += b.amount * 2;
} else if (b.type === 'black' && blackNumbers.includes(outcome)) {
payout += b.amount * 2;
} else if (b.type === 'dozen' && b.range) {
if (outcome >= b.range[0] && outcome <= b.range[1]) payout += b.amount * 3;
} else if (b.type === 'column' && b.column) {
if (outcome !== 0 && ((outcome - 1) % 3) + 1 === b.column) payout += b.amount * 3;
}
});
if (payout > 0) setMessages(m => [You won ${payout}, ...m].slice(0, 50));
else setMessages(m => [You lost this round (${outcome}), ...m].slice(0, 50));
setBalance(b => b + payout);
setCurrentBets([]);
}
function drawWheel(rotation) { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const size = 300; canvas.width = size; canvas.height = size; ctx.clearRect(0, 0, size, size);
const segments = 37;
const angle = (Math.PI * 2) / segments;
for (let i = 0; i < segments; i++) {
const start = i * angle + rotation;
ctx.beginPath();
ctx.moveTo(size / 2, size / 2);
ctx.arc(size / 2, size / 2, size / 2 - 10, start, start + angle);
ctx.closePath();
ctx.fillStyle = redNumbers.includes(i) ? 'red' : blackNumbers.includes(i) ? 'black' : 'green';
ctx.fill();
ctx.save();
ctx.translate(size / 2, size / 2);
ctx.rotate(start + angle / 2);
ctx.fillStyle = '#fff';
ctx.font = '12px sans-serif';
ctx.fillText(String(i), size / 2 - 40, 0);
ctx.restore();
}
ctx.beginPath();
ctx.arc(size / 2, size / 2, 30, 0, Math.PI * 2);
ctx.fillStyle = '#222';
ctx.fill();
ctx.fillStyle = '#c53030';
ctx.beginPath();
ctx.moveTo(size - 10, size / 2 - 10);
ctx.lineTo(size - 10, size / 2 + 10);
ctx.lineTo(size - 30, size / 2);
ctx.closePath();
ctx.fill();
}
function animateSpin(outcome, cb) { const segments = 37; const finalAngle = ((Math.PI * 2) - (outcome * (Math.PI * 2) / segments)) + Math.PI / 4; const start = performance.now(); const duration = 4000 + Math.random() * 2000;
function frame(now) {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
const rotation = eased * finalAngle * 6;
drawWheel(rotation);
if (t < 1) requestAnimationFrame(frame);
else cb();
}
requestAnimationFrame(frame);
}
function placeNumberBet(n) { placeBet('number', { number: n }); }
return (
Live Casino — Extended Bets Prototype
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="md:col-span-2 bg-gray-50 rounded-lg p-3 shadow">
<div className="flex gap-3">
<div className="w-2/3">
<div className="bg-black rounded overflow-hidden">
<video ref={videoRef} autoPlay playsInline muted className="w-full h-56 object-cover" />
</div>
</div>
<div className="w-1/3 space-y-2">
<div className="bg-white p-3 rounded shadow-sm">
<div className="text-xs text-gray-500">Balance</div>
<div className="text-xl font-semibold">${balance}</div>
</div>
<div className="bg-white p-3 rounded shadow-sm">
<div className="text-xs text-gray-500">Selected Chip</div>
<div className="flex gap-2 mt-2">
{[1,5,10,25,100].map(c => (
<button key={c} onClick={() => setSelectedChip(c)} className={`px-2 py-1 rounded ${selectedChip===c? 'ring-2 ring-blue-400':''}`}>{c}</button>
))}
</div>
</div>
<div className="bg-white p-3 rounded shadow-sm text-center">
<button onClick={spinWheel} disabled={isSpinning} className={`px-3 py-2 rounded ${isSpinning? 'bg-gray-400 text-white' : 'bg-green-600 text-white'}`}>
{isSpinning ? 'Spinning…' : 'Spin'}
</button>
</div>
</div>
</div>
<div className="mt-4 bg-white p-3 rounded shadow-sm">
<div className="flex gap-6">
<canvas ref={canvasRef} width={300} height={300} className="rounded" />
<div className="flex-1">
<div className="grid grid-cols-6 gap-2">
{Array.from({length: 37}, (_, i) => (
<button key={i} onClick={() => placeNumberBet(i)} className="border rounded p-2 text-sm">{i}</button>
))}
</div>
<div className="mt-3 flex flex-wrap gap-2">
<button onClick={() => placeBet('even')} className="px-3 py-1 bg-gray-100 rounded">Even</button>
<button onClick={() => placeBet('odd')} className="px-3 py-1 bg-gray-100 rounded">Odd</button>
<button onClick={() => placeBet('red')} className="px-3 py-1 bg-red-200 rounded">Red</button>
<button onClick={() => placeBet('black')} className="px-3 py-1 bg-black text-white rounded">Black</button>
<button onClick={() => placeBet('dozen',{range:[1,12]})} className="px-3 py-1 bg-gray-100 rounded">1-12</button>
<button onClick={() => placeBet('dozen',{range:[13,24]})} className="px-3 py-1 bg-gray-100 rounded">13-24</button>
<button onClick={() => placeBet('dozen',{range:[25,36]})} className="px-3 py-1 bg-gray-100 rounded">25-36</button>
<button onClick={() => placeBet('column',{column:1})} className="px-3 py-1 bg-gray-100 rounded">Col 1</button>
<button onClick={() => placeBet('column',{column:2})} className="px-3 py-1 bg-gray-100 rounded">Col 2</button>
<button onClick={() => placeBet('column',{column:3})} className="px-3 py-1 bg-gray-100 rounded">Col 3</button>
<button onClick={clearBets} className="px-3 py-1 bg-red-100 rounded">Clear Bets</button>
</div>
<div className="mt-3">
<div className="text-sm text-gray-500">Placed Bets</div>
<ul className="text-sm mt-1">
{currentBets.map(b => (
<li key={b.id}>{b.type === 'number' ? `Number ${b.number}` : b.type} — ${b.amount}</li>
))}
{!currentBets.length && <li className="text-gray-400">No bets</li>}
</ul>
</div>
</div>
</div>
</div>
</div>
<div className="bg-white p-3 rounded shadow-sm">
<div className="text-sm text-gray-500 mb-2">Activity</div>
<div className="h-80 overflow-auto border p-2 rounded bg-gray-50">
{messages.map((m, idx) => (<div key={idx} className="text-sm py-1">{m}</div>))}
{messages.length===0 && <div className="text-gray-400">No activity yet</div>}
</div>
</div>
</div>
<div className="mt-6 text-sm text-gray-600">
<strong>Pay Table:</strong>
<ul className="list-disc ml-5">
<li>Exact Number: 35:1</li>
<li>Even/Odd: 1:1</li>
<li>Red/Black: 1:1</li>
<li>Dozens (1-12, 13-24, 25-36): 2:1</li>
<li>Columns (1st, 2nd, 3rd): 2:1</li>
</ul>
</div>
</div>
); }
