From beaee4675d2c218bb262fde5f6528fc75c14a6aa Mon Sep 17 00:00:00 2001 From: EJClaw Date: Thu, 8 Oct 2026 10:14:29 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=84=EB=AC=B4=EB=8F=84=20=EC=95=89?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=B0=A9=EC=9D=80=20=EB=A7=88?= =?UTF-8?q?=EC=A7=80=EB=A7=89=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20[=EB=B0=A9?= =?UTF-8?q?=20=EB=82=98=EA=B0=80=EA=B8=B0]=EB=A1=9C=20=EB=82=98=EA=B0=80?= =?UTF-8?q?=EB=A9=B4=20=EB=B0=94=EB=A1=9C=20=EB=8B=AB=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컴퓨터가 있는 방에만 있던 '나가서 앉은 사람이 없으면 바로 닫기'를 모든 방에 적용 (아무도 들어오지 않은 대기실을 만든 사람이 나가면 목록에서 바로 사라짐) - 관전자가 남아 있으면 그대로 두고, 마지막 관전자가 나갈 때 닫음 - 사이트를 닫거나 다른 화면으로 간 경우(자리 유지)는 기존 규칙 그대로 Co-Authored-By: Claude Opus 5.5 --- apps/server/src/rooms/bots.room.test.ts | 32 +++++++++++++++++++++++++ apps/server/src/rooms/manager.ts | 4 ++-- docs/06-lobby-rooms.md | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/apps/server/src/rooms/bots.room.test.ts b/apps/server/src/rooms/bots.room.test.ts index 023b48c..e13945b 100644 --- a/apps/server/src/rooms/bots.room.test.ts +++ b/apps/server/src/rooms/bots.room.test.ts @@ -312,6 +312,38 @@ describe('practice rooms close once nobody is there', () => { expect(h.manager.get(room.code)).toBeUndefined(); }); + test('a room nobody joined closes as soon as the host leaves it', () => { + const h = harness(); + const room = h.create('A', 'omok', 'public'); + room.join(h.conn('A'), 'player'); + expect(h.manager.publicRooms('omok')).toHaveLength(1); + room.leave('A'); + expect(h.manager.get(room.code)).toBeUndefined(); + expect(h.store.openRooms()).toHaveLength(0); + expect(h.manager.publicRooms('omok')).toHaveLength(0); + // Taking a seat in another room frees the lobby seat: same thing. + const first = h.create('B'); + const b = h.conn('B'); + first.join(b, 'player'); + first.disconnect(b); + h.manager.releaseOtherLobbySeats('B', 'other'); + expect(h.manager.get(first.code)).toBeUndefined(); + }); + + test('the last person leaving a game on purpose closes it; a spectator still watching keeps it', () => { + const h = harness(); + const room = h.create('A'); + room.join(h.conn('A'), 'player'); + room.join(h.conn('B'), 'player'); + room.join(h.conn('S'), 'spectator'); + expect(room.start('A')).toBeNull(); + room.leave('A'); + room.leave('B'); + expect(h.manager.get(room.code)).toBe(room); // S is still connected + room.leave('S'); + expect(h.manager.get(room.code)).toBeUndefined(); + }); + test('rooms without computers keep the old timeouts; someone still connected keeps any room', () => { const h = harness(); const room = h.create('A'); diff --git a/apps/server/src/rooms/manager.ts b/apps/server/src/rooms/manager.ts index f9a6222..7645c3e 100644 --- a/apps/server/src/rooms/manager.ts +++ b/apps/server/src/rooms/manager.ts @@ -50,8 +50,8 @@ export class RoomManager { settings: this.deps.settings, botDelay: this.deps.botDelay, onEmpty: (room, why) => { - // Everyone left a room with computer players on purpose: nobody can come back to it, close it now. - if (why === 'leave' && this.byCode.get(room.code) === room && room.hasBots() && !room.hasPeopleSeated()) this.close(room); + // The last person left on purpose and nobody holds a seat (only computers, or nobody joined): close it now. + if (why === 'leave' && this.byCode.get(room.code) === room && !room.hasPeopleSeated()) this.close(room); }, onSeatChange: (room, userId, seated) => { if (isBotId(userId)) return; diff --git a/docs/06-lobby-rooms.md b/docs/06-lobby-rooms.md index e344d03..19773f6 100644 --- a/docs/06-lobby-rooms.md +++ b/docs/06-lobby-rooms.md @@ -95,7 +95,7 @@ interface Room { ``` - 한 사용자는 동시에 한 방에만 플레이어로 있을 수 있다(다른 방에 들어가면 이전 방 대기실 자리에서 빠짐. 이전 방이 게임 중이면 "진행 중인 게임이 있어요. 그래도 이동할까요?"). - 제한: 서버 전체 방 2,000개, 사용자당 동시에 만든 방 3개. -- 방 정리(1분마다): 접속이 하나도 없는 방 중 대기실은 10분, 끝난 방·멈춘 방은 30분 동안 활동이 없으면 닫는다. 컴퓨터가 있는 방(연습 방)은 마지막 사람이 나간 때부터 재서 대기실·끝난 방은 1분, 게임 중인 방은 30분 뒤 닫는다(사이트를 닫았으면 아래 규칙대로 1분). 사람이 [방 나가기]로 모두 나가 컴퓨터만 남으면 바로 닫는다. +- 방 정리(1분마다): 접속이 하나도 없는 방 중 대기실은 10분, 끝난 방·멈춘 방은 30분 동안 활동이 없으면 닫는다. 컴퓨터가 있는 방(연습 방)은 마지막 사람이 나간 때부터 재서 대기실·끝난 방은 1분, 게임 중인 방은 30분 뒤 닫는다(사이트를 닫았으면 아래 규칙대로 1분). 마지막 사람이 [방 나가기]로 나가서 자리에 앉은 사람이 없으면(컴퓨터만 남았거나 아무도 들어오지 않은 방) 바로 닫는다. - 게임 중이거나 끝난 방(대기실 제외)에서 자리에 앉은 사람이 모두 사이트를 닫으면(서버와 연결된 창이 하나도 없으면) 마지막 사람이 닫은 때부터 1분 뒤 정리 때 닫는다. 진행 중인 게임은 무효 처리되고, 관전자는 셈하지 않고, 남아 있던 관전자도 방에서 내보낸다. 대기실은 초대받은 사람이 들어올 수 있으므로 위 규칙을 따른다. 자리가 있는 사람은 다른 화면에 있어도 서버 연결을 유지한다(새로 연 창에서도 `/api/me`의 `activeRoom`이 있으면 연결). - 방 화면을 벗어나면(홈, 규칙 등) 클라이언트가 `detach`를 보낸다. 자리는 그대로이고 '접속 중'으로 남지만, 방 메시지를 받지 않고 방의 접속 수에서도 빠진다. 그 상태로 사이트를 닫으면 연결 끊김으로 처리한다.