강동현

disconnect 핸들링, 게임 버그 수정

......@@ -25,6 +25,7 @@ export class Connection {
this.socket = socket;
this.roomManager = roomManager;
socket.setHandler((raw) => this.handleRaw(raw));
socket.setDisconnectHandler(() => this.handleDisconnect());
}
public send<T extends ServerOutboundMessageKey>(
......@@ -81,4 +82,8 @@ export class Connection {
return { ok: true };
}
public handleDisconnect(): void {
this.user?.disconnected();
}
}
......
......@@ -3,6 +3,7 @@ import { RawMessage, ServerResponse } from "../../common";
export interface SocketWrapper {
setHandler: (listener: (raw: RawMessage) => ServerResponse<any>) => void;
setDisconnectHandler: (listener: () => void) => void;
send: (raw: RawMessage) => void;
}
......@@ -19,6 +20,12 @@ export class SocketIoWrapper implements SocketWrapper {
});
}
public setDisconnectHandler(listener: () => void) {
this.socketIo.on("disconnect", () => {
listener();
});
}
public send(raw: RawMessage) {
this.socketIo.emit("msg", raw);
}
......
......@@ -11,7 +11,7 @@ export class Game {
roundDuration: number;
readonly roundTerm: number = 5; // 다음 라운드 시작까지 기다리는 시간
wordCandidates: string[] = [];
word: string = "";
word?: string;
timer: {
startTimeMillis: number;
timeLeftMillis: number;
......@@ -66,7 +66,11 @@ export class Game {
},
chat: (user, message) => {
const text = message.message.trim();
if (this.roles.get(user) === "guesser" && text === this.word) {
if (
this.roles.get(user) === "guesser" &&
this.roundState === "running" &&
text === this.word
) {
this.acceptAnswer(user);
} else {
this.room.sendChat(user, text);
......@@ -122,6 +126,7 @@ export class Game {
private startNextRound(): void {
this.roundState = "choosing";
this.word = undefined;
this.round++;
this.roles.clear();
......@@ -164,7 +169,9 @@ export class Game {
this.stopTimer();
this.room.broadcast("finishRound", { answer: this.word });
if (this.word) {
this.room.broadcast("finishRound", { answer: this.word });
}
this.prepareNextRound();
}
......@@ -192,12 +199,15 @@ export class Game {
if (this.nextRoundTimerId) {
clearTimeout(this.nextRoundTimerId);
}
this.room.broadcast("finishRound", { answer: this.word });
if (this.word) {
this.room.broadcast("finishRound", { answer: this.word });
}
this.finishGame();
}
private acceptAnswer(user: User): void {
user.connection.send("answerAccepted", { answer: this.word });
user.connection.send("answerAccepted", { answer: this.word! });
this.changeRole(user, "winner");
let noGuesser = true;
......@@ -265,7 +275,7 @@ export class Game {
this.room.broadcast("role", { username: user.username, role });
}
join(user: User): void {
joined(user: User): void {
this.changeRole(user, "spectator");
this.sendTimer(user);
user.connection.send("startRound", {
......@@ -273,7 +283,7 @@ export class Game {
duration: this.roundDuration,
roles: this.makeRoleArray(),
});
if (this.roundState === "done") {
if (this.roundState === "done" && this.word) {
user.connection.send("finishRound", {
answer: this.word,
});
......@@ -289,7 +299,7 @@ export class Game {
});
}
leave(user: User): void {
left(user: User): void {
if (this.room.users.length < 2) {
this.forceFinishGame();
return;
......
......@@ -122,6 +122,8 @@ export class Room {
this.usersReady = this.usersReady.filter((u) => u !== user);
user.room = undefined;
this.game?.left(user);
this.broadcast("updateRoomUser", {
state: "removed",
user: {
......
......@@ -109,7 +109,7 @@ describe("라운드", () => {
guesserSockets[0].disconnect();
drawerSocket.socket.notReceived("finishRound");
guesserSockets[1].disconnect();
drawerSocket.socket.received("finishRound");
// 단어가 선택되지 않았으므로 finishRound가 수신되지 않습니다.
drawerSocket.socket.received("finishGame");
});
it("drawer가 단어를 선택하고 모든 guesser가 나가면 인원이 부족하므로 게임이 종료됩니다", () => {
......@@ -143,6 +143,7 @@ describe("라운드", () => {
const word = drawerSocket.socket.received("wordSet").words[0];
drawerSocket.testOk("chooseWord", { word });
guesserSockets[0].testOk("chat", { message: word });
guesserSockets[1].testOk("chat", { message: word });
guesserSockets[0].socket.received("finishRound");
guesserSockets[0].socket.notReceived("startRound");
......
......@@ -15,19 +15,8 @@ describe("라운드 브러시 설정", () => {
brushSettings
);
});
it("올바르지 않은 브러시 설정은 허용되지 않습니다", () => {
it("올바르지 않은 브러시 색상은 허용되지 않습니다", () => {
const { drawerSocket } = prepareGame(2);
drawerSocket.testNotOk("setBrush", {
size: 0,
color: "000000",
drawing: true,
});
drawerSocket.testNotOk("setBrush", {
size: 100,
color: "000000",
drawing: true,
});
drawerSocket.testNotOk("setBrush", {
size: 1,
color: "000",
......@@ -39,6 +28,21 @@ describe("라운드 브러시 설정", () => {
drawing: true,
});
});
it("올바르지 않은 브러시 사이즈는 Clamp 됩니다", () => {
const { drawerSocket, guesserSockets } = prepareGame(2);
drawerSocket.testOk("setBrush", {
size: 0,
color: "000000",
drawing: true,
});
expect(guesserSockets[0].socket.received("setBrush").size).eq(1);
drawerSocket.testOk("setBrush", {
size: 100,
color: "000000",
drawing: true,
});
expect(guesserSockets[0].socket.received("setBrush").size).eq(64);
});
it("drawer가 아닌 다른 사람들은 브러시를 설정할 수 없습니다", () => {
const { guesserSockets } = prepareGame(2);
......
......@@ -9,11 +9,12 @@ import { SocketWrapper } from "../../connection/SocketWrapper";
export class DummySocket implements SocketWrapper {
public handler?: (raw: RawMessage) => ServerResponse<any>;
public disconnectHandler?: () => void;
public receivedMessages: RawMessage[] = [];
public setHandler(handler: (raw: RawMessage) => ServerResponse<any>) {
this.handler = handler;
}
public setHandler(handler: (raw: RawMessage) => ServerResponse<any>) {}
public setDisconnectHandler(handler: () => void) {}
public send(raw: RawMessage): void {
this.receivedMessages.push(raw);
......
......@@ -55,6 +55,6 @@ export class SocketTester {
}
public disconnect(): void {
// TODO
this.connection.handleDisconnect();
}
}
......
......@@ -34,4 +34,8 @@ export class User {
},
});
}
public disconnected(): void {
this.room?.disconnect(this);
}
}
......