600 lines
20 KiB
Svelte
600 lines
20 KiB
Svelte
<script lang="ts">
|
|
let { data } = $props();
|
|
|
|
let online = $state(0);
|
|
let isConnected = $state(false);
|
|
let isConnecting = $state(false);
|
|
let ws = $state<WebSocket | null>(null);
|
|
|
|
// 사용자 정보
|
|
let nickname = $state('');
|
|
let capital = $state(10000); // 초기 자본금
|
|
let inputNickname = $state('');
|
|
let inputCapital = $state('10000');
|
|
|
|
// 로그아웃 함수
|
|
async function logout() {
|
|
await fetch('/api/logout', { method: 'POST' });
|
|
window.location.href = '/login';
|
|
}
|
|
|
|
// 주사위 게임 상태
|
|
let noMoreBet = $state(false);
|
|
let dice1 = $state<number | null>(null);
|
|
let dice2 = $state<number | null>(null);
|
|
let dice3 = $state<number | null>(null);
|
|
let noMoreBetStartTime = $state<number | null>(null);
|
|
let noMoreBetEndTime = $state<number | null>(null);
|
|
|
|
// 배팅 관련
|
|
let oddBet = $state(0);
|
|
let evenBet = $state(0);
|
|
let bigBet = $state(0);
|
|
let smallBet = $state(0);
|
|
let oddResult = $state<'win' | 'lose' | null>(null);
|
|
let evenResult = $state<'win' | 'lose' | null>(null);
|
|
let bigResult = $state<'win' | 'lose' | null>(null);
|
|
let smallResult = $state<'win' | 'lose' | null>(null);
|
|
let lastWinAmount = $state(0);
|
|
|
|
// 모든 사용자의 배팅 내역
|
|
type BettingInfo = {
|
|
nickname: string;
|
|
oddBet: number;
|
|
evenBet: number;
|
|
bigBet: number;
|
|
smallBet: number;
|
|
};
|
|
let allBettings = $state<BettingInfo[]>([]);
|
|
|
|
// Progress bar 관련
|
|
let progressPercent = $state(0);
|
|
let remainingTime = $state(0);
|
|
|
|
function connectWebSocket() {
|
|
isConnecting = true;
|
|
nickname = inputNickname;
|
|
capital = parseInt(inputCapital);
|
|
|
|
// WebSocket 프로토콜 결정 (https -> wss, http -> ws)
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsUrl = `${protocol}//${window.location.host}/api/counter`;
|
|
|
|
ws = new WebSocket(wsUrl);
|
|
|
|
ws.onopen = () => {
|
|
isConnected = true;
|
|
isConnecting = false;
|
|
console.log('WebSocket connected');
|
|
|
|
// 서버에 사용자 정보 전송
|
|
if (ws) {
|
|
ws.send(JSON.stringify({
|
|
type: 'setUser',
|
|
nickname: nickname,
|
|
capital: capital
|
|
}));
|
|
}
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
noMoreBet = data.noMoreBet;
|
|
dice1 = data.dice1;
|
|
dice2 = data.dice2;
|
|
dice3 = data.dice3;
|
|
noMoreBetStartTime = data.noMoreBetStartTime;
|
|
noMoreBetEndTime = data.noMoreBetEndTime;
|
|
online = data.online;
|
|
|
|
// 배팅 결과
|
|
if (data.capital !== undefined) {
|
|
capital = data.capital;
|
|
}
|
|
if (data.oddResult !== undefined) oddResult = data.oddResult;
|
|
if (data.evenResult !== undefined) evenResult = data.evenResult;
|
|
if (data.bigResult !== undefined) bigResult = data.bigResult;
|
|
if (data.smallResult !== undefined) smallResult = data.smallResult;
|
|
if (data.lastWinAmount !== undefined) lastWinAmount = data.lastWinAmount;
|
|
|
|
// 모든 사용자의 배팅 내역
|
|
if (data.allBettings) {
|
|
allBettings = data.allBettings;
|
|
}
|
|
|
|
// 새로운 라운드가 시작되면 배팅 초기화
|
|
if (!noMoreBet && data.dice1 === null) {
|
|
oddBet = 0;
|
|
evenBet = 0;
|
|
bigBet = 0;
|
|
smallBet = 0;
|
|
oddResult = null;
|
|
evenResult = null;
|
|
bigResult = null;
|
|
smallResult = null;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing message:', error);
|
|
}
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
isConnecting = false;
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
isConnected = false;
|
|
isConnecting = false;
|
|
console.log('WebSocket disconnected');
|
|
};
|
|
}
|
|
|
|
function disconnectWebSocket() {
|
|
if (ws) {
|
|
ws.close();
|
|
ws = null;
|
|
}
|
|
}
|
|
|
|
function betOdd() {
|
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
|
oddBet += 1000;
|
|
capital -= 1000;
|
|
ws.send(JSON.stringify({ type: 'bet', betType: 'odd', amount: 1000 }));
|
|
}
|
|
}
|
|
|
|
function betEven() {
|
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
|
evenBet += 1000;
|
|
capital -= 1000;
|
|
ws.send(JSON.stringify({ type: 'bet', betType: 'even', amount: 1000 }));
|
|
}
|
|
}
|
|
|
|
function betBig() {
|
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
|
bigBet += 1000;
|
|
capital -= 1000;
|
|
ws.send(JSON.stringify({ type: 'bet', betType: 'big', amount: 1000 }));
|
|
}
|
|
}
|
|
|
|
function betSmall() {
|
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
|
smallBet += 1000;
|
|
capital -= 1000;
|
|
ws.send(JSON.stringify({ type: 'bet', betType: 'small', amount: 1000 }));
|
|
}
|
|
}
|
|
|
|
// 주사위 표시를 위한 dots 배열 생성
|
|
function getDiceDots(number: number): boolean[][] {
|
|
const patterns: Record<number, boolean[][]> = {
|
|
1: [
|
|
[false, false, false],
|
|
[false, true, false],
|
|
[false, false, false]
|
|
],
|
|
2: [
|
|
[true, false, false],
|
|
[false, false, false],
|
|
[false, false, true]
|
|
],
|
|
3: [
|
|
[true, false, false],
|
|
[false, true, false],
|
|
[false, false, true]
|
|
],
|
|
4: [
|
|
[true, false, true],
|
|
[false, false, false],
|
|
[true, false, true]
|
|
],
|
|
5: [
|
|
[true, false, true],
|
|
[false, true, false],
|
|
[true, false, true]
|
|
],
|
|
6: [
|
|
[true, false, true],
|
|
[true, false, true],
|
|
[true, false, true]
|
|
]
|
|
};
|
|
return patterns[number] || patterns[1];
|
|
}
|
|
|
|
// Progress bar 업데이트
|
|
$effect(() => {
|
|
let interval: ReturnType<typeof setInterval> | null = null;
|
|
|
|
if (noMoreBetStartTime !== null && noMoreBetEndTime !== null) {
|
|
interval = setInterval(() => {
|
|
if (noMoreBetStartTime === null || noMoreBetEndTime === null) return;
|
|
|
|
const now = Date.now();
|
|
const total = noMoreBetEndTime - noMoreBetStartTime;
|
|
const elapsed = now - noMoreBetStartTime;
|
|
const remaining = Math.max(0, noMoreBetEndTime - now);
|
|
|
|
progressPercent = Math.min(100, (elapsed / total) * 100);
|
|
remainingTime = Math.ceil(remaining / 1000);
|
|
}, 100);
|
|
}
|
|
|
|
return () => {
|
|
if (interval) clearInterval(interval);
|
|
};
|
|
});
|
|
|
|
// 컴포넌트 언마운트 시 WebSocket 정리
|
|
$effect(() => {
|
|
return () => {
|
|
disconnectWebSocket();
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="min-h-screen bg-gradient-to-br from-purple-50 to-pink-100 flex items-center justify-center p-4">
|
|
<div class="max-w-3xl w-full bg-white rounded-2xl shadow-xl p-8">
|
|
<!-- 로그인 상태 표시 -->
|
|
{#if data.user}
|
|
<div class="mb-4 flex justify-between items-center bg-blue-50 p-4 rounded-lg">
|
|
<div>
|
|
<p class="text-sm text-gray-600">로그인됨</p>
|
|
<p class="font-semibold text-gray-800">{data.user.nickname} ({data.user.email})</p>
|
|
</div>
|
|
<button
|
|
onclick={logout}
|
|
class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition"
|
|
>
|
|
로그아웃
|
|
</button>
|
|
</div>
|
|
{:else}
|
|
<div class="mb-4 bg-yellow-50 p-4 rounded-lg text-center">
|
|
<p class="text-gray-700">
|
|
<a href="/login" class="text-blue-600 hover:underline">로그인</a> 또는
|
|
<a href="/register" class="text-blue-600 hover:underline">회원가입</a>을 해주세요.
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<h1 class="text-4xl font-bold text-center mb-8 text-gray-800">
|
|
Durable Objects TEST
|
|
</h1>
|
|
|
|
<!-- 연결 상태, 접속자 수, 자본금 (3 columns) -->
|
|
<div class="grid grid-cols-3 gap-4 mb-8">
|
|
<!-- 연결 상태 -->
|
|
<div
|
|
class="p-4 rounded-lg {isConnected ? 'bg-green-50 border-2 border-green-200' : 'bg-gray-50 border-2 border-gray-200'}">
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-3 h-3 rounded-full {isConnected ? 'bg-green-500 animate-pulse' : 'bg-gray-400'}"></div>
|
|
<span class="font-medium text-gray-700">
|
|
{isConnected ? '연결됨' : isConnecting ? '연결 중...' : '연결 안됨'}
|
|
</span>
|
|
</div>
|
|
{#if !isConnected}
|
|
<input
|
|
type="text"
|
|
bind:value={inputNickname}
|
|
placeholder="닉네임"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
<input
|
|
type="number"
|
|
bind:value={inputCapital}
|
|
placeholder="초기 자본금"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
<button
|
|
onclick={connectWebSocket}
|
|
disabled={isConnecting || !inputNickname || !inputCapital}
|
|
class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
{isConnecting ? '연결 중...' : '연결하기'}
|
|
</button>
|
|
{:else}
|
|
<button
|
|
onclick={disconnectWebSocket}
|
|
class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
|
>
|
|
연결 끊기
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 접속자 수 -->
|
|
<div
|
|
class="bg-gradient-to-br from-green-50 to-emerald-50 p-4 rounded-lg border-2 border-green-200 flex flex-col items-center justify-center">
|
|
<div class="text-4xl font-bold text-green-600 mb-1">
|
|
{online}
|
|
</div>
|
|
<div class="text-sm text-gray-600">실시간 접속자</div>
|
|
</div>
|
|
|
|
<!-- 현재 자본금 -->
|
|
<div
|
|
class="bg-gradient-to-br from-amber-50 to-yellow-50 p-4 rounded-lg border-2 border-amber-200 flex flex-col items-center justify-center">
|
|
{#if isConnected && nickname}
|
|
<div class="text-xs text-gray-600 mb-1">{nickname}</div>
|
|
<div class="text-3xl font-bold text-amber-700 mb-1">{capital.toLocaleString()}원</div>
|
|
<div class="text-xs text-gray-600">현재 자본금</div>
|
|
{#if lastWinAmount !== 0}
|
|
<div class="text-sm font-semibold mt-1 {lastWinAmount > 0 ? 'text-green-600' : 'text-red-600'}">
|
|
{lastWinAmount > 0 ? '+' : ''}{lastWinAmount.toLocaleString()}원
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<div class="text-2xl font-bold text-gray-400">-</div>
|
|
<div class="text-xs text-gray-600">자본금</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="mb-8 p-6 bg-gradient-to-br from-indigo-50 to-purple-50 rounded-xl border-2 border-indigo-200">
|
|
<div class="mb-4">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="font-semibold text-gray-700">
|
|
{noMoreBet ? '🚫 베팅 마감' : '✅ 베팅 가능'}
|
|
</span>
|
|
<span class="text-lg font-bold text-indigo-600">
|
|
{remainingTime}초
|
|
</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 rounded-full h-4 overflow-hidden">
|
|
<div
|
|
class="h-full transition-all duration-100 {noMoreBet ? 'bg-red-500' : 'bg-green-500'}"
|
|
style="width: {progressPercent}%"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm text-gray-600 text-center">
|
|
{noMoreBet ? '주사위를 굴리는 중... 다음 라운드를 기다려주세요' : '홀/짝, 대/소를 선택하세요!'}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- 주사위 디스플레이 -->
|
|
{#if dice1 !== null && dice2 !== null && dice3 !== null}
|
|
<div class="mb-8 p-6 bg-gradient-to-br from-yellow-50 to-orange-50 rounded-xl border-2 border-yellow-200">
|
|
<h2 class="text-2xl font-bold text-center mb-4 text-gray-800">
|
|
🎲 결과
|
|
</h2>
|
|
|
|
<!-- 3개의 주사위 -->
|
|
<div class="flex justify-center gap-6 mb-6">
|
|
{#each [dice1, dice2, dice3] as dice}
|
|
<div class="relative">
|
|
<div class="w-24 h-24 bg-white rounded-2xl shadow-2xl border-4 border-gray-800 p-3">
|
|
<div class="grid grid-cols-3 gap-1 h-full">
|
|
{#each getDiceDots(dice) as row}
|
|
{#each row as dot}
|
|
<div class="flex items-center justify-center">
|
|
{#if dot}
|
|
<div class="w-3 h-3 bg-gray-800 rounded-full"></div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
<div class="absolute -top-2 -right-2 w-8 h-8 bg-red-500 text-white
|
|
rounded-full flex items-center justify-center text-sm font-bold shadow-lg">
|
|
{dice}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- 합계 -->
|
|
<div class="text-center mb-4">
|
|
<div class="text-4xl font-bold text-indigo-600 mb-2">
|
|
합계: {dice1 + dice2 + dice3}
|
|
</div>
|
|
<div class="flex justify-center gap-4 text-2xl font-semibold">
|
|
<span class="{(dice1 + dice2 + dice3) % 2 === 1 ? 'text-blue-600' : 'text-gray-400'}">
|
|
{(dice1 + dice2 + dice3) % 2 === 1 ? '✓ 홀수' : '홀수'}
|
|
</span>
|
|
<span class="{(dice1 + dice2 + dice3) % 2 === 0 ? 'text-pink-600' : 'text-gray-400'}">
|
|
{(dice1 + dice2 + dice3) % 2 === 0 ? '✓ 짝수' : '짝수'}
|
|
</span>
|
|
</div>
|
|
<div class="flex justify-center gap-4 text-2xl font-semibold mt-2">
|
|
<span class="{(dice1 + dice2 + dice3) >= 10 ? 'text-orange-600' : 'text-gray-400'}">
|
|
{(dice1 + dice2 + dice3) >= 10 ? '✓ 대' : '대'}
|
|
</span>
|
|
<span class="{(dice1 + dice2 + dice3) <= 9 ? 'text-purple-600' : 'text-gray-400'}">
|
|
{(dice1 + dice2 + dice3) <= 9 ? '✓ 소' : '소'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 배팅 결과 표시 -->
|
|
{#if oddBet > 0 || evenBet > 0 || bigBet > 0 || smallBet > 0}
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
{#if oddBet > 0}
|
|
<div
|
|
class="p-3 rounded-lg {oddResult === 'win' ? 'bg-green-100 border-2 border-green-300' : oddResult === 'lose' ? 'bg-red-100 border-2 border-red-300' : 'bg-gray-100 border-2 border-gray-300'}">
|
|
<div class="text-sm text-gray-600">홀수 배팅</div>
|
|
<div class="text-lg font-bold">{oddBet.toLocaleString()}원</div>
|
|
{#if oddResult === 'win'}
|
|
<div class="text-green-600 font-semibold">✓ 승리 +{oddBet.toLocaleString()}원</div>
|
|
{:else if oddResult === 'lose'}
|
|
<div class="text-red-600 font-semibold">✗ 패배</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if evenBet > 0}
|
|
<div
|
|
class="p-3 rounded-lg {evenResult === 'win' ? 'bg-green-100 border-2 border-green-300' : evenResult === 'lose' ? 'bg-red-100 border-2 border-red-300' : 'bg-gray-100 border-2 border-gray-300'}">
|
|
<div class="text-sm text-gray-600">짝수 배팅</div>
|
|
<div class="text-lg font-bold">{evenBet.toLocaleString()}원</div>
|
|
{#if evenResult === 'win'}
|
|
<div class="text-green-600 font-semibold">✓ 승리 +{evenBet.toLocaleString()}원</div>
|
|
{:else if evenResult === 'lose'}
|
|
<div class="text-red-600 font-semibold">✗ 패배</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if bigBet > 0}
|
|
<div
|
|
class="p-3 rounded-lg {bigResult === 'win' ? 'bg-green-100 border-2 border-green-300' : bigResult === 'lose' ? 'bg-red-100 border-2 border-red-300' : 'bg-gray-100 border-2 border-gray-300'}">
|
|
<div class="text-sm text-gray-600">대 배팅</div>
|
|
<div class="text-lg font-bold">{bigBet.toLocaleString()}원</div>
|
|
{#if bigResult === 'win'}
|
|
<div class="text-green-600 font-semibold">✓ 승리 +{bigBet.toLocaleString()}원</div>
|
|
{:else if bigResult === 'lose'}
|
|
<div class="text-red-600 font-semibold">✗ 패배</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if smallBet > 0}
|
|
<div
|
|
class="p-3 rounded-lg {smallResult === 'win' ? 'bg-green-100 border-2 border-green-300' : smallResult === 'lose' ? 'bg-red-100 border-2 border-red-300' : 'bg-gray-100 border-2 border-gray-300'}">
|
|
<div class="text-sm text-gray-600">소 배팅</div>
|
|
<div class="text-lg font-bold">{smallBet.toLocaleString()}원</div>
|
|
{#if smallResult === 'win'}
|
|
<div class="text-green-600 font-semibold">✓ 승리 +{smallBet.toLocaleString()}원</div>
|
|
{:else if smallResult === 'lose'}
|
|
<div class="text-red-600 font-semibold">✗ 패배</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- 베팅 버튼 -->
|
|
<div class="mb-8 p-6 bg-gradient-to-br from-blue-50 to-cyan-50 rounded-xl border-2 border-blue-200">
|
|
<h2 class="text-xl font-bold text-center mb-4 text-gray-800">
|
|
배팅 선택 (1000원씩 배팅)
|
|
</h2>
|
|
|
|
<!-- 홀/짝/대/소 버튼 (4개 컬럼) -->
|
|
<div class="grid grid-cols-4 gap-4">
|
|
<button
|
|
onclick={betOdd}
|
|
disabled={!isConnected || noMoreBet || capital < 1000}
|
|
class="px-4 py-4 bg-blue-500 text-white rounded-xl font-bold text-xl
|
|
hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
transition-all transform hover:scale-105"
|
|
>
|
|
홀수
|
|
{#if oddBet > 0}
|
|
<div class="text-sm mt-1">{oddBet.toLocaleString()}원</div>
|
|
{/if}
|
|
</button>
|
|
<button
|
|
onclick={betEven}
|
|
disabled={!isConnected || noMoreBet || capital < 1000}
|
|
class="px-4 py-4 bg-pink-500 text-white rounded-xl font-bold text-xl
|
|
hover:bg-pink-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
transition-all transform hover:scale-105"
|
|
>
|
|
짝수
|
|
{#if evenBet > 0}
|
|
<div class="text-sm mt-1">{evenBet.toLocaleString()}원</div>
|
|
{/if}
|
|
</button>
|
|
<button
|
|
onclick={betBig}
|
|
disabled={!isConnected || noMoreBet || capital < 1000}
|
|
class="px-4 py-4 bg-orange-500 text-white rounded-xl font-bold text-xl
|
|
hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
transition-all transform hover:scale-105"
|
|
>
|
|
대 (大)
|
|
{#if bigBet > 0}
|
|
<div class="text-sm mt-1">{bigBet.toLocaleString()}원</div>
|
|
{/if}
|
|
</button>
|
|
<button
|
|
onclick={betSmall}
|
|
disabled={!isConnected || noMoreBet || capital < 1000}
|
|
class="px-4 py-4 bg-purple-500 text-white rounded-xl font-bold text-xl
|
|
hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
transition-all transform hover:scale-105"
|
|
>
|
|
소 (小)
|
|
{#if smallBet > 0}
|
|
<div class="text-sm mt-1">{smallBet.toLocaleString()}원</div>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
|
|
{#if capital < 1000 && isConnected}
|
|
<div class="mt-4 text-center text-sm text-red-600 font-semibold">
|
|
자본금이 부족합니다!
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- 전체 사용자 배팅 내역 -->
|
|
{#if isConnected && allBettings.length > 0}
|
|
<div class="mb-8 p-6 bg-gradient-to-br from-slate-50 to-gray-50 rounded-xl border-2 border-slate-200">
|
|
<h2 class="text-xl font-bold text-center mb-4 text-gray-800">
|
|
📊 전체 배팅 현황
|
|
</h2>
|
|
<div class="space-y-3 max-h-60 overflow-y-auto">
|
|
{#each allBettings as betting}
|
|
<div class="p-3 bg-white rounded-lg border border-gray-200">
|
|
<div class="font-semibold text-gray-800 mb-2">{betting.nickname}</div>
|
|
<div class="grid grid-cols-4 gap-2 text-sm">
|
|
{#if betting.oddBet > 0}
|
|
<div class="bg-blue-50 p-2 rounded text-center">
|
|
<div class="text-xs text-gray-600">홀</div>
|
|
<div class="font-semibold text-blue-600">{betting.oddBet.toLocaleString()}</div>
|
|
</div>
|
|
{/if}
|
|
{#if betting.evenBet > 0}
|
|
<div class="bg-pink-50 p-2 rounded text-center">
|
|
<div class="text-xs text-gray-600">짝</div>
|
|
<div class="font-semibold text-pink-600">{betting.evenBet.toLocaleString()}</div>
|
|
</div>
|
|
{/if}
|
|
{#if betting.bigBet > 0}
|
|
<div class="bg-orange-50 p-2 rounded text-center">
|
|
<div class="text-xs text-gray-600">대</div>
|
|
<div class="font-semibold text-orange-600">{betting.bigBet.toLocaleString()}</div>
|
|
</div>
|
|
{/if}
|
|
{#if betting.smallBet > 0}
|
|
<div class="bg-purple-50 p-2 rounded text-center">
|
|
<div class="text-xs text-gray-600">소</div>
|
|
<div class="font-semibold text-purple-600">{betting.smallBet.toLocaleString()}</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- 설명 -->
|
|
<div class="mt-8 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
|
<h2 class="font-semibold text-gray-800 mb-2">게임 규칙</h2>
|
|
<ul class="text-sm text-gray-600 space-y-1">
|
|
<li>✅ Cloudflare Durable Objects로 상태 관리</li>
|
|
<li>✅ WebSocket으로 실시간 양방향 통신</li>
|
|
<li>🎲 <strong>베팅 기간 (45초)</strong>: 홀수 또는 짝수 선택 가능</li>
|
|
<li>🚫 <strong>베팅 마감 (15초)</strong>: 주사위 결과 확인 및 대기</li>
|
|
<li>📊 Progress bar로 남은 시간 시각화</li>
|
|
<li>🔄 게임은 자동으로 반복됩니다</li>
|
|
<li>👥 모든 접속자에게 실시간 동기화</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|