272 lines
7.3 KiB
TypeScript
272 lines
7.3 KiB
TypeScript
import type { DurableObjectNamespace, DurableObjectState } from '@cloudflare/workers-types';
|
|
import type { Session, BettingInfo } from './types';
|
|
import { applyBetResults } from './game-results';
|
|
|
|
export interface Env {
|
|
COUNTER: DurableObjectNamespace;
|
|
}
|
|
|
|
|
|
export class CounterDurableObject {
|
|
private ctx: DurableObjectState;
|
|
private env: Env;
|
|
private sessions: Map<WebSocket, Session>;
|
|
|
|
// 주사위 게임 상태
|
|
private noMoreBet: boolean;
|
|
private dice1: number | 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) {
|
|
this.ctx = ctx;
|
|
this.env = env;
|
|
this.sessions = new Map();
|
|
|
|
// 주사위 게임 초기화
|
|
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 () => {
|
|
const storedNoMoreBet = await this.ctx.storage.get<boolean>('noMoreBet');
|
|
if (storedNoMoreBet !== undefined) {
|
|
this.noMoreBet = storedNoMoreBet;
|
|
}
|
|
});
|
|
|
|
// 게임 루프 시작
|
|
this.startGameLoop();
|
|
}
|
|
|
|
async fetch(request: Request): Promise<Response> {
|
|
// WebSocket 업그레이드 요청 처리
|
|
const upgradeHeader = request.headers.get('Upgrade');
|
|
if (!upgradeHeader || upgradeHeader !== 'websocket') {
|
|
return new Response('Expected Upgrade: websocket', { status: 426 });
|
|
}
|
|
|
|
// @ts-ignore - WebSocketPair는 Cloudflare Workers 런타임에서만 사용 가능
|
|
const webSocketPair = new WebSocketPair();
|
|
const [client, server] = Object.values(webSocketPair) as [WebSocket, WebSocket];
|
|
|
|
// 세션 생성
|
|
const session: Session = {
|
|
id: crypto.randomUUID(),
|
|
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 사용
|
|
// @ts-ignore - Cloudflare Workers types 불일치
|
|
this.ctx.acceptWebSocket(server);
|
|
this.sessions.set(server, session);
|
|
|
|
// 현재 상태 전송
|
|
this.broadcast();
|
|
|
|
return new Response(null, {
|
|
status: 101,
|
|
// @ts-ignore - webSocket 속성은 Cloudflare Workers에서 지원됨
|
|
webSocket: client
|
|
});
|
|
}
|
|
|
|
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
|
|
try {
|
|
const data = typeof message === 'string' ? JSON.parse(message) : null;
|
|
const session = this.sessions.get(ws);
|
|
|
|
if (!session) return;
|
|
|
|
if (data && data.type === 'setUser') {
|
|
// 사용자 정보 설정
|
|
session.nickname = data.nickname;
|
|
session.capital = data.capital;
|
|
this.broadcast();
|
|
} else if (data && data.type === 'bet' && !this.noMoreBet) {
|
|
// 배팅 처리
|
|
const amount = data.amount || 1000;
|
|
|
|
if (session.capital && session.capital >= amount) {
|
|
switch (data.betType) {
|
|
case 'odd':
|
|
session.oddBet += amount;
|
|
break;
|
|
case 'even':
|
|
session.evenBet += amount;
|
|
break;
|
|
case 'big':
|
|
session.bigBet += amount;
|
|
break;
|
|
case 'small':
|
|
session.smallBet += amount;
|
|
break;
|
|
}
|
|
// 클라이언트 측에서 자본금 차감은 이미 되어있으므로 서버에서도 동기화
|
|
session.capital -= amount;
|
|
this.broadcast();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error handling message:', error);
|
|
}
|
|
}
|
|
|
|
// 게임 루프 시작
|
|
private startGameLoop() {
|
|
// 처음 시작 시 베팅 기간으로 시작 (45초)
|
|
this.startBettingPeriod();
|
|
}
|
|
|
|
// 1번 로직: noMoreBet = true, 15초간 유지
|
|
private startNoMoreBetPeriod() {
|
|
this.noMoreBet = true;
|
|
this.noMoreBetStartTime = Date.now();
|
|
this.noMoreBetEndTime = this.noMoreBetStartTime + 15000; // 15초
|
|
|
|
// 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;
|
|
|
|
// 주사위 합계
|
|
const sum = this.dice1 + this.dice2 + this.dice3;
|
|
|
|
// 추출된 함수로 배팅 결과 계산 및 세션 갱신
|
|
applyBetResults(this.sessions, sum);
|
|
|
|
// 상태 저장
|
|
this.ctx.storage.put('noMoreBet', this.noMoreBet);
|
|
|
|
// 브로드캐스트 (15초간 한 번만)
|
|
this.broadcast();
|
|
|
|
// 15초 후 베팅 기간으로 전환
|
|
if (this.gameTimer) {
|
|
clearTimeout(this.gameTimer);
|
|
}
|
|
this.gameTimer = setTimeout(() => {
|
|
this.startBettingPeriod();
|
|
}, 15000);
|
|
}
|
|
|
|
// 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) {
|
|
// 세션 제거
|
|
this.sessions.delete(ws);
|
|
ws.close(code, 'Durable Object is closing WebSocket');
|
|
|
|
|
|
// 남은 클라이언트들에게 업데이트 전송
|
|
this.broadcast();
|
|
}
|
|
|
|
private broadcast() {
|
|
// WebSocket Hibernation API를 사용할 때는 getWebSockets()로 실제 연결 수를 확인
|
|
const connectedWebSockets = this.ctx.getWebSockets();
|
|
|
|
// 전체 사용자 배팅 내역 수집
|
|
const allBettings: BettingInfo[] = [];
|
|
|
|
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
|
|
});
|
|
}
|
|
});
|
|
|
|
// 각 세션별로 메시지 전송
|
|
// @ts-ignore - Cloudflare Workers types 불일치
|
|
connectedWebSockets.forEach((ws: WebSocket) => {
|
|
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);
|
|
} catch (error) {
|
|
console.error('Error broadcasting to client:', error);
|
|
}
|
|
});
|
|
}
|
|
}
|