- Pokémon savaş kuralları, tip eşleşmeleri, hareketler, istatistikler ve yeteneklerin iç içe geçtiği bir kural motoruna daha yakın olduğundan Prolog’un ilişki ve kural modeliyle özlü biçimde ifade edilebilir
- Prolog,
pokemon/1, type/2 gibi yüklemlerle olguları tanımlar; büyük harfle başlayan değişkenler ve birleştirme sayesinde tip ve hareket koşullarına uyan Pokémon’ları bulur
- Freeze-Dry öğrenen, Ice tipinde olan ve Special Attack değeri 120’den büyük Pokémon’ları bulmak için Prolog sorgusu, SQL’de birden fazla
EXISTS kullanmaktan daha kısadır
- Draft takımları
alex/1, morry/1 gibi yüklemlerle ifade edilir ve öncelik hareketi kurallarına hariç tutma koşulları ile Prankster etkisi katman katman eklenebilir
- Techno's Prep Doc gibi e-tablolar güçlüdür, ancak Prolog veritabanı rastgele kombinasyon sorgularında daha esnektir ve prologdex ile Scryer Prolog kullanılarak uygulanmıştır
Pokémon savaş kuralları neden mantıksal programlamaya uygun?
- Pokémon savaşları, birçok kuralın karmaşık biçimde birbirine geçtiği bir kural motoruna daha yakındır ve Prolog gibi mantıksal programlama dilleri bu ilişkileri özlü biçimde ifade etmek için uygundur
- Pokémon, tür adı taşıyan karakterlerdir ve Bulbasaur #1) ile Pecharunt #1025) arasında 1.000’den fazla tür vardır
- Ana seri savaşlarda, 6 üyeden oluşan takımlar karşılaşır; her Pokémon genellikle rakibe hasar veren 4 hareketten birini seçer ve rakip takımın tüm HP’sini 0’a indirirse kazanır
- Savaştaki performans; temel istatistiklere, öğrenebildiği hareket listesine, yeteneklere ve tiplere bağlı olarak değişir; kombinasyon sayısı çok fazla olduğu için bunları yazılımla takip etmek değerlidir
- Tipler hem hareketlere hem de Pokémon’lara atanır; bir hareket tipi rakibin tipine karşı güçlüyse 2 kat, zayıfsa 1/2 kat hasar verir
- Tip çarpanları birikir
- Scizor) Bug/Steel tipindedir ve her ikisi de Fire’a zayıf olduğu için Fire hareketlerinden 4 kat hasar alır
- Water/Ground tipi Swampert) üzerine Electric hareketi kullanılırsa, Ground bağışıklığı nedeniyle hasar 0 olur
Prolog’un temel modeli
- Prolog’da ilişkiler yüklem (predicate) ile tanımlanır
pokemon(bulbasaur).
pokemon(ivysaur).
pokemon(venusaur).
pokemon(charmander).
pokemon(charmeleon).
pokemon(charizard).
pokemon(squirtle).
pokemon(wartortle).
pokemon(blastoise).
pokemon/1, adı pokemon olan ve tek argüman alan bir yüklemdir; pokemon(squirtle). gibi bir sorgu, ilgili ifadenin doğru yapılıp yapılamayacağını denetler
?- pokemon(squirtle).
true.
?- pokemon(alex).
false.
- Pokémon tipleri
type/2 gibi iki argümanlı ilişkilerle ifade edilebilir; iki tipe sahip bir Pokémon için aynı Pokémon hakkında iki type olgusu tanımlanır
type(bulbasaur, grass).
type(bulbasaur, poison).
type(charmander, fire).
type(charizard, fire).
type(charizard, flying).
type(squirtle, water).
- Büyük harfle başlayan adlar değişkentir ve Prolog, değişken içeren sorguları mümkün olan tüm değerlerle birleştirmeyi (unify) dener
?- type(squirtle, Type).
Type = water.
?- type(venusaur, Type).
Type = grass
; Type = poison.
- İlk argümanı
type(Pokemon, grass). örneğindeki gibi değişken yaparsanız, tüm Grass tipi Pokémon’ları bulabilirsiniz; gerçek veride 164 sonuç döner
- Virgül, birden fazla yüklemin aynı anda sağlanması gerektiği anlamına gelir; aynı değişken adı da sorgu içinde aynı değeri almak zorundadır
?- 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.
- Iron Bundle) örneğinde olduğu gibi istatistikler ve öğrenilebilen hareketler de ilişkiler üzerinden sorgulanabilir
?- 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.
SpA #> 120 gibi kısıtlar eklenirse, Special Attack değeri 120’den büyük olan, Freeze-Dry öğrenen ve Ice tipindeki Pokémon’lar doğrudan bulunabilir
?- 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.
- Prolog’daki kural (rule) baş ve gövdeden oluşur; gövde doğruysa baş da birleştirilir
damaging_move(Move) :-
move_category(Move, physical)
; move_category(Move, special).
- Bu kural, Physical veya Special hareketleri doğrudan hasar veren hareketler olarak sınıflandırır
?- damaging_move(tackle).
true.
?- damaging_move(rest).
false.
SQL ile karşılaştırılan sorgu ifadeleri
- Şimdiye kadarki örnekler mantıksal olarak basit
and ve or birleşimleri olsa da, Prolog’da ilişki sorguları SQL’e göre daha kısa ve değiştirmesi daha kolay bir biçime dönüşür
- Aynı veriyi SQL ile kurarsanız Pokémon, tip ve hareketleri ayrı tablolarda tutabilirsiniz
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);
- Freeze-Dry öğrenen, Ice tipinde olan ve Special Attack değeri 120’den büyük Pokémon’ları SQL ile bulmak için
EXISTS ifadesini birkaç kez kullanmanız gerekir
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'
);
- Aynı Prolog sorgusu ise gerekli ilişkileri olduğu gibi sıralar
?- pokemon_spa(Pokemon, SpA),
SpA #> 120,
learns(Pokemon, freezedry),
type(Pokemon, ice).
- Koşullar eklenmeye devam ettikçe SQL sorguları kolayca karmaşıklaşabilir, ancak Prolog sorguları değişkenlerin nasıl çalıştığına alışınca okunması ve düzenlenmesi kolay bir yapıyı korur
Savaş kurallarını katman katman inşa etme yöntemi
- Pokémon savaşlarında ıskalama, stat artışı ve düşüşü, eşya etkileri, hasar aralığı, durum etkileri, hava, arazi ve Trick Room gibi alan etkileri, yetenekler, savaş öncesi stat dağılımı gibi birçok etkileşim kuralı bulunur
- Pokémon için yazılım geliştirirken bu karmaşıklığı ele alırken modeli yönetilebilir bir biçimde tutmak gerekir
- Prolog, anlık kombinasyonları betimleyen sorgu modeli ve tutarlı kural katmanlaması konusunda güçlüdür
- damage calculator ile bu karmaşıklığı doğrudan görebilirsiniz
Draft ligi ve öncelikli hareket sorguları
- Pokémon draft formatında her Pokémon’un bir değeri vardır ve oyuncular belirli puan sınırı içinde Pokémon seçerek yaklaşık 8 ila 11 üyeli bir takım kurar
- Gerçek savaşlar 6v6 olduğu için, rakibin getirebileceği altı Pokémon’luk kombinasyonlara hazırlanmak ve buna karşı çıkarılacak altı Pokémon’u seçmek önemlidir
- Kendi draft ettiğiniz Pokémon’ları
alex/1 gibi bir yüklemle doğrudan ifade edebilirsiniz
alex(meowscarada).
alex(weezinggalar).
alex(swampertmega).
alex(latios).
alex(volcarona).
alex(tornadus).
alex(politoed).
alex(archaludon).
alex(beartic).
alex(dusclops).
- Bu takımda Freeze-Dry öğrenen Pokémon’u bulma sorgusu basittir, ancak sonuç yoktur
?- alex(Pokemon), learns(Pokemon, freezedry).
false.
- Savaş sırası temelde Speed tarafından belirlenir, ancak hareketlerin öncelik (priority) değeri vardır ve daha yüksek öncelikli hareket önce gerçekleşir
- Çoğu hareketin önceliği 0’dır, ancak Accelerock gibi önceliği 1 olan hareketler, daha hızlı bir Pokémon’un önceliği 0 olan hareketinden önce gider
- Belirli bir Pokémon’un öğrendiği pozitif öncelikli hareketler
learns/2, move_priority/2 ve öncelik koşulu birleştirilerek bulunabilir
- Basit sorgu, Double Battles’ta daha anlamlı olan Helping Hand ve Ally Switch gibi hareketleri ya da pratikte anlamı düşük Bide gibi hareketleri de içerir
\+/1, hedef başarısız olduğunda doğru olur ve dif/2 iki terimin farklı olduğunu ifade eder; bu nedenle Double Battles için olan hareketleri ve Bide’ı dışlayan bir kural ekleyebilirsiniz
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
dif(Move, bide),
move_priority(Move, Priority),
Priority #> 0.
- Protect, Detect, Endure ve Magic Coat gibi savunma amaçlı hareketler de çıkarılırsa, geriye pratikte rakibe hasar ya da olumsuz etki verebilen öncelikli hareketler kalır
?- 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.
- Aynı kural rakip takım yüklemine uygulanırsa, rakibin sahip olduğu öncelikli hareketler de hemen bulunabilir
?- 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.
Prankster yeteneğinin genişletilmesi
- Prankster yeteneğine sahip Pokémon’ların durum hareketleri için öncelik değeri ek olarak +1 olur; bu etki mevcut
learns_priority/3 kuralına da eklenebilir
- Takım içinde Tornadus, Prankster yeteneğine sahiptir
?- alex(Pokemon), pokemon_ability(Pokemon, prankster).
Pokemon = tornadus
; false.
- Prolog’un
->/2 if/then sözdizimi kullanılarak, Pokémon Prankster ise ve hareket kategorisi status ise temel önceliğe 1 eklenmesi, aksi halde temel önceliğin olduğu gibi kullanılması sağlanabilir
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.
- Bu kuraldan sonra aynı sorgu, Tornadus’un Agility, Defog, Nasty Plot, Rain Dance, Tailwind, Taunt, Toxic gibi durum hareketlerini öncelik 1 ile içerir
- Tek bir kuralı genişleterek yetenek etkilerini de yansıtabilmek, Prolog’un katmanlı yapı avantajını gösterir
E-tablo tabanlı araçlarla karşılaştırma
- Pokémon topluluğunda rakip takımın öncelikli hareketleri gibi bilgileri bulmaya yarayan kaynaklar zaten var; bunların başında “Techno’s Prep Doc” gibi gelişmiş Google Sheets dosyaları geliyor
- Bu e-tablo, takımları girdiğinizde çok sayıda eşleşme bilgisi üretir ve çeşitli format desteği, hızlı taranabilen görsel materyaller ve otomatik tamamlama sunar
- Öncelikli hareketleri bulan formül,
FILTER, VLOOKUP, INDIRECT birleşimini kullanır; INDIRECT ise hücre referansı döndürür
={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"))}
- Backend sayfasında tüm hareketler listelenmiştir ve bu yapı, Prolog sorgularının hardcode edildiği bir sürüme daha yakındır
- Prolog veritabanı, dikkate değer hareket listesini hardcode etme yaklaşımına göre daha ölçeklenebilirdir ve herhangi bir hareket sorgulanabilir
- Örneğin Tornadus’un öğrenebildiği Special hareketler arasında Justin’in takım üyelerine karşı süper etkili olanları bulmak gibi, mevcut araçlarda yer almayan bileşik sorular da kısa biçimde ifade edilebilir
?- 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.
Uygulama notları ve sınırlamalar
1 yorum
Lobste.rs görüşleri
Prolog’u gerçekten üretken şekilde kullanan biri var mı merak ediyorum. İş için ya da kişisel kullanım için olabilir; şimdiye kadar sadece böyle oyuncak örnekler gördüm
Teknik olarak üretimde çalışan bir veya daha fazla Prolog kodum da var; bunlardan biri dahili analiz panosu, eskiden de bir iOS uygulamasının backend sunucusunu Prolog ile yazmıştım. Bu sırada, harici bir servis olmadan APNS bildirimleri göndermek için Prolog için bir HTTP/2 istemci kütüphanesi de yazmış oldum
Prolog topluluğunda onu web sunucusu gibi şeyler için kullanmayı sevenler elbette var, ama bana göre bu daha çok farklı bir skill tree açmak gibi hissettiriyor. Mesela, hangi problemlerde özel bir parser ya da DSL’in iyi uyacağını daha iyi fark etmeye başlıyorsunuz
Bu yazıyı yazdıktan sonra edindiğim bilgiyle IRS Fact Graph’in vergi mantığı motorunun yararlı bir alt kümesini yeniden uyguladım. Prolog bu iş için şaşırtıcı derecede iyi uyuyor çünkü imperatif bir uygulamada gözden kaçabilecek dokümante edilmemiş köşeleri ortaya çıkarıyor ve bunları çözmeye zorluyor
“Çalıştırma” kısmını henüz bitiremedim ama parsing yeterince tamamlandı ve bununla gayet iyi bir belge taslağı yazabildim. Büyük eksik parçalardan biri tarih aritmetiği; onu da ekleyince ayrıca paylaşmayı planlıyorum
DCG kullanıldığında Prolog, karmaşık yapılandırılmış metinleri parse etmekte harika. Eskiden awk yetmediğinde Python ya da JS düşünürdüm, ama şimdi yapı ve disiplin gereken yerlerde Prolog’un iyi uyduğunu görüyorum. Tek bir dosyaya sığan karmaşık bir kod tabanı yazmanın eski usul hissi de tatmin edici; ayrıca APL kadar aşırı kısaltılmış da değil
Örneğin kendisi küçük olabilir ama Pokémon örneği öyle değil. Küçük gibi görünen çoğu örnek de, çok kapsamlı biçimde uygulanmış gülünç derecede karmaşık savaş mekanikleri zaten mevcut olduğu için mümkün oldu. Mevcut araçların yaptığı işin bir kısmını yapan Prolog tabanlı bir kural motoru oluşturmaya ilgim var ve bunu yavaş yavaş deniyorum; avantajı, imperatif koda kıyasla olasılıkları depth-first search ile daha kolay ortaya çıkarması ve bakımının daha kolay olması
Scryer Prolog belgeleri de DocLog adını verdiğim bir Prolog programıyla üretiliyor. Bu programların kullandığı birkaç kütüphane de yazdım. SWI Prolog da kendi web sitesi, Swish adlı web IDE’si, ClioPatria sunucusu gibi yerlerde SWI’yi doğrudan kullanıyor
Bilgileri düzenlemek için SQLite’ı bir tür tip güvenli elektronik tablo gibi kullandığım oldu. Bunun üstünde hiç CRUD arayüzü olmasa da yapılabiliyordu
Yine de bu iş için her zaman en hoş görünen dil değildi. Bir süre Datalog’a da baktım ama çoğu uygulama, bu yazıdaki gibi kolayca bilgi kaydetmeye yarayan araçlar olmaktan çok, daha büyük programların içine gömülmek için tasarlanmış gibiydi
Belki de gerçekten kullanılması gereken araç Prolog’dur
Prolog, Datalog’un üst kümesi olduğu için Datalog’un yapabildiği her şeyi yapabilir, hatta daha fazlasını da. Ama bazen sorun o “daha fazlası” olur; çünkü artık Turing-complete bir dildir, yani hiç bitmeyebilir, akıl yürütmesi biraz daha zor olabilir ve güvenli olmayan şekillerde kullanılabilir
Datalog ise kısıtlı olduğu için bazı kestirme yollar kullanabilir ve bu yüzden performansı da çoğu zaman daha iyi olur
Son olarak, Prolog’da veri kodla aynıdır; yani homoiconicity vardır. Bu yüzden oluşturma/güncelleme/silme işlemleri aslında kaynak kodu değiştirmek anlamına gelir. Bunu dinamik olarak yapmak mümkün olsa da akışın pürüzsüz olmasını beklemek zor; dosyalarla yüklenmiş program arasında hâlâ bir miktar elle senkronizasyon gerekir
Prolog’u daha derinlemesine öğrenip bunu instruction set query için kullanmak istiyorum
Ama mantığı, miktarlar ve bit düzeyinde görülen tamsayılar da dahil olacak şekilde modellemek oldukça zor