การเขียนการจับคู่สำหรับ Dota 2014

สวัสดีทุกคน

ฤดูใบไม้ผลินี้ฉันเจอโปรเจ็กต์ที่พวกเขาได้เรียนรู้วิธีรันเซิร์ฟเวอร์ Dota 2 เวอร์ชัน 2014 และเล่นตามนั้น ฉันเป็นแฟนตัวยงของเกมนี้ และฉันไม่สามารถละทิ้งโอกาสพิเศษนี้ในการดื่มด่ำกับวัยเด็กของฉันได้

ฉันเจาะลึกมากและมันเกิดขึ้นที่ฉันเขียนบอท Discord ที่รับผิดชอบฟังก์ชั่นเกือบทั้งหมดที่เกมเวอร์ชันเก่าไม่รองรับนั่นคือการจับคู่
ก่อนที่จะมีการสร้างนวัตกรรมทั้งหมดด้วยบอท ล็อบบี้จะถูกสร้างขึ้นด้วยตนเอง เรารวบรวมการโต้ตอบ 10 รายการต่อข้อความและประกอบเซิร์ฟเวอร์ด้วยตนเอง หรือโฮสต์ล็อบบี้ในพื้นที่

การเขียนการจับคู่สำหรับ Dota 2014

ธรรมชาติของฉันในฐานะโปรแกรมเมอร์ไม่สามารถทนต่อการทำงานด้วยตนเองได้มากนัก และในชั่วข้ามคืนฉันก็ร่างบอทเวอร์ชันที่ง่ายที่สุดซึ่งจะยกเซิร์ฟเวอร์โดยอัตโนมัติเมื่อมีคน 10 คน

ฉันตัดสินใจเขียนใน nodejs ทันที เพราะฉันไม่ชอบ Python จริงๆ และฉันรู้สึกสบายใจมากขึ้นในสภาพแวดล้อมนี้

นี่เป็นประสบการณ์ครั้งแรกของฉันในการเขียนบอทสำหรับ Discord แต่มันกลายเป็นเรื่องง่ายมาก discord.js โมดูล npm อย่างเป็นทางการมีอินเทอร์เฟซที่สะดวกสำหรับการทำงานกับข้อความ การรวบรวมปฏิกิริยา ฯลฯ

ข้อจำกัดความรับผิดชอบ: ตัวอย่างโค้ดทั้งหมดเป็น "ปัจจุบัน" ซึ่งหมายความว่าได้ผ่านการเขียนซ้ำหลายครั้งในเวลากลางคืน

พื้นฐานของการค้นหาแมตช์คือ "คิว" ซึ่งผู้เล่นที่ต้องการเล่นจะถูกจัดวางและนำออกเมื่อพวกเขาไม่ต้องการหรือค้นหาเกม

นี่คือลักษณะของ "ผู้เล่น" ในตอนแรกมันเป็นเพียงรหัสผู้ใช้ใน Discord แต่มีแผนจะเปิดตัว/ค้นหาเกมจากไซต์ แต่สิ่งแรกสุดก่อนอื่น

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 กล่าวคือ โต้ตอบกับข้อความ:

การเขียนการจับคู่สำหรับ Dota 2014

3) เกตเวย์นี้ไปที่ RoomService และบอกว่า “ผู้ใช้จากความขัดแย้งต้องการเข้าสู่คิว โหมด: เกมที่ไม่มีการจัดอันดับ”

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 ส่งการตรวจสอบความพร้อมให้กับผู้ใช้ทุกคน

การเขียนการจับคู่สำหรับ Dota 2014

8) หากมีคนปฏิเสธหรือไม่ยอมรับเกมภายใน 3 นาที เราจะไม่ส่งพวกเขากลับเข้าคิว เราคืนคนอื่นให้เข้าคิวรอจนครบ 10 คนอีกครั้ง หากผู้เล่นทุกคนยอมรับเกมแล้ว ส่วนที่น่าสนใจก็จะเริ่มต้นขึ้น

การกำหนดค่าเซิร์ฟเวอร์เฉพาะ

เกมของเราโฮสต์บน VDS พร้อม Windows Server 2012 จากนี้เราสามารถสรุปได้หลายประการ:

  1. ไม่มีนักเทียบท่าอยู่ซึ่งโดนใจฉัน
  2. เราประหยัดค่าเช่า

ภารกิจคือการรันกระบวนการบน VDS จาก VPS บน Linux ฉันเขียนเซิร์ฟเวอร์ธรรมดาใน Flask ใช่ ฉันไม่ชอบ Python แต่คุณจะทำอย่างไร เขียนเซิร์ฟเวอร์นี้ลงไปได้เร็วและง่ายกว่า

ทำหน้าที่ 3 อย่าง:

  1. การเริ่มเซิร์ฟเวอร์ด้วยการกำหนดค่า - การเลือกแผนที่ จำนวนผู้เล่นที่จะเริ่มเกม และชุดปลั๊กอิน ฉันจะไม่เขียนเกี่ยวกับปลั๊กอินตอนนี้ - นั่นเป็นเรื่องราวที่แตกต่างกับการดื่มกาแฟหลายลิตรในตอนกลางคืนผสมกับน้ำตาและผมที่ฉีกขาด
  2. การหยุด/รีสตาร์ทเซิร์ฟเวอร์ในกรณีที่การเชื่อมต่อไม่สำเร็จ ซึ่งเราสามารถจัดการได้ด้วยตนเองเท่านั้น

ทุกอย่างเรียบง่ายที่นี่ ตัวอย่างโค้ดไม่เหมาะสมด้วยซ้ำ สคริปต์ 100 บรรทัด

ดังนั้นเมื่อคน 10 คนมารวมตัวกันและยอมรับเกม เซิร์ฟเวอร์ก็เปิดตัวและทุกคนก็กระตือรือร้นที่จะเล่น ลิงก์สำหรับเชื่อมต่อกับเกมก็ถูกส่งไปในข้อความส่วนตัว

การเขียนการจับคู่สำหรับ Dota 2014

เมื่อคลิกที่ลิงค์ ผู้เล่นจะเชื่อมต่อกับเซิร์ฟเวอร์เกม เท่านี้ก็เรียบร้อย หลังจากผ่านไปประมาณ 25 นาที "ห้อง" เสมือนจริงกับผู้เล่นจะถูกเคลียร์

ฉันขอโทษล่วงหน้าสำหรับความอึดอัดใจของบทความ ฉันไม่ได้เขียนที่นี่เป็นเวลานานและมีโค้ดมากเกินไปที่จะเน้นส่วนสำคัญ ก๋วยเตี๋ยวในระยะสั้น

หากฉันเห็นความสนใจในหัวข้อนี้จะมีส่วนที่สอง - มันจะมีความทุกข์ทรมานของฉันด้วยปลั๊กอินสำหรับ srcds (เซิร์ฟเวอร์เฉพาะของแหล่งที่มา) และอาจเป็นระบบการให้คะแนนและ mini-dotabuff ซึ่งเป็นไซต์ที่มีสถิติเกม

ลิงค์บางส่วน:

  1. เว็บไซต์ของเรา (สถิติ กระดานผู้นำ หน้า Landing Page ขนาดเล็ก และการดาวน์โหลดของลูกค้า)
  2. เซิร์ฟเวอร์ดิสคอร์ด

ที่มา: will.com

เพิ่มความคิดเห็น