Created
November 8, 2022 08:40
-
-
Save martytyty2098/baf42f8ff0bfae517971448ee0cdae19 to your computer and use it in GitHub Desktop.
This is my Object Oriented snake game in C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <string> | |
#include <conio.h> | |
#include <vector> | |
#include <chrono> | |
#include <thread> | |
#include <ctime> | |
/* this is default terminal window size, you can change it if you want, | |
but if these variables are not equal to the actual size of terminal window, | |
this program will not work properly */ | |
const int screen_width = 120; | |
const int screen_length = 29; // this variable must be 1 less than the actual length of the terminal window | |
enum dir { left = -1, up = -screen_width, right = 1, down = screen_width }; | |
struct coordinate | |
{ | |
int x_coord; | |
int y_coord; | |
}; | |
// pauses the program for a while after each frame to make the game playable | |
inline void sleep(int time) | |
{ | |
std::this_thread::sleep_for(std::chrono::milliseconds(time)); | |
} | |
// storing all drawable items as Subclasses of this Superclass | |
class Drawable { | |
public: | |
virtual void draw() = 0; | |
}; | |
class Field { | |
public: | |
Field(); | |
void show(); | |
inline unsigned char& operator()(int x, int y); | |
const unsigned char background = ' '; | |
// storing the map as 1d character array | |
unsigned char field[screen_length * screen_width + 1]; | |
}map; | |
void Field::show() { | |
std::cout << field; | |
} | |
Field::Field() | |
{ | |
for (int i = 0; i < screen_length * screen_width; ++i) { | |
field[i] = background; | |
} | |
field[screen_length * screen_width] = '\0'; | |
} | |
// this function allows me to access a 1d array by x-y coordinates, as if it were 2d | |
inline unsigned char& Field::operator()(int x, int y) | |
{ | |
return *(&field[0] + screen_width * x + y); | |
} | |
class Worm : public Drawable { | |
public: | |
Worm(); | |
virtual void draw(); | |
void move(); | |
void get_direction(char user_tap); | |
bool check_collision(); | |
unsigned char sumbol; | |
int length; | |
private: | |
// storing body of the snake as a vector | |
std::vector<coordinate*> body; | |
dir direction; | |
int actual_length(); | |
}snake; | |
// checks if the snake's head hit its tail | |
bool Worm::check_collision() | |
{ | |
// if x-y coordinates of the snake's head are equal to any part of its body, this function returns true | |
for (int i = 1; i < body.size(); ++i) { | |
if (body[0]->x_coord == body[i]->x_coord && body[0]->y_coord == body[i]->y_coord) { | |
return true; | |
} | |
} | |
return false; | |
} | |
// basic wasd controls | |
void Worm::get_direction(char user_tap) | |
{ | |
switch (user_tap) { | |
case 'w': | |
if (direction != down) direction = up; | |
break; | |
case 'a': | |
if (direction != right) direction = left; | |
break; | |
case 's': | |
if (direction != up) direction = down; | |
break; | |
case 'd': | |
if (direction != left) direction = right; | |
break; | |
} | |
} | |
// counts how many parts of the snake's body are actually on the screen at the moment | |
int Worm::actual_length() | |
{ | |
int count = 0; | |
for (int i = 0; i < screen_length * screen_width; ++i) { | |
if (map.field[i] == sumbol) { | |
count++; | |
} | |
} | |
return count; | |
} | |
// creates a new snake's body part, wich will become its head in the next frame | |
void Worm::move() | |
{ | |
// location of new body part depends on the direction the snake is currently facing | |
coordinate* temp = new coordinate; | |
temp->x_coord = body[0]->x_coord; | |
temp->y_coord = body[0]->y_coord; | |
if (direction == up || direction == down) { | |
temp->x_coord = body[0]->x_coord + direction / screen_width; | |
} | |
if (direction == left || direction == right) { | |
temp->y_coord = body[0]->y_coord + direction; | |
} | |
// when the snake enters edge of the screen, it exits in the opposite | |
if (temp->x_coord < 0) temp->x_coord = screen_length - 1; | |
if (temp->x_coord >= screen_length) temp->x_coord = 0; | |
if (temp->y_coord < 0) temp->y_coord = screen_width - 1; | |
if (temp->y_coord >= screen_width) temp->y_coord = 0; | |
body.emplace(body.begin(), temp); | |
// if the snake has not eaten in this frame, removes the last part of its body | |
if (actual_length() >= length) { | |
body.pop_back(); | |
} | |
} | |
// draws all body parts of the snake on the screen | |
void Worm::draw() { | |
for (int i = 0; i < body.size(); ++i) { | |
map(body[i]->x_coord, body[i]->y_coord) = sumbol; | |
} | |
} | |
Worm::Worm() | |
:length(4), | |
direction(up), | |
sumbol(219u) | |
{ | |
// each body part of the snake is represented in x-y coordinates | |
for (int x_temp = screen_length / 2, end = x_temp + length; x_temp < end; ++x_temp) | |
{ | |
coordinate* temp = new coordinate; | |
temp->x_coord = x_temp; | |
temp->y_coord = screen_width / 2; | |
body.push_back(temp); | |
} | |
} | |
class Food : public Drawable { | |
public: | |
Food(); | |
virtual void draw(); | |
void generate(); | |
coordinate spot[3]; | |
int value; | |
unsigned char sumbol; | |
private: | |
const int max_food_amount; | |
void remove_eaten(); | |
}; | |
// if the snake touches an apple, sets the x-coordinate of that apple to -1 | |
// if the x-coordinate is -1, this apple is considered non-existent | |
void Food::remove_eaten() | |
{ | |
for (int i = 0; i < max_food_amount; ++i) | |
{ | |
if (map(spot[i].x_coord, spot[i].y_coord) == snake.sumbol) | |
{ | |
snake.length += value; | |
spot[i].x_coord = -1; | |
} | |
} | |
} | |
// draws all apples on the screen | |
void Food::draw() | |
{ | |
remove_eaten(); | |
generate(); | |
for (int i = 0; i < max_food_amount; ++i) | |
{ | |
map(spot[i].x_coord, spot[i].y_coord) = sumbol; | |
} | |
} | |
// randomly generates a new position of non-existent apples on the screen | |
void Food::generate() | |
{ | |
for (int i = 0; i < max_food_amount; ++i) | |
{ | |
// repeat until apple appears in an empty tile | |
while (spot[i].x_coord < 0 || map(spot[i].x_coord, spot[i].y_coord) == snake.sumbol || map(spot[i].x_coord, spot[i].y_coord) == sumbol) | |
{ | |
spot[i].x_coord = rand() % screen_length; | |
spot[i].y_coord = rand() % screen_width; | |
} | |
} | |
} | |
Food::Food() | |
:value(1), | |
sumbol('@'), | |
max_food_amount(3) | |
{ | |
for (int i = 0; i < max_food_amount; ++i) { | |
// if the x-coordinate is -1, this apple is considered non-existent | |
spot[i].x_coord = -1; | |
} | |
generate(); | |
} | |
// draws all drawable items on the screen | |
void draw_all(std::vector<Drawable*> vect) | |
{ | |
for (int i = 0; i < screen_length * screen_width; ++i) { | |
map.field[i] = map.background; | |
} | |
for (int i = 0; i < vect.size(); ++i) { | |
vect[i]->draw(); | |
} | |
} | |
int main() | |
{ | |
srand((unsigned int)time(0)); | |
Food apples; | |
// storing all drawable items in this vector | |
std::vector<Drawable*> drawables{ &snake, &apples }; | |
draw_all(drawables); | |
while (!snake.check_collision()) { | |
if (_kbhit()) snake.get_direction(_getch()); | |
snake.move(); | |
draw_all(drawables); | |
map.show(); | |
sleep(100); | |
} | |
std::cout << "\n\nLENGTH : " << snake.length << '\n'; | |
std::cout << "Press ENTER to quit"; | |
while (_getch() != 13); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm new to OOP so I may be using it wrong.
Share your comments