Showing
6 changed files
with
360 additions
and
316 deletions
| ... | @@ -8,6 +8,7 @@ import { | ... | @@ -8,6 +8,7 @@ import { |
| 8 | Record, | 8 | Record, |
| 9 | Union, | 9 | Union, |
| 10 | Static, | 10 | Static, |
| 11 | + Optional, | ||
| 11 | } from "runtypes"; | 12 | } from "runtypes"; |
| 12 | import { | 13 | import { |
| 13 | Role, | 14 | Role, |
| ... | @@ -48,7 +49,12 @@ export class ServerInboundMessageRecordMap { | ... | @@ -48,7 +49,12 @@ export class ServerInboundMessageRecordMap { |
| 48 | }); | 49 | }); |
| 49 | 50 | ||
| 50 | // 방장이 게임을 시작합니다. | 51 | // 방장이 게임을 시작합니다. |
| 51 | - startGame = Record({}); | 52 | + // TODO: 주의! 아래 필드는 디버그 용도로만 사용됩니다. 추후에 준비 화면에서 공개적으로 설정하는 것으로 구현해야 합니다. |
| 53 | + startGame = Record({ | ||
| 54 | + maxRound: Optional(Number), | ||
| 55 | + roundDuration: Optional(Number), | ||
| 56 | + roundTerm: Optional(Number), | ||
| 57 | + }); | ||
| 52 | 58 | ||
| 53 | // drawer가 단어를 선택합니다. | 59 | // drawer가 단어를 선택합니다. |
| 54 | chooseWord = Record({ | 60 | chooseWord = Record({ | ... | ... |
| ... | @@ -54,6 +54,14 @@ export class Connection { | ... | @@ -54,6 +54,14 @@ export class Connection { |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | // Game > Room > User 순으로 전달 | 56 | // Game > Room > User 순으로 전달 |
| 57 | + if (this.user?.room?.game) { | ||
| 58 | + const response = this.user.room.game.handler.handle( | ||
| 59 | + type, | ||
| 60 | + this.user, | ||
| 61 | + message | ||
| 62 | + ); | ||
| 63 | + if (response) return response; | ||
| 64 | + } | ||
| 57 | if (this.user?.room) { | 65 | if (this.user?.room) { |
| 58 | const response = this.user.room.handler.handle(type, this.user, message); | 66 | const response = this.user.room.handler.handle(type, this.user, message); |
| 59 | if (response) return response; | 67 | if (response) return response; | ... | ... |
| 1 | +import { Role } from "../../common/dataType"; | ||
| 2 | +import { MessageHandler } from "../message/MessageHandler"; | ||
| 3 | +import { Room } from "../room/Room"; | ||
| 1 | import { User } from "../user/User"; | 4 | import { User } from "../user/User"; |
| 2 | 5 | ||
| 3 | -export interface Game { | 6 | +export class Game { |
| 4 | - join(user: User): void; | 7 | + room: Room; |
| 5 | - leave(user: User): void; | 8 | + maxRound: number; |
| 9 | + round: number = 0; | ||
| 10 | + roundState: "choosing" | "running" | "done" = "choosing"; | ||
| 11 | + roundDuration: number; | ||
| 12 | + readonly roundTerm: number = 5; // 다음 라운드 시작까지 기다리는 시간 | ||
| 13 | + wordCandidates: string[] = []; | ||
| 14 | + word: string = ""; | ||
| 15 | + timer: { | ||
| 16 | + startTimeMillis: number; | ||
| 17 | + timeLeftMillis: number; | ||
| 18 | + running: boolean; | ||
| 19 | + } = { startTimeMillis: 0, timeLeftMillis: 0, running: false }; | ||
| 20 | + timeoutTimerId?: NodeJS.Timeout; | ||
| 21 | + nextRoundTimerId?: NodeJS.Timeout; | ||
| 22 | + | ||
| 23 | + brush: { | ||
| 24 | + size: number; | ||
| 25 | + color: string; | ||
| 26 | + drawing: boolean; | ||
| 27 | + x: number; | ||
| 28 | + y: number; | ||
| 29 | + } = { | ||
| 30 | + size: 24, | ||
| 31 | + color: "000000", | ||
| 32 | + drawing: false, | ||
| 33 | + x: 0, | ||
| 34 | + y: 0, | ||
| 35 | + }; | ||
| 36 | + | ||
| 37 | + handler: MessageHandler; | ||
| 38 | + roles: Map<User, Role>; | ||
| 39 | + drawer?: User; | ||
| 40 | + | ||
| 41 | + constructor( | ||
| 42 | + room: Room, | ||
| 43 | + maxRound: number, | ||
| 44 | + roundDuration: number, | ||
| 45 | + roundTerm: number | ||
| 46 | + ) { | ||
| 47 | + this.room = room; | ||
| 48 | + | ||
| 49 | + // TODO: 방장이 설정 | ||
| 50 | + this.maxRound = maxRound; | ||
| 51 | + this.roundDuration = roundDuration; | ||
| 52 | + this.roundTerm = roundTerm; | ||
| 53 | + | ||
| 54 | + this.handler = new MessageHandler({ | ||
| 55 | + chooseWord: (user, message) => { | ||
| 56 | + if (user !== this.drawer || this.roundState !== "choosing") { | ||
| 57 | + return { ok: false }; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + const chosen = message.word; | ||
| 61 | + if (this.wordCandidates.includes(chosen)) { | ||
| 62 | + this.wordSelected(chosen); | ||
| 63 | + return { ok: true }; | ||
| 64 | + } | ||
| 65 | + return { ok: false }; | ||
| 66 | + }, | ||
| 67 | + chat: (user, message) => { | ||
| 68 | + const text = message.message.trim(); | ||
| 69 | + if (this.roles.get(user) === "guesser" && text === this.word) { | ||
| 70 | + this.acceptAnswer(user); | ||
| 71 | + } else { | ||
| 72 | + this.room.sendChat(user, text); | ||
| 73 | + } | ||
| 74 | + return { ok: true }; | ||
| 75 | + }, | ||
| 76 | + setBrush: (user, message) => { | ||
| 77 | + if (user !== this.drawer || !/^[0-9a-f]{6}$/.test(message.color)) { | ||
| 78 | + return { ok: false }; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + this.brush.size = Math.max(Math.min(message.size, 64), 1); | ||
| 82 | + this.brush.color = message.color; | ||
| 83 | + this.brush.drawing = message.drawing; | ||
| 84 | + | ||
| 85 | + this.room.broadcast( | ||
| 86 | + "setBrush", | ||
| 87 | + { | ||
| 88 | + size: this.brush.size, | ||
| 89 | + color: this.brush.color, | ||
| 90 | + drawing: this.brush.drawing, | ||
| 91 | + }, | ||
| 92 | + user | ||
| 93 | + ); | ||
| 94 | + | ||
| 95 | + return { ok: true }; | ||
| 96 | + }, | ||
| 97 | + moveBrush: (user, message) => { | ||
| 98 | + if (user !== this.drawer) { | ||
| 99 | + return { ok: false }; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + this.brush.x = Math.max(Math.min(message.x, 1), 0); | ||
| 103 | + this.brush.y = Math.max(Math.min(message.y, 1), 0); | ||
| 104 | + | ||
| 105 | + this.room.broadcast( | ||
| 106 | + "moveBrush", | ||
| 107 | + { | ||
| 108 | + x: this.brush.x, | ||
| 109 | + y: this.brush.y, | ||
| 110 | + }, | ||
| 111 | + user | ||
| 112 | + ); | ||
| 113 | + | ||
| 114 | + return { ok: true }; | ||
| 115 | + }, | ||
| 116 | + }); | ||
| 117 | + | ||
| 118 | + this.roles = new Map<User, Role>(); | ||
| 119 | + | ||
| 120 | + this.startNextRound(); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + private startNextRound(): void { | ||
| 124 | + this.roundState = "choosing"; | ||
| 125 | + this.round++; | ||
| 126 | + | ||
| 127 | + this.roles.clear(); | ||
| 128 | + | ||
| 129 | + this.drawer = this.pickDrawer(); | ||
| 130 | + this.room.users.forEach((user) => this.roles.set(user, "guesser")); | ||
| 131 | + this.roles.set(this.drawer, "drawer"); | ||
| 132 | + | ||
| 133 | + this.room.broadcast("startRound", { | ||
| 134 | + round: this.round, | ||
| 135 | + duration: this.roundDuration, | ||
| 136 | + roles: this.makeRoleArray(), | ||
| 137 | + }); | ||
| 138 | + | ||
| 139 | + this.wordCandidates = this.pickWords(); | ||
| 140 | + this.drawer.connection.send("wordSet", { words: this.wordCandidates }); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + private wordSelected(word: string): void { | ||
| 144 | + this.word = word; | ||
| 145 | + this.roundState = "running"; | ||
| 146 | + | ||
| 147 | + this.room.broadcast("wordChosen", { length: word.length }); | ||
| 148 | + | ||
| 149 | + this.startTimer(this.roundDuration * 1000); | ||
| 150 | + | ||
| 151 | + this.timeoutTimerId = setTimeout( | ||
| 152 | + () => this.finishRound(), | ||
| 153 | + this.roundDuration * 1000 | ||
| 154 | + ); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + public finishRound(): void { | ||
| 158 | + if (this.timeoutTimerId) { | ||
| 159 | + clearTimeout(this.timeoutTimerId); | ||
| 160 | + this.timeoutTimerId = undefined; | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + this.roundState = "done"; | ||
| 164 | + | ||
| 165 | + this.stopTimer(); | ||
| 166 | + | ||
| 167 | + this.room.broadcast("finishRound", { answer: this.word }); | ||
| 168 | + | ||
| 169 | + this.prepareNextRound(); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + private prepareNextRound(): void { | ||
| 173 | + this.nextRoundTimerId = setTimeout(() => { | ||
| 174 | + if (this.round == this.maxRound) { | ||
| 175 | + this.finishGame(); | ||
| 176 | + } else { | ||
| 177 | + this.startNextRound(); | ||
| 178 | + } | ||
| 179 | + }, this.roundTerm * 1000); | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + private finishGame(): void { | ||
| 183 | + this.room.broadcast("finishGame", {}); | ||
| 184 | + | ||
| 185 | + this.room.finishGame(); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + private forceFinishGame() { | ||
| 189 | + if (this.timeoutTimerId) { | ||
| 190 | + clearTimeout(this.timeoutTimerId); | ||
| 191 | + } | ||
| 192 | + if (this.nextRoundTimerId) { | ||
| 193 | + clearTimeout(this.nextRoundTimerId); | ||
| 194 | + } | ||
| 195 | + this.room.broadcast("finishRound", { answer: this.word }); | ||
| 196 | + this.finishGame(); | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + private acceptAnswer(user: User): void { | ||
| 200 | + user.connection.send("answerAccepted", { answer: this.word }); | ||
| 201 | + this.changeRole(user, "winner"); | ||
| 202 | + | ||
| 203 | + let noGuesser = true; | ||
| 204 | + this.roles.forEach((role, user) => { | ||
| 205 | + if (role === "guesser") { | ||
| 206 | + noGuesser = false; | ||
| 207 | + } | ||
| 208 | + }); | ||
| 209 | + | ||
| 210 | + if (noGuesser) { | ||
| 211 | + this.finishRound(); | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + private pickDrawer(): User { | ||
| 216 | + const candidates = this.room.users.filter((user) => user !== this.drawer); | ||
| 217 | + return candidates[Math.floor(Math.random() * candidates.length)]; | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + private pickWords(): string[] { | ||
| 221 | + return ["장난감", "백화점", "파티"]; | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + private startTimer(timeLeftMillis: number): void { | ||
| 225 | + this.timer = { | ||
| 226 | + startTimeMillis: Date.now(), | ||
| 227 | + timeLeftMillis, | ||
| 228 | + running: true, | ||
| 229 | + }; | ||
| 230 | + this.room.users.forEach((user) => this.sendTimer(user)); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + private stopTimer(): void { | ||
| 234 | + this.timer = { | ||
| 235 | + ...this.timer, | ||
| 236 | + running: false, | ||
| 237 | + }; | ||
| 238 | + this.room.users.forEach((user) => this.sendTimer(user)); | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + private sendTimer(user: User): void { | ||
| 242 | + user.connection.send("timer", { | ||
| 243 | + state: this.timer.running ? "started" : "stopped", | ||
| 244 | + time: Math.max( | ||
| 245 | + (this.timer.startTimeMillis + this.timer.timeLeftMillis - Date.now()) / | ||
| 246 | + 1000, | ||
| 247 | + 0 | ||
| 248 | + ), | ||
| 249 | + }); | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + private makeRoleArray(): { username: string; role: Role }[] { | ||
| 253 | + let roleArray: { | ||
| 254 | + username: string; | ||
| 255 | + role: Role; | ||
| 256 | + }[] = []; | ||
| 257 | + this.roles.forEach((role, user) => | ||
| 258 | + roleArray.push({ username: user.username, role: role }) | ||
| 259 | + ); | ||
| 260 | + return roleArray; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + private changeRole(user: User, role: Role) { | ||
| 264 | + this.roles.set(user, role); | ||
| 265 | + this.room.broadcast("role", { username: user.username, role }); | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + join(user: User): void { | ||
| 269 | + this.changeRole(user, "spectator"); | ||
| 270 | + this.sendTimer(user); | ||
| 271 | + user.connection.send("startRound", { | ||
| 272 | + round: this.round, | ||
| 273 | + duration: this.roundDuration, | ||
| 274 | + roles: this.makeRoleArray(), | ||
| 275 | + }); | ||
| 276 | + if (this.roundState === "done") { | ||
| 277 | + user.connection.send("finishRound", { | ||
| 278 | + answer: this.word, | ||
| 279 | + }); | ||
| 280 | + } | ||
| 281 | + user.connection.send("setBrush", { | ||
| 282 | + size: this.brush.size, | ||
| 283 | + color: this.brush.color, | ||
| 284 | + drawing: this.brush.drawing, | ||
| 285 | + }); | ||
| 286 | + user.connection.send("moveBrush", { | ||
| 287 | + x: this.brush.x, | ||
| 288 | + y: this.brush.y, | ||
| 289 | + }); | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + leave(user: User): void { | ||
| 293 | + if (this.room.users.length < 2) { | ||
| 294 | + this.forceFinishGame(); | ||
| 295 | + return; | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + this.roles.delete(user); | ||
| 299 | + | ||
| 300 | + if (user === this.drawer) { | ||
| 301 | + if (this.roundState === "choosing") { | ||
| 302 | + this.round--; // 이번 라운드를 다시 시작 | ||
| 303 | + this.startNextRound(); | ||
| 304 | + } else if (this.roundState === "running") { | ||
| 305 | + this.finishRound(); | ||
| 306 | + } | ||
| 307 | + } else { | ||
| 308 | + let guesserCount = 0; | ||
| 309 | + this.roles.forEach((role, user) => { | ||
| 310 | + if (role === "guesser") { | ||
| 311 | + guesserCount++; | ||
| 312 | + } | ||
| 313 | + }); | ||
| 314 | + if (guesserCount < 1) { | ||
| 315 | + if (this.roundState === "choosing") { | ||
| 316 | + this.round--; | ||
| 317 | + this.startNextRound(); | ||
| 318 | + } else if (this.roundState === "running") { | ||
| 319 | + this.finishRound(); | ||
| 320 | + } | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | + } | ||
| 6 | } | 324 | } | ... | ... |
server/game/WordGuessingGame.ts
deleted
100644 → 0
| 1 | -import { Role } from "../../common/dataType"; | ||
| 2 | -import { MessageHandler } from "../message/MessageHandler"; | ||
| 3 | -import { Room } from "../room/Room"; | ||
| 4 | -import { User } from "../user/User"; | ||
| 5 | -import { Game } from "./Game"; | ||
| 6 | - | ||
| 7 | -export class WorldGuessingGame implements Game { | ||
| 8 | - room: Room; | ||
| 9 | - maxRound: number; | ||
| 10 | - round: number = 0; | ||
| 11 | - roundState: "choosing" | "running" | "done" = "choosing"; | ||
| 12 | - roundDuration: number; | ||
| 13 | - readonly roundTerm: number = 5; // 다음 라운드 시작까지 기다리는 시간 | ||
| 14 | - wordCandidates: string[] = []; | ||
| 15 | - word: string = ""; | ||
| 16 | - timer: { | ||
| 17 | - startTimeMillis: number; | ||
| 18 | - timeLeftMillis: number; | ||
| 19 | - running: boolean; | ||
| 20 | - } = { startTimeMillis: 0, timeLeftMillis: 0, running: false }; | ||
| 21 | - timeoutTimerId?: NodeJS.Timeout; | ||
| 22 | - nextRoundTimerId?: NodeJS.Timeout; | ||
| 23 | - | ||
| 24 | - brush: { | ||
| 25 | - size: number; | ||
| 26 | - color: string; | ||
| 27 | - drawing: boolean; | ||
| 28 | - x: number; | ||
| 29 | - y: number; | ||
| 30 | - } = { | ||
| 31 | - size: 24, | ||
| 32 | - color: "000000", | ||
| 33 | - drawing: false, | ||
| 34 | - x: 0, | ||
| 35 | - y: 0, | ||
| 36 | - }; | ||
| 37 | - | ||
| 38 | - handler: MessageHandler; | ||
| 39 | - roles: Map<User, Role>; | ||
| 40 | - drawer?: User; | ||
| 41 | - | ||
| 42 | - constructor(room: Room) { | ||
| 43 | - this.room = room; | ||
| 44 | - | ||
| 45 | - // TODO: 방장이 설정 | ||
| 46 | - this.maxRound = 5; | ||
| 47 | - this.roundDuration = 60; | ||
| 48 | - | ||
| 49 | - this.handler = new MessageHandler({ | ||
| 50 | - chooseWord: (user, message) => { | ||
| 51 | - if (user !== this.drawer || this.roundState === "choosing") { | ||
| 52 | - return { ok: false }; | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | - const chosen = message.word; | ||
| 56 | - if (this.wordCandidates.includes(chosen)) { | ||
| 57 | - this.wordSelected(chosen); | ||
| 58 | - return { ok: true }; | ||
| 59 | - } | ||
| 60 | - return { ok: false }; | ||
| 61 | - }, | ||
| 62 | - chat: (user, message) => { | ||
| 63 | - const text = message.message.trim(); | ||
| 64 | - if (this.roles.get(user) === "guesser" && text === this.word) { | ||
| 65 | - this.acceptAnswer(user); | ||
| 66 | - } else { | ||
| 67 | - this.room.sendChat(user, text); | ||
| 68 | - } | ||
| 69 | - return { ok: true }; | ||
| 70 | - }, | ||
| 71 | - setBrush: (user, message) => { | ||
| 72 | - if (user !== this.drawer || !/^[0-9a-f]{6}$/.test(message.color)) { | ||
| 73 | - return { ok: false }; | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - this.brush.size = Math.max(Math.min(message.size, 64), 1); | ||
| 77 | - this.brush.color = message.color; | ||
| 78 | - this.brush.drawing = message.drawing; | ||
| 79 | - | ||
| 80 | - this.room.broadcast( | ||
| 81 | - "setBrush", | ||
| 82 | - { | ||
| 83 | - size: this.brush.size, | ||
| 84 | - color: this.brush.color, | ||
| 85 | - drawing: this.brush.drawing, | ||
| 86 | - }, | ||
| 87 | - user | ||
| 88 | - ); | ||
| 89 | - | ||
| 90 | - return { ok: true }; | ||
| 91 | - }, | ||
| 92 | - moveBrush: (user, message) => { | ||
| 93 | - if (user !== this.drawer) { | ||
| 94 | - return { ok: false }; | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - this.brush.x = Math.max(Math.min(message.x, 1), 0); | ||
| 98 | - this.brush.y = Math.max(Math.min(message.y, 1), 0); | ||
| 99 | - | ||
| 100 | - this.room.broadcast( | ||
| 101 | - "moveBrush", | ||
| 102 | - { | ||
| 103 | - x: this.brush.x, | ||
| 104 | - y: this.brush.y, | ||
| 105 | - }, | ||
| 106 | - user | ||
| 107 | - ); | ||
| 108 | - | ||
| 109 | - return { ok: true }; | ||
| 110 | - }, | ||
| 111 | - }); | ||
| 112 | - | ||
| 113 | - this.roles = new Map<User, Role>(); | ||
| 114 | - | ||
| 115 | - this.startNextRound(); | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - private startNextRound(): void { | ||
| 119 | - this.roundState = "choosing"; | ||
| 120 | - this.round++; | ||
| 121 | - | ||
| 122 | - this.roles.clear(); | ||
| 123 | - | ||
| 124 | - this.drawer = this.pickDrawer(); | ||
| 125 | - this.room.users.forEach((user) => this.roles.set(user, "guesser")); | ||
| 126 | - this.roles.set(this.drawer, "drawer"); | ||
| 127 | - | ||
| 128 | - this.room.broadcast("startRound", { | ||
| 129 | - round: this.round, | ||
| 130 | - duration: this.roundDuration, | ||
| 131 | - roles: this.makeRoleArray(), | ||
| 132 | - }); | ||
| 133 | - | ||
| 134 | - this.wordCandidates = this.pickWords(); | ||
| 135 | - this.drawer.connection.send("wordSet", { words: this.wordCandidates }); | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - private wordSelected(word: string): void { | ||
| 139 | - this.word = word; | ||
| 140 | - this.roundState = "running"; | ||
| 141 | - | ||
| 142 | - this.room.broadcast("wordChosen", { length: word.length }); | ||
| 143 | - | ||
| 144 | - this.startTimer(this.roundDuration * 1000); | ||
| 145 | - | ||
| 146 | - this.timeoutTimerId = setTimeout( | ||
| 147 | - this.finishRound, | ||
| 148 | - this.roundDuration * 1000 | ||
| 149 | - ); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - private finishRound(): void { | ||
| 153 | - if (this.timeoutTimerId) { | ||
| 154 | - clearTimeout(this.timeoutTimerId); | ||
| 155 | - this.timeoutTimerId = undefined; | ||
| 156 | - } | ||
| 157 | - | ||
| 158 | - this.roundState = "done"; | ||
| 159 | - | ||
| 160 | - this.stopTimer(); | ||
| 161 | - | ||
| 162 | - this.room.broadcast("finishRound", { answer: this.word }); | ||
| 163 | - | ||
| 164 | - this.prepareNextRound(); | ||
| 165 | - } | ||
| 166 | - | ||
| 167 | - private prepareNextRound(): void { | ||
| 168 | - this.nextRoundTimerId = setTimeout(() => { | ||
| 169 | - if (this.round == this.maxRound) { | ||
| 170 | - this.finishGame(); | ||
| 171 | - } else { | ||
| 172 | - this.startNextRound(); | ||
| 173 | - } | ||
| 174 | - }, this.roundTerm * 1000); | ||
| 175 | - } | ||
| 176 | - | ||
| 177 | - private finishGame(): void { | ||
| 178 | - this.room.broadcast("finishGame", {}); | ||
| 179 | - | ||
| 180 | - this.room.finishGame(); | ||
| 181 | - } | ||
| 182 | - | ||
| 183 | - private forceFinishGame() { | ||
| 184 | - if (this.timeoutTimerId) { | ||
| 185 | - clearTimeout(this.timeoutTimerId); | ||
| 186 | - } | ||
| 187 | - if (this.nextRoundTimerId) { | ||
| 188 | - clearTimeout(this.nextRoundTimerId); | ||
| 189 | - } | ||
| 190 | - this.room.broadcast("finishRound", { answer: this.word }); | ||
| 191 | - this.finishGame(); | ||
| 192 | - } | ||
| 193 | - | ||
| 194 | - private acceptAnswer(user: User): void { | ||
| 195 | - user.connection.send("answerAccepted", { answer: this.word }); | ||
| 196 | - this.changeRole(user, "winner"); | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - private pickDrawer(): User { | ||
| 200 | - const candidates = this.room.users.filter((user) => user !== this.drawer); | ||
| 201 | - return candidates[Math.floor(Math.random() * candidates.length)]; | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | - private pickWords(): string[] { | ||
| 205 | - return ["장난감", "백화점", "파티"]; | ||
| 206 | - } | ||
| 207 | - | ||
| 208 | - private startTimer(timeLeftMillis: number): void { | ||
| 209 | - this.timer = { | ||
| 210 | - startTimeMillis: Date.now(), | ||
| 211 | - timeLeftMillis, | ||
| 212 | - running: true, | ||
| 213 | - }; | ||
| 214 | - } | ||
| 215 | - | ||
| 216 | - private stopTimer(): void { | ||
| 217 | - this.timer = { | ||
| 218 | - ...this.timer, | ||
| 219 | - running: false, | ||
| 220 | - }; | ||
| 221 | - this.room.users.forEach((user) => this.sendTimer(user)); | ||
| 222 | - } | ||
| 223 | - | ||
| 224 | - private sendTimer(user: User): void { | ||
| 225 | - user.connection.send("timer", { | ||
| 226 | - state: this.timer.running ? "started" : "stopped", | ||
| 227 | - time: Math.max( | ||
| 228 | - (this.timer.startTimeMillis + this.timer.timeLeftMillis - Date.now()) / | ||
| 229 | - 1000, | ||
| 230 | - 0 | ||
| 231 | - ), | ||
| 232 | - }); | ||
| 233 | - } | ||
| 234 | - | ||
| 235 | - private makeRoleArray(): { username: string; role: Role }[] { | ||
| 236 | - let roleArray: { | ||
| 237 | - username: string; | ||
| 238 | - role: Role; | ||
| 239 | - }[] = []; | ||
| 240 | - this.roles.forEach((role, user) => | ||
| 241 | - roleArray.push({ username: user.username, role: role }) | ||
| 242 | - ); | ||
| 243 | - return roleArray; | ||
| 244 | - } | ||
| 245 | - | ||
| 246 | - private changeRole(user: User, role: Role) { | ||
| 247 | - this.roles.set(user, role); | ||
| 248 | - this.room.broadcast("role", { username: user.username, role }); | ||
| 249 | - } | ||
| 250 | - | ||
| 251 | - join(user: User): void { | ||
| 252 | - this.changeRole(user, "spectator"); | ||
| 253 | - this.sendTimer(user); | ||
| 254 | - user.connection.send("startRound", { | ||
| 255 | - round: this.round, | ||
| 256 | - duration: this.roundDuration, | ||
| 257 | - roles: this.makeRoleArray(), | ||
| 258 | - }); | ||
| 259 | - if (this.roundState === "done") { | ||
| 260 | - user.connection.send("finishRound", { | ||
| 261 | - answer: this.word, | ||
| 262 | - }); | ||
| 263 | - } | ||
| 264 | - user.connection.send("setBrush", { | ||
| 265 | - size: this.brush.size, | ||
| 266 | - color: this.brush.color, | ||
| 267 | - drawing: this.brush.drawing, | ||
| 268 | - }); | ||
| 269 | - user.connection.send("moveBrush", { | ||
| 270 | - x: this.brush.x, | ||
| 271 | - y: this.brush.y, | ||
| 272 | - }); | ||
| 273 | - } | ||
| 274 | - | ||
| 275 | - leave(user: User): void { | ||
| 276 | - if (this.room.users.length < 2) { | ||
| 277 | - this.forceFinishGame(); | ||
| 278 | - return; | ||
| 279 | - } | ||
| 280 | - | ||
| 281 | - this.roles.delete(user); | ||
| 282 | - | ||
| 283 | - if (user === this.drawer) { | ||
| 284 | - if (this.roundState === "choosing") { | ||
| 285 | - this.round--; // 이번 라운드를 다시 시작 | ||
| 286 | - this.startNextRound(); | ||
| 287 | - } else if (this.roundState === "running") { | ||
| 288 | - this.finishRound(); | ||
| 289 | - } | ||
| 290 | - } else { | ||
| 291 | - let guesserCount = 0; | ||
| 292 | - this.roles.forEach((role, user) => { | ||
| 293 | - if (role === "guesser") { | ||
| 294 | - guesserCount++; | ||
| 295 | - } | ||
| 296 | - }); | ||
| 297 | - if (guesserCount < 1) { | ||
| 298 | - if (this.roundState === "choosing") { | ||
| 299 | - this.round--; | ||
| 300 | - this.startNextRound(); | ||
| 301 | - } else if (this.roundState === "running") { | ||
| 302 | - this.finishRound(); | ||
| 303 | - } | ||
| 304 | - } | ||
| 305 | - } | ||
| 306 | - } | ||
| 307 | -} |
| ... | @@ -16,7 +16,7 @@ | ... | @@ -16,7 +16,7 @@ |
| 16 | }, | 16 | }, |
| 17 | "scripts": { | 17 | "scripts": { |
| 18 | "start": "nodemon index.ts", | 18 | "start": "nodemon index.ts", |
| 19 | - "test": "nyc mocha -r ts-node/register ./**/*.test.ts", | 19 | + "test": "nyc mocha -r ts-node/register --timeout 8000 ./**/*.test.ts", |
| 20 | "build": "tsc -b -v" | 20 | "build": "tsc -b -v" |
| 21 | }, | 21 | }, |
| 22 | "devDependencies": { | 22 | "devDependencies": { | ... | ... |
| ... | @@ -10,7 +10,6 @@ import { | ... | @@ -10,7 +10,6 @@ import { |
| 10 | import { RoomDescription, RoomInfo, UserData } from "../../common/dataType"; | 10 | import { RoomDescription, RoomInfo, UserData } from "../../common/dataType"; |
| 11 | import { RoomManager } from "./RoomManager"; | 11 | import { RoomManager } from "./RoomManager"; |
| 12 | import { Game } from "../game/Game"; | 12 | import { Game } from "../game/Game"; |
| 13 | -import { WorldGuessingGame } from "../game/WordGuessingGame"; | ||
| 14 | 13 | ||
| 15 | export class Room { | 14 | export class Room { |
| 16 | public readonly uuid: string; | 15 | public readonly uuid: string; |
| ... | @@ -72,7 +71,23 @@ export class Room { | ... | @@ -72,7 +71,23 @@ export class Room { |
| 72 | if (!result.ok) { | 71 | if (!result.ok) { |
| 73 | return result; | 72 | return result; |
| 74 | } | 73 | } |
| 75 | - this.startGame(); | 74 | + |
| 75 | + // TODO: 방장이 따로 메세지를 보내 설정할 수 있도록 수정해주세요. | ||
| 76 | + const settings = message; | ||
| 77 | + if (!settings.maxRound) { | ||
| 78 | + settings.maxRound = 5; | ||
| 79 | + } | ||
| 80 | + if (!settings.roundDuration) { | ||
| 81 | + settings.roundDuration = 60; | ||
| 82 | + } | ||
| 83 | + if (!settings.roundTerm) { | ||
| 84 | + settings.roundTerm = 5; | ||
| 85 | + } | ||
| 86 | + this.startGame( | ||
| 87 | + settings.maxRound, | ||
| 88 | + settings.roundDuration, | ||
| 89 | + settings.roundTerm | ||
| 90 | + ); | ||
| 76 | return { ok: true }; | 91 | return { ok: true }; |
| 77 | }, | 92 | }, |
| 78 | }); | 93 | }); |
| ... | @@ -179,8 +194,12 @@ export class Room { | ... | @@ -179,8 +194,12 @@ export class Room { |
| 179 | return { ok: true }; | 194 | return { ok: true }; |
| 180 | } | 195 | } |
| 181 | 196 | ||
| 182 | - private startGame(): void { | 197 | + private startGame( |
| 183 | - this.game = new WorldGuessingGame(this); | 198 | + maxRound: number, |
| 199 | + roundDuration: number, | ||
| 200 | + roundTerm: number | ||
| 201 | + ): void { | ||
| 202 | + this.game = new Game(this, maxRound, roundDuration, roundTerm); | ||
| 184 | } | 203 | } |
| 185 | 204 | ||
| 186 | public finishGame(): void { | 205 | public finishGame(): void { | ... | ... |
-
Please register or login to post a comment