463 lines
13 KiB
C++
463 lines
13 KiB
C++
#include <iostream>
|
|
#include <vector>
|
|
#include <cstdlib>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <ctime>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#ifdef _WIN32
|
|
#include <conio.h>
|
|
#else
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
// پیادهسازی توابع _kbhit و _getch برای سیستمهای غیر ویندوز
|
|
int _kbhit() {
|
|
struct termios oldt, newt;
|
|
int ch;
|
|
int oldf;
|
|
|
|
tcgetattr(STDIN_FILENO, &oldt);
|
|
newt = oldt;
|
|
newt.c_lflag &= ~(ICANON | ECHO);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
|
|
|
ch = getchar();
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
fcntl(STDIN_FILENO, F_SETFL, oldf);
|
|
|
|
if (ch != EOF) {
|
|
ungetc(ch, stdin);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _getch() {
|
|
struct termios oldt, newt;
|
|
int ch;
|
|
tcgetattr(STDIN_FILENO, &oldt);
|
|
newt = oldt;
|
|
newt.c_lflag &= ~(ICANON | ECHO);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
ch = getchar();
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
return ch;
|
|
}
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
// constant ثابت های بازی
|
|
const int BOARD_WIDTH = 40;
|
|
const int BOARD_HEIGHT = 20;
|
|
const int INITIAL_LIVES = 3;
|
|
const int BRICK_ROWS = 4;
|
|
const int BRICK_COLUMNS = 10;
|
|
|
|
// struct ساختار های بازی
|
|
struct Paddle {
|
|
int x;
|
|
int width;
|
|
};
|
|
|
|
struct Ball {
|
|
int x, y;
|
|
int vx, vy;
|
|
};
|
|
|
|
struct Brick {
|
|
int x, y;
|
|
bool active;
|
|
};
|
|
|
|
struct GameData {
|
|
// ذخیره تاریخچه بازی
|
|
string playerName;
|
|
int score;
|
|
int lives;
|
|
bool won;
|
|
string datetime;
|
|
};
|
|
|
|
struct GameState {
|
|
// وضعیت بازی در حال حاضر
|
|
Paddle paddle;
|
|
Ball ball;
|
|
vector<Brick> bricks;
|
|
int score;
|
|
int lives;
|
|
bool ballLaunched;
|
|
};
|
|
|
|
// پاک کردن صفحه بازی
|
|
void clearScreen() {
|
|
#ifdef _WIN32
|
|
system("cls");
|
|
#else
|
|
system("clear");
|
|
#endif
|
|
}
|
|
|
|
// ذخیره و خواندن تاریخچه game history
|
|
void saveMatch(const GameData& data) {
|
|
ofstream fout("game_history.txt", ios::app); //ios::app برای اینکه تمام تاریخچه بازیها ذخیره بشه
|
|
if (!fout) return;
|
|
|
|
fout << data.playerName << ","
|
|
<< data.score << ","
|
|
<< data.lives << ","
|
|
<< data.won << ","
|
|
<< data.datetime << "\n";
|
|
|
|
fout.close();
|
|
}
|
|
|
|
vector<GameData> getHistory() {
|
|
vector<GameData> history;
|
|
ifstream fin("game_history.txt");
|
|
if (!fin) return history;
|
|
|
|
string line;
|
|
while (getline(fin, line)) {
|
|
GameData gd;
|
|
|
|
size_t pos = 0;
|
|
vector<string> tokens;
|
|
string temp = line;
|
|
|
|
while ((pos = temp.find(',')) != string::npos) {
|
|
tokens.push_back(temp.substr(0, pos));
|
|
temp.erase(0, pos + 1);
|
|
}
|
|
tokens.push_back(temp);
|
|
|
|
if (tokens.size() == 5) {
|
|
gd.playerName = tokens[0];
|
|
gd.score = atoi(tokens[1].c_str());
|
|
gd.lives = atoi(tokens[2].c_str());
|
|
gd.won = (tokens[3] == "1");
|
|
gd.datetime = tokens[4];
|
|
history.push_back(gd);
|
|
}
|
|
}
|
|
|
|
fin.close();
|
|
return history;
|
|
}
|
|
// رسم صفحه ی بازی rendering
|
|
void printBoard(const GameState& game) {
|
|
clearScreen();
|
|
|
|
// سقف
|
|
for (int i = 0; i < BOARD_WIDTH + 2; ++i) cout << '#';
|
|
cout << '\n';
|
|
|
|
// درون صفحه
|
|
for (int y = 0; y < BOARD_HEIGHT; ++y) {
|
|
cout << '#';
|
|
for (int x = 0; x < BOARD_WIDTH; ++x) {
|
|
bool printed = false;
|
|
|
|
// توپ
|
|
if (x == game.ball.x && y == game.ball.y) {
|
|
cout << 'O';
|
|
printed = true;
|
|
}
|
|
// راکت
|
|
else if (y == BOARD_HEIGHT - 1 &&
|
|
x >= game.paddle.x &&
|
|
x < game.paddle.x + game.paddle.width) {
|
|
cout << '=';
|
|
printed = true;
|
|
}
|
|
// آجر ها
|
|
else {
|
|
for (size_t i = 0; i < game.bricks.size(); ++i) {
|
|
if (game.bricks[i].active &&
|
|
game.bricks[i].x == x &&
|
|
game.bricks[i].y == y) {
|
|
cout << '*';
|
|
printed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!printed) cout << ' ';
|
|
}
|
|
cout << "#\n";
|
|
}
|
|
|
|
// کف
|
|
for (int i = 0; i < BOARD_WIDTH + 2; ++i) cout << '#';
|
|
cout << '\n';
|
|
|
|
// HUD نمایش اطلاعات بازی
|
|
int bricksLeft = 0;
|
|
for (size_t i = 0; i < game.bricks.size(); ++i)
|
|
if (game.bricks[i].active) bricksLeft++;
|
|
|
|
int bricksDestroyed = game.score;
|
|
|
|
cout << "Lives: " << game.lives
|
|
<< " | Score: " << game.score
|
|
<< " | Bricks Left: " << bricksLeft
|
|
<< " | Bricks Destroyed: " << bricksDestroyed << "\n";
|
|
}
|
|
// گرفتن ورودی از کاربر برای حرکت و کنترل
|
|
void checkInput(GameState& game) {
|
|
if (_kbhit()) {
|
|
char key = (char)_getch();
|
|
|
|
// A و D کلید برای حرکت
|
|
if ((key == 'a' || key == 'A') && game.paddle.x > 0) game.paddle.x--;
|
|
else if ((key == 'd' || key == 'D') && game.paddle.x + game.paddle.width < BOARD_WIDTH) game.paddle.x++;
|
|
|
|
// متوقف شدن
|
|
else if (key == 'p' || key == 'P') {
|
|
cout << "\nGame Paused. Press C to continue or Q to quit.\n";
|
|
while (true) {
|
|
char c = (char)_getch();
|
|
if (c == 'c' || c == 'C') break;
|
|
if (c == 'q' || c == 'Q') { game.lives = 0; break; }
|
|
}
|
|
}
|
|
|
|
// پرتاب توپ با زدن کلید space
|
|
else if (!game.ballLaunched && key == ' ') {
|
|
game.ballLaunched = true;
|
|
}
|
|
|
|
// خروج
|
|
else if (key == 'q' || key == 'Q') {
|
|
game.lives = 0;
|
|
}
|
|
}
|
|
}
|
|
// آپدیت موقعیت و برخورد با دیواره و کم کردن جان بازیکن
|
|
void moveTheBall(GameState& game) {
|
|
if (!game.ballLaunched) return;
|
|
|
|
game.ball.x += game.ball.vx;
|
|
game.ball.y += game.ball.vy;
|
|
|
|
// برخورد با چپ و راست
|
|
if (game.ball.x <= 0 || game.ball.x >= BOARD_WIDTH - 1) game.ball.vx *= -1;
|
|
|
|
// برخورد با سقف
|
|
if (game.ball.y <= 0) game.ball.vy *= -1;
|
|
|
|
// رد شدن و کم شدن جان و ریست جای توپ
|
|
if (game.ball.y >= BOARD_HEIGHT) {
|
|
game.lives--;
|
|
game.ballLaunched = false;
|
|
|
|
game.ball.x = game.paddle.x + game.paddle.width / 2;
|
|
game.ball.y = BOARD_HEIGHT - 2;
|
|
}
|
|
}
|
|
// برخورد توپ با راکت با زاویه بر اساس محل برخورد
|
|
void checkPaddleHit(GameState& game) {
|
|
if (game.ball.y == BOARD_HEIGHT - 2 &&
|
|
game.ball.x >= game.paddle.x &&
|
|
game.ball.x < game.paddle.x + game.paddle.width) {
|
|
|
|
// برگشت عمودی
|
|
game.ball.vy = -game.ball.vy;
|
|
|
|
// براساس این که کجا برخورد کرده vx تغییر میکند
|
|
double rel = (game.ball.x - (game.paddle.x + game.paddle.width / 2.0))
|
|
/ (game.paddle.width / 2.0);
|
|
|
|
game.ball.vx = (int)(rel * 2.0);
|
|
if (game.ball.vx == 0) game.ball.vx = (rel < 0 ? -1 : 1);
|
|
}
|
|
}
|
|
// برخورد با آجر و حذف آجر و امتیاز گرفتن و برگشت عمودی
|
|
void checkBrickHit(GameState& game) {
|
|
for (size_t i = 0; i < game.bricks.size(); ++i) {
|
|
Brick &brick = game.bricks[i];
|
|
|
|
if (brick.active &&
|
|
game.ball.x == brick.x &&
|
|
game.ball.y == brick.y) {
|
|
|
|
brick.active = false;
|
|
game.score++;
|
|
game.ball.vy = -game.ball.vy;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// مقادیر اولیه بازی
|
|
GameState initializeGame() {
|
|
GameState game;
|
|
game.lives = INITIAL_LIVES;
|
|
game.score = 0;
|
|
game.ballLaunched = false;
|
|
|
|
// راکت پایین وسط
|
|
game.paddle.x = BOARD_WIDTH / 2 - 3;
|
|
game.paddle.width = 6;
|
|
|
|
// توپ بالای راکت
|
|
game.ball.x = game.paddle.x + 3;
|
|
game.ball.y = BOARD_HEIGHT - 2;
|
|
// حرکت اولیه توپ
|
|
game.ball.vx = 1;
|
|
game.ball.vy = -1;
|
|
|
|
// ساختن آجر ها
|
|
game.bricks.clear();
|
|
for (int row = 0; row < BRICK_ROWS; ++row) {
|
|
for (int col = 0; col < BRICK_COLUMNS; ++col) {
|
|
Brick brick;
|
|
brick.x = col * (BOARD_WIDTH / BRICK_COLUMNS);
|
|
brick.y = row + 2;
|
|
brick.active = true;
|
|
game.bricks.push_back(brick);
|
|
}
|
|
}
|
|
|
|
return game;
|
|
}
|
|
// حلقه بازی
|
|
void gameLoop(GameState& game, const string& playerName) {
|
|
const int FPS = 30;
|
|
chrono::milliseconds frameTime(1000 / FPS);
|
|
|
|
while (game.lives > 0) {
|
|
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
|
|
|
|
checkInput(game);
|
|
moveTheBall(game);
|
|
checkPaddleHit(game);
|
|
checkBrickHit(game);
|
|
printBoard(game);
|
|
|
|
// هیچ آجر فعالی برای بردن نمانده
|
|
bool anyBricks = false;
|
|
for (size_t i = 0; i < game.bricks.size(); ++i) {
|
|
if (game.bricks[i].active) { anyBricks = true; break; }
|
|
}
|
|
if (!anyBricks) break;
|
|
|
|
chrono::high_resolution_clock::time_point end = chrono::high_resolution_clock::now();
|
|
chrono::duration<double, milli> elapsed = end - start;
|
|
if (elapsed < frameTime) this_thread::sleep_for(frameTime - elapsed);
|
|
}
|
|
|
|
// پایان و ذخیره بازی
|
|
clearScreen();
|
|
|
|
GameData gd;
|
|
gd.playerName = playerName;
|
|
gd.score = game.score;
|
|
gd.lives = game.lives;
|
|
gd.won = (game.lives > 0);
|
|
|
|
time_t now = time(0);
|
|
gd.datetime = ctime(&now);
|
|
|
|
saveMatch(gd);
|
|
|
|
if (game.lives <= 0) cout << "Game Over!\n";
|
|
else cout << "Victory! All bricks destroyed!\n";
|
|
cout << "Final Score: " << game.score << "\n";
|
|
|
|
cout << "\nPress Enter to return to menu...";
|
|
cin.ignore(numeric_limits<streamsize>::max(), '\n');
|
|
cin.get();
|
|
}
|
|
// help توضیحات
|
|
void showHelp() {
|
|
clearScreen();
|
|
cout << "=== HELP ===\n";
|
|
cout << "A / Left : Move paddle left\n";
|
|
cout << "D / Right : Move paddle right\n";
|
|
cout << "Space : Launch ball\n";
|
|
cout << "P : Pause game\n";
|
|
cout << "Q : Quit game\n";
|
|
|
|
cout << "\nPress Enter to return to menu...";
|
|
cin.ignore(numeric_limits<streamsize>::max(), '\n');
|
|
cin.get();
|
|
}
|
|
// history تاریخچه
|
|
bool higherScore(const GameData& a, const GameData& b) {
|
|
return a.score > b.score;
|
|
}
|
|
|
|
void showGameHistory() {
|
|
clearScreen();
|
|
vector<GameData> history = getHistory();
|
|
sort(history.begin(), history.end(), higherScore);
|
|
|
|
cout << "=== GAME HISTORY ===\n";
|
|
for (size_t i = 0; i < history.size(); ++i) {
|
|
cout << history[i].playerName
|
|
<< " | Score: " << history[i].score
|
|
<< " | Lives: " << history[i].lives
|
|
<< " | " << (history[i].won ? "Won" : "Lost")
|
|
<< " | " << history[i].datetime
|
|
<< '\n';
|
|
}
|
|
|
|
cout << "\nPress Enter to return to menu...";
|
|
cin.ignore(numeric_limits<streamsize>::max(), '\n');
|
|
cin.get();
|
|
}
|
|
// منو بازی
|
|
void showMainMenu() {
|
|
while (true) {
|
|
clearScreen();
|
|
cout << "=== BREAKOUT GAME ===\n";
|
|
cout << "1. New Game\n";
|
|
cout << "2. Help\n";
|
|
cout << "3. Game History\n";
|
|
cout << "4. Exit\n";
|
|
cout << "Select an option: ";
|
|
|
|
char choice = (char)_getch();
|
|
|
|
if (choice == '1') {
|
|
clearScreen();
|
|
cout << "Enter Player Name: ";
|
|
string name;
|
|
cin >> name;
|
|
|
|
GameState game = initializeGame();
|
|
gameLoop(game, name);
|
|
}
|
|
else if (choice == '2') {
|
|
showHelp();
|
|
}
|
|
else if (choice == '3') {
|
|
showGameHistory();
|
|
}
|
|
else if (choice == '4') {
|
|
break;
|
|
}
|
|
else {
|
|
// کاربر ورودی نامعتبر زد این پیام رو بده
|
|
cout << "\nInvalid option! Press any key...";
|
|
(void)_getch();
|
|
}
|
|
}
|
|
}
|
|
int main() {
|
|
showMainMenu();
|
|
return 0;
|
|
}
|