Змея игра для консоли Windows, избегая мерцания от системы("ЦБС")


Я только что завершил свою реализацию классического змея, используя то, что я считаю уникальным методикам. Вместо того, карту быть очищены снова и снова, используя system("cls"), что создает мерзкое мерцание эффект, что глаза режет, я выбрал, чтобы упасть обратно на gotoxy() функции, которые позволили бы напечатать объект, куда я хочу в выходной. Программа работает безупречно, но я открыт для любых улучшений, которые можно сделать, чтобы сделать свою игру более эффективной/программа Короче. У вас есть какие-либо предложения или мнения?

#include <iostream>
#include <conio.h>
#include <vector>
#include <windows.h>
#include <cstdlib>
#include <ctime>
using namespace std;

struct SnakeHead{
    int length;
    int x;
    int y;
    char head;
    char movingDirection;
    int score;
};

struct food {
    int rowPosition;
    int columnPosition;
    char symbol;
};
//controls the flow of the game, and when it ends
bool gameOver = true; 
//these variables will control how wide and how long the board is. All you have to do change these variables, and the board size 
//in the output will adjust accordingly
const int NUM_COLUMNS = 40, NUM_ROWS = 20;
int prevSnakeSegX, prevSnakeSegY;//these variables will hold the previous x and y position of each Snake segment
void printBoard(SnakeHead &Snake, food &foodItem);
void placeFood(food &foodItem);
void playGame(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily);
void drawLegend(const SnakeHead &Snake, const food &FoodItem);
void movement(SnakeHead &Snake);
void readMovement(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily);
void XYCoordinatesBox();
void gotoxy(int x, int y);
void setData(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily);
bool selfCollision(SnakeHead &snake, vector<int> &tailx, vector<int> &taily);
void createSnakeBody(SnakeHead &Snake, vector<int> &tailx, vector<int> &taily);

int main()
{
    food foodItem;
    SnakeHead Snake;
    vector<int> tailx(20), taily(20);//these arrays will hold the x and y position of each snake segement, and will grow
    //accordingly each time a food object is eaten
    playGame(Snake, foodItem, tailx, taily);

    system("pause");
    return 0;
}

void playGame(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily) {
    setData(Snake, foodItem, tailx, taily);
    printBoard(Snake, foodItem);
    placeFood(foodItem);
    while (gameOver) {
        movement(Snake);//read the keyboard input the user entered at the console
        readMovement(Snake, foodItem, tailx, taily);//analyze which key was pressed, and act accordingly
        Sleep(60);//wait fifty milliseconds before going to the next iteration of the loop
    }
}

void setData(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily) {
    Snake.score = 0;
    Snake.length = 1;
    Snake.x = NUM_COLUMNS/2, Snake.y = NUM_ROWS/2;
    tailx[0] = Snake.x, taily[0] = Snake.y;
    Snake.head = 'o';
    foodItem.symbol = 'f';
}

//print the borders of the game that will cause a game over if the snake strikes it
void printBoard(SnakeHead &Snake, food &foodItem)
{
    for (int i = 0; i < NUM_ROWS; i++) {
        if (i > 0 and i < NUM_ROWS - 1) {//creates the borders on the left and right
            cout << "*";
            for (int k = 1; k < NUM_COLUMNS - 1; k++)
                cout << " ";
            cout << "*\n";
            continue;
        }
        for (int j = 0; j < NUM_COLUMNS; j++)//creates top most and bottom most borders
            cout << "*";
        cout << endl;
    }
    drawLegend(Snake, foodItem);
    XYCoordinatesBox();
}

//draw the legend over on the side to briefly guide the user on how the game is played
void drawLegend(const SnakeHead &Snake, const food &FoodItem)
{
    const int BOX_WIDTH = 29, BOX_LENGTH = 19; //these variable determine how big the "legend" will be in the output

    gotoxy(NUM_COLUMNS + 15, 2); cout << "LEGEND:";

    gotoxy(NUM_COLUMNS, 1);
    for (int i = 0; i < BOX_WIDTH; i++)
        cout << "-";

    gotoxy(NUM_COLUMNS, BOX_LENGTH);
    for (int k = 0; k < BOX_WIDTH; k++)
        cout << "-";

    gotoxy(NUM_COLUMNS + 5, 4); cout << Snake.head << " -> SnakeHead\n";
    gotoxy(NUM_COLUMNS + 5, 6); cout << FoodItem.symbol << " -> Food\n";
    gotoxy(NUM_COLUMNS + 5, 8); cout << "#  -> Border\n";
    gotoxy(NUM_COLUMNS + 5, 10); cout << "w  -> Move Up\n";
    gotoxy(NUM_COLUMNS + 5, 12); cout << "s  -> Move Down\n";
    gotoxy(NUM_COLUMNS + 5, 14); cout << "a  -> Move Left\n";
    gotoxy(NUM_COLUMNS + 5, 16); cout << "d  -> Move Right";
}

//create the box that will print out the x and y coordinates of the Snake head
void XYCoordinatesBox()
{
    gotoxy(NUM_COLUMNS, 20);
    for (int i = 1; i < 10; i++) {
        cout << "|\n";
        gotoxy(NUM_COLUMNS, 20 + i);
    }       
}

//function to check to see if the user has inputted one of the four valid keys (w s a d), and will assign the appropiate value
//to the snakes current direction
void movement(SnakeHead &SnakeHead)
{
    if (_kbhit()) {//check to see if a key is pressed. IF true, execute the block of code below
        char input = _getch();//save the key the user pressed

        //if w is pressed, and the snake is not moving down, set the direction to up!
        if (input == 'w' and SnakeHead.movingDirection != 's') SnakeHead.movingDirection  = 'w';
        //if s is pressed, and the snake is not moving up, set the direction to down!
        else if (input == 's' and SnakeHead.movingDirection != 'w') SnakeHead.movingDirection = 's';
        //if a is pressed, and the snake is not moving right, set the direction to left!
        else if (input == 'a' and SnakeHead.movingDirection != 'd') SnakeHead.movingDirection = 'a';
        //finally, if d is pressed, and the snake is not moving right, set the direction to left!
        else if (input == 'd' and SnakeHead.movingDirection != 'a') SnakeHead.movingDirection = 'd';
    }
}

/*
this function is where all the magic happens. It will accomplish the following tasks: 
1) print out the snake head, and the snake body
2)fill the tailx and taily arrays with the x and y positions of each snake segment of the snake body
3)Increment the x or y position of the snake head based on what key the user pressed
4)Determine whether or not the snake head hit a wall, its own body, or a food object*/
void readMovement(SnakeHead &Snake, food &foodItem, vector<int> &tailx, vector<int> &taily)
{
    //if the snake head has not hit a food object yet, go to position of the previous instance of the snake head. Otherwise go
    //to the previous instance of the very last snake segment of the snake body
    (Snake.length == 1) ? gotoxy(tailx[0], taily[0]) : gotoxy(prevSnakeSegX, prevSnakeSegY);
    cout << " ";//print out a blank to prevent the snake from leaving copies of itself from previous iteration of this function
    for (int i = 1; i < Snake.length; i++) { //print out the snake body
        gotoxy(tailx[i], taily[i]); cout << Snake.head;
    }
    gotoxy(Snake.x, Snake.y); cout << Snake.head;//print out the snake head

    createSnakeBody(Snake, tailx, taily);

    if (Snake.movingDirection == 'w') Snake.y--;
    else if (Snake.movingDirection == 's') Snake.y++;
    else if (Snake.movingDirection == 'a') Snake.x--;
    else if (Snake.movingDirection == 'd') Snake.x++;

    if ((Snake.x == 0 or Snake.x == NUM_COLUMNS -1) or (Snake.y == 0 or Snake.y == NUM_ROWS - 1) or selfCollision(Snake, tailx, taily)){
        gotoxy(30, 22); cout << "GAME OVER BITCH!!";
        gameOver = false;
    }else if (Snake.y == foodItem.rowPosition and Snake.x == foodItem.columnPosition) {
        placeFood(foodItem);
        Snake.score += 10;
        Snake.length += 3;
    }

    gotoxy(NUM_COLUMNS + 5, 20); 
    cout << "X: " << Snake.x << " Y: " << Snake.y;
    cout << "\nscore: " << Snake.score << endl;
    cout << "Snake Length: " << Snake.length << endl;
}

void placeFood(food &foodItem){
    srand(time(0));
    foodItem.rowPosition = rand() % 18 + 1, foodItem.columnPosition = rand() % (NUM_COLUMNS-2) + 1;

    gotoxy(foodItem.columnPosition, foodItem.rowPosition); cout << foodItem.symbol;
}

//assigns the x and y positions of each snake segment to the tailx and taily array
void createSnakeBody(SnakeHead &Snake, vector<int> &tailx, vector<int> &taily) {
    int prevHeadX = tailx[0];//save the previous x position of the Snake head
    int prevHeadY = taily[0];//save the previous y position of the Snake head
    tailx[0] = Snake.x;//update the current x position of the Snake head for the next iteration of this function
    taily[0] = Snake.y;//do the same for the y position
    for (int i = 1; i < Snake.length; i++) {
        prevSnakeSegX = tailx[i];//save the "ith" position of the tailx array
        prevSnakeSegY = taily[i];//save the "ith position of the taily array
        tailx[i] = prevHeadX;//give tailx a new value from the previous position of the snake head (will be shifted down to last position)
        taily[i] = prevHeadY;//do the same thing for the taily array
        prevHeadX = prevSnakeSegX;//assign a new value from the previous x position of a segment to the x pos of the snake head
        prevHeadY = prevSnakeSegY;//do the same for the previous y position of the y head
        tailx.push_back(int());//increase the size of the tailx array
        taily.push_back(int());//do the same from the taily array
    }
}

bool selfCollision(SnakeHead &snake, vector<int> &tailx, vector<int> &taily) {
    int len = snake.length;

    for (int i = 0; i < len; i++) 
        if (snake.x == tailx[i] and snake.y == taily[i] and snake.length > 1)
            return true;
    return false;
}

void gotoxy(int x, int y){
    COORD coord;

    coord.X = x;
    coord.Y = y;

    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}


1006
7
задан 19 февраля 2018 в 09:02 Источник Поделиться
Комментарии
1 ответ

Именования

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

struct SnakeHead{
//...
};

struct food {
//...
};

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

У вас есть подобные проблемы с именами переменных:

food foodItem;
SnakeHead Snake;

Как и раньше, быть последовательным с именами переменных. Даже с подсветкой синтаксиса здесь, кажется, что змея-это имя класса. Предпочитаю придерживаться верблюжьего здесь. (Также с вашей функцией XYCoordinates())

Общие Рекомендации


  • Как вы используете c++, ООП язык, вы могли бы хотеть рассмотреть разбивая код на кратные классы и объекты, такие, как класс SnakeGame что заботится о геймплее и обрабатывает весь игровой мир. Тогда вы сможете перемещать многие глобальные функции, которые вы используете в качестве функции-члены этого класса. Вы могли бы реализовать подобный класс для самой змеи.

  • <conio.h> нестандартное и не портативный, так что рассмотреть вопрос о переходе на PDCurses, который совместим с ncurses.

  • РЕ: system("pause");. Использования это не рекомендуется, и не портативный. Я бы заменил это что-то вроде std::cin.getили std::cin.ignore. (Более детально)

  • bool gameOver = true; если эта переменная должна выступать в качестве флага, пока игра не закончится, то вы должны использовать ее как таковую. Код while (gameOver) не имеет смысла сейчас, так как вы не обновите игру, пока она закончится.

  • Я бы заменил ваше использование endl С \nкак вы безосновательно промывки поток каждый раз при печати новой строки в противном случае.

  • Одна вещь, я заметил, что много в коде пары vectors для X и y координат. Я предлагаю вам создать структуру Point что содержит x и y координировать и создать вектор этих. (Вы можете даже использовать встроенный COORD)

3
ответ дан 20 февраля 2018 в 02:02 Источник Поделиться