์๋ ๋ชจ๋
์ด๋ฒ ๋ด์ ์ ๋ ์ฌ๋๋ค์ด Dota 2 ์๋ฒ ๋ฒ์ 2014๋ฅผ ์คํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๊ณ ์ด์ ๋ฐ๋ผ ํ๋ ์ดํ๋ ํ๋ก์ ํธ๋ฅผ ์ ํ์ต๋๋ค. ๋๋ ์ด ๊ฒ์์ ์ด๋ ฌํ ํฌ์ด๊ณ , ์ด๋ฆฐ ์์ ์ ๋ชฐ์ ํ ์ ์๋ ์ด ๋ ํนํ ๊ธฐํ๋ฅผ ๋์น ์ ์์์ต๋๋ค.
์ ๋ ๋งค์ฐ ๊น์ด ํ๊ณ ๋ค์๊ณ , ์ด์ ๋ฒ์ ์ ๊ฒ์์์ ์ง์๋์ง ์๋ ๊ฑฐ์ ๋ชจ๋ ๊ธฐ๋ฅ, ์ฆ ๋งค์น๋ฉ์ดํน์ ๋ด๋นํ๋ Discord ๋ด์ ์์ฑํ๊ฒ ๋์์ต๋๋ค.
๋ด์ ํตํ ๋ชจ๋ ํ์ ์ด์ ์๋ ๋ก๋น๊ฐ ์๋์ผ๋ก ์์ฑ๋์์ต๋๋ค. ๋ฉ์์ง์ ๋ํ 10๊ฐ์ ๋ฐ์์ ์์งํ๊ณ ์๋์ผ๋ก ์๋ฒ๋ฅผ ๊ตฌ์ฑํ๊ฑฐ๋ ๋ก์ปฌ ๋ก๋น๋ฅผ ํธ์คํ
ํ์ต๋๋ค.
ํ๋ก๊ทธ๋๋จธ๋ก์์ ๋ด ์ฑ๊ฒฉ์ ๊ทธ๋ ๊ฒ ๋ง์ ์์์
์ ๊ฒฌ๋ ์ ์์๊ธฐ ๋๋ฌธ์ ํ๋ฃป๋ฐค ์ฌ์ด์ 10๋ช
์ด ๋๋ฉด ์๋์ผ๋ก ์๋ฒ๋ฅผ ์ฌ๋ฆฌ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฒ์ ์ ๋ด์ ์ค์ผ์นํ์ต๋๋ค.
๋๋ Python์ ๋ณ๋ก ์ข์ํ์ง ์๊ณ ์ด ํ๊ฒฝ์ด ๋ ํธ์ํ๊ธฐ ๋๋ฌธ์ ์ฆ์ nodejs๋ก ์์ฑํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
Discord์ฉ ๋ด์ ์์ฑํ๋ ๊ฒ์ ์ด๋ฒ์ด ์ฒ์์ด์ง๋ง ๋งค์ฐ ๊ฐ๋จํ๋ค๋ ๊ฒ์ด ๋ฐํ์ก์ต๋๋ค. ๊ณต์ npm ๋ชจ๋ discord.js๋ ๋ฉ์์ง ์์ , ๋ฐ์ ์์ง ๋ฑ์ ์ํ ํธ๋ฆฌํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฉด์ฑ ์กฐํญ: ๋ชจ๋ ์ฝ๋ ์์ ๋ "ํ์ฌ"์ ๋๋ค. ์ฆ, ๋ฐค์ ์ฌ๋ฌ ๋ฒ ์ฌ์์ฑ์ ๊ฑฐ์ณค์์ ์๋ฏธํฉ๋๋ค.
๋งค์น๋ฉ์ดํน์ ๊ธฐ๋ณธ์ ํ๋ ์ด๋ฅผ ์ํ๋ ํ๋ ์ด์ด๊ฐ ๊ฒ์์ ์ํ์ง ์๊ฑฐ๋ ๊ฒ์์ ์ฐพ์ง ์์ ๋ ๋ฐฐ์น๋๊ณ ์ ๊ฑฐ๋๋ "๋๊ธฐ์ด"์ ๋๋ค.
์ด๊ฒ์ด ๋ฐ๋ก 'ํ๋ ์ด์ด'์ ๋ณธ์ง์ ๋๋ค. ์ฒ์์๋ Discord์ ์ฌ์ฉ์ ID์์ง๋ง ์ฌ์ดํธ์์ ๊ฒ์์ ์์/๊ฒ์ํ ๊ณํ์ด ์์ง๋ง ๋จผ์ ํด์ผ ํ ์ผ์ด ์์ต๋๋ค.
export enum Realm {
DISCORD,
EXTERNAL,
}
export default class QueuePlayer {
constructor(public readonly realm: Realm, public readonly id: string) {}
public is(qp: QueuePlayer): boolean {
return this.realm === qp.realm && this.id === qp.id;
}
static Discord(id: string) {
return new QueuePlayer(Realm.DISCORD, id);
}
static External(id: string) {
return new QueuePlayer(Realm.EXTERNAL, id);
}
}
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ ๋๊ธฐ์ด ์ธํฐํ์ด์ค๊ฐ ์์ต๋๋ค. ์ฌ๊ธฐ์๋ "ํ๋ ์ด์ด" ๋์ "๊ทธ๋ฃน" ํํ์ ์ถ์ํ๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋จ์ผ ํ๋ ์ด์ด์ ๊ฒฝ์ฐ ๊ทธ๋ฃน์ ๊ทธ๋ฃน์ ๋ชจ๋ ํ๋ ์ด์ด๋ก ๊ตฌ์ฑ๋๊ณ ๊ทธ๋ฃน์ ํ๋ ์ด์ด๋ ๊ฐ๊ฐ ๊ทธ๋ฃน์ ๋ชจ๋ ํ๋ ์ด์ด๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
export default interface IQueue extends EventEmitter {
inQueue: QueuePlayer[]
put(uid: Party): boolean;
remove(uid: Party): boolean;
removeAll(ids: Party[]): void;
mode: MatchmakingMode
roomSize: number;
clear(): void
}
๋๋ ์ํฉ์ ๊ตํํ๊ธฐ ์ํด ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ์ฌ๋ก์ ์ ํฉํ์ต๋๋ค. "10์ธ์ฉ ๊ฒ์์ด ๋ฐ๊ฒฌ๋์์ต๋๋ค"๋ผ๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ๊ฐ์ธ ๋ฉ์์ง๋ก ํ๋ ์ด์ด์๊ฒ ํ์ํ ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ์ ์๊ณ ๊ธฐ๋ณธ์ ์ธ ๋น์ฆ๋์ค ๋ก์ง์ ์ํํ ์ ์์ต๋๋ค. ์ค๋น ์ํ๋ฅผ ํ์ธํ๋ ์์ ์ ์์ํ๊ณ ๋ก๋น๋ฅผ ์ค๋นํฉ๋๋ค. ์ถ์ ๋ฑ์ ์ํด.
IOC์ ๊ฒฝ์ฐ InversifyJS๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋๋ ์ด ๋์๊ด์์ ์ผํ๋ฉด์ ์ฆ๊ฑฐ์ด ๊ฒฝํ์ ํ์ต๋๋ค. ๋น ๋ฅด๊ณ ์ฝ๊ฒ!
์ฐ๋ฆฌ ์๋ฒ์๋ ์ฌ๋ฌ ๋๊ธฐ์ด์ด ์์ต๋๋ค. 1x1, ์ผ๋ฐ/๋ฑ๊ธ ๋ฐ ๋ช ๊ฐ์ง ์ฌ์ฉ์ ์ ์ ๋ชจ๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์์ ๊ฒ์ ๊ฒ์ ์ฌ์ด์ ์ฑ๊ธํค RoomService๊ฐ ์์ต๋๋ค.
constructor(
@inject(GameServers) private gameServers: GameServers,
@inject(MatchStatsService) private stats: MatchStatsService,
@inject(PartyService) private partyService: PartyService
) {
super();
this.initQueue(MatchmakingMode.RANKED);
this.initQueue(MatchmakingMode.UNRANKED);
this.initQueue(MatchmakingMode.SOLOMID);
this.initQueue(MatchmakingMode.DIRETIDE);
this.initQueue(MatchmakingMode.GREEVILING);
this.partyService.addListener(
"party-update",
(event: PartyUpdatedEvent) => {
this.queues.forEach((q) => {
if (has(q.queue, (t) => t.is(event.party))) {
// if queue has this party, we re-add party
this.leaveQueue(event.qp, q.mode)
this.enterQueue(event.qp, q.mode)
}
});
}
);
this.partyService.addListener(
"party-removed",
(event: PartyUpdatedEvent) => {
this.queues.forEach((q) => {
if (has(q.queue, (t) => t.is(event.party))) {
// if queue has this party, we re-add party
q.remove(event.party)
}
});
}
);
}
(ํ๋ก์ธ์ค๊ฐ ๋๋ต์ ์ผ๋ก ์ด๋ป๊ฒ ์๊ฒผ๋์ง์ ๋ํ ์์ด๋์ด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ๊ตญ์๋ฅผ ์ฝ๋ฉ)
์ฌ๊ธฐ์๋ ๊ตฌํ๋ ๊ฐ ๊ฒ์ ๋ชจ๋์ ๋ํ ๋๊ธฐ์ด์ ์ด๊ธฐํํ๊ณ ๋๊ธฐ์ด์ ์กฐ์ ํ๊ณ ์ผ๋ถ ์ถฉ๋์ ํผํ๊ธฐ ์ํด "๊ทธ๋ฃน"์ ๋ณ๊ฒฝ ์ฌํญ๋ ์์ ํฉ๋๋ค.
์, ์๊ณ ํ์ จ์ต๋๋ค. ์ฃผ์ ์ ๊ด๋ จ ์๋ ์ฝ๋ ์กฐ๊ฐ์ ์ฝ์ ํ๊ณ ์ด์ ๋ฐ๋ก ๋งค์น๋ฉ์ดํน์ผ๋ก ๋์ด๊ฐ๊ฒ ์ต๋๋ค.
์ฌ๋ก๋ฅผ ๊ณ ๋ คํด ๋ด ์๋ค:
1) ์ฌ์ฉ์๊ฐ ํ๋ ์ด๋ฅผ ์ํฉ๋๋ค.
2) ๊ฒ์์ ์์ํ๊ธฐ ์ํด ๊ทธ๋ Gateway=Discord๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ฆ, ๋ฉ์์ง์ ๋ํ ๋ฐ์์ ํ์ํฉ๋๋ค.
3) ์ด ๊ฒ์ดํธ์จ์ด๋ RoomService๋ก ์ด๋ํ์ฌ "Discord์ ์ฌ์ฉ์๊ฐ ๋๊ธฐ์ด์ ๋ค์ด๊ฐ๊ณ ์ถ์ดํฉ๋๋ค. ๋ชจ๋: ๋ฑ๊ธ ์์ ๊ฒ์"์ด๋ผ๊ณ ๋งํฉ๋๋ค.
4) RoomService๋ ๊ฒ์ดํธ์จ์ด์ ์์ฒญ์ ์๋ฝํ๊ณ ์ฌ์ฉ์(๋ณด๋ค ์ ํํ๊ฒ๋ ์ฌ์ฉ์ ๊ทธ๋ฃน)๋ฅผ ์ํ๋ ๋๊ธฐ์ด๋ก ํธ์ํฉ๋๋ค.
5) ๋๊ธฐ์ด์ ํ๋ ์ดํ ์ ์๋ ํ๋ ์ด์ด๊ฐ ์ถฉ๋ถํ ๋๋ง๋ค ํ์ธํฉ๋๋ค. ๊ฐ๋ฅํ๋ค๋ฉด ์ด๋ฒคํธ๋ฅผ ๋ด๋ณด๋ ๋๋ค.
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService๋ ์ด ์ด๋ฒคํธ๋ฅผ ๊ฑฑ์ ์ค๋ฝ๊ฒ ๊ธฐ๋ํ๋ฉฐ ๋ชจ๋ ๋๊ธฐ์ด์ ์ฆ๊ฒ๊ฒ ๋ฃ๊ณ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ ๋ ฅ์ผ๋ก ํ๋ ์ด์ด ๋ชฉ๋ก์ ๋ฐ๊ณ , ๊ทธ๋ค๋ก๋ถํฐ ๊ฐ์์ "๋ฐฉ"์ ํ์ฑํ๊ณ , ๋ฌผ๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐํํฉ๋๋ค.
queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log(
`Room found mode: [${mode}]. Time to get free room for these guys`
);
const room = this.getFreeRoom(mode);
room.fill(event.players);
this.onRoomFormed(room);
});
7) ๊ทธ๋์ ์ฐ๋ฆฌ๋ "์ต๊ณ "๊ถํ์ธ ํด๋์ค์ ๋๋ฌํ์ต๋๋ค. ๋ด. ์ผ๋ฐ์ ์ผ๋ก ๊ทธ๋ ๊ฒ์ดํธ์จ์ด(๋ฌ์์์ด์์๋ ์ผ๋ง๋ ์ฌ๋ฏธ์์ด ๋ณด์ด๋์ง ์ดํดํ ์ ์์ต๋๋ค)์ ์ค๋งค์ ๋น์ฆ๋์ค ๋ ผ๋ฆฌ ์ฌ์ด์ ์ฐ๊ฒฐ์ ๋ค๋ฃน๋๋ค. ๋ด์ ์ด๋ฒคํธ๋ฅผ ์ฐ์ฐํ ๋ฃ๊ณ DiscordGateway์ ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ์ค๋น ํ์ธ์ ๋ณด๋ด๋๋ก ๋ช ๋ นํฉ๋๋ค.
8) ๋๊ตฐ๊ฐ๊ฐ 3๋ถ ์ด๋ด์ ๊ฒ์์ ๊ฑฐ๋ถํ๊ฑฐ๋ ์๋ฝํ์ง ์์ผ๋ฉด ๋๊ธฐ์ด๋ก ๋์๊ฐ์ง ์์ต๋๋ค. ๋ค๋ฅธ ๋ชจ๋ ์ฌ๋์ ๋๊ธฐ์ด๋ก ๋๋๋ฆฌ๊ณ ๋ค์ 10๋ช
์ด ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ๋ชจ๋ ํ๋ ์ด์ด๊ฐ ๊ฒ์์ ์๋ฝํ๋ฉด ํฅ๋ฏธ๋ก์ด ๋ถ๋ถ์ด ์์๋ฉ๋๋ค.
์ ์ฉ ์๋ฒ ๊ตฌ์ฑ
์ฐ๋ฆฌ ๊ฒ์์ Windows Server 2012๊ฐ ์ค์น๋ VDS์์ ํธ์คํ ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ช ๊ฐ์ง ๊ฒฐ๋ก ์ ๋ด๋ฆด ์ ์์ต๋๋ค.
- ๊ฑฐ๊ธฐ์ ๋์ปค๊ฐ ์์ด์ ๋ง์์ ๋ง์์ต๋๋ค.
- ์ง์ธ๋ฅผ ์ ์ฝํฉ๋๋ค
์์ ์ Linux์ VPS์์ VDS์ ํ๋ก์ธ์ค๋ฅผ ์คํํ๋ ๊ฒ์ ๋๋ค. ์ ๋ Flask๋ก ๊ฐ๋จํ ์๋ฒ๋ฅผ ์์ฑํ์ต๋๋ค. ๋ค, ์ ๋ Python์ ์ข์ํ์ง ์์ง๋ง ์ด๋ป๊ฒ ํ ์ ์๋์? ์ด ์๋ฒ๋ฅผ Python์ผ๋ก ์์ฑํ๋ ๊ฒ์ด ๋ ๋น ๋ฅด๊ณ ์ฝ์ต๋๋ค.
์ด๋ 3๊ฐ์ง ๊ธฐ๋ฅ์ ์ํํฉ๋๋ค:
- ๊ตฌ์ฑ์ ์ฌ์ฉํ์ฌ ์๋ฒ ์์ - ์ง๋, ๊ฒ์์ ์์ํ ํ๋ ์ด์ด ์ ๋ฐ ํ๋ฌ๊ทธ์ธ ์ธํธ๋ฅผ ์ ํํฉ๋๋ค. ์ง๊ธ์ ํ๋ฌ๊ทธ์ธ์ ๋ํด ๊ธ์ ์ฐ์ง ์๊ฒ ์ต๋๋ค. ๋ฐค์ ๋๋ฌผ๊ณผ ์ฐข์ด์ง ๋จธ๋ฆฌ์นด๋ฝ์ด ์์ธ ๋ช ๋ฆฌํฐ์ ์ปคํผ์ ๋ํด์๋ ๋ค๋ฅธ ์ด์ผ๊ธฐ์ ๋๋ค.
- ์ฐ๊ฒฐ์ ์คํจํ ๊ฒฝ์ฐ ์๋ฒ๋ฅผ ์ค์ง/๋ค์ ์์ํฉ๋๋ค. ์ด๋ ์๋์ผ๋ก๋ง ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์๋ ๋ชจ๋ ๊ฒ์ด ๊ฐ๋จํ๋ฉฐ ์ฝ๋ ์์ ๋ ์ ์ ํ์ง ์์ต๋๋ค. 100์ค ์คํฌ๋ฆฝํธ
๊ทธ๋์ 10๋ช ์ด ๋ชจ์ฌ ๊ฒ์์ ์๋ฝํ์ ๋ ์๋ฒ๊ฐ ์์๋์๊ณ ๋ชจ๋๊ฐ ํ๋ ์ด์ ์ด์คํ๊ณ ๊ฒ์์ ์ฐ๊ฒฐํ ์ ์๋ ๋งํฌ๊ฐ ๋น๊ณต๊ฐ ๋ฉ์์ง๋ก ์ ์ก๋์์ต๋๋ค.
๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด ํ๋ ์ด์ด๊ฐ ๊ฒ์ ์๋ฒ์ ์ฐ๊ฒฐ๋๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ์ฝ 25๋ถ ํ์ ํ๋ ์ด์ด๊ฐ ์๋ ๊ฐ์ "๋ฐฉ"์ด ์ง์์ง๋๋ค.
๊ธฐ์ฌ๊ฐ ์ด์ํ ์ ๋ฏธ๋ฆฌ ์ฌ๊ณผ๋๋ฆฌ๋ฉฐ, ์ค๋ซ๋์ ์ฌ๊ธฐ์ ๊ธ์ ์ฐ์ง ์์์ผ๋ฉฐ, ์ค์ํ ๋ถ๋ถ์ ๊ฐ์กฐํ๊ธฐ์๋ ์ฝ๋๊ฐ ๋๋ฌด ๋ง์ต๋๋ค. ๊ฐ๋จํ ๋งํด์ ๊ตญ์.
์ฃผ์ ์ ๋ํ ๊ด์ฌ์ด ๋ณด์ด๋ฉด ๋ ๋ฒ์งธ ๋ถ๋ถ์ด ์์ ๊ฒ์ ๋๋ค. ์ฌ๊ธฐ์๋ srcds(์์ค ์ ์ฉ ์๋ฒ)์ฉ ํ๋ฌ๊ทธ์ธ๊ณผ ์๋ง๋ ๋ฑ๊ธ ์์คํ ๋ฐ ๊ฒ์ ํต๊ณ๊ฐ ์๋ ์ฌ์ดํธ์ธ ๋ฏธ๋ ๋ํ๋ฒํ๊ฐ ํฌํจ๋ ๋ด ๊ณ ํต์ด ํฌํจ๋ ๊ฒ์ ๋๋ค.
์ผ๋ถ ๋งํฌ:
๋น์ฌ ์น์ฌ์ดํธ(ํต๊ณ, ๋ฆฌ๋๋ณด๋, ์๊ท๋ชจ ๋๋ฉ ํ์ด์ง ๋ฐ ํด๋ผ์ด์ธํธ ๋ค์ด๋ก๋) ๋์ค์ฝ๋ ์๋ฒ
์ถ์ฒ : habr.com