Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 게임 종료, 예외처리 구현 #79

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package codesquad.gaemimarble.exception;

import lombok.Getter;

@Getter
public class CustomException extends RuntimeException {
private final String playerId;
private final Long gameId;

public CustomException(String message, String playerId, Long gameId) {
super(message);
this.playerId = playerId;
this.gameId = gameId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import com.fasterxml.jackson.databind.ObjectMapper;

import codesquad.gaemimarble.exception.CustomException;
import codesquad.gaemimarble.game.controller.SocketDataSender;
import codesquad.gaemimarble.game.dto.request.GameMessage;
import codesquad.gaemimarble.game.controller.GameController;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +20,7 @@
public class WebSocketHandler extends TextWebSocketHandler {
private final ObjectMapper objectMapper;
private final GameController gameController;
private final SocketDataSender socketDataSender;

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Expand All @@ -37,7 +40,11 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message)
payload, expectedClass);
log.info("payload:{}", payload);
log.info("className:{}", mappedRequest.getClass().cast(mappedRequest));
gameController.handleRequest(mappedRequest);
try {
gameController.handleRequest(mappedRequest);
} catch (CustomException ex) {
socketDataSender.sendErrorMessage(ex.getGameId(), ex.getPlayerId(), ex.getMessage());
}
}

private Long extractGameIdFromUri(String uri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ private void sendEventResult(GameEventResultRequest gameEventResultRequest) {
gameEventNameResponse));
socketDataSender.send(gameEventResultRequest.getGameId(), new ResponseDTO<>(TypeConstants.STATUS_BOARD,
gameService.proceedEvent(gameEventNameResponse.getName(), gameEventResultRequest.getGameId())));
if (gameService.checkGameOver(gameEventResultRequest.getGameId())) {
socketDataSender.send(
gameEventResultRequest.getGameId(), new ResponseDTO<>(TypeConstants.GAME_OVER,
gameService.createUserRanking(gameEventResultRequest.getGameId())));
socketDataSender.close(gameEventResultRequest.getGameId());
}
}

@PostMapping("/api/games")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package codesquad.gaemimarble.game.controller;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import codesquad.gaemimarble.game.dto.ResponseDTO;
Expand All @@ -23,32 +20,31 @@
@RequiredArgsConstructor
@Slf4j
public class SocketDataSender {
private final ConcurrentMap<Long, Set<WebSocketSession>> gameSocketMap = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, ConcurrentMap<String, WebSocketSession>> gameSocketMap = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper;

public void createRoom(Long gameRoomId) {
gameSocketMap.put(gameRoomId, new HashSet<>());
gameSocketMap.put(gameRoomId, new ConcurrentHashMap<>());
}

public boolean saveSocket(Long gameId, String playerId, WebSocketSession session) {
Set<WebSocketSession> sessions = gameSocketMap.computeIfAbsent(gameId, key -> ConcurrentHashMap.newKeySet());
ConcurrentMap<String, WebSocketSession> socketMap = gameSocketMap.get(gameId);
try {
if (sessions.size() == 4) {
if (socketMap.values().size() == 4) {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(
new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse("full", "인원이 가득 찼습니다.")))));
new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse("인원이 가득 찼습니다.")))));
session.close();
return false;
}

boolean isDuplicate = sessions.stream()
.anyMatch(s -> s.getAttributes().get("playerId").equals(playerId));
boolean isDuplicate = socketMap.containsKey(playerId);

if (!isDuplicate) {
session.getAttributes().put("playerId", playerId);
sessions.add(session);
socketMap.put(playerId, session);
return true;
} else {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(
new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse("duplicate", "이미 접속한 플레이어입니다.")))));
new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse("이미 접속한 플레이어입니다.")))));
return false;
}
} catch (IOException e) {
Expand All @@ -58,7 +54,7 @@ public boolean saveSocket(Long gameId, String playerId, WebSocketSession session
}

public <T> void send(Long gameId, T object) {
for (WebSocketSession session : gameSocketMap.get(gameId)) {
for (WebSocketSession session : gameSocketMap.get(gameId).values()) {
try {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(object)));
} catch (IOException e) {
Expand All @@ -67,4 +63,28 @@ public <T> void send(Long gameId, T object) {
}
System.out.println("전송 완료");
}

public void sendErrorMessage(Long gameId, String playerId, String message) {
try {
if (playerId == null) {
send(gameId, new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse(message)));
return;
}
gameSocketMap.get(gameId).get(playerId).sendMessage(new TextMessage(objectMapper.writeValueAsString(
new ResponseDTO<>(TypeConstants.ERROR, new SocketErrorResponse(message)))));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}

public void close(Long gameId) {
for (WebSocketSession session : gameSocketMap.get(gameId).values()) {
try {
session.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
gameSocketMap.remove(gameId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@

@Getter
public class SocketErrorResponse {
private String errorType; // 나중에 에러 코드로 변경
private String message;

@Builder
public SocketErrorResponse(String errorType, String message) {
this.errorType = errorType;
public SocketErrorResponse(String message) {
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package codesquad.gaemimarble.game.dto.response;

import lombok.Builder;
import lombok.Getter;

@Getter
public class PlayerAsset {
private final String playerId;
private final Integer totalAsset;

@Builder
private PlayerAsset(String playerId, Integer totalAsset) {
this.playerId = playerId;
this.totalAsset = totalAsset;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package codesquad.gaemimarble.game.dto.response;

import java.util.List;

import lombok.Builder;
import lombok.Getter;

@Getter
public class UserRankingResponse {
List<PlayerAsset> ranking;

@Builder
private UserRankingResponse(List<PlayerAsset> ranking) {
this.ranking = ranking;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ public Player getPlayer(String playerId) {
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("해당하는 플레이어가 없습니다."));
}

public void incrementRoundCount() {
roundCount++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public final class TypeConstants {
public static final String GOLD_CARD = "goldCard";
public static final String ERROR = "error";
public static final String ROB = "rob";
public static final String GAME_OVER = "gameOver";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package codesquad.gaemimarble.game.service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -9,6 +10,7 @@

import org.springframework.stereotype.Service;

import codesquad.gaemimarble.exception.CustomException;
import codesquad.gaemimarble.game.dto.GameMapper;
import codesquad.gaemimarble.game.dto.request.GameEndTurnRequest;
import codesquad.gaemimarble.game.dto.request.GameEventResultRequest;
Expand All @@ -32,6 +34,8 @@
import codesquad.gaemimarble.game.dto.response.GamePrisonDiceResponse;
import codesquad.gaemimarble.game.dto.response.GameReadyResponse;
import codesquad.gaemimarble.game.dto.response.GameRoomCreateResponse;
import codesquad.gaemimarble.game.dto.response.PlayerAsset;
import codesquad.gaemimarble.game.dto.response.UserRankingResponse;
import codesquad.gaemimarble.game.dto.response.generalStatusBoard.GameStatusBoardResponse;
import codesquad.gaemimarble.game.dto.response.userStatusBoard.GameUserBoardResponse;
import codesquad.gaemimarble.game.entity.Board;
Expand Down Expand Up @@ -201,7 +205,7 @@ public GameStatusBoardResponse proceedEvent(String eventName, Long gameId) {
}
}
if (eventToProceed == null) {
throw new RuntimeException("이벤트 이름이 맞지 않습니다");
throw new CustomException("이벤트 이름이 맞지 않습니다", null, gameId);
}
GameStatus gameStatus = gameRepository.getGameStatus(gameId);
Map<Theme, Integer> impactMap = eventToProceed.getImpact();
Expand All @@ -215,6 +219,7 @@ public GameStatusBoardResponse proceedEvent(String eventName, Long gameId) {
}
}
updatePlayersAsset(gameStatus.getPlayers(), stockList);
gameStatus.incrementRoundCount();

return createGameStatusBoardResponse(gameId);
}
Expand Down Expand Up @@ -245,10 +250,12 @@ public GameUserBoardResponse buyStock(GameStockBuyRequest gameStockBuyRequest) {
.stream()
.filter(s -> s.getName().equals(gameStockBuyRequest.getStockName()))
.findFirst()
.orElseThrow(() -> new RuntimeException("존재하지 않는 주식이름입니다"));
.orElseThrow(() -> new CustomException("존재하지 않는 주식이름입니다", gameStockBuyRequest.getPlayerId(),
gameStockBuyRequest.getGameId()));
if (stock.getRemainingStock() < gameStockBuyRequest.getQuantity()
| player.getCashAsset() < stock.getCurrentPrice() * gameStockBuyRequest.getQuantity()) {
throw new RuntimeException("구매할 수량이 부족하거나, 플레이어 보유 캐쉬가 부족합니다");
throw new CustomException("구매할 수량이 부족하거나, 플레이어 보유 캐쉬가 부족합니다", gameStockBuyRequest.getPlayerId(),
gameStockBuyRequest.getGameId());
}
player.buy(stock, gameStockBuyRequest.getQuantity());
stock.decrementQuantity(gameStockBuyRequest.getQuantity());
Expand Down Expand Up @@ -281,7 +288,8 @@ public GameUserBoardResponse sellStock(GameSellStockRequest gameSellStockRequest
}
for (String stockName : sellingStockInfoMap.keySet()) {
if (player.getMyStocks().get(stockName) < sellingStockInfoMap.get(stockName)) {
throw new RuntimeException("플레이어가 보유한 주식보다 더 많이 팔수는 없습니다");
throw new CustomException("플레이어가 보유한 주식보다 더 많이 팔수는 없습니다", gameSellStockRequest.getPlayerId(),
gameSellStockRequest.getGameId());
}
}

Expand Down Expand Up @@ -323,13 +331,15 @@ public GameEndTurnResponse endTurn(GameEndTurnRequest gameEndTurnRequest) {
currentPlayerInfo.update(player);
}
}

return GameEndTurnResponse.builder().nextPlayerId(null).build();
}

public void teleport(GameTeleportRequest gameTeleportRequest) {
Player player = gameRepository.getPlayer(gameTeleportRequest.getGameId(), gameTeleportRequest.getPlayerId());
if (gameTeleportRequest.getLocation().equals(player.getLocation()) && player.getLocation() == 18) {
throw new RuntimeException("순간이동 칸으로 이동 할 수 없습니다");
throw new CustomException("순간이동 칸으로 이동 할 수 없습니다", gameTeleportRequest.getPlayerId(),
gameTeleportRequest.getGameId());
}
player.setLocation(
gameTeleportRequest.getLocation() > player.getLocation() ? gameTeleportRequest.getLocation() :
Expand All @@ -341,7 +351,7 @@ public GameStatusBoardResponse increaseCompanyStock(Long gameId, Integer locatio
String shareName = gameStatus.getBoard().getBoard().get(location);
Stock stock = gameStatus.getStocks().stream()
.filter(s -> s.getName().equals(shareName)).findFirst()
.orElseThrow(() -> new RuntimeException("존재하지 않는 주식입니다."));
.orElseThrow(() -> new CustomException("존재하지 않는 주식입니다.", null, gameId));
if (stock.getWasBought()) {
stock.changePrice(10);
}
Expand Down Expand Up @@ -408,4 +418,17 @@ public List<Player> rob(GameRobRequest gameRobRequest) {
target.addCashAsset(-10_000_000);
return List.of(taker, target);
}

public boolean checkGameOver(Long gameId) {
GameStatus gameStatus = gameRepository.getGameStatus(gameId);
return gameStatus.getRoundCount() > 15;
}

public UserRankingResponse createUserRanking(Long gameId) {
return UserRankingResponse.builder().ranking(gameRepository.getAllPlayer(gameId)
.stream()
.sorted(Comparator.comparing(Player::getTotalAsset).reversed())
.map(p -> PlayerAsset.builder().playerId(p.getPlayerId()).totalAsset(p.getTotalAsset()).build())
.collect(Collectors.toList())).build();
}
}