1. Web / Websocket : 웹소켓 통신 관련 라이브러리
2. Mariadb : mariaDB 데이터 베이스 이용 관련 라이브러리
3. jpa / lombok : 쉬운 DB 이용을 위한 라이브러리
MainController 클래스 :
-DB 확인 및 서버 통제를 위한 개발자 웹
-서버 상태 및 DB 확인 용도의 간단한 엔드포인트들로 구성
@RequestMapping("/")
@ResponseBody
public String home()
{
return "Home-Page";
}
-기본 엔드포인트와 연결
-서버 작동 확인용 기본 페이지
-서버 작동 시 "Home-Page"가 작성된 웹 페이지 출력
@RequestMapping("/showQuiz")
@ResponseBody
public String showQuiz(){
String result = null;
result = quizRepository.findAll().toString(); //Read
result = playerRepository.findAll().toString(); //Read
return result.toString();
}
-DB에 존재하는 문제 리스트 출력
@RequestMapping("/showPlayer")
@ResponseBody
public String showPlayer(){
String result = null;
result = playerRepository.findAll().toString(); //Read
return result.toString();
}
-DB에 존재하는 플레이어 목록 출력
@RequestMapping("/room")
@ResponseBody
public String room()
{
StringBuilder result = new StringBuilder();
result.append("[ current connected player ]")
.append("<br>");
for(WebSocketSession player : myWebSocketHandler.playerSessionList)
{
result.append(player.getId())
.append("<br>");
}
result.append("[ current battle room ]")
.append("<br>");
for(int i = 0; i<myWebSocketHandler.PlayRoomList.size(); i++)
{
result.append(i)
.append(". room : ")
.append("player - ")
.append(myWebSocketHandler.PlayRoomList.get(i).players.getFirst().getId())
.append(" / ")
.append(myWebSocketHandler.PlayRoomList.get(i).players.get(1).getId())
.append("<br>");
}
return result.toString();
}
-WebSocketHandler에 접근하여 현재 접속 중인 플레이어 세션 정보 및 현재 개설된 방에 대한 정보 확인
WebSocketConfiguration : spring 프레임워크에서 작동하는 웹소켓 서버를 생성 및 설정
WebSocketHandler : 웹소켓 서버 생명주기에 따라 함수 호출
Room : 접속한 플레이어 세션 관리 및 퀴즈 대전 관련 메커니즘 구현
주요 기능 - 웹소켓 서버 생성
1. 웹소켓 핸들러를 저장할 변수 생성
private final MyWebSocketHandler myWebSocketHandler;
-생성 및 등록 후 건드릴 수 없도록 final 변수로 객체 변수 작성
2. 웹소켓 핸들러를 생성하여 할당
public WebSocketConfig(MyWebSocketHandler myWebSocketHandler)
{
this.myWebSocketHandler = myWebSocketHandler;
}
-생성자에서 웹소켓 핸들러 객체 변수에 할당
3. 생성한 웹소켓 핸들러 등록
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry)
{
registry.addHandler(myWebSocketHandler, "/chat")
.setAllowedOrigins("*");
}
-서버의 "/chat" 엔드포인트로 웹소켓 핸들러를 등록
-CORS 허용 설정, 모든 접속을 허용 (모든 프로토콜/호스트/포트 허용)
주요 기능 - 웹소켓 라이프 사이클 함수 관리
0. 주요 멤버 변수
// 매칭
List<WebSocketSession> playerSessionList = new ArrayList<>();
List<Room> EmptyRoomList = new ArrayList<>(), PlayRoomList = new ArrayList<>();
-playerSessionList : 현재 서버에 접속한 플레이어 세션 목록
-EmptyRoomList : 플레이어 한 명만 접속한, 대기중인 방 목록
-PlayRoomList : 두 명의 플레이어가 참여한 게임이 진행중인 방 목록록
1. afterConnectionEstablished(WebSocketSession session) : 플레이어가 웹소켓 서버에 접속하면 호출, 방 생성 및 매칭 기능
playerSessionList.add(session);
-접속한 플레이어 목록에 추가
if(EmptyRoomList.isEmpty()) EmptyRoomList.add(new Room());
-대기중인 방이 없다면, 대기방 하나 추가
EmptyRoomList.getFirst().players.add(session);
-대기중인 방의 가장 첫 번째 방에 플레이어 추가
if(EmptyRoomList.getFirst().Check())
{
// 예시로 QuizRepository를 통해 count 개의 Quiz 데이터를 가져온다고 가정
List<Quiz> quizList = quizRepository.findRandomQuizzes(5); // 예시 메서드
EmptyRoomList.getFirst().Set(quizList);
PlayRoomList.add(EmptyRoomList.getFirst());
EmptyRoomList.removeFirst();
}
-Check() : 해당 방의 인원 수가 2명인지 확인
-DB에서 quiz 데이터 5개를 가져와서 Room 객체의 Set() 함수를 통해 해당 방의 퀴즈 세팅
-해당 방을 게임 진행중인 방으로 추가 및 대기방 목록에서 제거
2. afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) : 플레이어의 접속이 끊겼을 때 호출, 플레이어 접속 종료 및 방 삭제 기능
playerSessionList.remove(session);
var playerRoom = findPlayerRoom(session);
if(playerRoom != null)
{
for(WebSocketSession player : playerRoom.players) player.close();
PlayRoomList.remove(playerRoom);
}
else
{
for (Room room : EmptyRoomList)
{
if (room.players.contains(session)) playerRoom = room;
}
EmptyRoomList.remove(playerRoom);
}
-접속 중인 플레이어 목록에서 삭제
-플레이 중인 방에 접속중인 경우, 상대방 세션 접속을 끊고 진행중인 방 삭제
-플레이 중인 방에 없는 경우 접속 중인 대기방 삭제
( remove는 별도 null 예외처리 메커니즘 포함 )
2. handleTextMessage(WebSocketSession session, TextMessage message) : 플레이어가 메시지를 보내면 호출, 플레이어가 보낸 신호 판별 및 실행 함수로의 전달 기능
String msg = message.getPayload();
-getPayload를 통해 받은 TextMessage 형태의 데이터를 String 형태로 변환
var playerRoom = findPlayerRoom(session);
if(playerRoom == null)
{
// 매칭되지 않은 플레이어
}
else
{
// 매칭된 플레이어
}
-findPlayerRoom() : PlayRoomList에서 메세지를 보낸 플레이어의 방이 있는지 찾는 함수, 없을 경우 null을 반환하며 아직 매칭되지 않은 플레이어로 취급한다. 있는 경우 매칭된 플레이어로 취급한다.
// 매칭되지 않은 플레이어의 경우
{
// json 형태로 매핑
Map<String, Object> responseData = Map.of(
"type", "sign",
"contents", "fail"
);
jsonResponse = objectMapper.writeValueAsString(responseData);
// 클라이언트로 응답
session.sendMessage(new TextMessage(jsonResponse));
}
-메시지를 보낸 플레이어에게 실패했다는 내용의 신호를 json 형태로 전달
// 매칭된 경우
{
// json 형식 읽기
var data = objectMapper.readValue(msg, Map.class);
// 신호인 경우
if(Objects.equals(data.get("type").toString(), "sign"))
{
System.out.println("in sign");
// 내용에 따라 액션
actSign(session, data.get("contents").toString());
}
}
-신호를 보낸 경우, 신호에 따라 적절한 대응을 하는 actSign() 실행
3. actSign(WebSocketSession playerSession, String contents) : 받은 신호에 따라 적절한 대응 함수 실행
// 플레이어 방 찾기
var room = findPlayerRoom(playerSession);
System.out.println("in act");
switch (contents)
{
case "correct" : room.AnswerCorrect(playerSession); break; // 정답
case "ready" : room.AnswerReady(playerSession); break; // 준비
case "wrong" : room.AnswerWrong(playerSession); break; // 오답
case "late" : room.AnswerLate(playerSession); break; // 늦음
}
-플레이어가 접속 중인 Room 객체을 찾아, 해당 Room의 신호별 함수 실행
주요 기능 - 접속한 플레이어 세션 관리 및 퀴즈 대전 관련 메커니즘 구현
0. 주요 멤버 변수
List<WebSocketSession> players = new ArrayList<>();
int quizCount = 0;
boolean[] answers = new boolean[]{false, false};
-players : 현재 방에 접속 중인 플레이어 세션
-quizCount : 현재 진행한 문제의 개수
-answers : 두 플레이어의 정답 입력 여부
1. AnswerReady(WebSocketSession playerSession) : 준비 신호
// 응답 여부 기록
int index = players.indexOf(playerSession);
answers[index] = true;
// 둘 다 결과 입력했는지 확인
CheckForNextQuestion();
-신호를 보낸 플레이어 입력 여부 변경
-두 플레이어 모두 결과를 보내주었는 지 확인하는 CheckForNextQuestion() 함수 실행
2. AnswerCorrect(WebSocketSession playerSession) : 정답 신호
// 응답 여부 기록
int index = players.indexOf(playerSession);
answers[index] = true;
// 데이터 보냄
for(int i = 0; i<players.size(); i++)
{
if(i == index)
{
answers[i] = true;
SendSign(players.get(i), "correct");// 플레이어에게 정답 신호 보내기
}
else if(!answers[i])
{
answers[i] = true;
SendSign(players.get(i), "late");// 반대 플레이어에게 늦음 신호 보내기
}
}
// 둘 다 결과 입력했는지 확인
CheckForNextQuestion();
-신호를 보낸 플레이어 입력 여부 변경
-정답을 입력한 플레이어에게는 정답 신호를 보내고, 그 상대편 플레이어에게는 늦음 신호를 전송
-두 플레이어 모두 결과를 보내주었는 지 확인하는 CheckForNextQuestion() 함수 실행
3. AnswerWrong(WebSocketSession playerSession) : 오답 신호
// 응답 여부 기록
int index = players.indexOf(playerSession);
answers[index] = true;
// 데이터 보냄
SendSign(playerSession, "wrong");
// 둘 다 결과 입력했는지 확인
CheckForNextQuestion();
-신호를 보낸 플레이어 입력 여부 변경
-오답을 입력한 플레이어에게 오답 신호를 전송
-두 플레이어 모두 결과를 보내주었는 지 확인하는 CheckForNextQuestion() 함수 실행
4. AnswerLate(WebSocketSession playerSession) : 늦음 신호
// 응답 여부 기록
int index = players.indexOf(playerSession);
answers[index] = true;
// 데이터 보냄
SendSign(playerSession, "late");
// 둘 다 결과 입력했는지 확인
CheckForNextQuestion();
-신호를 보낸 플레이어 입력 여부 변경
-늦은 플레이어에게 늦음 신호를 전송
-두 플레이어 모두 결과를 보내주었는 지 확인하는 CheckForNextQuestion() 함수 실행
5. CheckForNextQuestion() : 두 플레이어 모두 결과를 보내주었는 지 확인
if(answers[0] && answers[1])
{
answers[0] = false;
answers[1] = false;
quizCount += 1;
// 3초 후에 다음 질문 호출
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.schedule(() ->
{
try
{
callNextQuestion();
} catch (IOException e)
{
System.err.println("Error during delay: " + e.getMessage());
}
}, 3, TimeUnit.SECONDS);
}
-두 플레이어가 모두 결과 신호를 보내준 경우 실행
-결과 입력 여부 초기화
-진행한 퀴즈 개수 1 증가
-3초 후 플레이어에게 다음 문제 신호를 보냄
6. CallNextQuestion() : 플레이어에게 다음 문제 신호를 전달
for(WebSocketSession playerSession : players)
{
var contents = quizCount < 6 ? "next" : "finish";
// 클라이언트로 응답
playerSession.sendMessage(new TextMessage(ToJson("sign", contents)));
}
-진행한 문제가 5개가 되지 않은 경우 "next" 신호를 보냄
-모든 문제를 진행한 경우 "finish" 신호를 보냄