Игра Snake в C ++

Это моя версия игры Snake, написанная на C ++. Как его можно улучшить и какие общие рекомендации будут полезны для будущих проектов?

#include <iostream>
#include <conio.h>

void run();
void printMap();
void initMap();
void move(int dx, int dy);
void update();
void changeDirection(char key);
void clearScreen();
void generateFood();

char getMapValue(int value);

// Map dimensions
const int mapwidth = 20;
const int mapheight = 20;

const int size = mapwidth * mapheight;

// The tile values for the map
int map[size];

// Snake head details
int headxpos;
int headypos;
int direction;

// Amount of food the snake has (How long the body is)
int food = 3;

// Determine if game is running
bool running;

int main()
{
    run();
    return 0;
}

// Main game function
void run()
{
    // Initialize the map
    initMap();
    running = true;
    while (running) {
        // If a key is pressed
        if (kbhit()) {
            // Change to direction determined by key pressed
            changeDirection(getch());
        }
        // Upate the map
        update();

        // Clear the screen
        clearScreen();

        // Print the map
        printMap();

        // wait 0.5 seconds
        _sleep(500);
    }

    // Print out game over text
    std::cout << "\t\t!!!Game over!" << std::endl << "\t\tYour score is: " << food;

    // Stop console from closing instantly
    std::cin.ignore();
}

// Changes snake direction from input
void changeDirection(char key) {
    /*
      W
    A + D
      S

      1
    4 + 2
      3
    */
    switch (key) {
    case 'w':
        if (direction != 2) direction = 0;
        break;
    case 'd':
        if (direction != 3) direction = 1;
        break;
    case 's':
        if (direction != 4) direction = 2;
        break;
    case 'a':
        if (direction != 5) direction = 3;
        break;
    }
}

// Moves snake head to new location
void move(int dx, int dy) {
    // determine new head position
    int newx = headxpos + dx;
    int newy = headypos + dy;

    // Check if there is food at location
    if (map[newx + newy * mapwidth] == -2) {
        // Increase food value (body length)
        food++;

        // Generate new food on map
        generateFood();
    }

    // Check location is free
    else if (map[newx + newy * mapwidth] != 0) {
        running = false;
    }

    // Move head to new location
    headxpos = newx;
    headypos = newy;
    map[headxpos + headypos * mapwidth] = food + 1;

}

// Clears screen
void clearScreen() {
    // Clear the screen
    system("cls");
}

// Generates new food on map
void generateFood() {
    int x = 0;
    int y = 0;
    do {
        // Generate random x and y values within the map
        x = rand() % (mapwidth - 2) + 1;
        y = rand() % (mapheight - 2) + 1;

        // If location is not free try again
    } while (map[x + y * mapwidth] != 0);

    // Place new food
    map[x + y * mapwidth] = -2;
}

// Updates the map
void update() {
    // Move in direction indicated
    switch (direction) {
    case 0: move(-1, 0);
        break;
    case 1: move(0, 1);
        break;
    case 2: move(1, 0);
        break;
    case 3: move(0, -1);
        break;
    }

    // Reduce snake values on map by 1
    for (int i = 0; i < size; i++) {
        if (map[i] > 0) map[i]--;
    }
}

// Initializes map
void initMap()
{
    // Places the initual head location in middle of map
    headxpos = mapwidth / 2;
    headypos = mapheight / 2;
    map[headxpos + headypos * mapwidth] = 1;

    // Places top and bottom walls 
    for (int x = 0; x < mapwidth; ++x) {
        map[x] = -1;
        map[x + (mapheight - 1) * mapwidth] = -1;
    }

    // Places left and right walls
    for (int y = 0; y < mapheight; y++) {
        map[0 + y * mapwidth] = -1;
        map[(mapwidth - 1) + y * mapwidth] = -1;
    }

    // Generates first food
    generateFood();
}

// Prints the map to console
void printMap()
{
    for (int x = 0; x < mapwidth; ++x) {
        for (int y = 0; y < mapheight; ++y) {
            // Prints the value at current x,y location
            std::cout << getMapValue(map[x + y * mapwidth]);
        }
        // Ends the line for next x value
        std::cout << std::endl;
    }
}

// Returns graphical character for display from map value
char getMapValue(int value)
{
    // Returns a part of snake body
    if (value > 0) return 'o';

    switch (value) {
        // Return wall
    case -1: return 'X';
        // Return food
    case -2: return 'O';
    }
}
36 голосов | спросил Funky 13 +04002014-10-13T12:59:22+04:00312014bEurope/MoscowMon, 13 Oct 2014 12:59:22 +0400 2014, 12:59:22

2 ответа


30

Если вы не знаете, как долго будет проходить игровой цикл на каждом компьютере, что делает ваш sleep константой, как правило, плохой практикой. Если вы знаете, что хотите 2fps, хороший способ сохранить его в очереди - это получить время в начале игрового цикла, а затем, в конце, узнать разницу и использовать это, чтобы рассчитать время, необходимое для сна чтобы сделать шаг одинаковым. Например, если цикл занимает 0,1 с, а вы хотите 2 кадра в секунду, то поставьте сон на 0,4 с.

Кроме этого, я бы сказал, что вам нужно иметь другую переменную рядом с food, которая является snakeLength или что-то еще. Я не знаю, вы распечатываете счет на экране, но если вы отслеживаете счет, я бы предположил, что вы хотите, чтобы он начинался с 0, а не 3, и еще 1 int isn Это большая сделка, когда вы получаете лучшую читаемость.

Возможно, подумайте о том, чтобы сделать direction перечисление, с UP, DOWN, LEFT и RIGHT, потому что сейчас это немного сложно, и вам не нужно будет слишком сильно менять логику, как перечислены int s с некоторыми дополнительными материалами, поэтому вы можете легко сравнивать то, как вы сейчас это делаете. Сказав это, я не уверен, что я следую тому, что относятся к вашим значениям direction, поскольку я не вижу нигде, что для direction установлено значение 5, так что проверка кажется нет необходимости.

В вашей функции generateFood вы напрямую обращаетесь к map, где вы создали функцию, которая выполняет это задание именно в getMapValue, поэтому вы можете подумать об использовании этого, так как в какой-то момент в будущем вы можете сделать его классом Map, а затем вы столкнетесь с ошибками при доступе к частным переменным (я бы надеялся!) .

Кроме этого, все выглядит довольно хорошо, поэтому я собираюсь начать nit-picking: P. Я просто предлагаю небольшие вещи, например, алфавитную последовательность ваших #include и прототипов функций. Это не такая уж большая сделка, так как у вас есть 2, но что-то иметь в виду. Кроме того, ваш clearScreen() и printMap() очень похож на Draw(), поэтому вы могли бы обернуть их как в этой функции и просто вызовите init, update, draw и cleanup (когда вы делаете объект загрузка и использование указателей и многое другое), поскольку вы, кажется, почти следуете шаблону игрового цикла ( как в сторону, если вы планируете делать больше игр, читайте эту целую книгу, это красиво), и чтение этой статьи лучше объясняет мою мысль о sleep.

ответил Yann 13 +04002014-10-13T14:13:18+04:00312014bEurope/MoscowMon, 13 Oct 2014 14:13:18 +0400 2014, 14:13:18
21

Этот обзор будет в основном касаться улучшения стиля кода и общего качества кода.

OOP:

Первое соображение заключается в том, что для программы на C ++ мы ожидаем увидеть некоторое объектно-ориентированное программирование - ООП. Ваша программа - это в основном структурированное программирование, которое намного больше похоже на C, чем на C ++.

Вы должны начать с реорганизации кода в несколько классов. Некоторые классы, такие как SnakeGame, Board /Map и Food приходят на ум.

Глобальные переменные:

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

Но предположим, что вы хотите придерживаться глобалов. Если это так, вы все равно должны попытаться свести к минимуму объем. Если переменные не нужны вне файла, в котором они были объявлены, вы должны обернуть их внутри неназванного пространства имен, чтобы сделать файл переменных в скобках. Вот так:

namespace 
{
    // The tile values for the map
    int map[size];

    // Snake head details
    int headxpos;
    int headypos;
    int direction;

    ...
} // namespace

Теперь эти переменные не могут быть доступны за пределами файла, где они были объявлены. Это уменьшает вероятность нежелательных изменений состояния другими модулями и упрощает отладку.

Кроме того, желательно всегда инициализировать глобальные переменные для некоторого стандартного безопасного значения. Эти переменные:

int headxpos;
int headypos;
int direction;
bool running;

Следует инициализировать что-то.

Именование лакомых кусочков:

Предпочтительным соглашением об именах для констант, например mapwidth и mapheight, является ALL_UPPERCASE. Это помогает читаемости, явно дифференцируя от изменяемых переменных к константам времени компиляции.

const int MAP_WIDTH      = 20;
const int MAP_HEIGHT     = 20;
const int TOTAL_MAP_SIZE = MAP_WIDTH * MAP_HEIGHT;

Кроме того, TOTAL_MAP_SIZE более описателен, чем просто size.

Разное:

Вы можете избежать прототипов функций, просто разместив main() в конце файла.

getMapValue() можно упростить, заменив if на случай по умолчанию в коммутаторе:

char getMapValue(int value)
{
    switch (value) {
    case -1 : return 'X'; // Return wall
    case -2 : return 'O'; // Return food
    default : return 'o'; // Returns a part of snake body
    }
}

Портативность:

conio.h, к сожалению, только для Windows, поэтому этот код не будет компилироваться на других ОС. Я не знаю простой замены kbhit() и getch(), но _sleep() может быть заменен на std::this_thread::sleep_for() в C ++ 11.

system("cls") также не является частью стандарта C ++ и поэтому не переносится.

ответил glampert 13 +04002014-10-13T18:04:57+04:00312014bEurope/MoscowMon, 13 Oct 2014 18:04:57 +0400 2014, 18:04:57

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132