오늘의 목표

더보기

✔️ 최종프로젝트 진행

 


⏱️ 오늘의 일정

최종프로젝트 진행


📜 최종프로젝트 진행

 

최종프로젝트를 진행했다.저번에 이어 게임 준비와 게임 시작 까지 완성해 게임의 기본적인 틀을 완성했다.

 

게임 준비 요청 처리

import net from 'net';
import { GlobalFailCode } from '../enumTyps.js';
import {
  CustomSocket,
  RedisUserData,
  Room,
  User,
} from '../../interface/interface.js';
import { config } from '../../config/config.js';
import { sendPacket } from '../../packet/createPacket.js';
import {
  getRoomByUserId,
  getRooms,
  getSocketByUser,
  getUserBySocket,
  setCharacterInfoInit,
  setRedisData,
} from '../handlerMethod.js';

export const gamePrepareHandler = async (
  socket: CustomSocket,
  payload: Object,
) => {
  try {
    // requset 보낸 유저
    const user: RedisUserData = await getUserBySocket(socket);

    // 유저가 있는 방 찾기
    if (user !== undefined) {
      const room: Room | null = await getRoomByUserId(user.id);
      if (room === null) {
        return;
      }
      if (room.users.length <= 1) {
        console.error('게임을 시작 할 수 없습니다(인원 부족).');
        const responseData = {
          success: false,
          failCode: GlobalFailCode.INVALID_REQUEST,
        };
        sendPacket(
          socket,
          config.packetType.GAME_PREPARE_RESPONSE,
          responseData,
        );
      } else {
        // 게임준비 시작 요건 충족
        const responseData = {
          success: true,
          failCode: GlobalFailCode.NONE,
        };
        sendPacket(
          socket,
          config.packetType.GAME_PREPARE_RESPONSE,
          responseData,
        );

        // 방에있는 유저들 캐릭터 랜덤 배정하기
        room.users = setCharacterInfoInit(room.users);
        const rooms: Room[] | null = await getRooms();
        if (!rooms) {
          return;
        }
        // 변경한 정보 덮어쓰기
        for (let i = 0; i < rooms.length; i++) {
          if (rooms[i].id === room.id) {
            rooms[i] = room;
            break;
          }
        }
        // 레디스에 있는 룸 배열에서 user가 속해 있는 방을 수정하고,
        // 위에서 수정한 방이 포함되어 있는 전체 배열을 넣음
        await setRedisData('roomData', rooms);

        // 방에있는 유저들에게 notifi 보내기
        for (let i = 0; i < room.users.length; i++) {
          const userSocket = await getSocketByUser(room.users[i]);
          if (!userSocket) {
            console.error('gamePrepareHandler: socket not found');
            return;
          }
          //console.dir(room, { depth: null });
          sendPacket(userSocket, config.packetType.GAME_PREPARE_NOTIFICATION, {
            room,
          });
        }
      }
    } else {
      console.error('위치: gamePrepareHandler, 유저를 찾을 수 없습니다.');
      const responseData = {
        success: false,
        failCode: GlobalFailCode.INVALID_REQUEST,
      };
      sendPacket(socket, config.packetType.GAME_PREPARE_RESPONSE, responseData);
    }
  } catch (err) {
    const responseData = {
      success: false,
      failCode: GlobalFailCode.INVALID_REQUEST,
    };
    sendPacket(socket, config.packetType.GAME_PREPARE_RESPONSE, responseData);
    console.error('gameStartHandler 오류', err);
  }
};

 

위 코드는 게임 준비 요청을 클라가 전송하면 서버에서 처리를 해주는 코드로,

우선 클라가 속한 방을 찾은 후, 방이 게임 준비 요건에 맞는지 검사한다.

 

방이 게임 준비 요건에 맞으면, 각 캐릭터들의 역할을 랜덤하게 서버가 정해주고 방 정보를 저장한 후,

방 안에 있는 유저들에게 noti를 날려 게임 준비가 완료되었다고 알려준다.

 

게임 준비가 완료되어 캐릭터와 역할이 정해진 모습


게임시작 요청 처리

import net from 'net';
import {
  CharacterPositionData,
  CustomSocket,
  RedisUserData,
} from '../../interface/interface.js';
import { GlobalFailCode, PhaseType } from '../enumTyps.js';
import { sendPacket } from '../../packet/createPacket.js';
import { config } from '../../config/config.js';
import {
  getRedisData,
  getRoomByUserId,
  getSocketByUser,
  getUserBySocket,
  setRedisData,
} from '../handlerMethod.js';

export const gameStartHandler = async (
  socket: CustomSocket,
  payload: Object,
) => {  
  try {
    // requset 보낸 유저
    const user: RedisUserData = await getUserBySocket(socket);
    // 유저가 있는 방 찾기
    if (user !== undefined) {
      const room = await getRoomByUserId(user.id);
      if (room === null) {
        const responseData = {
          success: false,
          failCode: GlobalFailCode.INVALID_REQUEST,
        };
        sendPacket(socket, config.packetType.GAME_START_RESPONSE, responseData);

        return;
      }

      if (room.users.length <= 1) {
        console.error('게임을 시작 할 수 없습니다.(인원 부족)');
      }

      const responseData = {
        success: true,
        failCode: GlobalFailCode.NONE,
      };
      sendPacket(socket, config.packetType.GAME_START_RESPONSE, responseData);

      let characterPositionDatas = await getRedisData('characterPositionDatas');
      if (!characterPositionDatas) {
        characterPositionDatas = { [room.id]: [] };
      } else {
        characterPositionDatas[room.id] = [];
      }

      for (let i = 0; i < room.users.length; i++) {
        // 위치 데이터 로직 필요
        const characterPositionData: CharacterPositionData = {
          id: room.users[i].id,
          x: 0 + 1 * i,
          y: 0 - 1 * i,
        };
        characterPositionDatas[room.id].push(characterPositionData);
      }

      await setRedisData('characterPositionDatas', characterPositionDatas);

      // 방에있는 유저들에게 notifi 보내기
      for (let i = 0; i < room.users.length; i++) {
        const userSocket = await getSocketByUser(room.users[i]);
        if (!userSocket) {
          console.error('gameStartHandler: socket not found');
          const responseData = {
            success: false,
            failCode: GlobalFailCode.INVALID_REQUEST,
          };
          sendPacket(
            socket,
            config.packetType.GAME_START_RESPONSE,
            responseData,
          );
          return;
        }
        // noti 데이터
        const now = Date.now() + 300000;
        const gameStateData = { phaseType: PhaseType.DAY, nextPhaseAt: now };
        const notifiData = {
          gameState: gameStateData,
          users: room.users,
          characterPositions: characterPositionDatas[room.id],
        };

        sendPacket(
          userSocket,
          config.packetType.GAME_START_NOTIFICATION,
          notifiData,
        );
      }
    } else {
      console.error('위치: gameStartHandler, 유저를 찾을 수 없습니다.');

      const responseData = {
        success: false,
        failCode: GlobalFailCode.INVALID_REQUEST,
      };
      sendPacket(socket, config.packetType.GAME_START_RESPONSE, responseData);
    }
  } catch (err) {
    const responseData = {
      success: false,
      failCode: GlobalFailCode.INVALID_REQUEST,
    };
    sendPacket(socket, config.packetType.GAME_START_RESPONSE, responseData);
    console.log('gameStartHandler 오류', err);
  }
};

 

위 코드는 게임 시작 요청을 클라가 전송하면 서버에서 처리를 해주는 코드로,

우선 클라가 속한 방을 찾은 후, 방이 게임 준비 요건에 맞는지 검사한다.

 

방이 게임 시작 요건에 맞으면, 각 캐릭터들의 좌표를 설정해주고, 레디스에 좌표를 저장 한 후

게임 시작에 필요한 정보를 설정하고, 방안에 있는 유저들에게 noti를 날려 게임시작이 완료되었다고 알려준다.

 

 

게임시작이 완료되어 캐릭터가 생성된 모습

 

 

게임 시작에서 캐릭터의 시작 위치를 랜덤하게 설정해줬는데,

추후에 미리 스폰 위치를 여러개 설정하고, 스폰 위치 중에서 랜덤하게 선택해 유저가 스폰될 수 있도록 고쳐야 겠다.

오늘의 목표

더보기

✔️ 최종프로젝트 진행

 


⏱️ 오늘의 일정

최종프로젝트 진행


📜 최종프로젝트 진행

 

 

 

게임에서 사용할 데이터들을 ERD를 활용해 작성했다.기본적으로 서버에서 사용하는 모든 데이터들은 redis를 통해 관리한다.

다만, 유저에 대한 소켓을 저장해야하는 부분만큼은 서버에서

export const socketSessions: Record<number, CustomSocket> = {};

 

위와 같이 따로 관리해주도록 했다. 

 

 

 

프로젝트가 위처럼 어느정도 기본틀은 갖추게 되어서,

 

캠프에서 제공해주는 클라기능은 위 그림에 남아있는

게임 준비완료, 게임 시작, 위치 업데이트까지 구현하고,

 

우리 팀만의 프로젝트를 진행하기로 결정했다.

 

 

 

 

여기까지 완성!

 

오늘의 목표

더보기

✔️ 최종프로젝트 진행


⏱️ 오늘의 일정

최종프로젝트 진행


📜 최종프로젝트 진행

 

최종 프로젝트 회의를 진행했다.

캠프에서 제공해주는 클라는 총 2가지 인데, 다음과 같다. 

 

 

 

3d 캐릭터로 구성되어 있는 턴제 기반의 클라

 


 

 

 

2D 캐릭터로 구성되어 있는 클라를 제공해줬다.

 


 

게임 플로우 정하기

 

팀원분들과 회의를 거쳐서 우리 팀이 만들고자 하는 게임을 정했다.

내용은 다음과 같다.

 

5명의 유저 중 1명이 보스가 되고 4명이 일반 유저가 되어 서로 싸우는 게임

  •  5명의 유저가 매칭이 되면 랜덤하게 1명이 보스가 되고 나머지 4명의 유저는 4개의 직업을 1개씩 나눠 가지고 게임이 시작된다.보스는 4명의 유저가 성장하기 전에 쫒아다니면서 그들을 죽인다.
  • 성장의 시간이 끝나면 탈출로가 열리고 해당 탈출로를 통해 탈출한 유저는 승리 그렇지 못한 유저는 패배가 된다.
  • 4명의 유저는 스폰되는 몬스터를 잡아서 보스에 대적할 수 있도록 실시간으로 성장하며 보스는 4명의 유저가 성장하기 전에 쫒아다니면서 그들을 죽인다.
  • 성장의 시간이 끝나면 탈출로가 열리고 해당 탈출로를 통해 탈출한 유저는 승리 그렇지 못한 유저는 패배가 된다.

 

위와 같은 게임 진행과정을 생각했을때, 개인적으로 2번 클라가 잘 어울린다고 생각을 해 2번을 추천했다.

높은 비율로 2번이 추천되어서 2번 클라를 선택했다.

 


ERD 그리기

 

 

User 테이블과 Match 테이블을 준비했다.

 

Match 테이블은 User가 게임 진행한 상황을 기록하는 테이블이다.

루트 노드 또는 임의 노드에서 인접한 노드부터 먼저 탐색하는 방법

 

큐를 통해 구현한다. ( 해당 노드의 주변부터 탐색해야하기 때문 )

 

  • 최소 비용 ( 즉, 모든 곳을 탐색하는 것보다 최소 비용이 우선일 때 )에 적합

시간 복잡도

  • 인접 행렬 : O ( V^2 )
  • 인접 리스트 : O ( V+E )

 

코드

#include <stdio.h>

int map[1001][1001], bfs[1001];
int queue[1001];

void init(int *, int size);

void BFS(int v, int N) {
	int front = 0, rear = 0;
	int pop;

	printf("%d ", v);
	queue[rear++] = v;
	bfs[v] = 1;

	while (front < rear) {
		pop = queue[front++];

		for (int i = 1; i <= N; i++) {
			if (map[pop][i] == 1 && bfs[i] == 0) {
				printf("%d ", i);
				queue[rear++] = i;
				bfs[i] = 1;
			}
		}
	}

	return;
}

int main(void) {

	init(map, sizeof(map) / 4);
	init(bfs, sizeof(bfs) / 4);
	init(queue, sizeof(queue) / 4);

	int N, M, V;
	scanf("%d%d%d", &N, &M, &V);

	for (int i = 0; i < M; i++)
	{
		int start, end;
		scanf("%d%d", &start, &end);
		map[start][end] = 1;
		map[end][start] = 1;
	}

	BFS(V, N);

	return 0;
}

void init(int *arr, int size) {
	for (int i = 0; i < size; i++)
	{
		arr[i] = 0;
	}
}

'알고리즘' 카테고리의 다른 글

[알고리즘] DFS  (0) 2024.11.11
[알고리즘] 힙 정렬 ( Heap Sort )  (0) 2024.11.10
[알고리즘] 배열 ( Array )  (0) 2024.09.17
[알고리즘] 합병 정렬  (0) 2024.08.28
[알고리즘] 퀵 정렬  (0) 2024.08.28

일종의 설계 기법이며, 설계 방법이다.

 

목적

SW 재사용성, 호환성, 유지 보수성을 보장

 

특징

디자인 패턴은 아이디어고, 특정한 구현이 아니다.

프로젝트에 항상 적용해야 하는 것은 아니지만, 추후 재사용, 호환, 유지 보수시 발생하는

문제 해결을 예방하기 위해 패턴을 만들어 둔 것이라고 생각하면 된다.

 

분류

1. 생성 패턴 ( Creational ) : 객체의 생성 방식 결정

 Class-creational patterns, Object-creational patterns

 

2. 구조 패턴 ( Structural ) : 객체간의 관계를 조직

 

3. 행위 패턴 ( Behavioral ) : 객체의 행위를 조직, 관리, 연합

 

'CS' 카테고리의 다른 글

[CS] IP 주소의 체계, 사설망, 공인망  (0) 2024.10.07
[CS] 랜카드, MAC 주소  (0) 2024.10.02
[CS] 웹서버와 게임서버  (0) 2024.08.25
[CS] 문자 집합과 인코딩  (0) 2024.08.19

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀이

✔️ 팀 프로젝트 진행


⏱️ 오늘의 일정

프로그래머스 코테 문제 풀이


📜 프로그래머스 코테 문제 풀이

 

H-Index

 

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/2/42747.%E2%80%85H%EF%BC%8DIndex

 

AlgorithmCodingTest/프로그래머스/2/42747. H-Index at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

 

📜 팀 프로젝트 진행

 

팀 프로젝트에서 맡은 파트인 게임 오버를 하려면 프로젝트가 어느정도 진행이 되야하는 상황이라서

팀원 분중 한분이 맡고 계셨던 걸 도와주는 식으로 우선 프로젝트를 진행했다.

추가로 맡은 부분은 위 그림처럼 회원가입 부분인데, 

export const registHandler = async ({ socket, payload }) => { 

  let registerResponsePayloadData = {};

  // 비밀번호 해시화
  let hashPassword = await bcrypt.hash(payload.password, 10);

  // 유저 검색
  let user = await DatabaseManager.GetInstance().findUser(payload.id, payload.email);

  if (user === undefined) {
    // 유저가 없다면 회원가입 진행
    DatabaseManager.GetInstance().createUser(payload.id, payload.email, hashPassword);

    registerResponsePayloadData.success = true;
    registerResponsePayloadData.message = '회원가입 성공';
    registerResponsePayloadData.failCode = 0;
  } else {
    // 이미 id값을 가진 유저가 DB에 저장되어 있음 ( 회원가입 실패 )
    registerResponsePayloadData.success = false;
    registerResponsePayloadData.message = '이미 존재하는 사용자입니다';
    registerResponsePayloadData.failCode = 1;
  }

  const registerResponsePacket = createResponse(
    PACKET_TYPE.REGISTER_RESPONSE,
    registerResponsePayloadData,
    socket.sequence,
  );
  
  socket.write(registerResponsePacket);
};


기본적으로 클라에서 보낸 아이디와 이메일이 DB에 있는지 확인하고, 회원 정보를 DB에 기록한다.

그 후 클라에게 적절한 응답 메세지 패킷을 보내주는걸로 구현을 완료했다.

 

 

+ Recent posts