-1

This is my code for snake. system("cls") is not efficient at all, the console flickers...

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

bool status = false, win = false;

struct Snake {
    int index_i;
    int index_j;
};

class Game {
private:
    enum eDir { UP, RIGHT, DOWN, LEFT };
    eDir direction;
    const int height = 25, width = 50, max_size = (height - 2)*(width - 2);
    int snake_size = 1, food_x, food_y, snake_x, snake_y, score, speed;
    char snake = '@', food = '*', frame = '#';
    Snake *snake_body = new Snake[max_size];
public:
    Game() {
        snake_x = height / 2;
        snake_y = width / 2;
        snake_body[0].index_i = snake_x;
        snake_body[0].index_j = snake_y;
        PutFood();
    }
    ~Game() {
        delete[] snake_body;
    }
    void DrawTable() {
        system("cls");
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                if (!i || i == height - 1 || !j || j == width - 1) {
                    cout << frame;
                }
                else if (i == food_x && j == food_y) {
                    cout << food;
                }
                else if (Check(i, j)) {
                    cout << snake;
                }
                else {
                    cout << " ";
                }
            }
            cout << endl;
        }
        cout << "Your current score is: " << score;
    }
    void Control() {
        if (_kbhit()) {
            switch (_getch()) {
            case 'w':
                direction = UP;
                break;
            case 'a':
                direction = LEFT;
                break;
            case 's':
                direction = DOWN;
                break;
            case 'd':
                direction = RIGHT;
                break;
            }
        }
    }
    void Process() {
        switch (direction) {
        case UP:
            snake_x--;
            Move();
            break;
        case LEFT:
            snake_y--;
            Move();
            break;
        case DOWN:
            snake_x++;
            Move();
            break;
        case RIGHT:
            snake_y++;
            Move();
            break;
        }
    }
    void Move() {
        /*for (int i = 0; i < snake_size; i++) {   tail collision logic (if you try to reverse your move, you die). Optional.
            if (snake_body[i].index_i == snake_x && snake_body[i].index_j == snake_y) {
                status = true;
                return;
            }
        }*/
        snake_body[snake_size].index_i = snake_x;
        snake_body[snake_size].index_j = snake_y;
        if (!snake_x || snake_x == height - 1 || !snake_y || snake_y == width - 1) { // collision logic
            status = true;
        }
        else if (snake_x == food_x && snake_y == food_y) {
            snake_size++;
            score++;
            if (snake_size == max_size) {
                win = true;
                return;
            }
            PutFood();
        }
        else {
            for (int index = 0; index < snake_size; index++) {
                snake_body[index].index_i = snake_body[index + 1].index_i;
                snake_body[index].index_j = snake_body[index + 1].index_j;
            }
            snake_body[snake_size].index_i = 0;
            snake_body[snake_size].index_j = 0;
        }
        Sleep(speed);
    }
    void PutFood() {
        srand(time(NULL));
        food_x = rand() % (height - 2) + 2;
        food_y = rand() % (width - 2) + 2;
    }
    bool Check(int i, int j) {
        for (int k = 0; k < snake_size; k++) {
            if (i == snake_body[k].index_i && j == snake_body[k].index_j) {
                return true;
            }
        }
        return false;
    }
    int getScore() {
        return score;
    }
    void setSpeed(int s) {
        speed = s;
    }
};

int main() {
    Game snake_game;
    char exit;
    string error = "Invalid choice, please choose 1-3";
    int speed, choice;
    cout << "Contol: WASD" << endl << "Set the difficulty level: " << endl << "1. Easy" << endl << "2. Normal" << endl << "3. Hard" << endl;
label:
    cin >> choice;
    try {
        if (choice < 1 || choice > 3) throw error;
    }
    catch (char *error) {
        cout << error << endl;
        goto label;
    }
    switch (choice) {
    case 1:
        speed = 250;
        break;
    case 2:
        speed = 75;
        break;
    case 3:
        speed = 0;
        break;
    }
    snake_game.setSpeed(speed);
    while (!status && !win) {
        snake_game.DrawTable();
        snake_game.Control();
        snake_game.Process();
    }
    if (status && !win) {
        system("cls");
        cout << "YOU LOST! Your score is: " << snake_game.getScore() << endl;
    }
    if (win) {
        system("cls");
        cout << "Congratulations! You won the game!" << endl << "Your score is: " << snake_game.getScore() << endl;
    }
    cin >> exit;
    return 0;
}
Asm .
  • 133
  • 2
  • 9
  • 2
    In standard C++, your options are very limited, since there's no notion of "console" in the language. `system("cls");` is actually runs an entire Windows program to clear the console. You could look into using a separate graphics library for rendering, but that might be very confusing if you're beginning C++. – alter_igel Nov 16 '18 at 21:46
  • Windows provides a variety of console handling functions - https://learn.microsoft.com/en-us/windows/console/console-functions –  Nov 16 '18 at 21:51
  • And to redraw the screen, you only need to erase the tail and add the new head. You don't need to redraw the whole thing. –  Nov 16 '18 at 21:59

5 Answers5

3

system("cls") is slow. Also, you don't need to refresh the whole screen since most of it doesn't change every frame. I see you have included windows.h so I am guessing you only need this to work on Windows. Therefore, I will suggest using the function SetConsoleCursorPosition from the Windows API.

Here is an example

   SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {10, 10});
   std::cout << ' ';

This code will change the cursor position to coordinates (10, 10) and output a space. You can do this for every "pixel" that you want to change, every frame.

  • 1
    +1. I'll ignore the portability concerns since this answer is using system-specific functions (with good reason), but `std::cout << ' ';` requires no explanation of what the 32 means. [In general avoid Magic Numbers.](https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad) – user4581301 Nov 16 '18 at 22:04
  • this will be very not efficient do `std::cout << (char)32;` for every place – RbMm Nov 16 '18 at 22:40
  • I have edited my answer to remove the magic number and unnecessary cast. – Hristijan Gjorshevski Nov 16 '18 at 22:45
  • this is nothing changed - every `std::cout << ' '` - is separate call to remote process - which is very heavy. do hundreds or thousands this calls (for every cursor position) + move cursor position also - remote and heave call - very not efficient. this is not solution at all – RbMm Nov 16 '18 at 22:51
  • 1
    Mixing calls to Windows' console API and C++' stream implementation is brittle. What's more, `std::cout` is usually buffered, so you will not see any updates to the screen until that buffer is flushed. That's probably not what you want in an interactive game. – IInspectable Nov 17 '18 at 17:07
2

On Unix systems, curses is the classic way to implement a text-based program like yours:

Using curses, programmers are able to write text-based applications without writing directly for any specific terminal type. The curses library on the executing system sends the correct control characters based on the terminal type. It provides an abstraction of one or more windows that maps onto the terminal screen. Each window is represented by a character matrix. The programmer sets up the desired appearance of each window, then tells the curses package to update the screen. The library determines a minimal set of changes that are needed to update the display and then executes these using the terminal's specific capabilities and control sequences. [Wikipedia]

There apparently is a Windows port being developed called PDCurses; you can see if it meets your needs.

Gnawme
  • 2,321
  • 1
  • 15
  • 21
0

system("cls") is actually runs an entire Windows program (cmd.exe) to clear the console. this is of course very not efficient. instead we need simply do the same, what cls command do inside cmd.exe. for clear screen we can use ScrollConsoleScreenBuffer - for replace content of console scree buffer to spaces

BOOL cls()
{
    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(hConsoleOutput, &csbi))
    {
        CHAR_INFO fi = { ' ', csbi.wAttributes };
        csbi.srWindow.Left = 0;
        csbi.srWindow.Top = 0;
        csbi.srWindow.Right = csbi.dwSize.X - 1;
        csbi.srWindow.Bottom = csbi.dwSize.Y - 1;
        return ScrollConsoleScreenBufferW(hConsoleOutput, &csbi.srWindow, 0, csbi.dwSize, &fi);
    }
    return FALSE;
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
0

The efficiency of system("cls") is too low. And here is the similar ways to clean screen:

    //First get the console handle and its info.
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(hConsoleOut, &csbiInfo);

    //Fill with ' ' in the whole console(number = X*Y).
    FillConsoleOutputCharacter(hConsoleOut, ' ', csbiInfo.dwSize.X * csbiInfo.dwSize.Y, home, &dummy);
    csbiInfo.dwCursorPosition.X = 0;
    csbiInfo.dwCursorPosition.Y = 0;

    //Set the Cursor Position to the Beginning.
    SetConsoleCursorPosition(hConsoleOut, csbiInfo.dwCursorPosition);
Drake Wu
  • 6,927
  • 1
  • 7
  • 30
-1

As you are using conio.h, you can use gotoxy(x, y) to go to the coordinate, which you want to delete and just perform a printf(" ") with a whitespace.

tangoal
  • 724
  • 1
  • 9
  • 28
  • `gotoxy` is from the Borland compiler, which hopefully nobody is using anymore. There is a way to emulate this functionality on windows mentioned [here](https://stackoverflow.com/questions/7955145/which-header-file-i-need-to-include-to-use-gotoxy-function). – alter_igel Nov 16 '18 at 21:48
  • @alterigel: yes, it is from Borland. However, as obviously it is available, it is an option. I don't see a reason why it shouldn't be used for a student project... – tangoal Nov 16 '18 at 21:50
  • I'd consider it as a last resort after using system calls and a console library targeting modern computer systems like curses failed me. – user4581301 Nov 16 '18 at 22:00