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

Feat/taggame #43

Merged
merged 16 commits into from
Sep 7, 2024
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
35 changes: 35 additions & 0 deletions src/main/java/kutaverse/game/taggame/domain/TagGameUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kutaverse.game.taggame.domain;

import kutaverse.game.map.domain.Status;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

@Getter
@NoArgsConstructor
public class TagGameUser {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터 형식은 map과 동일한가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이동을 위한 클래스라 동일하게 가져갈 생각입니다. 술래 매칭에 대한 처리가 아직 들어가지 않았기 때문에 필드가 추가될 수 있습니다

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌


private String userId;

private Double positionX;

private Double positionY;

private Double positionZ;

private Double rotationPitch;

private Double rotationYaw;

private Double rotationRoll;

private Status status;

private Double velocityX;

private Double velocityY;

private Double velocityZ;
}

15 changes: 13 additions & 2 deletions src/main/java/kutaverse/game/websocket/WebsocketConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import kutaverse.game.websocket.chat.ChatWebSocketHandler;
import kutaverse.game.websocket.map.MapWebSocketHandler;
import kutaverse.game.websocket.minigame.MiniGameWebsocketHandler;
import kutaverse.game.websocket.taggame.TagGameWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
Expand All @@ -30,17 +31,27 @@ public MiniGameWebsocketHandler miniGameWebsocketHandler() {
public ChatWebSocketHandler chatWebSocketHandler(ChatService chatService) {
return new ChatWebSocketHandler(chatService);
}

@Bean
public TagGameWebSocketHandler tagGameWebSocketHandler() {
return new TagGameWebSocketHandler();
}

@Bean
public ChatService chatService(ChatRepository chatRepository) {
return new ChatService(chatRepository);
}

@Bean
public SimpleUrlHandlerMapping handlerMapping(MapWebSocketHandler mapWsh, MiniGameWebsocketHandler miniWsh, ChatWebSocketHandler chatWsh) {
public SimpleUrlHandlerMapping handlerMapping(MapWebSocketHandler mapWsh,
MiniGameWebsocketHandler miniWsh,
ChatWebSocketHandler chatWsh,
TagGameWebSocketHandler tagGameWsh) {
return new SimpleUrlHandlerMapping(Map.of(
"/map", mapWsh,
"/game", miniWsh,
"/chat", chatWsh
"/chat", chatWsh,
"/taggame", tagGameWsh
), 1);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kutaverse.game.websocket.taggame;

import com.fasterxml.jackson.databind.ObjectMapper;
import kutaverse.game.websocket.taggame.handler.CustomHandler;
import kutaverse.game.websocket.taggame.handler.CustomHandlerMapping;
import kutaverse.game.websocket.taggame.util.TagGameRequestUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class TagGameWebSocketHandler implements WebSocketHandler {

private final ObjectMapper objectMapper = new ObjectMapper();

/**
* request로 반환후 handler를 실행한다.
* @param session the session to handle
* @return
*/
@Override
public Mono<Void> handle(WebSocketSession session) {
session.receive()
.map(message -> {
String payloadAsText = message.getPayloadAsText();
return TagGameRequestUtil.fromWebsocketMessage(payloadAsText);
})
.doOnNext(tagGameRequest->{
CustomHandler customHandler = CustomHandlerMapping.getHandler(tagGameRequest.getTagGameStatus());
customHandler.handler(tagGameRequest,session);
}).subscribe();
return Mono.never();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kutaverse.game.websocket.taggame.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class TagGameMatchRequest {

private String userId;

private String text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kutaverse.game.websocket.taggame.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import org.springframework.web.reactive.socket.WebSocketSession;

import java.util.Map;

@AllArgsConstructor
@Builder
public class TagGameMatchResponse {

private String roomId;

private String taggerId;

public static TagGameMatchResponse toDto(String roomId, Map.Entry<String, WebSocketSession> tagger) {
return TagGameMatchResponse.builder()
.roomId(roomId)
.taggerId(tagger.getKey())
.build();
}

@Override
public String toString() {
return "{" +
"roomId='" + roomId + '\'' +
", taggerId='" + taggerId + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kutaverse.game.websocket.taggame.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class TagGameRequest<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public class TagGameRequest<T> {

    private TagGameStatus tagGameStatus;

    private T request;
}

지금은 TagGame으로 status와 클래스가 지정되어 있는데 이것 또한 유연하게 공통 제네릭을 만들자는 의미죠?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문을 제대로 이해했는지 모르겠는데 TagGameStatus 를 GameRequestType으로 변경시켜 로직에 대한 enum 값을 추가할 생각이였습니다, 예를 들어

  1. MAP_SAVE (맵 이동)
  2. TAG_GAME_MATCHING (술래잡기 매칭)
  3. MINI_GAME_RUNNIG(수박게임 실행)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제대로 이해하신거 같습니다. 현재 코드에서는 제네릭 이름이나 status가 미니게임으로 특정되어 있어서 물어본거였습니다!


private TagGameStatus tagGameStatus;

private T request;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kutaverse.game.websocket.taggame.dto;

public enum TagGameStatus {

//매칭 중
WAITING_FOR_PLAYERS,
//게임 중
IN_PROGRESS,
//게임 종료
FINISHED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kutaverse.game.websocket.taggame.handler;

import org.springframework.web.reactive.socket.WebSocketSession;

public interface CustomHandler {

void handler(Object o, WebSocketSession session);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kutaverse.game.websocket.taggame.handler;

import jakarta.annotation.PostConstruct;
import kutaverse.game.websocket.map.handler.WebSocketHandler;
import kutaverse.game.websocket.taggame.dto.TagGameStatus;

import java.util.HashMap;
import java.util.Map;

public class CustomHandlerMapping {

private static final Map<TagGameStatus, CustomHandler> map=new HashMap<>();


static {
map.put(TagGameStatus.WAITING_FOR_PLAYERS,new TagGameMatchingHandler());
}

public static CustomHandler getHandler(TagGameStatus tagGameStatus){
return map.get(tagGameStatus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package kutaverse.game.websocket.taggame.handler;

import kutaverse.game.websocket.taggame.dto.TagGameMatchRequest;
import kutaverse.game.websocket.taggame.dto.TagGameRequest;
import kutaverse.game.websocket.taggame.util.TagGameMatchingQueue;
import lombok.RequiredArgsConstructor;
import org.springframework.web.reactive.socket.WebSocketSession;

@RequiredArgsConstructor
public class TagGameMatchingHandler implements CustomHandler{

@Override
public void handler(Object object, WebSocketSession webSocketSession) {
TagGameRequest tagGameRequest =(TagGameRequest) object;
TagGameMatchRequest tagGameMatchRequest = (TagGameMatchRequest) tagGameRequest.getRequest();
TagGameMatchingQueue.addPlayer(tagGameMatchRequest.getUserId(),webSocketSession);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package kutaverse.game.websocket.taggame.util;

import kutaverse.game.websocket.minigame.GameRoom;
import kutaverse.game.websocket.minigame.GameRoomManager;
import kutaverse.game.websocket.minigame.MatchingQueue;
import kutaverse.game.websocket.taggame.dto.TagGameMatchResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

@Component
@Slf4j
public class TagGameMatchingQueue {
private static final ArrayDeque<Map.Entry<String, WebSocketSession>> queueing = new ArrayDeque<>();
private static final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
private static final Random random = new Random();
public static void addPlayer(String userId, WebSocketSession webSocketSession){
if(!sessionMap.containsKey(userId)) {
queueing.offer(new AbstractMap.SimpleEntry<>(userId,webSocketSession));
sessionMap.put(userId,webSocketSession);
webSocketSession.send(Mono.just(webSocketSession.textMessage("매칭 대기 중 입니다."))).subscribe();
}

}

private static Map.Entry<String, WebSocketSession> getPlayer(){
return queueing.poll();
}

public static int getQueuePlayers(){
return queueing.size();
}

private String createGameRoom(List<Map.Entry<String, WebSocketSession>> players) {
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<String, WebSocketSession> player : players) {
stringBuilder.append(player.getKey());
stringBuilder.append(" ");
}
String roomId = stringBuilder.toString();
TagGameRoom tagGameRoom = new TagGameRoom(roomId,players);
TagGameRoomManager.addGameRoom(tagGameRoom);
return roomId;
}

@Scheduled(fixedRate = 1000)
public void matchPlayers(){
while(queueing.size() >= 4){
Map.Entry<String, WebSocketSession> player1 = TagGameMatchingQueue.getPlayer();
Map.Entry<String, WebSocketSession> player2 = TagGameMatchingQueue.getPlayer();
Map.Entry<String, WebSocketSession> player3 = TagGameMatchingQueue.getPlayer();
Map.Entry<String, WebSocketSession> player4 = TagGameMatchingQueue.getPlayer();

// 세션 값 확인
if(player1.getValue().isOpen() && player2.getValue().isOpen()
&& player3.getValue().isOpen() && player4.getValue().isOpen()){
List<Map.Entry<String, WebSocketSession>> players = List.of(player1, player2, player3, player4);
String gameRoom = createGameRoom(players);
Map.Entry<String, WebSocketSession> tagger = selectTagger(players);

//플레이어들에게 gamerood 공지
for (Map.Entry<String, WebSocketSession> player : players) {
WebSocketSession webSocketSession = player.getValue();
TagGameMatchResponse tagGameMatchResponse = TagGameMatchResponse.toDto(gameRoom, tagger);

WebSocketMessage webSocketMessage = webSocketSession.textMessage(tagGameMatchResponse.toString());
webSocketSession.send(Mono.just(webSocketMessage)).subscribe();
}



}
else{
if(player1.getValue().isOpen()){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뭔가 더 간단한 처리 방법이 있을거 같은데 이건 한 번 고민해보겠습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원래 자바에서 queue를 LinkedList 구현체로 사용하는 것으로 알고 있는데, Deque를 구현하셨더라고요.
LinkedList의 경우 인덱스로 접근할 수 있어서 코드 상으로는 더 좋을 수 있는데 queue는 head가 delete되어도 나머지 요소들이 앞으로 이동하는 오버헤드가 발생안해서 저렇게 구현하는게 일단 맞다고 생각합니다.
말씀하신 것과 같이 리펙토링이 필요한 부분이라고 저도 생각합니다

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다. 그냥 코드 자체를 조금 더 쉽게 작성할 수 있을거 같은데 안떠올라서 꺼낸 이야기였습니다.✌

MatchingQueue.requeue(player1);
}
if(player2.getValue().isOpen()){
MatchingQueue.requeue(player2);
}
if(player3.getValue().isOpen()){
MatchingQueue.requeue(player3);
}
if(player4.getValue().isOpen()){
MatchingQueue.requeue(player4);
}
}

}
}

/**
* 술래를 정한다
* @param players
* @return
*/
private Map.Entry<String, WebSocketSession> selectTagger(List<Map.Entry<String, WebSocketSession>> players) {

int randomIndex = random.nextInt(players.size()); // 4는 상한값으로, 0~3 사이의 값이 생성됩니다.

return players.get(randomIndex);
}
}
Loading
Loading