Kartlar dəsti C ++ dilində yazılmışdır

BlackJack oyununda istifadə etdiyim bir göyərtə yaratdım. Qara jak məntiqimi sildim və yalnız göyərtə, kart və minimum dilerdən ayrıldım.

Göyərtə sinfi oradadır, çünki pinnacle və ya skip-bo kimi əlavə göyərtə yaratmaq istəyirəm.

Kart obyektləri üçün bir Enum sinifindən və ya kart yaratmaq üçün bir keçid ifadəsindən istifadə etmək barədə.

Shuffle () barədə əmin deyildim, buna görə yalnız randomizeCards istifadə edirəm. Demək olar ki, bütün təlimlərdə rand () istifadə olunur (). Zəif bir generator olduğunu oxudum və başqa birini istifadə etdim. Hər cür layihədə təsadüfi istifadə etdim.

Məni əsasən ölçü və sürət narahat edir. Mümkün qədər kiçik və RAM-dan mümkün qədər səmərəli istifadə olunur.

1 Cavab 1

Dizayn baxışı

Kart sinfinə baxaraq başlayaq. Hazırlandığı kimi, Kartın 4 məlumat üzvü var:

  • dəyər
  • kostyum
  • ad
  • rütbə

dəyər imzasız bir int, digərləri isə hamısı std :: string s.

Bu olduqca israfdır. Bir kart həqiqətən iki xüsusiyyətə malikdir: rütbə və kostyum. Rütbəni bilirsinizsə, dəyəri və adı bilirsiniz, belə ki, sinifdəki məlumatların təkrarlanması sadəcə yer boşuna sərf olunur.

Kodunuzun necə tərtib olunduğunu belə görmürəm, çünki rütbə bir sətirdir, amma Deck :: makeDeck () içərisinə tam dəyərlər vurmağa çalışırsınız.

Bundan əlavə, səhv tiplərdən istifadə etmək də yer boşuna sərf edir. Kostyumun simli olmasına ehtiyac yoxdur. Mümkün 4 dəyərdən yalnız birinə sahib ola bilər. Kostyumu bir bayta sığdırmaq olar. Bir simli istifadə etmək yalnız deyil

20-50 dəfə daha böyükdür, bunun üçün yaddaş ayırma tələb oluna bilər və hər əməliyyat daha bahalı olur (məsələn, kostyumları müqayisə etmək çox daha bahalı simli müqayisə etmək əvəzinə sadə bayt dəyər müqayisəsi ola bilər ).

Düşünürəm ki, baxmalı olduğunuz ilk şey sadalamalardır. Sayımlar sabit, az sayda bir şeyə sahib olduğunuz zaman mükəmməldir . Məsələn, bir kart kostyumu kimi. Siz edə bilərsiniz:

Bu belədir! Daha da yaxşılaşdırmaq üçün ölçüsü bir bayt olması lazım olduğunu təyin edə bilərsiniz:

Sonra çap üçün bir sətrə çevrilmə kimi şeylərə ehtiyacınız varsa, lazım olduqda funksiyalar əlavə edə və ya əlavə edicilər əlavə edə bilərsiniz:

Rütbə üçün eyni şeyi edə bilərsiniz:

Sonra sizə lazım olan hər hansı bir funksiyanı əlavə edə bilərsiniz:

Bununla yanaşı, bir kart növü etmək əhəmiyyətsizdir:

Bu sizə lazım olan hər şeydir! Əlbətdə bir axın əlavə edən kimi faydalı funksiyalar əlavə edə bilərsiniz:

Fərqin nə qədər böyük olduğunu həqiqətən göstərmək üçün Kartınızın və mənim kartımın ölçülərini müqayisə edin. Clang və libc ++ istifadə:

  • Kart: 80
  • kart_t: 2
  • Kart: 104
  • kart_t: 2

Görürsən? Numaralandırmalardan istifadə etməklə kart sinfi

40-50 dəfə kiçikdir. 64 b bir önbellek xətti ölçüsü götürdüyünüzdə, əslində bütün bir kart_t göyərtəsini yalnız iki önbellek sətirinə sığdıra bilərsiniz, halbuki bir kart belə sığmaz . (Və həqiqətən ağıllı olsaydın, card_t ölçüsünü bir bayta endirə bilərsən , beləliklə bütün göyərtə bir önbellek sırasına sığar .) Və iki kartı müqayisə etmək kimi əməliyyatlar minlərlə dəfə daha sürətli olacaq.

Bir kart sinfi dizayn etsəydim, hər şeyi təmiz saxlamaq üçün həm kostyumu, həm də bu sinifin daxili növlərini hazırlayardım:

Cəngavər rütbəsi də daxil olmaqla daha çox göyərtə növünü dəstəkləmək istəyirsinizsə, bunu yalnız sıra_t sayımına əlavə edə bilərsiniz (say dəyərlərini cəngavər rütbəsi 12, kraliça 13, kral 14 olmaq üçün tənzimləyə bilərsiniz). Ulduzlar və ya dalğalar kimi daha çox kostyum istəyirsinizsə, problem yoxdu, yalnız suit_t-ə əlavə edin. Şaka edənlər, qozanlar və ya axmaq üçün dəstək əlavə etmək istəyirsinizsə, işlər bir az daha mürəkkəbləşir, amma gülünc bir şəkildə deyil. Bir "heç" kostyumu əlavə edə bilərsiniz ... ya da zarafatçılar üçün xüsusi "ağ" və "qara" kostyumlar əlavə edə bilərsiniz. Və ya uyğun kartları, kozurları, zarafatları və axmaqları ayırmaq üçün bir variant istifadə edə bilərsiniz.

Kart sinif ölçüsü azalmışdır sonra, göyərtə sinif da olacaq çox daha səmərəli.

Göyərtə sinfinizin 3 məlumat üzvü var, amma kostyumun mənasını görmürəm və kart makeDeck () funksiyanızda lokal dəyişən olmağın səhv bir yolu kimi görünür. Əslində ehtiyac duyduğunuz tək məlumat üzvü kartlardır.

İndi kartlar bir vektordur və bu da pis deyil ... ancaq bir vektorun tam gücünə ehtiyacınız olmadığını düşünün . Əvvəlcədən bilirsiniz ki, göyərtənizdə heç vaxt 52-dən çox kart ola bilməz. Nəzərə alsaq ki, Boost-un statik_vektoru kimi bir şey istifadə edə bilər və ümumiyyətlə hər hansı bir dinamik ayırma qarşısını ala bilərsiniz.

Ancaq bir başlanğıc üçün vektor yaxşıdır.

Bununla birlikdə, göyərtə sinfinin həqiqətən ehtiyacı olan bəzi üzv funksiyalarıdır, çünki yalnız kartların məlumat üzvünü ifşa etmək istəyirsinizsə, göyərtə sinifinə ümumiyyətlə sahib ola bilməzsiniz: sadəcə bir kart vektorundan istifadə edin. Yaxşı bir interfeys dizayn etmək bir sənətdir və bir elmdir, lakin layiqli bir göyərtə sinfi belə bir şeyə bənzəyir:

Bu interfeysdəki bütün funksiyalara ehtiyacınız olmaya bilər , ancaq bəziləri əlverişli olacaqdır. Bu interfeys ilə sadə bir oyun görünə bilər:

Satıcı

Satıcı sinifinin quruluşu pis deyil, ancaq göyərtə sinifindən həm oyunçu, həm də ev əlləri üçün istifadə edə bilərsiniz. Nəticədə onlar, bir mənada, göyərtələrdir.

Satıcı sinifindəki əsl problem, sadəcə çox şey etməsidir. Bu sadəcə bir satıcı deyil ... bütün oyunu oynayır ... və hətta oyunçunun yenidən oynamaq istədiyini soruşur ... və göyərtəni qarışdırır ... və kartları çəkir ....

Yıxın. Əvvəla, bu sinif üçün uyğun ad yəqin ki, “diler” deyil, əksinə “oyun” dur. Sinifin etməsi lazım olan şey yalnız quraşdırma və sonra oyun oynamaqdır. Qarışdırılır? Göyərtə sinfinin bunu idarə etməsinə icazə verin. Təsadüfi kartlar çəkirsiniz? Yenə də göyərtə sinfinin bunu idarə etməsinə icazə verin. İstifadəçidən yenidən oynatmaq istədiklərini soruşursunuz? Bunu başqa bir şey idarə etsin. Oyun sinfi yalnız oyunun özü haqqında olmalıdır .

Və oynamaq funksiyası belə bir şey ola bilər:

Oyun bitdikdən və (() döndükdən sonra, xarici bir döngə edə bilərsiniz - məsələn main () içində "yenidən oynamaq istəyirsən?" Soruşan bir cavab verə bilər və cavab bəli olsa, sadəcə zəng edəcək yenidən oynamaq.

Bir oyun proqramçısı kimi düşünün

Bütün oyunlar əsas etibarilə eyni quruluşa malikdir:

Yalnız bir konsol kart oyunu qədər sadə bir oyun da eyni naxışdan istifadə edir. Oyunu işə saldıqdan sonra (göyərtəni quraraq bütün oyunçulara əl uzadıb), əvvəlcə istifadəçidən nə etmək istədiklərini soruşduğunuz döngəyə başlayırsınız ... və sonra AI-nin öz növbəsini verməsini özündə ehtiva edən oyun vəziyyətini yeniləyirsiniz. qaliblərin olub-olmadığını yoxlamaq ... sonra hazırkı vəziyyəti oyunçuya çatdırır, hansı kartları saxladıqlarını və ya oyunun bitib-bitmədiyini (və kimin qazandığını) izah edirsən.

Yenidən oxumağa icazə vermək istəyirsinizsə, bütün bunları bir döngə ilə bağlamalısınız:

Hər hansı bir oyuna başlamazdan əvvəl təsadüfi say generatorunuzu qura bilərsiniz:

Bu yüksək səviyyəli dizayndan başlayaraq sizə lazım olan funksiyaları doldura bilərsiniz. Və bu şəkildə dizayn edərək, istəsəniz qrafik oyuna çevrilmək üçün ümumi quruluşa sahib olacaqsınız. (Fərqlər əsasən std :: cin-dən giriş əldə etməməyiniz və ya mətn çıxışı std :: cout-a göstərməməyinizlə əlaqədardır; yeniləmə funksiyasındakı həqiqi oyun məntiqi dəyişməz olmalıdır.)

Ümumi məsələlər

Std :: endl istifadə etməyin

Yalnız yeni bir xətt istəyirsinizsə, '\ n' istifadə edin. std :: endl yalnız yeni bir xətt çap etmir, bahalı ola bilən axını təmizləyir.

Buna görə 2 sətir aralığını istəyirsinizsə və std :: cout ən azı 4 dəfə yuyursunuz . Bu gülüncdür.

Hər kart çəkiləndə göyərtəni qarışdırmayın

Dəfələrlə qarışdırmaq əslində təsadüfiliyi yaxşılaşdırmır və xərc tələb edir.

Bunun əvəzinə oyunun əvvəlində yalnız bir dəfə qarışdırın. Axı real həyatda belə işləyir.

Bir dəfə qarışdırın və sonra kartları göyərtənin yuxarı hissəsindən götürməyə davam edin.

Kod baxışı

Heç vaxt, heç vaxt belə etmə. Sadəcə std :: lazım olduğu yerə qoyun.

Bu funksiyaları açıq şəkildə yazmağa ehtiyac yoxdur. Dərslər default olaraq konstruktorlar və destruktorlar alır. Əslində bu funksiyaları yazmaq üç problemə səbəb olur:

Bu inkişaf etdiriciləri çaşdırır.

Bu funksiyaları görən təcrübəli bir C ++ kodlayıcı dərhal şübhə doğuracaq, çünki niyə kimsə lazım olmayan kodu yazmaq üçün vaxt itirər. Biri oturub bunları yazdısa, deməli bunun üçün bir səbəbi var ... hə? İndi kodlayıcı səbəbi axtarmağa çalışaraq vaxt itirəcək.

Baxım yükü yaradır.

Yazdığınız hər kod sətri texniki borcunu artırır. Kodu oxumağı çətinləşdirir (çünki daha çox kod oxumaq daha az koddan daha çətindir), problemləri tapmaq daha çətindir (çünki vacib şeylərin ətrafında bir dəstə yararsız, səs-küy kodu varsa, vacib şeyləri görmək daha çətin olacaq, və buna görə də böcəklər olacaqdır) və daha çətin işləmək (çünki hər dəfə dəyişiklik etmək istədiyiniz zaman hər hansı bir ziddiyyətin olub olmadığını görmək üçün daha çox kodu yoxlamalısınız).

Əslində proqramınızı ləngidir.

Varsayılan konstruktoru və ya destruktoru bu cür müəyyənləşdirdiyiniz zaman, qeyri-əhəmiyyətsiz bir varsayılan konstruktor və qeyri-əhəmiyyətsiz bir destruktor yaradacaqsınız. Bu pisdir. Əhəmiyyətsiz standart qurucular və əhəmiyyətsiz dağıdıcılar yaxşı bir şeydir, çünki əhəmiyyətsizdirlərsə, tərtibçi onları əksər hallarda optimallaşdırır. Bu sizə böyük performans qazancları qazandıra bilər.

Buna görə yalnız ehtiyacınız olmayan funksiyaları yazmayın.

Adi nömrələr üçün imzasız növlərdən istifadə etməyin. Hesablamalar və müqayisələr edərkən sürprizlərə səbəb olurlar. Yalnız bir int istifadə edin.

Başqa, mən artıq olacaq yalnız 2 böyük bayt şey kart sinif aşağı, optimize edə bilərsiniz necə göstərdi blazingly sürətli.

Yenə də buna ehtiyacınız yoxdur, ona görə də yazmayın.

Bütün bu üzvlərin fikrini başa düşmürəm. Göyərtə bir dəstə kartdan başqa bir şey olmamalıdır. Niyə bir dəst dəst kostyuma və ayrıca, tək karta ehtiyacınız var?

Orada çox sehrli nömrələr gizlənmişdir. Sehrli nömrələr pis bir fikirdir. Həmişə nələrin olduğunu izah edən adlanan sabitlərdən istifadə etməlisiniz:

Dəyişənlərə mənalı adlar da verməlisən. İ və j kimi adlar, döngü sayğacı yalnız bir göstərici olduqda məna daşıyır və başqa bir məqsədə xidmət etmir. Amma bu dəyərlər nə başqa mənaları ifadə edir:

Bu funksiyanın növbəti problemi kart üçün xaricdən bir dəyişən istifadə etməyinizdir. Bunun heç bir mənası yoxdur. Kart yerli bir dəyişən olmalıdır.

Əlbətdə, funksiyanın ən böyük və ən açıq problemi inanılmaz dərəcədə təkrarlanmasıdır. Bunu xeyli dərəcədə sadələşdirə bilərsiniz:

Ancaq bu hələ çirkin bir funksiyadır. A daha yaxşı həll etmək müvafiq ilə, kart sinif müvafiq kostyum və rütbə dərsləri. Sonra yalnız belə bir şey edə bilərsiniz:

Fərqli kostyum və ya rütbəli və ya zarafatçılar və ya kozurların əlavə olunduğu fərqli bir oyun növü üçün bu serialları dəyişdirməlisiniz və / və ya xüsusi üçün üçüncü bir sıra əlavə etməlisiniz.

Müvafiq növlər hazırladığınız zaman təmiz və sadə C ++ kodu belə olur. Tipləri düzəldin və qalan hər şey sehrli şəkildə yerinə düşər.

Yenə də lazımsız kod.

Bu funksiyanın hazırda necə yazıldığı kimi, yalnız iki oyun əldə edirsiniz və sonra proqram çıxır. Bunun səbəbi, oyunu yenidən oynayan eyni funksiyanın ortasına "yenidən oynamaq" seçimini yerləşdirmisiniz.

“Yenidən oynamaq” anlayışını nəzərdən keçirmədən əvvəl deyərdim ki, əvvəlcə “oynamaq” ı düşünməlisiniz. Bir "oynamaq" funksiyanız varsa, "yenidən oynamaq" funksiyası sadəcə:

Və ya daha da yaxşı, bütün bu sorğu məntiqini öz funksiyasına ayırın:

"Oynat" funksiyası ayrıldıqda, bütün həqiqi oyun məntiqi ora gedə bilər.

Heç vaxt, heç vaxt C ++ dilində std :: exit () istifadə etməyin. Bu C funksiyasıdır; C ++ dilində düzgün işləmir.

Bu, göyərtədən bir kart çəkmək üçün olduqca özünəməxsus bir yoldur. Hər şeydən əvvəl, bir kartı göyərtənin ortasından təsadüfi bir şəkildə çəkmək mənasızdır. İnsanlar real həyatda kart oynamaq belə deyil. Oyunun əvvəlində göyərtəni bir dəfə qarışdırırlar və sonra yalnız göyərtənin yuxarı hissəsindən çəkirlər.

Ancaq sonra daha da özünəməxsus bir şey edirsən: kartı göyərtənin ortasından kopyalayırsan ... sonra göyərtənin üst hissəsindəki kartı ilk kartın üstünə köçürürsən ... sonra kartın üst hissəsindəki kartı çıxar. Yalnız göyərtədən bir kart çəkmək üçün çox gimnastika var. Göyərtə qarışdırılsaydı, bütün bunlar lazım olmayacaqdı.

Əlini burada konstant istinad kimi götürə bilərsiniz, çünki onu dəyişdirmirsiniz. Həm də bunun Dilerə üzv olmasında məntiqi görmürəm. Bu ümumiyyətlə faydalı bir funksiyadır, niyə onu pulsuz bir funksiyaya çevirməyək?

Hər halda, bir səhviniz var. i bir int… lakin hand.size () bir növ imzasız bir tam olan :: size_type vektorudur. Köhnəlmiş intervalları olan döngələrdən istifadə etməyin ... amma həqiqətən ehtiyacınız varsa, növləri düzəldin. Bu loop olmalıdır:

Ancaq yenə də bu cür şeylər üçün köhnə məktəbi ilmələr üçün istifadə etməyin. Bir sıra istifadə edin:

və ya bir alqoritm:

Kartlar özlərini necə çap edəcəklərini bilsəydilər, daha yaxşı olardı. Yenə də növləri düzəldin və qalan hər şey işləyir. Kartlar özlərini necə yazdıracağını bilsəydilər, bu funksiya sadəcə ola bilər:

Əllər üçün bir sinifiniz olsaydı daha yaxşı olardı (bu da göyərtə sinfini təkrar istifadə edə bilər), çünki o zaman bu funksiyaya heç ehtiyacınız olmazdı. Yalnız std :: cout

Burada etdiyiniz şey ağıllıdır, amma necə etdiyinizlə bağlı bir çox problem var.

Birincisi, int səhv tipdir. Bir vektoru indeksləşdirirsinizsə, istifadə etmək üçün düzgün növ vektorun ölçüsü_ tipidir. vector 's size_type imzasız olacaq (hə, bəli, yaxşı deyil, amma insanlar bunun ortaya çıxardığı problemləri anlamamışdan çox əvvəl verilmiş bir qərar idi) və eyni zamanda int-dən çox daha böyük ola bilər. Xalis nəticə budur ki, card.size () - 1 nəticəsini int olaraq sıxmağa çalışarsanız, nəticədə təyin olunmamış davranışı tetikleyebilirsiniz. Çox pis.

Buradakı düzəliş o qədər əhəmiyyətsizdir ki, demək olar ki, gülmək olar. Yalnız int deməyin. Yalnız avtomatik istifadə edin (və ya heç bir şey yoxdur):

Bu funksiyanın növbəti böyük problemi, hər dəfə bir sıra kartlara təsadüfi bir indeks istəməyinizdə yeni bir təsadüfi say generatoru qurmağınızdır. Bu heç bir məna daşımır və bu olduqca dərəcədə israfdır. Təsadüfi say generatorunu funksiyaya keçirtmək daha yaxşı olar:

Ancaq bu hələ də böyük bir funksiya deyil, çünki kartlar dəstinin boş olduğu vəziyyəti nəzərə almır.

Göyərtəni bir dəfə qarışdırsanız və sonra göyərtənin üstündən götürsəniz, bütün bu problemlər buxarlanacaq.