본문 바로가기

항해 99/Spring

WebSocket 활용 웹 게임 구현

요구 조건

  • WebSocket 통신을 이용한 유저간 1:1 인디언 포커 게임을 실행할 수 있어야 한다
  • 게임은 1판에 총 3개의 라운드가 진행되야 한다
  • 각 라운드는 규칙에 맞게 동작할 수 있어야 한다
  • 게임 종료 후 유저의 선택에 따라 게임을 다시 시작하거나 게임방 또는 로비로 나가도록 구현되야 한다

게임 규칙

  • 숫자 1~10까지의 숫자 카드 덱 2개를 사용한다
  • 라운드 종료 시 더 높은 숫자 카드를 가진 유저가 라운드에서 승리하고 베팅된 포인트를 모두 가져간다
  • 라운드 시작 시 플레이어는 각각 카드를 1장 받는다(자신의 카드는 라운드 종료 시까지 확인할 수 없음)
  • 베팅은 다음 3가지 중 하나를 선택할 수 있음
    • CHECK
      • 선턴인 유저 : 게임에 참가한 유저 중 포인트가 더 적은 유저의 10% 값 만큼 베팅한다
      • 후턴인 유저 : 상대가 베팅한 포인트만큼 베팅한다
      • 둘 다 CHECK인 경우 : 베팅 후 라운드를 종료된다
    • RAISE
      • CHECK 이후 또는 먼저하는 경우  : 초기 베팅 값(포인트가 적은 유저의 10%)의 2배만큼 베팅한다
      • RAISE 이후 하는 경우 : RAISE 값의 2배 만큼 베팅한다
      • RAISE 이후 CHECK나 DIE 가 나오면 베팅을 종료하고 라운드가 종료된다
      • RAISE 이후 CHECK 할 때 베팅할 포인트가 부족한 경우 모든 포인트가 베팅된다
    • DIE(FOLD)
      • 라운드를 종료하고 선택한 유저는 라운드 패배 처리 된다
      • 승리한 유저는 베팅된 포인트를 모두 획득한다
  • 라운드 종료 시 두 명의 카드 값이 같을 경우 무승부 처리 또는 임의로 지정한 덱의 카드를 가진 유저가 승리하도록 처리한다
    • 무승부 : 라운드에 베팅된 포인트를 다음 라운드로 이월하고 해당 라운드에서 승리한 유저가 포인트를 전부 가져간다
    • 승리 : 1번덱 혹은 2번덱의 카드를 가진 유저가 승리처리 되고 포인트를 라운드에 걸린 포인트를 전부 가져간다
  • 라운드 종료 시 보유한 포인트가 0인 유저는 다음 라운드를 진행할 수 없으며 즉시 게임에서 패배 처리 된다
  • 3번의 라운드 진행이 끝난 후 3번의 라운드에서 더 많은 포인트를 획득한 유저가 게임에서 승리한다
  • 게임 종료 후 유저는 게임을 다시 시작할 지 로비로 나갈지 선택할 수 있다
    • 둘 다 수락한 경우 : 카드 및 베팅 정보를 초기화하고 1라운드부터 게임을 다시 시작한다
    • 수락과 거절이 나온 경우 : 수락한 유저는 게임 방으로 돌아가 다른 참가자를 기다리게 되고, 거절한 유저는 로비로 나가게 된다
    • 둘 다 거절한 경우 : 둘 다 로비로 나가게 되고 게임방과 게임은 삭제된다

API 명세

  • 게임 구동(라운드 시작, 플레이어 행동, 라운드 종료, 게임 종료) : API주소/gameRoom/{gameRoomId}/{gameState}
  • 게임 준비, 게임 종료 후 유저 선택 추가할 수 있음
  • 구현하지 않는 API 파트 생략

초기 ERD

  • 기능 구현 중 필요한 사항으로 인한 수정 필요

초기 설계

리팩토링 및 테스트 과정을 통한 로직 수정 작업 필요한 상태임

Entity / Enum Class

Game Entity

  • 게임을 위한 도메인 모델, 카드 게임의 상태와 로직을 관리
  • 게임의 진행 상황을 추적하고, 플레이어 간 상호작용 및 게임의 논리적 흐름을 관리
package com.example.socketpractice.domain.game.entity;

import com.example.socketpractice.domain.user.entity.User;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.HashSet;
import java.util.Set;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Game {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ElementCollection(fetch = FetchType.LAZY)
    @Enumerated(EnumType.STRING) // Enum 타입을 저장
    private Set<Card> usedCards = new HashSet<>();

    @ManyToOne(fetch = FetchType.LAZY)
    private User playerOne;

    @ManyToOne(fetch = FetchType.LAZY)
    private User playerTwo;

    @Enumerated(EnumType.STRING) // 카드 Enum을 저장
    private Card playerOneCard;

    @Enumerated(EnumType.STRING) // 카드 Enum을 저장
    private Card playerTwoCard;

    private int betAmount;

    private int pot; // 현재 라운드의 포트
    private int nextRoundPot; // 다음 라운드로 이월할 포트

    @ManyToOne(fetch = FetchType.LAZY)
    private User foldedUser;

    // 플레이어가 라운드에서 획득한 포인트
    private int playerOneRoundPoints;
    private int playerTwoRoundPoints;

    // Constructor and methods
    public Game(User playerOne, User playerTwo) {
        this.playerOne = playerOne;
        this.playerTwo = playerTwo;
        this.usedCards = new HashSet<>();
    }

    public void addUsedCard(Card card) {
        this.usedCards.add(card);
    }

    public void setPlayerOneCard(Card card) {
        this.playerOneCard = card;
    }

    public void setPlayerTwoCard(Card card) {
        this.playerTwoCard = card;
    }

    public void setBetAmount(int betAmount) {
        this.betAmount = betAmount;
    }
    // 게임 팟을 가져옵니다.
    public int getPot() {
        return pot;
    }

    // 게임 팟을 설정합니다.
    public void setPot(int pot) {
        this.pot = pot;
    }

    // 게임에서 포기한 유저를 설정합니다.
    public void setFoldedUser(User user) {
        this.foldedUser = user;
    }

    public void addPlayerOneRoundPoints(int points) {
        this.playerOneRoundPoints += points;
    }

    public void addPlayerTwoRoundPoints(int points) {
        this.playerTwoRoundPoints += points;
    }

    public void setNextRoundPot(int pot) {
        // 다음 라운드로 이월할 포트 금액을 설정합니다.
        this.nextRoundPot += pot; // 이월될 금액을 누적합니다.
    }

    public void resetRound() {
        // 라운드 관련 정보를 초기화하는 메서드
        // 카드를 초기화하고, 포트를 다음 라운드로 이월하며, 현재 라운드의 포트를 다음 라운드의 포트로 설정합니다.
        this.pot = this.nextRoundPot; // 다음 라운드로 이월된 포트 금액을 현재 포트로 설정합니다.
        this.nextRoundPot = 0; // 다음 라운드 포트 초기화
        // 추가로 필요한 라운드 관련 정보 초기화 로직을 여기에 구현합니다.
    }

    // 게임과 관련된 상태를 초기화하는 메서드
    public void resetGame() {
        // 사용된 카드 목록을 비우고, 각 플레이어의 라운드별 획득 포인트를 0으로 리셋합니다.
        usedCards.clear();
        playerOneRoundPoints = 0;
        playerTwoRoundPoints = 0;

        // 초기 베팅 금액과 팟을 리셋합니다.
        pot = 0;
        nextRoundPot = 0;

        // 각 플레이어의 카드를 null 또는 초기 상태로 설정할 수 있습니다.
        // 예를 들어, 플레이어의 카드 필드가 있다면 이를 초기화합니다.
        // playerOneCard = null;
        // playerTwoCard = null;

        // 기타 필요한 상태 초기화 로직
        // 예: 라운드 수, 게임의 상태, 시간 제한 등을 초기화할 수 있습니다.
    }
}

 

 

GameState

  • 게임의 다양한 상태를 나타내는 enum(열거형)
  • 게임의 라이프사이클을 추적하는 데 사용
package com.example.socketpractice.domain.game.entity;

import lombok.Getter;

@Getter
public enum GameState {
    /* 게임 상태 Enum Class
    * READY : 게임 준비
    * START : 게임 시작
    * ACTION : 유저 행동
    * BET : 배팅
    * END : 게임 종료 */
    READY("READY"),
    START("START"),
    ACTION("ACTION"),
    END("END"),
    BET("BET")
    ;

    private final String gameState;
    GameState(String gameState) {
        this.gameState = gameState;
    }
}

 

 

Card

  • 게임에서 사용되는 카드를 나타내는 enum(열거형)
  • 게임 내에서 사용되는 카드의 종류와 그 속성을 정의하고 게임 로직이 카드 간의 상호작용을 적절히 처리할 수 있도록 함
package com.example.socketpractice.domain.game.entity;

import lombok.Getter;

@Getter
public enum Card {
    DECK1_CARD1(1, 1),
    DECK1_CARD2(1, 2),
    DECK1_CARD3(1, 3),
    DECK1_CARD4(1, 4),
    DECK1_CARD5(1, 5),
    DECK1_CARD6(1, 6),
    DECK1_CARD7(1, 7),
    DECK1_CARD8(1, 8),
    DECK1_CARD9(1, 9),
    DECK1_CARD10(1, 10),
    DECK2_CARD1(2, 1),
    DECK2_CARD2(2, 2),
    DECK2_CARD3(2, 3),
    DECK2_CARD4(2, 4),
    DECK2_CARD5(2, 5),
    DECK2_CARD6(2, 6),
    DECK2_CARD7(2, 7),
    DECK2_CARD8(2, 8),
    DECK2_CARD9(2, 9),
    DECK2_CARD10(2, 10)
    ;

    private final int number;
    private final int deckNumber;

    Card(int number, int deckNumber) {
        this.number = number;
        this.deckNumber = deckNumber;
    }
}

 

 

Betting

  • 게임에서의 베팅 옵션을 나타내는 enum(열거형)
  • 게임 내에서 플레이어가 선택할 수 있는 베팅 행동을 정의
package com.example.socketpractice.domain.game.entity;

import lombok.Getter;

@Getter
public enum Betting {
    /* 배팅 상태 Enum Class
    * CHECK : 상대와 같은 판돈 걸기
    * RAISE : 판돈의 2배 걸기
    * DIE : 라운드 포기하기 */
    CHECK("CHECK"),
    RAISE("RAISE"),
    DIE("DIE")
    ;

    private final String betting;
    Betting(String betting) {
        this.betting = betting;
    }
}

 

 

UserChoice

  • 게임 플레이어가 게임 진행 후에 선택할 수 있는 옵션을 나타내는 enum(열거형)
  • 게임을 다시 시작할 건지, 게임에서 나갈지 옵션을 선택할 수 있음
package com.example.socketpractice.domain.game.entity;

public enum UserChoice {
    PLAY_AGAIN("PLAY_AGAIN"), // 게임을 다시 하기로 선택
    LEAVE("LEAVE") // 게임방에서 나가기로 선택
    ;

    private final String userChoice;
    UserChoice(String userChoice) {
        this.userChoice = userChoice;
    }
}

 

 

GameRoom

  • 카드 게임의 방을 나타내는 entity, 게임 방과 관련된 데이터 및 로직을 캡슐화함
  • 게임 방의 생성, 게임의 시작 및 종료 등의 프로세스를 관리
  • 게임의 참여자 정보와 현재 게임의 상태를 추적하며, 게임 방과 게임 세션의 관리
package com.example.socketpractice.domain.game.entity;

import com.example.socketpractice.domain.user.entity.User;
import jakarta.persistence.*;
import lombok.Getter;

import java.util.Date;

@Entity
@Getter
@Table(name = "Game_room")
public class GameRoom {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "room_id")
    private Long roomId;
    @Column(name = "create_at")
    private Date createAt;
    @Column(name = "room_name")
    private String roomName;

    /* 유저 및 게임 관련*/
    @ManyToOne(fetch = FetchType.LAZY)
    private User playerOne;
    @ManyToOne(fetch = FetchType.LAZY)
    private User playerTwo;
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Game currentGame;

    public void setRoomId(Long roomId) {
        this.roomId = roomId;
    }

    public void setCreateAt(Date createAt) {
        this.createAt = createAt;
    }

    public void setRoomName(String roomName) {
        this.roomName = roomName;
    }

    public void startNewGame(User playerOne, User playerTwo) {
        this.currentGame = new Game(playerOne, playerTwo);
    }

    public void setCurrentGame(Game game) {
        this.currentGame = game;
    }

    // 게임을 종료할 때 호출하는 메서드입니다.
    public void endCurrentGame() {
        this.currentGame = null;
    }
}

 

 

Controller

GameController

  • 인디언 포커 게임의 실행 및 종료와 관련된 요청을 처리하는 컨트롤러
package com.example.socketpractice.domain.game.controller;

import com.example.socketpractice.domain.chat.entity.ChatMessage;
import com.example.socketpractice.domain.game.service.GameRoomService;
import com.example.socketpractice.domain.game.service.GameService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Tag(name = "게임 실행 컨트롤러", description = "인디언 포커 게임 실행 및 종료 컨트롤러입니다.")
@Slf4j
@Controller
@RequestMapping("/gameRoom")
public class GameController {

    private final SimpMessageSendingOperations messagingTemplate;
    private final GameRoomService gameRoomService;
    private final GameService gameService;
    public GameController(SimpMessageSendingOperations messagingTemplate, GameRoomService gameRoomService, GameService gameService) {
        this.messagingTemplate = messagingTemplate;
        this.gameRoomService = gameRoomService;
        this.gameService = gameService;
    }

    @MessageMapping("/{gameRoomId}/{gameState}")
    public void handleGameState(@DestinationVariable Long gameRoomId, @DestinationVariable String gameState, @Payload ChatMessage chatMessage) {
        switch (gameState) {
            case "START":
                gameService.startRound(gameRoomId);
                break;
            case "ACTION":
                gameService.playerAction(gameRoomId, chatMessage.getSender(), chatMessage.getContent());
                break;
            case "END":
                gameService.endRound(gameRoomId);
                break;
        }

        /* 게임 상태 업데이트 메시지를 클라이언트에 전송 */
        String destination = "/topic/gameRoom/" + gameRoomId;
        messagingTemplate.convertAndSend(destination, chatMessage);
    }
}

 

 

Repository

GameRepository

  • Game 엔티티에 대한 데이터 액세스를 처리하는 JPA 리포지토리
package com.example.socketpractice.domain.game.repository;

import com.example.socketpractice.domain.game.entity.Game;
import org.springframework.data.jpa.repository.JpaRepository;

public interface GameRepository extends JpaRepository<Game, Long> {
}

 

 

Service

GameService

  • 게임 관련 로직을 처리하는 서비스 클래스
  • 게임의 전반적인 흐름을 제어하고, 게임 상태를 관리하는 중추적인 역할을 수행
package com.example.socketpractice.domain.game.service;

import com.example.socketpractice.domain.game.entity.Card;
import com.example.socketpractice.domain.game.entity.Game;
import com.example.socketpractice.domain.game.entity.GameRoom;
import com.example.socketpractice.domain.game.entity.UserChoice;
import com.example.socketpractice.domain.game.repository.GameRepository;
import com.example.socketpractice.domain.game.repository.GameRoomRepository;
import com.example.socketpractice.domain.user.entity.User;
import com.example.socketpractice.domain.user.repository.UserRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@Service
public class GameService {

    private final GameRepository gameRepository;
    private final GameRoomRepository gameRoomRepository;
    private final UserRepository userRepository;
    private static final EnumSet<Card> ALL_CARDS = EnumSet.allOf(Card.class);

    public GameService(GameRepository gameRepository, GameRoomRepository gameRoomRepository, UserRepository userRepository) {
        this.gameRepository = gameRepository;
        this.gameRoomRepository = gameRoomRepository;
        this.userRepository = userRepository;
    }

    /* 게임 실행 관련 로직
     * 1. 라운드 시작 로직
     * 2. 플레이어 행동 처리 로직 - 채팅, 배팅
     * 3. 라운드 종료 로직
     * 4. 게임 종료 로직 */

    public void startRound(Long gameRoomId) {
        /* gameRoomId 사용 게임 룸 정보 검증 및 게임 인스턴스 확인 및 생성*/
        GameRoom gameRoom = gameRoomRepository.findById(gameRoomId)
                .orElseThrow(() -> new EntityNotFoundException("Game room not found with ID: " + gameRoomId));

        Game game = gameRoom.getCurrentGame();

        if (game == null) {
            gameRoom.startNewGame(gameRoom.getPlayerOne(), gameRoom.getPlayerTwo());
            game = gameRoom.getCurrentGame();
            gameRoomRepository.save(gameRoom);
        }

        /* 게임 라운드 시작 로직*/
        // 이전 라운드에서 사용된 카드를 제외한 카드 목록을 생성합니다.
        Set<Card> usedCards = game.getUsedCards();
        List<Card> availableCards = new ArrayList<>(EnumSet.complementOf(EnumSet.copyOf(usedCards)));

        // 카드를 섞습니다.
        Collections.shuffle(availableCards);

        // 랜덤 카드를 플레이어에게 할당합니다.
        Card playerOneCard = availableCards.get(0);
        Card playerTwoCard = availableCards.get(1);
        game.setPlayerOneCard(playerOneCard);
        game.setPlayerTwoCard(playerTwoCard);

        // 사용된 카드를 추적합니다.
        game.addUsedCard(playerOneCard);
        game.addUsedCard(playerTwoCard);

        // 초기 베팅 금액을 계산하고 설정합니다.
        int betAmount = calculateInitialBet(game.getPlayerOne(), game.getPlayerTwo());
        game.setBetAmount(betAmount);

        // 변경사항을 데이터베이스에 저장합니다.
        gameRoomRepository.save(gameRoom);
        // 게임 상태 업데이트, 채팅 시간 관리는 클라이언트 단에서 처리
    }

    @Transactional
    public void playerAction(Long gameRoomId, String nickname, String gameState) {
        // 플레이어 행동 처리(채팅, 배팅)
        GameRoom gameRoom = gameRoomRepository.findById(gameRoomId)
                .orElseThrow(() -> new EntityNotFoundException("Game room not found with ID: " + gameRoomId));

        Game game = gameRoom.getCurrentGame();
        if (game == null) {
            throw new IllegalStateException("Game not started or already ended.");
        }

        User user = userRepository.findByNickname(nickname)
                .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + nickname));

        switch (gameState.toUpperCase()) {
            case "CHECK":
                performCheckAction(game, user);
                break;
            case "RAISE":
                performRaiseAction(game, user);
                break;
            case "DIE":
                performDieAction(game, user);
                break;
            default:
                throw new IllegalArgumentException("Unknown action: " + gameState);
        }

        gameRoomRepository.save(gameRoom);
    }

    @Transactional
    public void endRound(Long gameRoomId) {
        GameRoom gameRoom = gameRoomRepository.findById(gameRoomId)
                .orElseThrow(() -> new EntityNotFoundException("Game room not found with ID: " + gameRoomId));

        Game game = gameRoom.getCurrentGame();
        if (game == null) {
            throw new IllegalStateException("No game is currently active in this room.");
        }

        User playerOne = game.getPlayerOne();
        User playerTwo = game.getPlayerTwo();
        Card playerOneCard = game.getPlayerOneCard();
        Card playerTwoCard = game.getPlayerTwoCard();

        int pot = game.getPot(); // 이번 라운드의 팟
        User roundWinner = determineWinner(playerOne, playerOneCard, playerTwo, playerTwoCard);

        if (roundWinner != null) {
            if (roundWinner.equals(playerOne)) {
                game.addPlayerOneRoundPoints(pot);
            } else {
                game.addPlayerTwoRoundPoints(pot);
            }
        } else {
            // 무승부인 경우, 다음 라운드로 팟을 이월합니다.
            game.setNextRoundPot(pot);
        }

        // 라운드 정보를 초기화하는 로직을 추가합니다. 예를 들어, 카드 초기화, 팟 초기화 등
        game.resetRound();

        gameRoomRepository.save(gameRoom);
    }


    @Transactional
    public void endGame(Long gameRoomId) {
        GameRoom gameRoom = gameRoomRepository.findById(gameRoomId)
                .orElseThrow(() -> new EntityNotFoundException("Game room not found with ID: " + gameRoomId));

        Game game = gameRoom.getCurrentGame();
        if (game == null) {
            throw new IllegalStateException("No game is currently active in this room.");
        }

        // 게임의 결과를 가져오고, 게임 관련 데이터를 초기화합니다.
        processGameResults(gameRoom);

        // 사용자의 선택을 받아 상태를 결정합니다.
        processUserChoices(gameRoom);
    }

    private int calculateInitialBet(User playerOne, User playerTwo) {
        int playerOnePoints = playerOne.getPoints();
        int playerTwoPoints = playerTwo.getPoints();
        int lowerPoints = Math.min(playerOnePoints, playerTwoPoints);
        return lowerPoints / 10; // 10%의 포인트를 초기 베팅 금액으로 설정
    }

    private void performCheckAction(Game game, User user) {
        // 첫 번째 플레이어가 '체크'할 경우 현재 베팅 금액에 변화를 주지 않습니다.
        boolean isFirstPlayer = game.getPlayerOne().equals(user);
        if (!isFirstPlayer) {
            // 두 번째 플레이어는 현재 베팅 금액을 팟에 추가합니다.
            int userPoints = user.getPoints();
            int currentBet = game.getBetAmount();
            if (userPoints >= currentBet) {
                user.setPoints(userPoints - currentBet); // 유저의 포인트를 감소시킵니다.
                game.setPot(game.getPot() + currentBet); // 팟을 증가시킵니다.
            } else {
                // 유저의 포인트가 현재 베팅 금액보다 적다면 예외를 발생시킵니다.
                throw new IllegalStateException("User does not have enough points to check.");
            }
        }
    }

    private void performRaiseAction(Game game, User user) {
        int raiseAmount = game.getBetAmount() * 2;
        int userPoints = user.getPoints();

        // The user can only raise if they have enough points.
        if (userPoints >= raiseAmount) {
            game.setBetAmount(raiseAmount);
            user.setPoints(userPoints - raiseAmount);
        } else {
            throw new IllegalStateException("User does not have enough points to raise.");
        }
    }

    private void performDieAction(Game game, User user) {
        // The user forfeits the round and possibly the game.
        game.setFoldedUser(user);
    }

    private static User getWinner(Game game) {
        User playerOne = game.getPlayerOne();
        User playerTwo = game.getPlayerTwo();

        int playerOneTotalPoints = game.getPlayerOneRoundPoints();
        int playerTwoTotalPoints = game.getPlayerTwoRoundPoints();

        User gameWinner;
        if (playerOneTotalPoints > playerTwoTotalPoints) {
            gameWinner = playerOne;
            playerOne.incrementWins();
            playerTwo.incrementLosses();
        } else if (playerTwoTotalPoints > playerOneTotalPoints) {
            gameWinner = playerTwo;
            playerTwo.incrementWins();
            playerOne.incrementLosses();
        } else {
            // 무승부 처리
            gameWinner = null;
        }
        return gameWinner;
    }

    private User determineWinner(User playerOne, Card playerOneCard, User playerTwo, Card playerTwoCard) {
        // 두 플레이어의 카드를 비교하여 승자를 결정하는 로직
        // 예시 로직을 사용하여 승자를 결정합니다. 실제 게임 룰에 맞게 수정할 필요가 있습니다.
        if (playerOneCard.getNumber() > playerTwoCard.getNumber()) {
            return playerOne;
        } else if (playerOneCard.getNumber() < playerTwoCard.getNumber()) {
            return playerTwo;
        } else {
            // 무승부인 경우
            return null;
        }

    }

    private void processGameResults(GameRoom gameRoom) {
        Game game = gameRoom.getCurrentGame();

        User playerOne = game.getPlayerOne();
        User playerTwo = game.getPlayerTwo();

        int playerOneTotalPoints = game.getPlayerOneRoundPoints();
        int playerTwoTotalPoints = game.getPlayerTwoRoundPoints();

        // 승자 결정
        User gameWinner = getWinner(game);

        // 게임 데이터 초기화
        game.resetGame();

        // 게임 룸 업데이트
        // 이 부분은 gameWinner의 값에 따라 게임 룸의 상태를 업데이트할 수도 있습니다.
        // 예를 들어, 게임의 결과를 기록하거나 특정 게임 룸 설정을 변경할 수 있습니다.

        // 승자가 결정된 경우 추가 처리
        if (gameWinner != null) {
            // 승자의 승리 횟수를 데이터베이스에 반영합니다.
            userRepository.save(gameWinner);
            // 게임 룸의 상태를 '대기 중'이나 '게임 종료' 등 적절한 상태로 업데이트할 수 있습니다.
        }

        // 무승부인 경우의 처리 로직도 여기에 포함될 수 있습니다.
        // 예를 들어, 게임 룸을 계속 활성 상태로 두거나, 무승부를 사용자에게 알릴 수 있습니다.
    }

    public void processUserChoices(GameRoom gameRoom) {
        // 이 예제에서는 사용자의 선택을 특정 저장소나 상태에서 가져오는 것으로 가정합니다.
        // 실제로는 사용자의 선택을 관리하는 별도의 로직이 필요합니다.

        // 사용자의 선택에 따라 게임방의 상태를 결정하고 조치를 취합니다.
        UserChoice playerOneChoice = getUserChoice(gameRoom.getPlayerOne());
        UserChoice playerTwoChoice = getUserChoice(gameRoom.getPlayerTwo());

        if (playerOneChoice == UserChoice.PLAY_AGAIN && playerTwoChoice == UserChoice.PLAY_AGAIN) {
            // 둘 다 다시 하기를 선택한 경우, 게임방을 재설정하고 새 게임을 시작합니다.
            gameRoom.startNewGame(gameRoom.getPlayerOne(), gameRoom.getPlayerTwo());
        } else if (playerOneChoice == UserChoice.LEAVE && playerTwoChoice == UserChoice.LEAVE) {
            // 둘 다 나가기를 선택한 경우, 게임방을 종료하고 삭제합니다.
            gameRoomRepository.delete(gameRoom);
        } else {
            // 한 명만 게임을 계속하려는 경우
            if (playerOneChoice == UserChoice.PLAY_AGAIN) {
                // Player One는 게임방으로 이동, Player Two는 로비로 이동
            } else {
                // Player Two는 게임방으로 이동, Player One은 로비로 이동
            }
            // 여기에서는 해당 플레이어를 로비로 보내는 로직을 구현해야 합니다.
        }
    }

    private UserChoice getUserChoice(User player) {
        // 이 메서드는 사용자의 선택을 반환합니다.
        // 예제에서는 선택을 바로 반환하고 있지만, 실제로는 사용자가 선택한 내용을 어딘가에서 가져와야 합니다.
        // 예: 데이터베이스, 세션, 캐시 등
        return UserChoice.PLAY_AGAIN; // 예시로 항상 'PLAY_AGAIN'을 반환하고 있습니다.
    }

}

 

 

초기 기능 구현 후 반성점

  • 게임 및 라운드 결과 반환을 위한 DTO 설계를 하지 않았음
  • 초기 ERD 설계에서 테이블 수정 사항이 많이 발생했음(WebSocket을 사용한 웹 게임 구현이 처음이어서 설계 시 문제가 많았음)
  • 초기 구현 로직에 수정이 필요한 부분이 많음(테스트 및 리팩토링 과정이 필요함)

수정 계획

  • 구현한 코드들에 대한 리팩토링

 

'항해 99 > Spring' 카테고리의 다른 글

Override, Overload / JPA 더티체킹 / JVM  (0) 2024.04.04
Call by Reference  (0) 2024.04.04
WebSocket - 실제 코드 분석  (1) 2024.04.03
WebSocket - STOMP 2  (0) 2024.03.30
WebSocket - STOMP 1  (1) 2024.03.29