Военная карточная игра со случайной игрой

Я пытаюсь изучить C ++ с опытом работы на Python. Это моя первая программа помимо общей практики. Я хотел переделать военную игру. Я хочу получить отзывы об эквиваленте питонности C ++. Некоторые ошибки, которые, как мне кажется, я совершил, могут быть связаны с неправильным использованием функций std, ненужными преобразованиями типов и ненужным дублированием данных, но я не уверен, с чего начать.

Для объяснения кода я не инициализировал сами карты, я просто использовал диапазон от 1 до 52 для обозначения карт. Я просто подумал, что для распространения карт было бы забавно попробовать использовать случайный двоичный файл с длиной бита 52 и распределить карты из него. Я также сомневался в while(true) цикл, мне нужен был другой способ сделать это, но он работает так, как задумано.

#include <iostream>
#include <string>
#include <random>
#include <algorithm>
#include <set>
using namespace std;

int randNum(int min, int max) {
    random_device r;
    default_random_engine e1(r());
    uniform_int_distribution<int> uniform_dist(min, max);
    int mean = uniform_dist(e1);
    return mean;
}

string toBinary(int n) {
    string r;
    while(n != 0) {
        r = (n % 2 == 0 ? "0" : "1") + r;
        n /= 2;
    }
    return r;
}

string deckShuffleSeed() {
    string deck;
    // Decimals to make a binary w/ bit length of 26
    // /2 # of cards in deck, therefore: consolidate 2, so we can use ints
    deck = toBinary(randNum(33554432, 67108863)) +
            toBinary(randNum(33554432, 67108863));
    return deck;
}

int randSetChoice(const set<int> &s) {
    auto ItRandChoice = std::next(s.begin(), randNum(0, int(s.size())));
    return *ItRandChoice;
}

string war() {
    // hand initialization
    set<int> p1, p2;
    string deckDealingSeed = deckShuffleSeed();
    for (int i = 1; i <= 52; i++) {
        int convSeedIndex = deckDealingSeed[i] - '0';
        if (convSeedIndex == 1) {
            p2.insert(i);
        } else {
            p1.insert(i);
        }
    }
    // game loop
    while (!p1.empty() || !p2.empty()) {
        int recursiveWarCount = 0;
        set<int> cardsOnTable;
        while(true) { // recursive war loop
            if (p1.size() > 3 * recursiveWarCount && p2.size() > 3 * recursiveWarCount) {
                int p1RandChoice = randSetChoice(p1), p2RandChoice = randSetChoice(p2);
                // round loop
                if (p1RandChoice / 4 > p2RandChoice / 4) { // p1 wins
                    p1.insert(p2RandChoice);
                    p2.erase(p2RandChoice);
                    if (recursiveWarCount > 0) {
                        p1.insert(cardsOnTable.begin(), cardsOnTable.end());
                        for (int i = 1; i < recursiveWarCount * 3; i++) {
                            int randTableChoice = randSetChoice(p2);
                            p1.insert(randTableChoice);
                            p2.erase(randTableChoice);
                        }
                    }
                    break;
                } else if (p1RandChoice / 4 < p2RandChoice / 4) { // p2 wins
                    p2.insert(p1RandChoice);
                    p1.erase(p1RandChoice);
                    if (recursiveWarCount > 0) {
                        p2.insert(cardsOnTable.begin(), cardsOnTable.end());
                        for (int i = 1; i < recursiveWarCount * 3; i++) {
                            int randTableChoice = randSetChoice(p1);
                            p2.insert(randTableChoice);
                            p1.erase(randTableChoice);
                        }
                    }
                    break;
                } else {  // war
                    recursiveWarCount += 1;
                    cardsOnTable.insert(p1RandChoice);
                    cardsOnTable.insert(p2RandChoice);
                }
            } else {
                if (p1.size() > p2.size()) {
                    return "p1";
                } else {
                    return "p2";
                }
            }

        }
    }
    if (p1.empty()) {
        return "p2";
    } else if(p2.empty()) {
        return "p2";
    } else {
        return "Something is wrong!";
    }
}



int main() {
    cout << war() << endl;
    return 1;
}

2 ответа
2

Представление колод карт

То, как вы храните колоды карт и перемешиваете их, действительно очень странно. Есть ненужное преобразование (даже для Python) из чисел в строки и обратно. С использованием std::set колода неправильная, потому что она не сохраняет порядок карт в колоде. Вместо этого я бы использовал std::deque (не каламбур); быстро вставлять и извлекать карты с любого конца и сохранять их порядок. Чтобы создать колоду из 52 карт (и пусть они будут представлены, используя ints на данный момент), вы можете написать:

std::deque<int> deck(52);
std::iota(deck.begin(), deck.end(), 1);

Здесь я использовал std::iota() для инициализации значения каждой карты в колоде, хотя обычный цикл for тоже подойдет. Чтобы перетасовать карты, используйте std::shuffle():

std::random_device rd;
std::default_random_engine rng(rd());
std::shuffle(deck.begin(), deck.end(), rng);

Затем, чтобы разделить колоду поровну между двумя игроками (как указано в правилах Война), просто скопируйте каждую половину колоды, используя std::dequeконструктор, который принимает два итератора:

auto midpoint = std::advance(deck.begin(), deck.size() / 2);
std::deque<int> p1(deck.begin(), midpoint);
std::deque<int> p2(midpoint, deck.end());

Теперь вы можете просто взять карту из колоды так:

auto card = p1.front();
p1.pop_front();

А чтобы подтолкнуть карту к обратной стороне колоды, вы можете написать:

p1.push_back(card);

Обратите внимание, что нам больше не нужен генератор случайных чисел после первоначального перемешивания.

Вы также можете переместить несколько карточек за один раз, используя insert() а также erase(). Вместо того, чтобы отслеживать recursiveWarCount и перемещать карты только тогда, когда война окончена, подумайте о перемещении карт из колод обоих игроков в колоду на столе на каждом этапе войны.

Избегайте повторений

Код для игрока 1, выигравшего войну, почти идентичен коду для игрока 2, выигравшего войну, только с p1 а также p2 в обратном порядке. Когда вы видите, что в основном дублируете одни и те же строки кода, подумайте о написании функции.

Вы также можете подумать о создании функции, которая перемещает карту из одной колоды в другую, чтобы вы могли просто вызвать ее вместо того, чтобы явно брать карту из одной колоды и добавлять ее в другую.

Рассмотрите возможность создания новых типов или псевдонимов типов

Явно пишу std::deque<int> каждый раз объявляете, что колода карт не велика; он довольно длинный, вы должны запомнить правильный тип, а по типу неясно, представляет ли он колоду карт. Было бы здорово иметь лучшее название для колоды карт. Вы можете создать псевдоним для этого типа с помощью using ключевое слово:

using Card = int;
using Deck = std::deque<Card>;

И тогда вы можете использовать это так:

Deck p1, p2;

Другой способ – создать class Card а также class Deck которые представляют карты и колоды карт и имеют функции-члены, которые выполняют с ними типичные операции, например get_value() для Card, а также shuffle(), split(), draw() и так далее Deck. Это немного больше работы, но это может значительно упростить реализацию реальной логики игры War.

    В ответе Г. Слипена уже упоминалось, что существует std::shuffle функция, но я хотел прямо указать на это.

    Я рекомендую даже опытным разработчикам прочитать алгоритм заголовок один раз в год, просто чтобы напомнить себе, что в нем есть.

    Что касается изучения идиом и лучших практик C ++, я предлагаю посмотреть видео с крупных конференций по C ++ на YouTube. У CppCon был Вернуться к основам трек в 2020 году.


    Не надо записывать using namespace std;.

    Однако вы можете в файле CPP (не в файле H) или внутри функции поместить отдельные using std::string; и т. д. (см. SF.7.)


     if (p1.size() > p2.size()) {
       return "p1";
     } else {
       return "p2";
     }
    

    Вы можете просто написать: return p1.size() > p2.size() ? "p1" : "p2";

    Это лучше не просто короче, поскольку в нем заранее говорится, что вы что-то возвращаете … затем переходит к уровню детализации, в зависимости от того, что вы возвращаете, то или иное. Как вы написали, это универсальный if утверждение и требует дополнительных когнитивных затрат, чтобы выяснить, что обе ветви выполняют похожий вещи, и ты return не важно что.


    Я не знаю, почему вы делаете случайный выбор во время игры: я думал, что игра ведется, беря вашу следующую карту по порядку, а не случайную карту из тех, что у вас есть.


    int p1RandChoice = randSetChoice(p1), p2RandChoice = randSetChoice(p2);
    

    Я хотел бы отметить, что в C ++ хороший стиль – помещать одно объявление для каждого оператора, а не группировать их вот так.

    Вы также должны делать вещи const когда ты можешь.


    Я думаю, что вы используете set (неупорядоченная коллекция?) усложняет код. Но даже используя deque, вы можете улучшить ситуацию, определив функцию для раздачи карты, как видя значение следующей карты и удаляя это из коллекции – отдельные операции. (Это имеет смысл для больших сложных типов, копирование которых следует избегать.)

    // not shown:  definition for card_t
    using deck = std::deque<card_t>;
    
    card_t draw (deck& d)
    {
        const auto x = d.front();
        d.pop_front();
        return x;
    }
    

    • Я думаю, что причина, по которой OP делает случайный выбор, состоит в том, чтобы имитировать перетасованную колоду карт при использовании std::set что естественным образом сохраняет их в порядке.

      – Г. Сон

    • Небольшая несогласованность в вашем последнем фрагменте кода: card_t имеет _t суффикс, но deck нет.

      – Г. Сон

    • 1

      Хорошим введением в алгоритмы также является эта презентация CppCon от Джонатана Боккара: Карта мира алгоритмов C ++ STL

      – Г. Сон

    • @ G.Sliepen Я решил, что хочу использовать переменную с именем card здесь и там, но единственные экземпляры deck были руками игрока и, возможно, стопкой сброса, использованной для войн.

      – JDłuosz

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *