Files
Breakout/FinalProject.cpp
T
2026-04-14 14:17:07 +00:00

1477 lines
49 KiB
C++

// ----- Headers -----
#include <iostream>
#include <windows.h>
#include <conio.h>
#include <fstream>
#include <ctime>
#include <vector>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <sstream>
using namespace std;
// ------- COLORS ---------
#define RESET "\033[0m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define CYAN "\033[36m"
#define BLUE "\033[34m"
#define YELLOW "\033[33m"
#define MAGENTA "\033[35m"
#define WHITE "\033[37m"
#define BRIGHTBLACK "\033[90m"
#define BRIGHTBLUE "\033[94m"
#define BRIGHTRED "\033[91m"
#define ORANGE "\033[38;5;202m"
#define PINK "\033[38;5;201m"
#define PURPLE "\033[38;5;93m"
#define BLACK "\033[30m"
#define LIGHTBLUE "\033[94m"
#define LIGHTMAGENTA "\033[95m"
#define BOLDYELLOW "\033[1;33m"
#define BRIGHTMAGENTA "\033[95m"
#define GOLD "\033[93m"
// ========== rendering ===========
// ----- Structs -----
struct paddelInfo {
float x;
int y;
int size;
int speed;
};
struct ballInfo {
float x;
float y;
float i_speed;
float j_speed;
bool stuckToPaddle;
};
struct ThemeStyle {
string borderColor;
string paddleColor;
string simpleBlockColor;
string hardBlockColor;
string ballColor;
string cursorColor;
string topicColor;
string ballChar;
string cursorChar;
string topicChar;
string paddleChar;
string blockChar;
};
struct RandomItem {
int x, y;
int power;
bool active;
};
ThemeStyle CurrentTheme;
paddelInfo paddel = {10.0f, 18, 5, 1};
ballInfo ball = {10.0f, 17.0f, 0.0f, 0.0f, false};
RandomItem Items[12];
// ----- Glubal variables -----
char board[19][20];
int heal = 3;
int score = 0;
int bestscore = 0;
string player;
int simpleBlocksDestroyed = 0;
int hardBlocksDestroyed = 0;
string WinOrLose;
int Theme = -1;
int level = -1;
clock_t slowStartTime = 0;
clock_t slowCooldownTime = 0;
const int slowDuration = 5; // seconds
const int slowCooldown = 15; // seconds
int normalBallDelay = 65;
int slowBallDelay = 110;
int ballDelay = 65;
int cooldownRemaining = 0;
clock_t powerOn;
clock_t now;
bool pKeyPressed = false;
bool Epressed = false;
bool paused = false;
int length = (paddel.size-1)/2;
bool start = false;
bool Isstarted = false;
bool LoadGameAvailable = false;
bool needRedraw = true;
bool slowActive = false;
bool difaultTheme = true;
bool difaultlevel = true;
// ----- Function Declarations -----
void initialSituation();
void DrawBoard();
void startGame ();
int Menu();
void inputHandeling();
void timing();
void WaitForKeyRelease();
void HideCursor();
void drawGameover(int choice);
int GameOverMenu();
string getPlayerName();
void drawBoxAnimation(int x, int y, int w, int h);
void saveGame(const string& player);
void clearSaveGame();
bool loadGame(string& player);
void saveHistory(const string& player, int score);
int extractScore(const string& line);
void showHistory();
void gotoxy(int x, int y);
void ClearInputBuffer();
void displayInform();
void DrawHelpItems();
void DrawSetting();
void DeterminThemeAnfLevel();
void PlayBreakoutIntro();
void creatrandomItems();
void ActivateRandomItem(int bx, int by);
void DrawRandomItems();
void ApplyPower(int power);
void RemovePower();
void HandleBlockDestruction(int x, int y);
void UpdateBall();
int main() {
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
HideCursor();
int choice = 0;
DeterminThemeAnfLevel();
while (true) {
int selected = Menu();
if (selected == 0) { // New Game
system("cls");
needRedraw = true;
Isstarted = false;
player = getPlayerName();
system("cls");
WaitForKeyRelease();
ClearInputBuffer();
startGame();
needRedraw = false;
}
else if (selected == 1 && LoadGameAvailable) { // Load Game
system("cls");
gotoxy(25, 13);
cout << RED "__1__" RESET ;
Sleep(500);
system("cls");
gotoxy(25, 13);
cout << RED "__2__" RESET ;
Sleep(500);
system("cls");
gotoxy(25, 13);
cout << RED "__3__" RESET ;
Sleep(200);
system("cls");
if (loadGame(player)) {
Isstarted = true;
if (ball.i_speed == 0 && ball.j_speed == 0) {
start = false;
}
else {
start = true;
}
startGame();
}
else {
system("cls");
cout << RED << "No saved game found!" << RESET;
Sleep(1500);
}
}
else if (selected == 2) { // Setting
DrawSetting();
DeterminThemeAnfLevel();
}
else if (selected == 3) { // History
showHistory();
}
else if (selected == 4) { // Help
system("cls");
DrawHelpItems();
// wait for exit
while (true) {
ClearInputBuffer();
char key = _getch();
if (key == 69 || key == 101) { // E
ClearInputBuffer();
break;
}
Sleep(50);
}
}
else if (selected == 5) { // exit
clearSaveGame();
break;
}
}
}
int Menu() {
PlayBreakoutIntro();
string options[] = {
"New Game",
"Load Game",
"Setting ⚙️",
"Game History 🗂️",
"Help",
"Exit"
};
gotoxy(10,9);
cout << BRIGHTBLUE << "│" << RESET << YELLOW << " Welcome to Breakout" << RESET;
gotoxy(10,10);
cout << BRIGHTBLUE << "│" << RESET << YELLOW << " Use ▲ ▼ to Move Up & Down" << RESET;
drawBoxAnimation(40, 8, 29, 12);
int selected = 0;
while (true) {
for (int i = 0; i < 6; i++) {
gotoxy(47, 11 + i);
// Load Game
if (i == 1 && !LoadGameAvailable) {
cout << BRIGHTBLACK << " " << options[i] << RESET;
continue;
}
if (i == selected)
cout << CurrentTheme.cursorColor << "> " << RESET << WHITE << options[i] << RESET;
else
cout << " " << options[i];
}
char key = _getch();
// UP
if (key == 72 && selected > 0) {
selected--;
if (selected == 1 && !LoadGameAvailable)
selected--;
}
// DOWN
if (key == 80 && selected < 5) {
selected++;
if (selected == 1 && !LoadGameAvailable)
selected++;
}
// ENTER
if (key == 13) {
if (selected == 1 && !LoadGameAvailable)
continue;
return selected;
}
}
}
void startGame() {
bool pKeyPressed = false;
bool paused = false;
int length = (paddel.size-1)/2;
if(!Isstarted && needRedraw) {
for (int x = 0 ; x < 5 ; x++)
for (int y = 0 ; y < 20; y++) {
if (y == 0 || y == 19) board[x][y] = ' ';
else{
if(level == 6) board[x][y] = rand() %2 == 0 ? '1' : '2';
else if(level == 7) board[x][y] = '2';
}
}
for (int x = 5 ; x < 19; x++)
for (int y = 0 ; y < 20 ; y++)
board[x][y] = ' ';
paddel.x = 10.0f;
paddel.y = 18;
heal = 3;
simpleBlocksDestroyed= 0;
hardBlocksDestroyed = 0;
score = 0;
}
if (needRedraw) {
ball.x = 10.0f;
ball.y = 17.0f;
ball.i_speed = 0.0f;
ball.j_speed = 0.0f;
}
DrawBoard();
WaitForKeyRelease();
ClearInputBuffer();
while (true) {
displayInform(); // Dynamic Info
clock_t frameStart = clock();
// --- MOVE PADDLE ---
if (GetAsyncKeyState('A') & 0x8000 && !paused) {
if (paddel.x - (float)length >= 0.0f) {
paddel.x -= paddel.speed;
if (!start) ball.x = paddel.x;
DrawBoard();
}
}
if (GetAsyncKeyState('D') & 0x8000 && !paused) {
if (paddel.x + (float)length <= 19.0f) {
paddel.x += paddel.speed;
if (!start) ball.x = paddel.x;
DrawBoard();
}
}
// --- START GAME ---
if (GetAsyncKeyState(VK_SPACE) & 0x8000 && !start && !paused) {
start = true;
ball.i_speed = (rand() % 2 == 0) ? 1.0f : -1.0f;
ball.j_speed = -1.0f;
DrawBoard();
}
// --- EXIT GAME ---
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
if (Isstarted) {
needRedraw = false;
saveGame(player);
LoadGameAvailable = true;
}
}
if (GetAsyncKeyState('P') & 0x8000) {
if (!pKeyPressed) {
paused = !paused;
start = !paused;
if(!Isstarted && paused) {
start = false;
paused = false;
}
pKeyPressed = true;
}
}
else {
pKeyPressed = false;
}
if (GetAsyncKeyState('E') & 0x8000) {
Isstarted = false;
start = false;
WaitForKeyRelease();
ClearInputBuffer();
break;
}
clock_t now = clock();
if ((GetAsyncKeyState(VK_RETURN) & 0x0001) &&
!slowActive &&
(now - slowCooldownTime) >= slowCooldown * CLOCKS_PER_SEC &&
start && !paused)
{
slowActive = true;
slowStartTime = now;
slowCooldownTime = now;
ballDelay = slowBallDelay;
}
if (slowActive &&
(now - slowStartTime) >= slowDuration * CLOCKS_PER_SEC)
{
slowActive = false;
ballDelay = normalBallDelay;
}
if ((now - slowCooldownTime) < slowCooldown * CLOCKS_PER_SEC) {
cooldownRemaining = slowCooldown - (now - slowCooldownTime) / CLOCKS_PER_SEC;
}
// --- MOVE BALL ---
if (start) {
Isstarted = true;
int bx = (int)round(ball.x);
int by = (int)round(ball.y);
HandleBlockDestruction(bx,by);
UpdateBall();
DrawBoard();
DrawRandomItems();
RemovePower ();
if (ball.y >= 18) {
Isstarted = false;
WaitForKeyRelease();
ClearInputBuffer();
heal --;
if (heal <= 0 || simpleBlocksDestroyed == 90) {
if (heal <= 0) {WinOrLose = RED "Lose" RESET;}
if (simpleBlocksDestroyed == 90) {WinOrLose = GREEN "Win" RESET;}
saveHistory(player, score);
int result = GameOverMenu();
if (result == 0) { // back to menu
Isstarted = false;
needRedraw = true;
start = false;
heal = 3;
simpleBlocksDestroyed = 0;
hardBlocksDestroyed = 0;
score = 0;
return;
}
else if (result == 1) { // new game
// reset board
for (int x = 0; x < 5; x++)
for (int y = 0; y < 20; y++){
if (level == 6) board[x][y] = (y == 0 || y == 19) ? ' ' : ((rand() % 2 == 0) ? '1' : '2');
else if(level == 7) board[x][y] = (y == 0 || y == 19) ? ' ' : '2';
}
for (int x = 5; x < 19; x++)
for (int y = 0; y < 20; y++)
board[x][y] = ' ';
heal = 3;
simpleBlocksDestroyed = 0;
hardBlocksDestroyed = 0;
score = 0;
system("cls");
}
}
// reset ball & paddle state
start = false;
paddel.x = 10.0f;
paddel.y = 18;
ball.x = 10.0f;
ball.y = 17.0f;
ball.i_speed = 0.0f;
ball.j_speed = 0.0f;
DrawBoard();
}
}
while ((clock() - frameStart) * 1000 / CLOCKS_PER_SEC < ballDelay) {
Sleep(1);
}
}
}
void displayInform() {
if (heal == 3) {
gotoxy(36, 4);
cout << " ❤️❤️❤️ ";
}
else if (heal == 2) {
gotoxy(36, 4);
cout << " ❤️❤️ ";
}
else if (heal == 1) {
gotoxy(36, 4);
cout << " ❤️ ";
}
if (cooldownRemaining > 1) {
gotoxy(74,25);
cout << cooldownRemaining;
gotoxy(75,25);
cout << "s ";
}
else {
gotoxy(74,25);
cout << RED " READY " RESET;
}
}
void WaitForKeyRelease() {
while ((GetAsyncKeyState(VK_RETURN) & 0x8000) || (GetAsyncKeyState(VK_SPACE) & 0x8000) ||(GetAsyncKeyState(VK_ESCAPE) & 0x8000)) {
Sleep(50);
}
}
void ClearInputBuffer() {
while (_kbhit())_getch();
}
void HideCursor() {
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO info;
info.dwSize = 100;
info.bVisible = FALSE;
SetConsoleCursorInfo(consoleHandle, &info);
}
void drawGameover(int choice) {
system("cls");
string options[2] = {"Back to Menu", "New Game"};
string Display = "";
Display += CurrentTheme.borderColor;
Display += "\n ┌───────────────────────────────────────┐\n";
Display += " │ │\n";
Display += " ├───────────────────┬───────────────────┤\n";
Display += " │ │ │\n";
Display += " │ │ │\n";
Display += " ├───────────────────┼───────────────────┤\n";
Display += " │ │ │\n";
Display += " │ │ │\n";
Display += " │ │ │\n";
Display += " └───────────────────┴───────────────────┘";
Display += RESET;
gotoxy(30, 7);
cout << Display;
// Win / Lose text
gotoxy(20, 9);
if (simpleBlocksDestroyed == 90)
cout << "YOU WIN";
else if (heal <= 0)
cout << BRIGHTRED << "YOU LOSE" << RESET;
// Scores title
gotoxy(11, 11);
cout << "Score";
gotoxy(29, 11);
cout << "Best Score";
// Scores values
gotoxy(12, 12);
cout << score;
gotoxy(31, 12);
cout << YELLOW << bestscore << RESET;
// Options
gotoxy(8, 15);
cout << options[0];
gotoxy(30, 15);
cout << options[1];
// Selector box
if (choice == 0)
{
gotoxy(5, 14);
cout << YELLOW << "┌───────────────┐" << RESET;
gotoxy(5, 16);
cout << YELLOW << "└───────────────┘" << RESET;
}
else if (choice == 1)
{
gotoxy(25, 14);
cout << YELLOW << "┌───────────────┐" << RESET;
gotoxy(25, 16);
cout << YELLOW << "└───────────────┘" << RESET;
}
}
int saveBestScore(int currentScore) {
ifstream inFile("bestscore.txt");
int savedBest = 0;
if (inFile.is_open()) {
inFile >> savedBest;
inFile.close();
}
if (currentScore > savedBest) {
savedBest = currentScore;
ofstream outFile("bestscore.txt");
outFile << savedBest;
outFile.close();
}
return savedBest;
}
int GameOverMenu() {
int choice = 0;
bestscore = saveBestScore(score);
while (true) {
drawGameover(choice);
char input = _getch();
if (input == 'a' || input == 'A') {
choice--;
if (choice < 0) choice = 1;
}
else if (input == 'd' || input == 'D') {
choice++;
if (choice > 1) choice = 0;
}
else if (input == '\r') {
ClearInputBuffer();
return choice;
}
}
}
void gotoxy(int x, int y) { cout << "\033[" << y << ";" << x << "H"; }
void drawBoxAnimation(int x, int y, int w, int h) { // x : right,left y : up,down w : width h : hight
gotoxy(x, y);
cout << CurrentTheme.borderColor << "╔" << RESET;
for (int i = 0; i < w - 2; i++) { cout << CurrentTheme.borderColor << "═" << RESET; Sleep(9); }
cout << CurrentTheme.borderColor << "╗" << RESET;
for (int i = 1; i < h - 1; i++) {
gotoxy(x, y + i);
cout << CurrentTheme.borderColor << "║" << RESET;
gotoxy(x + w - 1, y + i);
cout << CurrentTheme.borderColor << "║" << RESET;
Sleep(30);
}
gotoxy(x, y + h - 1);
cout << CurrentTheme.borderColor << "╚" << RESET;
for (int i = 0; i < w - 2; i++) cout << CurrentTheme.borderColor << "═" << RESET;
cout << CurrentTheme.borderColor << "╝" << RESET;
}
string getPlayerName() {
string name = "";
drawBoxAnimation(40, 8, 24, 4);
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO info;
GetConsoleCursorInfo(consoleHandle, &info);
info.bVisible = TRUE; // Show cursor
SetConsoleCursorInfo(consoleHandle, &info);
while (name.length() == 0) {
gotoxy(10, 9);
cout << BRIGHTBLUE << "│" << RESET << YELLOW << CurrentTheme.topicColor << " Enter your Name\n" << RESET;
gotoxy(10, 10);
cout << BRIGHTBLUE << "│" << RESET << BRIGHTBLACK << " max(10)" << RESET;
gotoxy(45, 9);
cout << " ";
gotoxy(45, 9);
cin >> name;
}
info.bVisible = FALSE; // Hide cursor
SetConsoleCursorInfo(consoleHandle, &info);
return name.substr(0, 10);
}
// rendering function
void DrawBoard() {
int length = (paddel.size - 1) / 2;
string frame;
frame += CurrentTheme.borderColor + " ┌────────────────────┐\n" + RESET;
for (int y = 0; y < 19; y++) {
frame += CurrentTheme.borderColor + " │" + RESET;
for (int x = 0; x < 20; x++) {
string display = " ";
// Paddle// Ball
int i = (int)round(ball.x);
int j = (int)round(ball.y);
if ((int)round(paddel.x) - length <= x && (int)round(paddel.x) + length >= x && (int)round(paddel.y) == y) {
display = CurrentTheme.paddleColor + CurrentTheme.paddleChar + RESET;
}
// Blocks
else if (board[y][x] == '1') display = CurrentTheme.simpleBlockColor + CurrentTheme.blockChar + RESET;
else if (board[y][x] == '2') display = CurrentTheme.hardBlockColor + CurrentTheme.blockChar + RESET;
else if (board[y][x] == 'h') display = CurrentTheme.simpleBlockColor + CurrentTheme.blockChar + RESET;
// Ball
else if (board[y][x] == ' ' && i == x && j == y)
display = CurrentTheme.ballChar;
frame += display;
}
frame += CurrentTheme.borderColor + "│\n" + RESET;
}
frame += CurrentTheme.borderColor + " └────────────────────┘\n" + RESET;
// --- Game Info ---
frame += BRIGHTBLUE " │" RESET YELLOW " Press Esc to Save Game\n" RESET;
frame += BRIGHTBLUE " │" RESET YELLOW " Press E to Exit\n" RESET;
frame += BRIGHTBLUE " │" RESET YELLOW " Press P to Pause or Resume\n" RESET;
frame += BRIGHTBLUE " │" RESET YELLOW " Press Enter to use your POWER after each 15s" RESET;
// Power bar
int barWidth = 10;
int filled = (slowCooldown - cooldownRemaining) * barWidth / slowCooldown;
frame += " POWER [";
for (int i = 0; i < barWidth; i++) frame += (i < filled ? "#" : "-");
frame += "] ";
gotoxy(0, 0);
cout << frame;
gotoxy(30, 3);
cout << "Player: " << player;
gotoxy(30, 4);
cout << "Lives: ";
gotoxy(30, 5);
cout << RED << "------------------" << RESET;
gotoxy(30,6);
cout << "Broken Blocks: " << to_string(simpleBlocksDestroyed + hardBlocksDestroyed);
gotoxy(30,7);
cout << "Score: " << to_string(score);
}
void HandleBlockDestruction(int x, int y) {
if (x < 0 || x >= 20 || y < 0 || y >= 19) return;
if (board[y][x] == '1') {
simpleBlocksDestroyed++;
score += 10;
board[y][x] = ' ';
}
else if (board[y][x] == '2') {
score += 10;
board[y][x] = 'h';
}
else if (board[y][x] == 'h') {
hardBlocksDestroyed++;
score += 10;
board[y][x] = ' ';
ActivateRandomItem(x, y);
}
}
void UpdateBall() {
if (ball.stuckToPaddle) {
ball.x = paddel.x;
ball.y = paddel.y - 1;
return;
}
// Store previous position for accurate collision detection
float prevX = ball.x;
float prevY = ball.y;
// Determine movement direction
bool movingLeft = (ball.i_speed < -0.05f);
bool movingRight = (ball.i_speed > 0.05f);
bool movingUp = (ball.j_speed < -0.05f);
bool movingDown = (ball.j_speed > 0.05f);
// ===== STEP 1: WALL COLLISIONS =====
// Check vertical walls first
if (movingLeft && ball.x + ball.i_speed <= 0) {
ball.x = 0;
ball.i_speed = fabs(ball.i_speed); // Reverse to right
movingLeft = false;
movingRight = true;
}
else if (movingRight && ball.x + ball.i_speed >= 19) {
ball.x = 19;
ball.i_speed = -fabs(ball.i_speed); // Reverse to left
movingRight = false;
movingLeft = true;
}
// Check top wall
if (movingUp && ball.y + ball.j_speed <= 0) {
ball.y = 0;
ball.j_speed = fabs(ball.j_speed); // Reverse to down
movingUp = false;
movingDown = true;
}
// ===== STEP 2: MOVE BALL =====
ball.x += ball.i_speed;
ball.y += ball.j_speed;
// Get current cell positions
int currX = (int)round(ball.x);
int currY = (int)round(ball.y);
int prevX_int = (int)round(prevX);
int prevY_int = (int)round(prevY);
// ===== STEP 3: BLOCK COLLISIONS WITH DIRECTIONAL PRIORITY =====
bool blockHit = false;
// For DIAGONAL MOVEMENT: Check primary axes first before diagonal
if (movingUp && movingRight) {
// Priority 1: Check cell ABOVE (vertical collision)
if (currY - 1 >= 0 && currX >= 0 && currX < 20 &&
(board[currY - 1][currX] == '1' || board[currY - 1][currX] == '2' || board[currY - 1][currX] == 'h')) {
HandleBlockDestruction(currX, currY - 1);
ball.j_speed = fabs(ball.j_speed); // Bounce down
ball.y = currY - 0.4f; // Position below the block
blockHit = true;
}
// Priority 2: Check cell to the RIGHT (horizontal collision)
else if (currX + 1 < 20 && currY >= 0 && currY < 19 &&
(board[currY][currX + 1] == '1' || board[currY][currX + 1] == '2' || board[currY][currX + 1] == 'h')) {
HandleBlockDestruction(currX + 1, currY);
ball.i_speed = -fabs(ball.i_speed); // Bounce left
ball.x = currX + 0.4f; // Position left of the block
blockHit = true;
}
// Priority 3: Check DIAGONAL cell (top-right corner)
else if (currX + 1 < 20 && currY - 1 >= 0 &&
(board[currY - 1][currX + 1] == '1' || board[currY - 1][currX + 1] == '2' || board[currY - 1][currX + 1] == 'h')) {
// Corner hit - reverse vertical direction
HandleBlockDestruction(currX + 1, currY - 1);
ball.j_speed = fabs(ball.j_speed); // Bounce down
ball.y = currY - 0.4f;
blockHit = true;
}
}
// Similar logic for other diagonal directions
else if (movingUp && movingLeft) {
// Priority 1: Above
if (currY - 1 >= 0 && currX >= 0 && currX < 20 &&
(board[currY - 1][currX] == '1' || board[currY - 1][currX] == '2' || board[currY - 1][currX] == 'h')) {
HandleBlockDestruction(currX, currY - 1);
ball.j_speed = fabs(ball.j_speed);
ball.y = currY - 0.4f;
blockHit = true;
}
// Priority 2: Left
else if (currX - 1 >= 0 && currY >= 0 && currY < 19 &&
(board[currY][currX - 1] == '1' || board[currY][currX - 1] == '2' || board[currY][currX - 1] == 'h')) {
HandleBlockDestruction(currX - 1, currY);
ball.i_speed = fabs(ball.i_speed); // Bounce right
ball.x = currX - 0.4f;
blockHit = true;
}
// Priority 3: Diagonal (top-left)
else if (currX - 1 >= 0 && currY - 1 >= 0 &&
(board[currY - 1][currX - 1] == '1' || board[currY - 1][currX - 1] == '2' || board[currY - 1][currX - 1] == 'h')) {
HandleBlockDestruction(currX - 1, currY - 1);
ball.j_speed = fabs(ball.j_speed);
ball.y = currY - 0.4f;
blockHit = true;
}
}
else if (movingDown && movingRight) {
// Priority 1: Below
if (currY + 1 < 19 && currX >= 0 && currX < 20 &&
(board[currY + 1][currX] == '1' || board[currY + 1][currX] == '2' || board[currY + 1][currX] == 'h')) {
HandleBlockDestruction(currX, currY + 1);
ball.j_speed = -fabs(ball.j_speed);
ball.y = currY + 0.4f;
blockHit = true;
}
// Priority 2: Right
else if (currX + 1 < 20 && currY >= 0 && currY < 19 &&
(board[currY][currX + 1] == '1' || board[currY][currX + 1] == '2' || board[currY][currX + 1] == 'h')) {
HandleBlockDestruction(currX + 1, currY);
ball.i_speed = -fabs(ball.i_speed);
ball.x = currX + 0.4f;
blockHit = true;
}
// Priority 3: Diagonal (bottom-right)
else if (currX + 1 < 20 && currY + 1 < 19 &&
(board[currY + 1][currX + 1] == '1' || board[currY + 1][currX + 1] == '2' || board[currY + 1][currX + 1] == 'h')) {
HandleBlockDestruction(currX + 1, currY + 1);
ball.j_speed = -fabs(ball.j_speed);
ball.y = currY + 0.4f;
blockHit = true;
}
}
else if (movingDown && movingLeft) {
// Priority 1: Below
if (currY + 1 < 19 && currX >= 0 && currX < 20 &&
(board[currY + 1][currX] == '1' || board[currY + 1][currX] == '2' || board[currY + 1][currX] == 'h')) {
HandleBlockDestruction(currX, currY + 1);
ball.j_speed = -fabs(ball.j_speed);
ball.y = currY + 0.4f;
blockHit = true;
}
// Priority 2: Left
else if (currX - 1 >= 0 && currY >= 0 && currY < 19 &&
(board[currY][currX - 1] == '1' || board[currY][currX - 1] == '2' || board[currY][currX - 1] == 'h')) {
HandleBlockDestruction(currX - 1, currY);
ball.i_speed = fabs(ball.i_speed);
ball.x = currX - 0.4f;
blockHit = true;
}
// Priority 3: Diagonal (bottom-left)
else if (currX - 1 >= 0 && currY + 1 < 19 &&
(board[currY + 1][currX - 1] == '1' || board[currY + 1][currX - 1] == '2' || board[currY + 1][currX - 1] == 'h')) {
HandleBlockDestruction(currX - 1, currY + 1);
ball.j_speed = -fabs(ball.j_speed);
ball.y = currY + 0.4f;
blockHit = true;
}
}
// For pure horizontal/vertical movement, check current cell
else if (!blockHit && currX >= 0 && currX < 20 && currY >= 0 && currY < 19) {
if (board[currY][currX] == '1' || board[currY][currX] == '2' || board[currY][currX] == 'h') {
HandleBlockDestruction(currX, currY);
if (movingLeft || movingRight) {
ball.i_speed *= -1; // Horizontal bounce
}
if (movingUp || movingDown) {
ball.j_speed *= -1; // Vertical bounce
}
blockHit = true;
}
}
// ===== STEP 4: PADDLE COLLISION (lowest priority) =====
int paddleLength = paddel.size / 2;
int paddleTop = paddel.y - 1;
// Only check paddle collision if ball is moving downward and near paddle
if (movingDown && currY >= paddleTop - 1 && currY <= paddel.y + 1) {
float paddleLeft = paddel.x - paddleLength;
float paddleRight = paddel.x + paddleLength;
// Check if ball is within paddle's horizontal range
if (ball.x >= paddleLeft && ball.x <= paddleRight) {
// Calculate precise hit position for angle reflection
float relativeHit = (ball.x - paddel.x) / (float)paddleLength;
ball.i_speed = relativeHit * 1.3f; // Wider angle distribution
// Ensure minimum horizontal speed to prevent straight up/down movement
if (fabs(ball.i_speed) < 0.35f) {
ball.i_speed = (ball.i_speed < 0) ? -0.35f : 0.35f;
}
ball.j_speed = -fabs(ball.j_speed); // Always bounce upward
ball.y = paddleTop - 0.2f; // Position just above paddle
}
}
}
void saveGame(const string& player) {
ofstream file("save.dat", ios::binary);
if (!file) return;
file.write((char*)&paddel.speed, sizeof(paddel.speed));
file.write((char*)&paddel.x, sizeof(paddel.x));
file.write((char*)&paddel.y, sizeof(paddel.y));
file.write((char*)&ball.i_speed, sizeof(ball.i_speed));
file.write((char*)&ball.j_speed, sizeof(ball.j_speed));
file.write((char*)&ball.x, sizeof(ball.x));
file.write((char*)&ball.y, sizeof(ball.y));
file.write((char*)&board, sizeof(board));
file.write((char*)&score, sizeof(score));
file.write((char*)&heal, sizeof(heal));
file.write((char*)&bestscore, sizeof(bestscore));
file.write((char*)&Theme ,sizeof(Theme));
int len = player.size();
file.write((char*)&len, sizeof(len));
file.write(player.c_str(), len);
file.close();
}
bool loadGame(string& player) {
ifstream file("save.dat", ios::binary);
if (!file) return false;
file.read((char*)&paddel.speed, sizeof(paddel.speed));
file.read((char*)&paddel.x, sizeof(paddel.x));
file.read((char*)&paddel.y, sizeof(paddel.y));
file.read((char*)&ball.i_speed, sizeof(ball.i_speed));
file.read((char*)&ball.j_speed, sizeof(ball.j_speed));
file.read((char*)&ball.x, sizeof(ball.x));
file.read((char*)&ball.y, sizeof(ball.y));
file.read((char*)&board, sizeof(board));
file.read((char*)&score, sizeof(score));
file.read((char*)&heal, sizeof(heal));
file.read((char*)&bestscore, sizeof(bestscore));
file.read((char*)&Theme ,sizeof(Theme));
int len;
file.read((char*)&len, sizeof(len));
player.resize(len);
file.read(&player[0], len);
file.close();
return true;
}
void saveHistory(const string& player, int score) {
ofstream file("history.txt", ios::app);
if (!file) return;
time_t now = time(0);
char* dt = ctime(&now);
if (dt) dt[strlen(dt) - 1] = '\0';
file << player << " " << WinOrLose << " | Score: " << score
<< " | " << dt
<< " | Details: simple Blocks Destroyed: " << simpleBlocksDestroyed
<< " hard Blocks Destroyed: " << hardBlocksDestroyed
<< " | Lives: " << heal << " |" << endl;
file.close();
}
int extractScore(const string& line) {
int pos = line.find("Score: ");
if (pos == -1) return -1;
pos += 7; // Length of "Score: "
int end = line.find(" |", pos);
if (end == -1) end = line.length();
int score = 0;
for (int i = pos; i < end && isdigit(line[i]); i++) {
score = score * 10 + (line[i] - '0');
}
return score;
}
void showHistory() {
system("cls");
ifstream file("history.txt");
gotoxy(55, 1);
cout << YELLOW << "===== GAME HISTORY =====\n\n" << RESET;
if (!file) {
gotoxy(5, 7);
cout << RED << "No history yet!" << RESET;
Sleep(2000);
return;
}
// Read all records
vector<string> history;
string line;
while (getline(file, line)) {
if (!line.empty()) history.push_back(line);
}
file.close();
if (history.empty()) {
gotoxy(5, 7);
cout << RED << "No data available." << RESET;
Sleep(2000);
return;
}
// Sort by score descending (using int indices)
sort(history.begin(), history.end(), [](const string& a, const string& b) {
return extractScore(a) > extractScore(b);
});
const int LINES_PER_PAGE = 6;
int currentPage = 0;
int totalRecords = history.size();
int totalPages = (totalRecords + LINES_PER_PAGE - 1) / LINES_PER_PAGE;
while (true) {
system("cls");
gotoxy(55, 1);
cout << YELLOW << "===== GAME HISTORY =====" << RESET << endl;
cout << "Page " << (currentPage + 1) << " of " << totalPages
<< " | Total Records: " << totalRecords << "\n\n";
int start = currentPage * LINES_PER_PAGE;
int end = start + LINES_PER_PAGE;
if (end > totalRecords) end = totalRecords;
for (int i = start; i < end; ++i) {
// Optional: Highlight top 3 scores
if (i == 0) cout << GOLD;
else if (i == 1) cout << GREEN;
else if (i == 2) cout << YELLOW;
cout << history[i] << RESET << endl;
}
cout << "\n[N]ext page | [P]revious page | [C]lear history | [E]xit: ";
char key = _getch();
key = tolower(key);
if (key == 'e') break;
else if (key == 'n' && currentPage < totalPages - 1) currentPage++;
else if (key == 'p' && currentPage > 0) currentPage--;
else if (key == 'c') {
cout << "\n" << RED << "Clear ALL history? (Y/N): " << RESET;
char confirm = _getch();
if (tolower(confirm) == 'y') {
ofstream clearFile("history.txt", ios::trunc);
clearFile.close();
cout << GREEN << "\nHistory cleared!" << RESET;
Sleep(1000);
return;
}
}
}
}
void clearSaveGame() {
ofstream file("save.dat", ios::trunc);
file.close();
}
void DrawHelpItems () {
cout << MAGENTA;
cout << MAGENTA << " ╔═══════════════════" << RESET << YELLOW << " 👾HELP👾" << RESET << MAGENTA << "═══════════════════╗\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " 🎮 " << YELLOW << "In-Game Controls" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "A" << RESET << " / " << BRIGHTBLACK << "D" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Move paddle left / right" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "SPACE" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Launch the ball" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "P" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Pause / Resume game" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "ENTER" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Decrease ball speed" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "ESC" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "save The Game" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "E " << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Exit Game" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " 🎮 " << YELLOW << "Menu Controls" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "▲ / ▼" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Navigate menu" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "ENTER" << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Select option" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "E " << RESET << " " << BRIGHTBLUE << "→" << RESET << " " << WHITE << "Back" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " ❤️ " << GREEN << "Lives: Start with 3 lives" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << BRIGHTBLACK << "Press E to Exit" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ║" << RESET << " " << MAGENTA << "║\n";
cout << MAGENTA << " ╚═══════════════════════════════════════════════╝\n";
cout << RESET;
}
void PlayBreakoutIntro() {
const char* logo[] = {
" \033[94m██████\033[35m╗\033[0m \033[94m██████\033[35m╗\033[0m \033[94m███████\033[35m╗\033[0m \033[94m█████\033[35m╗\033[0m \033[94m██\033[35m╗\033[0m \033[94m██\033[35m╗\033[0m \033[94m██████\033[35m╗\033[0m \033[94m██\033[35m╗\033[0m \033[94m██\033[35m╗\033[0m\033[94m████████\033[35m╗\033[0m",
" \033[94m██\033[35m╔══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m╔══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m╔════╝\033[0m\033[94m██\033[35m╔══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m╔╝\033[0m\033[94m██\033[35m╔═══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m\033[35m╚══\033[0m\033[94m██\033[35m╔══╝\033[0m",
" \033[94m██████\033[35m╔╝\033[0m\033[94m██████\033[35m╔╝\033[0m\033[94m█████\033[35m╗\033[0m \033[94m███████\033[35m║\033[0m\033[94m█████\033[35m╔╝\033[0m \033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m ",
" \033[94m██\033[35m╔══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m╔══\033[0m\033[94m██\033[35m╗\033[0m\033[94m██\033[35m╔══╝\033[0m \033[94m██\033[35m╔══\033[0m\033[94m██\033[35m║\033[0m\033[94m██\033[35m╔═\033[0m\033[94m██\033[35m╗\033[0m \033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m ",
" \033[94m██████\033[35m╔╝\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m\033[94m███████\033[35m╗\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m║\033[0m\033[94m██\033[35m║\033[0m \033[94m██\033[35m╗\033[0m\033[35m╚\033[0m\033[94m██████\033[35m╔╝\033[0m\033[35m╚\033[0m\033[94m██████\033[35m╔╝\033[0m \033[94m██\033[35m║\033[0m ",
LIGHTMAGENTA " ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ " RESET
};
system("cls");
for (int i = 0; i < 6; i++) {
cout << " " << LIGHTBLUE << logo[i] << RESET << endl;
Sleep(90);
}
gotoxy(39, 7);
cout << BRIGHTMAGENTA << " ♥ناریا بوخ یاه هچب حور هب میدقت "<< RESET;
Sleep(800);
}
void DrawSetting () {
system("cls");
string options[] = {
BRIGHTBLUE "Select One Theme" RESET,
"Skeleton",
"Cute",
"Galaxy",
"Random",
BRIGHTBLUE "Select a Level" RESET,
"Easy",
"Hard",
};
drawBoxAnimation(40, 8, 29, 12);
gotoxy(10, 9);
cout << BRIGHTBLUE << "│" << RESET << YELLOW <<" Press ENTER to Choose" << RESET;
gotoxy(10, 10);
cout << BRIGHTBLUE << "│" << RESET << YELLOW << " Press E to Exit" << RESET;
int selected;
if (difaultTheme)
selected = 1;
else
selected = Theme;
while (true) {
if (difaultTheme) {
gotoxy(54,10);
cout << CYAN "[ON]" RESET;
}
if (difaultlevel) {
gotoxy(44,15);
cout << CYAN "[ON]" RESET;
}
// redraw options
for (int i = 0; i < 8; i++) {
gotoxy(41, 9 + i);
cout << " "; // clear line
gotoxy(42, 9 + i);
if (i == selected && i != 0 && i != 5) {
cout << CurrentTheme.cursorColor << CurrentTheme.cursorChar << " " << options[i] << RESET;
} else {
cout << " " << options[i];
}
// Show [ON] only for valid selections
if (i >= 1 && i <= 4) {
if (!difaultTheme && Theme == i) {
cout << CYAN " [ON]" RESET;
}
} else if (i >= 6 && i <= 7) {
if (!difaultlevel && level == i) {
cout << CYAN " [ON]" RESET;
}
}
}
char key = _getch();
if (key == 72 && selected > 1) {
if (selected == 6) selected--;
selected--;
}
if (key == 80 && selected < 7) {
if (selected == 4) selected++;
selected++;
}
if (key == 13) { // ENTER
if (selected < 5){
difaultTheme = false;
if (Theme == selected) Theme = -1;
else Theme = selected;
}
else if (selected >5) {
difaultlevel = false;
if (level == selected) level = -1;
else level = selected;
}
}
if (key == 69 || key == 101){ // E
if (Theme == 4) Theme = rand() % 3 +1;
break;
}
}
}
void DeterminThemeAnfLevel() {
if (Theme == -1) Theme = 1; // Default (Skeleton)
if (level == -1) level = 6;
switch (Theme) {
case 1: // Skeleton
CurrentTheme.borderColor = WHITE;
CurrentTheme.paddleColor = BRIGHTRED;
CurrentTheme.simpleBlockColor = WHITE;
CurrentTheme.hardBlockColor = BRIGHTBLACK;
CurrentTheme.ballColor = WHITE;
CurrentTheme.cursorColor = BRIGHTRED;
CurrentTheme.paddleChar = "=";
CurrentTheme.blockChar = "█";
CurrentTheme.ballChar = "●";
CurrentTheme.cursorChar = "🕷️";
break;
case 2: // Cute
CurrentTheme.borderColor = PINK;
CurrentTheme.paddleColor = CYAN;
CurrentTheme.simpleBlockColor = MAGENTA;
CurrentTheme.hardBlockColor = ORANGE;
CurrentTheme.ballColor = WHITE;
CurrentTheme.cursorColor = CYAN;
CurrentTheme.paddleChar = "=";
CurrentTheme.blockChar = "█";
CurrentTheme.ballChar = "●";
CurrentTheme.cursorChar = "🍩";
break;
case 3: // Galaxy
CurrentTheme.borderColor = BRIGHTBLUE;
CurrentTheme.paddleColor = BOLDYELLOW;
CurrentTheme.simpleBlockColor = MAGENTA;
CurrentTheme.hardBlockColor = BRIGHTMAGENTA;
CurrentTheme.ballColor = WHITE;
CurrentTheme.cursorColor = PURPLE;
CurrentTheme.paddleChar = "═";
CurrentTheme.blockChar = "█";
CurrentTheme.ballChar = "●";
CurrentTheme.cursorChar = "🪐";
break;
}
}
void creatrandomItems() {
for (int i = 0; i < 12; i++) {
Items[i].x = rand() % 18 + 1;
Items[i].y = rand() % 6 + 1;
Items[i].power = rand() % 3;
Items[i].active = false;
}
}
void ActivateRandomItem(int bx, int by) {
if (rand() % 100 > 30) return;
for (int i = 0; i < 12; i++) {
if (!Items[i].active) {
Items[i].x = bx;
Items[i].y = by;
Items[i].power = rand() % 3;
Items[i].active = true;
break;
}
}
}
void DrawRandomItems() {
for (int i = 0; i < 12; i++) {
if (!Items[i].active) continue;
Items[i].y++;
int length = paddel.size / 2;
if (Items[i].y == paddel.y -1 &&
Items[i].x >= paddel.x - length &&
Items[i].x <= paddel.x + length)
{
ApplyPower(Items[i].power);
Items[i].active = false;
continue;
}
if (Items[i].y >= 19) {
Items[i].active = false;
continue;
}
gotoxy(Items[i].x + 4, Items[i].y + 1);
cout << CurrentTheme.cursorColor << "⬇" << RESET;
}
}
void ApplyPower(int power) {
switch (power) {
case 0: // Ball speed
powerOn = clock();
paddel.speed = 2;
break;
case 1: // Bigger Paddle
paddel.size += 2;
if (paddel.size > 5) paddel.size = 5;
powerOn = clock();
break;
case 2: // Extra Life
if (heal < 3) heal++;
break;
}
}
void RemovePower () {
if (clock() - powerOn > 5 * CLOCKS_PER_SEC) {
paddel.speed = 1;
paddel.size = 3;
}
}