Bot für Starcraft in Rust, C und jeder anderen Sprache

StarCraft: Brutkrieg . Wie viel mir das bedeutet. Und für viele von euch. So viele, dass ich bezweifelte, ob ich einen Link zum Wiki geben sollte.


Einmal klopfte Halt mich in eine persönliche E-Mail und bot an, Rust zu lernen. Wie alle normalen Menschen haben wir uns entschlossen, damit zu beginnen Hallo Welt Schreiben einer dynamischen Bibliothek für Windows, die in den Adressraum eines StarCraft-Spiels geladen und Einheiten verwaltet werden kann.


Der Artikel beschreibt den Prozess der Lösungsfindung mithilfe von Technologien und Techniken, mit denen Sie neue Dinge in der Rust-Sprache und ihrem Ökosystem lernen oder sich inspirieren lassen können, einen Bot in Ihrer Lieblingssprache zu implementieren, sei es C, C ++, Ruby, Python usw.


Dieser Artikel ist sicherlich unter der Hymne Südkoreas lesenswert:


Starcraft OST

Bwapi


Dieses Spiel ist bereits 20 Jahre alt. Und es ist immer noch beliebt , Meisterschaften versammeln ganze Hallen in den USA, sogar im Jahr 2017 , wo die Schlacht der Großmeister Jaedong gegen Bisu stattfand. Neben lebenden Spielern nehmen auch seelenlose Autos an Schlachten teil! Und das ist dank BWAPI möglich . Weitere nützliche Links .


Seit mehr als einem Jahrzehnt gibt es eine Community von Bot-Entwicklern rund um dieses Spiel. Enthusiasten schreiben Bots und nehmen an verschiedenen Meisterschaften teil. Viele von ihnen studieren KI und maschinelles Lernen. BWAPI wird von Universitäten verwendet, um ihre Studenten auszubilden. Es gibt sogar einen zuckenden Kanal , der Spiele überträgt.


Ein Team von Fans hat vor einigen Jahren die Starcraft-Interna umgekehrt und eine C ++ - API geschrieben, mit der Sie Bots schreiben, sich in den Spielprozess integrieren und erbärmliche kleine Leute dominieren können.


Wie so oft zuvor Um ein Haus zu bauen, muss man Erz besorgen, Werkzeuge schmieden ... Wenn Sie einen Bot schreiben, müssen Sie die API implementieren. Was kann Rust bieten?


Ffi


Die Interaktion mit anderen Sprachen von Rust ist ziemlich einfach. Dafür gibt es FFI . Lassen Sie mich einen kurzen Auszug aus der Dokumentation geben .


Angenommen, wir haben eine bissige Bibliothek mit einer bissigen ch- Headerdatei, aus der wir Funktionsdeklarationen kopieren.


Erstellen Sie ein Projekt mit Fracht .


$ cargo new --bin snappy Created binary (application) `snappy` project $ cd snappy snappy$ tree . ├── Cargo.toml └── src └── main.rs 1 directory, 2 files 

Cargo hat eine Standarddateistruktur für das Projekt erstellt.


Cargo.toml in Cargo.toml die Abhängigkeit von libc an :


 [dependencies] libc = "0.2" 

src/main.rs Datei src/main.rs sieht folgendermaßen aus:


 extern crate libc; //   C ,     size_t use libc::size_t; #[link(name = "snappy")] //       extern { //    ,    //  C  : // size_t snappy_max_compressed_length(size_t source_length); fn snappy_max_compressed_length(source_length: size_t) -> size_t; } fn main() { let x = unsafe { snappy_max_compressed_length(100) }; println!("max compressed length of a 100 byte buffer: {}", x); } 

Wir sammeln und betreiben:


 snappy$ cargo build ... snappy$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/snappy` max compressed length of a 100 byte buffer: 148 

Sie können nur den cargo run aufrufen, der vor dem Start den cargo build aufruft. Oder erstellen Sie ein Projekt und rufen Sie die Binärdatei direkt auf:


 snappy$ ./target/debug/snappy max compressed length of a 100 byte buffer: 148 

Der Code wird unter der Bedingung kompiliert, dass die Snappy-Bibliothek installiert ist (für Ubuntu muss das libsnappy-dev-Paket installiert sein).


 snappy$ ldd target/debug/snappy ... libsnappy.so.1 => /usr/lib/x86_64-linux-gnu/libsnappy.so.1 (0x00007f8de07cf000) 

Wie Sie sehen können, ist unsere Binärdatei mit der gemeinsam genutzten Bibliothek libsnappy verknüpft. Und der Aufruf snappy_max_compressed_length ist ein Funktionsaufruf aus dieser Bibliothek.


Rostbinder


Es wäre schön, wenn wir automatisch FFI generieren könnten. Glücklicherweise gibt es im Arsenal von Rastomanov einen solchen Nutzen, der Rostbinder genannt wird . Sie ist in der Lage, FFI-Bindungen an C-Bibliotheken (und einige C ++) zu generieren.


Installation:


 $ cargo install bindgen 

Wie sieht die Verwendung von Rostbinden aus? Wir nehmen die C / C ++ - Headerdateien, setzen das Dienstprogramm bindgen darauf und erhalten den generierten Rust-Code mit den Definitionen der Strukturstrukturen und Funktionen. So sieht die FFI-Generation für bissig aus:


 $ bindgen /usr/include/snappy-ch | grep -C 1 snappy_max_compressed_length extern "C" { pub fn snappy_max_compressed_length(source_length: usize) -> usize; } 

Es stellte sich heraus, dass Bindgen vor den BWAPI-Headern übergeben wird und Tonnen nicht verwendbarer Codeblätter generiert (aufgrund von Funktionen für virtuelle Elemente, std :: string in der öffentlichen API usw.). Die Sache ist, dass BWAPI in C ++ geschrieben ist. C ++ ist im Allgemeinen selbst in C ++ - Projekten schwierig zu verwenden. Sobald eine kompilierte Bibliothek besser mit demselben Linker verknüpft werden kann (identische Versionen), sollten Header-Dateien besser mit demselben Compiler analysiert werden (identische Versionen). Weil es viele Faktoren gibt, die das Ergebnis beeinflussen können. Zum Beispiel Mangeln , das GNU GCC immer noch nicht fehlerfrei implementieren kann . Diese Faktoren sind so bedeutend, dass sie selbst in gtest nicht überwunden werden konnten. Aus der Dokumentation geht hervor , dass es besser ist, gtest als Teil des Projekts mit demselben Compiler und demselben Linker zu erstellen.


Bwapi-c


C ist Verkehrssprache Programmierung. Wenn rust-bindgen für die C-Sprache gut funktioniert, warum nicht das BWAPI für C implementieren und dann dessen API verwenden? Gute Idee!


Ja, es ist eine gute Idee, bis Sie sich mit den Eingeweiden von BWAPI befassen und die Anzahl der Klassen und Methoden sehen, die implementiert werden müssen = (Insbesondere all diese Layouts von Strukturen im Speicher, Assemblern, Speicherpatches und anderen Horroren, für die wir keine Zeit haben. Es ist notwendig, die vorhandene Lösung maximal zu nutzen.


Aber wir müssen uns irgendwie mit Mangling, C ++ - Code, Vererbung und virtuellen Mitgliedsfunktionen befassen.


In C ++ gibt es zwei leistungsstarke Tools, mit denen wir unser Problem lösen können: opake Zeiger und extern "C" .


extern "C" {} ermöglicht es C ++ - Code, sich als C zu tarnen. Auf diese Weise können Sie reine Funktionsnamen generieren, ohne sie zu beschädigen.


Undurchsichtige Zeiger geben uns die Möglichkeit, einen Typ zu löschen und einen Zeiger auf "einen Typ" zu erstellen, dessen Implementierung wir nicht bereitstellen. Da dies nur eine Deklaration eines Typs ist und nicht dessen Implementierung, ist es unmöglich, diesen Typ als Wert zu verwenden. Er kann nur als Zeiger verwendet werden.


Nehmen wir an, wir haben einen solchen C ++ - Code:


 namespace cpp { struct Foo { int bar; virtual int get_bar() { return this->bar; } }; } // namespace cpp 

Wir können daraus einen C-Header machen:


 extern "C" { typedef struct Foo_ Foo; //    Foo //  cpp::Foo::get_bar int Foo_get_bar(Foo* self); } 

Und der C ++ - Teil, der die Verbindung zwischen dem C-Header und der C ++ - Implementierung darstellt:


 int Foo_get_bar(Foo* self) { //      cpp::Foo    ::get_bar return reinterpret_cast<cpp::Foo*>(self)->get_bar(); } 

Nicht alle Klassenmethoden mussten auf diese Weise behandelt werden. In BWAPI gibt es Klassen, Operationen, für die Sie sich mithilfe der Feldwerte dieser Strukturen implementieren können, z. B. typedef struct Position { int x; int y; } Position; typedef struct Position { int x; int y; } Position; und Methoden wie Position::get_distance .


Es gab diejenigen, die auf besondere Weise vor Gericht gestellt werden mussten. Ein AIModule sollte beispielsweise ein Zeiger auf eine C ++ - Klasse mit einem bestimmten Satz virtueller Elementfunktionen sein. Hier ist jedoch der Titel und die Implementierung .


Nach mehreren Monaten harter Arbeit, 554 Methoden und einem Dutzend Klassen wurde die plattformübergreifende Bibliothek BWAPI-C geboren, mit der Sie Bots in C schreiben können. Das Nebenprodukt war Cross-Compilation und die Möglichkeit, die API in einer anderen Sprache zu implementieren, die FFI und die cdecl-Aufrufkonvention unterstützt.


Wenn Sie eine Bibliothek schreiben, schreiben Sie bitte die C-API.


Das wichtigste Merkmal von BWAPI-C ist die umfassendste Integrationsfähigkeit in andere Sprachen. Python , Ruby , Rust , PHP , Java und viele andere wissen, wie man mit C arbeitet. Daher können Sie auch einen Bot darauf schreiben, wenn Sie ein wenig mit einer Datei arbeiten und Ihre Wrapper implementieren.


Einen Bot in C schreiben


Dieser Teil beschreibt die allgemeinen Prinzipien des Designs von Starcraft-Modulen.


Es gibt zwei Arten von Bots: Modul und Client. Wir werden uns ein Beispiel für das Schreiben eines Moduls ansehen.


Ein Modul ist eine herunterladbare Bibliothek, das allgemeine Prinzip des Ladens finden Sie hier . Das Modul sollte zwei Funktionen exportieren: newAIModule und gameInit .


Mit gameInit alles einfach. Diese Funktion wird aufgerufen, um einen Zeiger auf das aktuelle Spiel zu übergeben. Dieser Zeiger ist sehr wichtig, da in der Wildnis von BWAPI eine globale statische Variable lebt, die in einigen Teilen des Codes verwendet wird. Beschreiben gameInit :


 DLLEXPORT void gameInit(void* game) { BWAPIC_setGame(game); } 

newAIModule etwas komplizierter. Es sollte einen Zeiger auf eine C ++ - Klasse zurückgeben, die eine virtuelle Methodentabelle mit den Namen onXXXXX enthält , die bei bestimmten Spielereignissen aufgerufen werden. Definieren Sie die Struktur des Moduls:


 typedef struct ExampleAIModule { const AIModule_vtable* vtable_; const char* name; } ExampleAIModule; 

Das erste Feld muss ein Zeiger auf eine Methodentabelle sein (Magie, alles). Also die newAIModule Funktion:


 DLLEXPORT void* newAIModule() { ExampleAIModule* const module = (ExampleAIModule*) malloc( sizeof(ExampleAIModule) ); module->name = "ExampleAIModule"; module->vtable_ = &module_vtable; return createAIModuleWrapper( (AIModule*) module ); } 

createAIModuleWrapper ist eine weitere Magie, die einen C-Zeiger in einen Zeiger auf eine C ++ - Klasse mit virtual verwandelt Methoden Mitgliedsfunktionen.


module_vtable ist eine statische Variable in der Methodentabelle. Die Werte der Methoden sind mit Zeigern auf globale Funktionen gefüllt:


 static AIModule_vtable module_vtable = { onStart, onEnd, onFrame, onSendText, onReceiveText, onPlayerLeft, onNukeDetect, onUnitDiscover, onUnitEvade, onUnitShow, onUnitHide, onUnitCreate, onUnitDestroy, onUnitMorph, onUnitRenegade, onSaveGame, onUnitComplete }; void onEnd(AIModule* module, bool isWinner) { } void onFrame(AIModule* module) {} void onSendText(AIModule* module, const char* text) {} void onReceiveText(AIModule* module, Player* player, const char* text) {} void onPlayerLeft(AIModule* module, Player* player) {} void onNukeDetect(AIModule* module, Position target) {} void onUnitDiscover(AIModule* module, Unit* unit) {} void onUnitEvade(AIModule* module, Unit* unit) {} void onUnitShow(AIModule* module, Unit* unit) {} void onUnitHide(AIModule* module, Unit* unit) {} void onUnitCreate(AIModule* module, Unit* unit) {} void onUnitDestroy(AIModule* module, Unit* unit) {} void onUnitMorph(AIModule* module, Unit* unit) {} void onUnitRenegade(AIModule* module, Unit* unit) {} void onSaveGame(AIModule* module, const char* gameName) {} void onUnitComplete(AIModule* module, Unit* unit) {} 

Durch den Namen der Funktionen und ihre Signaturen ist klar, unter welchen Bedingungen und mit welchen Argumenten sie aufgerufen werden. Zum Beispiel habe ich alle Funktionen außer leer gemacht


 void onStart(AIModule* module) { ExampleAIModule* self = (ExampleAIModule*) module; Game* game = BWAPIC_getGame(); Game_sendText(game, "Hello from bwapi-c!"); Game_sendText(game, "My name is %s", self->name); } 

Diese Funktion wird aufgerufen, wenn das Spiel beginnt. Ein Zeiger auf das aktuelle Modul wird als Argument übergeben. BWAPIC_getGame gibt einen globalen Zeiger auf das Spiel zurück, das wir durch Aufrufen von BWAPIC_setGame . Wir zeigen also ein Beispiel für Cross-Compilation und Modulbetrieb:


 bwapi-c/example$ tree . ├── BWAPIC.dll └── Dll.c 0 directories, 2 files bwapi-c/example$ i686-w64-mingw32-gcc -mabi=ms -shared -o Dll.dll Dll.c -I../include -L. -lBWAPIC bwapi-c/example$ cp Dll.dll ~/Starcraft/bwapi-data/ bwapi-c/example$ cd ~/Starcraft/bwapi-data/ Starcraft$ wine bwheadless.exe -e StarCraft.exe -l bwapi-data/BWAPI.dll --headful ... ... ... 

Wir drücken Knöpfe und starten das Spiel. Weitere Informationen zum Start finden Sie auf der BWAPI- Website und in BWAPI-C .


Das Ergebnis des Moduls:


Bild


Ein etwas komplexeres Beispiel für ein Modul, das die Arbeit mit Iteratoren, die Steuerung von Einheiten, die Mineralsuche und Statistiken zeigt, finden Sie in bwapi-c / example / Dll.c.


bwapi-sys


Im Rasta-Ökosystem ist es üblich , Pakete auf eine bestimmte Weise aufzurufen, die mit nativen Bibliotheken verknüpft sind. Jedes foo-sys-Paket hat zwei wichtige Funktionen:


  • Link zur nativen libfoo-Bibliothek
  • Stellt Funktionsdeklarationen aus der libfoo-Bibliothek bereit. Es werden jedoch nur Deklarationen und Abstraktionen auf hoher Ebene in * -sys-Kisten nicht bereitgestellt.

Damit das * -sys-Paket erfolgreich verknüpft werden kann, integrieren sie eine native Bibliothekssuche und / oder Bibliotheksassembly aus den Quellen in das Paket.


Damit das * -sys-Paket Deklarationen bereitstellt, müssen diese entweder von Hand geschrieben oder mit bindgen generiert werden. Wieder bindgen. Versuch Nummer zwei =)


Das Erzeugen von Bindemitteln mit bwapi-c wird obszön:


 bindgen BWAPI.h -o lib.rs \ --opaque-type ".+_" \ --blacklist-type "std.*|__.+|.+_$|Game_v(Send|Print|Draw).*|va_list|.+_t$" \ --no-layout-tests \ --no-derive-debug \ --raw-line "#![allow(improper_ctypes, non_snake_case)]" \ -- -I../submodules/bwapi-c/include sed -i -r -- 's/.+\s+(.+)_;/pub struct \1;/' lib.rs 

Wobei BWAPI.h die Datei mit den Einschlüssen aller Header-Header von BWAPI-C ist.


Beispielsweise hat bindgen für bereits bekannte Funktionen die folgenden Deklarationen generiert:


 extern "C" { /// BWAPIC_setGame must be called from gameInit to initialize BWAPI::BroodwarPtr pub fn BWAPIC_setGame(game: *mut Game); } extern "C" { pub fn BWAPIC_getGame() -> *mut Game; } 

Es gibt zwei Strategien: Speichern des generierten Codes im Repository und Generieren von Code im laufenden Betrieb während der Montage. Beide Ansätze haben ihre Vor- und Nachteile .


Willkommen bwapi-sys , ein weiterer kleiner Schritt in Richtung unseres Ziels.


Erinnerst du dich, ich habe über plattformübergreifend gesprochen? Nlinker schloss sich dem Projekt an und setzte eine knifflige Strategie um. Wenn das Ziel Windows ist, laden Sie das bereits zusammengestellte BWAPIC vom Github herunter. Und für die verbleibenden Ziele sammeln wir BWAPI-C aus den Quellen für OpenBW (ich werde es Ihnen etwas später sagen).


bwapi-rs


Nachdem wir die Ordner haben, können wir Abstraktionen auf hoher Ebene beschreiben. Wir haben zwei Arten, mit denen wir arbeiten können: reine Werte und undurchsichtige Zeiger.


Mit reinen Werten ist alles einfacher. Nehmen Sie als Beispiel Farbe. Wir müssen es bequem machen, Rust-Code zu verwenden, damit wir Farben auf bequeme und natürliche Weise verwenden können:


 game.draw_line(CoordinateType::Screen, (10, 20), (30, 40), Color::Red); ^^^ 

Für eine bequeme Verwendung ist es daher erforderlich, eine Idiomatik für die Rust-Sprachaufzählung mit Konstanten aus C ++ zu definieren und Konvertierungsmethoden in bwapi_sys :: Color mit dem Merkmal std :: convert :: From zu definieren :


 // FFI version #[repr(C)] #[derive(Copy, Clone)] pub struct Color { pub color: ::std::os::raw::c_int, } // Idiomatic version #[derive(PartialEq, PartialOrd, Copy, Clone)] pub enum Color { Black = 0, Brown = 19, ... 

Obwohl der Einfachheit halber können Sie die Kiste enum-primitive-derivate verwenden .


Mit undurchsichtigen Zeigern ist es nicht schwieriger. Verwenden Sie dazu das Newtype- Muster:


 pub struct Player(*mut sys::Player); 

Das heißt, Player ist eine bestimmte Struktur mit einem privaten Feld - ein roher undurchsichtiger Zeiger von C. Und so können Sie die Player :: color-Methode beschreiben:


 impl Player { //    Player::getColor  bwapi-sys //extern "C" { // pub fn Player_getColor(self_: *mut Player) -> Color; //} pub fn color(&self) -> Color { // bwapi_sys::Player_getColor -    BWAPI-C // self.0 -   let color = unsafe { bwapi_sys::Player_getColor(self.0) }; color.into() //  bwapi_sys::Color -> Color } } 

Jetzt können wir unseren ersten Bot in Rust schreiben!


Einen Bot in Rust schreiben


Als Proof of Concept wird der Bot wie ein berühmtes Land aussehen: Alle Funktionen werden darin bestehen, Arbeiter einzustellen und Mineralien zu sammeln.


Nordkorea


Südkorea


Beginnen wir mit den erforderlichen newAIModule gameInit und newAIModule :


 #[no_mangle] pub unsafe extern "C" fn gameInit(game: *mut void) { bwapi_sys::BWAPIC_setGame(game as *mut bwapi_sys::Game); } #[no_mangle] pub unsafe extern "C" fn newAIModule() -> *mut void { let module = ExampleAIModule { name: String::from("ExampleAIModule") }; let result = wrap_handler(Box::new(module)); result } 

#[no_mangle] führt die gleiche Funktion wie extern "C" in C ++ aus. In wrap_handler geschieht jede Art von Magie durch das Ersetzen der virtuellen Funktionstabelle und das Verkleiden als C ++ - Klasse.


Die Beschreibung der Modulstruktur ist noch einfacher und schöner als in C:


 struct ExampleAIModule { name: String, } 

Fügen Sie einige Methoden zum Rendern von Statistiken und zum Erteilen von Befehlen hinzu:


 impl ExampleAIModule { fn draw_stat(&mut self) { let game = Game::get(); let message = format!("Frame {}", game.frame_count()); game.draw_text(CoordinateType::Screen, (10, 10), &message); } fn give_orders(&mut self) { let player = Game::get().self_player(); for unit in player.units() { match unit.get_type() { UnitType::Terran_SCV | UnitType::Zerg_Drone | UnitType::Protoss_Probe => { if !unit.is_idle() { continue; } if unit.is_carrying_gas() || unit.is_carrying_minerals() { unit.return_cargo(false); continue; } if let Some(mineral) = Game::get() .minerals() .min_by_key(|m| unit.distance_to(m)) { // WE REQUIRE MORE MINERALS unit.right_click(&mineral, false); } } UnitType::Terran_Command_Center => { unit.train(UnitType::Terran_SCV); } UnitType::Protoss_Nexus => { unit.train(UnitType::Protoss_Probe); } UnitType::Zerg_Hatchery | UnitType::Zerg_Lair | UnitType::Zerg_Hive => { unit.train(UnitType::Zerg_Drone); } _ => {} }; } } } 

Damit aus dem ExampleAIModule-Typ ein reales Modul wird, müssen Sie ihm beibringen, auf onXXXX- Ereignisse zu reagieren, für die Sie den EventHandler-Typ implementieren müssen, der analog zur virtuellen Tabelle AIModule_vtable aus C:


 impl EventHandler for ExampleAIModule { fn on_start(&mut self) { Game::get().send_text(&format!("Hello from Rust! My name is {}", self.name)); } fn on_end(&mut self, _is_winner: bool) {} fn on_frame(&mut self) { self.draw_stat(); self.give_orders(); } fn on_send_text(&mut self, _text: &str) {} fn on_receive_text(&mut self, _player: &mut Player, _text: &str) {} fn on_player_left(&mut self, _player: &mut Player) {} fn on_nuke_detect(&mut self, _target: Position) {} fn on_unit_discover(&mut self, _unit: &mut Unit) {} fn on_unit_evade(&mut self, _unit: &mut Unit) {} fn on_unit_show(&mut self, _unit: &mut Unit) {} fn on_unit_hide(&mut self, _unit: &mut Unit) {} fn on_unit_create(&mut self, _unit: &mut Unit) {} fn on_unit_destroy(&mut self, _unit: &mut Unit) {} fn on_unit_morph(&mut self, _unit: &mut Unit) {} fn on_unit_renegade(&mut self, _unit: &mut Unit) {} fn on_save_game(&mut self, _game_name: &str) {} fn on_unit_complete(&mut self, _unit: &mut Unit) {} } 

Das Erstellen und Starten des Moduls ist so einfach wie bei C:


 bwapi-rs$ cargo build --example dll --target=i686-pc-windows-gnu bwapi-rs$ cp ./target/i686-pc-windows-gnu/debug/examples/dll.dll ~/Starcraft/bwapi-data/Dll.dll bwapi-rs$ cd ~/Starcraft/bwapi-data/ Starcraft$ wine bwheadless.exe -e StarCraft.exe -l bwapi-data/BWAPI.dll --headful ... ... ... 

Und das Arbeitsvideo:



Ein bisschen über Cross-Compilation


Kurz gesagt, Rust ist wunderschön! Mit zwei Klicks können Sie viele Toolchains für verschiedene Plattformen platzieren. Insbesondere wird die i686-pc-windows-gnu-Toolchain durch den folgenden Befehl festgelegt:


 rustup target add i686-pc-windows-gnu 

Sie können die Kofig für Fracht auch im Stammverzeichnis des .cargo/config Projekts .cargo/config :


 [target.i686-pc-windows-gnu] linker = "i686-w64-mingw32-gcc" ar = "i686-w64-mingw32-ar" runner = "wine" 

Und das ist alles, was Sie tun müssen, um Ihr Rust-Projekt unter Linux unter Windows zu kompilieren.


Openbw


Diese Jungs gingen noch weiter. Sie beschlossen, eine Open-Source-Version des Spiels SC: BW zu schreiben! Und sie machen es ziemlich gut. Eines ihrer Ziele war die Implementierung von HD-Bildern, aber SC: Remastered hat sie übertroffen = (Im Moment können Sie ihre API zum Schreiben von Bots verwenden (ja, auch in C ++). Das Erstaunlichste ist jedoch die Möglichkeit, Wiederholungen direkt im Browser anzuzeigen.


Fazit


Während der Implementierung blieb ein ungelöstes Problem bestehen: Wir kontrollieren nicht die Eindeutigkeit von Links, und das gleichzeitige Vorhandensein von &mut und & beim Ändern eines Objekts führt zu undefiniertem Verhalten. Ärger. Halt versuchte, idiomatische Bindungen zu implementieren, aber seine Sicherung verblasste leicht. Um dieses Problem zu lösen, müssen Sie außerdem die C ++ - API qualitativ schaufeln und die const Qualifizierer korrekt festlegen.


Ich habe es wirklich genossen, an diesem Projekt zu arbeiten. Ich habe mir Wiederholungen angesehen und bin tief in die Atmosphäre eingetaucht. Dieses Spiel hat legacy 믿어 않을 정도 인 ein Vermächtnis hinterlassen. Kein Spiel ist bei SC: BW sehr beliebt, und seine Auswirkungen auf 대한민국 정치 에게 waren undenkbar. Pro-Gamer in Korea sind genauso beliebt wie koreanische Dramen, die zur Hauptsendezeit ausgestrahlt werden. 또한, 한국 에서 프로 게이머 라면 군대 군대 특별한 육군 에 에 입대 할 수 있다.


Es lebe StarCraft!


Referenzen


Source: https://habr.com/ru/post/de416743/


All Articles