Skip to content

[FEAT] 플레이어 카드에 유저 닉네임 로드 #106

Merged
cl-o-lc merged 1 commit intodevfrom
FEAT-단체전-이모티콘-채팅-플레이어카드
Dec 23, 2025

Hidden character warning

The head ref may contain hidden characters: "FEAT-\ub2e8\uccb4\uc804-\uc774\ubaa8\ud2f0\ucf58-\ucc44\ud305-\ud50c\ub808\uc774\uc5b4\uce74\ub4dc"
Merged

[FEAT] 플레이어 카드에 유저 닉네임 로드 #106
cl-o-lc merged 1 commit intodevfrom
FEAT-단체전-이모티콘-채팅-플레이어카드

Conversation

@kkhhmm3103
Copy link
Collaborator

@kkhhmm3103 kkhhmm3103 commented Dec 23, 2025

📌 작업 내용

  • MultiWebSocket에 플레이어 닉네임 브로드캐스트 로직 추가
  • HttpSession에서 loginNickname 추출
  • GAME_MULTI_START 시점에 slot 확정 후 닉네임 동기화
  • 늦게 입장한 유저도 기존 플레이어 닉네임을 모두 수신하도록 처리
  • MULTI_USER 메시지 수신 시 플레이어 카드 이름 업데이트

🔗 관련 이슈


👀 리뷰 시 참고사항

  • 아직 자신의 플레이어 카드를 우측 하단에 고정하지 않은 상태
  • 이모티콘 채팅 가능

💭 느낀 점

  • 단체전에서는 slot 확정 시점이 상태 동기화의 핵심

💻 스크린샷 (선택)

image

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 멀티플레이 게임 시작 시 플레이어 닉네임 및 슬롯 정보 자동 동기화
    • 게임 진행 중 이모지 반응 기능 추가 및 실시간 표시 개선
  • 개선사항

    • 멀티플레이 게임 페이지 UI를 반응형 레이아웃으로 개편하여 플레이어 정보 및 게임보드 배치 최적화
    • 게임 상태 표시(타이머, 상태 메시지, 기권 버튼) 기능 강화

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

단체전 게임의 플레이어 카드에 닉네임과 슬롯 정보를 추가하기 위해 백엔드 WebSocket 핸들러에서 룸별 세션 캐싱 및 EMOJI_CHAT 프로토콜을 확장하고, 프론트엔드에서 새로운 MULTI_USER 메시지 처리와 반응형 UI 레이아웃으로 리팩토링하였습니다.

Changes

코호트 / 파일 변경 요약
백엔드 WebSocket 핸들러
src/main/java/game/multi/ws/MultiWebSocket.java
룸별 세션 닉네임/슬롯 맵 추가; onOpen에서 룸 등록 및 캐싱 로직 통합; GAME_MULTI_START 감지 시 슬롯 캐싱 및 MULTI_USER 페이로드 생성; EMOJI_CHAT 프로토콜을 컨텍스트 기반 구조로 확장; broadcastMultiUserForTarget() 및 sendAllKnownUsersTo() 등 새 헬퍼 메서드 구현
프론트엔드 게임 UI 및 로직
src/main/webapp/WEB-INF/views/game/multi.jsp
절대 위치 기반 레이아웃에서 flex 기반 반응형 다중 열 구조로 변경; 캔버스 기반 오목판 재구성; WebSocket 메시지 핸들러 통합(MULTI_WAIT, GAME_MULTI_START, MULTI_TURN, MULTI_STONE, MULTI_WIN, EMOJI_CHAT, MULTI_USER); 타이머 라이프사이클 및 게임 상태 UI 업데이트 추가
클라이언트 이모지 채팅
src/main/webapp/static/chat/emojiChatMulti.js
새 window.onMultiUser(payload) 핸들러로 슬롯 닉네임 업데이트; 이모지 송신 시 자신의 슬롯에 즉시 표시 후 서버 전송; addEventListener 기반 버튼 바인딩으로 변경; 페이로드 검증 개선

Sequence Diagram(s)

sequenceDiagram
    participant Client as 클라이언트<br/>(브라우저)
    participant WS as WebSocket<br/>MultiWebSocket
    participant SessionCache as 세션<br/>캐시

    Client->>WS: onOpen(session)
    activate WS
    WS->>SessionCache: roomId/userId 등록<br/>sessionNickMap, sessionSlotMap 초기화
    SessionCache-->>WS: 캐시 준비 완료
    WS-->>Client: 연결 수립
    deactivate WS

    Client->>WS: GAME_MULTI_START 메시지
    activate WS
    WS->>SessionCache: cacheSlotIfStart()<br/>슬롯 감지 및 저장
    SessionCache-->>WS: 슬롯 반환
    WS->>SessionCache: buildMultiUserJson() 생성
    WS->>WS: 룸 전체에 MULTI_USER<br/>브로드캐스트
    WS->>WS: sendAllKnownUsersTo()<br/>기존 플레이어 정보 전달
    WS-->>Client: MULTI_USER 페이로드<br/>(slot, nickname)
    deactivate WS

    Client->>Client: onMultiUser(payload)<br/>슬롯 닉네임 카드 업데이트

    Client->>WS: EMOJI_CHAT 메시지<br/>(이모지)
    activate WS
    WS->>WS: 룸별 스코프로<br/>브로드캐스트
    WS-->>Client: EMOJI_CHAT 응답<br/>(from, fromNick, slot)
    deactivate WS

    Client->>Client: onEmojiChat(payload)<br/>플레이어 카드에 이모지 표시
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

FEAT

Suggested reviewers

  • cl-o-lc
  • gaeunnlee

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive PR은 #105의 주요 요구사항 중 플레이어 카드 닉네임 추가 및 이모티콘 채팅 기능을 구현했으나, 자신의 카드 우측 하단 고정 기능은 미완성 상태입니다. 우측 하단 카드 고정 기능이 미구현되었습니다. PR 설명에서 향후 추가 예정이라고 명시했으므로 이를 별도 PR이나 follow-up으로 계획하세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 플레이어 카드에 유저 닉네임 로드라는 주요 변경사항을 명확히 반영하고 있으며, 변경 내용과 일치합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 다중 플레이어 게임의 닉네임 브로드캐스트, 이모티콘 채팅, UI 재구성 등 #105 요구사항 범위 내에 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch FEAT-단체전-이모티콘-채팅-플레이어카드

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
src/main/java/game/multi/ws/MultiWebSocket.java (5)

35-42: 캐시 구조 설계 적절함

ConcurrentHashMap을 사용한 room 단위 세션/유저/닉/슬롯 캐싱 구조가 적절합니다. 다만, sessionSlotMap의 slot 값이 0~3 범위임을 명시하는 주석이 있으니, 범위 유효성 검증을 cacheSlotIfStart에서 추가하는 것을 권장합니다.


62-65: 세션 종료 시 CloseReason 추가 권장

멤버십 검증 실패 시 session.close()만 호출하면 클라이언트가 종료 사유를 알 수 없습니다. 디버깅과 사용자 경험을 위해 CloseReason을 포함하는 것이 좋습니다.

🔎 제안된 수정
 if (!multiPlayerDao.isMember(roomId, userId)) {
-    session.close();
+    session.close(new javax.websocket.CloseReason(
+        javax.websocket.CloseReason.CloseCodes.VIOLATED_POLICY,
+        "Not a member of this room"));
     return;
 }

154-158: 예외 무시(silent catch) 개선 필요

빈 catch 블록은 전송 실패 원인을 숨깁니다. 최소한 debug 레벨 로깅을 추가하여 문제 진단이 가능하도록 하세요.

🔎 제안된 수정
 try {
     synchronized (s) {
         s.getBasicRemote().sendText(json);
     }
-} catch (Exception ignore) {}
+} catch (Exception e) {
+    // 연결 끊김 등 일시적 오류는 무시하되 로깅
+    System.err.println("EMOJI broadcast 실패: " + e.getMessage());
+}

263-267: 슬롯 범위 유효성 검증 추가 권장

주석에서 slot이 0~3 범위라고 명시했으나, 실제로 범위를 검증하지 않습니다. 잘못된 slot 값이 캐싱되면 이후 로직에서 예상치 못한 동작이 발생할 수 있습니다.

🔎 제안된 수정
 if (obj.has("slot")) {
     int slot = obj.get("slot").getAsInt();
+    if (slot < 0 || slot > 3) {
+        return null; // 유효하지 않은 슬롯
+    }
     sessionSlotMap.put(target.getId(), slot);
     return slot;
 }

346-351: 반복되는 빈 catch 블록 패턴

파일 전체에서 catch (Exception ignore) {}가 반복됩니다. 공통 유틸리티 메서드로 추출하거나, 최소한 일관된 로깅을 추가하는 것을 권장합니다.

🔎 공통 전송 메서드 추출 예시
private void safeSend(Session s, String json) {
    if (s == null || !s.isOpen()) return;
    try {
        synchronized (s) {
            s.getBasicRemote().sendText(json);
        }
    } catch (Exception e) {
        // 연결 종료 시 발생할 수 있는 예외는 무시
    }
}
src/main/webapp/WEB-INF/views/game/multi.jsp (4)

85-97: 슬롯 번호와 요소 ID 매핑 확인 필요

플레이어 카드의 iddata-slot이 직관적이지 않습니다:

  • #p1 → slot 0, #p4 → slot 2 (왼쪽 열)
  • #p3 → slot 1, #p2 → slot 3 (오른쪽 열)

의도된 UI 배치라면 주석으로 명시하는 것이 유지보수에 도움됩니다.


254-266: MULTI_USER 처리 로직 중복

MULTI_USER 메시지 처리가 여기와 emojiChatMulti.jswindow.onMultiUser에 모두 구현되어 있습니다. emojiChatMulti.js에서 window.onMultiUser를 정의하지만 여기서는 직접 DOM을 업데이트합니다. 일관성을 위해 한 곳에서만 처리하거나, window.onMultiUser를 호출하세요.

🔎 제안된 수정
 /* 닉네임/슬롯 수신 */
 if (data.type === "MULTI_USER") {
-    const p = data.payload || {};
-    const slot = p.slot;
-    const nick = p.nickname;
-
-    const card = document.querySelector(`.player-card[data-slot='${slot}']`);
-    if (card) {
-        const nameEl = card.querySelector(".name");
-        if (nameEl && nick) nameEl.textContent = nick;
-    }
+    if (typeof window.onMultiUser === "function") {
+        window.onMultiUser(data.payload || {});
+    }
     return;
 }

308-318: 타이머 메모리 누수 방지 권장

startTimersetInterval이 게임 종료(MULTI_WIN, GAME_OVER) 시에만 정리됩니다. 페이지 이탈 시 타이머가 계속 실행될 수 있으므로 beforeunload 이벤트에서도 정리하는 것이 좋습니다.

🔎 제안된 수정
window.addEventListener("beforeunload", () => {
  clearInterval(timer);
});

290-306: fetch 응답 미사용 및 사용자 피드백 부재

fetch 응답(data)을 사용하지 않고, 3초 대기 후 리다이렉트합니다. 사용자에게 "잠시 후 방으로 이동합니다" 같은 피드백을 제공하는 것이 좋습니다.

🔎 제안된 수정
 function goToRoomView() {
   try { ws.close(); } catch (e) {}
+  statusDiv.innerText = "잠시 후 방으로 이동합니다...";

   fetch(contextPath + "/room/playersToRoom", {
     method: "POST",
     headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
     body: "roomId=" + encodeURIComponent(roomId)
   })
-    .then(res => res.json())
-    .then(data => {
+    .then(() => {
       setTimeout(() => {
         location.href = contextPath + "/room?roomId=" + encodeURIComponent(roomId) + "&playType=1";
       }, 3000);
     })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4290195 and 2d94972.

📒 Files selected for processing (3)
  • src/main/java/game/multi/ws/MultiWebSocket.java
  • src/main/webapp/WEB-INF/views/game/multi.jsp
  • src/main/webapp/static/chat/emojiChatMulti.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/webapp/static/chat/emojiChatMulti.js (1)
src/main/webapp/static/room/room.js (1)
  • ws (15-15)
🔇 Additional comments (7)
src/main/java/game/multi/ws/MultiWebSocket.java (2)

295-325: LGTM - 늦게 입장한 유저 동기화 처리

슬롯이 확정된 기존 유저들의 닉네임을 새로 입장한 유저에게 전송하는 로직이 잘 구현되어 있습니다. 4명 제한의 단체전이므로 반복 전송 방식도 적절합니다.


206-215: 중복 호출 우려는 근거 없음 - 코드가 의도대로 동작 중

handleOpen에서 4명 입장 시 정확히 4개의 SendJob 객체만 생성되며, 각 job은 서로 다른 session을 target으로 합니다. dispatch 메서드는 jobs 리스트를 순회하면서 각 job을 한 번씩만 처리하므로, 동일 세션에 대한 중복 호출은 발생하지 않습니다. sessionSlotMapConcurrentHashMap이므로 slot 캐싱과 이후 읽기도 스레드 안전하게 처리됩니다.

src/main/webapp/static/chat/emojiChatMulti.js (4)

12-14: XSS 취약점 가능성 검토

slot 값이 숫자로 검증되고 있어 현재는 안전하지만, 향후 유지보수 시 주의가 필요합니다. data-slot 속성 선택자에 직접 문자열을 삽입하는 패턴입니다.


38-48: LGTM - 이모지 수신 핸들러

slot이 숫자 타입인지 확인하는 검증이 적절히 추가되었습니다.


54-69: LGTM - MULTI_USER 핸들러

슬롯 타입 검증 및 카드 존재 확인 후 닉네임을 업데이트하는 로직이 안전하게 구현되었습니다.


93-100: LGTM - 이벤트 바인딩 개선

인라인 onclick 대신 addEventListener를 사용한 이벤트 바인딩이 적절합니다.

src/main/webapp/WEB-INF/views/game/multi.jsp (1)

10-71: LGTM - CSS 레이아웃

반응형 flex 레이아웃과 플레이어 카드 스타일링이 잘 구성되었습니다.

Comment on lines +81 to 87
/* 내 슬롯 카드에 즉시 표시 */
if (typeof window.mySlot === "number") {
showBubbleOnSlot(window.mySlot, emoji);
}

/* 서버는 문자열 프로토콜을 기대함 */
ws.send("EMOJI_CHAT:" + key);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

발신자에게 이모지 중복 표시 가능성

Line 82-84에서 발신자 슬롯에 즉시 이모지를 표시하고, 서버에서 브로드캐스트된 메시지도 다시 수신합니다. 서버가 발신자 포함 전체에 브로드캐스트하면 동일 이모지가 두 번 표시될 수 있습니다.

🔎 해결 방안

서버에서 발신자를 제외하고 브로드캐스트하거나, 클라이언트에서 자신의 슬롯 메시지를 필터링하세요:

 window.onEmojiChat = (payload) => {
   if (!payload) return;

   const slot = payload.slot;
+  // 자신이 보낸 이모지는 이미 표시됨
+  if (typeof window.mySlot === "number" && slot === window.mySlot) return;
+
   const key = payload.emoji;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
src/main/webapp/static/chat/emojiChatMulti.js around lines 81-87: the code
immediately shows the emoji on the sender's slot and also relies on a server
broadcast, causing duplicate display for the sender; to fix either (A) stop the
immediate local display and rely on the server broadcast, or (B) keep the
immediate display but add sender metadata to the outgoing message (e.g., include
slot/id) and update the incoming message handler to ignore messages where the
sender id/slot equals window.mySlot so the sender does not render the
broadcasted emoji again.

Comment on lines +355 to +359
function log(msg) {
const logDiv = document.getElementById("log");
logDiv.innerHTML += msg + "<br>";
logDiv.scrollTop = logDiv.scrollHeight;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

XSS 취약점: innerHTML 사용

log 함수에서 서버 메시지를 innerHTML로 삽입합니다. 서버 메시지가 신뢰할 수 없는 입력을 포함하면 XSS 공격에 취약합니다. textContent를 사용하거나 HTML 이스케이프 처리가 필요합니다.

🔎 제안된 수정
 function log(msg) {
   const logDiv = document.getElementById("log");
-  logDiv.innerHTML += msg + "<br>";
+  const line = document.createElement("div");
+  line.textContent = msg;
+  logDiv.appendChild(line);
   logDiv.scrollTop = logDiv.scrollHeight;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function log(msg) {
const logDiv = document.getElementById("log");
logDiv.innerHTML += msg + "<br>";
logDiv.scrollTop = logDiv.scrollHeight;
}
function log(msg) {
const logDiv = document.getElementById("log");
const line = document.createElement("div");
line.textContent = msg;
logDiv.appendChild(line);
logDiv.scrollTop = logDiv.scrollHeight;
}
🤖 Prompt for AI Agents
In src/main/webapp/WEB-INF/views/game/multi.jsp around lines 355 to 359, the log
function inserts server messages using innerHTML which allows XSS; change it to
use safe DOM methods (e.g., create a text node or set element.textContent for
the message, then append a <br> element created via document.createElement)
instead of concatenating into innerHTML, and maintain the existing scrollTop =
scrollHeight behavior so the log still autoscrolls.

Copy link
Collaborator

@ochanhyeok ochanhyeok left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!

@cl-o-lc cl-o-lc merged commit df33de3 into dev Dec 23, 2025
4 checks passed
@cl-o-lc cl-o-lc deleted the FEAT-단체전-이모티콘-채팅-플레이어카드 branch December 23, 2025 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 단체전 게임 채팅 플레이어 카드

3 participants