सबसे अधिक संभावना है, आपने पहले ही लेख के शीर्षक से अनुमान लगाया था कि स्रोत कोड में त्रुटियों पर ध्यान केंद्रित किया गया है। लेकिन यह केवल एकमात्र चीज नहीं है जो इस लेख में चर्चा की जाएगी। अगर C ++ और किसी और के कोड में त्रुटियों के अलावा, आप असामान्य गेम से आकर्षित होते हैं और आपको यह जानने में दिलचस्पी है कि ये "bagels" क्या हैं और वे उनके साथ क्या खाते हैं, तो बिल्ली का स्वागत है!
असामान्य खेलों के लिए मेरी खोज में, मैं एक कैटैक्स्लाइम डार्क डेज़ अहेड गेम में आया, जो अन्य असामान्य ग्राफिक्स से अलग है: यह एक काले रंग की पृष्ठभूमि पर बहु-रंगीन एएससीआईआई पात्रों का उपयोग करके लागू किया गया है।
इस खेल में क्या हड़ताली है और इसका ilk कितना है, इसमें सब कुछ लागू है। विशेष रूप से, कैटेक्लाइसम में, उदाहरण के लिए, यहां तक कि एक चरित्र बनाने के लिए मैं गाइडों की तलाश करना चाहता हूं, क्योंकि खेल में घटनाओं की विविधताओं का उल्लेख नहीं करने के लिए दर्जनों विभिन्न पैरामीटर, विशेषताएं और प्रारंभिक भूखंड हैं।
यह एक ओपन सोर्स गेम है, और C ++ में भी लिखा गया है। इसलिए पीवीएस-स्टूडियो स्थैतिक विश्लेषक के माध्यम से इस परियोजना को पारित करना और न चलाना असंभव था, जिसके विकास में मैं अब सक्रिय रूप से शामिल हूं। परियोजना ने स्वयं मुझे कोड की उच्च गुणवत्ता के साथ आश्चर्यचकित किया, हालांकि, इसमें अभी भी कुछ खामियां हैं और मैं इस लेख में उनमें से कई पर चर्चा करूंगा।
आज तक, पीवीएस-स्टूडियो का उपयोग करके बहुत सारे गेम का परीक्षण किया गया है। उदाहरण के लिए, आप हमारे अन्य लेख "
वीडियो गेम इंडस्ट्री में स्टेटिक एनालिसिस: टॉप 10 सॉफ्टवेयर एरर्स " पढ़ सकते हैं।
तर्क
उदाहरण 1:निम्न उदाहरण एक विशिष्ट प्रतिलिपि त्रुटि है।
V501 बाईं और 'के दाईं ओर समान उप-अभिव्यक्तियाँ हैं' || ऑपरेटर: rng (2, 7) <abs (z) || rng (2, 7) <abs (z) ओवरमैप। 1503
bool overmap::generate_sub( const int z ) { .... if( rng( 2, 7 ) < abs( z ) || rng( 2, 7 ) < abs( z ) ) { .... } .... }
यहां एक ही स्थिति को दो बार जांचा जाता है। सबसे अधिक संभावना है, अभिव्यक्ति की प्रतिलिपि बनाई गई थी और इसमें कुछ बदलना भूल गया था। मुझे यह कहना मुश्किल है कि क्या यह त्रुटि महत्वपूर्ण है, लेकिन चेक उद्देश्य के अनुसार काम नहीं करता है।
इसी तरह की चेतावनी:
- V501 समान उप-अभिव्यक्तियाँ 'one_in (100000 / to_turns <int> (dur))' बाईं ओर और '&&' ऑपरेटर के दाईं ओर होती हैं। player_hardcoded_effects.cpp 547
उदाहरण 2:V728 एक अत्यधिक जाँच को सरल बनाया जा सकता है। द '(A && B) || ((A &&! B) 'अभिव्यक्ति' बूल (ए) == बूल (बी) 'अभिव्यक्ति के बराबर है। इन्वेंट्री_यू.सी .पीसी 199
bool inventory_selector_preset::sort_compare( .... ) const { .... const bool left_fav = g->u.inv.assigned.count( lhs.location->invlet ); const bool right_fav = g->u.inv.assigned.count( rhs.location->invlet ); if( ( left_fav && right_fav ) || ( !left_fav && !right_fav ) ) { return .... } .... }
स्थिति में कोई त्रुटि नहीं है, लेकिन यह अनावश्यक रूप से जटिल है। यह उन लोगों पर दया करने के लायक होगा जिन्हें इस स्थिति को अलग करना है, और यह लिखना आसान है
अगर (बाएं_फ़ेव == right_fav) ।
इसी तरह की चेतावनी:
- V728 एक अत्यधिक जाँच को सरल बनाया जा सकता है। द '(A &&! B) || (ए! और बी) अभिव्यक्ति 'बूल (ए) के बराबर है! = बूल (बी)' अभिव्यक्ति। iuse_actor.cpp 2653
पीछे हटना
यह मेरे लिए एक खोज के रूप में निकला कि जिन खेलों को आज "बैगल्स" कहा जाता है, वे पुराने शैली के रॉगुलाइक गेम्स के बहुत हल्के अनुयायी हैं। यह सब 1980 के पंथ खेल के साथ शुरू हुआ, जो एक रोल मॉडल बन गया और कई छात्रों और प्रोग्रामरों को अपना गेम बनाने के लिए प्रेरित किया। मुझे लगता है कि डीएनडी बोर्ड की भूमिका निभाने वाले समुदाय और इसकी विविधताओं द्वारा भी बहुत कुछ लाया गया है।
Mikrooptimizatsii
उदाहरण 3:विश्लेषक चेतावनियों का अगला समूह एक त्रुटि नहीं दर्शाता है, लेकिन प्रोग्राम कोड के माइक्रोएप्टिमाइजेशन की संभावना है।
V801 प्रदर्शन में कमी। संदर्भ के रूप में दूसरे फ़ंक्शन तर्क को फिर से परिभाषित करना बेहतर है। 'Const ... टाइप करें' की जगह 'const ... & टाइप' पर विचार करें। map.cpp 4644
template <typename Stack> std::list<item> use_amount_stack( Stack stack, const itype_id type ) { std::list<item> ret; for( auto a = stack.begin(); a != stack.end() && quantity > 0; ) { if( a->use_amount( type, ret ) ) { a = stack.erase( a ); } else { ++a; } } return ret; }
यहाँ
itdpe_id std :: string को छुपाता है। चूंकि तर्क अभी भी निरंतर पारित किया गया है, जो इसे बदलने की अनुमति नहीं देगा, यह केवल फ़ंक्शन के लिए एक चर संदर्भ को पारित करने और कॉपी करने पर संसाधनों को बर्बाद न करने के लिए तेज़ होगा। और यद्यपि, सबसे अधिक संभावना है, वहां की रेखा बहुत छोटी होगी, लेकिन बिना किसी स्पष्ट कारण के लगातार नकल करना अनावश्यक है। इसके अलावा, इस फ़ंक्शन को विभिन्न स्थानों से बुलाया जाता है, जिनमें से कई, बदले में, बाहर से भी
टाइप करते हैं और इसे कॉपी करते हैं।
इसी तरह की चेतावनी:
- V801 प्रदर्शन में कमी। एक संदर्भ के रूप में तीसरे फ़ंक्शन तर्क को फिर से परिभाषित करना बेहतर है। 'Const ... evt_filter' को 'const ... & evt_filter' से बदलने पर विचार करें। input.cpp 691
- V801 प्रदर्शन में कमी। एक संदर्भ के रूप में पांचवें फ़ंक्शन तर्क को फिर से परिभाषित करना बेहतर है। 'Const ... कलर' को 'const ... और कलर' से बदलने पर विचार करें। आउटपुट। ह 207
- कुल में, विश्लेषक ने 32 ऐसी चेतावनियाँ उत्पन्न कीं।
उदाहरण 4:V813 प्रदर्शन में कमी। 'Str' तर्क को संभवतः एक निरंतर संदर्भ के रूप में प्रस्तुत किया जाना चाहिए। catacharset.cpp 256
std::string base64_encode( std::string str ) { if( str.length() > 0 && str[0] == '#' ) { return str; } int input_length = str.length(); std::string encoded_data( output_length, '\0' ); .... for( int i = 0, j = 0; i < input_length; ) { .... } for( int i = 0; i < mod_table[input_length % 3]; i++ ) { encoded_data[output_length - 1 - i] = '='; } return "#" + encoded_data; }
इस मामले में, तर्क, हालांकि स्थिर नहीं है, फ़ंक्शन के शरीर में नहीं बदलता है। इसलिए, अनुकूलन के लिए, एक निरंतर लिंक के माध्यम से इसे पारित करना अच्छा होगा, और संकलक को स्थानीय प्रतियां बनाने के लिए मजबूर नहीं करना चाहिए।
यह चेतावनी भी एकल नहीं थी, कुल 26 ऐसे मामले थे।
इसी तरह की चेतावनी:
- V813 प्रदर्शन में कमी। 'संदेश' तर्क को संभवतः एक निरंतर संदर्भ के रूप में प्रस्तुत किया जाना चाहिए। json.cpp 1452
- V813 प्रदर्शन में कमी। 'S' तर्क को संभवतः एक निरंतर संदर्भ के रूप में प्रस्तुत किया जाना चाहिए। catacharset.cpp 218
- और इसी तरह…
पीछे हटना II
कुछ क्लासिक रॉगुलाइक गेम अभी भी विकसित किए जा रहे हैं। यदि आप GitHub Cataclysm DDA या NetHack रिपॉजिटरी में जाते हैं, तो आप देख सकते हैं कि हर दिन सक्रिय रूप से परिवर्तन किए जा रहे हैं। नेटहैक आमतौर पर सबसे पुराना गेम है जिसे अभी भी विकसित किया जा रहा है: यह जुलाई 1987 में जारी किया गया था, और 2018 से नवीनतम संस्करण की तारीखें।
हालांकि, इस शैली के प्रसिद्ध खेलों में से एक, बौना किले है, जिसे 2002 के बाद से विकसित किया गया था और पहली बार 2006 में जारी किया गया था। "हारना मजेदार है" खेल का आदर्श वाक्य है, जो इसके सार को सही ढंग से दर्शाता है, क्योंकि इसे जीतना असंभव है। 2007 में इस गेम ने वोटिंग के परिणामस्वरूप वर्ष का सर्वश्रेष्ठ रॉगुलाइक गेम का खिताब अर्जित किया, जो कि ASCII GAMES वेबसाइट पर प्रतिवर्ष आयोजित किया जाता है।
वैसे, जो लोग इस खेल में रुचि रखते हैं, वे निम्नलिखित समाचारों में रुचि ले सकते हैं। बौना किले को बेहतर 32-बिट ग्राफिक्स के साथ स्टीम पर जारी किया जाएगा। एक अद्यतन छवि के साथ कि दो अनुभवी गेम मॉडरेटर काम कर रहे हैं, बौना किले के प्रीमियम संस्करण को अतिरिक्त संगीत ट्रैक और स्टीम वर्कशॉप के लिए समर्थन प्राप्त होगा। लेकिन अगर कुछ भी हो, तो बौना किले के भुगतान किए गए संस्करण के मालिक अपडेट किए गए ग्राफिक्स को एएससीआईआई में पिछले रूप में बदल पाएंगे।
अधिक जानकारी ।
ओवरराइडिंग असाइनमेंट ऑपरेटर
उदाहरण 5, 6:इसी तरह की चेतावनी की एक दिलचस्प जोड़ी भी थी।
V690 'JsonObject' क्लास एक कॉपी कंस्ट्रक्टर को लागू करता है, लेकिन इसमें '=' ऑपरेटर की कमी होती है। ऐसी कक्षा का उपयोग करना खतरनाक है। json.h 647
class JsonObject { private: .... JsonIn *jsin; .... public: JsonObject( JsonIn &jsin ); JsonObject( const JsonObject &jsobj ); JsonObject() : positions(), start( 0 ), end( 0 ), jsin( NULL ) {} ~JsonObject() { finish(); } void finish();
इस वर्ग में एक कॉपी कंस्ट्रक्टर और विध्वंसक है, हालांकि, यह असाइनमेंट ऑपरेटर को अधिभार नहीं देता है। यहाँ समस्या यह है कि स्वचालित रूप से उत्पन्न असाइनमेंट ऑपरेटर केवल
JsonIn को एक पॉइंटर दे
सकता है । परिणामस्वरूप,
JsonObject वर्ग की दोनों वस्तुएँ समान
JsonIn को इंगित
करती हैं । यह ज्ञात नहीं है कि ऐसी स्थिति अब कहीं उत्पन्न हो सकती है या नहीं, लेकिन किसी भी मामले में, यह एक रेक है कि कोई व्यक्ति जल्द या बाद में कदम रखेगा।
एक समान समस्या निम्न वर्ग में मौजूद है।
V690 'JsonArray' क्लास एक कॉपी कंस्ट्रक्टर को लागू करता है, लेकिन '=' ऑपरेटर की कमी है। ऐसी कक्षा का उपयोग करना खतरनाक है। json.h 820
class JsonArray { private: .... JsonIn *jsin; .... public: JsonArray( JsonIn &jsin ); JsonArray( const JsonArray &jsarr ); JsonArray() : positions(), ...., jsin( NULL ) {}; ~JsonArray() { finish(); } void finish();
आप "
द लॉ ऑफ द बिग टू " (या इस लेख "
सी ++: द बिग टू लॉ " के अनुवाद में) लेख में एक जटिल वर्ग के लिए एक असाइनमेंट ऑपरेटर के अधिभार की कमी के खतरे के बारे में अधिक पढ़ सकते हैं।
उदाहरण 7, 8:ओवरलोड असाइनमेंट ऑपरेटर से संबंधित एक और उदाहरण, लेकिन इस बार हम इसके विशिष्ट कार्यान्वयन के बारे में बात कर रहे हैं।
V794 असाइनमेंट ऑपरेटर को 'यह == & अन्य' के मामले से संरक्षित किया जाना चाहिए। मटका_ असामान्य.ह 49
class StringRef { public: .... private: friend struct StringRefTestAccess; char const* m_start; size_type m_size; char* m_data = nullptr; .... auto operator = ( StringRef const &other ) noexcept -> StringRef& { delete[] m_data; m_data = nullptr; m_start = other.m_start; m_size = other.m_size; return *this; }
समस्या यह है कि यह कार्यान्वयन ऑब्जेक्ट को स्वयं को असाइन करने से सुरक्षित नहीं है, जो एक असुरक्षित अभ्यास है। यही है, यदि इस ऑपरेटर को
* का संदर्भ दिया जाता है, तो मेमोरी रिसाव हो सकता है।
एक गलत काम के साथ एक गलत असाइनमेंट ऑपरेटर ओवरलोड का एक समान उदाहरण:
V794 असाइनमेंट ऑपरेटर को 'यह == & rhs' के मामले से सुरक्षित किया जाना चाहिए। player_activity.cpp 38
player_activity &player_activity::operator=( const player_activity &rhs ) { type = rhs.type; .... targets.clear(); targets.reserve( rhs.targets.size() ); std::transform( rhs.targets.begin(), rhs.targets.end(), std::back_inserter( targets ), []( const item_location & e ) { return e.clone(); } ); return *this; }
इस मामले में, जैसे कि वस्तु के असाइनमेंट पर कोई जांच नहीं है। लेकिन इसके अलावा, वेक्टर भरता है। यदि आप इस तरह के अधिभार द्वारा वस्तु को अपने आप को सौंपने की कोशिश करते हैं, तो
लक्ष्य क्षेत्र में हमें एक दोगुना वेक्टर मिलता है, जिसके कुछ तत्व दूषित होते हैं। हालांकि,
रूपांतरित करने से पहले स्पष्ट है कि ऑब्जेक्ट का वेक्टर साफ़ हो जाएगा और डेटा खो जाएगा।
पीछे हटना III
2008 में, बैगल्स ने एक औपचारिक परिभाषा भी हासिल कर ली, जिसे "बर्लिन व्याख्या" का महाकाव्य नाम दिया गया था। इस परिभाषा के अनुसार, इस तरह के खेलों की मुख्य विशेषताएं हैं:
- एक बेतरतीब ढंग से उत्पन्न दुनिया जो रिप्ले मूल्य बढ़ाती है;
- अनुमति: यदि आपका चरित्र मर जाता है, तो वह हमेशा के लिए मर जाता है और सभी आइटम खो जाते हैं;
- चरण-दर-चरण: खिलाड़ी की कार्रवाई के साथ केवल परिवर्तन होते हैं, जब तक कि कार्रवाई नहीं की जाती है - समय रुक जाता है;
- जीवन रक्षा: संसाधन बेहद सीमित हैं।
अच्छी तरह से और सबसे महत्वपूर्ण: बैगेल्स का उद्देश्य मुख्य रूप से दुनिया की खोज और खोज करना है, वस्तुओं का उपयोग करने के लिए नए तरीकों की खोज करना और डंगऑन के माध्यम से जाना।
कैटकैलीसम डीडीए में सामान्य स्थिति: जमे हुए और मौत के भूखे, आप प्यास से तड़प रहे हैं, और वास्तव में आपके पास पैरों के बजाय छह जाल हैं।
महत्वपूर्ण विवरण
उदाहरण 9:V1028 संभावित अतिप्रवाह। 'Size_t' प्रकार के 'प्रारंभ + बड़े' ऑपरेटर के कास्टिंग ऑपरेंड पर विचार करें, परिणाम नहीं। worldfactory.cpp 638
void worldfactory::draw_mod_list( int &start, .... ) { .... int larger = ....; unsigned int iNum = ....; .... for( .... ) { if( iNum >= static_cast<size_t>( start ) && iNum < static_cast<size_t>( start + larger ) ) { .... } .... } .... }
ऐसा लगता है कि प्रोग्रामर अतिप्रवाह से बचना चाहता था। लेकिन इस मामले में जोड़ का परिणाम लाना व्यर्थ है, क्योंकि संख्याओं को जोड़ने पर अतिप्रवाह होगा, और अर्थहीन परिणाम पर टाइप विस्तार किया जाएगा। इस स्थिति से बचने के लिए, आपको केवल एक प्रकार का तर्क देने की आवश्यकता है:
(static_cast <size_t> (start) + बड़ा) ।
उदाहरण 10:V530 फ़ंक्शन 'आकार' के रिटर्न मान का उपयोग करने की आवश्यकता है। worldfactory.cpp 1340
bool worldfactory::world_need_lua_build( std::string world_name ) { #ifndef LUA .... #endif
ऐसे मामलों के लिए, एक छोटी सी चाल है। यदि चर अप्रयुक्त है, तो किसी भी विधि को कॉल करने की कोशिश करने के बजाय, आप संकलक चेतावनी को दबाने के लिए बस
(शून्य) world_name लिख सकते हैं।
उदाहरण 11:V812 प्रदर्शन में कमी। 'गिनती' फ़ंक्शन का अप्रभावी उपयोग। यह संभवतः कॉल को 'खोज' फ़ंक्शन से बदल सकता है। खिलाी। ९ ६००
bool player::read( int inventory_position, const bool continuous ) { .... player_activity activity; if( !continuous || !std::all_of( learners.begin(), learners.end(), [&]( std::pair<npc *, std::string> elem ) { return std::count( activity.values.begin(), activity.values.end(), elem.first->getID() ) != 0; } ) { .... } .... }
इस तथ्य को देखते हुए कि
गिनती के परिणाम की तुलना शून्य से की जाती है, यह समझना है कि
गतिविधि के बीच कम से कम एक आवश्यक तत्व
है या नहीं । लेकिन
गणना पूरे कंटेनर से गुजरने के लिए मजबूर है, क्योंकि यह तत्व की सभी घटनाओं को गिनता है। इस स्थिति में, इसे
खोजने के लिए उपयोग करना तेज़ होगा, जो पहले मैच के बाद बंद हो जाता है।
उदाहरण 12:यदि आप एक सूक्ष्मता के बारे में जानते हैं तो निम्न त्रुटि का आसानी से पता लगाया जा सकता है।
V739 ईओएफ की तुलना 'चार' प्रकार के मूल्य से नहीं की जानी चाहिए। 'Ch' 'int' प्रकार का होना चाहिए। json.cpp 762
void JsonIn::skip_separator() { signed char ch; .... if (ch == ',') { if( ate_separator ) { .... } .... } else if (ch == EOF) { .... }
यह उन त्रुटियों में से एक है जिन्हें नोटिस करना मुश्किल हो सकता है यदि आप नहीं जानते कि
ईओएफ -1 के रूप
में परिभाषित किया गया है। तदनुसार, यदि आप इसे टाइप
किए गए चार्ट के चर के साथ तुलना करने का प्रयास करते हैं, तो स्थिति लगभग हमेशा
झूठी होती है । एकमात्र अपवाद यदि वर्ण कोड 0xFF (255) है। तुलना करते समय, ऐसा प्रतीक -1 में बदल जाएगा और स्थिति सही होगी।
उदाहरण 13:अगली छोटी गलती एक दिन गंभीर हो सकती है। कोई आश्चर्य नहीं कि यह CWE की सूची में
CWE-834 के रूप में है। और वैसे भी, उनमें से पांच थे।
V663 अनंत लूप संभव है। लूप से टूटने के लिए 'Cin.eof ()' स्थिति अपर्याप्त है। सशर्त अभिव्यक्ति के लिए 'cin.fail ()' फ़ंक्शन कॉल जोड़ने पर विचार करें। क्रिया .pp 46
void parse_keymap( std::istream &keymap_txt, .... ) { while( !keymap_txt.eof() ) { .... } }
जैसा कि चेतावनी में कहा गया है कि पढ़ते समय फ़ाइल के अंत तक पहुँचने के लिए जाँच करना पर्याप्त नहीं है, आपको
cin.fail () पढ़ने में त्रुटि के लिए भी जाँच करनी चाहिए। अधिक सुरक्षित पढ़ने के लिए कोड बदलें:
while( !keymap_txt.eof() ) { if(keymap_txt.fail()) { keymap_txt.clear(); keymap_txt.ignore(numeric_limits<streamsize>::max(),'\n'); break; } .... }
फ़ाइल से रीडिंग त्रुटि के मामले में स्ट्रीम से त्रुटि स्थिति (ध्वज) को हटाने के लिए
keymap_txt.clear () की आवश्यकता है, अन्यथा पाठ को आगे नहीं पढ़ा जा सकता है।
keymap_txt.ignore के साथ
num_limits < स्ट्रीम > :: अधिकतम () पैरामीटर और एक पंक्ति फ़ीड नियंत्रण चरित्र आपको बाकी पंक्ति को छोड़ देने की अनुमति देता है।
पढ़ने को रोकने का एक बहुत सरल तरीका है:
while( !keymap_txt ) { .... }
जब तर्क के संदर्भ में उपयोग किया जाता है, तो यह
ईओएफ तक पहुंचने के लिए खुद को
सच के बराबर मूल्य में बदल देता है।
पीछे हटना IV
अब सबसे लोकप्रिय खेल वे हैं जो रॉगुलाइक गेम और अन्य शैलियों के संकेतों को जोड़ते हैं: प्लेटफ़ॉर्मर, रणनीतियों आदि। ऐसे खेलों को रॉगुलाइक-जैसे या रग्गुलाइट कहा जाता है। इस तरह के खेलों में डोन्ट स्टारव, द बाइंडिंग ऑफ आइजैक, एफटीएल: फास्टर थान लाइट, डार्केस्ट डंगऑन और यहां तक कि डियाब्लो जैसे प्रसिद्ध खिताब शामिल हैं।
हालाँकि कई बार रॉगुलाइक और रॉगुलाईट के बीच का अंतर इतना छोटा होता है कि यह स्पष्ट नहीं होता है कि खेल किस शैली का है। किसी का मानना है कि बौना किला अब रग्गुलाइक नहीं है, लेकिन किसी के लिए, डियाब्लो एक क्लासिक बैगल है।
निष्कर्ष
हालाँकि यह परियोजना पूरी तरह से उच्च गुणवत्ता वाले कोड का एक उदाहरण है, और कई गंभीर त्रुटियों को खोजने के लिए संभव नहीं था, इसका मतलब यह नहीं है कि स्थैतिक विश्लेषण का उपयोग इसके लिए बेमानी है। बिंदु एक बार के चेक में नहीं है जो हम स्थैतिक कोड विश्लेषण की पद्धति को लोकप्रिय बनाने के लिए करते हैं, लेकिन विश्लेषक के नियमित उपयोग में है। फिर कई त्रुटियों को एक प्रारंभिक चरण में पहचाना जा सकता है और इसलिए, उन्हें सही करने की लागत को कम करें। गणना का
उदाहरण ।
माना खेल पर सक्रिय काम चल रहा है, और मॉडर्स का एक सक्रिय समुदाय है। इसके अलावा, यह आईओएस और एंड्रॉइड सहित कई प्लेटफार्मों पर पोर्ट किया गया है। तो, अगर आप इस खेल में रुचि रखते हैं, तो मैं आपको कोशिश करने की सलाह देता हूं!
