3 poin oleh GN⁺ 3 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Aturan pertarungan Pokémon lebih mirip mesin aturan tempat efektivitas tipe, jurus, stat, dan ability saling terkait, sehingga bisa diekspresikan dengan ringkas lewat model relasi dan aturan di Prolog
  • Prolog menyimpan fakta dengan predikat seperti pokemon/1 dan type/2, lalu menggunakan variabel berawalan huruf besar dan unifikasi untuk menemukan Pokémon yang cocok dengan kondisi tipe atau jurus
  • Untuk mencari Pokémon yang mempelajari Freeze-Dry, bertipe Ice, dan memiliki Special Attack di atas 120, kueri Prolog lebih singkat daripada beberapa EXISTS di SQL
  • Tim draft dapat dinyatakan dengan predikat seperti alex/1 dan morry/1, dan pada aturan priority move dapat ditambahkan kondisi pengecualian serta efek Prankster secara bertingkat
  • Spreadsheet seperti Techno's Prep Doc memang kuat, tetapi database Prolog lebih fleksibel untuk kueri kombinasi sebarang, dan diimplementasikan dengan prologdex serta Scryer Prolog

Mengapa aturan pertarungan Pokémon cocok untuk pemrograman logika

  • Pertarungan Pokémon lebih mirip mesin aturan dengan banyak aturan yang saling terkait secara kompleks, dan pemrograman logika seperti Prolog cocok untuk mengekspresikan relasi semacam ini secara ringkas
  • Pokémon adalah karakter dengan nama spesies, dan jumlahnya lebih dari 1.000 spesies, dari Bulbasaur #1) hingga Pecharunt #1025)
  • Dalam pertarungan seri utama, dua tim yang masing-masing terdiri dari 6 Pokémon saling bertarung, dan tiap Pokémon biasanya memilih salah satu dari 4 jurus yang umumnya memberi damage ke lawan; pemain menang jika berhasil menurunkan seluruh HP tim lawan menjadi 0
  • Performa dalam pertarungan bergantung pada base stat, daftar jurus yang bisa dipelajari, ability, dan tipe, sehingga banyaknya kombinasi membuatnya layak dilacak dengan software
  • Tipe melekat pada jurus maupun Pokémon, dan bila tipe jurus unggul terhadap tipe lawan maka damage menjadi 2x, sedangkan bila lemah menjadi 1/2x
  • Modifikasi tipe bersifat kumulatif
    • Scizor) bertipe Bug/Steel, dan karena keduanya lemah terhadap Fire, ia menerima damage 4x dari jurus Fire
    • Jika memakai jurus Electric ke Swampert) yang bertipe Water/Ground, damage menjadi 0 karena imun dari Ground

Model dasar Prolog

  • Dalam Prolog, relasi dideklarasikan sebagai predikat (predicate)
pokemon(bulbasaur).
pokemon(ivysaur).
pokemon(venusaur).
pokemon(charmander).
pokemon(charmeleon).
pokemon(charizard).
pokemon(squirtle).
pokemon(wartortle).
pokemon(blastoise).
  • pokemon/1 adalah predikat bernama pokemon dengan satu argumen, dan kueri seperti pokemon(squirtle). memeriksa apakah pernyataan itu bisa dibuat benar
?- pokemon(squirtle).
   true.

?- pokemon(alex).
   false.
  • Tipe Pokémon dapat dinyatakan sebagai relasi dua argumen seperti type/2, dan Pokémon dengan dua tipe cukup diberi dua fakta type untuk Pokémon yang sama
type(bulbasaur, grass).
type(bulbasaur, poison).
type(charmander, fire).
type(charizard, fire).
type(charizard, flying).
type(squirtle, water).
  • Nama yang diawali huruf besar adalah variabel, dan Prolog akan mencoba mengunifikasikan kueri yang berisi variabel dengan semua nilai yang mungkin
?- type(squirtle, Type).
   Type = water.

?- type(venusaur, Type).
   Type = grass
;  Type = poison.
  • Jika argumen pertama dibuat variabel seperti pada type(Pokemon, grass)., kita bisa menemukan semua Pokémon bertipe Grass, dan pada data nyata hasilnya ada 164
  • Tanda koma berarti semua predikat harus terpenuhi, dan nama variabel yang sama harus memiliki nilai yang sama di dalam kueri
?- type(Pokemon, water), type(Pokemon, ice).
   Pokemon = dewgong
;  Pokemon = cloyster
;  Pokemon = lapras
;  Pokemon = laprasgmax
;  Pokemon = spheal
;  Pokemon = sealeo
;  Pokemon = walrein
;  Pokemon = arctovish
;  Pokemon = ironbundle
;  false.
  • Seperti Iron Bundle), stat dan jurus yang bisa dipelajari juga dapat dikueri sebagai relasi
?- pokemon_spa(ironbundle, SpA).
   SpA = 124.

?- learns(ironbundle, Move), move_category(Move, special).
   Move = aircutter
;  Move = blizzard
;  Move = chillingwater
;  Move = freezedry
;  Move = hydropump
;  Move = hyperbeam
;  Move = icebeam
;  Move = icywind
;  Move = powdersnow
;  Move = swift
;  Move = terablast
;  Move = waterpulse
;  Move = whirlpool.
  • Jika dicampur dengan constraint seperti SpA #> 120, kita bisa langsung mencari Pokémon yang Special Attack-nya di atas 120, mempelajari Freeze-Dry, dan bertipe Ice
?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).
   Pokemon = glaceon, SpA = 130
;  Pokemon = kyurem, SpA = 130
;  Pokemon = kyuremwhite, SpA = 170
;  Pokemon = ironbundle, SpA = 124
;  false.
  • Aturan (rule) di Prolog terdiri dari head dan body; jika body benar maka head juga terunifikasi
damaging_move(Move) :-
  move_category(Move, physical)
; move_category(Move, special).
  • Aturan ini mengklasifikasikan jurus Physical atau Special sebagai jurus yang memberi damage secara langsung
?- damaging_move(tackle).
   true.
?- damaging_move(rest).
   false.

Ekspresi kueri yang dibandingkan dengan SQL

  • Contoh-contoh sejauh ini secara logis hanyalah kombinasi sederhana and dan or, tetapi di Prolog kueri relasi menjadi lebih ringkas dan lebih mudah diubah dibanding SQL
  • Jika data yang sama disusun dengan SQL, Pokémon, tipe, dan move dapat ditempatkan di tabel terpisah
CREATE TABLE pokemon (pokemon_name TEXT, special_attack INTEGER);
CREATE TABLE pokemon_types(pokemon_name TEXT, type TEXT);
CREATE TABLE pokemon_moves(pokemon_name TEXT, move TEXT, category TEXT);
  • Untuk mencari Pokémon bertipe Ice yang mempelajari Freeze-Dry dan memiliki Special Attack lebih besar dari 120 dengan SQL, kita harus memakai EXISTS beberapa kali
SELECT DISTINCT pokmeon, special_attack
FROM pokemon as p
WHERE
  p.special_attack > 120
  AND EXISTS (
    SELECT 1
    FROM pokemon_moves as pm
    WHERE p.pokemon_name = pm.pokemon_name AND move = 'freezedry'
  )
  AND EXISTS (
    SELECT 1
    FROM pokemon_types as pt
    WHERE p.pokemon_name = pt.pokemon_name AND type = 'ice'
  );
  • Kueri Prolog yang setara cukup mencantumkan relasi yang dibutuhkan apa adanya
?- pokemon_spa(Pokemon, SpA),
SpA #> 120,
learns(Pokemon, freezedry),
type(Pokemon, ice).
  • Saat kondisi terus bertambah, kueri SQL mudah menjadi rumit, tetapi kueri Prolog tetap mudah dibaca dan diubah setelah terbiasa dengan cara kerja variabel

Cara menumpuk aturan pertarungan berlapis-lapis

  • Dalam pertarungan Pokémon ada banyak aturan interaksi seperti serangan meleset, kenaikan/penurunan stat, efek item, rentang damage, status, efek field seperti cuaca·terrain·Trick Room, Ability, dan distribusi stat dasar sebelumnya
  • Saat membuat perangkat lunak untuk Pokémon, kompleksitas ini harus ditangani sambil menjaga model tetap dalam bentuk yang masih dapat dikelola
  • Prolog kuat dalam model kueri yang menggambarkan kombinasi ad hoc dan pelapisan aturan yang konsisten
  • Kompleksitas seperti ini bisa dilihat langsung di damage calculator

Draft league dan kueri move prioritas

  • Dalam draft Pokémon, setiap Pokémon memiliki nilai tertentu, dan pemain membangun tim berisi sekitar 8~11 Pokémon dengan memilih Pokémon dalam batas poin yang telah ditentukan
  • Karena pertarungan sebenarnya adalah 6v6, persiapan untuk mengantisipasi kombinasi enam Pokémon yang mungkin dibawa lawan dan memilih enam Pokémon untuk menghadapinya menjadi penting
  • Pokémon yang sudah dipilih sendiri bisa langsung dinyatakan dengan predikat seperti alex/1
alex(meowscarada).
alex(weezinggalar).
alex(swampertmega).
alex(latios).
alex(volcarona).
alex(tornadus).
alex(politoed).
alex(archaludon).
alex(beartic).
alex(dusclops).
  • Kueri untuk mencari Pokémon di tim ini yang mempelajari Freeze-Dry itu sederhana, tetapi hasilnya tidak ada
?- alex(Pokemon), learns(Pokemon, freezedry).
   false.
  • Urutan giliran dalam pertarungan pada dasarnya ditentukan oleh Speed, tetapi move memiliki prioritas (priority), dan move dengan prioritas lebih tinggi akan berjalan lebih dulu
  • Sebagian besar move memiliki prioritas 0, tetapi move dengan prioritas 1 seperti Accelerock akan bergerak lebih dulu daripada move prioritas 0 milik Pokémon yang lebih cepat
  • Move dengan prioritas positif yang dipelajari Pokémon tertentu dapat dicari dengan menggabungkan learns/2, move_priority/2, dan kondisi prioritas
  • Kueri sederhana juga akan mencakup move yang lebih bermakna di Double Battles seperti Helping Hand dan Ally Switch, atau move yang minim makna kompetitif seperti Bide
  • \+/1 bernilai benar saat tujuannya gagal, dan dif/2 berarti dua term berbeda, jadi kita bisa menambahkan aturan untuk mengecualikan move khusus Double Battles dan Bide
learns_priority(Mon, Move, Priority) :-
  learns(Mon, Move),
  \+ doubles_move(Move),
  dif(Move, bide),
  move_priority(Move, Priority),
  Priority #> 0.
  • Jika move protektif seperti Protect, Detect, Endure, dan Magic Coat juga dikecualikan, yang tersisa hanyalah move prioritas yang benar-benar bisa memberi damage atau efek negatif pada lawan
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
   Pokemon = meowscarada, Move = quickattack, Priority = 1
;  Pokemon = meowscarada, Move = suckerpunch, Priority = 1
;  Pokemon = beartic, Move = aquajet, Priority = 1
;  Pokemon = dusclops, Move = shadowsneak, Priority = 1
;  Pokemon = dusclops, Move = snatch, Priority = 4
;  Pokemon = dusclops, Move = suckerpunch, Priority = 1
;  false.
  • Jika aturan yang sama diterapkan pada predikat tim lawan, move prioritas yang dimiliki lawan juga bisa langsung ditemukan
?- morry(Pokemon), learns_priority(Pokemon, Move, Priority).
   Pokemon = mawilemega, Move = snatch, Priority = 4
;  Pokemon = mawilemega, Move = suckerpunch, Priority = 1
;  Pokemon = walkingwake, Move = aquajet, Priority = 1
;  Pokemon = ursaluna, Move = babydolleyes, Priority = 1
;  Pokemon = lokix, Move = feint, Priority = 2
;  Pokemon = lokix, Move = firstimpression, Priority = 2
;  Pokemon = lokix, Move = suckerpunch, Priority = 1
;  Pokemon = alakazam, Move = snatch, Priority = 4
;  Pokemon = skarmory, Move = feint, Priority = 2
;  Pokemon = froslass, Move = iceshard, Priority = 1
;  Pokemon = froslass, Move = snatch, Priority = 4
;  Pokemon = froslass, Move = suckerpunch, Priority = 1
;  Pokemon = dipplin, Move = suckerpunch, Priority = 1.

Ekstensi ability Prankster

  • Pokémon dengan ability Prankster mendapatkan tambahan prioritas +1 untuk move status, dan efek ini juga bisa ditambahkan ke aturan learns_priority/3 yang sudah ada
  • Di dalam tim, Tornadus memiliki ability Prankster
?- alex(Pokemon), pokemon_ability(Pokemon, prankster).
   Pokemon = tornadus
;  false.
  • Dengan menggunakan sintaks if/then ->/2 di Prolog, jika Pokémon adalah Prankster dan kategori move adalah status, maka 1 ditambahkan ke prioritas dasar; jika tidak, prioritas dasar dipakai apa adanya
learns_priority(Mon, Move, Priority) :-
  learns(Mon, Move),
  \+ doubles_move(Move),
  \+ protection_move(Move),
  Move \= bide,
  move_priority(Move, BasePriority),
  (
    pokemon_ability(Mon, prankster), move_category(Move, status) ->
      Priority #= BasePriority + 1
    ; Priority #= BasePriority
  ),
  Priority #> 0.
  • Setelah aturan ini ditambahkan, query yang sama akan memasukkan move status Tornadus seperti Agility, Defog, Nasty Plot, Rain Dance, Tailwind, Taunt, dan Toxic dengan prioritas 1
  • Dengan memperluas satu aturan untuk turut mencerminkan efek ability, keunggulan layering di Prolog menjadi terlihat

Perbandingan dengan alat berbasis spreadsheet

  • Di komunitas Pokémon, sudah ada sumber daya untuk mencari informasi seperti move prioritas milik tim lawan; contohnya advanced Google Sheets seperti “Techno’s Prep Doc”
  • Spreadsheet ini dapat menghasilkan banyak informasi matchup saat tim dimasukkan, serta menyediakan dukungan untuk berbagai format, materi visual yang mudah dipindai, dan autocomplete
  • Rumus untuk mencari move prioritas menggabungkan FILTER, VLOOKUP, dan INDIRECT, dan INDIRECT mengembalikan referensi sel
={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}
  • Di sheet Backend, semua move didaftarkan, dan struktur ini mirip dengan versi yang melakukan hardcode terhadap query Prolog
  • Basis data Prolog lebih mudah dikembangkan daripada pendekatan yang melakukan hardcode daftar move penting, dan bisa menanyakan move apa pun
  • Pertanyaan komposisional yang tidak ada di alat lama juga bisa diungkapkan dengan singkat, misalnya mencari move Special yang dipelajari Tornadus dan sangat efektif terhadap anggota tim Justin
?- justin(Target), learns(tornadus, Move), super_effective_move(Move, Target), move_category(Move, special).
   Target = charizardmegay, Move = chillingwater
;  Target = terapagosterastal, Move = focusblast
;  Target = alomomola, Move = grassknot
;  Target = scizor, Move = heatwave
;  Target = scizor, Move = incinerate
;  Target = runerigus, Move = chillingwater
;  Target = runerigus, Move = darkpulse
;  Target = runerigus, Move = grassknot
;  Target = runerigus, Move = icywind
;  Target = screamtail, Move = sludgebomb
;  Target = screamtail, Move = sludgewave
;  Target = trapinch, Move = chillingwater
;  Target = trapinch, Move = grassknot
;  Target = trapinch, Move = icywind
;  false.

Catatan implementasi dan keterbatasan

1 komentar

 
GN⁺ 3 jam lalu
Komentar Lobste.rs
  • Penasaran apakah ada orang yang benar-benar memakai Prolog secara produktif. Entah untuk pekerjaan atau penggunaan pribadi, sejauh ini saya cuma melihat contoh-contoh mainan seperti ini

    • Saya cukup suka Prolog, dan sebagai orang yang mungkin membuat implementasi language server Prolog yang paling luas dipakai, saya cukup sering memakainya untuk skrip kecil.
      Secara teknis saya juga punya satu atau lebih kode Prolog yang berjalan di produksi; salah satunya dashboard analitik internal, dan dulu backend server untuk aplikasi iOS juga saya tulis dengan Prolog. Dalam proses itu saya juga sampai membuat pustaka klien HTTP/2 untuk Prolog agar bisa mengirim notifikasi APNS tanpa layanan eksternal
    • Saya tidak tahu apakah ini jawaban yang memuaskan, tetapi sejak menulis posting blog itu saya jadi mulai menggunakan Prolog untuk masalah-masalah yang porosnya benar-benar berbeda dari kode yang pernah saya tulis sebelumnya.
      Memang ada orang di komunitas Prolog yang suka memakainya untuk hal seperti web server, tetapi bagi saya rasanya lebih seperti membuka pohon skill yang berbeda. Misalnya, saya jadi lebih peka mengenali kapan parser khusus atau DSL cocok untuk suatu masalah.
      Dengan pengetahuan yang saya dapat setelah menulis artikel itu, saya mengimplementasikan ulang subset yang berguna dari mesin logika pajak IRS Fact Graph. Prolog ternyata sangat cocok untuk pekerjaan ini, karena ia memunculkan sudut-sudut tak terdokumentasi yang mudah terlewat dalam implementasi imperatif, lalu memaksa kita menyelesaikannya.
      Bagian “eksekusi”-nya masih belum selesai, tetapi parsing-nya sudah cukup lengkap sehingga saya bisa menulis draf dokumen yang lumayan bagus. Masih ada satu fitur besar yang belum ada, yaitu aritmetika tanggal, dan kalau itu sudah masuk saya akan menuliskannya secara terpisah.
      Dengan DCG, Prolog sangat bagus untuk parsing teks terstruktur yang kompleks. Dulu kalau awk sudah tidak sanggup, saya biasanya berpikir ke Python atau JS, tetapi sekarang Prolog terasa pas untuk situasi yang butuh struktur dan disiplin. Ada juga kepuasan seperti gaya lama saat menulis codebase kompleks yang muat dalam satu file, dan tidak terlalu dipadatkan seperti APL.
      Contohnya sendiri memang sepele, tetapi kasus Pokémon tidak. Banyak contoh yang tampak sepele sebenarnya dimungkinkan karena sudah ada kode yang mengimplementasikan mekanika pertarungan yang sangat menyeluruh sampai tingkat yang nyaris lucu karena kompleksnya. Saya tertarik membuat mesin aturan Prolog yang menangani sebagian pekerjaan alat yang sudah ada, dan sudah sedikit mencobanya; kelebihannya adalah, dibanding kode imperatif, depth-first search lebih mudah menyingkap kemungkinan-kemungkinan dan lebih enak dirawat
    • Saya kadang memakainya untuk analisis program. Cukup banyak alat menyimpan informasi program dalam Datalog lalu memungkinkan kita menulis kueri di atasnya, tetapi kadang saya memakai alat yang menyediakan SWI-Prolog sungguhan agar bisa memanfaatkan backtracking atau fungsi
    • Blog saya dibuat dengan Prolog, dan kode sumbernya juga terbuka.
      Dokumentasi Scryer Prolog juga dihasilkan oleh program Prolog yang saya sebut DocLog. Saya juga membuat beberapa pustaka yang dipakai program-program itu. SWI Prolog sendiri juga memakai SWI secara langsung untuk situs webnya, web IDE bernama Swish, server ClioPatria, dan lain-lain
  • Saya pernah mengatur informasi dengan memakai SQLite semacam spreadsheet yang type-safe. Itu bisa dilakukan bahkan tanpa antarmuka CRUD apa pun di atasnya.
    Meski begitu, itu tidak selalu menjadi bahasa yang paling enak dilihat. Saya juga sempat melihat-lihat Datalog, tetapi kebanyakan implementasinya tampak lebih ditujukan untuk ditanamkan ke program yang lebih besar daripada sebagai alat untuk mencatat informasi dengan mudah seperti pada artikel ini.
    Mungkin saja Prolog memang alat yang seharusnya dipakai

    • Untuk Datalog bergaya basis data eksternal, ada Mangle dan Datomic, jadi mungkin cocok, walau saya sendiri belum pernah memakainya.
      Prolog adalah superset dari Datalog, jadi semua yang bisa dilakukan Datalog bisa dilakukan Prolog, dan bahkan lebih. Namun “lebih”-nya itu kadang justru menjadi masalah: sekarang ia adalah bahasa yang Turing-complete sehingga bisa saja tidak pernah selesai, penalarannya bisa sedikit lebih sulit, dan bisa dipakai dengan cara yang tidak aman.
      Datalog sering juga lebih cepat karena, berkat pembatasannya, ia bisa mengambil jalan pintas tertentu.
      Terakhir, di Prolog data itu sama dengan kode, yakni ada homoiconicity, sehingga operasi buat/ubah/hapus pada dasarnya berarti mengubah source code. Itu memang bisa dilakukan secara dinamis, tetapi sulit berharap alurnya akan mulus, dan tetap perlu sinkronisasi manual sampai batas tertentu antara file dan program yang sudah dimuat
  • Saya ingin belajar Prolog lebih dalam lalu memakainya untuk kueri instruction set.
    Namun memodelkan logika, termasuk integer dari sisi kuantitas dan pada level bit, ternyata cukup sulit