Apa yang bisa saya lakukan untuk menghindari bendera satu kali dan memeriksa seluruh kode saya?

17

Pertimbangkan permainan kartu, seperti Hearthstone .

Ada ratusan kartu yang melakukan berbagai hal, beberapa di antaranya unik bahkan untuk satu kartu! Misalnya, ada kartu (disebut Nozdormu) yang mengurangi putaran pemain menjadi hanya 15 detik!

Ketika Anda memiliki berbagai macam efek potensial, bagaimana Anda menghindari angka ajaib dan cek satu kali di seluruh kode Anda? Bagaimana cara menghindari metode "Check_Nozdormu_In_Play" di kelas PlayerTurnTime? Dan bagaimana seseorang dapat mengatur kode sehingga ketika Anda menambahkan lebih banyak efek, Anda tidak perlu memperbaiki sistem inti untuk mendukung hal-hal yang belum pernah mereka dukung sebelumnya?

Sable Dreamer
sumber
Apakah ini benar-benar masalah kinerja? Maksud saya, Anda dapat melakukan banyak hal gila dengan CPU modern dalam waktu singkat ..
Jari Komppa
11
Siapa yang mengatakan sesuatu tentang masalah kinerja? Masalah utama yang akan saya lihat adalah terus-menerus perlu menyesuaikan semua kode Anda setiap kali Anda membuat kartu baru.
jhocking
2
jadi tambahkan bahasa skrip dan skrip setiap kartu.
Jari Komppa
1
Tidak ada waktu untuk membuat jawaban yang tepat, tetapi alih-alih memiliki cek Nozdormu misalnya dan penyesuaian 15 detik di dalam kode kelas "PlayerTurnTime" yang menangani putaran pemain, Anda dapat mengkodekan kelas "PlayerTurnTime" untuk memanggil [kelas-, jika Anda ingin ] fungsi dipasok dari luar pada titik-titik tertentu. Kemudian kode kartu Nozdormu (dan semua kartu lain yang perlu memengaruhi plance yang sama) dapat mengimplementasikan fungsi untuk penyesuaian itu dan menyuntikkan fungsi itu di kelas PlayerTurnTime saat dibutuhkan. Mungkin bermanfaat untuk membaca tentang pola strategi dan injeksi ketergantungan dari buku Pola Desain klasik
Peteris
2
Pada titik tertentu saya harus bertanya-tanya apakah menambahkan pemeriksaan ad-hoc ke bit kode yang relevan adalah solusi paling sederhana.
pengguna253751

Jawaban:

12

Sudahkah Anda melihat sistem komponen entitas dan strategi pengiriman pesan acara?

Efek status harus merupakan komponen dari beberapa jenis yang dapat menerapkan efek gigihnya dalam metode OnCreate (), kedaluwarsa efeknya di OnRemoved () dan berlangganan pesan acara permainan untuk menerapkan efek yang terjadi sebagai reaksi terhadap sesuatu yang terjadi.

Jika efeknya terus-menerus bersyarat (bertahan selama X berubah, tetapi hanya berlaku dalam keadaan tertentu) Anda mungkin perlu memeriksa kondisi tersebut di berbagai fase.

Kemudian, Anda hanya memastikan bahwa gim Anda tidak memiliki angka sulap standar juga. Pastikan segala sesuatu yang dapat diubah adalah variabel yang digerakkan oleh data alih-alih standar kode keras dengan variabel yang digunakan untuk pengecualian apa pun.

Dengan cara ini, Anda tidak pernah berasumsi berapa panjang belokannya. Itu selalu merupakan variabel yang selalu diperiksa yang dapat diubah oleh efek apa pun dan mungkin dibatalkan kemudian oleh efek ketika itu berakhir. Anda tidak pernah memeriksa pengecualian sebelum default ke nomor ajaib Anda.

RobStone
sumber
2
"Pastikan segala sesuatu yang dapat diubah adalah variabel yang digerakkan oleh data alih-alih standar kode keras dengan variabel yang digunakan untuk pengecualian apa pun." - Ooh, saya agak suka itu. Itu sangat membantu, saya pikir!
Sable Dreamer
Bisakah Anda menguraikan "menerapkan efek gigih mereka"? Tidak akan berlangganan turnStarted dan kemudian mengubah nilai Length membuat kode undebugable dan atau lebih buruk lagi menghasilkan hasil yang tidak konsisten (ketika berinteraksi antara efek yang sama)?
wondra 3-15
Hanya untuk pelanggan yang akan menganggap rentang waktu tertentu. Anda harus memodelkan dengan cermat. Mungkin bagus untuk memiliki waktu belokan saat ini berbeda dari waktu pemain belok. PTT akan diperiksa untuk membuat belokan baru. CTT dapat diperiksa dengan kartu. Jika suatu efek meningkatkan waktu saat ini, UI pengatur waktu secara alami harus mengikutinya jika tidak memiliki kewarganegaraan.
RobStone
Untuk menjawab pertanyaan dengan lebih baik. Tidak ada yang lain yang menyimpan waktu berbelok atau apa pun berdasarkan itu. Selalu periksa.
RobStone
11

RobStone berada di jalur yang benar, tetapi saya ingin menguraikan karena ini adalah persis apa yang saya lakukan ketika saya menulis Dungeon Ho !, seorang Roguelike yang memiliki sistem efek yang sangat kompleks untuk senjata dan mantra.

Setiap kartu harus memiliki satu set efek yang melekat padanya, didefinisikan sedemikian rupa sehingga dapat menunjukkan apa efeknya, apa yang ditargetkan, bagaimana, dan berapa lama. Misalnya, efek "kerusakan lawan" mungkin terlihat seperti ini;

Effect type: deal damage (enumeration, string, what-have-you)
Effect amount: 20
Source: my weapon
Target: opponent
Effect Cost: 20
Cost Type: Mana

Kemudian, ketika efek kebakaran, miliki rutin generik menangani pemrosesan efek. Seperti orang idiot, saya menggunakan pernyataan case / switch yang sangat besar:

switch (effect_type)
{
     case DAMAGE:

     break;
}

Tetapi cara yang jauh lebih baik dan lebih modular untuk melakukannya adalah melalui polimorfisme. Buat kelas Efek yang membungkus semua data ini, buat subkelas untuk setiap jenis efek, dan kemudian minta kelas itu menimpa metode onExecute () yang khusus untuk kelas tersebut.

class Effect
{
    Object source;
    int amount;

    public void onExecute(Object target)
    {
          // Do nothing
    }
}

class DamageEffect extends Effect
{
    public void onExecute(Object target)
    {
          target.health -= amount;
    }
}

Jadi kita akan memiliki kelas Efek dasar, lalu kelas DamageEffect dengan metode onExecute (), jadi dalam kode pemrosesan kami, kami hanya akan pergi;

Effect effect = card.getActiveEffect();

effect.onExecute();

Cara untuk berurusan dengan mengetahui apa yang sedang dimainkan adalah dengan membuat Vector / Array / daftar tertaut / etc. efek aktif (dari tipe Efek, kelas dasar) yang melekat pada objek apa pun (termasuk playfield / "game"), jadi alih-alih harus memeriksa apakah efek tertentu sedang dimainkan, Anda cukup mengulangi semua efek yang melekat pada objek dan biarkan mereka mengeksekusi. Jika suatu efek tidak dilampirkan ke suatu objek, itu tidak dalam permainan.

Effect effect;

for (int o = 0; o < objects.length; o++)
{
    for (int e = 0; e < objects[o].effects.length; e++)
    {
         effect = objects[o].effects[e];

         effect.onExecute();
    }
}
Sandalfoot
sumber
Inilah tepatnya cara saya melakukannya. Keindahan di sini adalah pada dasarnya Anda memiliki sistem yang digerakkan oleh data, dan Anda dapat menyesuaikan logikanya dengan mudah berdasarkan per efek. Biasanya Anda harus melakukan pengecekan kondisi dalam logika eksekusi efek, tetapi masih jauh lebih masuk akal karena pemeriksaan ini hanya untuk efek yang dimaksud.
manabreak
1

Saya akan menawarkan beberapa saran. Beberapa dari mereka saling bertentangan. Tapi mungkin ada yang bermanfaat.

Pertimbangkan daftar versus bendera

Anda dapat mengulangi dunia dan memeriksa bendera pada setiap item untuk memutuskan apakah akan melakukan hal bendera. Atau Anda dapat menyimpan daftar hanya barang-barang yang harus melakukan hal bendera.

Pertimbangkan daftar & enumerasi

Anda dapat terus menambahkan bidang boolean ke kelas item Anda, isAThis dan isAThat. Atau Anda dapat memiliki daftar elemen string atau enum, seperti {"isAThis", "isAThat"} atau {IS_A_THIS, IS_A_THAT}. Dengan begitu Anda bisa menambahkan yang baru di enumerasi (atau string const) tanpa menambahkan bidang. Bukannya ada yang salah dengan menambahkan bidang ...

Pertimbangkan fungsi pointer

Alih-alih daftar bendera atau enum, bisa memiliki daftar tindakan untuk mengeksekusi untuk item itu dalam konteks yang berbeda. (Entitas-ish ...)

Pertimbangkan benda

Beberapa orang lebih suka pendekatan berbasis entitas data, atau skrip, atau komponen. Tapi hierarki benda kuno juga patut dipertimbangkan. Kelas dasar perlu menerima tindakan, seperti "mainkan kartu ini untuk fase-B" atau apa pun. Kemudian setiap jenis kartu dapat menimpa dan merespons sebagaimana mestinya. Mungkin ada objek pemain dan objek game juga, sehingga game dapat melakukan hal-hal seperti, if (player-> isowedToPlay ()) {do the play…}.

Pertimbangkan kemampuan debug

Satu hal yang menyenangkan tentang setumpuk bidang bendera adalah Anda dapat memeriksa & mencetak kondisi setiap item dengan cara yang sama. Jika keadaan diwakili oleh berbagai jenis, atau kantong komponen, atau pointer fungsi, atau berada di daftar yang berbeda, mungkin tidak cukup hanya dengan melihat bidang item. Ini semua kompromi.

Akhirnya, refactoring: Pertimbangkan tes unit

Tidak peduli seberapa besar Anda menggeneralisasikan arsitektur Anda, Anda akan dapat membayangkan hal-hal yang tidak tercakup. Maka Anda harus refactor. Mungkin sedikit, mungkin banyak.

Cara untuk membuat ini lebih aman adalah dengan tes unit. Dengan begitu Anda dapat yakin bahwa meskipun Anda menata ulang hal-hal di bawahnya (mungkin banyak!), Fungsionalitas yang ada masih berfungsi. Setiap unit tes terlihat, secara umum, seperti ini:

void test1()
{
   Game game;
   game.addThis();
   game.setupThat(); // use primary or backdoor API to get game to known state

   game.playCard(something something).

   int x = game.getSomeInternalState;
   assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}

Seperti yang Anda lihat, menjaga agar panggilan API tingkat atas agar game (atau pemain, kartu, & c) tetap stabil adalah kunci strategi pengujian unit.

david van brink
sumber
0

Alih-alih memikirkan setiap kartu secara individual, mulailah berpikir dalam hal kategori efek, dan kartu mengandung satu atau lebih dari kategori ini. Misalnya, untuk menghitung jumlah waktu dalam satu belokan, Anda dapat mengulangi semua kartu yang sedang dimainkan dan memeriksa kategori "memanipulasi durasi giliran" dari setiap kartu yang berisi kategori itu. Setiap kartu kemudian menambah atau menimpa durasi giliran berdasarkan aturan yang Anda putuskan.

Ini pada dasarnya adalah sistem komponen mini, di mana setiap objek "kartu" hanyalah sebuah wadah untuk sekelompok komponen efek.

jhocking
sumber
Karena kartu - dan kartu masa depan juga - dapat melakukan apa saja, saya berharap setiap kartu membawa skrip. Tetap saya cukup yakin ini bukan masalah kinerja nyata ..
Jari Komppa
4
sesuai komentar utama: Tidak ada yang (selain Anda) mengatakan sesuatu tentang masalah kinerja. Adapun skrip lengkap sebagai alternatif, uraikan hal itu sebagai jawaban.
jhocking