Biz Dota 2014 uchun o'yin yozmoqdamiz

Hammaga salom.

Bu bahorda men bir loyihaga duch keldim, unda bolalar Dota 2 serverining 2014 versiyasini qanday ishlatishni va shunga mos ravishda u bilan o'ynashni o'rgandilar. Men bu o‘yinning ashaddiy muxlisiman va bolaligimga sho‘ng‘ish uchun bu noyob imkoniyatni qo‘ldan boy bermadim.

Men juda chuqur kabutardim va shunday bo'ldiki, men o'yinning eski versiyasida qo'llab-quvvatlanmaydigan deyarli barcha funktsiyalar uchun javobgar bo'lgan Discord botini yozdim, ya'ni moslashuv.
Bot bilan barcha yangiliklardan oldin qabulxona qo'lda yaratilgan. Biz xabarga 10 ta munosabatni to'pladik va serverni qo'lda yig'dik yoki mahalliy qabulxonaga mezbonlik qildik.

Biz Dota 2014 uchun o'yin yozmoqdamiz

Mening dasturchi tabiatim bunchalik qo'lda ishlashga bardosh bera olmadi va bir kechada men 10 kishi bo'lganda serverni avtomatik ravishda ko'taradigan botning eng oddiy versiyasini chizdim.

Men darhol nodejs-da yozishga qaror qildim, chunki men Python-ni yoqtirmayman va bu muhitda o'zimni qulayroq his qilaman.

Bu mening Discord uchun bot yozishdagi birinchi tajribam, lekin bu juda oddiy bo'lib chiqdi. Rasmiy npm moduli discord.js xabarlar bilan ishlash, reaktsiyalarni yig'ish va hokazolar uchun qulay interfeysni taqdim etadi.

Rad etish: Barcha kod misollari "joriy", ya'ni ular tunda qayta yozishning bir necha takrorlanishidan o'tgan.

Kelishuvning asosi bu "navbat" bo'lib, unda o'ynashni xohlaydigan o'yinchilar o'yinni xohlamasa yoki topmasa, joylashtiriladi va olib tashlanadi.

"O'yinchi"ning mohiyati shunday ko'rinadi. Dastlab bu faqat Discord-da foydalanuvchi identifikatori edi, lekin saytdan o'yinlarni ishga tushirish/qidirish rejalari bor, lekin birinchi navbatda.

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

Va bu erda navbat interfeysi. Bu erda "o'yinchilar" o'rniga "guruh" shaklidagi mavhumlik ishlatiladi. Bitta o'yinchi uchun guruh o'zidan, guruhdagi o'yinchilar uchun esa mos ravishda guruhdagi barcha o'yinchilardan iborat.

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
}

Kontekstni almashish uchun voqealardan foydalanishga qaror qildim. Bu holatlar uchun mos edi - "10 kishilik o'yin topildi" voqeasi bo'yicha siz o'yinchilarga shaxsiy xabarlarda kerakli xabarni yuborishingiz va asosiy biznes mantig'ini bajarishingiz mumkin - tayyorlikni tekshirish, qabulxonani tayyorlash uchun topshiriqni ishga tushirishingiz mumkin. ishga tushirish uchun va boshqalar.

IOC uchun men InversifyJS dan foydalanaman. Men ushbu kutubxona bilan ishlashda yoqimli tajribaga egaman. Tez va oson!

Bizning serverimizda bir nechta navbat bor - biz 1x1, normal/reyting va bir nechta maxsus rejimlarni qo'shdik. Shuning uchun, foydalanuvchi va o'yin qidiruvi o'rtasida joylashgan singleton RoomService mavjud.

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

(Jarayonlar taxminan qanday ko'rinishi haqida tasavvur berish uchun noodle kodini kiriting)

Bu erda men amalga oshirilgan o'yin rejimlarining har biri uchun navbatni ishga tushiraman, shuningdek, navbatlarni sozlash va ba'zi nizolarni oldini olish uchun "guruhlar" dagi o'zgarishlarni tinglayman.

Shunday qilib, yaxshi, men mavzuga hech qanday aloqasi bo'lmagan kod qismlarini kiritdim va endi to'g'ridan-to'g'ri moslashishga o'tamiz.

Keling, ishni ko'rib chiqaylik:

1) Foydalanuvchi o'ynashni xohlaydi.

2) Qidiruvni boshlash uchun u Gateway=Discord-dan foydalanadi, ya'ni xabarga munosabat bildiradi:

Biz Dota 2014 uchun o'yin yozmoqdamiz

3) Ushbu shlyuz RoomService-ga o'tadi va "Ixtilofdan foydalanuvchi navbatga kirmoqchi, rejim: baholanmagan o'yin".

4) RoomService shlyuz so'rovini qabul qiladi va foydalanuvchini (aniqrog'i, foydalanuvchilar guruhini) kerakli navbatga surib qo'yadi.

5) Navbat har safar o'ynash uchun etarli o'yinchi borligini tekshiradi. Iloji bo'lsa, hodisani chiqaring:

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

6) RoomService ushbu voqeani intiqlik bilan kutgan holda har bir navbatni mamnuniyat bilan tinglashi aniq. Biz kirish sifatida o'yinchilar ro'yxatini olamiz, ulardan virtual "xona" tashkil qilamiz va, albatta, voqeani e'lon qilamiz:

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) Shunday qilib, biz "eng yuqori" hokimiyatga - sinfga etib keldik bot. Umuman olganda, u shlyuzlar o'rtasidagi bog'liqlik (rus tilida qanchalik kulgili ko'rinishini tushunolmayapman) va o'yinning biznes mantig'i bilan shug'ullanadi. Bot voqeani eshitadi va DiscordGateway-ga barcha foydalanuvchilarga tayyorlik tekshiruvini yuborishni buyuradi.

Biz Dota 2014 uchun o'yin yozmoqdamiz

8) Agar kimdir 3 daqiqa ichida o'yinni rad etsa yoki qabul qilmasa, biz ularni navbatga QAYTA EMAS. Biz hammani navbatga qaytaramiz va yana 10 kishi bo'lguncha kutamiz. Agar barcha o'yinchilar o'yinni qabul qilgan bo'lsa, unda qiziqarli qism boshlanadi.

Maxsus server konfiguratsiyasi

Bizning o'yinlarimiz Windows server 2012 bilan VDS-da joylashtirilgan. Bundan biz bir nechta xulosalar chiqarishimiz mumkin:

  1. Yuragimga tegib ketgan doker yo'q
  2. Biz ijara haqini tejaymiz

Vazifa Linuxdagi VPS-dan VDS-da jarayonni ishga tushirishdir. Men Flaskda oddiy server yozdim. Ha, menga Python yoqmaydi, lekin siz nima qila olasiz? Unga bu serverni yozish tezroq va osonroq.

U 3 ta funktsiyani bajaradi:

  1. Konfiguratsiya bilan serverni ishga tushirish - xaritani, o'yinni boshlash uchun o'yinchilar sonini va plaginlar to'plamini tanlash. Men hozir plaginlar haqida yozmayman - bu boshqa voqea, kechasi ko'z yoshlari va yirtilgan sochlar bilan aralashtirilgan litrli qahva.
  2. Muvaffaqiyatsiz ulanish holatlarida serverni to'xtatish/qayta ishga tushirish, biz buni faqat qo'lda hal qila olamiz.

Bu erda hamma narsa oddiy, kod misollari ham mos emas. 100 qatorli skript

Shunday qilib, 10 kishi yig'ilib, o'yinni qabul qilgach, server ishga tushdi va hamma o'ynashga ishtiyoqi bor edi, shaxsiy xabarlarda o'yinga ulanish uchun havola yuborildi.

Biz Dota 2014 uchun o'yin yozmoqdamiz

Havolani bosish orqali o'yinchi o'yin serveriga ulanadi va keyin hammasi. ~25 daqiqadan so'ng o'yinchilar bilan virtual "xona" tozalanadi.

Maqolaning noqulayligi uchun oldindan uzr so'rayman, men bu erda uzoq vaqt yozmadim va muhim bo'limlarni ta'kidlash uchun juda ko'p kod mavjud. Noodle, qisqasi.

Agar men mavzuga qiziqishni ko'rsam, ikkinchi qism bo'ladi - unda mening srcds plaginlari (Source bag'ishlangan server) va, ehtimol, reyting tizimi va mini-dotabuff, o'yin statistikasi bo'lgan sayt bo'ladi.

Ba'zi havolalar:

  1. Bizning veb-saytimiz (statistika, peshqadamlar jadvali, kichik ochilish sahifasi va mijozni yuklab olish)
  2. Discord serveri

Manba: www.habr.com

a Izoh qo'shish