I finally finished the code for a snake game I was working on. I would like it if you were to give me some advice as to things that can be improved.
#ifndef UNICODE
#define UNICODE
#endif
#include <iostream>
#include <Windows.h>
#include <conio.h>
#include <ctime>
#include <random>
#include <queue>
#include "Snake_segment.h"
typedef std::deque<Snake_segment> Snake_container;
const enum direction {
UP = 0,
RIGHT,
DOWN,
LEFT
};
// Constant variables
int nScreenWidth;
int nScreenHeight;
const int nFieldWidth = 40;
const int nFieldHeight = 15;
int score = 0;
bool bIsHit = true;
direction dir = direction::RIGHT;
void clear(wchar_t* buf);
void update(HANDLE hConsole, Snake_container& body, wchar_t* buf);
void directionCheck(char value);
void move(Snake_container& body, wchar_t* buf);
void genFood(wchar_t* buf);
void clearOnly(wchar_t* buf);
int main(void) {
DWORD dwbyteswritten = 0;
HANDLE stdH = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(stdH, &csbi);
nScreenWidth = csbi.dwSize.X;
nScreenHeight = csbi.dwSize.Y;
wchar_t* temp = new wchar_t[nScreenWidth * nScreenHeight];
clear(temp);
bool bPlay = false;
while (true) {
int choice;
std::wcout << L"1. Play" << std::endl;
std::wcout << L"2. Quit" << std::endl;
std::cin >> choice;
if (choice == 1) {
bIsHit = false;
bPlay = true;
break;
}
else if (choice == 2) {
return 0;
}
else {
std::wcout << L"Invalid input!";
WriteConsoleOutputCharacter(stdH, temp, nScreenHeight * nScreenWidth, { 0, 0 }, &dwbyteswritten);
}
}
const HANDLE hConsole = CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CONSOLE_TEXTMODE_BUFFER,
NULL
);
// Sets up the snake body
Snake_container body;
Snake_segment head;
head.posx = nFieldWidth / 2; head.posy = nFieldHeight / 2;
body.push_back(head);
Snake_segment tail = head;
--tail.posx;
body.push_back(tail);
// Builds the game buffer and clears it
wchar_t* buffer = new wchar_t[nScreenWidth * nScreenHeight];
SetConsoleActiveScreenBuffer(hConsole);
clear(buffer);
// Generates food and draws game
update(hConsole, body, buffer);
genFood(buffer);
// Main game loop
while (!bIsHit) {
if (_kbhit())
directionCheck(_getch());
move(body, buffer);
update(hConsole, body, buffer);
clear(buffer);
Sleep(200);
}
CloseHandle(hConsole);
if (bPlay) {
WriteConsoleOutputCharacter(stdH, temp, nScreenHeight * nScreenWidth, { 0, 0 }, &dwbyteswritten);
std::wcout << L"Game over!" << std::endl;
std::wcout << L"Score: " << score << std::endl;
Sleep(1000);
}
CloseHandle(stdH);
return 0;
}
void update(HANDLE hConsole, Snake_container& body, wchar_t* buf) {
DWORD dwBytesWritten = 0;
// Draws the screen
for (int i = 0; i < nFieldHeight; ++i) {
for (int j = 0; j < nFieldWidth; ++j) {
// Draws top and bottom walls
if (i == 0 || i == nFieldHeight - 1) buf[i * nScreenWidth + j] = L'#';
// Draws left and right walls
else if (j == 0 || j == nFieldWidth - 1) buf[i * nScreenWidth + j] = L'#';
// Draws free space
else if (buf[i * nScreenWidth + j] != L'*') buf[i * nScreenWidth + j] = L' ';
// Prints snake
for (int k = 0, n = body.size(); k < n; ++k) {
// Prints snake
if (buf[body[0].posx + body[0].posy * nScreenWidth] == L'#') bIsHit = true;
else if (buf[body[0].posx + body[0].posy * nScreenWidth] == L'o') bIsHit = true;
else if (body[k].posx == j && body[k].posy == i)
if (k)
buf[i * nScreenWidth + j] = L'o';
else buf[i * nScreenWidth + j] = L'@';
}
}
}
for (int i = 0; i < 37; ++i)
buf[nFieldHeight * nScreenWidth + i] = L"Use 'w, a, s, d' to change directions"[i];
WriteConsoleOutputCharacter(hConsole, buf, nScreenWidth * nScreenHeight, { 0, 0 }, &dwBytesWritten);
}
// Clears the buffer
void clear(wchar_t* buf) {
for (int i = 0; i < nScreenHeight; ++i) {
for (int j = 0; j < nScreenWidth; ++j)
if(buf[i * nScreenWidth + j] != L'*')
buf[i * nScreenWidth + j] = L' ';
}
}
// Changes the directions according to the value
void directionCheck(char value) {
switch (value) {
case 'a':
if (dir != direction::RIGHT) dir = direction::LEFT;
break;
case 'w':
if (dir != direction::DOWN) dir = direction::UP;
break;
case 'd':
if (dir != direction::LEFT) dir = direction::RIGHT;
break;
case 's':
if (dir != direction::UP) dir = direction::DOWN;
}
}
// Moves the snake appropriately
void move(Snake_container& body, wchar_t* buf) {
body[0].prevXpos = body[0].posx;
body[0].prevYpos = body[0].posy;
switch (dir) {
case direction::RIGHT:
++body[0].posx;
break;
case direction::DOWN:
++body[0].posy;
break;
case direction::LEFT:
--body[0].posx;
break;
case direction::UP:
--body[0].posy;
}
for (int i = 1, n = body.size(); i < n; ++i) {
body[i].prevXpos = body[i].posx;
body[i].prevYpos = body[i].posy;
body[i].posx = body[i - 1].prevXpos;
body[i].posy = body[i - 1].prevYpos;
}
if (buf[body[0].posx + body[0].posy * nScreenWidth] == L'*') {
Snake_segment tail_thing;
tail_thing.posx = body[body.size() - 1].prevXpos;
tail_thing.posy = body[body.size() - 1].prevYpos;
body.push_back(tail_thing);
clearOnly(buf);
genFood(buf);
score += 100;
}
}
// Generates the food
void genFood(wchar_t* buf) {
int fX; int fY;
do {
time_t tim = time(NULL);
srand(tim + rand());
fX = rand() % (nFieldWidth - 2) + 1;
fY = rand() % (nFieldHeight - 2) + 1;
} while (buf[fX + fY * nScreenWidth] != L' ');
buf[fX + fY * nScreenWidth] = L'*';
}
// Only clears * characters
void clearOnly(wchar_t* buf) {
for (int i = 0; i < nScreenHeight; ++i) {
for (int j = 0; j < nScreenWidth; ++j)
if (buf[i * nScreenWidth + j] == L'*')
buf[i * nScreenWidth + j] = L' ';
}
}
File "Snake_segment.h" looks like this:
class Snake_segment {
public:
int posx, posy, prevXpos, prevYpos;
};