ฉันจะทำอย่างไรเพื่อหลีกเลี่ยงการตั้งค่าสถานะและตรวจสอบครั้งเดียวตลอดทั้งรหัสของฉัน


18

พิจารณาการ์ดเกมเช่นHearthstone

มีการ์ดหลายร้อยใบที่ทำสิ่งที่หลากหลายซึ่งบางอย่างก็มีเอกลักษณ์แม้แต่บัตรเดียว! ตัวอย่างเช่นมีการ์ด (เรียกว่า Nozdormu) ที่ช่วยลดผู้เล่นหันไปเพียง 15 วินาที!

เมื่อคุณมีเอฟเฟกต์ที่อาจเป็นไปได้มากมายเช่นนี้คุณจะหลีกเลี่ยงตัวเลขเวทย์มนตร์และการตรวจสอบครั้งเดียวผ่านโค้ดของคุณได้อย่างไร วิธีใดวิธีหนึ่งที่หลีกเลี่ยงวิธี "Check_Nozdormu_In_Play" ในคลาส PlayerTurnTime และวิธีการหนึ่งที่สามารถจัดระเบียบรหัสดังกล่าวเมื่อคุณเพิ่มลักษณะพิเศษเพิ่มเติมคุณไม่จำเป็นต้อง refactor ระบบหลักเพื่อสนับสนุนสิ่งที่พวกเขาไม่เคยให้การสนับสนุนมาก่อน


นี่เป็นปัญหาด้านประสิทธิภาพหรือไม่ ฉันหมายความว่าคุณสามารถทำสิ่งต่างๆมากมายกับซีพียูสมัยใหม่ในเวลาไม่นาน ..
Jari Komppa

11
ใครพูดอะไรเกี่ยวกับปัญหาด้านประสิทธิภาพบ้าง ปัญหาหลักที่ฉันจะเห็นคือต้องปรับรหัสของคุณทุกครั้งที่คุณทำบัตรใหม่
jhocking

2
ดังนั้นเพิ่มภาษาสคริปต์และสคริปต์การ์ดแต่ละใบ
Jari Komppa

1
ไม่มีเวลาให้คำตอบที่เหมาะสม แต่แทนที่จะมีการตรวจสอบ Nozdormu และการปรับ 15 วินาทีภายในรหัสคลาส "PlayerTurnTime" ที่จัดการกับผู้เล่นรอบคุณสามารถรหัสคลาส "PlayerTurnTime" เพื่อเรียก [class- ถ้าคุณต้องการ ] ฟังก์ชั่นที่ให้มาจากภายนอกที่จุดเฉพาะ จากนั้นรหัสบัตร Nozdormu (และการ์ดอื่น ๆ ทั้งหมดที่จำเป็นต้องส่งผลกระทบต่อสถานการณ์เดียวกัน) สามารถใช้ฟังก์ชั่นสำหรับการปรับและฉีดฟังก์ชันนั้นในคลาส PlayerTurnTime เมื่อจำเป็น มันอาจจะมีประโยชน์ในการอ่านเกี่ยวกับรูปแบบกลยุทธ์และการฉีดพึ่งพาจากหนังสือ Design Patterns คลาสสิก
Peteris

2
ในบางจุดฉันต้องสงสัยว่าการเพิ่มการตรวจสอบเฉพาะกิจไปยังบิตของรหัสที่เกี่ยวข้องนั้นเป็นวิธีที่ง่ายที่สุดหรือไม่
user253751

คำตอบ:


12

คุณเคยดูระบบส่วนประกอบของเอนทิตีและกลยุทธ์การส่งข้อความเหตุการณ์หรือไม่?

ลักษณะพิเศษของสถานะควรเป็นองค์ประกอบของการเรียงลำดับบางอย่างซึ่งสามารถใช้เอฟเฟ็กต์แบบถาวรในวิธี OnCreate () หมดอายุเอฟเฟกต์ใน OnRemoved () และสมัครรับข้อความเหตุการณ์ของเกมเพื่อใช้เอฟเฟกต์ที่เกิดขึ้น

หากเอฟเฟกต์มีเงื่อนไขอย่างต่อเนื่อง (ใช้ได้กับการหมุน X แต่ใช้เฉพาะในบางสถานการณ์) คุณอาจต้องตรวจสอบเงื่อนไขเหล่านั้นในระยะต่าง ๆ

จากนั้นคุณต้องตรวจสอบให้แน่ใจว่าเกมของคุณไม่มีหมายเลขมายากลเริ่มต้นด้วย ตรวจสอบให้แน่ใจว่าทุกสิ่งที่สามารถเปลี่ยนแปลงได้คือตัวแปรที่ขับเคลื่อนด้วยข้อมูลแทนที่จะเป็นค่าเริ่มต้นที่กำหนดรหัสยากพร้อมตัวแปรที่ใช้สำหรับข้อยกเว้นใด ๆ

ด้วยวิธีนี้คุณจะไม่คิดเลยว่าความยาวเลี้ยวจะเป็นเท่าไหร่ มันเป็นตัวแปรที่ตรวจสอบอยู่ตลอดเวลาซึ่งสามารถเปลี่ยนแปลงได้ด้วยเอฟเฟกต์ใด ๆ และอาจยกเลิกได้ในภายหลังเมื่อเอฟเฟกต์หมดอายุ คุณไม่เคยตรวจสอบข้อยกเว้นก่อนที่จะเริ่มด้วยหมายเลขมายากลของคุณ


2
"ตรวจสอบให้แน่ใจว่าทุกสิ่งที่สามารถเปลี่ยนแปลงได้คือตัวแปรที่ขับเคลื่อนด้วยข้อมูลมากกว่าค่าเริ่มต้นที่กำหนดรหัสยากพร้อมตัวแปรที่ใช้สำหรับข้อยกเว้นใด ๆ " - โอ้ฉันค่อนข้างชอบ นั่นช่วยได้มากฉันคิดว่า!
Sable Dreamer

คุณช่วยอธิบายรายละเอียดเกี่ยวกับ จะไม่สมัครสมาชิกกับ turnStarted แล้วเปลี่ยนค่าความยาวทำให้รหัส undebugable และหรือแย่กว่านั้นทำให้เกิดผลลัพธ์ที่ไม่สอดคล้องกัน (เมื่อโต้ตอบระหว่างเอฟเฟกต์ที่คล้ายกัน)
wondra

เฉพาะสมาชิกที่จะถือว่ามีช่วงเวลาที่กำหนด คุณต้องทำตัวแบบอย่างระมัดระวัง มันอาจจะดีที่มีเวลาเปิดปัจจุบันแตกต่างจากเวลาเปิดของผู้เล่น ปตท. จะถูกตรวจสอบเพื่อสร้างเทิร์นใหม่ สามารถตรวจสอบ CTT ด้วยบัตร หากเอฟเฟกต์ควรเพิ่มเวลาปัจจุบัน UI ของตัวจับเวลาควรเป็นไปตามธรรมชาติหากไร้สถานะ
RobStone

เพื่อตอบคำถามให้ดีขึ้น ไม่มีอะไรอื่นเก็บเวลาเปิดหรืออะไรก็ตามที่เป็นไปตามนั้น ตรวจสอบทุกครั้ง
RobStone

11

RobStone อยู่ในเส้นทางที่ถูกต้อง แต่ฉันต้องการอธิบายอย่างละเอียดเพราะนี่คือสิ่งที่ฉันทำเมื่อฉันเขียน Dungeon Ho!, Roguelike ที่มีระบบเอฟเฟกต์ซับซ้อนสำหรับอาวุธและคาถา

การ์ดแต่ละใบควรมีชุดเอฟเฟกต์แนบมาด้วยซึ่งกำหนดไว้ในลักษณะที่สามารถระบุได้ว่าเอฟเฟกต์นั้นคืออะไรเป้าหมายและอย่างไรและนานเท่าไหร่ ตัวอย่างเช่นเอฟเฟกต์ "ทำลายคู่ต่อสู้" อาจมีลักษณะเช่นนี้

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

จากนั้นเมื่อเอฟเฟกต์ยิงให้มีรูทีนทั่วไปที่จัดการกับการประมวลผล เหมือนคนงี่เง่าฉันใช้คำสั่ง case / switch ขนาดใหญ่:

switch (effect_type)
{
     case DAMAGE:

     break;
}

แต่วิธีที่ดีกว่าและยิ่งกว่านั้นคือผ่านทาง polymorphism สร้างคลาส Effect ที่ล้อมข้อมูลทั้งหมดนี้สร้างคลาสย่อยสำหรับเอฟเฟกต์แต่ละประเภทจากนั้นให้คลาสนั้นแทนที่เมธอด onExecute () เฉพาะสำหรับคลาส

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;
    }
}

ดังนั้นเราต้องมีคลาส Effect พื้นฐานจากนั้นเป็นคลาส DamageEffect ด้วยเมธอด onExecute () ดังนั้นในโค้ดการประมวลผลของเราที่เราต้องการ

Effect effect = card.getActiveEffect();

effect.onExecute();

วิธีจัดการกับการรู้ว่ามีอะไรในการเล่นคือการสร้างรายการ Vector / Array / linked / etc ของเอฟเฟกต์ที่ใช้งานอยู่ (ของประเภทเอฟเฟกต์, คลาสฐาน) ที่ติดอยู่กับวัตถุใด ๆ (รวมถึง playfield / "เกม") ดังนั้นแทนที่จะต้องตรวจสอบว่ามีเอฟเฟกต์พิเศษอยู่ในการเล่นหรือไม่ วัตถุและปล่อยให้พวกเขาดำเนินการ หากเอฟเฟกต์ไม่ได้ติดอยู่กับวัตถุก็ไม่ได้อยู่ในการเล่น

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();
    }
}

นี่คือสิ่งที่ฉันทำ ความงามของที่นี่คือคุณมีระบบที่ขับเคลื่อนด้วยข้อมูลเป็นหลักและคุณสามารถปรับตรรกะได้อย่างง่ายดายบนพื้นฐานต่อผลกระทบ โดยปกติคุณจะต้องทำการตรวจสอบเงื่อนไขบางอย่างในตรรกะการดำเนินการของเอฟเฟกต์ แต่ก็ยังมีความเชื่อมโยงกันมากขึ้นเนื่องจากการตรวจสอบเหล่านี้มีผลกับคำถาม
manabreak

1

ฉันจะเสนอคำแนะนำเล็กน้อย บางคนขัดแย้งกัน แต่อาจมีประโยชน์บ้าง

พิจารณารายการเทียบกับธง

คุณสามารถย้ำไปทั่วโลกและตรวจสอบการตั้งค่าสถานะในแต่ละรายการเพื่อตัดสินใจว่าจะทำสิ่งที่ธง หรือคุณสามารถเก็บรายการของไอเท็มเหล่านั้นเท่านั้นที่ควรทำสิ่งแฟล็ก

พิจารณารายการและการแจกแจง

คุณสามารถเพิ่มเขตข้อมูลบูลีนไปยังคลาสไอเท็มของคุณคืออะไรนั่นคือและอะไร หรือคุณสามารถมีรายการของสตริงหรือองค์ประกอบ enum เช่น {“ isAThis”,“ isAThat”} หรือ {IS_A_THIS, IS_A_THAT} ด้วยวิธีนี้คุณสามารถเพิ่มอันใหม่ในการแจงนับ (หรือ const const) โดยไม่ต้องเพิ่มฟิลด์ ไม่ใช่ว่ามีอะไรผิดปกติกับการเพิ่มเขตข้อมูล ...

พิจารณาพอยน์เตอร์ของฟังก์ชัน

แทนที่จะเป็นรายการของแฟล็กหรือ enums สามารถมีรายการของการดำเนินการเพื่อดำเนินการสำหรับรายการนั้นในบริบทที่แตกต่างกัน (Entity-ish ... )

พิจารณาวัตถุ

บางคนชอบวิธีที่ขับเคลื่อนด้วยข้อมูลหรือใช้สคริปต์หรือส่วนประกอบเอนทิตี แต่ลำดับชั้นวัตถุแบบเก่าก็มีค่าควรพิจารณาเช่นกัน คลาสพื้นฐานจำเป็นต้องยอมรับการกระทำเช่น“ เล่นการ์ดนี้สำหรับเทิร์นเฟส B” หรืออะไรก็ตาม จากนั้นการ์ดแต่ละประเภทสามารถแทนที่และตอบสนองตามความเหมาะสม อาจมีวัตถุผู้เล่นและวัตถุเกมด้วยเช่นกันดังนั้นเกมสามารถทำสิ่งต่าง ๆ เช่นถ้า (player-> isAllowedToPlay ()) {เล่น ...

พิจารณาความสามารถในการดีบัก

เมื่อสิ่งที่ดีเกี่ยวกับกองเขตข้อมูลธงคือคุณสามารถตรวจสอบและพิมพ์สถานะของทุกรายการในลักษณะเดียวกัน หากสถานะถูกแสดงด้วยชนิดต่าง ๆ หรือส่วนประกอบของถุงหรือพอยน์เตอร์ของฟังก์ชันหรืออยู่ในรายการที่แตกต่างกันอาจไม่เพียงพอที่จะดูที่ฟิลด์ของรายการ มันคือการแลกเปลี่ยนทั้งหมด

ในที่สุดการปรับโครงสร้างใหม่: พิจารณาการทดสอบหน่วย

ไม่ว่าคุณจะพูดคุยกับสถาปัตยกรรมของคุณมากแค่ไหนคุณก็สามารถจินตนาการถึงสิ่งที่มันไม่ครอบคลุม แล้วคุณจะต้อง refactor อาจจะเล็กน้อยหรืออาจจะมาก

วิธีที่จะทำให้ปลอดภัยยิ่งขึ้นนี้คือร่างกายของการทดสอบหน่วย ด้วยวิธีนี้คุณสามารถมั่นใจได้ว่าแม้ว่าคุณจะจัดเรียงสิ่งต่าง ๆ ภายใต้ (อาจจะมาก!) การทำงานที่มีอยู่ยังคงใช้งานได้ การทดสอบแต่ละหน่วยโดยทั่วไปจะเป็นดังนี้:

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
}

อย่างที่คุณเห็นการทำให้การเรียก API ระดับบนสุดของเกม (หรือเครื่องเล่น, การ์ด, & c) มีความเสถียรเป็นกุญแจสำคัญในกลยุทธ์การทดสอบหน่วย


0

แทนที่จะคิดถึงการ์ดแต่ละใบแยกกันเริ่มคิดในแง่ของเอฟเฟกต์และการ์ดมีหนึ่งหรือมากกว่าหนึ่งประเภทเหล่านี้ ตัวอย่างเช่นในการคำนวณระยะเวลาในการเทิร์นคุณสามารถวนรอบการ์ดทั้งหมดที่อยู่ในการเล่นและตรวจสอบหมวดหมู่ "จัดการระยะเวลาการเปิด" ของการ์ดแต่ละใบที่มีประเภทนั้น การ์ดแต่ละใบจะเพิ่มขึ้นหรือเขียนทับระยะเวลาเทิร์นตามกฎที่คุณตัดสินใจ

นี่คือระบบมินิคอมโพเน้นท์โดยที่วัตถุ "การ์ด" แต่ละอันนั้นเป็นเพียงคอนเทนเนอร์สำหรับส่วนประกอบเอฟเฟกต์มากมาย


เนื่องจากการ์ด - และการ์ดในอนาคตด้วย - สามารถทำอะไรก็ได้ฉันคาดหวังว่าการ์ดแต่ละใบจะมีสคริปต์ ถึงกระนั้นฉันก็ค่อนข้างแน่ใจว่านี่ไม่ใช่ปัญหาด้านประสิทธิภาพจริง ๆ ..
Jari Komppa

4
ตามความเห็นหลัก: ไม่มีใคร (นอกเหนือจากคุณ) ได้พูดอะไรเกี่ยวกับปัญหาประสิทธิภาพการทำงาน สำหรับการเขียนสคริปต์แบบเต็มเป็นทางเลือกให้อธิบายอย่างละเอียดในคำตอบ
jhocking
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.