Today's blog will be primarily about a classic game that I was able to program, Snake. With the implementation of one of the data structure, Queue, in the previous post, I was able to program it. Though it's a simple-looking game, the programming part was not simple. As many functions were needed and I have to make sure no human logic errors would occur.
1. Logic:
I used Queue as a way to keep track of the Snake's coordinates. So, for each frame of movement, the coordinate at the front of the Queue(tail of the Snake) is Dequeued and the coordinate at the tail of the Queue (front of the Snake) will Enqueue whatever the next inputted coordinates is. And put that in a loop and Bam! We are done with the movement.
And in the Snake game, whenever you eat a fruit, your size increases. So, I did just that as well. Everytime the next cell's coordinate match with the fruit's coordinate, I simply don't Dequeue and move on from there.
2. Future ideas:
I'm thinking off adding a few power-ups or increase the difficulty over time like double the point power-up or an extra life power-up. But those seem kinda dull, so I'm hoping to get some recommendations.
3. Source Code:
- The Queue:
#include <iostream>
using namespace std;
class Node {
friend class Snake;
private:
int xpos, ypos;
Node *nextNode;
};
class Snake {
private:
Node *front, *rear;
public:
Snake() {
front = new Node();
rear = new Node();
front = NULL;
rear = NULL;
}
void enqueue(int x, int y) {
Node* newNode = new Node();
newNode->xpos = x;
newNode->ypos = y;
newNode->nextNode = NULL;
if (rear == NULL) {
rear = newNode;
front = newNode;
}
else {
rear->nextNode = newNode;
rear = newNode;
}
}
void dequeue(int& x, int& y) { // It will directly put data in given variables
if (!(this->isEmpty())) {
x = front->xpos;
y = front->ypos;
Node *temp = front;
front = front->nextNode;
delete temp;
if(this->isEmpty())
rear = NULL;
}
else cout << "Queue is Empty.\n";
}
void getFront(int& x, int& y) { // only return the node data from front of queue
if (!(this->isEmpty())) {
x = front->xpos;
y = front->ypos;
}
else cout << "Queue is empty.\n";
}
bool isEmpty() {
return (front == NULL);
}
};
- The Game:
#include <iostream>
#include <conio.h>
#include <stdlib.h>
#include <map>
#include <time.h>
#include <windows.h>
#include "Snake.cpp"
using namespace std;
//VARIABLES
map<int, map<int, char> > frame;
float speed = 10.0;
int i, j;
int width = 50, height = 20;
Snake snake;
int headX, headY;
int bodyX, bodyY;
int fruitX, fruitY;
char head = '\351';
char bodyPart = 'o';
char fruit = '\242';
int score;
bool gameOver;
bool direction; // 0 means vertical, 1 means horizontal
bool whichward; // In vertical: 0 means right 1 means left.
// In horizontal: 0 means up and 1 means down
//FUNCTION DECLARATION
void setUp(); // Settings
void addBody(); // Increase size when eat fruit
void update(); // Update the movement using queue
void generateFruit(); // Generate the fruit
void changeDirection(char dir); // Input of movement
void moveHead(); // Do the movement
void printMap(); // UI
//MAIN FUNCTION
int main() {
setUp();
generateFruit();
do {
update();
if (_kbhit()) {
changeDirection(_getch());
}
} while(gameOver == false);
cout << "Game Over.\n\n";
system("pause");
return 0;
}
void setUp() {
score = 0;
for(i = 0; i < width; i++)
frame[0][i] = '\262';
for(i = 1; i < height - 1; i++){
frame[i][0] = '\262';
for(j = 1; j < width - 1; j++)
frame[i][j] = ' ';
frame[i][j] = '\262';
}
for(i = 0; i < width; i++)
frame[height-1][i] = '\262';
srand(time(NULL));
headX = rand() % (width - 5);
headY = rand() % (height - 5);
frame[headY][headX] = head;
snake.enqueue(headX - 2, headY);
frame[headY][headX - 2] = bodyPart;
snake.enqueue(headX - 1, headY);
frame[headY][headX - 1] = bodyPart;
printMap();
direction = 0;
whichward = 0;
gameOver = false;
Sleep(1000/speed);
}
void addBody() {
moveHead();
printMap();
Sleep(1000/speed);
}
void update() {
moveHead();
snake.dequeue(bodyX,bodyY);
frame[bodyY][bodyX] = ' ';
printMap();
Sleep(1000/speed);
}
void generateFruit() {
fruitX = rand() % width;
fruitY = rand() % height;
if(frame[fruitY][fruitX] == ' ')
frame[fruitY][fruitX] = fruit;
else generateFruit();
}
void changeDirection(char dir) {
if((dir == 'w' || dir == 's') && direction == 0){
if(dir == 'w')
whichward = 0;
else
whichward = 1;
direction = 1;
}
else if((dir == 'a' || dir == 'd') && direction == 1){
if(dir == 'a')
whichward = 1;
else
whichward = 0;
direction = 0;
}
}
void moveHead() {
snake.enqueue(headX, headY);
frame[headY][headX] = bodyPart;
if(direction == 0 && whichward == 0)
headX++;
if(direction == 0 && whichward == 1)
headX--;
if(direction == 1 && whichward == 0)
headY--;
if(direction == 1 && whichward == 1)
headY++;
if(headX == fruitX && headY == fruitY){
score++;
addBody();
generateFruit();
}
if(frame[headY][headX] == '\262'
|| frame[headY][headX] == bodyPart)
gameOver = true;
frame[headY][headX] = head;
}
void printMap() {
system("cls");
for(i = 0; i < height; i++) {
for(j = 0; j < width; j++) {
cout << frame[i][j];
}
if (i == 10)
cout << " Score: " << score;
cout << endl;
}
}
P.S: I used map in the code as well though I have not learnt it. But it's mostly use for the drawing of the grid so it's not important. Maybe I'll learn it next week.
REFERENCES:
Codebytes for the Queue Logic
NVitanovic for the general Format of the Code
Top comments (0)