강동현

disconnect 핸들링, 게임 버그 수정

...@@ -25,6 +25,7 @@ export class Connection { ...@@ -25,6 +25,7 @@ export class Connection {
25 this.socket = socket; 25 this.socket = socket;
26 this.roomManager = roomManager; 26 this.roomManager = roomManager;
27 socket.setHandler((raw) => this.handleRaw(raw)); 27 socket.setHandler((raw) => this.handleRaw(raw));
28 + socket.setDisconnectHandler(() => this.handleDisconnect());
28 } 29 }
29 30
30 public send<T extends ServerOutboundMessageKey>( 31 public send<T extends ServerOutboundMessageKey>(
...@@ -81,4 +82,8 @@ export class Connection { ...@@ -81,4 +82,8 @@ export class Connection {
81 82
82 return { ok: true }; 83 return { ok: true };
83 } 84 }
85 +
86 + public handleDisconnect(): void {
87 + this.user?.disconnected();
88 + }
84 } 89 }
......
...@@ -3,6 +3,7 @@ import { RawMessage, ServerResponse } from "../../common"; ...@@ -3,6 +3,7 @@ import { RawMessage, ServerResponse } from "../../common";
3 3
4 export interface SocketWrapper { 4 export interface SocketWrapper {
5 setHandler: (listener: (raw: RawMessage) => ServerResponse<any>) => void; 5 setHandler: (listener: (raw: RawMessage) => ServerResponse<any>) => void;
6 + setDisconnectHandler: (listener: () => void) => void;
6 send: (raw: RawMessage) => void; 7 send: (raw: RawMessage) => void;
7 } 8 }
8 9
...@@ -19,6 +20,12 @@ export class SocketIoWrapper implements SocketWrapper { ...@@ -19,6 +20,12 @@ export class SocketIoWrapper implements SocketWrapper {
19 }); 20 });
20 } 21 }
21 22
23 + public setDisconnectHandler(listener: () => void) {
24 + this.socketIo.on("disconnect", () => {
25 + listener();
26 + });
27 + }
28 +
22 public send(raw: RawMessage) { 29 public send(raw: RawMessage) {
23 this.socketIo.emit("msg", raw); 30 this.socketIo.emit("msg", raw);
24 } 31 }
......
...@@ -11,7 +11,7 @@ export class Game { ...@@ -11,7 +11,7 @@ export class Game {
11 roundDuration: number; 11 roundDuration: number;
12 readonly roundTerm: number = 5; // 다음 라운드 시작까지 기다리는 시간 12 readonly roundTerm: number = 5; // 다음 라운드 시작까지 기다리는 시간
13 wordCandidates: string[] = []; 13 wordCandidates: string[] = [];
14 - word: string = ""; 14 + word?: string;
15 timer: { 15 timer: {
16 startTimeMillis: number; 16 startTimeMillis: number;
17 timeLeftMillis: number; 17 timeLeftMillis: number;
...@@ -66,7 +66,11 @@ export class Game { ...@@ -66,7 +66,11 @@ export class Game {
66 }, 66 },
67 chat: (user, message) => { 67 chat: (user, message) => {
68 const text = message.message.trim(); 68 const text = message.message.trim();
69 - if (this.roles.get(user) === "guesser" && text === this.word) { 69 + if (
70 + this.roles.get(user) === "guesser" &&
71 + this.roundState === "running" &&
72 + text === this.word
73 + ) {
70 this.acceptAnswer(user); 74 this.acceptAnswer(user);
71 } else { 75 } else {
72 this.room.sendChat(user, text); 76 this.room.sendChat(user, text);
...@@ -122,6 +126,7 @@ export class Game { ...@@ -122,6 +126,7 @@ export class Game {
122 126
123 private startNextRound(): void { 127 private startNextRound(): void {
124 this.roundState = "choosing"; 128 this.roundState = "choosing";
129 + this.word = undefined;
125 this.round++; 130 this.round++;
126 131
127 this.roles.clear(); 132 this.roles.clear();
...@@ -164,7 +169,9 @@ export class Game { ...@@ -164,7 +169,9 @@ export class Game {
164 169
165 this.stopTimer(); 170 this.stopTimer();
166 171
167 - this.room.broadcast("finishRound", { answer: this.word }); 172 + if (this.word) {
173 + this.room.broadcast("finishRound", { answer: this.word });
174 + }
168 175
169 this.prepareNextRound(); 176 this.prepareNextRound();
170 } 177 }
...@@ -192,12 +199,15 @@ export class Game { ...@@ -192,12 +199,15 @@ export class Game {
192 if (this.nextRoundTimerId) { 199 if (this.nextRoundTimerId) {
193 clearTimeout(this.nextRoundTimerId); 200 clearTimeout(this.nextRoundTimerId);
194 } 201 }
195 - this.room.broadcast("finishRound", { answer: this.word }); 202 + if (this.word) {
203 + this.room.broadcast("finishRound", { answer: this.word });
204 + }
205 +
196 this.finishGame(); 206 this.finishGame();
197 } 207 }
198 208
199 private acceptAnswer(user: User): void { 209 private acceptAnswer(user: User): void {
200 - user.connection.send("answerAccepted", { answer: this.word }); 210 + user.connection.send("answerAccepted", { answer: this.word! });
201 this.changeRole(user, "winner"); 211 this.changeRole(user, "winner");
202 212
203 let noGuesser = true; 213 let noGuesser = true;
...@@ -265,7 +275,7 @@ export class Game { ...@@ -265,7 +275,7 @@ export class Game {
265 this.room.broadcast("role", { username: user.username, role }); 275 this.room.broadcast("role", { username: user.username, role });
266 } 276 }
267 277
268 - join(user: User): void { 278 + joined(user: User): void {
269 this.changeRole(user, "spectator"); 279 this.changeRole(user, "spectator");
270 this.sendTimer(user); 280 this.sendTimer(user);
271 user.connection.send("startRound", { 281 user.connection.send("startRound", {
...@@ -273,7 +283,7 @@ export class Game { ...@@ -273,7 +283,7 @@ export class Game {
273 duration: this.roundDuration, 283 duration: this.roundDuration,
274 roles: this.makeRoleArray(), 284 roles: this.makeRoleArray(),
275 }); 285 });
276 - if (this.roundState === "done") { 286 + if (this.roundState === "done" && this.word) {
277 user.connection.send("finishRound", { 287 user.connection.send("finishRound", {
278 answer: this.word, 288 answer: this.word,
279 }); 289 });
...@@ -289,7 +299,7 @@ export class Game { ...@@ -289,7 +299,7 @@ export class Game {
289 }); 299 });
290 } 300 }
291 301
292 - leave(user: User): void { 302 + left(user: User): void {
293 if (this.room.users.length < 2) { 303 if (this.room.users.length < 2) {
294 this.forceFinishGame(); 304 this.forceFinishGame();
295 return; 305 return;
......
...@@ -122,6 +122,8 @@ export class Room { ...@@ -122,6 +122,8 @@ export class Room {
122 this.usersReady = this.usersReady.filter((u) => u !== user); 122 this.usersReady = this.usersReady.filter((u) => u !== user);
123 user.room = undefined; 123 user.room = undefined;
124 124
125 + this.game?.left(user);
126 +
125 this.broadcast("updateRoomUser", { 127 this.broadcast("updateRoomUser", {
126 state: "removed", 128 state: "removed",
127 user: { 129 user: {
......
...@@ -109,7 +109,7 @@ describe("라운드", () => { ...@@ -109,7 +109,7 @@ describe("라운드", () => {
109 guesserSockets[0].disconnect(); 109 guesserSockets[0].disconnect();
110 drawerSocket.socket.notReceived("finishRound"); 110 drawerSocket.socket.notReceived("finishRound");
111 guesserSockets[1].disconnect(); 111 guesserSockets[1].disconnect();
112 - drawerSocket.socket.received("finishRound"); 112 + // 단어가 선택되지 않았으므로 finishRound가 수신되지 않습니다.
113 drawerSocket.socket.received("finishGame"); 113 drawerSocket.socket.received("finishGame");
114 }); 114 });
115 it("drawer가 단어를 선택하고 모든 guesser가 나가면 인원이 부족하므로 게임이 종료됩니다", () => { 115 it("drawer가 단어를 선택하고 모든 guesser가 나가면 인원이 부족하므로 게임이 종료됩니다", () => {
...@@ -143,6 +143,7 @@ describe("라운드", () => { ...@@ -143,6 +143,7 @@ describe("라운드", () => {
143 const word = drawerSocket.socket.received("wordSet").words[0]; 143 const word = drawerSocket.socket.received("wordSet").words[0];
144 drawerSocket.testOk("chooseWord", { word }); 144 drawerSocket.testOk("chooseWord", { word });
145 guesserSockets[0].testOk("chat", { message: word }); 145 guesserSockets[0].testOk("chat", { message: word });
146 + guesserSockets[1].testOk("chat", { message: word });
146 147
147 guesserSockets[0].socket.received("finishRound"); 148 guesserSockets[0].socket.received("finishRound");
148 guesserSockets[0].socket.notReceived("startRound"); 149 guesserSockets[0].socket.notReceived("startRound");
......
...@@ -15,19 +15,8 @@ describe("라운드 브러시 설정", () => { ...@@ -15,19 +15,8 @@ describe("라운드 브러시 설정", () => {
15 brushSettings 15 brushSettings
16 ); 16 );
17 }); 17 });
18 - it("올바르지 않은 브러시 설정은 허용되지 않습니다", () => { 18 + it("올바르지 않은 브러시 색상은 허용되지 않습니다", () => {
19 const { drawerSocket } = prepareGame(2); 19 const { drawerSocket } = prepareGame(2);
20 -
21 - drawerSocket.testNotOk("setBrush", {
22 - size: 0,
23 - color: "000000",
24 - drawing: true,
25 - });
26 - drawerSocket.testNotOk("setBrush", {
27 - size: 100,
28 - color: "000000",
29 - drawing: true,
30 - });
31 drawerSocket.testNotOk("setBrush", { 20 drawerSocket.testNotOk("setBrush", {
32 size: 1, 21 size: 1,
33 color: "000", 22 color: "000",
...@@ -39,6 +28,21 @@ describe("라운드 브러시 설정", () => { ...@@ -39,6 +28,21 @@ describe("라운드 브러시 설정", () => {
39 drawing: true, 28 drawing: true,
40 }); 29 });
41 }); 30 });
31 + it("올바르지 않은 브러시 사이즈는 Clamp 됩니다", () => {
32 + const { drawerSocket, guesserSockets } = prepareGame(2);
33 + drawerSocket.testOk("setBrush", {
34 + size: 0,
35 + color: "000000",
36 + drawing: true,
37 + });
38 + expect(guesserSockets[0].socket.received("setBrush").size).eq(1);
39 + drawerSocket.testOk("setBrush", {
40 + size: 100,
41 + color: "000000",
42 + drawing: true,
43 + });
44 + expect(guesserSockets[0].socket.received("setBrush").size).eq(64);
45 + });
42 it("drawer가 아닌 다른 사람들은 브러시를 설정할 수 없습니다", () => { 46 it("drawer가 아닌 다른 사람들은 브러시를 설정할 수 없습니다", () => {
43 const { guesserSockets } = prepareGame(2); 47 const { guesserSockets } = prepareGame(2);
44 48
......
...@@ -9,11 +9,12 @@ import { SocketWrapper } from "../../connection/SocketWrapper"; ...@@ -9,11 +9,12 @@ import { SocketWrapper } from "../../connection/SocketWrapper";
9 9
10 export class DummySocket implements SocketWrapper { 10 export class DummySocket implements SocketWrapper {
11 public handler?: (raw: RawMessage) => ServerResponse<any>; 11 public handler?: (raw: RawMessage) => ServerResponse<any>;
12 + public disconnectHandler?: () => void;
12 public receivedMessages: RawMessage[] = []; 13 public receivedMessages: RawMessage[] = [];
13 14
14 - public setHandler(handler: (raw: RawMessage) => ServerResponse<any>) { 15 + public setHandler(handler: (raw: RawMessage) => ServerResponse<any>) {}
15 - this.handler = handler; 16 +
16 - } 17 + public setDisconnectHandler(handler: () => void) {}
17 18
18 public send(raw: RawMessage): void { 19 public send(raw: RawMessage): void {
19 this.receivedMessages.push(raw); 20 this.receivedMessages.push(raw);
......
...@@ -55,6 +55,6 @@ export class SocketTester { ...@@ -55,6 +55,6 @@ export class SocketTester {
55 } 55 }
56 56
57 public disconnect(): void { 57 public disconnect(): void {
58 - // TODO 58 + this.connection.handleDisconnect();
59 } 59 }
60 } 60 }
......
...@@ -34,4 +34,8 @@ export class User { ...@@ -34,4 +34,8 @@ export class User {
34 }, 34 },
35 }); 35 });
36 } 36 }
37 +
38 + public disconnected(): void {
39 + this.room?.disconnect(this);
40 + }
37 } 41 }
......