Përshëndetje të gjithëve.
Këtë pranverë kam gjetur një projekt ku ekipi mësoi të iniciojë një server Dota 2 të versionit 2014 dhe, për pasojë, të luajë në të. Jam një adhurues i madh i kësaj loje dhe nuk mund të lija pas një mundësie unike për të përjetuar sërish fëmijërinë time.
Jam futur shumë thellë dhe përfundimisht krijova një bot për Discord, i cili përbën praktikisht të gjitha funksionalitetet që nuk mbështeten në versionin e vjetër të lojës, veçanërisht matchmaking-un.
Para të gjitha ndryshimeve me botin, lobby-t krijoheshin manualisht. Në grup mblidhnim 10 reagime në mesazh dhe krijonim manualisht serverin, ose mikpritnim një lobby lokal.

Natyrshëm si programues, nuk mund të përballoja aq shumë punë manuale, kështu që brenda natës shqipërisha versionin më të thjeshtë të botit që automatikisht ngrinte serverin kur mblidheshin 10 persona.
Vendosa të shkruaj direkt në nodejs, sepse nuk e preferoj shumë Python, dhe ndjej më shumë komoditet në këtë mjedis.
Ky është eksperienca ime e parë në shkruajtjen e një boti për Discord, por rezultoi se ishte shumë e lehtë. Moduli zyrtar npm discord.js ofron një ndërfaqe të përshtatshme për të punuar me mesazhet, mbledhjen e reagimeve etj.
Disklejmer: të gjitha shembujt e kodit janë 'aktualë', domethënë janë kaluar nëpër disa iteracione riprogramimi natën.
Baza e matchmaking-ut është 'rendi', ku vendosen lojtarët që duan të luajnë dhe hiqen kur nuk duan më ose gjejnë një lojë.
Kështu duket entiteti 'lojtar'. Në fillim ishte thjesht id e përdoruesit në Discord, por në planet janë një launcher/faqja e lojës nga website-i, por do flasim për këtë më vonë.
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);
}
}
Ja një ndërfaqe e rendit. Këtu, në vend të 'lojtarëve', përdoret një abstraksion në formën e 'grupit'. Për një lojtar të vetëm, grupi përbëhet nga vetë ai, ndërsa për lojtarët në grup, përkatësisht nga të gjithë lojtarët e grupit.
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
}
Vendosa të përdor ngjarje për shkëmbyesin e kontekstit. Ky ishte i përshtatshëm për rastet — për ngjarjen 'lojë e gjetur për 10 persona', mund të dërgoj mesazhin e nevojshëm në mesazhe private dhe të ekzekutoj logjikën kryesore të biznesit — të nis një detyrë për të verifikuar gatishmërinë, të përgatis një lobby për nisje etj.
Për IOC po përdor InversifyJS. Kam një përvojë të këndshme në punën me këtë bibliotekë. Shpejt dhe thjesht!
Kemi disa radhë në server — janë shtuar modet 1x1, normale/sa me pikë, dhe disa custom games. Prandaj, kemi një singleton RoomService që ndodhet mes përdoruesit dhe kërkimit të lojës.
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)
}
});
}
);
}
(Një copë kod për të ilustruar se si duken proceset)
Këtu unë inicijoj radhën për çdo njëri nga modet e lojës që janë realizuar, si dhe dëgjoj ndryshimet e 'grupeve', për të rregulluar radhët dhe për të shmanguar disa konflikte.
Pra, unë bëmë një punë të mirë, kam futur pjesë kodi që nuk kanë lidhje me temën, tani do kalojmë drejtpërdrejt në matchmaking.
Le të shqyrtojmë rastin:
1) Përdoruesi dëshiron të luajë.
2) Për të filluar kërkimin, ai përdor Gateway=Discord, dmth, reagon në një mesazh:

3) Ky gateway shkonte në RoomService dhe thoshte 'Përdoruesi nga Discord dëshiron të hyjë në radhë, moda: lojë jo me pikë'.
4) RoomService pranon kërkesën e gateway-it dhe shton përdoruesin në radhën e duhur (sa më saktë, grupin e përdoruesit).
5) Rradha kontrollon në çdo ndryshim nëse ka mjaft lojtarë për të luajtur. Nëse është kështu — lëshojmë ngjarjen:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService, me siguri, pret me gëzim çdo radhë në pritje të kësaj ngjarjeje. Në hyrje marrim listën e lojtarëve, formojmë një 'dhomë' virtuale dhe, sigurisht, lëshojmë ngjarjen:
queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log(
`Modaliteti të gjetur: [${mode}]. Është koha të gjejmë një dhomë të lirë për këta djem`
);
const room = this.getFreeRoom(mode);
room.fill(event.players);
this.onRoomFormed(room);
});
7) Këtu jemi në instancën "më të lartë" - klasa Bot. Në përgjithësi, ai merret me lidhjen midis portave (sa qesharake duket kjo në rusisht nuk e di) dhe logjikës biznesore të ndeshjeve. Bot-i dëgjon ngjarjen dhe urdhëron DiscordGateway të dërgojë një kontroll mendimi për gatishmërinë tek të gjithë përdoruesit.

8) Nëse dikush e refuzon ose nuk e pranon lojën brenda 3 minutash, ne nuk i kthejmë ata në radhë. Të gjithë të tjerët kthehen në radhë dhe presim derisa përsëri të mblidhen 10 persona. Nëse të gjithë lojtarët e pranojnë lojën, fillon pjesa interessante.
Konfigurimi i serverit të dedikuar
Lojrat tona organizohen në VDS me Windows Server 2012. Nga kjo mund të nxjerrim disa përfundime:
- Nuk ka Docker, gjë që më preku thellë
- Ne kursim në qira
Kemi një detyrë: të ekzekutojmë një proces në VDS nga VPS në Linux. Kam shkruar një server të thjeshtë në Flask. Po, nuk e dua Python-in, por çfarë të bëj - ta shkruaj këtë server është më e shpejtë dhe më e lehtë.
Ai ka 3 funksione:
- Ekzekutimi i serverit me konfigurimin - zgjedhja e hartës, numri i lojtarëve për fillimin e lojës dhe grupi i shtojcave. Për shtojcat tani nuk do flas - është një histori e veçantë me litra kafe gjatë natës përziera me lot dhe flokë të hequra.
- Ndërprerja/rinisja e serverit në rast të lidhjeve të dështuara, të cilat mund t’i trajtojmë vetëm manualisht.
Këtu gjithçka është e thjeshtë, shembujt e kodit madje nuk janë të përshtatshëm. Një skenar me 100 rreshta
Pra, kur 10 njerëz mblidhen dhe pranojnë lojën, serveri është aktivizuar dhe të gjithë presin të luajnë, një lidhje për të hyrë në lojë dërgohet në mesazhet private.

Me një klikim të lidhjes, lojtari lidhet me serverin e lojës dhe gjithçka vjen vetë. Pas ~25 minutash, "dhoma" virtuale me lojtarët zbrazet.
Më falni për paqartësinë e artikullit, nuk kam shkruar këtu për një kohë, dhe ka shumë kod për të shkëputur pjesët e rëndësishme. Një tufë, më së shumti.
Nëse shoh interes për temën, atëherë do të jetë një pjesë e dytë - do të jenë mundimet e mia me shtojcat për srcds (Serveri i dedikuar për Source), dhe ndoshta sistemi i rangut dhe mini-dotabuff, një site për statistikat e lojërave.
Disa lidhje:
Burimi: habr.com
