Seorang programmer yang menandatangani dirinya dengan nama samaran Fleabit telah mengembangkan bahasa pemrogramannya selama enam bulan . Pertanyaan segera muncul: bahasa lain? Untuk apa?
Berikut argumennya:
- โ , , , . , garbage collection .
- Rust : , , โ enum- ; pattern matching ; , ; .. , Rust : ยซ , ยป; ; /, , .
- JavaScript, Lua, Python Ruby; Rust โ , - , . , garbage collector, , โ , GC , . GameLisp โ , .
- GameLisp, โ , , . enum- Rust, , . "" , .
Pertama-tama, kesederhanaan sintaksis dan kesederhanaan interpreter diambil dari Lisp di GameLisp: implementasi GameLisp bersama dengan "perpustakaan standar" sekarang membutuhkan 36 KLOC, dibandingkan, misalnya, dengan 455 KLOC di Python. Di sisi lain, dibandingkan dengan Lisp biasa, GameLisp tidak memiliki daftar dan lebih sedikit fokus pada pemrograman fungsional dan data yang tidak dapat diubah; sebaliknya, seperti kebanyakan bahasa scripting, GameLisp difokuskan pada pemrograman berorientasi objek yang penting.
Sintaks berbasis Lisp bisa sangat banyak, tetapi Anda dengan cepat terbiasa menulis (.print console (+ 2 2)), dll., Bukan console.print (2 + 2). Sintaks ini jauh lebih sederhana dan lebih fleksibel daripada dalam bahasa skrip yang sudah dikenal: koma dianggap sebagai karakter spasi, dan dapat digunakan untuk meningkatkan keterbacaan di mana saja dalam kode; alih-alih dua jenis tanda kurung {} (), hanya tanda kurung bulat yang digunakan; kebanyakan karakter ASCII dapat digunakan dalam karakter, jadi I ~ <3 ~ Lisp! ~ ^ _ ^ adalah nama yang valid untuk suatu fungsi atau variabel; Tidak dibutuhkan; untuk memisahkan operasi, dll. Saya dapat mengatakan bahwa tanpa pengalaman sebelumnya dengan Lisp, hanya dalam beberapa malam saya dapat menulis ulang NIBBLES.BAS klasik di GameLisp: http://atari.ruvds.com/nibbles.html
Semua yang ada di "perpustakaan standar" GameLisp untuk I / O adalah fungsi prn untuk mencetak ke stdout; tidak ada keyboard / mouse yang berfungsi, tidak ada file, tidak ada grafik, tidak ada suara. Diasumsikan bahwa pengguna GameLisp sendiri mengimplementasikan di Rust semua alat antarmuka yang relevan secara khusus dalam proyeknya. Sebagai contoh pengikatan semacam itu, mesin minimalis untuk game browser diposting di https://gamelisp.rs/playground/ menggunakan wasm-bindgenyang menyediakan kode GameLisp dengan play: down?, play: press?, play: release?, play: mouse-x, play: mouse-y, play: fill, and play: draw. Port of Nibbles saya menggunakan mesin yang sama - saya baru saja menambahkan fungsi untuk memutar suara. Menarik untuk membandingkan ukurannya: NIBBLES.BAS asli adalah 24 KB; port saya di GameLisp adalah 9KB; File WebAssembly dengan runtime Rust yang dikompilasi, interpreter GameLisp, dan kode permainan adalah 2,5 MB, dan juga dilengkapi dengan pengikatan JavaScript 11 KB yang dihasilkan oleh wasm-bindgen.
Bersama dengan mesin minimalis di https://gamelisp.rs/playground/Menambahkan implementasi GameLisp dari tiga game klasik: pong, tetris, dan sapper. Tetris dan Minesweeper lebih besar dan lebih kompleks daripada pelabuhan Nibbles saya, dan ada banyak hal yang bisa dipelajari dari kode mereka.
Untuk mendemonstrasikan kemampuan GameLisp, saya telah memilih dua contoh; yang pertama menyangkut makro. Di NIBBLES.BAS, level ditentukan oleh blok baris SELECT CASE dengan loop bersarang:
SELECT CASE curLevel
CASE 1
sammy(1).row = 25: sammy(2).row = 25
sammy(1).col = 50: sammy(2).col = 30
sammy(1).direction = 4: sammy(2).direction = 3
CASE 2
FOR i = 20 TO 60
Set 25, i, colorTable(3)
NEXT i
sammy(1).row = 7: sammy(2).row = 43
sammy(1).col = 60: sammy(2).col = 20
sammy(1).direction = 3: sammy(2).direction = 4
CASE 3
FOR i = 10 TO 40
Set i, 20, colorTable(3)
Set i, 60, colorTable(3)
NEXT i
sammy(1).row = 25: sammy(2).row = 25
sammy(1).col = 50: sammy(2).col = 30
sammy(1).direction = 1: sammy(2).direction = 2
...
Semua loop ini memiliki struktur serupa, yang dapat disertakan dalam makro:
(let-macro set-walls (range ..walls)
`(do ~..(map (fn1
`(forni (i ~..range) (set-wall ~.._))) walls)))
Dengan makro ini, deskripsi semua level dikurangi empat, dan menjadi sedekat mungkin dengan deskripsi deklaratif seperti JSON:
(match @level
(1 (set-locations '(25 50 right) '(25 30 left)))
(2 (set-walls (20 60) (25 i))
(set-locations '(7 60 left) '(43 20 right)))
(3 (set-walls (10 40) (i 20) (i 60))
(set-locations '(25 50 up) '(25 30 down)))
...
Dalam bahasa tanpa makro - misalnya, dalam JavaScript - implementasi serupa akan mengaburkan seluruh deskripsi level dengan lambda:
switch (level) {
case 1: setLocations([25, 50, "right"], [25, 30, "left"]); break;
case 2: setWalls([20, 60], i => [25, i]);
setLocations([7, 60, "left"], [43, 20, "right"]); break;
case 3: setWalls([10, 40], i => [i, 20], i => [i, 60]);
setLocations([25, 50, "up"], [25, 30, "down"]); break;
...
Contoh ini dengan jelas menunjukkan bagaimana kode JavaScript dipenuhi dengan berbagai tanda baca dan kata fungsi, yang dapat Anda lakukan tanpanya.
Contoh kedua saya adalah tentang mesin negara. Implementasi gim saya memiliki struktur berikut:
(defclass Game
...
(fsm
(state Playing
(field blink-rate (Rate 0.2))
(field blink-on)
(field move-rate (Rate 0.3))
(field target)
(field prize 1)
(state Paused
(init-state ()
(@center "*** PAUSED ***" 0))
(wrap Playing:update (dt)
(when (play:released? 'p)
(@center " LEVEL {@level} " 0)
(@disab! 'Paused))))
(met update (dt)
...
(when (play:released? 'p)
(@enab! 'Paused) (return))
...
; Move the snakes
(.at @move-rate dt (fn0
(for snake in @snakes (when (> [snake 'lives] 0)
(let position (clone [[snake 'body] 0]))
...
; If player runs into any point, he dies
(when (@occupied? position)
(play:sound 'die)
(dec! [snake 'lives])
(dec! [snake 'score] 10)
(if (all? (fn1 (== 0 [_ 'lives])) @snakes)
(@enab! 'Game-Over)
(@enab! 'Erase-Snake snake))
(return))
...
(state Game-Over
(init-state ()
(play:fill ..(@screen-coords 10 (-> @grid-width (/ 2) (- 16))) ..(@screen-coords 7 32) 255 255 255)
(play:fill ..(@screen-coords 11 (-> @grid-width (/ 2) (- 15))) ..(@screen-coords 5 30) ..@background)
(@center "G A M E O V E R" 13))
(met update (dt)))))
Pada setiap frame (saat dipanggil dari window.requestAnimationFrame), mesin game memanggil metode Game.update. Di dalam kelas Game, automaton ditentukan dari status Init-Level, Playing, Erase-Snake, Game-Over, yang masing-masing mendefinisikan metode pembaruan dengan caranya sendiri. Dalam status Bermain, lima bidang privat ditentukan yang tidak dapat diakses dari negara bagian lain. Selain itu, status Bermain memiliki status Dijeda bertingkat, yaitu permainan bisa dalam kondisi Bermain atau bermain: kondisi dijeda. Konstruktor status Paused mencetak baris yang sesuai di layar setiap kali transisi ke status ini; metode pembaruan, dalam keadaan ini, memeriksa untuk melihat apakah tombol P telah ditekan lagi, dan jika ditekan dan dilepaskan, keluar dari keadaan Jeda, kembali ke keadaan Bermain "biasa". Metode pembaruan status Bermain menangani penekanan tombol,menghitung posisi baru para pemain, dan jika salah satu dari mereka menabrak dinding, maka status Game-Over atau ke status Erase-Snake akan berubah. Konstruktor status Erase-Snake menarik karena dibutuhkan sebagai parameter tautan ke ular, yang harus dihapus dengan indah sebelum memulai kembali level. Terakhir, untuk status Game-Over, konstruktor menampilkan pesan yang sesuai di layar, dan metode pembaruan kosong, yang berarti bahwa terlepas dari tombol yang ditekan, tidak ada yang baru akan ditampilkan di layar, dan tidak mungkin untuk keluar dari status ini.Terakhir, untuk status Game-Over, konstruktor menampilkan pesan yang sesuai di layar, dan metode pembaruan kosong, yang berarti bahwa apa pun tombol yang ditekan, tidak ada yang baru yang akan ditampilkan di layar, dan tidak mungkin untuk keluar dari status ini.Terakhir, untuk status Game-Over, konstruktor menampilkan pesan yang sesuai di layar, dan metode pembaruan kosong, yang berarti bahwa terlepas dari tombol yang ditekan, tidak ada yang baru yang akan ditampilkan di layar, dan tidak mungkin untuk keluar dari status ini.
Demikian pula, game dapat diimplementasikan dalam bahasa skrip klasik: kelas Game akan memiliki kelas bertingkat InitLevel, Playing, EraseSnake, GameOver, akan ada kolom currentState, dan metode Game.update akan mendelegasikan panggilan ke currentState.update. Di dalam kelas Playing, akan ada kelas Paused bersarang, dan metode Playing.update pada gilirannya akan mendelegasikan panggilan ke sub-objek. Makro pustaka standar menyembunyikan pembuatan otomatis bidang currentState dan metode pendelegasian sehingga pengembang game melihat implementasi status yang bermakna daripada pembingkaian boilerplate mereka.
Alih-alih mesin negara, dimungkinkan untuk mengimplementasikan Nibbles sebagai loop:
while (lives>0) {
InitLevel;
while (prize<10) {
Playing;
if (dies) {
EraseSnake;
break;
}
}
}
GameOver;
Ini adalah bagaimana game QBasic asli diimplementasikan. Untuk mesin browser, loop seperti itu akan dibungkus dalam generator dengan hasil setelah rendering setiap frame, dan Game.update akan terdiri dari panggilan ke iter-next! .. Saya lebih suka implementasi sebagai robot karena dua alasan: pertama, ini adalah cara kerja implementasi Tetris. yang dikutip oleh penulis GameLisp sebagai contoh; dan kedua, tidak ada yang aneh tentang generator di GameLisp dibandingkan dengan bahasa scripting lainnya. Tujuan utama automata adalah untuk mengimplementasikan status karakter game (menunggu, menyerang, melarikan diri, dll.), Yang tidak mungkin dilakukan melalui loop di dalam generator. Argumen tambahan yang mendukung automata adalah isolasi data yang terkait dengan masing-masing status satu sama lain.