Brainstorming dengan transposisi
Terkadang saya macet dan harus mencari cara untuk memikirkan tugas dari sudut yang berbeda. Ada tugas yang bisa ditampilkan dalam bentuk matriks atau tabel. Struktur mereka terlihat seperti ini:
Sel yang saya kerjakan disusun dalam kolom dan baris. Mari kita ambil contoh dari gim sederhana:
Garis adalah kelas karakter: prajurit, penyihir, pencuri.
Kolom adalah jenis tindakan: serangan, pertahanan, tindakan khusus.
Matriks berisi semua kode untuk memproses setiap jenis tindakan untuk setiap jenis karakter.
Seperti apa kode itu? Biasanya, struktur seperti itu disusun dalam modul-modul tersebut:
Fighter
akan berisi kode untuk menangani serangan pedang, mengurangi kerusakan dengan baju besi dan pukulan kuat khusus.Mage
akan berisi kode penanganan bola api, refleksi kerusakan dan serangan pembekuan khusus.Thief
akan berisi kode untuk menangani serangan belati, menghindari kerusakan menghindar, dan serangan melucuti senjata khusus.
Terkadang berguna untuk mengubah posisi matriks. Kita bisa mengaturnya di sumbu lain
:
Attack
akan berisi kode untuk menangani serangan ayunan, bola api, dan belati.Defend
akan berisi kode untuk menangani pengurangan kerusakan, mencerminkan kerusakan dan menghindari kerusakan.Special
akan berisi serangan yang kuat, membekukan dan melucuti kode penanganan.
Saya diajari bahwa satu gaya "baik" dan yang lain "buruk". Tetapi tidak jelas bagi saya mengapa semuanya harus seperti itu. Alasannya adalah
asumsi bahwa kita akan sering menambahkan kelas karakter baru (kata benda), dan jarang menambahkan jenis tindakan baru (kata kerja). Dengan cara ini saya dapat menambahkan kode menggunakan modul baru tanpa menyentuh
semua yang tersedia. Dalam gim Anda, semuanya bisa berbeda. Melihat matriks transposisi, saya sadar akan adanya asumsi dan dapat meragukannya. Kemudian saya akan memikirkan jenis fleksibilitas yang saya butuhkan, dan hanya kemudian saya akan memilih struktur kode.
Mari kita lihat contoh lain.
Interpretasi bahasa pemrograman memiliki berbagai jenis node yang sesuai dengan primitif: konstanta, operator, loop, percabangan, fungsi, jenis, dll. Kita perlu menghasilkan kode untuk semuanya.
Hebat! Anda bisa membuat satu kelas untuk setiap jenis node, dan mereka semua bisa mewarisi dari kelas dasar
Node
. Tetapi kami mengandalkan asumsi bahwa kami akan menambahkan baris dan kolom yang lebih jarang. Apa yang terjadi dalam kompilator pengoptimalan? Kami menambahkan pass optimasi baru. Dan masing-masing dari mereka adalah kolom baru.
Jika saya ingin menambahkan pass optimisasi baru, maka saya perlu menambahkan metode baru untuk setiap kelas, dan semua kode pass optimisasi akan didistribusikan ke modul yang berbeda. Saya ingin menghindari situasi seperti itu! Oleh karena itu, dalam beberapa sistem, lapisan lain ditambahkan di atas ini. Menggunakan pola pengunjung, saya dapat menyimpan semua kode untuk menggabungkan loop dalam satu modul, daripada memecahnya menjadi beberapa file.
Jika Anda melihat matriks transposisi, kami akan membuka pendekatan lain:
Sekarang, alih-alih
kelas dengan
metode, saya dapat menggunakan
gabungan tag dan
pencocokan pola (mereka tidak didukung dalam semua bahasa pemrograman). Karena ini, seluruh kode dari setiap pass optimasi akan disimpan bersama-sama dan dapat dilakukan tanpa tidak langsung dari pola pengunjung.
Seringkali berguna untuk melihat masalah dari sudut pandang matriks. Jika Anda menerapkannya pada struktur berorientasi objek yang dipikirkan semua orang, itu dapat mengarahkan saya ke sesuatu yang lain, misalnya, ke pola "entitas-komponen-sistem", database relasional, atau pemrograman reaktif.
Dan ini tidak hanya berlaku untuk kode. Ini adalah contoh penerapan ide ini pada produk. Misalkan ada orang dengan minat berbeda:
Jika saya mengembangkan situs jejaring sosial, saya dapat memungkinkan orang untuk mengikuti berita
orang lain. Nick dapat mendaftar untuk Alice, karena mereka berdua tertarik pada mobil, dan pada Fenya, karena mereka berdua tertarik bepergian. Tapi Nick juga akan menerima posting Alice tentang matematika dan posting Fenya tentang politik. Jika saya mempertimbangkan matriks yang diubah, saya dapat memungkinkan orang untuk berlangganan
topik . Nick bisa bergabung dengan sekelompok pecinta mobil, serta sekelompok pelancong. Facebook dan Reddit dimulai sekitar waktu yang sama, tetapi mereka mengubah matriks satu sama lain. Facebook memungkinkan Anda untuk mengikuti orang; Reddit memungkinkan Anda untuk berlangganan topik.
Ketika saya berhenti atau ketika saya ingin mempertimbangkan alternatif, saya melihat masalah dan mencari kapak pemesanan yang berbeda di dalamnya. Terkadang melihat masalah dari sudut yang berbeda dapat memberikan solusi yang lebih baik.
Brainstorming Dekomposisi
Saya menggunakan teknik lain yang disebut dekomposisi.
Dalam aljabar, operasi
dekomposisi mengubah polinomial dari bentuk 5x² + 8x - 21 menjadi (x + 3) · (5x - 7). Untuk menyelesaikan persamaan 5x² + 8x - 21 = 0, pertama-tama kita dapat memasukkannya menjadi (x + 3) · (5x - 7) = 0. Kemudian kita dapat mengatakan bahwa x + 3 = 0
atau 5x - 7 = 0. Ekspansi mengubah tugas yang sulit menjadi beberapa tugas yang lebih mudah.
Mari kita lihat sebuah contoh: Saya memiliki enam kelas:
File
,
EncryptedFile
,
GzipFile
,
EncryptedGzipFile
,
BzipFile
,
EncryptedBzipFile
. Saya bisa menguraikannya menjadi sebuah matriks:
Menggunakan pola "dekorator" (atau pengotor), saya mengubah enam jenis file menjadi empat komponen: polos, gzip, bzip, terenkripsi. Ini sepertinya tidak banyak menghemat, tetapi jika saya menambahkan lebih banyak variasi, penghematan akan meningkat. Dekomposisi mengubah komponen O (M * N) menjadi komponen O (M + N).
Contoh lain: kadang-kadang orang bertanya kepada saya pertanyaan seperti
"bagaimana cara menulis interpolasi linier dalam C #?" . Saya dapat menulis banyak tutorial potensial:
Jika ada topik M dan bahasa N, maka saya bisa menulis tutorial M * N. Namun, ini
banyak pekerjaan. Sebagai gantinya, saya akan menulis tutorial tentang
interpolasi , orang lain akan menulis tutorial tentang C #, dan kemudian pembaca akan menggabungkan pengetahuan C # dengan pengetahuan interpolasi, dan menulis versinya tentang interpolasi di C #.
Seperti transposisi, dekomposisi tidak selalu membantu, tetapi jika berlaku, itu bisa sangat berguna.
Brainstorming mundur
Dalam dua bagian sebelumnya, saya berbicara tentang bagaimana saya kadang-kadang mendekati tugas, mencoba mengaturnya menjadi sebuah matriks. Terkadang ini tidak membantu dan kemudian saya mencoba melihat tugas dengan arah yang berlawanan. Mari kita lihat pembuatan peta prosedural, misalnya. Seringkali saya mulai dengan fungsi noise, kemudian menambahkan oktaf, menyesuaikan parameter, dan menambahkan layer. Saya melakukan ini karena saya memerlukan kartu yang memiliki properti tertentu.
Sangat mungkin untuk memulai dengan percobaan dengan parameter, tetapi ruang parameternya cukup besar, dan tidak diketahui apakah saya akan menemukan parameter yang paling cocok untuk kebutuhan saya. Karena itu, setelah bereksperimen sedikit, saya berhenti dan mulai berpikir dalam urutan terbalik: jika saya bisa menggambarkan apa yang saya butuhkan, maka ini dapat membantu dalam menemukan parameter.
Motivasi inilah yang membuat saya belajar aljabar. Jika kita memiliki persamaan bentuk
5x² + 8x - 21 = 0 , lalu apa yang akan menjadi
x ? Ketika saya tidak tahu aljabar, saya akan menyelesaikan persamaan ini, mencoba untuk mengganti nilai-nilai berbeda dari
x , pertama-tama memilihnya secara acak, dan kemudian menyesuaikannya ketika saya merasa bahwa saya sudah dekat dengan solusi. Aljabar memberi kita alat untuk pergi ke arah yang berbeda. Alih-alih menebak jawabannya, dia memberi saya alat (dekomposisi, atau persamaan kuadrat, atau metode Newton untuk pencarian iteratif untuk root), yang saya dapat lebih sadar gunakan untuk mencari nilai
x (-3 atau 7/5).
Saya merasa seperti saya sering masuk ke dalam situasi pemrograman semacam ini. Saat mengerjakan pembuatan peta prosedural, setelah bereksperimen dengan parameter untuk beberapa waktu, saya berhenti dan menyusun daftar apa yang seharusnya ada di dunia game dari
satu proyek :
- Pemain harus memulai permainan jauh dari pantai.
- Saat naik level, pemain harus naik ke atas bukit.
- Pemain seharusnya tidak dapat mencapai tepi peta.
- Ketika level bertambah, pemain harus bergabung dalam grup.
- Seharusnya ada monster sederhana di pantai tanpa banyak variasi.
- Di dataran harus ada berbagai monster dengan kesulitan sedang.
- Di daerah pegunungan pasti ada bos monster yang kompleks.
- Pasti ada semacam tengara yang memungkinkan pemain tetap pada tingkat kesulitan yang sama, dan tengara lain yang memungkinkan Anda naik atau turun di tingkat kesulitan.
Kompilasi daftar ini mengarah pada pembuatan pembatasan berikut:
- Dunia permainan haruslah pulau-pulau dengan banyak pantai dan puncak kecil di tengahnya.
- Ketinggian harus sesuai dengan kompleksitas monster.
- Pada ketinggian rendah dan tinggi, seharusnya ada lebih sedikit variabilitas bioma daripada di ketinggian sedang.
- Jalan harus tetap pada tingkat kesulitan yang sama.
- Sungai harus mengalir dari ketinggian besar ke kecil, dan memberi pemain kemampuan untuk naik / turun.
Keterbatasan ini membuat saya merancang generator peta. Dan dia menghasilkan kartu yang
jauh lebih baik daripada yang saya terima dengan menyesuaikan parameter, seperti yang biasa saya lakukan. Dan
artikel yang dihasilkan menarik banyak orang untuk membuat peta berdasarkan diagram Voronoi.
Tes unit adalah contoh lain. Disarankan agar saya membuat daftar contoh untuk diuji. Sebagai contoh, untuk kisi-kisi segi enam, saya mungkin berpikir bahwa saya perlu memeriksa kondisi
add(Hex(1, 2), Hex(3, 4)) == Hex(4, 6)
. Maka saya dapat mengingat bahwa Anda perlu memeriksa nol:
add(Hex(0, 1), Hex(7, 9)) == Hex(7, 10)
. Maka saya dapat mengingat bahwa Anda perlu memeriksa nilai negatif:
add(Hex(-3, 4) + Hex(7, -8)) == Hex(4, -4)
. Bagus, bagus, saya punya beberapa unit test.
Tetapi jika Anda berpikir sedikit lebih jauh, maka
sebenarnya saya periksa
add(Hex(A, B), Hex(C, D)) == Hex(A+C, B+D)
. Saya datang dengan tiga contoh yang ditunjukkan di atas berdasarkan aturan umum ini. Saya akan ke arah yang berlawanan dari aturan ini untuk datang ke unit test. Jika saya bisa langsung menyandikan aturan ini ke dalam sistem pengujian, maka sistem itu sendiri akan dapat bekerja dalam urutan terbalik untuk membuat contoh untuk pengujian. Ini disebut pengujian berbasis properti. (Lihat juga:
pengujian metamorf )
Contoh lain: pemecah kendala. Dalam sistem seperti itu, pengguna menggambarkan apa yang ingin dilihat dalam output, dan sistem menemukan cara untuk memenuhi kendala ini. Kutipan dari Buku Generasi Konten Prosedural,
bab 8 :
Dengan menggunakan metode konstruktif dari Bab 3, serta metode fraktal dan noise dari Bab 4, kita dapat membuat berbagai jenis data keluaran dengan menyetel algoritme hingga kami puas dengan data keluarannya. Tetapi jika kita tahu properti apa yang seharusnya dimiliki oleh konten yang dihasilkan, akan lebih mudah untuk menunjukkan secara langsung apa yang kita inginkan dari algoritma umum untuk menemukan konten yang memenuhi kriteria kita.
Buku ini menjelaskan pemrograman set jawaban (ASP), di mana kami menggambarkan struktur dari apa yang kami kerjakan (ubin adalah lantai dan dinding, ubin saling membatasi), struktur solusi yang kami cari (ruang bawah tanah adalah grup ubin terhubung dengan awal dan akhir) dan sifat-sifat solusi (lorong samping harus berisi tidak lebih dari 5 kamar, harus ada 1-2 loop di labirin, tiga asisten harus dikalahkan sebelum mencapai bos). Setelah itu, sistem menciptakan solusi yang memungkinkan dan memungkinkan Anda memutuskan apa yang harus dilakukan dengannya.
Baru-baru ini, pemecah kendala dikembangkan, yang menyebabkan minat besar karena namanya yang keren dan demo yang menarik: Wave Function Collapse.
[Ada artikel tentang pemecah masalah ini pada Habr.] Jika Anda memberinya contoh gambar untuk menunjukkan batasan apa yang dikenakan pada ubin tetangga, ia akan membuat contoh baru yang sesuai dengan pola yang diberikan. Karyanya dijelaskan dalam
WaveFunctionCollapse adalah Constraint Solving in the Wild :
WFC mengimplementasikan metode pencarian yang serakah tanpa kembali. Artikel ini mengeksplorasi WFC sebagai contoh metode keputusan berbasis kendala.
Saya telah mencapai banyak hal dengan bantuan pemecah kendala. Seperti halnya aljabar, sebelum saya belajar cara menggunakannya secara efektif, saya perlu belajar banyak.
Contoh lain:
pesawat ruang angkasa yang saya buat . Pemain dapat menyeret mesin ke mana saja, dan sistem akan menentukan mesin mana yang perlu diaktifkan ketika Anda mengklik W, A, S, D, Q, E. Misalnya, di kapal ini:
Jika Anda ingin terbang ke depan, maka sertakan dua mesin belakang. Jika Anda ingin berbelok ke kiri, maka hidupkan mesin kanan belakang dan kiri depan. Saya mencoba mencari solusi, memaksa sistem untuk
beralih pada banyak parameter :
Sistem bekerja, tetapi tidak sempurna. Kemudian saya menyadari bahwa ini adalah contoh lain di mana solusi dalam arah yang berlawanan dapat membantu. Ternyata pergerakan pesawat ruang angkasa dapat digambarkan dengan
sistem pembatasan linear . Jika saya memahami hal ini, saya bisa menggunakan pustaka siap pakai yang secara akurat menyelesaikan kendala, dan bukan metode coba-coba saya, yang mengembalikan perkiraan.
Dan contoh lain: proyek G9.js, di mana Anda dapat menyeret
output fungsi tertentu di layar, dan menentukan bagaimana mengubah
data input agar sesuai dengan data output yang diinginkan.
Demo G9.js tampak hebat! Pastikan untuk menghapus tanda komentar pada baris "hapus komentar baris berikut" di demo Rings.
Terkadang berguna untuk memikirkan tugas dalam urutan terbalik. Sering kali ternyata ini memberi saya solusi yang
lebih baik daripada dengan alasan langsung.