Anda dapat mengingat
Alexander Korotayev dalam versi peramban "Heroes of Might and Magic":
dekripsi laporannya tentang dia mengumpulkan sejumlah besar pandangan tentang Habrรฉ. Dan sekarang dia membuat game yang berfokus pada programmer: Anda harus memainkannya dengan kode JS.
Kali ini pengembangannya tidak memakan waktu berminggu-minggu, tetapi berminggu-minggu, tetapi tanpa tantangan yang menarik itu tidak bisa dilakukan. Bagaimana membuat game ini nyaman bahkan untuk pengembang yang belum pernah menyentuh JavaScript? Bagaimana melindungi diri Anda dari cara sederhana untuk mengakali permainan?

Akibatnya, Alexander sekali lagi membuat laporan tentang HolyJS, dan kami (penyelenggara konferensi) kembali menyiapkan versi teks untuk Habr.
Nama saya Alexander Korotaev, saya bekerja di Tinkoff.ru, saya terlibat dalam front-end. Selain itu, sebagai bagian dari komunitas Spb-frontend, saya membantu mengatur mitaps. Saya membuat podcast Drinkcast, kami mengundang orang-orang yang menarik dan mendiskusikan berbagai topik.
Apa inti dari mainan itu? Pertama, Anda perlu memilih unit dari yang diusulkan, ini adalah sistem RPG: setiap unit memiliki kekuatan dan kelemahannya sendiri. Anda melihat unit mana yang musuh pilih, dan pilih sebagai balas dendam padanya. Maka Anda perlu menulis skrip JavaScript untuk perilaku pasukan Anda - dengan kata lain, skrip "apa yang harus dilakukan setiap unit di medan perang."
Ini dilakukan dalam mode debug: pada kenyataannya, Anda mendebit kode, dan kemudian kedua lawan mendorong kode mereka, dan pertempuran antara kedua belah pihak dimulai.
Dengan demikian, Anda dapat melihat bagaimana dua skrip, dua logika, dua algoritma saling bertarung. Saya selalu ingin melakukan sesuatu seperti ini, dan sekarang akhirnya dilaksanakan.
Dalam aksi, semuanya terlihat seperti ini:
Dan seperti apa pengerjaan itu? Banyak pekerjaan masuk ke dokumentasi. Ketika pemain duduk di laptop, dia melihat dokumentasi, di mana semuanya dijelaskan dengan cukup rinci.
Saya membutuhkan banyak waktu untuk tata letak, revisi, dan mempertanyakan di antara orang-orang apakah itu jelas. Akibatnya, ternyata jelas untuk sishnikov, javists dan pengembang lain yang tidak tahu apa-apa tentang JS. Anda bahkan dapat mempromosikan JavaScript dengan mainan ini: "Ini tidak menakutkan, lihat bagaimana Anda dapat menulis di atasnya, bahkan sesuatu yang menyenangkan terjadi."

Kami memiliki turnamen besar di perusahaan kami, di mana hampir semua programmer yang kami ikuti.
Dari teknologi, saya menggunakan mesin game paling populer dari dunia JS - Phaser. Editor Ace terbesar dan paling umum digunakan. Ini adalah editor di web, sangat mirip dengan Sublime atau VSCode, dapat disematkan di halaman web. Saya juga menggunakan RxJS untuk bekerja dengan interaksi asinkron dari pengguna yang berbeda dan Preact untuk merender html. Dari teknologi asli, ia terutama bekerja dengan pekerja dan websocket.

Game untuk programmer
Apa game untuk programmer pada umumnya? Menurut pendapat saya, ini adalah permainan di mana Anda perlu kode, lalu dapatkan beberapa hasil lucu yang dapat dibandingkan dengan seseorang, ini adalah pertarungan. Dari game online yang tersedia ini, saya tahu
"Elevator Saga" - Anda menulis skrip untuk elevator berdasarkan parameter tertentu.
"Screeps" - tentang biologi, molekul, menulis skrip untuk mereka.
Ada juga mainan yang terkadang ada di konferensi. Yang paling populer dari mereka adalah "Code in the Dark", kami juga menyajikannya hari ini. Ngomong-ngomong, "Kode dalam gelap" mengilhami saya dalam beberapa hal.

Mengapa ini dilakukan? Saya mendapat tugas yang Anda butuhkan untuk membuat sesuatu yang keren dengan pendirian di konferensi, sesuatu yang tidak biasa. Bukannya ada eychars dengan kuesioner. Jelas bahwa setiap orang ingin menarik perhatian dan mengumpulkan kontak. Kami memutuskan untuk melangkah lebih jauh dan membuat sesuatu yang keren dan menyenangkan bagi para programmer. Saya menyadari bahwa programmer ingin bertarung, bersaing, dan kita perlu memberi mereka kesempatan seperti itu. Hal ini diperlukan untuk membuat stand di mana mereka akan datang dan berkode.
Gamifikasi Kami melakukan ini tidak hanya di kalangan programmer yang berlatih, tetapi juga di kalangan siswa. Kami mengadakan pertandingan semacam itu di institut pada Hari Karier. Kami perlu entah bagaimana melihat pria macam apa di sana, cocok untuk kami atau tidak. Kami menggunakan gamification untuk memikat orang ke dalam proses, untuk melihat bagaimana mereka bertindak, apa yang mereka lakukan. Mereka bermain dan terganggu, tetapi itu memberi kami informasi. Beberapa mendorong kode tanpa sekali pun menjalankannya, dan segera jelas bahwa itu terlalu dini untuk pengembangan.

Bagaimana tampilannya di versi pertama. Itu adalah layar utama dan dua laptop untuk pemain. Semua ini menghubungi server, server menyimpan State dan menggeledahnya di antara semua klien yang terhubung. Setiap layar adalah klien yang terhubung. Laptop pemain adalah layar interaktif dari mana keadaan ini dapat diubah. Layar terpasang dengan kaku ke satu server.
Kurangnya waktu
Kisah pertama yang saya temui dalam perkembangan ini adalah kisah yang saya miliki sangat sedikit waktu. Secara harfiah dalam lima menit sebuah ide muncul, dalam lima detik sebuah nama diciptakan ketika diperlukan untuk membuat
repositori di GitHub. Saya bisa menghabiskan semuanya hanya empat jam di malam hari, mengambilnya bahkan dari istri saya. Sebagai hasilnya, saya hanya memiliki tiga minggu sebelum konferensi untuk menyadari hal ini setidaknya. Dan itu semua dimulai sehingga itu hanya perlu untuk datang dengan ide dalam kerangka brainstorming, dalam lima menit ide itu lahir: "Mari kita menulis semacam kecerdasan buatan untuk RPG di JS". Itu keren, menyenangkan, saya bisa menerapkannya.

Pada implementasi pertama, layar memiliki editor kode dan layar pertempuran, di mana pertempuran itu sendiri. Phaser, Ace Editor, dan Node.js murni digunakan sebagai server tanpa kerangka kerja apa pun. Apa yang saya sesali kemudian, tetapi kemudian tidak ada yang khusus diperlukan dari server.

Saya berhasil menyadari kemudian Renderer, yang melukis pertempuran itu sendiri. Bagian yang paling sulit adalah kotak pasir untuk kode JS, yaitu kotak pasir di mana semuanya seharusnya dieksekusi secara terpisah untuk setiap pemain. Ada juga State sharing yang berasal dari server. Para pemain entah bagaimana berubah keadaan, melemparkannya ke server, server mengirim sisanya ke soket web. Server adalah sumber kebenaran, dan semua klien yang terhubung memercayai apa yang datang dari server.
Kotak pasir
Apa yang sangat sulit untuk menerapkan kotak pasir? Faktanya adalah kotak pasir adalah seluruh dunia untuk kode di mana kode itu harus ada. Artinya, Anda membuat baginya tentang dunia yang sama seperti di sekitar, tetapi hanya terdiri dari beberapa konvensi, dari API yang dengannya Anda dapat berinteraksi. Bagaimana cara mengimplementasikan ini di JS? Tampaknya JS tidak mampu melakukan ini, sangat penuh lubang dan gratis sehingga tidak akan berfungsi untuk sepenuhnya memasukkan kode pengguna dalam kotak tanpa menggunakan mesin virtual terpisah dengan OS yang terpisah.

Apa yang harus dilakukan kotak pasir?
Pertama, seperti yang saya katakan, pisahkan kodenya. Itu haruslah sebuah dunia yang darinya orang tidak bisa keluar.
Juga, API manajemen unit harus dibuang di sana. Pemain harus berinteraksi dengan medan perang, memindahkan unit, mengarahkan mereka, memberi mereka vektor serangan.
Dan setiap tindakan unit asinkron, yaitu, entah bagaimana itu harus bekerja dengan kode asinkron.

Apa yang ingin saya katakan tentang asinkron? Faktanya adalah bahwa di JS pada dasarnya diimplementasikan menggunakan janji-janji. Semuanya jelas bagi semua orang di sini, janji adalah hal yang hebat, mereka bekerja dengan sempurna, kita hampir selalu bersama kita. Selama bertahun-tahun, semua orang tahu cara bekerja dengan mereka, tetapi mainan ini bukan hanya untuk javascript. Bayangkan jika saya mulai menjelaskan kepada orang Jawa bagaimana menulis kode pertempuran menggunakan janji? Bagaimana melakukannya kemudian-kemudian-kemudian, mengapa kadang-kadang tidak perlu ... Apa yang harus dilakukan dengan kondisi atau loop?

Anda tentu saja dapat menggunakan cara terbaik dan mengambil sintaks async / menunggu. [Slide 8:57] Tetapi bisakah Anda juga membayangkan bagaimana programmer non-javascript dapat menjelaskan bahwa Anda harus menunggu sebelum hampir setiap baris? Akibatnya, cara terbaik untuk bekerja dengan asinkron adalah tidak menggunakannya sama sekali.

Buat kode paling sinkron dan API paling sederhana, mirip dengan hampir semua bahasa pemrograman. Kami membuat mainan tidak hanya untuk orang-orang yang menulis dalam JS, kami ingin membuatnya dapat diakses oleh semua orang yang tahu cara membuat kode.

Kita semua harus memulainya. Pengguna menulis kode, dan kita perlu menjalankannya, memindahkan unit di peta. Hal pertama yang terlintas dalam pikiran adalah bahwa kita membutuhkan eval () plus pernyataan yang
tidak direkomendasikan , yang
tidak direkomendasikan untuk digunakan pada MDN . Ini akan berhasil, tetapi ada masalah.

Sebagai contoh, kami memiliki kode yang benar-benar menghancurkan seluruh ide kami, yang mencegah kami dari melakukan sesuatu yang lebih jauh. Ini adalah kode yang memblokir eksekusi. Sesuatu harus dilakukan agar pengguna tidak dapat memblokir aplikasi. Misalnya, loop tanpa akhir dapat merusak segalanya. Jika peringatan () dan prompt () masih dapat didefinisikan ulang, maka kami tidak dapat mendefinisikan ulang loop yang tak terbatas sama sekali.
eval () jahat
Jadi kita sampai pada titik di mana eval () jahat. Bukan untuk apa-apa mereka menyebutnya jahat, karena itu adalah fungsi berbahaya yang sebenarnya menggabungkan semua yang paling bebas dan terbuka yang ada di JS, dan membuat kita benar-benar tidak berdaya. Dengan satu fungsi sederhana, kami membuat lubang besar dalam aplikasi kami.

Tetapi bagaimana jika saya memberi tahu Anda,
[dalam suara Steve Jobs] bahwa kami menemukan kembali eval ()?
Kami melakukan eval () pada teknologi lain, kerjanya hampir sama dengan eval yang sama () yang sudah kami miliki. Bahkan, saya memiliki fungsi eval () dalam kode saya, tetapi diimplementasikan menggunakan Pekerja, pernyataan with, dan Proxy.

Mengapa pekerja? Faktanya adalah mereka membuat utas eksekusi yang terpisah, yaitu, JS adalah utas tunggal, tetapi berkat pekerja kita bisa mendapatkan utas lain. Ini memberi kita banyak manfaat. Misalnya, dalam loop tak berujung yang sama, kita dapat menyela utas yang dibuat melalui pekerja dari utas utama, mungkin ini adalah alasan utama mengapa saya menggunakan pekerja. Jika pekerja berhasil bekerja lebih cepat daripada dalam satu detik, kami menganggapnya berhasil, kami mendapatkan hasilnya. Jika tidak, maka kita potong saja. Bahkan, kode pengguna untuk beberapa alasan tidak berfungsi, beberapa kesalahan aneh terjadi, atau diperlambat karena perulangan tak terbatas. Banyak hari ini mencoba menulis sementara (benar), saya memperingatkan bahwa ini tidak akan berhasil.

Untuk menulis pekerja kami, kami hanya perlu memberi makan skrip ke konstruktor pekerja, yang akan diunduh melalui http. Di dalam skrip, kita perlu membuat penangan pesan dari utas utama. Menggunakan fungsi postMessage () di dalam pekerja, kita bisa merutekan pesan ke utas utama. Dengan cara ini kami melakukan komunikasi antara dua utas. API sederhana yang cukup nyaman, tetapi ada sesuatu yang hilang di dalamnya, yaitu kode pengguna yang harus kita laksanakan pada pekerja ini. Kami tidak akan setiap kali membuat file skrip di server dan memberikannya kepada pekerja.

Saya menemukan cara menggunakan URL.createObjectURL (). Kami membuat blok dan memberi makan kepada pekerja src. Dengan demikian, itu membongkar kode kami langsung dari baris. Ngomong-ngomong, cara ini berfungsi dengan objek apa pun di DOM yang memiliki src-image berfungsi seperti ini, misalnya, dan bahkan dalam iframe Anda dapat memuat html hanya dengan membuatnya dari string. Cukup keren dan fleksibel, saya pikir. Kami juga dapat mengelola pekerja dengan hanya mengirimkannya objek kami yang dibuat khusus dari URL. Kami juga dapat menghentikannya dan sudah berfungsi sesuai kebutuhan, dan kami membuat kotak pasir pertama.

Interaksi asinkron lebih jauh, karena setiap pekerjaan dengan pekerja asinkron. Kami mengirim beberapa pesan, dan kami tidak dapat secara sinkron menunggu pesan berikutnya, pekerja hanya mengembalikan contoh kepada kami, dan kami dapat berlangganan pesan. Kami menangkap pesan menggunakan RxJS, kami membuat dua utas: satu untuk pesan yang berhasil dari pekerja, yang kedua untuk diselesaikan dengan batas waktu. Dua utas yang kemudian kami kontrol dengan penggabungannya.

RxJS memiliki operator yang memungkinkan kami untuk bekerja dengan utas. Bahkan, itu seperti lodash untuk operasi sinkron. Kami dapat menunjukkan beberapa fungsi dan tidak berpikir tentang bagaimana itu diterapkan di dalam, itu membebaskan kita dari sakit kepala. Kita perlu mulai berpikir dalam utas, operator gabungan menggabungkan utas kami, menanggapi pesan apa pun. Ini akan merespon baik timeout dan pesan. Kami hanya membutuhkan pesan pertama, masing-masing, setelah pesan pertama kami memberhentikan pekerja. Jika terjadi kesalahan, cetak kesalahan ini, jika berhasil, kami menyelesaikannya.

Semuanya sangat sederhana di sini. Kode kami menjadi deklaratif, kompleksitas asynchrony pergi ke suatu tempat. Yang utama adalah mempelajari operator ini.

Ini adalah cara kami bekerja dengan API Unit. Saya ingin Unit API sesederhana mungkin. Berbicara tentang JS, banyak orang berpikir bahwa itu sulit, Anda harus naik ke suatu tempat, belajar sesuatu. Dan saya ingin membuatnya sesederhana mungkin: semua yang ada di wilayah global, hanya ada ruang lingkup API Unit, tidak lebih. Semuanya untuk manajemen unit, bahkan pelengkapan otomatis.

[Slide 15:20] Solusinya menunjukkan sendiri bahwa semua ini dapat didorong ke dalam pernyataan yang sangat terlarang. Mari kita pahami mengapa itu dilarang.
Faktanya adalah bahwa dengan memiliki masalah. Misalnya, dengan, sayangnya, ada kebocoran di luar cakupan yang kami masukkan ke dalamnya, karena ia mencoba untuk melihat lebih dalam dari API Unit dan melihat ke dalam lingkup global.

Di sini contoh terakhir sangat keren, karena bahkan empat bisa berbahaya untuk kode kita, karena semua fungsi ini dapat dilakukan oleh kode pengguna. Pengguna dapat melakukan apa saja. Ini adalah permainan untuk programmer, dan mereka suka menjelajahi masalah dan cara meretas sesuatu.

Seperti yang saya katakan, cakupan sangat bocor, sehingga cakupan global selalu tersedia. Tidak peduli berapa banyak cakupan yang kami teliti pada kode kustom kami, tidak peduli berapa banyak cakupan yang kami bungkus, cakupan global akan tetap terlihat. Dan semua karena dengan.
Bahkan, itu tidak mengisolasi apa pun, itu hanya menambah kita lapisan abstraksi baru, ruang lingkup global baru. Tapi kita bisa mengubah perilaku ini dengan Proxy.

Faktanya adalah bahwa Proxy mencari semua panggilan kami ke objek yang diproksi melalui API baru, dan kami dapat mengontrol bagaimana perilaku permintaan data baru dalam objek ini.

Bahkan, dengan bekerja cukup sederhana. Ketika kami memberinya beberapa jenis variabel, ia memeriksa di bawah kap apakah variabel ini ada di objek (yaitu, ia mengeksekusi operator in), dan jika demikian, ia menjalankannya di objek, dan jika tidak, ia mengeksekusi di lingkup atas, di Kasus kami bersifat global. Cukup sederhana di sini. Hal utama yang membantu proxy kami adalah bahwa kami dapat mengesampingkan perilaku ini.

Ada yang namanya kait di Proxy. Suatu hal yang luar biasa yang memungkinkan kita untuk mem-proxy setiap permintaan ke objek. Kita dapat mengubah perilaku permintaan atribut, mengubah perilaku pekerjaan atribut, dan yang paling penting, kita dapat mengubah perilaku ini di operator. Ada kait memiliki, yang kita dapat mengembalikan hanya benar. Dengan demikian, kami mengambil dan sepenuhnya menipu pernyataan kami, membuat API kami jauh lebih aman dari sebelumnya.

Jika kita mencoba menjalankan eval (), dia pertama kali akan menanyakan apakah eval () ini ada di unitApi, mereka akan menjawab "ya" dan mendapatkan "undefined bukan fungsi". Ini tampaknya pertama kalinya saya senang dengan kesalahan ini! Kesalahan ini persis apa yang seharusnya kita terima. Kami mengambilnya dan memberi tahu pengguna: "Maaf, lupakan semua yang Anda ketahui tentang objek jendela, ini tidak ada lagi." Kami telah meninggalkan beberapa masalah, tetapi bukan itu saja.

Faktanya adalah bahwa pernyataan with all sama dari JS, JS dinamis dan sedikit aneh. Yang aneh adalah bahwa tidak semuanya berjalan seperti yang kita inginkan, tanpa melihat spesifikasi. Faktanya adalah bahwa dengan juga bekerja dengan properti prototipe. Artinya, kita bisa dengan mudah memberi dia sebuah array, jalankan kode tidak jelas ini. Semua fungsi array tersedia secara global dalam lingkup ini, yang terlihat agak aneh.

Ini tidak penting bagi kami, penting bagi kami bahwa pengguna dapat mengeksekusi valueOf () dan mendapatkan semua kotak pasir kami. Langsung ambil dan angkat, lihat apa isinya. Saya juga tidak ingin ini, jadi hal yang menarik diperkenalkan ke dalam spesifikasi: Symbol.unscopables. Yaitu, dalam spesifikasi baru untuk simbol, Symbol.unscopables diperkenalkan khusus untuk pernyataan with, yang dilarang. Karena mereka percaya ada orang lain yang menggunakannya. Misalnya, saya!

Jadi, kita akan membuat interseptor lain, di mana kita secara khusus memeriksa untuk melihat apakah simbol ini ada dalam daftar semua atribut yang tidak dapat dicopot. Jika tidak, maka kembalikan, tetapi jika demikian, maka maaf, kami tidak akan kembali. Kami juga tidak menggunakannya. Dan karenanya, dengan kita bahkan tidak bisa mendapatkan prototipe kotak pasir kita.

Kami masih memiliki lingkungan Pekerja. Ini adalah sesuatu yang menggantung di area global dan masih dapat diakses. Faktanya adalah bahwa jika Anda cukup menimpa ini, itu akan tersedia dalam prototipe. Hampir semuanya bisa ditarik keluar melalui prototipe di JS. Anehnya, semua metode ini masih tersedia melalui prototipe.

Saya hanya harus mengambil dan membersihkan semua ini. Kami melewati semua kunci dan membersihkan semuanya.

Dan kemudian kami meninggalkan sedikit telur Paskah untuk pengguna, yang masih mencoba menyebutnya. Kami mengambil fungsi yang biasa, yang utama bukan fungsi panah, yang memiliki cakupan, dan mengubah cakupannya ke objek kami, di mana kami meninggalkan telur Paskah kecil untuk pengguna yang ingin tahu yang ingin menampilkan beberapa ini atau diri di konsol. Saya percaya bahwa telur Paskah luar biasa dan harus dibiarkan dalam kode.

Lebih lanjut ternyata mereka hanya tersisa dengan API Unit kami. Kami benar-benar memblokir semuanya - bahkan, meninggalkan daftar putih. Kita perlu menambahkan API yang bermanfaat dan dibutuhkan. Misalnya, API Matematika, yang memiliki fungsi acak berguna yang banyak digunakan orang ketika menulis kode untuk unit.
Kita juga membutuhkan konsol dan banyak fungsi utilitarian lain yang tidak membawa fungsi destruktif. Kami membuat daftar sementara untuk API kami.
Ini bagus, karena jika kami membuat daftar hitam, kami akan bergantung pada pembaruan peramban apa pun yang terjadi tanpa sepengetahuan kami.
Dengan membuat daftar putih, kita dapat mulai menggunakan try-catch dalam kode kita. Kode terbungkus kami telah menangkap kesalahan dan dapat mengirimkannya ke pengguna, yang sangat penting untuk debugging.
Tetapi kenyataannya adalah bahwa metode konsol dari Worker tidak memanifestasikan dirinya dalam kode pengguna. Artinya, jika Anda membuka konsol dan masuk ke lingkungan pekerja, mereka akan tersedia, tetapi akan salah untuk memberi tahu pengguna "buka konsol dan lihat apa yang terjadi pada Anda". Saya memutuskan untuk membuat konsol ramah untuk pemain, di mana pemain, bahkan tanpa pengalaman dalam JavaScript, dapat melihat dengan ramah apa yang terjadi., , - . , . , 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() -.
semua kode yang dikirim ke pekerja diperlukan untuk membuat JS sepenuhnya aman. "Sisipan" oranye adalah kode pengguna yang sama. yang disuntikkan di sana. Itulah jumlah kode yang Anda perlukan untuk membuat JS aman. Acak () dan API unit juga disuntikkan di sana. Melalui suntikan, saya mendapatkan lebih banyak kode yang dikirim ke pekerja.
Berbagi negara: RxJS, server dan klien
Jadi kami mencari tahu apa yang kami miliki dengan klien. Sekarang mari kita bicara tentang berbagi negara, mengapa ini diperlukan dan bagaimana hal itu diatur. Kami telah menyatakan disimpan di server, server harus meraba-raba dengan klien yang terhubung. [Slide 28:48]
Kami memiliki empat peran klien berbeda yang dapat terhubung ke server: "pengguna kiri", "pengguna kanan", pemirsa yang melihat layar utama, dan administrator yang dapat melakukan apa saja.
Layar kiri tidak dapat mengubah status pemain yang tepat, pemirsa tidak dapat mengubah apa pun, dan admin dapat melakukan semuanya.

Mengapa ini menjadi masalah? Semuanya diatur dengan cukup sederhana. Setiap klien yang terhubung dapat melakukan sesi, server menerimanya dan menggabungkannya dengan status, yang ada di dalam server dan kemudian mendistribusikannya ke semua klien. Dia meraba-raba untuk setiap perubahan yang datang kepadanya. Itu perlu untuk menyaringnya entah bagaimana.

Pertama, saya akan mengatakan mengapa server juga memiliki RxJS. Semua interaksi dengan dua atau lebih pengguna yang terhubung menjadi tidak sinkron. Kami harus menunggu hasil dari kedua pengguna. Sebagai contoh, kedua pengguna mengklik tombol "Selesai", Anda harus menunggu keduanya untuk mengklik, dan hanya kemudian melakukan tindakan. Itu semua sangat mudah pada RxJS dengan cara ini:

Kami lagi beroperasi dengan utas, ada aliran dari soket, yang disebut soket. Untuk membuat satu utas yang hanya memantau pemutar kiri, kami cukup mengambil dan memfilter pesan dari soket ini oleh pemain kiri (dan juga yang kanan). Kemudian kita bisa menggabungkannya menggunakan operator forkJoin (), yang berfungsi seperti Promise.all () dan merupakan analognya. Kami menunggu kedua tindakan ini dan memanggil metode setState (), yang membuat status kami "siap". Ternyata kami menunggu kedua pemain dan mengubah status server. Pada RxJS, ini keluar sebagai deklaratif mungkin, itulah sebabnya saya menggunakannya.
Masih ada masalah dengan fakta bahwa para pemain dapat mengubah status satu sama lain. Mereka harus dilarang melakukan ini. Namun, mereka adalah programmer, ada preseden yang dicoba seseorang. Mari kita buat kelas terpisah untuk mereka yang diwarisi dari Klien.

Mereka akan memiliki logika dasar komunikasi pemain dengan server, dan di setiap kelas tertentu akan ada logika khusus untuk menyaring data.
Klien sebenarnya adalah kumpulan koneksi, koneksi dengan klien.

Dia hanya menyimpannya dan memiliki aliran onUnsafeMessage, yang sama sekali tidak aman: dia tidak bisa dipercaya, ini hanya pesan mentah dari pengguna yang dia terima. Kami menulis pesan mentah ini ke stream.
Selanjutnya, ketika menerapkan pemain tertentu, kami mengambil ini diUnsafeMessage dan memfilternya.

Kita hanya perlu memfilter data yang bisa kita dapatkan dari pemain ini, yang bisa kita percayai. Pemain kiri hanya dapat mengubah keadaan pemain kiri, masing-masing, kami mengambil dari semua data yang bisa ia kirim, hanya keadaan pemain kiri. Jika Anda tidak mengirimnya, oke. Jika dikirim - kami terima. Dengan demikian, dari pesan yang sama sekali tidak aman, kami mendapatkan pesan aman yang dapat kami percayai saat bekerja di ruangan.

Kami memiliki ruang permainan yang menyatukan pemain. Di dalam ruangan, kita bisa menulis fungsi yang bisa mengubah keadaan secara langsung, hanya dengan berlangganan aliran ini, yang sudah bisa kita percayai. Kami mengabstraksi dari banyak cek. Kami melakukan pemeriksaan berdasarkan peran, dan menyebutnya kelas terpisah. Kami membagi kode sedemikian rupa sehingga di dalam controller, di mana fungsi penting dari mengubah keadaan dilakukan, kode tersebut telah menjadi sesederhana dan sesederhana mungkin.
RxJS juga digunakan pada klien, itu terhubung ke soket di sisi sebaliknya, memancarkan peristiwa dan mengarahkan mereka dengan segala cara.
Dalam hal ini, saya ingin memberi contoh ketika saya perlu mengubah pasukan musuh yang tepat.

Untuk berlangganan, kami membuat aliran dari soket yang sama dan memfilternya. Kami memastikan bahwa ini benar-benar pemain yang tepat, dan menerima pesan darinya tentang pasukannya. Jika tidak ada pesan seperti itu, streaming tidak akan mengembalikan apa pun, tidak akan ada satu pesan pun di dalamnya, itu akan tetap diam sampai pemain mengubah pasukan. Kami segera memecahkan masalah pemfilteran secara deklaratif, kami tidak menganggapnya norak.
Dan ketika sesuatu telah datang dari aliran, kami memanggil fungsi setState (). Ini cukup sederhana, pendekatan yang memungkinkan kita melakukan segala sesuatu secara transparan dan deklaratif. Hal yang saya ambil dari proyek RxJS dan apa yang sangat membantu saya.

Saya membuat utas yang saya punya nama yang cukup jelas, yang saya mengerti cara bekerja, semuanya deklaratif, fungsi-fungsi yang diperlukan dipanggil, tidak ada keributan dengan banyak jika dan peristiwa penyaringan, semua ini dilakukan oleh RxJS.
Sejarah: dari satu pemain ke multipemain
Jadi, versi pertama mainan saya ditulis. Kita dapat mengatakan bahwa itu adalah pemain tunggal, karena hanya dua pemain yang dapat memainkannya, jumlah klien yang terhubung telah diperbaiki. Kami memiliki pemain kiri, pemain kanan dan layar di belakang, semua ini terhubung langsung ke server. Semuanya sudah dikeraskan, namun dalam tiga minggu hal itu dilakukan.
Saya menerima tawaran baru: untuk memperluas mainan untuk semua programmer di perusahaan sehingga mereka dapat membuka dan memainkannya di komputer mereka. Sehingga kami mendapatkan daftar pemimpin, multipemain sehingga mereka bisa bermain bersama. Kemudian saya menyadari bahwa saya memiliki banyak refactoring.

Ternyata tidak begitu sulit. Saya hanya menggabungkan semua entitas yang saya miliki ke kamar yang terpisah. Saya mendapat esensi "Kamar", yang bisa menggabungkan semua peran. Sekarang bukan pemain itu sendiri yang berkomunikasi langsung dengan server, tetapi kamar. Kamar-kamar sudah memproksikan permintaan langsung ke server, mengganti status, dan status telah terpisah untuk setiap kamar.

Saya mengambil dan menulis ulang semuanya, menambahkan daftar pemimpin, kami memberikan yang terbaik dengan hadiah. Itu hanya perlu memiliki sejumlah besar pengguna, itu sudah tidak mungkin untuk mengikuti semua orang, itu perlu untuk menulis sesuatu di mana mengumpulkan semua data.
JS Gamedev dan masalahnya
Dengan demikian, saya menjadi lebih serius berkenalan dengan JS-gamedev. Saya melakukan waddle tentang proyek terakhir selama sekitar tiga tahun, beristirahat secara berkala. Dan di sini saya mengalami dua kali selama tiga minggu. Setiap hari saya duduk dan melakukan sesuatu di malam hari.
Apa masalah yang ada dalam mengembangkan game di JS? Semuanya berbeda dari aplikasi bisnis kami, di mana tidak masalah untuk menulis sesuatu dari awal. Selain itu, banyak yang bahkan disambut: kita akan melakukan hal kita sendiri, mengingat cerita dengan NPM dan meninggalkan sendiri.

Tidak mungkin untuk melakukan ini di JS Gamedev, karena semua teknologi untuk menampilkan grafik sangat rendah sehingga secara ekonomis tidak menguntungkan untuk menulis sesuatu pada mereka. Jika saya mengambil mainan ini dan mulai menulisnya dari awal di WebGL, saya juga akan duduk di belakangnya selama sekitar enam bulan, hanya mencoba mencari tahu beberapa bug aneh. Mesin permainan paling populer Phaser menghapus masalah ini dari saya ...

... dan menambahkan yang baru kepada saya: 5 megabita dalam satu bundel. Dan tidak ada yang bisa dilakukan tentang itu, dia tidak tahu apa itu treeshaking sama sekali. Selain itu, hanya versi terbaru dari Phaser yang dapat bekerja dengan webpack dan bundel. Sebelum ini, Phaser hanya terhubung di tag html dari skrip, itu aneh bagi saya.
Saya berasal dari semua jenis skrip webpacks, dan dalam game JS dev, hampir tidak ada yang bisa melakukannya. Semua modul memiliki ketikan yang sangat buruk atau tidak memilikinya sama sekali, atau pada dasarnya tidak tahu cara menggunakan webpack, perlu untuk menemukan cara untuk membungkusnya. Ternyata, bahkan Ace Editor dalam bentuknya yang murni tidak bekerja dengan webpack sama sekali. Untuk mulai bekerja, Anda perlu mengunduh paket terpisah yang sudah dibungkus (brace).
Itu hampir sama dengan Phaser, tetapi dalam versi baru mereka melakukannya lebih atau kurang normal. Saya terus menulis di Phaser dan menemukan cara membuat semuanya berfungsi dengan webpack seperti dulu: mengetik dan tes bisa dilampirkan ke semua ini. Saya menemukan bahwa Anda dapat mengambil
PixiJS secara terpisah, yang merupakan render webpack, dan menemukan banyak modul untuknya yang siap untuk bekerja dengannya.

PixiJS adalah pustaka hebat yang dapat di-render baik di WebGL atau di Kanvas. Selain itu, Anda bahkan dapat menulis kode seolah-olah untuk Canvas, dan itu akan dirender di WebGL. Pustaka ini dapat membuat 2D dengan sangat cepat. Hal utama adalah mengetahui cara kerjanya dengan memori, agar tidak jatuh ke posisi ketika memori selesai.
Saya secara terpisah merekomendasikan repositori
awesome-pixijs di GitHub, di mana Anda dapat menemukan berbagai modul. Kebanyakan saya suka
React-pixi . Kita bisa mengabaikan pemecahan masalah dengan tampilan ketika kita menulis fungsi imperatif langsung di controller untuk menggambar bentuk geometris, sprite, animasi, dan banyak lagi. Kita semua dapat menandai di BEJ. Kami datang dari dunia JSX dengan aplikasi bisnis kami dan dapat menggunakannya lebih lanjut. Itulah yang saya suka abstraksi. React-pixi memberi kita abstraksi yang akrab ini.
Saya juga menyarankan Anda untuk mengambil tween.js - mesin animasi terkenal yang sama dari Phaser, yang memungkinkan Anda membuat animasi deklaratif yang agak mirip dengan animasi CSS: kami membuat transisi antar negara, dan tween.js memutuskan bagi kami cara memindahkan objek secara tepat.
Jenis pemain: siapa mereka dan bagaimana berteman dengan mereka
Saya menemukan pemain yang berbeda, dan saya juga ingin memberi tahu Anda tentang pengujian mainan. Saya mengumpulkan rekan-rekan di satu ruangan tertutup dan tidak membiarkan mereka keluar sampai mereka menyelesaikan permainan. Sayangnya, tidak semua orang bisa menyelesaikan permainan, karena pada awalnya saya punya banyak bug. Untungnya, saya mulai menguji segera setelah setidaknya beberapa prototipe yang berfungsi muncul. Jujur, tes pertama gagal karena beberapa pemain tidak memulai apa pun. Itu memalukan, tetapi memberi saya tendangan yang memungkinkan saya untuk melanjutkan.
Saat mainan Anda siap, Anda dapat diterima dengan sangat baik, atau dengan garpu rumput dan obor. Semua orang menunggu penggemar dari game, menunggu kebahagiaan yang akan Anda berikan kepada mereka. Dan Anda memberi mereka sesuatu yang tidak berfungsi sama sekali, meskipun tampaknya itu bekerja untuk Anda. Ketika Anda memiliki mainan online, bahkan ada lebih banyak bug seperti itu.
Sebagai hasilnya, orang-orang yang paling menyenangkan yang saya temui adalah "peneliti" yang selalu menemukan lebih banyak mainan Anda daripada yang sebenarnya. Mereka dapat dengan senang hati menambahkannya dengan segala macam hal kecil, mendorong Anda untuk menambahkan sesuatu. Namun, sayangnya, komunikasi dengan orang-orang ini tidak memberikan hal yang penting - stabilitas mainan.
Ada pemain biasa yang datang semata-mata demi kipas. Mereka kadang-kadang bahkan tidak menyadari adanya serangga, entah bagaimana menyelinap masuk dalam perjalanan menuju kesenangan.
Kategori lain adalah pengumpul bug, yang hampir semuanya tidak berfungsi. Orang-orang ini perlu menjadi teman, meskipun mereka akan berbicara banyak hal negatif. Kami perlu memulai hubungan aneh dengan mereka: mereka menyakiti Anda, dan Anda mencoba mengambil sesuatu yang berguna bagi diri Anda dari mereka, "mari duduk di depan komputer Anda dan lihat." Anda perlu bekerja dengan mereka, karena pada akhirnya orang-orang inilah yang akan membuat kualitas permainan Anda.
Anda hanya perlu menguji orang yang masih hidup. Mata Anda kabur, dan pengujian pasti akan menunjukkan apa yang disembunyikan. Anda mengembangkan mainan dan menyelam lebih dalam, menggergaji beberapa fitur, tetapi bahkan mungkin tidak diperlukan. Anda pergi langsung ke konsumen Anda, menunjukkan kepada mereka dan menonton bagaimana mereka bermain, tombol mana yang mereka tekan. Ini memberi Anda insentif untuk melakukan apa yang Anda butuhkan. Anda melihat bahwa beberapa orang terus-menerus menekan Ctrl + S, karena mereka terbiasa menyimpan kode - yah, setidaknya membuat kode berjalan pada Ctrl + S, pemain akan merasa lebih nyaman. Anda perlu menciptakan lingkungan yang nyaman bagi pemain, untuk ini Anda harus mencintainya dan mengikutinya.
Aturan 80/20 bekerja: Anda membuat demo 20% dari seluruh waktu pengembangan game, dan untuk pemain sepertinya 80% permainan selesai. Persepsi berfungsi sehingga mekanika dasar siap, semuanya bergerak dan berfungsi, yang artinya game hampir siap, dan pengembang akan segera menyelesaikannya. Namun pada kenyataannya, pengembang masih memiliki jalan keluar dari 80%. Seperti yang saya katakan, untuk waktu yang lama saya harus mengerjakan dokumentasi sehingga bisa dimengerti oleh semua orang. Saya menunjukkannya kepada banyak orang yang mengucapkan komentar mereka, saya menyaringnya, mencoba memahami esensi dari pernyataan itu. Dan banyak waktu yang saya perlukan untuk mencari bug.
Jadi dalam pengembangan game, saya hanya bisa menyarankan Anda untuk melakukan demo: mereka menyenangkan semua orang, tidak memerlukan banyak waktu, dan tidak ada yang benar-benar mengharapkan apa pun dari demo. Menyelesaikan permainan adalah proses yang membosankan, tetapi memulai itu bagus.
Akhirnya, saya meninggalkan Anda tautan:
HolyJS 2019 Piter , sebuah konferensi untuk pengembang JavaScript, akan diadakan 24-25 Mei di St. Petersburg. Pembicara pertama sudah muncul di situs.
Anda juga dapat mengajukan permohonan laporan, Call for Papers buka hingga 11 Maret.
Harga tiket akan naik pada 1 Februari.