Showing
9 changed files
with
59 additions
and
25 deletions
| ... | @@ -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); | ... | ... |
-
Please register or login to post a comment