게임기능추가
-자본금,닉네임,사용자별베팅,대소홀짝
This commit is contained in:
parent
7509f3d23c
commit
14a3743616
@ -7,40 +7,59 @@ export interface Env {
|
|||||||
interface Session {
|
interface Session {
|
||||||
id: string;
|
id: string;
|
||||||
webSocket: WebSocket;
|
webSocket: WebSocket;
|
||||||
quit?: boolean;
|
nickname?: string;
|
||||||
|
capital?: number;
|
||||||
|
// 배팅 정보
|
||||||
|
oddBet: number;
|
||||||
|
evenBet: number;
|
||||||
|
bigBet: number;
|
||||||
|
smallBet: number;
|
||||||
|
// 결과
|
||||||
|
oddResult?: 'win' | 'lose' | null;
|
||||||
|
evenResult?: 'win' | 'lose' | null;
|
||||||
|
bigResult?: 'win' | 'lose' | null;
|
||||||
|
smallResult?: 'win' | 'lose' | null;
|
||||||
|
lastWinAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CounterDurableObject {
|
export class CounterDurableObject {
|
||||||
private ctx: DurableObjectState;
|
private ctx: DurableObjectState;
|
||||||
private env: Env;
|
private env: Env;
|
||||||
private sessions: Map<WebSocket, Session>;
|
private sessions: Map<WebSocket, Session>;
|
||||||
private count: number;
|
|
||||||
private lastUpdate: number;
|
// 주사위 게임 상태
|
||||||
private diceNumber: number;
|
private noMoreBet: boolean;
|
||||||
private isPlaying: boolean;
|
private dice1: number | null;
|
||||||
private diceInterval: ReturnType<typeof setInterval> | null;
|
private dice2: number | null;
|
||||||
|
private dice3: number | null;
|
||||||
|
private noMoreBetStartTime: number | null;
|
||||||
|
private noMoreBetEndTime: number | null;
|
||||||
|
private gameTimer: ReturnType<typeof setTimeout> | null;
|
||||||
|
|
||||||
constructor(ctx: DurableObjectState, env: Env) {
|
constructor(ctx: DurableObjectState, env: Env) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.sessions = new Map();
|
this.sessions = new Map();
|
||||||
this.count = 0;
|
|
||||||
this.lastUpdate = Date.now();
|
|
||||||
this.diceNumber = 1;
|
|
||||||
this.isPlaying = false;
|
|
||||||
this.diceInterval = null;
|
|
||||||
|
|
||||||
// Durable Objects에서 영구 저장소로부터 카운트를 복원
|
// 주사위 게임 초기화
|
||||||
|
this.noMoreBet = false;
|
||||||
|
this.dice1 = null;
|
||||||
|
this.dice2 = null;
|
||||||
|
this.dice3 = null;
|
||||||
|
this.noMoreBetStartTime = null;
|
||||||
|
this.noMoreBetEndTime = null;
|
||||||
|
this.gameTimer = null;
|
||||||
|
|
||||||
|
// Durable Objects에서 영구 저장소로부터 상태 복원
|
||||||
this.ctx.blockConcurrencyWhile(async () => {
|
this.ctx.blockConcurrencyWhile(async () => {
|
||||||
const stored = await this.ctx.storage.get<number>('count');
|
const storedNoMoreBet = await this.ctx.storage.get<boolean>('noMoreBet');
|
||||||
if (stored !== undefined) {
|
if (storedNoMoreBet !== undefined) {
|
||||||
this.count = stored;
|
this.noMoreBet = storedNoMoreBet;
|
||||||
}
|
|
||||||
const storedDice = await this.ctx.storage.get<number>('diceNumber');
|
|
||||||
if (storedDice !== undefined) {
|
|
||||||
this.diceNumber = storedDice;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 게임 루프 시작
|
||||||
|
this.startGameLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(request: Request): Promise<Response> {
|
async fetch(request: Request): Promise<Response> {
|
||||||
@ -57,7 +76,18 @@ export class CounterDurableObject {
|
|||||||
// 세션 생성
|
// 세션 생성
|
||||||
const session: Session = {
|
const session: Session = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
webSocket: server
|
webSocket: server,
|
||||||
|
nickname: undefined,
|
||||||
|
capital: undefined,
|
||||||
|
oddBet: 0,
|
||||||
|
evenBet: 0,
|
||||||
|
bigBet: 0,
|
||||||
|
smallBet: 0,
|
||||||
|
oddResult: null,
|
||||||
|
evenResult: null,
|
||||||
|
bigResult: null,
|
||||||
|
smallResult: null,
|
||||||
|
lastWinAmount: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// WebSocket Hibernation API 사용
|
// WebSocket Hibernation API 사용
|
||||||
@ -78,66 +108,170 @@ export class CounterDurableObject {
|
|||||||
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
|
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
|
||||||
try {
|
try {
|
||||||
const data = typeof message === 'string' ? JSON.parse(message) : null;
|
const data = typeof message === 'string' ? JSON.parse(message) : null;
|
||||||
|
const session = this.sessions.get(ws);
|
||||||
|
|
||||||
if (data && data.type === 'increment') {
|
if (!session) return;
|
||||||
// 카운트 증가
|
|
||||||
this.count++;
|
|
||||||
this.lastUpdate = Date.now();
|
|
||||||
|
|
||||||
// 영구 저장소에 저장
|
if (data && data.type === 'setUser') {
|
||||||
await this.ctx.storage.put('count', this.count);
|
// 사용자 정보 설정
|
||||||
|
session.nickname = data.nickname;
|
||||||
// 모든 클라이언트에 브로드캐스트
|
session.capital = data.capital;
|
||||||
this.broadcast();
|
this.broadcast();
|
||||||
} else if (data && data.type === 'reset') {
|
} else if (data && data.type === 'bet' && !this.noMoreBet) {
|
||||||
// 카운트 리셋
|
// 배팅 처리
|
||||||
this.count = 0;
|
const amount = data.amount || 1000;
|
||||||
this.lastUpdate = Date.now();
|
|
||||||
await this.ctx.storage.put('count', this.count);
|
if (session.capital && session.capital >= amount) {
|
||||||
this.broadcast();
|
switch (data.betType) {
|
||||||
} else if (data && data.type === 'play') {
|
case 'odd':
|
||||||
// 주사위 플레이 시작
|
session.oddBet += amount;
|
||||||
this.startDiceRolling();
|
break;
|
||||||
} else if (data && data.type === 'stop') {
|
case 'even':
|
||||||
// 주사위 플레이 정지
|
session.evenBet += amount;
|
||||||
this.stopDiceRolling();
|
break;
|
||||||
|
case 'big':
|
||||||
|
session.bigBet += amount;
|
||||||
|
break;
|
||||||
|
case 'small':
|
||||||
|
session.smallBet += amount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 클라이언트 측에서 자본금 차감은 이미 되어있으므로 서버에서도 동기화
|
||||||
|
session.capital -= amount;
|
||||||
|
this.broadcast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error handling message:', error);
|
console.error('Error handling message:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private startDiceRolling() {
|
// 게임 루프 시작
|
||||||
if (this.isPlaying) return; // 이미 실행 중이면 무시
|
private startGameLoop() {
|
||||||
|
// 처음 시작 시 베팅 기간으로 시작 (45초)
|
||||||
this.isPlaying = true;
|
this.startBettingPeriod();
|
||||||
this.broadcast(); // 플레이 상태 전송
|
|
||||||
|
|
||||||
// 1초마다 주사위 굴리기
|
|
||||||
this.diceInterval = setInterval(async () => {
|
|
||||||
// 1-6 사이 랜덤 숫자
|
|
||||||
this.diceNumber = Math.floor(Math.random() * 6) + 1;
|
|
||||||
this.lastUpdate = Date.now();
|
|
||||||
|
|
||||||
// Durable Object Storage에 저장
|
|
||||||
await this.ctx.storage.put('diceNumber', this.diceNumber);
|
|
||||||
|
|
||||||
// 모든 클라이언트에 브로드캐스트
|
|
||||||
this.broadcast();
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopDiceRolling() {
|
// 1번 로직: noMoreBet = true, 15초간 유지
|
||||||
if (!this.isPlaying) return;
|
private startNoMoreBetPeriod() {
|
||||||
|
this.noMoreBet = true;
|
||||||
|
this.noMoreBetStartTime = Date.now();
|
||||||
|
this.noMoreBetEndTime = this.noMoreBetStartTime + 15000; // 15초
|
||||||
|
|
||||||
this.isPlaying = false;
|
// 3개의 주사위 랜덤 생성 (1-6)
|
||||||
|
this.dice1 = Math.floor(Math.random() * 6) + 1;
|
||||||
|
this.dice2 = Math.floor(Math.random() * 6) + 1;
|
||||||
|
this.dice3 = Math.floor(Math.random() * 6) + 1;
|
||||||
|
|
||||||
if (this.diceInterval) {
|
// 주사위 합계
|
||||||
clearInterval(this.diceInterval);
|
const sum = this.dice1 + this.dice2 + this.dice3;
|
||||||
this.diceInterval = null;
|
const isOdd = sum % 2 === 1;
|
||||||
|
const isBig = sum >= 10;
|
||||||
|
|
||||||
|
// 각 세션별로 배팅 결과 계산
|
||||||
|
this.sessions.forEach((session) => {
|
||||||
|
if (!session.capital) return;
|
||||||
|
|
||||||
|
let winAmount = 0;
|
||||||
|
|
||||||
|
// 홀수 배팅 결과
|
||||||
|
if (session.oddBet > 0) {
|
||||||
|
if (isOdd) {
|
||||||
|
session.oddResult = 'win';
|
||||||
|
winAmount += session.oddBet * 2;
|
||||||
|
} else {
|
||||||
|
session.oddResult = 'lose';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 짝수 배팅 결과
|
||||||
|
if (session.evenBet > 0) {
|
||||||
|
if (!isOdd) {
|
||||||
|
session.evenResult = 'win';
|
||||||
|
winAmount += session.evenBet * 2;
|
||||||
|
} else {
|
||||||
|
session.evenResult = 'lose';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대 배팅 결과
|
||||||
|
if (session.bigBet > 0) {
|
||||||
|
if (isBig) {
|
||||||
|
session.bigResult = 'win';
|
||||||
|
winAmount += session.bigBet * 2;
|
||||||
|
} else {
|
||||||
|
session.bigResult = 'lose';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 소 배팅 결과
|
||||||
|
if (session.smallBet > 0) {
|
||||||
|
if (!isBig) {
|
||||||
|
session.smallResult = 'win';
|
||||||
|
winAmount += session.smallBet * 2;
|
||||||
|
} else {
|
||||||
|
session.smallResult = 'lose';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 자본금 업데이트
|
||||||
|
const totalBet = session.oddBet + session.evenBet + session.bigBet + session.smallBet;
|
||||||
|
session.lastWinAmount = winAmount - totalBet;
|
||||||
|
session.capital += winAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 상태 저장
|
||||||
|
this.ctx.storage.put('noMoreBet', this.noMoreBet);
|
||||||
|
|
||||||
|
// 브로드캐스트 (15초간 한 번만)
|
||||||
|
this.broadcast();
|
||||||
|
|
||||||
|
// 15초 후 베팅 기간으로 전환
|
||||||
|
if (this.gameTimer) {
|
||||||
|
clearTimeout(this.gameTimer);
|
||||||
}
|
}
|
||||||
|
this.gameTimer = setTimeout(() => {
|
||||||
|
this.startBettingPeriod();
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
|
|
||||||
this.broadcast(); // 정지 상태 전송
|
// 2번 로직: noMoreBet = false, 45초간 유지
|
||||||
|
private startBettingPeriod() {
|
||||||
|
this.noMoreBet = false;
|
||||||
|
this.noMoreBetStartTime = Date.now();
|
||||||
|
this.noMoreBetEndTime = this.noMoreBetStartTime + 45000; // 45초
|
||||||
|
|
||||||
|
// 주사위 값 클리어
|
||||||
|
this.dice1 = null;
|
||||||
|
this.dice2 = null;
|
||||||
|
this.dice3 = null;
|
||||||
|
|
||||||
|
// 모든 세션의 배팅 및 결과 클리어
|
||||||
|
this.sessions.forEach((session) => {
|
||||||
|
session.oddBet = 0;
|
||||||
|
session.evenBet = 0;
|
||||||
|
session.bigBet = 0;
|
||||||
|
session.smallBet = 0;
|
||||||
|
session.oddResult = null;
|
||||||
|
session.evenResult = null;
|
||||||
|
session.bigResult = null;
|
||||||
|
session.smallResult = null;
|
||||||
|
session.lastWinAmount = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 상태 저장
|
||||||
|
this.ctx.storage.put('noMoreBet', this.noMoreBet);
|
||||||
|
|
||||||
|
// 브로드캐스트
|
||||||
|
this.broadcast();
|
||||||
|
|
||||||
|
// 45초 후 noMoreBet 기간으로 전환
|
||||||
|
if (this.gameTimer) {
|
||||||
|
clearTimeout(this.gameTimer);
|
||||||
|
}
|
||||||
|
this.gameTimer = setTimeout(() => {
|
||||||
|
this.startNoMoreBetPeriod();
|
||||||
|
}, 45000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async webSocketClose(ws: WebSocket, code: number, _reason: string, _wasClean: boolean) {
|
async webSocketClose(ws: WebSocket, code: number, _reason: string, _wasClean: boolean) {
|
||||||
@ -145,11 +279,6 @@ export class CounterDurableObject {
|
|||||||
this.sessions.delete(ws);
|
this.sessions.delete(ws);
|
||||||
ws.close(code, 'Durable Object is closing WebSocket');
|
ws.close(code, 'Durable Object is closing WebSocket');
|
||||||
|
|
||||||
// 남은 클라이언트가 없으면 주사위 정지
|
|
||||||
const connectedWebSockets = this.ctx.getWebSockets();
|
|
||||||
if (connectedWebSockets.length === 0) {
|
|
||||||
this.stopDiceRolling();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 남은 클라이언트들에게 업데이트 전송
|
// 남은 클라이언트들에게 업데이트 전송
|
||||||
this.broadcast();
|
this.broadcast();
|
||||||
@ -159,18 +288,52 @@ export class CounterDurableObject {
|
|||||||
// WebSocket Hibernation API를 사용할 때는 getWebSockets()로 실제 연결 수를 확인
|
// WebSocket Hibernation API를 사용할 때는 getWebSockets()로 실제 연결 수를 확인
|
||||||
const connectedWebSockets = this.ctx.getWebSockets();
|
const connectedWebSockets = this.ctx.getWebSockets();
|
||||||
|
|
||||||
const message = JSON.stringify({
|
// 전체 사용자 배팅 내역 수집
|
||||||
count: this.count,
|
const allBettings: Array<{
|
||||||
online: connectedWebSockets.length,
|
nickname: string;
|
||||||
lastUpdate: this.lastUpdate,
|
oddBet: number;
|
||||||
diceNumber: this.diceNumber,
|
evenBet: number;
|
||||||
isPlaying: this.isPlaying
|
bigBet: number;
|
||||||
|
smallBet: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
this.sessions.forEach((session) => {
|
||||||
|
if (session.nickname && (session.oddBet > 0 || session.evenBet > 0 || session.bigBet > 0 || session.smallBet > 0)) {
|
||||||
|
allBettings.push({
|
||||||
|
nickname: session.nickname,
|
||||||
|
oddBet: session.oddBet,
|
||||||
|
evenBet: session.evenBet,
|
||||||
|
bigBet: session.bigBet,
|
||||||
|
smallBet: session.smallBet
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 모든 연결된 WebSocket에 메시지 전송
|
// 각 세션별로 메시지 전송
|
||||||
// @ts-ignore - Cloudflare Workers types 불일치
|
// @ts-ignore - Cloudflare Workers types 불일치
|
||||||
connectedWebSockets.forEach((ws: WebSocket) => {
|
connectedWebSockets.forEach((ws: WebSocket) => {
|
||||||
try {
|
try {
|
||||||
|
const session = this.sessions.get(ws);
|
||||||
|
|
||||||
|
const message = JSON.stringify({
|
||||||
|
noMoreBet: this.noMoreBet,
|
||||||
|
dice1: this.dice1,
|
||||||
|
dice2: this.dice2,
|
||||||
|
dice3: this.dice3,
|
||||||
|
noMoreBetStartTime: this.noMoreBetStartTime,
|
||||||
|
noMoreBetEndTime: this.noMoreBetEndTime,
|
||||||
|
online: connectedWebSockets.length,
|
||||||
|
// 세션별 배팅 정보
|
||||||
|
capital: session?.capital,
|
||||||
|
oddResult: session?.oddResult,
|
||||||
|
evenResult: session?.evenResult,
|
||||||
|
bigResult: session?.bigResult,
|
||||||
|
smallResult: session?.smallResult,
|
||||||
|
lastWinAmount: session?.lastWinAmount,
|
||||||
|
// 전체 사용자 배팅 내역
|
||||||
|
allBettings: allBettings
|
||||||
|
});
|
||||||
|
|
||||||
ws.send(message);
|
ws.send(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error broadcasting to client:', error);
|
console.error('Error broadcasting to client:', error);
|
||||||
|
|||||||
@ -1,15 +1,52 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let count = $state(0);
|
|
||||||
let online = $state(0);
|
let online = $state(0);
|
||||||
let lastUpdate = $state<Date | null>(null);
|
|
||||||
let isConnected = $state(false);
|
let isConnected = $state(false);
|
||||||
let isConnecting = $state(false);
|
let isConnecting = $state(false);
|
||||||
let ws = $state<WebSocket | null>(null);
|
let ws = $state<WebSocket | null>(null);
|
||||||
let diceNumber = $state(1);
|
|
||||||
let isPlaying = $state(false);
|
// 사용자 정보
|
||||||
|
let nickname = $state('');
|
||||||
|
let capital = $state(10000); // 초기 자본금
|
||||||
|
let inputNickname = $state('');
|
||||||
|
let inputCapital = $state('10000');
|
||||||
|
|
||||||
|
// 주사위 게임 상태
|
||||||
|
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() {
|
function connectWebSocket() {
|
||||||
isConnecting = true;
|
isConnecting = true;
|
||||||
|
nickname = inputNickname;
|
||||||
|
capital = parseInt(inputCapital);
|
||||||
|
|
||||||
// WebSocket 프로토콜 결정 (https -> wss, http -> ws)
|
// WebSocket 프로토콜 결정 (https -> wss, http -> ws)
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
@ -21,16 +58,54 @@
|
|||||||
isConnected = true;
|
isConnected = true;
|
||||||
isConnecting = false;
|
isConnecting = false;
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
|
|
||||||
|
// 서버에 사용자 정보 전송
|
||||||
|
if (ws) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'setUser',
|
||||||
|
nickname: nickname,
|
||||||
|
capital: capital
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
count = data.count;
|
noMoreBet = data.noMoreBet;
|
||||||
|
dice1 = data.dice1;
|
||||||
|
dice2 = data.dice2;
|
||||||
|
dice3 = data.dice3;
|
||||||
|
noMoreBetStartTime = data.noMoreBetStartTime;
|
||||||
|
noMoreBetEndTime = data.noMoreBetEndTime;
|
||||||
online = data.online;
|
online = data.online;
|
||||||
lastUpdate = new Date(data.lastUpdate);
|
|
||||||
diceNumber = data.diceNumber || 1;
|
// 배팅 결과
|
||||||
isPlaying = data.isPlaying || false;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error parsing message:', error);
|
console.error('Error parsing message:', error);
|
||||||
}
|
}
|
||||||
@ -55,27 +130,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function incrementCount() {
|
function betOdd() {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
||||||
ws.send(JSON.stringify({ type: 'increment' }));
|
oddBet += 1000;
|
||||||
|
capital -= 1000;
|
||||||
|
ws.send(JSON.stringify({ type: 'bet', betType: 'odd', amount: 1000 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetCount() {
|
function betEven() {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
||||||
ws.send(JSON.stringify({ type: 'reset' }));
|
evenBet += 1000;
|
||||||
|
capital -= 1000;
|
||||||
|
ws.send(JSON.stringify({ type: 'bet', betType: 'even', amount: 1000 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playDice() {
|
function betBig() {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
||||||
ws.send(JSON.stringify({ type: 'play' }));
|
bigBet += 1000;
|
||||||
|
capital -= 1000;
|
||||||
|
ws.send(JSON.stringify({ type: 'bet', betType: 'big', amount: 1000 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopDice() {
|
function betSmall() {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN && !noMoreBet && capital >= 1000) {
|
||||||
ws.send(JSON.stringify({ type: 'stop' }));
|
smallBet += 1000;
|
||||||
|
capital -= 1000;
|
||||||
|
ws.send(JSON.stringify({ type: 'bet', betType: 'small', amount: 1000 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +199,29 @@
|
|||||||
return patterns[number] || patterns[1];
|
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 정리
|
// 컴포넌트 언마운트 시 WebSocket 정리
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -124,169 +230,332 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
<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-2xl w-full bg-white rounded-2xl shadow-xl p-8">
|
<div class="max-w-3xl w-full bg-white rounded-2xl shadow-xl p-8">
|
||||||
<h1 class="text-4xl font-bold text-center mb-8 text-gray-800">
|
<h1 class="text-4xl font-bold text-center mb-8 text-gray-800">
|
||||||
Cloudflare Durable Objects + WebSocket
|
Durable Objects TEST
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- 연결 상태 -->
|
<!-- 연결 상태, 접속자 수, 자본금 (3 columns) -->
|
||||||
<div class="mb-8 p-4 rounded-lg {isConnected ? 'bg-green-50 border-2 border-green-200' : 'bg-gray-50 border-2 border-gray-200'}">
|
<div class="grid grid-cols-3 gap-4 mb-8">
|
||||||
<div class="flex items-center justify-between">
|
<!-- 연결 상태 -->
|
||||||
<div class="flex items-center gap-2">
|
<div
|
||||||
<div class="w-3 h-3 rounded-full {isConnected ? 'bg-green-500 animate-pulse' : 'bg-gray-400'}"></div>
|
class="p-4 rounded-lg {isConnected ? 'bg-green-50 border-2 border-green-200' : 'bg-gray-50 border-2 border-gray-200'}">
|
||||||
<span class="font-medium text-gray-700">
|
<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 ? '연결 중...' : '연결 안됨'}
|
{isConnected ? '연결됨' : isConnecting ? '연결 중...' : '연결 안됨'}
|
||||||
</span>
|
</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>
|
||||||
{#if !isConnected}
|
|
||||||
<button
|
|
||||||
onclick={connectWebSocket}
|
|
||||||
disabled={isConnecting}
|
|
||||||
class="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="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
|
||||||
>
|
|
||||||
연결 끊기
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 카운터 디스플레이 -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<div class="text-center mb-6">
|
|
||||||
<div class="text-6xl font-bold text-indigo-600 mb-2">
|
|
||||||
{count}
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-600">전체 카운트</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 버튼들 -->
|
<!-- 접속자 수 -->
|
||||||
<div class="flex gap-4 justify-center mb-6">
|
<div
|
||||||
<button
|
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">
|
||||||
onclick={incrementCount}
|
<div class="text-4xl font-bold text-green-600 mb-1">
|
||||||
disabled={!isConnected}
|
|
||||||
class="px-8 py-3 bg-indigo-500 text-white rounded-lg font-semibold hover:bg-indigo-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all transform hover:scale-105"
|
|
||||||
>
|
|
||||||
카운트 증가 +1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onclick={resetCount}
|
|
||||||
disabled={!isConnected}
|
|
||||||
class="px-8 py-3 bg-orange-500 text-white rounded-lg font-semibold hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all transform hover:scale-105"
|
|
||||||
>
|
|
||||||
리셋
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 통계 정보 -->
|
|
||||||
<div class="grid grid-cols-2 gap-4 mb-8">
|
|
||||||
<div class="bg-blue-50 p-4 rounded-lg border-2 border-blue-100">
|
|
||||||
<div class="text-3xl font-bold text-blue-600 mb-1">
|
|
||||||
{online}
|
{online}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-600">실시간 접속자</div>
|
<div class="text-sm text-gray-600">실시간 접속자</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-purple-50 p-4 rounded-lg border-2 border-purple-100">
|
|
||||||
<div class="text-lg font-semibold text-purple-600 mb-1">
|
|
||||||
{lastUpdate ? lastUpdate.toLocaleTimeString('ko-KR') : '-'}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm text-gray-600">마지막 업데이트</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 주사위 성능 테스트 -->
|
<!-- 현재 자본금 -->
|
||||||
<div class="bg-gradient-to-br from-yellow-50 to-orange-50 p-6 rounded-xl border-2 border-yellow-200 mb-8">
|
<div
|
||||||
<h2 class="text-2xl font-bold text-center mb-4 text-gray-800">
|
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">
|
||||||
🎲 Durable Object 성능 테스트
|
{#if isConnected && nickname}
|
||||||
</h2>
|
<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>
|
||||||
<div class="flex justify-center mb-6">
|
{#if lastWinAmount !== 0}
|
||||||
<div class="relative">
|
<div class="text-sm font-semibold mt-1 {lastWinAmount > 0 ? 'text-green-600' : 'text-red-600'}">
|
||||||
<div class="w-32 h-32 bg-white rounded-2xl shadow-2xl border-4 border-gray-800 p-4
|
{lastWinAmount > 0 ? '+' : ''}{lastWinAmount.toLocaleString()}원
|
||||||
transform transition-all duration-300 {isPlaying ? 'animate-bounce' : ''}">
|
|
||||||
<div class="grid grid-cols-3 gap-2 h-full">
|
|
||||||
{#each getDiceDots(diceNumber) as row}
|
|
||||||
{#each row as dot}
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
{#if dot}
|
|
||||||
<div class="w-4 h-4 bg-gray-800 rounded-full"></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
<!-- 주사위 번호 표시 -->
|
|
||||||
<div class="absolute -top-3 -right-3 w-10 h-10 bg-red-500 text-white
|
|
||||||
rounded-full flex items-center justify-center text-xl font-bold shadow-lg">
|
|
||||||
{diceNumber}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 플레이 컨트롤 -->
|
|
||||||
<div class="flex gap-4 justify-center mb-4">
|
|
||||||
{#if !isPlaying}
|
|
||||||
<button
|
|
||||||
onclick={playDice}
|
|
||||||
disabled={!isConnected}
|
|
||||||
class="px-8 py-3 bg-green-500 text-white rounded-lg font-semibold
|
|
||||||
hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
||||||
transition-all transform hover:scale-105 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span class="text-2xl">▶️</span>
|
|
||||||
<span>플레이</span>
|
|
||||||
</button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<div class="text-2xl font-bold text-gray-400">-</div>
|
||||||
onclick={stopDice}
|
<div class="text-xs text-gray-600">자본금</div>
|
||||||
disabled={!isConnected}
|
|
||||||
class="px-8 py-3 bg-red-500 text-white rounded-lg font-semibold
|
|
||||||
hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed
|
|
||||||
transition-all transform hover:scale-105 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span class="text-2xl">⏸️</span>
|
|
||||||
<span>정지</span>
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 상태 표시 -->
|
<!-- Progress Bar -->
|
||||||
<div class="text-center">
|
<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="inline-block px-4 py-2 rounded-full {isPlaying ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'}">
|
<div class="mb-4">
|
||||||
{isPlaying ? '🔄 주사위가 1초마다 자동으로 굴러가는 중...' : '⏸️ 정지됨'}
|
<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>
|
</div>
|
||||||
|
<div class="text-sm text-gray-600 text-center">
|
||||||
<!-- 설명 -->
|
{noMoreBet ? '주사위를 굴리는 중... 다음 라운드를 기다려주세요' : '홀/짝, 대/소를 선택하세요!'}
|
||||||
<div class="mt-4 p-3 bg-white/50 rounded-lg text-sm text-gray-600">
|
|
||||||
<p class="font-semibold mb-1">💡 성능 테스트:</p>
|
|
||||||
<p>• 플레이 버튼 클릭 시 1초마다 주사위(1-6)가 자동으로 변경됩니다</p>
|
|
||||||
<p>• 모든 값은 Durable Object에 실시간 저장됩니다</p>
|
|
||||||
<p>• 모든 연결된 클라이언트에 즉시 동기화됩니다</p>
|
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<h2 class="font-semibold text-gray-800 mb-2">게임 규칙</h2>
|
||||||
<ul class="text-sm text-gray-600 space-y-1">
|
<ul class="text-sm text-gray-600 space-y-1">
|
||||||
<li>✅ Cloudflare Durable Objects로 상태 관리</li>
|
<li>✅ Cloudflare Durable Objects로 상태 관리</li>
|
||||||
<li>✅ WebSocket으로 실시간 양방향 통신</li>
|
<li>✅ WebSocket으로 실시간 양방향 통신</li>
|
||||||
<li>✅ 카운트 버튼 클릭으로 증가</li>
|
<li>🎲 <strong>베팅 기간 (45초)</strong>: 홀수 또는 짝수 선택 가능</li>
|
||||||
<li>✅ 실시간 접속자 수 표시</li>
|
<li>🚫 <strong>베팅 마감 (15초)</strong>: 주사위 결과 확인 및 대기</li>
|
||||||
<li>✅ 모든 클라이언트에 실시간 동기화</li>
|
<li>📊 Progress bar로 남은 시간 시각화</li>
|
||||||
<li>✅ Durable Objects 영구 저장소에 상태 저장</li>
|
<li>🔄 게임은 자동으로 반복됩니다</li>
|
||||||
<li>🎲 초단위 주사위 자동 롤링으로 성능 테스트</li>
|
<li>👥 모든 접속자에게 실시간 동기화</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user