Mēs rakstām matchmaking for Dota 2014

Sveiki visiem

Å opavasar saskāros ar projektu, kurā puiÅ”i iemācÄ«jās palaist Dota 2 servera versiju 2014 un attiecÄ«gi uz tās spēlēt. Esmu liels Ŕīs spēles cienÄ«tājs un nevarēju laist garām Å”o unikālo iespēju iegrimt bērnÄ«bā.

Es iegrimu ļoti dziļi, un sagadījās, ka uzrakstīju Discord botu, kas ir atbildīgs par gandrīz visu funkcionalitāti, kas netiek atbalstīta vecajā spēles versijā, proti, matchmaking.
Pirms visiem jauninājumiem ar robotu vestibils tika izveidots manuāli. Mēs apkopojām 10 reakcijas uz ziņojumu un manuāli izveidojām serveri vai mitinājām vietējo vestibilu.

Mēs rakstām matchmaking for Dota 2014

Mana programmētāja daba neizturēja tik lielu roku darbu, un pa nakti ieskicēju vienkārŔāko bota versiju, kas automātiski pacēla serveri, kad bija 10 cilvēki.

Es uzreiz nolēmu rakstÄ«t nodejs, jo man Ä«sti nepatÄ«k Python, un es jÅ«tos ērtāk Å”ajā vidē.

Å Ä« ir mana pirmā pieredze, rakstot robotu priekÅ” Discord, taču tas izrādÄ«jās ļoti vienkārÅ”i. Oficiālais npm modulis discord.js nodroÅ”ina ērtu saskarni darbam ar ziņojumiem, reakciju apkopoÅ”anai utt.

Atruna: visi koda piemēri ir ā€œpaÅ”reizējieā€, kas nozÄ«mē, ka tie naktÄ« ir pārrakstÄ«juÅ”i vairākas iterācijas.

Spēles pamatā ir ā€œrindaā€, kurā spēlētāji, kuri vēlas spēlēt, tiek ievietoti un izņemti, kad viņi nevēlas vai neatrod spēli.

Tā izskatās ā€œspēlētājaā€ bÅ«tÄ«ba. Sākotnēji tas bija tikai Discord lietotāja ID, taču tiek plānots palaist / meklēt spēles no vietnes, bet vispirms vispirms.

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);
  }
}

Un Å”eit ir rindas interfeiss. Å eit ā€œspēlētājuā€ vietā tiek izmantota abstrakcija ā€œgrupasā€ formā. Vienam spēlētājam grupu veido viņŔ pats, bet grupas spēlētājiem attiecÄ«gi visi grupas spēlētāji.

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
}

Es nolēmu izmantot notikumus, lai apmainÄ«tos ar kontekstu. Tas bija piemērots gadÄ«jumiem - pasākumā "tika atrasta spēle 10 cilvēkiem", jÅ«s varat nosÅ«tÄ«t nepiecieÅ”amo ziņu spēlētājiem privātās ziņās un veikt pamata biznesa loÄ£iku - palaist uzdevumu pārbaudÄ«t gatavÄ«bu, sagatavot vestibilu palaiÅ”anai un tā tālāk.

SOK izmantoju InversifyJS. Man ir patÄ«kama pieredze darbā ar Å”o bibliotēku. Ātri un vienkārÅ”i!

MÅ«su serverÄ« ir vairākas rindas - esam pievienojuÅ”i 1x1, parasto/novērtēto un pāris pielāgotus režīmus. Tāpēc starp lietotāju un spēles meklÄ“Å”anu ir viens vienÄ«gs 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)
          }
        });
      }
    );
  }

(Kodē nÅ«deles, lai sniegtu priekÅ”statu par to, kā procesi aptuveni izskatās)

Å eit es inicializēju rindu katram no ieviestajiem spēles režīmiem, kā arÄ« klausos izmaiņas ā€œgrupāsā€, lai pielāgotu rindas un izvairÄ«tos no dažiem konfliktiem.

Labi darÄ«ts, es ievietoju koda fragmentus, kuriem nav nekāda sakara ar tēmu, un tagad pāriesim tieÅ”i uz pirŔļu meklÄ“Å”anu.

Apskatīsim gadījumu:

1) Lietotājs vēlas spēlēt.

2) Lai sāktu meklÄ“Å”anu, viņŔ izmanto Gateway=Discord, tas ir, ievada reakciju uz ziņojumu:

Mēs rakstām matchmaking for Dota 2014

3) Šī vārteja pāriet uz RoomService un saka: "Lietotājs no nesaskaņām vēlas iekļūt rindā, režīms: spēle bez vērtējuma."

4) RoomService pieņem vārtejas pieprasījumu un nospiež lietotāju (precīzāk, lietotāju grupu) vēlamajā rindā.

5) Rinda pārbauda katru reizi, kad ir pietiekami daudz spēlētāju. Ja iespējams, izdod notikumu:

private onRoomFound(players: Party[]) {
    this.emit("room-found", {
      players,
    });
  }

6) RoomService acÄ«mredzot ar prieku uzklausa katru rindu, ar bažām gaidot Å”o notikumu. Mēs saņemam spēlētāju sarakstu kā ievadi, veidojam no viņiem virtuālu ā€œistabuā€ un, protams, izdodam notikumu:

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) Tātad mēs nonācām pie ā€œaugstākāsā€ autoritātes - klases Bot. Kopumā viņŔ nodarbojas ar saikni starp vārtiem (es nevaru saprast, cik smieklÄ«gi tas izskatās krieviski) un maču biznesa loÄ£iku. Bots nejauÅ”i noklausās notikumu un liek DiscordGateway nosÅ«tÄ«t gatavÄ«bas pārbaudi visiem lietotājiem.

Mēs rakstām matchmaking for Dota 2014

8) Ja kāds spēli noraida vai nepieņem 3 minÅ«Å”u laikā, tad rindā NEATgriežam. Visus pārējos atgriežam rindā un gaidām, kamēr atkal bÅ«s 10 cilvēki. Ja visi spēlētāji ir pieņēmuÅ”i spēli, tad sākas interesantā daļa.

ÄŖpaÅ”a servera konfigurācija

Mūsu spēles tiek mitinātas VDS ar Windows serveri 2012. No tā mēs varam izdarīt vairākus secinājumus:

  1. Uz tā nav dokera, kas man trāpīja sirdī
  2. Ietaupām uz īres rēķina

Uzdevums ir palaist procesu VDS no VPS operētājsistēmā Linux. Es uzrakstÄ«ju vienkārÅ”u serveri Flaskā. Jā, man nepatÄ«k Python, bet ko jÅ«s varat darÄ«t? UzrakstÄ«t Å”o serveri ir ātrāk un vienkārŔāk.

Tas veic 3 funkcijas:

  1. Servera palaiÅ”ana ar konfigurāciju - kartes atlase, spēlētāju skaits, lai sāktu spēli, un spraudņu komplekts. Es tagad nerakstÄ«Å”u par spraudņiem - tas ir cits stāsts ar litriem kafijas naktÄ«, kas sajaukta ar asarām un saplēstiem matiem.
  2. Servera apturÄ“Å”ana/restartÄ“Å”ana neveiksmÄ«gu savienojumu gadÄ«jumā, ko varam veikt tikai manuāli.

Å eit viss ir vienkārÅ”i, kodu piemēri pat nav piemēroti. 100 rindiņu skripts

Tātad, kad 10 cilvēki sapulcējās un pieņēma spēli, serveris tika palaists un visi gribēja spēlēt, privātās ziņās tika nosūtīta saite, lai pieslēgtos spēlei.

Mēs rakstām matchmaking for Dota 2014

NoklikŔķinot uz saites, spēlētājs izveido savienojumu ar spēles serveri, un tas arÄ« viss. Pēc ~25 minÅ«tēm virtuālā ā€œistabaā€ ar spēlētājiem tiek notÄ«rÄ«ta.

Jau iepriekÅ” atvainojos par raksta neveiklÄ«bu, ilgu laiku neesmu Å”eit rakstÄ«jis, un ir pārāk daudz koda, lai izceltu svarÄ«gas sadaļas. ÄŖsāk sakot, nÅ«deles.

Ja redzÄ“Å”u interesi par tēmu, bÅ«s otrā daļa - tajā bÅ«s manas mocÄ«bas ar spraudņiem priekÅ” srcds (Source dedicated server), un, iespējams, reitingu sistēma un mini-dotabuff, vietne ar spēļu statistiku.

Dažas saites:

  1. Mūsu vietne (statistika, līderu saraksts, maza galvenā lapa un klienta lejupielāde)
  2. Discord serveris

Avots: www.habr.com

Pievieno komentāru