dd/src/lib/counter-do.ts

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);
}
});
}
}