Skip to content

Commit

Permalink
Merge pull request #79 from codesquad-members-2023/feat/#78-gameover-…
Browse files Browse the repository at this point in the history
…exception

[BE] 게임 종료, 예외처리 구현
  • Loading branch information
yhpark95 authored Nov 7, 2023
2 parents f8b8e9d + 0d65a2c commit d0696c9
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 24 deletions.
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();
}
}

0 comments on commit d0696c9

Please sign in to comment.