- src/init/socket.js
초기화 및 설정
import { Server as SocketIO } from 'socket.io';
import registerHandler from '../handlers/register.handler.js';
const initSocket = (server) => {
const io = new SocketIO();
io.attach(server);
registerHandler(io);
};
export default initSocket;
Socket.IO 서버를 초기화하고 registerHandler을 통해 클라이언트 연결 인벤트를 처리한다.
- src/handlers/register.handler.js
사용자 등록 및 연결
import { addUser } from '../models/user.model.js';
import { v4 as uuidv4 } from 'uuid';
import { handleConnection, handleDisconnect, handlerEvent } from './helper.js';
const registerHandler = (io) => {
//접속시 이벤트
io.on('connection', (socket) => {
const userUUID = uuidv4();
addUser({ uuid: userUUID, socketId: socket.id });
handleConnection(socket, userUUID);
socket.on('event', (data) => handlerEvent(io, socket, data));
//접속 해제시 이벤트
socket.on('disconnect', (socket) => handleDisconnect(socket, userUUID));
});
};
export default registerHandler;
클라이언트가 서버에 열결될 때 호출된다. 새로운 사용자 UUID를 생성하고 사용자 정보를 저장한다.
handleConnection 을 호출하여 연결된 소캣에 대한 초기 처리를 한다.
연결된 소켓에서 event이벤트를 수신하면 handlerEvent를 호출하여 해당 이벤트를 처리한다.
- src/handlers/helper.js
이벤트 핸들링
import { Socket } from 'socket.io';
import { getUser, removeUser } from '../models/user.model.js';
import { getGameAssets } from '../init/assets.js';
import { createStage, getStage, setStages } from '../models/stage.model.js';
import { CLIENT_VERSION } from '../constants.js';
import handlerMappings from './handlerMapping.js';
export const handleDisconnect = (socket, uuid) => {
removeUser(socket.id);
console.log(`User disconnected:${socket.id}`);
console.log('Current users:', getUser());
};
//스테이지에 따라 더 높은 점수 획득
//1스테 , 0 ->1점
//2스테 , 1000 -> 2점
export const handleConnection = (socket, uuid) => {
console.log(`New user connected!${uuid} with socket ID ${socket.id}`);
console.log(`Current users:${getUser()}`);
createStage(uuid);
socket.emit('connection', { uuid });
};
export const handlerEvent = (io, socket, data) => {
if (!CLIENT_VERSION.includes(data.clientVersion)) {
socket.emit('response', { status: 'fail', message: 'Client version Not Found' });
return;
}
const handler = handlerMappings[data.handlerId];
if (!handler) {
socket.emit('response', { status: 'fail', message: 'Handler not found' });
return;
}
const response = handler(data.userId, data.payload);
if (response.broadcast) {
io.emit('response', 'broadcast');
return;
}
socket.emit('response', response);
};
handlerEvent 함수는 클라이언트의 요청에 따라 적절한 핸들러(gameStart,gameEnd,moveStageHandler)를 호출한다.
각 핸들러는 요청에 포함된 handlerId에 따라 다르게 동작한다.
핸들러가 성공적으로 처리되면 클라이언트에 응답을 전송한다.
- src/handlers/game.handler.js
게임 로직 처리
import { getGameAssets } from '../init/assets.js';
import { clearStage, getStage, setStages } from '../models/stage.model.js';
export const gameStart = (uuid, payload) => {
const { stages } = getGameAssets();
clearStage(uuid);
setStages(uuid, stages.data[0].id, payload.timeStamp);
console.log('stage:', getStage(uuid));
return { status: 'success' };
};
export const gameEnd = (uuid, payload) => {
//클라이언트는 게임 종료 시 타임스탬프와 총 점수를 줌
const { timeStamp: gemaEndTime, score } = payload;
const stages = getStage(uuid);
if (!stages.length) {
return { status: 'fail', message: 'No stages found for user' };
}
//각 스테이지의 지속 시간을 계산하여 촘 점수 계산
let totalScore = 0;
stages.forEach((stage, index) => {
let stageEndTime;
if (index === stages.length - 1) {
stageEndTime = gemaEndTime;
} else {
stageEndTime = stages[index + 1].timeStamp;
}
const stageDuration = (stageEndTime - stage.timeStamp) / 1000;
totalScore += stageDuration; // 1초당 1점
});
//점수와 타임스탬프를 검증
//오차범위 5
if (Math.abs(score - totalScore) > 5) {
return { status: 'fail', message: 'Score verification error' };
}
return { status: 'success', message: 'Game End', score };
};
gameStart:게임 시작시 호출되며 게임 자산을 가져와 초기 스테이지를 설정한다.
gameEnd:게임 종료시 호출되어 각 스테이지의 점수를 계산하고 검증한다. 점수가 일치하지 않는다면 오류 메시지를 반환한다.
- src/handlers/stage.handler.js
src/models/stage.model.js
스테이지 관리
src/handlers/stage.handler.js 코드
//유저는 스테이지를 하나씩 올라갈 수 있다. (1스테 ->2, 2->3)
import { getGameAssets } from '../init/assets.js';
import { getStage, setStages } from '../models/stage.model.js';
//유저는 일정 점수 도달시 다음 스테이지로 이동
export const moveStageHandler = (userId, payload) => {
//유저의 현재 스테이지 정보
let currentStages = getStage(userId);
if (!currentStages.length) {
return { status: 'fail', message: 'No stages found for user' };
}
//오름차순 -> 가장 큰 스테이지 ID를 확인 <- 유저의 현재 스테이지
currentStages.sort((a, b) => a.id - b.id);
const currentStage = currentStages[currentStages.length - 1];
//클라이언트 vs 서버 비교
if (currentStage.id !== payload.currentStages) {
console.log('asdasd:', currentStage);
return { status: 'fail', message: 'Current Stage Mismatch' };
}
//점수 검증
const serverTime = Date.now(); //현재 타임 스탬프
const elapsedTime = (serverTime - currentStages.timeStamp) / 1000;
//1스테이지 -> 2스테이지
//여기서 105 는 임의로 정한 오차범위임
if (elapsedTime < 100 || elapsedTime > 105) {
return { stages: 'fail', message: 'Invalid elapsed time' };
}
//타겟 스테이지 검즘 <- 게임에셋에 존재하는가?
const { stages } = getGameAssets();
if (!stages.data.some((stage) => stage.id === payload.targetStage)) {
return { status: 'fail', message: 'Target stage not found' };
}
setStages(userId, payload.targetStage, serverTime);
return { stage: 'success' };
};
사용자가 스테이지를 이동할 경우 호출된다. 현재 스테이지 정보를 확인하고 클라이언트에서 보낸 데이터와 비교하여
유효성을 검증한다. 성공적으로 이동하면 새로운 스테이지를 설정한다.
src/models/stage.model.js 코드
//key : uuid ,value : array -> 스테이지 정보는 배열
const stages = {};
//스테이지 초기화
export const createStage = (uuid) => {
stages[uuid] = [];
};
export const getStage = (uuid) => {
return stages[uuid];
};
export const setStages = (uuid, id, timeStamp) => {
return stages[uuid].push({ id, timeStamp });
};
export const clearStage = (uuid) => {
stages[uuid] = [];
};
스테이지 정보를 저장하는 객체를 관리한다 스테이지를 생성,가져오기,설정,초기화 하는 함수가 포함되어 있다.
- src/init/assets.js
자산 로딩
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const basePath = path.join(__dirname, '../../assets');
//파일 읽는 함수
//비동기 병렬로 파일을 읽는다.
const readFileAsync = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(path.join(basePath, filename), 'utf8', (err, data) => {
if (err) {
reject(err);
return;
}
resolve(JSON.parse(data));
});
});
};
//Promise.all()
export const loadGameAssets = async () => {
try {
const [stages, items, itemsUnlock] = await Promise.all([
readFileAsync('stage.json'),
readFileAsync('item.json'),
readFileAsync('item_unlock.json'),
]);
gameAssets = { stages, items, itemsUnlock };
return gameAssets;
} catch (err) {
console.log(err);
throw new Error('서버 에러:' + err.message);
}
};
let gameAssets = {};
export const getGameAssets = () => {
return gameAssets;
};
게임자산(스테이지,아이템 등)을 비동기로 로드하는함수가 포함되어있다.
loadGameAssets 함수가 호출되어 JSON 파일로부터 게임 자산을 읽는다. 이 자산은 다른 핸들러에서 참조됨
- src/models/user.model.js
사용자 관리
const users = [];
export const addUser = (user) => {
users.push(user);
};
export const removeUser = (socketId) => {
const index = users.findIndex((user) => user.socketId === socketId);
if (index !== -1) {
return users.splice(index, 1)[0];
}
};
export const getUser = () => {
return users;
};
사용자 정보를 관리하는 모듈이다. 사용자를 추가,제거하고 현재 사용자 목록을 가져오는 기능이 포함됨
'Node' 카테고리의 다른 글
socket 이벤트 / 버퍼(Buffer) (0) | 2024.10.23 |
---|---|
Socket.io 기본 기능 (0) | 2024.09.30 |
HTTP의 특징,webSocket의 특징 (2) | 2024.09.26 |
객체 지향 (1) | 2024.09.25 |
Mongoose Schema (0) | 2024.09.06 |