Files
Breakout/Breakout.cpp
T
2026-04-14 12:04:35 +03:30

618 lines
17 KiB
C++

#include <iostream>
#include <string>
#include <windows.h>
#include <conio.h>
#include <fstream>
#include <ctime>
#include <cmath>
#include <iomanip>
using namespace std;
const int SCREEN_WIDTH = 80;
const int SCREEN_HEIGHT = 26;
const int FIELD_START_Y = 2;
const int MAX_BRICKS = 300;
const int MAX_POWERUPS = 20;
const int MAX_SCORES = 50;
const string FILE_HIGHSCORE = "scores.txt";
const string FILE_SAVEGAME = "save.dat";
const string FILE_HISTORY = "history.txt";
enum ConsoleColor {
BLACK = 0, BLUE = 1, GREEN = 2, CYAN = 3, RED = 4, MAGENTA = 5, BROWN = 6,
LIGHTGRAY = 7, DARKGRAY = 8, LIGHTBLUE = 9, LIGHTGREEN = 10, LIGHTCYAN = 11,
LIGHTRED = 12, LIGHTMAGENTA = 13, YELLOW = 14, WHITE = 15
};
struct Vec2 {
float x, y;
};
struct Brick {
int x, y;
int hp;
int maxHp;
bool active;
int color;
};
struct Ball {
Vec2 pos;
Vec2 vel;
bool active;
bool stuckToPaddle;
};
struct PowerUp {
Vec2 pos;
char type;
int color;
bool active;
};
struct GameState {
int score;
int lives;
int level;
string playerName;
bool isGameOver;
bool isVictory;
int paddleWidth;
float paddleX;
};
struct ScoreEntry {
string name;
int score;
int level;
};
GameState game;
Ball ball;
Brick bricks[MAX_BRICKS];
int brickCount = 0;
PowerUp powerups[MAX_POWERUPS];
int powerupCount = 0;
char screenBuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
int colorBuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
char prevBuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
int prevColorBuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
int gameSpeed = 30;
string systemMessage = "";
int messageTimer = 0;
void SetupConsole() {
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hOut, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(hOut, &cursorInfo);
string cmd = "mode con cols=" + to_string(SCREEN_WIDTH) + " lines=" + to_string(SCREEN_HEIGHT);
system(cmd.c_str());
SetConsoleTitle("Breakout - Final Project");
}
void GotoXY(int x, int y) {
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void SetColor(int color) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
void ShowCursor(bool show) {
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hOut, &cursorInfo);
cursorInfo.bVisible = show;
SetConsoleCursorInfo(hOut, &cursorInfo);
}
void InitBuffers() {
for(int y=0; y<SCREEN_HEIGHT; y++) {
for(int x=0; x<SCREEN_WIDTH; x++) {
screenBuffer[y][x] = ' ';
colorBuffer[y][x] = BLACK;
prevBuffer[y][x] = '\0';
prevColorBuffer[y][x] = -1;
}
}
}
void ClearBuffer() {
for(int y=0; y<SCREEN_HEIGHT; y++) {
for(int x=0; x<SCREEN_WIDTH; x++) {
screenBuffer[y][x] = ' ';
colorBuffer[y][x] = BLACK;
}
}
}
void DrawToBuffer(int x, int y, char ch, int color) {
if(x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) {
screenBuffer[y][x] = ch;
colorBuffer[y][x] = color;
}
}
void DrawTextToBuffer(int x, int y, string text, int color) {
for(size_t i=0; i<text.length(); i++) {
DrawToBuffer(x + i, y, text[i], color);
}
}
void ShowMessage(string msg) {
systemMessage = msg;
messageTimer = 40;
}
void RenderFrame() {
for(int y=0; y<SCREEN_HEIGHT; y++) {
for(int x=0; x<SCREEN_WIDTH; x++) {
if(screenBuffer[y][x] != prevBuffer[y][x] || colorBuffer[y][x] != prevColorBuffer[y][x]) {
GotoXY(x, y);
SetColor(colorBuffer[y][x]);
cout << screenBuffer[y][x];
prevBuffer[y][x] = screenBuffer[y][x];
prevColorBuffer[y][x] = colorBuffer[y][x];
}
}
}
}
void InitLevel(int lvl) {
game.level = lvl;
game.isGameOver = false;
game.isVictory = false;
game.paddleWidth = max(6, 12 - lvl);
game.paddleX = SCREEN_WIDTH / 2 - game.paddleWidth / 2;
ball.active = true;
ball.stuckToPaddle = true;
ball.pos.x = game.paddleX + game.paddleWidth / 2;
ball.pos.y = SCREEN_HEIGHT - 2;
ball.vel.x = 0.5f;
ball.vel.y = -0.5f;
brickCount = 0;
powerupCount = 0;
int rows = 3 + lvl;
if(rows > 8) rows = 8;
for(int i=0; i<rows; i++) {
for(int j=4; j<SCREEN_WIDTH-4; j+=6) {
if(brickCount < MAX_BRICKS) {
bricks[brickCount].x = j;
bricks[brickCount].y = FIELD_START_Y + 1 + i;
bricks[brickCount].maxHp = (i % 3) + 1;
bricks[brickCount].hp = bricks[brickCount].maxHp;
bricks[brickCount].active = true;
if(bricks[brickCount].hp == 1) bricks[brickCount].color = LIGHTGREEN;
else if(bricks[brickCount].hp == 2) bricks[brickCount].color = YELLOW;
else bricks[brickCount].color = RED;
brickCount++;
}
}
}
}
void SpawnPowerUp(int x, int y) {
if(powerupCount < MAX_POWERUPS && rand() % 5 == 0) {
powerups[powerupCount].pos.x = (float)x;
powerups[powerupCount].pos.y = (float)y;
powerups[powerupCount].active = true;
int r = rand() % 3;
if(r == 0) {
powerups[powerupCount].type = 'W';
powerups[powerupCount].color = CYAN;
}
else if(r == 1) {
powerups[powerupCount].type = 'L';
powerups[powerupCount].color = MAGENTA;
}
else {
powerups[powerupCount].type = 'F';
powerups[powerupCount].color = WHITE;
}
powerupCount++;
}
}
void UpdatePowerUps() {
for(int i=0; i<powerupCount; i++) {
if(!powerups[i].active) continue;
powerups[i].pos.y += 0.2f;
if(powerups[i].pos.y >= SCREEN_HEIGHT - 1 && powerups[i].pos.y < SCREEN_HEIGHT + 1) {
if(powerups[i].pos.x >= game.paddleX && powerups[i].pos.x <= game.paddleX + game.paddleWidth) {
powerups[i].active = false;
game.score += 100;
if(powerups[i].type == 'W') {
game.paddleWidth += 4;
if(game.paddleWidth > 20) game.paddleWidth = 20;
ShowMessage("WIDE PADDLE!");
}
else if(powerups[i].type == 'L') {
game.lives++;
ShowMessage("EXTRA LIFE!");
}
else if(powerups[i].type == 'F') {
game.score += 500;
ShowMessage("BONUS POINTS!");
}
}
}
if(powerups[i].pos.y > SCREEN_HEIGHT) powerups[i].active = false;
}
}
void UpdateBall() {
if(ball.stuckToPaddle) {
ball.pos.x = game.paddleX + game.paddleWidth / 2;
ball.pos.y = SCREEN_HEIGHT - 2;
return;
}
ball.pos.x += ball.vel.x;
ball.pos.y += ball.vel.y;
if(ball.pos.x <= 1) {
ball.pos.x = 1;
ball.vel.x *= -1;
}
if(ball.pos.x >= SCREEN_WIDTH - 2) {
ball.pos.x = SCREEN_WIDTH - 2;
ball.vel.x *= -1;
}
if(ball.pos.y <= FIELD_START_Y) {
ball.pos.y = FIELD_START_Y;
ball.vel.y *= -1;
}
if(ball.pos.y >= SCREEN_HEIGHT) {
game.lives--;
if(game.lives <= 0) {
game.isGameOver = true;
} else {
ball.stuckToPaddle = true;
for(int i=0; i<powerupCount; i++) powerups[i].active = false;
ShowMessage("BALL LOST!");
}
}
}
void CheckCollisions() {
if(ball.pos.y >= SCREEN_HEIGHT - 2 && ball.pos.y < SCREEN_HEIGHT) {
if(ball.pos.x >= game.paddleX && ball.pos.x <= game.paddleX + game.paddleWidth) {
ball.vel.y = -fabs(ball.vel.y);
float hitPos = (ball.pos.x - (game.paddleX + game.paddleWidth / 2));
ball.vel.x = hitPos * 0.25f;
// Prevent sticking
ball.pos.y = SCREEN_HEIGHT - 2;
}
}
bool levelCleared = true;
for(int i=0; i<brickCount; i++) {
if(!bricks[i].active) continue;
levelCleared = false;
if(ball.pos.x >= bricks[i].x && ball.pos.x < bricks[i].x + 4 && (int)ball.pos.y == bricks[i].y) {
ball.vel.y *= -1;
bricks[i].hp--;
game.score += 10;
if(bricks[i].hp == 2) bricks[i].color = YELLOW;
else if(bricks[i].hp == 1) bricks[i].color = LIGHTGREEN;
if(bricks[i].hp <= 0) {
bricks[i].active = false;
game.score += 50;
SpawnPowerUp(bricks[i].x + 1, bricks[i].y);
}
break;
}
}
if(levelCleared) {
game.score += 1000;
InitLevel(game.level + 1);
ShowMessage("LEVEL COMPLETE!");
}
}
void SaveGame() {
ofstream f(FILE_SAVEGAME);
if(f.is_open()) {
f << game.playerName << endl;
f << game.score << " " << game.lives << " " << game.level << endl;
f << game.paddleX << " " << game.paddleWidth << endl;
f << ball.pos.x << " " << ball.pos.y << " " << ball.vel.x << " " << ball.vel.y << " " << ball.stuckToPaddle << endl;
f << brickCount << endl;
for(int i=0; i<brickCount; i++) {
f << bricks[i].x << " " << bricks[i].y << " " << bricks[i].hp << " " << bricks[i].maxHp << " " << bricks[i].active << " " << bricks[i].color << endl;
}
f << powerupCount << endl;
for(int i=0; i<powerupCount; i++) {
f << powerups[i].pos.x << " " << powerups[i].pos.y << " " << powerups[i].type << " " << powerups[i].color << " " << powerups[i].active << endl;
}
f.close();
ShowMessage("GAME SAVED!");
}
}
bool LoadGame() {
ifstream f(FILE_SAVEGAME);
if(f.is_open()) {
getline(f, game.playerName);
f >> game.score >> game.lives >> game.level;
f >> game.paddleX >> game.paddleWidth;
f >> ball.pos.x >> ball.pos.y >> ball.vel.x >> ball.vel.y >> ball.stuckToPaddle;
ball.active = true;
f >> brickCount;
for(int i=0; i<brickCount; i++) {
f >> bricks[i].x >> bricks[i].y >> bricks[i].hp >> bricks[i].maxHp >> bricks[i].active >> bricks[i].color;
}
f >> powerupCount;
for(int i=0; i<powerupCount; i++) {
f >> powerups[i].pos.x >> powerups[i].pos.y >> powerups[i].type >> powerups[i].color >> powerups[i].active;
}
f.close();
game.isGameOver = false;
game.isVictory = false;
return true;
}
return false;
}
void SaveHistory(string result) {
ofstream f(FILE_HISTORY, ios::app);
if(f.is_open()) {
time_t now = time(0);
string dt = ctime(&now);
dt = dt.substr(0, dt.length() - 1);
f << game.playerName << " | " << result << " | Sc: " << game.score << " | Lv: " << game.level << " | " << dt << endl;
f.close();
}
}
void SaveHighScore() {
ofstream f(FILE_HIGHSCORE, ios::app);
if(f.is_open()) {
f << game.playerName << " " << game.score << " " << game.level << endl;
f.close();
}
}
void RunGame() {
InitBuffers();
systemMessage = "";
messageTimer = 0;
while(!game.isGameOver && !game.isVictory) {
if(_kbhit()) {
char k = _getch();
if(k == 'a' || k == 'A') game.paddleX -= 2.5;
if(k == 'd' || k == 'D') game.paddleX += 2.5;
if(k == ' ') ball.stuckToPaddle = false;
if(k == 'p' || k == 'P') {
ShowMessage("PAUSED");
ClearBuffer();
DrawTextToBuffer(35, 12, "PAUSED", RED);
RenderFrame();
_getch();
}
if(k == 's' || k == 'S') SaveGame();
if(k == 27) break;
}
if(game.paddleX < 1) game.paddleX = 1;
if(game.paddleX + game.paddleWidth > SCREEN_WIDTH - 2)
game.paddleX = SCREEN_WIDTH - 1 - game.paddleWidth;
UpdateBall();
CheckCollisions();
UpdatePowerUps();
ClearBuffer();
for(int x=0; x<SCREEN_WIDTH; x++) DrawToBuffer(x, FIELD_START_Y, '#', LIGHTGRAY);
for(int y=FIELD_START_Y; y<SCREEN_HEIGHT; y++) {
DrawToBuffer(0, y, '#', LIGHTGRAY);
DrawToBuffer(SCREEN_WIDTH - 1, y, '#', LIGHTGRAY);
}
string info = "PLAYER: " + game.playerName + " SCORE: " + to_string(game.score) + " LIVES: " + to_string(game.lives) + " LEVEL: " + to_string(game.level);
DrawTextToBuffer(2, 0, info, WHITE);
if(messageTimer > 0) {
DrawTextToBuffer(SCREEN_WIDTH/2 - systemMessage.length()/2, SCREEN_HEIGHT/2, systemMessage, GREEN);
messageTimer--;
}
for(int i=0; i<brickCount; i++) {
if(bricks[i].active) {
DrawToBuffer(bricks[i].x, bricks[i].y, '[', bricks[i].color);
DrawToBuffer(bricks[i].x + 1, bricks[i].y, '=', bricks[i].color);
DrawToBuffer(bricks[i].x + 2, bricks[i].y, '=', bricks[i].color);
DrawToBuffer(bricks[i].x + 3, bricks[i].y, ']', bricks[i].color);
}
}
for(int i=0; i<powerupCount; i++) {
if(powerups[i].active) {
DrawToBuffer((int)powerups[i].pos.x, (int)powerups[i].pos.y, powerups[i].type, powerups[i].color);
}
}
for(int i=0; i<game.paddleWidth; i++) {
DrawToBuffer(game.paddleX + i, SCREEN_HEIGHT - 1, '=', CYAN);
}
DrawToBuffer((int)ball.pos.x, (int)ball.pos.y, 'O', WHITE);
RenderFrame();
Sleep(gameSpeed);
}
SaveHistory(game.isGameOver ? "LOST" : "QUIT");
if(game.isGameOver) SaveHighScore();
for(int y=0; y<SCREEN_HEIGHT; y++)
for(int x=0; x<SCREEN_WIDTH; x++)
prevBuffer[y][x] = '\0';
system("cls");
}
void ShowHighScores() {
system("cls");
SetColor(YELLOW); cout << "\n\n\t=== HALL OF FAME ===\n\n"; SetColor(WHITE);
ifstream f(FILE_HIGHSCORE);
ScoreEntry scores[MAX_SCORES];
int count = 0;
if(f.is_open()) {
while(count < MAX_SCORES && f >> scores[count].name >> scores[count].score >> scores[count].level) {
count++;
}
f.close();
for(int i=0; i<count; i++) {
for(int j=0; j<count-1; j++) {
if(scores[j].score < scores[j+1].score) {
ScoreEntry temp = scores[j];
scores[j] = scores[j+1];
scores[j+1] = temp;
}
}
}
for(int i=0; i<count && i<10; i++) {
cout << "\t" << (i + 1) << ". " << setw(15) << left << scores[i].name << " : " << scores[i].score << endl;
}
}
else {
cout << "\tNo records yet!\n";
}
cout << "\n\n\tPress any key...";
_getch();
}
void ShowHelp() {
system("cls");
SetColor(CYAN); cout << "\n\t=== HELP ===\n"; SetColor(WHITE);
cout << "\n\t[A]/[D] : Move [SPC]: Launch [S]: Save";
cout << "\n\n\tPower-ups:";
SetColor(CYAN); cout << "\n\t W : Wide Paddle";
SetColor(MAGENTA); cout << "\n\t L : Extra Life";
SetColor(WHITE); cout << "\n\t F : +500 Score";
cout << "\n\n\tPress any key...";
_getch();
}
void ShowSettings() {
system("cls");
cout << "\n\n\t=== SETTINGS ===\n";
cout << "\n\t1. Speed: " << (gameSpeed == 30 ? "Normal" : "Fast");
cout << "\n\t2. Back";
if(_getch() == '1') {
gameSpeed = (gameSpeed == 30) ? 15 : 30;
}
}
void ShowMenu() {
while(true) {
system("cls");
SetColor(LIGHTBLUE);
cout << R"(
____ _____ ______ _ __ ____ _ _ _______
| _ \| __ \| ____| /\ | |/ / / __ \| | | |__ __|
| |_) | |__) | |__ / \ | ' / | | | | | | | | |
| _ <| _ /| __| / /\ \ | < | | | | | | | | |
| |_) | | \ \| |____ / ____ \| . \ | |__| | |__| | | |
|____/|_| \_\______/_/ \_\_|\_\ \____/ \____/ |_|
)" << "\n";
SetColor(WHITE);
cout << "\t [1] NEW GAME\n";
cout << "\t [2] LOAD GAME\n";
cout << "\t [3] HIGH SCORES\n";
cout << "\t [4] SETTINGS\n";
cout << "\t [5] HELP\n";
cout << "\t [6] EXIT\n";
cout << "\n\t Select: ";
char k = _getch();
if(k == '1') {
system("cls");
ShowCursor(true);
SetColor(YELLOW);
cout << "\n\n\tEnter Player Name : ";
cin >> game.playerName;
ShowCursor(false);
game.score = 0;
game.lives = 3;
InitLevel(1);
RunGame();
}
else if(k == '2') {
if(LoadGame()) {
RunGame();
}
else {
cout << "\n\t No save file!";
Sleep(1000);
}
}
else if(k == '3') ShowHighScores();
else if(k == '4') ShowSettings();
else if(k == '5') ShowHelp();
else if(k == '6') exit(0);
}
}
int main() {
srand(time(0));
SetupConsole();
ofstream f(FILE_HISTORY, ios::app); f.close();
ofstream f2(FILE_HIGHSCORE, ios::app); f2.close();
ShowMenu();
return 0;
}