JS Battle: Wie ich meine Bewertung geschrieben habe ()

Sie können sich an Alexander Korotayev in der Browserversion der „Helden der Macht und Magie“ erinnern: Die Entschlüsselung seines Berichts über sie hat eine Vielzahl von Ansichten über Habré gesammelt. Und jetzt hat er ein Spiel gemacht, das sich auf Programmierer konzentriert: Sie müssen es mit einem JS-Code spielen.

Diesmal dauerte die Entwicklung nicht Wochen, sondern Wochen, aber ohne interessante Herausforderungen konnte sie es trotzdem nicht. Wie macht man das Spiel auch für Entwickler bequem, die JavaScript noch nicht berührt haben? Wie kann man sich vor einfachen Möglichkeiten schützen, ein Spiel zu überlisten?



Infolgedessen machte Alexander erneut einen Bericht über HolyJS, und wir (die Organisatoren der Konferenz) bereiteten erneut eine Textversion für Habr vor.


Mein Name ist Alexander Korotaev, ich arbeite bei Tinkoff.ru, ich bin im Front-End beschäftigt. Darüber hinaus helfe ich als Teil der Spb-Frontend-Community bei der Organisation von Mitaps. Ich mache den Drinkcast Podcast, wir laden interessante Leute ein und diskutieren verschiedene Themen.

Was ist die Essenz des Spielzeugs? Zunächst müssen Sie eine Einheit aus den vorgeschlagenen auswählen. Dies ist ein solches RPG-System: Jede Einheit hat ihre eigenen Stärken und Schwächen. Sie sehen, welche Einheiten der Feind gewählt hat, und wählen aus Rache an ihm. Dann müssen Sie in JavaScript ein Skript für das Verhalten Ihrer Armee schreiben - mit anderen Worten, das Skript "Was sollte jede Einheit auf dem Schlachtfeld tun?".

Dies geschieht im Debug-Modus: Tatsächlich wird der Code belastet, und dann drücken beide Gegner ihren Code, und der Kampf zwischen den beiden Seiten beginnt.

So können Sie sehen, wie zwei Skripte, zwei Logiken und zwei Algorithmen gegeneinander antreten. Ich wollte schon immer so etwas machen und jetzt ist es endlich implementiert.

In Aktion sieht alles so aus:


Und wie sah die Arbeit daran aus? In die Dokumentation wurde viel Arbeit gesteckt. Wenn sich der Spieler an den Laptop setzt, sieht er die Dokumentation, in der alles ausführlich beschrieben ist.

Ich habe viel Zeit für das Layout, die Überarbeitung und die Frage der Leute gebraucht, ob es klar ist. Infolgedessen stellte sich heraus, dass es für Sishnikov, Javisten und andere Entwickler, die nichts über JS wissen, eindeutig war. Sie können sogar JavaScript mit diesem Spielzeug bewerben: "Es ist nicht beängstigend, sehen Sie, wie Sie darauf schreiben können, sogar etwas, das Spaß macht, passiert."



Wir hatten ein großes Turnier in unserer Firma, an dem praktisch alle Programmierer teilnahmen, an denen wir teilgenommen haben.

Aus technologischer Sicht habe ich die beliebteste Spiel-Engine aus der Welt von JS - Phaser verwendet. Der größte und am häufigsten verwendete Ace Editor. Dies ist ein Editor im Web, der Sublime oder VSCode sehr ähnlich ist. Er kann in eine Webseite eingebettet werden. Ich habe auch RxJS verwendet, um mit asynchronen Interaktionen von verschiedenen Benutzern zu arbeiten, und Preact, um HTML zu rendern. Von den einheimischen Technologien arbeitete er insbesondere mit Arbeitern und Websocket.





Spiele für Programmierer


Was sind Spiele für Programmierer im Allgemeinen? Meiner Meinung nach sind dies Spiele, bei denen Sie codieren und dann ein lustiges Ergebnis erzielen müssen, das mit jemandem verglichen werden kann. Dies ist ein Kampf. Von diesen verfügbaren Online-Spielen kenne ich "Elevator Saga" - Sie schreiben Skripte für Aufzüge nach bestimmten Parametern. "Screeps" - über Biologie, Moleküle, schreiben Skripte für sie.

Es gibt auch Spielzeug, das manchmal auf Konferenzen ist. Das beliebteste davon ist „Code in the Dark“, das wir heute auch vorgestellt haben. Übrigens hat mich „Code in the Dark“ in gewisser Weise inspiriert.



Warum wurde das gemacht? Ich habe die Aufgabe, dass Sie sich mit einem Stand auf der Konferenz etwas Cooles einfallen lassen müssen, etwas Ungewöhnliches. Nicht dass es Eychars mit Fragebögen gab. Es ist klar, dass jeder Aufmerksamkeit erregen und Kontakte sammeln möchte. Wir haben uns entschlossen, weiter zu gehen und haben uns etwas Cooles und Lustiges für Programmierer ausgedacht. Mir wurde klar, dass Programmierer kämpfen und konkurrieren wollen, und wir müssen ihnen eine solche Gelegenheit geben. Es ist notwendig, einen Stand zu schaffen, auf den sie kommen und codieren.

Gamification Wir haben dies nicht nur unter praktizierenden Programmierern getan, sondern auch unter Studenten. Wir haben solche Spiele am Karrieretag an den Instituten abgehalten. Wir mussten irgendwie sehen, welche Typen es gab, die für uns geeignet waren oder nicht. Wir haben Gamification verwendet, um Menschen in den Prozess zu locken, um zu sehen, wie sie sich verhalten und was sie tun. Sie spielten und waren abgelenkt, aber das gab uns Informationen. Einige haben Code gepusht, ohne ihn einmal ausgeführt zu haben, und es war sofort klar, dass es für die Entwicklung zu früh war.



Wie es in der ersten Version aussah. Es war ein Hauptbildschirm und zwei Laptops für Spieler. All dies kontaktierte den Server, der Server speicherte den Status und durchsuchte ihn zwischen allen verbundenen Clients. Jeder Bildschirm war ein verbundener Client. Die Laptops der Spieler waren interaktive Bildschirme, von denen aus dieser Status geändert werden konnte. Bildschirme sind fest mit einem Server verbunden.

Zeitmangel Geschichte


Die erste Geschichte, die mir in dieser Entwicklung begegnet ist, ist die Geschichte, für die ich sehr wenig Zeit hatte. Buchstäblich in fünf Minuten kam eine Idee auf, in fünf Sekunden wurde ein Name erfunden, als ein Repository auf GitHub erstellt werden musste. Ich konnte alles nur vier Stunden am Abend verbringen und sie sogar meiner Frau wegnehmen. Infolgedessen hatte ich nur noch drei Wochen vor der Konferenz Zeit, um dies zumindest irgendwie zu realisieren. Alles begann so, dass es nur notwendig war, im Rahmen des Brainstormings eine Idee zu entwickeln. In fünf Minuten war die Idee geboren: „Schreiben wir eine Art künstliche Intelligenz für RPG in JS“. Es ist cool, macht Spaß, ich kann es implementieren.



In der ersten Implementierung hatte der Bildschirm einen Code-Editor und einen Kampfbildschirm, auf dem sich der Kampf selbst befand. Phaser, Ace Editor und pure Node.js wurden als Server ohne Frameworks verwendet. Was ich später bereute, aber dann war vom Server nichts Besonderes erforderlich.



Ich habe es geschafft, dann Renderer zu realisieren, der die Schlacht selbst gemalt hat. Der schwierigste Teil war die Sandbox für den JS-Code, dh eine Sandbox, in der alles für jeden Spieler isoliert ausgeführt werden sollte. Es gab auch eine Statusfreigabe, die vom Server kam. Die Spieler änderten irgendwie den Status, warfen ihn auf den Server, der Server schickte den Rest an die Web-Sockets. Der Server war eine Quelle der Wahrheit, und alle verbundenen Clients vertrauten dem, was vom Server kam.

Sandkasten


Was ist so schwierig, eine Sandbox zu implementieren? Tatsache ist, dass die Sandbox die ganze Welt für den Code ist, in dem der Code existieren muss. Das heißt, Sie erstellen für ihn ungefähr dieselbe Welt wie in der Umgebung, bestehen jedoch nur aus einigen Konventionen aus einer API, mit der Sie interagieren können. Wie implementiere ich dies auf JS? Es scheint, dass JS dazu nicht in der Lage ist, es ist so voller Lücken und frei, dass es einfach nicht funktioniert, den Benutzercode vollständig in eine Box einzuschließen, ohne eine separate virtuelle Maschine mit einem separaten Betriebssystem zu verwenden.



Was soll der Sandkasten tun?

Isolieren Sie zunächst, wie gesagt, den Code. Es muss eine Welt sein, aus der man nicht ausbrechen kann.

Außerdem sollte die Einheitenverwaltungs-API dort abgelegt werden. Die Spieler müssen mit dem Schlachtfeld interagieren, Einheiten bewegen, sie lenken und ihnen einen Angriffsvektor geben.
Und alle Aktionen von Einheiten sind asynchron, das heißt, sie sollten irgendwie mit asynchronem Code funktionieren.



Was wollte ich über Asynchronität sagen? Tatsache ist, dass es in JS grundsätzlich mit Versprechungen umgesetzt wird. Hier ist allen alles klar, Versprechen sind eine großartige Sache, sie funktionieren perfekt, wir waren fast immer bei uns. Viele Jahre lang weiß jeder, wie man mit ihnen arbeitet, aber dieses Spielzeug war nicht nur für Javascripts gedacht. Stellen Sie sich vor, ich würde den Javisten erklären, wie man mit Versprechungen einen Kampfcode schreibt? Wie geht es dann-dann-dann, warum ist es manchmal nicht notwendig ... Was tun mit Bedingungen oder Schleifen?



Sie können natürlich den besten Weg gehen und die asynchrone / warten-Syntax verwenden. [Folie 8:57] Aber können Sie sich auch vorstellen, wie Nicht-Javascript-Programmierer erklären können, dass Sie vor fast jeder Zeile warten müssen? Daher ist es am besten, überhaupt nicht mit Asynchronität zu arbeiten.



Erstellen Sie den synchronsten Code und die einfachste API, ähnlich wie bei fast jeder Programmiersprache. Wir machen ein Spielzeug nicht nur für Leute, die in JS schreiben, wir wollen es jedem zugänglich machen, der weiß, wie man programmiert.



Wir alle müssen es irgendwie anfangen. Der Benutzer schreibt den Code, und wir müssen ihn ausführen und die Einheiten auf der Karte verschieben. Das erste, was mir in den Sinn kommt, ist, dass wir eval () plus die nicht empfohlene with-Anweisung benötigen, die für die Verwendung auf MDN nicht empfohlen wird . Dies wird funktionieren, aber es gibt Probleme.



Zum Beispiel haben wir Code, der unsere gesamte Idee vollständig zerstört und uns daran hindert, etwas weiter zu tun. Dies ist Code, der die Ausführung blockiert. Es muss etwas getan werden, damit der Benutzer die Anwendung nicht blockieren kann. Zum Beispiel kann eine Endlosschleife alles brechen. Wenn alert () und prompt () immer noch neu definiert werden können, können wir eine Endlosschleife überhaupt nicht neu definieren.

eval () ist böse


So kommen wir zu dem Punkt, an dem eval () böse ist. Kein Wunder, dass es als böse bezeichnet wird, denn es ist eine heimtückische Funktion, die tatsächlich das Freieste und Offenste in JS beinhaltet und uns völlig schutzlos macht. Mit einer einfachen Funktion machen wir ein großes Loch in unserer Anwendung.



Aber was ist, wenn ich Ihnen [mit der Stimme von Steve Jobs] sage, dass wir eval () neu erfunden haben?

Wir haben eval () für andere Technologien durchgeführt. Es funktioniert fast genauso wie eval (), das wir bereits haben. Tatsächlich habe ich eine eval () - Funktion in meinem Code, die jedoch mithilfe von Workers, der with-Anweisung und Proxy implementiert wurde.



Warum Arbeiter? Tatsache ist, dass sie einen separaten Ausführungsthread erstellen, dh JS ist Single-Threaded, aber dank der Mitarbeiter können wir einen anderen Thread erhalten. Dies gibt uns viele Vorteile. Zum Beispiel können wir innerhalb derselben endlosen Zyklen den durch Worker erstellten Thread vom Haupt-Thread aus unterbrechen. Vielleicht ist dies der Hauptgrund, warum ich Worker verwendet habe. Wenn es dem Arbeiter gelungen ist, schneller als in einer Sekunde zu arbeiten, halten wir ihn für erfolgreich, wir erhalten sein Ergebnis. Wenn nicht, dann schneiden wir es einfach ab. Tatsächlich funktionierte der Benutzercode aus irgendeinem Grund nicht, es traten einige seltsame Fehler auf oder er wurde aufgrund einer Endlosschleife verlangsamt. Viele versuchten heute zu schreiben, während (wahr) ich warnte, dass dies nicht funktionieren würde.



Um unseren Worker zu schreiben, müssen wir nur das Skript an den Worker-Konstruktor weiterleiten, der über http heruntergeladen wird. Innerhalb des Skripts müssen wir einen Nachrichtenhandler aus dem Hauptthread erstellen. Mit der Funktion postMessage () in Worker können wir Nachrichten an den Hauptthread weiterleiten. Auf diese Weise stellen wir die Kommunikation zwischen den beiden Threads her. Eine ziemlich praktische einfache API, in der jedoch etwas fehlt, nämlich der Benutzercode, den wir in diesem Worker ausführen müssen. Wir werden nicht jedes Mal eine Skriptdatei auf dem Server generieren und sie dem Worker zuführen.



Ich habe einen Weg gefunden, URL.createObjectURL () zu verwenden. Wir machen einen Block und füttern ihn dem src-Arbeiter. Somit wird unser Code direkt aus der Zeile entladen. Übrigens funktioniert diese Art und Weise mit allen Objekten im DOM, die src - image haben, funktioniert beispielsweise so, und selbst in einem iframe können Sie HTML laden, indem Sie es einfach aus einer Zeichenfolge generieren. Ziemlich cool und flexibel, finde ich. Wir können den Worker auch verwalten, indem wir ihm einfach unser speziell generiertes Objekt von der URL übergeben. Wir können es auch beenden und es funktioniert bereits nach Bedarf und wir haben die erste Sandbox erstellt.



Asynchrone Interaktionen gehen noch weiter, da jede Arbeit mit Arbeitern asynchron ist. Wir haben eine Nachricht gesendet und können nicht synchron auf die nächste Nachricht warten. Der Worker gibt nur die Instanz an uns zurück und wir können Nachrichten abonnieren. Wir fangen die Nachricht mit RxJS ab und erstellen zwei Threads: einen für eine erfolgreiche Nachricht vom Worker und einen für die Fertigstellung durch Timeout. Zwei Threads, die wir dann mit ihrer Zusammenführung steuern.



RxJS verfügt über Operatoren, mit denen wir mit Threads arbeiten können. In der Tat ist es wie ein Lodash für synchrone Operationen. Wir können auf eine Funktion hinweisen und nicht darüber nachdenken, wie sie im Inneren implementiert ist. Sie lindert Kopfschmerzen. Wir müssen anfangen, in Threads zu denken, der Zusammenführungsoperator führt unsere Threads zusammen und antwortet auf jede Nachricht. Es reagiert sowohl auf das Zeitlimit als auch auf die Nachricht. Wir brauchen jeweils nur die allererste Nachricht, nach der ersten Nachricht beenden wir den Worker. Im Fehlerfall drucken Sie diesen Fehler aus. Im Erfolgsfall beheben wir ihn.



Hier ist alles ziemlich einfach. Unser Code wird deklarativ, die Komplexität der Asynchronität geht irgendwohin. Die Hauptsache ist, diese Operatoren zu lernen.



So arbeiten wir mit der Unit-API. Ich wollte, dass die Unit-API so einfach wie möglich ist. Wenn man über JS spricht, denken viele Leute, dass es schwierig ist, irgendwo zu klettern und etwas zu lernen. Und ich wollte es so einfach wie möglich machen: Alles im globalen Bereich, es gibt nur den Umfang der Unit-API, nichts weiter. Alles für die Einheitenverwaltung, auch für die automatische Vervollständigung.



[Folie 15:20] Die Lösung bietet sich an, dass all dies in die sehr verbotene Aussage hineingestoßen werden kann. Lassen Sie uns verstehen, warum es verboten ist.

Tatsache ist, dass mit seine Probleme hat. Zum Beispiel ist mit leider außerhalb des Bereichs, den wir hineingeworfen haben, undicht, weil es versucht, tiefer als die Unit-API und in den globalen Bereich zu schauen.


Hier ist das letzte Beispiel besonders cool, da sogar eine Vier für unseren Code gefährlich sein kann, da alle diese Funktionen vom Benutzercode ausgeführt werden können. Der Benutzer kann alles tun. Dies ist ein Spiel für Programmierer, und sie erforschen gerne die Probleme und Möglichkeiten, etwas zu hacken.



Wie gesagt, Bereiche sind sehr undicht, sodass immer ein globaler Bereich verfügbar ist. Unabhängig davon, wie viele Bereiche wir in unserem benutzerdefinierten Code festlegen, unabhängig davon, wie viele Bereiche wir umschließen, bleibt der globale Bereich weiterhin sichtbar. Und das alles wegen mit.

Tatsächlich isoliert es nichts, es fügt uns nur eine neue Abstraktionsebene hinzu, einen neuen globalen Bereich. Wir können dieses Verhalten jedoch mit Proxy ändern.



Tatsache ist, dass Proxy alle unsere Aufrufe an das Objekt überwacht, die über die neue API als Proxy ausgeführt werden, und wir können steuern, wie sich neue Datenanforderungen in diesem Objekt verhalten.



In der Tat funktioniert mit ganz einfach. Wenn wir ihm eine Art Variable zuführen, prüft er unter der Haube, ob sich diese Variable im Objekt befindet (dh, sie führt den Operator in aus), und wenn ja, führt sie sie im Objekt aus, und wenn nicht, wird sie im oberen Bereich ausgeführt Unser Fall ist global. Hier ist es ziemlich einfach. Die Hauptsache, bei der uns der Proxy hilft, ist, dass wir dieses Verhalten überschreiben können.



Es gibt so etwas wie Hooks in Proxy. Eine wunderbare Sache, mit der wir alle Anfragen an das Objekt weiterleiten können. Wir können das Verhalten der Attributanforderung ändern, das Verhalten des Attributjobs ändern und vor allem das Verhalten dieses Operators im Operator ändern. Es gibt einen Has-Hook, zu dem wir nur true zurückgeben können. Daher nehmen wir unsere with-Anweisung und täuschen sie vollständig, wodurch unsere API viel sicherer als zuvor ist.



Wenn wir versuchen, eval () auszuführen, fragt er zuerst, ob dieses eval () in unitApi ist, sie antworten mit "Ja" und erhalten "undefiniert ist keine Funktion". Dies scheint das erste Mal zu sein, dass ich mit diesem Fehler zufrieden bin! Dieser Fehler ist genau das, was wir hätten erhalten sollen. Wir nahmen es und sagten dem Benutzer: "Entschuldigung, vergessen Sie alles, was Sie über das Fensterobjekt wussten, das ist nicht mehr." Wir haben bereits einige Probleme hinterlassen, aber das ist noch nicht alles.



Tatsache ist, dass die with-Anweisung von JS alle gleich ist, JS dynamisch und etwas seltsam ist. Das Seltsame ist, dass nicht alles so funktioniert, wie wir es möchten, ohne auf die Spezifikation zu achten. Tatsache ist, dass mit auch mit Prototyp-Eigenschaften funktioniert. Das heißt, wir können ihm ein Array füttern und diesen obskuren Code ausführen. Alle Array-Funktionen sind in diesem Bereich als global verfügbar, was etwas seltsam aussieht.



Dies ist für uns nicht wichtig, es ist wichtig für uns, dass der Benutzer valueOf () ausführen und alle unsere Sandboxs abrufen kann. Direkt nehmen und abholen, sehen, was drin ist. Ich wollte das auch nicht, daher wurde eine interessante Sache in die Spezifikation aufgenommen: Symbol.unscopables. Das heißt, in der neuen Spezifikation für Symbole wurde Symbol.unscopables speziell für die with-Anweisung eingeführt, was verboten ist. Weil sie glauben, dass jemand anderes es benutzt. Zum Beispiel ich!



Daher werden wir einen weiteren Interceptor erstellen, bei dem wir speziell prüfen, ob dieses Symbol in der Liste aller nicht kopierbaren Attribute enthalten ist. Wenn nicht, dann senden Sie es zurück, aber wenn ja, dann werden wir leider nicht zurückkehren. Wir benutzen es auch nicht. Und so können wir mit nicht einmal einen Prototyp unserer Sandbox bekommen.



Wir haben immer noch die Worker-Umgebung. Dies ist etwas, das in einem globalen Bereich hängt und immer noch zugänglich ist. Tatsache ist, dass wenn Sie dies einfach überschreiben, es im Prototyp verfügbar ist. Fast alles kann durch den Prototyp in JS herausgezogen werden. Überraschenderweise sind alle diese Methoden immer noch über den Prototyp verfügbar.



Ich musste das alles nur nehmen und ausräumen. Wir gehen alle Schlüssel durch und reinigen alles.



Und dann hinterlassen wir dem Benutzer ein kleines Osterei, der immer noch versucht, dies zu nennen. Wir nehmen die übliche Funktion, die Hauptsache ist nicht die Pfeilfunktion, die einen Bereich hat, und ändern ihren Bereich auf unser Objekt, in dem wir ein kleines Osterei für einen besonders neugierigen Benutzer hinterlassen, der etwas davon oder sich selbst in der Konsole anzeigen möchte. Ich glaube, dass Ostereier wunderbar sind und im Code belassen werden sollten.



Es stellt sich ferner heraus, dass sie nur mit unserer Unit-API belassen wurden. Wir haben alles komplett blockiert - tatsächlich eine Whitelist hinterlassen. Wir müssen die APIs hinzufügen, die nützlich und notwendig sind. Zum Beispiel die Math-API, die eine nützliche Zufallsfunktion hat, die viele Leute beim Schreiben von Code für Einheiten verwenden.

Wir brauchen auch eine Konsole und viele andere nützliche Funktionen, die keine zerstörerische Funktion haben. Wir erstellen eine while-Liste für unsere APIs. , blacklist, , .



whitelist, try-catch . , .



, Worker . , worker, , « , » . , JavaScript , .

, , - . , . , webpack .



patchMethod(), , postMessage(). postMessage() console log, error, warn, info. , . , <div>, , , , , .



, . : - - — , , - . , , promises. , actions, .



actions. , real-time ? , real-time workers , worker, . - , . , , . , , . .



workers, . workers , . , . , , ( , ), . : — .


Math.random()


, , , . Math.random().

, , , . , Math.random() - .



, , ( ), JS , . .



, , , . , - .

, . , random(), .



, random() — « » , . , - , random() , . - , . , , random() .



. , , random() . - seed ( , , ).


, . , random(). , random() -.

, worker, , JS . «» — . . , JS . random() unit API. , worker.



State sharing: RxJS,


Also haben wir herausgefunden, was wir mit dem Kunden haben. Lassen Sie uns nun über das Teilen von Staaten sprechen, warum dies erforderlich ist und wie es organisiert wurde. Wir haben den Status auf dem Server gespeichert, der Server muss ihn mit verbundenen Clients fummeln. [Folie 28:48]

Wir haben vier Rollen verschiedener Clients, die eine Verbindung zum Server herstellen können: "linker Benutzer", "rechter Benutzer", ein Betrachter, der auf den Hauptbildschirm schaut, und ein Administrator, der alles tun kann.

Der linke Bildschirm kann den Status des rechten Players nicht ändern, der Betrachter kann nichts ändern und der Administrator kann alles tun.



Warum war das ein Problem? Alles ist ganz einfach angeordnet. Jeder verbundene Client kann eine Sitzung auslösen, der Server akzeptiert sie und führt sie mit dem Status zusammen, der sich im Server befindet, und verteilt sie dann an alle Clients. Er tastet nach Änderungen, die zu ihm kommen. Es war notwendig, es irgendwie zu filtern.



Zunächst möchte ich sagen, warum der Server auch über RxJS verfügt. Alle Interaktionen mit zwei oder mehr verbundenen Benutzern werden asynchron. Wir müssen auf die Ergebnisse beider Benutzer warten. Beispielsweise haben beide Benutzer auf die Schaltfläche „Fertig stellen“ geklickt. Sie müssen warten, bis beide klicken, und erst dann die Aktion ausführen. Auf RxJS war alles ziemlich einfach:



Wir arbeiten wieder mit Threads, es gibt einen Stream vom Socket, der Socket genannt wird. Um einen Thread zu erstellen, der nur den linken Player überwacht, nehmen und filtern wir einfach Nachrichten aus diesem Socket des linken Players (und ähnlich des rechten). Dann können wir sie mit dem forkJoin () -Operator kombinieren, der wie Promise.all () funktioniert und analog ist. Wir warten auf diese beiden Aktionen und rufen die setState () -Methode auf, die unseren Status auf "ready" setzt. Es stellt sich heraus, dass wir auf beide Spieler warten und den Status des Servers ändern. Bei RxJS ist dies so deklarativ wie möglich, weshalb ich es verwendet habe.

Es bleibt ein Problem mit der Tatsache, dass Spieler ihren Status ineinander ändern können. Dies muss ihnen untersagt werden. Trotzdem sind sie Programmierer, es gab Präzedenzfälle, die jemand ausprobiert hat. Erstellen wir für sie separate Klassen, die vom Client geerbt werden.



Sie haben die Grundlogik der Kommunikation des Spielers mit dem Server und in jeder Klasse gibt es seine benutzerdefinierte Logik zum Filtern von Daten.

Client ist eigentlich ein Pool von Verbindungen, Verbindungen mit Clients.



Er speichert sie nur und hat den onUnsafeMessage-Stream, der völlig unsicher ist: Ihm kann nicht vertraut werden, dies sind nur Rohnachrichten des Benutzers, den er empfängt. Wir schreiben diese Rohnachrichten in den Stream.

Wenn wir einen bestimmten Player implementieren, nehmen wir diese onUnsafeMessage und filtern sie.



Wir müssen nur die Daten filtern, die wir von diesem Spieler erhalten können, dem wir vertrauen können. Der linke Spieler kann nur den Status des linken Spielers ändern. Wir nehmen von allen Daten, die er senden konnte, nur den Status des linken Spielers. Wenn Sie es nicht gesendet haben, okay. Wenn gesendet - nehmen wir. So erhalten wir aus völlig unsicheren Nachrichten sichere Nachrichten, denen wir bei der Arbeit im Raum vertrauen können.



Wir haben Spielzimmer, in denen Spieler zusammenkommen. Innerhalb des Raums können wir genau die Funktionen schreiben, die den Status direkt ändern können, indem wir einfach diese Flows abonnieren, denen wir bereits vertrauen können. Wir haben von einer Reihe von Schecks abstrahiert. Wir haben die Überprüfungen anhand von Rollen durchgeführt und sie als separate Klassen bezeichnet. Wir haben den Code so aufgeteilt, dass der Code innerhalb der Steuerung, in der die wichtigen Funktionen zum Ändern des Zustands ausgeführt werden, so einfach und deklarativ wie möglich geworden ist.

RxJS wird auch auf dem Client verwendet, es ist auf der Rückseite mit dem Socket verbunden, sendet Ereignisse aus und leitet sie auf jede Weise um.

In diesem Fall möchte ich ein Beispiel geben, wenn ich die Armee des richtigen Gegners wechseln muss.



Um es zu abonnieren, erstellen wir einen Stream aus demselben Socket und filtern ihn. Wir stellen sicher, dass dies wirklich der richtige Spieler ist, und nehmen ihm eine Nachricht über seine Armee entgegen. Wenn es keine solche Nachricht gibt, gibt der Stream nichts zurück, es wird keine einzige Nachricht darin sein, es wird still bleiben, bis der Spieler die Armee wechselt. Wir lösen das Problem der Filterung von Ereignissen sofort deklarativ, wir haben es nicht kitschig.

Und wenn schon etwas aus dem Stream gekommen ist, rufen wir die Funktion setState () auf. Dies ist ganz einfach, genau der Ansatz, der es uns ermöglicht, alles transparent und deklarativ zu machen. Genau das, wofür ich das RxJS-Projekt übernommen habe und was mir sehr geholfen hat.



Ich erstelle Streams, die ich ganz klar benannt habe, mit denen ich arbeiten kann, alles ist deklarativ, die notwendigen Funktionen werden aufgerufen, es gibt keine Aufregung mit vielen if- und Filterereignissen, all dies wird von RxJS erledigt.

Verlauf: vom Einzelspieler zum Mehrspieler



Also wurde die erste Version meines Spielzeugs geschrieben. Wir können sagen, dass es ein Einzelspieler war, da nur zwei Spieler es spielen konnten, wurde die Anzahl der verbundenen Clients festgelegt. Wir hatten einen linken Spieler, einen rechten Spieler und den Bildschirm dahinter, die alle direkt mit dem Server verbunden waren. Alles war hart beschichtet, aber in drei Wochen war es geschafft.

Ich habe ein neues Angebot erhalten: das Spielzeug für alle Programmierer im Unternehmen zu erweitern, damit sie es öffnen und auf ihren Computern spielen können. Damit wir eine Liste der Anführer im Mehrspielermodus erhalten, damit sie zusammen spielen können. Dann wurde mir klar, dass ich viel umgestaltet hatte.



Es stellte sich als nicht so schwierig heraus. Ich habe einfach alle Entitäten, die ich hatte, in getrennten Räumen zusammengefasst. Ich habe die Essenz von "Room", die alle Rollen kombinieren kann. Jetzt kommunizieren nicht die Spieler selbst direkt mit dem Server, sondern die Räume. Die Räume haben bereits Anfragen direkt an den Server weitergeleitet, wodurch der Status ersetzt wurde, und der Status wurde für jeden Raum separat.



Ich habe alles genommen und neu geschrieben, eine Liste von Führungskräften hinzugefügt, wir haben die Besten mit Preisen ausgezeichnet. Es war nur notwendig, eine große Anzahl von Benutzern zu haben, es war bereits unmöglich, jedem zu folgen, es war notwendig, etwas zu schreiben, wo alle Daten gesammelt werden sollten.

JS Gamedev und seine Probleme



So lernte ich den JS-Gamedev ernsthafter kennen. Ich watschelte ungefähr drei Jahre lang über das letzte Projekt und ruhte mich regelmäßig aus. Und hier hatte ich beide Male drei Wochen lang. Jeden Tag saß ich und tat abends etwas.

Welche Probleme gibt es bei der Entwicklung von Spielen auf JS? Alles unterscheidet sich von unseren Geschäftsanwendungen, bei denen es kein Problem ist, etwas von Grund auf neu zu schreiben. Darüber hinaus wird vieles sogar begrüßt: Wir werden unser eigenes Ding machen, uns an die Geschichten mit NPM erinnern und uns selbst auf die linke Seite setzen.



In JS Gamedev ist dies nicht möglich, da alle Technologien zur Anzeige von Grafiken so niedrig sind, dass es wirtschaftlich banal ist, etwas darüber zu schreiben. Wenn ich dieses Spielzeug aufgreifen und anfangen würde, es von Grund auf auf WebGL zu schreiben, würde ich auch ungefähr sechs Monate dahinter sitzen und nur versuchen, einige seltsame Fehler herauszufinden. Die beliebteste Spiel-Engine Phaser hat diese Probleme von mir entfernt ...



... und neue hinzugefügt: 5 Megabyte in einem Bundle. Und nichts konnte dagegen unternommen werden, er weiß überhaupt nicht, was Baumschütteln ist. Darüber hinaus kann nur die neueste Version von Phaser mit Webpack und Bundles arbeiten. Zuvor war Phaser nur im HTML-Tag des Skripts verbunden, es war seltsam für mich.

Ich komme aus allen Arten von Webpacks-Skripten, und im JS-Spielentwickler kann das fast nichts. Alle Module haben eine extrem schlechte Eingabe oder haben sie überhaupt nicht oder wissen im Grunde nicht, wie man Webpack verwendet. Es war notwendig, Wege zu finden, um es zu verpacken. Wie sich herausstellte, funktioniert selbst Ace Editor in seiner reinen Form überhaupt nicht mit Webpack. Um mit der Arbeit zu beginnen, müssen Sie ein separates Paket herunterladen, in dem es bereits verpackt ist (Klammer).

Bei Phaser war es ungefähr genauso, aber in der neuen Version haben sie es mehr oder weniger normal gemacht. Ich schrieb weiter über Phaser und fand heraus, wie man alles mit Webpack so macht, wie wir es früher getan hatten: Sowohl Tippen als auch Tests konnten an all das angehängt werden. Ich habe festgestellt, dass Sie PixiJS , ein Webpack-Rendering, separat verwenden und viele Module dafür finden können, die bereit sind, damit zu arbeiten.



PixiJS ist eine großartige Bibliothek, die entweder auf WebGL oder auf Canvas gerendert werden kann. Darüber hinaus können Sie sogar Code wie für Canvas schreiben, der in WebGL gerendert wird. Diese Bibliothek kann 2D sehr schnell rendern. Die Hauptsache ist zu wissen, wie es mit dem Gedächtnis funktioniert, um nicht in eine Position zu fallen, wenn das Gedächtnis vorbei ist.

Ich empfehle separat das awesome-pixijs- Repository auf GitHub, wo Sie verschiedene Module finden können. Am meisten hat mir React-pixi gefallen. Wir können das Lösen von Problemen mit der Ansicht einfach ignorieren, wenn wir zwingende Funktionen direkt in den Controller schreiben, um geometrische Formen, Sprites, Animationen und mehr zu zeichnen. Wir können uns alle in JSX markieren. Wir sind mit unserer Geschäftsanwendung aus der JSX-Welt gekommen und können sie weiter nutzen. Dafür liebe ich Abstraktion. React-Pixi gibt uns diese vertraute Abstraktion.

Ich rate Ihnen auch, tween.js zu verwenden, die gleiche berühmte Animations-Engine von Phaser, mit der Sie deklarative Animationen erstellen können, die CSS-Animationen etwas ähnlich sind: Wir machen einen Übergang zwischen Zuständen, und tween.js entscheidet für uns genau, wie das Objekt verschoben wird.

Spielertypen: Wer sie sind und wie man sich mit ihnen anfreundet


Ich bin auf verschiedene Spieler gestoßen und möchte Ihnen auch über das Testen des Spielzeugs erzählen. Ich versammelte Kollegen in einem geschlossenen Raum und ließ sie nicht raus, bis sie das Spiel beendet hatten. Leider konnte nicht jeder das Spiel beenden, da ich am Anfang viele Fehler hatte. Glücklicherweise begann ich mit dem Testen, sobald mindestens ein funktionierender Prototyp erschien. Ehrlich gesagt, der erste Test ist fehlgeschlagen, weil einige Spieler nichts gestartet haben. Es war eine Schande, aber es gab mir einen Tritt, der es mir ermöglichte, weiterzumachen.

Wenn Ihr Spielzeug fertig ist, können Sie sehr gut oder mit einer Heugabel und Fackeln empfangen werden. Alle Leute warten auf eine Art Fan aus den Spielen und auf das Glück, das Sie ihnen geben werden. Und du gibst ihnen etwas, das überhaupt nicht funktioniert, obwohl es für dich zu funktionieren scheint. Wenn Sie ein Online-Spielzeug haben, gibt es noch mehr solche Fehler.

Die angenehmsten Menschen, denen ich begegnet bin, sind daher „Forscher“, die immer mehr in Ihrem Spielzeug finden, als sie wirklich sind. Sie können es angenehm mit allen möglichen kleinen Dingen ergänzen und Sie dazu auffordern, etwas hinzuzufügen. Leider gab die Kommunikation mit diesen Menschen nicht das Wichtigste - die Stabilität des Spielzeugs.

Es gibt gewöhnliche Spieler, die nur dem Fan zuliebe kommen. Manchmal bemerken sie nicht einmal Fehler und rutschen auf dem Weg zum Vergnügen irgendwie durch sie hindurch.

Eine andere Kategorie sind Fehlersammler, für die fast alles nicht funktioniert. Sie müssen mit solchen Menschen befreundet sein, obwohl sie viel Negativität sprechen. Wir müssen seltsame Beziehungen zu ihnen aufbauen: Sie verletzen dich, und du versuchst, ihnen etwas Nützliches abzunehmen. "Lass uns an deinem Computer sitzen und sehen." Sie müssen mit ihnen arbeiten, denn am Ende sind es diese Leute, die Ihre Spielqualität verbessern.

Sie müssen nur an lebenden Menschen testen. Ihr Auge ist verschwommen, und Tests werden sicherlich zeigen, was verborgen ist. Sie entwickeln ein Spielzeug und tauchen tiefer, sägen einige Funktionen, aber sie werden möglicherweise nicht einmal benötigt. Sie gehen direkt zu Ihren Verbrauchern, zeigen ihnen und beobachten, wie sie spielen und welche Tasten sie drücken. Dies gibt Ihnen einen Anreiz, genau das zu tun, was Sie brauchen. Sie sehen, dass einige Leute ständig Strg + S drücken, weil sie es gewohnt sind, den Code zu speichern. Wenn der Code zumindest auf Strg + S ausgeführt wird, fühlt sich der Spieler wohler. Sie müssen eine angenehme Umgebung für den Spieler schaffen, dafür müssen Sie ihn lieben und ihm folgen.

Die 80/20-Regel funktioniert: Sie erstellen 20% der Zeit eine Demo aus der gesamten Entwicklung des Spiels, und für den Spieler sieht es aus wie ein zu 80% abgeschlossenes Spiel. Die Wahrnehmung funktioniert so, dass die grundlegende Mechanik bereit ist, alles sich bewegt und funktioniert, was bedeutet, dass das Spiel fast fertig ist und der Entwickler es bald beenden wird. In Wirklichkeit hat der Entwickler jedoch immer noch einen Ausweg von 80%. Wie gesagt, ich musste lange an der Dokumentation arbeiten, damit sie für alle verständlich war. Ich habe es vielen Leuten gezeigt, die ihre Kommentare gesprochen haben, ich habe sie gefiltert und versucht, das Wesentliche der Aussagen zu verstehen. Und ich habe viel Zeit gebraucht, um nach Fehlern zu suchen.

In der Spieleentwicklung kann ich Ihnen daher nur raten, Demos zu machen: Sie begeistern alle, benötigen nicht viel Zeit und niemand erwartet wirklich etwas von den Demos. Das Beenden von Spielen ist ein langweiliger Prozess, aber der Einstieg ist großartig.

Zum Schluss hinterlasse ich Ihnen Links:

HolyJS 2019 Piter , eine Konferenz für JavaScript-Entwickler, findet vom 24. bis 25. Mai in St. Petersburg statt. Die ersten Redner sind bereits auf der Website erschienen.
Sie können auch einen Bericht beantragen. Call for Papers ist bis zum 11. März geöffnet.
Die Ticketpreise steigen am 1. Februar.

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


All Articles