풋살 시뮬레이터(축구 게임, 랭킹 조회)

김민재·2024년 9월 23일
0
post-custom-banner

축구 게임 기능

router.post("/soccer", authSigninMiddleware, async (req, res, next) => {
  const { accountId } = req.account;

  // 내 계정 이외의 계정과 매칭
  // 현재 로그인한 계정을 제외한 모든 계정 조회

  // 매칭 테이블에 실어버리기
  const isMatched = await prisma.matching.findFirst({
    where: {
      accountId: accountId,
    },
  });

  if (isMatched) {
    return res.status(400).json({ message: `이미 매칭을 돌리고 있습니다.` });
  }

  const myMatching = await prisma.matching.create({
    data: {
      accountId: accountId,
    },
  });

  const myRanking = await prisma.gameRankings.findFirst({
    where: {
      accountId: myMatching.accountId,
    },
  });

  let findCheck = false;

  try {
    const interval = setInterval(async () => {
      const matchingList = await prisma.matching.findMany({
        where: {
          NOT: {
            accountId: myMatching.accountId,
          },
        },
      });

      console.log(matchingList);

      if (!matchingList) {
        console.log("매칭을 찾는 중입니다.");
        setInterval(() => interval, 1000);
      }

      // 매칭 상대 하나 정하기
      const randomMatching = Math.floor(Math.random() * matchingList.length);

      const vsMatching = await prisma.matching.findFirst({
        where: {
          accountId: matchingList[randomMatching].accountId,
        },
      });

      const vsRanking = await prisma.gameRankings.findFirst({
        where: {
          accountId: matchingList[randomMatching].accountId,
          rankScore: {
            gte: myRanking.rankScore - 500,
            lte: myRanking.rankScore + 500,
          },
        },
        orderBy: {
          rankScore: "desc",
        },
      });

      if (!vsRanking) {
        console.log("상대를 찾는 중입니다.");
        setInterval(() => interval, 1000);
      } else {
        console.log("상대를 찾았습니다!");
        await gameStart(
          accountId,
          vsRanking,
          myRanking,
          myMatching,
          vsMatching,
        );
        findCheck = true;
        clearInterval(interval);
      }
    }, 1000);
  } catch (err) {
    next(err);
  }

  return res.status(200).json({ message: "축구 게임 끝" });
});

async function gameStart(
  accountId,
  vsRanking,
  myRanking,
  myMatching,
  vsMatching,
) {
  // 매칭 상대와 게임하기
  // 매칭 상대의 팀 정보와 내 팀 정보를 불러와서 비교하기
  const myTeam = await prisma.squads.findMany({
    where: {
      accountId: accountId,
    },
  });

  const vsTeam = await prisma.squads.findMany({
    where: {
      accountId: vsRanking.accountId,
    },
  });

  // console.log(vsTeam[0].accountId);
  // console.log(myTeam[0].accountId);
  if (!myTeam || !vsTeam) {
    return res.status(400).json({ errorMessage: `구성된 팀이 없어요.` });
  }

  // 나의 팀 전투력 측정
  let myTeamPower = 0;
  for (let key in myTeam) {
    const { playerId } = myTeam[key];

    const player = await prisma.players.findFirst({
      where: { playerId: playerId },
    });

    const roster = await prisma.rosters.findFirst({
      where: {
        playerId: playerId,
        accountId: myTeam[0].accountId,
      },
    });

    // 밑에 0.3 , 0.5는 예시
    // 플레이어 레벨당 1개의 스텟이 1씩 오른다.
    myTeamPower +=
      player.speed * 0.3 +
      player.acceleration * 0.3 +
      player.shootingFinish * 0.5 +
      player.shootingPower * 0.3 +
      player.pass * 0.3;
  }

  // 상대 팀 전투력 측정
  let vsTeamPower = 0;
  for (let key in vsTeam) {
    const { playerId } = vsTeam[key];

    const player = await prisma.players.findFirst({
      where: { playerId: playerId },
    });

    const roster = await prisma.rosters.findFirst({
      where: {
        playerId: playerId,
        accountId: vsTeam[0].accountId,
      },
    });

    // 밑에 0.3 , 0.5는 예시
    vsTeamPower +=
      player.speed * 0.3 +
      player.acceleration * 0.3 +
      player.shootingFinish * 0.5 +
      player.shootingPower * 0.3 +
      player.pass * 0.3;
  }

  let myResult = "";
  let vsResult = "";
  let comparePower = (myTeamPower + vsTeamPower) * Math.random();
  if (comparePower < myTeamPower) {
    // 나의 팀이 승리
    myResult = "win";
    vsResult = "lose";
  } else if (comparePower === myTeamPower) {
    // 동점
    myResult, (vsResult = "draw");
  } else {
    // 상대 팀이 승리
    myResult = "lose";
    vsResult = "win";
  }

  await prisma.$transaction(async (tx) => {
    await tx.gameRankings.update({
      data: {
        playRecords:
          myRanking.playRecords !== null
            ? myRanking.playRecords + " " + [vsTeam[0].accountId, myResult]
            : [vsTeam[0].accountId, myResult],
        rankScore:
          myResult === "win"
            ? myRanking.rankScore + 50
            : myResult === "lose"
              ? myRanking.rankScore - 50
              : myRanking.rankScore,
      },
      where: {
        rankingId: myRanking.rankingId,
      },
    });

    await tx.gameRankings.update({
      data: {
        playRecords:
          vsRanking.playRecords !== null
            ? vsRanking.playRecords + " " + [myTeam[0].accountId, vsResult]
            : [myTeam[0].accountId, vsResult],
        rankScore:
          vsResult === "win"
            ? vsRanking.rankScore + 50
            : vsResult === "lose"
              ? vsRanking.rankScore - 50
              : vsRanking.rankScore,
      },
      where: {
        rankingId: vsRanking.rankingId,
      },
    });

    await tx.matching.delete({
      where: {
        matchingId: myMatching.matchingId,
      },
    });

    await tx.matching.delete({
      where: {
        matchingId: vsMatching.matchingId,
      },
    });
  });

코드가 정말 길어서 설명하기 힘들지만...해야겠지?
먼저 자신의 계정 ID로 매칭을 돌리고 있는지 확인한다. (매칭 테이블에 존재 하는지 여부)
그리고 매칭을 돌리고 있지 않다면 인터벌을 통해서 매칭 상대를 탐색하게 되는데 매칭 상대는 자신보다 점수가 500점 낮거나 500점 높은 상대를 계속 찾게 된다.
매칭 상대를 찾았다면 인터벌을 빠져나가 다음 코드를 실행한다.
이때 자신과 상대 팀 전투력를 합쳐서 그 점수에 0 ~ 1.0 (0% ~ 100%)로 값을 나누어 해당 값이 자신의 계정이 가진 팀 전투력과 비교하여 결정하는데 이 때 문자열을 저장해서 다음에 오는 트랜잭션으로 한번에 처리한다.

트랜잭션에서 내부에 먼저 경기 기록이 있다면 해당 기록을 가져와 추가로 기록하여 나중에 랭킹 조회에서 이를 처리할 생각이다.
그리고 rankScore를 해당하는 값마다 더하거나 빼는 방식으로 처리한다.

그리고 매칭이 끝나서 경기가 끝났다면 해당 값들을 빼주는 방식이다.

랭킹 조회

// 전체 랭크 조회 (상위 랭크 조회도 가능)
router.get("/rank", async (req, res, next) => {
  const rank = await prisma.gameRankings.findMany({
    orderBy: { rankScore: "desc" },
  });

  if (!rank) {
    return res
      .status(400)
      .json({ errorMessage: `현재 등록된 랭킹 정보가 없습니다.` });
  }

  // 승률을 업데이트를 해줘야만 한다.
  let winCount = 0;
  for (let key in rank) {
    winCount = 0;
    let { playRecords } = rank[key];
    playRecords = playRecords.split(" ");

    if (playRecords) {
      for (let i = 0; i < playRecords.length; i++) {
        const [vs, result] = playRecords[i].split(",");
        //console.log(result);
        if (result === "win") {
          winCount++;
        }
      }
    }

    await prisma.gameRankings.update({
      data: {
        winningRate: (winCount / playRecords.length) * 100,
      },
      where: {
        rankingId: rank[key].rankingId,
      },
    });
  }

  const newRank = await prisma.gameRankings.findMany({
    select: {
      rankingId: true,
      accountId: true,
      winningRate: true,
      rankScore: true,
      playRecords: true,
    },
    orderBy: { rankScore: "desc" },
  });
  // 정렬 되어 있는 rank에서 key 값을 뽑음 => 순서대로 랭킹 정렬
  let saveRankings = [];

  setTimeout(() => {
    for (let key in newRank) {
      const ranking = +key;
      const rankInfo = newRank[key];
      saveRankings.push([ranking + 1, rankInfo]);
    }

    return res.status(200).json({ data: saveRankings });
  }, 1000);
});

전에 썼던 랭크 조회와는 다르게 추가된 기능이 있는데
바로 승률 갱신 기능이다. 즉, 현재 우리가 승률을 확인할 수 있는 것은 랭킹 뿐인데 랭킹 조회에서 조회되기전에 미리 승률을 갱신하여 승률에 위화감이 없도록 만들어 보았다.

profile
ㅇㅇ
post-custom-banner

0개의 댓글