TIL

[멋사 부트캠프 TIL회고] 오목 멀티플레이 오류 수정(2)

Cadi 2025. 3. 25. 16:29

잡다한 오류들 

 

1. 자동 닉네임 설정 오류

닉네임이 서버로 제대로 들어오나 ( 자동 닉네임 설정 ) DB에 제대로 반영되지 않는 문제 

-> 오타 수정으로 해결 완료

 

2. roomId가 rooms 배열에 남아 있어 빈 방에 들어가지는 문제 

let rooms = [];

현재 아래와 같은 방식으로 매칭된 방은 rooms에서 삭제해 주고 있고, 게임이 끝나면 둘 다 나가지기에 자동으로 삭제됨

                rooms = rooms.filter(room => room.roomId != matchedRoom.roomId);
 

 

그러나, 한 명이 매칭 중에 화면을 나갔을 때는 방에 아무도 남아 있지 않게 되어 방이 삭제되긴 하나

roomId는 그대로 rooms 배열에 남아 있는다. 따라서 새로운 사람이 매칭을 시작하면 그 roomId의 방으로 진입을 시도하고

없는 roomId이기에 새로운 방을 만들게 된다 ( 같은 roomId의 ) . 다만 우리의 코드가 rooms 배열에 있는 room으로 진입을 했을 때에 자동으로 게임이 시작되게 만들어 두었기 때문에 빈 방에서 게임을 시작하게 되는 오류가 발생했다. 

 

  socket.on('disconnect', () => {
            console.log('사용자가 연결을 끊었습니다.');

            for (let i = 0; i <rooms.length; i++){
                const roomId = rooms[i].roomId;
                const room = io.sockets.adapter.rooms.get(roomId);
                if(!room|| room.size ===0){
                    rooms.splice(i,1);
                    i--;
                }
            }
        });

 

따라서 위와 같이, disconnect 할 때 ( 매칭 중이다 나갈 때  ) 모든 방을 검사하여 빈 방이 있다면 삭제했다. 

효율성의 문제가 있을 수 있지만, 우선 rooms 배열은 '매칭 중'인 방들만을 의미하기 때문에 대규모 게임이 아닌 이상 

수천, 수만이 될 수 없고 수백까지의 배열 순회는 큰 리소스를 잡아먹지 않기에 이렇게 간단하게 작성했다. 

 

 

기능 추가 

 

1. 암호화 기능 추가

간단하게 모듈을 사용해서 암호화 기능을 추가했다. 

 

const bcrypt = require('bcrypt');
const saltRounds = 10;

 


        const hashPassword = await bcrypt.hash(userPassword, saltRounds);

        const newUser = new users({userId: userId,nickName: nickName, userPassword: hashPassword});

 

            const isMatch = await bcrypt.compare(userPassword, user.userPassword);
 

 

 

2. 정규식 표현으로 이메일 형식만 받음 .

3. 유저 정보 수정

exports.updateUserInfo = async (req, res) => {
    try {
        var userId = req.body.userId;
        console.log(`정보 수정을 요청한 userId : ${userId}`);
        var nickname = req.body.nickname;
        var profileIndex = req.body.profileIndex;
        var coin = req.body.coin;
        var grade = req.body.grade;
        var gradeScore = req.body.gradeScore;
   
        var user = await users.findOne({ userId: userId });
        if (user) {
            if (nickname != null) {
                user.nickname = nickname;
            }
            if (profileIndex != null) {
                user.profileIndex = profileIndex;
            }
           if (coin != null){
            user.coin = coin;
           }
           if(grade != null){
            user.grade = grade;
           }
           if (gradeScore != null){
            user.gradeScore = gradeScore;
           }
            await user.save();
            res.status(200).send('사용자 정보가 성공적으로 변경되었습니다.');
        }
        else {
            res.status(404).send('없는 유저 ID입니다.');
        }

    }
    catch (err) {
        console.log('cannot change the user Information ' + err);
        res.status(500).send('사용자 정보 변경 중 오류가 발생했습니다');
    }
}

 

들어온 유저 정보만 바꿀 수 있는 함수를 제작했다. 

이렇게 가져오기 위해서는 데이터 타입에서 int를 nullable로 만들어 주어야 한다.

그렇지 않고 그냥 int type으로 하게 된다면, 기본값인 0이 할당되게 되어 원하지 않는 결과가 나온다. 

[Serializable]
public struct ReviseUserData
{
    public string userId;
    public string nickname;
    public int? coin;
    public int? grade;
    public int? gradeScore;
    // public int win;
    // public int draw;
    // public int lose;
    public int? profileIndex;
}

 

nullable 타입은 Unity에서 기본적으로 제공하는 파서로는 파싱이 힘들기 때문에

newtonsoft에서 제공하는 파서를 사용해야 한다. 

string jsonString = JsonConvert.SerializeObject(reviseUserData,
    new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

이렇게 파싱하면, Null 변수는 전송되지 않는다. 

 

 

4. 랭킹 가져오기 

 
exports.getGradeRanking = async (req,res, next) =>{
    try{
        if(!req.session.isAuthenticated){
            return res.status(401).send("로그인이 필요합니다.");
        }
         const ranking = await users.find({},{
            userId: 0,
            nickname: 1,
            grade: 1,
            gradeScore: 1,
            profileIndex: 1,
         }).sort({grade: -1, gradeScore: -1}).limit(10);
          res.status(200).json({ranking});
        }
   
    catch(err){

    }
}

exports.getWinLoseRanking = async (req, res, nex ) =>{
    try{
if(!req.session.isAuthenticated){
    return res.status(401).send("로그인이 필요합니다.");
}
const winLoseRanking = await users.aggregate([
    {
        $addFields : {
            winLoseScore: { $subtract: ["$win","$lose"]}
        }
    },
    {$sort: {winLoseScore: -1}},
    {$limit: 10},
    {
        $project: {
            userId: 0,
            nickname: 1,
            win: 1,
            lose: 1,
            draw: 1,
            profileIndex: 1
        }
    }
]);

 

테스트 용도로 이렇게 만들었다.

아직 클라이언트 쪽에서 받는 코드가 생기려면 좀 멀어서 그냥 혼자 테스트 해 볼 예정이다. 

 


테스트 성공 ~ 

조금 헤맸다. 배열 형태로 전달하는데 리스트 형태로 받는다 

 

[System.Serializable]
public class Ranking
{
    public List<RankingData> ranking;
}

[System.Serializable]
public class RankingData
{
    public string nickname;
    public int grade;
    public int gradeScore;
    public int profileIndex;
    public int win;
    public int draw;
    public int lose;
}

 

public IEnumerator GetGradeRanking(Action success, Action failure)
{
    using (UnityWebRequest www =
           new UnityWebRequest(Constants.ServerURL + "users/getGradeRanking", UnityWebRequest.kHttpVerbGET))
    {
        www.downloadHandler = new DownloadHandlerBuffer();
        yield return www.SendWebRequest();
        if (www.result == UnityWebRequest.Result.ConnectionError ||
            www.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.Log("Error: " + www.error);
            failure?.Invoke();
        }
        else
        {
            var resultString = www.downloadHandler.text;
            var gradeRanking = JsonConvert.DeserializeObject<Ranking>(resultString);
            Debug.Log("1");
        }
    }
}

 

public IEnumerator GetWinLossRanking(Action success, Action failure)
{
    using (UnityWebRequest www = new UnityWebRequest(Constants.ServerURL + "users/getWinLossRanking",
               UnityWebRequest.kHttpVerbGET))
    {
        www.downloadHandler = new DownloadHandlerBuffer();
        yield return www.SendWebRequest();
        if (www.result == UnityWebRequest.Result.ConnectionError ||
            www.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.Log("Error: " + www.error);
            failure?.Invoke();
        }
        else
        {
            var resultString = www.downloadHandler.text;
            var winLossRanking = JsonConvert.DeserializeObject<Ranking>(resultString);
            Debug.Log("1");
        }
    }
}

 

 

문제 : 같은 프레임에 두 개의 UnityWebRequest를 요청하면 하나만 실행되는 문제

 

1. 기본적으로는 코루틴으로 실행하기 때문에 여러 개의 UnityWebRequest가 동시에 수 있다.

다만, 유니티의 네트워크 요청 처리 방식과 코루틴의 실행 방식이 겹치면서 충돌이 발생할 가능성이 높다. 

 

2. UnityWebRequest는 비동기적으로 동작하지만, 특정 조건에서 실행이 중단될 수 있다. 

 

이와 같은 가능성이 있다.