Përshëndetje të gjithëve.
Këtë pranverë u ndesha me një projekt, ku djemtë mësuan të nisin serverin Dota 2 version 2014 dhe, për rrjedhojë, të luajnë mbi të. Unë jam një adhurues i madh i kësaj loje dhe nuk mund të kaloj pa këtë mundësi unike për të përjetuar fëmijërinë time.
U zhyt shumë thellë, dhe kështu ndodhi që shkrova një bot për Discord, e cila menjëherë përfundon pothuajse të gjithë funksionalitetin, i cili nuk mbështetet në versionin e vjetër të lojës, atëherë matchmaking.
Para gjithë risive me botin, lobit krijohej me dorë. Mblidhja 10 reagime në mesazh dhe grumbulloja serverin me dorë, ose hostoja një lob lokal.

Natyra ime programatorit nuk e përballonte një numër të tillë pune manuale, dhe gjatë natës shkrova versionin më të thjeshtë të botit, i cili automatikisht ngriste serverin kur mblidheshin 10 persona.
Vendosa të shkruaj menjëherë në nodejs, sepse nuk e pëlqej shumë pythonin, dhe gjithashtu ndihesha më rehat në këtë mjedis.
Kjo është përvoja ime e parë në shkruajtjen e një boti për Discord, por rezultoi gjithçka mjaft e thjeshtë. Moduli zyrtar npm discord.js ofron një ndërfaqe të përshtatshme për të punuar me mesazhe, mbledhjen e reagimeve etj.
Diskleimi: të gjitha shembujt e kodit janë "aktualë", dmth kanë kaluar disa iteracione përmirësimi gjatë natës.
Thelbi i matchmaking-ut është "radhë", ku vendosen lojtarët që duan të luajnë dhe largohen kur nuk dëshirojnë më ose gjejnë një lojë.
Kështu duket entiteti "lojtar". Fillimisht ishte thjesht id e përdoruesit në Discord, por në planet janë lançuesi / kërkimi i lojës nga uebsajti, por për gjithçka në rend.
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 ndërfaqja e radhës. Këtu përdoret një abstraksion në formën e "grupit" në vend të "lojtarëve". 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ësh ngjarjet për të ndarë kontekstin. Ishte e përshtatshme për rastet - me ngjarjen "lojë e gjetur për 10 persona", mund të dërgosh një mesazh të veçantë në mesazhet private të lojtarëve, dhe të kryesh logjikën e biznesit kryesore - të fillosh një detyrë për të kontrolluar gatishmërinë, të përgatisësh lobin për lançim dhe kështu me radhë.
Për IOC unë përdor InversifyJS. Kam përvojë të këndshme me këtë bibliotekë. Shpejt dhe lehtë!
Në serverin tonë ka disa radhë - janë shtuar modet 1x1, normale/rank, dhe disa personalizime. Prandaj kemi një RoomService singleton, i cili ndodhet ndërmjet 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))) {
// nëse radhë ka këtë grup, ne e ripërdorim grupin
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))) {
// nëse radhë ka këtë grup, ne e hiqim grupin
q.remove(event.party)
}
});
}
);
}
(Një shpjegim i kodit për të ilustruar si duken proceset)
Këtu e inisializoj radhën për secilën nga modet e realizuara të lojës, dhe gjithashtu ndjek ndryshimet e "grupeve", për të rregulluar radhët dhe për të shmangur disa konflikte.
Pra, jam i mirë, kam shtuar fragmente kodi që nuk kanë lidhje me temën, tani kalojmë 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, domethënë vendos një reagim në mesazh:

3) Ky gateway shkon në RoomService, dhe thotë "Përdoruesi nga Discord dëshiron të hyjë në radhë, moda: lojë jo ranking."
4) RoomService pranon kërkesën e gateway-it, dhe e shtyn përdoruesin në radhën e duhur (në fakt, grupin e përdoruesit).
5) Radhë kontrollon për çdo ndryshim nëse ka mjaft lojtarë për të luajtur. Nëse është e mundur - emetojmë ngjarjen:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService, me sigurt se dëshiron me paduruar çdo radhë në pritje të këtij ngjarjeje. Kemi një listë lojtarësh, formojmë një "dhomë" virtuale prej tyre, dhe sigurisht që emitojmë ngjarjen:
queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log(
`Modaliteti i dhomës u gjet: [${mode}]. Koha për të marrë dhomën falas për këta njerëz`
);
const room = this.getFreeRoom(mode);
room.fill(event.players);
this.onRoomFormed(room);
});
7) Ja ku arritëm në instancën e "lartë" — klasën Bot. Në përgjithësi, ai merret me lidhjen midis portave (sa qesharake duket kjo në shqip, nuk mund ta besoj) dhe logjikës së biznesit të matchmaking. Boti përgjon ngjarjen dhe urdhëron DiscordGateway të dërgojë një kontroll për gatishmëri të gjithëve përdoruesve.

8) Nëse dikush e refuzon ose nuk pranon lojën brenda 3 minutave, ne nuk i kthejmë ata në radhë. Të gjithë të tjerët kthehen në radhë dhe presim derisa të mblidhen përsëri 10 njerëz. Nëse të gjithë lojtarët pranojnë lojën, fillon pjesa interesante.
Konfigurimi i serverit të dedikuar
Ne organizojmë lojrat në VDS me Windows server 2012. Nga kjo mund të nxjerrim disa përfundime:
- Nuk ka docker për të, që më goditi në zemër
- Ne po kursejmë në qira
Kemi një detyrë: të nisim një proces në VDS nga VPS në Linux. Kam shkruar një server të thjeshtë në Flask. Po, nuk e dua Python, por çfarë të bëj — ta shkruaj këtë server është më shpejt dhe më lehtë me të.
Ai realizon 3 funksione:
- Nisjen e serverit me konfigurimin — zgjedhjen e hartës, numrin e lojtarëve për fillimin e lojës, dhe grupin e pluginave. Për pluginat nuk do të flas tani — kjo është një histori e veçantë me litra kafe gjatë natës së përzier me lot dhe flokë të shkëputur.
- Ndalesa/ripërtritja e serverit në rast të lidhjeve të dështuara, të cilat mund t'i trajtojmë vetëm manualisht.
Këtu është gjithçka e thjeshtë, shembujt e kodit janë të papërshtatshëm. Skripti ka 100 rreshta
Pra, kur 10 njerëz janë mbledhur së bashku dhe kanë pranuar lojën, është nisur serveri dhe të gjithë janë të etur për të luajtur, në mesazhet personale vjen një lidhje për t'u lidhur me lojën.

Me klikimin e lidhjes, lojtari lidhet me serverin e lojës, dhe më pas gjithçka e bëjnë vetë. Pjesa virtuale me lojtarët pas ~25 minutash pastron.
Më falni paraprakisht për ngatërresat në artikull, s'kam shkruar këtu për një kohë të gjatë, dhe ka shumë kod që të nxjerr të rëndësishmet. Një grumbull, në të vërtetë.
Nëse shoh interes për temën, atëherë do të ketë një pjesë të dytë — aty do të jenë mundimet e mia me plugins për srcds (Server i dedikuar për burim), dhe ndoshta, sistemi i renditjes dhe mini-dotabuff, një faqe për statistikat e lojrave.
Disa lidhje:
Burimi: habr.com
