2

I've begun into C++ from a heavy C background. This is my first program, which I'm using as a learning experience. A simple Snake Game. Everything runs smoothly, the only issue is that no matter what I do, after the players score reaches 4, the next bit of food is ALWAYS spawned inside of the wall, in the bottom left corner (1 row up from the bottom.) Running into it = Game Over.

This is running on a Linux server. I've tried messing with the border parameters, but I'm having a hard time figuring out what to change, because everything looks fine to my eyes. (Clearly something is not.) I'm pasting the entire program below. It's quite short. I just need one of you guru's to read through/run the program and take me to school on something that's probably a simple fix. Pardon the excessive comments. I use these to teach (I have a following on pastebin as odd as that is to say)

/* 
  Snake Game - Tragedy
  My First Program In C++
  I'm Using This Much As A Learning Experience For Myself
  And Would Like To Help Those Reading The Code For This Goofy Game
  Understand C++ A Bit Better Too
  Therefore I'm Trying To Explain As Much As Possible In Real Time
*/
#include <iostream> //Standard
#include <stdlib.h> //Standard
#include <unistd.h> //For POSIX Access
#include <sys/ioctl.h> //For Display Window, TTY Window (Console Window) 
#include <termios.h> //For Line Buffering - See Below
#include <stdio.h> //Old Friend

#define CLRSCR "\e[1;1H\e[2J" //Command To Clear Terminal Screen - Change Accordingly

using namespace std; /* 
                    A NameSpace Is Used As Additional Information 
                    To Differentiate Between Similar Functions/Variables
                    That Have The Same Name In Different Libraries
                    Using 'namespace' You Can Define The Context
                    In Which Names Are Defined

                    Withoug Using The STD NameSpace, The Computer Will Try
                    To Call cout Or cin As If It Weren't Defined In A NameSpace
                    Trying To Call Something That Doesn't Exist = Error
                    So, Without Using namespace std; When You Write For Example:
                    'cout << value;' You'd Have To Write 'std::cout << value;''

                  */
//Create Boundaries
const int width = 50;
const int height = 25;
const char block = 'o';

void ClearScreen(void)
{
    cout << CLRSCR;
}

//Global Arrays For Data Records
int background[height][width]; // Background
int snake[50][2];              // Max Snake Length
int food[2] = {0,0};             // Snake Food
int score = 0;                 // Score
int snakelen = 3;              // Snake Starting Length
int snakespeedx = 1;           // Horizontal Speed
int snakespeedy = 1;           // Vertical Speed
int lap = 200;                   // Waiting Time Betweeen Frames


//Declaring Global Temporary Variables To Save Memory
int px, py, nx, ny; //Postions
char k;
int h, w;
int x, y;
int movementx = snakespeedx;      //Snake Movement
int movementy = 0;                //Snake Movement

//Check For Keyboard Press 
/*
  Reference Link:
    https://www.quora.com/With-which-function-can-I-replace-kbhit-in-C++-because-the-header-conio-h-doesnt-exist-in-linux

  Ubuntu Users:
    sudo apt-get install libncurses5-dev libncursesw5-dev

  Life Saver:
    http://www.flipcode.com/archives/_kbhit_for_Linux.shtml
*/
int bytesWaiting, i;
int _kbhit()
{
    static const int STDIN = 0;
    static bool initialized = false; //The Boolean Data Type Is Used To Declare A Variable Whose Value Will Be Set As True (1) Or False (0)

    if (! initialized)
    {
        //Use Termios To Turn Off Line Buffering
        termios term;
        tcgetattr(STDIN, &term);
        term.c_lflag &= ~ICANON;
        tcsetattr(STDIN, TCSANOW, &term);
        setbuf(stdin, NULL);
        initialized = true;
    }
    ioctl(STDIN, FIONREAD, &bytesWaiting);
    return bytesWaiting;
}

//Initialise background borders Onto Array
void initialise_background(void)
{
    //int i;
    // Insert Top Border
    for(i=0; i<width; i++)
    {
        background[0][i]=1;
    }
    //Insert Left Border
    for(i=0; i<height; i++)
    {
        background[i][0]=1;
    }
    //Insert Right Border
    for(i=0; i<height; i++)
    {
        background[i][width-1]=1;
    }
    //Insert Bottom Border
    for(i=0; i<width; i++)
    {
        background[height-1][i]=1;
    }
}

//Initialise Snake Coordinates
void initialise_snake(void)
{
    snake[0][0]=3; //Coordinates X
    snake[0][1]=3; //Coordinates Y

    snake[1][0]=3+1; //Coordinates X
    snake[1][1]=3; //Coordinates Y

    snake[2][0]=3+2; //Coordinates X
    snake[2][1]=3; //Coordinates Y

    snake[3][0]=3+3; //Coordinates X
    snake[3][1]=3; //Coordinates Y

    snake[4][0]=3+4; //Coordinates X
    snake[4][1]=3; //Coordinates Y
}

//Update Snake
void update_snake_coordination(void)
{
    //int px,py,nx, ny;
    px = snake[0][0];
    py = snake[0][1];
    snake[0][0] = px + movementx;
    snake[0][1] = py + movementy;
    nx = snake[0][0];
    ny = snake[0][1];

    for(i=1; i<snakelen; i++)
    {
        nx = snake[i][0];
        ny = snake[i][1];
        snake[i][0] = px;
        snake[i][1] = py;
        px = nx;
        py = ny;
    }
}

//Install Snake Coordinates Into Background Array = ( 1 To Draw And 0 To Erase)
void draw_snake_in_background(const int rev)
{
    //int x, y;
    for(i = 0; i<snakelen; i++)
    {
        x = snake[i][0];
        y = snake[i][1];
        if((x!=0)&&(y!=0))
        {
            background[y][x] = rev;
        }
    }
}

//Print Array Frame
void print_array_frame(void)
{
    for(h=0; h<height; h++)
    {
        for(w=0; w<width; w++)
        {
            i=background[h][w];
            if(i==1)
            {
                cout << block;
            }
            else if (i == 2)
            {
                cout << "+";
            }
            else
            {
                cout << " ";
            }
        }
        cout << endl;
    }
}

//Update Loop
void mainloop(void)
{
    ClearScreen();
    draw_snake_in_background(1); // Install Snake
    print_array_frame();         // Print Frame
    draw_snake_in_background(0); // Uninstall Snake
}

//Waiting Function
void sleepcp(int milliseconds) // Cross-Platform Sleep Function
{
    clock_t time_end;
    time_end = clock() + milliseconds * CLOCKS_PER_SEC/1000;
    while (clock() < time_end)
    {
        //
    }
}

//Reaction To Keyboard Press
void reaction_on_keyboard(const char k)
{
    if(k=='d'||k=='6')
    {
        //Right Turn
        movementx = snakespeedx;
        movementy = 0;
    }
    else if(k=='a'||k=='4')
    {
        //Left Turn
        movementx = -snakespeedx;
        movementy = 0;
    }
    else if(k=='w'||k=='8')
    {
        //Turn Up
        movementx = 0;
        movementy = -snakespeedy;
    }
    else if(k=='s'||k=='2')
    {
        //Turn Down
        movementx = 0;
        movementy = snakespeedy;
    }
    else if(k=='q'||k=='z'||k=='c')
    {
        cout << "[+] Exit Safely [+]"<<endl;
        exit(0);
    }
}

//Create Snake Food
void cook_food(void)
{
    if (food[0]==0)
    {
        x = rand() % width + 1;
        y = rand() % height + 1;
        food[0] = x;
        food[1] = y;
        background[y][x] = 2;
    }
}

//Check Snake & Food Status
void capture_food(void)
{
    x = food[0];
    y = food[1];

    if ((x==snake[0][0])&&(y==snake[0][1]))
    {
        background[y][x] = 0;
        food[0] = 0;
        score ++;
        snakelen ++;
        cook_food();
    }
}

//Check Snake is Not Touching Boundary
void check_over_lapping(void)
{
    //int px,py;
    px = snake[0][0];
    py = snake[0][1];

    if((px==0)||(px==(width-1))||(py==0)||(py==(height-1)))
    {
        cout << "[+]        Game Over           [+]" << endl;
        exit(0);
    }
}

//Loop
void loop(void)
{
    int frame = 0;
    x = 0;
    y = 0;

    while(x<500)
    {
        sleepcp(lap);
        if(_kbhit())   //If Keyboard Pressed
        {
            cin >> k; //Character
            reaction_on_keyboard(k);
        }
        mainloop();                 //RUn Main Loop FUnction
        update_snake_coordination();//Update Snake Coordinates
        check_over_lapping();       //Check Snake Status
        cook_food();                //Make Sure Food is Available
        capture_food();             //Snake Eaten Food?
        cout << "[ Frame : " << frame << "  | Score  : " << score << " ] "<< endl; //Print Status
        frame ++;
    }
}

//Main Trigger Function
main()
{
    initialise_background(); //Install All Variables
    initialise_snake();      //Install Snake data
    loop();                  //Run Update Loop
}

This is what happens when running the game:

oooooooooooooooooooooooooooooooooooooooooooooooooo
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                                                o
o                       ooooooo                  o
o                                                o
o                                                o
o                                                o
+<---Places Here Every Time                      o
oooooooooooooooooooooooooooooooooooooooooooooooooo
[ Frame : 169  | Score  : 4 ]

Any Help or Input is greatly appreciated!

Prophetic
  • 21
  • 3
  • 1
    Take my word for it, whether you're coming in from C or not, a game, even snake, is not how you want to start learning C++. In fact coming in from C can lend a sense of security that makes you miss C++ things, like [`std::this_thread::sleep_until`](https://en.cppreference.com/w/cpp/thread/sleep_until), that you might have picked up if you'd worked your way through a [book on c++](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). C++ is one crazy complicated language, and trying to learn it off a basis in C and then trial and error will result in a lot of error. – user4581301 Aug 29 '19 at 02:41
  • Thanks for your input, Truth is I wrote this in C and am slowly converting it to work with C++ To make myself learn (some of) it. I will be updating it as I progress :) – Prophetic Aug 29 '19 at 05:48

1 Answers1

3

Your background array is height * width. When you place food into the array, you place it at rand() % width + 1 and rand() % height + 1, which have a range of 1 to width and 1 to height respectively. If you generate food at an x coordinate of width or a y coordinate of height, you will read outside the bounds of your background array. What is happening is that the seed your program is initialised with is generating food at an x position of width and because of the way memory is laid out that is the same array location as background[y + 1][0].

You probably want to change cook_food as follows:

void cook_food(void)
{
    if (food[0]==0)
    {
        x = rand() % (width - 1) + 1;
        y = rand() % (height - 1) + 1;
        food[0] = x;
        food[1] = y;
        background[y][x] = 2;
    }
}

I would note that this isn't really a C++ program, no matter what you're compiling it with. Your are using a very C style, storing data in a C way, and calling C standard library functions. You may want to read the isocpp C++ FAQ, which has some pointers to resources for people learning C++.

A C++ implementation would probably want to use the std::uniform_int_distribution class in the standard library, which makes it much clearer what your minimum and maximum values are for your food X and Y coordinates. You would also have 'Food' and 'Snake' objects that tracked their X and Y coordinates, rather than storing those values in arrays that you use directly.

EDIT: You've been asking some questions in the comments about collision detection for the snake. I believe this method will detect snake-on-snake collision given the code above:

bool is_snake_touching_itself() {
    for (std::size_t i = 1; i < snakelen; ++i) {
        if (snake[0][0] == snake[i][0] && snake[0][1] == snake[i][1]) {
            return true;
        }
    }

    return false;
}
James Picone
  • 1,509
  • 9
  • 18
  • Something simple, that my tired eyes had overlooked. Thank you for your input. You're right to say that I may be stuck in old ways. I'm going to be reworking this program as I learn, implementing more ++. – Prophetic Aug 29 '19 at 03:01
  • Do you have any pointers on how to implement an "If Snake Touches Itself" Func? I've tried something like this void CheckSelf(void) { px = snake[i][0]; py = snake[i][1]; if((nx == px) || (ny == py)) //If Any Part Of Snake Touches itself { cout << "[+] Thanks For Playing! [+]" << endl; exit(0); However, that of course reads all positions of snake, not just the first position. Resulting in a Game Over simply for having a Snake that's bigger than 1. – Prophetic Aug 29 '19 at 05:18
  • For the snake to intersect itself its head must have the same coordinates as a non-head segment of the body. So you need to loop through the snake array, skipping the first element, comparing coordinates to the head - if any of them match, then you've collided. – James Picone Aug 29 '19 at 05:29
  • So it's been 3 hours of me trying to implement said loop. I feel like I myself, am the loop.. Any way for me to contact you privately? – Prophetic Aug 29 '19 at 09:03
  • @Prophetic I'd prefer not to act as a coach, sorry. I've added some code to my answer that I believe will detect what you're looking for. Good luck! – James Picone Aug 30 '19 at 03:46